verify.go 14 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 142 143 144 145 146
	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")
		}

		if usedAttrs[index.CredentialIndex] == nil {
			usedAttrs[index.CredentialIndex] = map[int]struct{}{}
		}
		usedAttrs[index.CredentialIndex][index.AttributeIndex] = struct{}{}

		metadata := MetadataFromInt(proofd.ADisclosed[1], configuration) // index 1 is metadata attribute
147
		attr, attrval, err := parseAttribute(index.AttributeIndex, metadata, proofd.ADisclosed[index.AttributeIndex])
148 149 150 151 152 153 154 155 156 157 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 186
		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
			}
		} 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)
		}
	}
187

188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
	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
210 211 212
}

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

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

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

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

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

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

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

267
	// Any attributes still in here do not satisfy any of the specified disjunctions; append them now
268 269
	for _, attr := range extraAttrs {
		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
}