server.go 6.1 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/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/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
47
48
49
50
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 {
51
52
53
		return nil, err
	}

54
	router := chi.NewRouter()
55

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

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

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

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

73
	return router, nil
74
75
}

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

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

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

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

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

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
147
		server.WriteError(w, server.ErrorSessionUnknown, "")
148
149
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
150
	server.WriteJson(w, res)
151
152
}

153
func handleJwtResult(w http.ResponseWriter, r *http.Request) {
154
	if conf.jwtPrivateKey == nil {
155
156
		server.WriteError(w, server.ErrorUnknown, "JWT signing not supported")
		return
157
	}
158
159
160
161
162

	res := irmarequestor.GetSessionResult(chi.URLParam(r, "token"))
	if res == nil {
		server.WriteError(w, server.ErrorSessionUnknown, "")
		return
163
	}
164
165
166
167
168
169
170
171
172
173
174
175
176

	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)
177
	resultJwt, err := token.SignedString(conf.jwtPrivateKey)
178
179
180
181
182
183
184
185
	if err != nil {
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
	}
	server.WriteString(w, resultJwt)
}

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