manager.go 5.05 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
22
	credentials map[string]map[int]*Credential
23
24
}

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

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

}

Sietse Ringers's avatar
Sietse Ringers committed
52
53
54
55
56
57
58
59
60
61
62
// attrs returns cm.attributes[id], initializing it to an empty slice if neccesary
func (cm *CredentialManager) attrs(id string) []*AttributeList {
	list, exists := cm.attributes[id]
	if !exists {
		list = make([]*AttributeList, 0, 1)
		cm.attributes[id] = list
	}
	return list
}

// creds returns cm.credentials[id], initializing it to an empty map if neccesary
63
func (cm *CredentialManager) creds(id string) map[int]*Credential {
Sietse Ringers's avatar
Sietse Ringers committed
64
65
	list, exists := cm.credentials[id]
	if !exists {
66
		list = make(map[int]*Credential)
Sietse Ringers's avatar
Sietse Ringers committed
67
68
69
70
71
		cm.credentials[id] = list
	}
	return list
}

Sietse Ringers's avatar
Sietse Ringers committed
72
73
// 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) {
Sietse Ringers's avatar
Sietse Ringers committed
74
75
	list := cm.attrs(id)
	if len(list) <= counter {
Sietse Ringers's avatar
Sietse Ringers committed
76
77
78
79
80
81
		return
	}
	return list[counter]
}

// Credential returns the requested credential, or nil if we do not have it.
82
func (cm *CredentialManager) Credential(id string, counter int) (cred *Credential, err error) {
Sietse Ringers's avatar
Sietse Ringers committed
83
84
85
	// 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
Sietse Ringers's avatar
Sietse Ringers committed
86
	if _, exists := cm.creds(id)[counter]; !exists {
Sietse Ringers's avatar
Sietse Ringers committed
87
88
89
90
91
92
93
94
95
96
97
98
99
		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
		}
100
101
102
103
104
		cred := newCredential(&gabi.Credential{
			Attributes: ints,
			Signature:  sig,
			Pk:         nil, // TODO
		})
Sietse Ringers's avatar
Sietse Ringers committed
105
106
107
108
		cm.credentials[id][counter] = cred
	}

	return cm.credentials[id][counter], nil
109
110
111
}

// ParseAndroidStorage parses an Android cardemu.xml shared preferences file
Sietse Ringers's avatar
Sietse Ringers committed
112
113
114
// 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.
115
116
117
func (cm *CredentialManager) ParseAndroidStorage() (err error) {
	exists, err := pathExists(cm.path(cardemuXML))
	if err != nil || !exists {
118
119
		return
	}
120
121

	bytes, err := ioutil.ReadFile(cm.path(cardemuXML))
Sietse Ringers's avatar
Sietse Ringers committed
122
	parsedxml := struct {
123
124
125
126
127
		Strings []struct {
			Name    string `xml:"name,attr"`
			Content string `xml:",chardata"`
		} `xml:"string"`
	}{}
Sietse Ringers's avatar
Sietse Ringers committed
128
	xml.Unmarshal(bytes, &parsedxml)
129

Sietse Ringers's avatar
Sietse Ringers committed
130
131
	parsedjson := make(map[string][]*gabi.Credential)
	for _, xmltag := range parsedxml.Strings {
132
133
		if xmltag.Name == "credentials" {
			jsontag := html.UnescapeString(xmltag.Content)
Sietse Ringers's avatar
Sietse Ringers committed
134
			if err = json.Unmarshal([]byte(jsontag), &parsedjson); err != nil {
135
136
				return
			}
137
138
139
		}
	}

Sietse Ringers's avatar
Sietse Ringers committed
140
141
	for _, list := range parsedjson {
		cm.secretkey = list[0].Attributes[0]
142
143
		for i, gabicred := range list {
			cred := newCredential(gabicred)
144
145
146
147
148
149
150
151
152
153
			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
			}
		}
154
	}
155
156
157

	if len(cm.credentials) > 0 {
		err = cm.storeAttributes()
158
		if err != nil {
159
			return err
160
		}
Sietse Ringers's avatar
Sietse Ringers committed
161
		err = cm.storeSecretKey(cm.secretkey)
162
		if err != nil {
163
			return err
164
165
166
		}
	}

167
	return
168
169
}

170
func (cm *CredentialManager) addCredential(cred *Credential) {
171
	id := cred.CredentialType().Identifier()
Sietse Ringers's avatar
Sietse Ringers committed
172
	cm.attributes[id] = append(cm.attrs(id), NewAttributeListFromInts(cred.Attributes[1:]))
173

174
	if _, exists := cm.credentials[id]; !exists {
175
		cm.credentials[id] = make(map[int]*Credential)
176
	}
Sietse Ringers's avatar
Sietse Ringers committed
177
178
	counter := len(cm.attributes[id]) - 1
	cm.credentials[id][counter] = cred
179
}
180

181
// Add adds the specified credential to the CredentialManager.
182
func (cm *CredentialManager) Add(cred *Credential) (err error) {
183
184
185
186
187
	if cred.CredentialType() == nil {
		return errors.New("cannot add unknown credential type")
	}

	cm.addCredential(cred)
Sietse Ringers's avatar
Sietse Ringers committed
188
	counter := len(cm.credentials[cred.CredentialType().Identifier()]) - 1
189
190

	err = cm.storeSignature(cred, counter)
191
192
193
	if err != nil {
		return
	}
194
	err = cm.storeAttributes()
195
196
	return
}