90 lines
3.1 KiB
Go
90 lines
3.1 KiB
Go
|
|
package runtime
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/ed25519"
|
||
|
|
"errors"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/tegwick/fluid-core/internal/contract"
|
||
|
|
"github.com/tegwick/fluid-core/internal/signing"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TestVerifiedRegistryRefusesUnsigned is the router-side half of Blueprint 35.
|
||
|
|
// The pipeline signs; this is what makes the signature mean something.
|
||
|
|
func TestVerifiedRegistryRefusesUnsigned(t *testing.T) {
|
||
|
|
signer, pub, err := signing.GenerateKey("key-1")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
reg := NewVerifiedRegistry(testInterface, signing.NewVerifier(map[string]ed25519.PublicKey{"key-1": pub}))
|
||
|
|
|
||
|
|
unsigned := descriptor("R-1", contract.RevisionStateStable)
|
||
|
|
if err := reg.PutRevision(unsigned); !errors.Is(err, signing.ErrUnsigned) {
|
||
|
|
t.Fatalf("unsigned descriptor accepted: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
sig, err := signer.Sign(contract.RevisionDescriptorDocument{Revision: unsigned})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
signed := unsigned
|
||
|
|
signed.Signature = &contract.RevisionSignature{
|
||
|
|
Algorithm: contract.RevisionSignatureAlgorithm(sig.Algorithm),
|
||
|
|
KeyID: sig.KeyID,
|
||
|
|
Value: sig.Value,
|
||
|
|
}
|
||
|
|
if err := reg.PutRevision(signed); err != nil {
|
||
|
|
t.Fatalf("correctly signed descriptor refused: %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestVerifiedRegistryRefusesTamperedDescriptor covers the case that matters
|
||
|
|
// most: an attacker promoting a signed experiment to stable, or repointing it
|
||
|
|
// at their own adapter.
|
||
|
|
func TestVerifiedRegistryRefusesTamperedDescriptor(t *testing.T) {
|
||
|
|
signer, pub, _ := signing.GenerateKey("key-1")
|
||
|
|
reg := NewVerifiedRegistry(testInterface, signing.NewVerifier(map[string]ed25519.PublicKey{"key-1": pub}))
|
||
|
|
|
||
|
|
original := descriptor("R-1", contract.RevisionStateExperiment)
|
||
|
|
sig, _ := signer.Sign(contract.RevisionDescriptorDocument{Revision: original})
|
||
|
|
|
||
|
|
attach := func(d contract.Revision) contract.Revision {
|
||
|
|
d.Signature = &contract.RevisionSignature{
|
||
|
|
Algorithm: contract.RevisionSignatureAlgorithm(sig.Algorithm),
|
||
|
|
KeyID: sig.KeyID,
|
||
|
|
Value: sig.Value,
|
||
|
|
}
|
||
|
|
return d
|
||
|
|
}
|
||
|
|
|
||
|
|
promoted := original
|
||
|
|
promoted.State = contract.RevisionStateStable
|
||
|
|
if err := reg.PutRevision(attach(promoted)); !errors.Is(err, signing.ErrBadSignature) {
|
||
|
|
t.Errorf("a promoted descriptor was accepted: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
redirected := original
|
||
|
|
redirected.Runtime.Upstream = "http://attacker:8080"
|
||
|
|
if err := reg.PutRevision(attach(redirected)); !errors.Is(err, signing.ErrBadSignature) {
|
||
|
|
t.Errorf("a redirected descriptor was accepted: %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestVerifiedRegistryRefusesUntrustedKey(t *testing.T) {
|
||
|
|
rogue, _, _ := signing.GenerateKey("rogue")
|
||
|
|
_, trusted, _ := signing.GenerateKey("key-1")
|
||
|
|
reg := NewVerifiedRegistry(testInterface, signing.NewVerifier(map[string]ed25519.PublicKey{"key-1": trusted}))
|
||
|
|
|
||
|
|
d := descriptor("R-1", contract.RevisionStateStable)
|
||
|
|
sig, _ := rogue.Sign(contract.RevisionDescriptorDocument{Revision: d})
|
||
|
|
d.Signature = &contract.RevisionSignature{
|
||
|
|
Algorithm: contract.RevisionSignatureAlgorithm(sig.Algorithm),
|
||
|
|
KeyID: sig.KeyID,
|
||
|
|
Value: sig.Value,
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := reg.PutRevision(d); !errors.Is(err, signing.ErrUnknownKey) {
|
||
|
|
t.Errorf("a descriptor signed by an untrusted key was accepted: %v", err)
|
||
|
|
}
|
||
|
|
}
|