handle.go 8.34 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
	return session.request, nil
67
68
}

69
70
71
72
func (session *session) handleGetStatus() (server.Status, *irma.RemoteError) {
	return session.status, nil
}

73
func (session *session) handlePostSignature(signature *irma.SignedMessage) (*irma.ProofStatus, *irma.RemoteError) {
Sietse Ringers's avatar
Sietse Ringers committed
74
75
	if session.status != server.StatusConnected {
		return nil, server.RemoteError(server.ErrorUnexpectedRequest, "Session not yet started or already finished")
76
	}
77
	session.markAlive()
78

79
80
	var err error
	var rerr *irma.RemoteError
81
	session.result.Signature = signature
82
	session.result.Disclosed, session.result.ProofStatus, err = signature.Verify(
83
		session.conf.IrmaConfiguration, session.request.(*irma.SignatureRequest))
84
	if err == nil {
Sietse Ringers's avatar
Sietse Ringers committed
85
		session.setStatus(server.StatusDone)
86
87
	} else {
		if err == irma.ErrorMissingPublicKey {
Sietse Ringers's avatar
Sietse Ringers committed
88
			rerr = session.fail(server.ErrorUnknownPublicKey, err.Error())
89
		} else {
Sietse Ringers's avatar
Sietse Ringers committed
90
			rerr = session.fail(server.ErrorUnknown, err.Error())
91
92
93
		}
	}
	return &session.result.ProofStatus, rerr
94
}
95

96
func (session *session) handlePostDisclosure(disclosure *irma.Disclosure) (*irma.ProofStatus, *irma.RemoteError) {
Sietse Ringers's avatar
Sietse Ringers committed
97
98
	if session.status != server.StatusConnected {
		return nil, server.RemoteError(server.ErrorUnexpectedRequest, "Session not yet started or already finished")
99
	}
100
	session.markAlive()
101

102
103
	var err error
	var rerr *irma.RemoteError
Sietse Ringers's avatar
Sietse Ringers committed
104
	session.result.Disclosed, session.result.ProofStatus, err = disclosure.Verify(
105
		session.conf.IrmaConfiguration, session.request.(*irma.DisclosureRequest))
106
	if err == nil {
Sietse Ringers's avatar
Sietse Ringers committed
107
		session.setStatus(server.StatusDone)
108
109
	} else {
		if err == irma.ErrorMissingPublicKey {
Sietse Ringers's avatar
Sietse Ringers committed
110
			rerr = session.fail(server.ErrorUnknownPublicKey, err.Error())
111
		} else {
Sietse Ringers's avatar
Sietse Ringers committed
112
			rerr = session.fail(server.ErrorUnknown, err.Error())
113
114
115
		}
	}
	return &session.result.ProofStatus, rerr
116
117
}

Sietse Ringers's avatar
Sietse Ringers committed
118
func (session *session) handlePostCommitments(commitments *irma.IssueCommitmentMessage) ([]*gabi.IssueSignatureMessage, *irma.RemoteError) {
Sietse Ringers's avatar
Sietse Ringers committed
119
120
	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
121
122
	}
	session.markAlive()
123

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

126
127
128
	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
129
	}
130

Sietse Ringers's avatar
Sietse Ringers committed
131
132
	// Compute list of public keys against which to verify the received proofs
	disclosureproofs := irma.ProofList(commitments.Proofs[:discloseCount])
133
	pubkeys, err := disclosureproofs.ExtractPublicKeys(session.conf.IrmaConfiguration)
Sietse Ringers's avatar
Sietse Ringers committed
134
	if err != nil {
135
		return nil, session.fail(server.ErrorMalformedInput, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
136
137
138
	}
	for _, cred := range request.Credentials {
		iss := cred.CredentialTypeID.IssuerIdentifier()
139
		pubkey, _ := session.conf.IrmaConfiguration.PublicKey(iss, cred.KeyCounter) // No error, already checked earlier
Sietse Ringers's avatar
Sietse Ringers committed
140
141
		pubkeys = append(pubkeys, pubkey)
	}
142

Sietse Ringers's avatar
Sietse Ringers committed
143
144
145
146
	// Verify and merge keyshare server proofs, if any
	for i, proof := range commitments.Proofs {
		pubkey := pubkeys[i]
		schemeid := irma.NewIssuerIdentifier(pubkey.Issuer).SchemeManagerIdentifier()
147
		if session.conf.IrmaConfiguration.SchemeManagers[schemeid].Distributed() {
Sietse Ringers's avatar
Sietse Ringers committed
148
149
			proofP, err := session.getProofP(commitments, schemeid)
			if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
150
				return nil, session.fail(server.ErrorKeyshareProofMissing, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
151
152
153
			}
			proof.MergeProofP(proofP, pubkey)
		}
154
155
	}

Sietse Ringers's avatar
Sietse Ringers committed
156
	// Verify all proofs and check disclosed attributes, if any, against request
157
158
159
160
	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,
	)
161
162
	if err != nil {
		if err == irma.ErrorMissingPublicKey {
Sietse Ringers's avatar
Sietse Ringers committed
163
			return nil, session.fail(server.ErrorUnknownPublicKey, "")
164
		} else {
Sietse Ringers's avatar
Sietse Ringers committed
165
			return nil, session.fail(server.ErrorUnknown, "")
166
167
168
		}
	}
	if session.result.ProofStatus == irma.ProofStatusExpired {
Sietse Ringers's avatar
Sietse Ringers committed
169
		return nil, session.fail(server.ErrorAttributesExpired, "")
170
	}
Sietse Ringers's avatar
Sietse Ringers committed
171
	if session.result.ProofStatus != irma.ProofStatusValid {
Sietse Ringers's avatar
Sietse Ringers committed
172
		return nil, session.fail(server.ErrorInvalidProofs, "")
173
	}
Sietse Ringers's avatar
Sietse Ringers committed
174
175
176
177
178

	// Compute CL signatures
	var sigs []*gabi.IssueSignatureMessage
	for i, cred := range request.Credentials {
		id := cred.CredentialTypeID.IssuerIdentifier()
179
180
		pk, _ := session.conf.IrmaConfiguration.PublicKey(id, cred.KeyCounter)
		sk, _ := session.conf.PrivateKey(id)
181
		issuer := gabi.NewIssuer(sk, pk, one)
182
183
184
185
		proof, ok := commitments.Proofs[i+discloseCount].(*gabi.ProofU)
		if !ok {
			return nil, session.fail(server.ErrorMalformedInput, "Received invalid issuance commitment")
		}
186
		attributes, err := cred.AttributeList(session.conf.IrmaConfiguration, 0x03)
Sietse Ringers's avatar
Sietse Ringers committed
187
		if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
188
			return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
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

		var witness *revocation.Witness
		var nonrevAttr *big.Int
		if session.conf.IrmaConfiguration.CredentialTypes[cred.CredentialTypeID].SupportsRevocation {
			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
215
		if err != nil {
Sietse Ringers's avatar
Sietse Ringers committed
216
			return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
217
		}
218
		sig.NonRevocationWitness = witness
Sietse Ringers's avatar
Sietse Ringers committed
219
		sigs = append(sigs, sig)
220
	}
Sietse Ringers's avatar
Sietse Ringers committed
221

Sietse Ringers's avatar
Sietse Ringers committed
222
	session.setStatus(server.StatusDone)
Sietse Ringers's avatar
Sietse Ringers committed
223
	return sigs, nil
224
}