import { ILogger, sp } from "@certd/basic"; import type { CertInfo } from "../cert-plugin/acme.js"; import { CertReader, CertReaderHandleContext } from "../cert-plugin/cert-reader.js"; import path from "path"; import os from "os"; import fs from "fs"; export { CertReader }; export type { CertInfo }; export class CertConverter { logger: ILogger; constructor(opts: { logger: ILogger }) { this.logger = opts.logger; } async convert(opts: { cert: CertInfo; pfxPassword: string; pfxArgs: string }): Promise<{ pfx: string; der: string; jks: string; }> { const certReader = new CertReader(opts.cert); let pfx: string; let der: string; let jks: string; const handle = async (ctx: CertReaderHandleContext) => { // 调用openssl 转pfx pfx = await this.convertPfx(ctx, opts.pfxPassword, opts.pfxArgs); // 转der der = await this.convertDer(ctx); jks = await this.convertJks(ctx, opts.pfxPassword); }; await certReader.readCertFile({ logger: this.logger, handle }); return { pfx, der, jks, }; } async exec(cmd: string) { process.env.LANG = "zh_CN.GBK"; await sp.spawn({ cmd: cmd, logger: this.logger, }); } private async convertPfx(opts: CertReaderHandleContext, pfxPassword: string, pfxArgs: string) { const { tmpCrtPath, tmpKeyPath } = opts; const pfxPath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + "_cert.pfx"); const dir = path.dirname(pfxPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } let passwordArg = "-passout pass:"; if (pfxPassword) { passwordArg = `-password pass:${pfxPassword}`; } // 兼容server 2016,旧版本不能用sha256 const oldPfxCmd = `openssl pkcs12 ${pfxArgs} -export -out ${pfxPath} -inkey ${tmpKeyPath} -in ${tmpCrtPath} ${passwordArg}`; // const newPfx = `openssl pkcs12 -export -out ${pfxPath} -inkey ${tmpKeyPath} -in ${tmpCrtPath} ${passwordArg}`; await this.exec(oldPfxCmd); const fileBuffer = fs.readFileSync(pfxPath); const pfxCert = fileBuffer.toString("base64"); fs.unlinkSync(pfxPath); return pfxCert; // // const applyTime = new Date().getTime(); // const filename = reader.buildCertFileName("pfx", applyTime); // this.saveFile(filename, fileBuffer); } private async convertDer(opts: CertReaderHandleContext) { const { tmpCrtPath } = opts; const derPath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + `_cert.der`); const dir = path.dirname(derPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } await this.exec(`openssl x509 -outform der -in ${tmpCrtPath} -out ${derPath}`); const fileBuffer = fs.readFileSync(derPath); const derCert = fileBuffer.toString("base64"); fs.unlinkSync(derPath); return derCert; } async convertJks(opts: CertReaderHandleContext, pfxPassword = "") { const jksPassword = pfxPassword || "123456"; try { const randomStr = Math.floor(Math.random() * 1000000) + ""; const p12Path = path.join(os.tmpdir(), "/certd/tmp/", randomStr + `_cert.p12`); const { tmpCrtPath, tmpKeyPath } = opts; let passwordArg = "-passout pass:"; if (jksPassword) { passwordArg = `-password pass:${jksPassword}`; } await this.exec(`openssl pkcs12 -export -in ${tmpCrtPath} -inkey ${tmpKeyPath} -out ${p12Path} -name certd ${passwordArg}`); const jksPath = path.join(os.tmpdir(), "/certd/tmp/", randomStr + `_cert.jks`); const dir = path.dirname(jksPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } await this.exec( `keytool -importkeystore -srckeystore ${p12Path} -srcstoretype PKCS12 -srcstorepass "${jksPassword}" -destkeystore ${jksPath} -deststoretype PKCS12 -deststorepass "${jksPassword}" ` ); fs.unlinkSync(p12Path); const fileBuffer = fs.readFileSync(jksPath); const certBase64 = fileBuffer.toString("base64"); fs.unlinkSync(jksPath); return certBase64; } catch (e) { this.logger.error("转换jks失败", e); return; } } }