@labacacia/nps-sdk 1.0.0-alpha.6 → 1.0.0-alpha.7
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 +115 -0
- package/CHANGELOG.md +124 -0
- package/README.cn.md +3 -1
- package/README.md +3 -1
- 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 +81 -0
- package/dist/core/frames.d.ts.map +1 -0
- package/dist/core/frames.js +154 -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 +22 -0
- package/dist/nwp/client.d.ts.map +1 -0
- package/dist/nwp/client.js +101 -0
- package/dist/nwp/client.js.map +1 -0
- package/dist/nwp/frames.d.ts +46 -0
- package/dist/nwp/frames.d.ts.map +1 -0
- package/dist/nwp/frames.js +81 -0
- package/dist/nwp/frames.js.map +1 -0
- package/dist/nwp/index.d.ts +5 -0
- package/dist/nwp/index.d.ts.map +1 -0
- package/{src/nwp/index.ts → dist/nwp/index.js} +2 -1
- package/dist/nwp/index.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 +9 -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/tests/ndp.test.ts
DELETED
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import { AnnounceFrame, ResolveFrame, GraphFrame } from "../src/ndp/frames.js";
|
|
6
|
-
import { InMemoryNdpRegistry } from "../src/ndp/ndp-registry.js";
|
|
7
|
-
import { NdpAnnounceValidator, NdpAnnounceResult } from "../src/ndp/validator.js";
|
|
8
|
-
import { parseNpsTxtRecord, extractHostFromTarget, type DnsTxtLookup } from "../src/ndp/dns-txt.js";
|
|
9
|
-
import { NipIdentity } from "../src/nip/identity.js";
|
|
10
|
-
import { createFullRegistry } from "../src/setup.js";
|
|
11
|
-
import { NpsFrameCodec } from "../src/core/index.js";
|
|
12
|
-
|
|
13
|
-
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
const NID = "urn:nps:node:example.com:data";
|
|
16
|
-
const ADDRS = [{ host: "example.com", port: 17433, protocol: "nwp" }];
|
|
17
|
-
const CAPS = ["nwp/query", "nwp/stream"];
|
|
18
|
-
|
|
19
|
-
function makeAnnounce(nid = NID, ttl = 300, id?: NipIdentity): AnnounceFrame {
|
|
20
|
-
const ident = id ?? NipIdentity.generate();
|
|
21
|
-
const timestamp = "2026-01-01T00:00:00Z";
|
|
22
|
-
const unsigned = {
|
|
23
|
-
nid, addresses: ADDRS, capabilities: CAPS, ttl, timestamp, node_type: null,
|
|
24
|
-
};
|
|
25
|
-
const sig = ident.sign(unsigned);
|
|
26
|
-
return new AnnounceFrame(nid, ADDRS, CAPS, ttl, timestamp, sig);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ── AnnounceFrame round-trip ──────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
describe("AnnounceFrame", () => {
|
|
32
|
-
it("toDict / fromDict roundtrip", () => {
|
|
33
|
-
const f = makeAnnounce();
|
|
34
|
-
const back = AnnounceFrame.fromDict(f.toDict());
|
|
35
|
-
expect(back.nid).toBe(NID);
|
|
36
|
-
expect(back.ttl).toBe(300);
|
|
37
|
-
expect(back.addresses[0]?.port).toBe(17433);
|
|
38
|
-
expect(back.capabilities).toContain("nwp/query");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("unsignedDict omits signature", () => {
|
|
42
|
-
const f = makeAnnounce();
|
|
43
|
-
const d = f.unsignedDict();
|
|
44
|
-
expect(d["signature"]).toBeUndefined();
|
|
45
|
-
expect(d["nid"]).toBe(NID);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("codec roundtrip (MsgPack)", () => {
|
|
49
|
-
const registry = createFullRegistry();
|
|
50
|
-
const codec = new NpsFrameCodec(registry);
|
|
51
|
-
const f = makeAnnounce();
|
|
52
|
-
const back = codec.decode(codec.encode(f)) as AnnounceFrame;
|
|
53
|
-
expect(back).toBeInstanceOf(AnnounceFrame);
|
|
54
|
-
expect(back.nid).toBe(NID);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// ── ResolveFrame round-trip ───────────────────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
describe("ResolveFrame", () => {
|
|
61
|
-
it("toDict / fromDict with resolved", () => {
|
|
62
|
-
const f = new ResolveFrame("nwp://example.com/data", "urn:nps:node:a:b", { host: "example.com", port: 17433, ttl: 300 });
|
|
63
|
-
const back = ResolveFrame.fromDict(f.toDict());
|
|
64
|
-
expect(back.target).toBe("nwp://example.com/data");
|
|
65
|
-
expect(back.requesterNid).toBe("urn:nps:node:a:b");
|
|
66
|
-
expect(back.resolved?.port).toBe(17433);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("toDict / fromDict without optional fields", () => {
|
|
70
|
-
const f = new ResolveFrame("nwp://example.com/data");
|
|
71
|
-
const back = ResolveFrame.fromDict(f.toDict());
|
|
72
|
-
expect(back.requesterNid).toBeUndefined();
|
|
73
|
-
expect(back.resolved).toBeUndefined();
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// ── GraphFrame round-trip ─────────────────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
describe("GraphFrame", () => {
|
|
80
|
-
it("toDict / fromDict with nodes", () => {
|
|
81
|
-
const nodes = [{ nid: NID, addresses: ADDRS, capabilities: CAPS }];
|
|
82
|
-
const f = new GraphFrame(1, true, nodes);
|
|
83
|
-
const back = GraphFrame.fromDict(f.toDict());
|
|
84
|
-
expect(back.seq).toBe(1);
|
|
85
|
-
expect(back.initialSync).toBe(true);
|
|
86
|
-
expect(back.nodes?.[0]?.nid).toBe(NID);
|
|
87
|
-
expect(back.patch).toBeUndefined();
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// ── InMemoryNdpRegistry ───────────────────────────────────────────────────────
|
|
92
|
-
|
|
93
|
-
describe("InMemoryNdpRegistry", () => {
|
|
94
|
-
it("announce + getByNid", () => {
|
|
95
|
-
const reg = new InMemoryNdpRegistry();
|
|
96
|
-
const f = makeAnnounce();
|
|
97
|
-
reg.announce(f);
|
|
98
|
-
expect(reg.getByNid(NID)).toBe(f);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("getByNid returns undefined for unknown NID", () => {
|
|
102
|
-
const reg = new InMemoryNdpRegistry();
|
|
103
|
-
expect(reg.getByNid("urn:nps:node:unknown:x")).toBeUndefined();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("announce with ttl=0 removes entry", () => {
|
|
107
|
-
const reg = new InMemoryNdpRegistry();
|
|
108
|
-
reg.announce(makeAnnounce(NID, 300));
|
|
109
|
-
expect(reg.getByNid(NID)).toBeDefined();
|
|
110
|
-
reg.announce(makeAnnounce(NID, 0));
|
|
111
|
-
expect(reg.getByNid(NID)).toBeUndefined();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("TTL expiry — getByNid returns undefined after expiry", () => {
|
|
115
|
-
const reg = new InMemoryNdpRegistry();
|
|
116
|
-
let now = 0;
|
|
117
|
-
reg.clock = () => now;
|
|
118
|
-
reg.announce(makeAnnounce(NID, 10));
|
|
119
|
-
now = 11_000;
|
|
120
|
-
expect(reg.getByNid(NID)).toBeUndefined();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("resolve returns host/port for matching target", () => {
|
|
124
|
-
const reg = new InMemoryNdpRegistry();
|
|
125
|
-
reg.announce(makeAnnounce());
|
|
126
|
-
const r = reg.resolve("nwp://example.com/data/sub");
|
|
127
|
-
expect(r).toBeDefined();
|
|
128
|
-
expect(r?.host).toBe("example.com");
|
|
129
|
-
expect(r?.port).toBe(17433);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("resolve returns undefined for non-matching target", () => {
|
|
133
|
-
const reg = new InMemoryNdpRegistry();
|
|
134
|
-
reg.announce(makeAnnounce());
|
|
135
|
-
expect(reg.resolve("nwp://other.com/data")).toBeUndefined();
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it("getAll returns active entries", () => {
|
|
139
|
-
const reg = new InMemoryNdpRegistry();
|
|
140
|
-
let now = 0;
|
|
141
|
-
reg.clock = () => now;
|
|
142
|
-
reg.announce(makeAnnounce("urn:nps:node:a.com:x", 100));
|
|
143
|
-
reg.announce(makeAnnounce("urn:nps:node:b.com:y", 1));
|
|
144
|
-
now = 2_000; // b expired
|
|
145
|
-
const all = reg.getAll();
|
|
146
|
-
expect(all).toHaveLength(1);
|
|
147
|
-
expect(all[0]?.nid).toBe("urn:nps:node:a.com:x");
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("resolve skips expired entries", () => {
|
|
151
|
-
const reg = new InMemoryNdpRegistry();
|
|
152
|
-
let now = 0;
|
|
153
|
-
reg.clock = () => now;
|
|
154
|
-
reg.announce(makeAnnounce(NID, 5));
|
|
155
|
-
now = 10_000;
|
|
156
|
-
expect(reg.resolve("nwp://example.com/data")).toBeUndefined();
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// ── nwpTargetMatchesNid ───────────────────────────────────────────────────────
|
|
161
|
-
|
|
162
|
-
describe("InMemoryNdpRegistry.nwpTargetMatchesNid", () => {
|
|
163
|
-
const match = InMemoryNdpRegistry.nwpTargetMatchesNid;
|
|
164
|
-
|
|
165
|
-
it("exact match", () => {
|
|
166
|
-
expect(match("urn:nps:node:example.com:data", "nwp://example.com/data")).toBe(true);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("sub-path match", () => {
|
|
170
|
-
expect(match("urn:nps:node:example.com:data", "nwp://example.com/data/sub")).toBe(true);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("different authority does not match", () => {
|
|
174
|
-
expect(match("urn:nps:node:other.com:data", "nwp://example.com/data")).toBe(false);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("sibling path does not match", () => {
|
|
178
|
-
expect(match("urn:nps:node:example.com:data", "nwp://example.com/dataset")).toBe(false);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("invalid NID format returns false", () => {
|
|
182
|
-
expect(match("invalid-nid", "nwp://example.com/data")).toBe(false);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it("non-nwp:// target returns false", () => {
|
|
186
|
-
expect(match("urn:nps:node:example.com:data", "http://example.com/data")).toBe(false);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("target without path slash returns false", () => {
|
|
190
|
-
expect(match("urn:nps:node:example.com:data", "nwp://example.com")).toBe(false);
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// ── NdpAnnounceResult ─────────────────────────────────────────────────────────
|
|
195
|
-
|
|
196
|
-
describe("NdpAnnounceResult", () => {
|
|
197
|
-
it("ok() returns isValid=true", () => {
|
|
198
|
-
const r = NdpAnnounceResult.ok();
|
|
199
|
-
expect(r.isValid).toBe(true);
|
|
200
|
-
expect(r.errorCode).toBeUndefined();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it("fail() returns isValid=false with code + message", () => {
|
|
204
|
-
const r = NdpAnnounceResult.fail("NDP-ERR", "bad sig");
|
|
205
|
-
expect(r.isValid).toBe(false);
|
|
206
|
-
expect(r.errorCode).toBe("NDP-ERR");
|
|
207
|
-
expect(r.message).toBe("bad sig");
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// ── NdpAnnounceValidator ──────────────────────────────────────────────────────
|
|
212
|
-
|
|
213
|
-
describe("NdpAnnounceValidator", () => {
|
|
214
|
-
it("fails when no key registered", () => {
|
|
215
|
-
const v = new NdpAnnounceValidator();
|
|
216
|
-
const r = v.validate(makeAnnounce());
|
|
217
|
-
expect(r.isValid).toBe(false);
|
|
218
|
-
expect(r.errorCode).toBe("NDP-ANNOUNCE-NID-MISMATCH");
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it("validates a correctly signed frame", () => {
|
|
222
|
-
const ident = NipIdentity.generate();
|
|
223
|
-
const v = new NdpAnnounceValidator();
|
|
224
|
-
v.registerPublicKey(NID, ident.pubKeyString);
|
|
225
|
-
const f = makeAnnounce(NID, 300, ident);
|
|
226
|
-
expect(v.validate(f).isValid).toBe(true);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it("rejects tampered frame (wrong signature)", () => {
|
|
230
|
-
const ident = NipIdentity.generate();
|
|
231
|
-
const v = new NdpAnnounceValidator();
|
|
232
|
-
v.registerPublicKey(NID, ident.pubKeyString);
|
|
233
|
-
// Build frame signed by a different key
|
|
234
|
-
const other = NipIdentity.generate();
|
|
235
|
-
const f = makeAnnounce(NID, 300, other);
|
|
236
|
-
expect(v.validate(f).isValid).toBe(false);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it("rejects signature with wrong prefix", () => {
|
|
240
|
-
const ident = NipIdentity.generate();
|
|
241
|
-
const v = new NdpAnnounceValidator();
|
|
242
|
-
v.registerPublicKey(NID, ident.pubKeyString);
|
|
243
|
-
const f = new AnnounceFrame(NID, ADDRS, CAPS, 300, "2026-01-01T00:00:00Z", "rsa:invalid");
|
|
244
|
-
const r = v.validate(f);
|
|
245
|
-
expect(r.isValid).toBe(false);
|
|
246
|
-
expect(r.errorCode).toBe("NDP-ANNOUNCE-SIG-INVALID");
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it("rejects corrupted base64 signature", () => {
|
|
250
|
-
const ident = NipIdentity.generate();
|
|
251
|
-
const v = new NdpAnnounceValidator();
|
|
252
|
-
v.registerPublicKey(NID, ident.pubKeyString);
|
|
253
|
-
const f = new AnnounceFrame(NID, ADDRS, CAPS, 300, "2026-01-01T00:00:00Z", "ed25519:!!!garbage!!!");
|
|
254
|
-
const r = v.validate(f);
|
|
255
|
-
expect(r.isValid).toBe(false);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it("removePublicKey removes registration", () => {
|
|
259
|
-
const ident = NipIdentity.generate();
|
|
260
|
-
const v = new NdpAnnounceValidator();
|
|
261
|
-
v.registerPublicKey(NID, ident.pubKeyString);
|
|
262
|
-
v.removePublicKey(NID);
|
|
263
|
-
expect(v.knownPublicKeys.has(NID)).toBe(false);
|
|
264
|
-
expect(v.validate(makeAnnounce(NID, 300, ident)).isValid).toBe(false);
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it("knownPublicKeys is readonly view", () => {
|
|
268
|
-
const v = new NdpAnnounceValidator();
|
|
269
|
-
v.registerPublicKey("urn:nps:node:a:1", "ed25519:aabb");
|
|
270
|
-
expect(v.knownPublicKeys.size).toBe(1);
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
// ── DnsTxtResolution ──────────────────────────────────────────────────────────
|
|
275
|
-
|
|
276
|
-
describe("DnsTxtResolution", () => {
|
|
277
|
-
// ── parseNpsTxtRecord ───────────────────────────────────────────────────────
|
|
278
|
-
|
|
279
|
-
it("parseNpsTxtRecord - valid full record", () => {
|
|
280
|
-
const parts = ["v=nps1 type=memory port=17434 nid=urn:nps:node:api.example.com:products fp=sha256:a3f9"];
|
|
281
|
-
const result = parseNpsTxtRecord(parts, "api.example.com");
|
|
282
|
-
expect(result).toBeDefined();
|
|
283
|
-
expect(result?.host).toBe("api.example.com");
|
|
284
|
-
expect(result?.port).toBe(17434);
|
|
285
|
-
expect(result?.ttl).toBe(300);
|
|
286
|
-
expect(result?.certFingerprint).toBe("sha256:a3f9");
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it("parseNpsTxtRecord - missing v returns undefined", () => {
|
|
290
|
-
const parts = ["type=memory port=17434 nid=urn:nps:node:api.example.com:products"];
|
|
291
|
-
expect(parseNpsTxtRecord(parts, "api.example.com")).toBeUndefined();
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it("parseNpsTxtRecord - wrong v returns undefined", () => {
|
|
295
|
-
const parts = ["v=nps2 nid=urn:nps:node:api.example.com:products"];
|
|
296
|
-
expect(parseNpsTxtRecord(parts, "api.example.com")).toBeUndefined();
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
it("parseNpsTxtRecord - missing nid returns undefined", () => {
|
|
300
|
-
const parts = ["v=nps1 type=memory port=17434"];
|
|
301
|
-
expect(parseNpsTxtRecord(parts, "api.example.com")).toBeUndefined();
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it("parseNpsTxtRecord - default port", () => {
|
|
305
|
-
const parts = ["v=nps1 nid=urn:nps:node:api.example.com:products"];
|
|
306
|
-
const result = parseNpsTxtRecord(parts, "api.example.com");
|
|
307
|
-
expect(result).toBeDefined();
|
|
308
|
-
expect(result?.port).toBe(17433);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it("parseNpsTxtRecord - with fingerprint", () => {
|
|
312
|
-
const parts = ["v=nps1 nid=urn:nps:node:api.example.com:products fp=sha256:deadbeef"];
|
|
313
|
-
const result = parseNpsTxtRecord(parts, "api.example.com");
|
|
314
|
-
expect(result?.certFingerprint).toBe("sha256:deadbeef");
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
// ── resolveWithDns ──────────────────────────────────────────────────────────
|
|
318
|
-
|
|
319
|
-
it("resolveWithDns - uses registry first (dns not called)", async () => {
|
|
320
|
-
const reg = new InMemoryNdpRegistry();
|
|
321
|
-
reg.announce(makeAnnounce("urn:nps:node:example.com:data", 300));
|
|
322
|
-
|
|
323
|
-
let dnsCalled = false;
|
|
324
|
-
const mockDns: DnsTxtLookup = {
|
|
325
|
-
resolveTxt: async (_hostname: string) => {
|
|
326
|
-
dnsCalled = true;
|
|
327
|
-
return [];
|
|
328
|
-
},
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const result = await reg.resolveWithDns("nwp://example.com/data", mockDns);
|
|
332
|
-
expect(result).toBeDefined();
|
|
333
|
-
expect(result?.host).toBe("example.com");
|
|
334
|
-
expect(dnsCalled).toBe(false);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it("resolveWithDns - falls back to dns when registry empty", async () => {
|
|
338
|
-
const reg = new InMemoryNdpRegistry();
|
|
339
|
-
|
|
340
|
-
const mockDns: DnsTxtLookup = {
|
|
341
|
-
resolveTxt: async (hostname: string) => {
|
|
342
|
-
expect(hostname).toBe("_nps-node.api.example.com");
|
|
343
|
-
return [["v=nps1 nid=urn:nps:node:api.example.com:products port=17434"]];
|
|
344
|
-
},
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
const result = await reg.resolveWithDns("nwp://api.example.com/products", mockDns);
|
|
348
|
-
expect(result).toBeDefined();
|
|
349
|
-
expect(result?.host).toBe("api.example.com");
|
|
350
|
-
expect(result?.port).toBe(17434);
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
it("resolveWithDns - invalid txt returns undefined", async () => {
|
|
354
|
-
const reg = new InMemoryNdpRegistry();
|
|
355
|
-
|
|
356
|
-
const mockDns: DnsTxtLookup = {
|
|
357
|
-
resolveTxt: async (_hostname: string) => {
|
|
358
|
-
// Missing v=nps1 and nid — invalid record
|
|
359
|
-
return [["type=memory port=17434"]];
|
|
360
|
-
},
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
const result = await reg.resolveWithDns("nwp://api.example.com/products", mockDns);
|
|
364
|
-
expect(result).toBeUndefined();
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it("resolveWithDns - empty records returns undefined", async () => {
|
|
368
|
-
const reg = new InMemoryNdpRegistry();
|
|
369
|
-
|
|
370
|
-
const mockDns: DnsTxtLookup = {
|
|
371
|
-
resolveTxt: async (_hostname: string) => [],
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
const result = await reg.resolveWithDns("nwp://api.example.com/products", mockDns);
|
|
375
|
-
expect(result).toBeUndefined();
|
|
376
|
-
});
|
|
377
|
-
});
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
// TypeScript parallel of Java AcmeAgent01Tests / .NET AcmeAgent01Tests
|
|
5
|
-
// per NPS-RFC-0002 §4.4. End-to-end agent-01 round-trip plus tampered-signature
|
|
6
|
-
// negative path.
|
|
7
|
-
|
|
8
|
-
import { describe, expect, it } from "vitest";
|
|
9
|
-
import * as ed25519 from "@noble/ed25519";
|
|
10
|
-
import { sha512 } from "@noble/hashes/sha512";
|
|
11
|
-
import * as x509 from "@peculiar/x509";
|
|
12
|
-
|
|
13
|
-
import { AssuranceLevel } from "../src/nip/assurance-level.js";
|
|
14
|
-
import * as ec from "../src/nip/error-codes.js";
|
|
15
|
-
import { issueRoot } from "../src/nip/x509/builder.js";
|
|
16
|
-
import { verify as verifyX509 } from "../src/nip/x509/verifier.js";
|
|
17
|
-
import { AcmeClient } from "../src/nip/acme/client.js";
|
|
18
|
-
import { AcmeServer } from "../src/nip/acme/server.js";
|
|
19
|
-
import * as Jws from "../src/nip/acme/jws.js";
|
|
20
|
-
import * as wire from "../src/nip/acme/wire.js";
|
|
21
|
-
import type {
|
|
22
|
-
Authorization, ChallengeRespondPayload, Directory, NewAccountPayload,
|
|
23
|
-
NewOrderPayload, Order, ProblemDetail,
|
|
24
|
-
} from "../src/nip/acme/messages.js";
|
|
25
|
-
import { generateDualKeyPair, randomHexSerial } from "./_rfc0002-keys.js";
|
|
26
|
-
|
|
27
|
-
ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
|
|
28
|
-
x509.cryptoProvider.set(globalThis.crypto);
|
|
29
|
-
|
|
30
|
-
interface Fixture {
|
|
31
|
-
caNid: string;
|
|
32
|
-
agentNid: string;
|
|
33
|
-
caRoot: x509.X509Certificate;
|
|
34
|
-
agentKeys: Awaited<ReturnType<typeof generateDualKeyPair>>;
|
|
35
|
-
server: AcmeServer;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function createFixture(): Promise<Fixture> {
|
|
39
|
-
const caNid = "urn:nps:ca:acme-test";
|
|
40
|
-
const agentNid = "urn:nps:agent:acme-test:1";
|
|
41
|
-
|
|
42
|
-
const caKeys = await generateDualKeyPair();
|
|
43
|
-
const caRoot = await issueRoot({
|
|
44
|
-
caNid, caKeys: caKeys.webCrypto,
|
|
45
|
-
notBefore: new Date(Date.now() - 60_000),
|
|
46
|
-
notAfter: new Date(Date.now() + 365 * 24 * 3600_000),
|
|
47
|
-
serialNumber: "01",
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const agentKeys = await generateDualKeyPair();
|
|
51
|
-
|
|
52
|
-
const server = new AcmeServer({
|
|
53
|
-
caNid, caKeys: caKeys.webCrypto, caRootCert: caRoot,
|
|
54
|
-
certValidityMs: 30 * 24 * 3600_000,
|
|
55
|
-
});
|
|
56
|
-
await server.start();
|
|
57
|
-
|
|
58
|
-
return { caNid, agentNid, caRoot, agentKeys, server };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
describe("ACME agent-01 — RFC-0002 §4.4 round-trip", () => {
|
|
62
|
-
|
|
63
|
-
it("issueAgentCert round-trip returns a PEM chain that verifies against the CA root", async () => {
|
|
64
|
-
const fx = await createFixture();
|
|
65
|
-
try {
|
|
66
|
-
const client = new AcmeClient({
|
|
67
|
-
directoryUrl: fx.server.directoryUrl,
|
|
68
|
-
privateKey: fx.agentKeys.privRaw,
|
|
69
|
-
publicKey: fx.agentKeys.pubRaw,
|
|
70
|
-
webCryptoKeys: fx.agentKeys.webCrypto,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const pem = await client.issueAgentCert(fx.agentNid);
|
|
74
|
-
expect(pem).toContain("BEGIN CERTIFICATE");
|
|
75
|
-
|
|
76
|
-
// Parse PEM chain and re-encode as base64url DER for the X.509 verifier.
|
|
77
|
-
const certs = x509.PemConverter.decode(pem)
|
|
78
|
-
.map((buf) => new x509.X509Certificate(buf));
|
|
79
|
-
expect(certs.length).toBeGreaterThan(0);
|
|
80
|
-
const chainB64 = certs.map((c) => b64uEncode(new Uint8Array(c.rawData)));
|
|
81
|
-
|
|
82
|
-
const result = await verifyX509({
|
|
83
|
-
certChainBase64UrlDer: chainB64,
|
|
84
|
-
assertedNid: fx.agentNid,
|
|
85
|
-
assertedAssuranceLevel: AssuranceLevel.ANONYMOUS,
|
|
86
|
-
trustedRootCerts: [fx.caRoot],
|
|
87
|
-
});
|
|
88
|
-
expect(result.valid).toBe(true);
|
|
89
|
-
expect(extractCn(result.leaf!.subject)).toBe(fx.agentNid);
|
|
90
|
-
} finally {
|
|
91
|
-
await fx.server.close();
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("respondAgent01 with tampered agent_signature → server returns NIP-ACME-CHALLENGE-FAILED", async () => {
|
|
96
|
-
const fx = await createFixture();
|
|
97
|
-
try {
|
|
98
|
-
// Drive the flow manually so we can splice in a forged challenge response.
|
|
99
|
-
const dirResp = await fetch(fx.server.directoryUrl);
|
|
100
|
-
expect(dirResp.ok).toBe(true);
|
|
101
|
-
const dir = await dirResp.json() as Directory;
|
|
102
|
-
|
|
103
|
-
const nonceResp = await fetch(dir.newNonce, { method: "HEAD" });
|
|
104
|
-
let nonce = nonceResp.headers.get("Replay-Nonce")!;
|
|
105
|
-
expect(nonce).not.toBeNull();
|
|
106
|
-
|
|
107
|
-
// newAccount.
|
|
108
|
-
const jwk = Jws.jwkFromPublicKey(fx.agentKeys.pubRaw);
|
|
109
|
-
const acctEnv = Jws.sign(
|
|
110
|
-
{ alg: Jws.ALG_EDDSA, nonce, url: dir.newAccount, jwk },
|
|
111
|
-
{ termsOfServiceAgreed: true } as NewAccountPayload,
|
|
112
|
-
fx.agentKeys.privRaw);
|
|
113
|
-
const acctResp = await postJose(dir.newAccount, acctEnv);
|
|
114
|
-
expect(acctResp.status).toBe(201);
|
|
115
|
-
const accountUrl = acctResp.headers.get("Location")!;
|
|
116
|
-
nonce = acctResp.headers.get("Replay-Nonce")!;
|
|
117
|
-
|
|
118
|
-
// newOrder.
|
|
119
|
-
const orderEnv = Jws.sign(
|
|
120
|
-
{ alg: Jws.ALG_EDDSA, nonce, url: dir.newOrder, kid: accountUrl },
|
|
121
|
-
{
|
|
122
|
-
identifiers: [{ type: wire.IDENTIFIER_TYPE_NID, value: fx.agentNid }],
|
|
123
|
-
} as NewOrderPayload,
|
|
124
|
-
fx.agentKeys.privRaw);
|
|
125
|
-
const orderResp = await postJose(dir.newOrder, orderEnv);
|
|
126
|
-
expect(orderResp.status).toBe(201);
|
|
127
|
-
const order = await orderResp.json() as Order;
|
|
128
|
-
nonce = orderResp.headers.get("Replay-Nonce")!;
|
|
129
|
-
|
|
130
|
-
// POST-as-GET on authz to discover the challenge URL + token.
|
|
131
|
-
const authzEnv = Jws.sign(
|
|
132
|
-
{ alg: Jws.ALG_EDDSA, nonce, url: order.authorizations[0], kid: accountUrl },
|
|
133
|
-
null, fx.agentKeys.privRaw);
|
|
134
|
-
const authzResp = await postJose(order.authorizations[0], authzEnv);
|
|
135
|
-
const authz = await authzResp.json() as Authorization;
|
|
136
|
-
nonce = authzResp.headers.get("Replay-Nonce")!;
|
|
137
|
-
|
|
138
|
-
const challenge = authz.challenges.find((c) => c.type === wire.CHALLENGE_AGENT_01);
|
|
139
|
-
expect(challenge).toBeDefined();
|
|
140
|
-
|
|
141
|
-
// ★ Tampered: sign challenge token with a *different* keypair, but submit
|
|
142
|
-
// the JWS envelope under the registered account's key — server verifies
|
|
143
|
-
// the JWS sig (passes with account key) and then verifies the agent
|
|
144
|
-
// signature against the account key (fails).
|
|
145
|
-
const forger = await generateDualKeyPair();
|
|
146
|
-
const tokenBytes = new TextEncoder().encode(challenge!.token);
|
|
147
|
-
const forgedSig = ed25519.sign(tokenBytes, forger.privRaw);
|
|
148
|
-
|
|
149
|
-
const chEnv = Jws.sign(
|
|
150
|
-
{ alg: Jws.ALG_EDDSA, nonce, url: challenge!.url, kid: accountUrl },
|
|
151
|
-
{ agent_signature: Jws.b64uEncode(forgedSig) } as ChallengeRespondPayload,
|
|
152
|
-
fx.agentKeys.privRaw);
|
|
153
|
-
const chResp = await postJose(challenge!.url, chEnv);
|
|
154
|
-
|
|
155
|
-
expect(chResp.status).toBe(400);
|
|
156
|
-
const problem = await chResp.json() as ProblemDetail;
|
|
157
|
-
expect(problem.type).toBe(ec.ACME_CHALLENGE_FAILED);
|
|
158
|
-
} finally {
|
|
159
|
-
await fx.server.close();
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
165
|
-
|
|
166
|
-
async function postJose(url: string, env: Jws.Envelope): Promise<Response> {
|
|
167
|
-
return await fetch(url, {
|
|
168
|
-
method: "POST",
|
|
169
|
-
headers: { "Content-Type": wire.CONTENT_TYPE_JOSE_JSON },
|
|
170
|
-
body: JSON.stringify(env),
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function b64uEncode(bytes: Uint8Array): string {
|
|
175
|
-
return Buffer.from(bytes).toString("base64").replace(/=+$/, "")
|
|
176
|
-
.replace(/\+/g, "-").replace(/\//g, "_");
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function extractCn(dn: string): string | null {
|
|
180
|
-
for (const rdn of dn.split(",")) {
|
|
181
|
-
const t = rdn.trim();
|
|
182
|
-
if (t.startsWith("CN=")) {
|
|
183
|
-
let v = t.slice(3);
|
|
184
|
-
if (v.startsWith("\"") && v.endsWith("\"")) v = v.slice(1, -1);
|
|
185
|
-
return v.replace(/\\([",+;<>\\])/g, "$1");
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Touch the import so unused-symbol lint doesn't trip.
|
|
192
|
-
void randomHexSerial;
|