Commit fb9733ed authored by Sietse Ringers's avatar Sietse Ringers
Browse files

Renaming, 3: unit tests

parent 12301e6b
package irmaclient
import (
"fmt"
"math/big"
"os"
"testing"
"time"
"encoding/json"
"github.com/credentials/irmago"
"github.com/credentials/irmago/internal/fs"
"github.com/mhe/gabi"
"github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
retCode := m.Run()
// TODO make testdata/storage
err := os.RemoveAll("testdata/storage/test")
if err != nil {
fmt.Println("Could not delete test storage")
os.Exit(1)
}
os.Exit(retCode)
}
type IgnoringClientHandler struct{}
func (i *IgnoringClientHandler) UpdateConfigurationStore(new *irmago.IrmaIdentifierSet) {}
func (i *IgnoringClientHandler) UpdateAttributes() {}
func (i *IgnoringClientHandler) EnrollmentError(manager irmago.SchemeManagerIdentifier, err error) {}
func (i *IgnoringClientHandler) EnrollmentSuccess(manager irmago.SchemeManagerIdentifier) {}
func parseStorage(t *testing.T) *Client {
exists, err := fs.PathExists("testdata/storage/test")
require.NoError(t, err, "pathexists() failed")
if !exists {
require.NoError(t, os.Mkdir("testdata/storage/test", 0755), "Could not create test storage")
}
manager, err := NewClient(
"testdata/storage/test",
"testdata/irma_configuration",
"testdata/oldstorage",
&IgnoringClientHandler{},
)
require.NoError(t, err)
return manager
}
func teardown(t *testing.T) {
require.NoError(t, os.RemoveAll("testdata/storage/test"))
}
// A convenience function for initializing big integers from known correct (10
// base) strings. Use with care, errors are ignored.
func s2big(s string) (r *big.Int) {
r, _ = new(big.Int).SetString(s, 10)
return
}
func verifyManagerIsUnmarshaled(t *testing.T, client *Client) {
cred, err := client.credential(irmago.NewCredentialTypeIdentifier("irma-demo.RU.studentCard"), 0)
require.NoError(t, err, "could not fetch credential")
require.NotNil(t, cred, "Credential should exist")
require.NotNil(t, cred.Attributes[0], "Metadata attribute of irma-demo.RU.studentCard should not be nil")
cred, err = client.credential(irmago.NewCredentialTypeIdentifier("test.test.mijnirma"), 0)
require.NoError(t, err, "could not fetch credential")
require.NotNil(t, cred, "Credential should exist")
require.NotNil(t, cred.Signature.KeyshareP)
require.NotEmpty(t, client.CredentialInfoList())
pk, err := cred.PublicKey()
require.NoError(t, err)
require.True(t,
cred.Signature.Verify(pk, cred.Attributes),
"Credential should be valid",
)
}
func verifyCredentials(t *testing.T, client *Client) {
var pk *gabi.PublicKey
var err error
for credtype, credsmap := range client.credentials {
for index, cred := range credsmap {
pk, err = cred.PublicKey()
require.NoError(t, err)
require.True(t,
cred.Credential.Signature.Verify(pk, cred.Attributes),
"Credential %s-%d was invalid", credtype.String(), index,
)
require.Equal(t, cred.Attributes[0], client.secretkey.Key,
"Secret key of credential %s-%d unequal to main secret key",
cred.CredentialType().Identifier().String(), index,
)
}
}
}
func verifyPaillierKey(t *testing.T, PrivateKey *paillierPrivateKey) {
require.NotNil(t, PrivateKey)
require.NotNil(t, PrivateKey.L)
require.NotNil(t, PrivateKey.U)
require.NotNil(t, PrivateKey.PublicKey.N)
require.Equal(t, big.NewInt(1), new(big.Int).Exp(big.NewInt(2), PrivateKey.L, PrivateKey.N))
require.Equal(t, PrivateKey.NSquared, new(big.Int).Exp(PrivateKey.N, big.NewInt(2), nil))
plaintext := "Hello Paillier!"
ciphertext, err := PrivateKey.Encrypt([]byte(plaintext))
require.NoError(t, err)
decrypted, err := PrivateKey.Decrypt(ciphertext)
require.NoError(t, err)
require.Equal(t, plaintext, string(decrypted))
}
func verifyKeyshareIsUnmarshaled(t *testing.T, client *Client) {
require.NotNil(t, client.paillierKeyCache)
require.NotNil(t, client.keyshareServers)
test := irmago.NewSchemeManagerIdentifier("test")
require.Contains(t, client.keyshareServers, test)
kss := client.keyshareServers[test]
require.NotEmpty(t, kss.Nonce)
verifyPaillierKey(t, kss.PrivateKey)
verifyPaillierKey(t, client.paillierKeyCache)
}
// TODO move up to irmago?
func verifyStoreIsLoaded(t *testing.T, store *irmago.ConfigurationStore, android bool) {
require.Contains(t, store.SchemeManagers, irmago.NewSchemeManagerIdentifier("irma-demo"))
require.Contains(t, store.SchemeManagers, irmago.NewSchemeManagerIdentifier("test"))
if android {
require.Contains(t, store.SchemeManagers, irmago.NewSchemeManagerIdentifier("test2"))
}
pk, err := store.PublicKey(irmago.NewIssuerIdentifier("irma-demo.RU"), 0)
require.NoError(t, err)
require.NotNil(t, pk)
require.NotNil(t, pk.N, "irma-demo.RU public key has no modulus")
require.Equal(t,
"Irma Demo",
store.SchemeManagers[irmago.NewSchemeManagerIdentifier("irma-demo")].Name["en"],
"irma-demo scheme manager has unexpected name")
require.Equal(t,
"Radboud University Nijmegen",
store.Issuers[irmago.NewIssuerIdentifier("irma-demo.RU")].Name["en"],
"irma-demo.RU issuer has unexpected name")
require.Equal(t,
"Student Card",
store.CredentialTypes[irmago.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")].ShortName["en"],
"irma-demo.RU.studentCard has unexpected name")
require.Equal(t,
"studentID",
store.CredentialTypes[irmago.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")].Attributes[2].ID,
"irma-demo.RU.studentCard.studentID has unexpected name")
// Hash algorithm pseudocode:
// Base64(SHA256("irma-demo.RU.studentCard")[0:16])
//require.Contains(t, store.reverseHashes, "1stqlPad5edpfS1Na1U+DA==",
// "irma-demo.RU.studentCard had improper hash")
//require.Contains(t, store.reverseHashes, "CLjnADMBYlFcuGOT7Z0xRg==",
// "irma-demo.MijnOverheid.root had improper hash")
}
func TestAndroidParse(t *testing.T) {
client := parseStorage(t)
verifyStoreIsLoaded(t, client.ConfigurationStore, true)
verifyManagerIsUnmarshaled(t, client)
verifyCredentials(t, client)
verifyKeyshareIsUnmarshaled(t, client)
teardown(t)
}
func TestUnmarshaling(t *testing.T) {
client := parseStorage(t)
// Do session so we can examine its log item later
logs, err := client.Logs()
require.NoError(t, err)
jwt := getIssuanceJwt("testip", irmago.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"))
sessionHelper(t, jwt, "issue", client)
newclient, err := NewClient("testdata/storage/test", "testdata/irma_configuration", "testdata/oldstorage", nil)
require.NoError(t, err)
verifyManagerIsUnmarshaled(t, newclient)
verifyCredentials(t, newclient)
verifyKeyshareIsUnmarshaled(t, newclient)
newlogs, err := newclient.Logs()
require.NoError(t, err)
require.True(t, len(newlogs) == len(logs)+1)
entry := newlogs[len(newlogs)-1]
require.NotNil(t, entry)
sessionjwt, err := entry.Jwt()
require.NoError(t, err)
require.Equal(t, "testip", sessionjwt.(*irmago.IdentityProviderJwt).ServerName)
require.NoError(t, err)
require.NotEmpty(t, entry.Disclosed)
require.NotEmpty(t, entry.Received)
response, err := entry.GetResponse()
require.NoError(t, err)
require.NotNil(t, response)
require.IsType(t, &gabi.IssueCommitmentMessage{}, response)
teardown(t)
}
func TestMetadataAttribute(t *testing.T) {
metadata := irmago.NewMetadataAttribute()
if metadata.Version() != 0x02 {
t.Errorf("Unexpected metadata version: %d", metadata.Version())
}
expiry := metadata.SigningDate().Unix() + int64(metadata.ValidityDuration()*irmago.ExpiryFactor)
if !time.Unix(expiry, 0).Equal(metadata.Expiry()) {
t.Errorf("Invalid signing date")
}
if metadata.KeyCounter() != 0 {
t.Errorf("Unexpected key counter")
}
}
func TestMetadataCompatibility(t *testing.T) {
store, err := irmago.NewConfigurationStore("testdata/irma_configuration", "")
require.NoError(t, err)
require.NoError(t, store.ParseFolder())
// An actual metadata attribute of an IRMA credential extracted from the IRMA app
attr := irmago.MetadataFromInt(s2big("49043481832371145193140299771658227036446546573739245068"), store)
require.NotNil(t, attr.CredentialType(), "attr.CredentialType() should not be nil")
require.Equal(t,
irmago.NewCredentialTypeIdentifier("irma-demo.RU.studentCard"),
attr.CredentialType().Identifier(),
"Metadata credential type was not irma-demo.RU.studentCard",
)
require.Equal(t, byte(0x02), attr.Version(), "Unexpected metadata version")
require.Equal(t, time.Unix(1499904000, 0), attr.SigningDate(), "Unexpected signing date")
require.Equal(t, time.Unix(1516233600, 0), attr.Expiry(), "Unexpected expiry date")
require.Equal(t, 2, attr.KeyCounter(), "Unexpected key counter")
teardown(t)
}
func TestCandidates(t *testing.T) {
client := parseStorage(t)
attrtype := irmago.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
disjunction := &irmago.AttributeDisjunction{
Attributes: []irmago.AttributeTypeIdentifier{attrtype},
}
attrs := client.Candidates(disjunction)
require.NotNil(t, attrs)
require.Len(t, attrs, 1)
attr := attrs[0]
require.NotNil(t, attr)
require.Equal(t, attr.Type, attrtype)
disjunction = &irmago.AttributeDisjunction{
Attributes: []irmago.AttributeTypeIdentifier{attrtype},
Values: map[irmago.AttributeTypeIdentifier]string{attrtype: "456"},
}
attrs = client.Candidates(disjunction)
require.NotNil(t, attrs)
require.Len(t, attrs, 1)
disjunction = &irmago.AttributeDisjunction{
Attributes: []irmago.AttributeTypeIdentifier{attrtype},
Values: map[irmago.AttributeTypeIdentifier]string{attrtype: "foobarbaz"},
}
attrs = client.Candidates(disjunction)
require.NotNil(t, attrs)
require.Empty(t, attrs)
teardown(t)
}
func TestTimestamp(t *testing.T) {
mytime := irmago.Timestamp(time.Unix(1500000000, 0))
timestruct := struct{ Time *irmago.Timestamp }{Time: &mytime}
bytes, err := json.Marshal(timestruct)
require.NoError(t, err)
timestruct = struct{ Time *irmago.Timestamp }{}
require.NoError(t, json.Unmarshal(bytes, &timestruct))
require.Equal(t, time.Time(*timestruct.Time).Unix(), int64(1500000000))
}
func TestServiceProvider(t *testing.T) {
var spjwt irmago.ServiceProviderJwt
var spjson = `{
"sprequest": {
"validity": 60,
"timeout": 60,
"request": {
"content": [
{
"label": "ID",
"attributes": ["irma-demo.RU.studentCard.studentID"]
}
]
}
}
}`
require.NoError(t, json.Unmarshal([]byte(spjson), &spjwt))
require.NotNil(t, spjwt.Request.Request.Content)
require.NotEmpty(t, spjwt.Request.Request.Content)
require.NotNil(t, spjwt.Request.Request.Content[0])
require.NotEmpty(t, spjwt.Request.Request.Content[0])
require.NotNil(t, spjwt.Request.Request.Content[0].Attributes)
require.NotEmpty(t, spjwt.Request.Request.Content[0].Attributes)
require.Equal(t, spjwt.Request.Request.Content[0].Attributes[0].Name(), "studentID")
require.NotNil(t, spjwt.Request.Request.Content.Find(irmago.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")))
}
func TestPaillier(t *testing.T) {
client := parseStorage(t)
challenge, _ := gabi.RandomBigInt(256)
comm, _ := gabi.RandomBigInt(1000)
resp, _ := gabi.RandomBigInt(1000)
sk := client.paillierKey(true)
bytes, err := sk.Encrypt(challenge.Bytes())
require.NoError(t, err)
cipher := new(big.Int).SetBytes(bytes)
bytes, err = sk.Encrypt(comm.Bytes())
require.NoError(t, err)
commcipher := new(big.Int).SetBytes(bytes)
// [[ c ]]^resp * [[ comm ]]
cipher.Exp(cipher, resp, sk.NSquared).Mul(cipher, commcipher).Mod(cipher, sk.NSquared)
bytes, err = sk.Decrypt(cipher.Bytes())
require.NoError(t, err)
plaintext := new(big.Int).SetBytes(bytes)
expected := new(big.Int).Set(challenge)
expected.Mul(expected, resp).Add(expected, comm)
require.Equal(t, plaintext, expected)
teardown(t)
}
func TestCredentialRemoval(t *testing.T) {
client := parseStorage(t)
id := irmago.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
id2 := irmago.NewCredentialTypeIdentifier("test.test.mijnirma")
cred, err := client.credential(id, 0)
require.NoError(t, err)
require.NotNil(t, cred)
err = client.RemoveCredentialByHash(cred.AttributeList().Hash())
require.NoError(t, err)
cred, err = client.credential(id, 0)
require.NoError(t, err)
require.Nil(t, cred)
cred, err = client.credential(id2, 0)
require.NoError(t, err)
require.NotNil(t, cred)
err = client.RemoveCredential(id2, 0)
require.NoError(t, err)
cred, err = client.credential(id2, 0)
require.NoError(t, err)
require.Nil(t, cred)
teardown(t)
}
func TestDownloadSchemeManager(t *testing.T) {
client := parseStorage(t)
require.NoError(t, client.ConfigurationStore.RemoveSchemeManager(irmago.NewSchemeManagerIdentifier("irma-demo")))
url := "https://raw.githubusercontent.com/credentials/irma_configuration/translate/irma-demo"
sm, err := client.ConfigurationStore.DownloadSchemeManager(url)
require.NoError(t, err)
require.NotNil(t, sm)
require.NoError(t, client.ConfigurationStore.AddSchemeManager(sm))
jwt := getIssuanceJwt("testip", irmago.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"))
sessionHelper(t, jwt, "issue", client)
teardown(t)
}
package irmago
package irmaclient
// TODO +build integration
......@@ -10,44 +10,45 @@ import (
"fmt"
"testing"
"github.com/credentials/irmago"
"github.com/go-errors/errors"
"github.com/stretchr/testify/require"
)
type TestHandler struct {
t *testing.T
c chan *SessionError
c chan *irmago.SessionError
client *Client
}
func (th TestHandler) MissingKeyshareEnrollment(manager SchemeManagerIdentifier) {
th.Failure(ActionUnknown, &SessionError{Err: errors.Errorf("Missing keyshare server %s", manager.String())})
func (th TestHandler) MissingKeyshareEnrollment(manager irmago.SchemeManagerIdentifier) {
th.Failure(irmago.ActionUnknown, &irmago.SessionError{Err: errors.Errorf("Missing keyshare server %s", manager.String())})
}
func (th TestHandler) StatusUpdate(action Action, status Status) {}
func (th TestHandler) Success(action Action) {
func (th TestHandler) StatusUpdate(action irmago.Action, status irmago.Status) {}
func (th TestHandler) Success(action irmago.Action) {
th.c <- nil
}
func (th TestHandler) Cancelled(action Action) {
th.c <- &SessionError{}
func (th TestHandler) Cancelled(action irmago.Action) {
th.c <- &irmago.SessionError{}
}
func (th TestHandler) Failure(action Action, err *SessionError) {
func (th TestHandler) Failure(action irmago.Action, err *irmago.SessionError) {
select {
case th.c <- err:
default:
th.t.Fatal(err)
}
}
func (th TestHandler) UnsatisfiableRequest(action Action, missing AttributeDisjunctionList) {
th.c <- &SessionError{
ErrorType: ErrorType("UnsatisfiableRequest"),
func (th TestHandler) UnsatisfiableRequest(action irmago.Action, missing irmago.AttributeDisjunctionList) {
th.c <- &irmago.SessionError{
ErrorType: irmago.ErrorType("UnsatisfiableRequest"),
}
}
func (th TestHandler) RequestVerificationPermission(request DisclosureRequest, ServerName string, callback PermissionHandler) {
choice := &DisclosureChoice{
Attributes: []*AttributeIdentifier{},
func (th TestHandler) RequestVerificationPermission(request irmago.DisclosureRequest, ServerName string, callback PermissionHandler) {
choice := &irmago.DisclosureChoice{
Attributes: []*irmago.AttributeIdentifier{},
}
var candidates []*AttributeIdentifier
var candidates []*irmago.AttributeIdentifier
for _, disjunction := range request.Content {
candidates = th.client.Candidates(disjunction)
require.NotNil(th.t, candidates)
......@@ -56,51 +57,51 @@ func (th TestHandler) RequestVerificationPermission(request DisclosureRequest, S
}
callback(true, choice)
}
func (th TestHandler) RequestIssuancePermission(request IssuanceRequest, ServerName string, callback PermissionHandler) {
dreq := DisclosureRequest{
func (th TestHandler) RequestIssuancePermission(request irmago.IssuanceRequest, ServerName string, callback PermissionHandler) {
dreq := irmago.DisclosureRequest{
SessionRequest: request.SessionRequest,
Content: request.Disclose,
}
th.RequestVerificationPermission(dreq, ServerName, callback)
}
func (th TestHandler) RequestSignaturePermission(request SignatureRequest, ServerName string, callback PermissionHandler) {
func (th TestHandler) RequestSignaturePermission(request irmago.SignatureRequest, ServerName string, callback PermissionHandler) {
th.RequestVerificationPermission(request.DisclosureRequest, ServerName, callback)
}
func (th TestHandler) RequestSchemeManagerPermission(manager *SchemeManager, callback func(proceed bool)) {
func (th TestHandler) RequestSchemeManagerPermission(manager *irmago.SchemeManager, callback func(proceed bool)) {
callback(true)
}
func (th TestHandler) RequestPin(remainingAttempts int, callback PinHandler) {
callback(true, "12345")
}
func getDisclosureJwt(name string, id AttributeTypeIdentifier) interface{} {
return NewServiceProviderJwt(name, &DisclosureRequest{
Content: AttributeDisjunctionList([]*AttributeDisjunction{{
func getDisclosureJwt(name string, id irmago.AttributeTypeIdentifier) interface{} {
return irmago.NewServiceProviderJwt(name, &irmago.DisclosureRequest{
Content: irmago.AttributeDisjunctionList([]*irmago.AttributeDisjunction{{
Label: "foo",
Attributes: []AttributeTypeIdentifier{id},
Attributes: []irmago.AttributeTypeIdentifier{id},
}}),
})
}
func getSigningJwt(name string, id AttributeTypeIdentifier) interface{} {
return NewSignatureRequestorJwt(name, &SignatureRequest{
func getSigningJwt(name string, id irmago.AttributeTypeIdentifier) interface{} {
return irmago.NewSignatureRequestorJwt(name, &irmago.SignatureRequest{
Message: "test",
MessageType: "STRING",
DisclosureRequest: DisclosureRequest{
Content: AttributeDisjunctionList([]*AttributeDisjunction{{
DisclosureRequest: irmago.DisclosureRequest{
Content: irmago.AttributeDisjunctionList([]*irmago.AttributeDisjunction{{
Label: "foo",
Attributes: []AttributeTypeIdentifier{id},
Attributes: []irmago.AttributeTypeIdentifier{id},
}}),
},
})
}
func getIssuanceJwt(name string, id AttributeTypeIdentifier) interface{} {
expiry := Timestamp(NewMetadataAttribute().Expiry())
credid1 := NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
credid2 := NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")
return NewIdentityProviderJwt(name, &IssuanceRequest{
Credentials: []*CredentialRequest{
func getIssuanceJwt(name string, id irmago.AttributeTypeIdentifier) interface{} {
expiry := irmago.Timestamp(irmago.NewMetadataAttribute().Expiry())
credid1 := irmago.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
credid2 := irmago.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")
return irmago.NewIdentityProviderJwt(name, &irmago.IssuanceRequest{
Credentials: []*irmago.CredentialRequest{
{
Validity: &expiry,
CredentialTypeID: &credid1,
......@@ -118,17 +119,17 @@ func getIssuanceJwt(name string, id AttributeTypeIdentifier) interface{} {
},
},
},
Disclose: AttributeDisjunctionList{
&AttributeDisjunction{Label: "foo", Attributes: []AttributeTypeIdentifier{id}},
Disclose: irmago.AttributeDisjunctionList{
&irmago.AttributeDisjunction{Label: "foo", Attributes: []irmago.AttributeTypeIdentifier{id}},
},
})
}
// StartSession starts an IRMA session by posting the request,
// and retrieving the QR contents from the specified url.
func StartSession(request interface{}, url string) (*Qr, error) {
server := NewHTTPTransport(url)
var response Qr
func StartSession(request interface{}, url string) (*irmago.Qr, error) {
server := irmago.NewHTTPTransport(url)
var response irmago.Qr
err := server.Post("", &response, request)
if err != nil {
return nil, err
......@@ -137,7 +138,7 @@ func StartSession(request interface{}, url string) (*Qr, error) {
}
func TestSigningSession(t *testing.T) {
id := NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
id := irmago.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
name := "testsigclient"
jwtcontents := getSigningJwt(name, id)
......@@ -145,7 +146,7 @@ func TestSigningSession(t *testing.T) {
}
func TestDisclosureSession(t *testing.T) {
id := NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
id := irmago.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
name := "testsp"
jwtcontents := getDisclosureJwt(name, id)
......@@ -153,7 +154,7 @@ func TestDisclosureSession(t *testing.T) {
}
func TestIssuanceSession(t *testing.T) {
id := NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
id := irmago.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
name := "testip"
jwtcontents := getIssuanceJwt(name, id)
......@@ -166,7 +167,7 @@ func sessionHelper(t *testing.T, jwtcontents interface{}, url string, client *Cl
client = parseStorage(t)
}
url = "http://localhost:8081/irma_api_server/api/v2/" + url
url = "http://localhost:8088/irma_api_server/api/v2/" + url
//url = "https://demo.irmacard.org/tomcat/irma_api_server/api/v2/" + url
headerbytes, err := json.Marshal(&map[string]string{"alg": "none", "typ": "JWT"})
......@@ -182,7 +183,7 @@ func sessionHelper(t *testing.T, jwtcontents interface{}, url string, client *Cl
require.NoError(t, transportErr)
qr.URL = url + "/" + qr.URL
c := make(chan *SessionError)
c := make(chan *irmago.SessionError)