@labacacia/nps-sdk 1.0.0-alpha.5 → 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.
Files changed (145) hide show
  1. package/CHANGELOG.cn.md +29 -5
  2. package/CHANGELOG.md +29 -5
  3. package/LICENSE +0 -0
  4. package/NOTICE +0 -0
  5. package/README.cn.md +8 -13
  6. package/README.md +8 -13
  7. package/dist/nip/index.d.ts +1 -0
  8. package/dist/nip/index.d.ts.map +1 -1
  9. package/dist/nip/index.js +2 -0
  10. package/dist/nip/index.js.map +1 -1
  11. package/dist/nip/reputation-client.d.ts +116 -0
  12. package/dist/nip/reputation-client.d.ts.map +1 -0
  13. package/dist/nip/reputation-client.js +261 -0
  14. package/dist/nip/reputation-client.js.map +1 -0
  15. package/dist/nip/x509/oids.d.ts +9 -10
  16. package/dist/nip/x509/oids.d.ts.map +1 -1
  17. package/dist/nip/x509/oids.js +3 -4
  18. package/dist/nip/x509/oids.js.map +1 -1
  19. package/dist/nwp/anchor-client.d.ts +109 -0
  20. package/dist/nwp/anchor-client.d.ts.map +1 -0
  21. package/dist/nwp/anchor-client.js +279 -0
  22. package/dist/nwp/anchor-client.js.map +1 -0
  23. package/dist/nwp/index.d.ts +1 -1
  24. package/dist/nwp/index.d.ts.map +1 -1
  25. package/dist/nwp/index.js +1 -1
  26. package/dist/nwp/index.js.map +1 -1
  27. package/doc/nps-sdk.core.cn.md +0 -0
  28. package/doc/nps-sdk.core.md +0 -0
  29. package/doc/nps-sdk.ncp.cn.md +0 -0
  30. package/doc/nps-sdk.ncp.md +0 -0
  31. package/doc/nps-sdk.ndp.cn.md +0 -0
  32. package/doc/nps-sdk.ndp.md +0 -0
  33. package/doc/nps-sdk.nop.cn.md +0 -0
  34. package/doc/nps-sdk.nop.md +0 -0
  35. package/doc/overview.cn.md +0 -0
  36. package/doc/overview.md +0 -0
  37. package/package.json +12 -1
  38. package/CONTRIBUTING.cn.md +0 -35
  39. package/CONTRIBUTING.md +0 -35
  40. package/dist/nwp/error-codes.d.ts +0 -42
  41. package/dist/nwp/error-codes.d.ts.map +0 -1
  42. package/dist/nwp/error-codes.js +0 -53
  43. package/dist/nwp/error-codes.js.map +0 -1
  44. package/nip-ca-server/Dockerfile +0 -27
  45. package/nip-ca-server/README.md +0 -45
  46. package/nip-ca-server/db/001_init.sql +0 -25
  47. package/nip-ca-server/docker-compose.yml +0 -29
  48. package/nip-ca-server/package.json +0 -23
  49. package/nip-ca-server/src/ca.ts +0 -155
  50. package/nip-ca-server/src/db.ts +0 -104
  51. package/nip-ca-server/src/index.ts +0 -157
  52. package/nip-ca-server/tsconfig.json +0 -13
  53. package/src/core/anchor-cache.ts +0 -129
  54. package/src/core/cache.ts +0 -93
  55. package/src/core/canonical-json.ts +0 -50
  56. package/src/core/codec.ts +0 -158
  57. package/src/core/codecs/index.ts +0 -5
  58. package/src/core/codecs/ncp-codec.ts +0 -170
  59. package/src/core/codecs/tier1-json-codec.ts +0 -33
  60. package/src/core/codecs/tier2-msgpack-codec.ts +0 -30
  61. package/src/core/crypto-provider.ts +0 -47
  62. package/src/core/exceptions.ts +0 -57
  63. package/src/core/frame-header.ts +0 -282
  64. package/src/core/frame-registry.ts +0 -91
  65. package/src/core/frames.ts +0 -184
  66. package/src/core/index.ts +0 -42
  67. package/src/core/registry.ts +0 -28
  68. package/src/core/status-codes.ts +0 -47
  69. package/src/index.ts +0 -10
  70. package/src/ncp/frames/anchor-frame.ts +0 -87
  71. package/src/ncp/frames/caps-frame.ts +0 -59
  72. package/src/ncp/frames/diff-frame.ts +0 -69
  73. package/src/ncp/frames/error-frame.ts +0 -26
  74. package/src/ncp/frames/hello-frame.ts +0 -50
  75. package/src/ncp/frames/stream-frame.ts +0 -35
  76. package/src/ncp/frames.ts +0 -251
  77. package/src/ncp/handshake.ts +0 -95
  78. package/src/ncp/index.ts +0 -13
  79. package/src/ncp/ncp-error-codes.ts +0 -36
  80. package/src/ncp/ncp-patch-format.ts +0 -16
  81. package/src/ncp/preamble.ts +0 -79
  82. package/src/ncp/registry.ts +0 -15
  83. package/src/ncp/stream-manager.ts +0 -212
  84. package/src/ndp/dns-txt.ts +0 -86
  85. package/src/ndp/frames.ts +0 -124
  86. package/src/ndp/index.ts +0 -8
  87. package/src/ndp/ndp-registry.ts +0 -116
  88. package/src/ndp/registry.ts +0 -12
  89. package/src/ndp/validator.ts +0 -64
  90. package/src/nip/acme/client.ts +0 -185
  91. package/src/nip/acme/index.ts +0 -8
  92. package/src/nip/acme/jws.ts +0 -109
  93. package/src/nip/acme/messages.ts +0 -85
  94. package/src/nip/acme/server.ts +0 -480
  95. package/src/nip/acme/wire.ts +0 -24
  96. package/src/nip/assurance-level.ts +0 -40
  97. package/src/nip/cert-format.ts +0 -9
  98. package/src/nip/error-codes.ts +0 -38
  99. package/src/nip/frames.ts +0 -138
  100. package/src/nip/identity.ts +0 -113
  101. package/src/nip/index.ts +0 -14
  102. package/src/nip/registry.ts +0 -12
  103. package/src/nip/verifier.ts +0 -122
  104. package/src/nip/x509/builder.ts +0 -91
  105. package/src/nip/x509/index.ts +0 -6
  106. package/src/nip/x509/oids.ts +0 -28
  107. package/src/nip/x509/verifier.ts +0 -214
  108. package/src/nop/client.ts +0 -103
  109. package/src/nop/frames.ts +0 -181
  110. package/src/nop/index.ts +0 -7
  111. package/src/nop/models.ts +0 -79
  112. package/src/nop/nop-types.ts +0 -208
  113. package/src/nop/registry.ts +0 -13
  114. package/src/nwp/client.ts +0 -114
  115. package/src/nwp/error-codes.ts +0 -62
  116. package/src/nwp/frames.ts +0 -116
  117. package/src/nwp/index.ts +0 -7
  118. package/src/nwp/registry.ts +0 -11
  119. package/src/setup.ts +0 -32
  120. package/tests/_rfc0002-keys.ts +0 -57
  121. package/tests/core/anchor-cache.test.ts +0 -242
  122. package/tests/core/codec.test.ts +0 -205
  123. package/tests/core/frame-registry.test.ts +0 -46
  124. package/tests/core.test.ts +0 -327
  125. package/tests/ncp/diff-binary-bitset.test.ts +0 -107
  126. package/tests/ncp/e2e-enc-reject.test.ts +0 -93
  127. package/tests/ncp/err-error-frame.test.ts +0 -152
  128. package/tests/ncp/frames.test.ts +0 -359
  129. package/tests/ncp/framing.test.ts +0 -233
  130. package/tests/ncp/hello-frame.test.ts +0 -122
  131. package/tests/ncp/inline-anchor.test.ts +0 -88
  132. package/tests/ncp/preamble.test.ts +0 -93
  133. package/tests/ncp/security.test.ts +0 -184
  134. package/tests/ncp/stream-window.test.ts +0 -167
  135. package/tests/ncp/stream.test.ts +0 -242
  136. package/tests/ncp/version-negotiation.test.ts +0 -123
  137. package/tests/ndp.test.ts +0 -377
  138. package/tests/nip-acme-agent01.test.ts +0 -192
  139. package/tests/nip-x509.test.ts +0 -280
  140. package/tests/nip.test.ts +0 -184
  141. package/tests/nop.test.ts +0 -344
  142. package/tests/nwp.test.ts +0 -237
  143. package/tsconfig.json +0 -20
  144. package/tsup.config.ts +0 -20
  145. package/vitest.config.ts +0 -10
@@ -1,53 +0,0 @@
1
- // Copyright 2026 INNO LOTUS PTY LTD
2
- // SPDX-License-Identifier: Apache-2.0
3
- /** NWP error code wire constants — mirror of `spec/error-codes.md` NWP section. */
4
- // ── Auth ─────────────────────────────────────────────────────────────────────
5
- export const AUTH_NID_SCOPE_VIOLATION = "NWP-AUTH-NID-SCOPE-VIOLATION";
6
- export const AUTH_NID_EXPIRED = "NWP-AUTH-NID-EXPIRED";
7
- export const AUTH_NID_REVOKED = "NWP-AUTH-NID-REVOKED";
8
- export const AUTH_NID_UNTRUSTED_ISSUER = "NWP-AUTH-NID-UNTRUSTED-ISSUER";
9
- export const AUTH_NID_CAPABILITY_MISSING = "NWP-AUTH-NID-CAPABILITY-MISSING";
10
- export const AUTH_ASSURANCE_TOO_LOW = "NWP-AUTH-ASSURANCE-TOO-LOW";
11
- export const AUTH_REPUTATION_BLOCKED = "NWP-AUTH-REPUTATION-BLOCKED";
12
- // ── Query ─────────────────────────────────────────────────────────────────────
13
- export const QUERY_FILTER_INVALID = "NWP-QUERY-FILTER-INVALID";
14
- export const QUERY_FIELD_UNKNOWN = "NWP-QUERY-FIELD-UNKNOWN";
15
- export const QUERY_CURSOR_INVALID = "NWP-QUERY-CURSOR-INVALID";
16
- export const QUERY_REGEX_UNSAFE = "NWP-QUERY-REGEX-UNSAFE";
17
- export const QUERY_VECTOR_UNSUPPORTED = "NWP-QUERY-VECTOR-UNSUPPORTED";
18
- export const QUERY_AGGREGATE_UNSUPPORTED = "NWP-QUERY-AGGREGATE-UNSUPPORTED";
19
- export const QUERY_AGGREGATE_INVALID = "NWP-QUERY-AGGREGATE-INVALID";
20
- export const QUERY_STREAM_UNSUPPORTED = "NWP-QUERY-STREAM-UNSUPPORTED";
21
- // ── Action ────────────────────────────────────────────────────────────────────
22
- export const ACTION_NOT_FOUND = "NWP-ACTION-NOT-FOUND";
23
- export const ACTION_PARAMS_INVALID = "NWP-ACTION-PARAMS-INVALID";
24
- export const ACTION_IDEMPOTENCY_CONFLICT = "NWP-ACTION-IDEMPOTENCY-CONFLICT";
25
- // ── Task ──────────────────────────────────────────────────────────────────────
26
- export const TASK_NOT_FOUND = "NWP-TASK-NOT-FOUND";
27
- export const TASK_ALREADY_CANCELLED = "NWP-TASK-ALREADY-CANCELLED";
28
- export const TASK_ALREADY_COMPLETED = "NWP-TASK-ALREADY-COMPLETED";
29
- export const TASK_ALREADY_FAILED = "NWP-TASK-ALREADY-FAILED";
30
- // ── Subscribe ─────────────────────────────────────────────────────────────────
31
- export const SUBSCRIBE_STREAM_NOT_FOUND = "NWP-SUBSCRIBE-STREAM-NOT-FOUND";
32
- export const SUBSCRIBE_LIMIT_EXCEEDED = "NWP-SUBSCRIBE-LIMIT-EXCEEDED";
33
- export const SUBSCRIBE_FILTER_UNSUPPORTED = "NWP-SUBSCRIBE-FILTER-UNSUPPORTED";
34
- export const SUBSCRIBE_INTERRUPTED = "NWP-SUBSCRIBE-INTERRUPTED";
35
- export const SUBSCRIBE_SEQ_TOO_OLD = "NWP-SUBSCRIBE-SEQ-TOO-OLD";
36
- // ── Infrastructure ────────────────────────────────────────────────────────────
37
- export const BUDGET_EXCEEDED = "NWP-BUDGET-EXCEEDED";
38
- export const DEPTH_EXCEEDED = "NWP-DEPTH-EXCEEDED";
39
- export const GRAPH_CYCLE = "NWP-GRAPH-CYCLE";
40
- export const NODE_UNAVAILABLE = "NWP-NODE-UNAVAILABLE";
41
- export const RATE_LIMIT_EXCEEDED = "NWP-RATE-LIMIT-EXCEEDED";
42
- // ── Manifest ──────────────────────────────────────────────────────────────────
43
- export const MANIFEST_VERSION_UNSUPPORTED = "NWP-MANIFEST-VERSION-UNSUPPORTED";
44
- export const MANIFEST_NODE_TYPE_REMOVED = "NWP-MANIFEST-NODE-TYPE-REMOVED";
45
- export const MANIFEST_NODE_TYPE_UNKNOWN = "NWP-MANIFEST-NODE-TYPE-UNKNOWN";
46
- // ── Topology (alpha.4+) ───────────────────────────────────────────────────────
47
- export const TOPOLOGY_UNAUTHORIZED = "NWP-TOPOLOGY-UNAUTHORIZED";
48
- export const TOPOLOGY_UNSUPPORTED_SCOPE = "NWP-TOPOLOGY-UNSUPPORTED-SCOPE";
49
- export const TOPOLOGY_DEPTH_UNSUPPORTED = "NWP-TOPOLOGY-DEPTH-UNSUPPORTED";
50
- export const TOPOLOGY_FILTER_UNSUPPORTED = "NWP-TOPOLOGY-FILTER-UNSUPPORTED";
51
- // ── Reserved type (alpha.5+) ─────────────────────────────────────────────────
52
- export const RESERVED_TYPE_UNSUPPORTED = "NWP-RESERVED-TYPE-UNSUPPORTED";
53
- //# sourceMappingURL=error-codes.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"error-codes.js","sourceRoot":"","sources":["../../src/nwp/error-codes.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,sCAAsC;AAEtC,mFAAmF;AAEnF,gFAAgF;AAChF,MAAM,CAAC,MAAM,wBAAwB,GAAM,8BAA8B,CAAC;AAC1E,MAAM,CAAC,MAAM,gBAAgB,GAAc,sBAAsB,CAAC;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAc,sBAAsB,CAAC;AAClE,MAAM,CAAC,MAAM,yBAAyB,GAAK,+BAA+B,CAAC;AAC3E,MAAM,CAAC,MAAM,2BAA2B,GAAG,iCAAiC,CAAC;AAC7E,MAAM,CAAC,MAAM,sBAAsB,GAAQ,4BAA4B,CAAC;AACxE,MAAM,CAAC,MAAM,uBAAuB,GAAO,6BAA6B,CAAC;AAEzE,iFAAiF;AACjF,MAAM,CAAC,MAAM,oBAAoB,GAAU,0BAA0B,CAAC;AACtE,MAAM,CAAC,MAAM,mBAAmB,GAAW,yBAAyB,CAAC;AACrE,MAAM,CAAC,MAAM,oBAAoB,GAAU,0BAA0B,CAAC;AACtE,MAAM,CAAC,MAAM,kBAAkB,GAAY,wBAAwB,CAAC;AACpE,MAAM,CAAC,MAAM,wBAAwB,GAAM,8BAA8B,CAAC;AAC1E,MAAM,CAAC,MAAM,2BAA2B,GAAG,iCAAiC,CAAC;AAC7E,MAAM,CAAC,MAAM,uBAAuB,GAAO,6BAA6B,CAAC;AACzE,MAAM,CAAC,MAAM,wBAAwB,GAAM,8BAA8B,CAAC;AAE1E,iFAAiF;AACjF,MAAM,CAAC,MAAM,gBAAgB,GAAc,sBAAsB,CAAC;AAClE,MAAM,CAAC,MAAM,qBAAqB,GAAS,2BAA2B,CAAC;AACvE,MAAM,CAAC,MAAM,2BAA2B,GAAG,iCAAiC,CAAC;AAE7E,iFAAiF;AACjF,MAAM,CAAC,MAAM,cAAc,GAAW,oBAAoB,CAAC;AAC3D,MAAM,CAAC,MAAM,sBAAsB,GAAG,4BAA4B,CAAC;AACnE,MAAM,CAAC,MAAM,sBAAsB,GAAG,4BAA4B,CAAC;AACnE,MAAM,CAAC,MAAM,mBAAmB,GAAM,yBAAyB,CAAC;AAEhE,iFAAiF;AACjF,MAAM,CAAC,MAAM,0BAA0B,GAAK,gCAAgC,CAAC;AAC7E,MAAM,CAAC,MAAM,wBAAwB,GAAO,8BAA8B,CAAC;AAC3E,MAAM,CAAC,MAAM,4BAA4B,GAAG,kCAAkC,CAAC;AAC/E,MAAM,CAAC,MAAM,qBAAqB,GAAU,2BAA2B,CAAC;AACxE,MAAM,CAAC,MAAM,qBAAqB,GAAU,2BAA2B,CAAC;AAExE,iFAAiF;AACjF,MAAM,CAAC,MAAM,eAAe,GAAQ,qBAAqB,CAAC;AAC1D,MAAM,CAAC,MAAM,cAAc,GAAS,oBAAoB,CAAC;AACzD,MAAM,CAAC,MAAM,WAAW,GAAY,iBAAiB,CAAC;AACtD,MAAM,CAAC,MAAM,gBAAgB,GAAO,sBAAsB,CAAC;AAC3D,MAAM,CAAC,MAAM,mBAAmB,GAAI,yBAAyB,CAAC;AAE9D,iFAAiF;AACjF,MAAM,CAAC,MAAM,4BAA4B,GAAG,kCAAkC,CAAC;AAC/E,MAAM,CAAC,MAAM,0BAA0B,GAAK,gCAAgC,CAAC;AAC7E,MAAM,CAAC,MAAM,0BAA0B,GAAK,gCAAgC,CAAC;AAE7E,iFAAiF;AACjF,MAAM,CAAC,MAAM,qBAAqB,GAAS,2BAA2B,CAAC;AACvE,MAAM,CAAC,MAAM,0BAA0B,GAAI,gCAAgC,CAAC;AAC5E,MAAM,CAAC,MAAM,0BAA0B,GAAI,gCAAgC,CAAC;AAC5E,MAAM,CAAC,MAAM,2BAA2B,GAAG,iCAAiC,CAAC;AAE7E,gFAAgF;AAChF,MAAM,CAAC,MAAM,yBAAyB,GAAG,+BAA+B,CAAC"}
@@ -1,27 +0,0 @@
1
- FROM node:22-alpine AS build
2
- WORKDIR /app
3
- COPY package*.json ./
4
- RUN npm ci
5
- COPY tsconfig.json .
6
- COPY src ./src
7
- COPY db ./db
8
- RUN npm run build
9
-
10
- FROM node:22-alpine AS runtime
11
- WORKDIR /app
12
-
13
- RUN addgroup -g 1001 nipca \
14
- && adduser -u 1001 -G nipca -D nipca \
15
- && mkdir -p /data && chown nipca:nipca /data
16
-
17
- COPY --from=build /app/dist ./dist
18
- COPY --from=build /app/node_modules ./node_modules
19
- COPY db ./db
20
-
21
- USER nipca
22
- EXPOSE 17440
23
-
24
- HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
25
- CMD wget -qO- http://localhost:17440/health || exit 1
26
-
27
- CMD ["node", "dist/index.js"]
@@ -1,45 +0,0 @@
1
- # NIP CA Server — TypeScript
2
-
3
- Fastify + better-sqlite3 implementation of the NIP Certificate Authority (NPS-3 §8).
4
-
5
- ## Quick Start
6
-
7
- ```bash
8
- docker compose up -d
9
- ```
10
-
11
- ## Environment Variables
12
-
13
- | Variable | Required | Default | Description |
14
- |----------|----------|---------|-------------|
15
- | `NIP_CA_NID` | Yes | — | CA NID |
16
- | `NIP_CA_PASSPHRASE` | Yes | — | Key file passphrase |
17
- | `NIP_CA_BASE_URL` | Yes | — | Public base URL |
18
- | `NIP_CA_DISPLAY_NAME` | No | `NPS CA` | |
19
- | `NIP_CA_KEY_FILE` | No | `/data/ca.key.enc` | |
20
- | `NIP_CA_DB_PATH` | No | `/data/ca.db` | |
21
- | `NIP_CA_AGENT_VALIDITY_DAYS` | No | `30` | |
22
- | `NIP_CA_NODE_VALIDITY_DAYS` | No | `90` | |
23
- | `NIP_CA_RENEWAL_WINDOW_DAYS` | No | `7` | |
24
- | `PORT` | No | `17440` | |
25
-
26
- ## API
27
-
28
- Same endpoints as all other NIP CA Server implementations — see [NPS-3 §8](../../spec/NPS-3-NIP.md).
29
-
30
- ## Local Development
31
-
32
- ```bash
33
- npm install
34
- NIP_CA_NID=urn:nps:org:ca.local \
35
- NIP_CA_PASSPHRASE=dev-pass \
36
- NIP_CA_BASE_URL=http://localhost:17440 \
37
- npm run dev
38
- ```
39
-
40
- ## Stack
41
-
42
- - **Runtime**: Node.js 22
43
- - **Framework**: Fastify
44
- - **Crypto**: Node.js built-in `crypto` (Ed25519 + AES-256-GCM + PBKDF2)
45
- - **Storage**: better-sqlite3
@@ -1,25 +0,0 @@
1
- -- NIP CA Server — SQLite schema
2
- -- NPS-3 §8 | Copyright 2026 INNO LOTUS PTY LTD | Apache-2.0
3
-
4
- CREATE TABLE IF NOT EXISTS nip_certificates (
5
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6
- nid TEXT NOT NULL,
7
- entity_type TEXT NOT NULL CHECK (entity_type IN ('agent','node','operator')),
8
- serial TEXT NOT NULL UNIQUE,
9
- pub_key TEXT NOT NULL,
10
- capabilities TEXT NOT NULL DEFAULT '[]',
11
- scope_json TEXT NOT NULL DEFAULT '{}',
12
- issued_by TEXT NOT NULL,
13
- issued_at TEXT NOT NULL,
14
- expires_at TEXT NOT NULL,
15
- revoked_at TEXT,
16
- revoke_reason TEXT,
17
- metadata_json TEXT,
18
- created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
19
- );
20
-
21
- CREATE INDEX IF NOT EXISTS idx_nip_certs_nid ON nip_certificates (nid, issued_at DESC);
22
- CREATE INDEX IF NOT EXISTS idx_nip_certs_serial ON nip_certificates (serial);
23
- CREATE INDEX IF NOT EXISTS idx_nip_certs_revoked ON nip_certificates (revoked_at)
24
- WHERE revoked_at IS NOT NULL;
25
- CREATE INDEX IF NOT EXISTS idx_nip_certs_expires ON nip_certificates (expires_at);
@@ -1,29 +0,0 @@
1
- version: "3.9"
2
-
3
- services:
4
- nip-ca:
5
- build: .
6
- restart: unless-stopped
7
- ports:
8
- - "${NIP_PORT:-17440}:17440"
9
- volumes:
10
- - ca_data:/data
11
- environment:
12
- NIP_CA_NID: ${NIP_CA_NID:?NIP_CA_NID is required}
13
- NIP_CA_PASSPHRASE: ${NIP_CA_PASSPHRASE:?NIP_CA_PASSPHRASE is required}
14
- NIP_CA_BASE_URL: ${NIP_CA_BASE_URL:?NIP_CA_BASE_URL is required}
15
- NIP_CA_DISPLAY_NAME: ${NIP_CA_DISPLAY_NAME:-NPS CA}
16
- NIP_CA_KEY_FILE: /data/ca.key.enc
17
- NIP_CA_DB_PATH: /data/ca.db
18
- NIP_CA_AGENT_VALIDITY_DAYS: ${NIP_CA_AGENT_VALIDITY_DAYS:-30}
19
- NIP_CA_NODE_VALIDITY_DAYS: ${NIP_CA_NODE_VALIDITY_DAYS:-90}
20
- NIP_CA_RENEWAL_WINDOW_DAYS: ${NIP_CA_RENEWAL_WINDOW_DAYS:-7}
21
- healthcheck:
22
- test: ["CMD", "wget", "-qO-", "http://localhost:17440/health"]
23
- interval: 30s
24
- timeout: 5s
25
- retries: 3
26
- start_period: 10s
27
-
28
- volumes:
29
- ca_data:
@@ -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
- }
@@ -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
- }
@@ -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
- });
@@ -1,13 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "outDir": "dist",
7
- "rootDir": "src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true
11
- },
12
- "include": ["src"]
13
- }