mirror of
https://github.com/certd/certd.git
synced 2026-04-24 20:57:26 +08:00
refactor: move
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "standard",
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.test.js", "*.spec.js"],
|
||||
"rules": {
|
||||
"no-unused-expressions": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
+2476
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@certd/certd",
|
||||
"version": "0.1.13",
|
||||
"description": "",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \\\"Error: no test specified\\\" && exit 1"
|
||||
},
|
||||
"type": "module",
|
||||
"author": "Greper",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^0.1.6",
|
||||
"@certd/api": "^0.1.13",
|
||||
"dayjs": "^1.9.7",
|
||||
"lodash-es": "^4.17.20",
|
||||
"node-forge": "^0.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.2.0",
|
||||
"eslint": "^7.15.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"mocha": "^8.2.1"
|
||||
},
|
||||
"gitHead": "4a421d5b142d453203c68ce6d1036e168ea2455b"
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import acme from '@certd/acme-client'
|
||||
import _ from 'lodash-es'
|
||||
import { util } from '@certd/api'
|
||||
const logger = util.logger
|
||||
export class AcmeService {
|
||||
constructor (store) {
|
||||
this.store = store
|
||||
}
|
||||
|
||||
async getAccountConfig (email) {
|
||||
let conf = this.store.get(this.buildAccountPath(email))
|
||||
if (conf == null) {
|
||||
conf = {}
|
||||
} else {
|
||||
conf = JSON.parse(conf)
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
buildAccountPath (email) {
|
||||
return this.store.buildKey(email, 'account.json')
|
||||
}
|
||||
|
||||
saveAccountConfig (email, conf) {
|
||||
this.store.set(this.buildAccountPath(email), JSON.stringify(conf))
|
||||
}
|
||||
|
||||
async getAcmeClient (email, isTest) {
|
||||
const conf = await this.getAccountConfig(email)
|
||||
if (conf.key == null) {
|
||||
conf.key = await this.createNewKey()
|
||||
this.saveAccountConfig(email, conf)
|
||||
}
|
||||
if (isTest == null) {
|
||||
isTest = process.env.CERTD_MODE === 'test'
|
||||
}
|
||||
const client = new acme.Client({
|
||||
directoryUrl: isTest ? acme.directory.letsencrypt.staging : acme.directory.letsencrypt.production,
|
||||
accountKey: conf.key,
|
||||
accountUrl: conf.accountUrl,
|
||||
backoffAttempts: 20,
|
||||
backoffMin: 5000,
|
||||
backoffMax: 10000
|
||||
})
|
||||
|
||||
if (conf.accountUrl == null) {
|
||||
const accountPayload = { termsOfServiceAgreed: true, contact: [`mailto:${email}`] }
|
||||
await client.createAccount(accountPayload)
|
||||
conf.accountUrl = client.getAccountUrl()
|
||||
this.saveAccountConfig(email, conf)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
async createNewKey () {
|
||||
const key = await acme.forge.createPrivateKey()
|
||||
return key.toString()
|
||||
}
|
||||
|
||||
async challengeCreateFn (authz, challenge, keyAuthorization, dnsProvider) {
|
||||
logger.info('Triggered challengeCreateFn()')
|
||||
|
||||
/* http-01 */
|
||||
if (challenge.type === 'http-01') {
|
||||
const filePath = `/var/www/html/.well-known/acme-challenge/${challenge.token}`
|
||||
const fileContents = keyAuthorization
|
||||
|
||||
logger.info(`Creating challenge response for ${authz.identifier.value} at path: ${filePath}`)
|
||||
|
||||
/* Replace this */
|
||||
logger.info(`Would write "${fileContents}" to path "${filePath}"`)
|
||||
// await fs.writeFileAsync(filePath, fileContents);
|
||||
} else if (challenge.type === 'dns-01') {
|
||||
/* dns-01 */
|
||||
const dnsRecord = `_acme-challenge.${authz.identifier.value}`
|
||||
const recordValue = keyAuthorization
|
||||
|
||||
logger.info(`Creating TXT record for ${authz.identifier.value}: ${dnsRecord}`)
|
||||
|
||||
/* Replace this */
|
||||
logger.info(`Would create TXT record "${dnsRecord}" with value "${recordValue}"`)
|
||||
|
||||
return await dnsProvider.createRecord({
|
||||
fullRecord: dnsRecord,
|
||||
type: 'TXT',
|
||||
value: recordValue
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function used to remove an ACME challenge response
|
||||
*
|
||||
* @param {object} authz Authorization object
|
||||
* @param {object} challenge Selected challenge
|
||||
* @param {string} keyAuthorization Authorization key
|
||||
* @param recordItem challengeCreateFn create record item
|
||||
* @param dnsProvider dnsProvider
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async challengeRemoveFn (authz, challenge, keyAuthorization, recordItem, dnsProvider) {
|
||||
logger.info('Triggered challengeRemoveFn()')
|
||||
|
||||
/* http-01 */
|
||||
if (challenge.type === 'http-01') {
|
||||
const filePath = `/var/www/html/.well-known/acme-challenge/${challenge.token}`
|
||||
|
||||
logger.info(`Removing challenge response for ${authz.identifier.value} at path: ${filePath}`)
|
||||
|
||||
/* Replace this */
|
||||
logger.info(`Would remove file on path "${filePath}"`)
|
||||
// await fs.unlinkAsync(filePath);
|
||||
} else if (challenge.type === 'dns-01') {
|
||||
const dnsRecord = `_acme-challenge.${authz.identifier.value}`
|
||||
const recordValue = keyAuthorization
|
||||
|
||||
logger.info(`Removing TXT record for ${authz.identifier.value}: ${dnsRecord}`)
|
||||
|
||||
/* Replace this */
|
||||
logger.info(`Would remove TXT record "${dnsRecord}" with value "${recordValue}"`)
|
||||
await dnsProvider.removeRecord({
|
||||
fullRecord: dnsRecord,
|
||||
type: 'TXT',
|
||||
value: keyAuthorization,
|
||||
record: recordItem
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async order ({ email, domains, dnsProvider, dnsProviderCreator, csrInfo, isTest }) {
|
||||
const client = await this.getAcmeClient(email, isTest)
|
||||
|
||||
let accountUrl
|
||||
try {
|
||||
accountUrl = client.getAccountUrl()
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
/* Create CSR */
|
||||
const { commonName, altNames } = this.buildCommonNameByDomains(domains)
|
||||
|
||||
const [key, csr] = await acme.forge.createCsr({
|
||||
commonName,
|
||||
...csrInfo,
|
||||
altNames
|
||||
})
|
||||
if (dnsProvider == null && dnsProviderCreator) {
|
||||
dnsProvider = await dnsProviderCreator()
|
||||
}
|
||||
if (dnsProvider == null) {
|
||||
throw new Error('dnsProvider 不能为空')
|
||||
}
|
||||
/* 自动申请证书 */
|
||||
const crt = await client.auto({
|
||||
csr,
|
||||
email: email,
|
||||
termsOfServiceAgreed: true,
|
||||
challengePriority: ['dns-01'],
|
||||
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
|
||||
return await this.challengeCreateFn(authz, challenge, keyAuthorization, dnsProvider)
|
||||
},
|
||||
challengeRemoveFn: async (authz, challenge, keyAuthorization, recordItem) => {
|
||||
return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem, dnsProvider)
|
||||
}
|
||||
})
|
||||
|
||||
// 保存账号url
|
||||
if (!accountUrl) {
|
||||
try {
|
||||
accountUrl = client.getAccountUrl()
|
||||
this.setAccountUrl(email, accountUrl)
|
||||
} catch (e) {
|
||||
logger.warn('保存accountUrl出错', e)
|
||||
}
|
||||
}
|
||||
/* Done */
|
||||
logger.debug(`CSR:\n${csr.toString()}`)
|
||||
logger.debug(`Certificate:\n${crt.toString()}`)
|
||||
logger.info('证书申请成功')
|
||||
return { key, crt, csr }
|
||||
}
|
||||
|
||||
buildCommonNameByDomains (domains) {
|
||||
if (typeof domains === 'string') {
|
||||
domains = domains.split(',')
|
||||
}
|
||||
if (domains.length === 0) {
|
||||
throw new Error('domain can not be empty')
|
||||
}
|
||||
const ret = {
|
||||
commonName: domains[0]
|
||||
}
|
||||
if (domains.length > 1) {
|
||||
ret.altNames = _.slice(domains, 1)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import { util, Store, dnsProviderRegistry } from '@certd/api'
|
||||
import { AcmeService } from './acme.js'
|
||||
import { FileStore } from './store/file-store.js'
|
||||
import { CertStore } from './store/cert-store.js'
|
||||
import dayjs from 'dayjs'
|
||||
import forge from 'node-forge'
|
||||
import DefaultDnsProviders from '@certd/dns-providers'
|
||||
|
||||
const logger = util.logger
|
||||
|
||||
DefaultDnsProviders.install()
|
||||
|
||||
export class Certd {
|
||||
constructor (options) {
|
||||
this.options = options
|
||||
this.email = options.cert.email
|
||||
this.domains = options.cert.domains
|
||||
|
||||
if (!(options.store instanceof Store)) {
|
||||
this.store = new FileStore(options.store || {})
|
||||
}
|
||||
this.certStore = new CertStore({
|
||||
store: this.store,
|
||||
email: options.cert.email,
|
||||
domains: this.domains
|
||||
})
|
||||
this.acme = new AcmeService(this.store)
|
||||
}
|
||||
|
||||
async certApply () {
|
||||
let oldCert
|
||||
try {
|
||||
oldCert = await this.readCurrentCert()
|
||||
} catch (e) {
|
||||
logger.warn('读取cert失败:', e)
|
||||
}
|
||||
|
||||
if (oldCert == null) {
|
||||
logger.info('还未申请过,准备申请新证书')
|
||||
} else {
|
||||
const ret = this.isWillExpire(oldCert.expires, this.options.cert.renewDays)
|
||||
if (!ret.isWillExpire) {
|
||||
logger.info('证书还未过期:', oldCert.expires, ',剩余', ret.leftDays, '天')
|
||||
if (this.options.args.forceCert) {
|
||||
logger.info('准备强制更新证书')
|
||||
} else {
|
||||
logger.info('暂不更新证书')
|
||||
|
||||
oldCert.isNew = false
|
||||
return oldCert
|
||||
}
|
||||
} else {
|
||||
logger.info('即将过期,准备更新证书')
|
||||
}
|
||||
}
|
||||
|
||||
// 执行证书申请步骤
|
||||
return await this.doCertApply()
|
||||
}
|
||||
|
||||
async doCertApply () {
|
||||
const options = this.options
|
||||
const dnsProvider = this.createDnsProvider(options)
|
||||
const cert = await this.acme.order({
|
||||
email: options.cert.email,
|
||||
domains: options.cert.domains,
|
||||
dnsProvider,
|
||||
csrInfo: options.cert.csrInfo,
|
||||
isTest: options.args.test
|
||||
})
|
||||
|
||||
await this.writeCert(cert)
|
||||
const certRet = await this.readCurrentCert()
|
||||
certRet.isNew = true
|
||||
return certRet
|
||||
}
|
||||
|
||||
createDnsProvider (options) {
|
||||
return this.createProviderByType(options.cert.dnsProvider, options.accessProviders)
|
||||
}
|
||||
|
||||
async writeCert (cert) {
|
||||
const newPath = await this.certStore.writeCert(cert)
|
||||
return {
|
||||
realPath: this.certStore.store.getActualKey(newPath),
|
||||
currentPath: this.certStore.store.getActualKey(this.certStore.currentMarkPath)
|
||||
}
|
||||
}
|
||||
|
||||
async readCurrentCert () {
|
||||
const cert = await this.certStore.readCert()
|
||||
if (cert == null) {
|
||||
return null
|
||||
}
|
||||
const { detail, expires } = this.getCrtDetail(cert.crt)
|
||||
const domain = this.certStore.getMainDomain(this.options.cert.domains)
|
||||
return {
|
||||
...cert, detail, expires, domain, domains: this.domains, email: this.email
|
||||
}
|
||||
}
|
||||
|
||||
getCrtDetail (crt) {
|
||||
const pki = forge.pki
|
||||
const detail = pki.certificateFromPem(crt.toString())
|
||||
const expires = detail.validity.notAfter
|
||||
return { detail, expires }
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否过期,默认提前20天
|
||||
* @param expires
|
||||
* @param maxDays
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isWillExpire (expires, maxDays = 20) {
|
||||
if (expires == null) {
|
||||
throw new Error('过期时间不能为空')
|
||||
}
|
||||
// 检查有效期
|
||||
const leftDays = dayjs(expires).diff(dayjs(), 'day')
|
||||
return {
|
||||
isWillExpire: leftDays < maxDays,
|
||||
leftDays
|
||||
}
|
||||
}
|
||||
|
||||
createProviderByType (props, accessProviders) {
|
||||
const { type } = props
|
||||
const Provider = dnsProviderRegistry.get(type)
|
||||
if (Provider == null) {
|
||||
throw new Error('暂不支持此dnsProvider,请先注册该provider:' + type)
|
||||
}
|
||||
return new Provider({ accessProviders, props })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import dayjs from 'dayjs'
|
||||
import crypto from 'crypto'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function md5 (content) {
|
||||
return crypto.createHash('md5').update(content).digest('hex')
|
||||
}
|
||||
export class CertStore {
|
||||
constructor ({ store, email, domains }) {
|
||||
this.store = store
|
||||
this.email = email
|
||||
this.domains = domains
|
||||
this.domain = this.getMainDomain(this.domains)
|
||||
this.safetyDomain = this.getSafetyDomain(this.domain)
|
||||
// this.domainDir = this.safetyDomain + '-' + md5(this.getDomainStr(this.domains))
|
||||
this.domainDir = this.safetyDomain
|
||||
this.certsRootPath = this.store.buildKey(this.email, 'certs')
|
||||
|
||||
this.currentMarkPath = this.store.buildKey(this.certsRootPath, this.domainDir, 'current.json')
|
||||
}
|
||||
|
||||
getMainDomain (domains) {
|
||||
if (domains == null) {
|
||||
return null
|
||||
}
|
||||
if (typeof domains === 'string') {
|
||||
return domains
|
||||
}
|
||||
if (domains.length > 0) {
|
||||
return domains[0]
|
||||
}
|
||||
}
|
||||
|
||||
getDomainStr (domains) {
|
||||
if (domains == null) {
|
||||
return null
|
||||
}
|
||||
if (typeof domains === 'string') {
|
||||
return domains
|
||||
}
|
||||
return domains.join(',')
|
||||
}
|
||||
|
||||
buildNewCertRootPath (dir) {
|
||||
if (dir == null) {
|
||||
dir = dayjs().format('YYYY.MM.DD.HHmmss')
|
||||
}
|
||||
return this.store.buildKey(this.certsRootPath, this.domainDir, dir)
|
||||
}
|
||||
|
||||
formatCert (pem) {
|
||||
pem = pem.replace(/\r/g, '')
|
||||
pem = pem.replace(/\n\n/g, '\n')
|
||||
pem = pem.replace(/\n$/g, '')
|
||||
return pem
|
||||
}
|
||||
|
||||
async writeCert (cert) {
|
||||
const newDir = this.buildNewCertRootPath()
|
||||
|
||||
const crtKey = this.buildKey(newDir, this.safetyDomain + '.crt')
|
||||
const priKey = this.buildKey(newDir, this.safetyDomain + '.key')
|
||||
const csrKey = this.buildKey(newDir, this.safetyDomain + '.csr')
|
||||
await this.store.set(crtKey, this.formatCert(cert.crt.toString()))
|
||||
await this.store.set(priKey, this.formatCert(cert.key.toString()))
|
||||
await this.store.set(csrKey, cert.csr.toString())
|
||||
|
||||
await this.store.set(this.currentMarkPath, JSON.stringify({ latest: newDir }))
|
||||
|
||||
return newDir
|
||||
}
|
||||
|
||||
async readCert (dir) {
|
||||
if (dir == null) {
|
||||
dir = await this.getCurrentDir()
|
||||
}
|
||||
if (dir == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const crtKey = this.buildKey(dir, this.safetyDomain + '.crt')
|
||||
const priKey = this.buildKey(dir, this.safetyDomain + '.key')
|
||||
const csrKey = this.buildKey(dir, this.safetyDomain + '.csr')
|
||||
const crt = await this.store.get(crtKey)
|
||||
if (crt == null) {
|
||||
return null
|
||||
}
|
||||
const key = await this.store.get(priKey)
|
||||
const csr = await this.store.get(csrKey)
|
||||
|
||||
return {
|
||||
crt: this.formatCert(crt),
|
||||
key: this.formatCert(key),
|
||||
csr,
|
||||
crtPath: this.store.getActualKey(crtKey),
|
||||
keyPath: this.store.getActualKey(priKey),
|
||||
certDir: this.store.getActualKey(dir)
|
||||
}
|
||||
}
|
||||
|
||||
buildKey (...keyItem) {
|
||||
return this.store.buildKey(...keyItem)
|
||||
}
|
||||
|
||||
getSafetyDomain (domain) {
|
||||
return domain.replace(/\*/g, '_')
|
||||
}
|
||||
|
||||
async getCurrentDir () {
|
||||
const current = await this.store.get(this.currentMarkPath)
|
||||
if (current == null) {
|
||||
return null
|
||||
}
|
||||
return JSON.parse(current).latest
|
||||
}
|
||||
|
||||
async getCurrentFile (file) {
|
||||
const currentDir = await this.getCurrentDir()
|
||||
const key = this.buildKey(currentDir, file)
|
||||
return this.store.get(key)
|
||||
}
|
||||
|
||||
async setCurrentFile (file, value) {
|
||||
const currentDir = await this.getCurrentDir()
|
||||
const key = this.buildKey(currentDir, file)
|
||||
return this.store.set(key, value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Store, util } from '@certd/api'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
const logger = util.logger
|
||||
export class FileStore extends Store {
|
||||
constructor (opts) {
|
||||
super()
|
||||
if (opts.rootDir != null) {
|
||||
this.rootDir = opts.rootDir
|
||||
} else {
|
||||
this.rootDir = util.path.getUserBasePath()
|
||||
}
|
||||
if (opts.test) {
|
||||
this.rootDir = path.join(this.rootDir, '/test/')
|
||||
}
|
||||
}
|
||||
|
||||
getActualKey (key) {
|
||||
// return 前缀+key
|
||||
return this.getPathByKey(key)
|
||||
}
|
||||
|
||||
buildKey (...keyItem) {
|
||||
return path.join(...keyItem)
|
||||
}
|
||||
|
||||
getPathByKey (key) {
|
||||
return path.join(this.rootDir, key)
|
||||
}
|
||||
|
||||
set (key, value) {
|
||||
const filePath = this.getPathByKey(key)
|
||||
const dir = path.dirname(filePath)
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
fs.writeFileSync(filePath, value)
|
||||
return filePath
|
||||
}
|
||||
|
||||
get (key) {
|
||||
const filePath = this.getPathByKey(key)
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return null
|
||||
}
|
||||
return fs.readFileSync(filePath).toString()
|
||||
}
|
||||
|
||||
link (targetPath, linkPath) {
|
||||
targetPath = this.getPathByKey(targetPath)
|
||||
linkPath = this.getPathByKey(linkPath)
|
||||
if (fs.existsSync(linkPath)) {
|
||||
try {
|
||||
fs.unlinkSync(linkPath)
|
||||
} catch (e) {
|
||||
logger.error('unlink error:', e)
|
||||
}
|
||||
}
|
||||
fs.symlinkSync(targetPath, linkPath, 'dir')
|
||||
}
|
||||
|
||||
unlink (linkPath) {
|
||||
linkPath = this.getPathByKey(linkPath)
|
||||
fs.unlinkSync(linkPath)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import pkg from 'chai'
|
||||
import { createOptions } from '../../../../../test/options.js'
|
||||
import { Certd } from '../../src'
|
||||
const { expect } = pkg
|
||||
describe('AliyunDnsProvider', function () {
|
||||
it('#申请证书-aliyun', async function () {
|
||||
this.timeout(300000)
|
||||
const options = createOptions()
|
||||
options.args = { forceCert: true, test: false }
|
||||
const certd = new Certd(options)
|
||||
const cert = await certd.certApply()
|
||||
expect(cert).ok
|
||||
expect(cert.crt).ok
|
||||
expect(cert.key).ok
|
||||
expect(cert.detail).ok
|
||||
expect(cert.expires).ok
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
import pkg from 'chai'
|
||||
import { Certd } from '../../src'
|
||||
import { createOptions } from '../../../../../test/options.js'
|
||||
const { expect } = pkg
|
||||
describe('DnspodDnsProvider', function () {
|
||||
it('#申请证书', async function () {
|
||||
this.timeout(300000)
|
||||
const options = createOptions()
|
||||
options.cert.domains = ['*.certd.xyz', '*.test.certd.xyz', '*.base.certd.xyz', 'certd.xyz']
|
||||
options.cert.dnsProvider = 'dnspod'
|
||||
options.args = { forceCert: true }
|
||||
const certd = new Certd(options)
|
||||
const cert = await certd.certApply()
|
||||
expect(cert).ok
|
||||
expect(cert.crt).ok
|
||||
expect(cert.key).ok
|
||||
expect(cert.detail).ok
|
||||
expect(cert.expires).ok
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,88 @@
|
||||
import chai from 'chai'
|
||||
import { Certd } from '../src'
|
||||
import { createOptions } from '../../../../test/options.js'
|
||||
const { expect } = chai
|
||||
const fakeCrt = `-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
AHxTaXliTVvS83dogw==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw
|
||||
GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2
|
||||
MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0
|
||||
8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym
|
||||
oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0
|
||||
ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN
|
||||
xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56
|
||||
dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9
|
||||
AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
|
||||
HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0
|
||||
BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu
|
||||
b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu
|
||||
Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF
|
||||
UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9
|
||||
AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp
|
||||
DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7
|
||||
IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf
|
||||
zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI
|
||||
PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w
|
||||
SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em
|
||||
2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0
|
||||
WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt
|
||||
n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU=
|
||||
-----END CERTIFICATE-----`
|
||||
describe('Certd', function () {
|
||||
it('#buildCertDir', function () {
|
||||
const options = createOptions()
|
||||
options.cert.email = 'xiaojunnuo@qq.com'
|
||||
options.cert.domains = ['*.docmirror.club']
|
||||
const certd = new Certd(options)
|
||||
const currentRootPath = certd.certStore.currentMarkPath
|
||||
console.log('rootDir', currentRootPath)
|
||||
expect(currentRootPath).match(/xiaojunnuo@qq.com\\certs\\_.docmirror.club\w*\\current.json/)
|
||||
})
|
||||
it('#writeAndReadCert', async function () {
|
||||
const options = createOptions()
|
||||
options.cert.email = 'xiaojunnuo@qq.com'
|
||||
options.cert.domains = ['*.domain.cn']
|
||||
const certd = new Certd(options)
|
||||
await certd.writeCert({ csr: 'csr', crt: fakeCrt, key: 'bbb' })
|
||||
|
||||
const cert = await certd.readCurrentCert()
|
||||
expect(cert).to.be.ok
|
||||
expect(cert.crt).ok
|
||||
expect(cert.key).to.be.ok
|
||||
expect(cert.detail).to.be.ok
|
||||
expect(cert.expires).to.be.ok
|
||||
console.log('cert:', JSON.stringify(cert))
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user