90 lines
2.3 KiB
Go
90 lines
2.3 KiB
Go
|
|
package content
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"crypto/sha256"
|
||
|
|
"encoding/hex"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestBindStableHash(t *testing.T) {
|
||
|
|
payload := Phase1Rendered{
|
||
|
|
"title": "Approve policy change v3",
|
||
|
|
"action": "sign",
|
||
|
|
"detail": map[string]any{"policy_id": "pol-42"},
|
||
|
|
}
|
||
|
|
a, err := Bind(payload)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Bind: %v", err)
|
||
|
|
}
|
||
|
|
b, err := Bind(payload)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Bind again: %v", err)
|
||
|
|
}
|
||
|
|
if a.ContentHash != b.ContentHash {
|
||
|
|
t.Errorf("content_hash not stable: %q vs %q", a.ContentHash, b.ContentHash)
|
||
|
|
}
|
||
|
|
if !bytes.Equal(a.ContentCanonical, b.ContentCanonical) {
|
||
|
|
t.Errorf("canonical bytes not stable")
|
||
|
|
}
|
||
|
|
if bytes.Equal(a.Salt, b.Salt) {
|
||
|
|
t.Errorf("salt should be random (equal across two calls)")
|
||
|
|
}
|
||
|
|
if bytes.Equal(a.Nonce, b.Nonce) {
|
||
|
|
t.Errorf("nonce should be random (equal across two calls)")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBindRejectsNil(t *testing.T) {
|
||
|
|
if _, err := Bind(nil); err == nil {
|
||
|
|
t.Fatalf("Bind(nil) must error")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestVerificationDigitsFormat(t *testing.T) {
|
||
|
|
b, err := Bind(Phase1Rendered{"k": "v"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if l := len(b.VerificationDigits); l != 6 {
|
||
|
|
t.Errorf("want 6 digits, got %d (%q)", l, b.VerificationDigits)
|
||
|
|
}
|
||
|
|
for _, r := range b.VerificationDigits {
|
||
|
|
if r < '0' || r > '9' {
|
||
|
|
t.Errorf("non-digit %q in verification digits %q", r, b.VerificationDigits)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestChallengeFor_Deterministic(t *testing.T) {
|
||
|
|
b, err := Bind(Phase1Rendered{"k": "v"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
got := ChallengeFor(b.Nonce, b.ContentCanonical)
|
||
|
|
if !bytes.Equal(got, b.Challenge) {
|
||
|
|
t.Errorf("ChallengeFor must reproduce the challenge")
|
||
|
|
}
|
||
|
|
// Flip a byte in canonical content — challenge must change.
|
||
|
|
alt := append([]byte(nil), b.ContentCanonical...)
|
||
|
|
alt[0] ^= 0xff
|
||
|
|
if bytes.Equal(ChallengeFor(b.Nonce, alt), b.Challenge) {
|
||
|
|
t.Errorf("challenge did not change when canonical content was tampered")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestRehashCanonicalMatchesBind(t *testing.T) {
|
||
|
|
b, err := Bind(Phase1Rendered{"foo": "bar"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if got := RehashCanonical(b.ContentCanonical); got != b.ContentHash {
|
||
|
|
t.Errorf("RehashCanonical=%q want %q", got, b.ContentHash)
|
||
|
|
}
|
||
|
|
// Sanity: computed independently
|
||
|
|
sum := sha256.Sum256(b.ContentCanonical)
|
||
|
|
if "sha256:"+hex.EncodeToString(sum[:]) != b.ContentHash {
|
||
|
|
t.Errorf("ContentHash does not match computed sha256")
|
||
|
|
}
|
||
|
|
}
|