server.go 6.38 KB
Newer Older
Sietse Ringers's avatar
Sietse Ringers committed
1
package irmaserver
2
3
4
5
6

import (
	"fmt"
	"io/ioutil"
	"net/http"
7
	"time"
8

9
	"github.com/dgrijalva/jwt-go"
10
	"github.com/go-chi/chi"
Sietse Ringers's avatar
Sietse Ringers committed
11
	"github.com/go-chi/cors"
12
	"github.com/privacybydesign/irmago"
Sietse Ringers's avatar
Sietse Ringers committed
13
14
	"github.com/privacybydesign/irmago/server"
	"github.com/privacybydesign/irmago/server/irmarequestor"
15
16
)

17
18
19
20
var (
	s    *http.Server
	conf *Configuration
)
21

22
// Start the server. If successful then it will not return until Stop() is called.
23
24
func Start(config *Configuration) error {
	handler, err := Handler(config)
25
	if err != nil {
26
27
28
		return err
	}

29
	// Start server
30
	s = &http.Server{Addr: fmt.Sprintf(":%d", config.Port), Handler: handler}
31
32
33
34
35
36
37
38
39
40
41
42
	err = s.ListenAndServe()
	if err == http.ErrServerClosed {
		return nil // Server was closed normally
	}

	return err
}

func Stop() {
	s.Close()
}

Sietse Ringers's avatar
Sietse Ringers committed
43
44
// Handler returns a http.Handler that handles all IRMA requestor messages
// and IRMA client messages.
45
46
func Handler(config *Configuration) (http.Handler, error) {
	conf = config
47
	if err := conf.initialize(); err != nil {
48
49
		return nil, err
	}
50
	if err := irmarequestor.Initialize(conf.Configuration); err != nil {
51
52
53
		return nil, err
	}

54
	router := chi.NewRouter()
55

Sietse Ringers's avatar
Sietse Ringers committed
56
57
58
	router.Use(cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
Sietse Ringers's avatar
Sietse Ringers committed
59
		AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete},
Sietse Ringers's avatar
Sietse Ringers committed
60
61
	}).Handler)

62
63
	// Mount server for irmaclient
	router.Mount("/irma/", irmarequestor.HttpHandlerFunc("/irma/"))
64

65
	// Server routes
66
	router.Post("/session", handleCreate)
67
	router.Delete("/session/{token}", handleDelete)
68
69
	router.Get("/session/{token}/status", handleStatus)
	router.Get("/session/{token}/result", handleResult)
Sietse Ringers's avatar
Sietse Ringers committed
70
71

	// Routes for getting signed JWTs containing the session result. Only work if configuration has a private key
72
73
	router.Get("/session/{token}/result-jwt", handleJwtResult)
	router.Get("/session/{token}/getproof", handleJwtProofs) // irma_api_server-compatible JWT
74

75
	return router, nil
76
77
}

78
79
80
func handleCreate(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
81
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
82
83
		return
	}
84

Sietse Ringers's avatar
Sietse Ringers committed
85
86
87
	// Authenticate request: check if the requestor is known and allowed to submit requests.
	// We do this by feeding the HTTP POST details to all known authenticators, and see if
	// one of them is applicable and able to authenticate the request.
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
	var (
		request   irma.SessionRequest
		requestor string
		rerr      *irma.RemoteError
		applies   bool
	)
	for _, authenticator := range authenticators {
		applies, request, requestor, rerr = authenticator.Authenticate(r.Header, body)
		if applies || rerr != nil {
			break
		}
	}
	if rerr != nil {
		server.WriteResponse(w, nil, rerr)
		return
	}
	if !applies {
Sietse Ringers's avatar
Sietse Ringers committed
105
		server.WriteError(w, server.ErrorInvalidRequest, "Request could not be authorized")
106
107
108
		return
	}

109
110
111
112
113
114
115
116
117
	// Authorize request: check if the requestor is allowed to verify or issue
	// the requested attributes or credentials
	if request.Action() == irma.ActionIssuing {
		allowed, reason := conf.CanIssue(requestor, request.(*irma.IssuanceRequest).Credentials)
		if !allowed {
			server.WriteError(w, server.ErrorUnauthorized, reason)
			return
		}
	}
Sietse Ringers's avatar
Sietse Ringers committed
118
	disjunctions := request.ToDisclose()
119
120
121
122
123
124
125
126
127
	if len(disjunctions) > 0 {
		allowed, reason := conf.CanVerifyOrSign(requestor, request.Action(), disjunctions)
		if !allowed {
			server.WriteError(w, server.ErrorUnauthorized, reason)
			return
		}
	}

	// Everything is authenticated and parsed, we're good to go!
128
129
	qr, _, err := irmarequestor.StartSession(request, nil)
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
130
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
131
132
133
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
134
	server.WriteJson(w, qr)
135
136
137
138
139
}

func handleStatus(w http.ResponseWriter, r *http.Request) {
	res := irmarequestor.GetSessionResult(chi.URLParam(r, "token"))
	if res == nil {
Sietse Ringers's avatar
Sietse Ringers committed
140
		server.WriteError(w, server.ErrorSessionUnknown, "")
141
142
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
143
	server.WriteJson(w, res.Status)
144
145
}

146
147
148
149
150
151
152
func handleDelete(w http.ResponseWriter, r *http.Request) {
	err := irmarequestor.CancelSession(chi.URLParam(r, "token"))
	if err != nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
	}
}

153
154
155
func handleResult(w http.ResponseWriter, r *http.Request) {
	res := irmarequestor.GetSessionResult(chi.URLParam(r, "token"))
	if res == nil {
Sietse Ringers's avatar
Sietse Ringers committed
156
		server.WriteError(w, server.ErrorSessionUnknown, "")
157
158
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
159
	server.WriteJson(w, res)
160
161
}

162
func handleJwtResult(w http.ResponseWriter, r *http.Request) {
163
	if conf.jwtPrivateKey == nil {
164
165
		server.WriteError(w, server.ErrorUnknown, "JWT signing not supported")
		return
166
	}
167
168
169
170
171

	res := irmarequestor.GetSessionResult(chi.URLParam(r, "token"))
	if res == nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
		return
172
	}
173
174
175
176
177
178
179
180
181
182
183
184
185

	claims := struct {
		jwt.StandardClaims
		*server.SessionResult
	}{
		SessionResult: res,
	}
	claims.Issuer = conf.JwtIssuer
	claims.IssuedAt = time.Now().Unix()
	claims.Subject = string(res.Type) + "_result"

	// Sign the jwt and return it
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
186
	resultJwt, err := token.SignedString(conf.jwtPrivateKey)
187
188
189
190
191
192
193
194
	if err != nil {
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
	}
	server.WriteString(w, resultJwt)
}

func handleJwtProofs(w http.ResponseWriter, r *http.Request) {
195
	if conf.jwtPrivateKey == nil {
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
		server.WriteError(w, server.ErrorUnknown, "JWT signing not supported")
		return
	}

	res := irmarequestor.GetSessionResult(chi.URLParam(r, "token"))
	if res == nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
		return
	}

	claims := jwt.MapClaims{}

	// Fill standard claims
	switch res.Type {
	case irma.ActionDisclosing:
		claims["subject"] = "verification_result"
	case irma.ActionSigning:
		claims["subject"] = "abs_result"
	default:
		if res == nil {
			server.WriteError(w, server.ErrorInvalidRequest, "")
			return
		}
	}
	claims["iat"] = time.Now().Unix()
	if conf.JwtIssuer != "" {
		claims["iss"] = conf.JwtIssuer
	}
	claims["status"] = res.Status

	// Disclosed credentials and possibly signature
	m := make(map[irma.AttributeTypeIdentifier]string, len(res.Disclosed))
	for _, attr := range res.Disclosed {
		m[attr.Identifier] = attr.Value[""]
	}
	claims["attributes"] = m
	if res.Signature != nil {
		claims["signature"] = res.Signature
	}

	// Sign the jwt and return it
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
238
	resultJwt, err := token.SignedString(conf.jwtPrivateKey)
239
240
241
	if err != nil {
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
242
	}
243
	server.WriteString(w, resultJwt)
244
}