/** * SQLite-based Certificate Manager for SmartACME * * Implements ICertManager interface to store SSL certificates directly in SQLite database. * Certificate PEM content is stored in the database, not on the filesystem. */ import * as plugins from '../plugins.ts'; import { logger } from '../logging.ts'; import { getErrorMessage } from '../utils/error.ts'; import { OneboxDatabase } from './database.ts'; export class SqliteCertManager implements plugins.smartacme.ICertManager { private database: OneboxDatabase; constructor(database: OneboxDatabase) { this.database = database; } /** * Initialize the certificate manager */ async init(): Promise { logger.info('Certificate manager initialized (database storage)'); } /** * Retrieve a certificate by domain name */ async retrieveCertificate(domainName: string): Promise { try { const dbCert = this.database.getSSLCertificate(domainName); if (!dbCert || !dbCert.keyPem || !dbCert.fullchainPem) { return null; } // Convert database format to SmartacmeCert format const cert = new plugins.smartacme.Cert({ id: dbCert.id?.toString() || domainName, domainName: dbCert.domain, created: dbCert.createdAt, privateKey: dbCert.keyPem, publicKey: dbCert.fullchainPem, // Full chain as public key csr: '', // CSR not stored separately validUntil: dbCert.expiryDate, }); return cert; } catch (error) { logger.warn(`Failed to retrieve certificate for ${domainName}: ${getErrorMessage(error)}`); return null; } } /** * Store a certificate */ async storeCertificate(cert: plugins.smartacme.Cert): Promise { try { const domain = cert.domainName; // Extract certificate from full chain (first certificate in the chain) const certPem = this.extractCertFromChain(cert.publicKey); // Store/update in database with PEM content const existing = this.database.getSSLCertificate(domain); if (existing) { this.database.updateSSLCertificate(domain, { certPem: certPem, keyPem: cert.privateKey, fullchainPem: cert.publicKey, expiryDate: cert.validUntil, }); } else { await this.database.createSSLCertificate({ domain, certPem: certPem, keyPem: cert.privateKey, fullchainPem: cert.publicKey, expiryDate: cert.validUntil, issuer: "Let's Encrypt", createdAt: cert.created, updatedAt: Date.now(), }); } logger.success(`Certificate stored for ${domain}`); } catch (error) { logger.error(`Failed to store certificate for ${cert.domainName}: ${getErrorMessage(error)}`); throw error; } } /** * Delete a certificate */ async deleteCertificate(domainName: string): Promise { try { const dbCert = this.database.getSSLCertificate(domainName); if (dbCert) { // Delete from database this.database.deleteSSLCertificate(domainName); logger.info(`Certificate deleted for ${domainName}`); } } catch (error) { logger.error(`Failed to delete certificate for ${domainName}: ${getErrorMessage(error)}`); throw error; } } /** * Close the certificate manager */ async close(): Promise { // SQLite database is managed by OneboxDatabase, nothing to close here logger.info('Certificate manager closed'); } /** * Wipe all certificates (for testing) */ async wipe(): Promise { try { const certs = this.database.getAllSSLCertificates(); for (const cert of certs) { await this.deleteCertificate(cert.domain); } logger.warn('All certificates wiped'); } catch (error) { logger.error(`Failed to wipe certificates: ${getErrorMessage(error)}`); throw error; } } /** * Extract the first certificate from a PEM chain */ private extractCertFromChain(fullChain: string): string { const certMatch = fullChain.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/); return certMatch ? certMatch[0] : fullChain; } }