mirror of https://github.com/certd/certd
feat: 支持ECC类型
parent
d4092e4929
commit
a7424e02f5
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export const Constants = {
|
export const Constants = {
|
||||||
|
dataDir: './data',
|
||||||
role: {
|
role: {
|
||||||
defaultUser: 3,
|
defaultUser: 3,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
Loading…
Reference in New Issue