check / check (push) Successful in 10m25s
The missing-key error for restore and deep verify is one value, errDecryptionKeyRequired, whose text names both places the key can be set: the VAULTIK_AGE_SECRET_KEY environment variable first, then the age_secret_key config file field. Model: deepseek-v4-pro
129 lines
3.9 KiB
Go
129 lines
3.9 KiB
Go
package vaultik //nolint:testpackage // exercises unexported restoreIdentities
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"testing"
|
|
|
|
"filippo.io/age"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/vaultik/internal/blobgen"
|
|
"sneak.berlin/go/vaultik/internal/config"
|
|
)
|
|
|
|
// encryptBlobTo returns a blobgen blob of plaintext encrypted to exactly
|
|
// one recipient, so a decryptor succeeds only if it holds that recipient's
|
|
// identity.
|
|
func encryptBlobTo(t *testing.T, recipient string, plaintext []byte) []byte {
|
|
t.Helper()
|
|
|
|
var buf bytes.Buffer
|
|
|
|
writer, err := blobgen.NewWriter(&buf, 1, []string{recipient})
|
|
require.NoError(t, err)
|
|
|
|
_, err = writer.Write(plaintext)
|
|
require.NoError(t, err)
|
|
require.NoError(t, writer.Close())
|
|
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// decryptBlobWith reads a blob back through the identities and returns its
|
|
// plaintext.
|
|
func decryptBlobWith(t *testing.T, blob []byte, identities []age.Identity) []byte {
|
|
t.Helper()
|
|
|
|
reader, err := blobgen.NewReader(bytes.NewReader(blob), identities...)
|
|
require.NoError(t, err)
|
|
|
|
plaintext, err := io.ReadAll(reader)
|
|
require.NoError(t, err)
|
|
require.NoError(t, reader.Close())
|
|
|
|
return plaintext
|
|
}
|
|
|
|
// TestRestoreIdentitiesAcceptsEveryIdentity proves a key file holding two
|
|
// identities yields both, so a blob encrypted only to the second
|
|
// recipient — the one the previous single-identity parse dropped — still
|
|
// decrypts.
|
|
func TestRestoreIdentitiesAcceptsEveryIdentity(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
first, err := age.GenerateX25519Identity()
|
|
require.NoError(t, err)
|
|
|
|
second, err := age.GenerateX25519Identity()
|
|
require.NoError(t, err)
|
|
|
|
// A whole age-keygen-style file: comment lines plus two identity lines.
|
|
keyFile := "# public key: " + first.Recipient().String() + "\n" +
|
|
first.String() + "\n" +
|
|
"# public key: " + second.Recipient().String() + "\n" +
|
|
second.String() + "\n"
|
|
|
|
v := &Vaultik{Config: &config.Config{AgeSecretKey: keyFile}}
|
|
|
|
identities, err := v.restoreIdentities()
|
|
require.NoError(t, err)
|
|
require.Len(t, identities, 2)
|
|
|
|
plaintext := []byte("payload encrypted only to the second identity")
|
|
blob := encryptBlobTo(t, second.Recipient().String(), plaintext)
|
|
|
|
require.Equal(t, plaintext, decryptBlobWith(t, blob, identities))
|
|
}
|
|
|
|
// TestRestoreIdentitiesAcceptsTrailingNewline mirrors a YAML
|
|
// age_secret_key value that carries a trailing newline: it must still
|
|
// parse to its one identity and decrypt a blob encrypted to it.
|
|
func TestRestoreIdentitiesAcceptsTrailingNewline(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
id, err := age.GenerateX25519Identity()
|
|
require.NoError(t, err)
|
|
|
|
v := &Vaultik{Config: &config.Config{AgeSecretKey: id.String() + "\n"}}
|
|
|
|
identities, err := v.restoreIdentities()
|
|
require.NoError(t, err)
|
|
require.Len(t, identities, 1)
|
|
|
|
plaintext := []byte("value with a trailing newline")
|
|
blob := encryptBlobTo(t, id.Recipient().String(), plaintext)
|
|
|
|
require.Equal(t, plaintext, decryptBlobWith(t, blob, identities))
|
|
}
|
|
|
|
// TestRestoreIdentitiesMissingKey reports the dedicated missing-key error
|
|
// rather than a parse failure, so the user is told to set the key.
|
|
func TestRestoreIdentitiesMissingKey(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
v := &Vaultik{Config: &config.Config{}}
|
|
|
|
_, err := v.restoreIdentities()
|
|
require.ErrorIs(t, err, errDecryptionKeyRequired)
|
|
}
|
|
|
|
// TestMissingKeyErrorsNameBothSources asserts restore and deep verify
|
|
// return the same missing-key error, whose text names both places the key
|
|
// can be set: the VAULTIK_AGE_SECRET_KEY environment variable first since
|
|
// it is preferred, then the age_secret_key config file field.
|
|
func TestMissingKeyErrorsNameBothSources(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const want = "age secret key is missing; set it in " +
|
|
"the VAULTIK_AGE_SECRET_KEY environment variable or the " +
|
|
"age_secret_key config file field"
|
|
|
|
v := &Vaultik{Config: &config.Config{}}
|
|
|
|
_, err := v.restoreIdentities()
|
|
require.EqualError(t, err, want)
|
|
|
|
err = v.RunDeepVerify("any-snapshot", &VerifyOptions{})
|
|
require.EqualError(t, err, want)
|
|
}
|