@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
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@labacacia/nip-ca-server",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"description": "NIP CA Server — TypeScript/Node.js",
|
|
5
|
-
"license": "Apache-2.0",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "dist/index.js",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"build": "tsc",
|
|
10
|
-
"start": "node dist/index.js",
|
|
11
|
-
"dev": "tsx src/index.ts"
|
|
12
|
-
},
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"better-sqlite3": "^11.7.0",
|
|
15
|
-
"fastify": "^5.2.1"
|
|
16
|
-
},
|
|
17
|
-
"devDependencies": {
|
|
18
|
-
"@types/better-sqlite3": "^7.6.12",
|
|
19
|
-
"@types/node": "^22.10.5",
|
|
20
|
-
"tsx": "^4.19.2",
|
|
21
|
-
"typescript": "^5.7.3"
|
|
22
|
-
}
|
|
23
|
-
}
|
package/nip-ca-server/src/ca.ts
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
import * as crypto from "node:crypto";
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import * as path from "node:path";
|
|
6
|
-
|
|
7
|
-
const PBKDF2_ITERS = 600_000;
|
|
8
|
-
const SALT_LEN = 16;
|
|
9
|
-
const NONCE_LEN = 12;
|
|
10
|
-
const KEY_LEN = 32;
|
|
11
|
-
|
|
12
|
-
export interface KeyEnvelope {
|
|
13
|
-
version: number;
|
|
14
|
-
algorithm: string;
|
|
15
|
-
pub_key: string;
|
|
16
|
-
salt: string;
|
|
17
|
-
nonce: string;
|
|
18
|
-
ciphertext: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function deriveKey(passphrase: string, salt: Buffer): Buffer {
|
|
22
|
-
return crypto.pbkdf2Sync(passphrase, salt, PBKDF2_ITERS, KEY_LEN, "sha256");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function pubKeyString(pubKey: crypto.KeyObject): string {
|
|
26
|
-
const raw = pubKey.export({ type: "pkcs8", format: "der" });
|
|
27
|
-
// Ed25519 PKCS8 DER = 44 bytes; raw public key is last 32 bytes
|
|
28
|
-
const rawPub = raw.subarray(raw.length - 32);
|
|
29
|
-
return "ed25519:" + rawPub.toString("hex");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function rawPrivateBytes(privKey: crypto.KeyObject): Buffer {
|
|
33
|
-
// Ed25519 private key in JWK → d field is base64url-encoded 32-byte seed
|
|
34
|
-
const jwk = privKey.export({ format: "jwk" }) as { d: string };
|
|
35
|
-
return Buffer.from(jwk.d, "base64url");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function generateKey(): crypto.KeyObject {
|
|
39
|
-
const { privateKey } = crypto.generateKeyPairSync("ed25519");
|
|
40
|
-
return privateKey;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function saveKey(privKey: crypto.KeyObject, filePath: string, passphrase: string): void {
|
|
44
|
-
const salt = crypto.randomBytes(SALT_LEN);
|
|
45
|
-
const nonce = crypto.randomBytes(NONCE_LEN);
|
|
46
|
-
const dk = deriveKey(passphrase, salt);
|
|
47
|
-
const seed = rawPrivateBytes(privKey);
|
|
48
|
-
const cipher = crypto.createCipheriv("aes-256-gcm", dk, nonce);
|
|
49
|
-
const encrypted = Buffer.concat([cipher.update(seed), cipher.final()]);
|
|
50
|
-
const tag = cipher.getAuthTag();
|
|
51
|
-
const ciphertext = Buffer.concat([encrypted, tag]);
|
|
52
|
-
const { privateKey: tmpPriv, publicKey: tmpPub } = crypto.generateKeyPairSync("ed25519", {
|
|
53
|
-
privateKeyEncoding: { type: "pkcs8", format: "der" },
|
|
54
|
-
publicKeyEncoding: { type: "spki", format: "der" },
|
|
55
|
-
});
|
|
56
|
-
// Re-derive public key from the same private key
|
|
57
|
-
const pubStr = pubKeyString(crypto.createPublicKey(privKey));
|
|
58
|
-
const envelope: KeyEnvelope = {
|
|
59
|
-
version: 1,
|
|
60
|
-
algorithm: "ed25519",
|
|
61
|
-
pub_key: pubStr,
|
|
62
|
-
salt: salt.toString("hex"),
|
|
63
|
-
nonce: nonce.toString("hex"),
|
|
64
|
-
ciphertext: ciphertext.toString("hex"),
|
|
65
|
-
};
|
|
66
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
67
|
-
fs.writeFileSync(filePath, JSON.stringify(envelope), { mode: 0o600 });
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function loadKey(filePath: string, passphrase: string): crypto.KeyObject {
|
|
71
|
-
const env: KeyEnvelope = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
72
|
-
const salt = Buffer.from(env.salt, "hex");
|
|
73
|
-
const nonce = Buffer.from(env.nonce, "hex");
|
|
74
|
-
const ctBuf = Buffer.from(env.ciphertext, "hex");
|
|
75
|
-
const tag = ctBuf.subarray(ctBuf.length - 16);
|
|
76
|
-
const ciphertext = ctBuf.subarray(0, ctBuf.length - 16);
|
|
77
|
-
const dk = deriveKey(passphrase, salt);
|
|
78
|
-
const decipher = crypto.createDecipheriv("aes-256-gcm", dk, nonce);
|
|
79
|
-
decipher.setAuthTag(tag);
|
|
80
|
-
let seed: Buffer;
|
|
81
|
-
try {
|
|
82
|
-
seed = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
83
|
-
} catch {
|
|
84
|
-
throw new Error("Key decryption failed — wrong passphrase?");
|
|
85
|
-
}
|
|
86
|
-
return crypto.createPrivateKey({ key: seed, format: "der", type: "pkcs8" } as any) ||
|
|
87
|
-
(() => {
|
|
88
|
-
// Construct PKCS8 DER for Ed25519 seed manually
|
|
89
|
-
// PKCS8 for Ed25519 = 0x302e020100300506032b657004220420 + 32-byte seed
|
|
90
|
-
const pkcs8Header = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
91
|
-
const der = Buffer.concat([pkcs8Header, seed]);
|
|
92
|
-
return crypto.createPrivateKey({ key: der, format: "der", type: "pkcs8" });
|
|
93
|
-
})();
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function canonicalJson(obj: Record<string, unknown>): Buffer {
|
|
97
|
-
const sorted = Object.keys(obj).sort().reduce((acc, k) => {
|
|
98
|
-
(acc as any)[k] = (obj as any)[k];
|
|
99
|
-
return acc;
|
|
100
|
-
}, {} as Record<string, unknown>);
|
|
101
|
-
return Buffer.from(JSON.stringify(sorted), "utf8");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function signDict(privKey: crypto.KeyObject, obj: Record<string, unknown>): string {
|
|
105
|
-
const sig = crypto.sign(null, canonicalJson(obj), privKey);
|
|
106
|
-
return "ed25519:" + sig.toString("base64");
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export interface IssuedCert {
|
|
110
|
-
nid: string;
|
|
111
|
-
pub_key: string;
|
|
112
|
-
capabilities: string[];
|
|
113
|
-
scope: Record<string, unknown>;
|
|
114
|
-
issued_by: string;
|
|
115
|
-
issued_at: string;
|
|
116
|
-
expires_at: string;
|
|
117
|
-
serial: string;
|
|
118
|
-
signature: string;
|
|
119
|
-
metadata?: Record<string, unknown>;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function issueCert(
|
|
123
|
-
privKey: crypto.KeyObject,
|
|
124
|
-
caNid: string,
|
|
125
|
-
subjectNid: string,
|
|
126
|
-
subjectPubKey: string,
|
|
127
|
-
capabilities: string[],
|
|
128
|
-
scope: Record<string, unknown>,
|
|
129
|
-
validityDays: number,
|
|
130
|
-
serial: string,
|
|
131
|
-
metadata?: Record<string, unknown> | null,
|
|
132
|
-
): IssuedCert {
|
|
133
|
-
const now = new Date();
|
|
134
|
-
const expires = new Date(now.getTime() + validityDays * 86400_000);
|
|
135
|
-
const fmt = (d: Date) => d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
136
|
-
const unsigned: Record<string, unknown> = {
|
|
137
|
-
capabilities,
|
|
138
|
-
expires_at: fmt(expires),
|
|
139
|
-
issued_at: fmt(now),
|
|
140
|
-
issued_by: caNid,
|
|
141
|
-
nid: subjectNid,
|
|
142
|
-
pub_key: subjectPubKey,
|
|
143
|
-
scope,
|
|
144
|
-
serial,
|
|
145
|
-
};
|
|
146
|
-
const signature = signDict(privKey, unsigned);
|
|
147
|
-
const cert: IssuedCert = { ...unsigned as any, signature };
|
|
148
|
-
if (metadata) cert.metadata = metadata;
|
|
149
|
-
return cert;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function generateNid(domain: string, entityType: string): string {
|
|
153
|
-
const uid = crypto.randomBytes(8).toString("hex");
|
|
154
|
-
return `urn:nps:${entityType}:${domain}:${uid}`;
|
|
155
|
-
}
|
package/nip-ca-server/src/db.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
import Database from "better-sqlite3";
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import * as path from "node:path";
|
|
6
|
-
|
|
7
|
-
const SCHEMA_PATH = path.join(__dirname, "..", "db", "001_init.sql");
|
|
8
|
-
|
|
9
|
-
export interface CertRecord {
|
|
10
|
-
id: number;
|
|
11
|
-
nid: string;
|
|
12
|
-
entity_type: string;
|
|
13
|
-
serial: string;
|
|
14
|
-
pub_key: string;
|
|
15
|
-
capabilities: string[];
|
|
16
|
-
scope: Record<string, unknown>;
|
|
17
|
-
issued_by: string;
|
|
18
|
-
issued_at: string;
|
|
19
|
-
expires_at: string;
|
|
20
|
-
revoked_at: string | null;
|
|
21
|
-
revoke_reason: string | null;
|
|
22
|
-
metadata: Record<string, unknown> | null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class CaDb {
|
|
26
|
-
private db: Database.Database;
|
|
27
|
-
|
|
28
|
-
constructor(dbPath: string) {
|
|
29
|
-
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
30
|
-
this.db = new Database(dbPath);
|
|
31
|
-
this.db.pragma("journal_mode = WAL");
|
|
32
|
-
this.db.pragma("foreign_keys = ON");
|
|
33
|
-
this.db.exec(fs.readFileSync(SCHEMA_PATH, "utf8"));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
nextSerial(): string {
|
|
37
|
-
const row = this.db
|
|
38
|
-
.prepare(
|
|
39
|
-
"SELECT COALESCE(MAX(CAST(REPLACE(serial,'0x','') AS INTEGER)),0)+1 AS n FROM nip_certificates",
|
|
40
|
-
)
|
|
41
|
-
.get() as { n: number };
|
|
42
|
-
return `0x${row.n.toString(16).toUpperCase().padStart(6, "0")}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
insert(rec: {
|
|
46
|
-
nid: string; entity_type: string; serial: string; pub_key: string;
|
|
47
|
-
capabilities: string[]; scope: Record<string, unknown>; issued_by: string;
|
|
48
|
-
issued_at: string; expires_at: string; metadata?: Record<string, unknown> | null;
|
|
49
|
-
}): number {
|
|
50
|
-
const result = this.db.prepare(
|
|
51
|
-
`INSERT INTO nip_certificates
|
|
52
|
-
(nid, entity_type, serial, pub_key, capabilities, scope_json,
|
|
53
|
-
issued_by, issued_at, expires_at, metadata_json)
|
|
54
|
-
VALUES (?,?,?,?,?,?,?,?,?,?)`,
|
|
55
|
-
).run(
|
|
56
|
-
rec.nid, rec.entity_type, rec.serial, rec.pub_key,
|
|
57
|
-
JSON.stringify(rec.capabilities), JSON.stringify(rec.scope),
|
|
58
|
-
rec.issued_by, rec.issued_at, rec.expires_at,
|
|
59
|
-
rec.metadata ? JSON.stringify(rec.metadata) : null,
|
|
60
|
-
);
|
|
61
|
-
return Number(result.lastInsertRowid);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
getActive(nid: string): CertRecord | null {
|
|
65
|
-
const row = this.db.prepare(
|
|
66
|
-
`SELECT * FROM nip_certificates
|
|
67
|
-
WHERE nid=? AND revoked_at IS NULL
|
|
68
|
-
ORDER BY issued_at DESC LIMIT 1`,
|
|
69
|
-
).get(nid);
|
|
70
|
-
return row ? this._toRecord(row as any) : null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
revoke(nid: string, reason: string): boolean {
|
|
74
|
-
const now = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
75
|
-
const r = this.db.prepare(
|
|
76
|
-
"UPDATE nip_certificates SET revoked_at=?, revoke_reason=? WHERE nid=? AND revoked_at IS NULL",
|
|
77
|
-
).run(now, reason, nid);
|
|
78
|
-
return r.changes > 0;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
crl(): Array<{ serial: string; nid: string; revoked_at: string; revoke_reason: string | null }> {
|
|
82
|
-
return this.db.prepare(
|
|
83
|
-
"SELECT serial, nid, revoked_at, revoke_reason FROM nip_certificates WHERE revoked_at IS NOT NULL ORDER BY revoked_at DESC",
|
|
84
|
-
).all() as any;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private _toRecord(row: any): CertRecord {
|
|
88
|
-
return {
|
|
89
|
-
id: row.id,
|
|
90
|
-
nid: row.nid,
|
|
91
|
-
entity_type: row.entity_type,
|
|
92
|
-
serial: row.serial,
|
|
93
|
-
pub_key: row.pub_key,
|
|
94
|
-
capabilities: JSON.parse(row.capabilities),
|
|
95
|
-
scope: JSON.parse(row.scope_json),
|
|
96
|
-
issued_by: row.issued_by,
|
|
97
|
-
issued_at: row.issued_at,
|
|
98
|
-
expires_at: row.expires_at,
|
|
99
|
-
revoked_at: row.revoked_at ?? null,
|
|
100
|
-
revoke_reason: row.revoke_reason ?? null,
|
|
101
|
-
metadata: row.metadata_json ? JSON.parse(row.metadata_json) : null,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
import * as crypto from "node:crypto";
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import Fastify from "fastify";
|
|
6
|
-
import { CaDb } from "./db.js";
|
|
7
|
-
import * as ca from "./ca.js";
|
|
8
|
-
|
|
9
|
-
// ── Config ────────────────────────────────────────────────────────────────────
|
|
10
|
-
const CA_NID = process.env["NIP_CA_NID"]!;
|
|
11
|
-
const CA_PASSPHRASE = process.env["NIP_CA_PASSPHRASE"]!;
|
|
12
|
-
const CA_BASE_URL = (process.env["NIP_CA_BASE_URL"] ?? "").replace(/\/$/, "");
|
|
13
|
-
const KEY_FILE = process.env["NIP_CA_KEY_FILE"] ?? "/data/ca.key.enc";
|
|
14
|
-
const DB_PATH = process.env["NIP_CA_DB_PATH"] ?? "/data/ca.db";
|
|
15
|
-
const DISPLAY_NAME = process.env["NIP_CA_DISPLAY_NAME"] ?? "NPS CA";
|
|
16
|
-
const AGENT_DAYS = parseInt(process.env["NIP_CA_AGENT_VALIDITY_DAYS"] ?? "30");
|
|
17
|
-
const NODE_DAYS = parseInt(process.env["NIP_CA_NODE_VALIDITY_DAYS"] ?? "90");
|
|
18
|
-
const RENEWAL_DAYS = parseInt(process.env["NIP_CA_RENEWAL_WINDOW_DAYS"] ?? "7");
|
|
19
|
-
const PORT = parseInt(process.env["PORT"] ?? "17440");
|
|
20
|
-
|
|
21
|
-
for (const k of ["NIP_CA_NID", "NIP_CA_PASSPHRASE", "NIP_CA_BASE_URL"]) {
|
|
22
|
-
if (!process.env[k]) throw new Error(`${k} is required`);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const CA_DOMAIN = CA_NID.split(":").slice(-2)[0] ?? "ca.local";
|
|
26
|
-
|
|
27
|
-
// ── Bootstrap ─────────────────────────────────────────────────────────────────
|
|
28
|
-
let caPriv: crypto.KeyObject;
|
|
29
|
-
let caPubStr: string;
|
|
30
|
-
|
|
31
|
-
if (!fs.existsSync(KEY_FILE)) {
|
|
32
|
-
caPriv = ca.generateKey();
|
|
33
|
-
ca.saveKey(caPriv, KEY_FILE, CA_PASSPHRASE);
|
|
34
|
-
} else {
|
|
35
|
-
caPriv = ca.loadKey(KEY_FILE, CA_PASSPHRASE);
|
|
36
|
-
}
|
|
37
|
-
caPubStr = ca.pubKeyString(crypto.createPublicKey(caPriv));
|
|
38
|
-
|
|
39
|
-
const db = new CaDb(DB_PATH);
|
|
40
|
-
|
|
41
|
-
// ── Server ─────────────────────────────────────────────────────────────────────
|
|
42
|
-
const app = Fastify({ logger: true });
|
|
43
|
-
|
|
44
|
-
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
45
|
-
function register(
|
|
46
|
-
body: { nid?: string; pub_key: string; capabilities?: string[]; scope?: object; metadata?: object },
|
|
47
|
-
entityType: string,
|
|
48
|
-
validityDays: number,
|
|
49
|
-
reply: any,
|
|
50
|
-
): void {
|
|
51
|
-
const nid = body.nid ?? ca.generateNid(CA_DOMAIN, entityType);
|
|
52
|
-
if (db.getActive(nid)) {
|
|
53
|
-
reply.code(409).send({ error_code: "NIP-CA-NID-ALREADY-EXISTS",
|
|
54
|
-
message: `${nid} already has an active certificate` });
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const serial = db.nextSerial();
|
|
58
|
-
const cert = ca.issueCert(caPriv, CA_NID, nid, body.pub_key,
|
|
59
|
-
body.capabilities ?? [], body.scope as any ?? {}, validityDays, serial,
|
|
60
|
-
(body as any).metadata ?? null);
|
|
61
|
-
db.insert({ nid, entity_type: entityType, serial, pub_key: body.pub_key,
|
|
62
|
-
capabilities: body.capabilities ?? [], scope: body.scope as any ?? {},
|
|
63
|
-
issued_by: CA_NID, issued_at: cert.issued_at, expires_at: cert.expires_at,
|
|
64
|
-
metadata: (body as any).metadata ?? null });
|
|
65
|
-
reply.code(201).send({ nid, serial, issued_at: cert.issued_at,
|
|
66
|
-
expires_at: cert.expires_at, ident_frame: cert });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ── Routes ─────────────────────────────────────────────────────────────────────
|
|
70
|
-
app.post("/v1/agents/register", async (req, reply) => {
|
|
71
|
-
register(req.body as any, "agent", AGENT_DAYS, reply);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
app.post("/v1/nodes/register", async (req, reply) => {
|
|
75
|
-
register(req.body as any, "node", NODE_DAYS, reply);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
app.post<{ Params: { "*": string } }>("/v1/agents/*", async (req, reply) => {
|
|
79
|
-
const parts = (req.params["*"] as string).split("/");
|
|
80
|
-
const action = parts.pop();
|
|
81
|
-
const nid = parts.join("/");
|
|
82
|
-
|
|
83
|
-
if (action === "renew") {
|
|
84
|
-
const rec = db.getActive(nid);
|
|
85
|
-
if (!rec) return reply.code(404).send({ error_code: "NIP-CA-NID-NOT-FOUND", message: `${nid} not found` });
|
|
86
|
-
const expMs = new Date(rec.expires_at).getTime();
|
|
87
|
-
const daysLeft = Math.floor((expMs - Date.now()) / 86400_000);
|
|
88
|
-
if (daysLeft > RENEWAL_DAYS)
|
|
89
|
-
return reply.code(400).send({ error_code: "NIP-CA-RENEWAL-TOO-EARLY",
|
|
90
|
-
message: `Renewal window opens in ${daysLeft - RENEWAL_DAYS} days` });
|
|
91
|
-
const serial = db.nextSerial();
|
|
92
|
-
const days = rec.entity_type === "agent" ? AGENT_DAYS : NODE_DAYS;
|
|
93
|
-
const cert = ca.issueCert(caPriv, CA_NID, nid, rec.pub_key,
|
|
94
|
-
rec.capabilities, rec.scope, days, serial, rec.metadata);
|
|
95
|
-
db.insert({ nid, entity_type: rec.entity_type, serial, pub_key: rec.pub_key,
|
|
96
|
-
capabilities: rec.capabilities, scope: rec.scope,
|
|
97
|
-
issued_by: CA_NID, issued_at: cert.issued_at, expires_at: cert.expires_at,
|
|
98
|
-
metadata: rec.metadata });
|
|
99
|
-
return reply.send({ nid, serial, issued_at: cert.issued_at,
|
|
100
|
-
expires_at: cert.expires_at, ident_frame: cert });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (action === "revoke") {
|
|
104
|
-
const body = req.body as any;
|
|
105
|
-
if (!db.revoke(nid, body?.reason ?? "cessation_of_operation"))
|
|
106
|
-
return reply.code(404).send({ error_code: "NIP-CA-NID-NOT-FOUND",
|
|
107
|
-
message: `${nid} not found or already revoked` });
|
|
108
|
-
return reply.send({ nid, revoked_at: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
109
|
-
reason: body?.reason ?? "cessation_of_operation" });
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
reply.code(404).send({ message: "Not found" });
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
app.get<{ Params: { "*": string } }>("/v1/agents/*", async (req, reply) => {
|
|
116
|
-
const parts = (req.params["*"] as string).split("/");
|
|
117
|
-
const action = parts.pop();
|
|
118
|
-
const nid = parts.join("/");
|
|
119
|
-
|
|
120
|
-
if (action === "verify") {
|
|
121
|
-
const rec = db.getActive(nid);
|
|
122
|
-
if (!rec) return reply.code(404).send({ error_code: "NIP-CA-NID-NOT-FOUND", message: `${nid} not found` });
|
|
123
|
-
const valid = new Date(rec.expires_at).getTime() > Date.now();
|
|
124
|
-
return reply.send({ valid, nid, entity_type: rec.entity_type, pub_key: rec.pub_key,
|
|
125
|
-
capabilities: rec.capabilities, issued_by: rec.issued_by,
|
|
126
|
-
issued_at: rec.issued_at, expires_at: rec.expires_at, serial: rec.serial,
|
|
127
|
-
error_code: valid ? null : "NIP-CERT-EXPIRED" });
|
|
128
|
-
}
|
|
129
|
-
reply.code(404).send({ message: "Not found" });
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
app.get("/v1/ca/cert", async (_req, reply) =>
|
|
133
|
-
reply.send({ nid: CA_NID, display_name: DISPLAY_NAME, pub_key: caPubStr, algorithm: "ed25519" }));
|
|
134
|
-
|
|
135
|
-
app.get("/v1/crl", async (_req, reply) =>
|
|
136
|
-
reply.send({ revoked: db.crl() }));
|
|
137
|
-
|
|
138
|
-
app.get("/.well-known/nps-ca", async (_req, reply) =>
|
|
139
|
-
reply.send({
|
|
140
|
-
nps_ca: "0.1", issuer: CA_NID, display_name: DISPLAY_NAME, public_key: caPubStr,
|
|
141
|
-
algorithms: ["ed25519"],
|
|
142
|
-
endpoints: {
|
|
143
|
-
register: `${CA_BASE_URL}/v1/agents/register`,
|
|
144
|
-
verify: `${CA_BASE_URL}/v1/agents/{nid}/verify`,
|
|
145
|
-
ocsp: `${CA_BASE_URL}/v1/agents/{nid}/verify`,
|
|
146
|
-
crl: `${CA_BASE_URL}/v1/crl`,
|
|
147
|
-
},
|
|
148
|
-
capabilities: ["agent", "node"],
|
|
149
|
-
max_cert_validity_days: Math.max(AGENT_DAYS, NODE_DAYS),
|
|
150
|
-
}));
|
|
151
|
-
|
|
152
|
-
app.get("/health", async (_req, reply) => reply.send({ status: "ok" }));
|
|
153
|
-
|
|
154
|
-
app.listen({ port: PORT, host: "0.0.0.0" }, (err) => {
|
|
155
|
-
if (err) { console.error(err); process.exit(1); }
|
|
156
|
-
console.log(`NIP CA Server listening on :${PORT}`);
|
|
157
|
-
});
|
package/src/core/anchor-cache.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD
|
|
3
|
-
//
|
|
4
|
-
// AnchorCache — Schema cache with TTL, LRU eviction, poison detection
|
|
5
|
-
// NPS-1 §5.3, §7.2, §9
|
|
6
|
-
|
|
7
|
-
import { NcpError } from "./frame-header.js";
|
|
8
|
-
import type { AnchorFrame } from "../ncp/frames/anchor-frame.js";
|
|
9
|
-
|
|
10
|
-
interface CacheEntry {
|
|
11
|
-
frame: AnchorFrame;
|
|
12
|
-
expiresAt: number; // epoch ms
|
|
13
|
-
lastAccessed: number; // epoch ms
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* AnchorFrame cache with:
|
|
18
|
-
* - TTL-based expiry (NPS-1 §5.3)
|
|
19
|
-
* - LRU eviction at maxSize (NPS-1 §9, default 1000)
|
|
20
|
-
* - Anchor poisoning detection (NPS-1 §7.2)
|
|
21
|
-
*/
|
|
22
|
-
export class AnchorCache {
|
|
23
|
-
private readonly cache = new Map<string, CacheEntry>();
|
|
24
|
-
private readonly maxSize: number;
|
|
25
|
-
private readonly getNow: () => number;
|
|
26
|
-
|
|
27
|
-
constructor(options?: { maxSize?: number; getNow?: () => number }) {
|
|
28
|
-
this.maxSize = options?.maxSize ?? 1000;
|
|
29
|
-
this.getNow = options?.getNow ?? (() => Date.now());
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Cache an AnchorFrame.
|
|
34
|
-
*
|
|
35
|
-
* - ttl=0: frame is valid but not cached (NPS-1 §4.1)
|
|
36
|
-
* - Same anchor_id + same schema: idempotent (no-op)
|
|
37
|
-
* - Same anchor_id + different schema: NCP-ANCHOR-ID-MISMATCH (poison detection)
|
|
38
|
-
*
|
|
39
|
-
* @throws {NcpError} NCP-ANCHOR-ID-MISMATCH on anchor poisoning.
|
|
40
|
-
*/
|
|
41
|
-
set(frame: AnchorFrame): void {
|
|
42
|
-
// ttl=0 means use once, don't cache
|
|
43
|
-
if (frame.ttl === 0) return;
|
|
44
|
-
|
|
45
|
-
const existing = this.cache.get(frame.anchor_id);
|
|
46
|
-
if (existing) {
|
|
47
|
-
// Poison detection: same ID, different schema
|
|
48
|
-
const existingJson = JSON.stringify(existing.frame.schema);
|
|
49
|
-
const newJson = JSON.stringify(frame.schema);
|
|
50
|
-
if (existingJson !== newJson) {
|
|
51
|
-
throw new NcpError(
|
|
52
|
-
"NCP-ANCHOR-ID-MISMATCH",
|
|
53
|
-
`Anchor poisoning detected: anchor_id ${frame.anchor_id} received with different schema`,
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
// Same schema — idempotent, update access time
|
|
57
|
-
existing.lastAccessed = this.getNow();
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// LRU eviction if at capacity
|
|
62
|
-
if (this.cache.size >= this.maxSize) {
|
|
63
|
-
this.evictLru();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const ttlMs = (frame.ttl ?? 3600) * 1000;
|
|
67
|
-
this.cache.set(frame.anchor_id, {
|
|
68
|
-
frame,
|
|
69
|
-
expiresAt: this.getNow() + ttlMs,
|
|
70
|
-
lastAccessed: this.getNow(),
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Get a cached AnchorFrame by anchor_id.
|
|
76
|
-
*
|
|
77
|
-
* @returns The cached frame, or null if not found or expired.
|
|
78
|
-
*/
|
|
79
|
-
get(anchorId: string): AnchorFrame | null {
|
|
80
|
-
const entry = this.cache.get(anchorId);
|
|
81
|
-
if (!entry) return null;
|
|
82
|
-
|
|
83
|
-
// Check TTL expiry
|
|
84
|
-
if (this.getNow() >= entry.expiresAt) {
|
|
85
|
-
this.cache.delete(anchorId);
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
entry.lastAccessed = this.getNow();
|
|
90
|
-
return entry.frame;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Get a cached AnchorFrame, throwing if not found.
|
|
95
|
-
* @throws {NcpError} NCP-ANCHOR-NOT-FOUND if not in cache or expired.
|
|
96
|
-
*/
|
|
97
|
-
getRequired(anchorId: string): AnchorFrame {
|
|
98
|
-
const frame = this.get(anchorId);
|
|
99
|
-
if (!frame) {
|
|
100
|
-
throw new NcpError(
|
|
101
|
-
"NCP-ANCHOR-NOT-FOUND",
|
|
102
|
-
`Schema anchor ${anchorId} not found in cache`,
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
return frame;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** Current cache size. */
|
|
109
|
-
get size(): number {
|
|
110
|
-
return this.cache.size;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** Evict the least recently accessed entry. */
|
|
114
|
-
private evictLru(): void {
|
|
115
|
-
let oldestKey: string | undefined;
|
|
116
|
-
let oldestTime = Infinity;
|
|
117
|
-
|
|
118
|
-
for (const [key, entry] of this.cache) {
|
|
119
|
-
if (entry.lastAccessed < oldestTime) {
|
|
120
|
-
oldestTime = entry.lastAccessed;
|
|
121
|
-
oldestKey = key;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (oldestKey) {
|
|
126
|
-
this.cache.delete(oldestKey);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
package/src/core/cache.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 INNO LOTUS PTY LTD
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* AnchorFrameCache — in-process cache for AnchorFrame instances (NPS-1 §4.1).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createHash } from "node:crypto";
|
|
9
|
-
import { NpsAnchorNotFoundError, NpsAnchorPoisonError } from "./exceptions.js";
|
|
10
|
-
import type { AnchorFrame, FrameSchema } from "../ncp/frames.js";
|
|
11
|
-
|
|
12
|
-
export class AnchorFrameCache {
|
|
13
|
-
private readonly _store = new Map<string, { frame: AnchorFrame; expiresAt: number }>();
|
|
14
|
-
|
|
15
|
-
// Allow clock injection for testing
|
|
16
|
-
clock: () => number = () => Date.now();
|
|
17
|
-
|
|
18
|
-
// ── Public API ────────────────────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
-
set(frame: AnchorFrame): string {
|
|
21
|
-
const anchorId = frame.anchorId.startsWith("sha256:")
|
|
22
|
-
? frame.anchorId
|
|
23
|
-
: AnchorFrameCache.computeAnchorId(frame.schema);
|
|
24
|
-
|
|
25
|
-
const existing = this._store.get(anchorId);
|
|
26
|
-
if (existing !== undefined && this.clock() < existing.expiresAt) {
|
|
27
|
-
if (!AnchorFrameCache._schemasEqual(existing.frame.schema, frame.schema)) {
|
|
28
|
-
throw new NpsAnchorPoisonError(anchorId);
|
|
29
|
-
}
|
|
30
|
-
// Same schema — idempotent; refresh TTL below
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const ttlMs = (frame.ttl ?? 3600) * 1000;
|
|
34
|
-
const expiresAt = this.clock() + ttlMs;
|
|
35
|
-
this._store.set(anchorId, { frame, expiresAt });
|
|
36
|
-
return anchorId;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
get(anchorId: string): AnchorFrame | undefined {
|
|
40
|
-
const entry = this._store.get(anchorId);
|
|
41
|
-
if (entry === undefined) return undefined;
|
|
42
|
-
if (this.clock() > entry.expiresAt) {
|
|
43
|
-
this._store.delete(anchorId);
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
return entry.frame;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
getRequired(anchorId: string): AnchorFrame {
|
|
50
|
-
const frame = this.get(anchorId);
|
|
51
|
-
if (frame === undefined) throw new NpsAnchorNotFoundError(anchorId);
|
|
52
|
-
return frame;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
invalidate(anchorId: string): void {
|
|
56
|
-
this._store.delete(anchorId);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
get size(): number {
|
|
60
|
-
this._evictExpired();
|
|
61
|
-
return this._store.size;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ── Static helpers ────────────────────────────────────────────────────────
|
|
65
|
-
|
|
66
|
-
static computeAnchorId(schema: FrameSchema): string {
|
|
67
|
-
const sorted = [...schema.fields]
|
|
68
|
-
.map((f) => {
|
|
69
|
-
const obj: Record<string, unknown> = { name: f.name, type: f.type };
|
|
70
|
-
if (f.semantic !== undefined) obj["semantic"] = f.semantic;
|
|
71
|
-
if (f.nullable !== undefined) obj["nullable"] = f.nullable;
|
|
72
|
-
return obj;
|
|
73
|
-
})
|
|
74
|
-
.sort((a, b) => String(a["name"]).localeCompare(String(b["name"])));
|
|
75
|
-
|
|
76
|
-
const canonical = JSON.stringify(sorted);
|
|
77
|
-
const digest = createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
78
|
-
return `sha256:${digest}`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ── Private ───────────────────────────────────────────────────────────────
|
|
82
|
-
|
|
83
|
-
private _evictExpired(): void {
|
|
84
|
-
const now = this.clock();
|
|
85
|
-
for (const [k, entry] of this._store) {
|
|
86
|
-
if (now > entry.expiresAt) this._store.delete(k);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private static _schemasEqual(a: FrameSchema, b: FrameSchema): boolean {
|
|
91
|
-
return AnchorFrameCache.computeAnchorId(a) === AnchorFrameCache.computeAnchorId(b);
|
|
92
|
-
}
|
|
93
|
-
}
|