fix(acme): parse issued certificate expiry from X.509 metadata and update build compatibility for dependency upgrades

This commit is contained in:
2026-03-27 22:21:16 +00:00
parent ab0ca6ccc3
commit 75def30b0a
15 changed files with 831 additions and 794 deletions

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartacme',
version: '9.3.0',
version: '9.3.1',
description: 'A TypeScript-based ACME client and server for certificate management with built-in CA, supporting LetsEncrypt and custom ACME authorities.'
}

View File

@@ -1,3 +1,4 @@
import 'reflect-metadata';
import * as crypto from 'node:crypto';
import type { IAcmeCsrOptions } from './acme.interfaces.js';

View File

@@ -1,8 +1,12 @@
// reflect-metadata polyfill (required by @peculiar/x509 v2 via tsyringe)
import 'reflect-metadata';
// node native
import * as crypto from 'node:crypto';
import * as fs from 'fs';
import * as path from 'path';
export { fs, path };
export { crypto, fs, path };
// @apiclient.xyz scope
import * as cloudflare from '@apiclient.xyz/cloudflare';

View File

@@ -85,21 +85,21 @@ export class SmartAcme {
private options: ISmartAcmeOptions;
// the acme client
private client: plugins.acme.AcmeClient;
private client!: plugins.acme.AcmeClient;
private smartdns = new plugins.smartdnsClient.Smartdns({});
public logger: plugins.smartlog.Smartlog;
// the account private key
private privateKey: string;
private privateKey!: string;
// certificate manager for persistence (implements ICertManager)
public certmanager: ICertManager;
public certmanager!: ICertManager;
// configured pluggable ACME challenge handlers
public challengeHandlers: plugins.handlers.IChallengeHandler<any>[];
private certmatcher: SmartacmeCertMatcher;
private certmatcher!: SmartacmeCertMatcher;
// retry/backoff configuration (resolved with defaults)
private retryOptions: { retries: number; factor: number; minTimeoutMs: number; maxTimeoutMs: number };
// track pending DNS challenges for graceful shutdown
@@ -558,6 +558,16 @@ export class SmartAcme {
// ── Step: store ───────────────────────────────────────────────────────
this.certIssuanceTask.notifyStep('store');
// Parse real X509 expiry from the issued PEM certificate
let validUntil: number;
try {
const x509 = new plugins.crypto.X509Certificate(cert.toString());
validUntil = new Date(x509.validTo).getTime();
} catch {
// Fallback to 90-day estimate if PEM parsing fails
validUntil = Date.now() + plugins.smarttime.getMilliSecondsFromUnits({ days: 90 });
}
const certRecord = new SmartacmeCert({
id: plugins.smartunique.shortId(),
domainName: certDomainName,
@@ -565,7 +575,7 @@ export class SmartAcme {
publicKey: cert.toString(),
csr: csr.toString(),
created: Date.now(),
validUntil: Date.now() + plugins.smarttime.getMilliSecondsFromUnits({ days: 90 }),
validUntil,
});
await this.certmanager.storeCertificate(certRecord);