carlos / contra Public

Clone
git clone https://amadan.net/carlos/contra

Plain git — no account needed to clone.

Download

Download this file

1package crypto
2
3import (
4 "crypto/aes"
5 "crypto/cipher"
6 "encoding/binary"
7)
8
9// Object types, as they appear in the AAD. Namespaced by scope so an
10// account-scoped type can never collide with a vault-scoped one.
11const (
12 ObjectWrapPW = "wrap.pw"
13 ObjectWrapRC = "wrap.rc"
14 ObjectWrapPRF = "wrap.prf"
15 ObjectItem = "item"
16 ObjectHandoff = "handoff"
17)
18
19// AAD builds the additional authenticated data every AEAD in CONTRA binds, so
20// a malicious server cannot transplant a ciphertext between slots or between
21// users.
22//
23// The scope id is the account id for account-scoped objects and the vault id
24// for vault-scoped ones — which is how PROTOCOL.md's two spellings of the
25// binding, "(account_id, object_type, object_id, version)" and "(vault_id,
26// item_id, version)", turn out to be one rule.
27//
28// Length-prefixed because plain concatenation is not injective: without the
29// lengths, different tuples produce identical AAD, which is the exact
30// transplant the binding exists to prevent. Domain-tagged so AAD from this
31// protocol cannot be replayed into another one.
32func AAD(scopeID []byte, objectType string, objectID []byte, version uint64) []byte {
33 out := make([]byte, 0, len(LabelAAD)+1+4+len(scopeID)+4+len(objectType)+4+len(objectID)+8)
34 out = append(out, LabelAAD...)
35 out = append(out, 0x00)
36 out = binary.BigEndian.AppendUint32(out, uint32(len(scopeID)))
37 out = append(out, scopeID...)
38 out = binary.BigEndian.AppendUint32(out, uint32(len(objectType)))
39 out = append(out, objectType...)
40 out = binary.BigEndian.AppendUint32(out, uint32(len(objectID)))
41 out = append(out, objectID...)
42 return binary.BigEndian.AppendUint64(out, version)
43}
44
45func gcm(key []byte) (cipher.AEAD, error) {
46 block, err := aes.NewCipher(key)
47 if err != nil {
48 return nil, err
49 }
50 return cipher.NewGCM(block)
51}
52
53// Seal returns ciphertext with the 128-bit tag appended, the layout the wrap
54// rows and item rows both store.
55func Seal(key, nonce, aad, plaintext []byte) ([]byte, error) {
56 a, err := gcm(key)
57 if err != nil {
58 return nil, err
59 }
60 return a.Seal(nil, nonce, plaintext, aad), nil
61}
62
63// Open reverses Seal. A failure here is either the wrong key or a tampered
64// blob; the wrap's KCV is what tells those apart.
65func Open(key, nonce, aad, sealed []byte) ([]byte, error) {
66 a, err := gcm(key)
67 if err != nil {
68 return nil, err
69 }
70 return a.Open(nil, nonce, sealed, aad)
71}
72