updates.go 4.55 KB
Newer Older
Sietse Ringers's avatar
Sietse Ringers committed
1
2
3
4
5
6
7
8
package irmago

import (
	"encoding/json"
	"encoding/xml"
	"html"
	"io/ioutil"
	"math/big"
9
	"time"
Sietse Ringers's avatar
Sietse Ringers committed
10

Sietse Ringers's avatar
Sietse Ringers committed
11
	"github.com/go-errors/errors"
Sietse Ringers's avatar
Sietse Ringers committed
12
13
14
	"github.com/mhe/gabi"
)

Sietse Ringers's avatar
Sietse Ringers committed
15
16
17
// This file contains the update mechanism for CredentialManager
// as well as updates themselves.

18
type update struct {
19
20
21
22
	When    Timestamp
	Number  int
	Success bool
	Error   *string
23
24
25
26
27
28
29
30
}

var credentialManagerUpdates = []func(manager *CredentialManager) error{
	func(manager *CredentialManager) error {
		_, err := manager.ParseAndroidStorage()
		return err
	},
}
31

32
33
34
35
36
37
// update performs any function from credentialManagerUpdates that has not
// already been executed in the past, keeping track of previously executed updates
// in the file at updatesFile.
func (cm *CredentialManager) update() error {
	// Load and parse file containing info about already performed updates
	var err error
Sietse Ringers's avatar
Sietse Ringers committed
38
	if cm.updates, err = cm.storage.LoadUpdates(); err != nil {
39
40
41
42
43
44
		return err
	}

	// Perform all new updates
	for i := len(cm.updates); i < len(credentialManagerUpdates); i++ {
		err = credentialManagerUpdates[i](cm)
Sietse Ringers's avatar
Sietse Ringers committed
45
		u := update{
46
47
48
49
50
51
			When:    Timestamp(time.Now()),
			Number:  i,
			Success: err == nil,
		}
		if err != nil {
			str := err.Error()
Sietse Ringers's avatar
Sietse Ringers committed
52
			u.Error = &str
53
		}
Sietse Ringers's avatar
Sietse Ringers committed
54
		cm.updates = append(cm.updates, u)
55
56
	}

Sietse Ringers's avatar
Sietse Ringers committed
57
	return cm.storage.StoreUpdates(cm.updates)
58
59
}

Sietse Ringers's avatar
Sietse Ringers committed
60
61
62
63
// ParseAndroidStorage parses an Android cardemu.xml shared preferences file
// 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.
64
func (cm *CredentialManager) ParseAndroidStorage() (present bool, err error) {
65
66
67
68
69
	if cm.androidStoragePath == "" {
		return false, nil
	}

	cardemuXML := cm.androidStoragePath + "/shared_prefs/cardemu.xml"
70
71
	present, err = PathExists(cardemuXML)
	if err != nil || !present {
Sietse Ringers's avatar
Sietse Ringers committed
72
73
		return
	}
74
	present = true
Sietse Ringers's avatar
Sietse Ringers committed
75

76
	bytes, err := ioutil.ReadFile(cardemuXML)
Sietse Ringers's avatar
Sietse Ringers committed
77
78
79
80
81
82
83
84
85
	if err != nil {
		return
	}
	parsedxml := struct {
		Strings []struct {
			Name    string `xml:"name,attr"`
			Content string `xml:",chardata"`
		} `xml:"string"`
	}{}
Sietse Ringers's avatar
Sietse Ringers committed
86
87
88
	if err = xml.Unmarshal(bytes, &parsedxml); err != nil {
		return
	}
Sietse Ringers's avatar
Sietse Ringers committed
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

	parsedjson := make(map[string][]*struct {
		Signature    *gabi.CLSignature `json:"signature"`
		Pk           *gabi.PublicKey   `json:"-"`
		Attributes   []*big.Int        `json:"attributes"`
		SharedPoints []*big.Int        `json:"public_sks"`
	})
	cm.keyshareServers = make(map[SchemeManagerIdentifier]*keyshareServer)
	for _, xmltag := range parsedxml.Strings {
		if xmltag.Name == "credentials" {
			jsontag := html.UnescapeString(xmltag.Content)
			if err = json.Unmarshal([]byte(jsontag), &parsedjson); err != nil {
				return
			}
		}
		if xmltag.Name == "keyshare" {
			jsontag := html.UnescapeString(xmltag.Content)
			if err = json.Unmarshal([]byte(jsontag), &cm.keyshareServers); err != nil {
				return
			}
		}
		if xmltag.Name == "KeyshareKeypairs" {
			jsontag := html.UnescapeString(xmltag.Content)
			keys := make([]*paillierPrivateKey, 0, 3)
			if err = json.Unmarshal([]byte(jsontag), &keys); err != nil {
				return
			}
			cm.paillierKeyCache = keys[0]
		}
	}

	for _, list := range parsedjson {
121
		cm.secretkey = &secretKey{Key: list[0].Attributes[0]}
Sietse Ringers's avatar
Sietse Ringers committed
122
123
124
125
126
127
128
129
		for _, oldcred := range list {
			gabicred := &gabi.Credential{
				Attributes: oldcred.Attributes,
				Signature:  oldcred.Signature,
			}
			if oldcred.SharedPoints != nil && len(oldcred.SharedPoints) > 0 {
				gabicred.Signature.KeyshareP = oldcred.SharedPoints[0]
			}
130
			var cred *credential
131
			if cred, err = newCredential(gabicred, cm.ConfigurationStore); err != nil {
132
				return
133
			}
Sietse Ringers's avatar
Sietse Ringers committed
134
			if cred.CredentialType() == nil {
135
136
				err = errors.New("cannot add unknown credential type")
				return
Sietse Ringers's avatar
Sietse Ringers committed
137
138
			}

139
140
			if err = cm.addCredential(cred, false); err != nil {
				return
Sietse Ringers's avatar
Sietse Ringers committed
141
142
143
144
145
			}
		}
	}

	if len(cm.credentials) > 0 {
Sietse Ringers's avatar
Sietse Ringers committed
146
		if err = cm.storage.StoreAttributes(cm.attributes); err != nil {
147
			return
Sietse Ringers's avatar
Sietse Ringers committed
148
		}
Sietse Ringers's avatar
Sietse Ringers committed
149
		if err = cm.storage.StoreSecretKey(cm.secretkey); err != nil {
150
			return
Sietse Ringers's avatar
Sietse Ringers committed
151
152
153
154
		}
	}

	if len(cm.keyshareServers) > 0 {
Sietse Ringers's avatar
Sietse Ringers committed
155
		if err = cm.storage.StoreKeyshareServers(cm.keyshareServers); err != nil {
156
			return
Sietse Ringers's avatar
Sietse Ringers committed
157
158
		}
	}
159
	cm.UnenrolledSchemeManagers = cm.unenrolledSchemeManagers()
Sietse Ringers's avatar
Sietse Ringers committed
160

Sietse Ringers's avatar
Sietse Ringers committed
161
	if err = cm.storage.StorePaillierKeys(cm.paillierKeyCache); err != nil {
162
		return
Sietse Ringers's avatar
Sietse Ringers committed
163
164
165
166
167
	}
	if cm.paillierKeyCache == nil {
		cm.paillierKey(false) // trigger calculating a new one
	}

168
	if err = cm.ConfigurationStore.Copy(cm.androidStoragePath+"/app_store/irma_configuration", false); err != nil {
169
170
171
		return
	}
	// Copy from assets again to ensure we have the latest versions
172
	return present, cm.ConfigurationStore.Copy(cm.irmaConfigurationPath, true)
Sietse Ringers's avatar
Sietse Ringers committed
173
}