@push.rocks/smartacme 9.1.3 → 9.3.0

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 (89) hide show
  1. package/dist_ts/00_commitinfo_data.js +3 -3
  2. package/dist_ts/acme/acme.classes.account.js +1 -1
  3. package/dist_ts/acme/acme.classes.challenge.js +1 -1
  4. package/dist_ts/acme/acme.classes.client.js +1 -1
  5. package/dist_ts/acme/acme.classes.crypto.d.ts +1 -1
  6. package/dist_ts/acme/acme.classes.crypto.js +1 -1
  7. package/dist_ts/acme/acme.classes.directory.js +1 -1
  8. package/dist_ts/acme/acme.classes.error.js +1 -1
  9. package/dist_ts/acme/acme.classes.http-client.js +1 -1
  10. package/dist_ts/acme/acme.classes.order.js +1 -1
  11. package/dist_ts/acme/acme.interfaces.js +1 -1
  12. package/dist_ts/acme/index.js +1 -1
  13. package/dist_ts/certmanagers/index.js +1 -1
  14. package/dist_ts/certmanagers/memory.js +1 -1
  15. package/dist_ts/certmanagers/mongo.js +1 -1
  16. package/dist_ts/handlers/Dns01Handler.js +1 -1
  17. package/dist_ts/handlers/Http01Handler.js +1 -1
  18. package/dist_ts/handlers/Http01MemoryHandler.js +1 -1
  19. package/dist_ts/handlers/IChallengeHandler.js +1 -1
  20. package/dist_ts/handlers/index.js +1 -1
  21. package/dist_ts/index.d.ts +2 -0
  22. package/dist_ts/index.js +4 -1
  23. package/dist_ts/interfaces/accountdata.js +1 -1
  24. package/dist_ts/interfaces/certmanager.js +1 -1
  25. package/dist_ts/interfaces/index.js +1 -1
  26. package/dist_ts/plugins.js +1 -1
  27. package/dist_ts/smartacme.classes.cert.js +1 -1
  28. package/dist_ts/smartacme.classes.certmatcher.js +1 -1
  29. package/dist_ts/smartacme.classes.smartacme.js +1 -1
  30. package/dist_ts_server/index.d.ts +4 -0
  31. package/dist_ts_server/index.js +4 -0
  32. package/dist_ts_server/server.classes.account.store.d.ts +12 -0
  33. package/dist_ts_server/server.classes.account.store.js +25 -0
  34. package/dist_ts_server/server.classes.acmeserver.d.ts +22 -0
  35. package/dist_ts_server/server.classes.acmeserver.js +100 -0
  36. package/dist_ts_server/server.classes.ca.d.ts +25 -0
  37. package/dist_ts_server/server.classes.ca.js +109 -0
  38. package/dist_ts_server/server.classes.challenge.verifier.d.ts +16 -0
  39. package/dist_ts_server/server.classes.challenge.verifier.js +56 -0
  40. package/dist_ts_server/server.classes.jws.verifier.d.ts +31 -0
  41. package/dist_ts_server/server.classes.jws.verifier.js +116 -0
  42. package/dist_ts_server/server.classes.nonce.d.ts +12 -0
  43. package/dist_ts_server/server.classes.nonce.js +32 -0
  44. package/dist_ts_server/server.classes.order.store.d.ts +21 -0
  45. package/dist_ts_server/server.classes.order.store.js +57 -0
  46. package/dist_ts_server/server.classes.router.d.ts +17 -0
  47. package/dist_ts_server/server.classes.router.js +99 -0
  48. package/dist_ts_server/server.handlers.account.d.ts +8 -0
  49. package/dist_ts_server/server.handlers.account.js +59 -0
  50. package/dist_ts_server/server.handlers.authz.d.ts +7 -0
  51. package/dist_ts_server/server.handlers.authz.js +41 -0
  52. package/dist_ts_server/server.handlers.cert.d.ts +7 -0
  53. package/dist_ts_server/server.handlers.cert.js +18 -0
  54. package/dist_ts_server/server.handlers.challenge.d.ts +10 -0
  55. package/dist_ts_server/server.handlers.challenge.js +110 -0
  56. package/dist_ts_server/server.handlers.directory.d.ts +5 -0
  57. package/dist_ts_server/server.handlers.directory.js +23 -0
  58. package/dist_ts_server/server.handlers.finalize.d.ts +8 -0
  59. package/dist_ts_server/server.handlers.finalize.js +65 -0
  60. package/dist_ts_server/server.handlers.nonce.d.ts +7 -0
  61. package/dist_ts_server/server.handlers.nonce.js +17 -0
  62. package/dist_ts_server/server.handlers.order.d.ts +11 -0
  63. package/dist_ts_server/server.handlers.order.js +131 -0
  64. package/dist_ts_server/server.interfaces.d.ts +72 -0
  65. package/dist_ts_server/server.interfaces.js +2 -0
  66. package/package.json +18 -13
  67. package/readme.hints.md +25 -1
  68. package/readme.md +122 -2
  69. package/ts/00_commitinfo_data.ts +2 -2
  70. package/ts/acme/acme.classes.crypto.ts +1 -1
  71. package/ts/index.ts +4 -0
  72. package/ts_server/index.ts +12 -0
  73. package/ts_server/server.classes.account.store.ts +27 -0
  74. package/ts_server/server.classes.acmeserver.ts +128 -0
  75. package/ts_server/server.classes.ca.ts +142 -0
  76. package/ts_server/server.classes.challenge.verifier.ts +61 -0
  77. package/ts_server/server.classes.jws.verifier.ts +153 -0
  78. package/ts_server/server.classes.nonce.ts +36 -0
  79. package/ts_server/server.classes.order.store.ts +72 -0
  80. package/ts_server/server.classes.router.ts +116 -0
  81. package/ts_server/server.handlers.account.ts +85 -0
  82. package/ts_server/server.handlers.authz.ts +58 -0
  83. package/ts_server/server.handlers.cert.ts +32 -0
  84. package/ts_server/server.handlers.challenge.ts +142 -0
  85. package/ts_server/server.handlers.directory.ts +30 -0
  86. package/ts_server/server.handlers.finalize.ts +93 -0
  87. package/ts_server/server.handlers.nonce.ts +29 -0
  88. package/ts_server/server.handlers.order.ts +177 -0
  89. package/ts_server/server.interfaces.ts +98 -0
@@ -0,0 +1,131 @@
1
+ import * as crypto from 'node:crypto';
2
+ import { AcmeServerError } from './server.classes.jws.verifier.js';
3
+ /**
4
+ * POST /new-order — Create a new ACME order.
5
+ */
6
+ export function createNewOrderHandler(baseUrl, jwsVerifier, orderStore) {
7
+ return async (req, res, _params, body) => {
8
+ const requestUrl = `${baseUrl}/new-order`;
9
+ const verified = await jwsVerifier.verify(body, requestUrl);
10
+ if (!verified.kid) {
11
+ throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'newOrder requires kid');
12
+ }
13
+ const { payload } = verified;
14
+ const identifiers = payload?.identifiers;
15
+ if (!identifiers || !Array.isArray(identifiers) || identifiers.length === 0) {
16
+ throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'Order must include at least one identifier');
17
+ }
18
+ const orderId = crypto.randomBytes(16).toString('hex');
19
+ const expires = new Date();
20
+ expires.setDate(expires.getDate() + 7);
21
+ // Create authorizations and challenges for each identifier
22
+ const authorizationIds = [];
23
+ for (const identifier of identifiers) {
24
+ const authzId = crypto.randomBytes(16).toString('hex');
25
+ const isWildcard = identifier.value.startsWith('*.');
26
+ const domain = isWildcard ? identifier.value.slice(2) : identifier.value;
27
+ // Create challenges for this authorization
28
+ const challengeIds = [];
29
+ // HTTP-01 challenge (not for wildcards)
30
+ if (!isWildcard) {
31
+ const http01Id = crypto.randomBytes(16).toString('hex');
32
+ const http01Token = crypto.randomBytes(32).toString('base64url');
33
+ await orderStore.createChallenge({
34
+ id: http01Id,
35
+ authorizationId: authzId,
36
+ type: 'http-01',
37
+ token: http01Token,
38
+ status: 'pending',
39
+ });
40
+ challengeIds.push(http01Id);
41
+ }
42
+ // DNS-01 challenge (always)
43
+ const dns01Id = crypto.randomBytes(16).toString('hex');
44
+ const dns01Token = crypto.randomBytes(32).toString('base64url');
45
+ await orderStore.createChallenge({
46
+ id: dns01Id,
47
+ authorizationId: authzId,
48
+ type: 'dns-01',
49
+ token: dns01Token,
50
+ status: 'pending',
51
+ });
52
+ challengeIds.push(dns01Id);
53
+ await orderStore.createAuthorization({
54
+ id: authzId,
55
+ orderId,
56
+ identifier: { type: 'dns', value: domain },
57
+ status: 'pending',
58
+ expires: expires.toISOString(),
59
+ challengeIds,
60
+ wildcard: isWildcard || undefined,
61
+ });
62
+ authorizationIds.push(authzId);
63
+ }
64
+ const order = await orderStore.createOrder({
65
+ id: orderId,
66
+ accountUrl: verified.kid,
67
+ status: 'pending',
68
+ identifiers,
69
+ authorizationIds,
70
+ expires: expires.toISOString(),
71
+ finalize: `${baseUrl}/finalize/${orderId}`,
72
+ });
73
+ const responseBody = {
74
+ status: order.status,
75
+ expires: order.expires,
76
+ identifiers: order.identifiers,
77
+ authorizations: order.authorizationIds.map((id) => `${baseUrl}/authz/${id}`),
78
+ finalize: order.finalize,
79
+ };
80
+ res.writeHead(201, {
81
+ 'Content-Type': 'application/json',
82
+ 'Location': `${baseUrl}/order/${orderId}`,
83
+ });
84
+ res.end(JSON.stringify(responseBody));
85
+ };
86
+ }
87
+ /**
88
+ * POST /order/:id — Poll order status (POST-as-GET).
89
+ */
90
+ export function createOrderPollHandler(baseUrl, jwsVerifier, orderStore) {
91
+ return async (req, res, params, body) => {
92
+ const orderId = params.id;
93
+ const requestUrl = `${baseUrl}/order/${orderId}`;
94
+ await jwsVerifier.verify(body, requestUrl);
95
+ const order = await orderStore.getOrder(orderId);
96
+ if (!order) {
97
+ throw new AcmeServerError(404, 'urn:ietf:params:acme:error:malformed', 'Order not found');
98
+ }
99
+ // Check if all authorizations are valid → transition to ready
100
+ if (order.status === 'pending') {
101
+ let allValid = true;
102
+ for (const authzId of order.authorizationIds) {
103
+ const authz = await orderStore.getAuthorization(authzId);
104
+ if (!authz || authz.status !== 'valid') {
105
+ allValid = false;
106
+ break;
107
+ }
108
+ }
109
+ if (allValid) {
110
+ await orderStore.updateOrder(orderId, { status: 'ready' });
111
+ order.status = 'ready';
112
+ }
113
+ }
114
+ const responseBody = {
115
+ status: order.status,
116
+ expires: order.expires,
117
+ identifiers: order.identifiers,
118
+ authorizations: order.authorizationIds.map((id) => `${baseUrl}/authz/${id}`),
119
+ finalize: order.finalize,
120
+ };
121
+ if (order.certificate) {
122
+ responseBody.certificate = order.certificate;
123
+ }
124
+ res.writeHead(200, {
125
+ 'Content-Type': 'application/json',
126
+ 'Location': requestUrl,
127
+ });
128
+ res.end(JSON.stringify(responseBody));
129
+ };
130
+ }
131
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLmhhbmRsZXJzLm9yZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfc2VydmVyL3NlcnZlci5oYW5kbGVycy5vcmRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEtBQUssTUFBTSxNQUFNLGFBQWEsQ0FBQztBQUV0QyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFJbkU7O0dBRUc7QUFDSCxNQUFNLFVBQVUscUJBQXFCLENBQ25DLE9BQWUsRUFDZixXQUF3QixFQUN4QixVQUE2QjtJQUU3QixPQUFPLEtBQUssRUFDVixHQUF5QixFQUN6QixHQUF3QixFQUN4QixPQUErQixFQUMvQixJQUFTLEVBQ00sRUFBRTtRQUNqQixNQUFNLFVBQVUsR0FBRyxHQUFHLE9BQU8sWUFBWSxDQUFDO1FBQzFDLE1BQU0sUUFBUSxHQUFHLE1BQU0sV0FBVyxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsVUFBVSxDQUFDLENBQUM7UUFFNUQsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLEVBQUUsQ0FBQztZQUNsQixNQUFNLElBQUksZUFBZSxDQUFDLEdBQUcsRUFBRSxzQ0FBc0MsRUFBRSx1QkFBdUIsQ0FBQyxDQUFDO1FBQ2xHLENBQUM7UUFFRCxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsUUFBUSxDQUFDO1FBQzdCLE1BQU0sV0FBVyxHQUFzQixPQUFPLEVBQUUsV0FBVyxDQUFDO1FBRTVELElBQUksQ0FBQyxXQUFXLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxJQUFJLFdBQVcsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDNUUsTUFBTSxJQUFJLGVBQWUsQ0FDdkIsR0FBRyxFQUNILHNDQUFzQyxFQUN0Qyw0Q0FBNEMsQ0FDN0MsQ0FBQztRQUNKLENBQUM7UUFFRCxNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQyxD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
@@ -0,0 +1,72 @@
1
+ import type { IAcmeIdentifier } from '../dist_ts/acme/acme.interfaces.js';
2
+ export interface IAcmeServerOptions {
3
+ port?: number;
4
+ hostname?: string;
5
+ baseUrl?: string;
6
+ /** When false, challenges auto-approve on trigger (useful for testing) */
7
+ challengeVerification?: boolean;
8
+ caOptions?: {
9
+ commonName?: string;
10
+ validityDays?: number;
11
+ certValidityDays?: number;
12
+ };
13
+ }
14
+ export interface IServerAccountStore {
15
+ create(account: IServerAccount): Promise<IServerAccount>;
16
+ getByThumbprint(thumbprint: string): Promise<IServerAccount | null>;
17
+ getByUrl(url: string): Promise<IServerAccount | null>;
18
+ }
19
+ export interface IServerOrderStore {
20
+ createOrder(order: IServerOrder): Promise<IServerOrder>;
21
+ getOrder(id: string): Promise<IServerOrder | null>;
22
+ updateOrder(id: string, updates: Partial<IServerOrder>): Promise<void>;
23
+ createAuthorization(authz: IServerAuthorization): Promise<IServerAuthorization>;
24
+ getAuthorization(id: string): Promise<IServerAuthorization | null>;
25
+ updateAuthorization(id: string, updates: Partial<IServerAuthorization>): Promise<void>;
26
+ createChallenge(challenge: IServerChallenge): Promise<IServerChallenge>;
27
+ getChallenge(id: string): Promise<IServerChallenge | null>;
28
+ updateChallenge(id: string, updates: Partial<IServerChallenge>): Promise<void>;
29
+ storeCertPem(orderId: string, pem: string): Promise<void>;
30
+ getCertPem(orderId: string): Promise<string | null>;
31
+ }
32
+ export interface IServerAccount {
33
+ id: string;
34
+ thumbprint: string;
35
+ url: string;
36
+ jwk: Record<string, string>;
37
+ status: string;
38
+ contact: string[];
39
+ createdAt: string;
40
+ }
41
+ export interface IServerOrder {
42
+ id: string;
43
+ accountUrl: string;
44
+ status: string;
45
+ identifiers: IAcmeIdentifier[];
46
+ authorizationIds: string[];
47
+ expires: string;
48
+ finalize: string;
49
+ certificate?: string;
50
+ }
51
+ export interface IServerAuthorization {
52
+ id: string;
53
+ orderId: string;
54
+ identifier: IAcmeIdentifier;
55
+ status: string;
56
+ expires: string;
57
+ challengeIds: string[];
58
+ wildcard?: boolean;
59
+ }
60
+ export interface IServerChallenge {
61
+ id: string;
62
+ authorizationId: string;
63
+ type: string;
64
+ token: string;
65
+ status: string;
66
+ validated?: string;
67
+ error?: {
68
+ type: string;
69
+ detail: string;
70
+ };
71
+ }
72
+ export type TRouteHandler = (req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse, params: Record<string, string>, body: any) => Promise<void>;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLmludGVyZmFjZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19zZXJ2ZXIvc2VydmVyLmludGVyZmFjZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@push.rocks/smartacme",
3
- "version": "9.1.3",
3
+ "version": "9.3.0",
4
4
  "private": false,
5
- "description": "A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.",
5
+ "description": "A TypeScript-based ACME client and server for certificate management with built-in CA, supporting LetsEncrypt and custom ACME authorities.",
6
6
  "main": "dist_ts/index.js",
7
7
  "typings": "dist_ts/index.d.ts",
8
8
  "type": "module",
9
9
  "scripts": {
10
10
  "test": "(tstest test/ --verbose --logfile --timeout 600)",
11
- "build": "(tsbuild)",
11
+ "build": "(tsbuild tsfolders)",
12
12
  "buildDocs": "tsdoc"
13
13
  },
14
14
  "repository": {
@@ -20,6 +20,9 @@
20
20
  "LetsEncrypt",
21
21
  "TypeScript",
22
22
  "certificate management",
23
+ "certificate authority",
24
+ "ACME server",
25
+ "PKI",
23
26
  "DNS challenges",
24
27
  "SSL/TLS",
25
28
  "secure communication",
@@ -28,9 +31,11 @@
28
31
  "crypto",
29
32
  "MongoDB",
30
33
  "dns-01 challenge",
34
+ "http-01 challenge",
31
35
  "token-based challenges",
32
36
  "certificate renewal",
33
- "wildcard certificates"
37
+ "wildcard certificates",
38
+ "RFC 8555"
34
39
  ],
35
40
  "author": "Task Venture Capital GmbH",
36
41
  "license": "MIT",
@@ -41,28 +46,28 @@
41
46
  "dependencies": {
42
47
  "@apiclient.xyz/cloudflare": "^7.1.0",
43
48
  "@peculiar/x509": "^1.14.3",
44
- "@push.rocks/lik": "^6.2.2",
45
- "@push.rocks/smartdata": "^7.0.15",
49
+ "@push.rocks/lik": "^6.3.1",
50
+ "@push.rocks/smartdata": "^7.1.0",
46
51
  "@push.rocks/smartdelay": "^3.0.5",
47
- "@push.rocks/smartdns": "^7.8.1",
48
- "@push.rocks/smartlog": "^3.1.10",
52
+ "@push.rocks/smartdns": "^7.9.0",
53
+ "@push.rocks/smartlog": "^3.2.1",
49
54
  "@push.rocks/smartnetwork": "^4.4.0",
50
55
  "@push.rocks/smartstring": "^4.1.0",
51
56
  "@push.rocks/smarttime": "^4.2.3",
52
57
  "@push.rocks/smartunique": "^3.0.9",
53
58
  "@push.rocks/taskbuffer": "^6.1.2",
54
- "@tsclass/tsclass": "^9.3.0"
59
+ "@tsclass/tsclass": "^9.5.0"
55
60
  },
56
61
  "devDependencies": {
57
- "@git.zone/tsbuild": "^4.1.2",
62
+ "@git.zone/tsbuild": "^4.3.0",
58
63
  "@git.zone/tsrun": "^2.0.1",
59
- "@git.zone/tstest": "^3.1.8",
64
+ "@git.zone/tstest": "^3.4.0",
60
65
  "@push.rocks/qenv": "^6.1.3",
61
- "@types/node": "^25.2.3"
66
+ "@types/node": "^25.5.0"
62
67
  },
63
68
  "files": [
64
69
  "ts/**/*",
65
- "ts_web/**/*",
70
+ "ts_*/**/*",
66
71
  "dist/**/*",
67
72
  "dist_*/**/*",
68
73
  "dist_ts/**/*",
package/readme.hints.md CHANGED
@@ -43,10 +43,34 @@ Key implementation details:
43
43
  - `TaskManager.start()` is called in `SmartAcme.start()` and `TaskManager.stop()` in `SmartAcme.stop()`.
44
44
  - The "no cronjobs specified" log messages during tests come from taskbuffer's internal CronManager polling — harmless noise when no cron tasks are scheduled.
45
45
 
46
+ ## ACME Directory Server (ts_server/)
47
+
48
+ As of v9.2.0, a built-in ACME Directory Server lives under `ts_server/`. This is a full RFC 8555-compliant CA server that allows running your own Certificate Authority.
49
+
50
+ Key files:
51
+ - `ts_server/server.classes.acmeserver.ts` — Top-level `AcmeServer` facade (start/stop/config)
52
+ - `ts_server/server.classes.ca.ts` — Self-signed root CA generation + certificate signing via `@peculiar/x509`
53
+ - `ts_server/server.classes.jws.verifier.ts` — JWS signature verification (inverse of `AcmeCrypto.createJws`)
54
+ - `ts_server/server.classes.router.ts` — Minimal HTTP router with `:param` support using raw `node:http`
55
+ - `ts_server/server.classes.nonce.ts` — Single-use replay nonce management
56
+ - `ts_server/server.classes.challenge.verifier.ts` — HTTP-01/DNS-01 verification (with bypass mode)
57
+ - `ts_server/server.classes.account.store.ts` — In-memory account storage
58
+ - `ts_server/server.classes.order.store.ts` — In-memory order/authz/challenge/cert storage
59
+ - `ts_server/server.handlers.*.ts` — Route handlers for each ACME endpoint
60
+
61
+ Design decisions:
62
+ - Uses raw `node:http` (no framework dependency — `@api.global/typedserver` was explicitly removed in v8.1.0)
63
+ - Zero new dependencies: uses `node:crypto`, `@peculiar/x509`, and existing project deps
64
+ - Reuses `AcmeCrypto` for JWK thumbprint/base64url, ACME interfaces for response types, `AcmeError` patterns
65
+ - `AcmeCrypto.getAlg()` was made public (was private) for use by the JWS verifier
66
+ - Storage interfaces (`IServerAccountStore`, `IServerOrderStore`) are pluggable, with in-memory defaults
67
+ - `challengeVerification: false` option auto-approves challenges for testing
68
+ - `tsbuild tsfolders` automatically compiles `ts_server/` to `dist_ts_server/`
69
+
46
70
  ## Dependency Notes
47
71
 
48
72
  - `acme-client` was replaced with custom implementation in `ts/acme/` + `@peculiar/x509` for CSR generation
49
73
  - `@push.rocks/smartfile`, `@api.global/typedserver`, `@push.rocks/smartrequest`, `@push.rocks/smartpromise` were removed as unused dependencies in v8.1.0
50
74
  - The `@apiclient.xyz/cloudflare` `convenience` namespace is deprecated but still functional. The `Dns01Handler` accepts an `IConvenientDnsProvider` interface which remains stable.
51
75
  - Test imports use `@git.zone/tstest/tapbundle` (not `@push.rocks/tapbundle`)
52
- - Build uses `tsbuild` (no flags needed, v4+)
76
+ - Build uses `tsbuild tsfolders` (v4.3.0+) auto-discovers and compiles `ts/` and `ts_server/` directories
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @push.rocks/smartacme
2
2
 
3
- A TypeScript-based ACME client for Let's Encrypt certificate management with a focus on simplicity and power. 🔒
3
+ A TypeScript-based ACME client and server for certificate management with a focus on simplicity and power. Includes a full RFC 8555-compliant ACME client for Let's Encrypt and a built-in ACME Directory Server for running your own Certificate Authority.
4
4
 
5
5
  ## Issue Reporting and Security
6
6
 
@@ -372,10 +372,117 @@ await smartAcme.stop();
372
372
  server.close();
373
373
  ```
374
374
 
375
+ ## ACME Directory Server (Built-in CA)
376
+
377
+ SmartAcme includes a full RFC 8555-compliant ACME Directory Server, allowing you to run your own Certificate Authority. This is useful for internal PKI, development/testing environments, and air-gapped networks.
378
+
379
+ ### Quick Start — ACME Server
380
+
381
+ ```typescript
382
+ import { server } from '@push.rocks/smartacme';
383
+
384
+ const acmeServer = new server.AcmeServer({
385
+ port: 14000,
386
+ challengeVerification: false, // Auto-approve challenges (for testing)
387
+ caOptions: {
388
+ commonName: 'My Internal CA',
389
+ certValidityDays: 365,
390
+ },
391
+ });
392
+
393
+ await acmeServer.start();
394
+ console.log(acmeServer.getDirectoryUrl()); // http://localhost:14000/directory
395
+ console.log(acmeServer.getCaCertPem()); // Root CA certificate in PEM format
396
+
397
+ // ... use it, then shut down
398
+ await acmeServer.stop();
399
+ ```
400
+
401
+ ### Server Options
402
+
403
+ ```typescript
404
+ interface IAcmeServerOptions {
405
+ port?: number; // Default: 14000
406
+ hostname?: string; // Default: '0.0.0.0'
407
+ baseUrl?: string; // Auto-built from hostname:port if not provided
408
+ challengeVerification?: boolean; // Default: true. Set false to auto-approve challenges
409
+ caOptions?: {
410
+ commonName?: string; // CA subject CN (default: 'SmartACME Test CA')
411
+ validityDays?: number; // Root cert validity in days (default: 3650)
412
+ certValidityDays?: number; // Issued cert validity in days (default: 90)
413
+ };
414
+ }
415
+ ```
416
+
417
+ ### Using the Server with SmartAcme Client
418
+
419
+ Point the SmartAcme client at your own ACME server for a fully self-contained PKI:
420
+
421
+ ```typescript
422
+ import { SmartAcme, certmanagers, handlers, server } from '@push.rocks/smartacme';
423
+
424
+ // 1. Start your own CA
425
+ const acmeServer = new server.AcmeServer({
426
+ port: 14000,
427
+ challengeVerification: false,
428
+ });
429
+ await acmeServer.start();
430
+
431
+ // 2. Set up the client pointing at your CA
432
+ const memHandler = new handlers.Http01MemoryHandler();
433
+ const smartAcme = new SmartAcme({
434
+ accountEmail: 'admin@internal.example.com',
435
+ certManager: new certmanagers.MemoryCertManager(),
436
+ environment: 'integration',
437
+ challengeHandlers: [memHandler],
438
+ directoryUrl: acmeServer.getDirectoryUrl(), // Use your own CA!
439
+ });
440
+
441
+ await smartAcme.start();
442
+ const cert = await smartAcme.getCertificateForDomain('myapp.internal');
443
+ // cert.publicKey — PEM certificate chain signed by your CA
444
+ // cert.privateKey — PEM private key
445
+
446
+ await smartAcme.stop();
447
+ await acmeServer.stop();
448
+ ```
449
+
450
+ ### Server Endpoints
451
+
452
+ The ACME server implements all RFC 8555 endpoints:
453
+
454
+ | Endpoint | Method | Description |
455
+ |----------|--------|-------------|
456
+ | `/directory` | GET | ACME directory with all endpoint URLs |
457
+ | `/new-nonce` | HEAD/GET | Fresh replay nonce |
458
+ | `/new-account` | POST | Account registration/lookup |
459
+ | `/new-order` | POST | Create certificate order |
460
+ | `/order/:id` | POST | Poll order status |
461
+ | `/authz/:id` | POST | Get authorization with challenges |
462
+ | `/challenge/:id` | POST | Trigger or poll challenge validation |
463
+ | `/finalize/:id` | POST | Submit CSR and issue certificate |
464
+ | `/cert/:id` | POST | Download PEM certificate chain |
465
+
466
+ ### Challenge Verification
467
+
468
+ By default, the server performs real challenge verification (HTTP-01 fetches the token, DNS-01 queries TXT records). Set `challengeVerification: false` to auto-approve all challenges — useful for testing or internal environments where domain validation isn't needed.
469
+
470
+ ### Root CA Certificate
471
+
472
+ Use `getCaCertPem()` to retrieve the root CA certificate for trust configuration:
473
+
474
+ ```typescript
475
+ import * as fs from 'fs';
476
+ fs.writeFileSync('/usr/local/share/ca-certificates/my-ca.crt', acmeServer.getCaCertPem());
477
+ // Then: sudo update-ca-certificates
478
+ ```
479
+
375
480
  ## Architecture
376
481
 
377
482
  Under the hood, SmartAcme uses a fully custom RFC 8555-compliant ACME protocol implementation (no external ACME libraries). Key internal modules:
378
483
 
484
+ ### Client Modules (`ts/acme/`)
485
+
379
486
  | Module | Purpose |
380
487
  |--------|---------|
381
488
  | `AcmeClient` | Top-level ACME facade — orders, authorizations, finalization |
@@ -386,7 +493,20 @@ Under the hood, SmartAcme uses a fully custom RFC 8555-compliant ACME protocol i
386
493
  | `AcmeChallengeManager` | Key authorization computation and challenge completion |
387
494
  | `TaskManager` | Constraint-based concurrency control, rate limiting, and request deduplication via `@push.rocks/taskbuffer` |
388
495
 
389
- All cryptographic operations use `node:crypto`. The only external crypto dependency is `@peculiar/x509` for CSR generation.
496
+ ### Server Modules (`ts_server/`)
497
+
498
+ | Module | Purpose |
499
+ |--------|---------|
500
+ | `AcmeServer` | Top-level server facade — start, stop, configuration |
501
+ | `AcmeServerCA` | Self-signed root CA generation and certificate signing via `@peculiar/x509` |
502
+ | `JwsVerifier` | JWS signature verification (inverse of `AcmeCrypto.createJws`) |
503
+ | `NonceManager` | Single-use replay nonce generation and validation |
504
+ | `ChallengeVerifier` | HTTP-01 and DNS-01 challenge verification (with bypass mode) |
505
+ | `AcmeRouter` | Minimal HTTP router with parameterized path support |
506
+ | `MemoryAccountStore` | In-memory ACME account storage |
507
+ | `MemoryOrderStore` | In-memory order, authorization, challenge, and certificate storage |
508
+
509
+ All cryptographic operations use `node:crypto`. The only external crypto dependency is `@peculiar/x509` for CSR generation and certificate signing.
390
510
 
391
511
  ## License and Legal Information
392
512
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartacme',
6
- version: '9.1.3',
7
- description: 'A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.'
6
+ version: '9.3.0',
7
+ description: 'A TypeScript-based ACME client and server for certificate management with built-in CA, supporting LetsEncrypt and custom ACME authorities.'
8
8
  }
@@ -175,7 +175,7 @@ export class AcmeCrypto {
175
175
  /**
176
176
  * Determine JWS algorithm from key type
177
177
  */
178
- private static getAlg(keyPem: string): string {
178
+ static getAlg(keyPem: string): string {
179
179
  const keyObj = crypto.createPrivateKey(keyPem);
180
180
  const keyType = keyObj.asymmetricKeyType;
181
181
  if (keyType === 'rsa') return 'RS256';
package/ts/index.ts CHANGED
@@ -10,5 +10,9 @@ export { certmanagers };
10
10
  import * as handlers from './handlers/index.js';
11
11
  export { handlers };
12
12
 
13
+ // server (ACME Directory Server / CA)
14
+ import * as server from '../ts_server/index.js';
15
+ export { server };
16
+
13
17
  // re-export taskbuffer event types for consumers
14
18
  export type { ITaskEvent, ITaskMetadata } from '@push.rocks/taskbuffer';
@@ -0,0 +1,12 @@
1
+ export { AcmeServer } from './server.classes.acmeserver.js';
2
+ export { AcmeServerError } from './server.classes.jws.verifier.js';
3
+ export { AcmeServerCA } from './server.classes.ca.js';
4
+ export type {
5
+ IAcmeServerOptions,
6
+ IServerAccountStore,
7
+ IServerOrderStore,
8
+ IServerAccount,
9
+ IServerOrder,
10
+ IServerAuthorization,
11
+ IServerChallenge,
12
+ } from './server.interfaces.js';
@@ -0,0 +1,27 @@
1
+ import type { IServerAccountStore, IServerAccount } from './server.interfaces.js';
2
+
3
+ /**
4
+ * In-memory account storage for the ACME server.
5
+ */
6
+ export class MemoryAccountStore implements IServerAccountStore {
7
+ private accounts = new Map<string, IServerAccount>();
8
+ private byThumbprint = new Map<string, string>();
9
+ private byUrl = new Map<string, string>();
10
+
11
+ async create(account: IServerAccount): Promise<IServerAccount> {
12
+ this.accounts.set(account.id, account);
13
+ this.byThumbprint.set(account.thumbprint, account.id);
14
+ this.byUrl.set(account.url, account.id);
15
+ return account;
16
+ }
17
+
18
+ async getByThumbprint(thumbprint: string): Promise<IServerAccount | null> {
19
+ const id = this.byThumbprint.get(thumbprint);
20
+ return id ? this.accounts.get(id) || null : null;
21
+ }
22
+
23
+ async getByUrl(url: string): Promise<IServerAccount | null> {
24
+ const id = this.byUrl.get(url);
25
+ return id ? this.accounts.get(id) || null : null;
26
+ }
27
+ }