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;
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";

View File

@ -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"));
}

View File

@ -17,6 +17,7 @@ export type CertReaderHandleContext = {
tmpIcPath?: string;
tmpJksPath?: string;
tmpOnePath?: string;
tmpP7bPath?: string;
};
export type CertReaderHandle = (ctx: CertReaderHandleContext) => Promise<void>;
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);
}
}

View File

@ -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);

View File

@ -8,7 +8,7 @@
<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" />
<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>
</a-form-item>
@ -17,7 +17,7 @@
</a-form-item>
<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" />
<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 label="微信支付" :name="['wxpay', 'enabled']" :required="true">
@ -25,7 +25,7 @@
</a-form-item>
<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" />
<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 label=" " :colon="false" :wrapper-col="{ span: 16 }">

View File

@ -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<void> {
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;
};
//执行前置命令