@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,116 +0,0 @@
1
- // Copyright 2026 INNO LOTUS PTY LTD
2
- // SPDX-License-Identifier: Apache-2.0
3
-
4
- import type { AnnounceFrame, NdpResolveResult } from "./frames.js";
5
- import {
6
- extractHostFromTarget,
7
- parseNpsTxtRecord,
8
- SystemDnsTxtLookup,
9
- type DnsTxtLookup,
10
- } from "./dns-txt.js";
11
-
12
- interface RegistryEntry {
13
- frame: AnnounceFrame;
14
- expiresAt: number;
15
- }
16
-
17
- export class InMemoryNdpRegistry {
18
- private readonly _store = new Map<string, RegistryEntry>();
19
-
20
- // Replaceable for testing
21
- clock: () => number = () => Date.now();
22
-
23
- announce(frame: AnnounceFrame): void {
24
- const expiresAt = this.clock() + frame.ttl * 1000;
25
- if (frame.ttl === 0) {
26
- this._store.delete(frame.nid);
27
- return;
28
- }
29
- this._store.set(frame.nid, { frame, expiresAt });
30
- }
31
-
32
- getByNid(nid: string): AnnounceFrame | undefined {
33
- const entry = this._store.get(nid);
34
- if (entry === undefined) return undefined;
35
- if (this.clock() > entry.expiresAt) {
36
- this._store.delete(nid);
37
- return undefined;
38
- }
39
- return entry.frame;
40
- }
41
-
42
- resolve(target: string): NdpResolveResult | undefined {
43
- for (const [nid, entry] of this._store) {
44
- if (this.clock() > entry.expiresAt) { this._store.delete(nid); continue; }
45
- if (!InMemoryNdpRegistry.nwpTargetMatchesNid(nid, target)) continue;
46
- const addr = entry.frame.addresses[0];
47
- if (addr === undefined) continue;
48
- return { host: addr.host, port: addr.port, ttl: entry.frame.ttl };
49
- }
50
- return undefined;
51
- }
52
-
53
- getAll(): AnnounceFrame[] {
54
- const now = this.clock();
55
- const result: AnnounceFrame[] = [];
56
- for (const [nid, entry] of this._store) {
57
- if (now > entry.expiresAt) { this._store.delete(nid); continue; }
58
- result.push(entry.frame);
59
- }
60
- return result;
61
- }
62
-
63
- async resolveWithDns(
64
- target: string,
65
- resolver: DnsTxtLookup = new SystemDnsTxtLookup(),
66
- ): Promise<NdpResolveResult | undefined> {
67
- // 1. Try in-memory registry first
68
- const cached = this.resolve(target);
69
- if (cached !== undefined) return cached;
70
-
71
- // 2. Extract hostname and fall back to DNS TXT lookup
72
- const host = extractHostFromTarget(target);
73
- if (host === undefined) return undefined;
74
-
75
- const txtHost = `_nps-node.${host}`;
76
- let records: string[][];
77
- try {
78
- records = await resolver.resolveTxt(txtHost);
79
- } catch {
80
- return undefined;
81
- }
82
-
83
- for (const record of records) {
84
- const result = parseNpsTxtRecord(record, host);
85
- if (result !== undefined) return result;
86
- }
87
-
88
- return undefined;
89
- }
90
-
91
- static nwpTargetMatchesNid(nid: string, target: string): boolean {
92
- // NID: urn:nps:node:{authority}:{path-segment}
93
- // target: nwp://{authority}/{path}
94
- const nidParts = nid.split(":");
95
- if (nidParts.length < 5 || nidParts[0] !== "urn" || nidParts[1] !== "nps" || nidParts[2] !== "node") {
96
- return false;
97
- }
98
- if (!target.startsWith("nwp://")) return false;
99
-
100
- const nidAuthority = nidParts[3]!;
101
- const nidPath = nidParts[4]!;
102
- const rest = target.slice("nwp://".length);
103
- const slashIdx = rest.indexOf("/");
104
- if (slashIdx === -1) return false;
105
-
106
- const urlAuthority = rest.slice(0, slashIdx);
107
- const urlPath = rest.slice(slashIdx + 1); // without leading slash
108
-
109
- if (urlAuthority !== nidAuthority) return false;
110
-
111
- // nidPath must be a prefix of urlPath at a segment boundary
112
- if (urlPath === nidPath) return true;
113
- if (urlPath.startsWith(nidPath + "/")) return true;
114
- return false;
115
- }
116
- }
@@ -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 { AnnounceFrame, GraphFrame, ResolveFrame } from "./frames.js";
7
-
8
- export function registerNdpFrames(registry: FrameRegistry): void {
9
- registry.register(FrameType.ANNOUNCE, AnnounceFrame);
10
- registry.register(FrameType.RESOLVE, ResolveFrame);
11
- registry.register(FrameType.GRAPH, GraphFrame);
12
- }
@@ -1,64 +0,0 @@
1
- // Copyright 2026 INNO LOTUS PTY LTD
2
- // SPDX-License-Identifier: Apache-2.0
3
-
4
- import * as ed25519 from "@noble/ed25519";
5
- import { sha512 } from "@noble/hashes/sha512";
6
- import type { AnnounceFrame } from "./frames.js";
7
-
8
- ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
9
-
10
- export interface NdpAnnounceResult {
11
- isValid: boolean;
12
- errorCode?: string;
13
- message?: string;
14
- }
15
-
16
- export const NdpAnnounceResult = {
17
- ok: (): NdpAnnounceResult => ({ isValid: true }),
18
- fail: (errorCode: string, message: string): NdpAnnounceResult => ({ isValid: false, errorCode, message }),
19
- };
20
-
21
- export class NdpAnnounceValidator {
22
- private readonly _keys = new Map<string, string>(); // nid → "ed25519:<hex>"
23
-
24
- registerPublicKey(nid: string, encodedPubKey: string): void {
25
- this._keys.set(nid, encodedPubKey);
26
- }
27
-
28
- removePublicKey(nid: string): void {
29
- this._keys.delete(nid);
30
- }
31
-
32
- get knownPublicKeys(): ReadonlyMap<string, string> {
33
- return this._keys;
34
- }
35
-
36
- validate(frame: AnnounceFrame): NdpAnnounceResult {
37
- const encoded = this._keys.get(frame.nid);
38
- if (encoded === undefined) {
39
- return NdpAnnounceResult.fail("NDP-ANNOUNCE-NID-MISMATCH", `No public key registered for NID: ${frame.nid}`);
40
- }
41
-
42
- try {
43
- const prefix = "ed25519:";
44
- const pubHex = encoded.startsWith(prefix) ? encoded.slice(prefix.length) : encoded;
45
- const pubKey = Buffer.from(pubHex, "hex");
46
-
47
- const sig = frame.signature;
48
- if (!sig.startsWith(prefix)) {
49
- return NdpAnnounceResult.fail("NDP-ANNOUNCE-SIG-INVALID", "Signature must start with 'ed25519:'");
50
- }
51
- const sigBytes = Buffer.from(sig.slice(prefix.length), "base64");
52
-
53
- const unsigned = frame.unsignedDict();
54
- const canonical = JSON.stringify(unsigned, Object.keys(unsigned).sort());
55
- const message = new TextEncoder().encode(canonical);
56
-
57
- const valid = ed25519.verify(sigBytes, message, pubKey);
58
- if (!valid) return NdpAnnounceResult.fail("NDP-ANNOUNCE-SIG-INVALID", "Ed25519 signature verification failed.");
59
- return NdpAnnounceResult.ok();
60
- } catch {
61
- return NdpAnnounceResult.fail("NDP-ANNOUNCE-SIG-INVALID", "Ed25519 signature verification failed.");
62
- }
63
- }
64
- }
@@ -1,185 +0,0 @@
1
- // Copyright 2026 INNO LOTUS PTY LTD
2
- // SPDX-License-Identifier: Apache-2.0
3
-
4
- /**
5
- * ACME client implementing the `agent-01` challenge type per NPS-RFC-0002 §4.4.
6
- *
7
- * Flow: newNonce → newAccount → newOrder → fetch authz → sign challenge token →
8
- * finalize with CSR → fetch leaf cert.
9
- */
10
-
11
- import * as ed25519 from "@noble/ed25519";
12
- import { sha512 } from "@noble/hashes/sha512";
13
- import * as x509 from "@peculiar/x509";
14
-
15
- import * as Jws from "./jws.js";
16
- import type {
17
- Authorization, Challenge, Directory, FinalizePayload,
18
- Identifier, NewAccountPayload, NewOrderPayload, Order,
19
- } from "./messages.js";
20
- import * as wire from "./wire.js";
21
-
22
- ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
23
- x509.cryptoProvider.set(globalThis.crypto);
24
-
25
- export interface AcmeClientOptions {
26
- /** ACME directory URL. */
27
- directoryUrl: string;
28
- /** Account/agent Ed25519 private key (32-byte raw). */
29
- privateKey: Uint8Array;
30
- /** Account/agent Ed25519 public key (32-byte raw). */
31
- publicKey: Uint8Array;
32
- /** Web Crypto Ed25519 keypair for CSR signing (must match privateKey). */
33
- webCryptoKeys: CryptoKeyPair;
34
- }
35
-
36
- export class AcmeClient {
37
- private directory: Directory | null = null;
38
- private accountUrl: string | null = null;
39
- private lastNonce: string | null = null;
40
-
41
- constructor(public readonly options: AcmeClientOptions) {}
42
-
43
- /** Drive the full agent-01 flow for `nid`. Returns issued PEM cert chain. */
44
- async issueAgentCert(nid: string): Promise<string> {
45
- await this.ensureDirectory();
46
- if (this.accountUrl === null) await this.newAccount();
47
- const order = await this.newOrder(nid);
48
- const authz = await this.fetchAuthz(order.authorizations[0]);
49
- await this.respondAgent01(authz);
50
- const finalized = await this.finalizeOrder(order, nid);
51
- return this.downloadPem(finalized.certificate!);
52
- }
53
-
54
- // ── Stages ───────────────────────────────────────────────────────────────
55
-
56
- private async ensureDirectory(): Promise<void> {
57
- if (this.directory !== null) return;
58
- const resp = await fetch(this.options.directoryUrl);
59
- ensureSuccess(resp);
60
- this.directory = await resp.json() as Directory;
61
- await this.refreshNonce();
62
- }
63
-
64
- private async refreshNonce(): Promise<void> {
65
- const resp = await fetch(this.directory!.newNonce, { method: "HEAD" });
66
- ensureSuccess(resp);
67
- this.lastNonce = resp.headers.get("Replay-Nonce");
68
- if (this.lastNonce === null) {
69
- throw new Error("server omitted Replay-Nonce");
70
- }
71
- }
72
-
73
- private async newAccount(): Promise<void> {
74
- const jwk = Jws.jwkFromPublicKey(this.options.publicKey);
75
- const env = Jws.sign(
76
- { alg: Jws.ALG_EDDSA, nonce: this.lastNonce!, url: this.directory!.newAccount, jwk },
77
- { termsOfServiceAgreed: true } as NewAccountPayload,
78
- this.options.privateKey);
79
-
80
- const resp = await this.post(this.directory!.newAccount, env);
81
- ensureSuccess(resp);
82
- this.accountUrl = resp.headers.get("Location");
83
- if (this.accountUrl === null) throw new Error("server omitted account Location");
84
- this.captureNonce(resp);
85
- }
86
-
87
- private async newOrder(nid: string): Promise<Order> {
88
- const env = Jws.sign(
89
- { alg: Jws.ALG_EDDSA, nonce: this.lastNonce!, url: this.directory!.newOrder, kid: this.accountUrl! },
90
- {
91
- identifiers: [{ type: wire.IDENTIFIER_TYPE_NID, value: nid } as Identifier],
92
- } as NewOrderPayload,
93
- this.options.privateKey);
94
-
95
- const resp = await this.post(this.directory!.newOrder, env);
96
- ensureSuccess(resp);
97
- this.captureNonce(resp);
98
- return await resp.json() as Order;
99
- }
100
-
101
- private async fetchAuthz(url: string): Promise<Authorization> {
102
- // POST-as-GET (RFC 8555 §6.3).
103
- const env = Jws.sign(
104
- { alg: Jws.ALG_EDDSA, nonce: this.lastNonce!, url, kid: this.accountUrl! },
105
- null,
106
- this.options.privateKey);
107
- const resp = await this.post(url, env);
108
- ensureSuccess(resp);
109
- this.captureNonce(resp);
110
- return await resp.json() as Authorization;
111
- }
112
-
113
- private async respondAgent01(authz: Authorization): Promise<void> {
114
- const challenge = authz.challenges.find((c) => c.type === wire.CHALLENGE_AGENT_01);
115
- if (!challenge) throw new Error("authz has no agent-01 challenge");
116
-
117
- // Sign the challenge token with the account/NID private key.
118
- const tokenBytes = new TextEncoder().encode(challenge.token);
119
- const sig = ed25519.sign(tokenBytes, this.options.privateKey);
120
-
121
- const env = Jws.sign(
122
- { alg: Jws.ALG_EDDSA, nonce: this.lastNonce!, url: challenge.url, kid: this.accountUrl! },
123
- { agent_signature: Jws.b64uEncode(sig) },
124
- this.options.privateKey);
125
- const resp = await this.post(challenge.url, env);
126
- ensureSuccess(resp);
127
- this.captureNonce(resp);
128
- }
129
-
130
- private async finalizeOrder(order: Order, nid: string): Promise<Order> {
131
- const csrDer = await this.buildCsr(nid);
132
- const env = Jws.sign(
133
- { alg: Jws.ALG_EDDSA, nonce: this.lastNonce!, url: order.finalize, kid: this.accountUrl! },
134
- { csr: Jws.b64uEncode(csrDer) } as FinalizePayload,
135
- this.options.privateKey);
136
- const resp = await this.post(order.finalize, env);
137
- ensureSuccess(resp);
138
- this.captureNonce(resp);
139
- return await resp.json() as Order;
140
- }
141
-
142
- private async downloadPem(certUrl: string): Promise<string> {
143
- const env = Jws.sign(
144
- { alg: Jws.ALG_EDDSA, nonce: this.lastNonce!, url: certUrl, kid: this.accountUrl! },
145
- null,
146
- this.options.privateKey);
147
- const resp = await this.post(certUrl, env);
148
- ensureSuccess(resp);
149
- this.captureNonce(resp);
150
- return await resp.text();
151
- }
152
-
153
- // ── helpers ──────────────────────────────────────────────────────────────
154
-
155
- private async post(url: string, env: Jws.Envelope): Promise<Response> {
156
- return await fetch(url, {
157
- method: "POST",
158
- headers: { "Content-Type": wire.CONTENT_TYPE_JOSE_JSON },
159
- body: JSON.stringify(env),
160
- });
161
- }
162
-
163
- private captureNonce(resp: Response): void {
164
- const nonce = resp.headers.get("Replay-Nonce");
165
- if (nonce !== null) this.lastNonce = nonce;
166
- }
167
-
168
- private async buildCsr(nid: string): Promise<Uint8Array> {
169
- const csr = await x509.Pkcs10CertificateRequestGenerator.create({
170
- name: `CN=${nid.replace(/([",+;<>\\])/g, "\\$1")}`,
171
- keys: this.options.webCryptoKeys,
172
- signingAlgorithm: { name: "Ed25519" },
173
- extensions: [
174
- new x509.SubjectAlternativeNameExtension([{ type: "url", value: nid }], false),
175
- ],
176
- });
177
- return new Uint8Array(csr.rawData);
178
- }
179
- }
180
-
181
- function ensureSuccess(resp: Response): void {
182
- if (!resp.ok) {
183
- throw new Error(`ACME ${resp.url} HTTP ${resp.status}`);
184
- }
185
- }
@@ -1,8 +0,0 @@
1
- // Copyright 2026 INNO LOTUS PTY LTD
2
- // SPDX-License-Identifier: Apache-2.0
3
-
4
- export * from "./client.js";
5
- export * from "./jws.js";
6
- export * from "./messages.js";
7
- export * from "./server.js";
8
- export * from "./wire.js";
@@ -1,109 +0,0 @@
1
- // Copyright 2026 INNO LOTUS PTY LTD
2
- // SPDX-License-Identifier: Apache-2.0
3
-
4
- /**
5
- * JWS signing helpers for ACME with Ed25519 (`alg: "EdDSA"` per RFC 8037).
6
- *
7
- * Wire shape (RFC 8555 §6.2 + RFC 7515 flattened JWS JSON serialization):
8
- * {
9
- * "protected": base64url(JSON({alg, nonce, url, [jwk|kid]})),
10
- * "payload": base64url(JSON(payload)),
11
- * "signature": base64url(Ed25519(protected || "." || payload))
12
- * }
13
- */
14
-
15
- import * as ed25519 from "@noble/ed25519";
16
- import { sha512 } from "@noble/hashes/sha512";
17
- import { sha256 } from "@noble/hashes/sha2";
18
-
19
- ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
20
-
21
- export const ALG_EDDSA = "EdDSA"; // RFC 8037 §3.1
22
- export const KTY_OKP = "OKP"; // RFC 8037 §2
23
- export const CRV_ED25519 = "Ed25519"; // RFC 8037 §2
24
-
25
- export interface Jwk {
26
- kty: string;
27
- crv: string;
28
- x: string;
29
- }
30
-
31
- export interface ProtectedHeader {
32
- alg: string;
33
- nonce: string;
34
- url: string;
35
- jwk?: Jwk;
36
- kid?: string;
37
- }
38
-
39
- export interface Envelope {
40
- protected: string;
41
- payload: string;
42
- signature: string;
43
- }
44
-
45
- export function jwkFromPublicKey(rawPubKey: Uint8Array): Jwk {
46
- if (rawPubKey.length !== 32) {
47
- throw new Error(`Ed25519 public key must be 32 bytes, got ${rawPubKey.length}`);
48
- }
49
- return { kty: KTY_OKP, crv: CRV_ED25519, x: b64uEncode(rawPubKey) };
50
- }
51
-
52
- export function publicKeyFromJwk(jwk: Jwk): Uint8Array {
53
- if (jwk.kty !== KTY_OKP || jwk.crv !== CRV_ED25519) {
54
- throw new Error(`JWK is not OKP/Ed25519: kty=${jwk.kty} crv=${jwk.crv}`);
55
- }
56
- return b64uDecode(jwk.x);
57
- }
58
-
59
- /** RFC 7638 §3 thumbprint of an Ed25519 JWK (lex-sorted compact JSON, SHA-256, base64url). */
60
- export function thumbprint(jwk: Jwk): string {
61
- const canonical = `{"crv":"${jwk.crv}","kty":"${jwk.kty}","x":"${jwk.x}"}`;
62
- return b64uEncode(sha256(new TextEncoder().encode(canonical)));
63
- }
64
-
65
- export function sign(
66
- header: ProtectedHeader,
67
- payload: unknown | null,
68
- privKey: Uint8Array,
69
- ): Envelope {
70
- const headerBytes = new TextEncoder().encode(JSON.stringify(header));
71
- const headerB64u = b64uEncode(headerBytes);
72
- const payloadB64u = payload === null
73
- ? ""
74
- : b64uEncode(new TextEncoder().encode(JSON.stringify(payload)));
75
- const signingInput = new TextEncoder().encode(`${headerB64u}.${payloadB64u}`);
76
- const sig = ed25519.sign(signingInput, privKey);
77
- return { protected: headerB64u, payload: payloadB64u, signature: b64uEncode(sig) };
78
- }
79
-
80
- /** Verify a JWS envelope. Returns the parsed protected header on success, else null. */
81
- export function verify(envelope: Envelope, pubKey: Uint8Array): ProtectedHeader | null {
82
- try {
83
- const signingInput = new TextEncoder().encode(`${envelope.protected}.${envelope.payload}`);
84
- const sigBytes = b64uDecode(envelope.signature);
85
- if (!ed25519.verify(sigBytes, signingInput, pubKey)) return null;
86
- const headerJson = new TextDecoder().decode(b64uDecode(envelope.protected));
87
- return JSON.parse(headerJson) as ProtectedHeader;
88
- } catch {
89
- return null;
90
- }
91
- }
92
-
93
- export function decodePayload<T = unknown>(envelope: Envelope): T | null {
94
- if (!envelope.payload) return null;
95
- return JSON.parse(new TextDecoder().decode(b64uDecode(envelope.payload))) as T;
96
- }
97
-
98
- // ── helpers ──────────────────────────────────────────────────────────────────
99
-
100
- export function b64uEncode(bytes: Uint8Array): string {
101
- return Buffer.from(bytes).toString("base64").replace(/=+$/, "")
102
- .replace(/\+/g, "-").replace(/\//g, "_");
103
- }
104
-
105
- export function b64uDecode(s: string): Uint8Array {
106
- const padded = s + "=".repeat((4 - (s.length % 4)) % 4);
107
- const std = padded.replace(/-/g, "+").replace(/_/g, "/");
108
- return new Uint8Array(Buffer.from(std, "base64"));
109
- }
@@ -1,85 +0,0 @@
1
- // Copyright 2026 INNO LOTUS PTY LTD
2
- // SPDX-License-Identifier: Apache-2.0
3
-
4
- /** ACME wire-level DTOs (RFC 8555 + NPS-RFC-0002 §4.4) — plain interfaces. */
5
-
6
- export interface DirectoryMeta {
7
- termsOfService?: string;
8
- website?: string;
9
- caaIdentities?: readonly string[];
10
- externalAccountRequired?: boolean;
11
- }
12
-
13
- export interface Directory {
14
- newNonce: string;
15
- newAccount: string;
16
- newOrder: string;
17
- revokeCert?: string;
18
- keyChange?: string;
19
- meta?: DirectoryMeta;
20
- }
21
-
22
- export interface NewAccountPayload {
23
- termsOfServiceAgreed?: boolean;
24
- contact?: readonly string[];
25
- onlyReturnExisting?: boolean;
26
- }
27
-
28
- export interface Account {
29
- status: string;
30
- contact?: readonly string[];
31
- orders?: string;
32
- }
33
-
34
- export interface Identifier {
35
- type: string; // "nid" per NPS-RFC-0002 §4.4
36
- value: string;
37
- }
38
-
39
- export interface NewOrderPayload {
40
- identifiers: readonly Identifier[];
41
- notBefore?: string;
42
- notAfter?: string;
43
- }
44
-
45
- export interface ProblemDetail {
46
- type: string;
47
- detail?: string;
48
- status?: number;
49
- }
50
-
51
- export interface Order {
52
- status: string;
53
- expires?: string;
54
- identifiers: readonly Identifier[];
55
- authorizations: readonly string[];
56
- finalize: string;
57
- certificate?: string;
58
- error?: ProblemDetail;
59
- }
60
-
61
- export interface Challenge {
62
- type: string; // "agent-01" per NPS-RFC-0002 §4.4
63
- url: string;
64
- status: string;
65
- token: string;
66
- validated?: string;
67
- error?: ProblemDetail;
68
- }
69
-
70
- export interface Authorization {
71
- status: string;
72
- expires?: string;
73
- identifier: Identifier;
74
- challenges: readonly Challenge[];
75
- }
76
-
77
- export interface ChallengeRespondPayload {
78
- /** base64url(Ed25519(token)) per NPS-RFC-0002 §4.4. */
79
- agent_signature: string;
80
- }
81
-
82
- export interface FinalizePayload {
83
- /** base64url(CSR DER). */
84
- csr: string;
85
- }