Skip to content

Commit 7ac7d2f

Browse files
fix(parquet): propagate nonce generation failures (#1045)
## What changed Check the secure-random read used to generate Parquet AES nonces and stop encryption if nonce generation fails. ## Why The encryptor previously ignored the result of `crypto/rand.Read`. On Go versions where that function returns an error, encryption could continue with an all-zero or partially filled nonce. Reusing a nonce undermines the security guarantees of both AES-GCM and AES-CTR. The test uses a package-local random-read seam to inject an entropy failure without replacing the process-wide random reader. It covers both supported cipher modes. ## Validation `go test ./parquet/internal/encryption`
1 parent 9e6dadb commit 7ac7d2f

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

parquet/internal/encryption/aes.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ const (
4343
bufferSizeLength = 4
4444
)
4545

46+
var readRandom = rand.Read
47+
4648
// Module constants for constructing the AAD bytes, the order here is
4749
// important as the constants are set via iota.
4850
const (
@@ -125,7 +127,9 @@ func (a *aesEncryptor) Encrypt(w io.Writer, src, key, aad []byte) int {
125127
}
126128

127129
nonce := make([]byte, NonceLength)
128-
rand.Read(nonce)
130+
if _, err := readRandom(nonce); err != nil {
131+
panic(fmt.Errorf("parquet: failed to generate encryption nonce: %w", err))
132+
}
129133

130134
if a.mode == gcmMode {
131135
aead, err := cipher.NewGCM(block)

parquet/internal/encryption/aes_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,42 @@ package encryption
1919
import (
2020
"bytes"
2121
"encoding/binary"
22+
"errors"
2223
"testing"
2324

2425
"github.com/apache/arrow-go/v18/parquet"
2526
"github.com/stretchr/testify/assert"
27+
"github.com/stretchr/testify/require"
2628
)
2729

30+
type failingRandomReader struct {
31+
err error
32+
}
33+
34+
func (r failingRandomReader) Read([]byte) (int, error) { return 0, r.err }
35+
36+
func TestAESEncryptRejectsNonceGenerationFailure(t *testing.T) {
37+
originalReadRandom := readRandom
38+
t.Cleanup(func() { readRandom = originalReadRandom })
39+
failingReader := failingRandomReader{err: errors.New("entropy unavailable")}
40+
readRandom = failingReader.Read
41+
42+
for _, tc := range []struct {
43+
name string
44+
alg parquet.Cipher
45+
}{
46+
{name: "GCM", alg: parquet.AesGcm},
47+
{name: "CTR", alg: parquet.AesCtr},
48+
} {
49+
t.Run(tc.name, func(t *testing.T) {
50+
encryptor := NewAesEncryptor(tc.alg, false)
51+
require.PanicsWithError(t,
52+
"parquet: failed to generate encryption nonce: entropy unavailable",
53+
func() { encryptor.Encrypt(&bytes.Buffer{}, []byte("data"), make([]byte, 16), nil) })
54+
})
55+
}
56+
}
57+
2858
func TestAESDecryptRejectsMalformedCiphertext(t *testing.T) {
2959
decryptor := newAesDecryptor(parquet.AesGcm, false)
3060
key := make([]byte, 16)

0 commit comments

Comments
 (0)