manager.go 5.29 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
22
	attributes  map[CredentialTypeIdentifier][]*AttributeList
	credentials map[CredentialTypeIdentifier]map[int]*Credential
23
24
}

Sietse Ringers's avatar
Sietse Ringers committed
25
26
func newCredentialManager() *CredentialManager {
	return &CredentialManager{
27
		credentials: make(map[CredentialTypeIdentifier]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
// attrs returns cm.attributes[id], initializing it to an empty slice if neccesary
53
func (cm *CredentialManager) attrs(id CredentialTypeIdentifier) []*AttributeList {
Sietse Ringers's avatar
Sietse Ringers committed
54
55
56
57
58
59
60
61
62
	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 CredentialTypeIdentifier) 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
// Attributes returns the attribute list of the requested credential, or nil if we do not have it.
73
func (cm *CredentialManager) Attributes(id CredentialTypeIdentifier, 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 CredentialTypeIdentifier, 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
		meta := MetadataFromInt(ints[1])
		pk := meta.PublicKey()
		if pk == nil {
			return nil, errors.New("unknown public key")
		}
105
106
107
		cred := newCredential(&gabi.Credential{
			Attributes: ints,
			Signature:  sig,
108
			Pk:         pk,
109
		})
Sietse Ringers's avatar
Sietse Ringers committed
110
111
112
113
		cm.credentials[id][counter] = cred
	}

	return cm.credentials[id][counter], nil
114
115
116
}

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

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

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

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

	if len(cm.credentials) > 0 {
		err = cm.storeAttributes()
163
		if err != nil {
164
			return err
165
		}
Sietse Ringers's avatar
Sietse Ringers committed
166
		err = cm.storeSecretKey(cm.secretkey)
167
		if err != nil {
168
			return err
169
170
171
		}
	}

172
	return
173
174
}

175
func (cm *CredentialManager) addCredential(cred *Credential) {
176
	id := cred.CredentialType().Identifier()
Sietse Ringers's avatar
Sietse Ringers committed
177
	cm.attributes[id] = append(cm.attrs(id), NewAttributeListFromInts(cred.Attributes[1:]))
178

179
	if _, exists := cm.credentials[id]; !exists {
180
		cm.credentials[id] = make(map[int]*Credential)
181
	}
Sietse Ringers's avatar
Sietse Ringers committed
182
183
	counter := len(cm.attributes[id]) - 1
	cm.credentials[id][counter] = cred
184
}
185

186
// Add adds the specified credential to the CredentialManager.
187
func (cm *CredentialManager) Add(cred *Credential) (err error) {
188
189
190
191
192
	if cred.CredentialType() == nil {
		return errors.New("cannot add unknown credential type")
	}

	cm.addCredential(cred)
Sietse Ringers's avatar
Sietse Ringers committed
193
	counter := len(cm.credentials[cred.CredentialType().Identifier()]) - 1
194
195

	err = cm.storeSignature(cred, counter)
196
197
198
	if err != nil {
		return
	}
199
	err = cm.storeAttributes()
200
201
	return
}