handle.go 9.99 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
38
39
	// we include the latest revocation records for the client here, as opposed to when the session
	// was started, so that the client always gets the very latest revocation records
	var err error
40
	if err = session.conf.IrmaConfiguration.RevocationSetRecords(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
203
204
		witness, nonrevAttr, err := session.handleRevocation(cred, attributes, sk)
		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
221
222
223
224

func (s *Server) handlePostRevocationRecords(
	cred irma.CredentialTypeIdentifier, records []*revocation.Record,
) (interface{}, *irma.RemoteError) {
	db, err := s.conf.IrmaConfiguration.RevocationDB(cred)
	if err != nil {
		return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
	}
225
226
	if err = db.AddRecords(records); err != nil {
		return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
227
228
229
230
231
232
233
	}
	return nil, nil
}

func (s *Server) handleGetRevocationRecords(
	cred irma.CredentialTypeIdentifier, index int,
) ([]*revocation.Record, *irma.RemoteError) {
234
	if _, ok := s.conf.RevocationServers[cred]; !ok {
235
236
237
238
239
240
241
242
243
244
245
246
		return nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server")
	}
	db, err := s.conf.IrmaConfiguration.RevocationDB(cred)
	if err != nil {
		return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
	}
	records, err := db.RevocationRecords(index)
	if err != nil {
		return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
	}
	return records, nil
}
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276

func (s *Server) handleRevoke(
	cred irma.CredentialTypeIdentifier, msg signed.Message,
) (string, *irma.RemoteError) {
	sk, err := s.conf.IrmaConfiguration.PrivateKey(cred.IssuerIdentifier())
	if err != nil {
		return "", server.RemoteError(server.ErrorUnknown, err.Error())
	}
	if sk == nil {
		return "", server.RemoteError(server.ErrorUnknownPublicKey, "")
	}
	rsk, err := sk.RevocationKey()
	if err != nil {
		return "", server.RemoteError(server.ErrorUnknown, err.Error())
	}

	var cmd revocation.Command
	if err = signed.UnmarshalVerify(&rsk.ECDSA.PublicKey, msg, &cmd); err != nil {
		return "", server.RemoteError(server.ErrorUnknown, err.Error())
	}

	db, err := s.conf.IrmaConfiguration.RevocationDB(cred)
	if err != nil {
		return "", server.RemoteError(server.ErrorUnknown, err.Error())
	}
	if err = db.RevokeAttr(rsk, cmd.E); err != nil {
		return "", server.RemoteError(server.ErrorUnknown, err.Error())
	}
	return "OK", nil
}