manager.go 4.8 KB
Newer Older
1
2
3
package irmago

import (
4
5
	"encoding/json"
	"encoding/xml"
6
	"errors"
7
	"html"
8
9
10
11
12
13
	"io/ioutil"
	"math/big"

	"github.com/mhe/gabi"
)

14
// Manager is the global instance of CredentialManager.
Sietse Ringers's avatar
Sietse Ringers committed
15
var Manager = newCredentialManager()
16
17
18
19
20

// CredentialManager manages credentials.
type CredentialManager struct {
	secretkey   *big.Int
	storagePath string
21
	attributes  map[string][]*AttributeList
Sietse Ringers's avatar
Sietse Ringers committed
22
	credentials map[string]map[int]*gabi.Credential
23
24
}

Sietse Ringers's avatar
Sietse Ringers committed
25
26
27
28
29
30
31
32
func newCredentialManager() *CredentialManager {
	return &CredentialManager{
		credentials: make(map[string]map[int]*gabi.Credential),
	}
}

func (cm *CredentialManager) generateSecretKey() (sk *big.Int, err error) {
	return gabi.RandomBigInt(gabi.DefaultSystemParameters[1024].Lm)
33
34
}

35
36
37
// Init deserializes the credentials from storage.
func (cm *CredentialManager) Init(path string) (err error) {
	cm.storagePath = path
38

Sietse Ringers's avatar
Sietse Ringers committed
39
40
41
42
43
	err = cm.ensureStorageExists()
	if err != nil {
		return err
	}
	cm.secretkey, err = cm.loadSecretKey()
44
45
46
	if err != nil {
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
47
	cm.attributes, err = cm.loadAttributes()
48
	return
Sietse Ringers's avatar
Sietse Ringers committed
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

}

// Attributes returns the attribute list of the requested credential, or nil if we do not have it.
func (cm *CredentialManager) Attributes(id string, counter int) (attributes *AttributeList) {
	list, exists := cm.attributes[id]
	if !exists || len(list) <= counter {
		return
	}
	return list[counter]
}

// Credential returns the requested credential, or nil if we do not have it.
func (cm *CredentialManager) Credential(id string, counter int) (cred *gabi.Credential, err error) {
	_, exists := cm.credentials[id]
	if !exists {
		cm.credentials[id] = make(map[int]*gabi.Credential)
	}

	// If the requested credential is not in credential map, we check if its attributes were
	// deserialized during Init(). If so, there should be a corresponding signature file,
	// so we read that, construct the credential, and add it to the credential map
	if _, exists := cm.credentials[id][counter]; !exists {
		attrs := cm.Attributes(id, counter)
		if attrs == nil { // We do not have the requested cred
			return
		}
		ints := append([]*big.Int{cm.secretkey}, attrs.Ints...)
		sig, err := cm.loadSignature(id, counter)
		if err != nil {
			return nil, err
		}
		if sig == nil {
			err = errors.New("signature file not found")
			return nil, err
		}
		cred := gabi.NewCredential(ints, sig, nil)
		cm.credentials[id][counter] = cred
	}

	return cm.credentials[id][counter], nil
90
91
92
}

// ParseAndroidStorage parses an Android cardemu.xml shared preferences file
Sietse Ringers's avatar
Sietse Ringers committed
93
94
95
// from the old Android IRMA app, parsing its credentials into the current instance,
// and saving them to storage.
// CAREFUL: this method overwrites any existing secret keys and attributes on storage.
96
97
98
func (cm *CredentialManager) ParseAndroidStorage() (err error) {
	exists, err := pathExists(cm.path(cardemuXML))
	if err != nil || !exists {
99
100
		return
	}
101
102

	bytes, err := ioutil.ReadFile(cm.path(cardemuXML))
Sietse Ringers's avatar
Sietse Ringers committed
103
	parsedxml := struct {
104
105
106
107
108
		Strings []struct {
			Name    string `xml:"name,attr"`
			Content string `xml:",chardata"`
		} `xml:"string"`
	}{}
Sietse Ringers's avatar
Sietse Ringers committed
109
	xml.Unmarshal(bytes, &parsedxml)
110

Sietse Ringers's avatar
Sietse Ringers committed
111
112
	parsedjson := make(map[string][]*gabi.Credential)
	for _, xmltag := range parsedxml.Strings {
113
114
		if xmltag.Name == "credentials" {
			jsontag := html.UnescapeString(xmltag.Content)
Sietse Ringers's avatar
Sietse Ringers committed
115
			if err = json.Unmarshal([]byte(jsontag), &parsedjson); err != nil {
116
117
				return
			}
118
119
120
		}
	}

Sietse Ringers's avatar
Sietse Ringers committed
121
122
	for _, list := range parsedjson {
		cm.secretkey = list[0].Attributes[0]
123
124
125
126
127
128
129
130
131
132
133
134
135
		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
			}
		}
136
	}
137
138
139

	if len(cm.credentials) > 0 {
		err = cm.storeAttributes()
140
		if err != nil {
141
			return err
142
		}
Sietse Ringers's avatar
Sietse Ringers committed
143
		err = cm.storeSecretKey(cm.secretkey)
144
		if err != nil {
145
			return err
146
147
148
		}
	}

149
	return
150
151
}

152
153
154
155
156
157
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:]))
158

159
	if _, exists := cm.credentials[id]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
160
		cm.credentials[id] = make(map[int]*gabi.Credential)
161
	}
Sietse Ringers's avatar
Sietse Ringers committed
162
163
	counter := len(cm.attributes[id]) - 1
	cm.credentials[id][counter] = cred
164
}
165

166
167
168
169
170
171
172
// 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)
Sietse Ringers's avatar
Sietse Ringers committed
173
	counter := len(cm.credentials[cred.CredentialType().Identifier()]) - 1
174
175

	err = cm.storeSignature(cred, counter)
176
177
178
	if err != nil {
		return
	}
179
	err = cm.storeAttributes()
180
181
	return
}