diff --git a/README.md b/README.md index b28651e..341e5c5 100644 --- a/README.md +++ b/README.md @@ -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 --name eosrio @@ -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) @@ -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) diff --git a/docs/concepts/identity-and-trust.md b/docs/concepts/identity-and-trust.md index 50565aa..73f515d 100644 --- a/docs/concepts/identity-and-trust.md +++ b/docs/concepts/identity-and-trust.md @@ -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 diff --git a/docs/concepts/security.md b/docs/concepts/security.md index 6ea5e95..4e15411 100644 --- a/docs/concepts/security.md +++ b/docs/concepts/security.md @@ -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 diff --git a/docs/concepts/swim-discovery.md b/docs/concepts/swim-discovery.md index 9def8da..e7a3775 100644 --- a/docs/concepts/swim-discovery.md +++ b/docs/concepts/swim-discovery.md @@ -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 diff --git a/docs/concepts/wire-protocol.md b/docs/concepts/wire-protocol.md index 38df891..69d5abe 100644 --- a/docs/concepts/wire-protocol.md +++ b/docs/concepts/wire-protocol.md @@ -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. diff --git a/docs/guide/trust-model.md b/docs/guide/trust-model.md index 16bdbc1..3216ca6 100644 --- a/docs/guide/trust-model.md +++ b/docs/guide/trust-model.md @@ -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) @@ -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 @@ -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 --name eosrio @@ -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? diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 8c6bbed..aad3c40 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -281,7 +281,7 @@ meshguard org-keygen Sign a node's public key with the organization key. ```bash -meshguard org-sign [--name