mirror of
https://github.com/certd/certd.git
synced 2026-07-02 09:37:32 +08:00
perf: 【破坏性更新】 证书压缩包不再生成文件存储,而是实时打包下载,证书申请插件不再输出certZip
自定义插件需要压缩包时可以调用new CertReader(certInfo).buildZip() 方式获取
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extension": ["ts"],
|
||||
"spec": "test/**/*.test.ts",
|
||||
"require": "ts-node/register"
|
||||
"node-option": ["loader=ts-node/esm", "no-warnings"]
|
||||
}
|
||||
@@ -10,10 +10,7 @@
|
||||
"before-build": "rimraf dist && rimraf tsconfig.tsbuildinfo && rimraf .rollup.cache",
|
||||
"build": "npm run before-build && tsc -p tsconfig.build.json --skipLibCheck",
|
||||
"dev-build": "npm run build",
|
||||
"build3": "rollup -c",
|
||||
"build2": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||
"test:unit": "cross-env NODE_ENV=unittest mocha",
|
||||
"pub": "npm publish",
|
||||
"compile": "tsc --skipLibCheck --watch",
|
||||
"format": "prettier --write src",
|
||||
@@ -24,12 +21,13 @@
|
||||
"@certd/basic": "^1.41.4",
|
||||
"@certd/pipeline": "^1.41.4",
|
||||
"dayjs": "^1.11.7",
|
||||
"jszip": "^3.10.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"psl": "^1.15.0",
|
||||
"punycode.js": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rimraf": "^5.0.5",
|
||||
"rimraf": "^5.0.5",
|
||||
"@types/chai": "^4.3.12",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
@@ -41,6 +39,7 @@
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"esmock": "^2.7.5",
|
||||
"mocha": "^10.6.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"prettier": "3.3.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.8.1",
|
||||
|
||||
@@ -6,6 +6,7 @@ import cryptoLib from "crypto";
|
||||
import { ILogger } from "@certd/basic";
|
||||
import dayjs from "dayjs";
|
||||
import { uniq } from "lodash-es";
|
||||
import JSZip from "jszip";
|
||||
|
||||
export interface ICertInfoGetter {
|
||||
getByPipelineId: (pipelineId: number) => Promise<CertInfo>;
|
||||
@@ -301,4 +302,70 @@ export class CertReader {
|
||||
static buildCertName(cert: CertInfo, useHash: boolean = false) {
|
||||
return new CertReader(cert).buildCertName("", useHash);
|
||||
}
|
||||
|
||||
async buildZip(): Promise<Buffer> {
|
||||
const cert = this.cert;
|
||||
|
||||
const zip = new JSZip();
|
||||
|
||||
if (cert.crt) {
|
||||
zip.file("证书.pem", cert.crt);
|
||||
}
|
||||
if (cert.key) {
|
||||
zip.file("私钥.pem", cert.key);
|
||||
}
|
||||
if (cert.ic) {
|
||||
zip.file("中间证书.pem", cert.ic);
|
||||
}
|
||||
if (cert.crt) {
|
||||
zip.file("cert.crt", cert.crt);
|
||||
}
|
||||
if (cert.key) {
|
||||
zip.file("cert.key", cert.key);
|
||||
}
|
||||
if (cert.ic) {
|
||||
zip.file("intermediate.crt", cert.ic);
|
||||
}
|
||||
if (cert.oc) {
|
||||
zip.file("origin.crt", cert.oc);
|
||||
}
|
||||
if (cert.one) {
|
||||
zip.file("one.pem", cert.one);
|
||||
}
|
||||
if (cert.p7b) {
|
||||
zip.file("cert.p7b", cert.p7b);
|
||||
}
|
||||
if (cert.pfx) {
|
||||
zip.file("cert.pfx", Buffer.from(cert.pfx, "base64"));
|
||||
}
|
||||
if (cert.der) {
|
||||
zip.file("cert.der", Buffer.from(cert.der, "base64"));
|
||||
}
|
||||
if (cert.jks) {
|
||||
zip.file("cert.jks", Buffer.from(cert.jks, "base64"));
|
||||
}
|
||||
|
||||
zip.file(
|
||||
"说明.txt",
|
||||
`证书文件说明
|
||||
cert.crt:证书文件,包含证书链,pem格式
|
||||
cert.key:私钥文件,pem格式
|
||||
intermediate.crt:中间证书文件,pem格式
|
||||
origin.crt:原始证书文件,不含证书链,pem格式
|
||||
one.pem: 证书和私钥简单合并成一个文件,pem格式,crt正文+key正文
|
||||
cert.pfx:pfx格式证书文件,iis服务器使用
|
||||
cert.der:der格式证书文件
|
||||
cert.jks:jks格式证书文件,java服务器使用
|
||||
`
|
||||
);
|
||||
|
||||
return zip.generateAsync({ type: "nodebuffer" });
|
||||
}
|
||||
|
||||
buildZipFilename(prefix = "cert"): string {
|
||||
let domain = this.getMainDomain();
|
||||
domain = domain.replaceAll(".", "_").replaceAll("*", "_");
|
||||
const timeStr = dayjs().format("YYYYMMDDHHmmss");
|
||||
return `${prefix}_${domain}_${timeStr}.zip`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/// <reference types="mocha" />
|
||||
/// <reference types="node" />
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { CertReader } from "../src/cert/cert-reader.js";
|
||||
import type { CertInfo } from "../src/cert/cert-reader.js";
|
||||
|
||||
// @ts-ignore
|
||||
import forge from "node-forge";
|
||||
|
||||
/**
|
||||
* Generate a minimal self-signed X.509 cert + key in PEM format for testing.
|
||||
*/
|
||||
function createSelfSignedCert(commonName: string): { crt: string; key: string } {
|
||||
const keypair = forge.pki.rsa.generateKeyPair(2048);
|
||||
|
||||
const cert = forge.pki.createCertificate();
|
||||
cert.publicKey = keypair.publicKey;
|
||||
cert.serialNumber = "01";
|
||||
cert.validFrom = new Date("2025-01-01").toISOString();
|
||||
cert.validTo = new Date("2026-01-01").toISOString();
|
||||
|
||||
const attrs = [{ name: "commonName", value: commonName }];
|
||||
cert.setSubject(attrs);
|
||||
cert.setIssuer(attrs);
|
||||
cert.sign(keypair.privateKey, forge.md.sha256.create());
|
||||
|
||||
return {
|
||||
crt: forge.pki.certificateToPem(cert),
|
||||
key: forge.pki.privateKeyToPem(keypair.privateKey),
|
||||
};
|
||||
}
|
||||
|
||||
const testCert = createSelfSignedCert("example.com");
|
||||
const testCertIc = createSelfSignedCert("intermediate.ca");
|
||||
const mockCertInfo: CertInfo = {
|
||||
crt: testCert.crt + "\n" + testCertIc.crt,
|
||||
key: testCert.key,
|
||||
oc: testCert.crt,
|
||||
ic: testCertIc.crt,
|
||||
one: testCert.crt + "\n" + testCert.key,
|
||||
p7b: "PKCS7 test content",
|
||||
pfx: Buffer.from("fake-pfx-data").toString("base64"),
|
||||
der: Buffer.from("fake-der-data").toString("base64"),
|
||||
jks: Buffer.from("fake-jks-data").toString("base64"),
|
||||
};
|
||||
|
||||
describe("CertReader.buildZip", () => {
|
||||
it("returns a non-empty Buffer", async () => {
|
||||
const reader = new CertReader(mockCertInfo);
|
||||
const buf = await reader.buildZip();
|
||||
assert.ok(Buffer.isBuffer(buf));
|
||||
assert.ok(buf.length > 0);
|
||||
});
|
||||
|
||||
it("produces a valid zip containing expected files", async () => {
|
||||
const reader = new CertReader(mockCertInfo);
|
||||
const buf = await reader.buildZip();
|
||||
const { default: JSZip } = await import("jszip");
|
||||
const zip = await JSZip.loadAsync(buf);
|
||||
|
||||
assert.ok(zip.file("证书.pem"), "should contain 证书.pem");
|
||||
assert.ok(zip.file("私钥.pem"), "should contain 私钥.pem");
|
||||
assert.ok(zip.file("中间证书.pem"), "should contain 中间证书.pem");
|
||||
assert.ok(zip.file("cert.crt"), "should contain cert.crt");
|
||||
assert.ok(zip.file("cert.key"), "should contain cert.key");
|
||||
assert.ok(zip.file("intermediate.crt"), "should contain intermediate.crt");
|
||||
assert.ok(zip.file("origin.crt"), "should contain origin.crt");
|
||||
assert.ok(zip.file("one.pem"), "should contain one.pem");
|
||||
assert.ok(zip.file("cert.p7b"), "should contain cert.p7b");
|
||||
assert.ok(zip.file("cert.pfx"), "should contain cert.pfx");
|
||||
assert.ok(zip.file("cert.der"), "should contain cert.der");
|
||||
assert.ok(zip.file("cert.jks"), "should contain cert.jks");
|
||||
assert.ok(zip.file("说明.txt"), "should contain 说明.txt");
|
||||
|
||||
const pemContent = await zip.file("证书.pem").async("string");
|
||||
assert.ok(pemContent.includes("-----BEGIN CERTIFICATE-----"));
|
||||
|
||||
const pfx = await zip.file("cert.pfx").async("nodebuffer");
|
||||
assert.equal(pfx.toString(), "fake-pfx-data");
|
||||
});
|
||||
});
|
||||
|
||||
describe("CertReader.buildZipFilename", () => {
|
||||
it("includes the main domain and timestamp", () => {
|
||||
const reader = new CertReader(mockCertInfo);
|
||||
const name = reader.buildZipFilename("cert");
|
||||
assert.ok(name.startsWith("cert_example_com_"));
|
||||
assert.ok(name.endsWith(".zip"));
|
||||
const tsPart = name.replace("cert_example_com_", "").replace(".zip", "");
|
||||
assert.match(tsPart, /^\d{14}$/);
|
||||
});
|
||||
|
||||
it("uses the default prefix when not provided", () => {
|
||||
const reader = new CertReader(mockCertInfo);
|
||||
const name = reader.buildZipFilename();
|
||||
assert.ok(name.startsWith("cert_example_com_"));
|
||||
});
|
||||
|
||||
it("wildcard domain replaces asterisk", () => {
|
||||
const wildcardCert = createSelfSignedCert("*.example.com");
|
||||
const wcInfo: CertInfo = { crt: wildcardCert.crt, key: wildcardCert.key };
|
||||
const reader = new CertReader(wcInfo);
|
||||
const name = reader.buildZipFilename("cert");
|
||||
assert.ok(name.startsWith("cert___example_com_"), "asterisk should be replaced, got: " + name);
|
||||
assert.ok(!name.includes("*"));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user