Fix every finding surfaced by the canonical .golangci.yml with golangci-lint v2.12.2 (refs #61), behavior-preserving throughout: - err113: dynamic errors replaced with package-level sentinels and %w wrapping; direct comparisons converted to errors.Is - goprintffuncname: printf-style helpers renamed with an f suffix (ui.Writer message methods, cli.ReportErrorf, database.Fatalf, vaultik stdoutf) and all call sites updated - revive: stuttering type names renamed (blob.Handler, blob.WithReader, blob.ChunkPosition, storage.URL, storage.Info), doc comments added, unused parameters blanked, package comments added - contextcheck/noctx: ctx threaded through blob.Packer (AddChunk/Flush/FinalizeBlob/PackChunks) and scanner call sites; context-aware exec and sql variants used - funlen/cyclop/gocognit/nestif/dupl: oversized or duplicated functions split into focused helpers across production and test code - paralleltest/tparallel/thelper/usetesting/testpackage: tests parallelized where safe (global log.Initialize kept in the serial phase), helpers marked, t.TempDir adopted, external test packages where only exported API is used - gosec: integer conversions clamped or justified, header timeouts added, remaining findings suppressed with per-site justifications - mnd/goconst/lll/wsl_v5/nlreturn/noinlineerr/errcheck and other mechanical findings fixed directly Remove the deprecated log.LogOptions alias (callers migrated to log.Options). make check is green.
136 lines
3.5 KiB
Go
136 lines
3.5 KiB
Go
package vaultik_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"filippo.io/age"
|
|
"sneak.berlin/go/vaultik/internal/blobgen"
|
|
"sneak.berlin/go/vaultik/internal/vaultik"
|
|
)
|
|
|
|
// buildHashTestBlob encrypts plaintext with blobgen.Writer and returns
|
|
// the encrypted bytes plus the expected double-SHA-256 hash.
|
|
func buildHashTestBlob(
|
|
t *testing.T, identity *age.X25519Identity, plaintext []byte,
|
|
) ([]byte, string) {
|
|
t.Helper()
|
|
|
|
var encBuf bytes.Buffer
|
|
|
|
writer, err := blobgen.NewWriter(&encBuf, 1,
|
|
[]string{identity.Recipient().String()})
|
|
if err != nil {
|
|
t.Fatalf("creating blobgen writer: %v", err)
|
|
}
|
|
|
|
_, err = writer.Write(plaintext)
|
|
if err != nil {
|
|
t.Fatalf("writing plaintext: %v", err)
|
|
}
|
|
|
|
err = writer.Close()
|
|
if err != nil {
|
|
t.Fatalf("closing writer: %v", err)
|
|
}
|
|
|
|
// Compute the double-SHA-256 hash of the plaintext (matches
|
|
// blobgen.Writer.Sum256).
|
|
firstHash := sha256.Sum256(plaintext)
|
|
secondHash := sha256.Sum256(firstHash[:])
|
|
correctHash := hex.EncodeToString(secondHash[:])
|
|
|
|
// Verify our hash matches what blobgen.Writer produces
|
|
writerHash := hex.EncodeToString(writer.Sum256())
|
|
if correctHash != writerHash {
|
|
t.Fatalf("hash computation mismatch: manual=%s, writer=%s",
|
|
correctHash, writerHash)
|
|
}
|
|
|
|
return encBuf.Bytes(), correctHash
|
|
}
|
|
|
|
// TestFetchAndDecryptBlobVerifiesHash verifies that FetchAndDecryptBlob checks
|
|
// the double-SHA-256 hash of the decrypted plaintext against the expected blob hash.
|
|
func TestFetchAndDecryptBlobVerifiesHash(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
identity, err := age.GenerateX25519Identity()
|
|
if err != nil {
|
|
t.Fatalf("generating identity: %v", err)
|
|
}
|
|
|
|
plaintext := []byte("hello world test data for blob hash verification")
|
|
encryptedData, correctHash := buildHashTestBlob(t, identity, plaintext)
|
|
|
|
// Set up mock storage with the blob at the correct path
|
|
mockStorage := NewMockStorer()
|
|
blobPath := "blobs/" + correctHash[:2] + "/" +
|
|
correctHash[2:4] + "/" + correctHash
|
|
|
|
mockStorage.mu.Lock()
|
|
mockStorage.data[blobPath] = encryptedData
|
|
mockStorage.mu.Unlock()
|
|
|
|
tv := vaultik.NewForTesting(mockStorage)
|
|
ctx := context.Background()
|
|
|
|
t.Run("correct hash succeeds", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rc, err := tv.FetchAndDecryptBlob(
|
|
ctx, correctHash, int64(len(encryptedData)), identity)
|
|
if err != nil {
|
|
t.Fatalf("expected success, got error: %v", err)
|
|
}
|
|
|
|
data, err := io.ReadAll(rc)
|
|
if err != nil {
|
|
t.Fatalf("reading stream: %v", err)
|
|
}
|
|
|
|
err = rc.Close()
|
|
if err != nil {
|
|
t.Fatalf("close (hash verification) failed: %v", err)
|
|
}
|
|
|
|
if !bytes.Equal(data, plaintext) {
|
|
t.Fatalf("decrypted data mismatch: got %q, want %q", data, plaintext)
|
|
}
|
|
})
|
|
|
|
t.Run("wrong hash fails", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Use a fake hash that doesn't match the actual plaintext
|
|
fakeHash := strings.Repeat("ab", 32) // 64 hex chars
|
|
fakePath := "blobs/" + fakeHash[:2] + "/" + fakeHash[2:4] + "/" + fakeHash
|
|
|
|
mockStorage.mu.Lock()
|
|
mockStorage.data[fakePath] = encryptedData
|
|
mockStorage.mu.Unlock()
|
|
|
|
rc, err := tv.FetchAndDecryptBlob(
|
|
ctx, fakeHash, int64(len(encryptedData)), identity)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error opening stream: %v", err)
|
|
}
|
|
// Read all data — hash is verified on Close
|
|
_, _ = io.ReadAll(rc)
|
|
|
|
err = rc.Close()
|
|
if err == nil {
|
|
t.Fatal("expected error for mismatched hash, got nil")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "hash mismatch") {
|
|
t.Fatalf("expected hash mismatch error, got: %v", err)
|
|
}
|
|
})
|
|
}
|