diff --git a/internal/crypto/nodetoken/nodetoken_test.go b/internal/crypto/nodetoken/nodetoken_test.go index f4dbcd1b4..455ea7ab0 100644 --- a/internal/crypto/nodetoken/nodetoken_test.go +++ b/internal/crypto/nodetoken/nodetoken_test.go @@ -96,7 +96,7 @@ func TestEncryptedNeverFallsBackToPlaintext(t *testing.T) { c, _ := NewCodec(ModeRequired, testRing(t, "k1", "k1")) enc, _ := c.Encrypt(1, "tok") // Corrupt the ciphertext body — must error, never return raw bytes. - bad := enc[:len(enc)-2] + "AA" + bad := flipLastCiphertextBit(t, enc) if _, err := c.Decrypt(1, bad); err == nil { t.Fatal("corrupted ciphertext must fail, not fall back to plaintext") } @@ -108,6 +108,19 @@ func TestEncryptedNeverFallsBackToPlaintext(t *testing.T) { } } +// flipLastCiphertextBit rewrites the body through its decoded bytes, because +// editing the trailing base64 characters can leave those bytes untouched. +func flipLastCiphertextBit(t *testing.T, stored string) string { + t.Helper() + cut := strings.LastIndex(stored, ":") + 1 + blob, err := base64.RawURLEncoding.DecodeString(stored[cut:]) + if err != nil { + t.Fatalf("decode ciphertext body: %v", err) + } + blob[len(blob)-1] ^= 0x01 + return stored[:cut] + base64.RawURLEncoding.EncodeToString(blob) +} + func TestEncryptionMarkerPassesThroughWhenDisabled(t *testing.T) { c, _ := NewCodec(ModeOff, nil) stored := "enc:v1:not-ciphertext"