diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts index 2d1e4362..3f55a27b 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts @@ -48,6 +48,7 @@ export type CertInfo = { der?: string; jks?: string; one?: string; + p7b?: string; }; export type SSLProvider = "letsencrypt" | "google" | "zerossl"; export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521"; diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts index e25707cb..d2725b8d 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base-convert.ts @@ -125,6 +125,10 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin { cert.jks = res.jks; } + if (cert.p7b == null && res.p7b) { + cert.p7b = res.p7b; + } + this.logger.info("转换证书格式成功"); } catch (e) { this.logger.error("转换证书格式失败", e); @@ -150,6 +154,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin { zip.file("intermediate.crt", cert.ic); zip.file("origin.crt", cert.oc); zip.file("one.pem", cert.one); + zip.file("cert.p7b", cert.p7b); if (cert.pfx) { zip.file("cert.pfx", Buffer.from(cert.pfx, "base64")); } diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts index d1d4d7b2..318aa0c4 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts @@ -17,6 +17,7 @@ export type CertReaderHandleContext = { tmpIcPath?: string; tmpJksPath?: string; tmpOnePath?: string; + tmpP7bPath?: string; }; export type CertReaderHandle = (ctx: CertReaderHandleContext) => Promise; export type HandleOpts = { logger: ILogger; handle: CertReaderHandle }; @@ -124,7 +125,7 @@ export class CertReader { return domain; } - saveToFile(type: "crt" | "key" | "pfx" | "der" | "oc" | "one" | "ic" | "jks", filepath?: string) { + saveToFile(type: "crt" | "key" | "pfx" | "der" | "oc" | "one" | "ic" | "jks" | "p7b", filepath?: string) { if (!this.cert[type]) { return; } @@ -138,7 +139,7 @@ export class CertReader { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } - if (type === "crt" || type === "key" || type === "ic" || type === "oc" || type === "one") { + if (type === "crt" || type === "key" || type === "ic" || type === "oc" || type === "one" || type === "p7b") { fs.writeFileSync(filepath, this.cert[type]); } else { fs.writeFileSync(filepath, Buffer.from(this.cert[type], "base64")); @@ -157,17 +158,19 @@ export class CertReader { const tmpDerPath = this.saveToFile("der"); const tmpJksPath = this.saveToFile("jks"); const tmpOnePath = this.saveToFile("one"); + const tmpP7bPath = this.saveToFile("p7b"); logger.info("本地文件写入成功"); try { return await opts.handle({ reader: this, - tmpCrtPath: tmpCrtPath, - tmpKeyPath: tmpKeyPath, - tmpPfxPath: tmpPfxPath, - tmpDerPath: tmpDerPath, - tmpIcPath: tmpIcPath, - tmpJksPath: tmpJksPath, - tmpOcPath: tmpOcPath, + tmpCrtPath, + tmpKeyPath, + tmpPfxPath, + tmpDerPath, + tmpIcPath, + tmpJksPath, + tmpOcPath, + tmpP7bPath, tmpOnePath, }); } catch (err) { @@ -189,6 +192,7 @@ export class CertReader { removeFile(tmpIcPath); removeFile(tmpJksPath); removeFile(tmpOnePath); + removeFile(tmpP7bPath); } } diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts index 59fec30c..dfb4cafa 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts @@ -18,11 +18,13 @@ export class CertConverter { pfx: string; der: string; jks: string; + p7b: string; }> { const certReader = new CertReader(opts.cert); let pfx: string; let der: string; let jks: string; + let p7b: string; const handle = async (ctx: CertReaderHandleContext) => { // 调用openssl 转pfx pfx = await this.convertPfx(ctx, opts.pfxPassword, opts.pfxArgs); @@ -31,6 +33,8 @@ export class CertConverter { der = await this.convertDer(ctx); jks = await this.convertJks(ctx, opts.pfxPassword); + + p7b = await this.convertP7b(ctx); }; await certReader.readCertFile({ logger: this.logger, handle }); @@ -39,6 +43,7 @@ export class CertConverter { pfx, der, jks, + p7b, }; } @@ -95,6 +100,23 @@ export class CertConverter { return derCert; } + async convertP7b(opts: CertReaderHandleContext) { + const { tmpCrtPath } = opts; + const p7bPath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + `_cert.p7b`); + const dir = path.dirname(p7bPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + //openssl crl2pkcs7 -nocrl \ + // -certfile your_domain.crt \ + // -certfile intermediate.crt \ + // -out chain.p7b + await this.exec(`openssl crl2pkcs7 -nocrl -certfile ${tmpCrtPath} -out ${p7bPath}`); + const fileBuffer = fs.readFileSync(p7bPath); + const p7bCert = fileBuffer.toString(); + fs.unlinkSync(p7bPath); + return p7bCert; + } async convertJks(opts: CertReaderHandleContext, pfxPassword = "") { const jksPassword = pfxPassword || "123456"; try { @@ -113,9 +135,7 @@ export class CertConverter { 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}" ` - ); + 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); diff --git a/packages/ui/certd-client/src/views/sys/settings/tabs/payment.vue b/packages/ui/certd-client/src/views/sys/settings/tabs/payment.vue index 2f69007f..d50e53da 100644 --- a/packages/ui/certd-client/src/views/sys/settings/tabs/payment.vue +++ b/packages/ui/certd-client/src/views/sys/settings/tabs/payment.vue @@ -8,7 +8,7 @@ @@ -17,7 +17,7 @@ -
需要开通电脑网站支付, 支付宝配置帮助文档
+
需要开通电脑网站支付, 支付宝配置帮助文档
@@ -25,7 +25,7 @@ -
需要开通Native支付, 微信配置帮助文档
+
需要开通Native支付, 微信配置帮助文档
diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts index e35bf5dd..8aea00cb 100644 --- a/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts @@ -39,6 +39,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { { value: 'der', label: 'der,一般用于Apache' }, { value: 'jks', label: 'jks,一般用于JAVA应用' }, { value: 'one', label: '证书私钥一体,crt+key简单合并为一个pem文件' }, + { value: 'p7b', label: 'p7b格式' }, ], }, required: true, @@ -71,7 +72,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { mergeScript: ` return { show: ctx.compute(({form})=>{ - return form.certType === 'pem'; + return form.certType === 'pem' || form.certType === 'p7b' ; }) } `, @@ -169,6 +170,24 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { }) onePath!: string; + @TaskInput({ + title: 'p7b证书保存路径', + helper: '填写应用原本的证书保存路径,路径要包含证书文件名,例如:/tmp/domain_cert.p7b', + component: { + placeholder: '/root/deploy/app/domain_cert.p7b', + }, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.certType === 'p7b'; + }) + } + `, + required: true, + rules: [{ type: 'filepath' }], + }) + p7bPath!: string; + @TaskInput({ title: '主机登录配置', helper: 'access授权', @@ -277,12 +296,17 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { }) hostOnePath!: string; + @TaskOutput({ + title: 'p7b证书保存路径', + }) + hostP7bPath!: string; + async onInstance() {} async execute(): Promise { const { cert, accessId } = this; - let { crtPath, keyPath, icPath, pfxPath, derPath, jksPath, onePath } = this; + let { crtPath, keyPath, icPath, pfxPath, derPath, jksPath, onePath,p7bPath } = this; const certReader = new CertReader(cert); const executeCmd = async ( script:string)=> { @@ -308,6 +332,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { env['HOST_DER_PATH'] = this.hostDerPath || ''; env['HOST_JKS_PATH'] = this.hostJksPath || ''; env['HOST_ONE_PATH'] = this.hostOnePath || ''; + env['HOST_P7B_PATH'] = this.hostOnePath || ''; } const scripts = script.split('\n'); @@ -320,7 +345,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { } const handle = async (opts: CertReaderHandleContext) => { - const { tmpCrtPath, tmpKeyPath, tmpDerPath, tmpJksPath, tmpPfxPath, tmpIcPath, tmpOnePath } = opts; + const { tmpCrtPath, tmpKeyPath, tmpDerPath, tmpJksPath, tmpPfxPath, tmpIcPath, tmpOnePath ,tmpP7bPath} = opts; if (accessId == null) { this.logger.error('复制到当前主机功能已迁移到 “复制到本机”插件,请换成复制到本机插件'); @@ -392,6 +417,14 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { remotePath: this.onePath, }); } + if (this.p7bPath) { + this.logger.info(`上传p7b证书到主机:${this.p7bPath}`); + p7bPath = this.p7bPath.trim(); + transports.push({ + localPath: tmpP7bPath, + remotePath: this.p7bPath, + }); + } this.logger.info('开始上传文件到服务器'); await sshClient.uploadFiles({ @@ -410,6 +443,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { this.hostDerPath = derPath; this.hostJksPath = jksPath; this.hostOnePath = onePath; + this.hostP7bPath = p7bPath; }; //执行前置命令