@absolutejs/deploy 0.12.0 → 0.13.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/README.md +15 -0
- package/dist/tls.d.ts +11 -0
- package/dist/tls.js +11 -4
- package/dist/tls.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -288,6 +288,21 @@ await installCertificateOnTarget(target, cert, {
|
|
|
288
288
|
});
|
|
289
289
|
```
|
|
290
290
|
|
|
291
|
+
For hosted platforms, customers can delegate only the ACME challenge instead
|
|
292
|
+
of granting access to their DNS account. Ask them to CNAME
|
|
293
|
+
`_acme-challenge.app.customer.com` into a validation zone you control, then
|
|
294
|
+
map the provider write while leaving propagation checks on the public name:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
const cert = await issueCertificate({
|
|
298
|
+
domains: ['app.customer.com'],
|
|
299
|
+
dnsProvider: platformValidationDns,
|
|
300
|
+
email: 'ops@platform.example',
|
|
301
|
+
mapDnsChallengeRecord: ({ domain }) =>
|
|
302
|
+
`${stableDomainToken(domain)}.acme.platform.example`,
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
291
306
|
`generateAccountKey()` / `exportAccount()` / `importAccount()`
|
|
292
307
|
round-trip the ECDSA P-256 keypair + `kid` so cert renewals reuse
|
|
293
308
|
the same account (avoids Let's Encrypt's account-creation rate
|
package/dist/tls.d.ts
CHANGED
|
@@ -54,6 +54,17 @@ export type IssueCertificateOptions = {
|
|
|
54
54
|
domains: string[];
|
|
55
55
|
/** DNS provider used for DNS-01 challenges (must own the zone). */
|
|
56
56
|
dnsProvider: DnsProvider;
|
|
57
|
+
/**
|
|
58
|
+
* Map the public ACME challenge name to the record name written through
|
|
59
|
+
* `dnsProvider`. Use this for CNAME-delegated DNS-01, where customers point
|
|
60
|
+
* `_acme-challenge.app.example.com` at a platform-owned validation zone.
|
|
61
|
+
* The propagation checker still receives the public challenge name because
|
|
62
|
+
* that is what the certificate authority resolves.
|
|
63
|
+
*/
|
|
64
|
+
mapDnsChallengeRecord?: (context: {
|
|
65
|
+
domain: string;
|
|
66
|
+
recordName: string;
|
|
67
|
+
}) => string;
|
|
57
68
|
/** Contact email for the ACME account. */
|
|
58
69
|
email: string;
|
|
59
70
|
/** ACME directory URL. Default Let's Encrypt production. */
|
package/dist/tls.js
CHANGED
|
@@ -326,16 +326,23 @@ var issueCertificate = async (options) => {
|
|
|
326
326
|
const txtBytes = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(keyAuthorization)));
|
|
327
327
|
const txtValue = base64UrlEncode(txtBytes);
|
|
328
328
|
const recordName = `_acme-challenge.${auth.identifier.value}`;
|
|
329
|
-
|
|
329
|
+
const providerRecordName = options.mapDnsChallengeRecord?.({
|
|
330
|
+
domain: auth.identifier.value,
|
|
331
|
+
recordName
|
|
332
|
+
}) ?? recordName;
|
|
333
|
+
if (providerRecordName.trim().length === 0) {
|
|
334
|
+
throw new Error("[deploy/tls] mapped DNS-01 record name must not be empty");
|
|
335
|
+
}
|
|
336
|
+
log(providerRecordName === recordName ? `[tls] DNS-01: setting ${recordName} = "${txtValue}"` : `[tls] DNS-01: setting delegated ${providerRecordName} for ${recordName} = "${txtValue}"`);
|
|
330
337
|
const record = await options.dnsProvider.upsert({
|
|
331
338
|
content: txtValue,
|
|
332
|
-
name:
|
|
339
|
+
name: providerRecordName,
|
|
333
340
|
ttl: 60,
|
|
334
341
|
type: "TXT"
|
|
335
342
|
});
|
|
336
343
|
dnsCreated.push({
|
|
337
344
|
recordId: record.id,
|
|
338
|
-
recordName,
|
|
345
|
+
recordName: providerRecordName,
|
|
339
346
|
recordValue: txtValue
|
|
340
347
|
});
|
|
341
348
|
cleanups.push(() => options.dnsProvider.delete(record.id));
|
|
@@ -525,5 +532,5 @@ export {
|
|
|
525
532
|
AcmeError
|
|
526
533
|
};
|
|
527
534
|
|
|
528
|
-
//# debugId=
|
|
535
|
+
//# debugId=769AB3C81A63D52E64756E2164756E21
|
|
529
536
|
//# sourceMappingURL=tls.js.map
|
package/dist/tls.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/tls.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @absolutejs/deploy/tls — ACME (RFC 8555) client for Let's Encrypt and\n * compatible CAs. DNS-01 challenges only — uses the {@link DnsProvider}\n * abstraction from `./dns`, so the same Cloudflare / Route 53 /\n * Hetzner-DNS providers that point hostnames at boxes also satisfy\n * ACME's challenge requirement.\n *\n * Zero peer deps. Bun's `crypto.subtle` covers JWS signing and ECDSA\n * key generation; a small DER encoder builds the CSR.\n *\n * Public API:\n *\n * issueCertificate({ domains, dnsProvider, email })\n * → { certificatePem, privateKeyPem, account, domains }\n *\n * installCertificateOnTarget(target, cert, { certPath, keyPath, reload? })\n * → uploads PEM files via Target.upload + optional reload exec\n *\n * exportAccountKey / importAccountKey for persistence between runs\n * (reuse the same account across cert renewals — cheaper, doesn't\n * hit Let's Encrypt's account-creation rate limit)\n */\n\nimport { X509Certificate } from 'node:crypto';\nimport type { Target } from './targets';\nimport type { DnsProvider } from './dns';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nexport const LETSENCRYPT_PRODUCTION =\n\t'https://acme-v02.api.letsencrypt.org/directory';\nexport const LETSENCRYPT_STAGING =\n\t'https://acme-staging-v02.api.letsencrypt.org/directory';\n\n// =============================================================================\n// base64url\n// =============================================================================\n\nconst base64UrlEncode = (bytes: Uint8Array | ArrayBuffer): string => {\n\tconst buf = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n\tlet binary = '';\n\tfor (const byte of buf) binary += String.fromCharCode(byte);\n\treturn btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');\n};\n\nconst base64UrlEncodeString = (value: string): string =>\n\tbase64UrlEncode(new TextEncoder().encode(value));\n\nconst base64UrlDecode = (value: string): Uint8Array => {\n\tconst padded = value.replaceAll('-', '+').replaceAll('_', '/').padEnd(\n\t\tMath.ceil(value.length / 4) * 4,\n\t\t'='\n\t);\n\tconst binary = atob(padded);\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let index = 0; index < binary.length; index += 1) {\n\t\tbytes[index] = binary.charCodeAt(index);\n\t}\n\treturn bytes;\n};\n\n// =============================================================================\n// DER encoding (minimal subset for CSR)\n// =============================================================================\n\nconst derLength = (length: number): Uint8Array => {\n\tif (length < 0x80) return Uint8Array.from([length]);\n\tconst bytes: number[] = [];\n\tlet remaining = length;\n\twhile (remaining > 0) {\n\t\tbytes.unshift(remaining & 0xff);\n\t\tremaining >>= 8;\n\t}\n\treturn Uint8Array.from([0x80 | bytes.length, ...bytes]);\n};\n\nconst derTag = (tag: number, payload: Uint8Array): Uint8Array => {\n\tconst length = derLength(payload.length);\n\tconst out = new Uint8Array(1 + length.length + payload.length);\n\tout[0] = tag;\n\tout.set(length, 1);\n\tout.set(payload, 1 + length.length);\n\treturn out;\n};\n\nconst derSeq = (children: Uint8Array[]): Uint8Array => {\n\tconst total = children.reduce((sum, child) => sum + child.length, 0);\n\tconst payload = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const child of children) {\n\t\tpayload.set(child, offset);\n\t\toffset += child.length;\n\t}\n\treturn derTag(0x30, payload);\n};\n\nconst derSet = (children: Uint8Array[]): Uint8Array => {\n\tconst total = children.reduce((sum, child) => sum + child.length, 0);\n\tconst payload = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const child of children) {\n\t\tpayload.set(child, offset);\n\t\toffset += child.length;\n\t}\n\treturn derTag(0x31, payload);\n};\n\nconst derInt = (value: number | Uint8Array): Uint8Array => {\n\tif (typeof value === 'number') {\n\t\t// Small unsigned int — used for the CSR version field (0).\n\t\tif (value === 0) return derTag(0x02, Uint8Array.from([0]));\n\t\tconst bytes: number[] = [];\n\t\tlet remaining = value;\n\t\twhile (remaining > 0) {\n\t\t\tbytes.unshift(remaining & 0xff);\n\t\t\tremaining >>= 8;\n\t\t}\n\t\tif ((bytes[0] as number) & 0x80) bytes.unshift(0); // ensure positive\n\t\treturn derTag(0x02, Uint8Array.from(bytes));\n\t}\n\t// Big-endian unsigned bytes — strip leading zeros, then prepend 0x00 if\n\t// the high bit is set (DER INTEGER is signed two's complement).\n\tlet start = 0;\n\twhile (start < value.length - 1 && value[start] === 0) start += 1;\n\tlet payload = value.subarray(start);\n\tif ((payload[0] as number) & 0x80) {\n\t\tconst padded = new Uint8Array(payload.length + 1);\n\t\tpadded.set(payload, 1);\n\t\tpayload = padded;\n\t}\n\treturn derTag(0x02, payload);\n};\n\nconst derOid = (oid: string): Uint8Array => {\n\tconst parts = oid.split('.').map((part) => Number.parseInt(part, 10));\n\tconst first = parts[0];\n\tconst second = parts[1];\n\tif (first === undefined || second === undefined) {\n\t\tthrow new Error(`[deploy/tls] invalid OID: ${oid}`);\n\t}\n\tconst bytes: number[] = [first * 40 + second];\n\tfor (let index = 2; index < parts.length; index += 1) {\n\t\tconst value = parts[index] as number;\n\t\tconst chunks: number[] = [];\n\t\tlet remaining = value;\n\t\tdo {\n\t\t\tchunks.unshift(remaining & 0x7f);\n\t\t\tremaining >>= 7;\n\t\t} while (remaining > 0);\n\t\tfor (let chunkIndex = 0; chunkIndex < chunks.length - 1; chunkIndex += 1) {\n\t\t\tchunks[chunkIndex] = (chunks[chunkIndex] as number) | 0x80;\n\t\t}\n\t\tbytes.push(...chunks);\n\t}\n\treturn derTag(0x06, Uint8Array.from(bytes));\n};\n\nconst derPrintableString = (value: string): Uint8Array =>\n\tderTag(0x13, new TextEncoder().encode(value));\n\nconst derUtf8String = (value: string): Uint8Array =>\n\tderTag(0x0c, new TextEncoder().encode(value));\n\nconst derIa5String = (value: string): Uint8Array =>\n\tderTag(0x16, new TextEncoder().encode(value));\n\nconst derOctetString = (payload: Uint8Array): Uint8Array =>\n\tderTag(0x04, payload);\n\nconst derBitString = (payload: Uint8Array): Uint8Array => {\n\t// 0x00 prefix = number of unused bits in the final byte; for byte-aligned\n\t// signatures + keys this is always zero.\n\tconst bits = new Uint8Array(payload.length + 1);\n\tbits[0] = 0;\n\tbits.set(payload, 1);\n\treturn derTag(0x03, bits);\n};\n\nconst derContextTag = (\n\ttag: number,\n\tpayload: Uint8Array,\n\tconstructed = true\n): Uint8Array => derTag(0xa0 + tag + (constructed ? 0 : -0x20), payload);\n\n// =============================================================================\n// ECDSA signature: raw r||s ↔ DER SEQUENCE\n// =============================================================================\n\nconst ecdsaRawToDer = (rawSig: Uint8Array): Uint8Array => {\n\tconst half = rawSig.length / 2;\n\tconst r = rawSig.subarray(0, half);\n\tconst s = rawSig.subarray(half);\n\treturn derSeq([derInt(r), derInt(s)]);\n};\n\n// =============================================================================\n// JWS (RFC 7515) Flattened JSON Serialization for ACME\n// =============================================================================\n\ntype JwsHeader = {\n\talg: 'ES256';\n\tnonce: string;\n\turl: string;\n\tjwk?: JsonWebKey;\n\tkid?: string;\n};\n\ntype JwsBody = {\n\tprotected: string;\n\tpayload: string;\n\tsignature: string;\n};\n\nconst signJws = async (\n\tprivateKey: CryptoKey,\n\theader: JwsHeader,\n\tpayload: object | string\n): Promise<JwsBody> => {\n\tconst protectedHeader = base64UrlEncodeString(JSON.stringify(header));\n\tconst payloadEncoded =\n\t\tpayload === ''\n\t\t\t? '' // POST-as-GET: empty string, NOT empty object\n\t\t\t: base64UrlEncodeString(JSON.stringify(payload));\n\tconst signingInput = new TextEncoder().encode(\n\t\t`${protectedHeader}.${payloadEncoded}`\n\t);\n\tconst rawSig = new Uint8Array(\n\t\tawait crypto.subtle.sign(\n\t\t\t{ hash: 'SHA-256', name: 'ECDSA' },\n\t\t\tprivateKey,\n\t\t\tsigningInput\n\t\t)\n\t);\n\treturn {\n\t\tpayload: payloadEncoded,\n\t\tprotected: protectedHeader,\n\t\tsignature: base64UrlEncode(rawSig)\n\t};\n};\n\n// =============================================================================\n// JWK helpers — canonical thumbprint, public-key extraction\n// =============================================================================\n\nconst exportPublicJwk = async (publicKey: CryptoKey): Promise<JsonWebKey> => {\n\tconst jwk = await crypto.subtle.exportKey('jwk', publicKey);\n\t// Strip private fields if the export included them (shouldn't, on a\n\t// public key, but be defensive).\n\treturn {\n\t\tcrv: jwk.crv,\n\t\tkty: jwk.kty,\n\t\tx: jwk.x,\n\t\ty: jwk.y\n\t};\n};\n\nconst jwkThumbprint = async (publicJwk: JsonWebKey): Promise<string> => {\n\t// RFC 7638 — canonical JSON: required fields only, lex order.\n\tconst canonical = JSON.stringify({\n\t\tcrv: publicJwk.crv,\n\t\tkty: publicJwk.kty,\n\t\tx: publicJwk.x,\n\t\ty: publicJwk.y\n\t});\n\tconst hash = await crypto.subtle.digest(\n\t\t'SHA-256',\n\t\tnew TextEncoder().encode(canonical)\n\t);\n\treturn base64UrlEncode(hash);\n};\n\n// =============================================================================\n// Account key — generation + JSON export/import for persistence\n// =============================================================================\n\nexport type AcmeAccount = {\n\tkey: CryptoKeyPair;\n\t/** Account URL — set after first registration; persisted for renewals. */\n\tkid?: string;\n};\n\nexport type AcmeAccountJson = {\n\tpublicJwk: JsonWebKey;\n\tprivateJwk: JsonWebKey;\n\tkid?: string;\n};\n\nexport const generateAccountKey = async (): Promise<AcmeAccount> => {\n\tconst key = await crypto.subtle.generateKey(\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['sign', 'verify']\n\t);\n\treturn { key };\n};\n\nexport const exportAccount = async (\n\taccount: AcmeAccount\n): Promise<AcmeAccountJson> => {\n\tconst publicJwk = await crypto.subtle.exportKey('jwk', account.key.publicKey);\n\tconst privateJwk = await crypto.subtle.exportKey(\n\t\t'jwk',\n\t\taccount.key.privateKey\n\t);\n\treturn {\n\t\tprivateJwk,\n\t\tpublicJwk,\n\t\t...(account.kid !== undefined ? { kid: account.kid } : {})\n\t};\n};\n\nexport const importAccount = async (\n\tjson: AcmeAccountJson\n): Promise<AcmeAccount> => {\n\tconst publicKey = await crypto.subtle.importKey(\n\t\t'jwk',\n\t\tjson.publicJwk,\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['verify']\n\t);\n\tconst privateKey = await crypto.subtle.importKey(\n\t\t'jwk',\n\t\tjson.privateJwk,\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['sign']\n\t);\n\treturn {\n\t\tkey: { privateKey, publicKey },\n\t\t...(json.kid !== undefined ? { kid: json.kid } : {})\n\t};\n};\n\n// =============================================================================\n// CSR — Certificate Signing Request\n// =============================================================================\n\nconst buildSanExtension = (domains: string[]): Uint8Array => {\n\t// SubjectAltName extension value: SEQUENCE OF GeneralName\n\tconst generalNames = domains.map((domain) =>\n\t\tderTag(0x82, new TextEncoder().encode(domain))\n\t);\n\tconst sanSequence = derSeq(generalNames);\n\tconst extensionValue = derOctetString(sanSequence);\n\treturn derSeq([derOid('2.5.29.17'), extensionValue]);\n};\n\nconst buildExtensionRequestAttribute = (domains: string[]): Uint8Array => {\n\tconst extensions = derSeq([buildSanExtension(domains)]);\n\treturn derSeq([\n\t\tderOid('1.2.840.113549.1.9.14'), // PKCS#9 extensionRequest\n\t\tderSet([extensions])\n\t]);\n};\n\nconst generateCertKeyPair = async (): Promise<CryptoKeyPair> =>\n\tcrypto.subtle.generateKey(\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['sign', 'verify']\n\t);\n\nconst buildCsr = async (\n\tdomains: string[],\n\tkeypair: CryptoKeyPair\n): Promise<Uint8Array> => {\n\tif (domains.length === 0) {\n\t\tthrow new Error('[deploy/tls] CSR requires at least one domain');\n\t}\n\tconst commonName = domains[0] as string;\n\n\tconst spkiDer = new Uint8Array(\n\t\tawait crypto.subtle.exportKey('spki', keypair.publicKey)\n\t);\n\n\tconst version = derInt(0);\n\tconst subject = derSeq([\n\t\tderSet([\n\t\t\tderSeq([\n\t\t\t\tderOid('2.5.4.3'), // commonName\n\t\t\t\tderUtf8String(commonName)\n\t\t\t])\n\t\t])\n\t]);\n\n\tconst attributes = derContextTag(0, buildExtensionRequestAttribute(domains));\n\n\tconst certificationRequestInfo = derSeq([\n\t\tversion,\n\t\tsubject,\n\t\tspkiDer,\n\t\tattributes\n\t]);\n\n\tconst rawSig = new Uint8Array(\n\t\tawait crypto.subtle.sign(\n\t\t\t{ hash: 'SHA-256', name: 'ECDSA' },\n\t\t\tkeypair.privateKey,\n\t\t\tcertificationRequestInfo as BufferSource\n\t\t)\n\t);\n\tconst sigDer = ecdsaRawToDer(rawSig);\n\n\tconst sigAlgorithm = derSeq([derOid('1.2.840.10045.4.3.2')]);\n\t// ecdsa-with-SHA256\n\tconst signatureBits = derBitString(sigDer);\n\n\treturn derSeq([certificationRequestInfo, sigAlgorithm, signatureBits]);\n};\n\n// =============================================================================\n// PEM\n// =============================================================================\n\nconst derToPem = (label: string, der: Uint8Array): string => {\n\tconst b64 = btoa(String.fromCharCode(...der));\n\tconst lines = b64.match(/.{1,64}/g) ?? [];\n\treturn `-----BEGIN ${label}-----\\n${lines.join('\\n')}\\n-----END ${label}-----\\n`;\n};\n\nconst exportEcPrivateKeyPem = async (\n\tkeypair: CryptoKeyPair\n): Promise<string> => {\n\tconst pkcs8 = new Uint8Array(\n\t\tawait crypto.subtle.exportKey('pkcs8', keypair.privateKey)\n\t);\n\treturn derToPem('PRIVATE KEY', pkcs8);\n};\n\n// =============================================================================\n// ACME client\n// =============================================================================\n\ntype AcmeDirectory = {\n\tnewNonce: string;\n\tnewAccount: string;\n\tnewOrder: string;\n};\n\ntype AcmeOrder = {\n\tstatus: string;\n\tidentifiers: Array<{ type: string; value: string }>;\n\tauthorizations: string[];\n\tfinalize: string;\n\tcertificate?: string;\n};\n\ntype AcmeAuthorization = {\n\tstatus: string;\n\tidentifier: { type: string; value: string };\n\tchallenges: Array<{\n\t\ttype: string;\n\t\tstatus: string;\n\t\turl: string;\n\t\ttoken: string;\n\t}>;\n};\n\nexport type IssueCertificateOptions = {\n\t/** Domain(s) to include. First is the CN; all are SANs. */\n\tdomains: string[];\n\t/** DNS provider used for DNS-01 challenges (must own the zone). */\n\tdnsProvider: DnsProvider;\n\t/** Contact email for the ACME account. */\n\temail: string;\n\t/** ACME directory URL. Default Let's Encrypt production. */\n\tdirectoryUrl?: string;\n\t/** Reuse an existing account. Pass `exportAccount`'s output via `importAccount`. */\n\taccount?: AcmeAccount;\n\t/** Override fetch (tests). Default global fetch. */\n\tfetch?: typeof fetch;\n\t/** Poll interval. Default 3 s. */\n\tpollIntervalMs?: number;\n\t/**\n\t * Max wait before notifying ACME that the DNS challenge is ready.\n\t * Set ~30-60s for Cloudflare; longer for slower providers. Default 30 s.\n\t */\n\tdnsPropagationDelayMs?: number;\n\t/** Max wait for the order to become valid. Default 5 min. */\n\torderTimeoutMs?: number;\n\t/** Status log lines. */\n\tonLog?: (line: string) => void;\n\t/** Override sleep (tests). */\n\tsleep?: (ms: number) => Promise<void>;\n\t/**\n\t * Optional pre-check: returns true when DNS has propagated globally.\n\t * Default: just wait `dnsPropagationDelayMs` then proceed. Override\n\t * for production to actually verify (e.g. resolve from multiple\n\t * public resolvers).\n\t */\n\tcheckDnsPropagated?: (\n\t\trecordName: string,\n\t\texpectedValue: string\n\t) => Promise<boolean>;\n};\n\nexport type IssuedCertificate = {\n\tcertificatePem: string;\n\tprivateKeyPem: string;\n\taccount: AcmeAccount;\n\tdomains: string[];\n};\n\nexport class AcmeError extends Error {\n\treadonly status: number;\n\treadonly body: unknown;\n\tconstructor(message: string, status: number, body: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'AcmeError';\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\nconst defaultSleep = (ms: number): Promise<void> =>\n\tnew Promise((resolve) => setTimeout(resolve, ms));\n\nexport const issueCertificate = async (\n\toptions: IssueCertificateOptions\n): Promise<IssuedCertificate> => {\n\tconst log = options.onLog ?? (() => {});\n\tconst sleep = options.sleep ?? defaultSleep;\n\tconst fetcher = options.fetch ?? fetch;\n\tconst directoryUrl = options.directoryUrl ?? LETSENCRYPT_PRODUCTION;\n\tconst pollMs = options.pollIntervalMs ?? 3_000;\n\tconst dnsDelay = options.dnsPropagationDelayMs ?? 30_000;\n\tconst orderTimeout = options.orderTimeoutMs ?? 5 * 60_000;\n\tconst account = options.account ?? (await generateAccountKey());\n\n\tif (options.domains.length === 0) {\n\t\tthrow new Error('[deploy/tls] at least one domain is required');\n\t}\n\n\t// 1. Fetch directory.\n\tlog(`[tls] fetching ACME directory ${directoryUrl}`);\n\tconst directoryResponse = await fetcher(directoryUrl);\n\tif (!directoryResponse.ok) {\n\t\tthrow new AcmeError(\n\t\t\t`failed to fetch ACME directory: ${directoryResponse.status}`,\n\t\t\tdirectoryResponse.status,\n\t\t\tawait directoryResponse.text()\n\t\t);\n\t}\n\tconst directory = (await directoryResponse.json()) as AcmeDirectory;\n\n\t// 2. Get initial nonce.\n\tlet nonce = await fetchNonce(fetcher, directory.newNonce);\n\n\t// Helper: signed POST. Tracks nonce; returns parsed JSON body + headers.\n\tconst post = async (\n\t\turl: string,\n\t\tpayload: object | string,\n\t\tidentification: { kid?: string; jwk?: JsonWebKey }\n\t): Promise<{ status: number; body: unknown; headers: Headers }> => {\n\t\tconst header: JwsHeader = {\n\t\t\talg: 'ES256',\n\t\t\tnonce,\n\t\t\turl,\n\t\t\t...(identification.kid !== undefined ? { kid: identification.kid } : {}),\n\t\t\t...(identification.jwk !== undefined ? { jwk: identification.jwk } : {})\n\t\t};\n\t\tconst jws = await signJws(account.key.privateKey, header, payload);\n\t\tconst response = await fetcher(url, {\n\t\t\tbody: JSON.stringify(jws),\n\t\t\theaders: { 'content-type': 'application/jose+json' },\n\t\t\tmethod: 'POST'\n\t\t});\n\t\tconst newNonce = response.headers.get('replay-nonce');\n\t\tif (newNonce !== null) nonce = newNonce;\n\t\tconst text = await response.text();\n\t\tconst body =\n\t\t\ttext.length > 0 && response.headers.get('content-type')?.includes('json')\n\t\t\t\t? JSON.parse(text)\n\t\t\t\t: text;\n\t\tif (!response.ok) {\n\t\t\tthrow new AcmeError(\n\t\t\t\t`ACME ${url} → ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t\tbody\n\t\t\t);\n\t\t}\n\t\treturn { body, headers: response.headers, status: response.status };\n\t};\n\n\t// 3. Register the account (or reuse if kid is set).\n\tconst publicJwk = await exportPublicJwk(account.key.publicKey);\n\tif (account.kid === undefined) {\n\t\tlog(`[tls] registering ACME account for ${options.email}`);\n\t\tconst result = await post(\n\t\t\tdirectory.newAccount,\n\t\t\t{\n\t\t\t\tcontact: [`mailto:${options.email}`],\n\t\t\t\ttermsOfServiceAgreed: true\n\t\t\t},\n\t\t\t{ jwk: publicJwk }\n\t\t);\n\t\taccount.kid = result.headers.get('location') ?? undefined;\n\t\tif (account.kid === undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t'[deploy/tls] ACME newAccount response missing Location header'\n\t\t\t);\n\t\t}\n\t\tlog(`[tls] account registered: ${account.kid}`);\n\t} else {\n\t\tlog(`[tls] reusing account ${account.kid}`);\n\t}\n\n\tconst accountKid = account.kid;\n\n\t// 4. Submit the order.\n\tlog(`[tls] submitting order for ${options.domains.join(', ')}`);\n\tconst orderResult = await post(\n\t\tdirectory.newOrder,\n\t\t{\n\t\t\tidentifiers: options.domains.map((domain) => ({\n\t\t\t\ttype: 'dns',\n\t\t\t\tvalue: domain\n\t\t\t}))\n\t\t},\n\t\t{ kid: accountKid }\n\t);\n\tlet order = orderResult.body as AcmeOrder;\n\tconst orderUrl = orderResult.headers.get('location');\n\tif (orderUrl === null) {\n\t\tthrow new Error(\n\t\t\t'[deploy/tls] ACME newOrder response missing Location header'\n\t\t);\n\t}\n\n\t// 5. For each authorization, complete the DNS-01 challenge.\n\tconst cleanups: Array<() => Promise<void>> = [];\n\tconst dnsCreated: Array<{\n\t\trecordId: string;\n\t\trecordName: string;\n\t\trecordValue: string;\n\t}> = [];\n\n\ttry {\n\t\tconst thumbprint = await jwkThumbprint(publicJwk);\n\n\t\tfor (const authUrl of order.authorizations) {\n\t\t\tconst authResult = await post(authUrl, '', { kid: accountKid });\n\t\t\tconst auth = authResult.body as AcmeAuthorization;\n\t\t\tconst challenge = auth.challenges.find((c) => c.type === 'dns-01');\n\t\t\tif (challenge === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[deploy/tls] no dns-01 challenge for ${auth.identifier.value}`\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst keyAuthorization = `${challenge.token}.${thumbprint}`;\n\t\t\tconst txtBytes = new Uint8Array(\n\t\t\t\tawait crypto.subtle.digest(\n\t\t\t\t\t'SHA-256',\n\t\t\t\t\tnew TextEncoder().encode(keyAuthorization)\n\t\t\t\t)\n\t\t\t);\n\t\t\tconst txtValue = base64UrlEncode(txtBytes);\n\t\t\tconst recordName = `_acme-challenge.${auth.identifier.value}`;\n\t\t\tlog(`[tls] DNS-01: setting ${recordName} = \"${txtValue}\"`);\n\t\t\tconst record = await options.dnsProvider.upsert({\n\t\t\t\tcontent: txtValue,\n\t\t\t\tname: recordName,\n\t\t\t\tttl: 60,\n\t\t\t\ttype: 'TXT'\n\t\t\t});\n\t\t\tdnsCreated.push({\n\t\t\t\trecordId: record.id,\n\t\t\t\trecordName,\n\t\t\t\trecordValue: txtValue\n\t\t\t});\n\t\t\tcleanups.push(() => options.dnsProvider.delete(record.id));\n\n\t\t\tif (options.checkDnsPropagated !== undefined) {\n\t\t\t\tlog('[tls] waiting for DNS propagation (custom checker)…');\n\t\t\t\tconst deadline = Date.now() + dnsDelay;\n\t\t\t\twhile (Date.now() < deadline) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tawait options.checkDnsPropagated(recordName, txtValue)\n\t\t\t\t\t) break;\n\t\t\t\t\tawait sleep(pollMs);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog(`[tls] sleeping ${dnsDelay}ms for DNS propagation`);\n\t\t\t\tawait sleep(dnsDelay);\n\t\t\t}\n\n\t\t\tlog(`[tls] notifying ACME of dns-01 readiness for ${auth.identifier.value}`);\n\t\t\tawait post(challenge.url, {}, { kid: accountKid });\n\n\t\t\t// Poll the authorization until valid/invalid.\n\t\t\tconst authDeadline = Date.now() + orderTimeout;\n\t\t\twhile (Date.now() < authDeadline) {\n\t\t\t\tconst polledResult = await post(authUrl, '', { kid: accountKid });\n\t\t\t\tconst polled = polledResult.body as AcmeAuthorization;\n\t\t\t\tif (polled.status === 'valid') break;\n\t\t\t\tif (polled.status === 'invalid') {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[deploy/tls] authorization failed for ${auth.identifier.value}: ${JSON.stringify(polled.challenges)}`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait sleep(pollMs);\n\t\t\t}\n\t\t}\n\n\t\t// 6. Finalize — generate cert keypair + CSR.\n\t\tlog('[tls] generating cert keypair + CSR');\n\t\tconst certKey = await generateCertKeyPair();\n\t\tconst csrDer = await buildCsr(options.domains, certKey);\n\t\tconst csr = base64UrlEncode(csrDer);\n\n\t\tawait post(order.finalize, { csr }, { kid: accountKid });\n\n\t\t// 7. Poll the order until valid + certificate URL appears.\n\t\tconst orderDeadline = Date.now() + orderTimeout;\n\t\twhile (Date.now() < orderDeadline) {\n\t\t\tconst refreshed = await post(orderUrl, '', { kid: accountKid });\n\t\t\torder = refreshed.body as AcmeOrder;\n\t\t\tif (order.status === 'valid' && order.certificate !== undefined) break;\n\t\t\tif (order.status === 'invalid') {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[deploy/tls] order failed: ${JSON.stringify(order)}`\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait sleep(pollMs);\n\t\t}\n\n\t\tif (order.certificate === undefined) {\n\t\t\tthrow new Error('[deploy/tls] order timed out before issuing certificate');\n\t\t}\n\n\t\t// 8. Download the cert (PEM bundle).\n\t\tlog(`[tls] downloading certificate from ${order.certificate}`);\n\t\tconst certResult = await post(order.certificate, '', { kid: accountKid });\n\t\tconst certificatePem =\n\t\t\ttypeof certResult.body === 'string'\n\t\t\t\t? certResult.body\n\t\t\t\t: String(certResult.body);\n\n\t\tconst privateKeyPem = await exportEcPrivateKeyPem(certKey);\n\n\t\treturn {\n\t\t\taccount,\n\t\t\tcertificatePem,\n\t\t\tdomains: options.domains,\n\t\t\tprivateKeyPem\n\t\t};\n\t} finally {\n\t\tfor (const cleanup of cleanups) {\n\t\t\ttry {\n\t\t\t\tawait cleanup();\n\t\t\t} catch (error) {\n\t\t\t\tlog(`[tls] cleanup failed (continuing): ${String(error)}`);\n\t\t\t}\n\t\t}\n\t\t// Suppress unused-variable lint without changing behavior.\n\t\tvoid dnsCreated;\n\t}\n};\n\nconst fetchNonce = async (\n\tfetcher: typeof fetch,\n\turl: string\n): Promise<string> => {\n\tconst response = await fetcher(url, { method: 'HEAD' });\n\tconst nonce = response.headers.get('replay-nonce');\n\tif (nonce === null) {\n\t\tthrow new Error('[deploy/tls] newNonce response missing Replay-Nonce header');\n\t}\n\treturn nonce;\n};\n\n// =============================================================================\n// installCertificateOnTarget\n// =============================================================================\n\nexport type InstallCertificateOptions = {\n\t/** Remote path for the cert chain. Default `/etc/ssl/<firstDomain>/fullchain.pem`. */\n\tcertPath?: string;\n\t/** Remote path for the private key. Default `/etc/ssl/<firstDomain>/privkey.pem`. */\n\tkeyPath?: string;\n\t/** Mode (chmod) for the cert + key. Default `600`. */\n\tmode?: string;\n\t/** Owner (chown) for the cert + key. Default unchanged. */\n\towner?: string;\n\t/** Optional reload command run after install (e.g. `'systemctl reload nginx'`). */\n\treload?: string;\n\t/** Override the writeTo helper (tests). */\n\twriteFile?: (target: Target, path: string, contents: string) => Promise<void>;\n};\n\nconst defaultWriteFile = async (\n\ttarget: Target,\n\tremotePath: string,\n\tcontents: string\n): Promise<void> => {\n\tconst escaped = contents.replaceAll(\"'\", \"'\\\\''\");\n\tawait target.exec(`mkdir -p \"$(dirname '${remotePath}')\"`);\n\tawait target.exec(`cat > '${remotePath}' <<'__ABS_TLS_EOF__'\\n${contents}__ABS_TLS_EOF__\\n`);\n\tvoid escaped;\n};\n\n/**\n * @internal — exposed for unit testing of cryptographic primitives.\n * Not part of the public API; consumers should NOT depend on this.\n */\nexport const __testing = {\n\tbase64UrlDecode,\n\tbase64UrlEncode,\n\tbase64UrlEncodeString,\n\tbuildCsr,\n\tderBitString,\n\tderInt,\n\tderOid,\n\tderSeq,\n\tderSet,\n\tecdsaRawToDer,\n\texportEcPrivateKeyPem,\n\texportPublicJwk,\n\tgenerateCertKeyPair,\n\tjwkThumbprint,\n\tsignJws\n};\n\n/**\n * Upload cert + private key to the target. Composable with the deploy\n * pipeline as a verify-step or post-deploy hook.\n */\nexport const installCertificateOnTarget = async (\n\ttarget: Target,\n\tcert: IssuedCertificate,\n\toptions: InstallCertificateOptions = {}\n): Promise<{ certPath: string; keyPath: string }> => {\n\tconst domain = cert.domains[0];\n\tif (domain === undefined) {\n\t\tthrow new Error('[deploy/tls] certificate has no domains');\n\t}\n\tconst certPath = options.certPath ?? `/etc/ssl/${domain}/fullchain.pem`;\n\tconst keyPath = options.keyPath ?? `/etc/ssl/${domain}/privkey.pem`;\n\tconst mode = options.mode ?? '600';\n\tconst writer = options.writeFile ?? defaultWriteFile;\n\n\tawait writer(target, certPath, cert.certificatePem);\n\tawait writer(target, keyPath, cert.privateKeyPem);\n\tawait target.exec(`chmod ${mode} '${certPath}' '${keyPath}'`);\n\tif (options.owner !== undefined) {\n\t\tawait target.exec(`chown ${options.owner} '${certPath}' '${keyPath}'`);\n\t}\n\tif (options.reload !== undefined) {\n\t\tawait target.exec(options.reload);\n\t}\n\n\treturn { certPath, keyPath };\n};\n\n// =============================================================================\n// Certificate inspection + renewal scheduling\n// =============================================================================\n\nexport type CertificateInspection = {\n\t/** CN + every SAN, deduplicated. */\n\tsubjects: string[];\n\t/** Issuance time, ms since epoch. */\n\tvalidFrom: number;\n\t/** Expiration time, ms since epoch. */\n\tvalidTo: number;\n\t/** Whole days remaining before `validTo`. Negative if expired. */\n\tdaysRemaining: number;\n\t/** True when the cert is past `validTo`. */\n\texpired: boolean;\n\t/** Issuer DN string (for \"Let's Encrypt vs staging vs other CA\" checks). */\n\tissuer: string;\n};\n\nconst parseSubjects = (x509: X509Certificate): string[] => {\n\tconst subjects = new Set<string>();\n\tconst cnMatch = x509.subject.match(/CN=([^,\\n]+)/);\n\tif (cnMatch !== null && cnMatch[1] !== undefined) {\n\t\tsubjects.add(cnMatch[1].trim());\n\t}\n\tconst san = x509.subjectAltName;\n\tif (san !== undefined && san !== null) {\n\t\tfor (const part of san.split(',')) {\n\t\t\tconst dnsMatch = part.trim().match(/^DNS:(.+)$/);\n\t\t\tif (dnsMatch !== null && dnsMatch[1] !== undefined) {\n\t\t\t\tsubjects.add(dnsMatch[1].trim());\n\t\t\t}\n\t\t}\n\t}\n\treturn [...subjects];\n};\n\n/**\n * Parse a PEM certificate into operator-shaped metadata. Used by\n * {@link renewCertificate} to decide whether to re-issue; also fine\n * for status pages and observability.\n */\nexport const inspectCertificate = (\n\tpem: string,\n\toptions: { now?: () => number } = {}\n): CertificateInspection => {\n\tconst x509 = new X509Certificate(pem);\n\tconst validFrom = new Date(x509.validFrom).getTime();\n\tconst validTo = new Date(x509.validTo).getTime();\n\tconst now = (options.now ?? Date.now)();\n\tconst daysRemaining = Math.floor(\n\t\t(validTo - now) / (24 * 60 * 60 * 1000)\n\t);\n\treturn {\n\t\tdaysRemaining,\n\t\texpired: validTo < now,\n\t\tissuer: x509.issuer,\n\t\tsubjects: parseSubjects(x509),\n\t\tvalidFrom,\n\t\tvalidTo\n\t};\n};\n\nexport type RenewCertificateOptions = IssueCertificateOptions & {\n\t/**\n\t * Current cert PEM. When provided, the cert's `validTo` decides\n\t * whether to re-issue; when absent, renewal always issues.\n\t */\n\tcurrentCertificatePem?: string;\n\t/**\n\t * Re-issue when fewer than this many days remain. Default 30,\n\t * matching certbot's standard schedule (Let's Encrypt issues 90-day\n\t * certs; renewing at 30 days leaves a 60-day error budget).\n\t */\n\trenewWhenDaysRemaining?: number;\n\t/** Force re-issue regardless of remaining days. Default false. */\n\tforce?: boolean;\n\t/** Override `Date.now()` for testing. */\n\tnow?: () => number;\n};\n\nexport type RenewalResult =\n\t| {\n\t\t\trenewed: true;\n\t\t\tcertificate: IssuedCertificate;\n\t\t\treason: 'forced' | 'no-current-cert' | 'expiring-soon';\n\t }\n\t| {\n\t\t\trenewed: false;\n\t\t\treason: 'still-fresh';\n\t\t\tinspection: CertificateInspection;\n\t };\n\n/**\n * Conditional cert renewal. Parses the supplied cert (if any),\n * decides whether to re-issue based on remaining days, and either\n * returns the existing cert info (\"still fresh\") or issues a new\n * one via {@link issueCertificate}.\n *\n * Wire to a scheduled function (cron / @absolutejs/sync schedule /\n * GitHub Action) running at least once a week. Idempotent: a fresh\n * cert returns `renewed: false` cheaply (one PEM parse, zero\n * network IO).\n *\n * @example\n * const result = await renewCertificate({\n * currentCertificatePem: existingPem, // omit to force first-issue\n * domains: ['api.example.com'],\n * dnsProvider: dns,\n * email: 'ops@example.com',\n * account: persistedAccount, // reuse to avoid CA rate limit\n * });\n * if (result.renewed) {\n * await installCertificateOnTarget(target, result.certificate, { reload });\n * }\n */\nexport const renewCertificate = async (\n\toptions: RenewCertificateOptions\n): Promise<RenewalResult> => {\n\tconst threshold = options.renewWhenDaysRemaining ?? 30;\n\tif (threshold < 0) {\n\t\tthrow new Error(\n\t\t\t'[deploy/tls] renewWhenDaysRemaining must be non-negative'\n\t\t);\n\t}\n\n\tif (options.force === true) {\n\t\tconst certificate = await issueCertificate(options);\n\t\treturn { certificate, reason: 'forced', renewed: true };\n\t}\n\n\tif (options.currentCertificatePem === undefined) {\n\t\tconst certificate = await issueCertificate(options);\n\t\treturn { certificate, reason: 'no-current-cert', renewed: true };\n\t}\n\n\tconst nowFn = options.now ?? Date.now;\n\tconst inspection = inspectCertificate(options.currentCertificatePem, {\n\t\tnow: nowFn\n\t});\n\n\tif (!inspection.expired && inspection.daysRemaining >= threshold) {\n\t\treturn { inspection, reason: 'still-fresh', renewed: false };\n\t}\n\n\tconst certificate = await issueCertificate(options);\n\treturn { certificate, reason: 'expiring-soon', renewed: true };\n};\n"
|
|
5
|
+
"/**\n * @absolutejs/deploy/tls — ACME (RFC 8555) client for Let's Encrypt and\n * compatible CAs. DNS-01 challenges only — uses the {@link DnsProvider}\n * abstraction from `./dns`, so the same Cloudflare / Route 53 /\n * Hetzner-DNS providers that point hostnames at boxes also satisfy\n * ACME's challenge requirement.\n *\n * Zero peer deps. Bun's `crypto.subtle` covers JWS signing and ECDSA\n * key generation; a small DER encoder builds the CSR.\n *\n * Public API:\n *\n * issueCertificate({ domains, dnsProvider, email })\n * → { certificatePem, privateKeyPem, account, domains }\n *\n * installCertificateOnTarget(target, cert, { certPath, keyPath, reload? })\n * → uploads PEM files via Target.upload + optional reload exec\n *\n * exportAccountKey / importAccountKey for persistence between runs\n * (reuse the same account across cert renewals — cheaper, doesn't\n * hit Let's Encrypt's account-creation rate limit)\n */\n\nimport { X509Certificate } from 'node:crypto';\nimport type { Target } from './targets';\nimport type { DnsProvider } from './dns';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nexport const LETSENCRYPT_PRODUCTION =\n\t'https://acme-v02.api.letsencrypt.org/directory';\nexport const LETSENCRYPT_STAGING =\n\t'https://acme-staging-v02.api.letsencrypt.org/directory';\n\n// =============================================================================\n// base64url\n// =============================================================================\n\nconst base64UrlEncode = (bytes: Uint8Array | ArrayBuffer): string => {\n\tconst buf = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n\tlet binary = '';\n\tfor (const byte of buf) binary += String.fromCharCode(byte);\n\treturn btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');\n};\n\nconst base64UrlEncodeString = (value: string): string =>\n\tbase64UrlEncode(new TextEncoder().encode(value));\n\nconst base64UrlDecode = (value: string): Uint8Array => {\n\tconst padded = value.replaceAll('-', '+').replaceAll('_', '/').padEnd(\n\t\tMath.ceil(value.length / 4) * 4,\n\t\t'='\n\t);\n\tconst binary = atob(padded);\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let index = 0; index < binary.length; index += 1) {\n\t\tbytes[index] = binary.charCodeAt(index);\n\t}\n\treturn bytes;\n};\n\n// =============================================================================\n// DER encoding (minimal subset for CSR)\n// =============================================================================\n\nconst derLength = (length: number): Uint8Array => {\n\tif (length < 0x80) return Uint8Array.from([length]);\n\tconst bytes: number[] = [];\n\tlet remaining = length;\n\twhile (remaining > 0) {\n\t\tbytes.unshift(remaining & 0xff);\n\t\tremaining >>= 8;\n\t}\n\treturn Uint8Array.from([0x80 | bytes.length, ...bytes]);\n};\n\nconst derTag = (tag: number, payload: Uint8Array): Uint8Array => {\n\tconst length = derLength(payload.length);\n\tconst out = new Uint8Array(1 + length.length + payload.length);\n\tout[0] = tag;\n\tout.set(length, 1);\n\tout.set(payload, 1 + length.length);\n\treturn out;\n};\n\nconst derSeq = (children: Uint8Array[]): Uint8Array => {\n\tconst total = children.reduce((sum, child) => sum + child.length, 0);\n\tconst payload = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const child of children) {\n\t\tpayload.set(child, offset);\n\t\toffset += child.length;\n\t}\n\treturn derTag(0x30, payload);\n};\n\nconst derSet = (children: Uint8Array[]): Uint8Array => {\n\tconst total = children.reduce((sum, child) => sum + child.length, 0);\n\tconst payload = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const child of children) {\n\t\tpayload.set(child, offset);\n\t\toffset += child.length;\n\t}\n\treturn derTag(0x31, payload);\n};\n\nconst derInt = (value: number | Uint8Array): Uint8Array => {\n\tif (typeof value === 'number') {\n\t\t// Small unsigned int — used for the CSR version field (0).\n\t\tif (value === 0) return derTag(0x02, Uint8Array.from([0]));\n\t\tconst bytes: number[] = [];\n\t\tlet remaining = value;\n\t\twhile (remaining > 0) {\n\t\t\tbytes.unshift(remaining & 0xff);\n\t\t\tremaining >>= 8;\n\t\t}\n\t\tif ((bytes[0] as number) & 0x80) bytes.unshift(0); // ensure positive\n\t\treturn derTag(0x02, Uint8Array.from(bytes));\n\t}\n\t// Big-endian unsigned bytes — strip leading zeros, then prepend 0x00 if\n\t// the high bit is set (DER INTEGER is signed two's complement).\n\tlet start = 0;\n\twhile (start < value.length - 1 && value[start] === 0) start += 1;\n\tlet payload = value.subarray(start);\n\tif ((payload[0] as number) & 0x80) {\n\t\tconst padded = new Uint8Array(payload.length + 1);\n\t\tpadded.set(payload, 1);\n\t\tpayload = padded;\n\t}\n\treturn derTag(0x02, payload);\n};\n\nconst derOid = (oid: string): Uint8Array => {\n\tconst parts = oid.split('.').map((part) => Number.parseInt(part, 10));\n\tconst first = parts[0];\n\tconst second = parts[1];\n\tif (first === undefined || second === undefined) {\n\t\tthrow new Error(`[deploy/tls] invalid OID: ${oid}`);\n\t}\n\tconst bytes: number[] = [first * 40 + second];\n\tfor (let index = 2; index < parts.length; index += 1) {\n\t\tconst value = parts[index] as number;\n\t\tconst chunks: number[] = [];\n\t\tlet remaining = value;\n\t\tdo {\n\t\t\tchunks.unshift(remaining & 0x7f);\n\t\t\tremaining >>= 7;\n\t\t} while (remaining > 0);\n\t\tfor (let chunkIndex = 0; chunkIndex < chunks.length - 1; chunkIndex += 1) {\n\t\t\tchunks[chunkIndex] = (chunks[chunkIndex] as number) | 0x80;\n\t\t}\n\t\tbytes.push(...chunks);\n\t}\n\treturn derTag(0x06, Uint8Array.from(bytes));\n};\n\nconst derPrintableString = (value: string): Uint8Array =>\n\tderTag(0x13, new TextEncoder().encode(value));\n\nconst derUtf8String = (value: string): Uint8Array =>\n\tderTag(0x0c, new TextEncoder().encode(value));\n\nconst derIa5String = (value: string): Uint8Array =>\n\tderTag(0x16, new TextEncoder().encode(value));\n\nconst derOctetString = (payload: Uint8Array): Uint8Array =>\n\tderTag(0x04, payload);\n\nconst derBitString = (payload: Uint8Array): Uint8Array => {\n\t// 0x00 prefix = number of unused bits in the final byte; for byte-aligned\n\t// signatures + keys this is always zero.\n\tconst bits = new Uint8Array(payload.length + 1);\n\tbits[0] = 0;\n\tbits.set(payload, 1);\n\treturn derTag(0x03, bits);\n};\n\nconst derContextTag = (\n\ttag: number,\n\tpayload: Uint8Array,\n\tconstructed = true\n): Uint8Array => derTag(0xa0 + tag + (constructed ? 0 : -0x20), payload);\n\n// =============================================================================\n// ECDSA signature: raw r||s ↔ DER SEQUENCE\n// =============================================================================\n\nconst ecdsaRawToDer = (rawSig: Uint8Array): Uint8Array => {\n\tconst half = rawSig.length / 2;\n\tconst r = rawSig.subarray(0, half);\n\tconst s = rawSig.subarray(half);\n\treturn derSeq([derInt(r), derInt(s)]);\n};\n\n// =============================================================================\n// JWS (RFC 7515) Flattened JSON Serialization for ACME\n// =============================================================================\n\ntype JwsHeader = {\n\talg: 'ES256';\n\tnonce: string;\n\turl: string;\n\tjwk?: JsonWebKey;\n\tkid?: string;\n};\n\ntype JwsBody = {\n\tprotected: string;\n\tpayload: string;\n\tsignature: string;\n};\n\nconst signJws = async (\n\tprivateKey: CryptoKey,\n\theader: JwsHeader,\n\tpayload: object | string\n): Promise<JwsBody> => {\n\tconst protectedHeader = base64UrlEncodeString(JSON.stringify(header));\n\tconst payloadEncoded =\n\t\tpayload === ''\n\t\t\t? '' // POST-as-GET: empty string, NOT empty object\n\t\t\t: base64UrlEncodeString(JSON.stringify(payload));\n\tconst signingInput = new TextEncoder().encode(\n\t\t`${protectedHeader}.${payloadEncoded}`\n\t);\n\tconst rawSig = new Uint8Array(\n\t\tawait crypto.subtle.sign(\n\t\t\t{ hash: 'SHA-256', name: 'ECDSA' },\n\t\t\tprivateKey,\n\t\t\tsigningInput\n\t\t)\n\t);\n\treturn {\n\t\tpayload: payloadEncoded,\n\t\tprotected: protectedHeader,\n\t\tsignature: base64UrlEncode(rawSig)\n\t};\n};\n\n// =============================================================================\n// JWK helpers — canonical thumbprint, public-key extraction\n// =============================================================================\n\nconst exportPublicJwk = async (publicKey: CryptoKey): Promise<JsonWebKey> => {\n\tconst jwk = await crypto.subtle.exportKey('jwk', publicKey);\n\t// Strip private fields if the export included them (shouldn't, on a\n\t// public key, but be defensive).\n\treturn {\n\t\tcrv: jwk.crv,\n\t\tkty: jwk.kty,\n\t\tx: jwk.x,\n\t\ty: jwk.y\n\t};\n};\n\nconst jwkThumbprint = async (publicJwk: JsonWebKey): Promise<string> => {\n\t// RFC 7638 — canonical JSON: required fields only, lex order.\n\tconst canonical = JSON.stringify({\n\t\tcrv: publicJwk.crv,\n\t\tkty: publicJwk.kty,\n\t\tx: publicJwk.x,\n\t\ty: publicJwk.y\n\t});\n\tconst hash = await crypto.subtle.digest(\n\t\t'SHA-256',\n\t\tnew TextEncoder().encode(canonical)\n\t);\n\treturn base64UrlEncode(hash);\n};\n\n// =============================================================================\n// Account key — generation + JSON export/import for persistence\n// =============================================================================\n\nexport type AcmeAccount = {\n\tkey: CryptoKeyPair;\n\t/** Account URL — set after first registration; persisted for renewals. */\n\tkid?: string;\n};\n\nexport type AcmeAccountJson = {\n\tpublicJwk: JsonWebKey;\n\tprivateJwk: JsonWebKey;\n\tkid?: string;\n};\n\nexport const generateAccountKey = async (): Promise<AcmeAccount> => {\n\tconst key = await crypto.subtle.generateKey(\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['sign', 'verify']\n\t);\n\treturn { key };\n};\n\nexport const exportAccount = async (\n\taccount: AcmeAccount\n): Promise<AcmeAccountJson> => {\n\tconst publicJwk = await crypto.subtle.exportKey('jwk', account.key.publicKey);\n\tconst privateJwk = await crypto.subtle.exportKey(\n\t\t'jwk',\n\t\taccount.key.privateKey\n\t);\n\treturn {\n\t\tprivateJwk,\n\t\tpublicJwk,\n\t\t...(account.kid !== undefined ? { kid: account.kid } : {})\n\t};\n};\n\nexport const importAccount = async (\n\tjson: AcmeAccountJson\n): Promise<AcmeAccount> => {\n\tconst publicKey = await crypto.subtle.importKey(\n\t\t'jwk',\n\t\tjson.publicJwk,\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['verify']\n\t);\n\tconst privateKey = await crypto.subtle.importKey(\n\t\t'jwk',\n\t\tjson.privateJwk,\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['sign']\n\t);\n\treturn {\n\t\tkey: { privateKey, publicKey },\n\t\t...(json.kid !== undefined ? { kid: json.kid } : {})\n\t};\n};\n\n// =============================================================================\n// CSR — Certificate Signing Request\n// =============================================================================\n\nconst buildSanExtension = (domains: string[]): Uint8Array => {\n\t// SubjectAltName extension value: SEQUENCE OF GeneralName\n\tconst generalNames = domains.map((domain) =>\n\t\tderTag(0x82, new TextEncoder().encode(domain))\n\t);\n\tconst sanSequence = derSeq(generalNames);\n\tconst extensionValue = derOctetString(sanSequence);\n\treturn derSeq([derOid('2.5.29.17'), extensionValue]);\n};\n\nconst buildExtensionRequestAttribute = (domains: string[]): Uint8Array => {\n\tconst extensions = derSeq([buildSanExtension(domains)]);\n\treturn derSeq([\n\t\tderOid('1.2.840.113549.1.9.14'), // PKCS#9 extensionRequest\n\t\tderSet([extensions])\n\t]);\n};\n\nconst generateCertKeyPair = async (): Promise<CryptoKeyPair> =>\n\tcrypto.subtle.generateKey(\n\t\t{ name: 'ECDSA', namedCurve: 'P-256' },\n\t\ttrue,\n\t\t['sign', 'verify']\n\t);\n\nconst buildCsr = async (\n\tdomains: string[],\n\tkeypair: CryptoKeyPair\n): Promise<Uint8Array> => {\n\tif (domains.length === 0) {\n\t\tthrow new Error('[deploy/tls] CSR requires at least one domain');\n\t}\n\tconst commonName = domains[0] as string;\n\n\tconst spkiDer = new Uint8Array(\n\t\tawait crypto.subtle.exportKey('spki', keypair.publicKey)\n\t);\n\n\tconst version = derInt(0);\n\tconst subject = derSeq([\n\t\tderSet([\n\t\t\tderSeq([\n\t\t\t\tderOid('2.5.4.3'), // commonName\n\t\t\t\tderUtf8String(commonName)\n\t\t\t])\n\t\t])\n\t]);\n\n\tconst attributes = derContextTag(0, buildExtensionRequestAttribute(domains));\n\n\tconst certificationRequestInfo = derSeq([\n\t\tversion,\n\t\tsubject,\n\t\tspkiDer,\n\t\tattributes\n\t]);\n\n\tconst rawSig = new Uint8Array(\n\t\tawait crypto.subtle.sign(\n\t\t\t{ hash: 'SHA-256', name: 'ECDSA' },\n\t\t\tkeypair.privateKey,\n\t\t\tcertificationRequestInfo as BufferSource\n\t\t)\n\t);\n\tconst sigDer = ecdsaRawToDer(rawSig);\n\n\tconst sigAlgorithm = derSeq([derOid('1.2.840.10045.4.3.2')]);\n\t// ecdsa-with-SHA256\n\tconst signatureBits = derBitString(sigDer);\n\n\treturn derSeq([certificationRequestInfo, sigAlgorithm, signatureBits]);\n};\n\n// =============================================================================\n// PEM\n// =============================================================================\n\nconst derToPem = (label: string, der: Uint8Array): string => {\n\tconst b64 = btoa(String.fromCharCode(...der));\n\tconst lines = b64.match(/.{1,64}/g) ?? [];\n\treturn `-----BEGIN ${label}-----\\n${lines.join('\\n')}\\n-----END ${label}-----\\n`;\n};\n\nconst exportEcPrivateKeyPem = async (\n\tkeypair: CryptoKeyPair\n): Promise<string> => {\n\tconst pkcs8 = new Uint8Array(\n\t\tawait crypto.subtle.exportKey('pkcs8', keypair.privateKey)\n\t);\n\treturn derToPem('PRIVATE KEY', pkcs8);\n};\n\n// =============================================================================\n// ACME client\n// =============================================================================\n\ntype AcmeDirectory = {\n\tnewNonce: string;\n\tnewAccount: string;\n\tnewOrder: string;\n};\n\ntype AcmeOrder = {\n\tstatus: string;\n\tidentifiers: Array<{ type: string; value: string }>;\n\tauthorizations: string[];\n\tfinalize: string;\n\tcertificate?: string;\n};\n\ntype AcmeAuthorization = {\n\tstatus: string;\n\tidentifier: { type: string; value: string };\n\tchallenges: Array<{\n\t\ttype: string;\n\t\tstatus: string;\n\t\turl: string;\n\t\ttoken: string;\n\t}>;\n};\n\nexport type IssueCertificateOptions = {\n\t/** Domain(s) to include. First is the CN; all are SANs. */\n\tdomains: string[];\n\t/** DNS provider used for DNS-01 challenges (must own the zone). */\n\tdnsProvider: DnsProvider;\n\t/**\n\t * Map the public ACME challenge name to the record name written through\n\t * `dnsProvider`. Use this for CNAME-delegated DNS-01, where customers point\n\t * `_acme-challenge.app.example.com` at a platform-owned validation zone.\n\t * The propagation checker still receives the public challenge name because\n\t * that is what the certificate authority resolves.\n\t */\n\tmapDnsChallengeRecord?: (context: {\n\t\tdomain: string;\n\t\trecordName: string;\n\t}) => string;\n\t/** Contact email for the ACME account. */\n\temail: string;\n\t/** ACME directory URL. Default Let's Encrypt production. */\n\tdirectoryUrl?: string;\n\t/** Reuse an existing account. Pass `exportAccount`'s output via `importAccount`. */\n\taccount?: AcmeAccount;\n\t/** Override fetch (tests). Default global fetch. */\n\tfetch?: typeof fetch;\n\t/** Poll interval. Default 3 s. */\n\tpollIntervalMs?: number;\n\t/**\n\t * Max wait before notifying ACME that the DNS challenge is ready.\n\t * Set ~30-60s for Cloudflare; longer for slower providers. Default 30 s.\n\t */\n\tdnsPropagationDelayMs?: number;\n\t/** Max wait for the order to become valid. Default 5 min. */\n\torderTimeoutMs?: number;\n\t/** Status log lines. */\n\tonLog?: (line: string) => void;\n\t/** Override sleep (tests). */\n\tsleep?: (ms: number) => Promise<void>;\n\t/**\n\t * Optional pre-check: returns true when DNS has propagated globally.\n\t * Default: just wait `dnsPropagationDelayMs` then proceed. Override\n\t * for production to actually verify (e.g. resolve from multiple\n\t * public resolvers).\n\t */\n\tcheckDnsPropagated?: (\n\t\trecordName: string,\n\t\texpectedValue: string\n\t) => Promise<boolean>;\n};\n\nexport type IssuedCertificate = {\n\tcertificatePem: string;\n\tprivateKeyPem: string;\n\taccount: AcmeAccount;\n\tdomains: string[];\n};\n\nexport class AcmeError extends Error {\n\treadonly status: number;\n\treadonly body: unknown;\n\tconstructor(message: string, status: number, body: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'AcmeError';\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\nconst defaultSleep = (ms: number): Promise<void> =>\n\tnew Promise((resolve) => setTimeout(resolve, ms));\n\nexport const issueCertificate = async (\n\toptions: IssueCertificateOptions\n): Promise<IssuedCertificate> => {\n\tconst log = options.onLog ?? (() => {});\n\tconst sleep = options.sleep ?? defaultSleep;\n\tconst fetcher = options.fetch ?? fetch;\n\tconst directoryUrl = options.directoryUrl ?? LETSENCRYPT_PRODUCTION;\n\tconst pollMs = options.pollIntervalMs ?? 3_000;\n\tconst dnsDelay = options.dnsPropagationDelayMs ?? 30_000;\n\tconst orderTimeout = options.orderTimeoutMs ?? 5 * 60_000;\n\tconst account = options.account ?? (await generateAccountKey());\n\n\tif (options.domains.length === 0) {\n\t\tthrow new Error('[deploy/tls] at least one domain is required');\n\t}\n\n\t// 1. Fetch directory.\n\tlog(`[tls] fetching ACME directory ${directoryUrl}`);\n\tconst directoryResponse = await fetcher(directoryUrl);\n\tif (!directoryResponse.ok) {\n\t\tthrow new AcmeError(\n\t\t\t`failed to fetch ACME directory: ${directoryResponse.status}`,\n\t\t\tdirectoryResponse.status,\n\t\t\tawait directoryResponse.text()\n\t\t);\n\t}\n\tconst directory = (await directoryResponse.json()) as AcmeDirectory;\n\n\t// 2. Get initial nonce.\n\tlet nonce = await fetchNonce(fetcher, directory.newNonce);\n\n\t// Helper: signed POST. Tracks nonce; returns parsed JSON body + headers.\n\tconst post = async (\n\t\turl: string,\n\t\tpayload: object | string,\n\t\tidentification: { kid?: string; jwk?: JsonWebKey }\n\t): Promise<{ status: number; body: unknown; headers: Headers }> => {\n\t\tconst header: JwsHeader = {\n\t\t\talg: 'ES256',\n\t\t\tnonce,\n\t\t\turl,\n\t\t\t...(identification.kid !== undefined ? { kid: identification.kid } : {}),\n\t\t\t...(identification.jwk !== undefined ? { jwk: identification.jwk } : {})\n\t\t};\n\t\tconst jws = await signJws(account.key.privateKey, header, payload);\n\t\tconst response = await fetcher(url, {\n\t\t\tbody: JSON.stringify(jws),\n\t\t\theaders: { 'content-type': 'application/jose+json' },\n\t\t\tmethod: 'POST'\n\t\t});\n\t\tconst newNonce = response.headers.get('replay-nonce');\n\t\tif (newNonce !== null) nonce = newNonce;\n\t\tconst text = await response.text();\n\t\tconst body =\n\t\t\ttext.length > 0 && response.headers.get('content-type')?.includes('json')\n\t\t\t\t? JSON.parse(text)\n\t\t\t\t: text;\n\t\tif (!response.ok) {\n\t\t\tthrow new AcmeError(\n\t\t\t\t`ACME ${url} → ${response.status}`,\n\t\t\t\tresponse.status,\n\t\t\t\tbody\n\t\t\t);\n\t\t}\n\t\treturn { body, headers: response.headers, status: response.status };\n\t};\n\n\t// 3. Register the account (or reuse if kid is set).\n\tconst publicJwk = await exportPublicJwk(account.key.publicKey);\n\tif (account.kid === undefined) {\n\t\tlog(`[tls] registering ACME account for ${options.email}`);\n\t\tconst result = await post(\n\t\t\tdirectory.newAccount,\n\t\t\t{\n\t\t\t\tcontact: [`mailto:${options.email}`],\n\t\t\t\ttermsOfServiceAgreed: true\n\t\t\t},\n\t\t\t{ jwk: publicJwk }\n\t\t);\n\t\taccount.kid = result.headers.get('location') ?? undefined;\n\t\tif (account.kid === undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t'[deploy/tls] ACME newAccount response missing Location header'\n\t\t\t);\n\t\t}\n\t\tlog(`[tls] account registered: ${account.kid}`);\n\t} else {\n\t\tlog(`[tls] reusing account ${account.kid}`);\n\t}\n\n\tconst accountKid = account.kid;\n\n\t// 4. Submit the order.\n\tlog(`[tls] submitting order for ${options.domains.join(', ')}`);\n\tconst orderResult = await post(\n\t\tdirectory.newOrder,\n\t\t{\n\t\t\tidentifiers: options.domains.map((domain) => ({\n\t\t\t\ttype: 'dns',\n\t\t\t\tvalue: domain\n\t\t\t}))\n\t\t},\n\t\t{ kid: accountKid }\n\t);\n\tlet order = orderResult.body as AcmeOrder;\n\tconst orderUrl = orderResult.headers.get('location');\n\tif (orderUrl === null) {\n\t\tthrow new Error(\n\t\t\t'[deploy/tls] ACME newOrder response missing Location header'\n\t\t);\n\t}\n\n\t// 5. For each authorization, complete the DNS-01 challenge.\n\tconst cleanups: Array<() => Promise<void>> = [];\n\tconst dnsCreated: Array<{\n\t\trecordId: string;\n\t\trecordName: string;\n\t\trecordValue: string;\n\t}> = [];\n\n\ttry {\n\t\tconst thumbprint = await jwkThumbprint(publicJwk);\n\n\t\tfor (const authUrl of order.authorizations) {\n\t\t\tconst authResult = await post(authUrl, '', { kid: accountKid });\n\t\t\tconst auth = authResult.body as AcmeAuthorization;\n\t\t\tconst challenge = auth.challenges.find((c) => c.type === 'dns-01');\n\t\t\tif (challenge === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[deploy/tls] no dns-01 challenge for ${auth.identifier.value}`\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst keyAuthorization = `${challenge.token}.${thumbprint}`;\n\t\t\tconst txtBytes = new Uint8Array(\n\t\t\t\tawait crypto.subtle.digest(\n\t\t\t\t\t'SHA-256',\n\t\t\t\t\tnew TextEncoder().encode(keyAuthorization)\n\t\t\t\t)\n\t\t\t);\n\t\t\tconst txtValue = base64UrlEncode(txtBytes);\n\t\t\tconst recordName = `_acme-challenge.${auth.identifier.value}`;\n\t\t\tconst providerRecordName =\n\t\t\t\toptions.mapDnsChallengeRecord?.({\n\t\t\t\t\tdomain: auth.identifier.value,\n\t\t\t\t\trecordName\n\t\t\t\t}) ?? recordName;\n\t\t\tif (providerRecordName.trim().length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'[deploy/tls] mapped DNS-01 record name must not be empty'\n\t\t\t\t);\n\t\t\t}\n\t\t\tlog(\n\t\t\t\tproviderRecordName === recordName\n\t\t\t\t\t? `[tls] DNS-01: setting ${recordName} = \"${txtValue}\"`\n\t\t\t\t\t: `[tls] DNS-01: setting delegated ${providerRecordName} for ${recordName} = \"${txtValue}\"`\n\t\t\t);\n\t\t\tconst record = await options.dnsProvider.upsert({\n\t\t\t\tcontent: txtValue,\n\t\t\t\tname: providerRecordName,\n\t\t\t\tttl: 60,\n\t\t\t\ttype: 'TXT'\n\t\t\t});\n\t\t\tdnsCreated.push({\n\t\t\t\trecordId: record.id,\n\t\t\t\trecordName: providerRecordName,\n\t\t\t\trecordValue: txtValue\n\t\t\t});\n\t\t\tcleanups.push(() => options.dnsProvider.delete(record.id));\n\n\t\t\tif (options.checkDnsPropagated !== undefined) {\n\t\t\t\tlog('[tls] waiting for DNS propagation (custom checker)…');\n\t\t\t\tconst deadline = Date.now() + dnsDelay;\n\t\t\t\twhile (Date.now() < deadline) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tawait options.checkDnsPropagated(recordName, txtValue)\n\t\t\t\t\t) break;\n\t\t\t\t\tawait sleep(pollMs);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog(`[tls] sleeping ${dnsDelay}ms for DNS propagation`);\n\t\t\t\tawait sleep(dnsDelay);\n\t\t\t}\n\n\t\t\tlog(`[tls] notifying ACME of dns-01 readiness for ${auth.identifier.value}`);\n\t\t\tawait post(challenge.url, {}, { kid: accountKid });\n\n\t\t\t// Poll the authorization until valid/invalid.\n\t\t\tconst authDeadline = Date.now() + orderTimeout;\n\t\t\twhile (Date.now() < authDeadline) {\n\t\t\t\tconst polledResult = await post(authUrl, '', { kid: accountKid });\n\t\t\t\tconst polled = polledResult.body as AcmeAuthorization;\n\t\t\t\tif (polled.status === 'valid') break;\n\t\t\t\tif (polled.status === 'invalid') {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[deploy/tls] authorization failed for ${auth.identifier.value}: ${JSON.stringify(polled.challenges)}`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait sleep(pollMs);\n\t\t\t}\n\t\t}\n\n\t\t// 6. Finalize — generate cert keypair + CSR.\n\t\tlog('[tls] generating cert keypair + CSR');\n\t\tconst certKey = await generateCertKeyPair();\n\t\tconst csrDer = await buildCsr(options.domains, certKey);\n\t\tconst csr = base64UrlEncode(csrDer);\n\n\t\tawait post(order.finalize, { csr }, { kid: accountKid });\n\n\t\t// 7. Poll the order until valid + certificate URL appears.\n\t\tconst orderDeadline = Date.now() + orderTimeout;\n\t\twhile (Date.now() < orderDeadline) {\n\t\t\tconst refreshed = await post(orderUrl, '', { kid: accountKid });\n\t\t\torder = refreshed.body as AcmeOrder;\n\t\t\tif (order.status === 'valid' && order.certificate !== undefined) break;\n\t\t\tif (order.status === 'invalid') {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[deploy/tls] order failed: ${JSON.stringify(order)}`\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait sleep(pollMs);\n\t\t}\n\n\t\tif (order.certificate === undefined) {\n\t\t\tthrow new Error('[deploy/tls] order timed out before issuing certificate');\n\t\t}\n\n\t\t// 8. Download the cert (PEM bundle).\n\t\tlog(`[tls] downloading certificate from ${order.certificate}`);\n\t\tconst certResult = await post(order.certificate, '', { kid: accountKid });\n\t\tconst certificatePem =\n\t\t\ttypeof certResult.body === 'string'\n\t\t\t\t? certResult.body\n\t\t\t\t: String(certResult.body);\n\n\t\tconst privateKeyPem = await exportEcPrivateKeyPem(certKey);\n\n\t\treturn {\n\t\t\taccount,\n\t\t\tcertificatePem,\n\t\t\tdomains: options.domains,\n\t\t\tprivateKeyPem\n\t\t};\n\t} finally {\n\t\tfor (const cleanup of cleanups) {\n\t\t\ttry {\n\t\t\t\tawait cleanup();\n\t\t\t} catch (error) {\n\t\t\t\tlog(`[tls] cleanup failed (continuing): ${String(error)}`);\n\t\t\t}\n\t\t}\n\t\t// Suppress unused-variable lint without changing behavior.\n\t\tvoid dnsCreated;\n\t}\n};\n\nconst fetchNonce = async (\n\tfetcher: typeof fetch,\n\turl: string\n): Promise<string> => {\n\tconst response = await fetcher(url, { method: 'HEAD' });\n\tconst nonce = response.headers.get('replay-nonce');\n\tif (nonce === null) {\n\t\tthrow new Error('[deploy/tls] newNonce response missing Replay-Nonce header');\n\t}\n\treturn nonce;\n};\n\n// =============================================================================\n// installCertificateOnTarget\n// =============================================================================\n\nexport type InstallCertificateOptions = {\n\t/** Remote path for the cert chain. Default `/etc/ssl/<firstDomain>/fullchain.pem`. */\n\tcertPath?: string;\n\t/** Remote path for the private key. Default `/etc/ssl/<firstDomain>/privkey.pem`. */\n\tkeyPath?: string;\n\t/** Mode (chmod) for the cert + key. Default `600`. */\n\tmode?: string;\n\t/** Owner (chown) for the cert + key. Default unchanged. */\n\towner?: string;\n\t/** Optional reload command run after install (e.g. `'systemctl reload nginx'`). */\n\treload?: string;\n\t/** Override the writeTo helper (tests). */\n\twriteFile?: (target: Target, path: string, contents: string) => Promise<void>;\n};\n\nconst defaultWriteFile = async (\n\ttarget: Target,\n\tremotePath: string,\n\tcontents: string\n): Promise<void> => {\n\tconst escaped = contents.replaceAll(\"'\", \"'\\\\''\");\n\tawait target.exec(`mkdir -p \"$(dirname '${remotePath}')\"`);\n\tawait target.exec(`cat > '${remotePath}' <<'__ABS_TLS_EOF__'\\n${contents}__ABS_TLS_EOF__\\n`);\n\tvoid escaped;\n};\n\n/**\n * @internal — exposed for unit testing of cryptographic primitives.\n * Not part of the public API; consumers should NOT depend on this.\n */\nexport const __testing = {\n\tbase64UrlDecode,\n\tbase64UrlEncode,\n\tbase64UrlEncodeString,\n\tbuildCsr,\n\tderBitString,\n\tderInt,\n\tderOid,\n\tderSeq,\n\tderSet,\n\tecdsaRawToDer,\n\texportEcPrivateKeyPem,\n\texportPublicJwk,\n\tgenerateCertKeyPair,\n\tjwkThumbprint,\n\tsignJws\n};\n\n/**\n * Upload cert + private key to the target. Composable with the deploy\n * pipeline as a verify-step or post-deploy hook.\n */\nexport const installCertificateOnTarget = async (\n\ttarget: Target,\n\tcert: IssuedCertificate,\n\toptions: InstallCertificateOptions = {}\n): Promise<{ certPath: string; keyPath: string }> => {\n\tconst domain = cert.domains[0];\n\tif (domain === undefined) {\n\t\tthrow new Error('[deploy/tls] certificate has no domains');\n\t}\n\tconst certPath = options.certPath ?? `/etc/ssl/${domain}/fullchain.pem`;\n\tconst keyPath = options.keyPath ?? `/etc/ssl/${domain}/privkey.pem`;\n\tconst mode = options.mode ?? '600';\n\tconst writer = options.writeFile ?? defaultWriteFile;\n\n\tawait writer(target, certPath, cert.certificatePem);\n\tawait writer(target, keyPath, cert.privateKeyPem);\n\tawait target.exec(`chmod ${mode} '${certPath}' '${keyPath}'`);\n\tif (options.owner !== undefined) {\n\t\tawait target.exec(`chown ${options.owner} '${certPath}' '${keyPath}'`);\n\t}\n\tif (options.reload !== undefined) {\n\t\tawait target.exec(options.reload);\n\t}\n\n\treturn { certPath, keyPath };\n};\n\n// =============================================================================\n// Certificate inspection + renewal scheduling\n// =============================================================================\n\nexport type CertificateInspection = {\n\t/** CN + every SAN, deduplicated. */\n\tsubjects: string[];\n\t/** Issuance time, ms since epoch. */\n\tvalidFrom: number;\n\t/** Expiration time, ms since epoch. */\n\tvalidTo: number;\n\t/** Whole days remaining before `validTo`. Negative if expired. */\n\tdaysRemaining: number;\n\t/** True when the cert is past `validTo`. */\n\texpired: boolean;\n\t/** Issuer DN string (for \"Let's Encrypt vs staging vs other CA\" checks). */\n\tissuer: string;\n};\n\nconst parseSubjects = (x509: X509Certificate): string[] => {\n\tconst subjects = new Set<string>();\n\tconst cnMatch = x509.subject.match(/CN=([^,\\n]+)/);\n\tif (cnMatch !== null && cnMatch[1] !== undefined) {\n\t\tsubjects.add(cnMatch[1].trim());\n\t}\n\tconst san = x509.subjectAltName;\n\tif (san !== undefined && san !== null) {\n\t\tfor (const part of san.split(',')) {\n\t\t\tconst dnsMatch = part.trim().match(/^DNS:(.+)$/);\n\t\t\tif (dnsMatch !== null && dnsMatch[1] !== undefined) {\n\t\t\t\tsubjects.add(dnsMatch[1].trim());\n\t\t\t}\n\t\t}\n\t}\n\treturn [...subjects];\n};\n\n/**\n * Parse a PEM certificate into operator-shaped metadata. Used by\n * {@link renewCertificate} to decide whether to re-issue; also fine\n * for status pages and observability.\n */\nexport const inspectCertificate = (\n\tpem: string,\n\toptions: { now?: () => number } = {}\n): CertificateInspection => {\n\tconst x509 = new X509Certificate(pem);\n\tconst validFrom = new Date(x509.validFrom).getTime();\n\tconst validTo = new Date(x509.validTo).getTime();\n\tconst now = (options.now ?? Date.now)();\n\tconst daysRemaining = Math.floor(\n\t\t(validTo - now) / (24 * 60 * 60 * 1000)\n\t);\n\treturn {\n\t\tdaysRemaining,\n\t\texpired: validTo < now,\n\t\tissuer: x509.issuer,\n\t\tsubjects: parseSubjects(x509),\n\t\tvalidFrom,\n\t\tvalidTo\n\t};\n};\n\nexport type RenewCertificateOptions = IssueCertificateOptions & {\n\t/**\n\t * Current cert PEM. When provided, the cert's `validTo` decides\n\t * whether to re-issue; when absent, renewal always issues.\n\t */\n\tcurrentCertificatePem?: string;\n\t/**\n\t * Re-issue when fewer than this many days remain. Default 30,\n\t * matching certbot's standard schedule (Let's Encrypt issues 90-day\n\t * certs; renewing at 30 days leaves a 60-day error budget).\n\t */\n\trenewWhenDaysRemaining?: number;\n\t/** Force re-issue regardless of remaining days. Default false. */\n\tforce?: boolean;\n\t/** Override `Date.now()` for testing. */\n\tnow?: () => number;\n};\n\nexport type RenewalResult =\n\t| {\n\t\t\trenewed: true;\n\t\t\tcertificate: IssuedCertificate;\n\t\t\treason: 'forced' | 'no-current-cert' | 'expiring-soon';\n\t }\n\t| {\n\t\t\trenewed: false;\n\t\t\treason: 'still-fresh';\n\t\t\tinspection: CertificateInspection;\n\t };\n\n/**\n * Conditional cert renewal. Parses the supplied cert (if any),\n * decides whether to re-issue based on remaining days, and either\n * returns the existing cert info (\"still fresh\") or issues a new\n * one via {@link issueCertificate}.\n *\n * Wire to a scheduled function (cron / @absolutejs/sync schedule /\n * GitHub Action) running at least once a week. Idempotent: a fresh\n * cert returns `renewed: false` cheaply (one PEM parse, zero\n * network IO).\n *\n * @example\n * const result = await renewCertificate({\n * currentCertificatePem: existingPem, // omit to force first-issue\n * domains: ['api.example.com'],\n * dnsProvider: dns,\n * email: 'ops@example.com',\n * account: persistedAccount, // reuse to avoid CA rate limit\n * });\n * if (result.renewed) {\n * await installCertificateOnTarget(target, result.certificate, { reload });\n * }\n */\nexport const renewCertificate = async (\n\toptions: RenewCertificateOptions\n): Promise<RenewalResult> => {\n\tconst threshold = options.renewWhenDaysRemaining ?? 30;\n\tif (threshold < 0) {\n\t\tthrow new Error(\n\t\t\t'[deploy/tls] renewWhenDaysRemaining must be non-negative'\n\t\t);\n\t}\n\n\tif (options.force === true) {\n\t\tconst certificate = await issueCertificate(options);\n\t\treturn { certificate, reason: 'forced', renewed: true };\n\t}\n\n\tif (options.currentCertificatePem === undefined) {\n\t\tconst certificate = await issueCertificate(options);\n\t\treturn { certificate, reason: 'no-current-cert', renewed: true };\n\t}\n\n\tconst nowFn = options.now ?? Date.now;\n\tconst inspection = inspectCertificate(options.currentCertificatePem, {\n\t\tnow: nowFn\n\t});\n\n\tif (!inspection.expired && inspection.daysRemaining >= threshold) {\n\t\treturn { inspection, reason: 'still-fresh', renewed: false };\n\t}\n\n\tconst certificate = await issueCertificate(options);\n\treturn { certificate, reason: 'expiring-soon', renewed: true };\n};\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "769AB3C81A63D52E64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absolutejs/deploy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Generic Bun-project deploy pipeline. A Target (localTarget / sshTarget) is anywhere you can exec + upload — DigitalOcean droplets, Linode, Hetzner, Vultr, your own boxes. Bundled pipeline: prepare → upload → install → build → link → restart → verify. Atomic symlink swap, release history, prune, hooks. SSH shells out to system ssh/rsync — zero ssh2 deps.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|