Test-only. internal/blobgen and internal/types had no negative or boundary coverage. Adds, in package blobgen_test: Writer-to-Reader round trips at the 64 KiB age-segment edges for random and compressible data, checking plaintext, byte counts and the reader/writer hashes by decrypting; a wrong-identity open; truncation and single-byte corruption of a multi-segment blob at every region; trailing bytes, empty input and garbage; rejected and accepted compression levels; nil, empty and invalid recipients; and a failing destination. In package types_test: Value/Scan round trips, NULL, wrong-type and malformed Scan, Parse and IsZero for FileID and BlobID. The "cut right after the age header and nonce" truncation is excluded: it reads as valid and empty today and belongs to #152. Model: opus-4-8
129 lines
4.2 KiB
Go
129 lines
4.2 KiB
Go
package blobgen_test
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/vaultik/internal/blobgen"
|
|
)
|
|
|
|
// TestWriterHashIsDoubleHash verifies that Writer.ContentID() returns
|
|
// SHA256(SHA256(plaintext)). Stored objects are named by this second hash so a
|
|
// name is not the plaintext's own SHA-256; this does not stop someone who
|
|
// already holds the plaintext from confirming it.
|
|
func TestWriterHashIsDoubleHash(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Test data - random data that doesn't compress well
|
|
testData := make([]byte, 1024*1024) // 1MB
|
|
_, err := rand.Read(testData)
|
|
require.NoError(t, err)
|
|
|
|
// Test recipient (generated with age-keygen)
|
|
testRecipient := "age1cplgrwj77ta54dnmydvvmzn64ltk83ankxl5sww04mrtmu62kv3s89gmvv"
|
|
|
|
// Create a buffer to capture the encrypted output
|
|
var encryptedBuf bytes.Buffer
|
|
|
|
// Create blobgen writer
|
|
writer, err := blobgen.NewWriter(&encryptedBuf, 3, []string{testRecipient})
|
|
require.NoError(t, err)
|
|
|
|
// Write test data
|
|
n, err := writer.Write(testData)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, len(testData), n)
|
|
|
|
// Close to flush all data
|
|
err = writer.Close()
|
|
require.NoError(t, err)
|
|
|
|
// Get the hash from the writer
|
|
writerHash := hex.EncodeToString(writer.ContentID())
|
|
|
|
// Calculate the expected double hash: SHA256(SHA256(plaintext))
|
|
firstHash := sha256.Sum256(testData)
|
|
secondHash := sha256.Sum256(firstHash[:])
|
|
expectedDoubleHash := hex.EncodeToString(secondHash[:])
|
|
|
|
// Also compute single hash to verify it's different
|
|
singleHashStr := hex.EncodeToString(firstHash[:])
|
|
|
|
t.Logf("Input size: %d bytes", len(testData))
|
|
t.Logf("Single hash (SHA256(data)): %s", singleHashStr)
|
|
t.Logf("Double hash (SHA256(SHA256(data))): %s", expectedDoubleHash)
|
|
t.Logf("Writer hash: %s", writerHash)
|
|
|
|
// The writer hash should match the double hash
|
|
assert.Equal(t, expectedDoubleHash, writerHash,
|
|
"Writer.ContentID() must be SHA256(SHA256(plaintext))")
|
|
|
|
// It must be the second hash, not the plaintext's own SHA-256.
|
|
assert.NotEqual(t, singleHashStr, writerHash,
|
|
"Writer hash must be the double hash, not the single SHA-256")
|
|
}
|
|
|
|
// TestWriterDeterministicHash verifies that the same input always produces
|
|
// the same hash, even with non-deterministic encryption.
|
|
func TestWriterDeterministicHash(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Test data
|
|
testData := []byte("Hello, World! This is test data for deterministic hashing.")
|
|
|
|
// Test recipient
|
|
testRecipient := "age1cplgrwj77ta54dnmydvvmzn64ltk83ankxl5sww04mrtmu62kv3s89gmvv"
|
|
|
|
// Create two writers and verify they produce the same hash
|
|
var buf1, buf2 bytes.Buffer
|
|
|
|
writer1, err := blobgen.NewWriter(&buf1, 3, []string{testRecipient})
|
|
require.NoError(t, err)
|
|
_, err = writer1.Write(testData)
|
|
require.NoError(t, err)
|
|
require.NoError(t, writer1.Close())
|
|
|
|
writer2, err := blobgen.NewWriter(&buf2, 3, []string{testRecipient})
|
|
require.NoError(t, err)
|
|
_, err = writer2.Write(testData)
|
|
require.NoError(t, err)
|
|
require.NoError(t, writer2.Close())
|
|
|
|
hash1 := hex.EncodeToString(writer1.ContentID())
|
|
hash2 := hex.EncodeToString(writer2.ContentID())
|
|
|
|
// Hashes should be identical (deterministic)
|
|
assert.Equal(t, hash1, hash2, "Same input should produce same hash")
|
|
|
|
// Encrypted outputs should be different (non-deterministic encryption)
|
|
assert.NotEqual(t, buf1.Bytes(), buf2.Bytes(),
|
|
"Encrypted outputs should differ due to non-deterministic encryption")
|
|
|
|
t.Logf("Hash 1: %s", hash1)
|
|
t.Logf("Hash 2: %s", hash2)
|
|
t.Logf("Encrypted size 1: %d bytes", buf1.Len())
|
|
t.Logf("Encrypted size 2: %d bytes", buf2.Len())
|
|
}
|
|
|
|
// TestNewWriterSecretKeyNotEchoed verifies that a secret key mistakenly passed
|
|
// as a recipient does not appear in the returned error. A recipient string can
|
|
// be sensitive, so the error must name only the position, not the value.
|
|
func TestNewWriterSecretKeyNotEchoed(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
secretKey := "AGE-SECRET-KEY-19CR5YSFW59HM4TLD6GX" +
|
|
"VEDMZFTVVF7PPHKUT68TXSFPK7APHXA2QS2NJA5"
|
|
|
|
var buf bytes.Buffer
|
|
|
|
_, err := blobgen.NewWriter(&buf, 3, []string{secretKey})
|
|
require.Error(t, err)
|
|
assert.NotContains(t, err.Error(), secretKey,
|
|
"error must not echo the recipient value")
|
|
}
|