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

import (
	"math/big"
	"time"
6
7
8

	"github.com/go-errors/errors"
	"github.com/mhe/gabi"
9
10
11
12
13
)

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

14
15
16
// Status is the proof status of a single attribute
type AttributeProofStatus string

17
const (
18
	ProofStatusValid             = ProofStatus("VALID")              // Proof is valid
19
	ProofStatusInvalid           = ProofStatus("INVALID")            // Proof is invalid
20
21
22
	ProofStatusInvalidTimestamp  = ProofStatus("INVALID_TIMESTAMP")  // Attribute-based signature had invalid timestamp
	ProofStatusUnmatchedRequest  = ProofStatus("UNMATCHED_REQUEST")  // Proof does not correspond to a specified request
	ProofStatusMissingAttributes = ProofStatus("MISSING_ATTRIBUTES") // Proof does not contain all requested attributes
23
24

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

28
29
30
31
32
	AttributeProofStatusPresent      = AttributeProofStatus("PRESENT")       // Attribute is disclosed and matches the value
	AttributeProofStatusExtra        = AttributeProofStatus("EXTRA")         // Attribute is disclosed, but wasn't requested in request
	AttributeProofStatusMissing      = AttributeProofStatus("MISSING")       // Attribute is NOT disclosed, but should be according to request
	AttributeProofStatusInvalidValue = AttributeProofStatus("INVALID_VALUE") // Attribute is disclosed, but has invalid value according to request
)
33

34
35
36
37
// VerificationResult is a result of verification of a SignedMessage or disclosure proof, containing all the disclosed attributes
type VerificationResult struct {
	Attributes []*DisclosedAttribute
	Status     ProofStatus
38
39
}

40
41
42
43
44
// DisclosedAttribute represents a disclosed attribute.
type DisclosedAttribute struct {
	Value      TranslatedString        `json:"value"` // Value of the disclosed attribute
	Identifier AttributeTypeIdentifier `json:"id"`
	Status     AttributeProofStatus    `json:"status"`
45
46
}

47
48
// ProofList is a gabi.ProofList with some extra methods.
type ProofList gabi.ProofList
49

50
51
var ErrorMissingPublicKey = errors.New("Missing public key")

52
// ExtractPublicKeys returns the public keys of each proof in the proofList, in the same order,
53
54
// for later use in verification of the proofList. If one of the proofs is not a ProofD
// an error is returned.
55
func (pl ProofList) ExtractPublicKeys(configuration *Configuration) ([]*gabi.PublicKey, error) {
56
	var publicKeys = make([]*gabi.PublicKey, 0, len(pl))
57

58
	for _, v := range pl {
59
60
61
62
63
64
65
66
		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
			}
67
68
69
			if publicKey == nil {
				return nil, ErrorMissingPublicKey
			}
70
71
			publicKeys = append(publicKeys, publicKey)
		default:
72
			return nil, errors.New("Cannot extract public key, not a disclosure proofD")
73
74
75
76
77
		}
	}
	return publicKeys, nil
}

78
// VerifyProofs verifies the proofs cryptographically.
79
func (pl ProofList) VerifyProofs(configuration *Configuration, context *big.Int, nonce *big.Int, publickeys []*gabi.PublicKey, isSig bool) (bool, error) {
80
81
82
83
	if publickeys == nil {
		var err error
		publickeys, err = pl.ExtractPublicKeys(configuration)
		if err != nil {
84
			return false, err
85
86
87
88
		}
	}

	if len(pl) != len(publickeys) {
89
		return false, errors.New("Insufficient public keys to verify the proofs")
90
	}
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

	// If the secret key comes from a credential whose scheme manager has a keyshare server,
	// then the secretkey = userpart + keysharepart.
	// So, we can only expect two secret key responses to be equal if their credentials
	// are both associated to either no keyshare server, or the same keyshare server.
	// (We have to check this here instead of in gabi, because gabi is unaware of schemes
	// and whether or not they are distributed.)
	secretkeyResponses := make(map[SchemeManagerIdentifier]*big.Int)
	nonKssSchemeID := NewSchemeManagerIdentifier(".") // We use this id for all schemes that don't use a kss
	for i, proof := range pl {
		schemeID := NewIssuerIdentifier(publickeys[i].Issuer).SchemeManagerIdentifier()
		if !configuration.SchemeManagers[schemeID].Distributed() {
			schemeID = nonKssSchemeID
		}
		if response, contains := secretkeyResponses[schemeID]; !contains {
			secretkeyResponses[schemeID] = proof.SecretKeyResponse()
		} else {
			if response.Cmp(proof.SecretKeyResponse()) != 0 {
109
				return false, nil
110
111
112
113
			}
		}
	}

114
	return gabi.ProofList(pl).Verify(publickeys, context, nonce, isSig), nil
115
}
116

117
118
// Expired returns true if any of the contained disclosure proofs is specified at the specified time,
// or now, when the specified time is nil.
119
func (pl ProofList) Expired(configuration *Configuration, t *time.Time) bool {
120
121
122
123
124
125
126
	if t == nil {
		temp := time.Now()
		t = &temp
	}
	for _, proof := range pl {
		proofd, ok := proof.(*gabi.ProofD)
		if !ok {
127
			continue
128
129
130
		}
		metadata := MetadataFromInt(proofd.ADisclosed[1], configuration) // index 1 is metadata attribute
		if metadata.Expiry().Before(*t) {
131
			return true
132
133
		}
	}
134
	return false
135
136
}

137
138
139
140
141
142
// DisclosedAttributes returns a slice containing the disclosed attributes that are present in the proof list.
// If a non-empty and non-nil AttributeDisjunctionList is included, then the first attributes in the returned slice match
// with the disjunction list in the disjunction list. If any of the given disjunctions is not matched by one
// of the disclosed attributes, then the corresponding item in the returned slice has status AttributeProofStatusMissing.
// The first return parameter of this function indicates whether or not all disjunctions (if present) are satisfied.
func (pl ProofList) DisclosedAttributes(configuration *Configuration, disjunctions AttributeDisjunctionList) (bool, []*DisclosedAttribute, error) {
143
	var list []*DisclosedAttribute
144
145
146
147
148
149
	list = make([]*DisclosedAttribute, len(disjunctions))
	for i := range list {
		// Populate list with AttributeProofStatusMissing; if an attribute that satisfies a disjunction
		// is found below, the corresponding entry in the list is overwritten
		list[i] = &DisclosedAttribute{
			Status: AttributeProofStatusMissing,
150
151
152
		}
	}

153
154
155
	// Temp slice for attributes that have not yet been matched to one of the disjunctions of the request
	// When we are done matching disclosed attributes against the request, filling the first few slots of list,
	// we append these to list just before returning
156
	extraAttrs := map[AttributeTypeIdentifier]*DisclosedAttribute{}
157

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
	for _, proof := range pl {
		proofd, ok := proof.(*gabi.ProofD)
		if !ok {
			continue
		}
		metadata := MetadataFromInt(proofd.ADisclosed[1], configuration) // index 1 is metadata attribute
		credtype := metadata.CredentialType()
		if credtype == nil {
			return false, nil, errors.New("ProofList contained a disclosure proof of an unkown credential type")
		}

		for k, v := range proofd.ADisclosed {
			if k < 2 {
				continue // skip metadata attribute
			}

			attrid := credtype.Attributes[k-2].GetAttributeTypeIdentifier()
			attrval := decodeAttribute(v, metadata.Version())
176
			attr := &DisclosedAttribute{
177
				Value:      translateAttribute(attrval),
178
179
180
181
				Identifier: attrid,
				Status:     AttributeProofStatusExtra,
			}
			extraAttrs[attrid] = attr
182
			if len(disjunctions) == 0 {
183
184
				continue
			}
185
186
187
188

			// See if the current attribute satisfies one of the disjunctions, if so, delete it from extraAttrs
			for i, disjunction := range disjunctions {
				if disjunction.attemptSatisfy(attrid, attrval) {
189
190
191
192
193
194
195
196
197
					if disjunction.satisfied() {
						attr.Status = AttributeProofStatusPresent
					} else {
						attr.Status = AttributeProofStatusInvalidValue
					}
					list[i] = attr
					delete(extraAttrs, attrid)
				}
			}
198
199
200
		}
	}

201
	// Any attributes still in here do not satisfy any of the specified disjunctions; append them now
202
203
	for _, attr := range extraAttrs {
		list = append(list, attr)
204
205
	}

206
	return len(disjunctions) == 0 || disjunctions.satisfied(), list, nil
207
208
}

209
210
211
212
213
214
func (pl ProofList) VerifyAgainstDisjunctions(
	configuration *Configuration,
	required AttributeDisjunctionList,
	context, nonce *big.Int,
	publickeys []*gabi.PublicKey,
	issig bool,
215
) ([]*DisclosedAttribute, ProofStatus, error) {
216
	// Cryptographically verify the IRMA disclosure proofs in the signature
217
218
219
	valid, err := pl.VerifyProofs(configuration, context, nonce, publickeys, issig)
	if !valid || err != nil {
		return nil, ProofStatusInvalid, err
220
221
222
223
224
	}

	// Next extract the contained attributes from the proofs, and match them to the signature request if present
	allmatched, list, err := pl.DisclosedAttributes(configuration, required)
	if err != nil {
225
		return nil, ProofStatusInvalid, err
226
227
228
229
	}

	// Return MISSING_ATTRIBUTES as proofstatus if one of the disjunctions in the request (if present) is not satisfied
	if !allmatched {
230
231
232
233
234
235
236
237
238
239
		return list, ProofStatusMissingAttributes, nil
	}

	return list, ProofStatusValid, nil
}

func (pl ProofList) Verify(configuration *Configuration, request *DisclosureRequest) ([]*DisclosedAttribute, ProofStatus, error) {
	list, status, err := pl.VerifyAgainstDisjunctions(configuration, request.Content, request.Context, request.Nonce, nil, false)
	if err != nil {
		return list, status, err
240
	}
241

242
243
	now := time.Now()
	if expired := pl.Expired(configuration, &now); expired {
244
		return list, ProofStatusExpired, nil
245
	}
246

247
	return list, status, nil
248
249
}

250
251
252
253
254
255
256
257
258
// Verify the attribute-based signature, optionally against a corresponding signature request. If the request is present
// (i.e. not nil), then the first attributes in the returned result match with the disjunction list in the request
// (that is, the i'th attribute in the result should satisfy the i'th disjunction in the request). If the request is not
// fully satisfied in this fasion, the Status of the result is ProofStatusMissingAttributes. Any remaining attributes
// (i.e. not asked for by the request) are also included in the result, after the attributes that match disjunctions
// in the request.
//
// The signature request is optional; if it is nil then the attribute-based signature is still verified, and all
// containing attributes returned in the result.
259
func (sm *SignedMessage) Verify(configuration *Configuration, request *SignatureRequest) ([]*DisclosedAttribute, ProofStatus, error) {
260
	var message string
261

262
	// First check if this signature matches the request
263
264
265
	if request != nil {
		request.Timestamp = sm.Timestamp
		if !sm.MatchesNonceAndContext(request) {
266
			return nil, ProofStatusUnmatchedRequest, nil
267
		}
268
269
270
271
272
		// If there is a request, then the signed message must be that of the request
		message = request.Message
	} else {
		// If not, we just verify that the signed message is a valid signature over its contained message
		message = sm.Message
273
274
275
	}

	// Verify the timestamp
276
	if sm.Timestamp != nil {
277
		if err := sm.VerifyTimestamp(message, configuration); err != nil {
278
			return nil, ProofStatusInvalidTimestamp, nil
279
		}
280
281
	}

282
	// Now, cryptographically verify the IRMA disclosure proofs in the signature
283
284
	pl := ProofList(sm.Signature)
	var required AttributeDisjunctionList
285
	if request != nil {
286
		required = request.Content
287
	}
288
289
290
	result, status, err := pl.VerifyAgainstDisjunctions(configuration, required, sm.Context, sm.GetNonce(), nil, true)
	if status != ProofStatusValid || err != nil {
		return result, status, err
291
292
	}

293
	// Check if a credential is expired
294
295
296
297
	var t time.Time
	if sm.Timestamp != nil {
		t = time.Unix(sm.Timestamp.Time, 0)
	}
298
	if expired := pl.Expired(configuration, &t); expired {
299
		if sm.Timestamp == nil {
300
301
302
			// 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.
303
			status = ProofStatusExpired
304
		} else {
305
306
			// 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
307
			status = ProofStatusInvalid
308
		}
309
		return result, status, nil
310
311
	}

312
	// All disjunctions satisfied and nothing expired, proof is valid!
313
	return result, ProofStatusValid, nil
314
}