mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-04 11:07:35 +01:00
* s3api: a known-answer fixture generator for SSE-S3 and filer-encrypted chunks TestGenerateDirectReadFixture writes, from this package's own encryption, the pieces a reader outside the gateway must handle: SSE-S3 chunks with their per-chunk metadata, the object-level key, a single-stream ciphertext, the KEK derived from s3.sse.key and wrapped under a passphrase in both on-disk formats, and chunks encrypted by the filer, plain and gzipped. It is skipped unless DIRECT_READ_FIXTURE names the output file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * s3api: the fixture generator restores the key manager and checks the write The test set the global key manager's KEK and left it; a later test in the same run then found a signing key where it expects none. The previous key is restored with t.Cleanup. The fixture file's close error is checked too, so a delayed write failure fails the test instead of leaving a short fixture. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK * s3api: the fixture generator unwraps both KEK formats before writing The legacy v1 wrap is built by hand in the test; reading v1 and v2 back through unwrapKEK ties both to the format the key manager accepts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * s3api: pin a legacy v1 wrapped KEK made outside the package Generating the v1 sample through legacyKEKWrappingSalt let a change to the salt pass on both sides; a vector wrapped elsewhere under the historical salt must still unwrap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
112 lines
3.6 KiB
Go
112 lines
3.6 KiB
Go
package s3api
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
// TestSetKEKPassphraseEnablesEncryptedRoundTrip exercises the wrap/unwrap
|
|
// round-trip after SetKEKPassphrase configures the manager. This is the
|
|
// primary behaviour the new env-var wiring relies on: once the passphrase
|
|
// is in place, wrapKEK produces v2-format ciphertext that unwrapKEK can
|
|
// reverse. Without the SetKEKPassphrase plumbing, deriveWrappingKey would
|
|
// fail with "no KEK passphrase configured".
|
|
func TestSetKEKPassphraseEnablesEncryptedRoundTrip(t *testing.T) {
|
|
km := NewSSES3KeyManager()
|
|
km.SetKEKPassphrase("test-passphrase-32-bytes-or-anything")
|
|
|
|
original := make([]byte, SSES3KeySize)
|
|
for i := range original {
|
|
original[i] = byte(i)
|
|
}
|
|
|
|
wrapped, err := km.wrapKEK(original)
|
|
if err != nil {
|
|
t.Fatalf("wrapKEK: %v", err)
|
|
}
|
|
|
|
got, isV2, err := km.unwrapKEK(wrapped)
|
|
if err != nil {
|
|
t.Fatalf("unwrapKEK: %v", err)
|
|
}
|
|
if !isV2 {
|
|
t.Fatal("wrapped output should be reported as v2 by unwrapKEK")
|
|
}
|
|
if string(got) != string(original) {
|
|
t.Fatalf("round-trip mismatch: got %x want %x", got, original)
|
|
}
|
|
}
|
|
|
|
// TestSetKEKPassphraseDifferentInstancesNoCollision proves random salts
|
|
// per-installation actually decorrelate two managers using the same
|
|
// passphrase: their wrapped payloads must differ even when the input KEK
|
|
// matches.
|
|
func TestSetKEKPassphraseDifferentInstancesNoCollision(t *testing.T) {
|
|
a := NewSSES3KeyManager()
|
|
a.SetKEKPassphrase("shared-passphrase")
|
|
b := NewSSES3KeyManager()
|
|
b.SetKEKPassphrase("shared-passphrase")
|
|
|
|
kek := make([]byte, SSES3KeySize)
|
|
for i := range kek {
|
|
kek[i] = 0xAB
|
|
}
|
|
|
|
wa, err := a.wrapKEK(kek)
|
|
if err != nil {
|
|
t.Fatalf("wrapKEK a: %v", err)
|
|
}
|
|
wb, err := b.wrapKEK(kek)
|
|
if err != nil {
|
|
t.Fatalf("wrapKEK b: %v", err)
|
|
}
|
|
if string(wa) == string(wb) {
|
|
t.Fatal("two managers with the same passphrase produced byte-identical wrapped output; salt is not random")
|
|
}
|
|
|
|
// Both must still self-roundtrip.
|
|
if got, _, _ := a.unwrapKEK(wa); string(got) != string(kek) {
|
|
t.Fatal("manager a failed self roundtrip")
|
|
}
|
|
if got, _, _ := b.unwrapKEK(wb); string(got) != string(kek) {
|
|
t.Fatal("manager b failed self roundtrip")
|
|
}
|
|
}
|
|
|
|
// TestNoPassphraseKeepsLegacyHexDecodePath confirms that a manager left
|
|
// without a passphrase still drives the historical plaintext-hex path
|
|
// (loadSuperKeyFromFiler logs a warning and reads hex). That's the
|
|
// fallback InitializeGlobalSSES3KeyManager keeps for upgrades.
|
|
func TestNoPassphraseKeepsLegacyHexDecodePath(t *testing.T) {
|
|
km := NewSSES3KeyManager()
|
|
if km.kekPassphrase != "" {
|
|
t.Fatalf("default passphrase should be empty, got %q", km.kekPassphrase)
|
|
}
|
|
// wrapKEK requires a passphrase; without one it must surface the error
|
|
// rather than producing unencrypted output.
|
|
if _, err := km.wrapKEK(make([]byte, SSES3KeySize)); err == nil {
|
|
t.Fatal("wrapKEK without passphrase should fail; got nil error")
|
|
}
|
|
}
|
|
|
|
// A KEK wrapped in the legacy v1 layout (nonce || ciphertext+tag) under the
|
|
// fixed salt "seaweedfs-sse-s3-kek-wrapping-v1", produced outside this
|
|
// package, so a change to legacyKEKWrappingSalt cannot pass by changing
|
|
// both sides.
|
|
func TestUnwrapKEKReadsPinnedLegacyV1(t *testing.T) {
|
|
km := NewSSES3KeyManager("legacy-passphrase")
|
|
got, isV2, err := km.unwrapKEK([]byte("oKGio6SlpqeoqaqrixOn/dNaewpX3OoMjLT3VUFh+zg+Hd1fqbiWa6Xhc3SAg6hZdXb+Pwo520Fc1dfj"))
|
|
if err != nil {
|
|
t.Fatalf("unwrapKEK: %v", err)
|
|
}
|
|
if isV2 {
|
|
t.Fatal("a legacy payload was reported as v2")
|
|
}
|
|
want := make([]byte, SSES3KeySize)
|
|
for i := range want {
|
|
want[i] = byte(i)
|
|
}
|
|
if string(got) != string(want) {
|
|
t.Fatalf("unwrapped %x, want %x", got, want)
|
|
}
|
|
}
|