verify.go 13.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
	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
30
)
31

32 33
// DisclosedAttribute represents a disclosed attribute.
type DisclosedAttribute struct {
34 35 36 37 38
	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"`
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 78
	// Empty proof lists are allowed (if consistent with the session request, which is checked elsewhere)
	if len(pl) == 0 {
		return true, nil
	}

79 80 81 82
	if publickeys == nil {
		var err error
		publickeys, err = pl.ExtractPublicKeys(configuration)
		if err != nil {
83
			return false, err
84 85 86 87
		}
	}

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

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

102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
	if !gabi.ProofList(pl).Verify(publickeys, context, nonce, isSig, keyshareServers) {
		return false, nil
	}

	// Verify that any singleton credential occurs at most once in the prooflist
	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")
		}
		if typ.IsSingleton {
			if !singletons[typ.Identifier()] { // Seen for the first time
				singletons[typ.Identifier()] = true
			} else { // Seen for the second time
				return false, nil
			}
		}
	}

	return true, nil
127
}
128

129 130
// Expired returns true if any of the contained disclosure proofs is specified at the specified time,
// or now, when the specified time is nil.
131
func (pl ProofList) Expired(configuration *Configuration, t *time.Time) bool {
132 133 134 135 136 137 138
	if t == nil {
		temp := time.Now()
		t = &temp
	}
	for _, proof := range pl {
		proofd, ok := proof.(*gabi.ProofD)
		if !ok {
139
			continue
140 141 142
		}
		metadata := MetadataFromInt(proofd.ADisclosed[1], configuration) // index 1 is metadata attribute
		if metadata.Expiry().Before(*t) {
143
			return true
144 145
		}
	}
146
	return false
147 148
}

149 150 151 152 153 154 155 156 157
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")
158 159
	}

160 161 162
	metadata := MetadataFromInt(proofd.ADisclosed[1], conf) // index 1 is metadata attribute
	return parseAttribute(index.AttributeIndex, metadata, proofd.ADisclosed[index.AttributeIndex])
}
163

164 165 166 167
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)
168
		if !ok {
169
			continue
170
		}
171 172 173 174
		disclosed[i] = map[int]struct{}{}
		for j := range proofd.ADisclosed {
			if j <= 1 {
				continue
175
			}
176
			disclosed[i][j] = struct{}{}
177 178 179
		}
	}

180 181
	for i, set := range d.Indices {
		if len(condiscon) <= i {
182 183
			continue
		}
184 185 186 187
		for _, index := range set {
			delete(disclosed[index.CredentialIndex], index.AttributeIndex)
		}
	}
188

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
	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
216
		}
217 218 219 220 221
		attr.Status = AttributeProofStatusExtra
		extra = append(extra, attr)
	}
	if len(extra) > 0 {
		list = append(list, extra)
222
	}
223

224
	return complete, list, nil
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
}

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())
	}
242 243 244 245
	status := AttributeProofStatusPresent
	if attrval == nil {
		status = AttributeProofStatusNull
	}
246
	return &DisclosedAttribute{
247 248 249 250 251
		Identifier:   attrid,
		RawValue:     attrval,
		Value:        NewTranslatedString(attrval),
		Status:       status,
		IssuanceTime: Timestamp(metadata.SigningDate()),
252
	}, attrval, nil
253 254
}

255
func (d *Disclosure) VerifyAgainstDisjunctions(
256
	configuration *Configuration,
257
	required AttributeConDisCon,
258 259 260
	context, nonce *big.Int,
	publickeys []*gabi.PublicKey,
	issig bool,
261
) ([][]*DisclosedAttribute, ProofStatus, error) {
262
	// Cryptographically verify the IRMA disclosure proofs in the signature
263
	valid, err := ProofList(d.Proofs).VerifyProofs(configuration, context, nonce, publickeys, issig)
264 265
	if !valid || err != nil {
		return nil, ProofStatusInvalid, err
266 267 268
	}

	// Next extract the contained attributes from the proofs, and match them to the signature request if present
269
	allmatched, list, err := d.DisclosedAttributes(configuration, required)
270
	if err != nil {
271
		return nil, ProofStatusInvalid, err
272 273 274 275
	}

	// Return MISSING_ATTRIBUTES as proofstatus if one of the disjunctions in the request (if present) is not satisfied
	if !allmatched {
276 277 278 279 280 281
		return list, ProofStatusMissingAttributes, nil
	}

	return list, ProofStatusValid, nil
}

282
func (d *Disclosure) Verify(configuration *Configuration, request *DisclosureRequest) ([][]*DisclosedAttribute, ProofStatus, error) {
283
	list, status, err := d.VerifyAgainstDisjunctions(configuration, request.Disclose, request.GetContext(), request.GetNonce(nil), nil, false)
284 285
	if err != nil {
		return list, status, err
286
	}
287

288
	now := time.Now()
289
	if expired := ProofList(d.Proofs).Expired(configuration, &now); expired {
290
		return list, ProofStatusExpired, nil
291
	}
292

293
	return list, status, nil
294 295
}

296 297 298 299 300 301 302 303 304
// 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.
305
func (sm *SignedMessage) Verify(configuration *Configuration, request *SignatureRequest) ([][]*DisclosedAttribute, ProofStatus, error) {
306
	var message string
307

308 309 310 311
	if len(sm.Signature) == 0 {
		return nil, ProofStatusInvalid, nil
	}

312
	// First check if this signature matches the request
313 314
	if request != nil {
		if !sm.MatchesNonceAndContext(request) {
315
			return nil, ProofStatusUnmatchedRequest, nil
316
		}
317 318 319 320 321
		// 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
322 323
	}

324
	// Now, cryptographically verify the IRMA disclosure proofs in the signature
325
	var required AttributeConDisCon
326
	if request != nil {
327
		required = request.Disclose
328
	}
329
	result, status, err := sm.Disclosure().VerifyAgainstDisjunctions(configuration, required, sm.Context, sm.GetNonce(), nil, true)
330 331
	if status != ProofStatusValid || err != nil {
		return result, status, err
332 333
	}

334
	// Next, verify the timestamp
335
	t := time.Now()
336
	if sm.Timestamp != nil {
337 338 339
		if err := sm.VerifyTimestamp(message, configuration); err != nil {
			return nil, ProofStatusInvalidTimestamp, nil
		}
340
		t = time.Unix(sm.Timestamp.Time, 0)
341 342 343
	}

	// Check if a credential was expired at creation time, according to the timestamp
344
	if expired := ProofList(sm.Signature).Expired(configuration, &t); expired {
345
		return result, ProofStatusExpired, nil
346 347
	}

348
	// The attributes were valid, nonexpired, and the request was satisfied
349
	return result, ProofStatusValid, nil
350
}
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386

// 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,
387
			Value:      NewTranslatedString(&value),
388 389 390 391 392 393
			Status:     AttributeProofStatusPresent,
		}
	}

	return disclosedAttributes, nil
}