handle.go 21.6 KB
Newer Older
1
package irmaserver
2
3

import (
4
5
	"context"
	"encoding/json"
6
	"fmt"
7
8
9
	"io/ioutil"
	"net/http"
	"strconv"
10
11
	"time"

12
	"github.com/privacybydesign/gabi"
13
	"github.com/privacybydesign/gabi/signed"
14
	irma "github.com/privacybydesign/irmago"
15
	"github.com/privacybydesign/irmago/internal/common"
Sietse Ringers's avatar
Sietse Ringers committed
16
	"github.com/privacybydesign/irmago/server"
17
18
19

	"github.com/go-chi/chi"
	"github.com/go-errors/errors"
20
	"github.com/sirupsen/logrus"
21
22
)

23
// This file contains the handler functions for the protocol messages.
Sietse Ringers's avatar
Sietse Ringers committed
24
25
26
// Maintaining the session state is done here, as well as checking whether the session is in the
// appropriate status before handling the request.

27
func (session *session) handleDelete() {
28
	if session.status.Finished() {
29
		return
30
	}
31
	session.markAlive()
Sietse Ringers's avatar
Sietse Ringers committed
32

33
	session.result = &server.SessionResult{Token: session.requestorToken, Status: irma.ServerStatusCancelled, Type: session.action}
34
	session.setStatus(irma.ServerStatusCancelled)
35
36
}

Ivar Derksen's avatar
Ivar Derksen committed
37
func (session *session) handleGetClientRequest(min, max *irma.ProtocolVersion, clientAuth irma.ClientAuthorization) (
38
	interface{}, *irma.RemoteError) {
39

40
	if session.status != irma.ServerStatusInitialized {
Sietse Ringers's avatar
Sietse Ringers committed
41
		return nil, server.RemoteError(server.ErrorUnexpectedRequest, "Session already started")
42
43
	}

44
	session.markAlive()
45
	logger := session.conf.Logger.WithFields(logrus.Fields{"session": session.requestorToken})
46

47
48
	var err error
	if session.version, err = session.chooseProtocolVersion(min, max); err != nil {
49
		return nil, session.fail(server.ErrorProtocolVersion, "")
50
51
	}

52
	// Protocol versions below 2.8 don't include an authorization header. Therefore skip the authorization
53
	// header presence check if a lower version is used.
54
	if clientAuth == "" && session.version.Above(2, 7) {
55
		return nil, session.fail(server.ErrorIrmaUnauthorized, "No authorization header provided")
56
57
	}
	session.clientAuth = clientAuth
58

59
	// we include the latest revocation updates for the client here, as opposed to when the session
60
	// was started, so that the client always gets the very latest revocation records
61
	if err = session.conf.IrmaConfiguration.Revocation.SetRevocationUpdates(session.request.Base()); err != nil {
62
		return nil, session.fail(server.ErrorRevocation, err.Error())
63
64
	}

65
66
67
68
69
70
71
72
73
	// Handle legacy clients that do not support condiscon, by attempting to convert the condiscon
	// session request to the legacy session request format
	legacy, legacyErr := session.request.Legacy()
	session.legacyCompatible = legacyErr == nil
	if legacyErr != nil {
		logger.Info("Using condiscon: backwards compatibility with legacy IRMA apps is disabled")
	}

	logger.WithFields(logrus.Fields{"version": session.version.String()}).Debugf("Protocol version negotiated")
74
	session.request.Base().ProtocolVersion = session.version
75

76
	if session.options.PairingMethod != irma.PairingMethodNone && session.version.Above(2, 7) {
77
		session.setStatus(irma.ServerStatusPairing)
78
	} else {
79
		session.setStatus(irma.ServerStatusConnected)
80
	}
81
82
83
84
85
86

	if session.version.Below(2, 5) {
		logger.Info("Returning legacy session format")
		legacy.Base().ProtocolVersion = session.version
		return legacy, nil
	}
87

88
	if session.version.Below(2, 8) {
89
		// These versions do not support the ClientSessionRequest format, so send the SessionRequest.
90
91
92
93
		request, err := session.getRequest()
		if err != nil {
			return nil, session.fail(server.ErrorRevocation, err.Error())
		}
94
		return request, nil
95
	}
96
	info, err := session.getClientRequest()
97
	if err != nil {
98
		return nil, session.fail(server.ErrorRevocation, err.Error())
99
	}
100
	return info, nil
101
102
}

103
func (session *session) handleGetStatus() (irma.ServerStatus, *irma.RemoteError) {
104
105
106
	return session.status, nil
}

107
func (session *session) handlePostSignature(signature *irma.SignedMessage) (*irma.ServerSessionResponse, *irma.RemoteError) {
108
	session.markAlive()
109

110
111
	var err error
	var rerr *irma.RemoteError
112
	session.result.Signature = signature
113
114
115
116
117
118

	// In case of chained sessions, we also expect attributes from previous sessions to be disclosed again.
	request := session.request.(*irma.SignatureRequest)
	request.Disclose = append(request.Disclose, session.implicitDisclosure...)

	session.result.Disclosed, session.result.ProofStatus, err = signature.Verify(session.conf.IrmaConfiguration, request)
119
120
121
122
	if err != nil && err == irma.ErrMissingPublicKey {
		rerr = session.fail(server.ErrorUnknownPublicKey, err.Error())
	} else if err != nil {
		rerr = session.fail(server.ErrorUnknown, err.Error())
123
	}
124

125
126
127
128
129
	return &irma.ServerSessionResponse{
		SessionType:     irma.ActionSigning,
		ProtocolVersion: session.version,
		ProofStatus:     session.result.ProofStatus,
	}, rerr
130
}
131

132
func (session *session) handlePostDisclosure(disclosure *irma.Disclosure) (*irma.ServerSessionResponse, *irma.RemoteError) {
133
	session.markAlive()
134

135
136
	var err error
	var rerr *irma.RemoteError
137
138
139
140
141
142

	// In case of chained sessions, we also expect attributes from previous sessions to be disclosed again.
	request := session.request.(*irma.DisclosureRequest)
	request.Disclose = append(request.Disclose, session.implicitDisclosure...)

	session.result.Disclosed, session.result.ProofStatus, err = disclosure.Verify(session.conf.IrmaConfiguration, request)
143
144
145
146
	if err != nil && err == irma.ErrMissingPublicKey {
		rerr = session.fail(server.ErrorUnknownPublicKey, err.Error())
	} else if err != nil {
		rerr = session.fail(server.ErrorUnknown, err.Error())
147
	}
148
149
150
151
152
153

	return &irma.ServerSessionResponse{
		SessionType:     irma.ActionDisclosing,
		ProtocolVersion: session.version,
		ProofStatus:     session.result.ProofStatus,
	}, rerr
154
155
}

156
func (session *session) handlePostCommitments(commitments *irma.IssueCommitmentMessage) (*irma.ServerSessionResponse, *irma.RemoteError) {
Sietse Ringers's avatar
Sietse Ringers committed
157
158
	session.markAlive()
	request := session.request.(*irma.IssuanceRequest)
159

160
161
162
	discloseCount := len(commitments.Proofs) - len(request.Credentials)
	if discloseCount < 0 {
		return nil, session.fail(server.ErrorMalformedInput, "Received insufficient proofs")
Sietse Ringers's avatar
Sietse Ringers committed
163
	}
164

Sietse Ringers's avatar
Sietse Ringers committed
165
166
	// Compute list of public keys against which to verify the received proofs
	disclosureproofs := irma.ProofList(commitments.Proofs[:discloseCount])
167
	pubkeys, err := disclosureproofs.ExtractPublicKeys(session.conf.IrmaConfiguration)
Sietse Ringers's avatar
Sietse Ringers committed
168
	if err != nil {
169
		return nil, session.fail(server.ErrorMalformedInput, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
170
171
172
	}
	for _, cred := range request.Credentials {
		iss := cred.CredentialTypeID.IssuerIdentifier()
173
		pubkey, _ := session.conf.IrmaConfiguration.PublicKey(iss, cred.KeyCounter) // No error, already checked earlier
Sietse Ringers's avatar
Sietse Ringers committed
174
175
		pubkeys = append(pubkeys, pubkey)
	}
176

Sietse Ringers's avatar
Sietse Ringers committed
177
178
179
180
	// Verify and merge keyshare server proofs, if any
	for i, proof := range commitments.Proofs {
		pubkey := pubkeys[i]
		schemeid := irma.NewIssuerIdentifier(pubkey.Issuer).SchemeManagerIdentifier()
181
		if session.conf.IrmaConfiguration.SchemeManagers[schemeid].Distributed() {
Sietse Ringers's avatar
Sietse Ringers committed
182
183
			proofP, err := session.getProofP(commitments, schemeid)
			if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
184
				return nil, session.fail(server.ErrorKeyshareProofMissing, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
185
186
187
			}
			proof.MergeProofP(proofP, pubkey)
		}
188
189
	}

Sietse Ringers's avatar
Sietse Ringers committed
190
	// Verify all proofs and check disclosed attributes, if any, against request
191
	now := time.Now()
192
	request.Disclose = append(request.Disclose, session.implicitDisclosure...)
193
194
195
	session.result.Disclosed, session.result.ProofStatus, err = commitments.Disclosure().VerifyAgainstRequest(
		session.conf.IrmaConfiguration, request, request.GetContext(), request.GetNonce(nil), pubkeys, &now, false,
	)
196
	if err != nil {
197
		if err == irma.ErrMissingPublicKey {
Sietse Ringers's avatar
Sietse Ringers committed
198
			return nil, session.fail(server.ErrorUnknownPublicKey, "")
199
		} else {
Sietse Ringers's avatar
Sietse Ringers committed
200
			return nil, session.fail(server.ErrorUnknown, "")
201
202
203
		}
	}
	if session.result.ProofStatus == irma.ProofStatusExpired {
Sietse Ringers's avatar
Sietse Ringers committed
204
		return nil, session.fail(server.ErrorAttributesExpired, "")
205
	}
Sietse Ringers's avatar
Sietse Ringers committed
206
	if session.result.ProofStatus != irma.ProofStatusValid {
Sietse Ringers's avatar
Sietse Ringers committed
207
		return nil, session.fail(server.ErrorInvalidProofs, "")
208
	}
Sietse Ringers's avatar
Sietse Ringers committed
209
210
211
212
213

	// Compute CL signatures
	var sigs []*gabi.IssueSignatureMessage
	for i, cred := range request.Credentials {
		id := cred.CredentialTypeID.IssuerIdentifier()
214
		pk, _ := session.conf.IrmaConfiguration.PublicKey(id, cred.KeyCounter)
215
		sk, _ := session.conf.IrmaConfiguration.PrivateKeys.Latest(id)
216
		issuer := gabi.NewIssuer(sk, pk, one)
217
218
219
220
		proof, ok := commitments.Proofs[i+discloseCount].(*gabi.ProofU)
		if !ok {
			return nil, session.fail(server.ErrorMalformedInput, "Received invalid issuance commitment")
		}
221
		attrs, witness, err := session.computeAttributes(sk, cred)
Sietse Ringers's avatar
Sietse Ringers committed
222
		if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
223
			return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
224
		}
225
		rb := session.conf.IrmaConfiguration.CredentialTypes[cred.CredentialTypeID].RandomBlindAttributeIndices()
226
		sig, err := issuer.IssueSignature(proof.U, attrs, witness, commitments.Nonce2, rb)
Sietse Ringers's avatar
Sietse Ringers committed
227
		if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
228
			return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
229
230
		}
		sigs = append(sigs, sig)
231
	}
Sietse Ringers's avatar
Sietse Ringers committed
232

233
234
235
236
237
238
239
240
241
	return &irma.ServerSessionResponse{
		SessionType:     irma.ActionIssuing,
		ProtocolVersion: session.version,
		ProofStatus:     session.result.ProofStatus,
		IssueSignatures: sigs,
	}, nil
}

func (session *session) nextSession() (irma.RequestorRequest, irma.AttributeConDisCon, error) {
242
243
	base := session.rrequest.Base()
	if base.NextSession == nil {
244
245
		return nil, nil, nil
	}
246
	url := base.NextSession.URL
247
248
249
250

	// Status is changed to DONE as soon as the next session URL is retrieved,
	// so right now the status must be CONNECTED
	if session.result.Status != irma.ServerStatusConnected ||
251
252
		session.result.ProofStatus != irma.ProofStatusValid ||
		session.result.Err != nil {
253
254
255
		return nil, nil, errors.New("session in invalid state")
	}

256
257
258
259
260
261
	var res interface{}
	var err error
	if session.conf.JwtRSAPrivateKey != nil {
		res, err = server.ResultJwt(
			session.result,
			session.conf.JwtIssuer,
262
			base.ResultJwtValidity,
263
264
			session.conf.JwtRSAPrivateKey,
		)
265
266
267
		if err != nil {
			return nil, nil, err
		}
268
269
270
271
	} else {
		res = session.result
	}

272
	var reqbts json.RawMessage
273
	err = irma.NewHTTPTransport("", false).Post(url, &reqbts, res)
274
	if err != nil {
275
276
277
278
		if sessErr, ok := err.(*irma.SessionError); ok && sessErr.RemoteStatus == http.StatusNoContent {
			// 204 instead of a new sessionRequest means no next session is coming
			return nil, nil, nil
		}
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
		return nil, nil, err
	}
	req, err := server.ParseSessionRequest([]byte(reqbts))
	if err != nil {
		return nil, nil, err
	}

	// Build list of attributes and values that were disclosed in this session
	// that need to be disclosed again in the next session(s)
	var disclosed irma.AttributeConDisCon
	for _, attrlist := range session.result.Disclosed {
		var con irma.AttributeCon
		for _, attr := range attrlist {
			con = append(con, irma.AttributeRequest{
				Type:  attr.Identifier,
				Value: attr.RawValue,
			})
		}
		disclosed = append(disclosed, irma.AttributeDisCon{con})
	}

	return req, disclosed, nil
}

func (s *Server) startNext(session *session, res *irma.ServerSessionResponse) error {
	next, disclosed, err := session.nextSession()
	if err != nil {
		return err
	}
	if next == nil {
		return nil
	}
311
	qr, token, _, err := s.StartSession(next, nil)
312
313
314
	if err != nil {
		return err
	}
315
	session.result.NextSession = token
316
	session.next = qr
317
318
319
320
321

	// All attributes that were disclosed in the previous session, as well as any attributes
	// from sessions before that, need to be disclosed in the new session as well
	newsession := s.sessions.get(token)
	newsession.implicitDisclosure = disclosed
322
	newsession.frontendAuth = session.frontendAuth
323
324
325
	res.NextSession = qr

	return nil
326
}
327

328
329
330
331
332
333
334
335
336
337
338
func (s *Server) handleSessionCommitments(w http.ResponseWriter, r *http.Request) {
	commitments := &irma.IssueCommitmentMessage{}
	bts, err := ioutil.ReadAll(r.Body)
	if err != nil {
		server.WriteError(w, server.ErrorMalformedInput, err.Error())
		return
	}
	if err := irma.UnmarshalValidate(bts, commitments); err != nil {
		server.WriteError(w, server.ErrorMalformedInput, err.Error())
		return
	}
339
340
341
342
343
344
345
346
347
348
	session := r.Context().Value("session").(*session)
	res, rerr := session.handlePostCommitments(commitments)
	if rerr != nil {
		server.WriteResponse(w, nil, rerr)
		return
	}
	if err = s.startNext(session, res); err != nil {
		server.WriteError(w, server.ErrorNextSession, err.Error())
		return
	}
349
	session.setStatus(irma.ServerStatusDone)
350
	server.WriteResponse(w, res, nil)
351
352
353
354
355
356
357
358
359
}

func (s *Server) handleSessionProofs(w http.ResponseWriter, r *http.Request) {
	bts, err := ioutil.ReadAll(r.Body)
	if err != nil {
		server.WriteError(w, server.ErrorMalformedInput, err.Error())
		return
	}
	session := r.Context().Value("session").(*session)
360
	var res *irma.ServerSessionResponse
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
	var rerr *irma.RemoteError
	switch session.action {
	case irma.ActionDisclosing:
		disclosure := &irma.Disclosure{}
		if err := irma.UnmarshalValidate(bts, disclosure); err != nil {
			server.WriteError(w, server.ErrorMalformedInput, err.Error())
			return
		}
		res, rerr = session.handlePostDisclosure(disclosure)
	case irma.ActionSigning:
		signature := &irma.SignedMessage{}
		if err := irma.UnmarshalValidate(bts, signature); err != nil {
			server.WriteError(w, server.ErrorMalformedInput, err.Error())
			return
		}
		res, rerr = session.handlePostSignature(signature)
	default:
		rerr = server.RemoteError(server.ErrorInvalidRequest, "")
	}
380
381
382
383
384
385
386
387
	if rerr != nil {
		server.WriteResponse(w, nil, rerr)
		return
	}
	if err = s.startNext(session, res); err != nil {
		server.WriteError(w, server.ErrorNextSession, err.Error())
		return
	}
388
	session.setStatus(irma.ServerStatusDone)
389
	server.WriteResponse(w, res, nil)
390
391
392
393
394
395
396
397
398
}

func (s *Server) handleSessionStatus(w http.ResponseWriter, r *http.Request) {
	res, err := r.Context().Value("session").(*session).handleGetStatus()
	server.WriteResponse(w, res, err)
}

func (s *Server) handleSessionStatusEvents(w http.ResponseWriter, r *http.Request) {
	session := r.Context().Value("session").(*session)
399
400
	session.locked = false
	session.Unlock()
401
402
	r = r.WithContext(context.WithValue(r.Context(), "sse", common.SSECtx{
		Component: server.ComponentSession,
403
		Arg:       string(session.clientToken),
404
	}))
405
	if err := s.SubscribeServerSentEvents(w, r, string(session.clientToken), false); err != nil {
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
	}
}

func (s *Server) handleSessionDelete(w http.ResponseWriter, r *http.Request) {
	r.Context().Value("session").(*session).handleDelete()
	w.WriteHeader(200)
}

func (s *Server) handleSessionGet(w http.ResponseWriter, r *http.Request) {
	var min, max irma.ProtocolVersion
	if err := json.Unmarshal([]byte(r.Header.Get(irma.MinVersionHeader)), &min); err != nil {
		server.WriteError(w, server.ErrorMalformedInput, err.Error())
		return
	}
	if err := json.Unmarshal([]byte(r.Header.Get(irma.MaxVersionHeader)), &max); err != nil {
		server.WriteError(w, server.ErrorMalformedInput, err.Error())
		return
	}
	session := r.Context().Value("session").(*session)
427
	clientAuth := irma.ClientAuthorization(r.Header.Get(irma.AuthorizationHeader))
Ivar Derksen's avatar
Ivar Derksen committed
428
	res, err := session.handleGetClientRequest(&min, &max, clientAuth)
429
430
431
	server.WriteResponse(w, res, err)
}

432
433
func (s *Server) handleSessionGetRequest(w http.ResponseWriter, r *http.Request) {
	session := r.Context().Value("session").(*session)
434
	if session.version.Below(2, 8) {
435
		server.WriteError(w, server.ErrorUnexpectedRequest, "Endpoint is not support in used protocol version")
436
437
		return
	}
438
	var rerr *irma.RemoteError
439
	request, err := session.getRequest()
440
441
442
443
	if err != nil {
		rerr = session.fail(server.ErrorRevocation, err.Error())
	}
	server.WriteResponse(w, request, rerr)
444
445
}

446
447
448
449
450
451
func (s *Server) handleFrontendStatus(w http.ResponseWriter, r *http.Request) {
	session := r.Context().Value("session").(*session)
	status := irma.FrontendSessionStatus{Status: session.status, NextSession: session.next}
	server.WriteResponse(w, status, nil)
}

452
453
454
455
456
457
458
459
460
461
462
463
464
465
func (s *Server) handleFrontendStatusEvents(w http.ResponseWriter, r *http.Request) {
	session := r.Context().Value("session").(*session)
	session.locked = false
	session.Unlock()
	r = r.WithContext(context.WithValue(r.Context(), "sse", common.SSECtx{
		Component: server.ComponentFrontendSession,
		Arg:       string(session.clientToken),
	}))
	if err := s.SubscribeServerSentEvents(w, r, string(session.clientToken), false); err != nil {
		server.WriteError(w, server.ErrorUnknown, err.Error())
		return
	}
}

466
func (s *Server) handleFrontendOptionsPost(w http.ResponseWriter, r *http.Request) {
467
	optionsRequest := &irma.FrontendOptionsRequest{}
468
469
470
471
472
473
474
475
476
477
	bts, err := ioutil.ReadAll(r.Body)
	if err != nil {
		server.WriteError(w, server.ErrorMalformedInput, err.Error())
		return
	}
	err = irma.UnmarshalValidate(bts, optionsRequest)
	if err != nil {
		server.WriteError(w, server.ErrorMalformedInput, err.Error())
		return
	}
478

479
	session := r.Context().Value("session").(*session)
480
481
482
483
484
485
486
487
	res, err := session.updateFrontendOptions(optionsRequest)
	if err != nil {
		server.WriteError(w, server.ErrorUnexpectedRequest, err.Error())
		return
	}
	server.WriteResponse(w, res, nil)
}

488
func (s *Server) handleFrontendPairingCompleted(w http.ResponseWriter, r *http.Request) {
489
	session := r.Context().Value("session").(*session)
490
	if err := session.pairingCompleted(); err != nil {
491
492
493
494
		server.WriteError(w, server.ErrorUnexpectedRequest, err.Error())
		return
	}
	w.WriteHeader(http.StatusNoContent)
495
496
497
498
499
500
501
502
}

func (s *Server) handleStaticMessage(w http.ResponseWriter, r *http.Request) {
	rrequest := s.conf.StaticSessionRequests[chi.URLParam(r, "name")]
	if rrequest == nil {
		server.WriteResponse(w, nil, server.RemoteError(server.ErrorInvalidRequest, "unknown static session"))
		return
	}
503
	qr, _, _, err := s.StartSession(rrequest, s.doResultCallback)
504
505
506
507
508
509
510
511
512
513
514
515
516
517
	if err != nil {
		server.WriteResponse(w, nil, server.RemoteError(server.ErrorMalformedInput, err.Error()))
		return
	}
	server.WriteResponse(w, qr, nil)
}

// GET revocation/events/{credtype}/{pkcounter}/{min}/{max}
func (s *Server) handleRevocationGetEvents(w http.ResponseWriter, r *http.Request) {
	cred := irma.NewCredentialTypeIdentifier(chi.URLParam(r, "id"))
	pkcounter, _ := strconv.ParseUint(chi.URLParam(r, "counter"), 10, 32)
	min, _ := strconv.ParseUint(chi.URLParam(r, "min"), 10, 64)
	max, _ := strconv.ParseUint(chi.URLParam(r, "max"), 10, 64)

518
	if settings := s.conf.RevocationSettings[cred]; settings == nil || !settings.Server {
519
520
		server.WriteBinaryResponse(w, nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server"))
		return
521
	}
522
	events, err := s.conf.IrmaConfiguration.Revocation.Events(cred, uint(pkcounter), min, max)
523
	if err != nil {
524
525
		server.WriteBinaryResponse(w, nil, server.RemoteError(server.ErrorRevocation, err.Error()))
		return
526
	}
527
528
529
530
531
532
533
534
535
536
537
	w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", irma.RevocationParameters.EventsCacheMaxAge))
	server.WriteBinaryResponse(w, events, nil)
}

func (s *Server) handleRevocationUpdateEvents(w http.ResponseWriter, r *http.Request) {
	if !s.conf.EnableSSE {
		server.WriteBinaryResponse(w, nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server"))
		return
	}
	id := chi.URLParam(r, "id")
	if id != "" {
538
539
540
		r = r.WithContext(context.WithValue(r.Context(), "sse", common.SSECtx{
			Component: server.ComponentRevocation,
			Arg:       id,
541
542
543
		}))
	}
	s.serverSentEvents.ServeHTTP(w, r)
544
545
}

546
// GET revocation/update/{credtype}/{count}[/{pkcounter}]
547
548
549
550
551
552
553
554
555
556
557
func (s *Server) handleRevocationGetUpdateLatest(w http.ResponseWriter, r *http.Request) {
	cred := irma.NewCredentialTypeIdentifier(chi.URLParam(r, "id")) // id
	count, _ := strconv.ParseUint(chi.URLParam(r, "count"), 10, 64) // count
	c := chi.URLParam(r, "counter")
	var counter *uint
	if c != "" {
		j, _ := strconv.ParseUint(chi.URLParam(r, "counter"), 10, 32) // counter
		k := uint(j)
		counter = &k
	}

558
	if settings := s.conf.RevocationSettings[cred]; settings == nil || !settings.Server {
559
560
		server.WriteBinaryResponse(w, nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server"))
		return
561
	}
562
	updates, err := s.conf.IrmaConfiguration.Revocation.UpdateLatest(cred, count, counter)
563
	if err != nil {
564
565
		server.WriteBinaryResponse(w, nil, server.RemoteError(server.ErrorRevocation, err.Error()))
		return
566
567
568
569
570
571
	}
	var mintime int64
	for _, u := range updates {
		if u.SignedAccumulator.Accumulator.Time < mintime || mintime == 0 {
			mintime = u.SignedAccumulator.Accumulator.Time
		}
572
	}
573
	maxage := mintime + int64(irma.RevocationParameters.AccumulatorUpdateInterval) - time.Now().Unix()
574
575
576
577
578
579
	w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", maxage))
	if counter == nil {
		server.WriteBinaryResponse(w, updates, nil)
	} else {
		server.WriteBinaryResponse(w, updates[*counter], nil)
	}
580
}
581

582
583
584
585
586
// POST revocation/issuancerecord/{credtype}/{counter}
func (s *Server) handleRevocationPostIssuanceRecord(w http.ResponseWriter, r *http.Request) {
	cred := irma.NewCredentialTypeIdentifier(chi.URLParam(r, "id"))
	counter, _ := strconv.ParseUint(chi.URLParam(r, "counter"), 10, 32)

587
	if settings := s.conf.RevocationSettings[cred]; settings == nil || !settings.Authority {
588
589
		server.WriteBinaryResponse(w, nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server"))
		return
590
591
592
593
	}

	// Grab the counter-th issuer public key, with which the message should be signed,
	// and verify and unmarshal the issuance record
594
	pk, err := s.conf.IrmaConfiguration.Revocation.Keys.PublicKey(cred.IssuerIdentifier(), uint(counter))
595
	if err != nil {
596
597
		server.WriteBinaryResponse(w, nil, server.RemoteError(server.ErrorRevocation, err.Error()))
		return
598
	}
599
	var rec irma.IssuanceRecord
600
601
602
603
604
	message, err := ioutil.ReadAll(r.Body)
	if err != nil {
		server.WriteBinaryResponse(w, nil, server.RemoteError(server.ErrorInvalidRequest, err.Error()))
		return
	}
605
	if err := signed.UnmarshalVerify(pk.ECDSA, message, &rec); err != nil {
606
607
		server.WriteBinaryResponse(w, nil, server.RemoteError(server.ErrorUnauthorized, err.Error()))
		return
608
	}
609
	if rec.CredType != cred {
610
611
		server.WriteBinaryResponse(w, nil, server.RemoteError(server.ErrorInvalidRequest, "issuance record of wrong credential type"))
		return
612
	}
613

614
	if err = s.conf.IrmaConfiguration.Revocation.AddIssuanceRecord(&rec); err != nil {
615
		server.WriteBinaryResponse(w, nil, server.RemoteError(server.ErrorRevocation, err.Error()))
616
	}
617
618
	w.WriteHeader(200)
	return
619
}