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

Move store, delete descriptions, add manager stub

parent f5eb4ef4
package irmago
import (
"crypto/sha256"
"math/big"
"github.com/mhe/gabi"
)
// AttributeList contains attributes, excluding the secret key,
// providing convenient access to the metadata attribute.
type AttributeList struct {
ints []*big.Int
strings []string
*gabi.MetadataAttribute `xml:"-"`
}
// NewAttributeListFromInts initializes a new AttributeList from a list of bigints.
func NewAttributeListFromInts(ints []*big.Int) *AttributeList {
return &AttributeList{
ints: ints,
MetadataAttribute: gabi.MetadataFromInt(ints[0]),
}
}
// TODO maybe remove
func (al *AttributeList) hash() string {
bytes := make([]byte, 20)
for _, i := range al.ints {
bytes = append(bytes, i.Bytes()...)
}
shasum := sha256.Sum256(bytes)
return string(shasum[:])
}
// Strings converts the current instance to human-readable strings.
func (al *AttributeList) Strings() []string {
if al.strings == nil {
al.strings = make([]string, len(al.ints)-1)
for index, num := range al.ints[1:] { // skip metadata
al.strings[index] = string(num.Bytes())
}
}
return al.strings
}
package irmago
import (
"encoding/xml"
"github.com/mhe/gabi"
)
// SchemeManager describes a scheme manager.
type SchemeManager struct {
Name string `xml:"Id"`
URL string `xml:"Contact"`
HRName string `xml:"Name"`
Description string
KeyshareServer string
KeyshareWebsite string
KeyshareAttribute string
XMLVersion int `xml:"version,attr"`
XMLName xml.Name `xml:"SchemeManager"`
}
// Issuer describes an issuer.
type Issuer struct {
HRName string `xml:"Name"`
HRShortName string `xml:"ShortName"`
Name string `xml:"ID"`
SchemeManagerName string `xml:"SchemeManager"`
ContactAddress string
ContactEMail string
URL string `xml:"baseURL"`
XMLVersion int `xml:"version,attr"`
}
// CredentialType is a description of a credential type, specifying (a.o.) its name, issuer, and attributes.
type CredentialType struct {
HRName string `xml:"Name"`
HRShortName string `xml:"ShortName"`
IssuerName string `xml:"IssuerID"`
SchemeManagerName string `xml:"SchemeManager"`
Name string `xml:"CredentialID"`
IsSingleton bool `xml:"ShouldBeSingleton"`
Description string
Attributes []AttributeDescription `xml:"Attributes>Attribute"`
XMLVersion int `xml:"version,attr"`
XMLName xml.Name `xml:"IssueSpecification"`
}
// AttributeDescription is a description of an attribute within a credential type.
type AttributeDescription struct {
Name string
Description string
}
// Identifier returns the identifier of the specified credential type.
func (cd *CredentialType) Identifier() string {
return cd.SchemeManagerName + "." + cd.IssuerName + "." + cd.Name
}
// Identifier returns the identifier of the specified issuer description.
func (id *Issuer) Identifier() string {
return id.SchemeManagerName + "." + id.Name
}
// CurrentPublicKey returns the latest known public key of the issuer identified by this instance.
func (id *Issuer) CurrentPublicKey() *gabi.PublicKey {
keys := MetaStore.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) *gabi.PublicKey {
keys := MetaStore.PublicKeys[id.Identifier()]
if keys == nil || index >= len(keys) {
return nil
}
return keys[index]
}
package irmago
// Contains identifiers for issuers, credential types, and attributes
// Thin wrapper about their string equivalents (e.g., "irma-demo.RU")
// in case of the "RU" issuer in the "irma-demo" domain
// Not sure if these are at all necessary. Avoid if possible, TODO: remove these?
import "strings"
// Base object for identifiers
type objectIdentifier struct {
string `json:"identifier"`
}
// IssuerIdentifier identifies an issuer.
type IssuerIdentifier struct {
objectIdentifier
}
// CredentialTypeIdentifier identifies a credential type
type CredentialTypeIdentifier struct {
objectIdentifier
issuer *IssuerIdentifier
}
// AttributeTypeIdentifier identifies an attribute within a credential type.
type AttributeTypeIdentifier struct {
objectIdentifier
cred *CredentialTypeIdentifier
}
// NewIssuerIdentifier returns a new IssuerIdentifier
func NewIssuerIdentifier(identifier string) *IssuerIdentifier {
return &IssuerIdentifier{
objectIdentifier: objectIdentifier{string: identifier},
}
}
// NewCredentialTypeIdentifier returns a new CredentialTypeIdentifier
func NewCredentialTypeIdentifier(identifier string) *CredentialTypeIdentifier {
return &CredentialTypeIdentifier{
objectIdentifier: objectIdentifier{string: identifier},
}
}
// NewAttributeTypeIdentifier returns a new AttributeTypeIdentifier
func NewAttributeTypeIdentifier(identifier string) *AttributeTypeIdentifier {
return &AttributeTypeIdentifier{
objectIdentifier: objectIdentifier{string: identifier},
}
}
func (o *objectIdentifier) split() []string {
return strings.Split(o.string, ".")
}
// SchemeManagerName returns the name of the scheme maanger of the current credential type.
func (ci *CredentialTypeIdentifier) SchemeManagerName() string {
return ci.split()[0]
}
// IssuerName returns the issuer name of the current credential type.
func (ci *CredentialTypeIdentifier) IssuerName() string {
return ci.split()[1]
}
// IssuerIdentifier returns the issuer identifier of the current credential type.
func (ci *CredentialTypeIdentifier) IssuerIdentifier() *IssuerIdentifier {
if ci.issuer == nil {
ci.issuer = NewIssuerIdentifier(strings.Join(ci.split()[:1], "."))
}
return ci.issuer
}
// CredentialName returns the name of the current credential type.
func (ci *CredentialTypeIdentifier) CredentialName() string {
return ci.split()[2]
}
// SchemeManagerName ...
func (ai *AttributeTypeIdentifier) SchemeManagerName() string {
return ai.split()[0]
}
// IssuerName ...
func (ai *AttributeTypeIdentifier) IssuerName() string {
return ai.split()[1]
}
// CredentialName ...
func (ai *AttributeTypeIdentifier) CredentialName() string {
return ai.split()[2]
}
// AttributeName ..
func (ai *AttributeTypeIdentifier) AttributeName() string {
return ai.split()[3]
}
// CredentialTypeIdentifier ...
func (ai *AttributeTypeIdentifier) CredentialTypeIdentifier() *CredentialTypeIdentifier {
if ai.cred == nil {
ai.cred = NewCredentialTypeIdentifier(strings.Join(ai.split()[:2], "."))
}
return ai.cred
}
// IssuerIdentifier ...
func (ai *AttributeTypeIdentifier) IssuerIdentifier() *IssuerIdentifier {
return ai.CredentialTypeIdentifier().IssuerIdentifier()
}
package irmago
import (
"encoding/binary"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseStore(t *testing.T) {
err := MetaStore.ParseFolder("testdata/irma_configuration")
if err != nil {
t.Fatal(err)
}
assert.NotNil(t, MetaStore.Issuers["irma-demo.RU"].CurrentPublicKey().N, "irma-demo.RU public key has no modulus")
assert.Equal(t, MetaStore.SchemeManagers["irma-demo"].HRName, "Irma Demo", "irma-demo scheme manager has unexpected name")
assert.Equal(t,
"Radboud Universiteit Nijmegen",
MetaStore.Issuers["irma-demo.RU"].HRName,
"irma-demo.RU issuer has unexpected name")
assert.Equal(t,
"Student Card",
MetaStore.Credentials["irma-demo.RU.studentCard"].HRShortName,
"irma-demo.RU.studentCard has unexpected name")
}
func TestInts(t *testing.T) {
t.Log(big.NewInt(2900).Bytes())
bytes := make([]byte, 2)
binary.BigEndian.PutUint16(bytes, 2900)
t.Log(bytes)
t.Log(binary.BigEndian.Uint16(bytes))
}
package irmago
import (
"errors"
"io/ioutil"
"math/big"
"net/http"
"os"
"github.com/mhe/gabi"
)
const (
skFile = "sk"
attributesFile = "attrs"
signaturesDir = "sigs"
)
// CredentialManager manages credentials.
type CredentialManager struct {
secretkey *big.Int
storagePath string
attributes map[string][]AttributeList
signatures map[string][]*gabi.CLSignature
}
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return true, err
}
func (cm *CredentialManager) path(file string) string {
return cm.storagePath + "/" + file
}
func (cm *CredentialManager) ensureStorageExists() (err error) {
exist, err := pathExists(cm.storagePath)
if err != nil {
return
}
if !exist {
return errors.New("credential storage path does not exist")
}
var file *os.File
exist, err = pathExists(cm.path(skFile))
if err != nil {
return
}
if !exist {
sk := big.NewInt(1) // TODO
file, err = os.Create(cm.path(skFile))
if err != nil {
return
}
defer file.Close()
_, err = file.Write(sk.Bytes())
if err != nil {
return
}
}
exist, err = pathExists(cm.path(attributesFile))
if err != nil {
return err
}
if !exist {
file, err = os.Create(cm.path(attributesFile))
if err != nil {
return
}
defer file.Close()
_, err = file.Write([]byte("{}"))
if err != nil {
return
}
}
return nil
}
func (cm *CredentialManager) init(path string) (err error) {
cm.storagePath = path
cm.ensureStorageExists()
bytes, err := ioutil.ReadFile(cm.path(skFile))
if err != nil {
return
}
cm.secretkey = new(big.Int).SetBytes(bytes)
return
}
func Test2(path string) (content string, err error) {
bytes, err := ioutil.ReadFile(path + "/file.txt")
content = string(bytes)
return
}
func Test(path string) (err error) {
err = ioutil.WriteFile(path+"/file.txt", []byte("TEST TEST"), 0755)
return
}
type NetworkHandler interface {
Success(content string)
Error(status int, error string)
}
func TestNetwork(url string, handler NetworkHandler) {
go func() {
resp, err := http.Get(url)
if err != nil {
handler.Error(0, err.Error())
return
}
defer resp.Body.Close()
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
handler.Error(0, err.Error())
return
}
content := string(bytes)
if resp.StatusCode < 200 || resp.StatusCode > 208 {
handler.Error(resp.StatusCode, content)
} else {
handler.Success(content)
}
}()
}
package irmago
import (
"encoding/xml"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"github.com/mhe/gabi"
)
// MetaStore is the global instance of ConfigurationStore
var MetaStore = ConfigurationStore{
make(map[string]*SchemeManager),
make(map[string]*Issuer),
make(map[string]*CredentialType),
make(map[string][]*gabi.PublicKey),
}
// ConfigurationStore keeps track of scheme managers, issuers, credential types and public keys.
// Use the global MetaStore instance.
type ConfigurationStore struct {
SchemeManagers map[string]*SchemeManager
Issuers map[string]*Issuer
Credentials map[string]*CredentialType
PublicKeys map[string][]*gabi.PublicKey
}
// 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 {
return iterateSubfolders(path, func(dir string) error {
manager := &SchemeManager{}
exists, err := pathToDescription(dir+"/description.xml", manager)
if err != nil {
return err
}
if exists {
MetaStore.SchemeManagers[manager.Name] = manager
return store.parseIssuerFolders(dir)
}
return nil
})
}
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 store.parseKeysFolder(issuer, dir+"/PublicKeys/")
}
return nil
})
}
func (store *ConfigurationStore) parseKeysFolder(issuer *Issuer, path string) error {
for i := 0; ; i++ {
file := path + strconv.Itoa(i) + ".xml"
if _, err := os.Stat(file); err != nil {
break
}
pk, err := gabi.NewPublicKeyFromFile(file)
if err != nil {
return err
}
MetaStore.PublicKeys[issuer.Identifier()] = append(MetaStore.PublicKeys[issuer.Identifier()], pk)
}
return nil
}
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 {
store.Credentials[cred.Identifier()] = cred
}
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
}
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