main's mesh-compose is the composer command (ADR 0482 clause 4) and keeps its name, which composer.Package and the build steps name; feat/r6's hand path tool, which composes one module for one node and signs it with the operator's key (ADR 0539 clause 10), moves to cmd/mesh-hand-compose.
101 lines
4.4 KiB
Go
101 lines
4.4 KiB
Go
package delivery
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"encoding/base64"
|
|
"fmt"
|
|
)
|
|
|
|
// A composition's signature (novox/hq ADR 0539 clause 3): the controller signs each composition it writes with its
|
|
// key, and the operator signs one made by hand with mesh-hand-compose with theirs. The node-engine trusts exactly these two
|
|
// signers and applies nothing of a composition whose signature fails or whose signer is neither. The signature is an
|
|
// ed25519 signature, as a declaration's is, of the composition's digest (ComposedDigestOf) after a context line, so a
|
|
// signature made for anything else never verifies here.
|
|
const composedSigningContext = "novox composition v1\n"
|
|
|
|
// Signature is who signed a composition and the signature: Signer the signer's ed25519 public key and Sig the
|
|
// signature, both in standard base64.
|
|
type Signature struct {
|
|
Signer string `json:"signer"`
|
|
Sig string `json:"sig"`
|
|
}
|
|
|
|
func (s Signature) check() error {
|
|
if key, err := base64.StdEncoding.DecodeString(s.Signer); err != nil || len(key) != ed25519.PublicKeySize {
|
|
return fmt.Errorf("delivery: a composition's signer %q is not an ed25519 public key in base64", s.Signer)
|
|
}
|
|
if sig, err := base64.StdEncoding.DecodeString(s.Sig); err != nil || len(sig) != ed25519.SignatureSize {
|
|
return fmt.Errorf("delivery: a composition's signature is not an ed25519 signature in base64")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SignComposed is c signed with key: its digest signed, and the key's public half named as the signer. A signature c
|
|
// already carries is replaced.
|
|
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
|
|
}
|
|
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
|
|
}
|
|
|
|
// VerifyComposed is c's digest when c is signed by one of the trusted keys and its signature verifies against its
|
|
// digest; otherwise it refuses, saying why, and nothing of c is to be applied: an unsigned composition, one whose
|
|
// signer is not a trusted key, and one changed after it was signed.
|
|
func VerifyComposed(c Composed, trusted []ed25519.PublicKey) (string, error) {
|
|
if c.Signature == nil {
|
|
return "", fmt.Errorf("delivery: the composition of %q on %s is unsigned, and only a signed one is applied", c.Module, c.Node)
|
|
}
|
|
if err := c.Signature.check(); err != nil {
|
|
return "", err
|
|
}
|
|
signer, _ := base64.StdEncoding.DecodeString(c.Signature.Signer)
|
|
var key ed25519.PublicKey
|
|
for _, t := range trusted {
|
|
if t.Equal(ed25519.PublicKey(signer)) {
|
|
key = t
|
|
}
|
|
}
|
|
if key == nil {
|
|
return "", fmt.Errorf("delivery: the composition of %q on %s is signed by %s, which is not a trusted signer (the controller's key or the operator's)",
|
|
c.Module, c.Node, c.Signature.Signer)
|
|
}
|
|
digest, err := ComposedDigestOf(c)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
sig, _ := base64.StdEncoding.DecodeString(c.Signature.Sig)
|
|
if !ed25519.Verify(key, []byte(composedSigningContext+digest), sig) {
|
|
return "", fmt.Errorf("delivery: the composition of %q on %s does not match its signature: it was changed after it was signed, or signed for another",
|
|
c.Module, c.Node)
|
|
}
|
|
return digest, nil
|
|
}
|