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

import (
4
5
	"time"

6
	"github.com/privacybydesign/gabi"
7
8
	"github.com/privacybydesign/gabi/big"
	"github.com/privacybydesign/gabi/revocation"
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
40
41
42
43
44
	// 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
	// TODO revocation database update mechanism
	var err error
	if err = session.request.Base().SetRevocationRecords(session.conf.IrmaConfiguration); err != nil {
		return nil, session.fail(server.ErrorUnknown, err.Error()) // TODO error type
	}

45
46
47
48
49
50
51
52
53
	// 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
54
		return nil, session.fail(server.ErrorProtocolVersion, "")
55
	}
56
	logger.WithFields(logrus.Fields{"version": session.version.String()}).Debugf("Protocol version negotiated")
57
	session.request.Base().ProtocolVersion = session.version
58

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

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

	// 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
80
81
}

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

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

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
131
func (session *session) handlePostCommitments(commitments *irma.IssueCommitmentMessage) ([]*gabi.IssueSignatureMessage, *irma.RemoteError) {
Sietse Ringers's avatar
Sietse Ringers committed
132
133
	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
134
135
	}
	session.markAlive()
136

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

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
169
	// Verify all proofs and check disclosed attributes, if any, against request
170
171
172
173
	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,
	)
174
175
	if err != nil {
		if err == irma.ErrorMissingPublicKey {
Sietse Ringers's avatar
Sietse Ringers committed
176
			return nil, session.fail(server.ErrorUnknownPublicKey, "")
177
		} else {
Sietse Ringers's avatar
Sietse Ringers committed
178
			return nil, session.fail(server.ErrorUnknown, "")
179
180
181
		}
	}
	if session.result.ProofStatus == irma.ProofStatusExpired {
Sietse Ringers's avatar
Sietse Ringers committed
182
		return nil, session.fail(server.ErrorAttributesExpired, "")
183
	}
Sietse Ringers's avatar
Sietse Ringers committed
184
	if session.result.ProofStatus != irma.ProofStatusValid {
Sietse Ringers's avatar
Sietse Ringers committed
185
		return nil, session.fail(server.ErrorInvalidProofs, "")
186
	}
Sietse Ringers's avatar
Sietse Ringers committed
187
188
189
190
191

	// Compute CL signatures
	var sigs []*gabi.IssueSignatureMessage
	for i, cred := range request.Credentials {
		id := cred.CredentialTypeID.IssuerIdentifier()
192
193
		pk, _ := session.conf.IrmaConfiguration.PublicKey(id, cred.KeyCounter)
		sk, _ := session.conf.PrivateKey(id)
194
		issuer := gabi.NewIssuer(sk, pk, one)
195
196
197
198
		proof, ok := commitments.Proofs[i+discloseCount].(*gabi.ProofU)
		if !ok {
			return nil, session.fail(server.ErrorMalformedInput, "Received invalid issuance commitment")
		}
199
		attributes, err := cred.AttributeList(session.conf.IrmaConfiguration, 0x03)
Sietse Ringers's avatar
Sietse Ringers committed
200
		if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
201
			return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
202
		}
203
204
205

		var witness *revocation.Witness
		var nonrevAttr *big.Int
206
		if session.conf.IrmaConfiguration.CredentialTypes[cred.CredentialTypeID].SupportsRevocation() {
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
			db, err := session.conf.IrmaConfiguration.RevocationDB(cred.CredentialTypeID)
			if err != nil {
				return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
			}
			if db.Enabled() {
				if witness, err = sk.RevocationGenerateWitness(&db.Current); err != nil {
					return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
				}
				nonrevAttr = witness.E
				if err = db.AddIssuanceRecord(&revocation.IssuanceRecord{
					Key:        cred.RevocationKey,
					Attr:       nonrevAttr,
					Issued:     time.Now().UnixNano(), // or (floored) cred issuance time?
					ValidUntil: attributes.Expiry().UnixNano(),
				}); err != nil {
					return nil, session.fail(server.ErrorUnknown, "failed to save nonrevocation witness")
				}
			}
		}

		sig, err := issuer.IssueSignature(proof.U, attributes.Ints, nonrevAttr, commitments.Nonce2)
Sietse Ringers's avatar
Sietse Ringers committed
228
		if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
229
			return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
230
		}
231
		sig.NonRevocationWitness = witness
Sietse Ringers's avatar
Sietse Ringers committed
232
		sigs = append(sigs, sig)
233
	}
Sietse Ringers's avatar
Sietse Ringers committed
234

Sietse Ringers's avatar
Sietse Ringers committed
235
	session.setStatus(server.StatusDone)
Sietse Ringers's avatar
Sietse Ringers committed
236
	return sigs, nil
237
}
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269

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
	}
	for _, r := range records {
		if err = db.Add(r.Message, r.PublicKeyIndex); err != nil {
			return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
		}
	}
	return nil, nil
}

func (s *Server) handleGetRevocationRecords(
	cred irma.CredentialTypeIdentifier, index int,
) ([]*revocation.Record, *irma.RemoteError) {
	if _, ok := s.conf.RevocationServers[cred]; ok {
		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
}