@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.
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/acme/acme.classes.account.js +1 -1
- package/dist_ts/acme/acme.classes.challenge.js +1 -1
- package/dist_ts/acme/acme.classes.client.js +1 -1
- package/dist_ts/acme/acme.classes.crypto.d.ts +1 -1
- package/dist_ts/acme/acme.classes.crypto.js +1 -1
- package/dist_ts/acme/acme.classes.directory.js +1 -1
- package/dist_ts/acme/acme.classes.error.js +1 -1
- package/dist_ts/acme/acme.classes.http-client.js +1 -1
- package/dist_ts/acme/acme.classes.order.js +1 -1
- package/dist_ts/acme/acme.interfaces.js +1 -1
- package/dist_ts/acme/index.js +1 -1
- package/dist_ts/certmanagers/index.js +1 -1
- package/dist_ts/certmanagers/memory.js +1 -1
- package/dist_ts/certmanagers/mongo.js +1 -1
- package/dist_ts/handlers/Dns01Handler.js +1 -1
- package/dist_ts/handlers/Http01Handler.js +1 -1
- package/dist_ts/handlers/Http01MemoryHandler.js +1 -1
- package/dist_ts/handlers/IChallengeHandler.js +1 -1
- package/dist_ts/handlers/index.js +1 -1
- package/dist_ts/index.d.ts +2 -0
- package/dist_ts/index.js +4 -1
- package/dist_ts/interfaces/accountdata.js +1 -1
- package/dist_ts/interfaces/certmanager.js +1 -1
- package/dist_ts/interfaces/index.js +1 -1
- package/dist_ts/plugins.js +1 -1
- package/dist_ts/smartacme.classes.cert.js +1 -1
- package/dist_ts/smartacme.classes.certmatcher.js +1 -1
- package/dist_ts/smartacme.classes.smartacme.js +1 -1
- package/dist_ts_server/index.d.ts +4 -0
- package/dist_ts_server/index.js +4 -0
- package/dist_ts_server/server.classes.account.store.d.ts +12 -0
- package/dist_ts_server/server.classes.account.store.js +25 -0
- package/dist_ts_server/server.classes.acmeserver.d.ts +22 -0
- package/dist_ts_server/server.classes.acmeserver.js +100 -0
- package/dist_ts_server/server.classes.ca.d.ts +25 -0
- package/dist_ts_server/server.classes.ca.js +109 -0
- package/dist_ts_server/server.classes.challenge.verifier.d.ts +16 -0
- package/dist_ts_server/server.classes.challenge.verifier.js +56 -0
- package/dist_ts_server/server.classes.jws.verifier.d.ts +31 -0
- package/dist_ts_server/server.classes.jws.verifier.js +116 -0
- package/dist_ts_server/server.classes.nonce.d.ts +12 -0
- package/dist_ts_server/server.classes.nonce.js +32 -0
- package/dist_ts_server/server.classes.order.store.d.ts +21 -0
- package/dist_ts_server/server.classes.order.store.js +57 -0
- package/dist_ts_server/server.classes.router.d.ts +17 -0
- package/dist_ts_server/server.classes.router.js +99 -0
- package/dist_ts_server/server.handlers.account.d.ts +8 -0
- package/dist_ts_server/server.handlers.account.js +59 -0
- package/dist_ts_server/server.handlers.authz.d.ts +7 -0
- package/dist_ts_server/server.handlers.authz.js +41 -0
- package/dist_ts_server/server.handlers.cert.d.ts +7 -0
- package/dist_ts_server/server.handlers.cert.js +18 -0
- package/dist_ts_server/server.handlers.challenge.d.ts +10 -0
- package/dist_ts_server/server.handlers.challenge.js +110 -0
- package/dist_ts_server/server.handlers.directory.d.ts +5 -0
- package/dist_ts_server/server.handlers.directory.js +23 -0
- package/dist_ts_server/server.handlers.finalize.d.ts +8 -0
- package/dist_ts_server/server.handlers.finalize.js +65 -0
- package/dist_ts_server/server.handlers.nonce.d.ts +7 -0
- package/dist_ts_server/server.handlers.nonce.js +17 -0
- package/dist_ts_server/server.handlers.order.d.ts +11 -0
- package/dist_ts_server/server.handlers.order.js +131 -0
- package/dist_ts_server/server.interfaces.d.ts +72 -0
- package/dist_ts_server/server.interfaces.js +2 -0
- package/package.json +18 -13
- package/readme.hints.md +25 -1
- package/readme.md +122 -2
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/acme/acme.classes.crypto.ts +1 -1
- package/ts/index.ts +4 -0
- package/ts_server/index.ts +12 -0
- package/ts_server/server.classes.account.store.ts +27 -0
- package/ts_server/server.classes.acmeserver.ts +128 -0
- package/ts_server/server.classes.ca.ts +142 -0
- package/ts_server/server.classes.challenge.verifier.ts +61 -0
- package/ts_server/server.classes.jws.verifier.ts +153 -0
- package/ts_server/server.classes.nonce.ts +36 -0
- package/ts_server/server.classes.order.store.ts +72 -0
- package/ts_server/server.classes.router.ts +116 -0
- package/ts_server/server.handlers.account.ts +85 -0
- package/ts_server/server.handlers.authz.ts +58 -0
- package/ts_server/server.handlers.cert.ts +32 -0
- package/ts_server/server.handlers.challenge.ts +142 -0
- package/ts_server/server.handlers.directory.ts +30 -0
- package/ts_server/server.handlers.finalize.ts +93 -0
- package/ts_server/server.handlers.nonce.ts +29 -0
- package/ts_server/server.handlers.order.ts +177 -0
- package/ts_server/server.interfaces.ts +98 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as http from 'node:http';
|
|
2
|
+
import { NonceManager } from './server.classes.nonce.js';
|
|
3
|
+
import { JwsVerifier } from './server.classes.jws.verifier.js';
|
|
4
|
+
import { MemoryAccountStore } from './server.classes.account.store.js';
|
|
5
|
+
import { MemoryOrderStore } from './server.classes.order.store.js';
|
|
6
|
+
import { AcmeServerCA } from './server.classes.ca.js';
|
|
7
|
+
import { ChallengeVerifier } from './server.classes.challenge.verifier.js';
|
|
8
|
+
import { AcmeRouter } from './server.classes.router.js';
|
|
9
|
+
import { createDirectoryHandler } from './server.handlers.directory.js';
|
|
10
|
+
import { createNonceHeadHandler, createNonceGetHandler } from './server.handlers.nonce.js';
|
|
11
|
+
import { createAccountHandler } from './server.handlers.account.js';
|
|
12
|
+
import { createNewOrderHandler, createOrderPollHandler } from './server.handlers.order.js';
|
|
13
|
+
import { createAuthzHandler } from './server.handlers.authz.js';
|
|
14
|
+
import { createChallengeHandler } from './server.handlers.challenge.js';
|
|
15
|
+
import { createFinalizeHandler } from './server.handlers.finalize.js';
|
|
16
|
+
import { createCertHandler } from './server.handlers.cert.js';
|
|
17
|
+
/**
|
|
18
|
+
* ACME Directory Server — a self-contained RFC 8555 Certificate Authority.
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* ```ts
|
|
22
|
+
* const server = new AcmeServer({ port: 14000 });
|
|
23
|
+
* await server.start();
|
|
24
|
+
* console.log(server.getDirectoryUrl()); // http://localhost:14000/directory
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class AcmeServer {
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.httpServer = null;
|
|
30
|
+
this.options = {
|
|
31
|
+
port: options.port ?? 14000,
|
|
32
|
+
hostname: options.hostname ?? '0.0.0.0',
|
|
33
|
+
...options,
|
|
34
|
+
};
|
|
35
|
+
this.baseUrl = options.baseUrl ?? `http://localhost:${this.options.port}`;
|
|
36
|
+
this.ca = new AcmeServerCA(options.caOptions);
|
|
37
|
+
}
|
|
38
|
+
async start() {
|
|
39
|
+
// Initialize CA
|
|
40
|
+
await this.ca.init();
|
|
41
|
+
// Create stores
|
|
42
|
+
const accountStore = new MemoryAccountStore();
|
|
43
|
+
const orderStore = new MemoryOrderStore();
|
|
44
|
+
// Create managers
|
|
45
|
+
const nonceManager = new NonceManager();
|
|
46
|
+
const jwsVerifier = new JwsVerifier(nonceManager, accountStore);
|
|
47
|
+
const challengeVerifier = new ChallengeVerifier(this.options.challengeVerification ?? true);
|
|
48
|
+
// Create router and register routes
|
|
49
|
+
const router = new AcmeRouter(nonceManager);
|
|
50
|
+
// Directory
|
|
51
|
+
router.addRoute('GET', '/directory', createDirectoryHandler(this.baseUrl));
|
|
52
|
+
// Nonce
|
|
53
|
+
router.addRoute('HEAD', '/new-nonce', createNonceHeadHandler());
|
|
54
|
+
router.addRoute('GET', '/new-nonce', createNonceGetHandler());
|
|
55
|
+
// Account
|
|
56
|
+
router.addRoute('POST', '/new-account', createAccountHandler(this.baseUrl, jwsVerifier, accountStore));
|
|
57
|
+
// Order
|
|
58
|
+
router.addRoute('POST', '/new-order', createNewOrderHandler(this.baseUrl, jwsVerifier, orderStore));
|
|
59
|
+
router.addRoute('POST', '/order/:id', createOrderPollHandler(this.baseUrl, jwsVerifier, orderStore));
|
|
60
|
+
// Authorization
|
|
61
|
+
router.addRoute('POST', '/authz/:id', createAuthzHandler(this.baseUrl, jwsVerifier, orderStore));
|
|
62
|
+
// Challenge
|
|
63
|
+
router.addRoute('POST', '/challenge/:id', createChallengeHandler(this.baseUrl, jwsVerifier, orderStore, accountStore, challengeVerifier));
|
|
64
|
+
// Finalize
|
|
65
|
+
router.addRoute('POST', '/finalize/:id', createFinalizeHandler(this.baseUrl, jwsVerifier, orderStore, this.ca));
|
|
66
|
+
// Certificate
|
|
67
|
+
router.addRoute('POST', '/cert/:id', createCertHandler(this.baseUrl, jwsVerifier, orderStore));
|
|
68
|
+
// Start HTTP server
|
|
69
|
+
this.httpServer = http.createServer((req, res) => {
|
|
70
|
+
router.handle(req, res).catch((err) => {
|
|
71
|
+
if (!res.headersSent) {
|
|
72
|
+
res.writeHead(500, { 'Content-Type': 'application/problem+json' });
|
|
73
|
+
res.end(JSON.stringify({
|
|
74
|
+
type: 'urn:ietf:params:acme:error:serverInternal',
|
|
75
|
+
detail: err instanceof Error ? err.message : 'Unknown error',
|
|
76
|
+
status: 500,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
await new Promise((resolve) => {
|
|
82
|
+
this.httpServer.listen(this.options.port, this.options.hostname, () => resolve());
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async stop() {
|
|
86
|
+
if (this.httpServer) {
|
|
87
|
+
await new Promise((resolve, reject) => {
|
|
88
|
+
this.httpServer.close((err) => (err ? reject(err) : resolve()));
|
|
89
|
+
});
|
|
90
|
+
this.httpServer = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
getDirectoryUrl() {
|
|
94
|
+
return `${this.baseUrl}/directory`;
|
|
95
|
+
}
|
|
96
|
+
getCaCertPem() {
|
|
97
|
+
return this.ca.getCaCertPem();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLmNsYXNzZXMuYWNtZXNlcnZlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzX3NlcnZlci9zZXJ2ZXIuY2xhc3Nlcy5hY21lc2VydmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxJQUFJLE1BQU0sV0FBVyxDQUFDO0FBRWxDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFDL0QsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFDdkUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDbkUsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ3RELE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHdDQUF3QyxDQUFDO0FBQzNFLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUN4RCxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUN4RSxPQUFPLEVBQUUsc0JBQXNCLEVBQUUscUJBQXFCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUMzRixPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUNwRSxPQUFPLEVBQUUscUJBQXFCLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUMzRixPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUNoRSxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUN4RSxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUN0RSxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUU5RDs7Ozs7Ozs7O0dBU0c7QUFDSCxN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
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Certificate Authority for the ACME server.
|
|
3
|
+
* Generates a self-signed root CA and signs certificates from CSRs.
|
|
4
|
+
* Uses @peculiar/x509 (already a project dependency).
|
|
5
|
+
*/
|
|
6
|
+
export declare class AcmeServerCA {
|
|
7
|
+
private options;
|
|
8
|
+
private caKeyPair;
|
|
9
|
+
private caCert;
|
|
10
|
+
private caCertPem;
|
|
11
|
+
private certValidityDays;
|
|
12
|
+
constructor(options?: {
|
|
13
|
+
commonName?: string;
|
|
14
|
+
validityDays?: number;
|
|
15
|
+
certValidityDays?: number;
|
|
16
|
+
});
|
|
17
|
+
init(): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Sign a CSR and return a PEM certificate chain (end-entity + root CA).
|
|
20
|
+
* @param csrDerBase64url - The CSR in base64url-encoded DER format (as sent by ACME clients)
|
|
21
|
+
*/
|
|
22
|
+
signCsr(csrDerBase64url: string): Promise<string>;
|
|
23
|
+
getCaCertPem(): string;
|
|
24
|
+
private randomSerialNumber;
|
|
25
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Certificate Authority for the ACME server.
|
|
4
|
+
* Generates a self-signed root CA and signs certificates from CSRs.
|
|
5
|
+
* Uses @peculiar/x509 (already a project dependency).
|
|
6
|
+
*/
|
|
7
|
+
export class AcmeServerCA {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.certValidityDays = options.certValidityDays ?? 90;
|
|
11
|
+
}
|
|
12
|
+
async init() {
|
|
13
|
+
const x509 = await import('@peculiar/x509');
|
|
14
|
+
const { webcrypto } = crypto;
|
|
15
|
+
x509.cryptoProvider.set(webcrypto);
|
|
16
|
+
// Generate RSA key pair for the CA
|
|
17
|
+
this.caKeyPair = await webcrypto.subtle.generateKey({
|
|
18
|
+
name: 'RSASSA-PKCS1-v1_5',
|
|
19
|
+
modulusLength: 2048,
|
|
20
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
|
21
|
+
hash: 'SHA-256',
|
|
22
|
+
}, true, ['sign', 'verify']);
|
|
23
|
+
const cn = this.options.commonName ?? 'SmartACME Test CA';
|
|
24
|
+
const validityDays = this.options.validityDays ?? 3650;
|
|
25
|
+
const notBefore = new Date();
|
|
26
|
+
const notAfter = new Date();
|
|
27
|
+
notAfter.setDate(notAfter.getDate() + validityDays);
|
|
28
|
+
// Create self-signed root CA certificate
|
|
29
|
+
this.caCert = await x509.X509CertificateGenerator.createSelfSigned({
|
|
30
|
+
serialNumber: this.randomSerialNumber(),
|
|
31
|
+
name: `CN=${cn}`,
|
|
32
|
+
notBefore,
|
|
33
|
+
notAfter,
|
|
34
|
+
signingAlgorithm: { name: 'RSASSA-PKCS1-v1_5' },
|
|
35
|
+
keys: this.caKeyPair,
|
|
36
|
+
extensions: [
|
|
37
|
+
new x509.BasicConstraintsExtension(true, undefined, true),
|
|
38
|
+
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
|
39
|
+
await x509.SubjectKeyIdentifierExtension.create(this.caKeyPair.publicKey),
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
this.caCertPem = this.caCert.toString('pem');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Sign a CSR and return a PEM certificate chain (end-entity + root CA).
|
|
46
|
+
* @param csrDerBase64url - The CSR in base64url-encoded DER format (as sent by ACME clients)
|
|
47
|
+
*/
|
|
48
|
+
async signCsr(csrDerBase64url) {
|
|
49
|
+
const x509 = await import('@peculiar/x509');
|
|
50
|
+
const { webcrypto } = crypto;
|
|
51
|
+
x509.cryptoProvider.set(webcrypto);
|
|
52
|
+
// Parse the CSR
|
|
53
|
+
const csrDer = Buffer.from(csrDerBase64url, 'base64url');
|
|
54
|
+
const csr = new x509.Pkcs10CertificateRequest(csrDer);
|
|
55
|
+
// Extract Subject Alternative Names from CSR extensions
|
|
56
|
+
const sanNames = [];
|
|
57
|
+
const sanExt = csr.extensions?.find((ext) => ext.type === '2.5.29.17');
|
|
58
|
+
if (sanExt) {
|
|
59
|
+
const san = new x509.SubjectAlternativeNameExtension(sanExt.rawData);
|
|
60
|
+
if (san.names) {
|
|
61
|
+
const jsonNames = san.names.toJSON();
|
|
62
|
+
for (const name of jsonNames) {
|
|
63
|
+
if (name.type === 'dns') {
|
|
64
|
+
sanNames.push({ type: 'dns', value: name.value });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// If no SAN found, use CN from subject
|
|
70
|
+
if (sanNames.length === 0) {
|
|
71
|
+
const cnMatch = csr.subject.match(/CN=([^,]+)/);
|
|
72
|
+
if (cnMatch) {
|
|
73
|
+
sanNames.push({ type: 'dns', value: cnMatch[1] });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const notBefore = new Date();
|
|
77
|
+
const notAfter = new Date();
|
|
78
|
+
notAfter.setDate(notAfter.getDate() + this.certValidityDays);
|
|
79
|
+
// Sign the certificate
|
|
80
|
+
const cert = await x509.X509CertificateGenerator.create({
|
|
81
|
+
serialNumber: this.randomSerialNumber(),
|
|
82
|
+
subject: csr.subject,
|
|
83
|
+
issuer: this.caCert.subject,
|
|
84
|
+
notBefore,
|
|
85
|
+
notAfter,
|
|
86
|
+
signingAlgorithm: { name: 'RSASSA-PKCS1-v1_5' },
|
|
87
|
+
publicKey: csr.publicKey,
|
|
88
|
+
signingKey: this.caKeyPair.privateKey,
|
|
89
|
+
extensions: [
|
|
90
|
+
new x509.BasicConstraintsExtension(false, undefined, true),
|
|
91
|
+
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true),
|
|
92
|
+
new x509.ExtendedKeyUsageExtension(['1.3.6.1.5.5.7.3.1'], // serverAuth
|
|
93
|
+
true),
|
|
94
|
+
new x509.SubjectAlternativeNameExtension(sanNames),
|
|
95
|
+
await x509.AuthorityKeyIdentifierExtension.create(this.caKeyPair.publicKey),
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
// Return PEM chain: end-entity cert + root CA cert
|
|
99
|
+
const certPem = cert.toString('pem');
|
|
100
|
+
return `${certPem}\n${this.caCertPem}`;
|
|
101
|
+
}
|
|
102
|
+
getCaCertPem() {
|
|
103
|
+
return this.caCertPem;
|
|
104
|
+
}
|
|
105
|
+
randomSerialNumber() {
|
|
106
|
+
return crypto.randomBytes(16).toString('hex');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLmNsYXNzZXMuY2EuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19zZXJ2ZXIvc2VydmVyLmNsYXNzZXMuY2EudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE1BQU0sTUFBTSxhQUFhLENBQUM7QUFFdEM7Ozs7R0FJRztBQUNILE1BQU0sT0FBTyxZQUFZO0lBTXZCLFlBQW9CLFVBQXFGLEVBQUU7UUFBdkYsWUFBTyxHQUFQLE9BQU8sQ0FBZ0Y7UUFDekcsSUFBSSxDQUFDLGdCQUFnQixHQUFHLE9BQU8sQ0FBQyxnQkFBZ0IsSUFBSSxFQUFFLENBQUM7SUFDekQsQ0FBQztJQUVELEtBQUssQ0FBQyxJQUFJO1FBQ1IsTUFBTSxJQUFJLEdBQUcsTUFBTSxNQUFNLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztRQUM1QyxNQUFNLEVBQUUsU0FBUyxFQUFFLEdBQUcsTUFBTSxDQUFDO1FBQzdCLElBQUksQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLFNBQWdCLENBQUMsQ0FBQztRQUUxQyxtQ0FBbUM7UUFDbkMsSUFBSSxDQUFDLFNBQVMsR0FBRyxNQUFNLFNBQVMsQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUNqRDtZQUNFLElBQUksRUFBRSxtQkFBbUI7WUFDekIsYUFBYSxFQUFFLElBQUk7WUFDbkIsY0FBYyxFQUFFLElBQUksVUFBVSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztZQUN6QyxJQUFJLEVBQUUsU0FBUztTQUNoQixFQUNELElBQUksRUFDSixDQUFDLE1BQU0sRUFBRSxRQUFRLENBQUMsQ0FDRixDQUFDO1FBRW5CLE1BQU0sRUFBRSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsVUFBVSxJQUFJLG1CQUFtQixDQUFDO1FBQzFELE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsWUFBWSxJQUFJLElBQUksQ0FBQztRQUN2RCxNQUFNLFNBQVMsR0FBRyxJ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
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies ACME challenges by making HTTP requests or DNS lookups.
|
|
3
|
+
*/
|
|
4
|
+
export declare class ChallengeVerifier {
|
|
5
|
+
private verificationEnabled;
|
|
6
|
+
constructor(verificationEnabled?: boolean);
|
|
7
|
+
/**
|
|
8
|
+
* Verify an HTTP-01 challenge by fetching the token from the domain.
|
|
9
|
+
*/
|
|
10
|
+
verifyHttp01(domain: string, token: string, expectedKeyAuth: string): Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Verify a DNS-01 challenge by looking up the TXT record.
|
|
13
|
+
*/
|
|
14
|
+
verifyDns01(domain: string, expectedHash: string): Promise<boolean>;
|
|
15
|
+
private httpGet;
|
|
16
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as http from 'node:http';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies ACME challenges by making HTTP requests or DNS lookups.
|
|
4
|
+
*/
|
|
5
|
+
export class ChallengeVerifier {
|
|
6
|
+
constructor(verificationEnabled = true) {
|
|
7
|
+
this.verificationEnabled = verificationEnabled;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Verify an HTTP-01 challenge by fetching the token from the domain.
|
|
11
|
+
*/
|
|
12
|
+
async verifyHttp01(domain, token, expectedKeyAuth) {
|
|
13
|
+
if (!this.verificationEnabled) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const url = `http://${domain}/.well-known/acme-challenge/${token}`;
|
|
18
|
+
const body = await this.httpGet(url);
|
|
19
|
+
return body.trim() === expectedKeyAuth.trim();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Verify a DNS-01 challenge by looking up the TXT record.
|
|
27
|
+
*/
|
|
28
|
+
async verifyDns01(domain, expectedHash) {
|
|
29
|
+
if (!this.verificationEnabled) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const { promises: dns } = await import('node:dns');
|
|
34
|
+
const records = await dns.resolveTxt(`_acme-challenge.${domain}`);
|
|
35
|
+
const flatRecords = records.map((r) => r.join(''));
|
|
36
|
+
return flatRecords.some((r) => r === expectedHash);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
httpGet(url) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const req = http.get(url, { timeout: 10000 }, (res) => {
|
|
45
|
+
const chunks = [];
|
|
46
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
47
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
48
|
+
});
|
|
49
|
+
req.on('error', reject);
|
|
50
|
+
req.on('timeout', () => {
|
|
51
|
+
req.destroy(new Error('HTTP-01 verification timeout'));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLmNsYXNzZXMuY2hhbGxlbmdlLnZlcmlmaWVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfc2VydmVyL3NlcnZlci5jbGFzc2VzLmNoYWxsZW5nZS52ZXJpZmllci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssSUFBSSxNQUFNLFdBQVcsQ0FBQztBQUVsQzs7R0FFRztBQUNILE1BQU0sT0FBTyxpQkFBaUI7SUFHNUIsWUFBWSxtQkFBbUIsR0FBRyxJQUFJO1FBQ3BDLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxtQkFBbUIsQ0FBQztJQUNqRCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxLQUFLLENBQUMsWUFBWSxDQUFDLE1BQWMsRUFBRSxLQUFhLEVBQUUsZUFBdUI7UUFDdkUsSUFBSSxDQUFDLElBQUksQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO1lBQzlCLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztRQUVELElBQUksQ0FBQztZQUNILE1BQU0sR0FBRyxHQUFHLFVBQVUsTUFBTSwrQkFBK0IsS0FBSyxFQUFFLENBQUM7WUFDbkUsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3JDLE9BQU8sSUFBSSxDQUFDLElBQUksRUFBRSxLQUFLLGVBQWUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUNoRCxDQUFDO1FBQUMsTUFBTSxDQUFDO1lBQ1AsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0gsS0FBSyxDQUFDLFdBQVcsQ0FBQyxNQUFjLEVBQUUsWUFBb0I7UUFDcEQsSUFBSSxDQUFDLElBQUksQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO1lBQzlCLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztRQUVELElBQUksQ0FBQztZQUNILE1BQU0sRUFBRSxRQUFRLEVBQUUsR0FBRyxFQUFFLEdBQUcsTUFBTSxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7WUFDbkQsTUFBTSxPQUFPLEdBQUcsTUFBTSxHQUFHLENBQUMsVUFBVSxDQUFDLG1CQUFtQixNQUFNLEVBQUUsQ0FBQyxDQUFDO1lBQ2xFLE1BQU0sV0FBVyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztZQUNuRCxPQUFPLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxZQUFZLENBQUMsQ0FBQztRQUNyRCxDQUFDO1FBQUMsTUFBTSxDQUFDO1lBQ1AsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO0lBQ0gsQ0FBQztJQUVPLE9BQU8sQ0FBQyxHQUFXO1FBQ3pCLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7WUFDckMsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FBQyxHQUFHLEVBQUUsRUFBRTtnQkFDcEQsTUFBTSxNQUFNLEdBQWEsRUFBRSxDQUFDO2dCQUM1QixHQUFHLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxDQUFDLEtBQWEsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO2dCQUN0RCxHQUFHLENBQUMsRUFBRSxDQUFDLEtBQUssRUFBRSxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ3hFLENBQUMsQ0FBQyxDQUFDO1lBQ0gsR0FBRyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7WUFDeEIsR0FBRyxDQUFDLEVBQUUsQ0FBQyxTQUFTLEVBQUUsR0FBRyxFQUFFO2dCQUNyQixHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksS0FBSyxDQUFDLDhCQUE4QixDQUFDLENBQUMsQ0FBQztZQUN6RCxDQUFDLENBQUMsQ0FBQztRQUNMLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztDQUNGIn0=
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { NonceManager } from './server.classes.nonce.js';
|
|
2
|
+
import type { IServerAccountStore } from './server.interfaces.js';
|
|
3
|
+
export interface IJwsVerifyResult {
|
|
4
|
+
jwk: Record<string, string>;
|
|
5
|
+
kid: string | null;
|
|
6
|
+
thumbprint: string;
|
|
7
|
+
payload: any;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Verifies JWS-signed ACME requests.
|
|
11
|
+
* This is the inverse of AcmeCrypto.createJws().
|
|
12
|
+
*/
|
|
13
|
+
export declare class JwsVerifier {
|
|
14
|
+
private nonceManager;
|
|
15
|
+
private accountStore;
|
|
16
|
+
constructor(nonceManager: NonceManager, accountStore: IServerAccountStore);
|
|
17
|
+
verify(body: {
|
|
18
|
+
protected: string;
|
|
19
|
+
payload: string;
|
|
20
|
+
signature: string;
|
|
21
|
+
}, expectedUrl: string): Promise<IJwsVerifyResult>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Simple error class for ACME server errors that maps to RFC 8555 problem responses.
|
|
25
|
+
*/
|
|
26
|
+
export declare class AcmeServerError extends Error {
|
|
27
|
+
readonly status: number;
|
|
28
|
+
readonly type: string;
|
|
29
|
+
readonly detail: string;
|
|
30
|
+
constructor(status: number, type: string, detail: string);
|
|
31
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import { AcmeCrypto } from '../dist_ts/acme/acme.classes.crypto.js';
|
|
3
|
+
/**
|
|
4
|
+
* Verifies JWS-signed ACME requests.
|
|
5
|
+
* This is the inverse of AcmeCrypto.createJws().
|
|
6
|
+
*/
|
|
7
|
+
export class JwsVerifier {
|
|
8
|
+
constructor(nonceManager, accountStore) {
|
|
9
|
+
this.nonceManager = nonceManager;
|
|
10
|
+
this.accountStore = accountStore;
|
|
11
|
+
}
|
|
12
|
+
async verify(body, expectedUrl) {
|
|
13
|
+
if (!body || !body.protected || body.signature === undefined) {
|
|
14
|
+
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'Invalid JWS structure');
|
|
15
|
+
}
|
|
16
|
+
// 1. Decode protected header
|
|
17
|
+
const headerJson = Buffer.from(body.protected, 'base64url').toString('utf-8');
|
|
18
|
+
let header;
|
|
19
|
+
try {
|
|
20
|
+
header = JSON.parse(headerJson);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'Invalid JWS protected header');
|
|
24
|
+
}
|
|
25
|
+
// 2. Validate required fields
|
|
26
|
+
const { alg, nonce, url, jwk, kid } = header;
|
|
27
|
+
if (!alg) {
|
|
28
|
+
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'Missing alg in protected header');
|
|
29
|
+
}
|
|
30
|
+
if (!nonce) {
|
|
31
|
+
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:badNonce', 'Missing nonce in protected header');
|
|
32
|
+
}
|
|
33
|
+
if (!url) {
|
|
34
|
+
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'Missing url in protected header');
|
|
35
|
+
}
|
|
36
|
+
// 3. Validate URL matches
|
|
37
|
+
if (url !== expectedUrl) {
|
|
38
|
+
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', `URL mismatch: expected ${expectedUrl}, got ${url}`);
|
|
39
|
+
}
|
|
40
|
+
// 4. Validate and consume nonce
|
|
41
|
+
if (!this.nonceManager.consume(nonce)) {
|
|
42
|
+
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:badNonce', 'Invalid or expired nonce');
|
|
43
|
+
}
|
|
44
|
+
// 5. Must have exactly one of jwk or kid
|
|
45
|
+
if (jwk && kid) {
|
|
46
|
+
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'JWS must have jwk or kid, not both');
|
|
47
|
+
}
|
|
48
|
+
if (!jwk && !kid) {
|
|
49
|
+
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'JWS must have jwk or kid');
|
|
50
|
+
}
|
|
51
|
+
// 6. Resolve the public key
|
|
52
|
+
let resolvedJwk;
|
|
53
|
+
if (jwk) {
|
|
54
|
+
resolvedJwk = jwk;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Look up account by kid (account URL)
|
|
58
|
+
const account = await this.accountStore.getByUrl(kid);
|
|
59
|
+
if (!account) {
|
|
60
|
+
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:accountDoesNotExist', 'Account not found for kid');
|
|
61
|
+
}
|
|
62
|
+
resolvedJwk = account.jwk;
|
|
63
|
+
}
|
|
64
|
+
// 7. Reconstruct public key and verify signature
|
|
65
|
+
const publicKey = crypto.createPublicKey({ key: resolvedJwk, format: 'jwk' });
|
|
66
|
+
const signingInput = `${body.protected}.${body.payload}`;
|
|
67
|
+
const signatureBuffer = Buffer.from(body.signature, 'base64url');
|
|
68
|
+
const supportedAlgs = ['RS256', 'ES256', 'ES384'];
|
|
69
|
+
if (!supportedAlgs.includes(alg)) {
|
|
70
|
+
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:badSignatureAlgorithm', `Unsupported algorithm: ${alg}`);
|
|
71
|
+
}
|
|
72
|
+
let valid;
|
|
73
|
+
if (alg.startsWith('RS')) {
|
|
74
|
+
valid = crypto.verify('sha256', Buffer.from(signingInput), publicKey, signatureBuffer);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
valid = crypto.verify('sha256', Buffer.from(signingInput), { key: publicKey, dsaEncoding: 'ieee-p1363' }, signatureBuffer);
|
|
78
|
+
}
|
|
79
|
+
if (!valid) {
|
|
80
|
+
throw new AcmeServerError(403, 'urn:ietf:params:acme:error:unauthorized', 'Invalid JWS signature');
|
|
81
|
+
}
|
|
82
|
+
// 8. Decode payload
|
|
83
|
+
let payload;
|
|
84
|
+
if (body.payload === '') {
|
|
85
|
+
payload = null; // POST-as-GET
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
try {
|
|
89
|
+
payload = JSON.parse(Buffer.from(body.payload, 'base64url').toString('utf-8'));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'Invalid JWS payload');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const thumbprint = AcmeCrypto.getJwkThumbprint(resolvedJwk);
|
|
96
|
+
return {
|
|
97
|
+
jwk: resolvedJwk,
|
|
98
|
+
kid: kid || null,
|
|
99
|
+
thumbprint,
|
|
100
|
+
payload,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Simple error class for ACME server errors that maps to RFC 8555 problem responses.
|
|
106
|
+
*/
|
|
107
|
+
export class AcmeServerError extends Error {
|
|
108
|
+
constructor(status, type, detail) {
|
|
109
|
+
super(`${type}: ${detail}`);
|
|
110
|
+
this.name = 'AcmeServerError';
|
|
111
|
+
this.status = status;
|
|
112
|
+
this.type = type;
|
|
113
|
+
this.detail = detail;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLmNsYXNzZXMuandzLnZlcmlmaWVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfc2VydmVyL3NlcnZlci5jbGFzc2VzLmp3cy52ZXJpZmllci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssTUFBTSxNQUFNLGFBQWEsQ0FBQztBQUN0QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFXL0Q7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLFdBQVc7SUFJdEIsWUFBWSxZQUEwQixFQUFFLFlBQWlDO1FBQ3ZFLElBQUksQ0FBQyxZQUFZLEdBQUcsWUFBWSxDQUFDO1FBQ2pDLElBQUksQ0FBQyxZQUFZLEdBQUcsWUFBWSxDQUFDO0lBQ25DLENBQUM7SUFFRCxLQUFLLENBQUMsTUFBTSxDQUNWLElBQStELEVBQy9ELFdBQW1CO1FBRW5CLElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQyxTQUFTLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDN0QsTUFBTSxJQUFJLGVBQWUsQ0FBQyxHQUFHLEVBQUUsc0NBQXNDLEVBQUUsdUJBQXVCLENBQUMsQ0FBQztRQUNsRyxDQUFDO1FBRUQsNkJBQTZCO1FBQzdCLE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxXQUFXLENBQUMsQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDOUUsSUFBSSxNQUEyQixDQUFDO1FBQ2hDLElBQUksQ0FBQztZQUNILE1BQU0sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ2xDLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxNQUFNLElBQUksZUFBZSxDQUFDLEdBQUcsRUFBRSxzQ0FBc0MsRUFBRSw4QkFBOEIsQ0FBQyxDQUFDO1FBQ3pHLENBQUM7UUFFRCw4QkFBOEI7UUFDOUIs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
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages ACME replay nonces.
|
|
3
|
+
* Each nonce is single-use: consumed on verification, fresh one issued with every response.
|
|
4
|
+
*/
|
|
5
|
+
export declare class NonceManager {
|
|
6
|
+
private nonces;
|
|
7
|
+
private nonceQueue;
|
|
8
|
+
private maxSize;
|
|
9
|
+
constructor(maxSize?: number);
|
|
10
|
+
generate(): string;
|
|
11
|
+
consume(nonce: string): boolean;
|
|
12
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Manages ACME replay nonces.
|
|
4
|
+
* Each nonce is single-use: consumed on verification, fresh one issued with every response.
|
|
5
|
+
*/
|
|
6
|
+
export class NonceManager {
|
|
7
|
+
constructor(maxSize = 10000) {
|
|
8
|
+
this.nonces = new Set();
|
|
9
|
+
this.nonceQueue = [];
|
|
10
|
+
this.maxSize = maxSize;
|
|
11
|
+
}
|
|
12
|
+
generate() {
|
|
13
|
+
const nonce = crypto.randomBytes(16).toString('base64url');
|
|
14
|
+
if (this.nonces.size >= this.maxSize) {
|
|
15
|
+
const oldest = this.nonceQueue.shift();
|
|
16
|
+
if (oldest) {
|
|
17
|
+
this.nonces.delete(oldest);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
this.nonces.add(nonce);
|
|
21
|
+
this.nonceQueue.push(nonce);
|
|
22
|
+
return nonce;
|
|
23
|
+
}
|
|
24
|
+
consume(nonce) {
|
|
25
|
+
if (this.nonces.has(nonce)) {
|
|
26
|
+
this.nonces.delete(nonce);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLmNsYXNzZXMubm9uY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19zZXJ2ZXIvc2VydmVyLmNsYXNzZXMubm9uY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE1BQU0sTUFBTSxhQUFhLENBQUM7QUFFdEM7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLFlBQVk7SUFLdkIsWUFBWSxPQUFPLEdBQUcsS0FBSztRQUpuQixXQUFNLEdBQUcsSUFBSSxHQUFHLEVBQVUsQ0FBQztRQUMzQixlQUFVLEdBQWEsRUFBRSxDQUFDO1FBSWhDLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO0lBQ3pCLENBQUM7SUFFRCxRQUFRO1FBQ04sTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDM0QsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDckMsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUN2QyxJQUFJLE1BQU0sRUFBRSxDQUFDO2dCQUNYLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQzdCLENBQUM7UUFDSCxDQUFDO1FBQ0QsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDdkIsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDNUIsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQsT0FBTyxDQUFDLEtBQWE7UUFDbkIsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQzNCLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQzFCLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztRQUNELE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztDQUNGIn0=
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IServerOrderStore, IServerOrder, IServerAuthorization, IServerChallenge } from './server.interfaces.js';
|
|
2
|
+
/**
|
|
3
|
+
* In-memory order/authorization/challenge/certificate storage for the ACME server.
|
|
4
|
+
*/
|
|
5
|
+
export declare class MemoryOrderStore implements IServerOrderStore {
|
|
6
|
+
private orders;
|
|
7
|
+
private authorizations;
|
|
8
|
+
private challenges;
|
|
9
|
+
private certPems;
|
|
10
|
+
createOrder(order: IServerOrder): Promise<IServerOrder>;
|
|
11
|
+
getOrder(id: string): Promise<IServerOrder | null>;
|
|
12
|
+
updateOrder(id: string, updates: Partial<IServerOrder>): Promise<void>;
|
|
13
|
+
createAuthorization(authz: IServerAuthorization): Promise<IServerAuthorization>;
|
|
14
|
+
getAuthorization(id: string): Promise<IServerAuthorization | null>;
|
|
15
|
+
updateAuthorization(id: string, updates: Partial<IServerAuthorization>): Promise<void>;
|
|
16
|
+
createChallenge(challenge: IServerChallenge): Promise<IServerChallenge>;
|
|
17
|
+
getChallenge(id: string): Promise<IServerChallenge | null>;
|
|
18
|
+
updateChallenge(id: string, updates: Partial<IServerChallenge>): Promise<void>;
|
|
19
|
+
storeCertPem(orderId: string, pem: string): Promise<void>;
|
|
20
|
+
getCertPem(orderId: string): Promise<string | null>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory order/authorization/challenge/certificate storage for the ACME server.
|
|
3
|
+
*/
|
|
4
|
+
export class MemoryOrderStore {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.orders = new Map();
|
|
7
|
+
this.authorizations = new Map();
|
|
8
|
+
this.challenges = new Map();
|
|
9
|
+
this.certPems = new Map();
|
|
10
|
+
}
|
|
11
|
+
async createOrder(order) {
|
|
12
|
+
this.orders.set(order.id, order);
|
|
13
|
+
return order;
|
|
14
|
+
}
|
|
15
|
+
async getOrder(id) {
|
|
16
|
+
return this.orders.get(id) || null;
|
|
17
|
+
}
|
|
18
|
+
async updateOrder(id, updates) {
|
|
19
|
+
const order = this.orders.get(id);
|
|
20
|
+
if (order) {
|
|
21
|
+
Object.assign(order, updates);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async createAuthorization(authz) {
|
|
25
|
+
this.authorizations.set(authz.id, authz);
|
|
26
|
+
return authz;
|
|
27
|
+
}
|
|
28
|
+
async getAuthorization(id) {
|
|
29
|
+
return this.authorizations.get(id) || null;
|
|
30
|
+
}
|
|
31
|
+
async updateAuthorization(id, updates) {
|
|
32
|
+
const authz = this.authorizations.get(id);
|
|
33
|
+
if (authz) {
|
|
34
|
+
Object.assign(authz, updates);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async createChallenge(challenge) {
|
|
38
|
+
this.challenges.set(challenge.id, challenge);
|
|
39
|
+
return challenge;
|
|
40
|
+
}
|
|
41
|
+
async getChallenge(id) {
|
|
42
|
+
return this.challenges.get(id) || null;
|
|
43
|
+
}
|
|
44
|
+
async updateChallenge(id, updates) {
|
|
45
|
+
const challenge = this.challenges.get(id);
|
|
46
|
+
if (challenge) {
|
|
47
|
+
Object.assign(challenge, updates);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async storeCertPem(orderId, pem) {
|
|
51
|
+
this.certPems.set(orderId, pem);
|
|
52
|
+
}
|
|
53
|
+
async getCertPem(orderId) {
|
|
54
|
+
return this.certPems.get(orderId) || null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLmNsYXNzZXMub3JkZXIuc3RvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19zZXJ2ZXIvc2VydmVyLmNsYXNzZXMub3JkZXIuc3RvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBT0E7O0dBRUc7QUFDSCxNQUFNLE9BQU8sZ0JBQWdCO0lBQTdCO1FBQ1UsV0FBTSxHQUFHLElBQUksR0FBRyxFQUF3QixDQUFDO1FBQ3pDLG1CQUFjLEdBQUcsSUFBSSxHQUFHLEVBQWdDLENBQUM7UUFDekQsZUFBVSxHQUFHLElBQUksR0FBRyxFQUE0QixDQUFDO1FBQ2pELGFBQVEsR0FBRyxJQUFJLEdBQUcsRUFBa0IsQ0FBQztJQXlEL0MsQ0FBQztJQXZEQyxLQUFLLENBQUMsV0FBVyxDQUFDLEtBQW1CO1FBQ25DLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDakMsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQsS0FBSyxDQUFDLFFBQVEsQ0FBQyxFQUFVO1FBQ3ZCLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLElBQUksSUFBSSxDQUFDO0lBQ3JDLENBQUM7SUFFRCxLQUFLLENBQUMsV0FBVyxDQUFDLEVBQVUsRUFBRSxPQUE4QjtRQUMxRCxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUNsQyxJQUFJLEtBQUssRUFBRSxDQUFDO1lBQ1YsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDaEMsQ0FBQztJQUNILENBQUM7SUFFRCxLQUFLLENBQUMsbUJBQW1CLENBQUMsS0FBMkI7UUFDbkQsSUFBSSxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEVBQUUsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUN6QyxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRCxLQUFLLENBQUMsZ0JBQWdCLENBQUMsRUFBVTtRQUMvQixPQUFPLElBQUksQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxJQUFJLElBQUksQ0FBQztJQUM3QyxDQUFDO0lBRUQsS0FBSyxDQUFDLG1CQUFtQixDQUFDLEVBQVUsRUFBRSxPQUFzQztRQUMxRSxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUMxQyxJQUFJLEtBQUssRUFBRSxDQUFDO1lBQ1YsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDaEMsQ0FBQztJQUNILENBQUM7SUFFRCxLQUFLLENBQUMsZUFBZSxDQUFDLFNBQTJCO1FBQy9DLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxFQUFFLEVBQUUsU0FBUyxDQUFDLENBQUM7UUFDN0MsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUVELEtBQUssQ0FBQyxZQUFZLENBQUMsRUFBVTtRQUMzQixPQUFPLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxJQUFJLElBQUksQ0FBQztJQUN6QyxDQUFDO0lBRUQsS0FBSyxDQUFDLGVBQWUsQ0FBQyxFQUFVLEVBQUUsT0FBa0M7UUFDbEUsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDMUMsSUFBSSxTQUFTLEVBQUUsQ0FBQztZQUNkLE1BQU0sQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ3BDLENBQUM7SUFDSCxDQUFDO0lBRUQsS0FBSyxDQUFDLFlBQVksQ0FBQyxPQUFlLEVBQUUsR0FBVztRQUM3QyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDbEMsQ0FBQztJQUVELEtBQUssQ0FBQyxVQUFVLENBQUMsT0FBZTtRQUM5QixPQUFPLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLElBQUksQ0FBQztJQUM1QyxDQUFDO0NBQ0YifQ==
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type * as http from 'node:http';
|
|
2
|
+
import type { TRouteHandler } from './server.interfaces.js';
|
|
3
|
+
import type { NonceManager } from './server.classes.nonce.js';
|
|
4
|
+
/**
|
|
5
|
+
* Minimal HTTP router for the ACME server.
|
|
6
|
+
* Supports parameterized paths like /order/:id.
|
|
7
|
+
*/
|
|
8
|
+
export declare class AcmeRouter {
|
|
9
|
+
private routes;
|
|
10
|
+
private nonceManager;
|
|
11
|
+
constructor(nonceManager: NonceManager);
|
|
12
|
+
addRoute(method: string, pattern: string, handler: TRouteHandler): void;
|
|
13
|
+
handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<void>;
|
|
14
|
+
private matchPath;
|
|
15
|
+
private parseBody;
|
|
16
|
+
private sendError;
|
|
17
|
+
}
|