165 lines
5.6 KiB
Go
165 lines
5.6 KiB
Go
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)),
|
|
}
|
|
}
|