Commit 205d5943 authored by Sietse Ringers's avatar Sietse Ringers

Add credential storing and conversion from old app format

parent ce7b9ce6
*.test
.idea/
testdata/storage/*
!testdata/storage/cardemu.xml
......@@ -10,15 +10,15 @@ import (
// 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:"-"`
Ints []*big.Int
strings []string
*gabi.MetadataAttribute `json:"-"`
}
// NewAttributeListFromInts initializes a new AttributeList from a list of bigints.
func NewAttributeListFromInts(ints []*big.Int) *AttributeList {
return &AttributeList{
ints: ints,
Ints: ints,
MetadataAttribute: gabi.MetadataFromInt(ints[0]),
}
}
......@@ -26,7 +26,7 @@ func NewAttributeListFromInts(ints []*big.Int) *AttributeList {
// TODO maybe remove
func (al *AttributeList) hash() string {
bytes := make([]byte, 20)
for _, i := range al.ints {
for _, i := range al.Ints {
bytes = append(bytes, i.Bytes()...)
}
shasum := sha256.Sum256(bytes)
......@@ -36,8 +36,8 @@ func (al *AttributeList) hash() string {
// 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 = make([]string, len(al.Ints)-1)
for index, num := range al.Ints[1:] { // skip metadata
al.strings[index] = string(num.Bytes())
}
}
......
// Work in progress on an IRMA client in Go. It will:
//* (De)serialize credentials from/to storage
//* Be the client (like the IRMA Android app) in the IRMA protocol
// see (https://credentials.github.io/protocols/irma-protocol/).
package irmago
package irmago
import "testing"
import (
"github.com/mhe/gabi"
"github.com/stretchr/testify/assert"
)
func TestAndroidParse(t *testing.T) {
gabi.MetaStore.ParseFolder("testdata/irma_configuration")
Manager.Init("testdata/storage")
err := Manager.ParseAndroidStorage()
assert.NoError(t, err, "ParseAndroidStorage failed")
assert.NotEmpty(t, Manager.credentials, "No credentials deserialized")
assert.Contains(t, Manager.credentials, "irma-demo.RU.studentCard", "irma-demo.RU.studentCard not deserialized")
assert.NotEmpty(t, Manager.credentials, "irma-demo.RU.studentCard not deserialized")
cred := Manager.credentials["irma-demo.RU.studentCard"][0]
assert.NotNil(t, cred.Attributes[0], "Metadata attribute of irma-demo.RU.studentCard should not be nil")
assert.True(t,
Manager.credentials["irma-demo.RU.studentCard"][0].Signature.Verify(cred.PublicKey(), cred.Attributes),
"Credential should be valid",
)
}
package irmago
import (
"encoding/json"
"encoding/xml"
"errors"
"html"
"io/ioutil"
"math/big"
"os"
"github.com/mhe/gabi"
)
const (
skFile = "sk"
attributesFile = "attrs"
signaturesDir = "sigs"
)
// Manager is the global instance of CredentialManager.
var Manager = CredentialManager{
attributes: make(map[string][]*AttributeList),
credentials: make(map[string][]*gabi.Credential),
}
// CredentialManager manages credentials.
type CredentialManager struct {
secretkey *big.Int
storagePath string
attributes map[string][]AttributeList
signatures map[string][]*gabi.CLSignature
attributes map[string][]*AttributeList
credentials map[string][]*gabi.Credential
}
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) generateSecretKey() (err error) {
cm.secretkey, err = gabi.RandomBigInt(gabi.DefaultSystemParameters[1024].Lm)
return
}
func (cm *CredentialManager) path(file string) string {
return cm.storagePath + "/" + file
}
// Init deserializes the credentials from storage.
func (cm *CredentialManager) Init(path string) (err error) {
cm.storagePath = path
func (cm *CredentialManager) ensureStorageExists() (err error) {
exist, err := pathExists(cm.storagePath)
cm.ensureStorageExists()
bytes, err := ioutil.ReadFile(cm.path(skFile))
if err != nil {
return
}
if !exist {
return errors.New("credential storage path does not exist")
}
cm.secretkey = new(big.Int).SetBytes(bytes)
var file *os.File
exist, err = pathExists(cm.path(skFile))
if err != nil {
return
}
// ParseAndroidStorage parses an Android cardemu.xml shared preferences file
// from the old Android IRMA app, parsing its credentials into the current instance.
func (cm *CredentialManager) ParseAndroidStorage() (err error) {
exists, err := pathExists(cm.path(cardemuXML))
if err != nil || !exists {
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
bytes, err := ioutil.ReadFile(cm.path(cardemuXML))
parsed := struct {
Strings []struct {
Name string `xml:"name,attr"`
Content string `xml:",chardata"`
} `xml:"string"`
}{}
xml.Unmarshal(bytes, &parsed)
for _, xmltag := range parsed.Strings {
if xmltag.Name == "credentials" {
jsontag := html.UnescapeString(xmltag.Content)
if err = json.Unmarshal([]byte(jsontag), &cm.credentials); err != nil {
return
}
}
}
exist, err = pathExists(cm.path(attributesFile))
if err != nil {
return err
for _, list := range cm.credentials {
if list != nil && len(list) > 0 {
cm.secretkey = list[0].Attributes[0]
}
for i, cred := range list {
// TODO move this metadata initialisation somehow into gabi.Credential?
cred.MetadataAttribute = gabi.MetadataFromInt(cred.Attributes[1])
if cred.CredentialType() == nil {
return errors.New("cannot add unknown credential type")
}
cm.addCredential(cred)
err = cm.storeSignature(cred, i)
if err != nil {
return err
}
}
}
if !exist {
file, err = os.Create(cm.path(attributesFile))
if len(cm.credentials) > 0 {
err = cm.storeAttributes()
if err != nil {
return
return err
}
defer file.Close()
_, err = file.Write([]byte("{}"))
err = cm.storeKey()
if err != nil {
return
return err
}
}
return nil
return
}
func (cm *CredentialManager) init(path string) (err error) {
cm.storagePath = path
func (cm *CredentialManager) addCredential(cred *gabi.Credential) {
id := cred.CredentialType().Identifier()
if _, exists := cm.attributes[id]; !exists {
cm.attributes[id] = make([]*AttributeList, 0, 1)
}
cm.attributes[id] = append(cm.attributes[id], NewAttributeListFromInts(cred.Attributes[1:]))
cm.ensureStorageExists()
if _, exists := cm.credentials[id]; !exists {
cm.credentials[id] = make([]*gabi.Credential, 0, 1)
}
cm.credentials[id] = append(cm.credentials[id], cred)
}
bytes, err := ioutil.ReadFile(cm.path(skFile))
// Add adds the specified credential to the CredentialManager.
func (cm *CredentialManager) Add(cred *gabi.Credential) (err error) {
if cred.CredentialType() == nil {
return errors.New("cannot add unknown credential type")
}
cm.addCredential(cred)
counter := len(cm.credentials) - 1
err = cm.storeSignature(cred, counter)
if err != nil {
return
}
cm.secretkey = new(big.Int).SetBytes(bytes)
err = cm.storeAttributes()
return
}
package irmago
import (
"encoding/json"
"errors"
"io/ioutil"
"os"
"strconv"
"github.com/mhe/gabi"
)
// Filenames in which we store stuff
const (
skFile = "sk"
attributesFile = "attrs"
signaturesDir = "sigs"
cardemuXML = "cardemu.xml"
)
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 {
err = cm.generateSecretKey()
if err != nil {
return
}
file, err = os.Create(cm.path(skFile))
if err != nil {
return
}
defer file.Close()
_, err = file.Write(cm.secretkey.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
}
}
exist, err = pathExists(cm.path(signaturesDir))
if err != nil {
return err
}
if !exist {
err = os.Mkdir(cm.path(signaturesDir), 0700)
}
return
}
func (cm *CredentialManager) storeKey() error {
return ioutil.WriteFile(cm.path(skFile), cm.secretkey.Bytes(), 0600)
}
func (cm *CredentialManager) storeSignature(cred *gabi.Credential, counter int) (err error) {
if cred.CredentialType() == nil {
return errors.New("cannot add unknown credential type")
}
credbytes, err := json.Marshal(cred.Signature)
if err != nil {
return err
}
// TODO existence check
filename := cm.path(signaturesDir) + "/" + cred.CredentialType().Identifier() + "-" + strconv.Itoa(counter)
err = ioutil.WriteFile(filename, credbytes, 0600)
return
}
func (cm *CredentialManager) storeAttributes() (err error) {
attrbytes, err := json.Marshal(cm.attributes)
if err != nil {
return
}
// TODO existence check
err = ioutil.WriteFile(cm.path(attributesFile), attrbytes, 0600)
return
}
This source diff could not be displayed because it is too large. You can view the blob instead.
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