verify.go 14.6 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
	"github.com/privacybydesign/gabi/revocation"
12 13 14 15 16
)

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

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

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

28 29 30
	AttributeProofStatusPresent = AttributeProofStatus("PRESENT") // Attribute is disclosed and matches the value
	AttributeProofStatusExtra   = AttributeProofStatus("EXTRA")   // Attribute is disclosed, but wasn't requested in request
	AttributeProofStatusNull    = AttributeProofStatus("NULL")    // Attribute is disclosed but is null
31
)
32

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

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

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

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

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

73
// VerifyProofs verifies the proofs cryptographically.
74 75 76 77
func (pl ProofList) VerifyProofs(
	configuration *Configuration,
	context *big.Int, nonce *big.Int,
	publickeys []*gabi.PublicKey,
78
	revRecords map[CredentialTypeIdentifier][]*revocation.Record,
79 80
	isSig bool,
) (bool, error) {
81 82 83 84 85
	// Empty proof lists are allowed (if consistent with the session request, which is checked elsewhere)
	if len(pl) == 0 {
		return true, nil
	}

86 87 88 89
	if publickeys == nil {
		var err error
		publickeys, err = pl.ExtractPublicKeys(configuration)
		if err != nil {
90
			return false, err
91 92 93 94
		}
	}

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

98 99 100
	// 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 {
101 102
		schemeID := NewIssuerIdentifier(publickeys[i].Issuer).SchemeManagerIdentifier()
		if !configuration.SchemeManagers[schemeID].Distributed() {
103
			keyshareServers[i] = "." // dummy value: no IRMA scheme will ever have this name
104
		} else {
105
			keyshareServers[i] = schemeID.Name()
106 107 108
		}
	}

109 110 111 112
	if !gabi.ProofList(pl).Verify(publickeys, context, nonce, isSig, keyshareServers) {
		return false, nil
	}

113 114 115
	// Perform per-proof verifications for each proof:
	// - verify that any singleton credential occurs at most once in the prooflist
	// - verify that all required nonrevocation proofs are present
116 117 118 119 120 121 122 123 124 125
	singletons := map[CredentialTypeIdentifier]bool{}
	for _, proof := range pl {
		proofd, ok := proof.(*gabi.ProofD)
		if !ok {
			continue
		}
		typ := MetadataFromInt(proofd.ADisclosed[1], configuration).CredentialType()
		if typ == nil {
			return false, errors.New("Received unknown credential type")
		}
126
		id := typ.Identifier()
127
		if typ.IsSingleton {
128 129
			if !singletons[id] { // Seen for the first time
				singletons[id] = true
130 131 132 133
			} else { // Seen for the second time
				return false, nil
			}
		}
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156

		// The cryptographic validity of all included nonrevocation proofs has already been checked
		// by ProofList.Verify() above, so all that remains here is to check if all expected
		// nonrevocation proofs are present, and against the expected accumulator value:
		// the last one in the update message set we provided along with the session request,
		// OR the last (newer) one that the client included in its reply (TODO).
		r := revRecords[id]
		if len(r) == 0 { // no nonrevocation proof was requested for this credential
			return true, nil
		}
		if !proofd.HasNonRevocationProof() {
			return false, nil
		}

		// grab last message from accumulator update message set in request
		keystore := configuration.RevocationKeystore(typ.Identifier().IssuerIdentifier())
		msg, err := r[len(r)-1].UnmarshalVerify(keystore)
		if err != nil {
			return false, err
		}
		if msg.Accumulator.Nu.Cmp(proofd.NonRevocationProof.Nu) != 0 {
			return false, errors.New("nonrevocation proof used wrong accumulator")
		}
157 158 159
	}

	return true, nil
160
}
161

162 163
// Expired returns true if any of the contained disclosure proofs is specified at the specified time,
// or now, when the specified time is nil.
164
func (pl ProofList) Expired(configuration *Configuration, t *time.Time) bool {
165 166 167 168 169 170 171
	if t == nil {
		temp := time.Now()
		t = &temp
	}
	for _, proof := range pl {
		proofd, ok := proof.(*gabi.ProofD)
		if !ok {
172
			continue
173 174 175
		}
		metadata := MetadataFromInt(proofd.ADisclosed[1], configuration) // index 1 is metadata attribute
		if metadata.Expiry().Before(*t) {
176
			return true
177 178
		}
	}
179
	return false
180 181
}

182 183 184 185 186 187 188 189 190
func extractAttribute(pl gabi.ProofList, index *DisclosedAttributeIndex, conf *Configuration) (*DisclosedAttribute, *string, error) {
	if len(pl) < index.CredentialIndex {
		return nil, nil, errors.New("Credential index out of range")
	}
	proofd, ok := pl[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 nil, nil, errors.New("ProofList contained proof of invalid type")
191 192
	}

193 194 195
	metadata := MetadataFromInt(proofd.ADisclosed[1], conf) // index 1 is metadata attribute
	return parseAttribute(index.AttributeIndex, metadata, proofd.ADisclosed[index.AttributeIndex])
}
196

197 198 199 200
func (d *Disclosure) extraIndices(condiscon AttributeConDisCon) []*DisclosedAttributeIndex {
	disclosed := make([]map[int]struct{}, len(d.Proofs))
	for i, proof := range d.Proofs {
		proofd, ok := proof.(*gabi.ProofD)
201
		if !ok {
202
			continue
203
		}
204 205 206 207
		disclosed[i] = map[int]struct{}{}
		for j := range proofd.ADisclosed {
			if j <= 1 {
				continue
208
			}
209
			disclosed[i][j] = struct{}{}
210 211 212
		}
	}

213 214
	for i, set := range d.Indices {
		if len(condiscon) <= i {
215 216
			continue
		}
217 218 219 220
		for _, index := range set {
			delete(disclosed[index.CredentialIndex], index.AttributeIndex)
		}
	}
221

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
	var extra []*DisclosedAttributeIndex
	for i, attrs := range disclosed {
		for j := range attrs {
			extra = append(extra, &DisclosedAttributeIndex{CredentialIndex: i, AttributeIndex: j})
		}
	}

	return extra
}

// DisclosedAttributes returns a slice containing for each item in the conjunction 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. The first return parameter of this function indicates whether or not all
// disjunctions (if present) are satisfied.
func (d *Disclosure) DisclosedAttributes(configuration *Configuration, condiscon AttributeConDisCon) (bool, [][]*DisclosedAttribute, error) {
	complete, list, err := condiscon.Satisfy(d, configuration)
	if err != nil {
		return false, nil, err
	}

	var extra []*DisclosedAttribute
	indices := d.extraIndices(condiscon)
	for _, index := range indices {
		attr, _, err := extractAttribute(d.Proofs, index, configuration)
		if err != nil {
			return false, nil, err
249
		}
250 251 252 253 254
		attr.Status = AttributeProofStatusExtra
		extra = append(extra, attr)
	}
	if len(extra) > 0 {
		list = append(list, extra)
255
	}
256

257
	return complete, list, nil
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
}

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())
	}
275 276 277 278
	status := AttributeProofStatusPresent
	if attrval == nil {
		status = AttributeProofStatusNull
	}
279
	return &DisclosedAttribute{
280 281 282 283 284
		Identifier:   attrid,
		RawValue:     attrval,
		Value:        NewTranslatedString(attrval),
		Status:       status,
		IssuanceTime: Timestamp(metadata.SigningDate()),
285
	}, attrval, nil
286 287
}

288
func (d *Disclosure) VerifyAgainstRequest(
289
	configuration *Configuration,
290
	request SessionRequest,
291 292
	context, nonce *big.Int,
	publickeys []*gabi.PublicKey,
293
	validAt *time.Time,
294
	issig bool,
295
) ([][]*DisclosedAttribute, ProofStatus, error) {
296
	var required AttributeConDisCon
297
	var revRecords map[CredentialTypeIdentifier][]*revocation.Record
298 299 300 301 302 303 304
	if request != nil {
		revRecords = request.Base().RevocationUpdates
		required = request.Disclosure().Disclose
	}

	// Cryptographically verify all included IRMA proofs
	valid, err := ProofList(d.Proofs).VerifyProofs(configuration, context, nonce, publickeys, revRecords, issig)
305 306
	if !valid || err != nil {
		return nil, ProofStatusInvalid, err
307 308 309
	}

	// Next extract the contained attributes from the proofs, and match them to the signature request if present
310
	allmatched, list, err := d.DisclosedAttributes(configuration, required)
311
	if err != nil {
312
		return nil, ProofStatusInvalid, err
313 314 315 316
	}

	// Return MISSING_ATTRIBUTES as proofstatus if one of the disjunctions in the request (if present) is not satisfied
	if !allmatched {
317 318 319
		return list, ProofStatusMissingAttributes, nil
	}

320 321 322 323 324
	// Check that all credentials were unexpired
	if expired := ProofList(d.Proofs).Expired(configuration, validAt); expired {
		return list, ProofStatusExpired, nil
	}

325 326 327
	return list, ProofStatusValid, nil
}

328
func (d *Disclosure) Verify(configuration *Configuration, request *DisclosureRequest) ([][]*DisclosedAttribute, ProofStatus, error) {
329
	return d.VerifyAgainstRequest(configuration, request, request.GetContext(), request.GetNonce(nil), nil, nil, false)
330 331
}

332 333 334 335 336 337 338 339 340
// 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.
341
func (sm *SignedMessage) Verify(configuration *Configuration, request *SignatureRequest) ([][]*DisclosedAttribute, ProofStatus, error) {
342
	var message string
343

344 345 346 347
	if len(sm.Signature) == 0 {
		return nil, ProofStatusInvalid, nil
	}

348
	// First check if this signature matches the request
349 350
	if request != nil {
		if !sm.MatchesNonceAndContext(request) {
351
			return nil, ProofStatusUnmatchedRequest, nil
352
		}
353 354 355 356 357
		// 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
358 359
	}

360
	// Next, verify the timestamp so we can safely use its time
361
	t := time.Now()
362
	if sm.Timestamp != nil {
363 364 365
		if err := sm.VerifyTimestamp(message, configuration); err != nil {
			return nil, ProofStatusInvalidTimestamp, nil
		}
366
		t = time.Unix(sm.Timestamp.Time, 0)
367 368
	}

369 370 371 372 373
	// Finally, cryptographically verify the IRMA disclosure proofs in the signature
	// and verify that it satisfies the signature request, if present
	var r SessionRequest // wrapper for request to avoid avoid https://golang.org/doc/faq#nil_error
	if request != nil {
		r = request
374
	}
375
	return sm.Disclosure().VerifyAgainstRequest(configuration, r, sm.Context, sm.GetNonce(), nil, &t, true)
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 411 412

// 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,
413
			Value:      NewTranslatedString(&value),
414 415 416 417 418 419
			Status:     AttributeProofStatusPresent,
		}
	}

	return disclosedAttributes, nil
}