package secret import ( "encoding/base64" "strings" "testing" ) func newBox(t *testing.T) *Box { t.Helper() k, err := NewKey() if err != nil { t.Fatal(err) } raw, _ := base64.StdEncoding.DecodeString(k) b, err := New(raw) if err != nil { t.Fatal(err) } return b } func TestRoundTrip(t *testing.T) { b := newBox(t) sealed, err := b.SealString("broker-password", "agent-1") if err != nil { t.Fatal(err) } if strings.Contains(string(sealed), "broker-password") { t.Fatal("the plaintext is visible in the ciphertext") } got, err := b.OpenString(sealed, "agent-1") if err != nil || got != "broker-password" { t.Fatalf("got %q, %v", got, err) } } // The aad binds the ciphertext to the row it lives in. Without it a value // copied from one agent's row to another decrypts happily, so a database write // becomes a way to hand one site another site's broker credentials. func TestCiphertextIsBoundToItsOwner(t *testing.T) { b := newBox(t) sealed, _ := b.SealString("broker-password", "agent-1") if _, err := b.OpenString(sealed, "agent-2"); err == nil { t.Fatal("a secret decrypted under the wrong agent id") } } func TestAnotherKeyCannotOpenIt(t *testing.T) { sealed, _ := newBox(t).SealString("broker-password", "agent-1") if _, err := newBox(t).OpenString(sealed, "agent-1"); err == nil { t.Fatal("a different key opened the ciphertext") } } func TestTamperingIsDetected(t *testing.T) { b := newBox(t) sealed, _ := b.SealString("broker-password", "agent-1") sealed[len(sealed)-1] ^= 0x01 if _, err := b.OpenString(sealed, "agent-1"); err == nil { t.Fatal("a modified ciphertext was accepted") } } func TestNonceIsFreshEachTime(t *testing.T) { b := newBox(t) // Identical plaintexts must not produce identical ciphertexts, or the // database shows at a glance which sites share a password. a, _ := b.SealString("same", "agent-1") c, _ := b.SealString("same", "agent-1") if string(a) == string(c) { t.Fatal("the nonce is being reused") } } func TestShortKeysAreRefusedRatherThanStretched(t *testing.T) { // A key derived from whatever somebody typed into an env var has unknown // entropy, and "it worked" would hide that permanently. if _, err := New([]byte("too short")); err == nil { t.Fatal("a 9-byte key was accepted") } if _, err := New(nil); err == nil { t.Fatal("an empty key was accepted") } } func TestTruncatedCiphertextDoesNotPanic(t *testing.T) { b := newBox(t) if _, err := b.Open([]byte{1, 2, 3}, "agent-1"); err == nil { t.Fatal("a three-byte ciphertext was accepted") } } func TestFromEnvReportsAMissingKeyByName(t *testing.T) { if _, err := FromEnv("BEHAVISION_SECRET_KEY_NOT_SET_IN_TESTS"); err != ErrNoKey { t.Fatalf("got %v", err) } t.Setenv("TEST_KEY", "not base64 !!!") if _, err := FromEnv("TEST_KEY"); err == nil { t.Fatal("garbage was accepted as a key") } k, _ := NewKey() t.Setenv("TEST_KEY", k) if _, err := FromEnv("TEST_KEY"); err != nil { t.Fatalf("a generated key was refused: %v", err) } }