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) } } }