2022-11-08 22:10:42 +08:00
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
import * as acme from "@certd/acme-client";
|
2024-10-07 03:21:16 +08:00
|
|
|
|
import { ClientExternalAccountBindingOptions, UrlMapping } from "@certd/acme-client";
|
2024-07-15 00:30:33 +08:00
|
|
|
|
import _ from "lodash-es";
|
2022-11-08 22:10:42 +08:00
|
|
|
|
import { Challenge } from "@certd/acme-client/types/rfc8555";
|
|
|
|
|
|
import { Logger } from "log4js";
|
2024-10-07 03:21:16 +08:00
|
|
|
|
import { IContext, utils } from "@certd/pipeline";
|
|
|
|
|
|
import { IDnsProvider, parseDomain } from "../../dns-provider/index.js";
|
|
|
|
|
|
|
|
|
|
|
|
export type CnameVerifyPlan = {
|
|
|
|
|
|
domain: string;
|
|
|
|
|
|
fullRecord: string;
|
|
|
|
|
|
dnsProvider: IDnsProvider;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type DomainVerifyPlan = {
|
|
|
|
|
|
domain: string;
|
|
|
|
|
|
type: "cname" | "dns";
|
|
|
|
|
|
dnsProvider?: IDnsProvider;
|
|
|
|
|
|
cnameVerifyPlan?: Record<string, CnameVerifyPlan>;
|
|
|
|
|
|
};
|
|
|
|
|
|
export type DomainsVerifyPlan = {
|
|
|
|
|
|
[key: string]: DomainVerifyPlan;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2023-05-23 18:01:20 +08:00
|
|
|
|
export type CertInfo = {
|
|
|
|
|
|
crt: string;
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
csr: string;
|
2024-09-22 23:19:10 +08:00
|
|
|
|
ic?: string;
|
2024-09-06 00:13:21 +08:00
|
|
|
|
pfx?: string;
|
|
|
|
|
|
der?: string;
|
2023-05-23 18:01:20 +08:00
|
|
|
|
};
|
2024-08-23 13:15:06 +08:00
|
|
|
|
export type SSLProvider = "letsencrypt" | "google" | "zerossl";
|
2024-08-25 11:56:15 +08:00
|
|
|
|
export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521";
|
2024-07-25 10:38:45 +08:00
|
|
|
|
type AcmeServiceOptions = {
|
|
|
|
|
|
userContext: IContext;
|
|
|
|
|
|
logger: Logger;
|
|
|
|
|
|
sslProvider: SSLProvider;
|
|
|
|
|
|
eab?: ClientExternalAccountBindingOptions;
|
|
|
|
|
|
skipLocalVerify?: boolean;
|
|
|
|
|
|
useMappingProxy?: boolean;
|
2024-10-10 15:32:25 +08:00
|
|
|
|
reverseProxy?: string;
|
2024-08-23 17:41:02 +08:00
|
|
|
|
privateKeyType?: PrivateKeyType;
|
2024-08-23 23:26:31 +08:00
|
|
|
|
signal?: AbortSignal;
|
2024-07-25 10:38:45 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2022-11-08 22:10:42 +08:00
|
|
|
|
export class AcmeService {
|
2024-07-25 10:38:45 +08:00
|
|
|
|
options: AcmeServiceOptions;
|
2022-11-08 22:10:42 +08:00
|
|
|
|
userContext: IContext;
|
|
|
|
|
|
logger: Logger;
|
2024-07-04 01:14:09 +08:00
|
|
|
|
sslProvider: SSLProvider;
|
2024-07-08 15:35:58 +08:00
|
|
|
|
skipLocalVerify = true;
|
2024-07-04 01:14:09 +08:00
|
|
|
|
eab?: ClientExternalAccountBindingOptions;
|
2024-07-25 10:38:45 +08:00
|
|
|
|
constructor(options: AcmeServiceOptions) {
|
|
|
|
|
|
this.options = options;
|
2022-11-08 22:10:42 +08:00
|
|
|
|
this.userContext = options.userContext;
|
|
|
|
|
|
this.logger = options.logger;
|
2024-07-04 01:14:09 +08:00
|
|
|
|
this.sslProvider = options.sslProvider || "letsencrypt";
|
|
|
|
|
|
this.eab = options.eab;
|
2024-07-08 15:35:58 +08:00
|
|
|
|
this.skipLocalVerify = options.skipLocalVerify ?? false;
|
2022-11-08 22:10:42 +08:00
|
|
|
|
acme.setLogger((text: string) => {
|
|
|
|
|
|
this.logger.info(text);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-23 13:15:06 +08:00
|
|
|
|
async getAccountConfig(email: string, urlMapping: UrlMapping): Promise<any> {
|
2024-08-21 10:34:50 +08:00
|
|
|
|
const conf = (await this.userContext.getObj(this.buildAccountKey(email))) || {};
|
2024-08-23 13:15:06 +08:00
|
|
|
|
if (urlMapping && urlMapping.mappings) {
|
|
|
|
|
|
for (const key in urlMapping.mappings) {
|
|
|
|
|
|
if (Object.prototype.hasOwnProperty.call(urlMapping.mappings, key)) {
|
|
|
|
|
|
const element = urlMapping.mappings[key];
|
|
|
|
|
|
if (conf.accountUrl?.indexOf(element) > -1) {
|
|
|
|
|
|
//如果用了代理url,要替换回去
|
|
|
|
|
|
conf.accountUrl = conf.accountUrl.replace(element, key);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-08-21 10:34:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
return conf;
|
2022-11-08 22:10:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
buildAccountKey(email: string) {
|
2024-07-04 01:14:09 +08:00
|
|
|
|
return `acme.config.${this.sslProvider}.${email}`;
|
2022-11-08 22:10:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async saveAccountConfig(email: string, conf: any) {
|
2023-05-09 14:11:01 +08:00
|
|
|
|
await this.userContext.setObj(this.buildAccountKey(email), conf);
|
2022-11-08 22:10:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getAcmeClient(email: string, isTest = false): Promise<acme.Client> {
|
2024-10-22 16:21:35 +08:00
|
|
|
|
const mappings = {};
|
2024-10-23 10:34:55 +08:00
|
|
|
|
if (this.sslProvider === "letsencrypt") {
|
2024-10-22 16:21:35 +08:00
|
|
|
|
mappings["acme-v02.api.letsencrypt.org"] = this.options.reverseProxy || "le.px.certd.handfree.work";
|
2024-10-23 10:34:55 +08:00
|
|
|
|
} else if (this.sslProvider === "google") {
|
2024-10-22 16:21:35 +08:00
|
|
|
|
mappings["dv.acme-v02.api.pki.goog"] = this.options.reverseProxy || "gg.px.certd.handfree.work";
|
|
|
|
|
|
}
|
2024-08-23 13:15:06 +08:00
|
|
|
|
const urlMapping: UrlMapping = {
|
|
|
|
|
|
enabled: false,
|
2024-10-22 16:21:35 +08:00
|
|
|
|
mappings,
|
2024-08-23 13:15:06 +08:00
|
|
|
|
};
|
|
|
|
|
|
const conf = await this.getAccountConfig(email, urlMapping);
|
2022-11-08 22:10:42 +08:00
|
|
|
|
if (conf.key == null) {
|
|
|
|
|
|
conf.key = await this.createNewKey();
|
|
|
|
|
|
await this.saveAccountConfig(email, conf);
|
2024-09-04 18:29:39 +08:00
|
|
|
|
this.logger.info(`创建新的Accountkey:${email}`);
|
2022-11-08 22:10:42 +08:00
|
|
|
|
}
|
2024-07-04 01:14:09 +08:00
|
|
|
|
let directoryUrl = "";
|
|
|
|
|
|
if (isTest) {
|
|
|
|
|
|
directoryUrl = acme.directory[this.sslProvider].staging;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
directoryUrl = acme.directory[this.sslProvider].production;
|
|
|
|
|
|
}
|
2024-07-25 10:38:45 +08:00
|
|
|
|
if (this.options.useMappingProxy) {
|
|
|
|
|
|
urlMapping.enabled = true;
|
2024-08-27 13:46:19 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
//测试directory是否可以访问
|
|
|
|
|
|
const isOk = await this.testDirectory(directoryUrl);
|
|
|
|
|
|
if (!isOk) {
|
|
|
|
|
|
this.logger.info("测试访问失败,自动使用代理");
|
|
|
|
|
|
urlMapping.enabled = true;
|
|
|
|
|
|
}
|
2024-07-25 10:38:45 +08:00
|
|
|
|
}
|
2022-11-08 22:10:42 +08:00
|
|
|
|
const client = new acme.Client({
|
2024-10-22 16:21:35 +08:00
|
|
|
|
sslProvider: this.sslProvider,
|
2024-07-04 01:14:09 +08:00
|
|
|
|
directoryUrl: directoryUrl,
|
2022-11-08 22:10:42 +08:00
|
|
|
|
accountKey: conf.key,
|
|
|
|
|
|
accountUrl: conf.accountUrl,
|
2024-07-04 01:14:09 +08:00
|
|
|
|
externalAccountBinding: this.eab,
|
2024-08-23 13:15:06 +08:00
|
|
|
|
backoffAttempts: 15,
|
2022-11-08 22:10:42 +08:00
|
|
|
|
backoffMin: 5000,
|
|
|
|
|
|
backoffMax: 10000,
|
2024-07-25 10:38:45 +08:00
|
|
|
|
urlMapping,
|
2024-08-23 23:26:31 +08:00
|
|
|
|
signal: this.options.signal,
|
2022-11-08 22:10:42 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (conf.accountUrl == null) {
|
|
|
|
|
|
const accountPayload = {
|
|
|
|
|
|
termsOfServiceAgreed: true,
|
|
|
|
|
|
contact: [`mailto:${email}`],
|
2024-07-04 01:14:09 +08:00
|
|
|
|
externalAccountBinding: this.eab,
|
2022-11-08 22:10:42 +08:00
|
|
|
|
};
|
|
|
|
|
|
await client.createAccount(accountPayload);
|
|
|
|
|
|
conf.accountUrl = client.getAccountUrl();
|
|
|
|
|
|
await this.saveAccountConfig(email, conf);
|
|
|
|
|
|
}
|
|
|
|
|
|
return client;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async createNewKey() {
|
2024-09-04 18:29:39 +08:00
|
|
|
|
const key = await acme.crypto.createPrivateKey(2048);
|
2022-11-08 22:10:42 +08:00
|
|
|
|
return key.toString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-07 03:21:16 +08:00
|
|
|
|
async challengeCreateFn(authz: any, challenge: any, keyAuthorization: string, dnsProvider: IDnsProvider, domainsVerifyPlan: DomainsVerifyPlan) {
|
2022-11-08 22:10:42 +08:00
|
|
|
|
this.logger.info("Triggered challengeCreateFn()");
|
|
|
|
|
|
|
|
|
|
|
|
/* http-01 */
|
2024-06-14 01:22:07 +08:00
|
|
|
|
const fullDomain = authz.identifier.value;
|
2022-11-08 22:10:42 +08:00
|
|
|
|
if (challenge.type === "http-01") {
|
|
|
|
|
|
const filePath = `/var/www/html/.well-known/acme-challenge/${challenge.token}`;
|
|
|
|
|
|
const fileContents = keyAuthorization;
|
|
|
|
|
|
|
2024-06-14 01:22:07 +08:00
|
|
|
|
this.logger.info(`Creating challenge response for ${fullDomain} at path: ${filePath}`);
|
2022-11-08 22:10:42 +08:00
|
|
|
|
|
|
|
|
|
|
/* Replace this */
|
|
|
|
|
|
this.logger.info(`Would write "${fileContents}" to path "${filePath}"`);
|
|
|
|
|
|
// await fs.writeFileAsync(filePath, fileContents);
|
|
|
|
|
|
} else if (challenge.type === "dns-01") {
|
|
|
|
|
|
/* dns-01 */
|
2024-10-07 03:21:16 +08:00
|
|
|
|
let fullRecord = `_acme-challenge.${fullDomain}`;
|
2022-11-08 22:10:42 +08:00
|
|
|
|
const recordValue = keyAuthorization;
|
|
|
|
|
|
|
2024-10-07 03:21:16 +08:00
|
|
|
|
this.logger.info(`Creating TXT record for ${fullDomain}: ${fullRecord}`);
|
2022-11-08 22:10:42 +08:00
|
|
|
|
/* Replace this */
|
2024-10-07 03:21:16 +08:00
|
|
|
|
this.logger.info(`Would create TXT record "${fullRecord}" with value "${recordValue}"`);
|
2022-11-08 22:10:42 +08:00
|
|
|
|
|
2024-10-07 03:21:16 +08:00
|
|
|
|
let domain = parseDomain(fullDomain);
|
2024-10-22 16:21:35 +08:00
|
|
|
|
this.logger.info("解析到域名domain=" + domain);
|
2024-10-07 03:21:16 +08:00
|
|
|
|
|
|
|
|
|
|
if (domainsVerifyPlan) {
|
|
|
|
|
|
//按照计划执行
|
|
|
|
|
|
const domainVerifyPlan = domainsVerifyPlan[domain];
|
|
|
|
|
|
if (domainVerifyPlan) {
|
|
|
|
|
|
if (domainVerifyPlan.type === "dns") {
|
|
|
|
|
|
dnsProvider = domainVerifyPlan.dnsProvider;
|
|
|
|
|
|
} else if (domainVerifyPlan.type === "cname") {
|
|
|
|
|
|
const cnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan;
|
|
|
|
|
|
if (cnameVerifyPlan) {
|
|
|
|
|
|
const cname = cnameVerifyPlan[fullDomain];
|
|
|
|
|
|
if (cname) {
|
|
|
|
|
|
dnsProvider = cname.dnsProvider;
|
|
|
|
|
|
domain = parseDomain(cname.domain);
|
|
|
|
|
|
fullRecord = cname.fullRecord;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.logger.error("未找到域名Cname校验计划,使用默认的dnsProvider");
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.logger.error("不支持的校验类型", domainVerifyPlan.type);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.logger.info("未找到域名校验计划,使用默认的dnsProvider");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let hostRecord = fullRecord.replace(`${domain}`, "");
|
|
|
|
|
|
if (hostRecord.endsWith(".")) {
|
|
|
|
|
|
hostRecord = hostRecord.substring(0, hostRecord.length - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const recordReq = {
|
|
|
|
|
|
domain,
|
|
|
|
|
|
fullRecord,
|
|
|
|
|
|
hostRecord,
|
2022-11-08 22:10:42 +08:00
|
|
|
|
type: "TXT",
|
|
|
|
|
|
value: recordValue,
|
2024-10-07 03:21:16 +08:00
|
|
|
|
};
|
|
|
|
|
|
this.logger.info("添加 TXT 解析记录", JSON.stringify(recordReq));
|
|
|
|
|
|
const recordRes = await dnsProvider.createRecord(recordReq);
|
|
|
|
|
|
this.logger.info("添加 TXT 解析记录成功", JSON.stringify(recordRes));
|
|
|
|
|
|
return {
|
|
|
|
|
|
recordReq,
|
|
|
|
|
|
recordRes,
|
|
|
|
|
|
dnsProvider,
|
|
|
|
|
|
};
|
2022-11-08 22:10:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Function used to remove an ACME challenge response
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {object} authz Authorization object
|
|
|
|
|
|
* @param {object} challenge Selected challenge
|
|
|
|
|
|
* @param {string} keyAuthorization Authorization key
|
2024-10-07 03:21:16 +08:00
|
|
|
|
* @param recordReq
|
|
|
|
|
|
* @param recordRes
|
2022-11-08 22:10:42 +08:00
|
|
|
|
* @param dnsProvider dnsProvider
|
|
|
|
|
|
* @returns {Promise}
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2024-10-07 03:21:16 +08:00
|
|
|
|
async challengeRemoveFn(authz: any, challenge: any, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider: IDnsProvider) {
|
2022-11-08 22:10:42 +08:00
|
|
|
|
this.logger.info("Triggered challengeRemoveFn()");
|
|
|
|
|
|
|
|
|
|
|
|
/* http-01 */
|
2024-06-14 01:22:07 +08:00
|
|
|
|
const fullDomain = authz.identifier.value;
|
2022-11-08 22:10:42 +08:00
|
|
|
|
if (challenge.type === "http-01") {
|
|
|
|
|
|
const filePath = `/var/www/html/.well-known/acme-challenge/${challenge.token}`;
|
|
|
|
|
|
|
2024-06-14 01:22:07 +08:00
|
|
|
|
this.logger.info(`Removing challenge response for ${fullDomain} at path: ${filePath}`);
|
2022-11-08 22:10:42 +08:00
|
|
|
|
|
|
|
|
|
|
/* Replace this */
|
|
|
|
|
|
this.logger.info(`Would remove file on path "${filePath}"`);
|
|
|
|
|
|
// await fs.unlinkAsync(filePath);
|
|
|
|
|
|
} else if (challenge.type === "dns-01") {
|
2024-10-07 03:21:16 +08:00
|
|
|
|
this.logger.info(`删除 TXT 解析记录:${JSON.stringify(recordReq)} ,recordRes = ${JSON.stringify(recordRes)}`);
|
2023-05-09 13:52:25 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await dnsProvider.removeRecord({
|
2024-10-07 03:21:16 +08:00
|
|
|
|
recordReq,
|
|
|
|
|
|
recordRes,
|
2023-05-09 13:52:25 +08:00
|
|
|
|
});
|
2024-10-07 03:21:16 +08:00
|
|
|
|
this.logger.info("删除解析记录成功");
|
2023-05-09 13:52:25 +08:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
this.logger.error("删除解析记录出错:", e);
|
|
|
|
|
|
throw e;
|
|
|
|
|
|
}
|
2022-11-08 22:10:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-23 17:41:02 +08:00
|
|
|
|
async order(options: {
|
|
|
|
|
|
email: string;
|
|
|
|
|
|
domains: string | string[];
|
2024-10-07 03:21:16 +08:00
|
|
|
|
dnsProvider?: any;
|
|
|
|
|
|
domainsVerifyPlan?: DomainsVerifyPlan;
|
2024-08-23 17:41:02 +08:00
|
|
|
|
csrInfo: any;
|
|
|
|
|
|
isTest?: boolean;
|
|
|
|
|
|
privateKeyType?: string;
|
|
|
|
|
|
}): Promise<CertInfo> {
|
2024-10-07 03:21:16 +08:00
|
|
|
|
const { email, isTest, domains, csrInfo, dnsProvider, domainsVerifyPlan } = options;
|
2022-11-08 22:10:42 +08:00
|
|
|
|
const client: acme.Client = await this.getAcmeClient(email, isTest);
|
|
|
|
|
|
|
|
|
|
|
|
/* Create CSR */
|
|
|
|
|
|
const { commonName, altNames } = this.buildCommonNameByDomains(domains);
|
2024-08-23 17:41:02 +08:00
|
|
|
|
let privateKey = null;
|
2024-08-25 12:07:47 +08:00
|
|
|
|
const privateKeyType = options.privateKeyType || "rsa_2048";
|
|
|
|
|
|
const privateKeyArr = privateKeyType.split("_");
|
2024-08-25 11:56:15 +08:00
|
|
|
|
const type = privateKeyArr[0];
|
2024-09-06 22:32:29 +08:00
|
|
|
|
let size = 2048;
|
|
|
|
|
|
if (privateKeyArr.length > 1) {
|
|
|
|
|
|
size = parseInt(privateKeyArr[1]);
|
|
|
|
|
|
}
|
2024-09-23 14:32:57 +08:00
|
|
|
|
|
|
|
|
|
|
let encodingType = "pkcs8";
|
|
|
|
|
|
if (privateKeyArr.length > 2) {
|
|
|
|
|
|
encodingType = privateKeyArr[2];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-25 11:56:15 +08:00
|
|
|
|
if (type == "ec") {
|
|
|
|
|
|
const name: any = "P-" + size;
|
2024-09-23 14:32:57 +08:00
|
|
|
|
privateKey = await acme.crypto.createPrivateEcdsaKey(name, encodingType);
|
2024-08-23 17:41:02 +08:00
|
|
|
|
} else {
|
2024-09-23 14:32:57 +08:00
|
|
|
|
privateKey = await acme.crypto.createPrivateRsaKey(size, encodingType);
|
2024-08-23 17:41:02 +08:00
|
|
|
|
}
|
2024-09-23 14:32:57 +08:00
|
|
|
|
|
|
|
|
|
|
let createCsr: any = acme.crypto.createCsr;
|
|
|
|
|
|
if (encodingType === "pkcs1") {
|
|
|
|
|
|
//兼容老版本
|
|
|
|
|
|
createCsr = acme.forge.createCsr;
|
|
|
|
|
|
}
|
|
|
|
|
|
const [key, csr] = await createCsr(
|
2024-08-23 17:41:02 +08:00
|
|
|
|
{
|
|
|
|
|
|
commonName,
|
|
|
|
|
|
...csrInfo,
|
|
|
|
|
|
altNames,
|
|
|
|
|
|
},
|
|
|
|
|
|
privateKey
|
|
|
|
|
|
);
|
2024-09-23 14:32:57 +08:00
|
|
|
|
|
2024-10-07 03:21:16 +08:00
|
|
|
|
if (dnsProvider == null && domainsVerifyPlan == null) {
|
|
|
|
|
|
throw new Error("dnsProvider 、 domainsVerifyPlan 不能都为空");
|
2022-11-08 22:10:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
/* 自动申请证书 */
|
|
|
|
|
|
const crt = await client.auto({
|
|
|
|
|
|
csr,
|
|
|
|
|
|
email: email,
|
|
|
|
|
|
termsOfServiceAgreed: true,
|
2024-07-08 15:35:58 +08:00
|
|
|
|
skipChallengeVerification: this.skipLocalVerify,
|
2022-11-08 22:10:42 +08:00
|
|
|
|
challengePriority: ["dns-01"],
|
2024-10-07 03:21:16 +08:00
|
|
|
|
challengeCreateFn: async (
|
|
|
|
|
|
authz: acme.Authorization,
|
|
|
|
|
|
challenge: Challenge,
|
|
|
|
|
|
keyAuthorization: string
|
|
|
|
|
|
): Promise<{ recordReq: any; recordRes: any; dnsProvider: any }> => {
|
|
|
|
|
|
return await this.challengeCreateFn(authz, challenge, keyAuthorization, dnsProvider, domainsVerifyPlan);
|
2022-11-08 22:10:42 +08:00
|
|
|
|
},
|
2024-10-07 03:21:16 +08:00
|
|
|
|
challengeRemoveFn: async (
|
|
|
|
|
|
authz: acme.Authorization,
|
|
|
|
|
|
challenge: Challenge,
|
|
|
|
|
|
keyAuthorization: string,
|
|
|
|
|
|
recordReq: any,
|
|
|
|
|
|
recordRes: any,
|
|
|
|
|
|
dnsProvider: IDnsProvider
|
|
|
|
|
|
): Promise<any> => {
|
|
|
|
|
|
return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordReq, recordRes, dnsProvider);
|
2022-11-08 22:10:42 +08:00
|
|
|
|
},
|
2024-08-23 23:26:31 +08:00
|
|
|
|
signal: this.options.signal,
|
2022-11-08 22:10:42 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2024-09-22 23:19:10 +08:00
|
|
|
|
const crtString = crt.toString();
|
2023-05-23 18:01:20 +08:00
|
|
|
|
const cert: CertInfo = {
|
2024-09-22 23:19:10 +08:00
|
|
|
|
crt: crtString,
|
2022-11-08 22:10:42 +08:00
|
|
|
|
key: key.toString(),
|
|
|
|
|
|
csr: csr.toString(),
|
|
|
|
|
|
};
|
|
|
|
|
|
/* Done */
|
|
|
|
|
|
this.logger.debug(`CSR:\n${cert.csr}`);
|
|
|
|
|
|
this.logger.debug(`Certificate:\n${cert.crt}`);
|
|
|
|
|
|
this.logger.info("证书申请成功");
|
|
|
|
|
|
return cert;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
buildCommonNameByDomains(domains: string | string[]): {
|
|
|
|
|
|
commonName: string;
|
|
|
|
|
|
altNames: string[] | undefined;
|
|
|
|
|
|
} {
|
|
|
|
|
|
if (typeof domains === "string") {
|
|
|
|
|
|
domains = domains.split(",");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (domains.length === 0) {
|
|
|
|
|
|
throw new Error("domain can not be empty");
|
|
|
|
|
|
}
|
|
|
|
|
|
const commonName = domains[0];
|
|
|
|
|
|
let altNames: undefined | string[] = undefined;
|
|
|
|
|
|
if (domains.length > 1) {
|
|
|
|
|
|
altNames = _.slice(domains, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
|
|
|
commonName,
|
|
|
|
|
|
altNames,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2024-08-27 13:46:19 +08:00
|
|
|
|
|
|
|
|
|
|
private async testDirectory(directoryUrl: string) {
|
|
|
|
|
|
try {
|
2024-09-09 17:29:09 +08:00
|
|
|
|
await utils.http.request({
|
2024-08-27 13:46:19 +08:00
|
|
|
|
url: directoryUrl,
|
|
|
|
|
|
method: "GET",
|
2024-09-04 11:26:56 +08:00
|
|
|
|
timeout: 10000,
|
2024-08-27 13:46:19 +08:00
|
|
|
|
});
|
|
|
|
|
|
} catch (e) {
|
2024-09-04 11:26:56 +08:00
|
|
|
|
this.logger.error(`${directoryUrl},测试访问失败`, e.stack);
|
2024-08-27 13:46:19 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.logger.info(`${directoryUrl},测试访问成功`);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2022-11-08 22:10:42 +08:00
|
|
|
|
}
|