diff --git a/app/controlplane/pkg/biz/attestationstate.go b/app/controlplane/pkg/biz/attestationstate.go index af4da45a3..50276ef2d 100644 --- a/app/controlplane/pkg/biz/attestationstate.go +++ b/app/controlplane/pkg/biz/attestationstate.go @@ -272,6 +272,12 @@ func decrypt(ciphertext []byte, passphrase string) ([]byte, error) { stream := cipher.NewCFBDecrypter(block, iv) stream.XORKeyStream(ciphertext, ciphertext) + // Too short to hold the magic prefix: a truncated or tampered row, reported like + // any other value that does not decrypt to the magic prefix. + if len(ciphertext) < len(magic) { + return nil, NewErrInvalidPassphrase() + } + // Check if the decrypted data starts with the magic string if string(ciphertext[:len(magic)]) != magic { // A mismatch means the state was encrypted with a different passphrase, diff --git a/app/controlplane/pkg/biz/attestationstate_test.go b/app/controlplane/pkg/biz/attestationstate_test.go index bd243d29d..b8c243ac6 100644 --- a/app/controlplane/pkg/biz/attestationstate_test.go +++ b/app/controlplane/pkg/biz/attestationstate_test.go @@ -49,3 +49,34 @@ func TestErrInvalidPassphraseIsDiscoverableWhenWrapped(t *testing.T) { wrapped := fmt.Errorf("failed to decrypt attestation state: %w", NewErrInvalidPassphrase()) assert.True(t, IsErrInvalidPassphrase(wrapped)) } + +// Truncated stored values (salt + IV + fewer bytes than the magic prefix) must return an error, not panic. +func TestDecryptShortInput(t *testing.T) { + testCases := []struct { + name string + size int + }{ + {name: "salt and IV only", size: saltSize + 16}, + {name: "one byte after IV", size: saltSize + 16 + 1}, + {name: "one byte short of magic", size: saltSize + 16 + len(magic) - 1}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var err error + require.NotPanics(t, func() { + _, err = decrypt(make([]byte, tc.size), "passphrase") + }) + assert.True(t, IsErrInvalidPassphrase(err)) + }) + } +} + +func TestEncryptDecryptRoundTrip(t *testing.T) { + enc, err := encrypt([]byte("data"), "passphrase") + require.NoError(t, err) + + got, err := decrypt(enc, "passphrase") + require.NoError(t, err) + assert.Equal(t, []byte("data"), got) +}