Commit 4140d9cb authored by Sietse Ringers's avatar Sietse Ringers
Browse files

More irma_configuration and manager downloading

parent 2c10dc67
......@@ -18,13 +18,14 @@ import (
"github.com/mhe/gabi"
)
// ConfigurationStore keeps track of scheme managers, issuers, credential types and public keys.
// 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
Credentials map[CredentialTypeIdentifier]*CredentialType
publicKeys map[IssuerIdentifier][]*gabi.PublicKey
publicKeys map[IssuerIdentifier]map[int]*gabi.PublicKey
reverseHashes map[string]CredentialTypeIdentifier
path string
initialized bool
......@@ -37,19 +38,13 @@ func NewConfigurationStore(path string, assets string) (store *ConfigurationStor
path: path,
}
var exist bool
if exist, err = PathExists(store.path); err != nil {
if err = ensureDirectoryExists(store.path); err != nil {
return nil, err
}
if !exist {
if err = ensureDirectoryExists(store.path); err != nil {
if assets != "" {
if err = store.Copy(assets, false); err != nil {
return nil, err
}
if assets != "" {
if err = store.Copy(assets, false); err != nil {
return nil, err
}
}
}
return
......@@ -62,7 +57,7 @@ func (store *ConfigurationStore) ParseFolder() error {
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.publicKeys = make(map[IssuerIdentifier]map[int]*gabi.PublicKey)
store.reverseHashes = make(map[string]CredentialTypeIdentifier)
err := iterateSubfolders(store.path, func(dir string) error {
......@@ -87,19 +82,12 @@ func (store *ConfigurationStore) ParseFolder() error {
// 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] = []*gabi.PublicKey{}
err := store.parseKeysFolder(id)
if err != nil {
store.publicKeys[id] = map[int]*gabi.PublicKey{}
if err := store.parseKeysFolder(id); err != nil {
return nil, err
}
}
list := store.publicKeys[id]
if len(list) > counter {
return list[counter], nil
}
return nil, nil
return store.publicKeys[id][counter], nil
}
func (store *ConfigurationStore) addReverseHash(credid CredentialTypeIdentifier) {
......@@ -136,27 +124,33 @@ func (store *ConfigurationStore) parseIssuerFolders(path string) error {
})
}
// parse $schememanager/$issuer/PublicKeys/$i.xml for $i = 1, ...
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 {
break
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 = issuer.Identifier().String()
store.publicKeys[issuer.Identifier()] = append(store.publicKeys[issuer.Identifier()], pk)
pk.Issuer = issuerid.String()
store.publicKeys[issuerid][i] = pk
}
return nil
}
// parse $schememanager/$issuer/Issues/*/description.xml
func (store *ConfigurationStore) parseCredentialsFolder(path string) error {
return iterateSubfolders(path, func(dir string) error {
cred := &CredentialType{}
......@@ -351,19 +345,32 @@ func (store *ConfigurationStore) Download(set *IrmaIdentifierSet) error {
}
for issid, list := range set.PublicKeys {
for _, count := range list {
manager := issid.SchemeManagerIdentifier()
suffix := fmt.Sprintf("/%s/PublicKeys/%d.xml", issid.Name(), count)
path := fmt.Sprintf("%s/%s/%s", store.path, manager.String(), suffix)
transport.GetFile(store.SchemeManagers[manager].URL+suffix, path)
pk, err := store.PublicKey(issid, count)
if err != nil {
return 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)
transport.GetFile(store.SchemeManagers[manager].URL+suffix, path)
}
}
}
}
for credid := range set.CredentialTypes {
if _, contains := store.Credentials[credid]; !contains {
manager := credid.IssuerIdentifier().SchemeManagerIdentifier()
suffix := fmt.Sprintf("/%s/Issues/%s/description.xml", credid.IssuerIdentifier().Name(), credid.Name())
path := fmt.Sprintf("%s/%s/%s", store.path, manager.String(), suffix)
transport.GetFile(store.SchemeManagers[manager].URL+suffix, path)
issuer := credid.IssuerIdentifier()
manager := issuer.SchemeManagerIdentifier()
local := fmt.Sprintf("%s/%s/%s/Issues", store.path, manager.Name(), issuer.Name())
if err := ensureDirectoryExists(local); err != nil {
return err
}
transport.GetFile(
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()),
)
}
}
......
......@@ -6,11 +6,15 @@ import (
"github.com/go-errors/errors"
)
// This file contains data types for scheme managers, issuers, credential types
// matching the XML files in irma_configuration.
// SchemeManager describes a scheme manager.
type SchemeManager struct {
ID string `xml:"Id"`
Name TranslatedString `xml:"Name"`
URL string `xml:"Contact"`
URL string `xml:"Url"`
Contact string `xml:"contact"`
Description TranslatedString
KeyshareServer string
KeyshareWebsite string
......@@ -83,6 +87,26 @@ func (ct CredentialType) IndexOf(ai AttributeTypeIdentifier) (int, error) {
// TranslatedString is a map of translated strings.
type TranslatedString map[string]string
type xmlTranslation struct {
XMLName xml.Name
Text string `xml:",chardata"`
}
type xmlTranslatedString struct {
Translations []xmlTranslation `xml:",any"`
}
// MarshalXML implements xml.Marshaler.
func (ts *TranslatedString) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
temp := &xmlTranslatedString{}
for lang, text := range *ts {
temp.Translations = append(temp.Translations,
xmlTranslation{XMLName: xml.Name{Local: lang}, Text: text},
)
}
return e.EncodeElement(temp, start)
}
// UnmarshalXML unmarshals an XML tag containing a string translated to multiple languages,
// for example: <Foo><en>Hello world</en><nl>Hallo wereld</nl></Foo>
// into a TranslatedString: { "en": "Hello world" , "nl": "Hallo wereld" }
......@@ -90,12 +114,7 @@ func (ts *TranslatedString) UnmarshalXML(d *xml.Decoder, start xml.StartElement)
if map[string]string(*ts) == nil {
*ts = TranslatedString(make(map[string]string))
}
temp := &struct {
Translations []struct {
XMLName xml.Name
Text string `xml:",chardata"`
} `xml:",any"`
}{}
temp := &xmlTranslatedString{}
if err := d.DecodeElement(temp, &start); err != nil {
return err
}
......
......@@ -29,8 +29,10 @@ func TestMain(m *testing.M) {
type IgnoringKeyshareHandler struct{}
func (i *IgnoringKeyshareHandler) StartRegistration(m *SchemeManager, callback func(e, p string) error) {
func (i *IgnoringKeyshareHandler) StartRegistration(m *SchemeManager, callback func(e, p string)) {
}
func (i *IgnoringKeyshareHandler) RegistrationError(err error) {}
func (i *IgnoringKeyshareHandler) RegistrationSuccess() {}
func parseStorage(t *testing.T) *CredentialManager {
exists, err := PathExists("testdata/storage/test")
......@@ -445,4 +447,11 @@ func TestDownloadSchemeManager(t *testing.T) {
sm, err := manager.ConfigurationStore.DownloadSchemeManager(url)
require.NoError(t, err)
require.NotNil(t, sm)
require.NoError(t, manager.ConfigurationStore.AddSchemeManager(sm))
jwt := getIssuanceJwt("testip", NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"))
sessionHelper(t, jwt, "issue", manager)
teardown(t)
}
......@@ -98,7 +98,9 @@ type proofPCommitmentMap struct {
// KeyshareHandler is used for asking the user for his email address and PIN,
// for registering at a keyshare server.
type KeyshareHandler interface {
StartRegistration(manager *SchemeManager, registrationCallback func(email, pin string) error)
StartRegistration(manager *SchemeManager, registrationCallback func(email, pin string))
RegistrationError(err error)
RegistrationSuccess()
}
const (
......
......@@ -131,9 +131,7 @@ func NewCredentialManager(
if keyshareHandler == nil {
return nil, errors.New("Keyshare server found but no KeyshareHandler was given")
}
keyshareHandler.StartRegistration(unenrolled[0], func(email, pin string) error {
return cm.KeyshareEnroll(unenrolled[0].Identifier(), email, pin)
})
cm.KeyshareEnroll(unenrolled[0], keyshareHandler)
default:
return nil, errors.New("Too many keyshare servers")
}
......@@ -570,7 +568,20 @@ func (cm *CredentialManager) unenrolledKeyshareServers() []*SchemeManager {
}
// KeyshareEnroll attempts to register at the keyshare server of the specified scheme manager.
func (cm *CredentialManager) KeyshareEnroll(managerID SchemeManagerIdentifier, email, pin string) error {
func (cm *CredentialManager) KeyshareEnroll(manager *SchemeManager, handler KeyshareHandler) {
handler.StartRegistration(manager, func(email, pin string) {
go func() {
err := cm.keyshareEnrollWorker(manager.Identifier(), email, pin)
if err != nil {
handler.RegistrationError(err)
} else {
handler.RegistrationSuccess()
}
}()
})
}
func (cm *CredentialManager) keyshareEnrollWorker(managerID SchemeManagerIdentifier, email, pin string) error {
manager, ok := cm.ConfigurationStore.SchemeManagers[managerID]
if !ok {
return errors.New("Unknown scheme manager")
......
......@@ -68,10 +68,11 @@ const (
// Actions
const (
ActionDisclosing = Action("disclosing")
ActionSigning = Action("signing")
ActionIssuing = Action("issuing")
ActionUnknown = Action("unknown")
ActionSchemeManager = Action("schememanager")
ActionDisclosing = Action("disclosing")
ActionSigning = Action("signing")
ActionIssuing = Action("issuing")
ActionUnknown = Action("unknown")
)
// Protocol errors
......
......@@ -28,6 +28,7 @@ type Handler interface {
AskIssuancePermission(request IssuanceRequest, ServerName string, callback PermissionHandler)
AskVerificationPermission(request DisclosureRequest, ServerName string, callback PermissionHandler)
AskSignaturePermission(request SignatureRequest, ServerName string, callback PermissionHandler)
AskSchemeManagerPermission(manager *SchemeManager, callback func(proceed bool))
AskPin(remainingAttempts int, callback func(proceed bool, pin string))
}
......@@ -45,6 +46,7 @@ type session struct {
irmaSession IrmaSession
transport *HTTPTransport
choice *DisclosureChoice
newmanager *SchemeManager
}
// We implement the handler for the keyshare protocol
......@@ -111,6 +113,7 @@ func (cm *CredentialManager) NewSession(qr *Qr, handler Handler) {
case ActionDisclosing: // nop
case ActionSigning: // nop
case ActionIssuing: // nop
case ActionSchemeManager: // nop
case ActionUnknown:
fallthrough
default:
......@@ -138,6 +141,11 @@ func (session *session) fail(err *SessionError) {
func (session *session) start() {
session.Handler.StatusUpdate(session.Action, StatusCommunicating)
if session.Action == ActionSchemeManager {
session.managerSession()
return
}
// Get the first IRMA protocol message and parse it
session.info = &SessionInfo{}
Err := session.transport.Get("jwt", session.info)
......@@ -300,3 +308,42 @@ func (session *session) sendResponse(message interface{}) {
_ = session.credManager.addLogEntry(log) // TODO err
session.Handler.Success(session.Action)
}
func (session *session) managerSession() {
manager, err := session.credManager.ConfigurationStore.DownloadSchemeManager(session.ServerURL)
if err != nil {
session.Handler.Failure(session.Action, &SessionError{Err: err}) // TODO
return
}
session.Handler.AskSchemeManagerPermission(manager, func(proceed bool) {
if !proceed {
session.Handler.Cancelled(session.Action)
return
}
session.newmanager = manager
if manager.Distributed() {
session.credManager.KeyshareEnroll(manager, KeyshareHandler(session))
} else {
session.RegistrationSuccess()
}
})
return
}
func (session *session) StartRegistration(manager *SchemeManager, callback func(email, pin string)) {
session.credManager.keyshareHandler.StartRegistration(manager, callback)
}
func (session *session) RegistrationError(err error) {
session.Handler.Failure(session.Action, &SessionError{Err: err}) // TODO
session.credManager.keyshareHandler.RegistrationError(err)
}
func (session *session) RegistrationSuccess() {
if err := session.credManager.ConfigurationStore.AddSchemeManager(session.newmanager); err != nil {
session.Handler.Failure(session.Action, &SessionError{})
return
}
session.Handler.Success(session.Action)
session.credManager.keyshareHandler.RegistrationSuccess()
}
......@@ -61,6 +61,9 @@ func (th TestHandler) AskIssuancePermission(request IssuanceRequest, ServerName
func (th TestHandler) AskSignaturePermission(request SignatureRequest, ServerName string, callback PermissionHandler) {
th.AskVerificationPermission(request.DisclosureRequest, ServerName, callback)
}
func (th TestHandler) AskSchemeManagerPermission(manager *SchemeManager, callback func(proceed bool)) {
callback(true)
}
func (th TestHandler) AskPin(remainingAttempts int, callback func(proceed bool, pin string)) {
callback(true, "12345")
}
......@@ -190,7 +193,7 @@ func registerKeyshareServer(t *testing.T, manager *CredentialManager) {
bytes := make([]byte, 8, 8)
rand.Read(bytes)
email := fmt.Sprintf("%s@example.com", hex.EncodeToString(bytes))
require.NoError(t, manager.KeyshareEnroll(NewSchemeManagerIdentifier("test"), email, "12345"))
require.NoError(t, manager.keyshareEnrollWorker(NewSchemeManagerIdentifier("test"), email, "12345"))
}
// Register a new account at the keyshare server and do an issuance, disclosure,
......
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