Commit 35fb735e authored by Sietse Ringers's avatar Sietse Ringers
Browse files

Load Idemix public keys on demand

parent 4b0c6374
......@@ -134,11 +134,15 @@ func (attr *MetadataAttribute) Bytes() []byte {
// PublicKey extracts identifier of the Idemix public key with which this instance was signed,
// and returns this public key.
func (attr *MetadataAttribute) PublicKey() *gabi.PublicKey {
func (attr *MetadataAttribute) PublicKey() (*gabi.PublicKey, error) {
if attr.pk == nil {
attr.pk = attr.store.PublicKey(attr.CredentialType().IssuerIdentifier(), attr.KeyCounter())
var err error
attr.pk, err = attr.store.PublicKey(attr.CredentialType().IssuerIdentifier(), attr.KeyCounter())
if err != nil {
return nil, err
}
}
return attr.pk
return attr.pk, nil
}
// Version returns the metadata version of this instance
......
......@@ -53,14 +53,18 @@ func NewCredentialInfo(ints []*big.Int, store *ConfigurationStore) *CredentialIn
}
}
func newCredential(gabicred *gabi.Credential, store *ConfigurationStore) (cred *credential) {
func newCredential(gabicred *gabi.Credential, store *ConfigurationStore) (*credential, error) {
meta := MetadataFromInt(gabicred.Attributes[1], store)
cred = &credential{
cred := &credential{
Credential: gabicred,
MetadataAttribute: meta,
}
cred.Pk = store.PublicKey(meta.CredentialType().IssuerIdentifier(), cred.KeyCounter())
return
var err error
cred.Pk, err = store.PublicKey(meta.CredentialType().IssuerIdentifier(), cred.KeyCounter())
if err != nil {
return nil, err
}
return cred, nil
}
// Len implements sort.Interface.
......
......@@ -4,8 +4,6 @@ import (
"encoding/xml"
"errors"
"github.com/mhe/gabi"
)
// SchemeManager describes a scheme manager.
......@@ -131,21 +129,3 @@ func (sm *SchemeManager) Identifier() SchemeManagerIdentifier {
func (sm *SchemeManager) Distributed() bool {
return len(sm.KeyshareServer) > 0
}
// CurrentPublicKey returns the latest known public key of the issuer identified by this instance.
func (id *Issuer) CurrentPublicKey(store *ConfigurationStore) *gabi.PublicKey {
keys := store.PublicKeys[id.Identifier()]
if keys == nil || len(keys) == 0 {
return nil
}
return keys[len(keys)-1]
}
// PublicKey returns the specified public key of the issuer identified by this instance.
func (id *Issuer) PublicKey(index int, store *ConfigurationStore) *gabi.PublicKey {
keys := store.PublicKeys[id.Identifier()]
if keys == nil || index >= len(keys) {
return nil
}
return keys[index]
}
......@@ -10,7 +10,6 @@ import (
"encoding/json"
"github.com/mhe/gabi"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
......@@ -47,7 +46,7 @@ func parseStorage(t *testing.T) *CredentialManager {
}
func teardown(t *testing.T) {
assert.NoError(t, os.RemoveAll("testdata/storage/test"))
require.NoError(t, os.RemoveAll("testdata/storage/test"))
}
// A convenience function for initializing big integers from known correct (10
......@@ -58,33 +57,39 @@ func s2big(s string) (r *big.Int) {
}
func parseAndroidStorage(t *testing.T, manager *CredentialManager) {
assert.NoError(t, manager.ParseAndroidStorage(), "ParseAndroidStorage() failed")
require.NoError(t, manager.ParseAndroidStorage(), "ParseAndroidStorage() failed")
}
func verifyManagerIsUnmarshaled(t *testing.T, manager *CredentialManager) {
cred, err := manager.credential(NewCredentialTypeIdentifier("irma-demo.RU.studentCard"), 0)
assert.NoError(t, err, "could not fetch credential")
assert.NotNil(t, cred, "Credential should exist")
assert.NotNil(t, cred.Attributes[0], "Metadata attribute of irma-demo.RU.studentCard should not be nil")
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 = manager.credential(NewCredentialTypeIdentifier("test.test.mijnirma"), 0)
assert.NoError(t, err, "could not fetch credential")
assert.NotNil(t, cred, "Credential should exist")
assert.NotNil(t, cred.Signature.KeyshareP)
require.NoError(t, err, "could not fetch credential")
require.NotNil(t, cred, "Credential should exist")
require.NotNil(t, cred.Signature.KeyshareP)
assert.NotEmpty(t, manager.CredentialInfoList())
require.NotEmpty(t, manager.CredentialInfoList())
assert.True(t,
cred.Signature.Verify(cred.PublicKey(), cred.Attributes),
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, manager *CredentialManager) {
var pk *gabi.PublicKey
var err error
for credtype, credsmap := range manager.credentials {
for index, cred := range credsmap {
pk, err = cred.PublicKey()
require.NoError(t, err)
require.True(t,
cred.Credential.Signature.Verify(cred.PublicKey(), cred.Attributes),
cred.Credential.Signature.Verify(pk, cred.Attributes),
"Credential %s-%d was invalid", credtype.String(), index,
)
require.Equal(t, cred.Attributes[0], manager.secretkey,
......@@ -123,30 +128,33 @@ func verifyKeyshareIsUnmarshaled(t *testing.T, manager *CredentialManager) {
}
func verifyStoreIsLoaded(t *testing.T, store *ConfigurationStore) {
assert.NotNil(t, store.Issuers[NewIssuerIdentifier("irma-demo.RU")].CurrentPublicKey(store).N, "irma-demo.RU public key has no modulus")
assert.Equal(t,
pk, err := store.PublicKey(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[NewSchemeManagerIdentifier("irma-demo")].Name["en"],
"irma-demo scheme manager has unexpected name")
assert.Equal(t,
require.Equal(t,
"Radboud Universiteit Nijmegen",
store.Issuers[NewIssuerIdentifier("irma-demo.RU")].Name["en"],
"irma-demo.RU issuer has unexpected name")
assert.Equal(t,
require.Equal(t,
"Student Card",
store.Credentials[NewCredentialTypeIdentifier("irma-demo.RU.studentCard")].ShortName["en"],
"irma-demo.RU.studentCard has unexpected name")
assert.Equal(t,
require.Equal(t,
"studentID",
store.Credentials[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])
assert.Contains(t, store.reverseHashes, "1stqlPad5edpfS1Na1U+DA==",
require.Contains(t, store.reverseHashes, "1stqlPad5edpfS1Na1U+DA==",
"irma-demo.RU.studentCard had improper hash")
assert.Contains(t, store.reverseHashes, "CLjnADMBYlFcuGOT7Z0xRg==",
require.Contains(t, store.reverseHashes, "CLjnADMBYlFcuGOT7Z0xRg==",
"irma-demo.MijnOverheid.root had improper hash")
}
......@@ -198,17 +206,17 @@ func TestMetadataCompatibility(t *testing.T) {
// An actual metadata attribute of an IRMA credential extracted from the IRMA app
attr := MetadataFromInt(s2big("49043481832371145193140299771658227036446546573739245068"), store)
assert.NotNil(t, attr.CredentialType(), "attr.CredentialType() should not be nil")
require.NotNil(t, attr.CredentialType(), "attr.CredentialType() should not be nil")
assert.Equal(t,
require.Equal(t,
NewCredentialTypeIdentifier("irma-demo.RU.studentCard"),
attr.CredentialType().Identifier(),
"Metadata credential type was not irma-demo.RU.studentCard",
)
assert.Equal(t, byte(0x02), attr.Version(), "Unexpected metadata version")
assert.Equal(t, time.Unix(1499904000, 0), attr.SigningDate(), "Unexpected signing date")
assert.Equal(t, time.Unix(1516233600, 0), attr.Expiry(), "Unexpected expiry date")
assert.Equal(t, 2, attr.KeyCounter(), "Unexpected key counter")
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)
}
......
......@@ -95,15 +95,21 @@ func (cm *CredentialManager) credential(id CredentialTypeIdentifier, counter int
return nil, err
}
meta := MetadataFromInt(ints[1], cm.Store)
pk := meta.PublicKey()
pk, err := meta.PublicKey()
if err != nil {
return nil, err
}
if pk == nil {
return nil, errors.New("unknown public key")
}
cred := newCredential(&gabi.Credential{
cred, err := newCredential(&gabi.Credential{
Attributes: ints,
Signature: sig,
Pk: pk,
}, cm.Store)
if err != nil {
return nil, err
}
cm.credentials[id][counter] = cred
}
......@@ -263,7 +269,10 @@ func (cm *CredentialManager) IssuanceProofBuilders(request *IssuanceRequest) (ga
proofBuilders := gabi.ProofBuilderList([]gabi.ProofBuilder{})
for _, futurecred := range request.Credentials {
pk := cm.Store.PublicKey(futurecred.Credential.IssuerIdentifier(), futurecred.KeyCounter)
pk, err := cm.Store.PublicKey(futurecred.Credential.IssuerIdentifier(), futurecred.KeyCounter)
if err != nil {
return nil, err
}
credBuilder := gabi.NewCredentialBuilder(pk, request.GetContext(), cm.secretkey, state.nonce2)
request.state.builders = append(request.state.builders, credBuilder)
proofBuilders = append(proofBuilders, credBuilder)
......@@ -297,7 +306,7 @@ func (cm *CredentialManager) ConstructCredentials(msg []*gabi.IssueSignatureMess
// First collect all credentials in a slice, so that if one of them induces an error,
// we save none of them to fail the session cleanly
creds := []*gabi.Credential{}
gabicreds := []*gabi.Credential{}
for i, sig := range msg {
attrs, err := request.Credentials[i].AttributeList(cm.Store)
if err != nil {
......@@ -307,11 +316,15 @@ func (cm *CredentialManager) ConstructCredentials(msg []*gabi.IssueSignatureMess
if err != nil {
return err
}
creds = append(creds, cred)
gabicreds = append(gabicreds, cred)
}
for _, cred := range creds {
cm.addCredential(newCredential(cred, cm.Store), true)
for _, gabicred := range gabicreds {
newcred, err := newCredential(gabicred, cm.Store)
if err != nil {
return err
}
cm.addCredential(newcred, true)
}
return nil
......
......@@ -76,7 +76,10 @@ func (cm *CredentialManager) ParseAndroidStorage() (err error) {
if oldcred.SharedPoints != nil && len(oldcred.SharedPoints) > 0 {
gabicred.Signature.KeyshareP = oldcred.SharedPoints[0]
}
cred := newCredential(gabicred, cm.Store)
cred, err := newCredential(gabicred, cm.Store)
if err != nil {
return err
}
if cred.CredentialType() == nil {
return errors.New("cannot add unknown credential type")
}
......
......@@ -17,9 +17,10 @@ type ConfigurationStore struct {
SchemeManagers map[SchemeManagerIdentifier]*SchemeManager
Issuers map[IssuerIdentifier]*Issuer
Credentials map[CredentialTypeIdentifier]*CredentialType
PublicKeys map[IssuerIdentifier][]*gabi.PublicKey
publicKeys map[IssuerIdentifier][]*gabi.PublicKey
reverseHashes map[string]CredentialTypeIdentifier
path string
initialized bool
}
......@@ -28,20 +29,28 @@ func NewConfigurationStore() (store *ConfigurationStore) {
SchemeManagers: make(map[SchemeManagerIdentifier]*SchemeManager),
Issuers: make(map[IssuerIdentifier]*Issuer),
Credentials: make(map[CredentialTypeIdentifier]*CredentialType),
PublicKeys: make(map[IssuerIdentifier][]*gabi.PublicKey),
publicKeys: make(map[IssuerIdentifier][]*gabi.PublicKey),
reverseHashes: make(map[string]CredentialTypeIdentifier),
}
return
}
// PublicKey returns the specified public key, or nil if not present in the ConfigurationStore.
func (store *ConfigurationStore) PublicKey(id IssuerIdentifier, counter int) *gabi.PublicKey {
if list, ok := store.PublicKeys[id]; ok {
if len(list) > counter {
return list[counter]
func (store *ConfigurationStore) PublicKey(id IssuerIdentifier, counter int) (*gabi.PublicKey, error) {
if _, contains := store.publicKeys[id]; !contains {
store.publicKeys[id] = []*gabi.PublicKey{}
err := store.parseKeysFolder(id)
if err != nil {
return nil, err
}
}
return nil
list := store.publicKeys[id]
if len(list) > counter {
return list[counter], nil
}
return nil, nil
}
func (store *ConfigurationStore) addReverseHash(credid CredentialTypeIdentifier) {
......@@ -64,6 +73,7 @@ func (store *ConfigurationStore) IsInitialized() bool {
// ParseFolder populates the current store by parsing the specified irma_configuration folder,
// listing the containing scheme managers, issuers, credential types and public keys.
func (store *ConfigurationStore) ParseFolder(path string) error {
store.path = path
err := iterateSubfolders(path, func(dir string) error {
manager := &SchemeManager{}
exists, err := pathToDescription(dir+"/description.xml", manager)
......@@ -95,13 +105,17 @@ func (store *ConfigurationStore) parseIssuerFolders(path string) error {
if err = store.parseCredentialsFolder(dir + "/Issues/"); err != nil {
return err
}
return store.parseKeysFolder(issuer, dir+"/PublicKeys/")
}
return nil
})
}
func (store *ConfigurationStore) parseKeysFolder(issuer *Issuer, path string) error {
func (store *ConfigurationStore) parseKeysFolder(issuerid IssuerIdentifier) error {
issuer := store.Issuers[issuerid]
path := store.path + "/" +
issuer.Identifier().SchemeManagerIdentifier().String() +
"/" + issuer.ID + "/PublicKeys/"
for i := 0; ; i++ {
file := path + strconv.Itoa(i) + ".xml"
if _, err := os.Stat(file); err != nil {
......@@ -112,7 +126,7 @@ func (store *ConfigurationStore) parseKeysFolder(issuer *Issuer, path string) er
return err
}
pk.Issuer = issuer.Identifier().String()
store.PublicKeys[issuer.Identifier()] = append(store.PublicKeys[issuer.Identifier()], pk)
store.publicKeys[issuer.Identifier()] = append(store.publicKeys[issuer.Identifier()], pk)
}
return nil
}
......
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