Restore files at 0600 and make the blob hash check unskippable (closes #163)
Regular files are now created with O_EXCL at mode 0600 and given their stored mode only after the content is written and closed, so a file whose stored mode is restrictive is never briefly readable by other local users mid-restore. A file whose write or close fails is removed rather than left partial, and a chmod failure is a user-visible warning instead of a debug line. hashVerifyReader.Close now errors when closed before EOF, so a short read or early close can never obtain a blob whose hash was not verified; downloadBlobToCache drops the cache entry on any such failure. verifyFile (--verify) now rejects a restored file with bytes past its last chunk. Tests cover each behaviour under umask 022. Model: opus-4-8
This commit was merged in pull request #182.
This commit is contained in:
1 parent
4c83e82543
commit
d9f0220f94
4 files changed
+462
-23
No files matched your search
@@ -133,3 +133,51 @@ func TestFetchAndDecryptBlobVerifiesHash(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestFetchAndDecryptBlobCloseBeforeEOFFails verifies the hash check
|
||||
// cannot be skipped: a caller that reads only part of the blob and then
|
||||
// closes gets an error rather than silently unverified bytes.
|
||||
func TestFetchAndDecryptBlobCloseBeforeEOFFails(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)
|
||||
|
||||
mockStorage := NewMockStorer()
|
||||
blobPath := "blobs/" + correctHash[:2] + "/" +
|
||||
correctHash[2:4] + "/" + correctHash
|
||||
|
||||
mockStorage.mu.Lock()
|
||||
mockStorage.data[blobPath] = encryptedData
|
||||
mockStorage.mu.Unlock()
|
||||
|
||||
tv := vaultik.NewForTesting(mockStorage)
|
||||
|
||||
rc, err := tv.FetchAndDecryptBlob(
|
||||
context.Background(), correctHash, int64(len(encryptedData)), identity)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error opening stream: %v", err)
|
||||
}
|
||||
|
||||
// Read one byte, far short of the plaintext length, then close.
|
||||
buf := make([]byte, 1)
|
||||
|
||||
_, err = rc.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("reading first byte: %v", err)
|
||||
}
|
||||
|
||||
err = rc.Close()
|
||||
if err == nil {
|
||||
t.Fatal("expected error closing before EOF, got nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "hash not verified") {
|
||||
t.Fatalf("expected not-verified error, got: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user