verify.go 11.6 KB
Newer Older
1 2 3 4 5 6
package irma

import (
	"fmt"
	"math/big"
	"time"
7 8 9

	"github.com/go-errors/errors"
	"github.com/mhe/gabi"
10 11 12 13 14 15 16 17
)

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

const (
	VALID              = ProofStatus("VALID")
	INVALID_CRYPTO     = ProofStatus("INVALID_CRYPTO")
18
	INVALID_TIMESTAMP  = ProofStatus("INVALID_TIMESTAMP")
19
	UNMATCHED_REQUEST  = ProofStatus("UNMATCHED_REQUEST")
20
	MISSING_ATTRIBUTES = ProofStatus("MISSING_ATTRIBUTES")
21 22 23 24

	// The contained attributes are currently expired, but it is not certain if they already were expired
	// during creation of the ABS.
	EXPIRED = ProofStatus("EXPIRED")
25 26 27 28
)

// ProofResult is a result of a complete proof, containing all the disclosed attributes and corresponding request
type ProofResult struct {
29
	Disjunctions []*DisclosedAttributeDisjunction `json:"disjunctions"`
30 31 32 33
	ProofStatus  ProofStatus
}

type SignatureProofResult struct {
34
	*ProofResult
35
	Message string `json:"message"`
36 37 38 39 40
}

// DisclosedCredential contains raw disclosed credentials, without any extra parsing information
type DisclosedCredential struct {
	metadataAttribute *MetadataAttribute
41
	Attributes        map[AttributeTypeIdentifier]*string `json:"attributes"`
42 43
}

44 45 46 47 48 49
type DisclosedCredentialList []*DisclosedCredential

// Helper function to check if an attribute is satisfied against a list of disclosed attributes
// This is the case if:
// attribute is contained in disclosed AND if a value is present: equal to that value
// al can be nil if you don't want to include attribute status for proof
50
func (disclosed DisclosedCredentialList) isAttributeSatisfied(attributeId AttributeTypeIdentifier, requestedValue *string) (bool, *AttributeResult) {
51 52 53 54 55
	ar := AttributeResult{
		AttributeId: attributeId,
	}

	for _, cred := range disclosed {
56
		disclosedAttributeValue := cred.Attributes[attributeId]
57 58

		// Continue to next credential if requested attribute isn't disclosed in this credential
59
		if disclosedAttributeValue == nil {
60 61 62 63 64 65 66
			continue
		}

		// If this is the disclosed attribute, check if value matches
		// Attribute is satisfied if:
		// - Attribute is disclosed (i.e. not nil)
		// - Value is empty OR value equal to disclosedValue
67
		ar.AttributeValue = *disclosedAttributeValue
68

69
		if requestedValue == nil || *disclosedAttributeValue == *requestedValue {
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
			ar.AttributeProofStatus = PRESENT
			return true, &ar
		} else {
			// If attribute is disclosed and present, but not equal to required value, mark it as invalid_value
			// We won't return true and continue searching in other disclosed attributes
			ar.AttributeProofStatus = INVALID_VALUE
		}
	}

	// If there is never a value assigned, then this attribute isn't disclosed, and thus missing
	if ar.AttributeValue == "" {
		ar.AttributeProofStatus = MISSING
	}
	return false, &ar
}

// Create a signature proof result and check disclosed credentials against a signature request
func (disclosed DisclosedCredentialList) createAndCheckSignatureProofResult(configuration *Configuration, sigRequest *SignatureRequest) *SignatureProofResult {
	signatureProofResult := SignatureProofResult{
		ProofResult: &ProofResult{},
90
		Message:     sigRequest.Message,
91 92 93
	}
	for _, content := range sigRequest.Content {
		isSatisfied, disjunction := content.SatisfyDisclosed(disclosed, configuration)
94
		signatureProofResult.Disjunctions = append(signatureProofResult.Disjunctions, disjunction)
95 96 97 98 99 100 101 102 103 104 105

		// If satisfied, continue to next one
		if isSatisfied {
			continue
		}

		// Else, set proof status to missing_attributes, but check other as well to add other disjunctions to result
		// (so user also knows attribute status of other disjunctions)
		signatureProofResult.ProofStatus = MISSING_ATTRIBUTES
	}

106
	signatureProofResult.Disjunctions = addExtraAttributes(disclosed, signatureProofResult.ProofResult)
107 108 109
	return &signatureProofResult
}

110 111
// Returns true if one of the disclosed credentials is expired at the specified time
func (disclosed DisclosedCredentialList) IsExpired(t time.Time) bool {
112
	for _, cred := range disclosed {
113
		if cred.IsExpired(t) {
114 115 116 117 118 119
			return true
		}
	}
	return false
}

120
func (proofResult *ProofResult) ToAttributeResultList() AttributeResultList {
121 122
	var resultList AttributeResultList

123
	for _, v := range proofResult.Disjunctions {
124 125 126 127 128 129
		result := AttributeResult{
			AttributeValue:       v.DisclosedValue,
			AttributeId:          v.DisclosedId,
			AttributeProofStatus: v.ProofStatus,
		}

130
		resultList = append(resultList, &result)
131
	}
132
	return resultList
133 134 135 136
}

// Returns true if this attrId is present in one of the disjunctions
func (proofResult *ProofResult) ContainsAttribute(attrId AttributeTypeIdentifier) bool {
137
	for _, disj := range proofResult.Disjunctions {
138 139 140 141 142 143 144 145 146 147
		for _, attr := range disj.Attributes {
			if attr == attrId {
				return true
			}
		}
	}

	return false
}

148 149
func (cred *DisclosedCredential) IsExpired(t time.Time) bool {
	return cred.metadataAttribute.Expiry().Before(t)
150 151 152
}

func NewDisclosedCredentialFromADisclosed(aDisclosed map[int]*big.Int, configuration *Configuration) *DisclosedCredential {
153
	attributes := make(map[AttributeTypeIdentifier]*string)
154 155 156 157 158 159 160 161 162 163

	metadata := MetadataFromInt(aDisclosed[1], configuration) // index 1 is metadata attribute
	cred := metadata.CredentialType()

	for k, v := range aDisclosed {
		if k < 2 {
			continue
		}

		description := cred.Attributes[k-2]
164 165
		attributeValue := decodeAttribute(v, metadata.Version())
		attributes[description.GetAttributeTypeIdentifier(cred.Identifier())] = attributeValue
166 167 168 169 170 171 172 173
	}

	return &DisclosedCredential{
		metadataAttribute: metadata,
		Attributes:        attributes,
	}
}

174 175
func extractPublicKeys(configuration *Configuration, proofList gabi.ProofList) ([]*gabi.PublicKey, error) {
	var publicKeys = make([]*gabi.PublicKey, 0, len(proofList))
176

177
	for _, v := range proofList {
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
		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
			}
			publicKeys = append(publicKeys, publicKey)
		default:
			return nil, errors.New("Cannot extract public key, not a disclosure proofD!")
		}
	}
	return publicKeys, nil
}

194 195
func extractDisclosedCredentials(conf *Configuration, proofList gabi.ProofList) (DisclosedCredentialList, error) {
	var credentials = make(DisclosedCredentialList, 0, len(proofList))
196

197
	for _, v := range proofList {
198 199 200 201 202 203 204 205 206 207 208 209 210 211
		switch v.(type) {
		case *gabi.ProofD:
			proof := v.(*gabi.ProofD)
			cred := NewDisclosedCredentialFromADisclosed(proof.ADisclosed, conf)
			credentials = append(credentials, cred)
		default:
			return nil, errors.New("Cannot extract credentials from proof, not a disclosure proofD!")
		}
	}

	return credentials, nil
}

// Add extra disclosed attributes to an existing and checked ProofResult in 'dummy disjunctions'
212
func addExtraAttributes(disclosed DisclosedCredentialList, proofResult *ProofResult) []*DisclosedAttributeDisjunction {
213 214
	returnDisjunctions := make([]*DisclosedAttributeDisjunction, len(proofResult.Disjunctions))
	copy(returnDisjunctions, proofResult.Disjunctions)
215

216
	for _, cred := range disclosed {
217 218 219 220 221 222
		for attrId := range cred.Attributes {
			if proofResult.ContainsAttribute(attrId) {
				continue
			}

			dummyDisj := DisclosedAttributeDisjunction{
223
				DisclosedValue: *cred.Attributes[attrId],
224 225 226 227 228 229 230 231 232 233 234
				DisclosedId:    attrId,
				ProofStatus:    EXTRA,
			}
			returnDisjunctions = append(returnDisjunctions, &dummyDisj)
		}
	}

	return returnDisjunctions
}

// Check an gabi prooflist against a signature proofrequest
235
func checkProofWithRequest(configuration *Configuration, irmaSignature *IrmaSignedMessage, sigRequest *SignatureRequest) *SignatureProofResult {
236
	disclosed, err := extractDisclosedCredentials(configuration, irmaSignature.Signature)
237 238 239 240

	if err != nil {
		fmt.Println(err)
		return &SignatureProofResult{
241
			ProofResult: &ProofResult{
242 243 244 245 246
				ProofStatus: INVALID_CRYPTO,
			},
		}
	}

247
	signatureProofResult := disclosed.createAndCheckSignatureProofResult(configuration, sigRequest)
248 249 250 251 252 253 254 255

	// Return MISSING_ATTRIBUTES as proofstatus if one attribute is missing
	// This status takes priority over 'EXPIRED'
	if signatureProofResult.ProofStatus == MISSING_ATTRIBUTES {
		return signatureProofResult
	}

	// If all disjunctions are satisfied, check if a credential is expired
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
	if irmaSignature.Timestamp == nil {
		if disclosed.IsExpired(time.Now()) {
			// At least one of the contained attributes has currently expired. We don't know the
			// creation time of the ABS so we can't ascertain that the attributes were still valid then.
			// Otherwise the signature is valid.
			signatureProofResult.ProofStatus = EXPIRED
			return signatureProofResult
		}
	} else {
		if disclosed.IsExpired(time.Unix(irmaSignature.Timestamp.Time, 0)) {
			// The ABS contains attributes that were expired at the time of creation of the ABS.
			// This must not happen and in this case the signature is invalid
			signatureProofResult.ProofStatus = INVALID_CRYPTO
			return signatureProofResult
		}
271 272 273 274 275 276 277 278
	}

	// All disjunctions satisfied and nothing expired, proof is valid!
	signatureProofResult.ProofStatus = VALID
	return signatureProofResult
}

// Verify an IRMA proof cryptographically
279
func verify(configuration *Configuration, proofList gabi.ProofList, context *big.Int, nonce *big.Int, isSig bool) bool {
280 281 282 283 284 285 286 287 288 289
	// Extract public keys
	pks, err := extractPublicKeys(configuration, proofList)
	if err != nil {
		return false
	}

	return proofList.Verify(pks, context, nonce, true, isSig)
}

// Verify a signature proof and check if the attributes match the attributes in the original request
290
func VerifySig(configuration *Configuration, irmaSignature *IrmaSignedMessage, sigRequest *SignatureRequest) *SignatureProofResult {
291 292 293 294 295 296 297 298 299 300 301 302 303
	// First, verify the timestamp, if any
	if irmaSignature.Timestamp != nil {
		if err := VerifyTimestamp(irmaSignature, sigRequest.Message, configuration); err != nil {
			return &SignatureProofResult{
				ProofResult: &ProofResult{
					ProofStatus: INVALID_TIMESTAMP,
				},
			}
		}
		sigRequest.Timestamp = irmaSignature.Timestamp
	}

	// Then, check if nonce and context of the signature match those of the signature request
304
	if !irmaSignature.MatchesNonceAndContext(sigRequest) {
305
		return &SignatureProofResult{
306
			ProofResult: &ProofResult{
307
				ProofStatus: UNMATCHED_REQUEST,
308 309 310 311 312
			},
		}
	}

	// Now, cryptographically verify the signature
313
	if !verify(configuration, irmaSignature.Signature, sigRequest.GetContext(), sigRequest.GetNonce(), true) {
314
		return &SignatureProofResult{
315
			ProofResult: &ProofResult{
316 317 318 319 320 321
				ProofStatus: INVALID_CRYPTO,
			},
		}
	}

	// Finally, check whether attribute values in proof satisfy the original signature request
322
	return checkProofWithRequest(configuration, irmaSignature, sigRequest)
323 324 325 326
}

// Verify a signature cryptographically, but do not check/compare with a signature request
func VerifySigWithoutRequest(configuration *Configuration, irmaSignature *IrmaSignedMessage) (ProofStatus, DisclosedCredentialList) {
327 328 329 330 331 332 333 334
	// First, verify the timestamp, if any
	if irmaSignature.Timestamp != nil {
		if err := VerifyTimestamp(irmaSignature, irmaSignature.Message, configuration); err != nil {
			return INVALID_TIMESTAMP, nil
		}
	}

	// Cryptographically verify the signature
335
	if !verify(configuration, irmaSignature.Signature, irmaSignature.Context, irmaSignature.GetNonce(), true) {
336 337 338 339
		return INVALID_CRYPTO, nil
	}

	// Extract attributes and return result
340
	disclosed, err := extractDisclosedCredentials(configuration, irmaSignature.Signature)
341 342 343 344 345 346

	if err != nil {
		fmt.Println(err)
		return INVALID_CRYPTO, nil
	}

347 348 349 350 351 352 353 354
	if irmaSignature.Timestamp == nil {
		if disclosed.IsExpired(time.Now()) {
			return EXPIRED, disclosed
		}
	} else {
		if disclosed.IsExpired(time.Unix(irmaSignature.Timestamp.Time, 0)) {
			return INVALID_CRYPTO, nil
		}
355 356 357
	}

	return VALID, disclosed
358
}