-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
hidden.go
352 lines (320 loc) · 10.6 KB
/
hidden.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
package hidden
import (
"encoding/hex"
"fmt"
"github.com/keybase/client/go/libkb"
"github.com/keybase/client/go/merkletree2"
"github.com/keybase/client/go/protocol/keybase1"
"github.com/keybase/client/go/sig3"
)
func importChain(mctx libkb.MetaContext, raw []sig3.ExportJSON) (ret []sig3.Generic, err error) {
ret = make([]sig3.Generic, 0, len(raw))
for _, s := range raw {
g, err := s.Import()
if err != nil {
return nil, err
}
ret = append(ret, g)
}
return ret, nil
}
func populateLink(mctx libkb.MetaContext, ret *keybase1.HiddenTeamChain, link sig3.Generic) (err error) {
hsh, err := sig3.Hash(link)
if err != nil {
return err
}
q := link.Seqno()
ret.Outer[q] = hsh.Export()
if q > ret.Last {
ret.Last = q
}
if sig3.IsStubbed(link) {
return nil
}
rotateKey, ok := link.(*sig3.RotateKey)
if !ok {
return nil
}
rkex, err := rotateKey.Export()
if err != nil {
return err
}
ret.Inner[q] = *rkex
// Because this link isn't stubbed, we can bump the `LastFull` field
// forward if it's one more than previous. ret.LastFull will start at 0
// so this should work for the first link.
if ret.LastFull+1 == q {
ret.LastFull = q
}
// For each PTK (right now we really only expect one - the Reader PTK),
// update our maximum PTK generation
for _, ptk := range rotateKey.PTKs() {
max, ok := ret.LastPerTeamKeys[ptk.PTKType]
if !ok || max < q {
ret.LastPerTeamKeys[ptk.PTKType] = q
}
if ptk.PTKType == keybase1.PTKType_READER {
_, found := ret.ReaderPerTeamKeys[ptk.Generation]
if found {
return newRepeatPTKGenerationError(ptk.Generation, "clashes another hidden link")
}
ret.ReaderPerTeamKeys[ptk.Generation] = q
}
}
ret.MerkleRoots[q] = link.Inner().MerkleRoot.Export()
return nil
}
// GenerateKeyRotationParams are for generating a sig3 KeyRotation to store on the hidden team chain.
// Fill in all parameters of the struct (there were too many to pass as a list)
type GenerateKeyRotationParams struct {
TeamID keybase1.TeamID
IsPublic bool
IsImplicit bool
MerkleRoot *libkb.MerkleRoot
Me libkb.UserForSignatures
SigningKey libkb.GenericKey
MainPrev keybase1.LinkTriple
HiddenPrev *keybase1.LinkTriple
Gen keybase1.PerTeamKeyGeneration
NewSigningKey libkb.NaclSigningKeyPair
NewEncryptionKey libkb.NaclDHKeyPair
Check keybase1.PerTeamSeedCheck
Admin *sig3.ChainLocation
}
// GenerateKeyRotation generates and signs a new sig3 KeyRotation. The result can be passed to
// sig/multi.json and stored along with other sig1, sig2 or sig3 signatures in an atomic transaction.
func GenerateKeyRotation(mctx libkb.MetaContext, p GenerateKeyRotationParams) (ret *libkb.SigMultiItem, ratchets *keybase1.HiddenTeamChainRatchetSet, err error) {
s3, ratchets, err := generateKeyRotationSig3(mctx, p)
if err != nil {
return nil, nil, err
}
sigMultiItem := &libkb.SigMultiItem{
SigningKID: p.SigningKey.GetKID(),
Type: "team.rotate_key_hidden",
SeqType: sig3.ChainTypeTeamPrivateHidden,
TeamID: p.TeamID,
Sig3: &libkb.Sig3{
Inner: s3.Inner,
Outer: s3.Outer,
Sig: s3.Sig,
},
PublicKeys: &libkb.SigMultiItemPublicKeys{
Encryption: p.NewEncryptionKey.GetKID(),
Signing: p.NewSigningKey.GetKID(),
},
Version: 3,
}
return sigMultiItem, ratchets, nil
}
func generateKeyRotationSig3(mctx libkb.MetaContext, p GenerateKeyRotationParams) (ret *sig3.ExportJSON, ratchets *keybase1.HiddenTeamChainRatchetSet, err error) {
outer := sig3.OuterLink{}
if p.HiddenPrev != nil {
outer.Seqno = p.HiddenPrev.Seqno + 1
if p.HiddenPrev.LinkID.IsNil() {
return nil, nil, NewGenerateError("unexpected nil prev")
}
tmp, err := sig3.ImportLinkID(p.HiddenPrev.LinkID)
if err != nil {
return nil, nil, err
}
outer.Prev = tmp
} else {
outer.Seqno = keybase1.Seqno(1)
}
tmp, err := sig3.ImportTail(p.MainPrev)
if err != nil {
return nil, nil, err
}
rsq := p.MerkleRoot.Seqno()
if rsq == nil {
return nil, nil, NewGenerateError("cannot work with a nil merkle root seqno")
}
teamIDimport, err := sig3.ImportTeamID(p.TeamID)
if err != nil {
return nil, nil, err
}
now := mctx.G().Clock().Now()
inner := sig3.InnerLink{
Ctime: sig3.TimeSec(now.Unix()),
ClientInfo: &sig3.ClientInfo{
Desc: libkb.GoClientID,
Version: libkb.Version,
},
MerkleRoot: sig3.MerkleRoot{
Ctime: sig3.TimeSec(p.MerkleRoot.Ctime()),
Hash: p.MerkleRoot.HashMeta(),
Seqno: *rsq,
},
ParentChain: *tmp,
Signer: sig3.Signer{
UID: sig3.ImportUID(p.Me.GetUID()),
KID: sig3.ImportKID(p.SigningKey.GetKID()),
EldestSeqno: p.Me.GetEldestSeqno(),
},
Team: &sig3.Team{
Admin: p.Admin,
TeamID: *teamIDimport,
IsPublic: p.IsPublic,
IsImplicit: p.IsImplicit,
},
}
checkPostImage, err := p.Check.Hash()
if err != nil {
return nil, nil, err
}
rkb := sig3.RotateKeyBody{
PTKs: []sig3.PerTeamKey{
{
AppkeyDerivationVersion: sig3.AppkeyDerivationXOR,
Generation: p.Gen,
SeedCheck: *checkPostImage,
EncryptionKID: p.NewEncryptionKey.GetBinaryKID(),
SigningKID: p.NewSigningKey.GetBinaryKID(),
PTKType: keybase1.PTKType_READER,
},
},
}
keyPair := func(g libkb.GenericKey) (*sig3.KeyPair, error) {
signing, ok := g.(libkb.NaclSigningKeyPair)
if !ok {
return nil, NewGenerateError("bad key pair, wrong type: %T", g)
}
if signing.Private == nil {
return nil, NewGenerateError("bad key pair, got null private key")
}
return sig3.NewKeyPair(*signing.Private, g.GetBinaryKID()), nil
}
rk := sig3.NewRotateKey(outer, inner, rkb)
outerKeyPair, err := keyPair(p.SigningKey)
if err != nil {
return nil, nil, err
}
innerKeyPair, err := keyPair(p.NewSigningKey)
if err != nil {
return nil, nil, err
}
sig, err := rk.Sign(*outerKeyPair, []sig3.KeyPair{*innerKeyPair})
if err != nil {
return nil, nil, err
}
bun, err := sig.Export()
if err != nil {
return nil, nil, err
}
outer = sig.Outer
outerHash, err := outer.Hash()
if err != nil {
return nil, nil, err
}
ratchets = &keybase1.HiddenTeamChainRatchetSet{}
ratchets.Add(keybase1.RatchetType_SELF,
keybase1.LinkTripleAndTime{
Triple: keybase1.LinkTriple{
Seqno: outer.Seqno,
SeqType: sig3.ChainTypeTeamPrivateHidden,
LinkID: outerHash.Export(),
},
Time: keybase1.ToTime(now),
},
)
return &bun, ratchets, nil
}
func CheckFeatureGateForSupportWithRotationType(mctx libkb.MetaContext, teamID keybase1.TeamID, rt keybase1.RotationType) (ret keybase1.RotationType, err error) {
if rt == keybase1.RotationType_VISIBLE {
return rt, nil
}
ok, err := checkFeatureGateForSupport(mctx, teamID)
if err != nil {
return rt, err
}
switch {
case rt == keybase1.RotationType_CLKR && !ok:
return keybase1.RotationType_VISIBLE, nil
case rt == keybase1.RotationType_CLKR && ok:
return keybase1.RotationType_HIDDEN, nil
case rt == keybase1.RotationType_HIDDEN && ok:
return keybase1.RotationType_HIDDEN, nil
case rt == keybase1.RotationType_HIDDEN && !ok:
return keybase1.RotationType_HIDDEN, NewHiddenChainNotSupportedError(teamID)
default:
return keybase1.RotationType_HIDDEN, fmt.Errorf("unhandled case")
}
}
type rawSupport struct {
Status libkb.AppStatus `json:"status"`
Support bool `json:"support"`
}
func (r *rawSupport) GetAppStatus() *libkb.AppStatus {
return &r.Status
}
func featureGateForTeamFromServer(mctx libkb.MetaContext, teamID keybase1.TeamID) (ok bool, err error) {
arg := libkb.NewAPIArg("team/supports_hidden_chain")
arg.SessionType = libkb.APISessionTypeREQUIRED
arg.Args = libkb.HTTPArgs{
"id": libkb.S{Val: string(teamID)},
}
var raw rawSupport
err = mctx.G().API.GetDecode(mctx, arg, &raw)
if err != nil {
return false, err
}
return raw.Support, nil
}
func checkFeatureGateForSupport(mctx libkb.MetaContext, teamID keybase1.TeamID) (ok bool, err error) {
userFlagEnabled := mctx.G().FeatureFlags.Enabled(mctx, libkb.FeatureCheckForHiddenChainSupport)
runmode := mctx.G().Env.GetRunMode()
if runmode != libkb.ProductionRunMode {
return true, nil
}
if runmode == libkb.ProductionRunMode && !userFlagEnabled {
return false, nil
}
return mctx.G().GetHiddenTeamChainManager().TeamSupportsHiddenChain(mctx, teamID)
}
func CheckFeatureGateForSupport(mctx libkb.MetaContext, teamID keybase1.TeamID) (err error) {
ok, err := checkFeatureGateForSupport(mctx, teamID)
if err != nil {
return err
}
if !ok {
return NewHiddenChainNotSupportedError(teamID)
}
return nil
}
func ProcessHiddenResponseFunc(m libkb.MetaContext, teamID keybase1.TeamID, apiRes *libkb.APIRes, blindRootHashStr string) (hiddenResp *libkb.MerkleHiddenResponse, err error) {
if CheckFeatureGateForSupport(m, teamID) != nil {
m.Debug("Skipped ProcessHiddenResponseFunc as the feature flag is off (%v)", err)
return &libkb.MerkleHiddenResponse{RespType: libkb.MerkleHiddenResponseTypeFLAGOFF}, nil
}
if blindRootHashStr == "" {
m.Debug("blind tree root not found in the main tree: %v", err)
// TODO: Y2K-770 Until the root of the blind tree starts getting
// included in the main tree, we can get such root from the server as an
// additional parameter and assume the server is honest.
blindRootHashStr, err = apiRes.Body.AtKey("last_blind_root_hash").GetString()
if err != nil {
return &libkb.MerkleHiddenResponse{RespType: libkb.MerkleHiddenResponseTypeNONE}, nil
}
m.Debug("the server is providing a blind tree root which is not included in the main tree. We trust the server on this as the blind tree is an experimental feature.")
}
blindRootHashBytes, err := hex.DecodeString(blindRootHashStr)
if err != nil {
return nil, err
}
return ParseAndVerifyCommittedHiddenLinkID(m, teamID, apiRes, merkletree2.Hash(blindRootHashBytes))
}
func ParseAndVerifyCommittedHiddenLinkID(m libkb.MetaContext, teamID keybase1.TeamID, apiRes *libkb.APIRes, blindHash merkletree2.Hash) (hiddenResp *libkb.MerkleHiddenResponse, err error) {
lastHiddenSeqnoInt, err := apiRes.Body.AtKey("last_hidden_seqno").GetInt()
if err != nil {
m.Debug("Error decoding last_hidden_seqno (%v), assuming the server did not send it.", err.Error())
return &libkb.MerkleHiddenResponse{RespType: libkb.MerkleHiddenResponseTypeNONE}, nil
}
lastHiddenSeqno := keybase1.Seqno(lastHiddenSeqnoInt)
return makeHiddenRespFromTeamLeaf(m, lastHiddenSeqno)
}
func makeHiddenRespFromTeamLeaf(m libkb.MetaContext, lastHiddenSeqno keybase1.Seqno) (hiddenResp *libkb.MerkleHiddenResponse, err error) {
return &libkb.MerkleHiddenResponse{
RespType: libkb.MerkleHiddenResponseTypeOK,
UncommittedSeqno: lastHiddenSeqno,
}, nil
}