Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ meshguard org-keygen

# Sign a node's identity with the org key
meshguard org-sign /path/to/node.pub --name node-1
# → node-1.cert (186 bytes)
# -> node-1.cert (v1, 186 bytes)

# Or bind the node's WireGuard key in a v2 cert
meshguard org-sign /path/to/node.pub --name node-1 --wg-pubkey /path/to/node.wg.pub
# -> node-1.cert (v2, 314 bytes)

# Remote peer: trust the org (one-time)
meshguard trust <org-pubkey> --org --name eosrio
Expand Down Expand Up @@ -149,7 +153,7 @@ For fleets, trust one org public key instead of N individual keys:
```
~/.config/meshguard/
├── identity.key / identity.pub # Node identity
├── node.cert # Org-signed certificate (186 bytes)
├── node.cert # Org-signed certificate (v1 186 B, v2 314 B)
├── authorized_keys/ # Individual peer trust
│ └── validator-1.pub
├── trusted_orgs/ # Org trust (auto-accept members)
Expand Down Expand Up @@ -190,7 +194,7 @@ Trust authorization order:
- Mesh IPv6 deterministically derived: `Blake3(pubkey) → fd99:6d67::X:Y/64` (ULA, RFC 4193)
- Dual-stack: both addresses assigned to the TUN interface automatically
- Authorized keys directory gates mesh membership
- **Org certificates**: 186-byte Ed25519-signed `NodeCertificate` for fleet trust
- **Org certificates**: Ed25519-signed `NodeCertificate` for fleet trust; v2 certs also bind the WireGuard public key
- **Mesh DNS**: deterministic `*.a1b2c3.mesh` domains per org, gossip-propagated aliases

### Discovery (SWIM)
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/identity-and-trust.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ When two nodes discover each other:

## Org Trust & Certificates

Organizations sign `NodeCertificate` structures (186 bytes) that bind a node's Ed25519 public key to an org identity. See the [Trust Model](/guide/trust-model) guide for full details.
Organizations sign `NodeCertificate` structures that bind a node's Ed25519 public key to an org identity. v1 certificates are 186 bytes; v2 certificates are 314 bytes and can also bind the node's WireGuard public key. See the [Trust Model](/guide/trust-model) guide for full details.

## Deterministic Mesh DNS

Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ meshguard's security model is built on three pillars: **cryptographic identity**
### Organization Keys

- **Org admins hold signing keys** — treat like root CA keys
- **Node certificates** — 186 bytes, stored at `~/.config/meshguard/node.cert`
- **Node certificates** — v1 186 bytes, v2 314 bytes, stored at `~/.config/meshguard/node.cert`
- **Revocation** — remove individual trust files immediately with `meshguard revoke`, or sign issuer-scoped org revocations with `meshguard org-revoke`

## Attack Surface
Expand Down
5 changes: 3 additions & 2 deletions docs/concepts/swim-discovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ Gossip entries are **piggybacked** on Ping and Ack messages — no dedicated gos

**Total per entry: 115 bytes.** Ping and Ack also carry an 8-byte incarnation
after the sequence number so peers can detect restarts. A fully-loaded Ping
with 8 gossip entries is 970 bytes before the optional 187-byte org certificate
extension, still under the default 1420-byte MTU.
with 8 gossip entries is 970 bytes before the optional org certificate
extension. New v2-sized extensions are 315 bytes including the presence byte,
still under the default 1420-byte MTU.

## Handshake Flow

Expand Down
6 changes: 5 additions & 1 deletion docs/concepts/wire-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,13 @@ org certificate extension.
The optional org certificate extension is appended to Ping/Ack after gossip as:

```
[1B present=1][186B NodeCertificate]
[1B present=1][314B NodeCertificate max]
```

New encoders emit the full 314-byte v2-sized certificate slot. Decoders also
accept the legacy `[1B present=1][186B NodeCertificate]` extension and zero-fill
the v2-only fields so v1 certificates continue to work during rollout.

## HandshakeInitiation (Type 1)

Standard Noise_IKpsk2 initiation message.
Expand Down
66 changes: 51 additions & 15 deletions docs/guide/trust-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ For fleet deployments, managing N individual keys becomes impractical. **Org tru
```
~/.config/meshguard/
├── identity.key / identity.pub # Node identity
├── node.cert # Org-signed certificate (186 bytes)
├── node.cert # Org-signed certificate (v1 186 B, v2 314 B)
├── authorized_keys/ # Individual peer trust
│ └── validator-1.pub
├── trusted_orgs/ # Org trust (auto-accept members)
Expand All @@ -75,19 +75,50 @@ For fleet deployments, managing N individual keys becomes impractical. **Org tru

### NodeCertificate Format

Each certificate is a fixed-size 186-byte binary structure:

| Field | Size | Description |
| ------------- | -------- | --------------------------------------- |
| `version` | 1 byte | Certificate version (currently 1) |
| `org_pubkey` | 32 bytes | Organization Ed25519 public key |
| `node_pubkey` | 32 bytes | Node Ed25519 public key |
| `node_name` | 32 bytes | DNS label, null-padded |
| `issued_at` | 8 bytes | Unix timestamp |
| `expires_at` | 8 bytes | Unix timestamp (0 = never) |
| `flags` | 1 byte | Reserved |
| `signature` | 64 bytes | Ed25519 signature over all prior fields |
| _padding_ | 8 bytes | Future expansion |
meshguard supports two node-certificate versions during the v2 rollout:

| Version | Wire size | Description |
| ------- | --------- | ----------- |
| v1 | 186 bytes | Org signs node identity, name, issue time, expiry, and flags. WireGuard public keys are still learned from gossip. |
| v2 | 314 bytes | v1 prefix plus signed WireGuard public key binding, issuer key, and optional delegated-issuer grant signature. |

The shared v1 prefix is:

| Field | Size | Description |
| ------------- | -------- | ---------------------------------------- |
| `version` | 1 byte | Certificate version (`1` or `2`) |
| `org_pubkey` | 32 bytes | Organization Ed25519 public key |
| `node_pubkey` | 32 bytes | Node Ed25519 public key |
| `node_name` | 32 bytes | DNS label, null-padded |
| `issued_at` | 8 bytes | Unix timestamp |
| `expires_at` | 8 bytes | Unix timestamp (0 = never) |
| `flags` | 1 byte | Delegation flag in v2, reserved in v1 |
| `signature` | 64 bytes | Ed25519 signature over versioned payload |
| _padding_ | 8 bytes | v1 padding before v2 extension fields |

v2 appends:

| Field | Size | Description |
| ----- | ---- | ----------- |
| `wg_pubkey` | 32 bytes | Signed X25519 WireGuard public key for this node |
| `issuer_pubkey` | 32 bytes | Zero for normal direct certs, or the delegated issuer key when the delegation flag is set |
| `delegation_signature` | 64 bytes | Org signature authorizing the delegated issuer; zero for direct org-signed v2 certs |

For direct v2 certs, the org signs the full v2 payload. For delegated v2 certs,
the issuer signs the node payload and the org signs a one-hop grant to that
issuer. The issuer can be a human/role key or a partner org key. Revoking the
issuer with `meshguard org-revoke` invalidates all leaves admitted through that
issuer for the revoking org.

### v1/v2 Migration Policy

Upgraded meshguard nodes accept legacy v1 certificates and v2 certificates.
Legacy meshguard nodes accept only v1 certificates. During a mixed-version
rollout, upgrade binaries first and continue issuing v1 certs until every
trust-enforcing peer has the v2 parser. Then issue v2 certs, usually with
`meshguard org-sign --wg-pubkey`, to bind each node identity to its WireGuard
public key. v1 peers remain supported, but their WireGuard key is still learned
from gossip instead of the certificate.

### Mesh DNS

Expand All @@ -107,9 +138,12 @@ Organizations can also claim **human-readable aliases** (e.g. `*.eosrio.mesh`) v
# Generate org keypair
meshguard org-keygen

# Sign a node's key
# Sign a node's key with a v1 certificate
meshguard org-sign /path/to/node.pub --name db-1

# Sign a v2 certificate that binds the node's WireGuard public key
meshguard org-sign /path/to/node.pub --name db-1 --wg-pubkey /path/to/node.wg.pub

# Trust an org (auto-accept all signed nodes)
meshguard trust <org-pubkey> --org --name eosrio

Expand Down Expand Up @@ -159,6 +193,8 @@ When a new peer connects, trust is evaluated in order:
- Verify Ed25519 signature
- Check certificate expiry
- Confirm issuing org is in `trusted_orgs/`
- For v2, bind the peer's WireGuard public key from the signed cert
- For delegated v2, verify the org-signed issuer grant and issuer signature
3. **Org vouch check** — has a trusted org vouched for this peer?
4. **Revocation check** — has the cert/vouch been revoked via gossip?

Expand Down
7 changes: 6 additions & 1 deletion docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,14 +281,19 @@ meshguard org-keygen
Sign a node's public key with the organization key.

```bash
meshguard org-sign <node-key-or-path> [--name <label>] [--expires <unix-timestamp>]
meshguard org-sign <node-key-or-path> [--name <label>] [--expires <unix-timestamp>] [--wg-pubkey <key-or-path>]
```

| Argument | Description |
| -------- | ----------- |
| `<node-key-or-path>` | Base64 public key string _or_ path to a `.pub` file |
| `--name` | Human-readable name for the node |
| `--expires` | Unix timestamp when the signature expires (default: 0, never expires) |
| `--wg-pubkey` | Base64 WireGuard public key string _or_ path to a file. When set, emits a v2 certificate that binds node identity to this WireGuard key. |

Without `--wg-pubkey`, `org-sign` emits a legacy v1 certificate for mixed-version
meshes. With `--wg-pubkey`, it emits a v2 certificate. Upgrade trust-enforcing
peers before relying on v2-only certificates.

---

Expand Down
2 changes: 2 additions & 0 deletions src/discovery/membership.zig
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub const Peer = struct {
// ── Org trust fields ──
/// Org public key (if peer authenticated via org cert)
org_pubkey: ?[32]u8 = null,
/// Cert issuer public key when admitted via delegated v2 cert.
cert_issuer_pubkey: ?[32]u8 = null,
/// Node name from certificate (e.g. "node-1")
org_node_name: [32]u8 = std.mem.zeroes([32]u8),
/// Certificate expiry (0 = never, null = no cert)
Expand Down
Loading
Loading