191 lines
7.7 KiB
Go
191 lines
7.7 KiB
Go
package delivery
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func aSigningKey(t *testing.T) (ed25519.PublicKey, ed25519.PrivateKey) {
|
|
t.Helper()
|
|
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return pub, priv
|
|
}
|
|
|
|
func aComposition() Composed {
|
|
return Composed{Node: "ace", Module: "m", FP: fp2of('a'), Resources: []json.RawMessage{
|
|
json.RawMessage(`{"id":"m.f","type":"file","module":"m","path":"/etc/m","content":"x"}`)}}
|
|
}
|
|
|
|
// 1943/AC5: a composition signed by the controller's key or by the operator's verifies against the two trusted keys,
|
|
// keeps its digest, and survives its encoding; one signed by another key, unsigned, or changed by one byte after it was
|
|
// signed does not verify, and nothing of it is to be applied.
|
|
func TestACompositionIsSignedByATrustedKey(t *testing.T) {
|
|
controllerPub, controller := aSigningKey(t)
|
|
operatorPub, operator := aSigningKey(t)
|
|
_, stranger := aSigningKey(t)
|
|
trusted := []ed25519.PublicKey{controllerPub, operatorPub}
|
|
c := aComposition()
|
|
want, _ := ComposedDigestOf(c)
|
|
|
|
for name, key := range map[string]ed25519.PrivateKey{"controller": controller, "operator": operator} {
|
|
signed, err := SignComposed(c, key)
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", name, err)
|
|
}
|
|
raw, err := EncodeComposed(signed)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
back, err := DecodeComposed(raw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
digest, err := VerifyComposed(back, trusted)
|
|
if err != nil || digest != want {
|
|
t.Errorf("%s's signature: %q, %v; want the digest %s", name, digest, err, want)
|
|
}
|
|
if d, _ := ComposedDigestOf(back); d != want {
|
|
t.Errorf("%s's signature changed the digest: %s", name, d)
|
|
}
|
|
|
|
changed := strings.Replace(string(raw), `"content":"x"`, `"content":"y"`, 1)
|
|
if changed == string(raw) {
|
|
t.Fatal("the test changed nothing")
|
|
}
|
|
if tampered, err := DecodeComposed([]byte(changed)); err != nil {
|
|
t.Fatal(err)
|
|
} else if _, err := VerifyComposed(tampered, trusted); err == nil || !strings.Contains(err.Error(), "signature") {
|
|
t.Errorf("%s: a composition changed by one byte verified: %v", name, err)
|
|
}
|
|
}
|
|
|
|
byStranger, _ := SignComposed(c, stranger)
|
|
if _, err := VerifyComposed(byStranger, trusted); err == nil || !strings.Contains(err.Error(), "not a trusted") {
|
|
t.Errorf("a stranger's signature = %v, want refused as no trusted signer", err)
|
|
}
|
|
if _, err := VerifyComposed(c, trusted); err == nil || !strings.Contains(err.Error(), "unsigned") {
|
|
t.Errorf("an unsigned composition = %v, want refused", err)
|
|
}
|
|
signed, _ := SignComposed(c, controller)
|
|
forged := signed
|
|
forged.Signature = &Signature{Signer: signed.Signature.Signer, Sig: byStranger.Signature.Sig}
|
|
if _, err := VerifyComposed(forged, trusted); err == nil {
|
|
t.Errorf("a stranger's signature under the controller's name verified")
|
|
}
|
|
if _, err := EncodeComposed(Composed{Node: "ace", Module: "m", FP: fp2of('a'), Resources: c.Resources,
|
|
Signature: &Signature{Signer: "not base64!", Sig: "x"}}); err == nil {
|
|
t.Errorf("a signature naming no key was encoded")
|
|
}
|
|
}
|
|
|
|
// 1943/AC5: a signer that keeps its private half signs through a function, and the composition verifies as one signed
|
|
// with the key; a function that signs with another key than the one named is refused.
|
|
func TestACompositionIsSignedByAKeptKey(t *testing.T) {
|
|
pub, priv := aSigningKey(t)
|
|
_, other := aSigningKey(t)
|
|
c := aComposition()
|
|
signed, err := SignComposedBy(c, pub, func(m []byte) ([]byte, error) { return ed25519.Sign(priv, m), nil })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := VerifyComposed(signed, []ed25519.PublicKey{pub}); err != nil {
|
|
t.Errorf("a composition signed through the function does not verify: %v", err)
|
|
}
|
|
if _, err := SignComposedBy(c, pub, func(m []byte) ([]byte, error) { return ed25519.Sign(other, m), nil }); err == nil {
|
|
t.Errorf("a signature by another key than the one named was accepted")
|
|
}
|
|
|
|
// L3 s1 (ADR 0550 §2): a message the vault or the controller signed under a kind's context verifies against the
|
|
// signers a node trusts and names which; an unknown key and another kind's context are refused.
|
|
vaultPub, vault := aSigningKey(t)
|
|
controllerPub, controller := aSigningKey(t)
|
|
trusted := []Signer{{Name: SignerController, Key: controllerPub}, {Name: SignerVault, Key: vaultPub}}
|
|
enrol, _ := SignContext(KindEnrolment)
|
|
certCtx, _ := SignContext(KindNodeCertificate)
|
|
msg := []byte("the reply")
|
|
for _, row := range []struct {
|
|
name string
|
|
key ed25519.PrivateKey
|
|
signed string
|
|
checked string
|
|
want string
|
|
}{
|
|
{"the vault's key verifies", vault, enrol, enrol, SignerVault},
|
|
{"the controller's key verifies", controller, enrol, enrol, SignerController},
|
|
{"an unknown key is refused", other, enrol, enrol, ""},
|
|
{"the wrong context is refused", vault, certCtx, enrol, ""},
|
|
} {
|
|
got, err := VerifyBy(trusted, row.checked, msg, SignMessage(row.key, row.signed, msg))
|
|
if row.want == "" && err == nil {
|
|
t.Errorf("%s: verified as %s", row.name, got.Name)
|
|
}
|
|
if row.want != "" && (err != nil || got.Name != row.want) {
|
|
t.Errorf("%s: %+v, %v", row.name, got.Name, err)
|
|
}
|
|
}
|
|
if _, err := SignContext("hand-over"); err == nil {
|
|
t.Error("a kind the vault does not sign has a context")
|
|
}
|
|
// The signers' key reads back only when its fingerprint is its key's, and what a node trusts names both holders.
|
|
k := NewSignerKey(vaultPub, time.Unix(0, 0))
|
|
raw, _ := json.Marshal(k)
|
|
if back, err := DecodeSignerKey(raw); err != nil || back.Fingerprint != SignerFingerprint(vaultPub) {
|
|
t.Errorf("the signers' key: %+v, %v", back, err)
|
|
}
|
|
k.Fingerprint = SignerFingerprint(controllerPub)
|
|
if raw, _ := json.Marshal(k); true {
|
|
if _, err := DecodeSignerKey(raw); err == nil {
|
|
t.Error("a signers' key naming another fingerprint was taken")
|
|
}
|
|
}
|
|
if got := Trusts(trusted, nil); len(got) != 2 || !TrustsKey(got, SignerVault, SignerFingerprint(vaultPub)) ||
|
|
!TrustsKey(got, SignerController, SignerFingerprint(controllerPub)) {
|
|
t.Errorf("trusts: %v", got)
|
|
}
|
|
// A certificate the vault's authority signs for a node's name and serving key checks against a bundle holding that
|
|
// authority, and not for another name, another key, or against the controller's alone; a bundle given again adds no
|
|
// certificate twice.
|
|
authority := func(pub ed25519.PublicKey, priv ed25519.PrivateKey) string {
|
|
tpl := AuthorityTemplate(pub, time.Now().Add(-time.Hour))
|
|
der, err := x509.CreateCertificate(nil, tpl, tpl, pub, priv)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}))
|
|
}
|
|
vaultCA, controllerCA := authority(vaultPub, vault), authority(controllerPub, controller)
|
|
parent, _ := ParseCertificate(vaultCA)
|
|
serving, _ := aSigningKey(t)
|
|
der, err := x509.CreateCertificate(nil, NodeCertificateTemplate("ace.internal", serving, time.Now().Add(-time.Hour)), parent, serving, vault)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
leaf := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}))
|
|
bundle := AuthorityBundle(controllerCA, AuthorityBundle(controllerCA, vaultCA))
|
|
if n := strings.Count(bundle, "BEGIN CERTIFICATE"); n != 2 {
|
|
t.Errorf("the bundle holds %d certificates, want 2", n)
|
|
}
|
|
now := time.Now()
|
|
if err := CheckNodeCertificate(leaf, []string{bundle}, "ace.internal", serving, now); err != nil {
|
|
t.Errorf("the vault's certificate does not check: %v", err)
|
|
}
|
|
for name, err := range map[string]error{
|
|
"another name": CheckNodeCertificate(leaf, []string{vaultCA}, "g14.internal", serving, now),
|
|
"another key": CheckNodeCertificate(leaf, []string{vaultCA}, "ace.internal", controllerPub, now),
|
|
"the controller's alone": CheckNodeCertificate(leaf, []string{controllerCA}, "ace.internal", serving, now),
|
|
} {
|
|
if err == nil {
|
|
t.Errorf("%s: checked", name)
|
|
}
|
|
}
|
|
}
|