perf: 支持p7b证书格式

v2-dev-plugin-config
xiaojunnuo 2025-08-25 18:21:38 +08:00
parent 70fcdc9ebb
commit d9f4a5793d
6 changed files with 82 additions and 18 deletions

View File

@ -48,6 +48,7 @@ export type CertInfo = {
der?: string; der?: string;
jks?: string; jks?: string;
one?: string; one?: string;
p7b?: string;
}; };
export type SSLProvider = "letsencrypt" | "google" | "zerossl"; export type SSLProvider = "letsencrypt" | "google" | "zerossl";
export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521"; export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521";

View File

@ -125,6 +125,10 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
cert.jks = res.jks; cert.jks = res.jks;
} }
if (cert.p7b == null && res.p7b) {
cert.p7b = res.p7b;
}
this.logger.info("转换证书格式成功"); this.logger.info("转换证书格式成功");
} catch (e) { } catch (e) {
this.logger.error("转换证书格式失败", e); this.logger.error("转换证书格式失败", e);
@ -150,6 +154,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
zip.file("intermediate.crt", cert.ic); zip.file("intermediate.crt", cert.ic);
zip.file("origin.crt", cert.oc); zip.file("origin.crt", cert.oc);
zip.file("one.pem", cert.one); zip.file("one.pem", cert.one);
zip.file("cert.p7b", cert.p7b);
if (cert.pfx) { if (cert.pfx) {
zip.file("cert.pfx", Buffer.from(cert.pfx, "base64")); zip.file("cert.pfx", Buffer.from(cert.pfx, "base64"));
} }

View File

@ -17,6 +17,7 @@ export type CertReaderHandleContext = {
tmpIcPath?: string; tmpIcPath?: string;
tmpJksPath?: string; tmpJksPath?: string;
tmpOnePath?: string; tmpOnePath?: string;
tmpP7bPath?: string;
}; };
export type CertReaderHandle = (ctx: CertReaderHandleContext) => Promise<void>; export type CertReaderHandle = (ctx: CertReaderHandleContext) => Promise<void>;
export type HandleOpts = { logger: ILogger; handle: CertReaderHandle }; export type HandleOpts = { logger: ILogger; handle: CertReaderHandle };
@ -124,7 +125,7 @@ export class CertReader {
return domain; 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]) { if (!this.cert[type]) {
return; return;
} }
@ -138,7 +139,7 @@ export class CertReader {
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true }); 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]); fs.writeFileSync(filepath, this.cert[type]);
} else { } else {
fs.writeFileSync(filepath, Buffer.from(this.cert[type], "base64")); fs.writeFileSync(filepath, Buffer.from(this.cert[type], "base64"));
@ -157,17 +158,19 @@ export class CertReader {
const tmpDerPath = this.saveToFile("der"); const tmpDerPath = this.saveToFile("der");
const tmpJksPath = this.saveToFile("jks"); const tmpJksPath = this.saveToFile("jks");
const tmpOnePath = this.saveToFile("one"); const tmpOnePath = this.saveToFile("one");
const tmpP7bPath = this.saveToFile("p7b");
logger.info("本地文件写入成功"); logger.info("本地文件写入成功");
try { try {
return await opts.handle({ return await opts.handle({
reader: this, reader: this,
tmpCrtPath: tmpCrtPath, tmpCrtPath,
tmpKeyPath: tmpKeyPath, tmpKeyPath,
tmpPfxPath: tmpPfxPath, tmpPfxPath,
tmpDerPath: tmpDerPath, tmpDerPath,
tmpIcPath: tmpIcPath, tmpIcPath,
tmpJksPath: tmpJksPath, tmpJksPath,
tmpOcPath: tmpOcPath, tmpOcPath,
tmpP7bPath,
tmpOnePath, tmpOnePath,
}); });
} catch (err) { } catch (err) {
@ -189,6 +192,7 @@ export class CertReader {
removeFile(tmpIcPath); removeFile(tmpIcPath);
removeFile(tmpJksPath); removeFile(tmpJksPath);
removeFile(tmpOnePath); removeFile(tmpOnePath);
removeFile(tmpP7bPath);
} }
} }

View File

@ -18,11 +18,13 @@ export class CertConverter {
pfx: string; pfx: string;
der: string; der: string;
jks: string; jks: string;
p7b: string;
}> { }> {
const certReader = new CertReader(opts.cert); const certReader = new CertReader(opts.cert);
let pfx: string; let pfx: string;
let der: string; let der: string;
let jks: string; let jks: string;
let p7b: string;
const handle = async (ctx: CertReaderHandleContext) => { const handle = async (ctx: CertReaderHandleContext) => {
// 调用openssl 转pfx // 调用openssl 转pfx
pfx = await this.convertPfx(ctx, opts.pfxPassword, opts.pfxArgs); pfx = await this.convertPfx(ctx, opts.pfxPassword, opts.pfxArgs);
@ -31,6 +33,8 @@ export class CertConverter {
der = await this.convertDer(ctx); der = await this.convertDer(ctx);
jks = await this.convertJks(ctx, opts.pfxPassword); jks = await this.convertJks(ctx, opts.pfxPassword);
p7b = await this.convertP7b(ctx);
}; };
await certReader.readCertFile({ logger: this.logger, handle }); await certReader.readCertFile({ logger: this.logger, handle });
@ -39,6 +43,7 @@ export class CertConverter {
pfx, pfx,
der, der,
jks, jks,
p7b,
}; };
} }
@ -95,6 +100,23 @@ export class CertConverter {
return derCert; 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 = "") { async convertJks(opts: CertReaderHandleContext, pfxPassword = "") {
const jksPassword = pfxPassword || "123456"; const jksPassword = pfxPassword || "123456";
try { try {
@ -113,9 +135,7 @@ export class CertConverter {
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
} }
await this.exec( await this.exec(`keytool -importkeystore -srckeystore ${p12Path} -srcstoretype PKCS12 -srcstorepass "${jksPassword}" -destkeystore ${jksPath} -deststoretype PKCS12 -deststorepass "${jksPassword}" `);
`keytool -importkeystore -srckeystore ${p12Path} -srcstoretype PKCS12 -srcstorepass "${jksPassword}" -destkeystore ${jksPath} -deststoretype PKCS12 -deststorepass "${jksPassword}" `
);
fs.unlinkSync(p12Path); fs.unlinkSync(p12Path);
const fileBuffer = fs.readFileSync(jksPath); const fileBuffer = fs.readFileSync(jksPath);

View File

@ -8,7 +8,7 @@
<a-form-item v-if="formState.yizhifu.enabled" label="易支付配置" :name="['yizhifu', 'accessId']" :required="true"> <a-form-item v-if="formState.yizhifu.enabled" label="易支付配置" :name="['yizhifu', 'accessId']" :required="true">
<access-selector v-model="formState.yizhifu.accessId" type="yizhifu" from="sys" /> <access-selector v-model="formState.yizhifu.accessId" type="yizhifu" from="sys" />
<div class="helper"> <div class="helper">
<a href="https://certd.docmirror.cn/comm/payments/yizhifu.html">彩虹易支付配置帮助文档</a> <a href="https://certd.docmirror.cn/guide/use/comm/payments/yizhifu.html">彩虹易支付配置帮助文档</a>
</div> </div>
</a-form-item> </a-form-item>
@ -17,7 +17,7 @@
</a-form-item> </a-form-item>
<a-form-item v-if="formState.alipay.enabled" label="支付宝配置" :name="['alipay', 'accessId']" :required="true"> <a-form-item v-if="formState.alipay.enabled" label="支付宝配置" :name="['alipay', 'accessId']" :required="true">
<access-selector v-model="formState.alipay.accessId" type="alipay" from="sys" /> <access-selector v-model="formState.alipay.accessId" type="alipay" from="sys" />
<div class="helper">需要开通电脑网站支付 <a href="https://certd.docmirror.cn/comm/payments/alipay.html">支付宝配置帮助文档</a></div> <div class="helper">需要开通电脑网站支付 <a href="https://certd.docmirror.cn/guide/use/comm/payments/alipay.html">支付宝配置帮助文档</a></div>
</a-form-item> </a-form-item>
<a-form-item label="微信支付" :name="['wxpay', 'enabled']" :required="true"> <a-form-item label="微信支付" :name="['wxpay', 'enabled']" :required="true">
@ -25,7 +25,7 @@
</a-form-item> </a-form-item>
<a-form-item v-if="formState.wxpay.enabled" label="微信支付配置" :name="['wxpay', 'accessId']" :required="true"> <a-form-item v-if="formState.wxpay.enabled" label="微信支付配置" :name="['wxpay', 'accessId']" :required="true">
<access-selector v-model="formState.wxpay.accessId" type="wxpay" from="sys" /> <access-selector v-model="formState.wxpay.accessId" type="wxpay" from="sys" />
<div class="helper">需要开通Native支付 <a href="https://certd.docmirror.cn/comm/payments/wxpay.html">微信配置帮助文档</a></div> <div class="helper">需要开通Native支付 <a href="https://certd.docmirror.cn/guide/use/comm/payments/wxpay.html">微信配置帮助文档</a></div>
</a-form-item> </a-form-item>
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }"> <a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">

View File

@ -39,6 +39,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
{ value: 'der', label: 'der一般用于Apache' }, { value: 'der', label: 'der一般用于Apache' },
{ value: 'jks', label: 'jks一般用于JAVA应用' }, { value: 'jks', label: 'jks一般用于JAVA应用' },
{ value: 'one', label: '证书私钥一体crt+key简单合并为一个pem文件' }, { value: 'one', label: '证书私钥一体crt+key简单合并为一个pem文件' },
{ value: 'p7b', label: 'p7b格式' },
], ],
}, },
required: true, required: true,
@ -71,7 +72,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
mergeScript: ` mergeScript: `
return { return {
show: ctx.compute(({form})=>{ 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; 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({ @TaskInput({
title: '主机登录配置', title: '主机登录配置',
helper: 'access授权', helper: 'access授权',
@ -277,12 +296,17 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
}) })
hostOnePath!: string; hostOnePath!: string;
@TaskOutput({
title: 'p7b证书保存路径',
})
hostP7bPath!: string;
async onInstance() {} async onInstance() {}
async execute(): Promise<void> { async execute(): Promise<void> {
const { cert, accessId } = this; 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 certReader = new CertReader(cert);
const executeCmd = async ( script:string)=> { const executeCmd = async ( script:string)=> {
@ -308,6 +332,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
env['HOST_DER_PATH'] = this.hostDerPath || ''; env['HOST_DER_PATH'] = this.hostDerPath || '';
env['HOST_JKS_PATH'] = this.hostJksPath || ''; env['HOST_JKS_PATH'] = this.hostJksPath || '';
env['HOST_ONE_PATH'] = this.hostOnePath || ''; env['HOST_ONE_PATH'] = this.hostOnePath || '';
env['HOST_P7B_PATH'] = this.hostOnePath || '';
} }
const scripts = script.split('\n'); const scripts = script.split('\n');
@ -320,7 +345,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
} }
const handle = async (opts: CertReaderHandleContext) => { 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) { if (accessId == null) {
this.logger.error('复制到当前主机功能已迁移到 “复制到本机”插件,请换成复制到本机插件'); this.logger.error('复制到当前主机功能已迁移到 “复制到本机”插件,请换成复制到本机插件');
@ -392,6 +417,14 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
remotePath: this.onePath, 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('开始上传文件到服务器'); this.logger.info('开始上传文件到服务器');
await sshClient.uploadFiles({ await sshClient.uploadFiles({
@ -410,6 +443,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
this.hostDerPath = derPath; this.hostDerPath = derPath;
this.hostJksPath = jksPath; this.hostJksPath = jksPath;
this.hostOnePath = onePath; this.hostOnePath = onePath;
this.hostP7bPath = p7bPath;
}; };
//执行前置命令 //执行前置命令