1package git
2
3import (
4	"fmt"
5
6	"golang.org/x/crypto/nacl/secretbox"
7	"golang.org/x/net/context"
8
9	"github.com/keybase/client/go/libkb"
10	"github.com/keybase/client/go/protocol/keybase1"
11	"github.com/keybase/client/go/teams"
12)
13
14// publicCryptKey is a zero key used for public repos
15var publicCryptKey keybase1.TeamApplicationKey
16
17func init() {
18	var zero [libkb.NaclDHKeySecretSize]byte
19	publicCryptKey = keybase1.TeamApplicationKey{
20		Application:   keybase1.TeamApplication_GIT_METADATA,
21		KeyGeneration: 1,
22		Key:           keybase1.Bytes32(zero),
23	}
24}
25
26// Crypto implements Cryptoer interface.
27type Crypto struct {
28	libkb.Contextified
29}
30
31var _ Cryptoer = &Crypto{}
32
33// NewCrypto returns a Crypto object.
34func NewCrypto(g *libkb.GlobalContext) *Crypto {
35	return &Crypto{
36		Contextified: libkb.NewContextified(g),
37	}
38}
39
40// Box encrypts the plaintext with the most current key for the given team. It yields a NaCl
41// ciphertext and nonce, and also says which generation of the key it used.
42func (c *Crypto) Box(ctx context.Context, plaintext []byte, teamSpec keybase1.TeamIDWithVisibility) (*keybase1.EncryptedGitMetadata, error) {
43	team, err := c.loadTeam(ctx, teamSpec, 0)
44	if err != nil {
45		return nil, err
46	}
47
48	public := teamSpec.Visibility == keybase1.TLFVisibility_PUBLIC
49
50	key := publicCryptKey
51	if !public {
52		key, err = team.GitMetadataKey(ctx)
53		if err != nil {
54			return nil, err
55		}
56	}
57
58	nonce, err := libkb.RandomNaclDHNonce()
59	if err != nil {
60		return nil, err
61	}
62
63	var encKey [libkb.NaclSecretBoxKeySize]byte = key.Key
64	sealed := secretbox.Seal(nil, plaintext, &nonce, &encKey)
65
66	return &keybase1.EncryptedGitMetadata{
67		V:   libkb.CurrentGitMetadataEncryptionVersion,
68		E:   sealed,
69		N:   nonce,
70		Gen: key.KeyGeneration,
71	}, nil
72}
73
74// Unbox decrypts the given ciphertext with the given nonce, for the given generation of the
75// given team. Can return an error. Will return a non-nil plaintext on success.
76func (c *Crypto) Unbox(ctx context.Context, teamSpec keybase1.TeamIDWithVisibility, metadata *keybase1.EncryptedGitMetadata) (plaintext []byte, err error) {
77	defer c.G().CTrace(ctx, fmt.Sprintf("git.Crypto#Unbox(%s, vis:%v)", teamSpec.TeamID, teamSpec.Visibility), &err)()
78
79	if metadata.V != 1 {
80		return nil, fmt.Errorf("invalid EncryptedGitMetadata version: %d", metadata.V)
81	}
82
83	public := teamSpec.Visibility == keybase1.TLFVisibility_PUBLIC
84	if public != teamSpec.TeamID.IsPublic() {
85		return nil, libkb.NewTeamVisibilityError(public, teamSpec.TeamID.IsPublic())
86	}
87
88	key := publicCryptKey
89	if !public {
90		key, err = c.fastLoadKeyAtGeneration(ctx, teamSpec, metadata)
91		if err != nil {
92			return nil, err
93		}
94	}
95
96	var encKey [libkb.NaclSecretBoxKeySize]byte = key.Key
97	var naclNonce [libkb.NaclDHNonceSize]byte = metadata.N
98
99	plaintext, ok := secretbox.Open(nil, metadata.E, &naclNonce, &encKey)
100	if !ok {
101		return nil, libkb.DecryptOpenError{}
102	}
103	return plaintext, nil
104}
105
106func (c *Crypto) fastLoadKeyAtGeneration(ctx context.Context, teamSpec keybase1.TeamIDWithVisibility, metadata *keybase1.EncryptedGitMetadata) (key keybase1.TeamApplicationKey, err error) {
107	teamID := teamSpec.TeamID
108	arg := keybase1.FastTeamLoadArg{
109		ID:                   teamID,
110		Public:               false,
111		Applications:         []keybase1.TeamApplication{keybase1.TeamApplication_GIT_METADATA},
112		KeyGenerationsNeeded: []keybase1.PerTeamKeyGeneration{metadata.Gen},
113	}
114	mctx := libkb.NewMetaContext(ctx, c.G())
115	res, err := mctx.G().GetFastTeamLoader().Load(mctx, arg)
116	if err != nil {
117		return key, err
118	}
119	n := len(res.ApplicationKeys)
120	if n != 1 {
121		return key, fmt.Errorf("wrong number of keys back from FTL; wanted 1 but got %d", n)
122	}
123	if metadata.Gen > 0 && res.ApplicationKeys[0].KeyGeneration != metadata.Gen {
124		return key, fmt.Errorf("wrong generation back from FTL; wanted %d but got %d", metadata.Gen, res.ApplicationKeys[0].KeyGeneration)
125	}
126
127	if res.ApplicationKeys[0].Application != keybase1.TeamApplication_GIT_METADATA {
128		return key, fmt.Errorf("wrong application; wanted %d but got %d", keybase1.TeamApplication_GIT_METADATA, res.ApplicationKeys[0].Application)
129	}
130	return res.ApplicationKeys[0], nil
131}
132
133func (c *Crypto) loadTeam(ctx context.Context, teamSpec keybase1.TeamIDWithVisibility, needKeyGeneration keybase1.PerTeamKeyGeneration) (*teams.Team, error) {
134	public := teamSpec.Visibility == keybase1.TLFVisibility_PUBLIC
135	arg := keybase1.LoadTeamArg{
136		ID:     teamSpec.TeamID,
137		Public: public,
138	}
139	if needKeyGeneration != 0 {
140		arg.Refreshers.NeedApplicationsAtGenerations = map[keybase1.PerTeamKeyGeneration][]keybase1.TeamApplication{
141			needKeyGeneration: {keybase1.TeamApplication_GIT_METADATA},
142		}
143	}
144	team, err := teams.Load(ctx, c.G(), arg)
145	if err != nil {
146		return nil, err
147	}
148
149	return team, nil
150}
151