@agentproto/secrets 0.2.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-Q6K3AT3V.mjs → chunk-2FHFW2AU.mjs} +3 -3
- package/dist/{chunk-Q6K3AT3V.mjs.map → chunk-2FHFW2AU.mjs.map} +1 -1
- package/dist/chunk-3YRYO6LK.mjs +222 -0
- package/dist/chunk-3YRYO6LK.mjs.map +1 -0
- package/dist/chunk-432SC3XS.mjs +100 -0
- package/dist/chunk-432SC3XS.mjs.map +1 -0
- package/dist/chunk-5HUHYBPR.mjs +69 -0
- package/dist/chunk-5HUHYBPR.mjs.map +1 -0
- package/dist/{chunk-NLZ5HXGO.mjs → chunk-7V6U6DII.mjs} +4 -4
- package/dist/{chunk-NLZ5HXGO.mjs.map → chunk-7V6U6DII.mjs.map} +1 -1
- package/dist/chunk-JNTDSOZQ.mjs +623 -0
- package/dist/chunk-JNTDSOZQ.mjs.map +1 -0
- package/dist/chunk-NQX3KBPW.mjs +50 -0
- package/dist/chunk-NQX3KBPW.mjs.map +1 -0
- package/dist/chunk-PCFPJ47T.mjs +28 -0
- package/dist/chunk-PCFPJ47T.mjs.map +1 -0
- package/dist/cli.mjs +10 -7
- package/dist/cli.mjs.map +1 -1
- package/dist/core-CIn0-z-Y.d.ts +42 -0
- package/dist/core-D8zVNPDb.d.ts +58 -0
- package/dist/identity/index.d.ts +25 -46
- package/dist/identity/index.mjs +76 -1
- package/dist/identity/index.mjs.map +1 -1
- package/dist/pairing/browser.d.ts +18 -0
- package/dist/pairing/browser.mjs +6 -0
- package/dist/pairing/browser.mjs.map +1 -0
- package/dist/pairing/index.d.ts +45 -440
- package/dist/pairing/index.mjs +33 -465
- package/dist/pairing/index.mjs.map +1 -1
- package/dist/provision/index.mjs +5 -2
- package/dist/provision/recipe/index.mjs +6 -3
- package/dist/seal/index.d.ts +21 -31
- package/dist/seal/index.mjs +4 -1
- package/dist/types-B2ZfEhsU.d.ts +57 -0
- package/dist/webcrypto-BU57p0_g.d.ts +652 -0
- package/package.json +9 -3
- package/dist/chunk-2DL6W33G.mjs +0 -126
- package/dist/chunk-2DL6W33G.mjs.map +0 -1
- package/dist/chunk-MVHOJPML.mjs +0 -132
- package/dist/chunk-MVHOJPML.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pairing/handshake.ts","../../src/pairing/offer-url.ts","../../src/pairing/rendezvous.ts","../../src/pairing/derive.ts","../../src/pairing/tunnel-handshake.ts"],"names":["b64url","hkdfSync","KEY_LEN","createHash","x25519PublicKey","createPublicKey","transcriptHash","deriveDirectionKeys","generateKeyPairSync","diffieHellman","isRecord","parseJson"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @agentproto/secrets/pairing — the `pair/v1` session handshake\n * (design: DESIGN §4).\n *\n * A Noise-flavoured, two-message handshake that lets a client and a daemon —\n * connected only through an untrusted rendezvous that byte-splices their\n * sockets — derive per-direction AEAD keys such that the rendezvous can neither\n * read nor forge the session:\n *\n * ```\n * client → daemon: e_pub // ephemeral X25519\n * ct₀ = Seal(to = daemon_x25519,\n * {clientPub: e_pub, clientName, offerToken})\n * daemon → client: d_e_pub, sig = Ed25519(daemon_ed25519,\n * transcript = sha256(e_pub ‖ ct₀ ‖ d_e_pub))\n * both: K = HKDF-SHA256(ECDH(e, d_e) ‖ ECDH(e, daemon_x25519),\n * salt = transcript, info = \"agentproto/pair/v1\")\n * → K_c2d ‖ K_d2c (two AES-256-GCM keys)\n * ```\n *\n * Why this shape:\n * - The client learned the daemon's static public keys out-of-band (the offer\n * URL / QR). Verifying `sig` against the offer's Ed25519 key proves the peer\n * is the daemon the human scanned, not a rendezvous impersonating it — MITM\n * protection without a CA.\n * - The daemon proves the client is authorised by opening `ct₀` (only the\n * daemon's X25519 private key can) and checking the one-time `offerToken`.\n * - `sig` covers the whole transcript and the transcript salts the key\n * schedule, so any tampering with `e_pub`, `ct₀`, or `d_e_pub` in flight\n * makes either the signature or the derived keys disagree — the session\n * fails closed, never continuing with attacker-chosen material.\n *\n * This module is deliberately **transport-agnostic**: it produces and consumes\n * plain messages (`encode*`/`decode*` give byte arrays). The code that pumps\n * those bytes over a `FrameSink` lives in `@agentproto/acp/tunnel`, which stays\n * free of any dependency on this package — it receives only the derived keys.\n * All crypto stays here so the acp layer never touches key material beyond the\n * two symmetric session keys.\n */\n\nimport {\n createPublicKey,\n createPrivateKey,\n createHash,\n diffieHellman,\n hkdfSync,\n generateKeyPairSync,\n type KeyObject,\n} from \"node:crypto\"\nimport { seal, unseal, SealError } from \"../seal/index.js\"\nimport {\n identityFingerprint,\n signTranscript,\n verifyTranscript,\n type DaemonIdentity,\n} from \"../identity/index.js\"\n\n/** Wire version of the handshake. Bumped if the message shape or key schedule\n * changes; both sides refuse a version they don't recognise. */\nexport const PAIR_VERSION = 1 as const\n\n/** HKDF `info` — domain-separates this key schedule from every other HKDF use\n * in the codebase (seal boxes, future rendezvous-token derivation). */\nconst HKDF_INFO = \"agentproto/pair/v1\"\n\n/** Length of each direction key: AES-256 → 32 bytes, two of them → 64. */\nconst KEY_LEN = 32\n\n/** Stable, machine-readable failure codes. Every rejection maps to one of\n * these so callers (and tests) branch on a code, not a message string. */\nexport type PairingErrorCode =\n | \"malformed_hello\"\n | \"malformed_reply\"\n | \"invalid_key\"\n | \"unseal_failed\"\n | \"ephemeral_mismatch\"\n | \"offer_rejected\"\n | \"bad_signature\"\n // P2: offer-URL codec (offer-url.ts) rejection vectors.\n | \"malformed_offer\"\n | \"offer_expired\"\n\n/** Raised for every handshake failure. Never carries key material or\n * plaintext; the `code` is the contract, the message is for humans. */\nexport class PairingError extends Error {\n readonly code: PairingErrorCode\n constructor(code: PairingErrorCode, message: string) {\n super(message)\n this.name = \"PairingError\"\n this.code = code\n }\n}\n\n/** Client → daemon. `ePub` is the client ephemeral X25519 public key (base64\n * SPKI DER); `ct0` is the sealed hello payload (a `seal()` envelope string). */\nexport interface PairingHello {\n v: typeof PAIR_VERSION\n ePub: string\n ct0: string\n}\n\n/** Daemon → client. `dePub` is the daemon ephemeral X25519 public key (base64\n * SPKI DER); `sig` is the Ed25519 transcript signature (base64). */\nexport interface PairingReply {\n v: typeof PAIR_VERSION\n dePub: string\n sig: string\n}\n\n/** The sealed hello payload, opened only by the daemon. */\ninterface HelloPayload {\n /** Echo of the client ephemeral public key — bound inside the seal so a\n * rendezvous can't swap `ePub` without breaking the seal. */\n clientPub: string\n /** Human-facing client label the daemon shows on `pair accept`. */\n clientName: string\n /** One-time offer token from the offer URL — the daemon's proof the client\n * holds a fresh, unspent offer. */\n offerToken: string\n}\n\n/**\n * The result of a completed handshake, on either side. `sendKey`/`recvKey` are\n * already role-adjusted (a client's `sendKey` is a daemon's `recvKey`), so the\n * consumer — `wrapE2E` — never has to know which side it is.\n */\nexport interface PairingSession {\n /** AES-256-GCM key for frames THIS side sends. 32 bytes. */\n sendKey: Uint8Array\n /** AES-256-GCM key for frames THIS side receives. 32 bytes. */\n recvKey: Uint8Array\n /** Fingerprint of the peer's static identity (the daemon's X25519 key on the\n * client side; in P1, the client's ephemeral key on the daemon side, since\n * clients have no persisted identity until P2's pairings store). */\n peerFingerprint: string\n /** `sha256(e_pub ‖ ct₀ ‖ d_e_pub)` — the exact transcript both sides bound\n * to. A later phase channel-binds reconnect tokens to this. */\n transcriptHash: Uint8Array\n /**\n * The human-facing client label carried in the sealed hello. On the daemon\n * side this is the name the peer chose (surfaced in `pairings.json` and on\n * `pair accept`); on the client side it echoes the name this side supplied.\n * Optional so pre-P2 callers constructing a session literal are unaffected —\n * P2 (pairing-registry) reads it to name a persisted pairing without opening\n * the seal a second time. Populated by both handshake entry points below.\n */\n clientName?: string\n}\n\n// ─── key helpers ────────────────────────────────────────────────\n\nfunction x25519PublicKey(b64Der: string, what: string): KeyObject {\n try {\n return createPublicKey({\n key: Buffer.from(b64Der, \"base64\"),\n format: \"der\",\n type: \"spki\",\n })\n } catch {\n throw new PairingError(\"invalid_key\", `invalid ${what} X25519 public key`)\n }\n}\n\nfunction x25519PrivateKey(b64Der: string, what: string): KeyObject {\n try {\n return createPrivateKey({\n key: Buffer.from(b64Der, \"base64\"),\n format: \"der\",\n type: \"pkcs8\",\n })\n } catch {\n throw new PairingError(\"invalid_key\", `invalid ${what} X25519 private key`)\n }\n}\n\n/**\n * Compute the transcript hash from the raw wire bytes of each message part.\n * Using the wire bytes (not a re-export of the parsed key) guarantees both\n * sides hash identical bytes regardless of any DER canonicalisation.\n */\nfunction transcriptHash(ePubB64: string, ct0: string, dePubB64: string): Buffer {\n return createHash(\"sha256\")\n .update(Buffer.from(ePubB64, \"base64\"))\n .update(Buffer.from(ct0, \"utf8\"))\n .update(Buffer.from(dePubB64, \"base64\"))\n .digest()\n}\n\n/** Derive the two direction keys from the two ECDH outputs, salted by the\n * transcript. Concatenation order (ECDH(e,d_e) then ECDH(e,static)) and the\n * key split (c2d then d2c) are identical on both sides. */\nfunction deriveDirectionKeys(\n ecdhEphemeral: Buffer,\n ecdhStatic: Buffer,\n transcript: Buffer\n): { kc2d: Buffer; kd2c: Buffer } {\n const ikm = Buffer.concat([ecdhEphemeral, ecdhStatic])\n const okm = Buffer.from(hkdfSync(\"sha256\", ikm, transcript, HKDF_INFO, KEY_LEN * 2))\n return {\n kc2d: okm.subarray(0, KEY_LEN),\n kd2c: okm.subarray(KEY_LEN, KEY_LEN * 2),\n }\n}\n\n// ─── client side ────────────────────────────────────────────────\n\n/** Everything the client learned from the offer URL, plus its chosen name. */\nexport interface ClientHandshakeParams {\n /** Daemon static X25519 public key (base64 SPKI DER) — the seal recipient\n * and one ECDH input. From the offer's `pk`. */\n daemonX25519Pub: string\n /** Daemon static Ed25519 public key (base64 SPKI DER) — verifies `sig`.\n * From the offer's `sk`. */\n daemonEd25519Pub: string\n /** One-time offer token. From the offer's `t`. */\n offerToken: string\n /** Human-facing client label the daemon displays on accept. */\n clientName: string\n}\n\n/** A started client handshake: send `hello`, then feed the daemon's reply to\n * `complete` to derive the session. */\nexport interface StartedClientHandshake {\n hello: PairingHello\n /** Verify the daemon reply and derive the session. Throws `PairingError` on\n * a bad signature, malformed reply, or invalid key — never returns partial\n * state. */\n complete(reply: PairingReply): PairingSession\n}\n\n/**\n * Begin a client handshake. Generates the client ephemeral keypair, seals the\n * hello payload to the daemon's static key, and returns the `hello` to send\n * plus a `complete` to run once the daemon replies.\n */\nexport function startClientHandshake(\n params: ClientHandshakeParams\n): StartedClientHandshake {\n // Validate the daemon keys up front so a bad offer fails before we transmit.\n const daemonStatic = x25519PublicKey(params.daemonX25519Pub, \"daemon\")\n\n const ephemeral = generateKeyPairSync(\"x25519\")\n const ePubB64 = ephemeral.publicKey\n .export({ type: \"spki\", format: \"der\" })\n .toString(\"base64\")\n\n const payload: HelloPayload = {\n clientPub: ePubB64,\n clientName: params.clientName,\n offerToken: params.offerToken,\n }\n const ct0 = seal(JSON.stringify(payload), params.daemonX25519Pub)\n const hello: PairingHello = { v: PAIR_VERSION, ePub: ePubB64, ct0 }\n\n const complete = (reply: PairingReply): PairingSession => {\n if (reply.v !== PAIR_VERSION) {\n throw new PairingError(\"malformed_reply\", `unsupported reply version ${reply.v}`)\n }\n const dePub = x25519PublicKey(reply.dePub, \"daemon ephemeral\")\n const transcript = transcriptHash(ePubB64, ct0, reply.dePub)\n\n if (!verifyTranscript(params.daemonEd25519Pub, transcript, reply.sig)) {\n throw new PairingError(\n \"bad_signature\",\n \"daemon transcript signature did not verify against the offered key\"\n )\n }\n\n const ecdhEphemeral = diffieHellman({ privateKey: ephemeral.privateKey, publicKey: dePub })\n const ecdhStatic = diffieHellman({ privateKey: ephemeral.privateKey, publicKey: daemonStatic })\n const { kc2d, kd2c } = deriveDirectionKeys(ecdhEphemeral, ecdhStatic, transcript)\n\n return {\n sendKey: kc2d,\n recvKey: kd2c,\n peerFingerprint: identityFingerprint(params.daemonX25519Pub),\n transcriptHash: transcript,\n clientName: params.clientName,\n }\n }\n\n return { hello, complete }\n}\n\n// ─── daemon side ────────────────────────────────────────────────\n\n/** What the daemon brings to the handshake: its identity, and a predicate that\n * validates (and, in P2, spends) the one-time offer token. */\nexport interface DaemonHandshakeParams {\n identity: DaemonIdentity\n /**\n * Validate the presented offer token. Returning false rejects the handshake\n * with `offer_rejected`. The daemon owns single-use + expiry policy here so\n * this module never needs to know about the offer store — a stale or already\n * spent token simply returns false.\n */\n verifyOfferToken: (token: string) => boolean\n}\n\n/** A completed daemon handshake: send `reply`, keep `session`. */\nexport interface DaemonHandshakeResult {\n reply: PairingReply\n session: PairingSession\n}\n\n/**\n * Respond to a client hello. Opens the sealed payload with the daemon's X25519\n * private key, checks the offer token and the ephemeral-key binding, signs the\n * transcript, and derives the session. Throws `PairingError` on any failure —\n * a tampered `ct₀`, a swapped `ePub`, a rejected token — before producing any\n * reply, so a rejected client learns nothing and gets no session.\n */\nexport function respondToHandshake(\n hello: PairingHello,\n params: DaemonHandshakeParams\n): DaemonHandshakeResult {\n if (hello.v !== PAIR_VERSION) {\n throw new PairingError(\"malformed_hello\", `unsupported hello version ${hello.v}`)\n }\n const clientEphemeral = x25519PublicKey(hello.ePub, \"client ephemeral\")\n\n let payloadJson: string\n try {\n payloadJson = unseal(hello.ct0, params.identity.x25519.priv)\n } catch (err) {\n if (err instanceof SealError) {\n throw new PairingError(\n \"unseal_failed\",\n \"could not open sealed hello — wrong daemon key or tampered ct₀\"\n )\n }\n throw err\n }\n\n const payload = parseHelloPayload(payloadJson)\n\n // The sealed payload echoes the client ephemeral key; it must match the\n // cleartext ePub, or a rendezvous swapped ePub while relaying the intact\n // (opaque) seal. Binds the ephemeral to the sealed offer proof.\n if (payload.clientPub !== hello.ePub) {\n throw new PairingError(\n \"ephemeral_mismatch\",\n \"sealed clientPub does not match the hello ephemeral key\"\n )\n }\n\n if (!params.verifyOfferToken(payload.offerToken)) {\n throw new PairingError(\"offer_rejected\", \"offer token was rejected (unknown, expired, or spent)\")\n }\n\n const daemonEphemeral = generateKeyPairSync(\"x25519\")\n const dePubB64 = daemonEphemeral.publicKey\n .export({ type: \"spki\", format: \"der\" })\n .toString(\"base64\")\n\n const transcript = transcriptHash(hello.ePub, hello.ct0, dePubB64)\n const sig = signTranscript(params.identity.ed25519.priv, transcript)\n\n const daemonStaticPriv = x25519PrivateKey(params.identity.x25519.priv, \"daemon static\")\n const ecdhEphemeral = diffieHellman({ privateKey: daemonEphemeral.privateKey, publicKey: clientEphemeral })\n const ecdhStatic = diffieHellman({ privateKey: daemonStaticPriv, publicKey: clientEphemeral })\n const { kc2d, kd2c } = deriveDirectionKeys(ecdhEphemeral, ecdhStatic, transcript)\n\n return {\n reply: { v: PAIR_VERSION, dePub: dePubB64, sig },\n session: {\n // Daemon sends on c2d's counterpart: it RECEIVES client→daemon (kc2d)\n // and SENDS daemon→client (kd2c).\n sendKey: kd2c,\n recvKey: kc2d,\n peerFingerprint: identityFingerprint(payload.clientPub),\n transcriptHash: transcript,\n clientName: payload.clientName,\n },\n }\n}\n\n// ─── message (de)serialization ──────────────────────────────────\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null\n}\n\nfunction parseHelloPayload(json: string): HelloPayload {\n let parsed: unknown\n try {\n parsed = JSON.parse(json)\n } catch {\n throw new PairingError(\"malformed_hello\", \"sealed hello payload was not valid JSON\")\n }\n if (\n !isRecord(parsed) ||\n typeof parsed[\"clientPub\"] !== \"string\" ||\n typeof parsed[\"clientName\"] !== \"string\" ||\n typeof parsed[\"offerToken\"] !== \"string\"\n ) {\n throw new PairingError(\"malformed_hello\", \"sealed hello payload is missing required fields\")\n }\n return {\n clientPub: parsed[\"clientPub\"],\n clientName: parsed[\"clientName\"],\n offerToken: parsed[\"offerToken\"],\n }\n}\n\n/** Serialize a handshake message to bytes for transport. */\nexport function encodePairingMessage(message: PairingHello | PairingReply): Uint8Array {\n return Buffer.from(JSON.stringify(message), \"utf8\")\n}\n\n/** Parse + validate a client hello from raw bytes. Truncated or malformed\n * input throws `PairingError(\"malformed_hello\")` — never a partial object. */\nexport function decodePairingHello(bytes: Uint8Array): PairingHello {\n const parsed = parseJson(bytes, \"malformed_hello\")\n if (\n !isRecord(parsed) ||\n parsed[\"v\"] !== PAIR_VERSION ||\n typeof parsed[\"ePub\"] !== \"string\" ||\n typeof parsed[\"ct0\"] !== \"string\"\n ) {\n throw new PairingError(\"malformed_hello\", \"hello message is malformed\")\n }\n return { v: PAIR_VERSION, ePub: parsed[\"ePub\"], ct0: parsed[\"ct0\"] }\n}\n\n/** Parse + validate a daemon reply from raw bytes. Truncated or malformed\n * input throws `PairingError(\"malformed_reply\")`. */\nexport function decodePairingReply(bytes: Uint8Array): PairingReply {\n const parsed = parseJson(bytes, \"malformed_reply\")\n if (\n !isRecord(parsed) ||\n parsed[\"v\"] !== PAIR_VERSION ||\n typeof parsed[\"dePub\"] !== \"string\" ||\n typeof parsed[\"sig\"] !== \"string\"\n ) {\n throw new PairingError(\"malformed_reply\", \"reply message is malformed\")\n }\n return { v: PAIR_VERSION, dePub: parsed[\"dePub\"], sig: parsed[\"sig\"] }\n}\n\nfunction parseJson(bytes: Uint8Array, code: PairingErrorCode): unknown {\n try {\n return JSON.parse(Buffer.from(bytes).toString(\"utf8\"))\n } catch {\n throw new PairingError(code, \"message was not valid JSON (truncated or corrupt)\")\n }\n}\n","/**\n * The pairing offer URL codec (design: DESIGN §2).\n *\n * `agentproto pair offer` prints a single URL (also renderable as a QR):\n *\n * ```\n * agentproto://pair?v=1\n * &rv=<rendezvous ws/wss url> // where both sides meet\n * &id=<fingerprint> // daemon identity fingerprint (16 hex)\n * &pk=<b64url x25519 SPKI DER> // daemon static encryption key\n * &sk=<b64url ed25519 SPKI DER> // daemon signing key\n * &t=<one-time offer token> // rendezvous routing + first-contact proof\n * &exp=<unix seconds> // offer expiry\n * ```\n *\n * The URL **is** the bootstrap secret. It carries the daemon's public keys, so\n * a client that scans it can pin the daemon and detect a man-in-the-middle\n * rendezvous (verifying the handshake signature against `sk`); and it carries a\n * one-time, short-TTL token so a stranger who never saw the URL can't pair.\n *\n * This module is a **pure codec** — it validates structure and echoes bytes; it\n * performs no I/O and no network calls, so it is safe to run on either side\n * (the daemon builds it, the client parses it). It lives in `@agentproto/secrets`\n * beside the handshake so both sides share one authority on the format.\n *\n * Key material travels **base64url** in the URL (no `+`/`/`/`=` to percent-\n * escape). The handshake, however, speaks standard base64 SPKI DER, so\n * `parseOfferUrl` returns `daemonX25519Pub`/`daemonEd25519Pub` already converted\n * back to standard base64 — feed them straight into `startClientHandshake`.\n */\n\nimport { identityFingerprint } from \"../identity/index.js\"\nimport { PairingError } from \"./handshake.js\"\n\n/** URL scheme + host for offer URLs. */\nexport const OFFER_URL_SCHEME = \"agentproto:\" as const\nexport const OFFER_URL_HOST = \"pair\" as const\n/** Offer-format version. Bumped if the param set changes. */\nexport const OFFER_VERSION = 1 as const\n\n/**\n * A parsed, structurally-valid pairing offer. `daemonX25519Pub` /\n * `daemonEd25519Pub` are standard-base64 SPKI DER (handshake-ready). `token` is\n * the opaque one-time offer token verbatim (the daemon checks it against its\n * offer store). `exp` is unix **seconds**.\n */\nexport interface PairingOffer {\n v: typeof OFFER_VERSION\n /** Rendezvous endpoint both sides dial (ws:// or wss://). */\n rendezvousUrl: string\n /** Daemon identity fingerprint (16 hex) — must equal fingerprint(pk). */\n fingerprint: string\n /** Daemon static X25519 public key, standard base64 SPKI DER. */\n daemonX25519Pub: string\n /** Daemon static Ed25519 public key, standard base64 SPKI DER. */\n daemonEd25519Pub: string\n /** One-time offer token (routing + first-contact proof). */\n token: string\n /** Offer expiry, unix seconds. */\n exp: number\n}\n\n// ─── base64 ⇄ base64url ──────────────────────────────────────────\n\nfunction b64ToB64url(b64: string): string {\n return b64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\")\n}\n\nfunction b64urlToB64(b64url: string): string {\n const replaced = b64url.replace(/-/g, \"+\").replace(/_/g, \"/\")\n const pad = replaced.length % 4\n return pad === 0 ? replaced : replaced + \"=\".repeat(4 - pad)\n}\n\n/** True when `s` is a non-empty base64url string (the alphabet only). */\nfunction isB64url(s: string): boolean {\n return s.length > 0 && /^[A-Za-z0-9_-]+$/.test(s)\n}\n\n// ─── encode ──────────────────────────────────────────────────────\n\n/**\n * Build the offer URL from an offer. The public keys come in as standard\n * base64 (the shape the identity file + handshake use) and are emitted as\n * base64url. `token` is emitted verbatim (callers mint it as base64url).\n */\nexport function encodeOfferUrl(offer: PairingOffer): string {\n const params = new URLSearchParams()\n params.set(\"v\", String(OFFER_VERSION))\n params.set(\"rv\", offer.rendezvousUrl)\n params.set(\"id\", offer.fingerprint)\n params.set(\"pk\", b64ToB64url(offer.daemonX25519Pub))\n params.set(\"sk\", b64ToB64url(offer.daemonEd25519Pub))\n params.set(\"t\", offer.token)\n params.set(\"exp\", String(offer.exp))\n return `${OFFER_URL_SCHEME}//${OFFER_URL_HOST}?${params.toString()}`\n}\n\n// ─── parse ───────────────────────────────────────────────────────\n\nexport interface ParseOfferOptions {\n /**\n * When set, the parser rejects an offer whose `exp` is at or before this\n * instant (unix **milliseconds**) with `PairingError(\"offer_expired\")`. Omit\n * to parse structure only and let the caller decide when to check expiry\n * (the daemon's offer store is the authoritative single-use + expiry gate).\n */\n now?: number\n}\n\n/**\n * Parse + strictly validate an offer URL. Throws `PairingError` — never returns\n * a partial object — on any structural problem:\n *\n * - `malformed_offer`: wrong scheme/host, unknown version, missing/blank\n * params, non-base64url keys/token, non-integer `exp`, or a `fingerprint`\n * that does not match `fingerprint(pk)` (tamper detection: a rendezvous or\n * link-mangler that swaps the daemon key can't keep `id` consistent).\n * - `offer_expired`: only when `opts.now` is supplied and `exp` has passed.\n */\nexport function parseOfferUrl(url: string, opts: ParseOfferOptions = {}): PairingOffer {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n throw new PairingError(\"malformed_offer\", \"offer is not a valid URL\")\n }\n if (parsed.protocol !== OFFER_URL_SCHEME) {\n throw new PairingError(\n \"malformed_offer\",\n `offer scheme must be \"${OFFER_URL_SCHEME}//\" (got \"${parsed.protocol}//\")`,\n )\n }\n // `agentproto://pair?…` parses with host=\"pair\"; tolerate `agentproto:pair?…`\n // (host=\"\" pathname=\"pair\") too, since some QR scanners/relayers normalise the\n // authority away.\n const host = parsed.host || parsed.pathname.replace(/^\\/+/, \"\")\n if (host !== OFFER_URL_HOST) {\n throw new PairingError(\"malformed_offer\", `offer host must be \"${OFFER_URL_HOST}\"`)\n }\n\n const q = parsed.searchParams\n const v = q.get(\"v\")\n if (v !== String(OFFER_VERSION)) {\n throw new PairingError(\"malformed_offer\", `unsupported offer version \"${v ?? \"(absent)\"}\"`)\n }\n\n const rendezvousUrl = req(q, \"rv\")\n // The rendezvous must be a ws/wss URL; reject anything else early so a\n // tampered offer can't point the client at an arbitrary scheme.\n let rvParsed: URL\n try {\n rvParsed = new URL(rendezvousUrl)\n } catch {\n throw new PairingError(\"malformed_offer\", \"offer `rv` is not a valid URL\")\n }\n if (rvParsed.protocol !== \"ws:\" && rvParsed.protocol !== \"wss:\") {\n throw new PairingError(\"malformed_offer\", \"offer `rv` must be a ws:// or wss:// URL\")\n }\n\n const fingerprint = req(q, \"id\")\n if (!/^[0-9a-f]{16}$/.test(fingerprint)) {\n throw new PairingError(\"malformed_offer\", \"offer `id` is not a 16-hex fingerprint\")\n }\n\n const pkUrl = req(q, \"pk\")\n const skUrl = req(q, \"sk\")\n if (!isB64url(pkUrl) || !isB64url(skUrl)) {\n throw new PairingError(\"malformed_offer\", \"offer `pk`/`sk` must be base64url\")\n }\n const daemonX25519Pub = b64urlToB64(pkUrl)\n const daemonEd25519Pub = b64urlToB64(skUrl)\n\n const token = req(q, \"t\")\n if (!isB64url(token)) {\n throw new PairingError(\"malformed_offer\", \"offer `t` (token) must be base64url\")\n }\n\n const expRaw = req(q, \"exp\")\n const exp = Number(expRaw)\n if (!Number.isInteger(exp) || exp <= 0) {\n throw new PairingError(\"malformed_offer\", \"offer `exp` is not a positive unix timestamp\")\n }\n\n // Integrity: `id` must be the fingerprint OF the offered X25519 key. This is\n // what turns the URL into a self-authenticating bootstrap secret — a party\n // that swaps `pk` for their own key can't also produce a matching `id` without\n // it being obviously a different fingerprint the human never saw.\n if (identityFingerprint(daemonX25519Pub) !== fingerprint) {\n throw new PairingError(\n \"malformed_offer\",\n \"offer `id` does not match fingerprint(pk) — tampered or corrupt offer\",\n )\n }\n\n if (opts.now !== undefined && exp * 1000 <= opts.now) {\n throw new PairingError(\"offer_expired\", \"offer has expired\")\n }\n\n return {\n v: OFFER_VERSION,\n rendezvousUrl,\n fingerprint,\n daemonX25519Pub,\n daemonEd25519Pub,\n token,\n exp,\n }\n}\n\nfunction req(q: URLSearchParams, key: string): string {\n const v = q.get(key)\n if (v === null || v === \"\") {\n throw new PairingError(\"malformed_offer\", `offer is missing required param \"${key}\"`)\n }\n return v\n}\n","/**\n * The hosted rendezvous broker — the meeting point `pair offer` defaults to\n * when neither `--rendezvous` nor `pairing.rendezvous` is configured.\n *\n * ## Why it lives here\n *\n * It sits in `@agentproto/secrets/pairing`, beside `offer-url.ts` — the codec\n * that bakes this endpoint into every offer's `rv=` param. The daemon runtime\n * already depends on `@agentproto/secrets`, so it reaches this constant without\n * `@agentproto/runtime` gaining a dependency on `@agentproto/rendezvous`. That\n * matters: the broker package is a deliberately lean, standalone container (its\n * only dep is `ws`), and nothing on the daemon/CLI side should have to pull it\n * in just to learn the default endpoint's URL.\n *\n * ## What the broker sees\n *\n * The hosted broker only ever relays **ciphertext** — it learns the routing\n * token, the peers' IPs, ciphertext sizes, and timing; never plaintext, and it\n * cannot inject or alter frames (the pairing handshake is transcript-bound and\n * every frame is AEAD-sealed). See `docs/cli/concepts/pairing.md` for the full\n * threat model.\n *\n * ## Pointing elsewhere / self-hosting\n *\n * The broker is self-hostable (`agentproto rendezvous serve`). To route through\n * your own instead of the hosted default, set `pairing.rendezvous` in\n * `config.json` (or pass `--rendezvous` for a single offer). To disable the\n * default entirely — so a daemon never reaches the hosted broker unless an\n * endpoint is named explicitly — set `pairing.rendezvous: \"\"` (an explicit\n * opt-out; `pair offer` then requires `--rendezvous`).\n */\nexport const HOSTED_RENDEZVOUS_URL = \"wss://rdv.agentproto.sh/v1\" as const\n","/**\n * Pairing-derived key material (design: DESIGN §3/§4).\n *\n * Two derivations, both HKDF-SHA256 over secret session material so the\n * untrusted rendezvous — which sees the handshake transcript in the clear —\n * can never reproduce them:\n *\n * - **pair root** `K_pair = HKDF(session, \"pair-root\")`: the long-term shared\n * secret persisted by both sides (daemon `pairings.json`, client\n * `credentials.json`). Everything a reconnect needs derives from it.\n * - **epoch routing token** `t' = HKDF(K_pair, \"rv-route\" ‖ epoch)`: the\n * rendezvous routing token for a reconnect, rotated per UTC day so the\n * broker can't link a pairing's sessions across days. Both sides derive the\n * same token for the same epoch without any further communication.\n *\n * ## Why the pair root is derived order-independently\n *\n * A `PairingSession` exposes `sendKey`/`recvKey`, which are **role-swapped**\n * between the two peers (the client's `sendKey` is the daemon's `recvKey`). To\n * get an identical root on both sides without threading a \"which side am I\"\n * flag, we sort the two keys byte-wise before mixing them: the *set* {sendKey,\n * recvKey} is identical on both sides, so the sorted concatenation — and thus\n * the HKDF output — is identical. Both keys are secret ECDH-derived material the\n * rendezvous never sees, so the root (and every epoch token) stays secret.\n *\n * This uses the shipped P1 `PairingSession` verbatim (no change to the key\n * schedule) — it only reads the two direction keys and the transcript hash.\n */\n\nimport { hkdfSync } from \"node:crypto\"\nimport type { PairingSession } from \"./handshake.js\"\n\nconst PAIR_ROOT_INFO = \"agentproto/pair-root\"\nconst RV_ROUTE_INFO_PREFIX = \"agentproto/rv-route\"\n/** Fixed salt for the epoch-token HKDF — the pair root is the (secret) IKM,\n * the epoch rides in `info`, so the salt is a constant domain tag. */\nconst RV_ROUTE_SALT = \"agentproto/rv-route-salt\"\n\n/** Length of a derived epoch routing token, in bytes (→ 22 base64url chars,\n * same width as a 16-byte offer token). */\nconst ROUTE_TOKEN_LEN = 16\n/** Length of the pair root, in bytes. */\nconst PAIR_ROOT_LEN = 32\n\nconst MS_PER_DAY = 86_400_000\n\nfunction toBuf(u8: Uint8Array): Buffer {\n return Buffer.isBuffer(u8) ? u8 : Buffer.from(u8)\n}\n\nfunction b64url(bytes: Buffer): string {\n return bytes.toString(\"base64\").replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\")\n}\n\n/**\n * Derive the long-term pair root from a completed handshake session. Returns\n * standard base64 (persisted in `pairings.json` / `credentials.json`). Both\n * peers, despite role-swapped direction keys, produce the identical root.\n */\nexport function derivePairRoot(session: PairingSession): string {\n const k1 = toBuf(session.sendKey)\n const k2 = toBuf(session.recvKey)\n // Order-independent: sort the two keys so client and daemon mix them the same\n // way regardless of which is \"send\" for them.\n const [lo, hi] = Buffer.compare(k1, k2) <= 0 ? [k1, k2] : [k2, k1]\n const ikm = Buffer.concat([lo, hi])\n const okm = hkdfSync(\"sha256\", ikm, toBuf(session.transcriptHash), PAIR_ROOT_INFO, PAIR_ROOT_LEN)\n return Buffer.from(okm).toString(\"base64\")\n}\n\n/** The current pairing epoch — the UTC day number. Injectable `now` (ms) for\n * tests; defaults to the wall clock. */\nexport function currentEpoch(now: number = Date.now()): number {\n return Math.floor(now / MS_PER_DAY)\n}\n\n/**\n * Derive the rendezvous routing token for a pairing at a given epoch:\n * `t' = HKDF(pairRoot, \"rv-route\" ‖ epoch)`. Returned as base64url so it drops\n * straight into a `?t=` upgrade param. Deterministic — both sides derive the\n * same token for the same `(pairRoot, epoch)`.\n */\nexport function deriveEpochRoutingToken(pairRoot: string, epoch: number): string {\n const ikm = Buffer.from(pairRoot, \"base64\")\n // 8-byte big-endian epoch appended to the info prefix, so a bit-flip in the\n // epoch can never collide two epochs' tokens.\n const epochBytes = Buffer.alloc(8)\n epochBytes.writeBigUInt64BE(BigInt(epoch))\n const info = Buffer.concat([Buffer.from(RV_ROUTE_INFO_PREFIX, \"utf8\"), epochBytes])\n const okm = hkdfSync(\"sha256\", ikm, Buffer.from(RV_ROUTE_SALT, \"utf8\"), info, ROUTE_TOKEN_LEN)\n return b64url(Buffer.from(okm))\n}\n\n/**\n * The set of epoch routing tokens a peer should accept/dial to bridge clock\n * skew around a day boundary: the current epoch and the previous one (design:\n * PLAN \"accept current and previous epoch\"). The daemon parks on both so a\n * client whose clock sits on either side of midnight still finds it; the client\n * likewise tries both when reconnecting.\n */\nexport function epochRoutingTokens(\n pairRoot: string,\n now: number = Date.now(),\n): { epoch: number; token: string }[] {\n const epoch = currentEpoch(now)\n return [\n { epoch, token: deriveEpochRoutingToken(pairRoot, epoch) },\n { epoch: epoch - 1, token: deriveEpochRoutingToken(pairRoot, epoch - 1) },\n ]\n}\n","/**\n * @agentproto/secrets/pairing — the `tunnel-e2e/v1` handshake.\n *\n * The reverse tunnel (`agentproto serve --connect <host>`) is a DIFFERENT trust\n * relationship from the client↔daemon pairing in ./handshake.ts:\n *\n * - There is no offer URL, no QR, no PKI. The daemon and the host already\n * share one pre-provisioned secret — the `apt_` **tunnel token** the daemon\n * presents as its WS bearer. Both ends hold it before the socket opens.\n * - So instead of authenticating with a static-key seal + Ed25519 signature,\n * both ends authenticate by proving knowledge of that shared token, and get\n * forward secrecy from a fresh ephemeral X25519 exchange.\n *\n * ```\n * daemon → host: offer = { ePub_d, mac_d = HMAC(K_auth, \"offer/v1\" ‖ ePub_d) }\n * host → daemon: accept = { ePub_h, mac_h = HMAC(K_auth, \"accept/v1\" ‖ ePub_d ‖ ePub_h) }\n * both: K_auth = HKDF(ikm = utf8(token),\n * salt = sha256(token), info = \"…/auth/v1\") // token-only MAC key\n * K = HKDF(ikm = ECDH(e_d, e_h),\n * salt = sha256(token), info = \"…/v1\") // → K_d2h ‖ K_h2d\n * ```\n *\n * Why this shape:\n * - **Mutual authentication at handshake time.** `mac_d` binds the daemon\n * ephemeral to the token; the host verifies it and refuses (`bad_auth`) if\n * the token differs — no partial session, no plaintext, before the daemon's\n * first byte. `mac_h` binds BOTH ephemerals to the token; the daemon\n * verifies it symmetrically. A man-in-the-middle without the token cannot\n * forge either MAC for its own ephemeral, and cannot reuse a recorded one\n * (it lacks the matching private key to finish the ECDH). So the confirm is\n * a **transcript/confirm step that fails a token mismatch AT handshake time,\n * not mid-stream** — exactly what a wrong `tunnel.token` on one side needs.\n * - **Forward secrecy.** The session keys mix ONLY the ephemeral ECDH output;\n * the long-term token merely salts them. A later token compromise can't\n * decrypt a recorded past session.\n * - **Salt-binds the token.** Because `sha256(token)` salts the session-key\n * HKDF too, even if the MACs were somehow bypassed the derived AEAD keys\n * still disagree under a mismatched token → the channel fails closed.\n *\n * Like ./handshake.ts, this module is deliberately **transport-agnostic**: it\n * produces and consumes plain byte messages (`encode*`/`decode*`). The code that\n * pumps those bytes over a `FrameSink` and wraps the channel lives in\n * `@agentproto/acp/tunnel`, which never depends on this package — it receives\n * only the two derived symmetric keys. Everything crypto stays here.\n */\n\nimport {\n createHash,\n createHmac,\n diffieHellman,\n hkdfSync,\n generateKeyPairSync,\n createPublicKey,\n timingSafeEqual,\n type KeyObject,\n} from \"node:crypto\"\n\n/** Wire version. Bumped if the message shape or key schedule changes; both\n * sides refuse a version they don't recognise. */\nexport const TUNNEL_E2E_VERSION = 1 as const\n\n/** HKDF `info` for the two direction session keys — domain-separates this key\n * schedule from every other HKDF use in the codebase. */\nconst SESSION_INFO = \"agentproto/tunnel-e2e/v1\"\n/** HKDF `info` for the token-only MAC key used to authenticate the handshake. */\nconst AUTH_INFO = \"agentproto/tunnel-e2e/auth/v1\"\n/** Label prefixes bound into each MAC so an offer MAC can never be replayed as\n * an accept MAC (and vice versa). */\nconst OFFER_MAC_LABEL = \"offer/v1\"\nconst ACCEPT_MAC_LABEL = \"accept/v1\"\n\n/** Length of each AES-256 direction key (bytes); two of them → 64. */\nconst KEY_LEN = 32\n/** Length of the token-only MAC key (bytes). */\nconst AUTH_KEY_LEN = 32\n\n/** Stable, machine-readable failure codes. Every rejection maps to one of these\n * so callers (and tests) branch on a code, not a message string. */\nexport type TunnelHandshakeErrorCode =\n | \"malformed_offer\"\n | \"malformed_accept\"\n | \"invalid_key\"\n | \"bad_auth\" // an HMAC did not verify — the two ends hold different tunnel tokens (or a MITM)\n | \"unsupported_version\"\n\n/** Raised for every tunnel-handshake failure. Never carries key material or the\n * token; the `code` is the contract, the message is for humans. */\nexport class TunnelHandshakeError extends Error {\n readonly code: TunnelHandshakeErrorCode\n constructor(code: TunnelHandshakeErrorCode, message: string) {\n super(message)\n this.name = \"TunnelHandshakeError\"\n this.code = code\n }\n}\n\n/** Daemon → host. `ePub` is the daemon ephemeral X25519 public key (base64 SPKI\n * DER); `mac` authenticates it under the shared tunnel token. */\nexport interface TunnelOffer {\n v: typeof TUNNEL_E2E_VERSION\n ePub: string\n mac: string\n}\n\n/** Host → daemon. `ePub` is the host ephemeral X25519 public key (base64 SPKI\n * DER); `mac` authenticates both ephemerals under the shared tunnel token. */\nexport interface TunnelAccept {\n v: typeof TUNNEL_E2E_VERSION\n ePub: string\n mac: string\n}\n\n/**\n * A completed tunnel handshake, on either side. `sendKey`/`recvKey` are already\n * role-adjusted (the daemon's `sendKey` is the host's `recvKey`), so the\n * consumer — `wrapE2E` — never has to know which side it is.\n */\nexport interface TunnelE2ESession {\n /** AES-256-GCM key for frames THIS side sends. 32 bytes. */\n sendKey: Uint8Array\n /** AES-256-GCM key for frames THIS side receives. 32 bytes. */\n recvKey: Uint8Array\n /** `sha256(SESSION_INFO ‖ ePub_d ‖ ePub_h)` — the exact transcript both sides\n * bound to. Exposed for parity with `PairingSession`; not required to use. */\n transcriptHash: Uint8Array\n}\n\n// ─── key + mac helpers ──────────────────────────────────────────\n\n/** Salt shared by both HKDF steps: the SHA-256 of the pre-shared tunnel token.\n * Binding it here is what authenticates the two ends — a peer without the\n * token derives a different salt, so both the MAC key and the session keys\n * disagree and the channel fails closed. */\nfunction tokenSalt(token: string): Buffer {\n return createHash(\"sha256\").update(Buffer.from(token, \"utf8\")).digest()\n}\n\n/** The token-only MAC key. Derived solely from the token (no ephemeral), so\n * each side can compute it independently before/without the peer's ephemeral. */\nfunction deriveAuthKey(token: string): Buffer {\n return Buffer.from(\n hkdfSync(\"sha256\", Buffer.from(token, \"utf8\"), tokenSalt(token), AUTH_INFO, AUTH_KEY_LEN),\n )\n}\n\n/** HMAC-SHA256 over `label ‖ <raw parts>` under the token-only auth key. The\n * parts are the raw DER bytes of the ephemeral public keys, so both sides MAC\n * identical bytes regardless of any base64 re-encoding. */\nfunction computeMac(authKey: Buffer, label: string, ...parts: Buffer[]): Buffer {\n const h = createHmac(\"sha256\", authKey)\n h.update(Buffer.from(label, \"utf8\"))\n for (const p of parts) h.update(p)\n return h.digest()\n}\n\n/** Constant-time compare of a received MAC (base64) against the expected MAC.\n * Length-guards first so `timingSafeEqual` never throws on a truncated MAC. */\nfunction macMatches(expected: Buffer, receivedB64: string): boolean {\n let received: Buffer\n try {\n received = Buffer.from(receivedB64, \"base64\")\n } catch {\n return false\n }\n if (received.length !== expected.length) return false\n return timingSafeEqual(received, expected)\n}\n\nfunction x25519PublicKey(b64Der: string): KeyObject {\n try {\n return createPublicKey({ key: Buffer.from(b64Der, \"base64\"), format: \"der\", type: \"spki\" })\n } catch {\n throw new TunnelHandshakeError(\"invalid_key\", \"peer ephemeral X25519 public key is invalid\")\n }\n}\n\n/** The transcript hash: `sha256(SESSION_INFO ‖ ePub_d_raw ‖ ePub_h_raw)`. */\nfunction transcriptHash(ePubDRaw: Buffer, ePubHRaw: Buffer): Buffer {\n return createHash(\"sha256\")\n .update(Buffer.from(SESSION_INFO, \"utf8\"))\n .update(ePubDRaw)\n .update(ePubHRaw)\n .digest()\n}\n\n/** Derive the two direction keys from the ephemeral ECDH output, salted by the\n * token. The key split (d2h then h2d) is identical on both sides. */\nfunction deriveDirectionKeys(\n ecdh: Buffer,\n salt: Buffer,\n): { kd2h: Buffer; kh2d: Buffer } {\n const okm = Buffer.from(hkdfSync(\"sha256\", ecdh, salt, SESSION_INFO, KEY_LEN * 2))\n return { kd2h: okm.subarray(0, KEY_LEN), kh2d: okm.subarray(KEY_LEN, KEY_LEN * 2) }\n}\n\n/** Generate an ephemeral X25519 keypair and return the private KeyObject plus\n * its public half as base64 SPKI DER (the wire form) and raw DER bytes (the\n * MAC/transcript form). */\nfunction generateEphemeral(): { priv: KeyObject; pubB64: string; pubRaw: Buffer } {\n const kp = generateKeyPairSync(\"x25519\")\n const pubRaw = kp.publicKey.export({ type: \"spki\", format: \"der\" })\n return { priv: kp.privateKey, pubB64: pubRaw.toString(\"base64\"), pubRaw }\n}\n\n// ─── daemon (initiator) side ────────────────────────────────────\n\n/** A started daemon handshake: send `offer`, then feed the host's `accept` to\n * `complete` to derive the session. */\nexport interface StartedTunnelHandshake {\n offer: TunnelOffer\n /**\n * Verify the host accept and derive the session. Throws `TunnelHandshakeError`\n * on a bad MAC (`bad_auth` — the host holds a different token), a malformed\n * accept, or an invalid key — never returns partial state.\n */\n complete(accept: TunnelAccept): TunnelE2ESession\n}\n\n/**\n * Begin the daemon (initiator) side. Generates the daemon ephemeral keypair,\n * MACs it under the token, and returns the `offer` to send plus a `complete` to\n * run once the host replies with its `accept`.\n */\nexport function startTunnelHandshake(token: string): StartedTunnelHandshake {\n const authKey = deriveAuthKey(token)\n const salt = tokenSalt(token)\n const eph = generateEphemeral()\n\n const offer: TunnelOffer = {\n v: TUNNEL_E2E_VERSION,\n ePub: eph.pubB64,\n mac: computeMac(authKey, OFFER_MAC_LABEL, eph.pubRaw).toString(\"base64\"),\n }\n\n const complete = (accept: TunnelAccept): TunnelE2ESession => {\n if (accept.v !== TUNNEL_E2E_VERSION) {\n throw new TunnelHandshakeError(\n \"unsupported_version\",\n `unsupported accept version ${String(accept.v)}`,\n )\n }\n const hostPub = x25519PublicKey(accept.ePub)\n const hostPubRaw = Buffer.from(accept.ePub, \"base64\")\n\n const expected = computeMac(authKey, ACCEPT_MAC_LABEL, eph.pubRaw, hostPubRaw)\n if (!macMatches(expected, accept.mac)) {\n throw new TunnelHandshakeError(\n \"bad_auth\",\n \"host accept did not authenticate under the shared tunnel token\",\n )\n }\n\n const ecdh = diffieHellman({ privateKey: eph.priv, publicKey: hostPub })\n const { kd2h, kh2d } = deriveDirectionKeys(ecdh, salt)\n return {\n // Daemon SENDS daemon→host (kd2h) and RECEIVES host→daemon (kh2d).\n sendKey: kd2h,\n recvKey: kh2d,\n transcriptHash: transcriptHash(eph.pubRaw, hostPubRaw),\n }\n }\n\n return { offer, complete }\n}\n\n// ─── host (responder) side ──────────────────────────────────────\n\n/** A completed host handshake: send `accept`, keep `session`. */\nexport interface TunnelHandshakeResult {\n accept: TunnelAccept\n session: TunnelE2ESession\n}\n\n/**\n * Respond to a daemon offer (host side). Verifies the offer MAC under the shared\n * token, generates the host ephemeral, MACs both ephemerals, and derives the\n * session. Throws `TunnelHandshakeError` on a bad MAC (`bad_auth`), malformed\n * offer, or invalid key BEFORE producing any accept — so a mismatched token\n * yields no session and no reply, failing closed at handshake time.\n */\nexport function respondToTunnelHandshake(\n offer: TunnelOffer,\n token: string,\n): TunnelHandshakeResult {\n if (offer.v !== TUNNEL_E2E_VERSION) {\n throw new TunnelHandshakeError(\n \"unsupported_version\",\n `unsupported offer version ${String(offer.v)}`,\n )\n }\n const authKey = deriveAuthKey(token)\n const salt = tokenSalt(token)\n\n const daemonPub = x25519PublicKey(offer.ePub)\n const daemonPubRaw = Buffer.from(offer.ePub, \"base64\")\n\n const expected = computeMac(authKey, OFFER_MAC_LABEL, daemonPubRaw)\n if (!macMatches(expected, offer.mac)) {\n throw new TunnelHandshakeError(\n \"bad_auth\",\n \"daemon offer did not authenticate under the shared tunnel token\",\n )\n }\n\n const eph = generateEphemeral()\n const accept: TunnelAccept = {\n v: TUNNEL_E2E_VERSION,\n ePub: eph.pubB64,\n mac: computeMac(authKey, ACCEPT_MAC_LABEL, daemonPubRaw, eph.pubRaw).toString(\"base64\"),\n }\n\n const ecdh = diffieHellman({ privateKey: eph.priv, publicKey: daemonPub })\n const { kd2h, kh2d } = deriveDirectionKeys(ecdh, salt)\n return {\n accept,\n session: {\n // Host SENDS host→daemon (kh2d) and RECEIVES daemon→host (kd2h).\n sendKey: kh2d,\n recvKey: kd2h,\n transcriptHash: transcriptHash(daemonPubRaw, eph.pubRaw),\n },\n }\n}\n\n// ─── message (de)serialization ──────────────────────────────────\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null\n}\n\n/** Serialize a handshake message to bytes for transport. */\nexport function encodeTunnelMessage(message: TunnelOffer | TunnelAccept): Uint8Array {\n return Buffer.from(JSON.stringify(message), \"utf8\")\n}\n\nfunction parseJson(bytes: Uint8Array, code: TunnelHandshakeErrorCode): unknown {\n try {\n return JSON.parse(Buffer.from(bytes).toString(\"utf8\"))\n } catch {\n throw new TunnelHandshakeError(code, \"message was not valid JSON (truncated or corrupt)\")\n }\n}\n\n/** Parse + validate a daemon offer from raw bytes. Truncated or malformed input\n * throws `TunnelHandshakeError(\"malformed_offer\")` — never a partial object. */\nexport function decodeTunnelOffer(bytes: Uint8Array): TunnelOffer {\n const parsed = parseJson(bytes, \"malformed_offer\")\n if (\n !isRecord(parsed) ||\n parsed[\"v\"] !== TUNNEL_E2E_VERSION ||\n typeof parsed[\"ePub\"] !== \"string\" ||\n typeof parsed[\"mac\"] !== \"string\"\n ) {\n throw new TunnelHandshakeError(\"malformed_offer\", \"offer message is malformed\")\n }\n return { v: TUNNEL_E2E_VERSION, ePub: parsed[\"ePub\"], mac: parsed[\"mac\"] }\n}\n\n/** Parse + validate a host accept from raw bytes. Truncated or malformed input\n * throws `TunnelHandshakeError(\"malformed_accept\")`. */\nexport function decodeTunnelAccept(bytes: Uint8Array): TunnelAccept {\n const parsed = parseJson(bytes, \"malformed_accept\")\n if (\n !isRecord(parsed) ||\n parsed[\"v\"] !== TUNNEL_E2E_VERSION ||\n typeof parsed[\"ePub\"] !== \"string\" ||\n typeof parsed[\"mac\"] !== \"string\"\n ) {\n throw new TunnelHandshakeError(\"malformed_accept\", \"accept message is malformed\")\n }\n return { v: TUNNEL_E2E_VERSION, ePub: parsed[\"ePub\"], mac: parsed[\"mac\"] }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/pairing/index.ts"],"names":["startClientHandshake","respondToHandshake","respondToLegacyHandshake","parseOfferUrl","derivePairRoot","deriveEpochRoutingToken","deriveEpochAuthToken","deriveEpochTokens","deriveOfferTokens","epochRoutingTokens","startTunnelHandshake","respondToTunnelHandshake"],"mappings":";;;;;;;;;;;;;;AAwCO,SAASA,qBAAAA,CACd,MAAA,EACA,MAAA,GAAyB,kBAAA,EACkB;AAC3C,EAAA,OAAiB,oBAAA,CAAqB,QAAQ,MAAM,CAAA;AACtD;AAGO,SAASC,mBAAAA,CACd,KAAA,EACA,MAAA,EACA,MAAA,GAAyB,kBAAA,EACiB;AAC1C,EAAA,OAAiB,kBAAA,CAAmB,KAAA,EAAO,MAAA,EAAQ,MAAM,CAAA;AAC3D;AAqBO,SAASC,yBAAAA,CACd,UAAA,EACA,QAAA,EACA,MAAA,GAAyB,kBAAA,EAC8B;AACvD,EAAA,OAAiB,wBAAA,CAAyB,UAAA,EAAY,QAAA,EAAU,MAAM,CAAA;AACxE;AAGO,SAASC,eACd,GAAA,EACA,IAAA,GAAmC,EAAC,EACpC,SAAyB,kBAAA,EACO;AAChC,EAAA,OAAgB,aAAA,CAAc,GAAA,EAAK,IAAA,EAAM,MAAM,CAAA;AACjD;AASO,SAASC,eAAAA,CACd,OAAA,EACA,MAAA,GAAyB,kBAAA,EACR;AACjB,EAAA,OAAc,cAAA,CAAe,SAAS,MAAM,CAAA;AAC9C;AAGO,SAASC,wBAAAA,CACd,QAAA,EACA,KAAA,EACA,MAAA,GAAyB,kBAAA,EACR;AACjB,EAAA,OAAc,uBAAA,CAAwB,QAAA,EAAU,KAAA,EAAO,MAAM,CAAA;AAC/D;AAGO,SAASC,qBAAAA,CACd,QAAA,EACA,KAAA,EACA,MAAA,GAAyB,kBAAA,EACR;AACjB,EAAA,OAAc,oBAAA,CAAqB,QAAA,EAAU,KAAA,EAAO,MAAM,CAAA;AAC5D;AAGO,SAASC,kBAAAA,CACd,QAAA,EACA,KAAA,EACA,MAAA,GAAyB,kBAAA,EACQ;AACjC,EAAA,OAAc,iBAAA,CAAkB,QAAA,EAAU,KAAA,EAAO,MAAM,CAAA;AACzD;AAGO,SAASC,kBAAAA,CACd,WAAA,EACA,MAAA,GAAyB,kBAAA,EACQ;AACjC,EAAA,OAAc,iBAAA,CAAkB,aAAa,MAAM,CAAA;AACrD;AAGO,SAASC,oBACd,QAAA,EACA,GAAA,GAAc,KAAK,GAAA,EAAI,EACvB,SAAyB,kBAAA,EACgC;AACzD,EAAA,OAAc,kBAAA,CAAmB,QAAA,EAAU,GAAA,EAAK,MAAM,CAAA;AACxD;AAmBO,SAASC,qBAAAA,CACd,KAAA,EACA,MAAA,GAAyB,kBAAA,EACe;AACxC,EAAA,OAAc,oBAAA,CAAqB,OAAO,MAAM,CAAA;AAClD;AAGO,SAASC,yBAAAA,CACd,KAAA,EACA,KAAA,EACA,MAAA,GAAyB,kBAAA,EACc;AACvC,EAAA,OAAc,wBAAA,CAAyB,KAAA,EAAO,KAAA,EAAO,MAAM,CAAA;AAC7D","file":"index.mjs","sourcesContent":["/**\n * @agentproto/secrets/pairing — the `pair/v2` E2E session handshake (Node entry).\n *\n * See ./handshake.ts for the protocol. This barrel re-exports the public\n * surface: the two entry points (`startClientHandshake`, `respondToHandshake`),\n * their message (de)serializers, and the typed error.\n *\n * The protocol code is shared with the browser-safe entry\n * (`@agentproto/secrets/pairing/browser`, ./browser.ts); the only difference\n * here is the default `CryptoProvider`: `node:crypto`. Every crypto function is\n * async and takes an optional trailing `crypto` to override it — e.g. pass\n * `webCryptoProvider` to run the exact browser code path under Node.\n */\n\nimport { nodeCryptoProvider } from \"../crypto/node.js\"\nimport type { CryptoProvider } from \"../crypto/types.js\"\nimport * as derive from \"./derive.js\"\nimport * as handshake from \"./handshake.js\"\nimport * as offerUrl from \"./offer-url.js\"\nimport * as tunnel from \"./tunnel-handshake.js\"\n\nexport {\n PAIR_VERSION,\n LEGACY_PAIR_VERSION,\n PAIRING_PROTOCOL_OUTDATED_MESSAGE,\n PairingError,\n encodePairingMessage,\n decodePairingHello,\n decodePairingReply,\n type PairingErrorCode,\n type PairingHello,\n type PairingReply,\n type PairingSession,\n type ClientHandshakeParams,\n type StartedClientHandshake,\n type DaemonHandshakeParams,\n type DaemonHandshakeResult,\n} from \"./handshake.js\"\n\n/** Begin a client handshake (see ./handshake.ts). */\nexport function startClientHandshake(\n params: handshake.ClientHandshakeParams,\n crypto: CryptoProvider = nodeCryptoProvider,\n): Promise<handshake.StartedClientHandshake> {\n return handshake.startClientHandshake(params, crypto)\n}\n\n/** Respond to a client hello (see ./handshake.ts). */\nexport function respondToHandshake(\n hello: handshake.PairingHello,\n params: handshake.DaemonHandshakeParams,\n crypto: CryptoProvider = nodeCryptoProvider,\n): Promise<handshake.DaemonHandshakeResult> {\n return handshake.respondToHandshake(hello, params, crypto)\n}\n\n// P2 — offer-URL codec (shared by daemon `pair offer` + client `pair accept`).\nexport {\n OFFER_URL_SCHEME,\n OFFER_URL_HOST,\n OFFER_VERSION,\n PAIR_WEB_URL,\n PAIR_WEB_URL_TEMPLATE_CLOUD,\n DEFAULT_PAIR_PAGE,\n PAIR_PAGE_FP_PLACEHOLDER,\n encodeOfferUrl,\n encodeOfferWebUrl,\n resolvePairPageUrl,\n expectedPairHost,\n type PairingOffer,\n type ParseOfferOptions,\n} from \"./offer-url.js\"\n\n/** Answer a retired pair/v1 hello with a notice-only channel (see\n * ./handshake.ts). Never serve over the result. */\nexport function respondToLegacyHandshake(\n helloBytes: Uint8Array,\n identity: Parameters<typeof handshake.respondToLegacyHandshake>[1],\n crypto: CryptoProvider = nodeCryptoProvider,\n): ReturnType<typeof handshake.respondToLegacyHandshake> {\n return handshake.respondToLegacyHandshake(helloBytes, identity, crypto)\n}\n\n/** Parse + strictly validate an offer URL (see ./offer-url.ts). */\nexport function parseOfferUrl(\n url: string,\n opts: offerUrl.ParseOfferOptions = {},\n crypto: CryptoProvider = nodeCryptoProvider,\n): Promise<offerUrl.PairingOffer> {\n return offerUrl.parseOfferUrl(url, opts, crypto)\n}\n\n// The hosted rendezvous broker `pair offer` defaults to (no flag, no config).\nexport { HOSTED_RENDEZVOUS_URL } from \"./rendezvous.js\"\n\n// P2 — pairing-derived key material (pair root + route/auth tokens).\nexport { currentEpoch, importPairRootKey, type RouteAuthTokens } from \"./derive.js\"\n\n/** Derive the long-term pair root from a completed session (see ./derive.ts). */\nexport function derivePairRoot(\n session: Pick<handshake.PairingSession, \"sendKey\" | \"recvKey\" | \"transcriptHash\">,\n crypto: CryptoProvider = nodeCryptoProvider,\n): Promise<string> {\n return derive.derivePairRoot(session, crypto)\n}\n\n/** Derive the broker ROUTE token for an epoch (see ./derive.ts). */\nexport function deriveEpochRoutingToken(\n pairRoot: string | CryptoKey,\n epoch: number,\n crypto: CryptoProvider = nodeCryptoProvider,\n): Promise<string> {\n return derive.deriveEpochRoutingToken(pairRoot, epoch, crypto)\n}\n\n/** Derive the sealed-hello AUTH token for an epoch (see ./derive.ts). */\nexport function deriveEpochAuthToken(\n pairRoot: string | CryptoKey,\n epoch: number,\n crypto: CryptoProvider = nodeCryptoProvider,\n): Promise<string> {\n return derive.deriveEpochAuthToken(pairRoot, epoch, crypto)\n}\n\n/** Route + auth tokens for an epoch (see ./derive.ts). */\nexport function deriveEpochTokens(\n pairRoot: string | CryptoKey,\n epoch: number,\n crypto: CryptoProvider = nodeCryptoProvider,\n): Promise<derive.RouteAuthTokens> {\n return derive.deriveEpochTokens(pairRoot, epoch, crypto)\n}\n\n/** Route + auth tokens for an offer secret (see ./derive.ts). */\nexport function deriveOfferTokens(\n offerSecret: string,\n crypto: CryptoProvider = nodeCryptoProvider,\n): Promise<derive.RouteAuthTokens> {\n return derive.deriveOfferTokens(offerSecret, crypto)\n}\n\n/** Current + previous epoch route/auth tokens (see ./derive.ts). */\nexport function epochRoutingTokens(\n pairRoot: string | CryptoKey,\n now: number = Date.now(),\n crypto: CryptoProvider = nodeCryptoProvider,\n): Promise<({ epoch: number } & derive.RouteAuthTokens)[]> {\n return derive.epochRoutingTokens(pairRoot, now, crypto)\n}\n\n// tunnel-e2e — token-authenticated handshake for `serve --connect` (no PKI;\n// both ends share the pre-provisioned tunnel token).\nexport {\n TUNNEL_E2E_VERSION,\n TunnelHandshakeError,\n encodeTunnelMessage,\n decodeTunnelOffer,\n decodeTunnelAccept,\n type TunnelHandshakeErrorCode,\n type TunnelOffer,\n type TunnelAccept,\n type TunnelE2ESession,\n type StartedTunnelHandshake,\n type TunnelHandshakeResult,\n} from \"./tunnel-handshake.js\"\n\n/** Begin the daemon side of `tunnel-e2e/v1` (see ./tunnel-handshake.ts). */\nexport function startTunnelHandshake(\n token: string,\n crypto: CryptoProvider = nodeCryptoProvider,\n): Promise<tunnel.StartedTunnelHandshake> {\n return tunnel.startTunnelHandshake(token, crypto)\n}\n\n/** Respond to a daemon `tunnel-e2e/v1` offer (see ./tunnel-handshake.ts). */\nexport function respondToTunnelHandshake(\n offer: tunnel.TunnelOffer,\n token: string,\n crypto: CryptoProvider = nodeCryptoProvider,\n): Promise<tunnel.TunnelHandshakeResult> {\n return tunnel.respondToTunnelHandshake(offer, token, crypto)\n}\n\n// The crypto seam — both providers, so a caller can force either one.\nexport { nodeCryptoProvider } from \"../crypto/node.js\"\nexport { webCryptoProvider } from \"../crypto/webcrypto.js\"\nexport type { CryptoProvider, KeyPairDer } from \"../crypto/types.js\"\n"]}
|
package/dist/provision/index.mjs
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export { extractJsonPath, httpTarget, provisionSealed, resolveCredential } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
1
|
+
export { extractJsonPath, httpTarget, provisionSealed, resolveCredential } from '../chunk-7V6U6DII.mjs';
|
|
2
|
+
import '../chunk-PCFPJ47T.mjs';
|
|
3
|
+
import '../chunk-5HUHYBPR.mjs';
|
|
4
|
+
import '../chunk-432SC3XS.mjs';
|
|
5
|
+
import '../chunk-3YRYO6LK.mjs';
|
|
3
6
|
//# sourceMappingURL=index.mjs.map
|
|
4
7
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export { BUILTIN_RECIPES, claudeCodeOauthRecipe, codexRecipe, credentialSourceSpecSchema, defineProvisionRecipe, geminiRecipe, getRecipe, listRecipeIds, listRecipes, parseRecipeManifest, parseRecipeManifestRaw, provisionRecipeFrontmatterSchema, recipeMethodSchema, registerRecipe, resolveRecipeMethod, resolveSourceSpec } from '../../chunk-
|
|
2
|
-
import '../../chunk-
|
|
3
|
-
import '../../chunk-
|
|
1
|
+
export { BUILTIN_RECIPES, claudeCodeOauthRecipe, codexRecipe, credentialSourceSpecSchema, defineProvisionRecipe, geminiRecipe, getRecipe, listRecipeIds, listRecipes, parseRecipeManifest, parseRecipeManifestRaw, provisionRecipeFrontmatterSchema, recipeMethodSchema, registerRecipe, resolveRecipeMethod, resolveSourceSpec } from '../../chunk-2FHFW2AU.mjs';
|
|
2
|
+
import '../../chunk-7V6U6DII.mjs';
|
|
3
|
+
import '../../chunk-PCFPJ47T.mjs';
|
|
4
|
+
import '../../chunk-5HUHYBPR.mjs';
|
|
5
|
+
import '../../chunk-432SC3XS.mjs';
|
|
6
|
+
import '../../chunk-3YRYO6LK.mjs';
|
|
4
7
|
//# sourceMappingURL=index.mjs.map
|
|
5
8
|
//# sourceMappingURL=index.mjs.map
|
package/dist/seal/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { C as CryptoProvider } from '../types-B2ZfEhsU.js';
|
|
2
|
+
import { c as SealKeyPair } from '../core-CIn0-z-Y.js';
|
|
3
|
+
export { S as SEAL_ALG, a as SEAL_VERSION, b as SealError } from '../core-CIn0-z-Y.js';
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* @agentproto/secrets/seal — anonymous public-key sealing for secret values.
|
|
3
7
|
*
|
|
@@ -12,66 +16,52 @@
|
|
|
12
16
|
* anywhere but the origin machine and, transiently, inside the recipient's
|
|
13
17
|
* unseal boundary.
|
|
14
18
|
*
|
|
15
|
-
* Construction (ECIES,
|
|
19
|
+
* Construction (ECIES, no native dep):
|
|
16
20
|
* - X25519 ephemeral key agreement with the recipient's public key
|
|
17
21
|
* - HKDF-SHA256 over the shared secret, salted by both public keys
|
|
18
22
|
* - AES-256-GCM AEAD for the payload (authenticated; tampering fails closed)
|
|
19
23
|
*
|
|
20
|
-
* This is the same shape as libsodium's `crypto_box_seal
|
|
21
|
-
*
|
|
24
|
+
* This is the same shape as libsodium's `crypto_box_seal`. The construction
|
|
25
|
+
* lives once in ./core.ts against a `CryptoProvider`; this Node entry defaults
|
|
26
|
+
* to `node:crypto`, and `@agentproto/secrets/pairing/browser` re-exports the
|
|
27
|
+
* same code over WebCrypto. Every function is async (WebCrypto is) and takes an
|
|
28
|
+
* optional trailing `crypto` to override the provider.
|
|
22
29
|
*
|
|
23
30
|
* Sealing (confidentiality) is NOT signing (authenticity): this hides the
|
|
24
31
|
* value, it does not prove who sent it. Bind the sender separately (e.g. an
|
|
25
32
|
* authenticated transport) when provenance matters.
|
|
26
33
|
*/
|
|
27
|
-
|
|
28
|
-
* evolve without ambiguity at the unseal site. */
|
|
29
|
-
declare const SEAL_ALG: "x25519-hkdf-sha256-aes256gcm";
|
|
30
|
-
declare const SEAL_VERSION: 1;
|
|
31
|
-
/** Raised for every seal/unseal failure. The message is safe to surface —
|
|
32
|
-
* it never contains key material or plaintext. */
|
|
33
|
-
declare class SealError extends Error {
|
|
34
|
-
constructor(message: string);
|
|
35
|
-
}
|
|
36
|
-
/** An X25519 keypair for sealing. Both halves are base64-encoded DER so they
|
|
37
|
-
* travel as plain strings (env vars, JSON, config). The public half is safe
|
|
38
|
-
* to publish; the private half must stay with the unseal boundary. */
|
|
39
|
-
interface SealKeyPair {
|
|
40
|
-
/** base64 DER (SPKI) X25519 public key — publishable. */
|
|
41
|
-
publicKey: string;
|
|
42
|
-
/** base64 DER (PKCS8) X25519 private key — secret. */
|
|
43
|
-
privateKey: string;
|
|
44
|
-
}
|
|
34
|
+
|
|
45
35
|
/**
|
|
46
36
|
* Mint a fresh sealing keypair. The recipient (e.g. a server) generates this
|
|
47
37
|
* once, stores `privateKey` in its own secret store, and publishes
|
|
48
38
|
* `publicKey` for senders to seal against.
|
|
49
39
|
*/
|
|
50
|
-
declare function generateSealKeyPair(): SealKeyPair
|
|
40
|
+
declare function generateSealKeyPair(crypto?: CryptoProvider): Promise<SealKeyPair>;
|
|
51
41
|
/**
|
|
52
42
|
* Derive the publishable public key from a stored private key. Lets a
|
|
53
43
|
* recipient hold only the private half (one secret) and serve the public
|
|
54
44
|
* half on demand — the seal-key a sender needs.
|
|
55
45
|
*/
|
|
56
|
-
declare function sealingPublicKey(privateKey: string): string
|
|
46
|
+
declare function sealingPublicKey(privateKey: string, crypto?: CryptoProvider): Promise<string>;
|
|
57
47
|
/**
|
|
58
48
|
* Stable short identifier for a sealing key, derived from the public key.
|
|
59
49
|
* Lets the seal-key endpoint and the sealed envelope name which key was used
|
|
60
50
|
* so rotation is unambiguous. Not a secret.
|
|
61
51
|
*/
|
|
62
|
-
declare function sealKeyId(publicKey: string): string
|
|
52
|
+
declare function sealKeyId(publicKey: string, crypto?: CryptoProvider): Promise<string>;
|
|
63
53
|
/**
|
|
64
|
-
* Seal a plaintext value to a recipient's public key.
|
|
54
|
+
* Seal a plaintext value to a recipient's public key. Resolves to a single
|
|
65
55
|
* base64 string (the envelope) that only the matching private key can open.
|
|
66
56
|
* The sender needs nothing but the public key.
|
|
67
57
|
*/
|
|
68
|
-
declare function seal(plaintext: string | Uint8Array, recipientPublicKey: string): string
|
|
58
|
+
declare function seal(plaintext: string | Uint8Array, recipientPublicKey: string, crypto?: CryptoProvider): Promise<string>;
|
|
69
59
|
/**
|
|
70
|
-
* Open a sealed envelope with the recipient's private key,
|
|
71
|
-
* original UTF-8 plaintext.
|
|
72
|
-
* unsupported version/alg, the wrong key, or any tampering (the AEAD tag
|
|
60
|
+
* Open a sealed envelope with the recipient's private key, resolving to the
|
|
61
|
+
* original UTF-8 plaintext. Rejects with `SealError` on a malformed envelope,
|
|
62
|
+
* an unsupported version/alg, the wrong key, or any tampering (the AEAD tag
|
|
73
63
|
* fails closed — a modified ciphertext never decrypts to garbage, it throws).
|
|
74
64
|
*/
|
|
75
|
-
declare function unseal(sealed: string, privateKey: string): string
|
|
65
|
+
declare function unseal(sealed: string, privateKey: string, crypto?: CryptoProvider): Promise<string>;
|
|
76
66
|
|
|
77
|
-
export {
|
|
67
|
+
export { CryptoProvider, SealKeyPair, generateSealKeyPair, seal, sealKeyId, sealingPublicKey, unseal };
|
package/dist/seal/index.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { generateSealKeyPair, seal, sealKeyId, sealingPublicKey, unseal } from '../chunk-PCFPJ47T.mjs';
|
|
2
|
+
import '../chunk-5HUHYBPR.mjs';
|
|
3
|
+
export { SEAL_ALG, SEAL_VERSION, SealError } from '../chunk-432SC3XS.mjs';
|
|
4
|
+
import '../chunk-3YRYO6LK.mjs';
|
|
2
5
|
//# sourceMappingURL=index.mjs.map
|
|
3
6
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The crypto seam behind the pairing handshake, the seal box and the daemon
|
|
3
|
+
* identity: the handful of primitives they need, and nothing else.
|
|
4
|
+
*
|
|
5
|
+
* Two implementations:
|
|
6
|
+
* - `nodeCryptoProvider` (`./node.ts`) — `node:crypto`; the default in every
|
|
7
|
+
* Node entry point (`@agentproto/secrets/pairing`, `/seal`, `/identity`).
|
|
8
|
+
* - `webCryptoProvider` (`./webcrypto.ts`) — `globalThis.crypto.subtle`; the
|
|
9
|
+
* default in the browser-safe entry (`@agentproto/secrets/pairing/browser`).
|
|
10
|
+
* Works in current Chrome / Safari / Firefox and in Node ≥ 20.
|
|
11
|
+
*
|
|
12
|
+
* The protocol code on top is written once against this interface, so both
|
|
13
|
+
* runtimes run the same key schedule. The methods are Promise-based because
|
|
14
|
+
* WebCrypto is; `randomBytes` stays synchronous (`getRandomValues` is).
|
|
15
|
+
*
|
|
16
|
+
* Key encoding: keys cross this seam as DER bytes — SPKI for public keys,
|
|
17
|
+
* PKCS#8 for private keys — which is exactly what travels on the wire and in
|
|
18
|
+
* `identity.json` (base64 of the same bytes). Both implementations import and
|
|
19
|
+
* export the same DER, so keys minted by one are usable by the other.
|
|
20
|
+
*/
|
|
21
|
+
interface CryptoProvider {
|
|
22
|
+
/** Implementation label, for diagnostics and tests ("node" | "webcrypto"). */
|
|
23
|
+
readonly name: string;
|
|
24
|
+
/** `n` cryptographically-random bytes. */
|
|
25
|
+
randomBytes(n: number): Uint8Array;
|
|
26
|
+
sha256(data: Uint8Array): Promise<Uint8Array>;
|
|
27
|
+
hmacSha256(key: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
|
|
28
|
+
/** RFC 5869 HKDF with SHA-256; `length` output bytes. */
|
|
29
|
+
hkdfSha256(ikm: Uint8Array, salt: Uint8Array, info: Uint8Array, length: number): Promise<Uint8Array>;
|
|
30
|
+
/** Fresh X25519 keypair: `publicKey` SPKI DER, `privateKey` PKCS#8 DER. */
|
|
31
|
+
x25519GenerateKeyPair(): Promise<KeyPairDer>;
|
|
32
|
+
/** Throws if `spki` is not an importable X25519 public key. */
|
|
33
|
+
x25519ValidatePublicKey(spki: Uint8Array): Promise<void>;
|
|
34
|
+
/** The SPKI DER public half of a PKCS#8 X25519 private key. */
|
|
35
|
+
x25519PublicKeyFromPrivate(pkcs8: Uint8Array): Promise<Uint8Array>;
|
|
36
|
+
/** X25519 ECDH → 32-byte shared secret. */
|
|
37
|
+
x25519(privatePkcs8: Uint8Array, publicSpki: Uint8Array): Promise<Uint8Array>;
|
|
38
|
+
/** Fresh Ed25519 keypair: `publicKey` SPKI DER, `privateKey` PKCS#8 DER. */
|
|
39
|
+
ed25519GenerateKeyPair(): Promise<KeyPairDer>;
|
|
40
|
+
/** Throws if `spki` is not an importable Ed25519 public key. */
|
|
41
|
+
ed25519ValidatePublicKey(spki: Uint8Array): Promise<void>;
|
|
42
|
+
ed25519Sign(privatePkcs8: Uint8Array, message: Uint8Array): Promise<Uint8Array>;
|
|
43
|
+
/** Never throws on a bad signature — resolves false. */
|
|
44
|
+
ed25519Verify(publicSpki: Uint8Array, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
|
|
45
|
+
/** AES-256-GCM, 16-byte tag. Returns `ciphertext ‖ tag`. */
|
|
46
|
+
aesGcmEncrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Promise<Uint8Array>;
|
|
47
|
+
/** Opens `ciphertext ‖ tag`. Rejects on any authentication failure. */
|
|
48
|
+
aesGcmDecrypt(key: Uint8Array, iv: Uint8Array, sealed: Uint8Array, aad?: Uint8Array): Promise<Uint8Array>;
|
|
49
|
+
}
|
|
50
|
+
interface KeyPairDer {
|
|
51
|
+
/** SPKI DER. */
|
|
52
|
+
publicKey: Uint8Array;
|
|
53
|
+
/** PKCS#8 DER. */
|
|
54
|
+
privateKey: Uint8Array;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type { CryptoProvider as C, KeyPairDer as K };
|