-
Notifications
You must be signed in to change notification settings - Fork 3
/
envelope.go
351 lines (296 loc) · 8.79 KB
/
envelope.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
package jess
import (
"errors"
"fmt"
"github.com/mr-tron/base58"
"github.com/safing/structures/dsd"
)
// Envelope holds configuration for jess to put data into a letter.
type Envelope struct { //nolint:maligned // TODO
Version uint8
Name string
SuiteID string
suite *Suite
// Secret keys and passwords
Secrets []*Signet
// Sender related signets
// When closing: private keys for signatures
// When opening: public keys for signatures
Senders []*Signet
// Recipient related signets
// When closing: public keys for key exchange or key encapsulation
// When opening: private keys for key exchange or key encapsulation
Recipients []*Signet
// For users, envelopes describe how a letter is closed.
// Therefore Secrets and Senders always refer to private keys and Recipients to public keys in that context.
// These distinctions are important in order for the user to easily and confidently distinguish what is going to happen. Think of it as "human security".
// SecurityLevel is the security level of the envelope when it was created
SecurityLevel int
// flag to signify if envelope is used for opening
opening bool
}
// NewUnconfiguredEnvelope returns an unconfigured, but slightly initialized envelope.
func NewUnconfiguredEnvelope() *Envelope {
e := &Envelope{
Version: 1,
}
return e
}
// Correspondence returns a new session configured with the envelope.
func (e *Envelope) Correspondence(trustStore TrustStore) (*Session, error) {
return e.initCorrespondence(trustStore, false)
}
func (e *Envelope) initCorrespondence(trustStore TrustStore, verifying bool) (*Session, error) {
err := e.LoadSuite()
if err != nil {
return nil, err
}
//nolint:gocritic // TODO: see below
if verifying {
// TODO: prep sender signets only
// TODO: for this to work, newSession needs to only check verification related things
// err = e.prepSignets(e.Senders, e.opening, trustStore)
err = e.PrepareSignets(trustStore)
} else {
// prep all signets
err = e.PrepareSignets(trustStore)
}
if err != nil {
return nil, err
}
return newSession(e)
}
// WireCorrespondence returns a new wire session (live communication) configured with the envelope.
func (e *Envelope) WireCorrespondence(trustStore TrustStore) (*Session, error) {
s, err := e.Correspondence(trustStore)
if err != nil {
return nil, err
}
err = s.initWireSession()
if err != nil {
return nil, err
}
return s, nil
}
// Check returns whether the envelope is valid and can be used as is.
func (e *Envelope) Check(trustStore TrustStore) error {
_, err := e.Correspondence(trustStore)
return err
}
// Suite returns the loaded suite.
func (e *Envelope) Suite() *Suite {
return e.suite
}
// LoadSuite loads the suite specified in the envelope.
func (e *Envelope) LoadSuite() error {
if e.suite == nil {
suite, ok := GetSuite(e.SuiteID)
if !ok {
return fmt.Errorf("suite %s does not exist", e.SuiteID)
}
e.suite = suite
}
return nil
}
// ReloadSuite forces reloading the suite specified in the envelope.
func (e *Envelope) ReloadSuite() error {
e.suite = nil
return e.LoadSuite()
}
// LoopSecrets loops over all secrets of the given scheme.
func (e *Envelope) LoopSecrets(scheme string, fn func(*Signet) error) error {
for _, signet := range e.Secrets {
if len(scheme) == 0 || signet.Scheme == scheme {
err := fn(signet)
if err != nil {
return err
}
}
}
return nil
}
// LoopSenders loops over all sender signets of the given scheme.
func (e *Envelope) LoopSenders(scheme string, fn func(*Signet) error) error {
for _, signet := range e.Senders {
if len(scheme) == 0 || signet.Scheme == scheme {
err := fn(signet)
if err != nil {
return err
}
}
}
return nil
}
// LoopRecipients loops over all recipient signets of the given scheme.
func (e *Envelope) LoopRecipients(scheme string, fn func(*Signet) error) error {
for _, signet := range e.Recipients {
if len(scheme) == 0 || signet.Scheme == scheme {
err := fn(signet)
if err != nil {
return err
}
}
}
return nil
}
// PrepareSignets checks that all signets of the envelope are ready to use. It will fetch referenced signets and load the keys.
func (e *Envelope) PrepareSignets(storage TrustStore) error {
err := e.prepSignets(e.Secrets, e.opening, storage)
if err != nil {
return err
}
err = e.prepSignets(e.Senders, e.opening, storage)
if err != nil {
return err
}
return e.prepSignets(e.Recipients, !e.opening, storage)
}
// prepSignets checks that all signets of the envelope are ready to use.
func (e *Envelope) prepSignets(signets []*Signet, recipients bool, storage TrustStore) error {
for i, signet := range signets {
// load from storage
if len(signet.Key) == 0 {
if signet.Scheme == SignetSchemePassword {
err := fillPassword(signet, !recipients, storage, e.suite.SecurityLevel)
if err != nil {
return fmt.Errorf(`failed to get password for "%s": %w`, signet.ID, err)
}
continue
}
// keys are _always_ signets
if signet.Scheme == SignetSchemeKey {
recipients = false
// TODO: spills to next loop
}
// signet is referrer
if len(signet.ID) == 0 {
return errors.New("signets must have a scheme+key or an ID")
}
// check if we have a storage
if storage == nil {
return fmt.Errorf(`failed to get signet with ID "%s": no truststore provided`, signet.ID)
}
// get signet from trust store
newSignet, err := storage.GetSignet(signet.ID, recipients)
if err != nil {
return fmt.Errorf(`failed to get signet with ID "%s" from truststore: %w`, signet.ID, err)
}
// check for scheme mismatch
if signet.Scheme != "" && signet.Scheme != newSignet.Scheme {
return fmt.Errorf(`failed to apply signet with ID "%s" from truststore: was expected to be of type %s, but is %s`, signet.ID, signet.Scheme, newSignet.Scheme)
}
// apply signet back into envelope
signet = newSignet
signets[i] = newSignet
}
// unwrap protection
if signet.Protection != nil {
return errors.New("protected signets are not yet supported")
}
// load signet
switch signet.Scheme {
case SignetSchemeKey, SignetSchemePassword:
// no loading needed
default:
err := signet.LoadKey()
if err != nil {
return err
}
}
}
return nil
}
func fillPassword(signet *Signet, createPassword bool, storage TrustStore, minSecurityLevel int) (err error) {
if createPassword {
if createPasswordCallback == nil {
return nil // ignore
}
} else if getPasswordCallback == nil {
return nil
}
// find reference
if signet.Info == nil || signet.Info.Name == "" {
// check trust store for name
if len(signet.ID) > 0 && storage != nil {
// get signet from trust store
newSignet, err := storage.GetSignet(signet.ID, false)
if err == nil && newSignet.Info != nil {
if signet.Info == nil {
signet.Info = newSignet.Info
} else {
signet.Info.Name = newSignet.Info.Name
}
}
}
}
if createPassword {
return createPasswordCallback(signet, minSecurityLevel)
}
return getPasswordCallback(signet)
}
// CleanSignets cleans all the signets from all the non-necessary data as well
// as key material.
// This is for preparing for serializing and saving the signet.
func (e *Envelope) CleanSignets() {
for i, signet := range e.Secrets {
e.Secrets[i] = &Signet{
Version: signet.Version,
ID: signet.ID,
Scheme: signet.Scheme,
}
}
for i, signet := range e.Senders {
e.Senders[i] = &Signet{
Version: signet.Version,
ID: signet.ID,
Scheme: signet.Scheme,
}
}
for i, signet := range e.Recipients {
e.Recipients[i] = &Signet{
Version: signet.Version,
ID: signet.ID,
Scheme: signet.Scheme,
}
}
}
// ToBytes serializes the envelope to a byte slice.
func (e *Envelope) ToBytes() ([]byte, error) {
// Minimize data and remove any key material.
e.CleanSignets()
// Serialize envelope.
data, err := dsd.Dump(e, dsd.CBOR)
if err != nil {
return nil, fmt.Errorf("failed to serialize the envelope: %w", err)
}
return data, nil
}
// EnvelopeFromBytes parses and loads a serialized envelope.
func EnvelopeFromBytes(data []byte) (*Envelope, error) {
e := &Envelope{}
// Parse envelope from data.
if _, err := dsd.Load(data, e); err != nil {
return nil, fmt.Errorf("failed to parse data format: %w", err)
}
return e, nil
}
// ToBase58 serializes the envelope and encodes it with base58.
func (e *Envelope) ToBase58() (string, error) {
// Serialize Signet.
data, err := e.ToBytes()
if err != nil {
return "", err
}
// Encode and return.
return base58.Encode(data), nil
}
// EnvelopeFromBase58 parses and loads a base58 encoded serialized envelope.
func EnvelopeFromBase58(base58Encoded string) (*Envelope, error) {
// Decode string.
data, err := base58.Decode(base58Encoded)
if err != nil {
return nil, fmt.Errorf("failed to decode base58: %w", err)
}
// Parse and return.
return EnvelopeFromBytes(data)
}