@labacacia/nps-sdk 1.0.0-alpha.6 → 1.0.0-alpha.8
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/CHANGELOG.cn.md +126 -0
- package/CHANGELOG.md +135 -0
- package/README.cn.md +9 -2
- package/README.md +9 -2
- package/dist/core/anchor-cache.d.ts +42 -0
- package/dist/core/anchor-cache.d.ts.map +1 -0
- package/dist/core/anchor-cache.js +104 -0
- package/dist/core/anchor-cache.js.map +1 -0
- package/dist/core/cache.d.ts +14 -0
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +80 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/canonical-json.d.ts +12 -0
- package/dist/core/canonical-json.d.ts.map +1 -0
- package/dist/core/canonical-json.js +44 -0
- package/dist/core/canonical-json.js.map +1 -0
- package/dist/core/codec.d.ts +32 -0
- package/dist/core/codec.d.ts.map +1 -0
- package/dist/core/codec.js +119 -0
- package/dist/core/codec.js.map +1 -0
- package/dist/core/codecs/index.d.ts +4 -0
- package/dist/core/codecs/index.d.ts.map +1 -0
- package/{src/core/codecs/index.ts → dist/core/codecs/index.js} +1 -0
- package/dist/core/codecs/index.js.map +1 -0
- package/dist/core/codecs/ncp-codec.d.ts +39 -0
- package/dist/core/codecs/ncp-codec.d.ts.map +1 -0
- package/dist/core/codecs/ncp-codec.js +93 -0
- package/dist/core/codecs/ncp-codec.js.map +1 -0
- package/dist/core/codecs/tier1-json-codec.d.ts +10 -0
- package/dist/core/codecs/tier1-json-codec.d.ts.map +1 -0
- package/{src/core/codecs/tier1-json-codec.ts → dist/core/codecs/tier1-json-codec.js} +11 -16
- package/dist/core/codecs/tier1-json-codec.js.map +1 -0
- package/dist/core/codecs/tier2-msgpack-codec.d.ts +10 -0
- package/dist/core/codecs/tier2-msgpack-codec.d.ts.map +1 -0
- package/{src/core/codecs/tier2-msgpack-codec.ts → dist/core/codecs/tier2-msgpack-codec.js} +10 -14
- package/dist/core/codecs/tier2-msgpack-codec.js.map +1 -0
- package/dist/core/crypto-provider.d.ts +31 -0
- package/dist/core/crypto-provider.d.ts.map +1 -0
- package/dist/core/crypto-provider.js +10 -0
- package/dist/core/crypto-provider.js.map +1 -0
- package/dist/core/exceptions.d.ts +27 -0
- package/dist/core/exceptions.d.ts.map +1 -0
- package/dist/core/exceptions.js +52 -0
- package/dist/core/exceptions.js.map +1 -0
- package/dist/core/frame-header.d.ts +87 -0
- package/dist/core/frame-header.d.ts.map +1 -0
- package/dist/core/frame-header.js +185 -0
- package/dist/core/frame-header.js.map +1 -0
- package/dist/core/frame-registry.d.ts +35 -0
- package/dist/core/frame-registry.d.ts.map +1 -0
- package/dist/core/frame-registry.js +63 -0
- package/dist/core/frame-registry.js.map +1 -0
- package/dist/core/frames.d.ts +82 -0
- package/dist/core/frames.d.ts.map +1 -0
- package/dist/core/frames.js +155 -0
- package/dist/core/frames.js.map +1 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/index.d.ts.map +1 -0
- package/{src/core/index.ts → dist/core/index.js} +3 -23
- package/dist/core/index.js.map +1 -0
- package/dist/core/registry.d.ts +11 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +17 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/status-codes.d.ts +29 -0
- package/dist/core/status-codes.d.ts.map +1 -0
- package/dist/core/status-codes.js +39 -0
- package/dist/core/status-codes.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +1 -1
- package/dist/index.js.map +1 -0
- package/dist/ncp/frames/anchor-frame.d.ts +29 -0
- package/dist/ncp/frames/anchor-frame.d.ts.map +1 -0
- package/dist/ncp/frames/anchor-frame.js +54 -0
- package/dist/ncp/frames/anchor-frame.js.map +1 -0
- package/dist/ncp/frames/caps-frame.d.ts +29 -0
- package/dist/ncp/frames/caps-frame.d.ts.map +1 -0
- package/dist/ncp/frames/caps-frame.js +29 -0
- package/dist/ncp/frames/caps-frame.js.map +1 -0
- package/dist/ncp/frames/diff-frame.d.ts +32 -0
- package/dist/ncp/frames/diff-frame.d.ts.map +1 -0
- package/dist/ncp/frames/diff-frame.js +37 -0
- package/dist/ncp/frames/diff-frame.js.map +1 -0
- package/dist/ncp/frames/error-frame.d.ts +16 -0
- package/dist/ncp/frames/error-frame.d.ts.map +1 -0
- package/dist/ncp/frames/error-frame.js +13 -0
- package/dist/ncp/frames/error-frame.js.map +1 -0
- package/dist/ncp/frames/hello-frame.d.ts +21 -0
- package/dist/ncp/frames/hello-frame.d.ts.map +1 -0
- package/dist/ncp/frames/hello-frame.js +25 -0
- package/dist/ncp/frames/hello-frame.js.map +1 -0
- package/dist/ncp/frames/stream-frame.d.ts +16 -0
- package/dist/ncp/frames/stream-frame.d.ts.map +1 -0
- package/dist/ncp/frames/stream-frame.js +18 -0
- package/dist/ncp/frames/stream-frame.js.map +1 -0
- package/dist/ncp/frames.d.ts +94 -0
- package/dist/ncp/frames.d.ts.map +1 -0
- package/dist/ncp/frames.js +192 -0
- package/dist/ncp/frames.js.map +1 -0
- package/dist/ncp/handshake.d.ts +30 -0
- package/dist/ncp/handshake.d.ts.map +1 -0
- package/dist/ncp/handshake.js +80 -0
- package/dist/ncp/handshake.js.map +1 -0
- package/dist/ncp/index.d.ts +12 -0
- package/dist/ncp/index.d.ts.map +1 -0
- package/{src/ncp/index.ts → dist/ncp/index.js} +1 -0
- package/dist/ncp/index.js.map +1 -0
- package/dist/ncp/ncp-error-codes.d.ts +23 -0
- package/dist/ncp/ncp-error-codes.d.ts.map +1 -0
- package/dist/ncp/ncp-error-codes.js +34 -0
- package/dist/ncp/ncp-error-codes.js.map +1 -0
- package/dist/ncp/ncp-patch-format.d.ts +7 -0
- package/dist/ncp/ncp-patch-format.d.ts.map +1 -0
- package/dist/ncp/ncp-patch-format.js +13 -0
- package/dist/ncp/ncp-patch-format.js.map +1 -0
- package/dist/ncp/preamble.d.ts +47 -0
- package/dist/ncp/preamble.d.ts.map +1 -0
- package/dist/ncp/preamble.js +74 -0
- package/dist/ncp/preamble.js.map +1 -0
- package/dist/ncp/registry.d.ts +3 -0
- package/dist/ncp/registry.d.ts.map +1 -0
- package/dist/ncp/registry.js +13 -0
- package/dist/ncp/registry.js.map +1 -0
- package/dist/ncp/stream-manager.d.ts +57 -0
- package/dist/ncp/stream-manager.d.ts.map +1 -0
- package/dist/ncp/stream-manager.js +163 -0
- package/dist/ncp/stream-manager.js.map +1 -0
- package/dist/ndp/dns-txt.d.ts +35 -0
- package/dist/ndp/dns-txt.d.ts.map +1 -0
- package/dist/ndp/dns-txt.js +67 -0
- package/dist/ndp/dns-txt.js.map +1 -0
- package/dist/ndp/frames.d.ts +56 -0
- package/dist/ndp/frames.d.ts.map +1 -0
- package/dist/ndp/frames.js +87 -0
- package/dist/ndp/frames.js.map +1 -0
- package/dist/ndp/index.d.ts +6 -0
- package/dist/ndp/index.d.ts.map +1 -0
- package/{src/ndp/index.ts → dist/ndp/index.js} +1 -1
- package/dist/ndp/index.js.map +1 -0
- package/dist/ndp/ndp-registry.d.ts +13 -0
- package/dist/ndp/ndp-registry.d.ts.map +1 -0
- package/dist/ndp/ndp-registry.js +104 -0
- package/dist/ndp/ndp-registry.js.map +1 -0
- package/dist/ndp/registry.d.ts +3 -0
- package/dist/ndp/registry.d.ts.map +1 -0
- package/dist/ndp/registry.js +10 -0
- package/dist/ndp/registry.js.map +1 -0
- package/dist/ndp/validator.d.ts +18 -0
- package/dist/ndp/validator.d.ts.map +1 -0
- package/dist/ndp/validator.js +48 -0
- package/dist/ndp/validator.js.map +1 -0
- package/dist/nip/acme/client.d.ts +31 -0
- package/dist/nip/acme/client.d.ts.map +1 -0
- package/dist/nip/acme/client.js +136 -0
- package/dist/nip/acme/client.js.map +1 -0
- package/dist/nip/acme/index.d.ts +6 -0
- package/dist/nip/acme/index.d.ts.map +1 -0
- package/{src/nip/acme/index.ts → dist/nip/acme/index.js} +1 -1
- package/dist/nip/acme/index.js.map +1 -0
- package/dist/nip/acme/jws.d.ts +31 -0
- package/dist/nip/acme/jws.d.ts.map +1 -0
- package/dist/nip/acme/jws.js +76 -0
- package/dist/nip/acme/jws.js.map +1 -0
- package/dist/nip/acme/messages.d.ts +71 -0
- package/dist/nip/acme/messages.d.ts.map +1 -0
- package/dist/nip/acme/messages.js +4 -0
- package/dist/nip/acme/messages.js.map +1 -0
- package/dist/nip/acme/server.d.ts +41 -0
- package/dist/nip/acme/server.d.ts.map +1 -0
- package/dist/nip/acme/server.js +458 -0
- package/dist/nip/acme/server.js.map +1 -0
- package/dist/nip/acme/wire.d.ts +19 -0
- package/dist/nip/acme/wire.d.ts.map +1 -0
- package/dist/nip/acme/wire.js +21 -0
- package/dist/nip/acme/wire.js.map +1 -0
- package/dist/nip/assurance-level.d.ts +19 -0
- package/dist/nip/assurance-level.d.ts.map +1 -0
- package/dist/nip/assurance-level.js +38 -0
- package/dist/nip/assurance-level.js.map +1 -0
- package/dist/nip/cert-format.d.ts +5 -0
- package/dist/nip/cert-format.d.ts.map +1 -0
- package/dist/nip/cert-format.js +6 -0
- package/dist/nip/cert-format.js.map +1 -0
- package/dist/nip/error-codes.d.ts +25 -0
- package/dist/nip/error-codes.d.ts.map +1 -0
- package/{src/nip/error-codes.ts → dist/nip/error-codes.js} +19 -25
- package/dist/nip/error-codes.js.map +1 -0
- package/dist/nip/frames.d.ts +53 -0
- package/dist/nip/frames.d.ts.map +1 -0
- package/dist/nip/frames.js +106 -0
- package/dist/nip/frames.js.map +1 -0
- package/dist/nip/identity.d.ts +18 -0
- package/dist/nip/identity.d.ts.map +1 -0
- package/dist/nip/identity.js +94 -0
- package/dist/nip/identity.js.map +1 -0
- package/dist/nip/index.d.ts +11 -0
- package/dist/nip/index.d.ts.map +1 -0
- package/{src/nip/index.ts → dist/nip/index.js} +3 -2
- package/dist/nip/index.js.map +1 -0
- package/dist/nip/registry.d.ts +3 -0
- package/dist/nip/registry.d.ts.map +1 -0
- package/dist/nip/registry.js +10 -0
- package/dist/nip/registry.js.map +1 -0
- package/dist/nip/reputation-client.d.ts +116 -0
- package/dist/nip/reputation-client.d.ts.map +1 -0
- package/dist/nip/reputation-client.js +261 -0
- package/dist/nip/reputation-client.js.map +1 -0
- package/dist/nip/verifier.d.ts +23 -0
- package/dist/nip/verifier.d.ts.map +1 -0
- package/dist/nip/verifier.js +90 -0
- package/dist/nip/verifier.js.map +1 -0
- package/dist/nip/x509/builder.d.ts +35 -0
- package/dist/nip/x509/builder.d.ts.map +1 -0
- package/dist/nip/x509/builder.js +59 -0
- package/dist/nip/x509/builder.js.map +1 -0
- package/dist/nip/x509/index.d.ts +4 -0
- package/dist/nip/x509/index.d.ts.map +1 -0
- package/{src/nip/x509/index.ts → dist/nip/x509/index.js} +1 -1
- package/dist/nip/x509/index.js.map +1 -0
- package/dist/nip/x509/oids.d.ts +16 -0
- package/dist/nip/x509/oids.d.ts.map +1 -0
- package/{src/nip/x509/oids.ts → dist/nip/x509/oids.js} +5 -10
- package/dist/nip/x509/oids.js.map +1 -0
- package/dist/nip/x509/verifier.d.ts +26 -0
- package/dist/nip/x509/verifier.d.ts.map +1 -0
- package/dist/nip/x509/verifier.js +171 -0
- package/dist/nip/x509/verifier.js.map +1 -0
- package/dist/nop/client.d.ts +34 -0
- package/dist/nop/client.d.ts.map +1 -0
- package/dist/nop/client.js +90 -0
- package/dist/nop/client.js.map +1 -0
- package/dist/nop/frames.d.ts +65 -0
- package/dist/nop/frames.d.ts.map +1 -0
- package/dist/nop/frames.js +148 -0
- package/dist/nop/frames.js.map +1 -0
- package/dist/nop/index.d.ts +5 -0
- package/dist/nop/index.d.ts.map +1 -0
- package/{src/nop/index.ts → dist/nop/index.js} +1 -1
- package/dist/nop/index.js.map +1 -0
- package/dist/nop/models.d.ts +58 -0
- package/dist/nop/models.d.ts.map +1 -0
- package/dist/nop/models.js +50 -0
- package/dist/nop/models.js.map +1 -0
- package/dist/nop/nop-types.d.ts +136 -0
- package/dist/nop/nop-types.d.ts.map +1 -0
- package/dist/nop/nop-types.js +44 -0
- package/dist/nop/nop-types.js.map +1 -0
- package/dist/nop/registry.d.ts +3 -0
- package/dist/nop/registry.d.ts.map +1 -0
- package/dist/nop/registry.js +11 -0
- package/dist/nop/registry.js.map +1 -0
- package/dist/nwp/anchor-client.d.ts +109 -0
- package/dist/nwp/anchor-client.d.ts.map +1 -0
- package/dist/nwp/anchor-client.js +279 -0
- package/dist/nwp/anchor-client.js.map +1 -0
- package/dist/nwp/client.d.ts +28 -0
- package/dist/nwp/client.d.ts.map +1 -0
- package/dist/nwp/client.js +142 -0
- package/dist/nwp/client.js.map +1 -0
- package/dist/nwp/frames.d.ts +107 -0
- package/dist/nwp/frames.d.ts.map +1 -0
- package/dist/nwp/frames.js +198 -0
- package/dist/nwp/frames.js.map +1 -0
- package/dist/nwp/index.d.ts +6 -0
- package/dist/nwp/index.d.ts.map +1 -0
- package/{src/nwp/index.ts → dist/nwp/index.js} +3 -1
- package/dist/nwp/index.js.map +1 -0
- package/dist/nwp/manifest.d.ts +69 -0
- package/dist/nwp/manifest.d.ts.map +1 -0
- package/dist/nwp/manifest.js +4 -0
- package/dist/nwp/manifest.js.map +1 -0
- package/dist/nwp/registry.d.ts +3 -0
- package/dist/nwp/registry.d.ts.map +1 -0
- package/dist/nwp/registry.js +10 -0
- package/dist/nwp/registry.js.map +1 -0
- package/dist/setup.d.ts +10 -0
- package/dist/setup.d.ts.map +1 -0
- package/{src/setup.ts → dist/setup.js} +13 -16
- package/dist/setup.js.map +1 -0
- package/package.json +12 -1
- package/CONTRIBUTING.cn.md +0 -35
- package/CONTRIBUTING.md +0 -35
- package/nip-ca-server/Dockerfile +0 -27
- package/nip-ca-server/README.md +0 -45
- package/nip-ca-server/db/001_init.sql +0 -25
- package/nip-ca-server/docker-compose.yml +0 -29
- package/nip-ca-server/package.json +0 -23
- package/nip-ca-server/src/ca.ts +0 -155
- package/nip-ca-server/src/db.ts +0 -104
- package/nip-ca-server/src/index.ts +0 -157
- package/nip-ca-server/tsconfig.json +0 -13
- package/src/core/anchor-cache.ts +0 -129
- package/src/core/cache.ts +0 -93
- package/src/core/canonical-json.ts +0 -50
- package/src/core/codec.ts +0 -158
- package/src/core/codecs/ncp-codec.ts +0 -170
- package/src/core/crypto-provider.ts +0 -47
- package/src/core/exceptions.ts +0 -57
- package/src/core/frame-header.ts +0 -282
- package/src/core/frame-registry.ts +0 -91
- package/src/core/frames.ts +0 -184
- package/src/core/registry.ts +0 -28
- package/src/core/status-codes.ts +0 -47
- package/src/ncp/frames/anchor-frame.ts +0 -87
- package/src/ncp/frames/caps-frame.ts +0 -59
- package/src/ncp/frames/diff-frame.ts +0 -69
- package/src/ncp/frames/error-frame.ts +0 -26
- package/src/ncp/frames/hello-frame.ts +0 -50
- package/src/ncp/frames/stream-frame.ts +0 -35
- package/src/ncp/frames.ts +0 -251
- package/src/ncp/handshake.ts +0 -95
- package/src/ncp/ncp-error-codes.ts +0 -36
- package/src/ncp/ncp-patch-format.ts +0 -16
- package/src/ncp/preamble.ts +0 -79
- package/src/ncp/registry.ts +0 -15
- package/src/ncp/stream-manager.ts +0 -212
- package/src/ndp/dns-txt.ts +0 -86
- package/src/ndp/frames.ts +0 -124
- package/src/ndp/ndp-registry.ts +0 -116
- package/src/ndp/registry.ts +0 -12
- package/src/ndp/validator.ts +0 -64
- package/src/nip/acme/client.ts +0 -185
- package/src/nip/acme/jws.ts +0 -109
- package/src/nip/acme/messages.ts +0 -85
- package/src/nip/acme/server.ts +0 -480
- package/src/nip/acme/wire.ts +0 -24
- package/src/nip/assurance-level.ts +0 -40
- package/src/nip/cert-format.ts +0 -9
- package/src/nip/frames.ts +0 -138
- package/src/nip/identity.ts +0 -113
- package/src/nip/registry.ts +0 -12
- package/src/nip/verifier.ts +0 -122
- package/src/nip/x509/builder.ts +0 -91
- package/src/nip/x509/verifier.ts +0 -214
- package/src/nop/client.ts +0 -103
- package/src/nop/frames.ts +0 -181
- package/src/nop/models.ts +0 -79
- package/src/nop/nop-types.ts +0 -208
- package/src/nop/registry.ts +0 -13
- package/src/nwp/client.ts +0 -114
- package/src/nwp/frames.ts +0 -116
- package/src/nwp/registry.ts +0 -11
- package/tests/_rfc0002-keys.ts +0 -57
- package/tests/core/anchor-cache.test.ts +0 -242
- package/tests/core/codec.test.ts +0 -205
- package/tests/core/frame-registry.test.ts +0 -46
- package/tests/core.test.ts +0 -327
- package/tests/ncp/diff-binary-bitset.test.ts +0 -107
- package/tests/ncp/e2e-enc-reject.test.ts +0 -93
- package/tests/ncp/err-error-frame.test.ts +0 -152
- package/tests/ncp/frames.test.ts +0 -359
- package/tests/ncp/framing.test.ts +0 -233
- package/tests/ncp/hello-frame.test.ts +0 -122
- package/tests/ncp/inline-anchor.test.ts +0 -88
- package/tests/ncp/preamble.test.ts +0 -93
- package/tests/ncp/security.test.ts +0 -184
- package/tests/ncp/stream-window.test.ts +0 -167
- package/tests/ncp/stream.test.ts +0 -242
- package/tests/ncp/version-negotiation.test.ts +0 -123
- package/tests/ndp.test.ts +0 -377
- package/tests/nip-acme-agent01.test.ts +0 -192
- package/tests/nip-x509.test.ts +0 -280
- package/tests/nip.test.ts +0 -184
- package/tests/nop.test.ts +0 -344
- package/tests/nwp.test.ts +0 -237
- package/tsconfig.json +0 -20
- package/tsup.config.ts +0 -20
- package/vitest.config.ts +0 -10
package/src/nip/acme/server.ts
DELETED
|
@@ -1,480 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* In-process ACME server implementing the `agent-01` challenge for NPS-RFC-0002 §4.4.
|
|
6
|
-
*
|
|
7
|
-
* Backed by Node's stdlib `http.createServer`. Suitable for tests and reference
|
|
8
|
-
* deployments. State is kept in memory.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import * as ed25519 from "@noble/ed25519";
|
|
12
|
-
import { sha512 } from "@noble/hashes/sha512";
|
|
13
|
-
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
14
|
-
import { randomBytes } from "node:crypto";
|
|
15
|
-
import * as x509 from "@peculiar/x509";
|
|
16
|
-
|
|
17
|
-
import { AssuranceLevel } from "../assurance-level.js";
|
|
18
|
-
import { ACME_CHALLENGE_FAILED } from "../error-codes.js";
|
|
19
|
-
import { issueLeaf } from "../x509/builder.js";
|
|
20
|
-
import * as Jws from "./jws.js";
|
|
21
|
-
import type {
|
|
22
|
-
Authorization, Challenge, ChallengeRespondPayload, Directory,
|
|
23
|
-
FinalizePayload, Identifier, NewOrderPayload, Order, ProblemDetail,
|
|
24
|
-
} from "./messages.js";
|
|
25
|
-
import * as wire from "./wire.js";
|
|
26
|
-
|
|
27
|
-
ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
|
|
28
|
-
x509.cryptoProvider.set(globalThis.crypto);
|
|
29
|
-
|
|
30
|
-
export interface AcmeServerOptions {
|
|
31
|
-
caNid: string;
|
|
32
|
-
caKeys: CryptoKeyPair; // Web Crypto Ed25519 keypair (for issuing X.509 leaves).
|
|
33
|
-
caRootCert: x509.X509Certificate;
|
|
34
|
-
certValidityMs: number;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface OrderState {
|
|
38
|
-
id: string;
|
|
39
|
-
identifier: Identifier;
|
|
40
|
-
status: string;
|
|
41
|
-
authzId: string;
|
|
42
|
-
finalizeUrl: string;
|
|
43
|
-
accountUrl: string;
|
|
44
|
-
certificateUrl?: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface AuthzState {
|
|
48
|
-
id: string;
|
|
49
|
-
identifier: Identifier;
|
|
50
|
-
status: string;
|
|
51
|
-
challengeIds: string[];
|
|
52
|
-
accountUrl: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface ChallengeState {
|
|
56
|
-
id: string;
|
|
57
|
-
type: string;
|
|
58
|
-
status: string;
|
|
59
|
-
token: string;
|
|
60
|
-
authzId: string;
|
|
61
|
-
accountUrl: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export class AcmeServer {
|
|
65
|
-
private readonly server: Server;
|
|
66
|
-
private readonly nonces = new Set<string>();
|
|
67
|
-
private readonly accountJwks = new Map<string, Jws.Jwk>();
|
|
68
|
-
private readonly orders = new Map<string, OrderState>();
|
|
69
|
-
private readonly authzs = new Map<string, AuthzState>();
|
|
70
|
-
private readonly challenges = new Map<string, ChallengeState>();
|
|
71
|
-
private readonly certs = new Map<string, string>();
|
|
72
|
-
private boundPort: number = 0;
|
|
73
|
-
|
|
74
|
-
constructor(public readonly options: AcmeServerOptions) {
|
|
75
|
-
this.server = createServer((req, res) => this.dispatch(req, res));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async start(): Promise<this> {
|
|
79
|
-
await new Promise<void>((resolve) => {
|
|
80
|
-
this.server.listen(0, "127.0.0.1", () => resolve());
|
|
81
|
-
});
|
|
82
|
-
const addr = this.server.address();
|
|
83
|
-
this.boundPort = typeof addr === "object" && addr !== null ? addr.port : 0;
|
|
84
|
-
return this;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
close(): Promise<void> {
|
|
88
|
-
return new Promise((resolve) => this.server.close(() => resolve()));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
get baseUrl(): string { return `http://127.0.0.1:${this.boundPort}`; }
|
|
92
|
-
get directoryUrl(): string { return `${this.baseUrl}/directory`; }
|
|
93
|
-
|
|
94
|
-
// ── Routing ──────────────────────────────────────────────────────────────
|
|
95
|
-
|
|
96
|
-
private async dispatch(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
97
|
-
const url = req.url ?? "/";
|
|
98
|
-
const method = req.method ?? "GET";
|
|
99
|
-
try {
|
|
100
|
-
if (method === "GET" && url === "/directory") return this.handleDirectory(res);
|
|
101
|
-
if (url === "/new-nonce") return this.handleNewNonce(method, res);
|
|
102
|
-
if (method === "POST" && url === "/new-account") return await this.handleNewAccount(req, res);
|
|
103
|
-
if (method === "POST" && url === "/new-order") return await this.handleNewOrder(req, res);
|
|
104
|
-
if (method === "POST" && url.startsWith("/authz/")) return await this.handleAuthz(req, res, url);
|
|
105
|
-
if (method === "POST" && url.startsWith("/chall/")) return await this.handleChallenge(req, res, url);
|
|
106
|
-
if (method === "POST" && url.startsWith("/finalize/")) return await this.handleFinalize(req, res, url);
|
|
107
|
-
if (method === "POST" && url.startsWith("/cert/")) return await this.handleCert(req, res, url);
|
|
108
|
-
if (method === "POST" && url.startsWith("/order/")) return await this.handleOrder(req, res, url);
|
|
109
|
-
this.sendProblem(res, 404, "urn:ietf:params:acme:error:malformed", "no such resource");
|
|
110
|
-
} catch (e) {
|
|
111
|
-
this.sendProblem(res, 500, "urn:ietf:params:acme:error:serverInternal",
|
|
112
|
-
(e as Error).message);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ── Endpoint handlers ────────────────────────────────────────────────────
|
|
117
|
-
|
|
118
|
-
private handleDirectory(res: ServerResponse): void {
|
|
119
|
-
const dir: Directory = {
|
|
120
|
-
newNonce: `${this.baseUrl}/new-nonce`,
|
|
121
|
-
newAccount: `${this.baseUrl}/new-account`,
|
|
122
|
-
newOrder: `${this.baseUrl}/new-order`,
|
|
123
|
-
};
|
|
124
|
-
this.sendJson(res, 200, dir);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
private handleNewNonce(method: string, res: ServerResponse): void {
|
|
128
|
-
res.statusCode = method === "HEAD" ? 200 : 204;
|
|
129
|
-
res.setHeader("Replay-Nonce", this.mintNonce());
|
|
130
|
-
res.setHeader("Cache-Control", "no-store");
|
|
131
|
-
res.end();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private async handleNewAccount(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
135
|
-
const env = await this.readEnvelope(req, res);
|
|
136
|
-
if (!env) return;
|
|
137
|
-
const header = this.parseHeader(env, res);
|
|
138
|
-
if (!header) return;
|
|
139
|
-
if (!header.jwk) {
|
|
140
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:malformed",
|
|
141
|
-
"newAccount must include a 'jwk' member");
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (!this.consumeNonce(header.nonce)) {
|
|
145
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:badNonce", "invalid nonce");
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
const pub = Jws.publicKeyFromJwk(header.jwk);
|
|
149
|
-
if (Jws.verify(env, pub) === null) {
|
|
150
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:malformed",
|
|
151
|
-
"JWS signature verify failed");
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const accountId = `acc-${shortId()}`;
|
|
156
|
-
const accountUrl = `${this.baseUrl}/account/${accountId}`;
|
|
157
|
-
this.accountJwks.set(accountUrl, header.jwk);
|
|
158
|
-
|
|
159
|
-
res.statusCode = 201;
|
|
160
|
-
res.setHeader("Content-Type", "application/json");
|
|
161
|
-
res.setHeader("Location", accountUrl);
|
|
162
|
-
res.setHeader("Replay-Nonce", this.mintNonce());
|
|
163
|
-
res.end(JSON.stringify({ status: wire.Status.VALID }));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private async handleNewOrder(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
167
|
-
const env = await this.readEnvelope(req, res);
|
|
168
|
-
if (!env) return;
|
|
169
|
-
const header = this.parseHeader(env, res);
|
|
170
|
-
if (!header) return;
|
|
171
|
-
if (!this.consumeNonce(header.nonce)) {
|
|
172
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:badNonce", "invalid nonce"); return;
|
|
173
|
-
}
|
|
174
|
-
if (!this.verifyAccount(env, header)) {
|
|
175
|
-
this.sendProblem(res, 401, "urn:ietf:params:acme:error:accountDoesNotExist",
|
|
176
|
-
`unknown kid: ${header.kid ?? "<missing>"}`);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const payload = Jws.decodePayload<NewOrderPayload>(env);
|
|
181
|
-
if (!payload || !payload.identifiers?.length) {
|
|
182
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:malformed", "missing identifiers");
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
const ident = payload.identifiers[0];
|
|
186
|
-
const orderId = `ord-${shortId()}`;
|
|
187
|
-
const authzId = `az-${shortId()}`;
|
|
188
|
-
const challId = `ch-${shortId()}`;
|
|
189
|
-
const token = Jws.b64uEncode(new Uint8Array(randomBytes(32)));
|
|
190
|
-
|
|
191
|
-
const orderUrl = `${this.baseUrl}/order/${orderId}`;
|
|
192
|
-
const authzUrl = `${this.baseUrl}/authz/${authzId}`;
|
|
193
|
-
const challUrl = `${this.baseUrl}/chall/${challId}`;
|
|
194
|
-
const finalizeUrl = `${this.baseUrl}/finalize/${orderId}`;
|
|
195
|
-
|
|
196
|
-
this.challenges.set(challId, {
|
|
197
|
-
id: challId, type: wire.CHALLENGE_AGENT_01, status: wire.Status.PENDING,
|
|
198
|
-
token, authzId, accountUrl: header.kid ?? "",
|
|
199
|
-
});
|
|
200
|
-
this.authzs.set(authzId, {
|
|
201
|
-
id: authzId, identifier: ident, status: wire.Status.PENDING,
|
|
202
|
-
challengeIds: [challId], accountUrl: header.kid ?? "",
|
|
203
|
-
});
|
|
204
|
-
this.orders.set(orderId, {
|
|
205
|
-
id: orderId, identifier: ident, status: wire.Status.PENDING,
|
|
206
|
-
authzId, finalizeUrl, accountUrl: header.kid ?? "",
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
const order: Order = {
|
|
210
|
-
status: wire.Status.PENDING,
|
|
211
|
-
identifiers: [ident],
|
|
212
|
-
authorizations: [authzUrl],
|
|
213
|
-
finalize: finalizeUrl,
|
|
214
|
-
};
|
|
215
|
-
res.statusCode = 201;
|
|
216
|
-
res.setHeader("Content-Type", "application/json");
|
|
217
|
-
res.setHeader("Location", orderUrl);
|
|
218
|
-
res.setHeader("Replay-Nonce", this.mintNonce());
|
|
219
|
-
res.end(JSON.stringify(order));
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
private async handleAuthz(req: IncomingMessage, res: ServerResponse, url: string): Promise<void> {
|
|
223
|
-
const env = await this.readEnvelope(req, res); if (!env) return;
|
|
224
|
-
const header = this.parseHeader(env, res); if (!header) return;
|
|
225
|
-
if (!this.consumeNonce(header.nonce)) {
|
|
226
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:badNonce", "invalid nonce"); return;
|
|
227
|
-
}
|
|
228
|
-
if (!this.verifyAccount(env, header)) {
|
|
229
|
-
this.sendProblem(res, 401, "urn:ietf:params:acme:error:unauthorized", "bad sig"); return;
|
|
230
|
-
}
|
|
231
|
-
const id = url.replace(/^\/authz\//, "");
|
|
232
|
-
const az = this.authzs.get(id);
|
|
233
|
-
if (!az) { this.sendProblem(res, 404, "urn:ietf:params:acme:error:malformed", "no authz"); return; }
|
|
234
|
-
|
|
235
|
-
const challenges: Challenge[] = az.challengeIds.map((cid) => {
|
|
236
|
-
const cs = this.challenges.get(cid)!;
|
|
237
|
-
return {
|
|
238
|
-
type: cs.type, url: `${this.baseUrl}/chall/${cs.id}`,
|
|
239
|
-
status: cs.status, token: cs.token,
|
|
240
|
-
};
|
|
241
|
-
});
|
|
242
|
-
const authz: Authorization = {
|
|
243
|
-
status: az.status, identifier: az.identifier, challenges,
|
|
244
|
-
};
|
|
245
|
-
res.setHeader("Replay-Nonce", this.mintNonce());
|
|
246
|
-
this.sendJson(res, 200, authz);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private async handleChallenge(req: IncomingMessage, res: ServerResponse, url: string): Promise<void> {
|
|
250
|
-
const env = await this.readEnvelope(req, res); if (!env) return;
|
|
251
|
-
const header = this.parseHeader(env, res); if (!header) return;
|
|
252
|
-
if (!this.consumeNonce(header.nonce)) {
|
|
253
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:badNonce", "invalid nonce"); return;
|
|
254
|
-
}
|
|
255
|
-
const accountJwk = this.accountJwks.get(header.kid ?? "");
|
|
256
|
-
if (!accountJwk) {
|
|
257
|
-
this.sendProblem(res, 401, "urn:ietf:params:acme:error:accountDoesNotExist", "unknown kid");
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
const accountPub = Jws.publicKeyFromJwk(accountJwk);
|
|
261
|
-
if (Jws.verify(env, accountPub) === null) {
|
|
262
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:malformed", "JWS sig fail"); return;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const id = url.replace(/^\/chall\//, "");
|
|
266
|
-
const ch = this.challenges.get(id);
|
|
267
|
-
if (!ch) { this.sendProblem(res, 404, "urn:ietf:params:acme:error:malformed", "no chall"); return; }
|
|
268
|
-
|
|
269
|
-
const payload = Jws.decodePayload<ChallengeRespondPayload>(env);
|
|
270
|
-
if (!payload?.agent_signature) {
|
|
271
|
-
ch.status = wire.Status.INVALID;
|
|
272
|
-
this.sendProblem(res, 400, ACME_CHALLENGE_FAILED,
|
|
273
|
-
"missing agent_signature in challenge response");
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
try {
|
|
277
|
-
const sigBytes = Jws.b64uDecode(payload.agent_signature);
|
|
278
|
-
const tokenBytes = new TextEncoder().encode(ch.token);
|
|
279
|
-
if (!ed25519.verify(sigBytes, tokenBytes, accountPub)) {
|
|
280
|
-
ch.status = wire.Status.INVALID;
|
|
281
|
-
this.sendProblem(res, 400, ACME_CHALLENGE_FAILED,
|
|
282
|
-
"agent-01 signature did not verify");
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
} catch (e) {
|
|
286
|
-
ch.status = wire.Status.INVALID;
|
|
287
|
-
this.sendProblem(res, 400, ACME_CHALLENGE_FAILED,
|
|
288
|
-
`agent-01 verification error: ${(e as Error).message}`);
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
ch.status = wire.Status.VALID;
|
|
293
|
-
const az = this.authzs.get(ch.authzId);
|
|
294
|
-
if (az) az.status = wire.Status.VALID;
|
|
295
|
-
for (const o of this.orders.values()) {
|
|
296
|
-
if (o.authzId === ch.authzId) o.status = wire.Status.READY;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
res.setHeader("Replay-Nonce", this.mintNonce());
|
|
300
|
-
this.sendJson(res, 200, {
|
|
301
|
-
type: ch.type, url: `${this.baseUrl}/chall/${ch.id}`,
|
|
302
|
-
status: ch.status, token: ch.token,
|
|
303
|
-
} as Challenge);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
private async handleFinalize(req: IncomingMessage, res: ServerResponse, url: string): Promise<void> {
|
|
307
|
-
const env = await this.readEnvelope(req, res); if (!env) return;
|
|
308
|
-
const header = this.parseHeader(env, res); if (!header) return;
|
|
309
|
-
if (!this.consumeNonce(header.nonce)) {
|
|
310
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:badNonce", "invalid nonce"); return;
|
|
311
|
-
}
|
|
312
|
-
if (!this.verifyAccount(env, header)) {
|
|
313
|
-
this.sendProblem(res, 401, "urn:ietf:params:acme:error:unauthorized", "bad sig"); return;
|
|
314
|
-
}
|
|
315
|
-
const orderId = url.replace(/^\/finalize\//, "");
|
|
316
|
-
const os = this.orders.get(orderId);
|
|
317
|
-
if (!os) { this.sendProblem(res, 404, "urn:ietf:params:acme:error:malformed", "no order"); return; }
|
|
318
|
-
if (os.status !== wire.Status.READY) {
|
|
319
|
-
this.sendProblem(res, 403, "urn:ietf:params:acme:error:orderNotReady",
|
|
320
|
-
`order is in state '${os.status}', not 'ready'`);
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
const fp = Jws.decodePayload<FinalizePayload>(env);
|
|
324
|
-
if (!fp?.csr) {
|
|
325
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:malformed", "missing csr"); return;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
try {
|
|
329
|
-
const csrDer = Jws.b64uDecode(fp.csr);
|
|
330
|
-
const csr = new x509.Pkcs10CertificateRequest(csrDer.buffer as ArrayBuffer);
|
|
331
|
-
const subjectCn = (() => {
|
|
332
|
-
for (const rdn of csr.subject.split(",")) {
|
|
333
|
-
const t = rdn.trim();
|
|
334
|
-
if (t.startsWith("CN=")) return t.slice(3).replace(/\\([",+;<>\\])/g, "$1");
|
|
335
|
-
}
|
|
336
|
-
return null as string | null;
|
|
337
|
-
})();
|
|
338
|
-
if (subjectCn !== os.identifier.value) {
|
|
339
|
-
this.sendProblem(res, 400, "NIP-CERT-SUBJECT-NID-MISMATCH",
|
|
340
|
-
`CSR subject CN '${subjectCn ?? ""}' does not match order identifier '${os.identifier.value}'`);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
const subjectPub = await csr.publicKey.export();
|
|
344
|
-
const now = new Date();
|
|
345
|
-
const leaf = await issueLeaf({
|
|
346
|
-
subjectNid: os.identifier.value,
|
|
347
|
-
subjectPublicKey: subjectPub,
|
|
348
|
-
caKeys: this.options.caKeys,
|
|
349
|
-
issuerNid: this.options.caNid,
|
|
350
|
-
role: "agent",
|
|
351
|
-
assuranceLevel: AssuranceLevel.ANONYMOUS,
|
|
352
|
-
notBefore: new Date(now.getTime() - 60_000),
|
|
353
|
-
notAfter: new Date(now.getTime() + this.options.certValidityMs),
|
|
354
|
-
serialNumber: randomHexSerial(),
|
|
355
|
-
});
|
|
356
|
-
const certId = `crt-${shortId()}`;
|
|
357
|
-
const certUrl = `${this.baseUrl}/cert/${certId}`;
|
|
358
|
-
const pem = leaf.toString("pem") + this.options.caRootCert.toString("pem");
|
|
359
|
-
this.certs.set(certId, pem);
|
|
360
|
-
os.status = wire.Status.VALID;
|
|
361
|
-
os.certificateUrl = certUrl;
|
|
362
|
-
} catch (e) {
|
|
363
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:badCSR",
|
|
364
|
-
`CSR processing failed: ${(e as Error).message}`);
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
const authzUrl = `${this.baseUrl}/authz/${os.authzId}`;
|
|
369
|
-
res.setHeader("Replay-Nonce", this.mintNonce());
|
|
370
|
-
this.sendJson(res, 200, {
|
|
371
|
-
status: os.status, identifiers: [os.identifier],
|
|
372
|
-
authorizations: [authzUrl], finalize: os.finalizeUrl,
|
|
373
|
-
certificate: os.certificateUrl,
|
|
374
|
-
} as Order);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
private async handleCert(req: IncomingMessage, res: ServerResponse, url: string): Promise<void> {
|
|
378
|
-
const env = await this.readEnvelope(req, res); if (!env) return;
|
|
379
|
-
const header = this.parseHeader(env, res); if (!header) return;
|
|
380
|
-
if (!this.consumeNonce(header.nonce)) {
|
|
381
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:badNonce", "invalid nonce"); return;
|
|
382
|
-
}
|
|
383
|
-
if (!this.verifyAccount(env, header)) {
|
|
384
|
-
this.sendProblem(res, 401, "urn:ietf:params:acme:error:unauthorized", "bad sig"); return;
|
|
385
|
-
}
|
|
386
|
-
const certId = url.replace(/^\/cert\//, "");
|
|
387
|
-
const pem = this.certs.get(certId);
|
|
388
|
-
if (!pem) { this.sendProblem(res, 404, "urn:ietf:params:acme:error:malformed", "no cert"); return; }
|
|
389
|
-
|
|
390
|
-
res.statusCode = 200;
|
|
391
|
-
res.setHeader("Content-Type", wire.CONTENT_TYPE_PEM_CERT);
|
|
392
|
-
res.setHeader("Replay-Nonce", this.mintNonce());
|
|
393
|
-
res.end(pem);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
private async handleOrder(req: IncomingMessage, res: ServerResponse, url: string): Promise<void> {
|
|
397
|
-
const env = await this.readEnvelope(req, res); if (!env) return;
|
|
398
|
-
const header = this.parseHeader(env, res); if (!header) return;
|
|
399
|
-
if (!this.consumeNonce(header.nonce)) {
|
|
400
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:badNonce", "invalid nonce"); return;
|
|
401
|
-
}
|
|
402
|
-
if (!this.verifyAccount(env, header)) {
|
|
403
|
-
this.sendProblem(res, 401, "urn:ietf:params:acme:error:unauthorized", "bad sig"); return;
|
|
404
|
-
}
|
|
405
|
-
const orderId = url.replace(/^\/order\//, "");
|
|
406
|
-
const os = this.orders.get(orderId);
|
|
407
|
-
if (!os) { this.sendProblem(res, 404, "urn:ietf:params:acme:error:malformed", "no order"); return; }
|
|
408
|
-
const authzUrl = `${this.baseUrl}/authz/${os.authzId}`;
|
|
409
|
-
res.setHeader("Replay-Nonce", this.mintNonce());
|
|
410
|
-
this.sendJson(res, 200, {
|
|
411
|
-
status: os.status, identifiers: [os.identifier],
|
|
412
|
-
authorizations: [authzUrl], finalize: os.finalizeUrl,
|
|
413
|
-
certificate: os.certificateUrl,
|
|
414
|
-
} as Order);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// ── helpers ──────────────────────────────────────────────────────────────
|
|
418
|
-
|
|
419
|
-
private mintNonce(): string {
|
|
420
|
-
const n = Jws.b64uEncode(new Uint8Array(randomBytes(16)));
|
|
421
|
-
this.nonces.add(n);
|
|
422
|
-
return n;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
private consumeNonce(nonce: string): boolean {
|
|
426
|
-
return this.nonces.delete(nonce);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
private verifyAccount(env: Jws.Envelope, header: Jws.ProtectedHeader): boolean {
|
|
430
|
-
if (!header.kid) return false;
|
|
431
|
-
const jwk = this.accountJwks.get(header.kid);
|
|
432
|
-
if (!jwk) return false;
|
|
433
|
-
return Jws.verify(env, Jws.publicKeyFromJwk(jwk)) !== null;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
private async readEnvelope(req: IncomingMessage, res: ServerResponse): Promise<Jws.Envelope | null> {
|
|
437
|
-
try {
|
|
438
|
-
const chunks: Buffer[] = [];
|
|
439
|
-
for await (const chunk of req) {
|
|
440
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
441
|
-
}
|
|
442
|
-
const body = Buffer.concat(chunks).toString("utf8");
|
|
443
|
-
return JSON.parse(body) as Jws.Envelope;
|
|
444
|
-
} catch (e) {
|
|
445
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:malformed",
|
|
446
|
-
`body read/parse failed: ${(e as Error).message}`);
|
|
447
|
-
return null;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
private parseHeader(env: Jws.Envelope, res: ServerResponse): Jws.ProtectedHeader | null {
|
|
452
|
-
try {
|
|
453
|
-
return JSON.parse(new TextDecoder().decode(Jws.b64uDecode(env.protected))) as Jws.ProtectedHeader;
|
|
454
|
-
} catch (e) {
|
|
455
|
-
this.sendProblem(res, 400, "urn:ietf:params:acme:error:malformed",
|
|
456
|
-
`malformed protected header: ${(e as Error).message}`);
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
private sendJson(res: ServerResponse, status: number, body: unknown): void {
|
|
462
|
-
res.statusCode = status;
|
|
463
|
-
res.setHeader("Content-Type", "application/json");
|
|
464
|
-
res.end(JSON.stringify(body));
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
private sendProblem(res: ServerResponse, status: number, type: string, detail: string): void {
|
|
468
|
-
res.statusCode = status;
|
|
469
|
-
res.setHeader("Content-Type", wire.CONTENT_TYPE_PROBLEM);
|
|
470
|
-
res.end(JSON.stringify({ type, detail, status } as ProblemDetail));
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function shortId(): string {
|
|
475
|
-
return Buffer.from(randomBytes(8)).toString("hex");
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function randomHexSerial(): string {
|
|
479
|
-
return Buffer.from(randomBytes(20)).toString("hex");
|
|
480
|
-
}
|
package/src/nip/acme/wire.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
/** ACME wire constants (RFC 8555 + NPS-RFC-0002 §4.4). */
|
|
5
|
-
|
|
6
|
-
export const CONTENT_TYPE_JOSE_JSON = "application/jose+json";
|
|
7
|
-
export const CONTENT_TYPE_PROBLEM = "application/problem+json";
|
|
8
|
-
export const CONTENT_TYPE_PEM_CERT = "application/pem-certificate-chain";
|
|
9
|
-
|
|
10
|
-
export const CHALLENGE_AGENT_01 = "agent-01";
|
|
11
|
-
export const IDENTIFIER_TYPE_NID = "nid";
|
|
12
|
-
|
|
13
|
-
/** ACME status enumeration values (RFC 8555 §7.1.6). */
|
|
14
|
-
export const Status = {
|
|
15
|
-
PENDING: "pending",
|
|
16
|
-
READY: "ready",
|
|
17
|
-
PROCESSING: "processing",
|
|
18
|
-
VALID: "valid",
|
|
19
|
-
INVALID: "invalid",
|
|
20
|
-
EXPIRED: "expired",
|
|
21
|
-
DEACTIVATED: "deactivated",
|
|
22
|
-
REVOKED: "revoked",
|
|
23
|
-
SUBMITTED: "submitted",
|
|
24
|
-
} as const;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
/** Agent identity assurance level per NPS-RFC-0003 §5.1.1. */
|
|
5
|
-
export type AssuranceLevelWire = "anonymous" | "attested" | "verified";
|
|
6
|
-
|
|
7
|
-
export class AssuranceLevel {
|
|
8
|
-
static readonly ANONYMOUS = new AssuranceLevel("anonymous", 0);
|
|
9
|
-
static readonly ATTESTED = new AssuranceLevel("attested", 1);
|
|
10
|
-
static readonly VERIFIED = new AssuranceLevel("verified", 2);
|
|
11
|
-
|
|
12
|
-
private constructor(
|
|
13
|
-
public readonly wire: AssuranceLevelWire,
|
|
14
|
-
public readonly rank: number,
|
|
15
|
-
) {}
|
|
16
|
-
|
|
17
|
-
meetsOrExceeds(required: AssuranceLevel): boolean {
|
|
18
|
-
return this.rank >= required.rank;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Parse a wire string. `null`, `undefined`, or `""` → `ANONYMOUS`
|
|
23
|
-
* (backward compat per NPS-RFC-0003 §5.1.1). Any other unrecognised
|
|
24
|
-
* non-empty value throws — callers MUST surface it as `NIP-ASSURANCE-UNKNOWN`.
|
|
25
|
-
*/
|
|
26
|
-
static fromWire(wire: string | null | undefined): AssuranceLevel {
|
|
27
|
-
if (!wire) return AssuranceLevel.ANONYMOUS; // null, undefined, or ""
|
|
28
|
-
for (const level of [AssuranceLevel.ANONYMOUS, AssuranceLevel.ATTESTED, AssuranceLevel.VERIFIED]) {
|
|
29
|
-
if (level.wire === wire) return level;
|
|
30
|
-
}
|
|
31
|
-
throw new Error(`Unknown assurance_level: ${JSON.stringify(wire)}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
static fromRank(rank: number): AssuranceLevel {
|
|
35
|
-
for (const level of [AssuranceLevel.ANONYMOUS, AssuranceLevel.ATTESTED, AssuranceLevel.VERIFIED]) {
|
|
36
|
-
if (level.rank === rank) return level;
|
|
37
|
-
}
|
|
38
|
-
throw new Error(`Unknown assurance_level rank: ${rank}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
package/src/nip/cert-format.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
/** Wire-form constants for `IdentFrame.cert_format` (NPS-RFC-0002 §4.5). */
|
|
5
|
-
|
|
6
|
-
export const V1_PROPRIETARY = "v1-proprietary" as const;
|
|
7
|
-
export const V2_X509 = "v2-x509" as const;
|
|
8
|
-
|
|
9
|
-
export type CertFormat = typeof V1_PROPRIETARY | typeof V2_X509;
|
package/src/nip/frames.ts
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import { EncodingTier, FrameType } from "../core/frames.js";
|
|
5
|
-
import type { NpsFrame } from "../core/codec.js";
|
|
6
|
-
import { AssuranceLevel } from "./assurance-level.js";
|
|
7
|
-
|
|
8
|
-
export interface IdentMetadata {
|
|
9
|
-
issuer: string;
|
|
10
|
-
issuedAt: string;
|
|
11
|
-
expiresAt?: string;
|
|
12
|
-
capabilities?: readonly string[];
|
|
13
|
-
scopes?: readonly string[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface IdentFrameOptions {
|
|
17
|
-
assuranceLevel?: AssuranceLevel | null; // RFC-0003
|
|
18
|
-
certFormat?: string | null; // RFC-0002 — null treated as "v1-proprietary"
|
|
19
|
-
certChain?: readonly string[] | null; // RFC-0002 — base64url(DER), [leaf, intermediates..., root]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class IdentFrame implements NpsFrame {
|
|
23
|
-
readonly frameType = FrameType.IDENT;
|
|
24
|
-
readonly preferredTier = EncodingTier.MSGPACK;
|
|
25
|
-
|
|
26
|
-
readonly assuranceLevel: AssuranceLevel | null;
|
|
27
|
-
readonly certFormat: string | null;
|
|
28
|
-
readonly certChain: readonly string[] | null;
|
|
29
|
-
|
|
30
|
-
constructor(
|
|
31
|
-
public readonly nid: string,
|
|
32
|
-
public readonly pubKey: string,
|
|
33
|
-
public readonly metadata: IdentMetadata,
|
|
34
|
-
public readonly signature: string,
|
|
35
|
-
options: IdentFrameOptions = {},
|
|
36
|
-
) {
|
|
37
|
-
this.assuranceLevel = options.assuranceLevel ?? null;
|
|
38
|
-
this.certFormat = options.certFormat ?? null;
|
|
39
|
-
this.certChain = options.certChain ?? null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
unsignedDict(): Record<string, unknown> {
|
|
43
|
-
const out: Record<string, unknown> = {
|
|
44
|
-
nid: this.nid,
|
|
45
|
-
pub_key: this.pubKey,
|
|
46
|
-
metadata: this.metadata,
|
|
47
|
-
};
|
|
48
|
-
if (this.assuranceLevel !== null) out["assurance_level"] = this.assuranceLevel.wire;
|
|
49
|
-
// cert_format / cert_chain deliberately excluded from the signed payload —
|
|
50
|
-
// the v1 Ed25519 signature covers only (nid, pub_key, metadata, [assurance_level]).
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
toDict(): Record<string, unknown> {
|
|
55
|
-
const out: Record<string, unknown> = { ...this.unsignedDict(), signature: this.signature };
|
|
56
|
-
if (this.certFormat !== null) out["cert_format"] = this.certFormat;
|
|
57
|
-
if (this.certChain !== null) out["cert_chain"] = [...this.certChain];
|
|
58
|
-
return out;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
static fromDict(data: Record<string, unknown>): IdentFrame {
|
|
62
|
-
const lvl = data["assurance_level"];
|
|
63
|
-
const assuranceLevel = typeof lvl === "string" ? AssuranceLevel.fromWire(lvl) : null;
|
|
64
|
-
const chainRaw = data["cert_chain"];
|
|
65
|
-
const certChain = Array.isArray(chainRaw) ? (chainRaw as string[]) : null;
|
|
66
|
-
return new IdentFrame(
|
|
67
|
-
data["nid"] as string,
|
|
68
|
-
data["pub_key"] as string,
|
|
69
|
-
data["metadata"] as IdentMetadata,
|
|
70
|
-
data["signature"] as string,
|
|
71
|
-
{
|
|
72
|
-
assuranceLevel,
|
|
73
|
-
certFormat: (data["cert_format"] as string | undefined) ?? null,
|
|
74
|
-
certChain,
|
|
75
|
-
},
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export class TrustFrame implements NpsFrame {
|
|
81
|
-
readonly frameType = FrameType.TRUST;
|
|
82
|
-
readonly preferredTier = EncodingTier.MSGPACK;
|
|
83
|
-
|
|
84
|
-
constructor(
|
|
85
|
-
public readonly issuerNid: string,
|
|
86
|
-
public readonly subjectNid: string,
|
|
87
|
-
public readonly scopes: readonly string[],
|
|
88
|
-
public readonly expiresAt: string,
|
|
89
|
-
public readonly signature: string,
|
|
90
|
-
) {}
|
|
91
|
-
|
|
92
|
-
toDict(): Record<string, unknown> {
|
|
93
|
-
return {
|
|
94
|
-
issuer_nid: this.issuerNid,
|
|
95
|
-
subject_nid: this.subjectNid,
|
|
96
|
-
scopes: this.scopes,
|
|
97
|
-
expires_at: this.expiresAt,
|
|
98
|
-
signature: this.signature,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
static fromDict(data: Record<string, unknown>): TrustFrame {
|
|
103
|
-
return new TrustFrame(
|
|
104
|
-
data["issuer_nid"] as string,
|
|
105
|
-
data["subject_nid"] as string,
|
|
106
|
-
data["scopes"] as string[],
|
|
107
|
-
data["expires_at"] as string,
|
|
108
|
-
data["signature"] as string,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export class RevokeFrame implements NpsFrame {
|
|
114
|
-
readonly frameType = FrameType.REVOKE;
|
|
115
|
-
readonly preferredTier = EncodingTier.MSGPACK;
|
|
116
|
-
|
|
117
|
-
constructor(
|
|
118
|
-
public readonly nid: string,
|
|
119
|
-
public readonly reason?: string,
|
|
120
|
-
public readonly revokedAt?: string,
|
|
121
|
-
) {}
|
|
122
|
-
|
|
123
|
-
toDict(): Record<string, unknown> {
|
|
124
|
-
return {
|
|
125
|
-
nid: this.nid,
|
|
126
|
-
reason: this.reason ?? null,
|
|
127
|
-
revoked_at: this.revokedAt ?? null,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
static fromDict(data: Record<string, unknown>): RevokeFrame {
|
|
132
|
-
return new RevokeFrame(
|
|
133
|
-
data["nid"] as string,
|
|
134
|
-
(data["reason"] as string | null) ?? undefined,
|
|
135
|
-
(data["revoked_at"] as string | null) ?? undefined,
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
}
|