package delivery import ( "crypto/ed25519" "crypto/rand" "encoding/json" "strings" "testing" ) 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") } }