v0.33.0 cf26365

secretbox.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3// Package secretbox seals the values of CI secrets with AES-256-GCM under the
4// server's master key (ERMITE_SECRETS_KEY, ADR 0019). Each value gets its own
5// random nonce, and the additional data given by the caller (the scope and the
6// name of the secret) is authenticated with it, so a sealed value moved to
7// another secret does not open.
8package secretbox
9
10import (
11	"crypto/aes"
12	"crypto/cipher"
13	"crypto/hmac"
14	"crypto/rand"
15	"crypto/sha256"
16	"errors"
17	"fmt"
18
19	"bnema.dev/ermite/internal/ports"
20)
21
22// KeyLen is the length of the master key.
23const KeyLen = 32
24
25// version is the first byte of a sealed value, so that the format can change.
26const version byte = 1
27
28// Box seals and opens secret values. It is safe for concurrent use.
29type Box struct {
30	aead  cipher.AEAD
31	check []byte
32}
33
34var _ ports.SecretSealer = (*Box)(nil)
35
36// New builds a Box from a key of KeyLen bytes. The error never holds the key.
37func New(key []byte) (*Box, error) {
38	if len(key) != KeyLen {
39		return nil, fmt.Errorf("the secrets key must be %d bytes, got %d", KeyLen, len(key))
40	}
41	block, err := aes.NewCipher(key)
42	if err != nil {
43		return nil, errors.New("the secrets key is not usable")
44	}
45	aead, err := cipher.NewGCM(block)
46	if err != nil {
47		return nil, errors.New("the secrets key is not usable")
48	}
49	mac := hmac.New(sha256.New, key)
50	mac.Write([]byte("ermite secrets key check v1"))
51	return &Box{aead: aead, check: mac.Sum(nil)}, nil
52}
53
54// Seal returns version, nonce and ciphertext (with its tag) of plaintext,
55// bound to aad.
56func (b *Box) Seal(plaintext, aad []byte) []byte {
57	out := make([]byte, 1, 1+b.aead.NonceSize()+len(plaintext)+b.aead.Overhead())
58	out[0] = version
59	nonce := make([]byte, b.aead.NonceSize())
60	_, _ = rand.Read(nonce) // crypto/rand does not fail: it aborts the program
61	out = append(out, nonce...)
62	return b.aead.Seal(out, nonce, plaintext, aad)
63}
64
65// Open returns the plaintext of a value sealed with the same key and aad. Any
66// other key, aad or a changed byte is ports.ErrSecretNotOpened.
67func (b *Box) Open(sealed, aad []byte) ([]byte, error) {
68	ns := b.aead.NonceSize()
69	if len(sealed) < 1+ns+b.aead.Overhead() || sealed[0] != version {
70		return nil, ports.ErrSecretNotOpened
71	}
72	plain, err := b.aead.Open(nil, sealed[1:1+ns], sealed[1+ns:], aad)
73	if err != nil {
74		return nil, ports.ErrSecretNotOpened
75	}
76	return plain, nil
77}
78
79// KeyCheck is a value derived from the key that does not reveal it (an HMAC of
80// a constant), stored beside the secrets so that the server can tell, at start,
81// that it runs with another key than the one they were sealed with.
82func (b *Box) KeyCheck() []byte { return append([]byte(nil), b.check...) }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close