manager.go 5.12 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

}

Sietse Ringers's avatar
Sietse Ringers committed
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 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
func (cm *CredentialManager) creds(id string) map[int]*gabi.Credential {
	list, exists := cm.credentials[id]
	if !exists {
		list = make(map[int]*gabi.Credential)
		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
82
83
84
85
		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) {
	// 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
100
101
102
103
104
		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
105
106
107
}

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
136
137
	for _, list := range parsedjson {
		cm.secretkey = list[0].Attributes[0]
138
139
140
141
142
143
144
145
146
147
148
149
150
		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
			}
		}
151
	}
152
153
154

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

164
	return
165
166
}

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

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

178
179
180
181
182
183
184
// 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
185
	counter := len(cm.credentials[cred.CredentialType().Identifier()]) - 1
186
187

	err = cm.storeSignature(cred, counter)
188
189
190
	if err != nil {
		return
	}
191
	err = cm.storeAttributes()
192
193
	return
}