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

Separating storage methods from CredentialManager

parent 9c527942
......@@ -27,22 +27,38 @@ type ConfigurationStore struct {
// NewConfigurationStore returns a new configuration store. After this
// ParseFolder() should be called to parse the specified path.
func NewConfigurationStore(path string) (store *ConfigurationStore) {
func NewConfigurationStore(path string, assets string) (store *ConfigurationStore, err error) {
store = &ConfigurationStore{
SchemeManagers: make(map[SchemeManagerIdentifier]*SchemeManager),
Issuers: make(map[IssuerIdentifier]*Issuer),
Credentials: make(map[CredentialTypeIdentifier]*CredentialType),
publicKeys: make(map[IssuerIdentifier][]*gabi.PublicKey),
reverseHashes: make(map[string]CredentialTypeIdentifier),
path: path,
path: path,
}
var exist bool
if exist, err = PathExists(store.path); err != nil {
return nil, err
}
if !exist {
if err = ensureDirectoryExists(store.path); err != nil {
return nil, err
}
if assets != "" {
if err = store.Copy(assets, false); err != nil {
return nil, err
}
}
}
return
}
// ParseFolder populates the current store by parsing the storage path,
// listing the containing scheme managers, issuers and credential types.
func (store *ConfigurationStore) ParseFolder() error {
*store = *NewConfigurationStore(store.path) // Init all maps
// Init all maps
store.SchemeManagers = make(map[SchemeManagerIdentifier]*SchemeManager)
store.Issuers = make(map[IssuerIdentifier]*Issuer)
store.Credentials = make(map[CredentialTypeIdentifier]*CredentialType)
store.publicKeys = make(map[IssuerIdentifier][]*gabi.PublicKey)
store.reverseHashes = make(map[string]CredentialTypeIdentifier)
err := iterateSubfolders(store.path, func(dir string) error {
manager := &SchemeManager{}
......
......@@ -227,7 +227,8 @@ func TestMetadataAttribute(t *testing.T) {
}
func TestMetadataCompatibility(t *testing.T) {
store := NewConfigurationStore("testdata/irma_configuration")
store, err := 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
......@@ -248,8 +249,9 @@ func TestMetadataCompatibility(t *testing.T) {
}
func TestAttributeDisjunctionMarshaling(t *testing.T) {
store := NewConfigurationStore("testdata/irma_configuration")
store.ParseFolder()
store, err := NewConfigurationStore("testdata/irma_configuration", "")
require.NoError(t, err)
require.NoError(t, store.ParseFolder())
disjunction := AttributeDisjunction{}
var _ json.Unmarshaler = &disjunction
......
......@@ -15,13 +15,13 @@ import (
// CredentialManager manages credentials.
type CredentialManager struct {
secretkey *secretKey
storagePath string
attributes map[CredentialTypeIdentifier][]*AttributeList
credentials map[CredentialTypeIdentifier]map[int]*credential
keyshareServers map[SchemeManagerIdentifier]*keyshareServer
paillierKeyCache *paillierPrivateKey
logs []*LogEntry
storage storage
irmaConfigurationPath string
androidStoragePath string
ConfigurationStore *ConfigurationStore
......@@ -32,6 +32,91 @@ type secretKey struct {
Key *big.Int
}
// NewCredentialManager creates a new CredentialManager that uses the directory
// specified by storagePath for (de)serializing itself. irmaConfigurationPath
// is the path to a (possibly readonly) folder containing irma_configuration;
// androidStoragePath is an optional path to the files of the old android app
// (specify "" if you do not want to parse the old android app files),
// and keyshareHandler is used for when a registration to a keyshare server needs
// to happen.
// The credential manager returned by this function has been fully deserialized
// and is ready for use.
//
// NOTE: It is the responsibility of the caller that there exists a directory
// at storagePath!
func NewCredentialManager(
storagePath string,
irmaConfigurationPath string,
androidStoragePath string,
keyshareHandler KeyshareHandler,
) (*CredentialManager, error) {
var err error
if err = AssertPathExists(storagePath); err != nil {
return nil, err
}
if err = AssertPathExists(irmaConfigurationPath); err != nil {
return nil, err
}
var store *ConfigurationStore
if store, err = NewConfigurationStore(storagePath+"/irma_configuration", irmaConfigurationPath); err != nil {
return nil, err
}
if err = store.ParseFolder(); err != nil {
return nil, err
}
cm := &CredentialManager{
credentials: make(map[CredentialTypeIdentifier]map[int]*credential),
keyshareServers: make(map[SchemeManagerIdentifier]*keyshareServer),
attributes: make(map[CredentialTypeIdentifier][]*AttributeList),
irmaConfigurationPath: irmaConfigurationPath,
androidStoragePath: androidStoragePath,
ConfigurationStore: store,
storage: storage{storagePath: storagePath, ConfigurationStore: store},
}
// Ensure storage path exists, and populate it with necessary files
if err = cm.storage.ensureStorageExists(); err != nil {
return nil, err
}
// Perform new update functions from credentialManagerUpdates, if any
if err = cm.update(); err != nil {
return nil, err
}
// Load our stuff
if cm.secretkey, err = cm.storage.loadSecretKey(); err != nil {
return nil, err
}
if cm.attributes, err = cm.storage.loadAttributes(); err != nil {
return nil, err
}
if cm.paillierKeyCache, err = cm.storage.loadPaillierKeys(); err != nil {
return nil, err
}
if cm.keyshareServers, err = cm.storage.loadKeyshareServers(); err != nil {
return nil, err
}
unenrolled := cm.unenrolledKeyshareServers()
switch len(unenrolled) {
case 0: // nop
case 1:
if keyshareHandler == nil {
return nil, errors.New("Keyshare server found but no KeyshareHandler was given")
}
keyshareHandler.StartRegistration(unenrolled[0], func(email, pin string) {
cm.KeyshareEnroll(unenrolled[0].Identifier(), email, pin)
})
default:
return nil, errors.New("Too many keyshare servers")
}
return cm, nil
}
// CredentialInfoList returns a list of information of all contained credentials.
func (cm *CredentialManager) CredentialInfoList() CredentialInfoList {
list := CredentialInfoList([]*CredentialInfo{})
......@@ -57,7 +142,7 @@ func (cm *CredentialManager) remove(id CredentialTypeIdentifier, index int, stor
attrs := list[index]
cm.attributes[id] = append(list[:index], list[index+1:]...)
if storenow {
cm.storeAttributes()
cm.storage.storeAttributes(cm.attributes)
}
// Remove credential
......@@ -69,7 +154,7 @@ func (cm *CredentialManager) remove(id CredentialTypeIdentifier, index int, stor
}
// Remove signature from storage
if err := os.Remove(cm.signatureFilename(attrs)); err != nil {
if err := os.Remove(cm.storage.signatureFilename(attrs)); err != nil {
return err
}
......@@ -99,18 +184,10 @@ func (cm *CredentialManager) RemoveAllCredentials() error {
return err
}
}
if err := cm.storeAttributes(); err != nil {
if err := cm.storage.storeAttributes(cm.attributes); err != nil {
return err
}
return cm.storeLogs()
}
func (cm *CredentialManager) generateSecretKey() (*secretKey, error) {
key, err := gabi.RandomBigInt(gabi.DefaultSystemParameters[1024].Lm)
if err != nil {
return nil, err
}
return &secretKey{Key: key}, nil
return cm.storage.storeLogs(cm.logs)
}
// attrs returns cm.attributes[id], initializing it to an empty slice if neccesary
......@@ -168,7 +245,7 @@ func (cm *CredentialManager) credential(id CredentialTypeIdentifier, counter int
if attrs == nil { // We do not have the requested cred
return
}
sig, err := cm.loadSignature(attrs)
sig, err := cm.storage.loadSignature(attrs)
if err != nil {
return nil, err
}
......@@ -209,11 +286,11 @@ func (cm *CredentialManager) addCredential(cred *credential, storeAttributes boo
counter := len(cm.attributes[id]) - 1
cm.credentials[id][counter] = cred
if err = cm.storeSignature(cred, counter); err != nil {
if err = cm.storage.storeSignature(cred); err != nil {
return
}
if storeAttributes {
err = cm.storeAttributes()
err = cm.storage.storeAttributes(cm.attributes)
}
return
}
......@@ -473,7 +550,7 @@ func (cm *CredentialManager) KeyshareEnroll(managerID SchemeManagerIdentifier, e
}
cm.keyshareServers[managerID] = kss
return cm.storeKeyshareServers()
return cm.storage.storeKeyshareServers(cm.keyshareServers)
}
// KeyshareRemove unregisters the keyshare server of the specified scheme manager.
......@@ -482,13 +559,13 @@ func (cm *CredentialManager) KeyshareRemove(manager SchemeManagerIdentifier) err
return errors.New("Can't uninstall unknown keyshare server")
}
delete(cm.keyshareServers, manager)
return cm.storeKeyshareServers()
return cm.storage.storeKeyshareServers(cm.keyshareServers)
}
func (cm *CredentialManager) addLogEntry(entry *LogEntry, storenow bool) error {
cm.logs = append(cm.logs, entry)
if storenow {
return cm.storeLogs()
return cm.storage.storeLogs(cm.logs)
}
return nil
}
......@@ -496,7 +573,7 @@ func (cm *CredentialManager) addLogEntry(entry *LogEntry, storenow bool) error {
func (cm *CredentialManager) Logs() ([]*LogEntry, error) {
if cm.logs == nil || len(cm.logs) == 0 {
var err error
cm.logs, err = cm.loadLogs()
cm.logs, err = cm.storage.loadLogs()
if err != nil {
return nil, err
}
......
......@@ -7,12 +7,17 @@ import (
"io/ioutil"
"os"
"path"
"time"
"github.com/go-errors/errors"
"github.com/mhe/gabi"
)
// Storage provider for a CredentialManager
type storage struct {
storagePath string
ConfigurationStore *ConfigurationStore
}
// Filenames in which we store stuff
const (
skFile = "sk"
......@@ -24,6 +29,19 @@ const (
signaturesDir = "sigs"
)
// AssertPathExists returns nil only if it has been successfully
// verified that the specified path exists.
func AssertPathExists(path string) error {
exist, err := PathExists(path)
if err != nil {
return err
}
if !exist {
return errors.Errorf("Path %s does not exist", path)
}
return nil
}
// PathExists checks if the specified path exists.
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
......@@ -71,146 +89,25 @@ func saveFile(filepath string, content []byte) (err error) {
return os.Rename(dir+"/"+tempfilename, filepath)
}
// NewCredentialManager creates a new CredentialManager that uses the directory
// specified by storagePath for (de)serializing itself. irmaConfigurationPath
// is the path to a (possibly readonly) folder containing irma_configuration;
// androidStoragePath is an optional path to the files of the old android app
// (specify "" if you do not want to parse the old android app files),
// and keyshareHandler is used for when a registration to a keyshare server needs
// to happen.
// The credential manager returned by this function has been fully deserialized
// and is ready for use.
//
// NOTE: It is the responsibility of the caller that there exists a directory
// at storagePath!
func NewCredentialManager(
storagePath string,
irmaConfigurationPath string,
androidStoragePath string,
keyshareHandler KeyshareHandler,
) (*CredentialManager, error) {
var err error
cm := &CredentialManager{
credentials: make(map[CredentialTypeIdentifier]map[int]*credential),
keyshareServers: make(map[SchemeManagerIdentifier]*keyshareServer),
attributes: make(map[CredentialTypeIdentifier][]*AttributeList),
irmaConfigurationPath: irmaConfigurationPath,
androidStoragePath: androidStoragePath,
ConfigurationStore: NewConfigurationStore(storagePath + "/irma_configuration"),
}
exists, err := PathExists(cm.irmaConfigurationPath)
func generateSecretKey() (*secretKey, error) {
key, err := gabi.RandomBigInt(gabi.DefaultSystemParameters[1024].Lm)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.New("irmaConfigurationPath does not exist")
}
// Ensure storage path exists, and populate it with necessary files
cm.storagePath = storagePath
if err = cm.ensureStorageExists(); err != nil {
return nil, err
}
if err = cm.ConfigurationStore.ParseFolder(); err != nil {
return nil, err
}
// Perform new update functions from credentialManagerUpdates, if any
if err = cm.update(); err != nil {
return nil, err
}
// Load our stuff
if cm.secretkey, err = cm.loadSecretKey(); err != nil {
return nil, err
}
if cm.attributes, err = cm.loadAttributes(); err != nil {
return nil, err
}
if cm.paillierKeyCache, err = cm.loadPaillierKeys(); err != nil {
return nil, err
}
if cm.keyshareServers, err = cm.loadKeyshareServers(); err != nil {
return nil, err
}
unenrolled := cm.unenrolledKeyshareServers()
switch len(unenrolled) {
case 0: // nop
case 1:
if keyshareHandler == nil {
return nil, errors.New("Keyshare server found but no KeyshareHandler was given")
}
keyshareHandler.StartRegistration(unenrolled[0], func(email, pin string) {
cm.KeyshareEnroll(unenrolled[0].Identifier(), email, pin)
})
default:
return nil, errors.New("Too many keyshare servers")
}
return cm, nil
return &secretKey{Key: key}, nil
}
// update performs any function from credentialManagerUpdates that has not
// already been executed in the past, keeping track of previously executed updates
// in the file at updatesFile.
func (cm *CredentialManager) update() error {
// Load and parse file containing info about already performed updates
var err error
exists, err := PathExists(cm.path(updatesFile))
if err != nil {
return err
}
if !exists {
cm.updates = []update{}
} else {
var bytes []byte
bytes, err = ioutil.ReadFile(cm.path(updatesFile))
if err != nil {
return err
}
if err = json.Unmarshal(bytes, &cm.updates); err != nil {
return err
}
}
// Perform all new updates
for i := len(cm.updates); i < len(credentialManagerUpdates); i++ {
err = credentialManagerUpdates[i](cm)
update := update{
When: Timestamp(time.Now()),
Number: i,
Success: err == nil,
}
if err != nil {
str := err.Error()
update.Error = &str
}
cm.updates = append(cm.updates, update)
}
// Save updates file
bytes, err := json.Marshal(cm.updates)
if err != nil {
return err
}
saveFile(cm.path(updatesFile), bytes)
return nil
}
func (cm *CredentialManager) path(file string) string {
return cm.storagePath + "/" + file
func (s *storage) path(p string) string {
return s.storagePath + "/" + p
}
func (cm *CredentialManager) signatureFilename(attrs *AttributeList) string {
func (s *storage) signatureFilename(attrs *AttributeList) string {
// We take the SHA256 hash over all attributes as the filename for the signature.
// This means that the signatures of two credentials that have identical attributes
// will be written to the same file, one overwriting the other - but that doesn't
// matter, because either one of the signatures is valid over both attribute lists,
// so keeping one of them suffices.
return cm.path(signaturesDir) + "/" + attrs.hash()
return s.path(signaturesDir) + "/" + attrs.hash()
}
// ensureStorageExists initializes the credential storage folder,
......@@ -218,36 +115,22 @@ func (cm *CredentialManager) signatureFilename(attrs *AttributeList) string {
// NOTE: we do not create the folder if it does not exist!
// Setting it up in a properly protected location (e.g., with automatic
// backups to iCloud/Google disabled) is the responsibility of the user.
func (cm *CredentialManager) ensureStorageExists() error {
exist, err := PathExists(cm.storagePath)
if err != nil {
return err
}
if !exist {
return errors.New("credential storage path does not exist")
}
if exist, err = PathExists(cm.ConfigurationStore.path); err != nil {
func (s *storage) ensureStorageExists() error {
if err := AssertPathExists(s.storagePath); err != nil {
return err
}
if !exist {
if err = ensureDirectoryExists(cm.ConfigurationStore.path); err != nil {
return err
}
cm.ConfigurationStore.Copy(cm.irmaConfigurationPath, false)
}
return ensureDirectoryExists(cm.path(signaturesDir))
return ensureDirectoryExists(s.path(signaturesDir))
}
func (cm *CredentialManager) storeSecretKey(sk *secretKey) error {
func (s *storage) storeSecretKey(sk *secretKey) error {
bytes, err := json.Marshal(sk)
if err != nil {
return err
}
return saveFile(cm.path(skFile), bytes)
return saveFile(s.path(skFile), bytes)
}
func (cm *CredentialManager) storeSignature(cred *credential, counter int) (err error) {
func (s *storage) storeSignature(cred *credential) (err error) {
if cred.CredentialType() == nil {
return errors.New("cannot add unknown credential type")
}
......@@ -257,61 +140,66 @@ func (cm *CredentialManager) storeSignature(cred *credential, counter int) (err
return err
}
filename := cm.signatureFilename(cred.AttributeList())
filename := s.signatureFilename(cred.AttributeList())
err = saveFile(filename, credbytes)
return
}
func (cm *CredentialManager) storeAttributes() error {
func (s *storage) storeAttributes(attributes map[CredentialTypeIdentifier][]*AttributeList) error {
temp := []*AttributeList{}
for _, attrlistlist := range cm.attributes {
for _, attrlistlist := range attributes {
for _, attrlist := range attrlistlist {
temp = append(temp, attrlist)
}
}
if attrbytes, err := json.Marshal(temp); err == nil {
return saveFile(cm.path(attributesFile), attrbytes)
return saveFile(s.path(attributesFile), attrbytes)
} else {
return err
}
}
func (cm *CredentialManager) storeKeyshareServers() (err error) {
bts, err := json.Marshal(cm.keyshareServers)
func (s *storage) storeKeyshareServers(keyshareServers map[SchemeManagerIdentifier]*keyshareServer) (err error) {
bts, err := json.Marshal(keyshareServers)
if err != nil {
return
}
err = saveFile(cm.path(kssFile), bts)
err = saveFile(s.path(kssFile), bts)
return
}
func (cm *CredentialManager) storePaillierKeys() (err error) {
bts, err := json.Marshal(cm.paillierKeyCache)
func (s *storage) storePaillierKeys(key *paillierPrivateKey) (err error) {
bts, err := json.Marshal(key)
if err != nil {
return
}
err = saveFile(cm.path(paillierFile), bts)
err = saveFile(s.path(paillierFile), bts)
return
}
func (cm *CredentialManager) storeLogs() (err error) {
bts, err := json.Marshal(cm.logs)
func (s *storage) storeLogs(logs []*LogEntry) (err error) {
bts, err := json.Marshal(logs)
if err != nil {
return
}
err = saveFile(cm.path(logsFile), bts)
err = saveFile(s.path(logsFile), bts)
return
}
func (cm *CredentialManager) loadSignature(attrs *AttributeList) (signature *gabi.CLSignature, err error) {
sigpath := cm.signatureFilename(attrs)
exists, err := PathExists(sigpath)
func (s *storage) storeUpdates(updates []update) (err error) {
bts, err := json.Marshal(updates)
if err != nil {
return
}
if !exists {
return nil, errors.New("Signature file not found")
err = saveFile(s.path(updatesFile), bts)
return
}
func (s *storage) loadSignature(attrs *AttributeList) (signature *gabi.CLSignature, err error) {
sigpath := s.signatureFilename(attrs)
if err := AssertPathExists(sigpath); err != nil {
return nil, err
}
bytes, err := ioutil.ReadFile(sigpath)
if err != nil {
......@@ -324,16 +212,16 @@ func (cm *CredentialManager) loadSignature(attrs *AttributeList) (signature *gab
// loadSecretKey retrieves and returns the secret key from storage, or if no secret key
// was found in storage, it generates, saves, and returns a new secret key.
func (cm *CredentialManager) loadSecretKey() (*secretKey, error) {
func (s *storage) loadSecretKey() (*secretKey, error) {
sk := &secretKey{}
var err error
exists, err := PathExists(cm.path(skFile))
exists, err := PathExists(s.path(skFile))
if err != nil {
return nil, err
}
if exists {
var bytes []byte
if bytes, err = ioutil.ReadFile(cm.path(skFile)); err != nil {
if bytes, err = ioutil.ReadFile(s.path(skFile)); err != nil {
return nil, err
}
if err = json.Unmarshal(bytes, sk); err != nil {
......@@ -342,23 +230,23 @@ func (cm *CredentialManager) loadSecretKey() (*secretKey, error) {
return sk, err
}
sk, err = cm.generateSecretKey()
sk, err = generateSecretKey()
if err != nil {
return nil, err
}
err = cm.storeSecretKey(sk)
err = s.storeSecretKey(sk)
if err != nil {
return nil, err
}
return sk, nil
}
func (cm *CredentialManager) loadAttributes() (list map[CredentialTypeIdentifier][]*AttributeList, err error) {
exists, err := PathExists(cm.path(attributesFile))