feat: 支持ECC类型

pull/148/head
xiaojunnuo 2024-08-25 11:56:15 +08:00
parent d4092e4929
commit a7424e02f5
8 changed files with 124 additions and 16 deletions

View File

@ -14,7 +14,7 @@ export type CertInfo = {
csr: string; csr: string;
}; };
export type SSLProvider = "letsencrypt" | "google" | "zerossl"; export type SSLProvider = "letsencrypt" | "google" | "zerossl";
export type PrivateKeyType = "rsa" | "ec"; export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521";
type AcmeServiceOptions = { type AcmeServiceOptions = {
userContext: IContext; userContext: IContext;
logger: Logger; logger: Logger;
@ -226,12 +226,16 @@ export class AcmeService {
/* Create CSR */ /* Create CSR */
const { commonName, altNames } = this.buildCommonNameByDomains(domains); const { commonName, altNames } = this.buildCommonNameByDomains(domains);
let privateKey = null; let privateKey = null;
if (options.privateKeyType == "ec") { const privateKeyArr = options.privateKeyType.split("_");
privateKey = await acme.crypto.createPrivateEcdsaKey(); const type = privateKeyArr[0];
const size = parseInt(privateKeyArr[1]);
if (type == "ec") {
const name: any = "P-" + size;
privateKey = await acme.crypto.createPrivateEcdsaKey(name);
} else { } else {
privateKey = await acme.crypto.createPrivateRsaKey(); privateKey = await acme.crypto.createPrivateRsaKey(size);
} }
const [key, csr] = await acme.forge.createCsr( const [key, csr] = await acme.crypto.createCsr(
{ {
commonName, commonName,
...csrInfo, ...csrInfo,

View File

@ -133,10 +133,10 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
const cert: CertInfo = certReader.toCertInfo(); const cert: CertInfo = certReader.toCertInfo();
this.cert = cert; this.cert = cert;
this._result.pipelineVars.certExpiresTime = dayjs(certReader.detail.validity.notAfter).valueOf(); this._result.pipelineVars.certExpiresTime = dayjs(certReader.detail.notAfter).valueOf();
if (isNew) { if (isNew) {
const applyTime = dayjs(certReader.detail.validity.notBefore).format("YYYYMMDD_HHmmss"); const applyTime = dayjs(certReader.detail.notBefore).format("YYYYMMDD_HHmmss");
await this.zipCert(cert, applyTime); await this.zipCert(cert, applyTime);
} else { } else {
this.extendsFiles(); this.extendsFiles();

View File

@ -1,8 +1,8 @@
import { CertInfo } from "./acme.js"; import { CertInfo } from "./acme.js";
import fs from "fs"; import fs from "fs";
import os from "os"; import os from "os";
import forge from "node-forge";
import path from "path"; import path from "path";
import { crypto } from "@certd/acme-client";
export class CertReader implements CertInfo { export class CertReader implements CertInfo {
crt: string; crt: string;
key: string; key: string;
@ -29,9 +29,8 @@ export class CertReader implements CertInfo {
} }
getCrtDetail(crt: string) { getCrtDetail(crt: string) {
const pki = forge.pki; const detail = crypto.readCertificateInfo(crt.toString());
const detail = pki.certificateFromPem(crt.toString()); const expires = detail.notAfter;
const expires = detail.validity.notAfter;
return { detail, expires }; return { detail, expires };
} }

View File

@ -44,13 +44,18 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
@TaskInput({ @TaskInput({
title: "证书私钥类型", title: "证书私钥类型",
value: "rsa", value: "rsa_2048",
component: { component: {
name: "a-select", name: "a-select",
vModel: "value", vModel: "value",
options: [ options: [
{ value: "rsa", label: "RSA" }, { value: "rsa_1024", label: "RSA 1024" },
{ value: "ec", label: "EC" }, { value: "rsa_2048", label: "RSA 2048" },
{ value: "rsa_3072", label: "RSA 3072" },
{ value: "rsa_4096", label: "RSA 4096" },
{ value: "ec_256", label: "EC 256" },
{ value: "ec_384", label: "EC 384" },
{ value: "ec_521", label: "EC 521" },
], ],
}, },
required: true, required: true,

View File

@ -342,7 +342,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
title: "历史记录保持数", title: "历史记录保持数",
type: "number", type: "number",
form: { form: {
value: 10, value: 20,
helper: "历史记录保持条数,多余的会被删除" helper: "历史记录保持条数,多余的会被删除"
}, },
column: { column: {

View File

@ -1,4 +1,5 @@
export const Constants = { export const Constants = {
dataDir: './data',
role: { role: {
defaultUser: 3, defaultUser: 3,
}, },

View File

@ -63,7 +63,7 @@ export class HistoryService extends BaseService<HistoryEntity> {
return id; return id;
} }
private async clear(pipelineId: number, keepCount = 10) { private async clear(pipelineId: number, keepCount = 20) {
const count = await this.repository.count({ const count = await this.repository.count({
where: { where: {
pipelineId, pipelineId,

View File

@ -0,0 +1,99 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
import { CertInfo, CertReader } from '@certd/plugin-cert';
import * as fs from 'fs';
import { Constants } from '../../../../basic/constants.js';
import path from 'path';
@IsTaskPlugin({
name: 'CopyToLocal',
title: '复制到本机',
group: pluginGroups.host.key,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class CopyCertToLocalPlugin extends AbstractTaskPlugin {
@TaskInput({
title: '证书保存路径',
helper: '需要有写入权限,路径要包含证书文件名,文件名不能用*?!等特殊符号\n推荐使用相对路径将写入与数据库同级目录无需映射例如./tmp/cert.pem',
component: {
placeholder: './tmp/cert.pem',
},
})
crtPath!: string;
@TaskInput({
title: '私钥保存路径',
helper: '需要有写入权限,路径要包含私钥文件名,文件名不能用*?!等特殊符号\n推荐使用相对路径将写入与数据库同级目录无需映射例如./tmp/cert.key',
component: {
placeholder: './tmp/cert.key',
},
})
keyPath!: string;
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'pi-output-selector',
},
required: true,
})
cert!: CertInfo;
@TaskOutput({
title: '证书保存路径',
})
hostCrtPath!: string;
@TaskOutput({
title: '私钥保存路径',
})
hostKeyPath!: string;
async onInstance() {}
copyFile(srcFile: string, destFile: string) {
this.logger.info(`复制文件:${srcFile} => ${destFile}`);
const dir = path.dirname(destFile);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.copyFileSync(srcFile, destFile);
}
async execute(): Promise<void> {
let { crtPath, keyPath } = this;
const certReader = new CertReader(this.cert);
this.logger.info('将证书写入本地缓存文件');
const saveCrtPath = certReader.saveToFile('crt');
const saveKeyPath = certReader.saveToFile('key');
this.logger.info('本地文件写入成功');
try {
this.logger.info('复制到目标路径');
crtPath = crtPath.startsWith('/') ? crtPath : path.join(Constants.dataDir, crtPath);
keyPath = keyPath.startsWith('/') ? keyPath : path.join(Constants.dataDir, keyPath);
// crtPath = path.resolve(crtPath);
// keyPath = path.resolve(keyPath);
this.copyFile(saveCrtPath, crtPath);
this.copyFile(saveKeyPath, keyPath);
this.logger.info('证书复制成功crtPath=', crtPath, ',keyPath=', keyPath);
this.logger.info('请注意,如果使用的是相对路径,那么文件就在你的数据库同级目录下,默认是/data/certd/下面');
this.logger.info('请注意,如果使用的是绝对路径,文件在容器内的目录下,你需要给容器做目录映射才能复制到宿主机');
} catch (e) {
this.logger.error(`复制失败:${e.message}`);
throw e;
} finally {
//删除临时文件
this.logger.info('删除临时文件');
fs.unlinkSync(saveCrtPath);
fs.unlinkSync(saveKeyPath);
}
this.logger.info('执行完成');
//输出
this.hostCrtPath = crtPath;
this.hostKeyPath = keyPath;
}
}
new CopyCertToLocalPlugin();