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

import (
	"math/big"
	"time"
6 7 8

	"github.com/go-errors/errors"
	"github.com/mhe/gabi"
9 10 11 12 13
)

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

14 15 16
// Status is the proof status of a single attribute
type AttributeProofStatus string

17
const (
18 19 20 21 22
	ProofStatusValid             = ProofStatus("VALID")              // Proof is valid
	ProofStatusInvalidCrypto     = ProofStatus("INVALID_CRYPTO")     // Proof is invalid
	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
23 24

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

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

34 35 36 37
// VerificationResult is a result of verification of a SignedMessage or disclosure proof, containing all the disclosed attributes
type VerificationResult struct {
	Attributes []*DisclosedAttribute
	Status     ProofStatus
38 39
}

40 41 42 43 44
// DisclosedAttribute represents a disclosed attribute.
type DisclosedAttribute struct {
	Value      TranslatedString        `json:"value"` // Value of the disclosed attribute
	Identifier AttributeTypeIdentifier `json:"id"`
	Status     AttributeProofStatus    `json:"status"`
45 46
}

47 48
// ProofList is a gabi.ProofList with some extra methods.
type ProofList gabi.ProofList
49

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

56
	for _, v := range pl {
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
		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
}

73 74
// VerifyProofs verifies the proofs cryptographically.
func (pl ProofList) VerifyProofs(configuration *Configuration, context *big.Int, nonce *big.Int, isSig bool) bool {
75
	// Extract public keys
76
	pks, err := pl.extractPublicKeys(configuration)
77 78
	if err != nil {
		return false
79
	}
80 81
	return gabi.ProofList(pl).Verify(pks, context, nonce, true, isSig)
}
82

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
// Expired returns true if any of the contained disclosure proofs is specified at the specified time,
// or now, when the specified time is nil.
func (pl ProofList) Expired(configuration *Configuration, t *time.Time) (bool, error) {
	if t == nil {
		temp := time.Now()
		t = &temp
	}
	for _, proof := range pl {
		proofd, ok := proof.(*gabi.ProofD)
		if !ok {
			return false, errors.New("ProofList contained a proof that was not a disclosure proof")
		}
		metadata := MetadataFromInt(proofd.ADisclosed[1], configuration) // index 1 is metadata attribute
		if metadata.Expiry().Before(*t) {
			return true, nil
		}
	}
	return false, nil
101 102
}

103 104 105 106 107 108
// 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.
func (pl ProofList) DisclosedAttributes(configuration *Configuration, disjunctions AttributeDisjunctionList) (bool, []*DisclosedAttribute, error) {
109
	var list []*DisclosedAttribute
110 111 112 113 114 115
	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,
116 117 118
		}
	}

119 120 121
	// 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
122
	extraAttrs := map[AttributeTypeIdentifier]*DisclosedAttribute{}
123

124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
	for _, proof := range pl {
		proofd, ok := proof.(*gabi.ProofD)
		if !ok {
			continue
		}
		metadata := MetadataFromInt(proofd.ADisclosed[1], configuration) // index 1 is metadata attribute
		credtype := metadata.CredentialType()
		if credtype == nil {
			return false, nil, errors.New("ProofList contained a disclosure proof of an unkown credential type")
		}

		for k, v := range proofd.ADisclosed {
			if k < 2 {
				continue // skip metadata attribute
			}

			attrid := credtype.Attributes[k-2].GetAttributeTypeIdentifier()
			attrval := decodeAttribute(v, metadata.Version())
142
			attr := &DisclosedAttribute{
143
				Value:      translateAttribute(attrval),
144 145 146 147
				Identifier: attrid,
				Status:     AttributeProofStatusExtra,
			}
			extraAttrs[attrid] = attr
148
			if len(disjunctions) == 0 {
149 150
				continue
			}
151 152 153 154

			// See if the current attribute satisfies one of the disjunctions, if so, delete it from extraAttrs
			for i, disjunction := range disjunctions {
				if disjunction.attemptSatisfy(attrid, attrval) {
155 156 157 158 159 160 161 162 163
					if disjunction.satisfied() {
						attr.Status = AttributeProofStatusPresent
					} else {
						attr.Status = AttributeProofStatusInvalidValue
					}
					list[i] = attr
					delete(extraAttrs, attrid)
				}
			}
164 165 166
		}
	}

167
	// Any attributes still in here do not satisfy any of the specified disjunctions; append them now
168 169
	for _, attr := range extraAttrs {
		list = append(list, attr)
170 171
	}

172
	return len(disjunctions) == 0 || disjunctions.satisfied(), list, nil
173 174
}

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
func (pl ProofList) verifyAgainstDisjunctions(configuration *Configuration, required AttributeDisjunctionList, nonce, context *big.Int, issig bool) ([]*DisclosedAttribute, ProofStatus) {
	// Cryptographically verify the IRMA disclosure proofs in the signature
	if !pl.VerifyProofs(configuration, nonce, context, issig) {
		return nil, ProofStatusInvalidCrypto
	}

	// Next extract the contained attributes from the proofs, and match them to the signature request if present
	allmatched, list, err := pl.DisclosedAttributes(configuration, required)
	if err != nil {
		return nil, ProofStatusInvalidCrypto
	}

	// Return MISSING_ATTRIBUTES as proofstatus if one of the disjunctions in the request (if present) is not satisfied
	// This status takes priority over 'EXPIRED'
	if !allmatched {
		return list, ProofStatusMissingAttributes

	}
	return list, ProofStatusValid
}

func (pl ProofList) Verify(configuration *Configuration, request *DisclosureRequest) *VerificationResult {
	list, status := pl.verifyAgainstDisjunctions(configuration, request.Content, request.Nonce, request.Context, false)
	return &VerificationResult{
		Attributes: list,
		Status:     status,
	}
}

204 205 206 207 208 209 210 211 212 213 214 215
// 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.
func (sm *SignedMessage) Verify(configuration *Configuration, request *SignatureRequest) (result *VerificationResult) {
	var message string
	result = &VerificationResult{}
216

217
	// First check if this signature matches the request
218 219 220 221 222
	if request != nil {
		request.Timestamp = sm.Timestamp
		if !sm.MatchesNonceAndContext(request) {
			result.Status = ProofStatusUnmatchedRequest
			return
223
		}
224 225 226 227 228
		// 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
229 230 231
	}

	// Verify the timestamp
232
	if sm.Timestamp != nil {
233 234 235
		if err := sm.VerifyTimestamp(message, configuration); err != nil {
			result.Status = ProofStatusInvalidTimestamp
			return
236
		}
237 238
	}

239
	// Now, cryptographically verify the IRMA disclosure proofs in the signature
240 241
	pl := ProofList(sm.Signature)
	var required AttributeDisjunctionList
242
	if request != nil {
243
		required = request.Content
244
	}
245 246
	result.Attributes, result.Status = pl.verifyAgainstDisjunctions(configuration, required, sm.Context, sm.GetNonce(), true)
	if result.Status != ProofStatusValid {
247
		return
248 249
	}

250
	// Check if a credential is expired
251 252 253 254
	var t time.Time
	if sm.Timestamp != nil {
		t = time.Unix(sm.Timestamp.Time, 0)
	}
255
	expired, err := pl.Expired(configuration, &t)
256 257 258 259 260 261
	if err != nil {
		result.Status = ProofStatusInvalidCrypto
		return
	}
	if expired {
		if sm.Timestamp == nil {
262 263 264 265
			// 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.
			result.Status = ProofStatusExpired
266
		} else {
267 268 269
			// 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
			result.Status = ProofStatusInvalidCrypto
270
		}
271
		return
272 273
	}

274 275 276
	// All disjunctions satisfied and nothing expired, proof is valid!
	result.Status = ProofStatusValid
	return
277
}