-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
crypto.go
375 lines (327 loc) · 11.2 KB
/
crypto.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
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.
package engine
import (
"sync"
"github.com/keybase/client/go/kbcrypto"
"github.com/keybase/client/go/libkb"
keybase1 "github.com/keybase/client/go/protocol/keybase1"
"golang.org/x/crypto/nacl/box"
"golang.org/x/net/context"
)
// getKeyMu synchronizes all accesses to the need to pull in pinentries/secret keys
// for this user.
var getKeyMu sync.Mutex
// GetMySecretKey uses ActiveDevice to get a secret key for the current user.
//
// It used to have functionality to load the user and prompt for a passphrase to
// unlock the keys, but that is outdated now. Either you are logged in and
// have your device keys cached, or you aren't.
//
// If the key isn't found in the ActiveDevice cache, this will return LoginRequiredError.
func GetMySecretKey(ctx context.Context, g *libkb.GlobalContext, secretKeyType libkb.SecretKeyType, reason string) (libkb.GenericKey, error) {
key, err := g.ActiveDevice.KeyByType(secretKeyType)
if err != nil {
if _, ok := err.(libkb.NotFoundError); ok {
g.Log.CDebugf(ctx, "GetMySecretKey: no device key of type %s in ActiveDevice, returning LoginRequiredError", secretKeyType)
return nil, libkb.LoginRequiredError{Context: "GetMySecretKey"}
}
g.Log.CDebugf(ctx, "GetMySecretKey(%s), unexpected error: %s", secretKeyType, err)
return nil, err
}
return key, nil
}
// GetMySecretKeyWithUID is like GetMySecretKey but returns an error if uid is not active.
func GetMySecretKeyWithUID(ctx context.Context, g *libkb.GlobalContext, uid keybase1.UID, secretKeyType libkb.SecretKeyType, reason string) (libkb.GenericKey, error) {
key, err := g.ActiveDevice.KeyByTypeWithUID(uid, secretKeyType)
if err != nil {
if _, ok := err.(libkb.NotFoundError); ok {
g.Log.CDebugf(ctx, "GetMySecretKeyWithUID: no device key of type %s in ActiveDevice, returning LoginRequiredError", secretKeyType)
return nil, libkb.LoginRequiredError{Context: "GetMySecretKey"}
}
g.Log.CDebugf(ctx, "GetMySecretKeyWithUID(%s), unexpected error: %s", secretKeyType, err)
return nil, err
}
return key, nil
}
// SignED25519 signs the given message with the current user's private
// signing key.
func SignED25519(ctx context.Context, g *libkb.GlobalContext, arg keybase1.SignED25519Arg) (ret keybase1.ED25519SignatureInfo, err error) {
signingKey, err := GetMySecretKey(ctx, g, libkb.DeviceSigningKeyType, arg.Reason)
if err != nil {
return
}
kp, ok := signingKey.(libkb.NaclSigningKeyPair)
if !ok || kp.Private == nil {
err = libkb.KeyCannotSignError{}
return
}
sig := kp.Private.Sign(arg.Msg)
publicKey := kp.Public
ret = keybase1.ED25519SignatureInfo{
Sig: keybase1.ED25519Signature(sig),
PublicKey: keybase1.ED25519PublicKey(publicKey),
}
return
}
// SignED25519ForKBFS signs the given message with the current user's private
// signing key on behalf of KBFS.
func SignED25519ForKBFS(ctx context.Context, g *libkb.GlobalContext, arg keybase1.SignED25519ForKBFSArg) (
ret keybase1.ED25519SignatureInfo, err error) {
signingKey, err := GetMySecretKey(ctx, g, libkb.DeviceSigningKeyType, arg.Reason)
if err != nil {
return
}
kp, ok := signingKey.(libkb.NaclSigningKeyPair)
if !ok || kp.Private == nil {
err = libkb.KeyCannotSignError{}
return
}
var sigInfo kbcrypto.NaclSigInfo
sigInfo, err = kp.SignV2(arg.Msg, kbcrypto.SignaturePrefixKBFS)
if err != nil {
return
}
publicKey := kp.Public
ret = keybase1.ED25519SignatureInfo{
Sig: keybase1.ED25519Signature(sigInfo.Sig),
PublicKey: keybase1.ED25519PublicKey(publicKey),
}
return
}
// SignToString signs the given message with the current user's private
// signing key and outputs the serialized NaclSigInfo string.
func SignToString(ctx context.Context, g *libkb.GlobalContext, arg keybase1.SignToStringArg) (sig string, err error) {
signingKey, err := GetMySecretKey(ctx, g, libkb.DeviceSigningKeyType, arg.Reason)
if err != nil {
return
}
kp, ok := signingKey.(libkb.NaclSigningKeyPair)
if !ok || kp.Private == nil {
err = libkb.KeyCannotSignError{}
return
}
sig, _, err = kp.SignToString(arg.Msg)
return
}
// UnboxBytes32 decrypts the given message with the current user's
// private encryption key and the given nonce and peer public key.
func UnboxBytes32(ctx context.Context, g *libkb.GlobalContext, arg keybase1.UnboxBytes32Arg) (bytes32 keybase1.Bytes32, err error) {
encryptionKey, err := GetMySecretKey(ctx, g, libkb.DeviceEncryptionKeyType, arg.Reason)
if err != nil {
return
}
return unboxBytes32(encryptionKey, arg.EncryptedBytes32, arg.Nonce, arg.PeersPublicKey)
}
// UnboxBytes32Any will decrypt any of the KID, ciphertext, nonce
// bundles in arg.Bundles. Key preference order: cached device keys,
// cached paper keys, local device key, user-entered paper key.
// It returns the KID and bundle index along with the plaintext.
func UnboxBytes32Any(m libkb.MetaContext, getSecretUI func() libkb.SecretUI, arg keybase1.UnboxBytes32AnyArg) (res keybase1.UnboxAnyRes, err error) {
defer m.Trace("UnboxBytes32Any", &err)()
// find a matching secret key for a bundle in arg.Bundles
key, index, err := getMatchingSecretKey(m, getSecretUI, arg)
if err != nil {
return res, err
}
// decrypt the bundle's ciphertext
plaintext, err := unboxBytes32(key, arg.Bundles[index].Ciphertext, arg.Bundles[index].Nonce, arg.Bundles[index].PublicKey)
if err != nil {
return res, err
}
// return plaintext, kid, and index
res.Plaintext = plaintext
res.Kid = key.GetKID()
res.Index = index
return res, nil
}
func unboxBytes32(encryptionKey libkb.GenericKey, ciphertext keybase1.EncryptedBytes32, nonce keybase1.BoxNonce, peerPubKey keybase1.BoxPublicKey) (bytes32 keybase1.Bytes32, err error) {
kp, ok := encryptionKey.(libkb.NaclDHKeyPair)
if !ok {
err = libkb.KeyCannotDecryptError{}
return
}
if kp.Private == nil {
err = libkb.NoSecretKeyError{}
return
}
decryptedData, ok := box.Open(nil, ciphertext[:], (*[24]byte)(&nonce), (*[32]byte)(&peerPubKey), (*[32]byte)(kp.Private))
if !ok {
err = libkb.DecryptionError{}
return
}
if len(decryptedData) != len(bytes32) {
err = libkb.DecryptionError{}
return
}
copy(bytes32[:], decryptedData)
return
}
func getMatchingSecretKey(m libkb.MetaContext, getSecretUI func() libkb.SecretUI, arg keybase1.UnboxBytes32AnyArg) (key libkb.GenericKey, index int, err error) {
// first check cached keys
key, index, err = matchingCachedKey(m, arg)
if err != nil {
return nil, 0, err
}
if key != nil {
return key, index, nil
}
m.Debug("getMatchingSecretKey: acquiring lock")
getKeyMu.Lock()
defer func() {
getKeyMu.Unlock()
m.Debug("getMatchingSecretKey: lock released")
}()
m.Debug("getMatchingSecretKey: lock acquired")
// check cache after acquiring lock
key, index, err = matchingCachedKey(m, arg)
if err != nil {
return nil, 0, err
}
if key != nil {
return key, index, nil
}
m.Debug("getMatchingSecretKey: no matching cached device key found")
// load the user
me, err := libkb.LoadMe(libkb.NewLoadUserArgWithMetaContext(m))
if err != nil {
return nil, 0, err
}
// need secretUI now:
secretUI := getSecretUI()
// check the device key for this user
key, index, err = matchingDeviceKey(m, secretUI, arg, me)
if err != nil {
return nil, 0, err
}
if key != nil {
return key, index, nil
}
m.Debug("getMatchingSecretKey: no matching device key found")
if !arg.PromptPaper {
m.Debug("UnboxBytes32Any/getMatchingSecretKey: not checking paper keys (promptPaper == false)")
return nil, 0, libkb.NoSecretKeyError{}
}
// check the paper keys for this user
key, index, err = matchingPaperKey(m, secretUI, arg, me)
if err != nil {
return nil, 0, err
}
if key != nil {
return key, index, nil
}
return nil, 0, libkb.NoSecretKeyError{}
}
// check cached keys for arg.Bundles match.
func matchingCachedKey(m libkb.MetaContext, arg keybase1.UnboxBytes32AnyArg) (key libkb.GenericKey, index int, err error) {
// check device key first
dkey, err := m.ActiveDevice().EncryptionKey()
if err == nil && dkey != nil {
if n, ok := kidMatch(dkey, arg.Bundles); ok {
return dkey, n, nil
}
}
device := m.ActiveDevice().ProvisioningKey(m)
if device != nil {
pkey := device.EncryptionKey()
if n, ok := kidMatch(pkey, arg.Bundles); ok {
return pkey, n, nil
}
}
return nil, 0, nil
}
// check device key for arg.Bundles match.
func matchingDeviceKey(m libkb.MetaContext, secretUI libkb.SecretUI, arg keybase1.UnboxBytes32AnyArg, me *libkb.User) (key libkb.GenericKey, index int, err error) {
ekey, err := me.GetDeviceSubkey()
if err == nil {
if n, ok := kidMatch(ekey, arg.Bundles); ok {
// unlock this key
parg := libkb.SecretKeyPromptArg{
Ska: libkb.SecretKeyArg{
Me: me,
KeyType: libkb.DeviceEncryptionKeyType,
},
SecretUI: secretUI,
Reason: arg.Reason,
UseCancelCache: true,
}
key, err := m.G().Keyrings.GetSecretKeyWithPrompt(m, parg)
if err != nil {
return nil, 0, err
}
return key, n, nil
}
m.Debug("matchingDeviceKey: no match found for ekey in arg.Bundles")
logNoMatch(m, ekey, arg.Bundles)
} else {
m.Debug("matchingDeviceKey: ignoring error getting device subkey: %s", err)
}
return nil, 0, nil
}
// check all the user's paper keys for arg.Bundles match
func matchingPaperKey(m libkb.MetaContext, secretUI libkb.SecretUI, arg keybase1.UnboxBytes32AnyArg, me *libkb.User) (key libkb.GenericKey, index int, err error) {
cki := me.GetComputedKeyInfos()
if cki == nil {
return nil, 0, nil
}
var matchingPaper []*libkb.Device
for _, pdev := range cki.PaperDevices() {
enckey, err := me.GetComputedKeyFamily().GetEncryptionSubkeyForDevice(pdev.ID)
if err != nil {
return nil, 0, err
}
if _, ok := kidMatch(enckey, arg.Bundles); ok {
m.Debug("matching paper key: %s", *pdev.Description)
matchingPaper = append(matchingPaper, pdev)
}
}
if len(matchingPaper) == 0 {
m.Debug("no matching paper keys found")
return nil, 0, nil
}
phrase, err := libkb.GetPaperKeyForCryptoPassphrase(m, secretUI, arg.Reason, matchingPaper)
if err != nil {
return nil, 0, err
}
paperPhrase, err := libkb.NewPaperKeyPhraseCheckVersion(m, phrase)
if err != nil {
return nil, 0, err
}
bkarg := &PaperKeyGenArg{
Passphrase: paperPhrase,
SkipPush: true,
}
bkeng := NewPaperKeyGen(m.G(), bkarg)
if err := RunEngine2(m, bkeng); err != nil {
return nil, 0, err
}
// find the index for the key they entered (and make sure the key they entered matches)
if n, ok := kidMatch(bkeng.EncKey(), arg.Bundles); ok {
m.ActiveDevice().CacheProvisioningKey(m, bkeng.DeviceWithKeys())
return bkeng.EncKey(), n, nil
}
return nil, 0, nil
}
func kidMatch(key libkb.GenericKey, bundles []keybase1.CiphertextBundle) (int, bool) {
if key == nil {
return -1, false
}
kid := key.GetKID()
for i, bundle := range bundles {
if kid.Equal(bundle.Kid) {
return i, true
}
}
return -1, false
}
func logNoMatch(m libkb.MetaContext, key libkb.GenericKey, bundles []keybase1.CiphertextBundle) {
if key == nil {
m.Debug("logNoMatch: key is nil")
return
}
kid := key.GetKID()
m.Debug("logNoMatch: desired kid: %s", kid)
for i, bundle := range bundles {
m.Debug("logNoMatch: kid %d: %s (%v)", i, bundle.Kid, kid.Equal(bundle.Kid))
}
}