128 lines
4.1 KiB
Go
128 lines
4.1 KiB
Go
package clusterauth
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSignAndVerifyRawPayload(t *testing.T) {
|
|
keys, err := GenerateKeyPair()
|
|
if err != nil {
|
|
t.Fatalf("GenerateKeyPair: %v", err)
|
|
}
|
|
payload := json.RawMessage(`{"cluster_id":"cluster-1","schema_version":"test.v1","value":1}`)
|
|
|
|
signature, err := SignRaw(keys.PrivateKeyB64, payload, time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC))
|
|
if err != nil {
|
|
t.Fatalf("SignRaw: %v", err)
|
|
}
|
|
if signature.KeyFingerprint != keys.Fingerprint {
|
|
t.Fatalf("fingerprint = %q, want %q", signature.KeyFingerprint, keys.Fingerprint)
|
|
}
|
|
if err := VerifyRaw(keys.PublicKeyB64, payload, signature); err != nil {
|
|
t.Fatalf("VerifyRaw: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestVerifyRawRejectsTamperedPayload(t *testing.T) {
|
|
keys, err := GenerateKeyPair()
|
|
if err != nil {
|
|
t.Fatalf("GenerateKeyPair: %v", err)
|
|
}
|
|
payload := json.RawMessage(`{"cluster_id":"cluster-1","schema_version":"test.v1","value":1}`)
|
|
signature, err := SignRaw(keys.PrivateKeyB64, payload, time.Date(2026, 4, 28, 12, 0, 0, 0, time.UTC))
|
|
if err != nil {
|
|
t.Fatalf("SignRaw: %v", err)
|
|
}
|
|
|
|
tampered := json.RawMessage(`{"cluster_id":"cluster-1","schema_version":"test.v1","value":2}`)
|
|
if err := VerifyRaw(keys.PublicKeyB64, tampered, signature); !errors.Is(err, ErrInvalidSignature) {
|
|
t.Fatalf("err = %v, want ErrInvalidSignature", err)
|
|
}
|
|
}
|
|
|
|
func TestVerifyQuorumRawAcceptsThreshold(t *testing.T) {
|
|
payload := json.RawMessage(`{"cluster_id":"cluster-1","schema_version":"rap.node_update_plan_authority.v1","action":"update"}`)
|
|
descriptor, keys := testQuorum(t, 3, 2)
|
|
payloadHash, err := HashRaw(payload)
|
|
if err != nil {
|
|
t.Fatalf("HashRaw: %v", err)
|
|
}
|
|
quorumHash, err := QuorumDescriptorHash(descriptor)
|
|
if err != nil {
|
|
t.Fatalf("QuorumDescriptorHash: %v", err)
|
|
}
|
|
signatureA, err := SignRaw(keys[0].PrivateKeyB64, payload, time.Date(2026, 5, 16, 12, 0, 0, 0, time.UTC))
|
|
if err != nil {
|
|
t.Fatalf("SignRaw A: %v", err)
|
|
}
|
|
signatureB, err := SignRaw(keys[1].PrivateKeyB64, payload, time.Date(2026, 5, 16, 12, 0, 1, 0, time.UTC))
|
|
if err != nil {
|
|
t.Fatalf("SignRaw B: %v", err)
|
|
}
|
|
envelope := QuorumEnvelope{
|
|
SchemaVersion: QuorumEnvelopeVersion,
|
|
ClusterID: "cluster-1",
|
|
Epoch: "epoch-1",
|
|
Threshold: 2,
|
|
PayloadSHA256: payloadHash,
|
|
QuorumSHA256: quorumHash,
|
|
Signatures: []Signature{signatureA, signatureB},
|
|
}
|
|
if err := VerifyQuorumRaw(descriptor, payload, envelope, "update-authority"); err != nil {
|
|
t.Fatalf("VerifyQuorumRaw: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestVerifyQuorumRawRejectsBelowThreshold(t *testing.T) {
|
|
payload := json.RawMessage(`{"cluster_id":"cluster-1","schema_version":"rap.node_update_plan_authority.v1","action":"update"}`)
|
|
descriptor, keys := testQuorum(t, 3, 2)
|
|
payloadHash, _ := HashRaw(payload)
|
|
quorumHash, _ := QuorumDescriptorHash(descriptor)
|
|
signature, err := SignRaw(keys[0].PrivateKeyB64, payload, time.Date(2026, 5, 16, 12, 0, 0, 0, time.UTC))
|
|
if err != nil {
|
|
t.Fatalf("SignRaw: %v", err)
|
|
}
|
|
envelope := QuorumEnvelope{
|
|
SchemaVersion: QuorumEnvelopeVersion,
|
|
ClusterID: "cluster-1",
|
|
Epoch: "epoch-1",
|
|
Threshold: 2,
|
|
PayloadSHA256: payloadHash,
|
|
QuorumSHA256: quorumHash,
|
|
Signatures: []Signature{signature},
|
|
}
|
|
if err := VerifyQuorumRaw(descriptor, payload, envelope, "update-authority"); !errors.Is(err, ErrInvalidSignature) {
|
|
t.Fatalf("err = %v, want ErrInvalidSignature", err)
|
|
}
|
|
}
|
|
|
|
func testQuorum(t *testing.T, count int, threshold int) (QuorumDescriptor, []KeyPair) {
|
|
t.Helper()
|
|
descriptor := QuorumDescriptor{
|
|
SchemaVersion: QuorumSchemaVersion,
|
|
ClusterID: "cluster-1",
|
|
Epoch: "epoch-1",
|
|
Threshold: threshold,
|
|
}
|
|
keys := make([]KeyPair, 0, count)
|
|
for i := 0; i < count; i++ {
|
|
keyPair, err := GenerateKeyPair()
|
|
if err != nil {
|
|
t.Fatalf("GenerateKeyPair: %v", err)
|
|
}
|
|
descriptor.Members = append(descriptor.Members, QuorumMember{
|
|
NodeID: fmt.Sprintf("authority-%d", i+1),
|
|
Role: "update-authority",
|
|
PublicKey: keyPair.PublicKeyB64,
|
|
PublicKeyFingerprint: keyPair.Fingerprint,
|
|
Scopes: []string{"update-authority"},
|
|
})
|
|
keys = append(keys, keyPair)
|
|
}
|
|
return descriptor, keys
|
|
}
|