forked from keybase/client
/
crypto.go
150 lines (126 loc) · 4.63 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
package git
import (
"fmt"
"golang.org/x/crypto/nacl/secretbox"
"golang.org/x/net/context"
"github.com/keybase/client/go/libkb"
"github.com/keybase/client/go/protocol/keybase1"
"github.com/keybase/client/go/teams"
)
// publicCryptKey is a zero key used for public repos
var publicCryptKey keybase1.TeamApplicationKey
func init() {
var zero [libkb.NaclDHKeySecretSize]byte
publicCryptKey = keybase1.TeamApplicationKey{
Application: keybase1.TeamApplication_GIT_METADATA,
KeyGeneration: 1,
Key: keybase1.Bytes32(zero),
}
}
// Crypto implements Cryptoer interface.
type Crypto struct {
libkb.Contextified
}
var _ Cryptoer = &Crypto{}
// NewCrypto returns a Crypto object.
func NewCrypto(g *libkb.GlobalContext) *Crypto {
return &Crypto{
Contextified: libkb.NewContextified(g),
}
}
// Box encrypts the plaintext with the most current key for the given team. It yields a NaCl
// ciphertext and nonce, and also says which generation of the key it used.
func (c *Crypto) Box(ctx context.Context, plaintext []byte, teamSpec keybase1.TeamIDWithVisibility) (*keybase1.EncryptedGitMetadata, error) {
team, err := c.loadTeam(ctx, teamSpec, 0)
if err != nil {
return nil, err
}
public := teamSpec.Visibility == keybase1.TLFVisibility_PUBLIC
key := publicCryptKey
if !public {
key, err = team.GitMetadataKey(ctx)
if err != nil {
return nil, err
}
}
nonce, err := libkb.RandomNaclDHNonce()
if err != nil {
return nil, err
}
var encKey [libkb.NaclSecretBoxKeySize]byte = key.Key
sealed := secretbox.Seal(nil, plaintext, &nonce, &encKey)
return &keybase1.EncryptedGitMetadata{
V: libkb.CurrentGitMetadataEncryptionVersion,
E: sealed,
N: nonce,
Gen: key.KeyGeneration,
}, nil
}
// Unbox decrypts the given ciphertext with the given nonce, for the given generation of the
// given team. Can return an error. Will return a non-nil plaintext on success.
func (c *Crypto) Unbox(ctx context.Context, teamSpec keybase1.TeamIDWithVisibility, metadata *keybase1.EncryptedGitMetadata) (plaintext []byte, err error) {
defer c.G().CTrace(ctx, fmt.Sprintf("git.Crypto#Unbox(%s, vis:%v)", teamSpec.TeamID, teamSpec.Visibility), &err)()
if metadata.V != 1 {
return nil, fmt.Errorf("invalid EncryptedGitMetadata version: %d", metadata.V)
}
public := teamSpec.Visibility == keybase1.TLFVisibility_PUBLIC
if public != teamSpec.TeamID.IsPublic() {
return nil, libkb.NewTeamVisibilityError(public, teamSpec.TeamID.IsPublic())
}
key := publicCryptKey
if !public {
key, err = c.fastLoadKeyAtGeneration(ctx, teamSpec, metadata)
if err != nil {
return nil, err
}
}
var encKey [libkb.NaclSecretBoxKeySize]byte = key.Key
var naclNonce [libkb.NaclDHNonceSize]byte = metadata.N
plaintext, ok := secretbox.Open(nil, metadata.E, &naclNonce, &encKey)
if !ok {
return nil, libkb.DecryptOpenError{}
}
return plaintext, nil
}
func (c *Crypto) fastLoadKeyAtGeneration(ctx context.Context, teamSpec keybase1.TeamIDWithVisibility, metadata *keybase1.EncryptedGitMetadata) (key keybase1.TeamApplicationKey, err error) {
teamID := teamSpec.TeamID
arg := keybase1.FastTeamLoadArg{
ID: teamID,
Public: false,
Applications: []keybase1.TeamApplication{keybase1.TeamApplication_GIT_METADATA},
KeyGenerationsNeeded: []keybase1.PerTeamKeyGeneration{metadata.Gen},
}
mctx := libkb.NewMetaContext(ctx, c.G())
res, err := mctx.G().GetFastTeamLoader().Load(mctx, arg)
if err != nil {
return key, err
}
n := len(res.ApplicationKeys)
if n != 1 {
return key, fmt.Errorf("wrong number of keys back from FTL; wanted 1 but got %d", n)
}
if metadata.Gen > 0 && res.ApplicationKeys[0].KeyGeneration != metadata.Gen {
return key, fmt.Errorf("wrong generation back from FTL; wanted %d but got %d", metadata.Gen, res.ApplicationKeys[0].KeyGeneration)
}
if res.ApplicationKeys[0].Application != keybase1.TeamApplication_GIT_METADATA {
return key, fmt.Errorf("wrong application; wanted %d but got %d", keybase1.TeamApplication_GIT_METADATA, res.ApplicationKeys[0].Application)
}
return res.ApplicationKeys[0], nil
}
func (c *Crypto) loadTeam(ctx context.Context, teamSpec keybase1.TeamIDWithVisibility, needKeyGeneration keybase1.PerTeamKeyGeneration) (*teams.Team, error) {
public := teamSpec.Visibility == keybase1.TLFVisibility_PUBLIC
arg := keybase1.LoadTeamArg{
ID: teamSpec.TeamID,
Public: public,
}
if needKeyGeneration != 0 {
arg.Refreshers.NeedApplicationsAtGenerations = map[keybase1.PerTeamKeyGeneration][]keybase1.TeamApplication{
needKeyGeneration: {keybase1.TeamApplication_GIT_METADATA},
}
}
team, err := teams.Load(ctx, c.G(), arg)
if err != nil {
return nil, err
}
return team, nil
}