@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/src/nip/identity.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* NipIdentity — Ed25519 key management and signing for NPS NID identity.
|
|
6
|
-
* Uses @noble/ed25519 for signing; node:crypto for key storage encryption.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import * as ed25519 from "@noble/ed25519";
|
|
10
|
-
import { sha512 } from "@noble/hashes/sha512";
|
|
11
|
-
import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "node:crypto";
|
|
12
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
13
|
-
|
|
14
|
-
// noble/ed25519 requires sha512 to be set explicitly in Node environments
|
|
15
|
-
ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
|
|
16
|
-
|
|
17
|
-
const KEY_FILE_VERSION = 1;
|
|
18
|
-
const PBKDF2_ITERS = 600_000;
|
|
19
|
-
const SALT_BYTES = 16;
|
|
20
|
-
const IV_BYTES = 12;
|
|
21
|
-
const KEY_BYTES = 32;
|
|
22
|
-
|
|
23
|
-
interface KeyFileEnvelope {
|
|
24
|
-
version: number;
|
|
25
|
-
salt: string; // hex
|
|
26
|
-
iv: string; // hex
|
|
27
|
-
ciphertext: string; // hex
|
|
28
|
-
pubKey: string; // hex
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export class NipIdentity {
|
|
32
|
-
private constructor(
|
|
33
|
-
private readonly _privKey: Uint8Array,
|
|
34
|
-
public readonly pubKey: Uint8Array,
|
|
35
|
-
) {}
|
|
36
|
-
|
|
37
|
-
// ── Factory ───────────────────────────────────────────────────────────────
|
|
38
|
-
|
|
39
|
-
static generate(): NipIdentity {
|
|
40
|
-
const priv = ed25519.utils.randomPrivateKey();
|
|
41
|
-
const pub = ed25519.getPublicKey(priv);
|
|
42
|
-
return new NipIdentity(priv, pub);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
static fromPrivateKey(privKey: Uint8Array): NipIdentity {
|
|
46
|
-
const pub = ed25519.getPublicKey(privKey);
|
|
47
|
-
return new NipIdentity(privKey, pub);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Load from an AES-256-GCM encrypted key file. */
|
|
51
|
-
static load(path: string, passphrase: string): NipIdentity {
|
|
52
|
-
const envelope = JSON.parse(readFileSync(path, "utf8")) as KeyFileEnvelope;
|
|
53
|
-
const salt = Buffer.from(envelope.salt, "hex");
|
|
54
|
-
const iv = Buffer.from(envelope.iv, "hex");
|
|
55
|
-
const ct = Buffer.from(envelope.ciphertext, "hex");
|
|
56
|
-
|
|
57
|
-
const dk = pbkdf2Sync(passphrase, salt, PBKDF2_ITERS, KEY_BYTES, "sha256");
|
|
58
|
-
const decipher = createDecipheriv("aes-256-gcm", dk, iv);
|
|
59
|
-
// Last 16 bytes of ciphertext are the GCM auth tag
|
|
60
|
-
const authTag = ct.slice(ct.length - 16);
|
|
61
|
-
const body = ct.slice(0, ct.length - 16);
|
|
62
|
-
(decipher as ReturnType<typeof createDecipheriv> & { setAuthTag(tag: Buffer): void }).setAuthTag(authTag);
|
|
63
|
-
const priv = Buffer.concat([decipher.update(body), decipher.final()]);
|
|
64
|
-
return NipIdentity.fromPrivateKey(new Uint8Array(priv));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Save to an AES-256-GCM encrypted key file. */
|
|
68
|
-
save(path: string, passphrase: string): void {
|
|
69
|
-
const salt = randomBytes(SALT_BYTES);
|
|
70
|
-
const iv = randomBytes(IV_BYTES);
|
|
71
|
-
const dk = pbkdf2Sync(passphrase, salt, PBKDF2_ITERS, KEY_BYTES, "sha256");
|
|
72
|
-
const cipher = createCipheriv("aes-256-gcm", dk, iv);
|
|
73
|
-
const body = Buffer.concat([cipher.update(Buffer.from(this._privKey)), cipher.final()]);
|
|
74
|
-
const tag = (cipher as ReturnType<typeof createCipheriv> & { getAuthTag(): Buffer }).getAuthTag();
|
|
75
|
-
|
|
76
|
-
const envelope: KeyFileEnvelope = {
|
|
77
|
-
version: KEY_FILE_VERSION,
|
|
78
|
-
salt: salt.toString("hex"),
|
|
79
|
-
iv: iv.toString("hex"),
|
|
80
|
-
ciphertext: Buffer.concat([body, tag]).toString("hex"),
|
|
81
|
-
pubKey: Buffer.from(this.pubKey).toString("hex"),
|
|
82
|
-
};
|
|
83
|
-
writeFileSync(path, JSON.stringify(envelope, null, 2), "utf8");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ── Signing ───────────────────────────────────────────────────────────────
|
|
87
|
-
|
|
88
|
-
/** Sign a dict payload. Returns `ed25519:<base64url>`. */
|
|
89
|
-
sign(payload: Record<string, unknown>): string {
|
|
90
|
-
const canonical = JSON.stringify(payload, Object.keys(payload).sort());
|
|
91
|
-
const bytes = new TextEncoder().encode(canonical);
|
|
92
|
-
const sig = ed25519.sign(bytes, this._privKey);
|
|
93
|
-
return `ed25519:${Buffer.from(sig).toString("base64")}`;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Verify a signature string against a dict payload. */
|
|
97
|
-
verify(payload: Record<string, unknown>, signature: string): boolean {
|
|
98
|
-
if (!signature.startsWith("ed25519:")) return false;
|
|
99
|
-
try {
|
|
100
|
-
const canonical = JSON.stringify(payload, Object.keys(payload).sort());
|
|
101
|
-
const bytes = new TextEncoder().encode(canonical);
|
|
102
|
-
const sigBytes = Buffer.from(signature.slice("ed25519:".length), "base64");
|
|
103
|
-
return ed25519.verify(sigBytes, bytes, this.pubKey);
|
|
104
|
-
} catch {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Public key as `ed25519:<hex>` string. */
|
|
110
|
-
get pubKeyString(): string {
|
|
111
|
-
return `ed25519:${Buffer.from(this.pubKey).toString("hex")}`;
|
|
112
|
-
}
|
|
113
|
-
}
|
package/src/nip/registry.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import { FrameRegistry } from "../core/registry.js";
|
|
5
|
-
import { FrameType } from "../core/frames.js";
|
|
6
|
-
import { IdentFrame, TrustFrame, RevokeFrame } from "./frames.js";
|
|
7
|
-
|
|
8
|
-
export function registerNipFrames(registry: FrameRegistry): void {
|
|
9
|
-
registry.register(FrameType.IDENT, IdentFrame);
|
|
10
|
-
registry.register(FrameType.TRUST, TrustFrame);
|
|
11
|
-
registry.register(FrameType.REVOKE, RevokeFrame);
|
|
12
|
-
}
|
package/src/nip/verifier.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* NipIdentVerifier — Phase 1 dual-trust IdentFrame verifier per NPS-RFC-0002 §8.1.
|
|
6
|
-
*
|
|
7
|
-
* Steps:
|
|
8
|
-
* 1. v1 Ed25519 signature check against the issuer's CA public key.
|
|
9
|
-
* 2. Optional minimum assurance level check.
|
|
10
|
-
* 3b. X.509 chain validation (only if `cert_format === "v2-x509"` AND
|
|
11
|
-
* `trustedX509Roots` is configured).
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import * as ed25519 from "@noble/ed25519";
|
|
15
|
-
import { sha512 } from "@noble/hashes/sha512";
|
|
16
|
-
import type { X509Certificate } from "@peculiar/x509";
|
|
17
|
-
|
|
18
|
-
import { AssuranceLevel } from "./assurance-level.js";
|
|
19
|
-
import * as cf from "./cert-format.js";
|
|
20
|
-
import * as ec from "./error-codes.js";
|
|
21
|
-
import type { IdentFrame } from "./frames.js";
|
|
22
|
-
import { verify as verifyX509 } from "./x509/verifier.js";
|
|
23
|
-
|
|
24
|
-
// noble/ed25519 needs sha512 wired up.
|
|
25
|
-
ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
|
|
26
|
-
|
|
27
|
-
export interface NipVerifierOptions {
|
|
28
|
-
/** Map of issuer NID → CA public key string (`ed25519:<hex>`). */
|
|
29
|
-
trustedCaPublicKeys?: Readonly<Record<string, string>>;
|
|
30
|
-
/** X.509 trust anchors. Empty/undefined makes Step 3b reject v2 frames. */
|
|
31
|
-
trustedX509Roots?: readonly X509Certificate[];
|
|
32
|
-
/** Minimum required assurance level (NPS-RFC-0003). */
|
|
33
|
-
minAssuranceLevel?: AssuranceLevel;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface NipIdentVerifyResult {
|
|
37
|
-
valid: boolean;
|
|
38
|
-
stepFailed: number; // 0 = none, 1 = sig, 2 = assurance, 3 = X.509
|
|
39
|
-
errorCode?: string;
|
|
40
|
-
message?: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function ok(): NipIdentVerifyResult { return { valid: true, stepFailed: 0 }; }
|
|
44
|
-
|
|
45
|
-
function fail(stepFailed: number, errorCode: string, message: string): NipIdentVerifyResult {
|
|
46
|
-
return { valid: false, stepFailed, errorCode, message };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export class NipIdentVerifier {
|
|
50
|
-
constructor(public readonly options: NipVerifierOptions) {}
|
|
51
|
-
|
|
52
|
-
async verify(frame: IdentFrame, issuerNid: string): Promise<NipIdentVerifyResult> {
|
|
53
|
-
// Step 1: v1 Ed25519 signature check ────────────────────────────────
|
|
54
|
-
const caPubKeyStr = this.options.trustedCaPublicKeys?.[issuerNid];
|
|
55
|
-
if (caPubKeyStr === undefined) {
|
|
56
|
-
return fail(1, ec.CERT_UNTRUSTED_ISSUER,
|
|
57
|
-
`no trusted CA public key for issuer: ${issuerNid}`);
|
|
58
|
-
}
|
|
59
|
-
if (!frame.signature?.startsWith("ed25519:")) {
|
|
60
|
-
return fail(1, ec.CERT_SIGNATURE_INVALID, "missing or malformed signature");
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
const caPubBytes = parsePubKeyString(caPubKeyStr);
|
|
64
|
-
const sigBytes = Buffer.from(frame.signature.slice("ed25519:".length), "base64");
|
|
65
|
-
const canonical = canonicalJson(frame.unsignedDict());
|
|
66
|
-
const msg = new TextEncoder().encode(canonical);
|
|
67
|
-
if (!ed25519.verify(sigBytes, msg, caPubBytes)) {
|
|
68
|
-
return fail(1, ec.CERT_SIGNATURE_INVALID,
|
|
69
|
-
"v1 Ed25519 signature did not verify against issuer CA key");
|
|
70
|
-
}
|
|
71
|
-
} catch (e) {
|
|
72
|
-
return fail(1, ec.CERT_SIGNATURE_INVALID,
|
|
73
|
-
`v1 signature verification error: ${(e as Error).message}`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Step 2: minimum assurance level ───────────────────────────────────
|
|
77
|
-
const minLevel = this.options.minAssuranceLevel;
|
|
78
|
-
if (minLevel !== undefined) {
|
|
79
|
-
const got = frame.assuranceLevel ?? AssuranceLevel.ANONYMOUS;
|
|
80
|
-
if (!got.meetsOrExceeds(minLevel)) {
|
|
81
|
-
return fail(2, ec.ASSURANCE_MISMATCH,
|
|
82
|
-
`assurance_level (${got.wire}) below required minimum (${minLevel.wire})`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Step 3b: X.509 chain check (only if both opt-ins present) ──────────
|
|
87
|
-
const trustedRoots = this.options.trustedX509Roots ?? [];
|
|
88
|
-
const hasV2Trust = trustedRoots.length > 0;
|
|
89
|
-
const isV2Frame = frame.certFormat === cf.V2_X509;
|
|
90
|
-
if (hasV2Trust && isV2Frame) {
|
|
91
|
-
const x509Result = await verifyX509({
|
|
92
|
-
certChainBase64UrlDer: frame.certChain ?? [],
|
|
93
|
-
assertedNid: frame.nid,
|
|
94
|
-
assertedAssuranceLevel: frame.assuranceLevel,
|
|
95
|
-
trustedRootCerts: trustedRoots,
|
|
96
|
-
});
|
|
97
|
-
if (!x509Result.valid) {
|
|
98
|
-
return fail(3,
|
|
99
|
-
x509Result.errorCode ?? ec.CERT_FORMAT_INVALID,
|
|
100
|
-
x509Result.message ?? "X.509 chain validation failed");
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return ok();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Canonical JSON matching NipIdentity.sign — top-level keys filtered/ordered
|
|
110
|
-
* via `Object.keys(payload).sort()` as JSON.stringify replacer.
|
|
111
|
-
*/
|
|
112
|
-
function canonicalJson(payload: Record<string, unknown>): string {
|
|
113
|
-
return JSON.stringify(payload, Object.keys(payload).sort());
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Parse `ed25519:<hex>` into a 32-byte Uint8Array public key. */
|
|
117
|
-
function parsePubKeyString(s: string): Uint8Array {
|
|
118
|
-
if (!s.startsWith("ed25519:")) {
|
|
119
|
-
throw new Error(`Unsupported public key format: ${s}`);
|
|
120
|
-
}
|
|
121
|
-
return new Uint8Array(Buffer.from(s.slice("ed25519:".length), "hex"));
|
|
122
|
-
}
|
package/src/nip/x509/builder.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Issues NPS X.509 NID certificates per NPS-RFC-0002 §4.
|
|
6
|
-
*
|
|
7
|
-
* Backed by @peculiar/x509 + Web Crypto Ed25519 (Node 22+).
|
|
8
|
-
*
|
|
9
|
-
* Two factory functions:
|
|
10
|
-
* - {@link issueLeaf} — leaf cert with critical NPS EKU + SAN URI = NID + assurance-level extension.
|
|
11
|
-
* - {@link issueRoot} — self-signed root for testing / private-CA use.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import * as x509 from "@peculiar/x509";
|
|
15
|
-
|
|
16
|
-
import { AssuranceLevel } from "../assurance-level.js";
|
|
17
|
-
import { EKU_AGENT_IDENTITY, EKU_NODE_IDENTITY, NID_ASSURANCE_LEVEL } from "./oids.js";
|
|
18
|
-
|
|
19
|
-
// Initialize @peculiar/x509 cryptoProvider once on first import. Web Crypto
|
|
20
|
-
// (globalThis.crypto) supports Ed25519 in Node 18+.
|
|
21
|
-
x509.cryptoProvider.set(globalThis.crypto);
|
|
22
|
-
|
|
23
|
-
export type LeafRole = "agent" | "node";
|
|
24
|
-
|
|
25
|
-
export interface IssueLeafOptions {
|
|
26
|
-
subjectNid: string;
|
|
27
|
-
subjectPublicKey: CryptoKey; // Ed25519 public key (Web Crypto)
|
|
28
|
-
caKeys: CryptoKeyPair; // CA's keypair (we need privateKey to sign)
|
|
29
|
-
issuerNid: string;
|
|
30
|
-
role: LeafRole;
|
|
31
|
-
assuranceLevel: AssuranceLevel;
|
|
32
|
-
notBefore: Date;
|
|
33
|
-
notAfter: Date;
|
|
34
|
-
serialNumber: string; // hex string, no "0x" prefix
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface IssueRootOptions {
|
|
38
|
-
caNid: string;
|
|
39
|
-
caKeys: CryptoKeyPair;
|
|
40
|
-
notBefore: Date;
|
|
41
|
-
notAfter: Date;
|
|
42
|
-
serialNumber: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Issue a leaf NPS NID certificate (RFC-0002 §4.1). */
|
|
46
|
-
export async function issueLeaf(opts: IssueLeafOptions): Promise<x509.X509Certificate> {
|
|
47
|
-
const ekuOid = opts.role === "node" ? EKU_NODE_IDENTITY : EKU_AGENT_IDENTITY;
|
|
48
|
-
|
|
49
|
-
// ASN.1 ENUMERATED encoding of assurance level: tag=0x0A, len=0x01, value=<rank>.
|
|
50
|
-
const assuranceDer = new Uint8Array([0x0A, 0x01, opts.assuranceLevel.rank]);
|
|
51
|
-
|
|
52
|
-
return x509.X509CertificateGenerator.create({
|
|
53
|
-
serialNumber: opts.serialNumber,
|
|
54
|
-
issuer: `CN=${escapeDn(opts.issuerNid)}`,
|
|
55
|
-
subject: `CN=${escapeDn(opts.subjectNid)}`,
|
|
56
|
-
notBefore: opts.notBefore,
|
|
57
|
-
notAfter: opts.notAfter,
|
|
58
|
-
publicKey: opts.subjectPublicKey,
|
|
59
|
-
signingAlgorithm: { name: "Ed25519" },
|
|
60
|
-
signingKey: opts.caKeys.privateKey,
|
|
61
|
-
extensions: [
|
|
62
|
-
new x509.BasicConstraintsExtension(false, undefined, true),
|
|
63
|
-
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true),
|
|
64
|
-
new x509.ExtendedKeyUsageExtension([ekuOid], true),
|
|
65
|
-
new x509.SubjectAlternativeNameExtension([{ type: "url", value: opts.subjectNid }], false),
|
|
66
|
-
new x509.Extension(NID_ASSURANCE_LEVEL, false, assuranceDer),
|
|
67
|
-
],
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Issue a self-signed CA root cert (testing / private CA). */
|
|
72
|
-
export async function issueRoot(opts: IssueRootOptions): Promise<x509.X509Certificate> {
|
|
73
|
-
return x509.X509CertificateGenerator.createSelfSigned({
|
|
74
|
-
serialNumber: opts.serialNumber,
|
|
75
|
-
name: `CN=${escapeDn(opts.caNid)}`,
|
|
76
|
-
notBefore: opts.notBefore,
|
|
77
|
-
notAfter: opts.notAfter,
|
|
78
|
-
signingAlgorithm: { name: "Ed25519" },
|
|
79
|
-
keys: opts.caKeys,
|
|
80
|
-
extensions: [
|
|
81
|
-
new x509.BasicConstraintsExtension(true, undefined, true),
|
|
82
|
-
new x509.KeyUsagesExtension(
|
|
83
|
-
x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
|
84
|
-
],
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function escapeDn(value: string): string {
|
|
89
|
-
// Escape characters that have special meaning in RFC 4514 DN syntax.
|
|
90
|
-
return value.replace(/([",+;<>\\])/g, "\\$1");
|
|
91
|
-
}
|
package/src/nip/x509/verifier.ts
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Verifies NPS X.509 NID certificate chains per NPS-RFC-0002 §4.
|
|
6
|
-
*
|
|
7
|
-
* Stages (RFC §4.6):
|
|
8
|
-
* 1. Decode chain (base64url DER → @peculiar/x509 X509Certificate).
|
|
9
|
-
* 2. Leaf EKU check — critical, contains agent-identity OR node-identity OID.
|
|
10
|
-
* 3. Subject CN / SAN URI match against asserted NID.
|
|
11
|
-
* 4. Assurance-level extension match against asserted level (if both present).
|
|
12
|
-
* 5. Chain signature verification — leaf → intermediates → trusted root.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import * as x509 from "@peculiar/x509";
|
|
16
|
-
|
|
17
|
-
import { AssuranceLevel } from "../assurance-level.js";
|
|
18
|
-
import * as ec from "../error-codes.js";
|
|
19
|
-
import {
|
|
20
|
-
EKU_AGENT_IDENTITY,
|
|
21
|
-
EKU_NODE_IDENTITY,
|
|
22
|
-
NID_ASSURANCE_LEVEL,
|
|
23
|
-
OID_EXTENDED_KEY_USAGE,
|
|
24
|
-
} from "./oids.js";
|
|
25
|
-
|
|
26
|
-
x509.cryptoProvider.set(globalThis.crypto);
|
|
27
|
-
|
|
28
|
-
export interface NipX509VerifyResult {
|
|
29
|
-
valid: boolean;
|
|
30
|
-
errorCode?: string;
|
|
31
|
-
message?: string;
|
|
32
|
-
leaf?: x509.X509Certificate;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function ok(leaf: x509.X509Certificate): NipX509VerifyResult {
|
|
36
|
-
return { valid: true, leaf };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function fail(errorCode: string, message: string): NipX509VerifyResult {
|
|
40
|
-
return { valid: false, errorCode, message };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface VerifyOptions {
|
|
44
|
-
certChainBase64UrlDer: readonly string[];
|
|
45
|
-
assertedNid: string;
|
|
46
|
-
assertedAssuranceLevel: AssuranceLevel | null;
|
|
47
|
-
trustedRootCerts: readonly x509.X509Certificate[];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export async function verify(opts: VerifyOptions): Promise<NipX509VerifyResult> {
|
|
51
|
-
// Stage 1: decode chain ─────────────────────────────────────────────────
|
|
52
|
-
if (!opts.certChainBase64UrlDer.length) {
|
|
53
|
-
return fail(ec.CERT_FORMAT_INVALID, "cert_chain is empty");
|
|
54
|
-
}
|
|
55
|
-
let chain: x509.X509Certificate[];
|
|
56
|
-
try {
|
|
57
|
-
chain = opts.certChainBase64UrlDer.map((s) => new x509.X509Certificate(b64uDecode(s).buffer as ArrayBuffer));
|
|
58
|
-
} catch (e) {
|
|
59
|
-
return fail(ec.CERT_FORMAT_INVALID, `DER decode failed: ${(e as Error).message}`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const leaf = chain[0];
|
|
63
|
-
|
|
64
|
-
// Stage 2: EKU check ────────────────────────────────────────────────────
|
|
65
|
-
const ekuResult = checkLeafEku(leaf);
|
|
66
|
-
if (!ekuResult.valid) return ekuResult;
|
|
67
|
-
|
|
68
|
-
// Stage 3: subject CN / SAN URI match ──────────────────────────────────
|
|
69
|
-
const subjectResult = checkSubjectNid(leaf, opts.assertedNid);
|
|
70
|
-
if (!subjectResult.valid) return subjectResult;
|
|
71
|
-
|
|
72
|
-
// Stage 4: assurance-level extension ───────────────────────────────────
|
|
73
|
-
const assuranceResult = checkAssuranceLevel(leaf, opts.assertedAssuranceLevel);
|
|
74
|
-
if (!assuranceResult.valid) return assuranceResult;
|
|
75
|
-
|
|
76
|
-
// Stage 5: chain signature verification ────────────────────────────────
|
|
77
|
-
const chainResult = await checkChainSignature(chain, opts.trustedRootCerts);
|
|
78
|
-
if (!chainResult.valid) return chainResult;
|
|
79
|
-
|
|
80
|
-
return ok(leaf);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ── Stage helpers ──────────────────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
function checkLeafEku(leaf: x509.X509Certificate): NipX509VerifyResult {
|
|
86
|
-
const ekuExt = leaf.extensions.find(
|
|
87
|
-
(e) => e.type === OID_EXTENDED_KEY_USAGE,
|
|
88
|
-
) as x509.ExtendedKeyUsageExtension | undefined;
|
|
89
|
-
if (!ekuExt) {
|
|
90
|
-
return fail(ec.CERT_EKU_MISSING, "leaf has no ExtendedKeyUsage extension");
|
|
91
|
-
}
|
|
92
|
-
if (!ekuExt.critical) {
|
|
93
|
-
return fail(ec.CERT_EKU_MISSING, "ExtendedKeyUsage extension is not marked critical");
|
|
94
|
-
}
|
|
95
|
-
const usages = ekuExt.usages as readonly string[];
|
|
96
|
-
if (!usages.includes(EKU_AGENT_IDENTITY) && !usages.includes(EKU_NODE_IDENTITY)) {
|
|
97
|
-
return fail(ec.CERT_EKU_MISSING,
|
|
98
|
-
"ExtendedKeyUsage does not contain agent-identity or node-identity OID");
|
|
99
|
-
}
|
|
100
|
-
return ok(leaf);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function checkSubjectNid(leaf: x509.X509Certificate, assertedNid: string): NipX509VerifyResult {
|
|
104
|
-
// @peculiar/x509 parses the subject DN; iterate to find CN.
|
|
105
|
-
const cn = extractCn(leaf.subject);
|
|
106
|
-
if (cn !== assertedNid) {
|
|
107
|
-
return fail(ec.CERT_SUBJECT_NID_MISMATCH,
|
|
108
|
-
`leaf subject CN (${cn ?? "<missing>"}) does not match asserted NID (${assertedNid})`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const sanExt = leaf.getExtension(x509.SubjectAlternativeNameExtension);
|
|
112
|
-
if (!sanExt) {
|
|
113
|
-
return fail(ec.CERT_SUBJECT_NID_MISMATCH, "leaf has no Subject Alternative Name extension");
|
|
114
|
-
}
|
|
115
|
-
// SubjectAlternativeNameExtension exposes general-name objects with `type: "url"` for URIs.
|
|
116
|
-
const uris = sanExt.names
|
|
117
|
-
.toJSON()
|
|
118
|
-
.filter((n: { type: string }) => n.type === "url")
|
|
119
|
-
.map((n: { value: string }) => n.value);
|
|
120
|
-
if (!uris.includes(assertedNid)) {
|
|
121
|
-
return fail(ec.CERT_SUBJECT_NID_MISMATCH, "no SAN URI matches asserted NID");
|
|
122
|
-
}
|
|
123
|
-
return ok(leaf);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function checkAssuranceLevel(
|
|
127
|
-
leaf: x509.X509Certificate, asserted: AssuranceLevel | null,
|
|
128
|
-
): NipX509VerifyResult {
|
|
129
|
-
if (asserted === null) return ok(leaf);
|
|
130
|
-
const ext = leaf.extensions.find((e) => e.type === NID_ASSURANCE_LEVEL);
|
|
131
|
-
if (!ext) {
|
|
132
|
-
// Optional in v0.1 — pass silently.
|
|
133
|
-
return ok(leaf);
|
|
134
|
-
}
|
|
135
|
-
const der = new Uint8Array(ext.value);
|
|
136
|
-
// Decode ASN.1 ENUMERATED: tag=0x0A, len=0x01, content=<rank>.
|
|
137
|
-
if (der.length !== 3 || der[0] !== 0x0A || der[1] !== 0x01) {
|
|
138
|
-
return fail(ec.CERT_FORMAT_INVALID,
|
|
139
|
-
`malformed assurance-level extension: ${Buffer.from(der).toString("hex")}`);
|
|
140
|
-
}
|
|
141
|
-
const rank = der[2];
|
|
142
|
-
let certLevel: AssuranceLevel;
|
|
143
|
-
try {
|
|
144
|
-
certLevel = AssuranceLevel.fromRank(rank);
|
|
145
|
-
} catch {
|
|
146
|
-
return fail(ec.ASSURANCE_UNKNOWN,
|
|
147
|
-
`assurance-level extension contains unknown value: ${rank}`);
|
|
148
|
-
}
|
|
149
|
-
if (certLevel !== asserted) {
|
|
150
|
-
return fail(ec.ASSURANCE_MISMATCH,
|
|
151
|
-
`cert assurance-level (${certLevel.wire}) does not match asserted (${asserted.wire})`);
|
|
152
|
-
}
|
|
153
|
-
return ok(leaf);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async function checkChainSignature(
|
|
157
|
-
chain: readonly x509.X509Certificate[],
|
|
158
|
-
trustedRoots: readonly x509.X509Certificate[],
|
|
159
|
-
): Promise<NipX509VerifyResult> {
|
|
160
|
-
if (!trustedRoots.length) {
|
|
161
|
-
return fail(ec.CERT_FORMAT_INVALID, "no trusted X.509 roots configured");
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
// Walk leaf → intermediates: each must be signed by its successor.
|
|
165
|
-
for (let i = 0; i < chain.length - 1; i++) {
|
|
166
|
-
const okStep = await chain[i].verify({ publicKey: await chain[i + 1].publicKey.export(), signatureOnly: true });
|
|
167
|
-
if (!okStep) {
|
|
168
|
-
return fail(ec.CERT_FORMAT_INVALID, `chain link ${i} signature did not verify`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// The last cert in the chain MUST chain to a trusted root.
|
|
172
|
-
const last = chain[chain.length - 1];
|
|
173
|
-
for (const root of trustedRoots) {
|
|
174
|
-
if (Buffer.from(last.rawData).equals(Buffer.from(root.rawData))) {
|
|
175
|
-
return ok(chain[0]);
|
|
176
|
-
}
|
|
177
|
-
try {
|
|
178
|
-
const okStep = await last.verify({ publicKey: await root.publicKey.export(), signatureOnly: true });
|
|
179
|
-
if (okStep) return ok(chain[0]);
|
|
180
|
-
} catch {
|
|
181
|
-
/* try next root */
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return fail(ec.CERT_FORMAT_INVALID, "chain does not anchor to any trusted root");
|
|
185
|
-
} catch (e) {
|
|
186
|
-
return fail(ec.CERT_FORMAT_INVALID,
|
|
187
|
-
`chain signature verification error: ${(e as Error).message}`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ── helpers ────────────────────────────────────────────────────────────────
|
|
192
|
-
|
|
193
|
-
function extractCn(dn: string): string | null {
|
|
194
|
-
// @peculiar/x509 returns DN strings in RFC 4514 format ("CN=...,O=...").
|
|
195
|
-
for (const rdn of dn.split(",")) {
|
|
196
|
-
const trimmed = rdn.trim();
|
|
197
|
-
if (trimmed.startsWith("CN=")) {
|
|
198
|
-
let value = trimmed.slice(3);
|
|
199
|
-
// Strip surrounding quotes if any.
|
|
200
|
-
if (value.startsWith("\"") && value.endsWith("\"")) {
|
|
201
|
-
value = value.slice(1, -1);
|
|
202
|
-
}
|
|
203
|
-
// Unescape.
|
|
204
|
-
return value.replace(/\\([",+;<>\\])/g, "$1");
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function b64uDecode(s: string): Uint8Array {
|
|
211
|
-
const padded = s + "=".repeat((4 - (s.length % 4)) % 4);
|
|
212
|
-
const std = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
213
|
-
return new Uint8Array(Buffer.from(std, "base64"));
|
|
214
|
-
}
|
package/src/nop/client.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import { NpsFrameCodec } from "../core/codec.js";
|
|
5
|
-
import { EncodingTier } from "../core/frames.js";
|
|
6
|
-
import { FrameRegistry } from "../core/registry.js";
|
|
7
|
-
import { registerNcpFrames } from "../ncp/registry.js";
|
|
8
|
-
import { registerNopFrames } from "./registry.js";
|
|
9
|
-
import { TaskState } from "./models.js";
|
|
10
|
-
import type { TaskFrame } from "./frames.js";
|
|
11
|
-
|
|
12
|
-
const TERMINAL_STATES = new Set<TaskState>([
|
|
13
|
-
TaskState.COMPLETED,
|
|
14
|
-
TaskState.FAILED,
|
|
15
|
-
TaskState.CANCELLED,
|
|
16
|
-
]);
|
|
17
|
-
|
|
18
|
-
export class NopTaskStatus {
|
|
19
|
-
constructor(private readonly _raw: Record<string, unknown>) {}
|
|
20
|
-
|
|
21
|
-
get taskId(): string { return this._raw["task_id"] as string; }
|
|
22
|
-
get state(): TaskState { return this._raw["state"] as TaskState; }
|
|
23
|
-
get isTerminal(): boolean { return TERMINAL_STATES.has(this._raw["state"] as TaskState); }
|
|
24
|
-
get aggregatedResult(): unknown { return this._raw["aggregated_result"]; }
|
|
25
|
-
get errorCode(): string | undefined { return (this._raw["error_code"] as string | null) ?? undefined; }
|
|
26
|
-
get errorMessage(): string | undefined { return (this._raw["error_message"] as string | null) ?? undefined; }
|
|
27
|
-
get nodeResults(): Record<string, unknown> { return (this._raw["node_results"] as Record<string, unknown> | undefined) ?? {}; }
|
|
28
|
-
get raw(): Record<string, unknown> { return this._raw; }
|
|
29
|
-
|
|
30
|
-
toString(): string {
|
|
31
|
-
return `NopTaskStatus(taskId=${this.taskId}, state=${String(this._raw["state"])})`;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export class NopClient {
|
|
36
|
-
private readonly _baseUrl: string;
|
|
37
|
-
private readonly _codec: NpsFrameCodec;
|
|
38
|
-
private readonly _tier: EncodingTier;
|
|
39
|
-
|
|
40
|
-
constructor(
|
|
41
|
-
baseUrl: string,
|
|
42
|
-
options: { defaultTier?: EncodingTier; registry?: FrameRegistry } = {},
|
|
43
|
-
) {
|
|
44
|
-
this._baseUrl = baseUrl.replace(/\/$/, "");
|
|
45
|
-
this._tier = options.defaultTier ?? EncodingTier.MSGPACK;
|
|
46
|
-
|
|
47
|
-
const registry = options.registry ?? (() => {
|
|
48
|
-
const r = new FrameRegistry();
|
|
49
|
-
registerNcpFrames(r);
|
|
50
|
-
registerNopFrames(r);
|
|
51
|
-
return r;
|
|
52
|
-
})();
|
|
53
|
-
this._codec = new NpsFrameCodec(registry);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async submit(frame: TaskFrame): Promise<string> {
|
|
57
|
-
const wire = this._codec.encode(frame, { overrideTier: this._tier });
|
|
58
|
-
const res = await fetch(`${this._baseUrl}/task`, {
|
|
59
|
-
method: "POST",
|
|
60
|
-
body: wire as BodyInit,
|
|
61
|
-
headers: { "Content-Type": "application/x-nps-frame", "Accept": "application/json" },
|
|
62
|
-
});
|
|
63
|
-
if (!res.ok) throw new Error(`NOP /task failed: HTTP ${res.status}`);
|
|
64
|
-
const data = await res.json() as Record<string, unknown>;
|
|
65
|
-
return data["task_id"] as string;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async getStatus(taskId: string): Promise<NopTaskStatus> {
|
|
69
|
-
const res = await fetch(`${this._baseUrl}/task/${taskId}`, {
|
|
70
|
-
headers: { "Accept": "application/json" },
|
|
71
|
-
});
|
|
72
|
-
if (!res.ok) throw new Error(`NOP /task/${taskId} failed: HTTP ${res.status}`);
|
|
73
|
-
return new NopTaskStatus(await res.json() as Record<string, unknown>);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async cancel(taskId: string): Promise<void> {
|
|
77
|
-
const res = await fetch(`${this._baseUrl}/task/${taskId}/cancel`, {
|
|
78
|
-
method: "POST",
|
|
79
|
-
headers: { "Accept": "application/json" },
|
|
80
|
-
});
|
|
81
|
-
if (!res.ok) throw new Error(`NOP /task/${taskId}/cancel failed: HTTP ${res.status}`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async wait(
|
|
85
|
-
taskId: string,
|
|
86
|
-
options: { pollIntervalMs?: number; timeoutMs?: number } = {},
|
|
87
|
-
): Promise<NopTaskStatus> {
|
|
88
|
-
const pollIntervalMs = options.pollIntervalMs ?? 1000;
|
|
89
|
-
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
90
|
-
const deadline = Date.now() + timeoutMs;
|
|
91
|
-
|
|
92
|
-
while (true) {
|
|
93
|
-
const status = await this.getStatus(taskId);
|
|
94
|
-
if (status.isTerminal) return status;
|
|
95
|
-
|
|
96
|
-
const remaining = deadline - Date.now();
|
|
97
|
-
if (remaining <= 0) {
|
|
98
|
-
throw new Error(`Task '${taskId}' did not complete within ${timeoutMs}ms (state: ${String(status.raw["state"])}).`);
|
|
99
|
-
}
|
|
100
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(pollIntervalMs, remaining)));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|