mirror of
https://github.com/saymrwulf/swisspost-evoting-go-poc.git
synced 2026-09-05 20:30:43 +00:00
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>
127 lines
3.8 KiB
Go
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
|
|
}
|