package agekey_test import ( "bytes" "strings" "testing" "git.eeqj.de/sneak/keyfunc/internal/agekey" "git.eeqj.de/sneak/keyfunc/internal/derive" "github.com/stretchr/testify/require" ) // The recipients the example mnemonic produces at the first two // indexes. They are what makes the derivation reproducible: if these // change, every file anyone encrypted becomes unreadable. const ( recipientZero = "age1xwdy9y6ckyfsgjc8k02e9uhsf3fmjy0ufysew" + "lj68kmx5n67e3nsg2mftq" recipientOne = "age1pmm92sxaf5mazjwvjph7dx2zq9r5p8l3rarfg" + "qm7hmakqhvgyy4q5p3w7j" ) // 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 TestTheIdentityIsWrittenTheWayAgeWritesOne(t *testing.T) { t.Parallel() require.True(t, strings.HasPrefix(forIndex(t, 0).Identity(), "AGE-SECRET-KEY-1"), ) } 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) var sealed bytes.Buffer err := mine.Encrypt( &sealed, strings.NewReader("hello"), []string{theirs.Recipient()}, 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 := forIndex(t, 0).Encrypt( &bytes.Buffer{}, strings.NewReader("hello"), []string{"not a recipient"}, false, ) 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 }