Files
keyfunc/internal/agekey/agekey_test.go
T
sneak 1a9cc39dd2
check / check (push) Waiting to run
Check arguments before looking for the mnemonic (closes #79)
The key index, --words, the --to recipients and the input file of age encrypt and age decrypt were checked only after the mnemonic had been obtained, so a run that could not succeed still prompted for it or ran the mnemonic command. options.Index now refuses an index above derive.MaxIndex, which covers every command group; the mnemonic command calls the new childmnemonic.CheckWords first; age encrypt reads its recipients with the new agekey.Recipients, and both age commands open their input, before the key is derived. Encrypt now takes the parsed recipients. The README says which checks come first.

Model: opus-5-5
2026-10-14 18:26:16 +00:00

167 lines
4.0 KiB
Go

package agekey_test
import (
"bytes"
"strings"
"testing"
"github.com/stretchr/testify/require"
"sneak.berlin/go/keyfunc/internal/agekey"
"sneak.berlin/go/keyfunc/internal/derive"
)
// The recipients the example mnemonic produces at the first two
// indexes, and the secret key behind the first of them. They are what
// makes the derivation reproducible: if the recipients change, every
// file anyone encrypted becomes unreadable, and if the secret key
// changes, the key is no longer the one other tools derive from the
// same mnemonic.
const (
recipientZero = "age1xwdy9y6ckyfsgjc8k02e9uhsf3fmjy0ufysew" +
"lj68kmx5n67e3nsg2mftq"
recipientOne = "age1pmm92sxaf5mazjwvjph7dx2zq9r5p8l3rarfg" +
"qm7hmakqhvgyy4q5p3w7j"
identityZero = "AGE-SECRET-KEY-19QKK2P38598XLXMQFFU3P7J9PLDD" +
"7527T70JDHGDJ7AMNF3XT44S00JFU5"
)
// example returns the mnemonic every BIP-39 document uses to show its
// test vectors: eleven abandons and about.
func example() string {
return strings.Repeat("abandon ", 11) + "about"
}
func TestTooFewBytesAreRefused(t *testing.T) {
t.Parallel()
_, err := agekey.New([]byte("short"))
require.ErrorIs(t, err, agekey.ErrSize)
}
func TestTheSameMnemonicAlwaysGivesTheSameKey(t *testing.T) {
t.Parallel()
require.Equal(t, recipientZero, forIndex(t, 0).Recipient())
require.Equal(t, recipientOne, forIndex(t, 1).Recipient())
}
func TestTheSameMnemonicAlwaysGivesTheSameSecretKey(t *testing.T) {
t.Parallel()
require.Equal(t, identityZero, forIndex(t, 0).Identity())
}
func TestWhatWasEncryptedComesBack(t *testing.T) {
t.Parallel()
// Every byte value, so nothing assumes the input is text, and
// then text, which is what most of it will be.
payloads := map[string][]byte{
"every byte": everyByte(),
"text": []byte("the quick brown fox\nand a second line\n"),
}
forms := map[string]bool{"binary": false, "armored": true}
for name, payload := range payloads {
for form, armored := range forms {
t.Run(name+" "+form, func(t *testing.T) {
t.Parallel()
key := forIndex(t, 0)
var sealed, opened bytes.Buffer
err := key.Encrypt(
&sealed, bytes.NewReader(payload), nil, armored,
)
require.NoError(t, err)
err = key.Decrypt(&opened, &sealed)
require.NoError(t, err)
require.Equal(t, payload, opened.Bytes())
})
}
}
}
func TestTheArmoredFormIsText(t *testing.T) {
t.Parallel()
var sealed bytes.Buffer
err := forIndex(t, 0).Encrypt(
&sealed, strings.NewReader("hello"), nil, true,
)
require.NoError(t, err)
require.True(t, strings.HasPrefix(
sealed.String(), "-----BEGIN AGE ENCRYPTED FILE-----",
))
}
func TestAFileForSomebodyElseIsRefused(t *testing.T) {
t.Parallel()
var sealed, opened bytes.Buffer
err := forIndex(t, 1).Encrypt(
&sealed, strings.NewReader("hello"), nil, false,
)
require.NoError(t, err)
err = forIndex(t, 0).Decrypt(&opened, &sealed)
require.ErrorIs(t, err, agekey.ErrNotRecipient)
require.Empty(t, opened.Bytes())
}
func TestAnExtraRecipientCanReadItTooAndSoCanTheDerivedOne(t *testing.T) {
t.Parallel()
mine, theirs := forIndex(t, 0), forIndex(t, 1)
to, err := agekey.Recipients([]string{theirs.Recipient()})
require.NoError(t, err)
var sealed bytes.Buffer
err = mine.Encrypt(&sealed, strings.NewReader("hello"), to, false)
require.NoError(t, err)
for _, key := range []*agekey.Key{mine, theirs} {
var opened bytes.Buffer
require.NoError(t, key.Decrypt(&opened, bytes.NewReader(sealed.Bytes())))
require.Equal(t, "hello", opened.String())
}
}
func TestARecipientThatIsNotOneIsRefused(t *testing.T) {
t.Parallel()
_, err := agekey.Recipients([]string{"not a recipient"})
require.Error(t, err)
}
// everyByte returns a payload holding all 256 byte values.
func everyByte() []byte {
out := make([]byte, 256)
for i := range out {
out[i] = byte(i)
}
return out
}
// forIndex derives the key for one index.
func forIndex(t *testing.T, index uint32) *agekey.Key {
t.Helper()
material, err := derive.Bytes(example(), agekey.Application, index)
require.NoError(t, err)
key, err := agekey.New(material)
require.NoError(t, err)
return key
}