diff --git a/changelog.md b/changelog.md index 8461cb7..d269db0 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2025-02-24 - 3.11.0 - feat(Port80Handler) +Add automatic certificate issuance with ACME client + +- Implemented automatic certificate issuance using 'acme-client' for Port80Handler. +- Converts account key and CSR from Buffers to strings for processing. +- Implemented HTTP-01 challenge handling for certificate acquisition. +- New certificates are fetched and added dynamically. + ## 2025-02-24 - 3.10.5 - fix(portproxy) Fix incorrect import path in test file diff --git a/package.json b/package.json index fd69e0b..06f6a85 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@tsclass/tsclass": "^4.4.0", "@types/minimatch": "^5.1.2", "@types/ws": "^8.5.14", + "acme-client": "^5.4.0", "minimatch": "^9.0.3", "pretty-ms": "^9.2.0", "ws": "^8.18.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58a2b95..1958a21 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: '@types/ws': specifier: ^8.5.14 version: 8.5.14 + acme-client: + specifier: ^5.4.0 + version: 5.4.0 minimatch: specifier: ^9.0.3 version: 9.0.5 @@ -683,6 +686,39 @@ packages: '@pdf-lib/upng@1.0.1': resolution: {integrity: sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==} + '@peculiar/asn1-cms@2.3.15': + resolution: {integrity: sha512-B+DoudF+TCrxoJSTjjcY8Mmu+lbv8e7pXGWrhNp2/EGJp9EEcpzjBCar7puU57sGifyzaRVM03oD5L7t7PghQg==} + + '@peculiar/asn1-csr@2.3.15': + resolution: {integrity: sha512-caxAOrvw2hUZpxzhz8Kp8iBYKsHbGXZPl2KYRMIPvAfFateRebS3136+orUpcVwHRmpXWX2kzpb6COlIrqCumA==} + + '@peculiar/asn1-ecc@2.3.15': + resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==} + + '@peculiar/asn1-pfx@2.3.15': + resolution: {integrity: sha512-E3kzQe3J2xV9DP6SJS4X6/N1e4cYa2xOAK46VtvpaRk8jlheNri8v0rBezKFVPB1rz/jW8npO+u1xOvpATFMWg==} + + '@peculiar/asn1-pkcs8@2.3.15': + resolution: {integrity: sha512-/PuQj2BIAw1/v76DV1LUOA6YOqh/UvptKLJHtec/DQwruXOCFlUo7k6llegn8N5BTeZTWMwz5EXruBw0Q10TMg==} + + '@peculiar/asn1-pkcs9@2.3.15': + resolution: {integrity: sha512-yiZo/1EGvU1KiQUrbcnaPGWc0C7ElMMskWn7+kHsCFm+/9fU0+V1D/3a5oG0Jpy96iaXggQpA9tzdhnYDgjyFg==} + + '@peculiar/asn1-rsa@2.3.15': + resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==} + + '@peculiar/asn1-schema@2.3.15': + resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==} + + '@peculiar/asn1-x509-attr@2.3.15': + resolution: {integrity: sha512-TWJVJhqc+IS4MTEML3l6W1b0sMowVqdsnI4dnojg96LvTuP8dga9f76fjP07MUuss60uSyT2ckoti/2qHXA10A==} + + '@peculiar/asn1-x509@2.3.15': + resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==} + + '@peculiar/x509@1.12.3': + resolution: {integrity: sha512-+Mzq+W7cNEKfkNZzyLl6A6ffqc3r21HGZUezgfKxpZrkORfOqgRXnS80Zu0IV6a9Ue9QBJeKD7kN0iWfc3bhRQ==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1563,6 +1599,10 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acme-client@5.4.0: + resolution: {integrity: sha512-mORqg60S8iML6XSmVjqjGHJkINrCGLMj2QvDmFzI9vIlv1RGlyjmw3nrzaINJjkNsYXC41XhhD5pfy7CtuGcbA==} + engines: {node: '>= 16'} + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -1626,6 +1666,10 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + asn1js@3.0.5: + resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==} + engines: {node: '>=12.0.0'} + astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -1643,6 +1687,9 @@ packages: resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} engines: {node: '>=4'} + axios@1.7.9: + resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + b4a@1.6.7: resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} @@ -3463,6 +3510,13 @@ packages: resolution: {integrity: sha512-+vZPU8iBSdCx1Kn5hHas80fyo0TiVyMeqLGv/1dygX2HKhAZjO9YThadbRTCoTYq0yWw+w/CysldPsEekDtjDQ==} engines: {node: '>=14.1.0'} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.1.3: + resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} + engines: {node: '>=6.0.0'} + qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} @@ -3508,6 +3562,9 @@ packages: resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==} engines: {node: '>= 14.18.0'} + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -3897,6 +3954,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tsyringe@4.8.0: + resolution: {integrity: sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==} + engines: {node: '>= 6.0.0'} + turndown-plugin-gfm@1.0.2: resolution: {integrity: sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg==} @@ -5212,6 +5273,96 @@ snapshots: dependencies: pako: 1.0.11 + '@peculiar/asn1-cms@2.3.15': + dependencies: + '@peculiar/asn1-schema': 2.3.15 + '@peculiar/asn1-x509': 2.3.15 + '@peculiar/asn1-x509-attr': 2.3.15 + asn1js: 3.0.5 + tslib: 2.8.1 + + '@peculiar/asn1-csr@2.3.15': + dependencies: + '@peculiar/asn1-schema': 2.3.15 + '@peculiar/asn1-x509': 2.3.15 + asn1js: 3.0.5 + tslib: 2.8.1 + + '@peculiar/asn1-ecc@2.3.15': + dependencies: + '@peculiar/asn1-schema': 2.3.15 + '@peculiar/asn1-x509': 2.3.15 + asn1js: 3.0.5 + tslib: 2.8.1 + + '@peculiar/asn1-pfx@2.3.15': + dependencies: + '@peculiar/asn1-cms': 2.3.15 + '@peculiar/asn1-pkcs8': 2.3.15 + '@peculiar/asn1-rsa': 2.3.15 + '@peculiar/asn1-schema': 2.3.15 + asn1js: 3.0.5 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs8@2.3.15': + dependencies: + '@peculiar/asn1-schema': 2.3.15 + '@peculiar/asn1-x509': 2.3.15 + asn1js: 3.0.5 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs9@2.3.15': + dependencies: + '@peculiar/asn1-cms': 2.3.15 + '@peculiar/asn1-pfx': 2.3.15 + '@peculiar/asn1-pkcs8': 2.3.15 + '@peculiar/asn1-schema': 2.3.15 + '@peculiar/asn1-x509': 2.3.15 + '@peculiar/asn1-x509-attr': 2.3.15 + asn1js: 3.0.5 + tslib: 2.8.1 + + '@peculiar/asn1-rsa@2.3.15': + dependencies: + '@peculiar/asn1-schema': 2.3.15 + '@peculiar/asn1-x509': 2.3.15 + asn1js: 3.0.5 + tslib: 2.8.1 + + '@peculiar/asn1-schema@2.3.15': + dependencies: + asn1js: 3.0.5 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/asn1-x509-attr@2.3.15': + dependencies: + '@peculiar/asn1-schema': 2.3.15 + '@peculiar/asn1-x509': 2.3.15 + asn1js: 3.0.5 + tslib: 2.8.1 + + '@peculiar/asn1-x509@2.3.15': + dependencies: + '@peculiar/asn1-schema': 2.3.15 + asn1js: 3.0.5 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/x509@1.12.3': + dependencies: + '@peculiar/asn1-cms': 2.3.15 + '@peculiar/asn1-csr': 2.3.15 + '@peculiar/asn1-ecc': 2.3.15 + '@peculiar/asn1-pkcs9': 2.3.15 + '@peculiar/asn1-rsa': 2.3.15 + '@peculiar/asn1-schema': 2.3.15 + '@peculiar/asn1-x509': 2.3.15 + pvtsutils: 1.3.6 + reflect-metadata: 0.2.2 + tslib: 2.8.1 + tsyringe: 4.8.0 + '@pkgjs/parseargs@0.11.0': optional: true @@ -6783,6 +6934,16 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + acme-client@5.4.0: + dependencies: + '@peculiar/x509': 1.12.3 + asn1js: 3.0.5 + axios: 1.7.9(debug@4.4.0) + debug: 4.4.0 + node-forge: 1.3.1 + transitivePeerDependencies: + - supports-color + agent-base@6.0.2: dependencies: debug: 4.3.4 @@ -6834,6 +6995,12 @@ snapshots: array-union@2.1.0: {} + asn1js@3.0.5: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.1.3 + tslib: 2.8.1 + astral-regex@2.0.0: {} async-mutex@0.3.2: @@ -6846,6 +7013,14 @@ snapshots: axe-core@4.10.2: {} + axios@1.7.9(debug@4.4.0): + dependencies: + follow-redirects: 1.15.9(debug@4.4.0) + form-data: 4.0.1 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + b4a@1.6.7: {} bail@2.0.2: {} @@ -8954,6 +9129,12 @@ snapshots: - supports-color - utf-8-validate + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.1.3: {} + qs@6.13.0: dependencies: side-channel: 1.1.0 @@ -9008,6 +9189,8 @@ snapshots: readdirp@4.1.1: {} + reflect-metadata@0.2.2: {} + regenerator-runtime@0.14.1: {} registry-auth-token@5.0.3: @@ -9488,6 +9671,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tsyringe@4.8.0: + dependencies: + tslib: 1.14.1 + turndown-plugin-gfm@1.0.2: {} turndown@7.2.0: diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 37ebe44..1ce0c99 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartproxy', - version: '3.10.5', + version: '3.11.0', description: 'a proxy for handling high workloads of proxying' } diff --git a/ts/classes.port80handler.ts b/ts/classes.port80handler.ts index 92be9f4..413ea1a 100644 --- a/ts/classes.port80handler.ts +++ b/ts/classes.port80handler.ts @@ -55,8 +55,8 @@ export class Port80Handler { if (this.acmeClient) { return this.acmeClient; } - // Generate a new account key. - this.accountKey = await acme.forge.createPrivateKey(); + // Generate a new account key and convert Buffer to string. + this.accountKey = (await acme.forge.createPrivateKey()).toString(); this.acmeClient = new acme.Client({ directoryUrl: acme.directory.letsencrypt.production, // Use production for a real certificate // For testing, you could use: @@ -170,7 +170,11 @@ export class Port80Handler { domainInfo.challengeKeyAuthorization = keyAuthorization; // Notify the ACME server that the challenge is ready. - await client.verifyChallenge(authz, challenge, keyAuthorization); + // The acme-client examples show that verifyChallenge takes three arguments: + // (authorization, challenge, keyAuthorization). However, the official TypeScript + // types appear to be out-of-sync. As a workaround, we cast client to 'any'. + await (client as any).verifyChallenge(authz, challenge, keyAuthorization); + await client.completeChallenge(challenge); // Wait until the challenge is validated. await client.waitForValidStatus(challenge); @@ -178,9 +182,12 @@ export class Port80Handler { } // Generate a CSR and a new private key for the domain. - const [csr, privateKey] = await acme.forge.createCsr({ + // Convert the resulting Buffers to strings. + const [csrBuffer, privateKeyBuffer] = await acme.forge.createCsr({ commonName: domain, }); + const csr = csrBuffer.toString(); + const privateKey = privateKeyBuffer.toString(); // Finalize the order and obtain the certificate. await client.finalizeOrder(order, csr); @@ -195,8 +202,7 @@ export class Port80Handler { delete domainInfo.challengeKeyAuthorization; console.log(`Certificate obtained for ${domain}`); - // In a real application, you would persist the certificate and key, - // then reload your TLS server with the new credentials. + // In a production system, persist the certificate and key and reload your TLS server. } catch (error) { console.error(`Error during certificate issuance for ${domain}:`, error); const domainInfo = this.domainCertificates.get(domain); @@ -206,7 +212,3 @@ export class Port80Handler { } } } - -// Example usage: -// const handler = new Port80Handler(); -// handler.addDomain('example.com'); \ No newline at end of file diff --git a/ts/index.ts b/ts/index.ts index 79e143e..d8259f9 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -1,3 +1,4 @@ export * from './classes.networkproxy.js'; export * from './classes.portproxy.js'; +export * from './classes.port80handler.js'; export * from './classes.sslredirect.js';