Newer
Older
import (
"encoding/base64"
"encoding/xml"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"fmt"
"strings"
"github.com/credentials/irmago/internal/fs"
"github.com/go-errors/errors"
// ConfigurationStore keeps track of scheme managers, issuers, credential types and public keys,
// dezerializing them from an irma_configuration folder, and downloads and saves new ones on demand.
type ConfigurationStore struct {
SchemeManagers map[SchemeManagerIdentifier]*SchemeManager
Issuers map[IssuerIdentifier]*Issuer
CredentialTypes map[CredentialTypeIdentifier]*CredentialType
publicKeys map[IssuerIdentifier]map[int]*gabi.PublicKey
reverseHashes map[string]CredentialTypeIdentifier
// NewConfigurationStore returns a new configuration store. After this
// ParseFolder() should be called to parse the specified path.
func NewConfigurationStore(path string, assets string) (store *ConfigurationStore, err error) {
if err = fs.EnsureDirectoryExists(store.path); err != nil {
if assets != "" {
if err = store.Copy(assets, false); err != nil {
return nil, err
}
}
// ParseFolder populates the current store by parsing the storage path,
// listing the containing scheme managers, issuers and credential types.
func (store *ConfigurationStore) ParseFolder() error {
// Init all maps
store.SchemeManagers = make(map[SchemeManagerIdentifier]*SchemeManager)
store.Issuers = make(map[IssuerIdentifier]*Issuer)
store.CredentialTypes = make(map[CredentialTypeIdentifier]*CredentialType)
store.publicKeys = make(map[IssuerIdentifier]map[int]*gabi.PublicKey)
store.reverseHashes = make(map[string]CredentialTypeIdentifier)
err := iterateSubfolders(store.path, func(dir string) error {
manager := &SchemeManager{}
exists, err := pathToDescription(dir+"/description.xml", manager)
if err != nil {
return err
}
if exists {
store.SchemeManagers[manager.Identifier()] = manager
return store.parseIssuerFolders(dir)
}
return nil
})
if err != nil {
return err
}
store.initialized = true
return nil
}
// PublicKey returns the specified public key, or nil if not present in the ConfigurationStore.
func (store *ConfigurationStore) PublicKey(id IssuerIdentifier, counter int) (*gabi.PublicKey, error) {
if _, contains := store.publicKeys[id]; !contains {
store.publicKeys[id] = map[int]*gabi.PublicKey{}
if err := store.parseKeysFolder(id); err != nil {
return store.publicKeys[id][counter], nil
func (store *ConfigurationStore) addReverseHash(credid CredentialTypeIdentifier) {
hash := sha256.Sum256([]byte(credid.String()))
store.reverseHashes[base64.StdEncoding.EncodeToString(hash[:16])] = credid
}
func (store *ConfigurationStore) hashToCredentialType(hash []byte) *CredentialType {
if str, exists := store.reverseHashes[base64.StdEncoding.EncodeToString(hash)]; exists {
return store.CredentialTypes[str]
// IsInitialized indicates whether this instance has successfully been initialized.
func (store *ConfigurationStore) IsInitialized() bool {
return store.initialized
}
func (store *ConfigurationStore) parseIssuerFolders(path string) error {
return iterateSubfolders(path, func(dir string) error {
issuer := &Issuer{}
exists, err := pathToDescription(dir+"/description.xml", issuer)
if err != nil {
return err
}
if exists {
store.Issuers[issuer.Identifier()] = issuer
if err = store.parseCredentialsFolder(dir + "/Issues/"); err != nil {
return err
}
}
return nil
})
}
// parse $schememanager/$issuer/PublicKeys/$i.xml for $i = 1, ...
func (store *ConfigurationStore) parseKeysFolder(issuerid IssuerIdentifier) error {
path := fmt.Sprintf("%s/%s/%s/PublicKeys/*.xml", store.path, issuerid.SchemeManagerIdentifier().Name(), issuerid.Name())
files, err := filepath.Glob(path)
if err != nil {
return err
}
for _, file := range files {
filename := filepath.Base(file)
count := filename[:len(filename)-4]
i, err := strconv.Atoi(count)
if err != nil {
continue
}
pk, err := gabi.NewPublicKeyFromFile(file)
if err != nil {
return err
}
pk.Issuer = issuerid.String()
store.publicKeys[issuerid][i] = pk
// parse $schememanager/$issuer/Issues/*/description.xml
func (store *ConfigurationStore) parseCredentialsFolder(path string) error {
return iterateSubfolders(path, func(dir string) error {
cred := &CredentialType{}
exists, err := pathToDescription(dir+"/description.xml", cred)
if err != nil {
return err
}
if exists {
credid := cred.Identifier()
store.CredentialTypes[credid] = cred
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
store.addReverseHash(credid)
}
return nil
})
}
// iterateSubfolders iterates over the subfolders of the specified path,
// calling the specified handler each time. If anything goes wrong, or
// if the caller returns a non-nil error, an error is immediately returned.
func iterateSubfolders(path string, handler func(string) error) error {
dirs, err := filepath.Glob(path + "/*")
if err != nil {
return err
}
for _, dir := range dirs {
stat, err := os.Stat(dir)
if err != nil {
return err
}
if !stat.IsDir() {
continue
}
err = handler(dir)
if err != nil {
return err
}
}
return nil
}
func pathToDescription(path string, description interface{}) (bool, error) {
if _, err := os.Stat(path); err != nil {
return false, nil
}
file, err := os.Open(path)
if err != nil {
return true, err
}
defer file.Close()
bytes, err := ioutil.ReadAll(file)
if err != nil {
return true, err
}
err = xml.Unmarshal(bytes, description)
if err != nil {
return true, err
}
return true, nil
}
// Contains checks if the store contains the specified credential type.
func (store *ConfigurationStore) Contains(cred CredentialTypeIdentifier) bool {
return store.SchemeManagers[cred.IssuerIdentifier().SchemeManagerIdentifier()] != nil &&
store.Issuers[cred.IssuerIdentifier()] != nil &&
store.CredentialTypes[cred] != nil
func (store *ConfigurationStore) Copy(source string, parse bool) error {
if err := fs.EnsureDirectoryExists(store.path); err != nil {
err := filepath.Walk(source, filepath.WalkFunc(
func(path string, info os.FileInfo, err error) error {
if path == source {
return nil
}
subpath := path[len(source):]
if info.IsDir() {
if err := fs.EnsureDirectoryExists(store.path + subpath); err != nil {
return err
}
} else {
srcfile, err := os.Open(path)
if err != nil {
return err
}
defer srcfile.Close()
bytes, err := ioutil.ReadAll(srcfile)
if err != nil {
return err
}
if err := fs.SaveFile(store.path+subpath, bytes); err != nil {
return err
}
}
return nil
}),
)
if err != nil {
return err
}
if parse {
return store.ParseFolder()
}
return nil
func (store *ConfigurationStore) DownloadSchemeManager(url string) (*SchemeManager, error) {
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
url = "https://" + url
}
if url[len(url)-1] == '/' {
url = url[:len(url)-1]
}
if strings.HasSuffix(url, "/description.xml") {
url = url[:len(url)-len("/description.xml")]
}
b, err := NewHTTPTransport(url).GetBytes("/description.xml")
if err != nil {
return nil, err
}
manager := &SchemeManager{}
if err = xml.Unmarshal(b, manager); err != nil {
return nil, err
}
manager.URL = url // TODO?
return manager, nil
}
func (store *ConfigurationStore) RemoveSchemeManager(id SchemeManagerIdentifier) error {
// Remove everything falling under the manager's responsibility
for credid := range store.CredentialTypes {
if credid.IssuerIdentifier().SchemeManagerIdentifier() == id {
delete(store.CredentialTypes, credid)
}
}
for issid := range store.Issuers {
if issid.SchemeManagerIdentifier() == id {
delete(store.Issuers, issid)
}
}
for issid := range store.publicKeys {
if issid.SchemeManagerIdentifier() == id {
delete(store.publicKeys, issid)
}
}
// Remove from storage
return os.RemoveAll(fmt.Sprintf("%s/%s", store.path, id.String()))
// or, remove above iterations and call .ParseFolder()?
}
func (store *ConfigurationStore) AddSchemeManager(manager *SchemeManager) error {
name := manager.ID
if err := fs.EnsureDirectoryExists(fmt.Sprintf("%s/%s", store.path, name)); err != nil {
return err
}
b, err := xml.Marshal(manager)
if err != nil {
return err
}
if err := fs.SaveFile(fmt.Sprintf("%s/%s/description.xml", store.path, name), b); err != nil {
return err
}
store.SchemeManagers[NewSchemeManagerIdentifier(name)] = manager
func (store *ConfigurationStore) Download(set *IrmaIdentifierSet) (*IrmaIdentifierSet, error) {
var err error
downloaded := &IrmaIdentifierSet{
SchemeManagers: map[SchemeManagerIdentifier]struct{}{},
Issuers: map[IssuerIdentifier]struct{}{},
CredentialTypes: map[CredentialTypeIdentifier]struct{}{},
}
for manid := range set.SchemeManagers {
if _, contains = store.SchemeManagers[manid]; !contains {
return nil, errors.Errorf("Unknown scheme manager: %s", manid)
}
}
transport := NewHTTPTransport("")
for issid := range set.Issuers {
if _, contains = store.Issuers[issid]; !contains {
url := store.SchemeManagers[issid.SchemeManagerIdentifier()].URL + "/" + issid.Name()
path := fmt.Sprintf("%s/%s/%s", store.path, issid.SchemeManagerIdentifier().String(), issid.Name())
if err = transport.GetFile(url+"/description.xml", path+"/description.xml"); err != nil {
return nil, err
}
if transport.GetFile(url+"/logo.png", path+"/logo.png"); err != nil {
return nil, err
}
downloaded.Issuers[issid] = struct{}{}
}
for issid, list := range set.PublicKeys {
for _, count := range list {
pk, err := store.PublicKey(issid, count)
if err != nil {
return nil, err
}
if pk == nil {
manager := issid.SchemeManagerIdentifier()
suffix := fmt.Sprintf("/%s/PublicKeys/%d.xml", issid.Name(), count)
path := fmt.Sprintf("%s/%s/%s", store.path, manager.String(), suffix)
if transport.GetFile(store.SchemeManagers[manager].URL+suffix, path); err != nil {
}
}
}
for credid := range set.CredentialTypes {
if _, contains := store.CredentialTypes[credid]; !contains {
issuer := credid.IssuerIdentifier()
manager := issuer.SchemeManagerIdentifier()
local := fmt.Sprintf("%s/%s/%s/Issues", store.path, manager.Name(), issuer.Name())
if err := fs.EnsureDirectoryExists(local); err != nil {
fmt.Sprintf("%s/%s/Issues/%s/description.xml",
store.SchemeManagers[manager].URL, issuer.Name(), credid.Name()),
fmt.Sprintf("%s/%s/description.xml", local, credid.Name()),
); err != nil {
return nil, err
}
downloaded.CredentialTypes[credid] = struct{}{}
return downloaded, store.ParseFolder()