Commit 1ac28e84 authored by Koen van Ingen's avatar Koen van Ingen
Browse files

Return proofresult for each separate attribute

parent 7914f1c0
......@@ -8,6 +8,7 @@ import (
"math/big"
"time"
"fmt"
"github.com/go-errors/errors"
"github.com/mhe/gabi"
)
......@@ -18,6 +19,25 @@ const (
metadataLength = 1 + 3 + 2 + 2 + 16
)
type AttributeResultList struct {
AttributeResults []*AttributeResult
}
type AttributeResult struct {
AttributeValue TranslatedString // Value of the disclosed attribute
AttributeId AttributeTypeIdentifier
AttributeProofStatus AttributeProofStatus
}
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
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
)
var (
metadataVersion = []byte{0x02}
......@@ -329,7 +349,8 @@ func (disjunction *AttributeDisjunction) Satisfied() bool {
// 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
func isAttributeSatisfied(attribute AttributeTypeIdentifier, value string, disclosed []*CredentialInfo, conf *Configuration) bool {
// 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 {
for _, cred := range disclosed {
credentialType := cred.GetCredentialType(conf)
index, err := credentialType.IndexOf(attribute)
......@@ -340,12 +361,19 @@ func isAttributeSatisfied(attribute AttributeTypeIdentifier, value string, discl
}
disclosedAttributeValue := cred.Attributes[index]
// If it contains this attribute, check if value match (it must be disclosed (i.e. not nil) and match the value)
// Attribute is Statiisfied if:
// If it contains this attribute, check if value matches (it must be disclosed (i.e. not nil) and match the value)
// Attribute is satisfied if:
// - Attribute is disclosed (i.e. not nil)
// - Value is empty OR value equal to disclosedValue
if disclosedAttributeValue != nil && (value == "" || disclosedAttributeValue["en"] == value) { // TODO: fix translation/attr typing
return true
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)
}
}
}
return false
......@@ -353,15 +381,26 @@ func isAttributeSatisfied(attribute AttributeTypeIdentifier, value string, discl
// 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) bool {
func (disjunction *AttributeDisjunction) SatisfyDisclosed(disclosed []*CredentialInfo, conf *Configuration, al *AttributeResultList) bool {
for _, attr := range disjunction.Attributes {
value := disjunction.Values[attr]
if isAttributeSatisfied(attr, value, disclosed, conf) {
if isAttributeSatisfied(attr, value, disclosed, conf, al) {
return true
}
}
// Add all missing attributes
for _, attr := range disjunction.Attributes {
ar := AttributeResult{
AttributeId: attr,
AttributeProofStatus: MISSING,
}
if !al.ContainsAttributeId(ar.AttributeId) {
al.Append(ar)
}
}
return false
}
......@@ -477,3 +516,87 @@ 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) String() string {
// TODO: pretty print?
str := "Attribute --- Value --- ProofStatus:"
for _, v := range al.AttributeResults {
str = str + "\n" + v.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.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
}
......@@ -10,12 +10,17 @@ 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 ProofStatus
resultChannel chan Result
sigRequest *irma.SignatureRequest // Request used to create signature
sigVerifyRequest *irma.SignatureRequest // Request used to verify signature
}
......@@ -51,7 +56,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 ProofStatus)
resultChannel := make(chan Result)
sigRequestJSON := []byte(request)
invalidSigRequestJSON := []byte(invalidRequest)
......@@ -84,8 +89,13 @@ func TestManualSession(t *testing.T) {
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result != VALID {
t.Logf("Invalid proof result: %v Expected: %v", result, VALID)
result := <-ms.resultChannel
if ps := result.proofStatus; ps != VALID {
t.Logf("Invalid proof result: %v Expected: %v", ps, VALID)
t.Fail()
}
if attrStatus := result.attributes.AttributeResults[0].AttributeProofStatus; attrStatus != irma.PRESENT {
t.Logf("Invalid attribute result value: %v Expected: %v", attrStatus, irma.PRESENT)
t.Fail()
}
test.ClearTestStorage(t)
......@@ -127,8 +137,8 @@ func TestManualSessionInvalidNonce(t *testing.T) {
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result != INVALID_CRYPTO {
t.Logf("Invalid proof result: %v Expected: %v", result, INVALID_CRYPTO)
if result := <-ms.resultChannel; result.proofStatus != INVALID_CRYPTO {
t.Logf("Invalid proof result: %v Expected: %v", result.proofStatus, INVALID_CRYPTO)
t.Fail()
}
test.ClearTestStorage(t)
......@@ -152,8 +162,19 @@ func TestManualSessionInvalidRequest(t *testing.T) {
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result != MISSING_ATTRIBUTES {
t.Logf("Invalid proof result: %v Expected: %v", result, MISSING_ATTRIBUTES)
result := <-ms.resultChannel
if ps := result.proofStatus; ps != MISSING_ATTRIBUTES {
t.Logf("Invalid proof result: %v Expected: %v", ps, 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)
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)
t.Fail()
}
test.ClearTestStorage(t)
......@@ -177,8 +198,13 @@ func TestManualSessionInvalidAttributeValue(t *testing.T) {
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result != MISSING_ATTRIBUTES {
t.Logf("Invalid proof result: %v Expected: %v", result, MISSING_ATTRIBUTES)
result := <-ms.resultChannel
if ps := result.proofStatus; ps != MISSING_ATTRIBUTES {
t.Logf("Invalid proof result: %v Expected: %v", ps, MISSING_ATTRIBUTES)
t.Fail()
}
if attrStatus := result.attributes.AttributeResults[0].AttributeProofStatus; attrStatus != irma.INVALID_VALUE {
t.Logf("Invalid attribute result value: %v Expected: %v", attrStatus, irma.INVALID_VALUE)
t.Fail()
}
test.ClearTestStorage(t)
......@@ -200,8 +226,8 @@ func TestManualKeyShareSession(t *testing.T) {
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result != VALID {
t.Logf("Invalid proof result: %v Expected: %v", result, VALID)
if result := <-ms.resultChannel; result.proofStatus != VALID {
t.Logf("Invalid proof result: %v Expected: %v", result.proofStatus, VALID)
t.Fail()
}
test.ClearTestStorage(t)
......@@ -232,8 +258,8 @@ func TestManualSessionMultiProof(t *testing.T) {
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result != VALID {
t.Logf("Invalid proof result: %v Expected: %v", result, VALID)
if result := <-ms.resultChannel; result.proofStatus != VALID {
t.Logf("Invalid proof result: %v Expected: %v", result.proofStatus, VALID)
t.Fail()
}
test.ClearTestStorage(t)
......@@ -254,8 +280,8 @@ func TestManualSessionInvalidProof(t *testing.T) {
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result != INVALID_CRYPTO {
t.Logf("Invalid proof result: %v Expected: %v", result, INVALID_CRYPTO)
if result := <-ms.resultChannel; result.proofStatus != INVALID_CRYPTO {
t.Logf("Invalid proof result: %v Expected: %v", result.proofStatus, INVALID_CRYPTO)
t.Fail()
}
test.ClearTestStorage(t)
......@@ -268,7 +294,8 @@ func (sh *ManualSessionHandler) Success(irmaAction irma.Action, result string) {
result = corruptProofString(result)
go func() {
sh.resultChannel <- VerifySig(client.Configuration, result, sh.sigVerifyRequest)
proofStatus, attributeResultList := VerifySig(client.Configuration, result, sh.sigVerifyRequest)
sh.resultChannel <- Result{proofStatus, attributeResultList}
}()
}
sh.errorChannel <- nil
......
......@@ -8,6 +8,12 @@ import (
"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 (
......@@ -58,28 +64,29 @@ func extractDisclosedCredentials(configuration *irma.Configuration, proofList *g
return credentials, nil
}
func checkProofWithRequest(configuration *irma.Configuration, proofList *gabi.ProofList, sigRequest *irma.SignatureRequest) ProofStatus {
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
return INVALID_CRYPTO, nil
}
al := irma.AttributeResultListFromDisclosed(credentials, configuration)
for _, content := range sigRequest.Content {
if !content.SatisfyDisclosed(credentials, configuration) {
return MISSING_ATTRIBUTES
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
return EXPIRED, al
}
}
return VALID
return VALID, al
}
// Verify an IRMA proof cryptographically
......@@ -95,7 +102,7 @@ func verify(configuration *irma.Configuration, proofList *gabi.ProofList, contex
}
// 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 {
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
......@@ -104,12 +111,12 @@ func VerifySig(configuration *irma.Configuration, proofString string, sigRequest
err := proofList.UnmarshalJSON(proofBytes)
if err != nil {
fmt.Printf("Error unmarshalling JSON: %v\n", err)
return INVALID_JSON
return INVALID_JSON, nil
}
// Now, cryptographically verify the signature
if !verify(configuration, &proofList, sigRequest.GetContext(), sigRequest.GetNonce(), true) {
return INVALID_CRYPTO
return INVALID_CRYPTO, nil
}
// Finally, check whether attribute values in proof satisfy the original signature request
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment