Sign a composition through a signer that keeps its private half (issue 1943, S5)

The controller's identity store signs a message and never hands its key out, as it signed declarations.
This commit is contained in:
2026-10-24 16:42:41 +02:00
parent 64a502d16a
commit e64486b0e6
2 changed files with 41 additions and 3 deletions
+23 -3
View File
@@ -36,13 +36,33 @@ func SignComposed(c Composed, key ed25519.PrivateKey) (Composed, error) {
if len(key) != ed25519.PrivateKeySize {
return Composed{}, fmt.Errorf("delivery: the key to sign a composition with is not an ed25519 private key")
}
return SignComposedBy(c, key.Public().(ed25519.PublicKey), func(message []byte) ([]byte, error) {
return ed25519.Sign(key, message), nil
})
}
// SignComposedBy is SignComposed for a signer that keeps its private half to itself (the controller's identity store
// signs a message and hands back the signature): sign signs the message it is given with the key whose public half is
// signer. A signature that does not verify against signer is refused, so a composition is never written signed by a
// key other than the one it names.
func SignComposedBy(c Composed, signer ed25519.PublicKey, sign func(message []byte) ([]byte, error)) (Composed, error) {
if len(signer) != ed25519.PublicKeySize {
return Composed{}, fmt.Errorf("delivery: the signer of a composition is not an ed25519 public key")
}
c.Signature = nil
digest, err := ComposedDigestOf(c)
if err != nil {
return Composed{}, err
}
sig := ed25519.Sign(key, []byte(composedSigningContext+digest))
c.Signature = &Signature{Signer: base64.StdEncoding.EncodeToString(key.Public().(ed25519.PublicKey)),
Sig: base64.StdEncoding.EncodeToString(sig)}
message := []byte(composedSigningContext + digest)
sig, err := sign(message)
if err != nil {
return Composed{}, err
}
if !ed25519.Verify(signer, message, sig) {
return Composed{}, fmt.Errorf("delivery: the signature made for the composition of %q on %s does not verify against its signer", c.Module, c.Node)
}
c.Signature = &Signature{Signer: base64.StdEncoding.EncodeToString(signer), Sig: base64.StdEncoding.EncodeToString(sig)}
return c, nil
}
+18
View File
@@ -83,3 +83,21 @@ func TestACompositionIsSignedByATrustedKey(t *testing.T) {
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")
}
}