swisspost-evoting-go-poc/pkg/transport/bus.go
saymrwulf f7cbe4f634 Add multi-party transport-security layer (Rust-signed)
Foundation for the multi-party re-architecture: an authenticated, confidential
message transport between the separate parties of the system.

- identity.go: per-party Ed25519 + X25519 identities and an Ed25519 root CA.
  Certificates are real X.509, but signed through a crypto.Signer shim whose
  Sign() calls the Rust Ed25519 — so x509.CreateCertificate's signature bytes
  are produced in Rust. Verification extracts TBS bytes and calls the Rust
  verifier, never Go's x509 internals. No RSA anywhere.
- envelope.go: signed inter-party messages over an injective length-prefixed
  encoding of all fields; sign/verify via Rust.
- channel.go: X25519 ECDH (Rust) → session key → AES-256-GCM confidential
  payloads.
- bus.go: CA-anchored directory + message router that verifies every request
  and reply signature before delivery (authenticity enforced at the boundary).

Tests cover cert-chain verification (incl. foreign-CA rejection), envelope
tamper rejection, forged-sender rejection at the bus, and secure-channel
round-trip with associated-data binding.

ARCHITECTURE.md documents the parties, the transport, and the message inventory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:02:54 +02:00

127 lines
3.8 KiB
Go

package transport
import (
"crypto/ed25519"
"crypto/x509"
"fmt"
)
// Peer is the public identity information the directory holds for a party:
// its CA-verified certificate and the Ed25519/X25519 public keys bound by it.
type Peer struct {
Name string
EdPub []byte
XPub []byte
Cert *x509.Certificate
}
// Directory maps party names to their verified public identities. A party is
// only added after its certificate chains to the trusted CA (verified in Rust).
type Directory struct {
ca *CA
peers map[string]*Peer
}
// NewDirectory creates an empty directory anchored to a CA.
func NewDirectory(ca *CA) *Directory {
return &Directory{ca: ca, peers: make(map[string]*Peer)}
}
// Register validates id's certificate against the CA and adds it. The XPub is
// carried alongside (it is not part of the Ed25519 cert, but is distributed
// through the same trusted enrollment step in this PoC).
func (d *Directory) Register(id *Identity) error {
if err := d.ca.VerifyCertificate(id.Cert); err != nil {
return fmt.Errorf("register %s: %w", id.Name, err)
}
if id.Cert.Subject.CommonName != id.Name {
return fmt.Errorf("register %s: cert CN %q does not match name", id.Name, id.Cert.Subject.CommonName)
}
// Bind the Ed25519 public key from the (verified) certificate, not from the
// identity struct, so a party cannot register a cert for one key while
// signing with another.
certPub, ok := id.Cert.PublicKey.(ed25519.PublicKey)
if !ok {
return fmt.Errorf("register %s: certificate is not Ed25519", id.Name)
}
d.peers[id.Name] = &Peer{
Name: id.Name,
EdPub: []byte(certPub),
XPub: id.XPub,
Cert: id.Cert,
}
return nil
}
// Lookup returns the peer for name, or an error if unknown.
func (d *Directory) Lookup(name string) (*Peer, error) {
p, ok := d.peers[name]
if !ok {
return nil, fmt.Errorf("unknown party %q", name)
}
return p, nil
}
// Handler processes an inbound envelope and optionally returns a reply.
type Handler func(env *Envelope) (*Envelope, error)
// Bus is an in-process message router. It emulates the network between parties:
// every message it carries has its Ed25519 signature verified (in Rust) against
// the sender's directory entry before delivery, and replies are verified on the
// way back. This is where authenticity is enforced at the transport boundary.
type Bus struct {
dir *Directory
handlers map[string]Handler
Log func(format string, args ...any)
count int
}
// NewBus creates a bus over a directory.
func NewBus(dir *Directory) *Bus {
return &Bus{
dir: dir,
handlers: make(map[string]Handler),
Log: func(string, ...any) {},
}
}
// Handle registers the handler a party uses to receive messages addressed to it.
func (b *Bus) Handle(name string, h Handler) { b.handlers[name] = h }
// Count returns the number of verified messages carried so far.
func (b *Bus) Count() int { return b.count }
// Send verifies the request signature, delivers it to the recipient handler,
// then verifies the reply signature (if any) before returning it.
func (b *Bus) Send(env *Envelope) (*Envelope, error) {
sender, err := b.dir.Lookup(env.From)
if err != nil {
return nil, fmt.Errorf("send: %w", err)
}
if err := env.Verify(sender.EdPub); err != nil {
return nil, err
}
b.count++
b.Log(" [transport] %s -> %s : %s (%d bytes, encrypted=%v) signature OK",
env.From, env.To, env.Type, len(env.Payload), env.Encrypted)
h, ok := b.handlers[env.To]
if !ok {
return nil, fmt.Errorf("send: no handler for %q", env.To)
}
reply, err := h(env)
if err != nil {
return nil, fmt.Errorf("handler %s: %w", env.To, err)
}
if reply != nil {
replier, err := b.dir.Lookup(reply.From)
if err != nil {
return nil, fmt.Errorf("reply: %w", err)
}
if err := reply.Verify(replier.EdPub); err != nil {
return nil, fmt.Errorf("reply verification: %w", err)
}
b.count++
}
return reply, nil
}