Commit dfc27bea authored by Koen van Ingen's avatar Koen van Ingen
Browse files

Refactor and use different types for proof results

parent 1ac28e84
......@@ -22,18 +22,18 @@ const (
type AttributeResultList struct {
AttributeResults []*AttributeResult
}
type AttributeResult struct {
AttributeValue TranslatedString // Value of the disclosed attribute
AttributeId AttributeTypeIdentifier
AttributeProofStatus AttributeProofStatus
AttributeValue string `json:"value"` // Value of the disclosed attribute
AttributeId AttributeTypeIdentifier `json:"id"`
AttributeProofStatus AttributeProofStatus `json:"status"`
}
// AttributeProofStatus is the proof status of a single attribute
type AttributeProofStatus string
const (
PRESENT = AttributeProofStatus("PRESENT") // Attribute is disclosed and matches the value
UNKNOWN = AttributeProofStatus("UNKNOWN") // Attribute is disclosed, but status is yet unknown
PRESENT = AttributeProofStatus("PRESENT") // Attribute is disclosed and matches the value
EXTRA = AttributeProofStatus("EXTRA")
MISSING = AttributeProofStatus("MISSING") // Attribute is NOT disclosed, but should be according to request
INVALID_VALUE = AttributeProofStatus("INVALID_VALUE") // Attribute is disclosed, but has invalid value according to request
)
......@@ -54,7 +54,7 @@ type metadataField struct {
offset int
}
// MetadataAttribute represent a metadata attribute. Contains the credential type, signing date, validity, and the public key counter.
// metadataAttribute represents a metadata attribute. Contains the credential type, signing date, validity, and the public key counter.
type MetadataAttribute struct {
Int *big.Int
pk *gabi.PublicKey
......@@ -79,16 +79,6 @@ func NewAttributeListFromInts(ints []*big.Int, conf *Configuration) *AttributeLi
}
}
// NewAttributeListFromInts initializes a new AttributeList from disclosed attributes of a prooflist
func NewAttributeListFromADisclosed(aResponses map[int]*big.Int, aDisclosed map[int]*big.Int, conf *Configuration) (*AttributeList, error) {
ints, err := convertProofResponsesToInts(aResponses, aDisclosed)
if err != nil {
return nil, err
}
return NewAttributeListFromInts(ints, conf), nil
}
func (al *AttributeList) Info() *CredentialInfo {
if al.info == nil {
al.info = NewCredentialInfo(al.Ints, al.Conf)
......@@ -323,9 +313,27 @@ type AttributeDisjunction struct {
selected *AttributeTypeIdentifier
}
// AttributeDisjunction with the disclosed value. Label, Attributes,Valued can be nil if there is no matching disjunction
type DisclosedAttributeDisjunction struct {
AttributeDisjunction
DisclosedValue string
DisclosedId AttributeTypeIdentifier
ProofStatus AttributeProofStatus
}
// An AttributeDisjunctionList is a list of AttributeDisjunctions.
type AttributeDisjunctionList []*AttributeDisjunction
func NewDisclosedDisjunctionFromList(ad *AttributeDisjunction, ar *AttributeResult) *DisclosedAttributeDisjunction {
return &DisclosedAttributeDisjunction{
AttributeDisjunction: *ad,
DisclosedValue: ar.AttributeValue,
DisclosedId: ar.AttributeId,
ProofStatus: ar.AttributeProofStatus,
}
}
// HasValues indicates if the attributes of this disjunction have values
// that should be satisfied.
func (disjunction *AttributeDisjunction) HasValues() bool {
......@@ -350,58 +358,60 @@ func (disjunction *AttributeDisjunction) Satisfied() bool {
// 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
func isAttributeSatisfied(attribute AttributeTypeIdentifier, value string, disclosed []*CredentialInfo, conf *Configuration, al *AttributeResultList) bool {
func isAttributeSatisfied(attributeId AttributeTypeIdentifier, requestedValue string, disclosed []*DisclosedCredential) (bool, *AttributeResult) {
ar := AttributeResult{
AttributeId: attributeId,
}
for _, cred := range disclosed {
credentialType := cred.GetCredentialType(conf)
index, err := credentialType.IndexOf(attribute)
disclosedAttributeValue := cred.GetAttributeValue(attributeId)
if err != nil {
// Specified credential does not contain this attribute, move to next cred in list of disclosed credentials
// Continue to next credential if requested attribute isn't disclosed in this credential
if disclosedAttributeValue == "" {
continue
}
disclosedAttributeValue := cred.Attributes[index]
// If it contains this attribute, check if value matches (it must be disclosed (i.e. not nil) and match the value)
// 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
if disclosedAttributeValue != nil {
if value == "" || disclosedAttributeValue["en"] == value { // TODO: fix translation/attr typing
al.SetProofStatus(attribute, disclosedAttributeValue, PRESENT)
return true
} 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
al.SetProofStatus(attribute, disclosedAttributeValue, INVALID_VALUE)
}
ar.AttributeValue = disclosedAttributeValue
if requestedValue == "" || disclosedAttributeValue == requestedValue {
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
}
}
return false
// 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
}
// Check whether specified attributedisjunction satisfy a list of disclosed attributes
// We return true if one of the attributes in the disjunction is satisfied
func (disjunction *AttributeDisjunction) SatisfyDisclosed(disclosed []*CredentialInfo, conf *Configuration, al *AttributeResultList) bool {
func (disjunction *AttributeDisjunction) SatisfyDisclosed(disclosed []*DisclosedCredential, conf *Configuration) (bool, *DisclosedAttributeDisjunction) {
var attributeResult *AttributeResult
for _, attr := range disjunction.Attributes {
value := disjunction.Values[attr]
requestedValue := disjunction.Values[attr]
if isAttributeSatisfied(attr, value, disclosed, conf, al) {
return true
}
}
var isSatisfied bool
isSatisfied, attributeResult = isAttributeSatisfied(attr, requestedValue, disclosed)
// Add all missing attributes
for _, attr := range disjunction.Attributes {
ar := AttributeResult{
AttributeId: attr,
AttributeProofStatus: MISSING,
}
if !al.ContainsAttributeId(ar.AttributeId) {
al.Append(ar)
if isSatisfied {
return true, NewDisclosedDisjunctionFromList(disjunction, attributeResult)
}
}
return false
// Nothing satisfied, attributeResult will contain the last attribute of the original request
// TODO: do we want this?
return false, NewDisclosedDisjunctionFromList(disjunction, attributeResult)
}
// MatchesConfig returns true if all attributes contained in the disjunction are
......@@ -517,19 +527,8 @@ func (disjunction *AttributeDisjunction) UnmarshalJSON(bytes []byte) error {
return nil
}
// From here attributeResult related functions. TODO: move to separate file?
func (al *AttributeResultList) Append(result AttributeResult) {
al.AttributeResults = append(al.AttributeResults, &result)
}
func (al *AttributeResultList) ContainsAttributeId(attrId AttributeTypeIdentifier) bool {
for _, ar := range al.AttributeResults {
if ar.AttributeId == attrId {
return true
}
}
return false
func (al *AttributeResultList) Append(result *AttributeResult) {
al.AttributeResults = append(al.AttributeResults, result)
}
func (al *AttributeResultList) String() string {
......@@ -541,62 +540,9 @@ func (al *AttributeResultList) String() string {
return str
}
// Set the proof status to a new status for a specified attribute. An attribute is specified by an attributeTypeIdentifier
func (al *AttributeResultList) SetProofStatus(attrID AttributeTypeIdentifier, attrValue TranslatedString, status AttributeProofStatus) bool {
for _, ar := range al.AttributeResults {
// TODO: translation
if ar.AttributeId == attrID && ar.AttributeValue["en"] == attrValue["en"] {
ar.AttributeProofStatus = status
return true
}
}
return false
}
func (ar *AttributeResult) GetAttributeDescription(conf *Configuration) (*AttributeDescription, error) {
cred := conf.CredentialTypes[NewCredentialTypeIdentifier(ar.AttributeId.Parent())]
index, err := cred.IndexOf(ar.AttributeId)
if err != nil {
return nil, err
}
return &cred.Attributes[index], nil
}
func (ar *AttributeResult) String() string {
// TODO: translated string!
return fmt.Sprintf("%v --- %v --- %v",
ar.AttributeId,
ar.AttributeValue["en"],
ar.AttributeValue,
ar.AttributeProofStatus)
}
func AttributeResultListFromDisclosed(disclosed []*CredentialInfo, conf *Configuration) *AttributeResultList {
al := AttributeResultList{}
for _, cred := range disclosed {
credentialType := cred.GetCredentialType(conf)
for _, attr := range credentialType.Attributes {
attrId := NewAttributeTypeIdentifier(cred.CredentialTypeID.String() + "." + attr.ID)
index, err := credentialType.IndexOf(attrId)
if err != nil {
// Specified credential does not contain this attribute, move to next
break
}
disclosedAttributeValue := cred.Attributes[index]
if disclosedAttributeValue != nil {
al.Append(AttributeResult{
AttributeValue: disclosedAttributeValue,
AttributeId: attrId,
AttributeProofStatus: UNKNOWN,
})
}
}
}
return &al
}
......@@ -48,41 +48,6 @@ func NewCredentialInfo(ints []*big.Int, conf *Configuration) *CredentialInfo {
}
}
// Convert proof responses to Ints, adding nils for undislosed attributes
func convertProofResponsesToInts(aResponses map[int]*big.Int, aDisclosed map[int]*big.Int) ([]*big.Int, error) {
var ints []*big.Int
length := len(aResponses) + len(aDisclosed)
for i := 1; i < length; i++ {
if aResponses[i] == nil {
if aDisclosed[i] == nil {
// If index not found in aResponses it must be in aDisclosed
return nil, &SessionError{
ErrorType: ErrorCrypto,
Info: fmt.Sprintf("Missing attribute index: %v", i),
} // TODO: error type?
}
ints = append(ints, aDisclosed[i])
} else {
// Don't include value of hidden attributes
ints = append(ints, nil)
}
}
return ints, nil
}
// NewAttributeListFromInts initializes a new AttributeList from disclosed attributes of a prooflist
func NewCredentialInfoFromADisclosed(aResponses map[int]*big.Int, aDisclosed map[int]*big.Int, conf *Configuration) (*CredentialInfo, error) {
ints, err := convertProofResponsesToInts(aResponses, aDisclosed)
if err != nil {
return nil, err
}
return NewCredentialInfo(ints, conf), nil
}
func (ci CredentialInfo) GetCredentialType(conf *Configuration) *CredentialType {
return conf.CredentialTypes[ci.CredentialTypeID]
}
......
......@@ -81,6 +81,10 @@ type AttributeDescription struct {
Description TranslatedString
}
func (ad AttributeDescription) GetAttributeTypeIdentifier(cred CredentialTypeIdentifier) AttributeTypeIdentifier {
return NewAttributeTypeIdentifier(cred.String() + "." + ad.ID)
}
// IndexOf returns the index of the specified attribute if present,
// or an error (and -1) if not present.
func (ct CredentialType) IndexOf(ai AttributeTypeIdentifier) (int, error) {
......
......@@ -10,17 +10,12 @@ import (
"testing"
)
type Result struct {
proofStatus ProofStatus
attributes *irma.AttributeResultList
}
type ManualSessionHandler struct {
permissionHandler PermissionHandler
pinHandler PinHandler
t *testing.T
errorChannel chan *irma.SessionError
resultChannel chan Result
resultChannel chan *irma.SignatureProofResult
sigRequest *irma.SignatureRequest // Request used to create signature
sigVerifyRequest *irma.SignatureRequest // Request used to verify signature
}
......@@ -56,7 +51,7 @@ func corruptProofString(proof string) string {
// Create a ManualSessionHandler for unit tests
func createManualSessionHandler(request string, invalidRequest string, t *testing.T) ManualSessionHandler {
errorChannel := make(chan *irma.SessionError)
resultChannel := make(chan Result)
resultChannel := make(chan *irma.SignatureProofResult)
sigRequestJSON := []byte(request)
invalidSigRequestJSON := []byte(invalidRequest)
......@@ -90,11 +85,11 @@ func TestManualSession(t *testing.T) {
// No errors, obtain proof result from channel
result := <-ms.resultChannel
if ps := result.proofStatus; ps != VALID {
t.Logf("Invalid proof result: %v Expected: %v", ps, VALID)
if ps := result.ProofStatus; ps != irma.VALID {
t.Logf("Invalid proof result: %v Expected: %v", ps, irma.VALID)
t.Fail()
}
if attrStatus := result.attributes.AttributeResults[0].AttributeProofStatus; attrStatus != irma.PRESENT {
if attrStatus := result.ToAttributeResultList().AttributeResults[0].AttributeProofStatus; attrStatus != irma.PRESENT {
t.Logf("Invalid attribute result value: %v Expected: %v", attrStatus, irma.PRESENT)
t.Fail()
}
......@@ -137,8 +132,8 @@ func TestManualSessionInvalidNonce(t *testing.T) {
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result.proofStatus != INVALID_CRYPTO {
t.Logf("Invalid proof result: %v Expected: %v", result.proofStatus, INVALID_CRYPTO)
if result := <-ms.resultChannel; result.ProofStatus != irma.INVALID_CRYPTO {
t.Logf("Invalid proof result: %v Expected: %v", result.ProofStatus, irma.INVALID_CRYPTO)
t.Fail()
}
test.ClearTestStorage(t)
......@@ -163,18 +158,19 @@ func TestManualSessionInvalidRequest(t *testing.T) {
// No errors, obtain proof result from channel
result := <-ms.resultChannel
if ps := result.proofStatus; ps != MISSING_ATTRIBUTES {
t.Logf("Invalid proof result: %v Expected: %v", ps, MISSING_ATTRIBUTES)
if ps := result.ProofStatus; ps != irma.MISSING_ATTRIBUTES {
t.Logf("Invalid proof result: %v Expected: %v", ps, irma.MISSING_ATTRIBUTES)
t.Fail()
}
// First attribute result is UNKOWN, since it is disclosed, but not matching the sigrequest
if attrStatus := result.attributes.AttributeResults[0].AttributeProofStatus; attrStatus != irma.UNKNOWN {
t.Logf("Invalid attribute result value: %v Expected: %v", attrStatus, irma.UNKNOWN)
// First attribute result is MISSING, because it is in the request but not disclosed
if attrStatus := result.ToAttributeResultList().AttributeResults[0].AttributeProofStatus; attrStatus != irma.MISSING {
t.Logf("Invalid attribute result value: %v Expected: %v", attrStatus, irma.MISSING)
t.Fail()
}
// Second attribute result is MISSING, because it is in the request but not disclosed
if attrStatus := result.attributes.AttributeResults[1].AttributeProofStatus; attrStatus != irma.MISSING {
t.Logf("Invalid attribute result value: %v Expected: %v", attrStatus, irma.MISSING)
// Second attribute result is EXTRA, since it is disclosed, but not matching the sigrequest
if attrStatus := result.ToAttributeResultList().AttributeResults[1].AttributeProofStatus; attrStatus != irma.EXTRA {
t.Logf("Invalid attribute result value: %v Expected: %v", attrStatus, irma.EXTRA)
t.Fail()
}
test.ClearTestStorage(t)
......@@ -199,11 +195,11 @@ func TestManualSessionInvalidAttributeValue(t *testing.T) {
// No errors, obtain proof result from channel
result := <-ms.resultChannel
if ps := result.proofStatus; ps != MISSING_ATTRIBUTES {
t.Logf("Invalid proof result: %v Expected: %v", ps, MISSING_ATTRIBUTES)
if ps := result.ProofStatus; ps != irma.MISSING_ATTRIBUTES {
t.Logf("Invalid proof result: %v Expected: %v", ps, irma.MISSING_ATTRIBUTES)
t.Fail()
}
if attrStatus := result.attributes.AttributeResults[0].AttributeProofStatus; attrStatus != irma.INVALID_VALUE {
if attrStatus := result.ToAttributeResultList().AttributeResults[0].AttributeProofStatus; attrStatus != irma.INVALID_VALUE {
t.Logf("Invalid attribute result value: %v Expected: %v", attrStatus, irma.INVALID_VALUE)
t.Fail()
}
......@@ -226,8 +222,8 @@ func TestManualKeyShareSession(t *testing.T) {
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result.proofStatus != VALID {
t.Logf("Invalid proof result: %v Expected: %v", result.proofStatus, VALID)
if result := <-ms.resultChannel; result.ProofStatus != irma.VALID {
t.Logf("Invalid proof result: %v Expected: %v", result.ProofStatus, irma.VALID)
t.Fail()
}
test.ClearTestStorage(t)
......@@ -258,8 +254,17 @@ func TestManualSessionMultiProof(t *testing.T) {
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result.proofStatus != VALID {
t.Logf("Invalid proof result: %v Expected: %v", result.proofStatus, VALID)
result := <-ms.resultChannel
if ps := result.ProofStatus; ps != irma.VALID {
t.Logf("Invalid proof result: %v Expected: %v", result.ProofStatus, irma.VALID)
t.Fail()
}
if attrStatus := result.ToAttributeResultList().AttributeResults[0].AttributeProofStatus; attrStatus != irma.PRESENT {
t.Logf("Invalid attribute result value: %v Expected: %v", attrStatus, irma.PRESENT)
t.Fail()
}
if attrStatus := result.ToAttributeResultList().AttributeResults[1].AttributeProofStatus; attrStatus != irma.PRESENT {
t.Logf("Invalid attribute result value: %v Expected: %v", attrStatus, irma.PRESENT)
t.Fail()
}
test.ClearTestStorage(t)
......@@ -280,8 +285,8 @@ func TestManualSessionInvalidProof(t *testing.T) {
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result.proofStatus != INVALID_CRYPTO {
t.Logf("Invalid proof result: %v Expected: %v", result.proofStatus, INVALID_CRYPTO)
if result := <-ms.resultChannel; result.ProofStatus != irma.INVALID_CRYPTO {
t.Logf("Invalid proof result: %v Expected: %v", result.ProofStatus, irma.INVALID_CRYPTO)
t.Fail()
}
test.ClearTestStorage(t)
......@@ -294,8 +299,7 @@ func (sh *ManualSessionHandler) Success(irmaAction irma.Action, result string) {
result = corruptProofString(result)
go func() {
proofStatus, attributeResultList := VerifySig(client.Configuration, result, sh.sigVerifyRequest)
sh.resultChannel <- Result{proofStatus, attributeResultList}
sh.resultChannel <- irma.VerifySig(client.Configuration, result, sh.sigVerifyRequest)
}()
}
sh.errorChannel <- nil
......
package irmaclient
import (
"fmt"
"github.com/go-errors/errors"
"github.com/mhe/gabi"
"github.com/privacybydesign/irmago"
"math/big"
)
// TODO: move to irma package?
type ProofResult struct {
proofStatus ProofStatus // The overall proofstatus, should be VALID in order to accept the proof
attributes []irma.AttributeResult
}
type ProofStatus string
const (
VALID = ProofStatus("VALID")
EXPIRED = ProofStatus("EXPIRED")
INVALID_CRYPTO = ProofStatus("INVALID_CRYPTO")
INVALID_JSON = ProofStatus("INVALID_JSON")
MISSING_ATTRIBUTES = ProofStatus("MISSING_ATTRIBUTES")
)
func extractPublicKeys(configuration *irma.Configuration, proofList *gabi.ProofList) ([]*gabi.PublicKey, error) {
var publicKeys []*gabi.PublicKey
for _, v := range *proofList {
switch v.(type) {
case *gabi.ProofD:
proof := v.(*gabi.ProofD)
metadata := irma.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
}
func extractDisclosedCredentials(configuration *irma.Configuration, proofList *gabi.ProofList) ([]*irma.CredentialInfo, error) {
var credentials []*irma.CredentialInfo
for _, v := range *proofList {
switch v.(type) {
case *gabi.ProofD:
proof := v.(*gabi.ProofD)
irmaCredentialInfo, err := irma.NewCredentialInfoFromADisclosed(proof.AResponses, proof.ADisclosed, configuration)
if err != nil {
return nil, err
}
credentials = append(credentials, irmaCredentialInfo)
default:
return nil, errors.New("Cannot extract attributes from proof, not a disclosure proofD!")
}
}
return credentials, nil
}
func checkProofWithRequest(configuration *irma.Configuration, proofList *gabi.ProofList, sigRequest *irma.SignatureRequest) (ProofStatus, *irma.AttributeResultList) {
credentials, err := extractDisclosedCredentials(configuration, proofList)
if err != nil {
fmt.Println(err)
return INVALID_CRYPTO, nil
}
al := irma.AttributeResultListFromDisclosed(credentials, configuration)
for _, content := range sigRequest.Content {
if !content.SatisfyDisclosed(credentials, configuration, al) {
return MISSING_ATTRIBUTES, al
}
}
// Check if a credential is expired
for _, cred := range credentials {
if cred.IsExpired() {
return EXPIRED, al
}
}
return VALID, al
}
// Verify an IRMA proof cryptographically
func verify(configuration *irma.Configuration, proofList *gabi.ProofList, context *big.Int, nonce *big.Int, isSig bool) bool {
// Extract public keys
pks, err := extractPublicKeys(configuration, proofList)
if err != nil {
fmt.Printf("Error extracting public key: %v\n", err)
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
func VerifySig(configuration *irma.Configuration, proofString string, sigRequest *irma.SignatureRequest) (ProofStatus, *irma.AttributeResultList) {
// First, unmarshal proof and check if all the attributes in the proofstring match the signature request
var proofList gabi.ProofList
proofBytes := []byte(proofString)
err := proofList.UnmarshalJSON(proofBytes)
if err != nil {
fmt.Printf("Error unmarshalling JSON: %v\n", err)
return INVALID_JSON, nil
}
// Now, cryptographically verify the signature
if !verify(configuration, &proofList, sigRequest.GetContext(), sigRequest.GetNonce(), true) {
return INVALID_CRYPTO, nil
}
// Finally, check whether attribute values in proof satisfy the original signature request
return checkProofWithRequest(configuration, &proofList, sigRequest)
}
package irma
import (
"fmt"
"github.com/go-errors/errors"
"github.com/mhe/gabi"
"math/big"
"time"
)
// ProofStatus is the status of the complete proof
type ProofStatus string
const (
VALID = ProofStatus("VALID")
EXPIRED = ProofStatus("EXPIRED")
INVALID_CRYPTO = ProofStatus("INVALID_CRYPTO")
INVALID_SYNTAX = ProofStatus("INVALID_SYNTAX")
MISSING_ATTRIBUTES = ProofStatus("MISSING_ATTRIBUTES")
)
// ProofResult is a result of a complete proof, containing all the disclosed attributes and corresponding request
type ProofResult struct {
disjunctions []*DisclosedAttributeDisjunction
ProofStatus ProofStatus
}
type SignatureProofResult struct {
ProofResult
message string
}
// DisclosedCredential contains raw disclosed credentials, without any extra parsing information
type DisclosedCredential struct {
metadataAttribute *MetadataAttribute
Attributes map[AttributeTypeIdentifier]*big.Int