From 4fda84e737505f2604b87e6a4de63c25f4830631 Mon Sep 17 00:00:00 2001 From: Khanh Dinh Nguyen <77538987+beiryu@users.noreply.github.com> Date: Thu, 15 Oct 2026 07:13:33 +0700 Subject: [PATCH 1/2] fix(controlplane): return error on short attestation state ciphertext Fixes #3600 Signed-off-by: Khanh Dinh Nguyen <77538987+beiryu@users.noreply.github.com> --- app/controlplane/pkg/biz/attestationstate.go | 4 +++ .../pkg/biz/attestationstate_test.go | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/app/controlplane/pkg/biz/attestationstate.go b/app/controlplane/pkg/biz/attestationstate.go index af4da45a3..7e3b4af8a 100644 --- a/app/controlplane/pkg/biz/attestationstate.go +++ b/app/controlplane/pkg/biz/attestationstate.go @@ -272,6 +272,10 @@ func decrypt(ciphertext []byte, passphrase string) ([]byte, error) { stream := cipher.NewCFBDecrypter(block, iv) stream.XORKeyStream(ciphertext, ciphertext) + if len(ciphertext) < len(magic) { + return nil, fmt.Errorf("ciphertext too short") + } + // 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..769006801 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.Error(t, 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) +} From b30a8ac5a9bcfd6e0ae501c800447922c8b73ce4 Mon Sep 17 00:00:00 2001 From: Khanh Dinh Nguyen <77538987+beiryu@users.noreply.github.com> Date: Thu, 15 Oct 2026 15:47:09 +0700 Subject: [PATCH 2/2] fix(controlplane): report short attestation state as invalid passphrase A row too short to hold the magic prefix is a truncated or tampered value; report it like any other value that does not decrypt to the prefix, as the tampered-row integration test expects. Signed-off-by: Khanh Dinh Nguyen <77538987+beiryu@users.noreply.github.com> --- app/controlplane/pkg/biz/attestationstate.go | 4 +++- app/controlplane/pkg/biz/attestationstate_test.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controlplane/pkg/biz/attestationstate.go b/app/controlplane/pkg/biz/attestationstate.go index 7e3b4af8a..50276ef2d 100644 --- a/app/controlplane/pkg/biz/attestationstate.go +++ b/app/controlplane/pkg/biz/attestationstate.go @@ -272,8 +272,10 @@ 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, fmt.Errorf("ciphertext too short") + return nil, NewErrInvalidPassphrase() } // Check if the decrypted data starts with the magic string diff --git a/app/controlplane/pkg/biz/attestationstate_test.go b/app/controlplane/pkg/biz/attestationstate_test.go index 769006801..b8c243ac6 100644 --- a/app/controlplane/pkg/biz/attestationstate_test.go +++ b/app/controlplane/pkg/biz/attestationstate_test.go @@ -67,7 +67,7 @@ func TestDecryptShortInput(t *testing.T) { require.NotPanics(t, func() { _, err = decrypt(make([]byte, tc.size), "passphrase") }) - assert.Error(t, err) + assert.True(t, IsErrInvalidPassphrase(err)) }) } }