From e5dc3bc543f05f243c309e6fe0e6fc90a4593862 Mon Sep 17 00:00:00 2001 From: jedarden Date: Thu, 30 Apr 2026 23:04:29 -0400 Subject: [PATCH] fix: accept base64-encoded AES keys (OpenBao stores keys as base64, not hex) The encryption key stored in OpenBao/K8s secrets is base64-encoded but the API and worker crypto functions expected hex. Add parseAESKey() that accepts both formats (tries hex first, falls back to base64). Co-Authored-By: Claude Sonnet 4.6 --- cmd/acb-api/crypto.go | 37 +++++++++++++++++++++++++------------ cmd/acb-api/crypto_test.go | 20 ++++++++++++++++++++ cmd/acb-worker/crypto.go | 28 ++++++++++++++++++++++------ 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/cmd/acb-api/crypto.go b/cmd/acb-api/crypto.go index fc32d0c..f037f11 100644 --- a/cmd/acb-api/crypto.go +++ b/cmd/acb-api/crypto.go @@ -4,11 +4,30 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" + "encoding/base64" "encoding/hex" "fmt" "io" ) +// parseAESKey decodes a 32-byte AES key encoded as either 64 hex chars or 44 base64 chars. +func parseAESKey(s string) ([]byte, error) { + if b, err := hex.DecodeString(s); err == nil && len(b) == 32 { + return b, nil + } + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + b, err = base64.RawStdEncoding.DecodeString(s) + } + if err != nil { + return nil, fmt.Errorf("decode key: not valid hex or base64") + } + if len(b) != 32 { + return nil, fmt.Errorf("encryption key must be 32 bytes") + } + return b, nil +} + func generateID(prefix string, nBytes int) (string, error) { b := make([]byte, nBytes) if _, err := io.ReadFull(rand.Reader, b); err != nil { @@ -25,13 +44,10 @@ func generateSecret() (string, error) { return hex.EncodeToString(b), nil } -func encryptSecret(plaintext, keyHex string) (string, error) { - key, err := hex.DecodeString(keyHex) +func encryptSecret(plaintext, keyStr string) (string, error) { + key, err := parseAESKey(keyStr) if err != nil { - return "", fmt.Errorf("decode key: %w", err) - } - if len(key) != 32 { - return "", fmt.Errorf("encryption key must be 32 bytes (64 hex chars)") + return "", err } block, err := aes.NewCipher(key) @@ -52,13 +68,10 @@ func encryptSecret(plaintext, keyHex string) (string, error) { return hex.EncodeToString(ciphertext), nil } -func decryptSecret(ciphertextHex, keyHex string) (string, error) { - key, err := hex.DecodeString(keyHex) +func decryptSecret(ciphertextHex, keyStr string) (string, error) { + key, err := parseAESKey(keyStr) if err != nil { - return "", fmt.Errorf("decode key: %w", err) - } - if len(key) != 32 { - return "", fmt.Errorf("encryption key must be 32 bytes (64 hex chars)") + return "", err } ciphertext, err := hex.DecodeString(ciphertextHex) diff --git a/cmd/acb-api/crypto_test.go b/cmd/acb-api/crypto_test.go index 3375de4..2a106d5 100644 --- a/cmd/acb-api/crypto_test.go +++ b/cmd/acb-api/crypto_test.go @@ -103,3 +103,23 @@ func TestEncrypt_InvalidKeyLength(t *testing.T) { t.Error("short key should produce error") } } + +func TestEncryptDecrypt_Base64Key(t *testing.T) { + // Base64-encoded 32-byte key (as stored in OpenBao/K8s secrets) + key := "TWHJiNNgJ4qCFeK56mQ4HWee7JVuOgddXW0T3UkiX3M=" + plaintext := "base64-key-test-secret" + + encrypted, err := encryptSecret(plaintext, key) + if err != nil { + t.Fatalf("encrypt with base64 key failed: %v", err) + } + + decrypted, err := decryptSecret(encrypted, key) + if err != nil { + t.Fatalf("decrypt with base64 key failed: %v", err) + } + + if decrypted != plaintext { + t.Errorf("decrypted = %q, want %q", decrypted, plaintext) + } +} diff --git a/cmd/acb-worker/crypto.go b/cmd/acb-worker/crypto.go index d971742..28d2df7 100644 --- a/cmd/acb-worker/crypto.go +++ b/cmd/acb-worker/crypto.go @@ -3,17 +3,33 @@ package main import ( "crypto/aes" "crypto/cipher" + "encoding/base64" "encoding/hex" "fmt" ) -func decryptSecret(ciphertextHex, keyHex string) (string, error) { - key, err := hex.DecodeString(keyHex) - if err != nil { - return "", fmt.Errorf("decode key: %w", err) +// parseAESKey decodes a 32-byte AES key encoded as either 64 hex chars or 44 base64 chars. +func parseAESKey(s string) ([]byte, error) { + if b, err := hex.DecodeString(s); err == nil && len(b) == 32 { + return b, nil } - if len(key) != 32 { - return "", fmt.Errorf("encryption key must be 32 bytes (64 hex chars)") + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + b, err = base64.RawStdEncoding.DecodeString(s) + } + if err != nil { + return nil, fmt.Errorf("decode key: not valid hex or base64") + } + if len(b) != 32 { + return nil, fmt.Errorf("encryption key must be 32 bytes") + } + return b, nil +} + +func decryptSecret(ciphertextHex, keyStr string) (string, error) { + key, err := parseAESKey(keyStr) + if err != nil { + return "", err } ciphertext, err := hex.DecodeString(ciphertextHex)