package authority import ( "crypto/ed25519" "encoding/base64" "encoding/json" "errors" "fmt" "testing" ) func TestVerifyRawAcceptsSignedPayload(t *testing.T) { publicKey, privateKey, err := ed25519.GenerateKey(nil) if err != nil { t.Fatalf("GenerateKey: %v", err) } payload := json.RawMessage(`{"cluster_id":"cluster-1","schema_version":"test.v1"}`) canonical, err := CanonicalJSON(payload) if err != nil { t.Fatalf("CanonicalJSON: %v", err) } signature := Signature{ SchemaVersion: SignatureSchemaVersion, Algorithm: AlgorithmEd25519, KeyFingerprint: Fingerprint(publicKey), Signature: base64.StdEncoding.EncodeToString(ed25519.Sign(privateKey, canonical)), } if err := VerifyRaw(base64.StdEncoding.EncodeToString(publicKey), payload, signature); err != nil { t.Fatalf("VerifyRaw: %v", err) } } func TestVerifyRawRejectsTamperedPayload(t *testing.T) { publicKey, privateKey, err := ed25519.GenerateKey(nil) if err != nil { t.Fatalf("GenerateKey: %v", err) } payload := json.RawMessage(`{"cluster_id":"cluster-1","schema_version":"test.v1"}`) canonical, err := CanonicalJSON(payload) if err != nil { t.Fatalf("CanonicalJSON: %v", err) } signature := Signature{ SchemaVersion: SignatureSchemaVersion, Algorithm: AlgorithmEd25519, KeyFingerprint: Fingerprint(publicKey), Signature: base64.StdEncoding.EncodeToString(ed25519.Sign(privateKey, canonical)), } tampered := json.RawMessage(`{"cluster_id":"cluster-2","schema_version":"test.v1"}`) if err := VerifyRaw(base64.StdEncoding.EncodeToString(publicKey), tampered, signature); !errors.Is(err, ErrInvalidSignature) { t.Fatalf("err = %v, want ErrInvalidSignature", err) } } func TestVerifyQuorumRawAcceptsThreshold(t *testing.T) { payload := json.RawMessage(`{"schema_version":"rap.node_update_plan_authority.v1","cluster_id":"cluster-1","action":"update"}`) descriptor, privateKeys := testQuorumDescriptor(t, 3, 2) payloadHash, err := HashRaw(payload) if err != nil { t.Fatalf("payload hash: %v", err) } quorumHash, err := QuorumDescriptorHash(descriptor) if err != nil { t.Fatalf("quorum hash: %v", err) } envelope := QuorumEnvelope{ SchemaVersion: QuorumEnvelopeVersion, ClusterID: "cluster-1", Epoch: "epoch-1", Threshold: 2, PayloadSHA256: payloadHash, QuorumSHA256: quorumHash, Signatures: []Signature{ signTestPayload(t, payload, privateKeys[0]), signTestPayload(t, payload, privateKeys[1]), }, } if err := VerifyQuorumRaw(descriptor, payload, envelope, "update-authority"); err != nil { t.Fatalf("VerifyQuorumRaw: %v", err) } } func TestVerifyQuorumRawRejectsBelowThreshold(t *testing.T) { payload := json.RawMessage(`{"schema_version":"rap.node_update_plan_authority.v1","cluster_id":"cluster-1","action":"update"}`) descriptor, privateKeys := testQuorumDescriptor(t, 3, 2) payloadHash, _ := HashRaw(payload) quorumHash, _ := QuorumDescriptorHash(descriptor) envelope := QuorumEnvelope{ SchemaVersion: QuorumEnvelopeVersion, ClusterID: "cluster-1", Epoch: "epoch-1", Threshold: 2, PayloadSHA256: payloadHash, QuorumSHA256: quorumHash, Signatures: []Signature{signTestPayload(t, payload, privateKeys[0])}, } if err := VerifyQuorumRaw(descriptor, payload, envelope, "update-authority"); !errors.Is(err, ErrInvalidSignature) { t.Fatalf("err = %v, want ErrInvalidSignature", err) } } func TestVerifyQuorumRawRejectsTamperedDescriptor(t *testing.T) { payload := json.RawMessage(`{"schema_version":"rap.node_update_plan_authority.v1","cluster_id":"cluster-1","action":"update"}`) descriptor, privateKeys := testQuorumDescriptor(t, 3, 2) payloadHash, _ := HashRaw(payload) quorumHash, _ := QuorumDescriptorHash(descriptor) descriptor.Threshold = 1 envelope := QuorumEnvelope{ SchemaVersion: QuorumEnvelopeVersion, ClusterID: "cluster-1", Epoch: "epoch-1", Threshold: 2, PayloadSHA256: payloadHash, QuorumSHA256: quorumHash, Signatures: []Signature{ signTestPayload(t, payload, privateKeys[0]), signTestPayload(t, payload, privateKeys[1]), }, } if err := VerifyQuorumRaw(descriptor, payload, envelope, "update-authority"); !errors.Is(err, ErrInvalidSignature) { t.Fatalf("err = %v, want ErrInvalidSignature", err) } } func testQuorumDescriptor(t *testing.T, members int, threshold int) (QuorumDescriptor, []ed25519.PrivateKey) { t.Helper() descriptor := QuorumDescriptor{ SchemaVersion: QuorumSchemaVersion, ClusterID: "cluster-1", Epoch: "epoch-1", Threshold: threshold, } privateKeys := make([]ed25519.PrivateKey, 0, members) for i := 0; i < members; i++ { publicKey, privateKey, err := ed25519.GenerateKey(nil) if err != nil { t.Fatalf("GenerateKey: %v", err) } descriptor.Members = append(descriptor.Members, QuorumMember{ NodeID: fmt.Sprintf("authority-%d", i+1), Role: "update-authority", PublicKey: base64.StdEncoding.EncodeToString(publicKey), PublicKeyFingerprint: Fingerprint(publicKey), Scopes: []string{"update-authority"}, }) privateKeys = append(privateKeys, privateKey) } return descriptor, privateKeys } func signTestPayload(t *testing.T, payload json.RawMessage, privateKey ed25519.PrivateKey) Signature { t.Helper() canonical, err := CanonicalJSON(payload) if err != nil { t.Fatalf("CanonicalJSON: %v", err) } publicKey := privateKey.Public().(ed25519.PublicKey) return Signature{ SchemaVersion: SignatureSchemaVersion, Algorithm: AlgorithmEd25519, KeyFingerprint: Fingerprint(publicKey), Signature: base64.StdEncoding.EncodeToString(ed25519.Sign(privateKey, canonical)), } }