server.go 5.84 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"
11
	"github.com/privacybydesign/irmago"
Sietse Ringers's avatar
Sietse Ringers committed
12
13
	"github.com/privacybydesign/irmago/server"
	"github.com/privacybydesign/irmago/server/irmarequestor"
14
15
)

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

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

28
	// Start server
29
	s = &http.Server{Addr: fmt.Sprintf(":%d", config.Port), Handler: handler}
30
31
32
33
34
35
36
37
38
39
40
41
	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
42
43
// Handler returns a http.Handler that handles all IRMA requestor messages
// and IRMA client messages.
44
45
46
47
48
49
func Handler(config *Configuration) (http.Handler, error) {
	conf = config
	if err := irmarequestor.Initialize(conf.Configuration); err != nil {
		return nil, err
	}
	if err := conf.initialize(); err != nil {
50
51
52
		return nil, err
	}

53
	router := chi.NewRouter()
54

55
56
	// Mount server for irmaclient
	router.Mount("/irma/", irmarequestor.HttpHandlerFunc("/irma/"))
57

58
59
60
61
	// Server routes
	router.Post("/create", handleCreate)
	router.Get("/status/{token}", handleStatus)
	router.Get("/result/{token}", handleResult)
Sietse Ringers's avatar
Sietse Ringers committed
62
63

	// Routes for getting signed JWTs containing the session result. Only work if configuration has a private key
64
	router.Get("/result-jwt/{token}", handleJwtResult)
Sietse Ringers's avatar
Sietse Ringers committed
65
	router.Get("/getproof/{token}", handleJwtProofs) // irma_api_server-compatible JWT
66

67
	return router, nil
68
69
}

70
71
72
func handleCreate(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
73
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
74
75
		return
	}
76

Sietse Ringers's avatar
Sietse Ringers committed
77
78
79
	// 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.
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
	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
97
		server.WriteError(w, server.ErrorInvalidRequest, "Request could not be authorized")
98
99
100
		return
	}

101
102
103
104
105
106
107
108
109
	// 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
110
	disjunctions := request.ToDisclose()
111
112
113
114
115
116
117
118
119
	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!
120
121
	qr, _, err := irmarequestor.StartSession(request, nil)
	if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
122
		server.WriteError(w, server.ErrorInvalidRequest, err.Error())
123
124
125
		return
	}

Sietse Ringers's avatar
Sietse Ringers committed
126
	server.WriteJson(w, qr)
127
128
129
130
131
}

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
132
		server.WriteError(w, server.ErrorSessionUnknown, "")
133
134
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
135
	server.WriteJson(w, res.Status)
136
137
138
139
140
}

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
141
		server.WriteError(w, server.ErrorSessionUnknown, "")
142
143
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
144
	server.WriteJson(w, res)
145
146
}

147
148
149
150
func handleJwtResult(w http.ResponseWriter, r *http.Request) {
	if conf.privateKey == nil {
		server.WriteError(w, server.ErrorUnknown, "JWT signing not supported")
		return
151
	}
152
153
154
155
156

	res := irmarequestor.GetSessionResult(chi.URLParam(r, "token"))
	if res == nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
		return
157
	}
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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

	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)
	resultJwt, err := token.SignedString(conf.privateKey)
	if err != nil {
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
	}
	server.WriteString(w, resultJwt)
}

func handleJwtProofs(w http.ResponseWriter, r *http.Request) {
	if conf.privateKey == nil {
		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)
	resultJwt, err := token.SignedString(conf.privateKey)
	if err != nil {
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
227
	}
228
	server.WriteString(w, resultJwt)
229
}