Commit 7914f1c0 authored by Koen van Ingen's avatar Koen van Ingen
Browse files

More verify tests and return proof result

parent 941328fc
......@@ -3,6 +3,8 @@ package irma
import (
"math/big"
"strings"
"github.com/privacybydesign/irmago/internal/fs"
"time"
)
// CredentialInfo contains all information of an IRMA credential.
......@@ -85,6 +87,11 @@ func (ci CredentialInfo) GetCredentialType(conf *Configuration) *CredentialType
return conf.CredentialTypes[ci.CredentialTypeID]
}
// Returns true if credential is expired at moment of calling this function
func (ci CredentialInfo) IsExpired() bool {
return ci.Expires.Before(Timestamp(time.Now()))
}
// Len implements sort.Interface.
func (cl CredentialInfoList) Len() int {
return len(cl)
......
......@@ -3,19 +3,21 @@ package irmaclient
import (
"encoding/json"
"fmt"
"testing"
"github.com/go-errors/errors"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/test"
"github.com/privacybydesign/irmago"
"testing"
)
type ManualSessionHandler struct {
permissionHandler PermissionHandler
pinHandler PinHandler
t *testing.T
c chan *irma.SessionError
sigRequest *irma.SignatureRequest
errorChannel chan *irma.SessionError
resultChannel chan ProofStatus
sigRequest *irma.SignatureRequest // Request used to create signature
sigVerifyRequest *irma.SignatureRequest // Request used to verify signature
}
var client *Client
......@@ -46,55 +48,162 @@ func corruptProofString(proof string) string {
return proof
}
func TestManualSession(t *testing.T) {
invalidate = false
channel := make(chan *irma.SessionError)
client = parseStorage(t)
// 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)
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
sigRequestJSON := []byte(request)
invalidSigRequestJSON := []byte(invalidRequest)
sigRequest := &irma.SignatureRequest{}
invalidSigRequest := &irma.SignatureRequest{}
json.Unmarshal(sigRequestJSON, sigRequest)
json.Unmarshal(invalidSigRequestJSON, invalidSigRequest)
return ManualSessionHandler{
t: t,
errorChannel: errorChannel,
resultChannel: resultChannel,
sigRequest: sigRequest,
sigVerifyRequest: invalidSigRequest,
}
}
func TestManualSession(t *testing.T) {
invalidate = false
ms := ManualSessionHandler{
t: t,
c: channel,
sigRequest: sigRequest,
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
ms := createManualSessionHandler(request, request, t)
client = parseStorage(t)
client.NewManualSession(request, &ms)
if err := <-ms.errorChannel; err != nil {
test.ClearTestStorage(t)
t.Fatal(*err)
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result != VALID {
t.Logf("Invalid proof result: %v Expected: %v", result, VALID)
t.Fail()
}
test.ClearTestStorage(t)
}
// Test if the session fails with unsatisfiable error if we cannot satify the signature request
func TestManualSessionUnsatisfiable(t *testing.T) {
invalidate = false
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":{\"irma-demo.RU.studentCard.studentID\": \"123\"}}]}"
ms := createManualSessionHandler(request, request, t)
client = parseStorage(t)
client.NewManualSession(request, &ms)
// Fail test if we won't get UnsatisfiableRequest error
if err := <-ms.errorChannel; err.ErrorType != irma.ErrorType("UnsatisfiableRequest") {
test.ClearTestStorage(t)
t.Fatal(*err)
}
test.ClearTestStorage(t)
}
// Test if proof verification fails with status 'ERROR_CRYPTO' if we verify it with an invalid nonce
func TestManualSessionInvalidNonce(t *testing.T) {
invalidate = false
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
invalidRequest := "{\"nonce\": 1, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
ms := createManualSessionHandler(request, invalidRequest, t)
client = parseStorage(t)
client.NewManualSession(request, &ms)
if err := <-channel; err != nil {
if err := <-ms.errorChannel; err != nil {
test.ClearTestStorage(t)
t.Fatal(*err)
}
// 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)
t.Fail()
}
test.ClearTestStorage(t)
}
func TestManualKeyShareSession(t *testing.T) {
// Test if proof verification fails with status 'MISSING_ATTRIBUTES' if we provide it with a non-matching signature request
func TestManualSessionInvalidRequest(t *testing.T) {
invalidate = false
channel := make(chan *irma.SessionError)
keyshareRequestString := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"test.test.mijnirma.email\"]}]}"
keyshareRequestJSON := []byte(keyshareRequestString)
keyshareRequest := &irma.SignatureRequest{}
json.Unmarshal(keyshareRequestJSON, keyshareRequest)
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
invalidRequest := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.university\"]}]}"
ms := createManualSessionHandler(request, invalidRequest, t)
client = parseStorage(t)
client.NewManualSession(request, &ms)
if err := <-ms.errorChannel; err != nil {
test.ClearTestStorage(t)
t.Fatal(*err)
}
manualSessionHandler := ManualSessionHandler{
t: t,
c: channel,
sigRequest: keyshareRequest,
// 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)
t.Fail()
}
test.ClearTestStorage(t)
}
// Test if proof verification fails with status 'MISSING_ATTRIBUTES' if we provide it with invalid attribute values
func TestManualSessionInvalidAttributeValue(t *testing.T) {
invalidate = false
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":{\"irma-demo.RU.studentCard.studentID\": \"456\"}}]}"
invalidRequest := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":{\"irma-demo.RU.studentCard.studentID\": \"123\"}}]}"
ms := createManualSessionHandler(request, invalidRequest, t)
client = parseStorage(t)
client.NewManualSession(request, &ms)
client.NewManualSession(keyshareRequestString, &manualSessionHandler)
if err := <-ms.errorChannel; err != nil {
test.ClearTestStorage(t)
t.Fatal(*err)
}
// 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)
t.Fail()
}
test.ClearTestStorage(t)
}
func TestManualKeyShareSession(t *testing.T) {
invalidate = false
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"test.test.mijnirma.email\"]}]}"
if err := <-channel; err != nil {
ms := createManualSessionHandler(request, request, t)
client = parseStorage(t)
client.NewManualSession(request, &ms)
if err := <-ms.errorChannel; err != nil {
test.ClearTestStorage(t)
t.Fatal(*err)
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result != VALID {
t.Logf("Invalid proof result: %v Expected: %v", result, VALID)
t.Fail()
}
test.ClearTestStorage(t)
}
......@@ -103,9 +212,9 @@ func TestManualSessionMultiProof(t *testing.T) {
client = parseStorage(t)
// First, we need to issue an extra credential (BSN)
is := ManualSessionHandler{t: t, c: make(chan *irma.SessionError)}
is := ManualSessionHandler{t: t, errorChannel: make(chan *irma.SessionError)}
go issue(t, is)
if err := <-is.c; err != nil {
if err := <-is.errorChannel; err != nil {
fmt.Println("Error during initial issueing!")
t.Fatal(*err)
}
......@@ -113,48 +222,42 @@ func TestManualSessionMultiProof(t *testing.T) {
// Request to sign with both BSN and StudentID
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]},{\"label\":\"BSN\",\"attributes\":[\"irma-demo.MijnOverheid.root.BSN\"]}]}"
channel := make(chan *irma.SessionError)
sigRequestJSON := []byte(request)
sigRequest := &irma.SignatureRequest{}
json.Unmarshal(sigRequestJSON, sigRequest)
ms := ManualSessionHandler{
t: t,
c: channel,
sigRequest: sigRequest,
}
ms := createManualSessionHandler(request, request, t)
client.NewManualSession(request, &ms)
if err := <-channel; err != nil {
if err := <-ms.errorChannel; err != nil {
test.ClearTestStorage(t)
t.Fatal(*err)
}
// No errors, obtain proof result from channel
if result := <-ms.resultChannel; result != VALID {
t.Logf("Invalid proof result: %v Expected: %v", result, VALID)
t.Fail()
}
test.ClearTestStorage(t)
}
func TestManualSessionInvalidProof(t *testing.T) {
invalidate = true
channel := make(chan *irma.SessionError)
client = parseStorage(t)
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
sigRequestJSON := []byte(request)
sigRequest := &irma.SignatureRequest{}
json.Unmarshal(sigRequestJSON, sigRequest)
ms := ManualSessionHandler{
t: t,
c: channel,
sigRequest: sigRequest,
}
ms := createManualSessionHandler(request, request, t)
client = parseStorage(t)
client.NewManualSession(request, &ms)
if err := <-channel; err.ErrorType != "Proof does not verify" {
test.ClearTestStorage(t)
if err := <-ms.errorChannel; err != nil {
test.ClearTestStorage(t)
t.Fatal(*err)
}
// 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)
t.Fail()
}
test.ClearTestStorage(t)
}
......@@ -164,19 +267,16 @@ func (sh *ManualSessionHandler) Success(irmaAction irma.Action, result string) {
// Make proof corrupt if we want to test invalid proofs
result = corruptProofString(result)
if !verifySig(client, result, sh.sigRequest) {
sh.c <- &irma.SessionError{
ErrorType: irma.ErrorType("Proof does not verify"),
}
return
}
go func() {
sh.resultChannel <- VerifySig(client.Configuration, result, sh.sigVerifyRequest)
}()
}
sh.c <- nil
sh.errorChannel <- nil
}
func (sh *ManualSessionHandler) UnsatisfiableRequest(irmaAction irma.Action, missingAttributes irma.AttributeDisjunctionList) {
// This function is called from main thread, which blocks go channel, so need go routine here
go func() {
sh.c <- &irma.SessionError{
sh.errorChannel <- &irma.SessionError{
ErrorType: irma.ErrorType("UnsatisfiableRequest"),
}
}()
......@@ -201,29 +301,18 @@ func (sh *ManualSessionHandler) RequestIssuancePermission(request irma.IssuanceR
// These handlers should not be called, fail test if they are called
func (sh *ManualSessionHandler) Cancelled(irmaAction irma.Action) {
sh.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("Session was cancelled")})
sh.errorChannel <- &irma.SessionError{Err: errors.New("Session was cancelled")}
}
func (sh *ManualSessionHandler) KeyshareEnrollmentMissing(manager irma.SchemeManagerIdentifier) {
sh.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.Errorf("Missing keyshare server %s", manager.String())})
func (sh *ManualSessionHandler) MissingKeyshareEnrollment(manager irma.SchemeManagerIdentifier) {
sh.errorChannel <- &irma.SessionError{Err: errors.Errorf("Missing keyshare server %s", manager.String())}
}
func (sh *ManualSessionHandler) RequestSchemeManagerPermission(manager *irma.SchemeManager, callback func(proceed bool)) {
sh.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("Unexpected session type")})
sh.errorChannel <- &irma.SessionError{Err: errors.New("Unexpected session type")}
}
func (sh *ManualSessionHandler) RequestVerificationPermission(request irma.DisclosureRequest, verifierName string, ph PermissionHandler) {
sh.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("Unexpected session type")})
sh.errorChannel <- &irma.SessionError{Err: errors.New("Unexpected session type")}
}
func (sh *ManualSessionHandler) Failure(irmaAction irma.Action, err *irma.SessionError) {
fmt.Println(err.Err)
select {
case sh.c <- err:
// nop
default:
sh.t.Fatal(err)
}
}
func (sh *ManualSessionHandler) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
sh.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("KeyshareBlocked")})
}
func (sh *ManualSessionHandler) KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier) {
sh.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("KeyshareEnrollmentIncomplete")})
sh.errorChannel <- err
}
......@@ -8,14 +8,24 @@ import (
"math/big"
)
func extractPublicKeys(client *Client, proofList *gabi.ProofList) ([]*gabi.PublicKey, error) {
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], client.Configuration) // index 1 is metadata attribute
metadata := irma.MetadataFromInt(proof.ADisclosed[1], configuration) // index 1 is metadata attribute
publicKey, err := metadata.PublicKey()
if err != nil {
return nil, err
......@@ -29,14 +39,14 @@ func extractPublicKeys(client *Client, proofList *gabi.ProofList) ([]*gabi.Publi
return publicKeys, nil
}
func extractDisclosedCredentials(client *Client, proofList *gabi.ProofList) ([]*irma.CredentialInfo, error) {
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, client.Configuration)
irmaCredentialInfo, err := irma.NewCredentialInfoFromADisclosed(proof.AResponses, proof.ADisclosed, configuration)
if err != nil {
return nil, err
}
......@@ -48,25 +58,34 @@ func extractDisclosedCredentials(client *Client, proofList *gabi.ProofList) ([]*
return credentials, nil
}
func checkProofWithRequest(client *Client, proofList *gabi.ProofList, sigRequest *irma.SignatureRequest) bool {
credentials, err := extractDisclosedCredentials(client, proofList)
func checkProofWithRequest(configuration *irma.Configuration, proofList *gabi.ProofList, sigRequest *irma.SignatureRequest) ProofStatus {
credentials, err := extractDisclosedCredentials(configuration, proofList)
if err != nil {
fmt.Println(err)
return false
return INVALID_CRYPTO
}
for _, content := range sigRequest.Content {
if !content.SatisfyDisclosed(credentials, client.Configuration) {
return false
if !content.SatisfyDisclosed(credentials, configuration) {
return MISSING_ATTRIBUTES
}
}
return true
// Check if a credential is expired
for _, cred := range credentials {
if cred.IsExpired() {
return EXPIRED
}
}
return VALID
}
func verify(client *Client, proofList *gabi.ProofList, context *big.Int, nonce *big.Int, isSig bool) bool {
// 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(client, proofList)
pks, err := extractPublicKeys(configuration, proofList)
if err != nil {
fmt.Printf("Error extracting public key: %v\n", err)
return false
......@@ -76,7 +95,7 @@ func verify(client *Client, proofList *gabi.ProofList, context *big.Int, nonce *
}
// Verify a signature proof and check if the attributes match the attributes in the original request
func verifySig(client *Client, proofString string, sigRequest *irma.SignatureRequest) bool {
func VerifySig(configuration *irma.Configuration, proofString string, sigRequest *irma.SignatureRequest) ProofStatus {
// First, unmarshal proof and check if all the attributes in the proofstring match the signature request
var proofList gabi.ProofList
......@@ -85,14 +104,14 @@ func verifySig(client *Client, proofString string, sigRequest *irma.SignatureReq
err := proofList.UnmarshalJSON(proofBytes)
if err != nil {
fmt.Printf("Error unmarshalling JSON: %v\n", err)
return false
return INVALID_JSON
}
// Now, cryptographically verify the signature
if !verify(client, &proofList, sigRequest.GetContext(), sigRequest.GetNonce(), true) {
return false
if !verify(configuration, &proofList, sigRequest.GetContext(), sigRequest.GetNonce(), true) {
return INVALID_CRYPTO
}
// Finally, check whether attribute values in proof satisfy the original signature request
return checkProofWithRequest(client, &proofList, sigRequest)
return checkProofWithRequest(configuration, &proofList, sigRequest)
}
......@@ -269,6 +269,11 @@ func (sr *SignatureRequest) GetNonce() *big.Int {
return new(big.Int).SetBytes(asn1hash[:])
}
// Check if Timestamp is before other Timestamp. Used for checking expiry of attributes
func (t Timestamp) Before(u Timestamp) bool {
return time.Time(t).Before(time.Time(u))
}
// MarshalJSON marshals a timestamp.
func (t *Timestamp) MarshalJSON() ([]byte, error) {
ts := time.Time(*t).Unix()
......
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