handle.go 10.8 KB
Newer Older
1
package servercore
2
3

import (
4
5
	"time"

6
	"github.com/privacybydesign/gabi"
7
	"github.com/privacybydesign/gabi/revocation"
8
	"github.com/privacybydesign/gabi/signed"
9
	"github.com/privacybydesign/irmago"
Sietse Ringers's avatar
Sietse Ringers committed
10
	"github.com/privacybydesign/irmago/server"
11
	"github.com/sirupsen/logrus"
12
13
)

Sietse Ringers's avatar
Sietse Ringers committed
14
15
16
17
18
// This file contains the handler functions for the protocol messages, receiving and returning normally
// Go-typed messages here (JSON (un)marshalling is handled by the router).
// Maintaining the session state is done here, as well as checking whether the session is in the
// appropriate status before handling the request.

19
func (session *session) handleDelete() {
20
	if session.status.Finished() {
21
		return
22
	}
23
	session.markAlive()
Sietse Ringers's avatar
Sietse Ringers committed
24

25
	session.result = &server.SessionResult{Token: session.token, Status: server.StatusCancelled, Type: session.action}
Sietse Ringers's avatar
Sietse Ringers committed
26
	session.setStatus(server.StatusCancelled)
27
28
}

29
func (session *session) handleGetRequest(min, max *irma.ProtocolVersion) (irma.SessionRequest, *irma.RemoteError) {
Sietse Ringers's avatar
Sietse Ringers committed
30
31
	if session.status != server.StatusInitialized {
		return nil, server.RemoteError(server.ErrorUnexpectedRequest, "Session already started")
32
33
	}

34
	session.markAlive()
35
36
	logger := session.conf.Logger.WithFields(logrus.Fields{"session": session.token})

37
	// we include the latest revocation updates for the client here, as opposed to when the session
38
39
	// was started, so that the client always gets the very latest revocation records
	var err error
40
	if err = session.conf.IrmaConfiguration.Revocation.SetRevocationUpdates(session.request.Base()); err != nil {
41
42
43
		return nil, session.fail(server.ErrorUnknown, err.Error()) // TODO error type
	}

44
45
46
47
48
49
50
51
52
	// 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")
	}

	if session.version, err = session.chooseProtocolVersion(min, max); err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
53
		return nil, session.fail(server.ErrorProtocolVersion, "")
54
	}
55
	logger.WithFields(logrus.Fields{"version": session.version.String()}).Debugf("Protocol version negotiated")
56
	session.request.Base().ProtocolVersion = session.version
57

Sietse Ringers's avatar
Sietse Ringers committed
58
	session.setStatus(server.StatusConnected)
59
60
61
62
63
64

	if session.version.Below(2, 5) {
		logger.Info("Returning legacy session format")
		legacy.Base().ProtocolVersion = session.version
		return legacy, nil
	}
65
66
67
68
69
70
71
72
73
74
75
76
77
78

	// In case of issuance requests, strip revocation keys from []CredentialRequest
	isreq, issuing := session.request.(*irma.IssuanceRequest)
	if !issuing {
		return session.request, nil
	}
	cpy, err := copyObject(isreq)
	if err != nil {
		return nil, session.fail(server.ErrorUnknown, err.Error()) // TODO error type
	}
	for _, cred := range cpy.(*irma.IssuanceRequest).Credentials {
		cred.RevocationKey = ""
	}
	return cpy.(*irma.IssuanceRequest), nil
79
80
}

81
82
83
84
func (session *session) handleGetStatus() (server.Status, *irma.RemoteError) {
	return session.status, nil
}

85
func (session *session) handlePostSignature(signature *irma.SignedMessage) (*irma.ProofStatus, *irma.RemoteError) {
Sietse Ringers's avatar
Sietse Ringers committed
86
87
	if session.status != server.StatusConnected {
		return nil, server.RemoteError(server.ErrorUnexpectedRequest, "Session not yet started or already finished")
88
	}
89
	session.markAlive()
90

91
92
	var err error
	var rerr *irma.RemoteError
93
	session.result.Signature = signature
94
	session.result.Disclosed, session.result.ProofStatus, err = signature.Verify(
95
		session.conf.IrmaConfiguration, session.request.(*irma.SignatureRequest))
96
	if err == nil {
Sietse Ringers's avatar
Sietse Ringers committed
97
		session.setStatus(server.StatusDone)
98
99
	} else {
		if err == irma.ErrorMissingPublicKey {
Sietse Ringers's avatar
Sietse Ringers committed
100
			rerr = session.fail(server.ErrorUnknownPublicKey, err.Error())
101
		} else {
Sietse Ringers's avatar
Sietse Ringers committed
102
			rerr = session.fail(server.ErrorUnknown, err.Error())
103
104
105
		}
	}
	return &session.result.ProofStatus, rerr
106
}
107

108
func (session *session) handlePostDisclosure(disclosure *irma.Disclosure) (*irma.ProofStatus, *irma.RemoteError) {
Sietse Ringers's avatar
Sietse Ringers committed
109
110
	if session.status != server.StatusConnected {
		return nil, server.RemoteError(server.ErrorUnexpectedRequest, "Session not yet started or already finished")
111
	}
112
	session.markAlive()
113

114
115
	var err error
	var rerr *irma.RemoteError
Sietse Ringers's avatar
Sietse Ringers committed
116
	session.result.Disclosed, session.result.ProofStatus, err = disclosure.Verify(
117
		session.conf.IrmaConfiguration, session.request.(*irma.DisclosureRequest))
118
	if err == nil {
Sietse Ringers's avatar
Sietse Ringers committed
119
		session.setStatus(server.StatusDone)
120
121
	} else {
		if err == irma.ErrorMissingPublicKey {
Sietse Ringers's avatar
Sietse Ringers committed
122
			rerr = session.fail(server.ErrorUnknownPublicKey, err.Error())
123
		} else {
Sietse Ringers's avatar
Sietse Ringers committed
124
			rerr = session.fail(server.ErrorUnknown, err.Error())
125
126
127
		}
	}
	return &session.result.ProofStatus, rerr
128
129
}

Sietse Ringers's avatar
Sietse Ringers committed
130
func (session *session) handlePostCommitments(commitments *irma.IssueCommitmentMessage) ([]*gabi.IssueSignatureMessage, *irma.RemoteError) {
Sietse Ringers's avatar
Sietse Ringers committed
131
132
	if session.status != server.StatusConnected {
		return nil, server.RemoteError(server.ErrorUnexpectedRequest, "Session not yet started or already finished")
Sietse Ringers's avatar
Sietse Ringers committed
133
134
	}
	session.markAlive()
135

Sietse Ringers's avatar
Sietse Ringers committed
136
	request := session.request.(*irma.IssuanceRequest)
137

138
139
140
	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
141
	}
142

Sietse Ringers's avatar
Sietse Ringers committed
143
144
	// Compute list of public keys against which to verify the received proofs
	disclosureproofs := irma.ProofList(commitments.Proofs[:discloseCount])
145
	pubkeys, err := disclosureproofs.ExtractPublicKeys(session.conf.IrmaConfiguration)
Sietse Ringers's avatar
Sietse Ringers committed
146
	if err != nil {
147
		return nil, session.fail(server.ErrorMalformedInput, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
148
149
150
	}
	for _, cred := range request.Credentials {
		iss := cred.CredentialTypeID.IssuerIdentifier()
151
		pubkey, _ := session.conf.IrmaConfiguration.PublicKey(iss, cred.KeyCounter) // No error, already checked earlier
Sietse Ringers's avatar
Sietse Ringers committed
152
153
		pubkeys = append(pubkeys, pubkey)
	}
154

Sietse Ringers's avatar
Sietse Ringers committed
155
156
157
158
	// Verify and merge keyshare server proofs, if any
	for i, proof := range commitments.Proofs {
		pubkey := pubkeys[i]
		schemeid := irma.NewIssuerIdentifier(pubkey.Issuer).SchemeManagerIdentifier()
159
		if session.conf.IrmaConfiguration.SchemeManagers[schemeid].Distributed() {
Sietse Ringers's avatar
Sietse Ringers committed
160
161
			proofP, err := session.getProofP(commitments, schemeid)
			if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
162
				return nil, session.fail(server.ErrorKeyshareProofMissing, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
163
164
165
			}
			proof.MergeProofP(proofP, pubkey)
		}
166
167
	}

Sietse Ringers's avatar
Sietse Ringers committed
168
	// Verify all proofs and check disclosed attributes, if any, against request
169
170
171
172
	now := time.Now()
	session.result.Disclosed, session.result.ProofStatus, err = commitments.Disclosure().VerifyAgainstRequest(
		session.conf.IrmaConfiguration, request, request.GetContext(), request.GetNonce(nil), pubkeys, &now, false,
	)
173
174
	if err != nil {
		if err == irma.ErrorMissingPublicKey {
Sietse Ringers's avatar
Sietse Ringers committed
175
			return nil, session.fail(server.ErrorUnknownPublicKey, "")
176
		} else {
Sietse Ringers's avatar
Sietse Ringers committed
177
			return nil, session.fail(server.ErrorUnknown, "")
178
179
180
		}
	}
	if session.result.ProofStatus == irma.ProofStatusExpired {
Sietse Ringers's avatar
Sietse Ringers committed
181
		return nil, session.fail(server.ErrorAttributesExpired, "")
182
	}
Sietse Ringers's avatar
Sietse Ringers committed
183
	if session.result.ProofStatus != irma.ProofStatusValid {
Sietse Ringers's avatar
Sietse Ringers committed
184
		return nil, session.fail(server.ErrorInvalidProofs, "")
185
	}
Sietse Ringers's avatar
Sietse Ringers committed
186
187
188
189
190

	// Compute CL signatures
	var sigs []*gabi.IssueSignatureMessage
	for i, cred := range request.Credentials {
		id := cred.CredentialTypeID.IssuerIdentifier()
191
192
		pk, _ := session.conf.IrmaConfiguration.PublicKey(id, cred.KeyCounter)
		sk, _ := session.conf.PrivateKey(id)
193
		issuer := gabi.NewIssuer(sk, pk, one)
194
195
196
197
		proof, ok := commitments.Proofs[i+discloseCount].(*gabi.ProofU)
		if !ok {
			return nil, session.fail(server.ErrorMalformedInput, "Received invalid issuance commitment")
		}
198
		attributes, err := cred.AttributeList(session.conf.IrmaConfiguration, 0x03)
Sietse Ringers's avatar
Sietse Ringers committed
199
		if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
200
			return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
201
		}
202
		witness, nonrevAttr, err := session.issuanceHandleRevocation(cred, attributes, sk)
203
204
		if err != nil {
			return nil, session.fail(server.ErrorIssuanceFailed, err.Error()) // TODO error type
205
206
		}
		sig, err := issuer.IssueSignature(proof.U, attributes.Ints, nonrevAttr, commitments.Nonce2)
Sietse Ringers's avatar
Sietse Ringers committed
207
		if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
208
			return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
209
		}
210
		sig.NonRevocationWitness = witness
Sietse Ringers's avatar
Sietse Ringers committed
211
		sigs = append(sigs, sig)
212
	}
Sietse Ringers's avatar
Sietse Ringers committed
213

Sietse Ringers's avatar
Sietse Ringers committed
214
	session.setStatus(server.StatusDone)
Sietse Ringers's avatar
Sietse Ringers committed
215
	return sigs, nil
216
}
217

218
219
220
// POST revocation/update/{credtype}
func (s *Server) handlePostUpdate(typ irma.CredentialTypeIdentifier, update *revocation.Update) (interface{}, *irma.RemoteError) {
	if err := s.conf.IrmaConfiguration.Revocation.AddUpdate(typ, update); err != nil {
221
		return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
222
223
224
225
	}
	return nil, nil
}

226
227
// GET revocation/updatefrom/{credtype}/{index}
func (s *Server) handleGetUpdateFrom(
228
	cred irma.CredentialTypeIdentifier, index uint64,
229
230
231
) (*revocation.Update, *irma.RemoteError) {
	if settings := s.conf.RevocationSettings[cred]; settings == nil ||
		!(settings.Mode == irma.RevocationModeProxy || settings.Mode == irma.RevocationModeServer) {
232
233
		return nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server")
	}
234
	update, err := s.conf.IrmaConfiguration.Revocation.UpdateFrom(cred, index)
235
236
237
	if err != nil {
		return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
	}
238
	return update, nil
239
240
}

241
242
// GET revocation/updatelatest/{credtype}/{count}
func (s *Server) handleGetUpdateLatest(
243
	cred irma.CredentialTypeIdentifier, count uint64,
244
245
246
) (*revocation.Update, *irma.RemoteError) {
	if settings := s.conf.RevocationSettings[cred]; settings == nil ||
		!(settings.Mode == irma.RevocationModeProxy || settings.Mode == irma.RevocationModeServer) {
247
248
		return nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server")
	}
249
	update, err := s.conf.IrmaConfiguration.Revocation.UpdateLatest(cred, count)
250
251
252
	if err != nil {
		return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
	}
253
	return update, nil
254
}
255

256
// POST revocation/issuancerecord/{credtype}/{keycounter}
257
func (s *Server) handlePostIssuanceRecord(
258
	cred irma.CredentialTypeIdentifier, counter uint64, message []byte,
259
) (string, *irma.RemoteError) {
260
	if settings := s.conf.RevocationSettings[cred]; settings == nil || settings.Mode != irma.RevocationModeServer {
261
262
263
264
265
		return "", server.RemoteError(server.ErrorInvalidRequest, "not supported by this server")
	}

	// Grab the counter-th issuer public key, with which the message should be signed,
	// and verify and unmarshal the issuance record
266
	pk, err := s.conf.IrmaConfiguration.Revocation.Keys.PublicKey(cred.IssuerIdentifier(), uint(counter))
267
268
269
	if err != nil {
		return "", server.RemoteError(server.ErrorUnknown, err.Error())
	}
270
	var rec irma.IssuanceRecord
271
	if err := signed.UnmarshalVerify(pk.ECDSA, message, &rec); err != nil {
272
		return "", server.RemoteError(server.ErrorUnauthorized, err.Error())
273
	}
274
275
	if rec.CredType != cred {
		return "", server.RemoteError(server.ErrorInvalidRequest, "issuance record of wrong credential type")
276
	}
277

278
	if err = s.conf.IrmaConfiguration.Revocation.AddIssuanceRecord(&rec); err != nil {
279
280
281
282
		return "", server.RemoteError(server.ErrorUnknown, err.Error())
	}
	return "OK", nil
}