oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1package docs
2
3import (
4 "crypto/aes"
5 "crypto/cipher"
6 "crypto/rand"
7 "crypto/sha256"
8 "encoding/base64"
9 "encoding/binary"
10 "encoding/hex"
11 "errors"
12 "strings"
13
14 "github.com/google/uuid"
15)
16
17const contentEncoding = 1
18const contentUpdateKind byte = 1
19const contentSnapshotKind byte = 2
20const contentKeyCheckKind byte = 3
21
22var developmentContentKey = sha256.Sum256([]byte("docs-development-content-key-not-a-secret"))
23var errContentKey = errors.New("docs: invalid content key")
24var errContentAuthentication = errors.New("docs: content authentication failed")
25var errContentEncoding = errors.New("docs: unsupported content encoding")
26
27type contentCipher struct {
28 aead cipher.AEAD
29 fingerprint string
30}
31
32func newContentCipher(encoded, env string) (*contentCipher, error) {
33 encoded = strings.TrimSpace(encoded)
34 var key []byte
35 if encoded == "" && env == "development" {
36 key = developmentContentKey[:]
37 } else {
38 var err error
39 key, err = base64.StdEncoding.Strict().DecodeString(encoded)
40 if err != nil || len(key) != 32 {
41 return nil, errContentKey
42 }
43 }
44 fingerprint := sha256.Sum256(key)
45 devFingerprint := sha256.Sum256(developmentContentKey[:])
46 if env != "development" && fingerprint == devFingerprint {
47 return nil, errContentKey
48 }
49 block, err := aes.NewCipher(key)
50 if err != nil {
51 return nil, errContentKey
52 }
53 aead, err := cipher.NewGCM(block)
54 if err != nil {
55 return nil, err
56 }
57 return &contentCipher{aead: aead, fingerprint: hex.EncodeToString(fingerprint[:])}, nil
58}
59
60func contentAAD(kind byte, guid string, seq int64, abi uint32) ([]byte, error) {
61 id, err := uuid.Parse(guid)
62 if err != nil || seq < 0 {
63 return nil, errContentAuthentication
64 }
65 aad := append([]byte("oficina/docs/content/v1"), kind)
66 aad = append(aad, id[:]...)
67 aad = binary.BigEndian.AppendUint64(aad, uint64(seq))
68 aad = binary.BigEndian.AppendUint32(aad, abi)
69 return aad, nil
70}
71
72func (c *contentCipher) seal(kind byte, guid string, seq int64, abi uint32, plaintext []byte) ([]byte, error) {
73 aad, err := contentAAD(kind, guid, seq, abi)
74 if err != nil {
75 return nil, err
76 }
77 nonce := make([]byte, c.aead.NonceSize())
78 if _, err := rand.Read(nonce); err != nil {
79 return nil, err
80 }
81 return c.aead.Seal(nonce, nonce, plaintext, aad), nil
82}
83
84func (c *contentCipher) open(kind byte, guid string, seq int64, abi uint32, sealed []byte) ([]byte, error) {
85 aad, err := contentAAD(kind, guid, seq, abi)
86 if err != nil || len(sealed) < c.aead.NonceSize()+c.aead.Overhead() {
87 return nil, errContentAuthentication
88 }
89 n := c.aead.NonceSize()
90 plaintext, err := c.aead.Open(nil, sealed[:n], sealed[n:], aad)
91 if err != nil {
92 return nil, errContentAuthentication
93 }
94 return plaintext, nil
95}
96