verify.go 15.5 KB
Newer Older
1 2 3
package irma

import (
4
	"crypto/rsa"
5
	"time"
6

7
	"github.com/dgrijalva/jwt-go"
8
	"github.com/go-errors/errors"
9 10
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
11 12 13 14 15
)

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

16 17 18
// Status is the proof status of a single attribute
type AttributeProofStatus string

19
const (
20
	ProofStatusValid             = ProofStatus("VALID")              // Proof is valid
21
	ProofStatusInvalid           = ProofStatus("INVALID")            // Proof is invalid
22 23 24
	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
25
	ProofStatusExpired           = ProofStatus("EXPIRED")            // Attributes were expired at proof creation time (now, or according to timestamp in case of abs)
26

27 28 29 30 31
	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
)
32

33 34
// DisclosedAttribute represents a disclosed attribute.
type DisclosedAttribute struct {
35
	RawValue   *string                 `json:"rawvalue"`
36 37 38
	Value      TranslatedString        `json:"value"` // Value of the disclosed attribute
	Identifier AttributeTypeIdentifier `json:"id"`
	Status     AttributeProofStatus    `json:"status"`
39 40
}

41 42
// ProofList is a gabi.ProofList with some extra methods.
type ProofList gabi.ProofList
43

44 45
var ErrorMissingPublicKey = errors.New("Missing public key")

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

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

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

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

86 87 88
	// 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 {
89 90
		schemeID := NewIssuerIdentifier(publickeys[i].Issuer).SchemeManagerIdentifier()
		if !configuration.SchemeManagers[schemeID].Distributed() {
91
			keyshareServers[i] = "." // dummy value: no IRMA scheme will ever have this name
92
		} else {
93
			keyshareServers[i] = schemeID.Name()
94 95 96
		}
	}

97
	return gabi.ProofList(pl).Verify(publickeys, context, nonce, isSig, keyshareServers), nil
98
}
99

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

120 121 122 123 124
// 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.
125
func (d *Disclosure) DisclosedAttributes(configuration *Configuration, disjunctions AttributeDisjunctionList) (bool, []*DisclosedAttribute, error) {
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
	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
145
		attr, attrval, err := parseAttribute(index.AttributeIndex, metadata, proofd.ADisclosed[index.AttributeIndex])
146 147 148 149 150 151 152 153 154 155 156
		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
			}
157 158 159 160
			if usedAttrs[index.CredentialIndex] == nil {
				usedAttrs[index.CredentialIndex] = map[int]struct{}{}
			}
			usedAttrs[index.CredentialIndex][index.AttributeIndex] = struct{}{}
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 187 188
		} 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)
		}
	}
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,
210
		RawValue:   attrval,
211
		Value:      NewTranslatedString(attrval),
212
	}, attrval, nil
213 214 215
}

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

226 227 228
	// 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
229
	extraAttrs := map[AttributeTypeIdentifier]*DisclosedAttribute{}
230

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

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

243 244 245
			attr, attrval, err := parseAttribute(attrIndex, metadata, attrInt)
			if err != nil {
				return false, nil, err
246
			}
247

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

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

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

276
	return len(disjunctions) == 0 || disjunctions.satisfied(), list, nil
277 278
}

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

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

	// Return MISSING_ATTRIBUTES as proofstatus if one of the disjunctions in the request (if present) is not satisfied
	if !allmatched {
300 301 302 303 304 305
		return list, ProofStatusMissingAttributes, nil
	}

	return list, ProofStatusValid, nil
}

306 307
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)
308 309
	if err != nil {
		return list, status, err
310
	}
311

312
	now := time.Now()
313
	if expired := ProofList(d.Proofs).Expired(configuration, &now); expired {
314
		return list, ProofStatusExpired, nil
315
	}
316

317
	return list, status, nil
318 319
}

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

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

	// Verify the timestamp
346
	if sm.Timestamp != nil {
347
		if err := sm.VerifyTimestamp(message, configuration); err != nil {
348
			return nil, ProofStatusInvalidTimestamp, nil
349
		}
350 351
	}

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

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

372
	// All disjunctions satisfied and nothing expired, proof is valid!
373
	return result, ProofStatusValid, nil
374
}
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410

// ExpiredError indicates that something (e.g. a JWT) has expired.
type ExpiredError struct {
	Err error // underlying error
}

func (e ExpiredError) Error() string {
	return "irmago: expired (" + e.Err.Error() + ")"
}

// ParseApiServerJwt verifies and parses a JWT as returned by an irma_api_server after a disclosure request into a key-value pair.
func ParseApiServerJwt(inputJwt string, signingKey *rsa.PublicKey) (map[AttributeTypeIdentifier]*DisclosedAttribute, error) {
	claims := struct {
		jwt.StandardClaims
		Attributes map[AttributeTypeIdentifier]string `json:"attributes"`
	}{}
	_, err := jwt.ParseWithClaims(inputJwt, claims, func(token *jwt.Token) (interface{}, error) {
		return signingKey, nil
	})
	if err != nil {
		if err, ok := err.(*jwt.ValidationError); ok && (err.Errors&jwt.ValidationErrorExpired) != 0 {
			return nil, ExpiredError{err}
		} else {
			return nil, err
		}
	}

	if claims.Subject != "disclosure_result" {
		return nil, errors.New("JWT is not a disclosure result")
	}

	disclosedAttributes := make(map[AttributeTypeIdentifier]*DisclosedAttribute, len(claims.Attributes))
	for id, value := range claims.Attributes {
		disclosedAttributes[id] = &DisclosedAttribute{
			Identifier: id,
			RawValue:   &value,
411
			Value:      NewTranslatedString(&value),
412 413 414 415 416 417
			Status:     AttributeProofStatusPresent,
		}
	}

	return disclosedAttributes, nil
}