verify.go 12.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 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 125 126 127 128
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")
129 130
	}

131 132 133
	metadata := MetadataFromInt(proofd.ADisclosed[1], conf) // index 1 is metadata attribute
	return parseAttribute(index.AttributeIndex, metadata, proofd.ADisclosed[index.AttributeIndex])
}
134

135 136 137 138
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)
139
		if !ok {
140
			continue
141
		}
142 143 144 145
		disclosed[i] = map[int]struct{}{}
		for j := range proofd.ADisclosed {
			if j <= 1 {
				continue
146
			}
147
			disclosed[i][j] = struct{}{}
148 149 150
		}
	}

151 152
	for i, set := range d.Indices {
		if len(condiscon) <= i {
153 154
			continue
		}
155 156 157 158
		for _, index := range set {
			delete(disclosed[index.CredentialIndex], index.AttributeIndex)
		}
	}
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
	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
187
		}
188 189 190 191 192
		attr.Status = AttributeProofStatusExtra
		extra = append(extra, attr)
	}
	if len(extra) > 0 {
		list = append(list, extra)
193
	}
194

195
	return complete, list, nil
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
}

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,
215
		RawValue:   attrval,
216
		Value:      NewTranslatedString(attrval),
217
		Status:     AttributeProofStatusPresent,
218
	}, attrval, nil
219 220
}

221
func (d *Disclosure) VerifyAgainstDisjunctions(
222
	configuration *Configuration,
223
	required AttributeConDisCon,
224 225 226
	context, nonce *big.Int,
	publickeys []*gabi.PublicKey,
	issig bool,
227
) ([][]*DisclosedAttribute, ProofStatus, error) {
228
	// Cryptographically verify the IRMA disclosure proofs in the signature
229
	valid, err := ProofList(d.Proofs).VerifyProofs(configuration, context, nonce, publickeys, issig)
230 231
	if !valid || err != nil {
		return nil, ProofStatusInvalid, err
232 233 234
	}

	// Next extract the contained attributes from the proofs, and match them to the signature request if present
235
	allmatched, list, err := d.DisclosedAttributes(configuration, required)
236
	if err != nil {
237
		return nil, ProofStatusInvalid, err
238 239 240 241
	}

	// Return MISSING_ATTRIBUTES as proofstatus if one of the disjunctions in the request (if present) is not satisfied
	if !allmatched {
242 243 244 245 246 247
		return list, ProofStatusMissingAttributes, nil
	}

	return list, ProofStatusValid, nil
}

248
func (d *Disclosure) Verify(configuration *Configuration, request *DisclosureRequest) ([][]*DisclosedAttribute, ProofStatus, error) {
249
	list, status, err := d.VerifyAgainstDisjunctions(configuration, request.Disclose, request.GetContext(), request.GetNonce(nil), nil, false)
250 251
	if err != nil {
		return list, status, err
252
	}
253

254
	now := time.Now()
255
	if expired := ProofList(d.Proofs).Expired(configuration, &now); expired {
256
		return list, ProofStatusExpired, nil
257
	}
258

259
	return list, status, nil
260 261
}

262 263 264 265 266 267 268 269 270
// 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.
271
func (sm *SignedMessage) Verify(configuration *Configuration, request *SignatureRequest) ([][]*DisclosedAttribute, ProofStatus, error) {
272
	var message string
273

274
	// First check if this signature matches the request
275 276
	if request != nil {
		if !sm.MatchesNonceAndContext(request) {
277
			return nil, ProofStatusUnmatchedRequest, nil
278
		}
279 280 281 282 283
		// 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
284 285 286
	}

	// Verify the timestamp
287 288
	if err := sm.VerifyTimestamp(message, configuration); err != nil {
		return nil, ProofStatusInvalidTimestamp, nil
289 290
	}

291
	// Now, cryptographically verify the IRMA disclosure proofs in the signature
292
	var required AttributeConDisCon
293
	if request != nil {
294
		required = request.Disclose
295
	}
296
	result, status, err := sm.Disclosure().VerifyAgainstDisjunctions(configuration, required, sm.Context, sm.GetNonce(), nil, true)
297 298
	if status != ProofStatusValid || err != nil {
		return result, status, err
299 300
	}

301
	// Check if a credential is expired
302
	t := time.Unix(sm.Timestamp.Time, 0)
303
	if expired := ProofList(sm.Signature).Expired(configuration, &t); expired {
304 305
		// The ABS contains attributes that were expired at the time of creation of the ABS.
		return result, ProofStatusExpired, nil
306 307
	}

308
	// All disjunctions satisfied and nothing expired, proof is valid!
309
	return result, ProofStatusValid, nil
310
}
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346

// 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,
347
			Value:      NewTranslatedString(&value),
348 349 350 351 352 353
			Status:     AttributeProofStatusPresent,
		}
	}

	return disclosedAttributes, nil
}