verify.go 11.6 KB
Newer Older
1
2
3
4
5
6
package irma

import (
	"fmt"
	"math/big"
	"time"
7
8
9

	"github.com/go-errors/errors"
	"github.com/mhe/gabi"
10
11
12
13
14
15
16
17
)

// ProofStatus is the status of the complete proof
type ProofStatus string

const (
	VALID              = ProofStatus("VALID")
	INVALID_CRYPTO     = ProofStatus("INVALID_CRYPTO")
18
	INVALID_TIMESTAMP  = ProofStatus("INVALID_TIMESTAMP")
19
	UNMATCHED_REQUEST  = ProofStatus("UNMATCHED_REQUEST")
20
	MISSING_ATTRIBUTES = ProofStatus("MISSING_ATTRIBUTES")
21
22
23
24

	// The contained attributes are currently expired, but it is not certain if they already were expired
	// during creation of the ABS.
	EXPIRED = ProofStatus("EXPIRED")
25
26
27
28
)

// ProofResult is a result of a complete proof, containing all the disclosed attributes and corresponding request
type ProofResult struct {
29
	Disjunctions []*DisclosedAttributeDisjunction `json:"disjunctions"`
30
31
32
33
	ProofStatus  ProofStatus
}

type SignatureProofResult struct {
34
	*ProofResult
35
	Message string `json:"message"`
36
37
38
39
40
}

// DisclosedCredential contains raw disclosed credentials, without any extra parsing information
type DisclosedCredential struct {
	metadataAttribute *MetadataAttribute
41
	Attributes        map[AttributeTypeIdentifier]*string `json:"attributes"`
42
43
}

44
45
46
47
48
49
type DisclosedCredentialList []*DisclosedCredential

// Helper function to check if an attribute is satisfied against a list of disclosed attributes
// This is the case if:
// attribute is contained in disclosed AND if a value is present: equal to that value
// al can be nil if you don't want to include attribute status for proof
50
func (disclosed DisclosedCredentialList) isAttributeSatisfied(attributeId AttributeTypeIdentifier, requestedValue *string) (bool, *AttributeResult) {
51
52
53
54
55
	ar := AttributeResult{
		AttributeId: attributeId,
	}

	for _, cred := range disclosed {
56
		disclosedAttributeValue := cred.Attributes[attributeId]
57
58

		// Continue to next credential if requested attribute isn't disclosed in this credential
59
		if disclosedAttributeValue == nil {
60
61
62
63
64
65
66
			continue
		}

		// If this is the disclosed attribute, check if value matches
		// Attribute is satisfied if:
		// - Attribute is disclosed (i.e. not nil)
		// - Value is empty OR value equal to disclosedValue
67
		ar.AttributeValue = *disclosedAttributeValue
68

69
		if requestedValue == nil || *disclosedAttributeValue == *requestedValue {
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
			ar.AttributeProofStatus = PRESENT
			return true, &ar
		} else {
			// If attribute is disclosed and present, but not equal to required value, mark it as invalid_value
			// We won't return true and continue searching in other disclosed attributes
			ar.AttributeProofStatus = INVALID_VALUE
		}
	}

	// If there is never a value assigned, then this attribute isn't disclosed, and thus missing
	if ar.AttributeValue == "" {
		ar.AttributeProofStatus = MISSING
	}
	return false, &ar
}

// Create a signature proof result and check disclosed credentials against a signature request
func (disclosed DisclosedCredentialList) createAndCheckSignatureProofResult(configuration *Configuration, sigRequest *SignatureRequest) *SignatureProofResult {
	signatureProofResult := SignatureProofResult{
		ProofResult: &ProofResult{},
90
		Message:     sigRequest.Message,
91
92
93
	}
	for _, content := range sigRequest.Content {
		isSatisfied, disjunction := content.SatisfyDisclosed(disclosed, configuration)
94
		signatureProofResult.Disjunctions = append(signatureProofResult.Disjunctions, disjunction)
95
96
97
98
99
100
101
102
103
104
105

		// If satisfied, continue to next one
		if isSatisfied {
			continue
		}

		// Else, set proof status to missing_attributes, but check other as well to add other disjunctions to result
		// (so user also knows attribute status of other disjunctions)
		signatureProofResult.ProofStatus = MISSING_ATTRIBUTES
	}

106
	signatureProofResult.Disjunctions = addExtraAttributes(disclosed, signatureProofResult.ProofResult)
107
108
109
	return &signatureProofResult
}

110
111
// Returns true if one of the disclosed credentials is expired at the specified time
func (disclosed DisclosedCredentialList) IsExpired(t time.Time) bool {
112
	for _, cred := range disclosed {
113
		if cred.IsExpired(t) {
114
115
116
117
118
119
			return true
		}
	}
	return false
}

120
func (proofResult *ProofResult) ToAttributeResultList() AttributeResultList {
121
122
	var resultList AttributeResultList

123
	for _, v := range proofResult.Disjunctions {
124
125
126
127
128
129
		result := AttributeResult{
			AttributeValue:       v.DisclosedValue,
			AttributeId:          v.DisclosedId,
			AttributeProofStatus: v.ProofStatus,
		}

130
		resultList = append(resultList, &result)
131
	}
132
	return resultList
133
134
135
136
}

// Returns true if this attrId is present in one of the disjunctions
func (proofResult *ProofResult) ContainsAttribute(attrId AttributeTypeIdentifier) bool {
137
	for _, disj := range proofResult.Disjunctions {
138
139
140
141
142
143
144
145
146
147
		for _, attr := range disj.Attributes {
			if attr == attrId {
				return true
			}
		}
	}

	return false
}

148
149
func (cred *DisclosedCredential) IsExpired(t time.Time) bool {
	return cred.metadataAttribute.Expiry().Before(t)
150
151
152
}

func NewDisclosedCredentialFromADisclosed(aDisclosed map[int]*big.Int, configuration *Configuration) *DisclosedCredential {
153
	attributes := make(map[AttributeTypeIdentifier]*string)
154
155
156
157
158
159
160
161
162
163

	metadata := MetadataFromInt(aDisclosed[1], configuration) // index 1 is metadata attribute
	cred := metadata.CredentialType()

	for k, v := range aDisclosed {
		if k < 2 {
			continue
		}

		description := cred.Attributes[k-2]
164
165
		attributeValue := decodeAttribute(v, metadata.Version())
		attributes[description.GetAttributeTypeIdentifier(cred.Identifier())] = attributeValue
166
167
168
169
170
171
172
173
	}

	return &DisclosedCredential{
		metadataAttribute: metadata,
		Attributes:        attributes,
	}
}

174
175
func extractPublicKeys(configuration *Configuration, proofList gabi.ProofList) ([]*gabi.PublicKey, error) {
	var publicKeys = make([]*gabi.PublicKey, 0, len(proofList))
176

177
	for _, v := range proofList {
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
		switch v.(type) {
		case *gabi.ProofD:
			proof := v.(*gabi.ProofD)
			metadata := MetadataFromInt(proof.ADisclosed[1], configuration) // index 1 is metadata attribute
			publicKey, err := metadata.PublicKey()
			if err != nil {
				return nil, err
			}
			publicKeys = append(publicKeys, publicKey)
		default:
			return nil, errors.New("Cannot extract public key, not a disclosure proofD!")
		}
	}
	return publicKeys, nil
}

194
195
func extractDisclosedCredentials(conf *Configuration, proofList gabi.ProofList) (DisclosedCredentialList, error) {
	var credentials = make(DisclosedCredentialList, 0, len(proofList))
196

197
	for _, v := range proofList {
198
199
200
201
202
203
204
205
206
207
208
209
210
211
		switch v.(type) {
		case *gabi.ProofD:
			proof := v.(*gabi.ProofD)
			cred := NewDisclosedCredentialFromADisclosed(proof.ADisclosed, conf)
			credentials = append(credentials, cred)
		default:
			return nil, errors.New("Cannot extract credentials from proof, not a disclosure proofD!")
		}
	}

	return credentials, nil
}

// Add extra disclosed attributes to an existing and checked ProofResult in 'dummy disjunctions'
212
func addExtraAttributes(disclosed DisclosedCredentialList, proofResult *ProofResult) []*DisclosedAttributeDisjunction {
213
214
	returnDisjunctions := make([]*DisclosedAttributeDisjunction, len(proofResult.Disjunctions))
	copy(returnDisjunctions, proofResult.Disjunctions)
215

216
	for _, cred := range disclosed {
217
218
219
220
221
222
		for attrId := range cred.Attributes {
			if proofResult.ContainsAttribute(attrId) {
				continue
			}

			dummyDisj := DisclosedAttributeDisjunction{
223
				DisclosedValue: *cred.Attributes[attrId],
224
225
226
227
228
229
230
231
232
233
234
				DisclosedId:    attrId,
				ProofStatus:    EXTRA,
			}
			returnDisjunctions = append(returnDisjunctions, &dummyDisj)
		}
	}

	return returnDisjunctions
}

// Check an gabi prooflist against a signature proofrequest
235
func checkProofWithRequest(configuration *Configuration, irmaSignature *IrmaSignedMessage, sigRequest *SignatureRequest) *SignatureProofResult {
236
	disclosed, err := extractDisclosedCredentials(configuration, irmaSignature.Signature)
237
238
239
240

	if err != nil {
		fmt.Println(err)
		return &SignatureProofResult{
241
			ProofResult: &ProofResult{
242
243
244
245
246
				ProofStatus: INVALID_CRYPTO,
			},
		}
	}

247
	signatureProofResult := disclosed.createAndCheckSignatureProofResult(configuration, sigRequest)
248
249
250
251
252
253
254
255

	// Return MISSING_ATTRIBUTES as proofstatus if one attribute is missing
	// This status takes priority over 'EXPIRED'
	if signatureProofResult.ProofStatus == MISSING_ATTRIBUTES {
		return signatureProofResult
	}

	// If all disjunctions are satisfied, check if a credential is expired
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
	if irmaSignature.Timestamp == nil {
		if disclosed.IsExpired(time.Now()) {
			// At least one of the contained attributes has currently expired. We don't know the
			// creation time of the ABS so we can't ascertain that the attributes were still valid then.
			// Otherwise the signature is valid.
			signatureProofResult.ProofStatus = EXPIRED
			return signatureProofResult
		}
	} else {
		if disclosed.IsExpired(time.Unix(irmaSignature.Timestamp.Time, 0)) {
			// The ABS contains attributes that were expired at the time of creation of the ABS.
			// This must not happen and in this case the signature is invalid
			signatureProofResult.ProofStatus = INVALID_CRYPTO
			return signatureProofResult
		}
271
272
273
274
275
276
277
278
	}

	// All disjunctions satisfied and nothing expired, proof is valid!
	signatureProofResult.ProofStatus = VALID
	return signatureProofResult
}

// Verify an IRMA proof cryptographically
279
func verify(configuration *Configuration, proofList gabi.ProofList, context *big.Int, nonce *big.Int, isSig bool) bool {
280
281
282
283
284
285
286
287
288
289
	// Extract public keys
	pks, err := extractPublicKeys(configuration, proofList)
	if err != nil {
		return false
	}

	return proofList.Verify(pks, context, nonce, true, isSig)
}

// Verify a signature proof and check if the attributes match the attributes in the original request
290
func VerifySig(configuration *Configuration, irmaSignature *IrmaSignedMessage, sigRequest *SignatureRequest) *SignatureProofResult {
291
292
293
294
295
296
297
298
299
300
301
302
303
	// First, verify the timestamp, if any
	if irmaSignature.Timestamp != nil {
		if err := VerifyTimestamp(irmaSignature, sigRequest.Message, configuration); err != nil {
			return &SignatureProofResult{
				ProofResult: &ProofResult{
					ProofStatus: INVALID_TIMESTAMP,
				},
			}
		}
		sigRequest.Timestamp = irmaSignature.Timestamp
	}

	// Then, check if nonce and context of the signature match those of the signature request
304
	if !irmaSignature.MatchesNonceAndContext(sigRequest) {
305
		return &SignatureProofResult{
306
			ProofResult: &ProofResult{
307
				ProofStatus: UNMATCHED_REQUEST,
308
309
310
311
312
			},
		}
	}

	// Now, cryptographically verify the signature
313
	if !verify(configuration, irmaSignature.Signature, sigRequest.GetContext(), sigRequest.GetNonce(), true) {
314
		return &SignatureProofResult{
315
			ProofResult: &ProofResult{
316
317
318
319
320
321
				ProofStatus: INVALID_CRYPTO,
			},
		}
	}

	// Finally, check whether attribute values in proof satisfy the original signature request
322
	return checkProofWithRequest(configuration, irmaSignature, sigRequest)
323
324
325
326
}

// Verify a signature cryptographically, but do not check/compare with a signature request
func VerifySigWithoutRequest(configuration *Configuration, irmaSignature *IrmaSignedMessage) (ProofStatus, DisclosedCredentialList) {
327
328
329
330
331
332
333
334
	// First, verify the timestamp, if any
	if irmaSignature.Timestamp != nil {
		if err := VerifyTimestamp(irmaSignature, irmaSignature.Message, configuration); err != nil {
			return INVALID_TIMESTAMP, nil
		}
	}

	// Cryptographically verify the signature
335
	if !verify(configuration, irmaSignature.Signature, irmaSignature.Context, irmaSignature.GetNonce(), true) {
336
337
338
339
		return INVALID_CRYPTO, nil
	}

	// Extract attributes and return result
340
	disclosed, err := extractDisclosedCredentials(configuration, irmaSignature.Signature)
341
342
343
344
345
346

	if err != nil {
		fmt.Println(err)
		return INVALID_CRYPTO, nil
	}

347
348
349
350
351
352
353
354
	if irmaSignature.Timestamp == nil {
		if disclosed.IsExpired(time.Now()) {
			return EXPIRED, disclosed
		}
	} else {
		if disclosed.IsExpired(time.Unix(irmaSignature.Timestamp.Time, 0)) {
			return INVALID_CRYPTO, nil
		}
355
356
357
	}

	return VALID, disclosed
358
}