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

import (
	"time"
5 6

	"github.com/go-errors/errors"
7 8
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
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
	ProofStatusExpired           = ProofStatus("EXPIRED")            // Attributes were expired at proof creation time (now, or according to timestamp in case of abs)
24

25 26 27 28 29
	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
)
30

31 32 33 34 35
// 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"`
36 37
}

38 39
// ProofList is a gabi.ProofList with some extra methods.
type ProofList gabi.ProofList
40

41 42
var ErrorMissingPublicKey = errors.New("Missing public key")

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

49
	for _, v := range pl {
50 51 52 53 54 55 56 57
		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
			}
58 59 60
			if publicKey == nil {
				return nil, ErrorMissingPublicKey
			}
61 62
			publicKeys = append(publicKeys, publicKey)
		default:
63
			return nil, errors.New("Cannot extract public key, not a disclosure proofD")
64 65 66 67 68
		}
	}
	return publicKeys, nil
}

69
// VerifyProofs verifies the proofs cryptographically.
70
func (pl ProofList) VerifyProofs(configuration *Configuration, context *big.Int, nonce *big.Int, publickeys []*gabi.PublicKey, isSig bool) (bool, error) {
71 72 73 74
	if publickeys == nil {
		var err error
		publickeys, err = pl.ExtractPublicKeys(configuration)
		if err != nil {
75
			return false, err
76 77 78 79
		}
	}

	if len(pl) != len(publickeys) {
80
		return false, errors.New("Insufficient public keys to verify the proofs")
81
	}
82

83 84 85
	// Compute slice to inform gabi of which proofs should be verified to share the same secret key
	keyshareServers := make([]string, len(pl))
	for i := range pl {
86 87
		schemeID := NewIssuerIdentifier(publickeys[i].Issuer).SchemeManagerIdentifier()
		if !configuration.SchemeManagers[schemeID].Distributed() {
88
			keyshareServers[i] = "." // dummy value: no IRMA scheme will ever have this name
89
		} else {
90
			keyshareServers[i] = schemeID.Name()
91 92 93
		}
	}

94
	return gabi.ProofList(pl).Verify(publickeys, context, nonce, isSig, keyshareServers), nil
95
}
96

97 98
// Expired returns true if any of the contained disclosure proofs is specified at the specified time,
// or now, when the specified time is nil.
99
func (pl ProofList) Expired(configuration *Configuration, t *time.Time) bool {
100 101 102 103 104 105 106
	if t == nil {
		temp := time.Now()
		t = &temp
	}
	for _, proof := range pl {
		proofd, ok := proof.(*gabi.ProofD)
		if !ok {
107
			continue
108 109 110
		}
		metadata := MetadataFromInt(proofd.ADisclosed[1], configuration) // index 1 is metadata attribute
		if metadata.Expiry().Before(*t) {
111
			return true
112 113
		}
	}
114
	return false
115 116
}

117 118 119 120 121
// 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.
122
func (d *Disclosure) DisclosedAttributes(configuration *Configuration, disjunctions AttributeDisjunctionList) (bool, []*DisclosedAttribute, error) {
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
	if d.Indices == nil || len(disjunctions) == 0 {
		return ProofList(d.Proofs).DisclosedAttributes(configuration, disjunctions)
	}

	list := make([]*DisclosedAttribute, len(disjunctions))
	usedAttrs := map[int]map[int]struct{}{} // keep track of attributes that satisfy the disjunctions

	// For each of the disjunctions, lookup the attribute that the user sent to satisfy this disjunction,
	// using the indices specified by the user in d.Indices. Then see if the attribute satisfies the disjunction.
	for i, disjunction := range disjunctions {
		index := d.Indices[i][0]
		proofd, ok := d.Proofs[index.CredentialIndex].(*gabi.ProofD)
		if !ok {
			// If with the index the user told us to look for the required attribute at this specific location,
			// and the proof here is not a disclosure proof, then reject
			return false, nil, errors.New("ProofList contained proof of invalid type")
		}

		metadata := MetadataFromInt(proofd.ADisclosed[1], configuration) // index 1 is metadata attribute
142
		attr, attrval, err := parseAttribute(index.AttributeIndex, metadata, proofd.ADisclosed[index.AttributeIndex])
143 144 145 146 147 148 149 150 151 152 153
		if err != nil {
			return false, nil, err
		}

		if disjunction.attemptSatisfy(attr.Identifier, attrval) {
			list[i] = attr
			if disjunction.satisfied() {
				list[i].Status = AttributeProofStatusPresent
			} else {
				list[i].Status = AttributeProofStatusInvalidValue
			}
154 155 156 157
			if usedAttrs[index.CredentialIndex] == nil {
				usedAttrs[index.CredentialIndex] = map[int]struct{}{}
			}
			usedAttrs[index.CredentialIndex][index.AttributeIndex] = struct{}{}
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
		} else {
			list[i] = &DisclosedAttribute{Status: AttributeProofStatusMissing}
		}
	}

	// Loop over any extra attributes in d.Proofs not requested in any of the disjunctions
	for i, proof := range d.Proofs {
		proofd, ok := proof.(*gabi.ProofD)
		if !ok {
			continue
		}
		metadata := MetadataFromInt(proofd.ADisclosed[1], configuration) // index 1 is metadata attribute
		for attrIndex, attrInt := range proofd.ADisclosed {
			if attrIndex == 0 || attrIndex == 1 { // Never add secret key or metadata (i.e. no-attribute disclosure) as extra
				continue // Note that the secret should never be disclosed by the client, but we skip it to be sure
			}
			if _, used := usedAttrs[i][attrIndex]; used {
				continue
			}

			attr, _, err := parseAttribute(attrIndex, metadata, attrInt)
			if err != nil {
				return false, nil, err
			}
			attr.Status = AttributeProofStatusExtra
			list = append(list, attr)
		}
	}
186

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
	return len(disjunctions) == 0 || disjunctions.satisfied(), list, nil
}

func parseAttribute(index int, metadata *MetadataAttribute, attr *big.Int) (*DisclosedAttribute, *string, error) {
	var attrid AttributeTypeIdentifier
	var attrval *string
	credtype := metadata.CredentialType()
	if credtype == nil {
		return nil, nil, errors.New("ProofList contained a disclosure proof of an unkown credential type")
	}
	if index == 1 {
		attrid = NewAttributeTypeIdentifier(credtype.Identifier().String())
		p := "present"
		attrval = &p
	} else {
		attrid = credtype.AttributeTypes[index-2].GetAttributeTypeIdentifier()
		attrval = decodeAttribute(attr, metadata.Version())
	}
	return &DisclosedAttribute{
		Identifier: attrid,
		Value:      translateAttribute(attrval),
	}, attrval, nil
209 210 211
}

func (pl ProofList) DisclosedAttributes(configuration *Configuration, disjunctions AttributeDisjunctionList) (bool, []*DisclosedAttribute, error) {
212
	var list []*DisclosedAttribute
213 214 215 216 217 218
	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,
219 220 221
		}
	}

222 223 224
	// 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
225
	extraAttrs := map[AttributeTypeIdentifier]*DisclosedAttribute{}
226

227
	for _, proof := range pl {
228 229 230 231 232 233
		proofd, ok := proof.(*gabi.ProofD)
		if !ok {
			continue
		}
		metadata := MetadataFromInt(proofd.ADisclosed[1], configuration) // index 1 is metadata attribute

234 235
		for attrIndex, attrInt := range proofd.ADisclosed {
			if attrIndex == 0 {
236
				continue // Should never be disclosed, but skip it to be sure
237 238
			}

239 240 241
			attr, attrval, err := parseAttribute(attrIndex, metadata, attrInt)
			if err != nil {
				return false, nil, err
242
			}
243

244 245
			if attrIndex > 1 { // Never add metadata (i.e. no-attribute disclosure) as extra
				extraAttrs[attr.Identifier] = attr
246
			}
247
			if len(disjunctions) == 0 {
248 249
				continue
			}
250 251 252

			// See if the current attribute satisfies one of the disjunctions, if so, delete it from extraAttrs
			for i, disjunction := range disjunctions {
253
				if disjunction.attemptSatisfy(attr.Identifier, attrval) {
254 255 256 257 258 259
					if disjunction.satisfied() {
						attr.Status = AttributeProofStatusPresent
					} else {
						attr.Status = AttributeProofStatusInvalidValue
					}
					list[i] = attr
260
					delete(extraAttrs, attr.Identifier)
261 262
				}
			}
263 264 265
		}
	}

266
	// Any attributes still in here do not satisfy any of the specified disjunctions; append them now
267
	for _, attr := range extraAttrs {
268
		attr.Status = AttributeProofStatusExtra
269
		list = append(list, attr)
270 271
	}

272
	return len(disjunctions) == 0 || disjunctions.satisfied(), list, nil
273 274
}

275
func (d *Disclosure) VerifyAgainstDisjunctions(
276 277 278 279 280
	configuration *Configuration,
	required AttributeDisjunctionList,
	context, nonce *big.Int,
	publickeys []*gabi.PublicKey,
	issig bool,
281
) ([]*DisclosedAttribute, ProofStatus, error) {
282
	// Cryptographically verify the IRMA disclosure proofs in the signature
283
	valid, err := ProofList(d.Proofs).VerifyProofs(configuration, context, nonce, publickeys, issig)
284 285
	if !valid || err != nil {
		return nil, ProofStatusInvalid, err
286 287 288
	}

	// Next extract the contained attributes from the proofs, and match them to the signature request if present
289
	allmatched, list, err := d.DisclosedAttributes(configuration, required)
290
	if err != nil {
291
		return nil, ProofStatusInvalid, err
292 293 294 295
	}

	// Return MISSING_ATTRIBUTES as proofstatus if one of the disjunctions in the request (if present) is not satisfied
	if !allmatched {
296 297 298 299 300 301
		return list, ProofStatusMissingAttributes, nil
	}

	return list, ProofStatusValid, nil
}

302 303
func (d *Disclosure) Verify(configuration *Configuration, request *DisclosureRequest) ([]*DisclosedAttribute, ProofStatus, error) {
	list, status, err := d.VerifyAgainstDisjunctions(configuration, request.Content, request.Context, request.Nonce, nil, false)
304 305
	if err != nil {
		return list, status, err
306
	}
307

308
	now := time.Now()
309
	if expired := ProofList(d.Proofs).Expired(configuration, &now); expired {
310
		return list, ProofStatusExpired, nil
311
	}
312

313
	return list, status, nil
314 315
}

316 317 318 319 320 321 322 323 324
// 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.
325
func (sm *SignedMessage) Verify(configuration *Configuration, request *SignatureRequest) ([]*DisclosedAttribute, ProofStatus, error) {
326
	var message string
327

328
	// First check if this signature matches the request
329 330 331
	if request != nil {
		request.Timestamp = sm.Timestamp
		if !sm.MatchesNonceAndContext(request) {
332
			return nil, ProofStatusUnmatchedRequest, nil
333
		}
334 335 336 337 338
		// 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
339 340 341
	}

	// Verify the timestamp
342
	if sm.Timestamp != nil {
343
		if err := sm.VerifyTimestamp(message, configuration); err != nil {
344
			return nil, ProofStatusInvalidTimestamp, nil
345
		}
346 347
	}

348
	// Now, cryptographically verify the IRMA disclosure proofs in the signature
349
	var required AttributeDisjunctionList
350
	if request != nil {
351
		required = request.Content
352
	}
353
	result, status, err := sm.Disclosure().VerifyAgainstDisjunctions(configuration, required, sm.Context, sm.GetNonce(), nil, true)
354 355
	if status != ProofStatusValid || err != nil {
		return result, status, err
356 357
	}

358
	// Check if a credential is expired
359 360 361 362
	var t time.Time
	if sm.Timestamp != nil {
		t = time.Unix(sm.Timestamp.Time, 0)
	}
363
	if expired := ProofList(sm.Signature).Expired(configuration, &t); expired {
364 365
		// The ABS contains attributes that were expired at the time of creation of the ABS.
		return result, ProofStatusExpired, nil
366 367
	}

368
	// All disjunctions satisfied and nothing expired, proof is valid!
369
	return result, ProofStatusValid, nil
370
}