Compare commits

..

165 Commits

Author SHA1 Message Date
xiaojunnuo
e5da46cfc3 v1.22.2 2024-07-24 02:25:12 +08:00
xiaojunnuo
eabb3e38b5 chore: 2024-07-24 02:24:37 +08:00
xiaojunnuo
46140c8efa build: prepare to build 2024-07-24 02:24:04 +08:00
xiaojunnuo
95d071ba56 chore: 2024-07-24 02:23:09 +08:00
xiaojunnuo
3c9c3ca3b0 build: prepare to build 2024-07-24 02:19:05 +08:00
xiaojunnuo
e7c4ade57d build: prepare to build 2024-07-24 02:18:13 +08:00
xiaojunnuo
ca524657b6 build: prepare to build 2024-07-24 02:17:12 +08:00
xiaojunnuo
bc02559bc7 chore: 2024-07-24 02:17:06 +08:00
xiaojunnuo
741172fd98 chore: 2024-07-24 02:16:12 +08:00
xiaojunnuo
83d0209775 chore: 2024-07-24 02:11:38 +08:00
xiaojunnuo
6693d1acfb chore: 2024-07-24 00:42:50 +08:00
xiaojunnuo
a2c43b50a6 fix: 修复创建流水线时,无法根据dns类型默认正确的dns授权的bug
Closes https://github.com/certd/certd/issues/97
2024-07-24 00:42:33 +08:00
xiaojunnuo
f7fc06e657 chore: 2024-07-23 23:39:13 +08:00
xiaojunnuo
b9fe3b9c87 chore: github action build image 2024-07-23 23:23:45 +08:00
xiaojunnuo
06be993afc chore: github action build image 2024-07-23 23:16:55 +08:00
xiaojunnuo
b6ef39fb30 chore: github action build image 2024-07-23 23:12:50 +08:00
xiaojunnuo
0b131c00ed chore: github action build image 2024-07-23 23:06:11 +08:00
xiaojunnuo
b6b8661c36 chore: github action build image 2024-07-23 23:03:15 +08:00
xiaojunnuo
7bf19f8f6f chore: github action build image 2024-07-23 23:00:56 +08:00
xiaojunnuo
c9d9c6513b chore: github action build image 2024-07-23 22:59:23 +08:00
xiaojunnuo
4e7b7ae974 chore: github action build image 2024-07-23 22:57:55 +08:00
xiaojunnuo
dfcabc02a4 chore: github action build image 2024-07-23 22:56:16 +08:00
xiaojunnuo
6f2c5674c9 chore: github action build image 2024-07-23 22:54:23 +08:00
xiaojunnuo
2877b9b505 chore: github action build image 2024-07-23 22:52:56 +08:00
xiaojunnuo
e40bb9e14d chore: github action build image 2024-07-23 22:50:15 +08:00
xiaojunnuo
d456ff9830 chore: github action build image 2024-07-23 22:48:30 +08:00
xiaojunnuo
ffddb3b4ac chore: github action build image 2024-07-23 22:40:05 +08:00
xiaojunnuo
a6113f237b chore: 2024-07-23 22:38:38 +08:00
xiaojunnuo
093520b686 chore: 2024-07-23 22:37:32 +08:00
xiaojunnuo
72bff652f7 chore: github action build 2024-07-23 13:30:36 +08:00
xiaojunnuo
9559bdf817 chore: github action build 2024-07-23 13:27:17 +08:00
xiaojunnuo
5a88b8c24e chore: github action build 2024-07-23 12:44:42 +08:00
xiaojunnuo
a9ebac82c7 chore: github action build 2024-07-23 12:43:10 +08:00
xiaojunnuo
cfd8836083 chore: github action build 2024-07-23 12:42:48 +08:00
xiaojunnuo
e01e59b188 chore: github action build 2024-07-23 12:41:36 +08:00
xiaojunnuo
d2fd729961 chore: github action build 2024-07-23 12:40:36 +08:00
xiaojunnuo
5d4ff2e3b7 chore: github action build 2024-07-23 12:39:03 +08:00
xiaojunnuo
6e5133f6b8 chore: github action build 2024-07-23 12:37:21 +08:00
xiaojunnuo
a96d5839b2 chore: github action build 2024-07-23 12:35:24 +08:00
xiaojunnuo
a827bc306a chore: 2024-07-21 03:21:51 +08:00
xiaojunnuo
d8b3d7a6e0 v1.22.1 2024-07-21 03:11:54 +08:00
xiaojunnuo
b8f072909b build: prepare to build 2024-07-21 03:10:14 +08:00
xiaojunnuo
fa48f2b2f0 build: prepare to build 2024-07-21 03:07:32 +08:00
xiaojunnuo
019a1fe24e chore: 2024-07-21 03:02:13 +08:00
xiaojunnuo
427620d34f perf: 创建证书任务增加定时任务和邮件通知输入 2024-07-21 02:59:02 +08:00
xiaojunnuo
a5a0c1f6e7 perf: 支持配置启动后自动触发一次任务 2024-07-21 02:32:03 +08:00
xiaojunnuo
affef13037 perf: 创建证书任务可以选择lege插件 2024-07-21 02:26:03 +08:00
xiaojunnuo
4afbf20c1a chore: 重构image build 2024-07-20 19:38:15 +08:00
xiaojunnuo
0ef7b036dd chore: 1 2024-07-20 19:27:07 +08:00
xiaojunnuo
17ef7b8b9e chore: 1 2024-07-20 19:25:54 +08:00
xiaojunnuo
15eba52fad chore: 1 2024-07-20 19:25:45 +08:00
xiaojunnuo
f2d894b036 chore: 1 2024-07-20 18:39:51 +08:00
xiaojunnuo
d9da27710e chore: 1 2024-07-20 18:33:11 +08:00
xiaojunnuo
981bff70c3 chore: 1 2024-07-20 18:28:19 +08:00
xiaojunnuo
bb30d6e02f chore: 1 2024-07-20 18:18:34 +08:00
xiaojunnuo
b09ccda54d chore: 1 2024-07-20 18:17:48 +08:00
xiaojunnuo
a5de8d79ec chore: 1 2024-07-20 18:11:56 +08:00
xiaojunnuo
a092a1e843 chore: 1 2024-07-20 18:11:38 +08:00
xiaojunnuo
1c6740feff chore: sqlite换成better-sqlite 2024-07-20 18:07:32 +08:00
xiaojunnuo
79c6e05e02 chore: sqlite换成better-sqlite 2024-07-20 18:04:07 +08:00
xiaojunnuo
31e2085c16 chore: 1 2024-07-20 17:53:19 +08:00
xiaojunnuo
64fda2f1a0 chore: 1 2024-07-20 17:35:07 +08:00
xiaojunnuo
a674719a8b chore: 1 2024-07-20 17:27:12 +08:00
xiaojunnuo
2f7ef0620b chore: 1 2024-07-20 14:27:41 +08:00
xiaojunnuo
153e98b593 chore: 1 2024-07-20 14:26:01 +08:00
xiaojunnuo
d62ea41671 chore: 1 2024-07-20 10:17:38 +08:00
xiaojunnuo
8fcd9813d3 chore: 1 2024-07-20 10:11:05 +08:00
xiaojunnuo
dcbf8c85dd chore: 1 2024-07-20 10:09:18 +08:00
xiaojunnuo
ea0eafdb16 chore: 1 2024-07-20 10:05:39 +08:00
xiaojunnuo
eda89a057a chore: 1 2024-07-20 09:22:20 +08:00
xiaojunnuo
21e6eef1d3 chore: mv libs 2024-07-19 18:08:51 +08:00
xiaojunnuo
54a27c1840 chore: mv libs 2024-07-19 17:22:54 +08:00
xiaojunnuo
a3be0a1618 chore: 1 2024-07-19 16:01:55 +08:00
xiaojunnuo
8e19e44f4c chore: 1 2024-07-19 15:44:10 +08:00
xiaojunnuo
e5d93bd114 chore: 1 2024-07-19 15:41:36 +08:00
xiaojunnuo
47fe3d5826 v1.22.0 2024-07-19 15:28:41 +08:00
xiaojunnuo
8409f5fd4a build: prepare to build 2024-07-19 15:27:50 +08:00
xiaojunnuo
634a468ec4 build: prepare to build 2024-07-19 15:26:29 +08:00
xiaojunnuo
7ed0544664 build: prepare to build 2024-07-19 15:23:59 +08:00
xiaojunnuo
3d4a046634 build: prepare to build 2024-07-19 15:20:13 +08:00
xiaojunnuo
96954b4aaa chore: 1 2024-07-19 15:15:35 +08:00
xiaojunnuo
d77fc11552 chore: 1 2024-07-19 15:14:03 +08:00
xiaojunnuo
5bad0bbde8 build: prepare to build 2024-07-19 15:13:46 +08:00
xiaojunnuo
cd85ac611b build: prepare to build 2024-07-19 15:03:40 +08:00
xiaojunnuo
0bc6d0a211 feat: 支持lego,海量DNS提供商 2024-07-18 21:10:13 +08:00
xiaojunnuo
b1cd055342 chore: lfs 2024-07-18 21:09:22 +08:00
xiaojunnuo
303097b835 chore: 1 2024-07-18 11:17:13 +08:00
xiaojunnuo
a438028002 chore: 1 2024-07-17 13:14:49 +08:00
xiaojunnuo
4813872bcc chore: 1 2024-07-17 01:33:55 +08:00
xiaojunnuo
3b19bfb429 feat: 支持postgresql 2024-07-17 01:30:00 +08:00
Greper
a917a7bca6 Merge pull request #93 from certd/acme_sync
[acme] sync upgrade [trident-sync]
2024-07-16 03:24:25 +08:00
GitHub Actions Bot
86e64af35c 🔱: [acme] sync upgrade with 5 commits [trident-sync]
Temp remove Node v22 from matrix, broke CNAME tests
Invalidate ACME directory cache after 24 hours
Directory URLs for Google ACME provider
Bump Pebble v2.6.0
2024-07-15 19:24:17 +00:00
xiaojunnuo
bf6f1d8137 chore: 2024-07-15 02:13:53 +08:00
xiaojunnuo
b1688525db perf: 优化一些小细节 2024-07-15 02:05:26 +08:00
xiaojunnuo
2fd14430a2 Merge remote-tracking branch 'origin/v2' into v2
# Conflicts:
#	package.json
#	packages/core/pipeline/package.json
#	packages/libs/k8s/package.json
#	packages/plugins/plugin-cert/package.json
#	packages/ui/certd-server/package.json
#	packages/ui/certd-server/src/plugins/plugin-host/lib/ssh.ts
#	packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts
2024-07-15 01:33:04 +08:00
xiaojunnuo
bd3d959944 perf: 增加备案号设置 2024-07-15 01:29:35 +08:00
xiaojunnuo
390e4853a5 perf: 自动生成jwtkey,无需手动配置 2024-07-15 01:29:19 +08:00
xiaojunnuo
485e603b51 feat: 升级midway,支持esm 2024-07-15 00:30:33 +08:00
Greper
3ccd7ad95d Merge pull request #89 from certd/client_sync
[client] sync upgrade [trident-sync]
2024-07-13 03:23:54 +08:00
GitHub Actions Bot
54d9050483 🔱: [client] sync upgrade with 2 commits [trident-sync]
perf: 优化editorwang示例
2024-07-12 19:23:47 +00:00
Greper
6d58c68e9b Merge pull request #87 from certd/client_sync
[client] sync upgrade [trident-sync]
2024-07-12 03:23:58 +08:00
GitHub Actions Bot
80019d4dc1 🔱: [client] sync upgrade with 3 commits [trident-sync]
docs: 1
docs: 1
2024-07-11 19:23:48 +00:00
xiaojunnuo
031df8fc35 v1.21.2 2024-07-08 15:58:51 +08:00
xiaojunnuo
62419a8212 build: prepare to build 2024-07-08 15:57:13 +08:00
xiaojunnuo
09d93af853 build: prepare to build 2024-07-08 15:47:51 +08:00
xiaojunnuo
b6ab178de9 chore: 1 2024-07-08 15:47:36 +08:00
xiaojunnuo
5a08f27d2c build: prepare to build 2024-07-08 15:36:18 +08:00
xiaojunnuo
fe91d94090 perf: 申请证书时可以选择跳过本地dns校验 2024-07-08 15:35:58 +08:00
xiaojunnuo
56ab3269d2 chore: 1 2024-07-08 12:05:56 +08:00
xiaojunnuo
c8243c573f chore: 1 2024-07-08 12:04:59 +08:00
xiaojunnuo
0b769a1c86 v1.21.1 2024-07-08 11:58:09 +08:00
xiaojunnuo
a8189a974d build: prepare to build 2024-07-08 11:46:13 +08:00
xiaojunnuo
322875d241 chore: 1 2024-07-08 11:45:31 +08:00
xiaojunnuo
ed8a54a2bc chore: 1 2024-07-08 11:29:11 +08:00
xiaojunnuo
5ba9831ed1 perf: 上传到主机,支持设置不mkdirs 2024-07-08 11:19:02 +08:00
xiaojunnuo
f9c9fce581 docs: 1 2024-07-08 11:10:08 +08:00
xiaojunnuo
bc650a32cd docs: 1 2024-07-08 10:59:19 +08:00
xiaojunnuo
c6d3e3fe5b docs: 1 2024-07-08 10:57:42 +08:00
xiaojunnuo
73acc62af1 docs: 1 2024-07-08 10:56:19 +08:00
xiaojunnuo
0d4491f3a0 docs: 1 2024-07-08 10:55:55 +08:00
xiaojunnuo
85248044ab docs: 1 2024-07-08 10:53:55 +08:00
xiaojunnuo
c532449102 docs: 1 2024-07-05 10:56:29 +08:00
xiaojunnuo
970c7fd8a0 perf: 说明优化,默认值优化 2024-07-04 02:22:52 +08:00
xiaojunnuo
4656019898 v1.21.0 2024-07-04 01:15:51 +08:00
xiaojunnuo
7eceabb2d8 build: prepare to build 2024-07-04 01:14:33 +08:00
xiaojunnuo
eade2c2b68 feat: 支持zero ssl 2024-07-04 01:14:09 +08:00
xiaojunnuo
6ec950818c v1.20.17 2024-07-03 23:45:57 +08:00
xiaojunnuo
7058b20df4 build: prepare to build 2024-07-03 23:43:55 +08:00
xiaojunnuo
a09b0e48c1 perf: 文件上传提示由cert.crt改为cert.pem 2024-07-03 23:39:12 +08:00
xiaojunnuo
664bb66a91 Merge branch 'client_sync' into v2
# Conflicts:
#	packages/ui/certd-client/CHANGELOG.md
#	packages/ui/certd-client/package.json
2024-07-03 23:37:08 +08:00
xiaojunnuo
eba333de7a perf: 优化cname verify 2024-07-03 23:36:06 +08:00
xiaojunnuo
f47b35f6d5 perf: 创建dns解析后,强制等待60s 2024-07-03 23:27:35 +08:00
Greper
c04707c0f7 fix: 修复对Windows powershell 的支持
修复对Windows powershell 的支持 #73
2024-07-03 23:22:29 +08:00
ltxhhz
22ebcd4dd1 fixed #73 2024-07-03 18:30:38 +08:00
xiaojunnuo
d46dab4fdd v1.20.16 2024-07-02 00:36:55 +08:00
xiaojunnuo
d44849c53c build: prepare to build 2024-07-02 00:35:41 +08:00
xiaojunnuo
dbc5a3c6b3 docs: 1 2024-07-02 00:33:21 +08:00
xiaojunnuo
4a5fa767ed fix: 修复配置了cdn cname后申请失败的bug 2024-07-02 00:18:28 +08:00
xiaojunnuo
1b7debc6a4 Merge remote-tracking branch 'origin/acme_sync' into v2
# Conflicts:
#	packages/core/acme-client/package.json
#	packages/core/acme-client/src/auto.js
#	packages/core/acme-client/src/axios.js
#	packages/core/acme-client/src/http.js
2024-07-01 23:09:57 +08:00
xiaojunnuo
19a6b94680 v1.20.15 2024-06-28 16:06:55 +08:00
xiaojunnuo
65a72b8d60 build: prepare to build 2024-06-28 14:27:19 +08:00
xiaojunnuo
7f61cab101 perf: 支持windows文件上传 2024-06-27 16:38:43 +08:00
GitHub Actions Bot
692e2b5b96 🔱: [client] sync upgrade with 2 commits [trident-sync]
perf: 增加示例,FsInDrawer
2024-06-26 19:24:03 +00:00
xiaojunnuo
37caef38ad chore: 1 2024-06-26 19:07:37 +08:00
xiaojunnuo
9cc01db1d5 fix: 修复无法强制取消任务的bug 2024-06-26 19:05:35 +08:00
xiaojunnuo
9172440f79 chore: 1 2024-06-26 18:37:36 +08:00
xiaojunnuo
e0eb3a4413 perf: 腾讯云dns provider 支持腾讯云的accessId 2024-06-26 18:36:11 +08:00
xiaojunnuo
ae0f16bf35 chore: doc 2024-06-26 13:58:17 +08:00
xiaojunnuo
6c9ed162e3 chore: doc 2024-06-26 13:48:22 +08:00
xiaojunnuo
3849b52cdf chore: ssh优化 2024-06-25 12:28:37 +08:00
xiaojunnuo
9ecfcb5814 chore: ssh优化 2024-06-25 12:25:57 +08:00
xiaojunnuo
54ad09f755 chore: 1 2024-06-25 11:27:13 +08:00
xiaojunnuo
6ee4dc165b chore: 1 2024-06-25 11:22:11 +08:00
xiaojunnuo
8e2eb89696 chore: 1 2024-06-25 11:22:02 +08:00
xiaojunnuo
9d397cc8be chore: 1 2024-06-25 11:02:29 +08:00
xiaojunnuo
cbfb0755b3 chore: 1 2024-06-25 10:52:58 +08:00
xiaojunnuo
d8d127ee9d chore: 1 2024-06-24 09:59:14 +08:00
xiaojunnuo
0ed5430e80 chore: 1 2024-06-24 09:54:41 +08:00
xiaojunnuo
878c1f52fa chore: 1 2024-06-24 09:53:21 +08:00
GitHub Actions Bot
586d23fc55 🔱: [client] sync upgrade with 6 commits [trident-sync]
build: publish success
fix: 修复多级表头时列设置的问题

https://github.com/fast-crud/fast-crud/issues/175
chore: 1
fix: 修复element示例中远程搜索下拉框label不显示的bug

https://github.com/fast-crud/fast-crud/issues/422
fix: 修复独立使用对话框 openDialog方法await无返回值的bug
2024-06-23 19:23:47 +00:00
xiaojunnuo
6900452b49 v1.20.14 2024-06-24 01:05:48 +08:00
xiaojunnuo
b97e0e512d build: prepare to build 2024-06-24 01:04:25 +08:00
xiaojunnuo
f740ff517f fix: 修复修改密码功能异常问题 2024-06-24 01:04:03 +08:00
GitHub Actions Bot
162e10909b 🔱: [acme] sync upgrade with 7 commits [trident-sync]
Small crypto docs fix 2
Small crypto docs fix
Bump v5.3.1
Discourage use of cert subject common name, examples and docs
Style refactor docs and examples
Bump dependencies
2024-05-23 19:24:12 +00:00
GitHub Actions Bot
0f1ae6ccd9 🔱: [acme] sync upgrade with 3 commits [trident-sync]
Clean up eslintrc, style refactor and formatting fixes
Update auto.js

see https://github.com/publishlab/node-acme-client/issues/88#issuecomment-2105255828
2024-05-22 19:24:07 +00:00
391 changed files with 7263 additions and 3324 deletions

57
.github/workflows/build-image.yml vendored Normal file
View File

@@ -0,0 +1,57 @@
name: build-image
on:
push:
branches: ['v2']
paths:
- "build.trigger"
# schedule:
# - # 国际时间 19:17 执行北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
# - cron: '17 19 * * *'
permissions:
contents: read
jobs:
build-certd-image:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: get_certd_version
id: get_certd_version
uses: actions/github-script@v6
with:
result-encoding: string
script: |
const fs = require('fs');
const path = require('path');
const pnpmWorkspace = "./pnpm-workspace.yaml";
fs.unlinkSync(pnpmWorkspace)
const jsonFilePath = "./packages/ui/certd-server/package.json";
const jsonContent = fs.readFileSync(jsonFilePath, 'utf-8');
const pkg = JSON.parse(jsonContent)
console.log("certd_version:",pkg.version);
return pkg.version
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to aliyun container Registry
uses: docker/login-action@v3
with:
registry: registry.cn-shenzhen.aliyuncs.com
username: ${{ secrets.aliyun_cs_username }}
password: ${{ secrets.aliyun_cs_password }}
- name: Build and push
uses: docker/build-push-action@v6.5.0
with:
# platforms: linux/amd64,linux/arm64
push: true
context: ./packages/ui/
tags: |
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{steps.get_certd_version.outputs.result}}

5
.gitignore vendored
View File

@@ -34,3 +34,8 @@ gen
docker/image/workspace docker/image/workspace
/packages/core/lego /packages/core/lego
tsconfig.tsbuildinfo
test/**/*.js
/packages/ui/certd-server/data/db.sqlite
/packages/ui/certd-server/data/keys.yaml

3
.npmrc
View File

@@ -1 +1,2 @@
link-workspace-packages=deep link-workspace-packages=true
prefer-workspace-packages=true

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"printWidth": 160,
"bracketSpacing": true,
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "avoid"
}

View File

@@ -3,6 +3,84 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.2](https://github.com/certd/certd/compare/v1.22.1...v1.22.2) (2024-07-23)
### Bug Fixes
* 修复创建流水线时无法根据dns类型默认正确的dns授权的bug ([a2c43b5](https://github.com/certd/certd/commit/a2c43b50a6069ed48958fd142844a8568c2af452))
## [1.22.1](https://github.com/certd/certd/compare/v1.22.0...v1.22.1) (2024-07-20)
### Performance Improvements
* 创建证书任务可以选择lege插件 ([affef13](https://github.com/certd/certd/commit/affef130378030c517250c58a4e787b0fc85d7d1))
* 创建证书任务增加定时任务和邮件通知输入 ([427620d](https://github.com/certd/certd/commit/427620d34f3b8ad6933005faf1878908441a2453))
* 支持配置启动后自动触发一次任务 ([a5a0c1f](https://github.com/certd/certd/commit/a5a0c1f6e7a3f05e581005e491d5b102ee854412))
# [1.22.0](https://github.com/certd/certd/compare/v1.21.2...v1.22.0) (2024-07-19)
### Features
* 升级midway支持esm ([485e603](https://github.com/certd/certd/commit/485e603b5165c28bc08694997726eaf2a585ebe7))
* 支持lego海量DNS提供商 ([0bc6d0a](https://github.com/certd/certd/commit/0bc6d0a211920fb0084d705e1db67ee1e7262c44))
* 支持postgresql ([3b19bfb](https://github.com/certd/certd/commit/3b19bfb4291e89064b3b407a80dae092d54747d5))
### Performance Improvements
* 优化一些小细节 ([b168852](https://github.com/certd/certd/commit/b1688525dbbbfd67e0ab1cf5b4ddfbe9d394f370))
* 增加备案号设置 ([bd3d959](https://github.com/certd/certd/commit/bd3d959944db63a5690b55ee150e1007133868b9))
* 自动生成jwtkey无需手动配置 ([390e485](https://github.com/certd/certd/commit/390e4853a570390a97df6a3b3882579f9547eeb4))
## [1.21.2](https://github.com/certd/certd/compare/v1.21.1...v1.21.2) (2024-07-08)
### Performance Improvements
* 申请证书时可以选择跳过本地dns校验 ([fe91d94](https://github.com/certd/certd/commit/fe91d94090d22ed0a3ea753ba74dfaa1bf057c17))
## [1.21.1](https://github.com/certd/certd/compare/v1.21.0...v1.21.1) (2024-07-08)
### Performance Improvements
* 上传到主机支持设置不mkdirs ([5ba9831](https://github.com/certd/certd/commit/5ba9831ed1aa6ec6057df246f1035b36b9c41d2e))
* 说明优化,默认值优化 ([970c7fd](https://github.com/certd/certd/commit/970c7fd8a0f557770e973d8462ee5684ef742810))
# [1.21.0](https://github.com/certd/certd/compare/v1.20.17...v1.21.0) (2024-07-03)
### Features
* 支持zero ssl ([eade2c2](https://github.com/certd/certd/commit/eade2c2b681569f03e9cd466e7d5bcd6703ed492))
## [1.20.17](https://github.com/certd/certd/compare/v1.20.16...v1.20.17) (2024-07-03)
### Performance Improvements
* 创建dns解析后强制等待60s ([f47b35f](https://github.com/certd/certd/commit/f47b35f6d5bd7d675005c3e286b7e9a029201f8b))
* 文件上传提示由cert.crt改为cert.pem ([a09b0e4](https://github.com/certd/certd/commit/a09b0e48c176f3ed763791bd50322c29729f7c1c))
* 优化cname verify ([eba333d](https://github.com/certd/certd/commit/eba333de7a5b5ef4b0b7eaa904f578720102fa61))
## [1.20.16](https://github.com/certd/certd/compare/v1.20.15...v1.20.16) (2024-07-01)
### Bug Fixes
* 修复配置了cdn cname后申请失败的bug ([4a5fa76](https://github.com/certd/certd/commit/4a5fa767edc347d03d29a467e86c9a4d70b0220c))
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
### Bug Fixes
* 修复无法强制取消任务的bug ([9cc01db](https://github.com/certd/certd/commit/9cc01db1d569a5c45bb3e731f35d85df324a8e62))
### Performance Improvements
* 腾讯云dns provider 支持腾讯云的accessId ([e0eb3a4](https://github.com/certd/certd/commit/e0eb3a441384d474fe2923c69b25318264bdc9df))
* 支持windows文件上传 ([7f61cab](https://github.com/certd/certd/commit/7f61cab101fa13b4e88234e9ad47434e6130fed2))
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
### Bug Fixes
* 修复修改密码功能异常问题 ([f740ff5](https://github.com/certd/certd/commit/f740ff517f521dce361284c2c54bccc68aee0ea2))
## [1.20.13](https://github.com/certd/certd/compare/v1.20.12...v1.20.13) (2024-06-18) ## [1.20.13](https://github.com/certd/certd/compare/v1.20.12...v1.20.13) (2024-06-18)
### Bug Fixes ### Bug Fixes

View File

@@ -1,17 +1,20 @@
# CertD # CertD
CertD 是一个免费全自动申请和部署SSL证书的工具。 CertD 是一个免费全自动申请和自动部署更新SSL证书的工具。
后缀D取自linux守护进程的命名风格意为证书守护进程。 后缀D取自linux守护进程的命名风格意为证书守护进程。
关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签
## 一、特性 ## 一、特性
本项目不仅支持证书申请过程自动化,还可以自动化部署证书,让你的证书永不过期。 本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
* 全自动申请证书支持阿里云、腾讯云、华为云、Cloudflare注册的域名 * 全自动申请证书支持阿里云、腾讯云、华为云、Cloudflare注册的域名
* 全自动部署证书(目前支持服务器上传部署、部署到阿里云、腾讯云等) * 全自动部署更新证书(目前支持服务器上传部署、部署到阿里云、腾讯云等)
* 支持通配符域名 * 支持通配符域名
* 支持多个域名打到一个证书上 * 支持多个域名打到一个证书上
* 邮件通知 * 邮件通知
* 证书自动更新 * 证书自动更新
* 私有化部署,安全
* 免费、免费、免费([阿里云单个通配符域名证书最便宜也要1800/年](https://yundun.console.aliyun.com/?p=cas#/certExtend/buy/cn-hangzhou) * 免费、免费、免费([阿里云单个通配符域名证书最便宜也要1800/年](https://yundun.console.aliyun.com/?p=cas#/certExtend/buy/cn-hangzhou)
@@ -49,74 +52,71 @@ https://certd.handsfree.work/
1.2 安装docker 1.2 安装docker
https://docs.docker.com/engine/install/ https://docs.docker.com/engine/install/
选择对应的操作系统,按照官方文档执行命令即可
1.3 安装docker-compose
https://docs.docker.com/compose/install/linux/
### 2. 下载docker-compose.yaml文件 ### 2. 运行certd
[docker-compose.yaml下载](https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml) [docker-compose.yaml下载](https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml)
```bash ```bash
# 随便创建一个目录
mkdir certd mkdir certd
# 进入目录
cd certd cd certd
# wget下载docker-compose.yaml文件 # 下载docker-compose.yaml文件或者手动下载放到certd目录下
wget https://raw.githubusercontent.com/certd/certd/v2/docker/run/docker-compose.yaml
# 或者使用gitee地址
wget https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml wget https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml
# 根据需要修改里面的配置 # 可以根据需要修改里面的配置
# 1.修改镜像版本号【可选】 # 1.修改镜像版本号【可选】
# 2.配置数据保存路径【可选】 # 2.配置数据保存路径【可选】
# 3.配置certd_auth_jwt_secret【必须 # 3.修改端口号【可选
vi docker-compose.yaml vi docker-compose.yaml # 【可选】
# 启动certd
```
> 镜像版本号与release版本号同步
https://github.com/certd/certd/releases
### 3. 运行
```bash
# 如果docker compose是插件化安装
export CERTD_VERSION=latest
docker compose up -d docker compose up -d
#如果docker compose是独立安装
export CERTD_VERSION=latest
docker-compose up -d
``` ```
### 4. 访问 当前版本号: ![](https://img.shields.io/npm/v/%40certd%2Fpipeline)
http://your_server_ip:7001 如果提示 没有compose命令,请安装docker-compose
默认账号密码admin/123456 https://docs.docker.com/compose/install/linux/
记得修改密码
### 3. 访问
http://your_server_ip:7001
默认账号密码admin/123456
记得修改密码
### 5. 升级 ### 4. 升级
* 修改版本号,重新运行 `docker compose up -d` 即可 * 修改`docker-compose.yaml`中的镜像版本号
* 重新运行 `docker compose up -d` 即可
* 数据存在`/data/certd`目录下,不用担心数据丢失 * 数据存在`/data/certd`目录下,不用担心数据丢失
## 五、一些说明
## 五、一些说明及问题处理
### 1. 一些说明
* 本项目ssl证书提供商为letencrypt * 本项目ssl证书提供商为letencrypt
* 申请过程遵循acme协议 * 申请过程遵循acme协议
* 需要验证域名所有权一般有两种方式目前本项目仅支持dns-01 * 需要验证域名所有权一般有两种方式目前本项目仅支持dns-01
* http-01 在网站根目录下放置一份txt文件 * http-01 在网站根目录下放置一份txt文件
* dns-01 需要给域名添加txt解析记录通配符域名只能用这种方式 * dns-01 需要给域名添加txt解析记录通配符域名只能用这种方式
* 证书续期: * 证书续期:
* 实际上acme并没有续期概念 * 实际上没有办法不改变证书文件本身情况下直接续期或者续签
* 我们所说的续期,其实就是按照全套流程重新申请一份新证书。 * 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去
* 免费证书过期时间90天以后可能还会缩短所以自动化部署必不可少 * 免费证书过期时间90天以后可能还会缩短所以自动化部署必不可少
* 设置每天自动运行当证书过期前20天会自动重新申请证书并部署 * 设置每天自动运行当证书过期前20天会自动重新申请证书并部署
### 2. 问题处理 ## 六、不同平台的设置说明
#### 2.1 忘记管理员密码
* [Cloudflare](./doc/cf/cf.md)
* [腾讯云](./doc/tencent/tencent.md)
* [windows主机](./doc/host/host.md)
## 七、问题处理
### 7.1 忘记管理员密码
解决方法如下: 解决方法如下:
1. 修改docker-compose.yaml文件将环境变量`certd_system_resetAdminPassword`改为`true` 1. 修改docker-compose.yaml文件将环境变量`certd_system_resetAdminPassword`改为`true`
```yaml ```yaml
@@ -136,9 +136,9 @@ docker logs -f --tail 500 certd
```shell ```shell
docker compose up -d docker compose up -d
``` ```
5. 使用admin/123456登录系统请及时修改管理员密码 5. 使用`admin/123456`登录系统,请及时修改管理员密码
## 、联系作者 ## 、联系作者
如有疑问欢迎加入群聊请备注certd 如有疑问欢迎加入群聊请备注certd
* QQ群141236433 * QQ群141236433
* 微信群: * 微信群:
@@ -150,7 +150,7 @@ docker compose up -d
<img height="230" src="./doc/images/me.png"> <img height="230" src="./doc/images/me.png">
</p> </p>
## 捐赠 ## 九、捐赠
媳妇儿说:“一天到晚搞开源,也不管管老婆孩子!😡😡😡” 媳妇儿说:“一天到晚搞开源,也不管管老婆孩子!😡😡😡”
拜托各位捐赠支持一下,让媳妇儿开心开心,我也能有更多时间进行开源项目,感谢🙏🙏🙏 拜托各位捐赠支持一下,让媳妇儿开心开心,我也能有更多时间进行开源项目,感谢🙏🙏🙏
<p align="center"> <p align="center">
@@ -158,12 +158,16 @@ docker compose up -d
</p> </p>
## 、贡献代码 ## 、贡献代码
[贡献插件教程](./plugin.md) [贡献插件教程](./plugin.md)
## 、我的其他项目 ## 十一、我的其他项目求Star
* [袖手GPT](https://ai.handsfree.work/) ChatGPT国内可用无需FQ每日免费额度 * [袖手GPT](https://ai.handsfree.work/) ChatGPT国内可用无需FQ每日免费额度
* [fast-crud](https://gitee.com/fast-crud/fast-crud/) 基于vue3的crud快速开发框架 * [fast-crud](https://gitee.com/fast-crud/fast-crud/) 基于vue3的crud快速开发框架
* [dev-sidecar](https://github.com/docmirror/dev-sidecar/) 直连访问github工具无需FQ解决github无法访问的问题 * [dev-sidecar](https://github.com/docmirror/dev-sidecar/) 直连访问github工具无需FQ解决github无法访问的问题
## 十二、版本更新日志
https://github.com/certd/certd/blob/v2/CHANGELOG.md

1
build.trigger Normal file
View File

@@ -0,0 +1 @@
3

View File

@@ -1,6 +1,5 @@
import http from 'axios' import http from 'axios'
import fs from 'fs' import fs from 'fs'
//读取 packages/core/pipline/package.json的版本号 //读取 packages/core/pipline/package.json的版本号
import {default as packageJson} from './packages/core/pipeline/package.json' assert { type: "json" }; import {default as packageJson} from './packages/core/pipeline/package.json' assert { type: "json" };
@@ -33,8 +32,9 @@ async function getPackages(directoryPath) {
async function getAllPackages() { async function getAllPackages() {
const base = await getPackages("./packages/core") const base = await getPackages("./packages/core")
const plugins = await getPackages("./packages/plugins") const plugins = await getPackages("./packages/plugins")
const libs = await getPackages("./packages/libs")
return base.concat(plugins) return base.concat(plugins).concat(libs)
} }
async function sync() { async function sync() {
@@ -49,7 +49,7 @@ async function sync() {
data: {} data: {}
}) })
console.log(`sync success:${pkg}`) console.log(`sync success:${pkg}`)
await sleep(100*1000) await sleep(30*1000)
} }
} }
@@ -79,7 +79,7 @@ async function triggerBuild() {
} }
}) })
console.log(`webhook success:${webhook}`) console.log(`webhook success:${webhook}`)
await sleep(10*60*1000) await sleep(30*60*1000)
} }
} }
@@ -87,9 +87,9 @@ async function triggerBuild() {
async function start() { async function start() {
// await build() // await build()
console.log("等待60秒") console.log("等待60秒")
await sleep(200 * 1000) await sleep(100* 1000)
await sync() await sync()
await sleep(60 * 1000) await sleep(100 * 1000)
await triggerBuild() await triggerBuild()
} }

15
doc/cf/cf.md Normal file
View File

@@ -0,0 +1,15 @@
# Cloudflare
## CF Token申请
### 申请地址:
https://dash.cloudflare.com/profile/api-tokens
### 权限设置:
需要设置权限和资源范围
权限包括Zone.Zone.edit, Zone.DNS.edit
资源范围要包含对应域名推荐直接设置为All Zones
最终效果如下,可以切换语言为英文对比如下图检查
![](./cf_token.png)

BIN
doc/cf/cf_token.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

24
doc/host/host.md Normal file
View File

@@ -0,0 +1,24 @@
# 远程主机
远程主机基于ssh协议通过ssh连接远程主机执行命令。
## windows开启OpenSSH Server
1. 安装OpenSSH Server
请前往Microsoft官方文档查看如何开启openSSH
https://learn.microsoft.com/zh-cn/windows-server/administration/openssh/openssh_install_firstuse?tabs=gui#install-openssh-for-windows
2. 启动OpenSSH Server服务
```
win+R 弹出运行对话框,输入 services.msc 打开服务管理器
找到 OpenSSH SSH Server
启动ssh server服务并且设置为自动启动
```
3. 测试ssh登录
使用你常用的ssh客户端连接你的windows主机进行测试
```cmd
# 如何确定你用户名
C:\Users\xiaoj>
↑↑↑↑---------这个就是windows ssh的登录用户名
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

16
doc/tencent/tencent.md Normal file
View File

@@ -0,0 +1,16 @@
# 腾讯云
## DNSPOD 授权设置
目前腾讯云管理的域名的dns暂时只支持从DNSPOD进行设置
打开 https://console.dnspod.cn/account/token/apikey
然后按如下方式获取DNSPOD的授权
![](./dnspod-token.png)
## 腾讯云API密钥设置
腾讯云其他部署需要API密钥需要在腾讯云控制台进行设置
打开 https://console.cloud.tencent.com/cam/capi
然后按如下方式获取腾讯云的API密钥
![](./tencent-access.png)

View File

@@ -1,20 +0,0 @@
FROM registry.cn-shenzhen.aliyuncs.com/handsfree/node:18-alpine
EXPOSE 7001
ENV NODE_ENV production
ENV MIDWAY_SERVER_ENV production
WORKDIR /app/
#RUN npm install -g pnpm
#RUN npm install cross-env -g --registry=https://registry.npmmirror.com
#RUN npm install pm2 -g --registry=https://registry.npmmirror.com
#RUN pm2 install pm2-logrotate
ADD ./workspace/certd-server/ /app/
RUN sed -i "s/workspace://g" "/app/package.json"
RUN yarn install --production --registry=https://registry.npmmirror.com
#RUN yarn install --production
RUN npm run build
#CMD ["pm2-runtime", "start", "./bootstrap.js","--name", "certd","-i","1"]
CMD ["npm", "run","start"]

View File

@@ -1,37 +0,0 @@
#!/bin/bash
set -e
echo "请先输入一个版本号:"
read version
echo "您输入的版本号是: $version"
echo "登录aliyun镜像仓库"
sudo docker login --username=252959493@qq.com registry.cn-shenzhen.aliyuncs.com
build=$(pwd)
cd ../../
root=$(pwd)
echo "安装依赖"
#pnpm install --registry=https://registry.npmmirror.com
pnpm install
echo "client build"
cd $root/packages/ui/certd-client
pnpm run build
echo "client build success"
echo "server build"
cd $root/packages/ui/certd-server
pnpm run build
echo "server build success"
echo "rm node_modules"
rm ./node_modules -rf
echo "copy to workspace"
mkdir -p $build/workspace/certd-server
\cp ./* $build/workspace/certd-server -rf
\cp ../certd-client/dist/* $build/workspace/certd-server/public/ -rf
#export TAG=$version
#sudo -E docker compose build
#sudo -E docker compose push

View File

@@ -1,26 +1,27 @@
version: '3.3' version: '3.3'
services: services:
certd: certd:
# 镜像 # ↓↓↓↓↓ --- 1、 修改镜像版本号,或者干脆写成latest 如果设置了环境变量 export CERTD_VERSION=latest这里可以不修改 # 镜像 # ↓↓↓↓↓ --- 1、 镜像版本号,建议改成固定版本号【可选】
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${CERTD_VERSION} image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
container_name: certd # 容器名 container_name: certd # 容器名
restart: unless-stopped # 自动重启 restart: unless-stopped # 自动重启
volumes: volumes:
# ↓↓↓↓↓ ------------------------------------------------------- 2、 修改数据库以及证书存储路径【可选】 # ↓↓↓↓↓ ------------------------------------------------------- 2、 数据库以及证书存储路径,默认存在宿主机的/data/certd/目录下【可选】
- /data/certd:/app/data - /data/certd:/app/data
ports: # 端口映射 ports: # 端口映射
# ↓↓↓↓ 如果端口有冲突可以修改第一个7001为其他不冲突的端口号 # ↓↓↓↓ ----------------------------------------------------------3、如果端口有冲突可以修改第一个7001为其他不冲突的端口号【可选】
- "7001:7001" - "7001:7001"
environment: # 环境变量 environment: # 环境变量
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- certd_auth_jwt_secret=changeme
# ↑↑↑↑↑ ---------------------------------- 3、 修改成你的自定义密钥【必须,安全需要】
- certd_system_resetAdminPassword=false - certd_system_resetAdminPassword=false
# ↑↑↑↑↑ 如果忘记管理员密码可以设置为true重启之后管理员密码将改成123456然后请及时修改回false # ↑↑↑↑↑---------------------------4、如果忘记管理员密码可以设置为true重启之后管理员密码将改成123456然后请及时修改回false【可选】
- certd_cron_immediateTriggerOnce=false
# ↑↑↑↑↑---------------------------5、如果设置为true启动后所有配置了cron的流水线任务都将被立即触发一次【可选】
- VITE_APP_ICP_NO=
# ↑↑↑↑↑ -----------------------------------------6、这里可以设置备案号【可选】
# 设置环境变量即可自定义certd配置 # 设置环境变量即可自定义certd配置
# 服务端配置项见: packages/ui/certd-server/src/config/config.default.ts # 服务端配置项见: packages/ui/certd-server/src/config/config.default.ts
# 服务端配置规则: certd_ + 配置项, 点号用_代替 # 服务端配置规则: certd_ + 配置项, 点号用_代替
# 如jwt密钥配置为 auth.jwt.secret则设置环境变量 certd_auth_jwt_secret=changeme
# 客户端配置项见: packages/ui/certd-client/.env # 客户端配置项见: packages/ui/certd-client/.env
# 按实际名称配置环境变量即可,如: VITE_APP_API=http://localhost:7001 # 按实际名称配置环境变量即可,如: VITE_APP_API=http://localhost:7001

View File

@@ -1,13 +0,0 @@
#!/bin/bash
set -e
# 判断$CERTD_VERSION 是否存在
if [ -n "$CERTD_VERSION" ]; then
echo "CERTD_VERSION is set = $CERTD_VERSION"
else
echo "CERTD_VERSION is not set"
echo "请先输入一个版本号(如 1.0.6)"
read CERTD_VERSION
fi
echo "您输入的版本号是: $CERTD_VERSION"
sudo -E docker compose up -d

View File

@@ -9,5 +9,5 @@
} }
}, },
"npmClient": "pnpm", "npmClient": "pnpm",
"version": "1.20.13" "version": "1.22.2"
} }

View File

@@ -12,15 +12,17 @@
"scripts": { "scripts": {
"start": "lerna bootstrap --hoist", "start": "lerna bootstrap --hoist",
"i-all": "lerna link && lerna exec npm install ", "i-all": "lerna link && lerna exec npm install ",
"publish": "npm run prepublishOnly1 && lerna publish --conventional-commits && npm run afterpublishOnly && npm run deploy1", "publish": "npm run prepublishOnly1 && lerna publish --conventional-commits --create-release github && npm run afterpublishOnly && npm run deploy1",
"afterpublishOnly": "", "afterpublishOnly": "",
"prepublishOnly1": "npm run before-build && lerna run build ", "prepublishOnly1": "npm run check && npm run before-build && lerna run build ",
"before-build": "cd ./packages/core/acme-client && time /t >build.md && git add ./build.md && git commit -m \"build: prepare to build\"", "before-build": "cd ./packages/core/acme-client && time /t >build.md && git add ./build.md && git commit -m \"build: prepare to build\"",
"deploy1": "node --experimental-json-modules deploy.js " "deploy1": "node --experimental-json-modules deploy.js ",
"check": "node --experimental-json-modules publish-check.js",
"init": "lerna run build"
}, },
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"axios": "^1.4.0", "axios": "^1.7.2",
"lodash": "^4.17.21" "lodash": "^4.17.21"
}, },
"workspaces": [ "workspaces": [

View File

@@ -9,15 +9,8 @@ env:
rules: rules:
indent: [2, 4, { SwitchCase: 1, VariableDeclarator: 1 }] indent: [2, 4, { SwitchCase: 1, VariableDeclarator: 1 }]
brace-style: [2, 'stroustrup', { allowSingleLine: true }] brace-style: [2, 'stroustrup', { allowSingleLine: true }]
space-before-function-paren: [2, { anonymous: 'never', named: 'never' }]
func-names: 0 func-names: 0
prefer-destructuring: 0
object-curly-newline: 0
class-methods-use-this: 0 class-methods-use-this: 0
wrap-iife: [2, 'inside']
no-param-reassign: 0 no-param-reassign: 0
comma-dangle: [2, 'never']
max-len: [1, 200, 2, { ignoreUrls: true, ignoreComments: false }] max-len: [1, 200, 2, { ignoreUrls: true, ignoreComments: false }]
no-multiple-empty-lines: [2, { max: 2, maxBOF: 0, maxEOF: 0 }]
prefer-object-spread: 0
import/no-useless-path-segments: 0 import/no-useless-path-segments: 0

View File

@@ -5,8 +5,10 @@
set -euo pipefail set -euo pipefail
# Download and install # Download and install
wget -nv "https://github.com/letsencrypt/pebble/releases/download/v${PEBBLECTS_VERSION}/pebble-challtestsrv_linux-amd64" -O /usr/local/bin/pebble-challtestsrv wget -nv "https://github.com/letsencrypt/pebble/releases/download/v${PEBBLECTS_VERSION}/pebble-challtestsrv-linux-amd64.tar.gz" -O /tmp/pebble-challtestsrv.tar.gz
tar zxvf /tmp/pebble-challtestsrv.tar.gz -C /tmp
mv /tmp/pebble-challtestsrv-linux-amd64/linux/amd64/pebble-challtestsrv /usr/local/bin/pebble-challtestsrv
chown root:root /usr/local/bin/pebble-challtestsrv chown root:root /usr/local/bin/pebble-challtestsrv
chmod 0755 /usr/local/bin/pebble-challtestsrv chmod 0755 /usr/local/bin/pebble-challtestsrv

View File

@@ -22,8 +22,10 @@ wget -nv "https://raw.githubusercontent.com/letsencrypt/pebble/v${PEBBLE_VERSION
wget -nv "https://raw.githubusercontent.com/letsencrypt/pebble/v${PEBBLE_VERSION}/test/config/${CONFIG_NAME}" -O /etc/pebble/pebble.json wget -nv "https://raw.githubusercontent.com/letsencrypt/pebble/v${PEBBLE_VERSION}/test/config/${CONFIG_NAME}" -O /etc/pebble/pebble.json
# Download and install Pebble # Download and install Pebble
wget -nv "https://github.com/letsencrypt/pebble/releases/download/v${PEBBLE_VERSION}/pebble_linux-amd64" -O /usr/local/bin/pebble wget -nv "https://github.com/letsencrypt/pebble/releases/download/v${PEBBLE_VERSION}/pebble-linux-amd64.tar.gz" -O /tmp/pebble.tar.gz
tar zxvf /tmp/pebble.tar.gz -C /tmp
mv /tmp/pebble-linux-amd64/linux/amd64/pebble /usr/local/bin/pebble
chown root:root /usr/local/bin/pebble chown root:root /usr/local/bin/pebble
chmod 0755 /usr/local/bin/pebble chmod 0755 /usr/local/bin/pebble

View File

@@ -1,4 +1,3 @@
---
name: test name: test
on: [push, pull_request] on: [push, pull_request]
@@ -9,10 +8,9 @@ jobs:
strategy: strategy:
matrix: matrix:
node: [16, 18, 20, 22] node: [16, 18, 20]
eab: [0, 1] eab: [0, 1]
# #
# Environment # Environment
# #
@@ -21,9 +19,9 @@ jobs:
FORCE_COLOR: 1 FORCE_COLOR: 1
NPM_CONFIG_COLOR: always NPM_CONFIG_COLOR: always
PEBBLE_VERSION: 2.3.1 PEBBLE_VERSION: 2.6.0
PEBBLE_ALTERNATE_ROOTS: 2 PEBBLE_ALTERNATE_ROOTS: 2
PEBBLECTS_VERSION: 2.3.1 PEBBLECTS_VERSION: 2.6.0
PEBBLECTS_DNS_PORT: 8053 PEBBLECTS_DNS_PORT: 8053
COREDNS_VERSION: 1.11.1 COREDNS_VERSION: 1.11.1
@@ -41,7 +39,6 @@ jobs:
ACME_HTTP_PORT: 5002 ACME_HTTP_PORT: 5002
ACME_HTTPS_PORT: 5003 ACME_HTTPS_PORT: 5003
# #
# Pipeline # Pipeline
# #

View File

@@ -3,6 +3,57 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.2](https://github.com/publishlab/node-acme-client/compare/v1.22.1...v1.22.2) (2024-07-23)
**Note:** Version bump only for package @certd/acme-client
## [1.22.1](https://github.com/publishlab/node-acme-client/compare/v1.22.0...v1.22.1) (2024-07-20)
**Note:** Version bump only for package @certd/acme-client
# [1.22.0](https://github.com/publishlab/node-acme-client/compare/v1.21.2...v1.22.0) (2024-07-19)
### Features
* 升级midway支持esm ([485e603](https://github.com/publishlab/node-acme-client/commit/485e603b5165c28bc08694997726eaf2a585ebe7))
## [1.21.2](https://github.com/publishlab/node-acme-client/compare/v1.21.1...v1.21.2) (2024-07-08)
**Note:** Version bump only for package @certd/acme-client
## [1.21.1](https://github.com/publishlab/node-acme-client/compare/v1.21.0...v1.21.1) (2024-07-08)
**Note:** Version bump only for package @certd/acme-client
# [1.21.0](https://github.com/publishlab/node-acme-client/compare/v1.20.17...v1.21.0) (2024-07-03)
### Features
* 支持zero ssl ([eade2c2](https://github.com/publishlab/node-acme-client/commit/eade2c2b681569f03e9cd466e7d5bcd6703ed492))
## [1.20.17](https://github.com/publishlab/node-acme-client/compare/v1.20.16...v1.20.17) (2024-07-03)
### Performance Improvements
* 创建dns解析后强制等待60s ([f47b35f](https://github.com/publishlab/node-acme-client/commit/f47b35f6d5bd7d675005c3e286b7e9a029201f8b))
* 优化cname verify ([eba333d](https://github.com/publishlab/node-acme-client/commit/eba333de7a5b5ef4b0b7eaa904f578720102fa61))
## [1.20.16](https://github.com/publishlab/node-acme-client/compare/v1.20.15...v1.20.16) (2024-07-01)
### Bug Fixes
* 修复配置了cdn cname后申请失败的bug ([4a5fa76](https://github.com/publishlab/node-acme-client/commit/4a5fa767edc347d03d29a467e86c9a4d70b0220c))
## [1.20.15](https://github.com/publishlab/node-acme-client/compare/v1.20.14...v1.20.15) (2024-06-28)
### Performance Improvements
* 腾讯云dns provider 支持腾讯云的accessId ([e0eb3a4](https://github.com/publishlab/node-acme-client/commit/e0eb3a441384d474fe2923c69b25318264bdc9df))
## [1.20.14](https://github.com/publishlab/node-acme-client/compare/v1.20.13...v1.20.14) (2024-06-23)
**Note:** Version bump only for package @certd/acme-client
## [1.20.13](https://github.com/publishlab/node-acme-client/compare/v1.20.12...v1.20.13) (2024-06-18) ## [1.20.13](https://github.com/publishlab/node-acme-client/compare/v1.20.12...v1.20.13) (2024-06-18)
**Note:** Version bump only for package @certd/acme-client **Note:** Version bump only for package @certd/acme-client
@@ -45,7 +96,12 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
# Changelog # Changelog
## v5.3.1 ## v5.4.0
* `added` Directory URLs for [Google](https://cloud.google.com/certificate-manager/docs/overview) ACME provider
* `fixed` Invalidate ACME directory cache after 24 hours
## v5.3.1 (2024-05-22)
* `fixed` Allow `client.auto()` being called with an empty CSR common name * `fixed` Allow `client.auto()` being called with an empty CSR common name
* `fixed` Bug when calling `updateAccountKey()` with external account binding * `fixed` Bug when calling `updateAccountKey()` with external account binding
@@ -82,7 +138,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
* `fixed` Upgrade `axios@0.26.1` * `fixed` Upgrade `axios@0.26.1`
* `fixed` Upgrade `node-forge@1.3.0` - [CVE-2022-24771](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24771), [CVE-2022-24772](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24772), [CVE-2022-24773](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24773) * `fixed` Upgrade `node-forge@1.3.0` - [CVE-2022-24771](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24771), [CVE-2022-24772](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24772), [CVE-2022-24773](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24773)
## 4.2.4 (2022-03-19) ## v4.2.4 (2022-03-19)
* `fixed` Use SHA-256 when signing CSRs * `fixed` Use SHA-256 when signing CSRs

View File

@@ -9,13 +9,13 @@ This module is written to handle communication with a Boulder/Let's Encrypt-styl
## Compatibility ## Compatibility
| acme-client | Node.js | | | acme-client | Node.js | |
| ------------- | --------- | ----------------------------------------- | | ----------- | ------- | ----------------------------------------- |
| v5.x | >= v16 | [Upgrade guide](docs/upgrade-v5.md) | | v5.x | >= v16 | [Upgrade guide](docs/upgrade-v5.md) |
| v4.x | >= v10 | [Changelog](CHANGELOG.md#v400-2020-05-29) | | v4.x | >= v10 | [Changelog](CHANGELOG.md#v400-2020-05-29) |
| v3.x | >= v8 | [Changelog](CHANGELOG.md#v300-2019-07-13) | | v3.x | >= v8 | [Changelog](CHANGELOG.md#v300-2019-07-13) |
| v2.x | >= v4 | [Changelog](CHANGELOG.md#v200-2018-04-02) | | v2.x | >= v4 | [Changelog](CHANGELOG.md#v200-2018-04-02) |
| v1.x | >= v4 | [Changelog](CHANGELOG.md#v100-2017-10-20) | | v1.x | >= v4 | [Changelog](CHANGELOG.md#v100-2017-10-20) |
## Table of contents ## Table of contents
@@ -49,7 +49,7 @@ const accountPrivateKey = '<PEM encoded private key>';
const client = new acme.Client({ const client = new acme.Client({
directoryUrl: acme.directory.letsencrypt.staging, directoryUrl: acme.directory.letsencrypt.staging,
accountKey: accountPrivateKey accountKey: accountPrivateKey,
}); });
``` ```
@@ -59,6 +59,9 @@ const client = new acme.Client({
acme.directory.buypass.staging; acme.directory.buypass.staging;
acme.directory.buypass.production; acme.directory.buypass.production;
acme.directory.google.staging;
acme.directory.google.production;
acme.directory.letsencrypt.staging; acme.directory.letsencrypt.staging;
acme.directory.letsencrypt.production; acme.directory.letsencrypt.production;
@@ -75,8 +78,8 @@ const client = new acme.Client({
accountKey: accountPrivateKey, accountKey: accountPrivateKey,
externalAccountBinding: { externalAccountBinding: {
kid: 'YOUR-EAB-KID', kid: 'YOUR-EAB-KID',
hmacKey: 'YOUR-EAB-HMAC-KEY' hmacKey: 'YOUR-EAB-HMAC-KEY',
} },
}); });
``` ```
@@ -90,7 +93,7 @@ In some cases, for example with some EAB providers, this account creation step m
const client = new acme.Client({ const client = new acme.Client({
directoryUrl: acme.directory.letsencrypt.staging, directoryUrl: acme.directory.letsencrypt.staging,
accountKey: accountPrivateKey, accountKey: accountPrivateKey,
accountUrl: 'https://acme-v02.api.letsencrypt.org/acme/acct/12345678' accountUrl: 'https://acme-v02.api.letsencrypt.org/acme/acct/12345678',
}); });
``` ```
@@ -113,8 +116,7 @@ const privateRsaKey = await acme.crypto.createPrivateRsaKey();
const privateEcdsaKey = await acme.crypto.createPrivateEcdsaKey(); const privateEcdsaKey = await acme.crypto.createPrivateEcdsaKey();
const [certificateKey, certificateCsr] = await acme.crypto.createCsr({ const [certificateKey, certificateCsr] = await acme.crypto.createCsr({
commonName: '*.example.com', altNames: ['example.com', '*.example.com'],
altNames: ['example.com']
}); });
``` ```
@@ -139,7 +141,7 @@ const autoOpts = {
email: 'test@example.com', email: 'test@example.com',
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn: async (authz, challenge, keyAuthorization) => {}, challengeCreateFn: async (authz, challenge, keyAuthorization) => {},
challengeRemoveFn: async (authz, challenge, keyAuthorization) => {} challengeRemoveFn: async (authz, challenge, keyAuthorization) => {},
}; };
const certificate = await client.auto(autoOpts); const certificate = await client.auto(autoOpts);
@@ -156,7 +158,7 @@ To modify challenge priority, provide a list of challenge types in `challengePri
```js ```js
await client.auto({ await client.auto({
..., ...,
challengePriority: ['http-01', 'dns-01'] challengePriority: ['http-01', 'dns-01'],
}); });
``` ```
@@ -171,7 +173,7 @@ To completely disable `acme-client`s internal challenge verification, enable `sk
```js ```js
await client.auto({ await client.auto({
..., ...,
skipChallengeVerification: true skipChallengeVerification: true,
}); });
``` ```
@@ -185,14 +187,14 @@ For more fine-grained control you can interact with the ACME API using the metho
```js ```js
const account = await client.createAccount({ const account = await client.createAccount({
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
contact: ['mailto:test@example.com'] contact: ['mailto:test@example.com'],
}); });
const order = await client.createOrder({ const order = await client.createOrder({
identifiers: [ identifiers: [
{ type: 'dns', value: 'example.com' }, { type: 'dns', value: 'example.com' },
{ type: 'dns', value: '*.example.com' } { type: 'dns', value: '*.example.com' },
] ],
}); });
``` ```
@@ -207,7 +209,7 @@ const acme = require('acme-client');
acme.axios.defaults.proxy = { acme.axios.defaults.proxy = {
host: '127.0.0.1', host: '127.0.0.1',
port: 9000 port: 9000,
}; };
``` ```

View File

@@ -1 +1 @@
00:54 02:24

View File

@@ -63,7 +63,7 @@ Create ACME client instance
```js ```js
const client = new acme.Client({ const client = new acme.Client({
directoryUrl: acme.directory.letsencrypt.staging, directoryUrl: acme.directory.letsencrypt.staging,
accountKey: 'Private key goes here' accountKey: 'Private key goes here',
}); });
``` ```
**Example** **Example**
@@ -75,7 +75,7 @@ const client = new acme.Client({
accountUrl: 'Optional account URL goes here', accountUrl: 'Optional account URL goes here',
backoffAttempts: 10, backoffAttempts: 10,
backoffMin: 5000, backoffMin: 5000,
backoffMax: 30000 backoffMax: 30000,
}); });
``` ```
**Example** **Example**
@@ -86,8 +86,8 @@ const client = new acme.Client({
accountKey: 'Private key goes here', accountKey: 'Private key goes here',
externalAccountBinding: { externalAccountBinding: {
kid: 'YOUR-EAB-KID', kid: 'YOUR-EAB-KID',
hmacKey: 'YOUR-EAB-HMAC-KEY' hmacKey: 'YOUR-EAB-HMAC-KEY',
} },
}); });
``` ```
<a name="AcmeClient+getTermsOfServiceUrl"></a> <a name="AcmeClient+getTermsOfServiceUrl"></a>
@@ -145,7 +145,7 @@ https://datatracker.ietf.org/doc/html/rfc8555#section-7.3
Create a new account Create a new account
```js ```js
const account = await client.createAccount({ const account = await client.createAccount({
termsOfServiceAgreed: true termsOfServiceAgreed: true,
}); });
``` ```
**Example** **Example**
@@ -153,7 +153,7 @@ Create a new account with contact info
```js ```js
const account = await client.createAccount({ const account = await client.createAccount({
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
contact: ['mailto:test@example.com'] contact: ['mailto:test@example.com'],
}); });
``` ```
<a name="AcmeClient+updateAccount"></a> <a name="AcmeClient+updateAccount"></a>
@@ -174,7 +174,7 @@ https://datatracker.ietf.org/doc/html/rfc8555#section-7.3.2
Update existing account Update existing account
```js ```js
const account = await client.updateAccount({ const account = await client.updateAccount({
contact: ['mailto:foo@example.com'] contact: ['mailto:foo@example.com'],
}); });
``` ```
<a name="AcmeClient+updateAccountKey"></a> <a name="AcmeClient+updateAccountKey"></a>
@@ -218,8 +218,8 @@ Create a new order
const order = await client.createOrder({ const order = await client.createOrder({
identifiers: [ identifiers: [
{ type: 'dns', value: 'example.com' }, { type: 'dns', value: 'example.com' },
{ type: 'dns', value: 'test.example.com' } { type: 'dns', value: 'test.example.com' },
] ],
}); });
``` ```
<a name="AcmeClient+getOrder"></a> <a name="AcmeClient+getOrder"></a>
@@ -452,7 +452,7 @@ Revoke certificate with reason
```js ```js
const certificate = { ... }; // Previously created certificate const certificate = { ... }; // Previously created certificate
const result = await client.revokeCertificate(certificate, { const result = await client.revokeCertificate(certificate, {
reason: 4 reason: 4,
}); });
``` ```
<a name="AcmeClient+auto"></a> <a name="AcmeClient+auto"></a>
@@ -479,7 +479,7 @@ Auto mode
Order a certificate using auto mode Order a certificate using auto mode
```js ```js
const [certificateKey, certificateRequest] = await acme.crypto.createCsr({ const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
commonName: 'test.example.com' altNames: ['test.example.com'],
}); });
const certificate = await client.auto({ const certificate = await client.auto({
@@ -491,14 +491,14 @@ const certificate = await client.auto({
}, },
challengeRemoveFn: async (authz, challenge, keyAuthorization) => { challengeRemoveFn: async (authz, challenge, keyAuthorization) => {
// Clean up challenge here // Clean up challenge here
} },
}); });
``` ```
**Example** **Example**
Order a certificate using auto mode with preferred chain Order a certificate using auto mode with preferred chain
```js ```js
const [certificateKey, certificateRequest] = await acme.crypto.createCsr({ const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
commonName: 'test.example.com' altNames: ['test.example.com'],
}); });
const certificate = await client.auto({ const certificate = await client.auto({
@@ -507,7 +507,7 @@ const certificate = await client.auto({
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
preferredChain: 'DST Root CA X3', preferredChain: 'DST Root CA X3',
challengeCreateFn: async () => {}, challengeCreateFn: async () => {},
challengeRemoveFn: async () => {} challengeRemoveFn: async () => {},
}); });
``` ```
<a name="Client"></a> <a name="Client"></a>

View File

@@ -239,29 +239,30 @@ Create a Certificate Signing Request
Create a Certificate Signing Request Create a Certificate Signing Request
```js ```js
const [certificateKey, certificateRequest] = await acme.crypto.createCsr({ const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
commonName: 'test.example.com' altNames: ['test.example.com'],
}); });
``` ```
**Example** **Example**
Certificate Signing Request with both common and alternative names Certificate Signing Request with both common and alternative names
> *Warning*: Certificate subject common name has been [deprecated](https://letsencrypt.org/docs/glossary/#def-CN) and its use is [discouraged](https://cabforum.org/uploads/BRv1.2.3.pdf).
```js ```js
const [certificateKey, certificateRequest] = await acme.crypto.createCsr({ const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
keySize: 4096, keySize: 4096,
commonName: 'test.example.com', commonName: 'test.example.com',
altNames: ['foo.example.com', 'bar.example.com'] altNames: ['foo.example.com', 'bar.example.com'],
}); });
``` ```
**Example** **Example**
Certificate Signing Request with additional information Certificate Signing Request with additional information
```js ```js
const [certificateKey, certificateRequest] = await acme.crypto.createCsr({ const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
commonName: 'test.example.com', altNames: ['test.example.com'],
country: 'US', country: 'US',
state: 'California', state: 'California',
locality: 'Los Angeles', locality: 'Los Angeles',
organization: 'The Company Inc.', organization: 'The Company Inc.',
organizationUnit: 'IT Department', organizationUnit: 'IT Department',
emailAddress: 'contact@example.com' emailAddress: 'contact@example.com',
}); });
``` ```
**Example** **Example**
@@ -270,8 +271,9 @@ Certificate Signing Request with ECDSA private key
const certificateKey = await acme.crypto.createPrivateEcdsaKey(); const certificateKey = await acme.crypto.createPrivateEcdsaKey();
const [, certificateRequest] = await acme.crypto.createCsr({ const [, certificateRequest] = await acme.crypto.createCsr({
commonName: 'test.example.com' altNames: ['test.example.com'],
}, certificateKey); }, certificateKey);
```
<a name="createAlpnCertificate"></a> <a name="createAlpnCertificate"></a>
## createAlpnCertificate(authz, keyAuthorization, [keyPem]) ⇒ <code>Promise.&lt;Array.&lt;buffer&gt;&gt;</code> ## createAlpnCertificate(authz, keyAuthorization, [keyPem]) ⇒ <code>Promise.&lt;Array.&lt;buffer&gt;&gt;</code>
@@ -298,6 +300,7 @@ Create a ALPN certificate with ECDSA private key
```js ```js
const alpnKey = await acme.crypto.createPrivateEcdsaKey(); const alpnKey = await acme.crypto.createPrivateEcdsaKey();
const [, alpnCertificate] = await acme.crypto.createAlpnCertificate(authz, keyAuthorization, alpnKey); const [, alpnCertificate] = await acme.crypto.createAlpnCertificate(authz, keyAuthorization, alpnKey);
```
<a name="isAlpnCertificateAuthorizationValid"></a> <a name="isAlpnCertificateAuthorizationValid"></a>
## isAlpnCertificateAuthorizationValid(certPem, keyAuthorization) ⇒ <code>boolean</code> ## isAlpnCertificateAuthorizationValid(certPem, keyAuthorization) ⇒ <code>boolean</code>

View File

@@ -222,29 +222,30 @@ Create a Certificate Signing Request
Create a Certificate Signing Request Create a Certificate Signing Request
```js ```js
const [certificateKey, certificateRequest] = await acme.forge.createCsr({ const [certificateKey, certificateRequest] = await acme.forge.createCsr({
commonName: 'test.example.com' altNames: ['test.example.com'],
}); });
``` ```
**Example** **Example**
Certificate Signing Request with both common and alternative names Certificate Signing Request with both common and alternative names
> *Warning*: Certificate subject common name has been [deprecated](https://letsencrypt.org/docs/glossary/#def-CN) and its use is [discouraged](https://cabforum.org/uploads/BRv1.2.3.pdf).
```js ```js
const [certificateKey, certificateRequest] = await acme.forge.createCsr({ const [certificateKey, certificateRequest] = await acme.forge.createCsr({
keySize: 4096, keySize: 4096,
commonName: 'test.example.com', commonName: 'test.example.com',
altNames: ['foo.example.com', 'bar.example.com'] altNames: ['foo.example.com', 'bar.example.com'],
}); });
``` ```
**Example** **Example**
Certificate Signing Request with additional information Certificate Signing Request with additional information
```js ```js
const [certificateKey, certificateRequest] = await acme.forge.createCsr({ const [certificateKey, certificateRequest] = await acme.forge.createCsr({
commonName: 'test.example.com', altNames: ['test.example.com'],
country: 'US', country: 'US',
state: 'California', state: 'California',
locality: 'Los Angeles', locality: 'Los Angeles',
organization: 'The Company Inc.', organization: 'The Company Inc.',
organizationUnit: 'IT Department', organizationUnit: 'IT Department',
emailAddress: 'contact@example.com' emailAddress: 'contact@example.com',
}); });
``` ```
**Example** **Example**
@@ -253,5 +254,5 @@ Certificate Signing Request with predefined private key
const certificateKey = await acme.forge.createPrivateKey(); const certificateKey = await acme.forge.createPrivateKey();
const [, certificateRequest] = await acme.forge.createCsr({ const [, certificateRequest] = await acme.forge.createCsr({
commonName: 'test.example.com' altNames: ['test.example.com'],
}, certificateKey); }, certificateKey);

View File

@@ -8,7 +8,6 @@ function log(m) {
process.stdout.write(`${m}\n`); process.stdout.write(`${m}\n`);
} }
/** /**
* Function used to satisfy an ACME challenge * Function used to satisfy an ACME challenge
* *
@@ -25,7 +24,6 @@ async function challengeCreateFn(authz, challenge, keyAuthorization) {
log(keyAuthorization); log(keyAuthorization);
} }
/** /**
* Function used to remove an ACME challenge response * Function used to remove an ACME challenge response
* *
@@ -41,30 +39,29 @@ async function challengeRemoveFn(authz, challenge, keyAuthorization) {
log(keyAuthorization); log(keyAuthorization);
} }
/** /**
* Main * Main
*/ */
module.exports = async function() { module.exports = async () => {
/* Init client */ /* Init client */
const client = new acme.Client({ const client = new acme.Client({
directoryUrl: acme.directory.letsencrypt.staging, directoryUrl: acme.directory.letsencrypt.staging,
accountKey: await acme.crypto.createPrivateKey() accountKey: await acme.crypto.createPrivateKey(),
}); });
/* Register account */ /* Register account */
await client.createAccount({ await client.createAccount({
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
contact: ['mailto:test@example.com'] contact: ['mailto:test@example.com'],
}); });
/* Place new order */ /* Place new order */
const order = await client.createOrder({ const order = await client.createOrder({
identifiers: [ identifiers: [
{ type: 'dns', value: 'example.com' }, { type: 'dns', value: 'example.com' },
{ type: 'dns', value: '*.example.com' } { type: 'dns', value: '*.example.com' },
] ],
}); });
/** /**
@@ -138,8 +135,7 @@ module.exports = async function() {
/* Finalize order */ /* Finalize order */
const [key, csr] = await acme.crypto.createCsr({ const [key, csr] = await acme.crypto.createCsr({
commonName: '*.example.com', altNames: ['example.com', '*.example.com'],
altNames: ['example.com']
}); });
const finalized = await client.finalizeOrder(order, csr); const finalized = await client.finalizeOrder(order, csr);

View File

@@ -9,7 +9,6 @@ function log(m) {
process.stdout.write(`${m}\n`); process.stdout.write(`${m}\n`);
} }
/** /**
* Function used to satisfy an ACME challenge * Function used to satisfy an ACME challenge
* *
@@ -47,7 +46,6 @@ async function challengeCreateFn(authz, challenge, keyAuthorization) {
} }
} }
/** /**
* Function used to remove an ACME challenge response * Function used to remove an ACME challenge response
* *
@@ -80,25 +78,24 @@ async function challengeRemoveFn(authz, challenge, keyAuthorization) {
/* Replace this */ /* Replace this */
log(`Would remove TXT record "${dnsRecord}" with value "${recordValue}"`); log(`Would remove TXT record "${dnsRecord}" with value "${recordValue}"`);
// await dnsProvider.removeRecord(dnsRecord, 'TXT'); // await dnsProvider.removeRecord(dnsRecord, 'TXT', recordValue);
} }
} }
/** /**
* Main * Main
*/ */
module.exports = async function() { module.exports = async () => {
/* Init client */ /* Init client */
const client = new acme.Client({ const client = new acme.Client({
directoryUrl: acme.directory.letsencrypt.staging, directoryUrl: acme.directory.letsencrypt.staging,
accountKey: await acme.crypto.createPrivateKey() accountKey: await acme.crypto.createPrivateKey(),
}); });
/* Create CSR */ /* Create CSR */
const [key, csr] = await acme.crypto.createCsr({ const [key, csr] = await acme.crypto.createCsr({
commonName: 'example.com' altNames: ['example.com'],
}); });
/* Certificate */ /* Certificate */
@@ -107,7 +104,7 @@ module.exports = async function() {
email: 'test@example.com', email: 'test@example.com',
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn, challengeCreateFn,
challengeRemoveFn challengeRemoveFn,
}); });
/* Done */ /* Done */

View File

@@ -19,7 +19,6 @@ function log(m) {
process.stdout.write(`${(new Date()).toISOString()} ${m}\n`); process.stdout.write(`${(new Date()).toISOString()} ${m}\n`);
} }
/** /**
* Main * Main
*/ */
@@ -33,18 +32,16 @@ function log(m) {
log('Initializing ACME client'); log('Initializing ACME client');
const client = new acme.Client({ const client = new acme.Client({
directoryUrl: acme.directory.letsencrypt.staging, directoryUrl: acme.directory.letsencrypt.staging,
accountKey: await acme.crypto.createPrivateKey() accountKey: await acme.crypto.createPrivateKey(),
}); });
/** /**
* Order wildcard certificate * Order wildcard certificate
*/ */
log(`Creating CSR for ${WILDCARD_DOMAIN}`); log(`Creating CSR for ${WILDCARD_DOMAIN}`);
const [key, csr] = await acme.crypto.createCsr({ const [key, csr] = await acme.crypto.createCsr({
commonName: WILDCARD_DOMAIN, altNames: [WILDCARD_DOMAIN, `*.${WILDCARD_DOMAIN}`],
altNames: [`*.${WILDCARD_DOMAIN}`]
}); });
log(`Ordering certificate for ${WILDCARD_DOMAIN}`); log(`Ordering certificate for ${WILDCARD_DOMAIN}`);
@@ -60,12 +57,11 @@ function log(m) {
challengeRemoveFn: (authz, challenge, keyAuthorization) => { challengeRemoveFn: (authz, challenge, keyAuthorization) => {
/* TODO: Implement this */ /* TODO: Implement this */
log(`[TODO] Remove TXT record key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`); log(`[TODO] Remove TXT record key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`);
} },
}); });
log(`Certificate for ${WILDCARD_DOMAIN} created successfully`); log(`Certificate for ${WILDCARD_DOMAIN} created successfully`);
/** /**
* HTTPS server * HTTPS server
*/ */
@@ -78,7 +74,7 @@ function log(m) {
const httpsServer = https.createServer({ const httpsServer = https.createServer({
key, key,
cert cert,
}, requestListener); }, requestListener);
httpsServer.listen(HTTPS_SERVER_PORT, () => { httpsServer.listen(HTTPS_SERVER_PORT, () => {

View File

@@ -23,7 +23,6 @@ function log(m) {
process.stdout.write(`${(new Date()).toISOString()} ${m}\n`); process.stdout.write(`${(new Date()).toISOString()} ${m}\n`);
} }
/** /**
* On-demand certificate generation using http-01 * On-demand certificate generation using http-01
*/ */
@@ -52,7 +51,7 @@ async function getCertOnDemand(client, servername, attempt = 0) {
/* Create CSR */ /* Create CSR */
log(`Creating CSR for ${servername}`); log(`Creating CSR for ${servername}`);
const [key, csr] = await acme.crypto.createCsr({ const [key, csr] = await acme.crypto.createCsr({
commonName: servername altNames: [servername],
}); });
/* Order certificate */ /* Order certificate */
@@ -67,7 +66,7 @@ async function getCertOnDemand(client, servername, attempt = 0) {
}, },
challengeRemoveFn: (authz, challenge) => { challengeRemoveFn: (authz, challenge) => {
delete challengeResponses[challenge.token]; delete challengeResponses[challenge.token];
} },
}); });
/* Done, store certificate */ /* Done, store certificate */
@@ -77,7 +76,6 @@ async function getCertOnDemand(client, servername, attempt = 0) {
return certificateStore[servername]; return certificateStore[servername];
} }
/** /**
* Main * Main
*/ */
@@ -91,10 +89,9 @@ async function getCertOnDemand(client, servername, attempt = 0) {
log('Initializing ACME client'); log('Initializing ACME client');
const client = new acme.Client({ const client = new acme.Client({
directoryUrl: acme.directory.letsencrypt.staging, directoryUrl: acme.directory.letsencrypt.staging,
accountKey: await acme.crypto.createPrivateKey() accountKey: await acme.crypto.createPrivateKey(),
}); });
/** /**
* HTTP server * HTTP server
*/ */
@@ -129,7 +126,6 @@ async function getCertOnDemand(client, servername, attempt = 0) {
log(`HTTP server listening on port ${HTTP_SERVER_PORT}`); log(`HTTP server listening on port ${HTTP_SERVER_PORT}`);
}); });
/** /**
* HTTPS server * HTTPS server
*/ */
@@ -158,7 +154,7 @@ async function getCertOnDemand(client, servername, attempt = 0) {
log(`[ERROR] ${e.message}`); log(`[ERROR] ${e.message}`);
cb(e.message); cb(e.message);
} }
} },
}, requestListener); }, requestListener);
httpsServer.listen(HTTPS_SERVER_PORT, () => { httpsServer.listen(HTTPS_SERVER_PORT, () => {

View File

@@ -22,7 +22,6 @@ function log(m) {
process.stdout.write(`${(new Date()).toISOString()} ${m}\n`); process.stdout.write(`${(new Date()).toISOString()} ${m}\n`);
} }
/** /**
* On-demand certificate generation using tls-alpn-01 * On-demand certificate generation using tls-alpn-01
*/ */
@@ -51,7 +50,7 @@ async function getCertOnDemand(client, servername, attempt = 0) {
/* Create CSR */ /* Create CSR */
log(`Creating CSR for ${servername}`); log(`Creating CSR for ${servername}`);
const [key, csr] = await acme.crypto.createCsr({ const [key, csr] = await acme.crypto.createCsr({
commonName: servername altNames: [servername],
}); });
/* Order certificate */ /* Order certificate */
@@ -66,7 +65,7 @@ async function getCertOnDemand(client, servername, attempt = 0) {
}, },
challengeRemoveFn: (authz) => { challengeRemoveFn: (authz) => {
delete alpnResponses[authz.identifier.value]; delete alpnResponses[authz.identifier.value];
} },
}); });
/* Done, store certificate */ /* Done, store certificate */
@@ -76,7 +75,6 @@ async function getCertOnDemand(client, servername, attempt = 0) {
return certificateStore[servername]; return certificateStore[servername];
} }
/** /**
* Main * Main
*/ */
@@ -90,10 +88,9 @@ async function getCertOnDemand(client, servername, attempt = 0) {
log('Initializing ACME client'); log('Initializing ACME client');
const client = new acme.Client({ const client = new acme.Client({
directoryUrl: acme.directory.letsencrypt.staging, directoryUrl: acme.directory.letsencrypt.staging,
accountKey: await acme.crypto.createPrivateKey() accountKey: await acme.crypto.createPrivateKey(),
}); });
/** /**
* ALPN responder * ALPN responder
*/ */
@@ -118,14 +115,14 @@ async function getCertOnDemand(client, servername, attempt = 0) {
log(`Found ALPN certificate for ${servername}, serving secure context`); log(`Found ALPN certificate for ${servername}, serving secure context`);
cb(null, tls.createSecureContext({ cb(null, tls.createSecureContext({
key: alpnResponses[servername][0], key: alpnResponses[servername][0],
cert: alpnResponses[servername][1] cert: alpnResponses[servername][1],
})); }));
} }
catch (e) { catch (e) {
log(`[ERROR] ${e.message}`); log(`[ERROR] ${e.message}`);
cb(e.message); cb(e.message);
} }
} },
}); });
/* Terminate once TLS handshake has been established */ /* Terminate once TLS handshake has been established */
@@ -137,7 +134,6 @@ async function getCertOnDemand(client, servername, attempt = 0) {
log(`ALPN responder listening on port ${ALPN_RESPONDER_PORT}`); log(`ALPN responder listening on port ${ALPN_RESPONDER_PORT}`);
}); });
/** /**
* HTTPS server * HTTPS server
*/ */
@@ -166,7 +162,7 @@ async function getCertOnDemand(client, servername, attempt = 0) {
log(`[ERROR] ${e.message}`); log(`[ERROR] ${e.message}`);
cb(e.message); cb(e.message);
} }
} },
}, requestListener); }, requestListener);
httpsServer.listen(HTTPS_SERVER_PORT, () => { httpsServer.listen(HTTPS_SERVER_PORT, () => {

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client", "description": "Simple and unopinionated ACME client",
"private": false, "private": false,
"author": "nmorsman", "author": "nmorsman",
"version": "1.20.13", "version": "1.22.2",
"main": "src/index.js", "main": "src/index.js",
"types": "types/index.d.ts", "types": "types/index.d.ts",
"license": "MIT", "license": "MIT",
@@ -16,24 +16,24 @@
"types" "types"
], ],
"dependencies": { "dependencies": {
"@peculiar/x509": "^1.9.7", "@peculiar/x509": "^1.10.0",
"asn1js": "^3.0.5", "asn1js": "^3.0.5",
"axios": "^1.6.5", "axios": "^1.7.2",
"debug": "^4.1.1", "debug": "^4.1.1",
"https-proxy-agent": "^7.0.4", "https-proxy-agent": "^7.0.4",
"node-forge": "^1.3.1" "node-forge": "^1.3.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.11.5", "@types/node": "^20.12.12",
"chai": "^4.4.1", "chai": "^4.4.1",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.2",
"eslint": "^8.56.0", "eslint": "^8.57.0",
"eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"jsdoc-to-markdown": "^8.0.0", "jsdoc-to-markdown": "^8.0.1",
"mocha": "^10.2.0", "mocha": "^10.4.0",
"nock": "^13.5.0", "nock": "^13.5.4",
"tsd": "^0.30.4", "tsd": "^0.31.0",
"typescript": "^4.8.4", "typescript": "^4.8.4",
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },
@@ -59,5 +59,5 @@
"bugs": { "bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues" "url": "https://github.com/publishlab/node-acme-client/issues"
}, },
"gitHead": "a31f1c7f5e71fa946de9bf0283e11d6ce049b3e9" "gitHead": "47fe3d5826661f678d081ab53e67c847a3239d88"
} }

View File

@@ -4,7 +4,6 @@
const util = require('./util'); const util = require('./util');
/** /**
* AcmeApi * AcmeApi
* *
@@ -18,7 +17,6 @@ class AcmeApi {
this.accountUrl = accountUrl; this.accountUrl = accountUrl;
} }
/** /**
* Get account URL * Get account URL
* *
@@ -34,7 +32,6 @@ class AcmeApi {
return this.accountUrl; return this.accountUrl;
} }
/** /**
* ACME API request * ACME API request
* *
@@ -59,7 +56,6 @@ class AcmeApi {
return resp; return resp;
} }
/** /**
* ACME API request by resource name helper * ACME API request by resource name helper
* *
@@ -78,7 +74,6 @@ class AcmeApi {
return this.apiRequest(resourceUrl, payload, validStatusCodes, { includeJwsKid, includeExternalAccountBinding }); return this.apiRequest(resourceUrl, payload, validStatusCodes, { includeJwsKid, includeExternalAccountBinding });
} }
/** /**
* Get Terms of Service URL if available * Get Terms of Service URL if available
* *
@@ -91,7 +86,6 @@ class AcmeApi {
return this.http.getMetaField('termsOfService'); return this.http.getMetaField('termsOfService');
} }
/** /**
* Create new account * Create new account
* *
@@ -104,7 +98,7 @@ class AcmeApi {
async createAccount(data) { async createAccount(data) {
const resp = await this.apiResourceRequest('newAccount', data, [200, 201], { const resp = await this.apiResourceRequest('newAccount', data, [200, 201], {
includeJwsKid: false, includeJwsKid: false,
includeExternalAccountBinding: (data.onlyReturnExisting !== true) includeExternalAccountBinding: (data.onlyReturnExisting !== true),
}); });
/* Set account URL */ /* Set account URL */
@@ -115,7 +109,6 @@ class AcmeApi {
return resp; return resp;
} }
/** /**
* Update account * Update account
* *
@@ -129,7 +122,6 @@ class AcmeApi {
return this.apiRequest(this.getAccountUrl(), data, [200, 202]); return this.apiRequest(this.getAccountUrl(), data, [200, 202]);
} }
/** /**
* Update account key * Update account key
* *
@@ -143,7 +135,6 @@ class AcmeApi {
return this.apiResourceRequest('keyChange', data, [200]); return this.apiResourceRequest('keyChange', data, [200]);
} }
/** /**
* Create new order * Create new order
* *
@@ -157,7 +148,6 @@ class AcmeApi {
return this.apiResourceRequest('newOrder', data, [201]); return this.apiResourceRequest('newOrder', data, [201]);
} }
/** /**
* Get order * Get order
* *
@@ -171,7 +161,6 @@ class AcmeApi {
return this.apiRequest(url, null, [200]); return this.apiRequest(url, null, [200]);
} }
/** /**
* Finalize order * Finalize order
* *
@@ -186,7 +175,6 @@ class AcmeApi {
return this.apiRequest(url, data, [200]); return this.apiRequest(url, data, [200]);
} }
/** /**
* Get identifier authorization * Get identifier authorization
* *
@@ -200,7 +188,6 @@ class AcmeApi {
return this.apiRequest(url, null, [200]); return this.apiRequest(url, null, [200]);
} }
/** /**
* Update identifier authorization * Update identifier authorization
* *
@@ -215,7 +202,6 @@ class AcmeApi {
return this.apiRequest(url, data, [200]); return this.apiRequest(url, data, [200]);
} }
/** /**
* Complete challenge * Complete challenge
* *
@@ -230,7 +216,6 @@ class AcmeApi {
return this.apiRequest(url, data, [200]); return this.apiRequest(url, data, [200]);
} }
/** /**
* Revoke certificate * Revoke certificate
* *
@@ -245,6 +230,5 @@ class AcmeApi {
} }
} }
/* Export API */ /* Export API */
module.exports = AcmeApi; module.exports = AcmeApi;

View File

@@ -4,6 +4,7 @@
const { readCsrDomains } = require('./crypto'); const { readCsrDomains } = require('./crypto');
const { log } = require('./logger'); const { log } = require('./logger');
const { wait } = require('./wait');
const defaultOpts = { const defaultOpts = {
csr: null, csr: null,
@@ -13,10 +14,9 @@ const defaultOpts = {
skipChallengeVerification: false, skipChallengeVerification: false,
challengePriority: ['http-01', 'dns-01'], challengePriority: ['http-01', 'dns-01'],
challengeCreateFn: async () => { throw new Error('Missing challengeCreateFn()'); }, challengeCreateFn: async () => { throw new Error('Missing challengeCreateFn()'); },
challengeRemoveFn: async () => { throw new Error('Missing challengeRemoveFn()'); } challengeRemoveFn: async () => { throw new Error('Missing challengeRemoveFn()'); },
}; };
/** /**
* ACME client auto mode * ACME client auto mode
* *
@@ -25,8 +25,8 @@ const defaultOpts = {
* @returns {Promise<buffer>} Certificate * @returns {Promise<buffer>} Certificate
*/ */
module.exports = async function(client, userOpts) { module.exports = async (client, userOpts) => {
const opts = Object.assign({}, defaultOpts, userOpts); const opts = { ...defaultOpts, ...userOpts };
const accountPayload = { termsOfServiceAgreed: opts.termsOfServiceAgreed }; const accountPayload = { termsOfServiceAgreed: opts.termsOfServiceAgreed };
if (!Buffer.isBuffer(opts.csr)) { if (!Buffer.isBuffer(opts.csr)) {
@@ -36,7 +36,9 @@ module.exports = async function(client, userOpts) {
if (opts.email) { if (opts.email) {
accountPayload.contact = [`mailto:${opts.email}`]; accountPayload.contact = [`mailto:${opts.email}`];
} }
if (opts.externalAccountBinding) {
accountPayload.externalAccountBinding = opts.externalAccountBinding;
}
/** /**
* Register account * Register account
@@ -53,7 +55,6 @@ module.exports = async function(client, userOpts) {
await client.createAccount(accountPayload); await client.createAccount(accountPayload);
} }
/** /**
* Parse domains from CSR * Parse domains from CSR
*/ */
@@ -64,7 +65,6 @@ module.exports = async function(client, userOpts) {
log(`[auto] Resolved ${uniqueDomains.length} unique domains from parsing the Certificate Signing Request`); log(`[auto] Resolved ${uniqueDomains.length} unique domains from parsing the Certificate Signing Request`);
/** /**
* Place order * Place order
*/ */
@@ -76,7 +76,6 @@ module.exports = async function(client, userOpts) {
log(`[auto] Placed certificate order successfully, received ${authorizations.length} identity authorizations`); log(`[auto] Placed certificate order successfully, received ${authorizations.length} identity authorizations`);
/** /**
* Resolve and satisfy challenges * Resolve and satisfy challenges
*/ */
@@ -118,11 +117,23 @@ module.exports = async function(client, userOpts) {
let recordItem = null; let recordItem = null;
try { try {
recordItem = await opts.challengeCreateFn(authz, challenge, keyAuthorization); recordItem = await opts.challengeCreateFn(authz, challenge, keyAuthorization);
log(`[auto] [${d}] challengeCreateFn success`);
log(`[auto] [${d}] add challengeRemoveFn()`);
clearTasks.push(async () => {
/* Trigger challengeRemoveFn(), suppress errors */
log(`[auto] [${d}] Trigger challengeRemoveFn()`);
try {
await opts.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem);
}
catch (e) {
log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`);
}
});
// throw new Error('测试异常'); // throw new Error('测试异常');
/* Challenge verification */ /* Challenge verification */
if (opts.skipChallengeVerification === true) { if (opts.skipChallengeVerification === true) {
log(`[auto] [${d}] Skipping challenge verification since skipChallengeVerification=true`); log(`[auto] [${d}] Skipping challenge verification since skipChallengeVerification=truewait 60s`);
await wait(60 * 1000);
} }
else { else {
log(`[auto] [${d}] Running challenge verification`); log(`[auto] [${d}] Running challenge verification`);
@@ -140,19 +151,6 @@ module.exports = async function(client, userOpts) {
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message}`); log(`[auto] [${d}] challengeCreateFn threw error: ${e.message}`);
throw e; throw e;
} }
finally {
log(`[auto] [${d}] add challengeRemoveFn()`);
clearTasks.push(async () => {
/* Trigger challengeRemoveFn(), suppress errors */
log(`[auto] [${d}] Trigger challengeRemoveFn()`);
try {
await opts.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem);
}
catch (e) {
log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`);
}
});
}
} }
catch (e) { catch (e) {
/* Deactivate pending authz when unable to complete challenge */ /* Deactivate pending authz when unable to complete challenge */
@@ -177,7 +175,6 @@ module.exports = async function(client, userOpts) {
await challengeFunc(authz); await challengeFunc(authz);
}); });
function runAllPromise(tasks) { function runAllPromise(tasks) {
let promise = Promise.resolve(); let promise = Promise.resolve();
tasks.forEach((task) => { tasks.forEach((task) => {
@@ -186,14 +183,20 @@ module.exports = async function(client, userOpts) {
return promise; return promise;
} }
// function runPromisePa(tasks) { async function runPromisePa(tasks) {
// return Promise.all(tasks.map((task) => task())); const results = [];
// } // eslint-disable-next-line no-await-in-loop,no-restricted-syntax
for (const task of tasks) {
results.push(task());
// eslint-disable-next-line no-await-in-loop
await wait(10000);
}
return Promise.all(results);
}
try { try {
log('开始challenge'); log('开始challenge');
await runAllPromise(challengePromises); await runPromisePa(challengePromises);
log('challenge结束'); log('challenge结束');
@@ -210,11 +213,18 @@ module.exports = async function(client, userOpts) {
} }
catch (e) { catch (e) {
log('证书申请失败'); log('证书申请失败');
throw e; log(e);
throw new Error(`证书申请失败:${e.message}`);
} }
finally { finally {
log(`清理challenge痕迹length:${clearTasks.length}`); log(`清理challenge痕迹length:${clearTasks.length}`);
await runAllPromise(clearTasks); try {
await runAllPromise(clearTasks);
}
catch (e) {
log('清理challenge失败');
log(e);
}
} }
// try { // try {

View File

@@ -5,7 +5,6 @@
const axios = require('axios'); const axios = require('axios');
const pkg = require('./../package.json'); const pkg = require('./../package.json');
/** /**
* Instance * Instance
*/ */
@@ -19,9 +18,8 @@ instance.defaults.headers.common['User-Agent'] = `node-${pkg.name}/${pkg.version
instance.defaults.acmeSettings = { instance.defaults.acmeSettings = {
httpChallengePort: 80, httpChallengePort: 80,
httpsChallengePort: 443, httpsChallengePort: 443,
tlsAlpnChallengePort: 443 tlsAlpnChallengePort: 443,
}; };
// instance.defaults.proxy = { // instance.defaults.proxy = {
// host: '192.168.34.139', // host: '192.168.34.139',
// port: 10811 // port: 10811
@@ -35,7 +33,6 @@ instance.defaults.acmeSettings = {
instance.defaults.adapter = 'http'; instance.defaults.adapter = 'http';
/** /**
* Export instance * Export instance
*/ */

View File

@@ -13,7 +13,6 @@ const verify = require('./verify');
const util = require('./util'); const util = require('./util');
const auto = require('./auto'); const auto = require('./auto');
/** /**
* ACME states * ACME states
* *
@@ -24,7 +23,6 @@ const validStates = ['ready', 'valid'];
const pendingStates = ['pending', 'processing']; const pendingStates = ['pending', 'processing'];
const invalidStates = ['invalid']; const invalidStates = ['invalid'];
/** /**
* Default options * Default options
* *
@@ -38,10 +36,9 @@ const defaultOpts = {
externalAccountBinding: {}, externalAccountBinding: {},
backoffAttempts: 10, backoffAttempts: 10,
backoffMin: 5000, backoffMin: 5000,
backoffMax: 30000 backoffMax: 30000,
}; };
/** /**
* AcmeClient * AcmeClient
* *
@@ -61,7 +58,7 @@ const defaultOpts = {
* ```js * ```js
* const client = new acme.Client({ * const client = new acme.Client({
* directoryUrl: acme.directory.letsencrypt.staging, * directoryUrl: acme.directory.letsencrypt.staging,
* accountKey: 'Private key goes here' * accountKey: 'Private key goes here',
* }); * });
* ``` * ```
* *
@@ -73,7 +70,7 @@ const defaultOpts = {
* accountUrl: 'Optional account URL goes here', * accountUrl: 'Optional account URL goes here',
* backoffAttempts: 10, * backoffAttempts: 10,
* backoffMin: 5000, * backoffMin: 5000,
* backoffMax: 30000 * backoffMax: 30000,
* }); * });
* ``` * ```
* *
@@ -84,8 +81,8 @@ const defaultOpts = {
* accountKey: 'Private key goes here', * accountKey: 'Private key goes here',
* externalAccountBinding: { * externalAccountBinding: {
* kid: 'YOUR-EAB-KID', * kid: 'YOUR-EAB-KID',
* hmacKey: 'YOUR-EAB-HMAC-KEY' * hmacKey: 'YOUR-EAB-HMAC-KEY',
* } * },
* }); * });
* ``` * ```
*/ */
@@ -96,19 +93,17 @@ class AcmeClient {
opts.accountKey = Buffer.from(opts.accountKey); opts.accountKey = Buffer.from(opts.accountKey);
} }
this.opts = Object.assign({}, defaultOpts, opts); this.opts = { ...defaultOpts, ...opts };
this.backoffOpts = { this.backoffOpts = {
attempts: this.opts.backoffAttempts, attempts: this.opts.backoffAttempts,
min: this.opts.backoffMin, min: this.opts.backoffMin,
max: this.opts.backoffMax max: this.opts.backoffMax,
}; };
this.http = new HttpClient(this.opts.directoryUrl, this.opts.accountKey, this.opts.externalAccountBinding); this.http = new HttpClient(this.opts.directoryUrl, this.opts.accountKey, this.opts.externalAccountBinding);
this.api = new AcmeApi(this.http, this.opts.accountUrl); this.api = new AcmeApi(this.http, this.opts.accountUrl);
} }
/** /**
* Get Terms of Service URL if available * Get Terms of Service URL if available
* *
@@ -128,7 +123,6 @@ class AcmeClient {
return this.api.getTermsOfServiceUrl(); return this.api.getTermsOfServiceUrl();
} }
/** /**
* Get current account URL * Get current account URL
* *
@@ -150,7 +144,6 @@ class AcmeClient {
return this.api.getAccountUrl(); return this.api.getAccountUrl();
} }
/** /**
* Create a new account * Create a new account
* *
@@ -162,7 +155,7 @@ class AcmeClient {
* @example Create a new account * @example Create a new account
* ```js * ```js
* const account = await client.createAccount({ * const account = await client.createAccount({
* termsOfServiceAgreed: true * termsOfServiceAgreed: true,
* }); * });
* ``` * ```
* *
@@ -170,7 +163,7 @@ class AcmeClient {
* ```js * ```js
* const account = await client.createAccount({ * const account = await client.createAccount({
* termsOfServiceAgreed: true, * termsOfServiceAgreed: true,
* contact: ['mailto:test@example.com'] * contact: ['mailto:test@example.com'],
* }); * });
* ``` * ```
*/ */
@@ -196,7 +189,6 @@ class AcmeClient {
} }
} }
/** /**
* Update existing account * Update existing account
* *
@@ -208,7 +200,7 @@ class AcmeClient {
* @example Update existing account * @example Update existing account
* ```js * ```js
* const account = await client.updateAccount({ * const account = await client.updateAccount({
* contact: ['mailto:foo@example.com'] * contact: ['mailto:foo@example.com'],
* }); * });
* ``` * ```
*/ */
@@ -236,7 +228,6 @@ class AcmeClient {
return resp.data; return resp.data;
} }
/** /**
* Update account private key * Update account private key
* *
@@ -282,7 +273,6 @@ class AcmeClient {
return resp.data; return resp.data;
} }
/** /**
* Create a new order * Create a new order
* *
@@ -296,8 +286,8 @@ class AcmeClient {
* const order = await client.createOrder({ * const order = await client.createOrder({
* identifiers: [ * identifiers: [
* { type: 'dns', value: 'example.com' }, * { type: 'dns', value: 'example.com' },
* { type: 'dns', value: 'test.example.com' } * { type: 'dns', value: 'test.example.com' },
* ] * ],
* }); * });
* ``` * ```
*/ */
@@ -314,7 +304,6 @@ class AcmeClient {
return resp.data; return resp.data;
} }
/** /**
* Refresh order object from CA * Refresh order object from CA
* *
@@ -376,7 +365,6 @@ class AcmeClient {
return resp.data; return resp.data;
} }
/** /**
* Get identifier authorizations from order * Get identifier authorizations from order
* *
@@ -406,7 +394,6 @@ class AcmeClient {
})); }));
} }
/** /**
* Deactivate identifier authorization * Deactivate identifier authorization
* *
@@ -427,10 +414,7 @@ class AcmeClient {
throw new Error('Unable to deactivate identifier authorization, URL not found'); throw new Error('Unable to deactivate identifier authorization, URL not found');
} }
const data = { const data = { status: 'deactivated' };
status: 'deactivated'
};
const resp = await this.api.updateAuthorization(authz.url, data); const resp = await this.api.updateAuthorization(authz.url, data);
/* Add URL to response */ /* Add URL to response */
@@ -438,7 +422,6 @@ class AcmeClient {
return resp.data; return resp.data;
} }
/** /**
* Get key authorization for ACME challenge * Get key authorization for ACME challenge
* *
@@ -480,7 +463,6 @@ class AcmeClient {
throw new Error(`Unable to produce key authorization, unknown challenge type: ${challenge.type}`); throw new Error(`Unable to produce key authorization, unknown challenge type: ${challenge.type}`);
} }
/** /**
* Verify that ACME challenge is satisfied * Verify that ACME challenge is satisfied
* *
@@ -515,7 +497,6 @@ class AcmeClient {
return util.retry(verifyFn, this.backoffOpts); return util.retry(verifyFn, this.backoffOpts);
} }
/** /**
* Notify CA that challenge has been completed * Notify CA that challenge has been completed
* *
@@ -536,7 +517,6 @@ class AcmeClient {
return resp.data; return resp.data;
} }
/** /**
* Wait for ACME provider to verify status on a order, authorization or challenge * Wait for ACME provider to verify status on a order, authorization or challenge
* *
@@ -593,7 +573,6 @@ class AcmeClient {
return util.retry(verifyFn, this.backoffOpts); return util.retry(verifyFn, this.backoffOpts);
} }
/** /**
* Get certificate from ACME order * Get certificate from ACME order
* *
@@ -640,7 +619,6 @@ class AcmeClient {
return resp.data; return resp.data;
} }
/** /**
* Revoke certificate * Revoke certificate
* *
@@ -660,7 +638,7 @@ class AcmeClient {
* ```js * ```js
* const certificate = { ... }; // Previously created certificate * const certificate = { ... }; // Previously created certificate
* const result = await client.revokeCertificate(certificate, { * const result = await client.revokeCertificate(certificate, {
* reason: 4 * reason: 4,
* }); * });
* ``` * ```
*/ */
@@ -671,7 +649,6 @@ class AcmeClient {
return resp.data; return resp.data;
} }
/** /**
* Auto mode * Auto mode
* *
@@ -689,7 +666,7 @@ class AcmeClient {
* @example Order a certificate using auto mode * @example Order a certificate using auto mode
* ```js * ```js
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({ * const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
* commonName: 'test.example.com' * altNames: ['test.example.com'],
* }); * });
* *
* const certificate = await client.auto({ * const certificate = await client.auto({
@@ -701,14 +678,14 @@ class AcmeClient {
* }, * },
* challengeRemoveFn: async (authz, challenge, keyAuthorization) => { * challengeRemoveFn: async (authz, challenge, keyAuthorization) => {
* // Clean up challenge here * // Clean up challenge here
* } * },
* }); * });
* ``` * ```
* *
* @example Order a certificate using auto mode with preferred chain * @example Order a certificate using auto mode with preferred chain
* ```js * ```js
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({ * const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
* commonName: 'test.example.com' * altNames: ['test.example.com'],
* }); * });
* *
* const certificate = await client.auto({ * const certificate = await client.auto({
@@ -717,7 +694,7 @@ class AcmeClient {
* termsOfServiceAgreed: true, * termsOfServiceAgreed: true,
* preferredChain: 'DST Root CA X3', * preferredChain: 'DST Root CA X3',
* challengeCreateFn: async () => {}, * challengeCreateFn: async () => {},
* challengeRemoveFn: async () => {} * challengeRemoveFn: async () => {},
* }); * });
* ``` * ```
*/ */
@@ -727,6 +704,5 @@ class AcmeClient {
} }
} }
/* Export client */ /* Export client */
module.exports = AcmeClient; module.exports = AcmeClient;

View File

@@ -13,7 +13,6 @@ const forge = require('node-forge');
const generateKeyPair = promisify(forge.pki.rsa.generateKeyPair); const generateKeyPair = promisify(forge.pki.rsa.generateKeyPair);
/** /**
* Attempt to parse forge object from PEM encoded string * Attempt to parse forge object from PEM encoded string
* *
@@ -54,7 +53,6 @@ function forgeObjectFromPem(input) {
return result; return result;
} }
/** /**
* Parse domain names from a certificate or CSR * Parse domain names from a certificate or CSR
* *
@@ -93,11 +91,10 @@ function parseDomains(obj) {
return { return {
commonName, commonName,
altNames altNames,
}; };
} }
/** /**
* Generate a private RSA key * Generate a private RSA key
* *
@@ -123,7 +120,6 @@ async function createPrivateKey(size = 2048) {
exports.createPrivateKey = createPrivateKey; exports.createPrivateKey = createPrivateKey;
/** /**
* Create public key from a private RSA key * Create public key from a private RSA key
* *
@@ -136,14 +132,13 @@ exports.createPrivateKey = createPrivateKey;
* ``` * ```
*/ */
exports.createPublicKey = async function(key) { exports.createPublicKey = async (key) => {
const privateKey = forge.pki.privateKeyFromPem(key); const privateKey = forge.pki.privateKeyFromPem(key);
const publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e); const publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e);
const pemKey = forge.pki.publicKeyToPem(publicKey); const pemKey = forge.pki.publicKeyToPem(publicKey);
return Buffer.from(pemKey); return Buffer.from(pemKey);
}; };
/** /**
* Parse body of PEM encoded object from buffer or string * Parse body of PEM encoded object from buffer or string
* If multiple objects are chained, the first body will be returned * If multiple objects are chained, the first body will be returned
@@ -157,7 +152,6 @@ exports.getPemBody = (str) => {
return forge.util.encode64(msg.body); return forge.util.encode64(msg.body);
}; };
/** /**
* Split chain of PEM encoded objects from buffer or string into array * Split chain of PEM encoded objects from buffer or string into array
* *
@@ -167,7 +161,6 @@ exports.getPemBody = (str) => {
exports.splitPemChain = (str) => forge.pem.decode(str).map(forge.pem.encode); exports.splitPemChain = (str) => forge.pem.decode(str).map(forge.pem.encode);
/** /**
* Get modulus * Get modulus
* *
@@ -182,7 +175,7 @@ exports.splitPemChain = (str) => forge.pem.decode(str).map(forge.pem.encode);
* ``` * ```
*/ */
exports.getModulus = async function(input) { exports.getModulus = async (input) => {
if (!Buffer.isBuffer(input)) { if (!Buffer.isBuffer(input)) {
input = Buffer.from(input); input = Buffer.from(input);
} }
@@ -191,7 +184,6 @@ exports.getModulus = async function(input) {
return Buffer.from(forge.util.hexToBytes(obj.n.toString(16)), 'binary'); return Buffer.from(forge.util.hexToBytes(obj.n.toString(16)), 'binary');
}; };
/** /**
* Get public exponent * Get public exponent
* *
@@ -206,7 +198,7 @@ exports.getModulus = async function(input) {
* ``` * ```
*/ */
exports.getPublicExponent = async function(input) { exports.getPublicExponent = async (input) => {
if (!Buffer.isBuffer(input)) { if (!Buffer.isBuffer(input)) {
input = Buffer.from(input); input = Buffer.from(input);
} }
@@ -215,7 +207,6 @@ exports.getPublicExponent = async function(input) {
return Buffer.from(forge.util.hexToBytes(obj.e.toString(16)), 'binary'); return Buffer.from(forge.util.hexToBytes(obj.e.toString(16)), 'binary');
}; };
/** /**
* Read domains from a Certificate Signing Request * Read domains from a Certificate Signing Request
* *
@@ -231,7 +222,7 @@ exports.getPublicExponent = async function(input) {
* ``` * ```
*/ */
exports.readCsrDomains = async function(csr) { exports.readCsrDomains = async (csr) => {
if (!Buffer.isBuffer(csr)) { if (!Buffer.isBuffer(csr)) {
csr = Buffer.from(csr); csr = Buffer.from(csr);
} }
@@ -240,7 +231,6 @@ exports.readCsrDomains = async function(csr) {
return parseDomains(obj); return parseDomains(obj);
}; };
/** /**
* Read information from a certificate * Read information from a certificate
* *
@@ -260,7 +250,7 @@ exports.readCsrDomains = async function(csr) {
* ``` * ```
*/ */
exports.readCertificateInfo = async function(cert) { exports.readCertificateInfo = async (cert) => {
if (!Buffer.isBuffer(cert)) { if (!Buffer.isBuffer(cert)) {
cert = Buffer.from(cert); cert = Buffer.from(cert);
} }
@@ -270,15 +260,14 @@ exports.readCertificateInfo = async function(cert) {
return { return {
issuer: { issuer: {
commonName: issuerCn ? issuerCn.value : null commonName: issuerCn ? issuerCn.value : null,
}, },
domains: parseDomains(obj), domains: parseDomains(obj),
notAfter: obj.validity.notAfter, notAfter: obj.validity.notAfter,
notBefore: obj.validity.notBefore notBefore: obj.validity.notBefore,
}; };
}; };
/** /**
* Determine ASN.1 type for CSR subject short name * Determine ASN.1 type for CSR subject short name
* Note: https://datatracker.ietf.org/doc/html/rfc5280 * Note: https://datatracker.ietf.org/doc/html/rfc5280
@@ -299,7 +288,6 @@ function getCsrValueTagClass(shortName) {
} }
} }
/** /**
* Create array of short names and values for Certificate Signing Request subjects * Create array of short names and values for Certificate Signing Request subjects
* *
@@ -319,7 +307,6 @@ function createCsrSubject(subjectObj) {
}, []); }, []);
} }
/** /**
* Create array of alt names for Certificate Signing Requests * Create array of alt names for Certificate Signing Requests
* Note: https://github.com/digitalbazaar/forge/blob/dfdde475677a8a25c851e33e8f81dca60d90cfb9/lib/x509.js#L1444-L1454 * Note: https://github.com/digitalbazaar/forge/blob/dfdde475677a8a25c851e33e8f81dca60d90cfb9/lib/x509.js#L1444-L1454
@@ -336,7 +323,6 @@ function formatCsrAltNames(altNames) {
}); });
} }
/** /**
* Create a Certificate Signing Request * Create a Certificate Signing Request
* *
@@ -356,29 +342,30 @@ function formatCsrAltNames(altNames) {
* @example Create a Certificate Signing Request * @example Create a Certificate Signing Request
* ```js * ```js
* const [certificateKey, certificateRequest] = await acme.forge.createCsr({ * const [certificateKey, certificateRequest] = await acme.forge.createCsr({
* commonName: 'test.example.com' * altNames: ['test.example.com'],
* }); * });
* ``` * ```
* *
* @example Certificate Signing Request with both common and alternative names * @example Certificate Signing Request with both common and alternative names
* > *Warning*: Certificate subject common name has been [deprecated](https://letsencrypt.org/docs/glossary/#def-CN) and its use is [discouraged](https://cabforum.org/uploads/BRv1.2.3.pdf).
* ```js * ```js
* const [certificateKey, certificateRequest] = await acme.forge.createCsr({ * const [certificateKey, certificateRequest] = await acme.forge.createCsr({
* keySize: 4096, * keySize: 4096,
* commonName: 'test.example.com', * commonName: 'test.example.com',
* altNames: ['foo.example.com', 'bar.example.com'] * altNames: ['foo.example.com', 'bar.example.com'],
* }); * });
* ``` * ```
* *
* @example Certificate Signing Request with additional information * @example Certificate Signing Request with additional information
* ```js * ```js
* const [certificateKey, certificateRequest] = await acme.forge.createCsr({ * const [certificateKey, certificateRequest] = await acme.forge.createCsr({
* commonName: 'test.example.com', * altNames: ['test.example.com'],
* country: 'US', * country: 'US',
* state: 'California', * state: 'California',
* locality: 'Los Angeles', * locality: 'Los Angeles',
* organization: 'The Company Inc.', * organization: 'The Company Inc.',
* organizationUnit: 'IT Department', * organizationUnit: 'IT Department',
* emailAddress: 'contact@example.com' * emailAddress: 'contact@example.com',
* }); * });
* ``` * ```
* *
@@ -387,11 +374,11 @@ function formatCsrAltNames(altNames) {
* const certificateKey = await acme.forge.createPrivateKey(); * const certificateKey = await acme.forge.createPrivateKey();
* *
* const [, certificateRequest] = await acme.forge.createCsr({ * const [, certificateRequest] = await acme.forge.createCsr({
* commonName: 'test.example.com' * altNames: ['test.example.com'],
* }, certificateKey); * }, certificateKey);
*/ */
exports.createCsr = async function(data, key = null) { exports.createCsr = async (data, key = null) => {
if (!key) { if (!key) {
key = await createPrivateKey(data.keySize); key = await createPrivateKey(data.keySize);
} }
@@ -423,7 +410,7 @@ exports.createCsr = async function(data, key = null) {
L: data.locality, L: data.locality,
O: data.organization, O: data.organization,
OU: data.organizationUnit, OU: data.organizationUnit,
E: data.emailAddress E: data.emailAddress,
}); });
csr.setSubject(subject); csr.setSubject(subject);
@@ -434,8 +421,8 @@ exports.createCsr = async function(data, key = null) {
name: 'extensionRequest', name: 'extensionRequest',
extensions: [{ extensions: [{
name: 'subjectAltName', name: 'subjectAltName',
altNames: formatCsrAltNames(data.altNames) altNames: formatCsrAltNames(data.altNames),
}] }],
}]); }]);
} }

View File

@@ -22,7 +22,6 @@ const subjectAltNameOID = '2.5.29.17';
/* id-pe-acmeIdentifier - https://datatracker.ietf.org/doc/html/rfc8737#section-6.1 */ /* id-pe-acmeIdentifier - https://datatracker.ietf.org/doc/html/rfc8737#section-6.1 */
const alpnAcmeIdentifierOID = '1.3.6.1.5.5.7.1.31'; const alpnAcmeIdentifierOID = '1.3.6.1.5.5.7.1.31';
/** /**
* Determine key type and info by attempting to derive public key * Determine key type and info by attempting to derive public key
* *
@@ -35,7 +34,7 @@ function getKeyInfo(keyPem) {
const result = { const result = {
isRSA: false, isRSA: false,
isECDSA: false, isECDSA: false,
publicKey: crypto.createPublicKey(keyPem) publicKey: crypto.createPublicKey(keyPem),
}; };
if (result.publicKey.asymmetricKeyType === 'rsa') { if (result.publicKey.asymmetricKeyType === 'rsa') {
@@ -51,7 +50,6 @@ function getKeyInfo(keyPem) {
return result; return result;
} }
/** /**
* Generate a private RSA key * Generate a private RSA key
* *
@@ -74,8 +72,8 @@ async function createPrivateRsaKey(modulusLength = 2048) {
modulusLength, modulusLength,
privateKeyEncoding: { privateKeyEncoding: {
type: 'pkcs8', type: 'pkcs8',
format: 'pem' format: 'pem',
} },
}); });
return Buffer.from(pair.privateKey); return Buffer.from(pair.privateKey);
@@ -83,7 +81,6 @@ async function createPrivateRsaKey(modulusLength = 2048) {
exports.createPrivateRsaKey = createPrivateRsaKey; exports.createPrivateRsaKey = createPrivateRsaKey;
/** /**
* Alias of `createPrivateRsaKey()` * Alias of `createPrivateRsaKey()`
* *
@@ -92,7 +89,6 @@ exports.createPrivateRsaKey = createPrivateRsaKey;
exports.createPrivateKey = createPrivateRsaKey; exports.createPrivateKey = createPrivateRsaKey;
/** /**
* Generate a private ECDSA key * Generate a private ECDSA key
* *
@@ -115,14 +111,13 @@ exports.createPrivateEcdsaKey = async (namedCurve = 'P-256') => {
namedCurve, namedCurve,
privateKeyEncoding: { privateKeyEncoding: {
type: 'pkcs8', type: 'pkcs8',
format: 'pem' format: 'pem',
} },
}); });
return Buffer.from(pair.privateKey); return Buffer.from(pair.privateKey);
}; };
/** /**
* Get a public key derived from a RSA or ECDSA key * Get a public key derived from a RSA or ECDSA key
* *
@@ -140,13 +135,12 @@ exports.getPublicKey = (keyPem) => {
const publicKey = info.publicKey.export({ const publicKey = info.publicKey.export({
type: info.isECDSA ? 'spki' : 'pkcs1', type: info.isECDSA ? 'spki' : 'pkcs1',
format: 'pem' format: 'pem',
}); });
return Buffer.from(publicKey); return Buffer.from(publicKey);
}; };
/** /**
* Get a JSON Web Key derived from a RSA or ECDSA key * Get a JSON Web Key derived from a RSA or ECDSA key
* *
@@ -163,7 +157,7 @@ exports.getPublicKey = (keyPem) => {
function getJwk(keyPem) { function getJwk(keyPem) {
const jwk = crypto.createPublicKey(keyPem).export({ const jwk = crypto.createPublicKey(keyPem).export({
format: 'jwk' format: 'jwk',
}); });
/* Sort keys */ /* Sort keys */
@@ -175,7 +169,6 @@ function getJwk(keyPem) {
exports.getJwk = getJwk; exports.getJwk = getJwk;
/** /**
* Produce CryptoKeyPair and signing algorithm from a PEM encoded private key * Produce CryptoKeyPair and signing algorithm from a PEM encoded private key
* *
@@ -191,7 +184,7 @@ async function getWebCryptoKeyPair(keyPem) {
/* Signing algorithm */ /* Signing algorithm */
const sigalg = { const sigalg = {
name: 'RSASSA-PKCS1-v1_5', name: 'RSASSA-PKCS1-v1_5',
hash: { name: 'SHA-256' } hash: { name: 'SHA-256' },
}; };
if (info.isECDSA) { if (info.isECDSA) {
@@ -215,7 +208,6 @@ async function getWebCryptoKeyPair(keyPem) {
return [{ privateKey, publicKey }, sigalg]; return [{ privateKey, publicKey }, sigalg];
} }
/** /**
* Split chain of PEM encoded objects from string into array * Split chain of PEM encoded objects from string into array
* *
@@ -235,7 +227,6 @@ function splitPemChain(chainPem) {
exports.splitPemChain = splitPemChain; exports.splitPemChain = splitPemChain;
/** /**
* Parse body of PEM encoded object and return a Base64URL string * Parse body of PEM encoded object and return a Base64URL string
* If multiple objects are chained, the first body will be returned * If multiple objects are chained, the first body will be returned
@@ -256,7 +247,6 @@ exports.getPemBodyAsB64u = (pem) => {
return Buffer.from(dec).toString('base64url'); return Buffer.from(dec).toString('base64url');
}; };
/** /**
* Parse domains from a certificate or CSR * Parse domains from a certificate or CSR
* *
@@ -277,11 +267,10 @@ function parseDomains(input) {
return { return {
commonName, commonName,
altNames altNames,
}; };
} }
/** /**
* Read domains from a Certificate Signing Request * Read domains from a Certificate Signing Request
* *
@@ -307,7 +296,6 @@ exports.readCsrDomains = (csrPem) => {
return parseDomains(csr); return parseDomains(csr);
}; };
/** /**
* Read information from a certificate * Read information from a certificate
* If multiple certificates are chained, the first will be read * If multiple certificates are chained, the first will be read
@@ -338,15 +326,14 @@ exports.readCertificateInfo = (certPem) => {
return { return {
issuer: { issuer: {
commonName: cert.issuerName.getField('CN').pop() || null commonName: cert.issuerName.getField('CN').pop() || null,
}, },
domains: parseDomains(cert), domains: parseDomains(cert),
notBefore: cert.notBefore, notBefore: cert.notBefore,
notAfter: cert.notAfter notAfter: cert.notAfter,
}; };
}; };
/** /**
* Determine ASN.1 character string type for CSR subject field name * Determine ASN.1 character string type for CSR subject field name
* *
@@ -369,7 +356,6 @@ function getCsrAsn1CharStringType(field) {
} }
} }
/** /**
* Create array of subject fields for a Certificate Signing Request * Create array of subject fields for a Certificate Signing Request
* *
@@ -391,7 +377,6 @@ function createCsrSubject(input) {
}, []); }, []);
} }
/** /**
* Create x509 subject alternate name extension * Create x509 subject alternate name extension
* *
@@ -409,7 +394,6 @@ function createSubjectAltNameExtension(altNames) {
})); }));
} }
/** /**
* Create a Certificate Signing Request * Create a Certificate Signing Request
* *
@@ -429,29 +413,30 @@ function createSubjectAltNameExtension(altNames) {
* @example Create a Certificate Signing Request * @example Create a Certificate Signing Request
* ```js * ```js
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({ * const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
* commonName: 'test.example.com' * altNames: ['test.example.com'],
* }); * });
* ``` * ```
* *
* @example Certificate Signing Request with both common and alternative names * @example Certificate Signing Request with both common and alternative names
* > *Warning*: Certificate subject common name has been [deprecated](https://letsencrypt.org/docs/glossary/#def-CN) and its use is [discouraged](https://cabforum.org/uploads/BRv1.2.3.pdf).
* ```js * ```js
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({ * const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
* keySize: 4096, * keySize: 4096,
* commonName: 'test.example.com', * commonName: 'test.example.com',
* altNames: ['foo.example.com', 'bar.example.com'] * altNames: ['foo.example.com', 'bar.example.com'],
* }); * });
* ``` * ```
* *
* @example Certificate Signing Request with additional information * @example Certificate Signing Request with additional information
* ```js * ```js
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({ * const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
* commonName: 'test.example.com', * altNames: ['test.example.com'],
* country: 'US', * country: 'US',
* state: 'California', * state: 'California',
* locality: 'Los Angeles', * locality: 'Los Angeles',
* organization: 'The Company Inc.', * organization: 'The Company Inc.',
* organizationUnit: 'IT Department', * organizationUnit: 'IT Department',
* emailAddress: 'contact@example.com' * emailAddress: 'contact@example.com',
* }); * });
* ``` * ```
* *
@@ -460,8 +445,9 @@ function createSubjectAltNameExtension(altNames) {
* const certificateKey = await acme.crypto.createPrivateEcdsaKey(); * const certificateKey = await acme.crypto.createPrivateEcdsaKey();
* *
* const [, certificateRequest] = await acme.crypto.createCsr({ * const [, certificateRequest] = await acme.crypto.createCsr({
* commonName: 'test.example.com' * altNames: ['test.example.com'],
* }, certificateKey); * }, certificateKey);
* ```
*/ */
exports.createCsr = async (data, keyPem = null) => { exports.createCsr = async (data, keyPem = null) => {
@@ -489,7 +475,7 @@ exports.createCsr = async (data, keyPem = null) => {
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment), // eslint-disable-line no-bitwise new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment), // eslint-disable-line no-bitwise
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 */ /* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 */
createSubjectAltNameExtension(data.altNames) createSubjectAltNameExtension(data.altNames),
]; ];
/* Create CSR */ /* Create CSR */
@@ -504,8 +490,8 @@ exports.createCsr = async (data, keyPem = null) => {
L: data.locality, L: data.locality,
O: data.organization, O: data.organization,
OU: data.organizationUnit, OU: data.organizationUnit,
E: data.emailAddress E: data.emailAddress,
}) }),
}); });
/* Done */ /* Done */
@@ -513,7 +499,6 @@ exports.createCsr = async (data, keyPem = null) => {
return [keyPem, Buffer.from(pem)]; return [keyPem, Buffer.from(pem)];
}; };
/** /**
* Create a self-signed ALPN certificate for TLS-ALPN-01 challenges * Create a self-signed ALPN certificate for TLS-ALPN-01 challenges
* *
@@ -533,6 +518,7 @@ exports.createCsr = async (data, keyPem = null) => {
* ```js * ```js
* const alpnKey = await acme.crypto.createPrivateEcdsaKey(); * const alpnKey = await acme.crypto.createPrivateEcdsaKey();
* const [, alpnCertificate] = await acme.crypto.createAlpnCertificate(authz, keyAuthorization, alpnKey); * const [, alpnCertificate] = await acme.crypto.createAlpnCertificate(authz, keyAuthorization, alpnKey);
* ```
*/ */
exports.createAlpnCertificate = async (authz, keyAuthorization, keyPem = null) => { exports.createAlpnCertificate = async (authz, keyAuthorization, keyPem = null) => {
@@ -564,7 +550,7 @@ exports.createAlpnCertificate = async (authz, keyAuthorization, keyPem = null) =
await x509.SubjectKeyIdentifierExtension.create(keys.publicKey), await x509.SubjectKeyIdentifierExtension.create(keys.publicKey),
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 */ /* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 */
createSubjectAltNameExtension([commonName]) createSubjectAltNameExtension([commonName]),
]; ];
/* ALPN extension */ /* ALPN extension */
@@ -581,8 +567,8 @@ exports.createAlpnCertificate = async (authz, keyAuthorization, keyPem = null) =
notBefore: now, notBefore: now,
notAfter: now, notAfter: now,
name: createCsrSubject({ name: createCsrSubject({
CN: commonName CN: commonName,
}) }),
}); });
/* Done */ /* Done */
@@ -590,7 +576,6 @@ exports.createAlpnCertificate = async (authz, keyAuthorization, keyPem = null) =
return [keyPem, Buffer.from(pem)]; return [keyPem, Buffer.from(pem)];
}; };
/** /**
* Validate that a ALPN certificate contains the expected key authorization * Validate that a ALPN certificate contains the expected key authorization
* *

View File

@@ -36,10 +36,12 @@ class HttpClient {
this.externalAccountBinding = externalAccountBinding; this.externalAccountBinding = externalAccountBinding;
this.maxBadNonceRetries = 5; this.maxBadNonceRetries = 5;
this.directory = null;
this.jwk = null; this.jwk = null;
}
this.directoryCache = null;
this.directoryMaxAge = 86400;
this.directoryTimestamp = 0;
}
/** /**
* HTTP request * HTTP request
@@ -70,17 +72,18 @@ class HttpClient {
return resp; return resp;
} }
/** /**
* Ensure provider directory exists * Get ACME provider directory
* *
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.1 * https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.1
* *
* @returns {Promise} * @returns {Promise<object>} ACME directory contents
*/ */
async getDirectory() { async getDirectory() {
if (!this.directory) { const age = (Math.floor(Date.now() / 1000) - this.directoryTimestamp);
if (!this.directoryCache || (age > this.directoryMaxAge)) {
const resp = await this.request(this.directoryUrl, 'get'); const resp = await this.request(this.directoryUrl, 'get');
if (resp.status >= 400) { if (resp.status >= 400) {
@@ -91,10 +94,11 @@ class HttpClient {
throw new Error('Attempting to read ACME directory returned no data'); throw new Error('Attempting to read ACME directory returned no data');
} }
this.directory = resp.data; this.directoryCache = resp.data;
} }
}
return this.directoryCache;
}
/** /**
* Get JSON Web Key * Get JSON Web Key
@@ -110,7 +114,6 @@ class HttpClient {
return this.jwk; return this.jwk;
} }
/** /**
* Get nonce from directory API endpoint * Get nonce from directory API endpoint
* *
@@ -130,7 +133,6 @@ class HttpClient {
return resp.headers['replay-nonce']; return resp.headers['replay-nonce'];
} }
/** /**
* Get URL for a directory resource * Get URL for a directory resource
* *
@@ -139,16 +141,15 @@ class HttpClient {
*/ */
async getResourceUrl(resource) { async getResourceUrl(resource) {
await this.getDirectory(); const dir = await this.getDirectory();
if (!this.directory[resource]) { if (!dir[resource]) {
throw new Error(`Unable to locate API resource URL in ACME directory: "${resource}"`); throw new Error(`Unable to locate API resource URL in ACME directory: "${resource}"`);
} }
return this.directory[resource]; return dir[resource];
} }
/** /**
* Get directory meta field * Get directory meta field
* *
@@ -157,16 +158,15 @@ class HttpClient {
*/ */
async getMetaField(field) { async getMetaField(field) {
await this.getDirectory(); const dir = await this.getDirectory();
if (('meta' in this.directory) && (field in this.directory.meta)) { if (('meta' in dir) && (field in dir.meta)) {
return this.directory.meta[field]; return dir.meta[field];
} }
return null; return null;
} }
/** /**
* Prepare HTTP request body for signature * Prepare HTTP request body for signature
* *
@@ -199,11 +199,10 @@ class HttpClient {
/* Body */ /* Body */
return { return {
payload: payload ? Buffer.from(JSON.stringify(payload)).toString('base64url') : '', payload: payload ? Buffer.from(JSON.stringify(payload)).toString('base64url') : '',
protected: Buffer.from(JSON.stringify(header)).toString('base64url') protected: Buffer.from(JSON.stringify(header)).toString('base64url'),
}; };
} }
/** /**
* Create JWS HTTP request body using HMAC * Create JWS HTTP request body using HMAC
* *
@@ -226,7 +225,6 @@ class HttpClient {
return result; return result;
} }
/** /**
* Create JWS HTTP request body using RSA or ECC * Create JWS HTTP request body using RSA or ECC
* *
@@ -267,13 +265,12 @@ class HttpClient {
result.signature = signer.sign({ result.signature = signer.sign({
key: this.accountKey, key: this.accountKey,
padding: RSA_PKCS1_PADDING, padding: RSA_PKCS1_PADDING,
dsaEncoding: 'ieee-p1363' dsaEncoding: 'ieee-p1363',
}, 'base64url'); }, 'base64url');
return result; return result;
} }
/** /**
* Signed HTTP request * Signed HTTP request
* *
@@ -309,7 +306,7 @@ class HttpClient {
const data = this.createSignedBody(url, payload, { nonce, kid }); const data = this.createSignedBody(url, payload, { nonce, kid });
const resp = await this.request(url, 'post', { data }); const resp = await this.request(url, 'post', { data });
/* Retry on bad nonce - https://datatracker.ietf.org/doc/html/draft-ietf-acme-acme-10#section-6.4 */ /* Retry on bad nonce - https://datatracker.ietf.org/doc/html/rfc8555#section-6.5 */
if (resp.data && resp.data.type && (resp.status === 400) && (resp.data.type === 'urn:ietf:params:acme:error:badNonce') && (attempts < this.maxBadNonceRetries)) { if (resp.data && resp.data.type && (resp.status === 400) && (resp.data.type === 'urn:ietf:params:acme:error:badNonce') && (attempts < this.maxBadNonceRetries)) {
nonce = resp.headers['replay-nonce'] || null; nonce = resp.headers['replay-nonce'] || null;
attempts += 1; attempts += 1;
@@ -323,6 +320,5 @@ class HttpClient {
} }
} }
/* Export client */ /* Export client */
module.exports = HttpClient; module.exports = HttpClient;

View File

@@ -4,7 +4,6 @@
exports.Client = require('./client'); exports.Client = require('./client');
/** /**
* Directory URLs * Directory URLs
*/ */
@@ -12,18 +11,22 @@ exports.Client = require('./client');
exports.directory = { exports.directory = {
buypass: { buypass: {
staging: 'https://api.test4.buypass.no/acme/directory', staging: 'https://api.test4.buypass.no/acme/directory',
production: 'https://api.buypass.com/acme/directory' production: 'https://api.buypass.com/acme/directory',
},
google: {
staging: 'https://dv.acme-v02.test-api.pki.goog/directory',
production: 'https://dv.acme-v02.api.pki.goog/directory',
}, },
letsencrypt: { letsencrypt: {
staging: 'https://acme-staging-v02.api.letsencrypt.org/directory', staging: 'https://acme-staging-v02.api.letsencrypt.org/directory',
production: 'https://acme-v02.api.letsencrypt.org/directory' production: 'https://acme-v02.api.letsencrypt.org/directory',
}, },
zerossl: { zerossl: {
production: 'https://acme.zerossl.com/v2/DV90' staging: 'https://acme.zerossl.com/v2/DV90',
} production: 'https://acme.zerossl.com/v2/DV90',
},
}; };
/** /**
* Crypto * Crypto
*/ */
@@ -31,14 +34,12 @@ exports.directory = {
exports.crypto = require('./crypto'); exports.crypto = require('./crypto');
exports.forge = require('./crypto/forge'); exports.forge = require('./crypto/forge');
/** /**
* Axios * Axios
*/ */
exports.axios = require('./axios'); exports.axios = require('./axios');
/** /**
* Logger * Logger
*/ */

View File

@@ -6,7 +6,6 @@ const debug = require('debug')('acme-client');
let logger = () => {}; let logger = () => {};
/** /**
* Set logger function * Set logger function
* *
@@ -17,11 +16,10 @@ exports.setLogger = (fn) => {
logger = fn; logger = fn;
}; };
/** /**
* Log message * Log message
* *
* @param {string} Message * @param {string} msg Message
*/ */
exports.log = (msg) => { exports.log = (msg) => {

View File

@@ -7,7 +7,6 @@ const dns = require('dns').promises;
const { readCertificateInfo, splitPemChain } = require('./crypto'); const { readCertificateInfo, splitPemChain } = require('./crypto');
const { log } = require('./logger'); const { log } = require('./logger');
/** /**
* Exponential backoff * Exponential backoff
* *
@@ -26,7 +25,6 @@ class Backoff {
this.attempts = 0; this.attempts = 0;
} }
/** /**
* Get backoff duration * Get backoff duration
* *
@@ -40,7 +38,6 @@ class Backoff {
} }
} }
/** /**
* Retry promise * Retry promise
* *
@@ -70,7 +67,6 @@ async function retryPromise(fn, attempts, backoff) {
} }
} }
/** /**
* Retry promise * Retry promise
* *
@@ -87,7 +83,6 @@ function retry(fn, { attempts = 5, min = 5000, max = 30000 } = {}) {
return retryPromise(fn, attempts, backoff); return retryPromise(fn, attempts, backoff);
} }
/** /**
* Parse URLs from link header * Parse URLs from link header
* *
@@ -107,7 +102,6 @@ function parseLinkHeader(header, rel = 'alternate') {
return results.filter((r) => r); return results.filter((r) => r);
} }
/** /**
* Find certificate chain with preferred issuer common name * Find certificate chain with preferred issuer common name
* - If issuer is found in multiple chains, the closest to root wins * - If issuer is found in multiple chains, the closest to root wins
@@ -157,7 +151,6 @@ function findCertificateChainForIssuer(chains, issuer) {
return chains[0]; return chains[0];
} }
/** /**
* Find and format error in response object * Find and format error in response object
* *
@@ -178,7 +171,6 @@ function formatResponseError(resp) {
return result.replace(/\n/g, ''); return result.replace(/\n/g, '');
} }
/** /**
* Resolve root domain name by looking for SOA record * Resolve root domain name by looking for SOA record
* *
@@ -204,7 +196,6 @@ async function resolveDomainBySoaRecord(recordName) {
} }
} }
/** /**
* Get DNS resolver using domains authoritative NS records * Get DNS resolver using domains authoritative NS records
* *
@@ -245,7 +236,6 @@ async function getAuthoritativeDnsResolver(recordName) {
return resolver; return resolver;
} }
/** /**
* Attempt to retrieve TLS ALPN certificate from peer * Attempt to retrieve TLS ALPN certificate from peer
* *
@@ -267,7 +257,7 @@ async function retrieveTlsAlpnCertificate(host, port, timeout = 30000) {
port, port,
servername: host, servername: host,
rejectUnauthorized: false, rejectUnauthorized: false,
ALPNProtocols: ['acme-tls/1'] ALPNProtocols: ['acme-tls/1'],
}); });
socket.setTimeout(timeout); socket.setTimeout(timeout);
@@ -299,7 +289,6 @@ async function retrieveTlsAlpnCertificate(host, port, timeout = 30000) {
}); });
} }
/** /**
* Export utils * Export utils
*/ */
@@ -310,5 +299,5 @@ module.exports = {
findCertificateChainForIssuer, findCertificateChainForIssuer,
formatResponseError, formatResponseError,
getAuthoritativeDnsResolver, getAuthoritativeDnsResolver,
retrieveTlsAlpnCertificate retrieveTlsAlpnCertificate,
}; };

View File

@@ -9,7 +9,6 @@ const axios = require('./axios');
const util = require('./util'); const util = require('./util');
const { isAlpnCertificateAuthorizationValid } = require('./crypto'); const { isAlpnCertificateAuthorizationValid } = require('./crypto');
/** /**
* Verify ACME HTTP challenge * Verify ACME HTTP challenge
* *
@@ -43,25 +42,24 @@ async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix =
return true; return true;
} }
/** /**
* Walk DNS until TXT records are found * Walk DNS until TXT records are found
*/ */
async function walkDnsChallengeRecord(recordName, resolver = dns) { async function walkDnsChallengeRecord(recordName, resolver = dns) {
/* Resolve CNAME record first */ /* Resolve CNAME record first */
try { // try {
log(`Checking name for CNAME records: ${recordName}`); // log(`Checking name for CNAME records: ${recordName}`);
const cnameRecords = await resolver.resolveCname(recordName); // const cnameRecords = await resolver.resolveCname(recordName);
//
if (cnameRecords.length) { // if (cnameRecords.length) {
log(`CNAME record found at ${recordName}, new challenge record name: ${cnameRecords[0]}`); // log(`CNAME record found at ${recordName}, new challenge record name: ${cnameRecords[0]}`);
return walkDnsChallengeRecord(cnameRecords[0]); // return walkDnsChallengeRecord(cnameRecords[0]);
} // }
} // }
catch (e) { // catch (e) {
log(`No CNAME records found for name: ${recordName}`); // log(`No CNAME records found for name: ${recordName}`);
} // }
/* Resolve TXT records */ /* Resolve TXT records */
try { try {
@@ -81,7 +79,6 @@ async function walkDnsChallengeRecord(recordName, resolver = dns) {
throw new Error(`No TXT records found for name: ${recordName}`); throw new Error(`No TXT records found for name: ${recordName}`);
} }
/** /**
* Verify ACME DNS challenge * Verify ACME DNS challenge
* *
@@ -121,7 +118,6 @@ async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '
return true; return true;
} }
/** /**
* Verify ACME TLS ALPN challenge * Verify ACME TLS ALPN challenge
* *
@@ -149,7 +145,6 @@ async function verifyTlsAlpnChallenge(authz, challenge, keyAuthorization) {
return true; return true;
} }
/** /**
* Export API * Export API
*/ */
@@ -157,5 +152,5 @@ async function verifyTlsAlpnChallenge(authz, challenge, keyAuthorization) {
module.exports = { module.exports = {
'http-01': verifyHttpChallenge, 'http-01': verifyHttpChallenge,
'dns-01': verifyDnsChallenge, 'dns-01': verifyDnsChallenge,
'tls-alpn-01': verifyTlsAlpnChallenge 'tls-alpn-01': verifyTlsAlpnChallenge,
}; };

View File

@@ -0,0 +1,9 @@
async function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
module.exports = {
wait
};

View File

@@ -16,7 +16,6 @@ const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443; const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
const tlsAlpnPort = axios.defaults.acmeSettings.tlsAlpnChallengePort || 443; const tlsAlpnPort = axios.defaults.acmeSettings.tlsAlpnChallengePort || 443;
describe('pebble', () => { describe('pebble', () => {
const httpsAgent = new https.Agent({ rejectUnauthorized: false }); const httpsAgent = new https.Agent({ rejectUnauthorized: false });
@@ -39,18 +38,16 @@ describe('pebble', () => {
const testTlsAlpn01ChallengeHost = `${uuid()}.${domainName}`; const testTlsAlpn01ChallengeHost = `${uuid()}.${domainName}`;
const testTlsAlpn01ChallengeValue = uuid(); const testTlsAlpn01ChallengeValue = uuid();
/** /**
* Pebble CTS required * Pebble CTS required
*/ */
before(function() { before(function () {
if (!cts.isEnabled()) { if (!cts.isEnabled()) {
this.skip(); this.skip();
} }
}); });
/** /**
* DNS mocking * DNS mocking
*/ */
@@ -92,7 +89,6 @@ describe('pebble', () => {
}); });
}); });
/** /**
* HTTP-01 challenge response * HTTP-01 challenge response
*/ */
@@ -118,7 +114,6 @@ describe('pebble', () => {
}); });
}); });
/** /**
* HTTPS-01 challenge response * HTTPS-01 challenge response
*/ */
@@ -143,7 +138,7 @@ describe('pebble', () => {
/* Assert HTTP 302 */ /* Assert HTTP 302 */
const resp = await axios.get(`http://${testHttps01ChallengeHost}:${httpPort}/.well-known/acme-challenge/${testHttps01ChallengeToken}`, { const resp = await axios.get(`http://${testHttps01ChallengeHost}:${httpPort}/.well-known/acme-challenge/${testHttps01ChallengeToken}`, {
maxRedirects: 0, maxRedirects: 0,
validateStatus: null validateStatus: null,
}); });
assert.strictEqual(resp.status, 302); assert.strictEqual(resp.status, 302);
@@ -165,7 +160,6 @@ describe('pebble', () => {
}); });
}); });
/** /**
* DNS-01 challenge response * DNS-01 challenge response
*/ */
@@ -188,7 +182,6 @@ describe('pebble', () => {
}); });
}); });
/** /**
* TLS-ALPN-01 challenge response * TLS-ALPN-01 challenge response
*/ */

View File

@@ -9,7 +9,6 @@ const axios = require('./../src/axios');
const HttpClient = require('./../src/http'); const HttpClient = require('./../src/http');
const pkg = require('./../package.json'); const pkg = require('./../package.json');
describe('http', () => { describe('http', () => {
let testClient; let testClient;
@@ -20,7 +19,6 @@ describe('http', () => {
const defaultUaEndpoint = `http://${uuid()}.example.com`; const defaultUaEndpoint = `http://${uuid()}.example.com`;
const customUaEndpoint = `http://${uuid()}.example.com`; const customUaEndpoint = `http://${uuid()}.example.com`;
/** /**
* HTTP mocking * HTTP mocking
*/ */
@@ -43,7 +41,6 @@ describe('http', () => {
axios.defaults.headers.common['User-Agent'] = defaultUserAgent; axios.defaults.headers.common['User-Agent'] = defaultUserAgent;
}); });
/** /**
* Initialize * Initialize
*/ */
@@ -52,7 +49,6 @@ describe('http', () => {
testClient = new HttpClient(); testClient = new HttpClient();
}); });
/** /**
* HTTP verbs * HTTP verbs
*/ */
@@ -65,7 +61,6 @@ describe('http', () => {
assert.strictEqual(resp.data, 'ok'); assert.strictEqual(resp.data, 'ok');
}); });
/** /**
* User-Agent * User-Agent
*/ */

View File

@@ -5,7 +5,6 @@
const { assert } = require('chai'); const { assert } = require('chai');
const logger = require('./../src/logger'); const logger = require('./../src/logger');
describe('logger', () => { describe('logger', () => {
let lastLogMessage = null; let lastLogMessage = null;
@@ -13,7 +12,6 @@ describe('logger', () => {
lastLogMessage = msg; lastLogMessage = msg;
} }
/** /**
* Logger * Logger
*/ */
@@ -23,7 +21,6 @@ describe('logger', () => {
assert.isNull(lastLogMessage); assert.isNull(lastLogMessage);
}); });
it('should log with custom logger', () => { it('should log with custom logger', () => {
logger.setLogger(customLoggerFn); logger.setLogger(customLoggerFn);

View File

@@ -9,7 +9,6 @@ const verify = require('./../src/verify');
const domainName = process.env.ACME_DOMAIN_NAME || 'example.com'; const domainName = process.env.ACME_DOMAIN_NAME || 'example.com';
describe('verify', () => { describe('verify', () => {
const challengeTypes = ['http-01', 'dns-01']; const challengeTypes = ['http-01', 'dns-01'];
@@ -30,18 +29,16 @@ describe('verify', () => {
const testTlsAlpn01Challenge = { type: 'dns-01', status: 'pending', token: uuid() }; const testTlsAlpn01Challenge = { type: 'dns-01', status: 'pending', token: uuid() };
const testTlsAlpn01Key = uuid(); const testTlsAlpn01Key = uuid();
/** /**
* Pebble CTS required * Pebble CTS required
*/ */
before(function() { before(function () {
if (!cts.isEnabled()) { if (!cts.isEnabled()) {
this.skip(); this.skip();
} }
}); });
/** /**
* API * API
*/ */
@@ -50,7 +47,6 @@ describe('verify', () => {
assert.containsAllKeys(verify, challengeTypes); assert.containsAllKeys(verify, challengeTypes);
}); });
/** /**
* http-01 * http-01
*/ */
@@ -81,7 +77,6 @@ describe('verify', () => {
}); });
}); });
/** /**
* https-01 * https-01
*/ */
@@ -102,7 +97,6 @@ describe('verify', () => {
}); });
}); });
/** /**
* dns-01 * dns-01
*/ */
@@ -133,7 +127,6 @@ describe('verify', () => {
}); });
}); });
/** /**
* tls-alpn-01 * tls-alpn-01
*/ */

View File

@@ -9,10 +9,9 @@ const spec = require('./spec');
const forge = require('./../src/crypto/forge'); const forge = require('./../src/crypto/forge');
const cryptoEngines = { const cryptoEngines = {
forge forge,
}; };
describe('crypto-legacy', () => { describe('crypto-legacy', () => {
let testPemKey; let testPemKey;
let testCert; let testCert;
@@ -28,7 +27,6 @@ describe('crypto-legacy', () => {
const testCertPath = path.join(__dirname, 'fixtures', 'certificate.crt'); const testCertPath = path.join(__dirname, 'fixtures', 'certificate.crt');
const testSanCertPath = path.join(__dirname, 'fixtures', 'san-certificate.crt'); const testSanCertPath = path.join(__dirname, 'fixtures', 'san-certificate.crt');
/** /**
* Fixtures * Fixtures
*/ */
@@ -50,7 +48,6 @@ describe('crypto-legacy', () => {
}); });
}); });
/** /**
* Engines * Engines
*/ */
@@ -62,7 +59,6 @@ describe('crypto-legacy', () => {
let testNonCnCsr; let testNonCnCsr;
let testNonAsciiCsr; let testNonAsciiCsr;
/** /**
* Key generation * Key generation
*/ */
@@ -83,14 +79,13 @@ describe('crypto-legacy', () => {
publicKeyStore.push(key.toString().replace(/[\r\n]/gm, '')); publicKeyStore.push(key.toString().replace(/[\r\n]/gm, ''));
}); });
/** /**
* Certificate Signing Request * Certificate Signing Request
*/ */
it('should generate a csr', async () => { it('should generate a csr', async () => {
const [key, csr] = await engine.createCsr({ const [key, csr] = await engine.createCsr({
commonName: testCsrDomain commonName: testCsrDomain,
}); });
assert.isTrue(Buffer.isBuffer(key)); assert.isTrue(Buffer.isBuffer(key));
@@ -102,7 +97,7 @@ describe('crypto-legacy', () => {
it('should generate a san csr', async () => { it('should generate a san csr', async () => {
const [key, csr] = await engine.createCsr({ const [key, csr] = await engine.createCsr({
commonName: testSanCsrDomains[0], commonName: testSanCsrDomains[0],
altNames: testSanCsrDomains.slice(1, testSanCsrDomains.length) altNames: testSanCsrDomains.slice(1, testSanCsrDomains.length),
}); });
assert.isTrue(Buffer.isBuffer(key)); assert.isTrue(Buffer.isBuffer(key));
@@ -113,7 +108,7 @@ describe('crypto-legacy', () => {
it('should generate a csr without common name', async () => { it('should generate a csr without common name', async () => {
const [key, csr] = await engine.createCsr({ const [key, csr] = await engine.createCsr({
altNames: testSanCsrDomains altNames: testSanCsrDomains,
}); });
assert.isTrue(Buffer.isBuffer(key)); assert.isTrue(Buffer.isBuffer(key));
@@ -126,7 +121,7 @@ describe('crypto-legacy', () => {
const [key, csr] = await engine.createCsr({ const [key, csr] = await engine.createCsr({
commonName: testCsrDomain, commonName: testCsrDomain,
organization: '大安區', organization: '大安區',
organizationUnit: '中文部門' organizationUnit: '中文部門',
}); });
assert.isTrue(Buffer.isBuffer(key)); assert.isTrue(Buffer.isBuffer(key));
@@ -167,7 +162,6 @@ describe('crypto-legacy', () => {
assert.deepStrictEqual(result.altNames, [testCsrDomain]); assert.deepStrictEqual(result.altNames, [testCsrDomain]);
}); });
/** /**
* Certificate * Certificate
*/ */
@@ -188,7 +182,6 @@ describe('crypto-legacy', () => {
assert.deepEqual(info.domains.altNames, testSanCsrDomains.slice(1, testSanCsrDomains.length)); assert.deepEqual(info.domains.altNames, testSanCsrDomains.slice(1, testSanCsrDomains.length));
}); });
/** /**
* PEM utils * PEM utils
*/ */
@@ -214,7 +207,6 @@ describe('crypto-legacy', () => {
}); });
}); });
/** /**
* Modulus and exponent * Modulus and exponent
*/ */
@@ -246,7 +238,6 @@ describe('crypto-legacy', () => {
}); });
}); });
/** /**
* Verify identical results * Verify identical results
*/ */

View File

@@ -50,7 +50,6 @@ dGVzdGluZ3Rlc3Rpbmd0ZXN0aW5ndGVzdGluZ3Rlc3Rpbmd0ZXN0aW5ndGVzdGluZ3Rlc3Rpbmd0ZXN0
-----END TEST----- -----END TEST-----
`; `;
describe('crypto', () => { describe('crypto', () => {
const testCsrDomain = 'example.com'; const testCsrDomain = 'example.com';
const testSanCsrDomains = ['example.com', 'test.example.com', 'abc.example.com']; const testSanCsrDomains = ['example.com', 'test.example.com', 'abc.example.com'];
@@ -58,7 +57,6 @@ describe('crypto', () => {
const testCertPath = path.join(__dirname, 'fixtures', 'certificate.crt'); const testCertPath = path.join(__dirname, 'fixtures', 'certificate.crt');
const testSanCertPath = path.join(__dirname, 'fixtures', 'san-certificate.crt'); const testSanCertPath = path.join(__dirname, 'fixtures', 'san-certificate.crt');
/** /**
* Key types * Key types
*/ */
@@ -68,24 +66,23 @@ describe('crypto', () => {
createKeyFns: { createKeyFns: {
s1024: () => crypto.createPrivateRsaKey(1024), s1024: () => crypto.createPrivateRsaKey(1024),
s2048: () => crypto.createPrivateRsaKey(), s2048: () => crypto.createPrivateRsaKey(),
s4096: () => crypto.createPrivateRsaKey(4096) s4096: () => crypto.createPrivateRsaKey(4096),
}, },
jwkSpecFn: spec.jwk.rsa jwkSpecFn: spec.jwk.rsa,
}, },
ecdsa: { ecdsa: {
createKeyFns: { createKeyFns: {
p256: () => crypto.createPrivateEcdsaKey(), p256: () => crypto.createPrivateEcdsaKey(),
p384: () => crypto.createPrivateEcdsaKey('P-384'), p384: () => crypto.createPrivateEcdsaKey('P-384'),
p521: () => crypto.createPrivateEcdsaKey('P-521') p521: () => crypto.createPrivateEcdsaKey('P-521'),
}, },
jwkSpecFn: spec.jwk.ecdsa jwkSpecFn: spec.jwk.ecdsa,
} },
}).forEach(([name, { createKeyFns, jwkSpecFn }]) => { }).forEach(([name, { createKeyFns, jwkSpecFn }]) => {
describe(name, () => { describe(name, () => {
const testPrivateKeys = {}; const testPrivateKeys = {};
const testPublicKeys = {}; const testPublicKeys = {};
/** /**
* Iterate through all generator variations * Iterate through all generator variations
*/ */
@@ -97,7 +94,6 @@ describe('crypto', () => {
let testNonAsciiCsr; let testNonAsciiCsr;
let testAlpnCertificate; let testAlpnCertificate;
/** /**
* Keys and JWK * Keys and JWK
*/ */
@@ -132,14 +128,13 @@ describe('crypto', () => {
jwkSpecFn(jwk); jwkSpecFn(jwk);
}); });
/** /**
* Certificate Signing Request * Certificate Signing Request
*/ */
it(`${n}/should generate a csr`, async () => { it(`${n}/should generate a csr`, async () => {
const [key, csr] = await crypto.createCsr({ const [key, csr] = await crypto.createCsr({
commonName: testCsrDomain commonName: testCsrDomain,
}, testPrivateKeys[n]); }, testPrivateKeys[n]);
assert.isTrue(Buffer.isBuffer(key)); assert.isTrue(Buffer.isBuffer(key));
@@ -151,7 +146,7 @@ describe('crypto', () => {
it(`${n}/should generate a san csr`, async () => { it(`${n}/should generate a san csr`, async () => {
const [key, csr] = await crypto.createCsr({ const [key, csr] = await crypto.createCsr({
commonName: testSanCsrDomains[0], commonName: testSanCsrDomains[0],
altNames: testSanCsrDomains.slice(1, testSanCsrDomains.length) altNames: testSanCsrDomains.slice(1, testSanCsrDomains.length),
}, testPrivateKeys[n]); }, testPrivateKeys[n]);
assert.isTrue(Buffer.isBuffer(key)); assert.isTrue(Buffer.isBuffer(key));
@@ -162,7 +157,7 @@ describe('crypto', () => {
it(`${n}/should generate a csr without common name`, async () => { it(`${n}/should generate a csr without common name`, async () => {
const [key, csr] = await crypto.createCsr({ const [key, csr] = await crypto.createCsr({
altNames: testSanCsrDomains altNames: testSanCsrDomains,
}, testPrivateKeys[n]); }, testPrivateKeys[n]);
assert.isTrue(Buffer.isBuffer(key)); assert.isTrue(Buffer.isBuffer(key));
@@ -175,7 +170,7 @@ describe('crypto', () => {
const [key, csr] = await crypto.createCsr({ const [key, csr] = await crypto.createCsr({
commonName: testCsrDomain, commonName: testCsrDomain,
organization: '大安區', organization: '大安區',
organizationUnit: '中文部門' organizationUnit: '中文部門',
}, testPrivateKeys[n]); }, testPrivateKeys[n]);
assert.isTrue(Buffer.isBuffer(key)); assert.isTrue(Buffer.isBuffer(key));
@@ -186,7 +181,7 @@ describe('crypto', () => {
it(`${n}/should generate a csr with key as string`, async () => { it(`${n}/should generate a csr with key as string`, async () => {
const [key, csr] = await crypto.createCsr({ const [key, csr] = await crypto.createCsr({
commonName: testCsrDomain commonName: testCsrDomain,
}, testPrivateKeys[n].toString()); }, testPrivateKeys[n].toString());
assert.isTrue(Buffer.isBuffer(key)); assert.isTrue(Buffer.isBuffer(key));
@@ -195,11 +190,10 @@ describe('crypto', () => {
it(`${n}/should throw with invalid key`, async () => { it(`${n}/should throw with invalid key`, async () => {
await assert.isRejected(crypto.createCsr({ await assert.isRejected(crypto.createCsr({
commonName: testCsrDomain commonName: testCsrDomain,
}, testPublicKeys[n])); }, testPublicKeys[n]));
}); });
/** /**
* Domain and info resolver * Domain and info resolver
*/ */
@@ -243,7 +237,6 @@ describe('crypto', () => {
}); });
}); });
/** /**
* ALPN * ALPN
*/ */
@@ -284,7 +277,6 @@ describe('crypto', () => {
}); });
}); });
/** /**
* Common functionality * Common functionality
*/ */
@@ -294,7 +286,6 @@ describe('crypto', () => {
let testCert; let testCert;
let testSanCert; let testSanCert;
it('should read private key fixture', async () => { it('should read private key fixture', async () => {
testPemKey = await fs.readFile(testKeyPath); testPemKey = await fs.readFile(testKeyPath);
assert.isTrue(Buffer.isBuffer(testPemKey)); assert.isTrue(Buffer.isBuffer(testPemKey));
@@ -310,21 +301,19 @@ describe('crypto', () => {
assert.isTrue(Buffer.isBuffer(testSanCert)); assert.isTrue(Buffer.isBuffer(testSanCert));
}); });
/** /**
* CSR with auto-generated key * CSR with auto-generated key
*/ */
it('should generate a csr with default key', async () => { it('should generate a csr with default key', async () => {
const [key, csr] = await crypto.createCsr({ const [key, csr] = await crypto.createCsr({
commonName: testCsrDomain commonName: testCsrDomain,
}); });
assert.isTrue(Buffer.isBuffer(key)); assert.isTrue(Buffer.isBuffer(key));
assert.isTrue(Buffer.isBuffer(csr)); assert.isTrue(Buffer.isBuffer(csr));
}); });
/** /**
* Certificate * Certificate
*/ */
@@ -352,7 +341,6 @@ describe('crypto', () => {
}); });
}); });
/** /**
* ALPN * ALPN
*/ */
@@ -365,7 +353,6 @@ describe('crypto', () => {
assert.isTrue(Buffer.isBuffer(cert)); assert.isTrue(Buffer.isBuffer(cert));
}); });
/** /**
* PEM utils * PEM utils
*/ */

View File

@@ -20,35 +20,32 @@ const clientOpts = {
directoryUrl, directoryUrl,
backoffAttempts: 5, backoffAttempts: 5,
backoffMin: 1000, backoffMin: 1000,
backoffMax: 5000 backoffMax: 5000,
}; };
if (capEabEnabled && process.env.ACME_EAB_KID && process.env.ACME_EAB_HMAC_KEY) { if (capEabEnabled && process.env.ACME_EAB_KID && process.env.ACME_EAB_HMAC_KEY) {
clientOpts.externalAccountBinding = { clientOpts.externalAccountBinding = {
kid: process.env.ACME_EAB_KID, kid: process.env.ACME_EAB_KID,
hmacKey: process.env.ACME_EAB_HMAC_KEY hmacKey: process.env.ACME_EAB_HMAC_KEY,
}; };
} }
describe('client', () => { describe('client', () => {
const testDomain = `${uuid()}.${domainName}`; const testDomain = `${uuid()}.${domainName}`;
const testDomainAlpn = `${uuid()}.${domainName}`; const testDomainAlpn = `${uuid()}.${domainName}`;
const testDomainWildcard = `*.${testDomain}`; const testDomainWildcard = `*.${testDomain}`;
const testContact = `mailto:test-${uuid()}@nope.com`; const testContact = `mailto:test-${uuid()}@nope.com`;
/** /**
* Pebble CTS required * Pebble CTS required
*/ */
before(function() { before(function () {
if (!cts.isEnabled()) { if (!cts.isEnabled()) {
this.skip(); this.skip();
} }
}); });
/** /**
* Key types * Key types
*/ */
@@ -58,18 +55,18 @@ describe('client', () => {
createKeyFn: () => acme.crypto.createPrivateRsaKey(), createKeyFn: () => acme.crypto.createPrivateRsaKey(),
createKeyAltFns: { createKeyAltFns: {
s1024: () => acme.crypto.createPrivateRsaKey(1024), s1024: () => acme.crypto.createPrivateRsaKey(1024),
s4096: () => acme.crypto.createPrivateRsaKey(4096) s4096: () => acme.crypto.createPrivateRsaKey(4096),
}, },
jwkSpecFn: spec.jwk.rsa jwkSpecFn: spec.jwk.rsa,
}, },
ecdsa: { ecdsa: {
createKeyFn: () => acme.crypto.createPrivateEcdsaKey(), createKeyFn: () => acme.crypto.createPrivateEcdsaKey(),
createKeyAltFns: { createKeyAltFns: {
p384: () => acme.crypto.createPrivateEcdsaKey('P-384'), p384: () => acme.crypto.createPrivateEcdsaKey('P-384'),
p521: () => acme.crypto.createPrivateEcdsaKey('P-521') p521: () => acme.crypto.createPrivateEcdsaKey('P-521'),
}, },
jwkSpecFn: spec.jwk.ecdsa jwkSpecFn: spec.jwk.ecdsa,
} },
}).forEach(([name, { createKeyFn, createKeyAltFns, jwkSpecFn }]) => { }).forEach(([name, { createKeyFn, createKeyAltFns, jwkSpecFn }]) => {
describe(name, () => { describe(name, () => {
let testIssuers; let testIssuers;
@@ -97,7 +94,6 @@ describe('client', () => {
let testCertificateAlpn; let testCertificateAlpn;
let testCertificateWildcard; let testCertificateWildcard;
/** /**
* Fixtures * Fixtures
*/ */
@@ -114,11 +110,11 @@ describe('client', () => {
it('should generate certificate signing request', async () => { it('should generate certificate signing request', async () => {
[, testCsr] = await acme.crypto.createCsr({ commonName: testDomain }, await createKeyFn()); [, testCsr] = await acme.crypto.createCsr({ commonName: testDomain }, await createKeyFn());
[, testCsrAlpn] = await acme.crypto.createCsr({ commonName: testDomainAlpn }, await createKeyFn()); [, testCsrAlpn] = await acme.crypto.createCsr({ altNames: [testDomainAlpn] }, await createKeyFn());
[, testCsrWildcard] = await acme.crypto.createCsr({ commonName: testDomainWildcard }, await createKeyFn()); [, testCsrWildcard] = await acme.crypto.createCsr({ altNames: [testDomainWildcard] }, await createKeyFn());
}); });
it('should resolve certificate issuers [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() { it('should resolve certificate issuers [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function () {
if (!capAlternateCertRoots) { if (!capAlternateCertRoots) {
this.skip(); this.skip();
} }
@@ -134,7 +130,6 @@ describe('client', () => {
}); });
}); });
/** /**
* Initialize clients * Initialize clients
*/ */
@@ -142,7 +137,7 @@ describe('client', () => {
it('should initialize client', () => { it('should initialize client', () => {
testClient = new acme.Client({ testClient = new acme.Client({
...clientOpts, ...clientOpts,
accountKey: testAccountKey accountKey: testAccountKey,
}); });
}); });
@@ -151,12 +146,11 @@ describe('client', () => {
jwkSpecFn(jwk); jwkSpecFn(jwk);
}); });
/** /**
* Terms of Service * Terms of Service
*/ */
it('should produce tos url [ACME_CAP_META_TOS_FIELD]', async function() { it('should produce tos url [ACME_CAP_META_TOS_FIELD]', async function () {
if (!capMetaTosField) { if (!capMetaTosField) {
this.skip(); this.skip();
} }
@@ -165,7 +159,7 @@ describe('client', () => {
assert.isString(tos); assert.isString(tos);
}); });
it('should not produce tos url [!ACME_CAP_META_TOS_FIELD]', async function() { it('should not produce tos url [!ACME_CAP_META_TOS_FIELD]', async function () {
if (capMetaTosField) { if (capMetaTosField) {
this.skip(); this.skip();
} }
@@ -174,12 +168,11 @@ describe('client', () => {
assert.isNull(tos); assert.isNull(tos);
}); });
/** /**
* Create account * Create account
*/ */
it('should refuse account creation without tos [ACME_CAP_META_TOS_FIELD]', async function() { it('should refuse account creation without tos [ACME_CAP_META_TOS_FIELD]', async function () {
if (!capMetaTosField) { if (!capMetaTosField) {
this.skip(); this.skip();
} }
@@ -187,7 +180,7 @@ describe('client', () => {
await assert.isRejected(testClient.createAccount()); await assert.isRejected(testClient.createAccount());
}); });
it('should refuse account creation without eab [ACME_CAP_EAB_ENABLED]', async function() { it('should refuse account creation without eab [ACME_CAP_EAB_ENABLED]', async function () {
if (!capEabEnabled) { if (!capEabEnabled) {
this.skip(); this.skip();
} }
@@ -195,17 +188,17 @@ describe('client', () => {
const client = new acme.Client({ const client = new acme.Client({
...clientOpts, ...clientOpts,
accountKey: testAccountKey, accountKey: testAccountKey,
externalAccountBinding: null externalAccountBinding: null,
}); });
await assert.isRejected(client.createAccount({ await assert.isRejected(client.createAccount({
termsOfServiceAgreed: true termsOfServiceAgreed: true,
})); }));
}); });
it('should create an account', async () => { it('should create an account', async () => {
testAccount = await testClient.createAccount({ testAccount = await testClient.createAccount({
termsOfServiceAgreed: true termsOfServiceAgreed: true,
}); });
spec.rfc8555.account(testAccount); spec.rfc8555.account(testAccount);
@@ -217,7 +210,6 @@ describe('client', () => {
assert.isString(testAccountUrl); assert.isString(testAccountUrl);
}); });
/** /**
* Create account with alternate key sizes * Create account with alternate key sizes
*/ */
@@ -226,11 +218,11 @@ describe('client', () => {
it(`should create account with key=${k}`, async () => { it(`should create account with key=${k}`, async () => {
const client = new acme.Client({ const client = new acme.Client({
...clientOpts, ...clientOpts,
accountKey: await altKeyFn() accountKey: await altKeyFn(),
}); });
const account = await client.createAccount({ const account = await client.createAccount({
termsOfServiceAgreed: true termsOfServiceAgreed: true,
}); });
spec.rfc8555.account(account); spec.rfc8555.account(account);
@@ -238,7 +230,6 @@ describe('client', () => {
}); });
}); });
/** /**
* Find existing account using secondary client * Find existing account using secondary client
*/ */
@@ -246,22 +237,22 @@ describe('client', () => {
it('should throw when trying to find account using invalid account key', async () => { it('should throw when trying to find account using invalid account key', async () => {
const client = new acme.Client({ const client = new acme.Client({
...clientOpts, ...clientOpts,
accountKey: testAccountSecondaryKey accountKey: testAccountSecondaryKey,
}); });
await assert.isRejected(client.createAccount({ await assert.isRejected(client.createAccount({
onlyReturnExisting: true onlyReturnExisting: true,
})); }));
}); });
it('should find existing account using account key', async () => { it('should find existing account using account key', async () => {
const client = new acme.Client({ const client = new acme.Client({
...clientOpts, ...clientOpts,
accountKey: testAccountKey accountKey: testAccountKey,
}); });
const account = await client.createAccount({ const account = await client.createAccount({
onlyReturnExisting: true onlyReturnExisting: true,
}); });
spec.rfc8555.account(account); spec.rfc8555.account(account);
@@ -269,7 +260,6 @@ describe('client', () => {
assert.deepStrictEqual(account.key, testAccount.key); assert.deepStrictEqual(account.key, testAccount.key);
}); });
/** /**
* Account URL * Account URL
*/ */
@@ -278,7 +268,7 @@ describe('client', () => {
const client = new acme.Client({ const client = new acme.Client({
...clientOpts, ...clientOpts,
accountKey: testAccountKey, accountKey: testAccountKey,
accountUrl: 'https://acme-staging-v02.api.letsencrypt.org/acme/acct/1' accountUrl: 'https://acme-staging-v02.api.letsencrypt.org/acme/acct/1',
}); });
await assert.isRejected(client.updateAccount()); await assert.isRejected(client.updateAccount());
@@ -288,11 +278,11 @@ describe('client', () => {
const client = new acme.Client({ const client = new acme.Client({
...clientOpts, ...clientOpts,
accountKey: testAccountKey, accountKey: testAccountKey,
accountUrl: testAccountUrl accountUrl: testAccountUrl,
}); });
const account = await client.createAccount({ const account = await client.createAccount({
onlyReturnExisting: true onlyReturnExisting: true,
}); });
spec.rfc8555.account(account); spec.rfc8555.account(account);
@@ -300,7 +290,6 @@ describe('client', () => {
assert.deepStrictEqual(account.key, testAccount.key); assert.deepStrictEqual(account.key, testAccount.key);
}); });
/** /**
* Update account contact info * Update account contact info
*/ */
@@ -316,12 +305,11 @@ describe('client', () => {
assert.include(account.contact, testContact); assert.include(account.contact, testContact);
}); });
/** /**
* Change account private key * Change account private key
*/ */
it('should change account private key [ACME_CAP_UPDATE_ACCOUNT_KEY]', async function() { it('should change account private key [ACME_CAP_UPDATE_ACCOUNT_KEY]', async function () {
if (!capUpdateAccountKey) { if (!capUpdateAccountKey) {
this.skip(); this.skip();
} }
@@ -329,7 +317,7 @@ describe('client', () => {
await testClient.updateAccountKey(testAccountSecondaryKey); await testClient.updateAccountKey(testAccountSecondaryKey);
const account = await testClient.createAccount({ const account = await testClient.createAccount({
onlyReturnExisting: true onlyReturnExisting: true,
}); });
spec.rfc8555.account(account); spec.rfc8555.account(account);
@@ -337,7 +325,6 @@ describe('client', () => {
assert.notDeepEqual(account.key, testAccount.key); assert.notDeepEqual(account.key, testAccount.key);
}); });
/** /**
* Create new certificate order * Create new certificate order
*/ */
@@ -357,7 +344,6 @@ describe('client', () => {
}); });
}); });
/** /**
* Get status of existing certificate order * Get status of existing certificate order
*/ */
@@ -371,7 +357,6 @@ describe('client', () => {
})); }));
}); });
/** /**
* Get identifier authorization * Get identifier authorization
*/ */
@@ -401,7 +386,6 @@ describe('client', () => {
}); });
}); });
/** /**
* Generate challenge key authorization * Generate challenge key authorization
*/ */
@@ -418,7 +402,6 @@ describe('client', () => {
[testKeyAuthorization, testKeyAuthorizationAlpn, testKeyAuthorizationWildcard].forEach((k) => assert.isString(k)); [testKeyAuthorization, testKeyAuthorizationAlpn, testKeyAuthorizationWildcard].forEach((k) => assert.isString(k));
}); });
/** /**
* Deactivate identifier authorization * Deactivate identifier authorization
*/ */
@@ -427,8 +410,8 @@ describe('client', () => {
const order = await testClient.createOrder({ const order = await testClient.createOrder({
identifiers: [ identifiers: [
{ type: 'dns', value: `${uuid()}.${domainName}` }, { type: 'dns', value: `${uuid()}.${domainName}` },
{ type: 'dns', value: `${uuid()}.${domainName}` } { type: 'dns', value: `${uuid()}.${domainName}` },
] ],
}); });
const authzCollection = await testClient.getAuthorizations(order); const authzCollection = await testClient.getAuthorizations(order);
@@ -445,7 +428,6 @@ describe('client', () => {
}); });
}); });
/** /**
* Verify satisfied challenge * Verify satisfied challenge
*/ */
@@ -460,7 +442,6 @@ describe('client', () => {
await testClient.verifyChallenge(testAuthzWildcard, testChallengeWildcard); await testClient.verifyChallenge(testAuthzWildcard, testChallengeWildcard);
}); });
/** /**
* Complete challenge * Complete challenge
*/ */
@@ -474,7 +455,6 @@ describe('client', () => {
})); }));
}); });
/** /**
* Wait for valid challenge * Wait for valid challenge
*/ */
@@ -483,7 +463,6 @@ describe('client', () => {
await Promise.all([testChallenge, testChallengeAlpn, testChallengeWildcard].map(async (c) => testClient.waitForValidStatus(c))); await Promise.all([testChallenge, testChallengeAlpn, testChallengeWildcard].map(async (c) => testClient.waitForValidStatus(c)));
}); });
/** /**
* Finalize order * Finalize order
*/ */
@@ -500,7 +479,6 @@ describe('client', () => {
assert.strictEqual(testOrderWildcard.url, finalizeWildcard.url); assert.strictEqual(testOrderWildcard.url, finalizeWildcard.url);
}); });
/** /**
* Wait for valid order * Wait for valid order
*/ */
@@ -509,7 +487,6 @@ describe('client', () => {
await Promise.all([testOrder, testOrderAlpn, testOrderWildcard].map(async (o) => testClient.waitForValidStatus(o))); await Promise.all([testOrder, testOrderAlpn, testOrderWildcard].map(async (o) => testClient.waitForValidStatus(o)));
}); });
/** /**
* Get certificate * Get certificate
*/ */
@@ -525,7 +502,7 @@ describe('client', () => {
}); });
}); });
it('should get alternate certificate chain [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() { it('should get alternate certificate chain [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function () {
if (!capAlternateCertRoots) { if (!capAlternateCertRoots) {
this.skip(); this.skip();
} }
@@ -539,7 +516,7 @@ describe('client', () => {
})); }));
}); });
it('should get default chain with invalid preference [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() { it('should get default chain with invalid preference [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function () {
if (!capAlternateCertRoots) { if (!capAlternateCertRoots) {
this.skip(); this.skip();
} }
@@ -551,7 +528,6 @@ describe('client', () => {
assert.strictEqual(testIssuers[0], info.issuer.commonName); assert.strictEqual(testIssuers[0], info.issuer.commonName);
}); });
/** /**
* Revoke certificate * Revoke certificate
*/ */
@@ -568,7 +544,6 @@ describe('client', () => {
await assert.isRejected(testClient.getCertificate(testOrderWildcard)); await assert.isRejected(testClient.getCertificate(testOrderWildcard));
}); });
/** /**
* Deactivate account * Deactivate account
*/ */
@@ -581,7 +556,6 @@ describe('client', () => {
assert.strictEqual(account.status, 'deactivated'); assert.strictEqual(account.status, 'deactivated');
}); });
/** /**
* Verify that no new orders can be made * Verify that no new orders can be made
*/ */

View File

@@ -18,17 +18,16 @@ const clientOpts = {
directoryUrl, directoryUrl,
backoffAttempts: 5, backoffAttempts: 5,
backoffMin: 1000, backoffMin: 1000,
backoffMax: 5000 backoffMax: 5000,
}; };
if (capEabEnabled && process.env.ACME_EAB_KID && process.env.ACME_EAB_HMAC_KEY) { if (capEabEnabled && process.env.ACME_EAB_KID && process.env.ACME_EAB_HMAC_KEY) {
clientOpts.externalAccountBinding = { clientOpts.externalAccountBinding = {
kid: process.env.ACME_EAB_KID, kid: process.env.ACME_EAB_KID,
hmacKey: process.env.ACME_EAB_HMAC_KEY hmacKey: process.env.ACME_EAB_HMAC_KEY,
}; };
} }
describe('client.auto', () => { describe('client.auto', () => {
const testDomain = `${uuid()}.${domainName}`; const testDomain = `${uuid()}.${domainName}`;
const testHttpDomain = `${uuid()}.${domainName}`; const testHttpDomain = `${uuid()}.${domainName}`;
@@ -40,21 +39,19 @@ describe('client.auto', () => {
const testSanDomains = [ const testSanDomains = [
`${uuid()}.${domainName}`, `${uuid()}.${domainName}`,
`${uuid()}.${domainName}`, `${uuid()}.${domainName}`,
`${uuid()}.${domainName}` `${uuid()}.${domainName}`,
]; ];
/** /**
* Pebble CTS required * Pebble CTS required
*/ */
before(function() { before(function () {
if (!cts.isEnabled()) { if (!cts.isEnabled()) {
this.skip(); this.skip();
} }
}); });
/** /**
* Key types * Key types
*/ */
@@ -64,16 +61,16 @@ describe('client.auto', () => {
createKeyFn: () => acme.crypto.createPrivateRsaKey(), createKeyFn: () => acme.crypto.createPrivateRsaKey(),
createKeyAltFns: { createKeyAltFns: {
s1024: () => acme.crypto.createPrivateRsaKey(1024), s1024: () => acme.crypto.createPrivateRsaKey(1024),
s4096: () => acme.crypto.createPrivateRsaKey(4096) s4096: () => acme.crypto.createPrivateRsaKey(4096),
} },
}, },
ecdsa: { ecdsa: {
createKeyFn: () => acme.crypto.createPrivateEcdsaKey(), createKeyFn: () => acme.crypto.createPrivateEcdsaKey(),
createKeyAltFns: { createKeyAltFns: {
p384: () => acme.crypto.createPrivateEcdsaKey('P-384'), p384: () => acme.crypto.createPrivateEcdsaKey('P-384'),
p521: () => acme.crypto.createPrivateEcdsaKey('P-521') p521: () => acme.crypto.createPrivateEcdsaKey('P-521'),
} },
} },
}).forEach(([name, { createKeyFn, createKeyAltFns }]) => { }).forEach(([name, { createKeyFn, createKeyAltFns }]) => {
describe(name, () => { describe(name, () => {
let testIssuers; let testIssuers;
@@ -82,12 +79,11 @@ describe('client.auto', () => {
let testSanCertificate; let testSanCertificate;
let testWildcardCertificate; let testWildcardCertificate;
/** /**
* Fixtures * Fixtures
*/ */
it('should resolve certificate issuers [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() { it('should resolve certificate issuers [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function () {
if (!capAlternateCertRoots) { if (!capAlternateCertRoots) {
this.skip(); this.skip();
} }
@@ -103,7 +99,6 @@ describe('client.auto', () => {
}); });
}); });
/** /**
* Initialize client * Initialize client
*/ */
@@ -111,31 +106,30 @@ describe('client.auto', () => {
it('should initialize client', async () => { it('should initialize client', async () => {
testClient = new acme.Client({ testClient = new acme.Client({
...clientOpts, ...clientOpts,
accountKey: await createKeyFn() accountKey: await createKeyFn(),
}); });
}); });
/** /**
* Invalid challenge response * Invalid challenge response
*/ */
it('should throw on invalid challenge response', async () => { it('should throw on invalid challenge response', async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: `${uuid()}.${domainName}` commonName: `${uuid()}.${domainName}`,
}, await createKeyFn()); }, await createKeyFn());
await assert.isRejected(testClient.auto({ await assert.isRejected(testClient.auto({
csr, csr,
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn: cts.challengeNoopFn, challengeCreateFn: cts.challengeNoopFn,
challengeRemoveFn: cts.challengeNoopFn challengeRemoveFn: cts.challengeNoopFn,
}), /^authorization not found/i); }), /^authorization not found/i);
}); });
it('should throw on invalid challenge response with opts.skipChallengeVerification=true', async () => { it('should throw on invalid challenge response with opts.skipChallengeVerification=true', async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: `${uuid()}.${domainName}` commonName: `${uuid()}.${domainName}`,
}, await createKeyFn()); }, await createKeyFn());
await assert.isRejected(testClient.auto({ await assert.isRejected(testClient.auto({
@@ -143,38 +137,37 @@ describe('client.auto', () => {
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
skipChallengeVerification: true, skipChallengeVerification: true,
challengeCreateFn: cts.challengeNoopFn, challengeCreateFn: cts.challengeNoopFn,
challengeRemoveFn: cts.challengeNoopFn challengeRemoveFn: cts.challengeNoopFn,
})); }));
}); });
/** /**
* Challenge function exceptions * Challenge function exceptions
*/ */
it('should throw on challengeCreate exception', async () => { it('should throw on challengeCreate exception', async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: `${uuid()}.${domainName}` commonName: `${uuid()}.${domainName}`,
}, await createKeyFn()); }, await createKeyFn());
await assert.isRejected(testClient.auto({ await assert.isRejected(testClient.auto({
csr, csr,
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn: cts.challengeThrowFn, challengeCreateFn: cts.challengeThrowFn,
challengeRemoveFn: cts.challengeNoopFn challengeRemoveFn: cts.challengeNoopFn,
}), /^oops$/); }), /^oops$/);
}); });
it('should not throw on challengeRemove exception', async () => { it('should not throw on challengeRemove exception', async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: `${uuid()}.${domainName}` commonName: `${uuid()}.${domainName}`,
}, await createKeyFn()); }, await createKeyFn());
const cert = await testClient.auto({ const cert = await testClient.auto({
csr, csr,
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn: cts.challengeCreateFn, challengeCreateFn: cts.challengeCreateFn,
challengeRemoveFn: cts.challengeThrowFn challengeRemoveFn: cts.challengeThrowFn,
}); });
assert.isString(cert); assert.isString(cert);
@@ -188,8 +181,8 @@ describe('client.auto', () => {
`${uuid()}.${domainName}`, `${uuid()}.${domainName}`,
`${uuid()}.${domainName}`, `${uuid()}.${domainName}`,
`${uuid()}.${domainName}`, `${uuid()}.${domainName}`,
`${uuid()}.${domainName}` `${uuid()}.${domainName}`,
] ],
}, await createKeyFn()); }, await createKeyFn());
await assert.isRejected(testClient.auto({ await assert.isRejected(testClient.auto({
@@ -205,28 +198,27 @@ describe('client.auto', () => {
results.push(true); results.push(true);
return cts.challengeCreateFn(...args); return cts.challengeCreateFn(...args);
}, },
challengeRemoveFn: cts.challengeRemoveFn challengeRemoveFn: cts.challengeRemoveFn,
})); }));
assert.strictEqual(results.length, 5); assert.strictEqual(results.length, 5);
assert.deepStrictEqual(results, [false, false, false, true, true]); assert.deepStrictEqual(results, [false, false, false, true, true]);
}); });
/** /**
* Order certificates * Order certificates
*/ */
it('should order certificate', async () => { it('should order certificate', async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: testDomain commonName: testDomain,
}, await createKeyFn()); }, await createKeyFn());
const cert = await testClient.auto({ const cert = await testClient.auto({
csr, csr,
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn: cts.challengeCreateFn, challengeCreateFn: cts.challengeCreateFn,
challengeRemoveFn: cts.challengeRemoveFn challengeRemoveFn: cts.challengeRemoveFn,
}); });
assert.isString(cert); assert.isString(cert);
@@ -235,7 +227,7 @@ describe('client.auto', () => {
it('should order certificate using http-01', async () => { it('should order certificate using http-01', async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: testHttpDomain commonName: testHttpDomain,
}, await createKeyFn()); }, await createKeyFn());
const cert = await testClient.auto({ const cert = await testClient.auto({
@@ -243,7 +235,7 @@ describe('client.auto', () => {
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn: cts.assertHttpChallengeCreateFn, challengeCreateFn: cts.assertHttpChallengeCreateFn,
challengeRemoveFn: cts.challengeRemoveFn, challengeRemoveFn: cts.challengeRemoveFn,
challengePriority: ['http-01'] challengePriority: ['http-01'],
}); });
assert.isString(cert); assert.isString(cert);
@@ -251,7 +243,7 @@ describe('client.auto', () => {
it('should order certificate using https-01', async () => { it('should order certificate using https-01', async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: testHttpsDomain commonName: testHttpsDomain,
}, await createKeyFn()); }, await createKeyFn());
const cert = await testClient.auto({ const cert = await testClient.auto({
@@ -259,7 +251,7 @@ describe('client.auto', () => {
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn: cts.assertHttpsChallengeCreateFn, challengeCreateFn: cts.assertHttpsChallengeCreateFn,
challengeRemoveFn: cts.challengeRemoveFn, challengeRemoveFn: cts.challengeRemoveFn,
challengePriority: ['http-01'] challengePriority: ['http-01'],
}); });
assert.isString(cert); assert.isString(cert);
@@ -267,7 +259,7 @@ describe('client.auto', () => {
it('should order certificate using dns-01', async () => { it('should order certificate using dns-01', async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: testDnsDomain commonName: testDnsDomain,
}, await createKeyFn()); }, await createKeyFn());
const cert = await testClient.auto({ const cert = await testClient.auto({
@@ -275,7 +267,7 @@ describe('client.auto', () => {
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn: cts.assertDnsChallengeCreateFn, challengeCreateFn: cts.assertDnsChallengeCreateFn,
challengeRemoveFn: cts.challengeRemoveFn, challengeRemoveFn: cts.challengeRemoveFn,
challengePriority: ['dns-01'] challengePriority: ['dns-01'],
}); });
assert.isString(cert); assert.isString(cert);
@@ -283,7 +275,7 @@ describe('client.auto', () => {
it('should order certificate using tls-alpn-01', async () => { it('should order certificate using tls-alpn-01', async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: testAlpnDomain commonName: testAlpnDomain,
}, await createKeyFn()); }, await createKeyFn());
const cert = await testClient.auto({ const cert = await testClient.auto({
@@ -291,7 +283,7 @@ describe('client.auto', () => {
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn: cts.assertTlsAlpnChallengeCreateFn, challengeCreateFn: cts.assertTlsAlpnChallengeCreateFn,
challengeRemoveFn: cts.challengeRemoveFn, challengeRemoveFn: cts.challengeRemoveFn,
challengePriority: ['tls-alpn-01'] challengePriority: ['tls-alpn-01'],
}); });
assert.isString(cert); assert.isString(cert);
@@ -299,14 +291,14 @@ describe('client.auto', () => {
it('should order san certificate', async () => { it('should order san certificate', async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
altNames: testSanDomains altNames: testSanDomains,
}, await createKeyFn()); }, await createKeyFn());
const cert = await testClient.auto({ const cert = await testClient.auto({
csr, csr,
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn: cts.challengeCreateFn, challengeCreateFn: cts.challengeCreateFn,
challengeRemoveFn: cts.challengeRemoveFn challengeRemoveFn: cts.challengeRemoveFn,
}); });
assert.isString(cert); assert.isString(cert);
@@ -315,15 +307,14 @@ describe('client.auto', () => {
it('should order wildcard certificate', async () => { it('should order wildcard certificate', async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: testWildcardDomain, altNames: [testWildcardDomain, `*.${testWildcardDomain}`],
altNames: [`*.${testWildcardDomain}`]
}, await createKeyFn()); }, await createKeyFn());
const cert = await testClient.auto({ const cert = await testClient.auto({
csr, csr,
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn: cts.challengeCreateFn, challengeCreateFn: cts.challengeCreateFn,
challengeRemoveFn: cts.challengeRemoveFn challengeRemoveFn: cts.challengeRemoveFn,
}); });
assert.isString(cert); assert.isString(cert);
@@ -332,7 +323,7 @@ describe('client.auto', () => {
it('should order certificate with opts.skipChallengeVerification=true', async () => { it('should order certificate with opts.skipChallengeVerification=true', async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: `${uuid()}.${domainName}` commonName: `${uuid()}.${domainName}`,
}, await createKeyFn()); }, await createKeyFn());
const cert = await testClient.auto({ const cert = await testClient.auto({
@@ -340,20 +331,20 @@ describe('client.auto', () => {
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
skipChallengeVerification: true, skipChallengeVerification: true,
challengeCreateFn: cts.challengeCreateFn, challengeCreateFn: cts.challengeCreateFn,
challengeRemoveFn: cts.challengeRemoveFn challengeRemoveFn: cts.challengeRemoveFn,
}); });
assert.isString(cert); assert.isString(cert);
}); });
it('should order alternate certificate chain [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() { it('should order alternate certificate chain [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function () {
if (!capAlternateCertRoots) { if (!capAlternateCertRoots) {
this.skip(); this.skip();
} }
await Promise.all(testIssuers.map(async (issuer) => { await Promise.all(testIssuers.map(async (issuer) => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: `${uuid()}.${domainName}` commonName: `${uuid()}.${domainName}`,
}, await createKeyFn()); }, await createKeyFn());
const cert = await testClient.auto({ const cert = await testClient.auto({
@@ -361,7 +352,7 @@ describe('client.auto', () => {
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
preferredChain: issuer, preferredChain: issuer,
challengeCreateFn: cts.challengeCreateFn, challengeCreateFn: cts.challengeCreateFn,
challengeRemoveFn: cts.challengeRemoveFn challengeRemoveFn: cts.challengeRemoveFn,
}); });
const rootCert = acme.crypto.splitPemChain(cert).pop(); const rootCert = acme.crypto.splitPemChain(cert).pop();
@@ -371,13 +362,13 @@ describe('client.auto', () => {
})); }));
}); });
it('should get default chain with invalid preference [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function() { it('should get default chain with invalid preference [ACME_CAP_ALTERNATE_CERT_ROOTS]', async function () {
if (!capAlternateCertRoots) { if (!capAlternateCertRoots) {
this.skip(); this.skip();
} }
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: `${uuid()}.${domainName}` commonName: `${uuid()}.${domainName}`,
}, await createKeyFn()); }, await createKeyFn());
const cert = await testClient.auto({ const cert = await testClient.auto({
@@ -385,7 +376,7 @@ describe('client.auto', () => {
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
preferredChain: uuid(), preferredChain: uuid(),
challengeCreateFn: cts.challengeCreateFn, challengeCreateFn: cts.challengeCreateFn,
challengeRemoveFn: cts.challengeRemoveFn challengeRemoveFn: cts.challengeRemoveFn,
}); });
const rootCert = acme.crypto.splitPemChain(cert).pop(); const rootCert = acme.crypto.splitPemChain(cert).pop();
@@ -394,7 +385,6 @@ describe('client.auto', () => {
assert.strictEqual(testIssuers[0], info.issuer.commonName); assert.strictEqual(testIssuers[0], info.issuer.commonName);
}); });
/** /**
* Order certificate with alternate key sizes * Order certificate with alternate key sizes
*/ */
@@ -402,21 +392,20 @@ describe('client.auto', () => {
Object.entries(createKeyAltFns).forEach(([k, altKeyFn]) => { Object.entries(createKeyAltFns).forEach(([k, altKeyFn]) => {
it(`should order certificate with key=${k}`, async () => { it(`should order certificate with key=${k}`, async () => {
const [, csr] = await acme.crypto.createCsr({ const [, csr] = await acme.crypto.createCsr({
commonName: testDomain commonName: testDomain,
}, await altKeyFn()); }, await altKeyFn());
const cert = await testClient.auto({ const cert = await testClient.auto({
csr, csr,
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengeCreateFn: cts.challengeCreateFn, challengeCreateFn: cts.challengeCreateFn,
challengeRemoveFn: cts.challengeRemoveFn challengeRemoveFn: cts.challengeRemoveFn,
}); });
assert.isString(cert); assert.isString(cert);
}); });
}); });
/** /**
* Read certificates * Read certificates
*/ */
@@ -425,7 +414,7 @@ describe('client.auto', () => {
const info = acme.crypto.readCertificateInfo(testCertificate); const info = acme.crypto.readCertificateInfo(testCertificate);
spec.crypto.certificateInfo(info); spec.crypto.certificateInfo(info);
assert.strictEqual(info.domains.commonName, testDomain); assert.isNull(info.domains.commonName);
assert.deepStrictEqual(info.domains.altNames, [testDomain]); assert.deepStrictEqual(info.domains.altNames, [testDomain]);
}); });
@@ -433,7 +422,7 @@ describe('client.auto', () => {
const info = acme.crypto.readCertificateInfo(testSanCertificate); const info = acme.crypto.readCertificateInfo(testSanCertificate);
spec.crypto.certificateInfo(info); spec.crypto.certificateInfo(info);
assert.strictEqual(info.domains.commonName, testSanDomains[0]); assert.isNull(info.domains.commonName);
assert.deepStrictEqual(info.domains.altNames, testSanDomains); assert.deepStrictEqual(info.domains.altNames, testSanDomains);
}); });
@@ -441,7 +430,7 @@ describe('client.auto', () => {
const info = acme.crypto.readCertificateInfo(testWildcardCertificate); const info = acme.crypto.readCertificateInfo(testWildcardCertificate);
spec.crypto.certificateInfo(info); spec.crypto.certificateInfo(info);
assert.strictEqual(info.domains.commonName, testWildcardDomain); assert.isNull(info.domains.commonName);
assert.deepStrictEqual(info.domains.altNames, [testWildcardDomain, `*.${testWildcardDomain}`]); assert.deepStrictEqual(info.domains.altNames, [testWildcardDomain, `*.${testWildcardDomain}`]);
}); });
}); });

View File

@@ -8,7 +8,6 @@ const axios = require('./../src/axios');
const apiBaseUrl = process.env.ACME_CHALLTESTSRV_URL || null; const apiBaseUrl = process.env.ACME_CHALLTESTSRV_URL || null;
const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443; const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
/** /**
* Send request * Send request
*/ */
@@ -21,20 +20,18 @@ async function request(apiPath, data = {}) {
await axios.request({ await axios.request({
url: `${apiBaseUrl}/${apiPath}`, url: `${apiBaseUrl}/${apiPath}`,
method: 'post', method: 'post',
data data,
}); });
return true; return true;
} }
/** /**
* State * State
*/ */
exports.isEnabled = () => !!apiBaseUrl; exports.isEnabled = () => !!apiBaseUrl;
/** /**
* DNS * DNS
*/ */
@@ -42,7 +39,6 @@ exports.isEnabled = () => !!apiBaseUrl;
exports.addDnsARecord = async (host, addresses) => request('add-a', { host, addresses }); exports.addDnsARecord = async (host, addresses) => request('add-a', { host, addresses });
exports.setDnsCnameRecord = async (host, target) => request('set-cname', { host, target }); exports.setDnsCnameRecord = async (host, target) => request('set-cname', { host, target });
/** /**
* Challenge response * Challenge response
*/ */
@@ -55,7 +51,7 @@ async function addHttps01ChallengeResponse(token, content, targetHostname) {
await addHttp01ChallengeResponse(token, content); await addHttp01ChallengeResponse(token, content);
return request('add-redirect', { return request('add-redirect', {
path: `/.well-known/acme-challenge/${token}`, path: `/.well-known/acme-challenge/${token}`,
targetURL: `https://${targetHostname}:${httpsPort}/.well-known/acme-challenge/${token}` targetURL: `https://${targetHostname}:${httpsPort}/.well-known/acme-challenge/${token}`,
}); });
} }
@@ -72,7 +68,6 @@ exports.addHttps01ChallengeResponse = addHttps01ChallengeResponse;
exports.addDns01ChallengeResponse = addDns01ChallengeResponse; exports.addDns01ChallengeResponse = addDns01ChallengeResponse;
exports.addTlsAlpn01ChallengeResponse = addTlsAlpn01ChallengeResponse; exports.addTlsAlpn01ChallengeResponse = addTlsAlpn01ChallengeResponse;
/** /**
* Challenge response mock functions * Challenge response mock functions
*/ */

View File

@@ -7,7 +7,6 @@ const util = require('./../src/util');
const pebbleManagementUrl = process.env.ACME_PEBBLE_MANAGEMENT_URL || null; const pebbleManagementUrl = process.env.ACME_PEBBLE_MANAGEMENT_URL || null;
/** /**
* Pebble * Pebble
*/ */
@@ -26,7 +25,6 @@ async function getPebbleCertIssuers() {
return info.map((i) => i.issuer.commonName); return info.map((i) => i.issuer.commonName);
} }
/** /**
* Get certificate issuers * Get certificate issuers
*/ */

View File

@@ -7,14 +7,12 @@ const chai = require('chai');
const chaiAsPromised = require('chai-as-promised'); const chaiAsPromised = require('chai-as-promised');
const axios = require('./../src/axios'); const axios = require('./../src/axios');
/** /**
* Add promise support to Chai * Add promise support to Chai
*/ */
chai.use(chaiAsPromised); chai.use(chaiAsPromised);
/** /**
* Challenge test server ports * Challenge test server ports
*/ */
@@ -31,7 +29,6 @@ if (process.env.ACME_TLSALPN_PORT) {
axios.defaults.acmeSettings.tlsAlpnChallengePort = process.env.ACME_TLSALPN_PORT; axios.defaults.acmeSettings.tlsAlpnChallengePort = process.env.ACME_TLSALPN_PORT;
} }
/** /**
* External account binding * External account binding
*/ */

View File

@@ -7,7 +7,6 @@ const { assert } = require('chai');
const spec = {}; const spec = {};
module.exports = spec; module.exports = spec;
/** /**
* ACME * ACME
*/ */
@@ -120,7 +119,6 @@ spec.rfc8555.challenge = (obj) => {
} }
}; };
/** /**
* Crypto * Crypto
*/ */
@@ -150,7 +148,6 @@ spec.crypto.certificateInfo = (obj) => {
assert.strictEqual(Object.prototype.toString.call(obj.notAfter), '[object Date]'); assert.strictEqual(Object.prototype.toString.call(obj.notAfter), '[object Date]');
}; };
/** /**
* JWK * JWK
*/ */

View File

@@ -3,9 +3,10 @@
"module": "commonjs", "module": "commonjs",
"lib": ["es6"], "lib": ["es6"],
"strict": true, "strict": true,
"noEmit": true, "noEmit": false,
"esModuleInterop": true, "esModuleInterop": true,
"baseUrl": ".", "baseUrl": ".",
"composite": true,
"paths": { "acme-client": ["."] } "paths": { "acme-client": ["."] }
} }
} }

View File

@@ -15,7 +15,6 @@ export type PublicKeyString = string;
export type CertificateString = string; export type CertificateString = string;
export type CsrString = string; export type CsrString = string;
/** /**
* Augmented ACME interfaces * Augmented ACME interfaces
*/ */
@@ -28,7 +27,6 @@ export interface Authorization extends rfc8555.Authorization {
url: string; url: string;
} }
/** /**
* Client * Client
*/ */
@@ -80,7 +78,6 @@ export class Client {
auto(opts: ClientAutoOptions): Promise<string>; auto(opts: ClientAutoOptions): Promise<string>;
} }
/** /**
* Directory URLs * Directory URLs
*/ */
@@ -90,16 +87,20 @@ export const directory: {
staging: string, staging: string,
production: string production: string
}, },
google: {
staging: string,
production: string
},
letsencrypt: { letsencrypt: {
staging: string, staging: string,
production: string production: string
}, },
zerossl: { zerossl: {
staging: string,
production: string production: string
} }
}; };
/** /**
* Crypto * Crypto
*/ */
@@ -177,14 +178,12 @@ export interface CryptoLegacyInterface {
export const forge: CryptoLegacyInterface; export const forge: CryptoLegacyInterface;
/** /**
* Axios * Axios
*/ */
export const axios: AxiosInstance; export const axios: AxiosInstance;
/** /**
* Logger * Logger
*/ */

View File

@@ -4,7 +4,6 @@
import * as acme from 'acme-client'; import * as acme from 'acme-client';
(async () => { (async () => {
/* Client */ /* Client */
const accountKey = await acme.crypto.createPrivateKey(); const accountKey = await acme.crypto.createPrivateKey();

View File

@@ -27,7 +27,6 @@ export interface AccountUpdateRequest {
termsOfServiceAgreed?: boolean; termsOfServiceAgreed?: boolean;
} }
/** /**
* Order * Order
* *
@@ -53,7 +52,6 @@ export interface OrderCreateRequest {
notAfter?: string; notAfter?: string;
} }
/** /**
* Authorization * Authorization
* *
@@ -73,7 +71,6 @@ export interface Identifier {
value: string; value: string;
} }
/** /**
* Challenge * Challenge
* *
@@ -102,7 +99,6 @@ export interface DnsChallenge extends ChallengeAbstract {
export type Challenge = HttpChallenge | DnsChallenge; export type Challenge = HttpChallenge | DnsChallenge;
/** /**
* Certificate * Certificate
* *

View File

@@ -23,4 +23,4 @@ dist-ssr
*.sln *.sln
*.sw? *.sw?
test/user.secret.ts test/user.secret.*

View File

@@ -0,0 +1,2 @@
link-workspace-packages=true
prefer-workspace-packages=true

View File

@@ -3,6 +3,52 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.2](https://github.com/certd/certd/compare/v1.22.1...v1.22.2) (2024-07-23)
**Note:** Version bump only for package @certd/pipeline
## [1.22.1](https://github.com/certd/certd/compare/v1.22.0...v1.22.1) (2024-07-20)
### Performance Improvements
* 创建证书任务可以选择lege插件 ([affef13](https://github.com/certd/certd/commit/affef130378030c517250c58a4e787b0fc85d7d1))
# [1.22.0](https://github.com/certd/certd/compare/v1.21.2...v1.22.0) (2024-07-19)
### Features
* 升级midway支持esm ([485e603](https://github.com/certd/certd/commit/485e603b5165c28bc08694997726eaf2a585ebe7))
* 支持lego海量DNS提供商 ([0bc6d0a](https://github.com/certd/certd/commit/0bc6d0a211920fb0084d705e1db67ee1e7262c44))
* 支持postgresql ([3b19bfb](https://github.com/certd/certd/commit/3b19bfb4291e89064b3b407a80dae092d54747d5))
## [1.21.2](https://github.com/certd/certd/compare/v1.21.1...v1.21.2) (2024-07-08)
**Note:** Version bump only for package @certd/pipeline
## [1.21.1](https://github.com/certd/certd/compare/v1.21.0...v1.21.1) (2024-07-08)
**Note:** Version bump only for package @certd/pipeline
# [1.21.0](https://github.com/certd/certd/compare/v1.20.17...v1.21.0) (2024-07-03)
**Note:** Version bump only for package @certd/pipeline
## [1.20.17](https://github.com/certd/certd/compare/v1.20.16...v1.20.17) (2024-07-03)
**Note:** Version bump only for package @certd/pipeline
## [1.20.16](https://github.com/certd/certd/compare/v1.20.15...v1.20.16) (2024-07-01)
**Note:** Version bump only for package @certd/pipeline
## [1.20.15](https://github.com/certd/certd/compare/v1.20.14...v1.20.15) (2024-06-28)
**Note:** Version bump only for package @certd/pipeline
## [1.20.14](https://github.com/certd/certd/compare/v1.20.13...v1.20.14) (2024-06-23)
**Note:** Version bump only for package @certd/pipeline
## [1.20.13](https://github.com/certd/certd/compare/v1.20.12...v1.20.13) (2024-06-18) ## [1.20.13](https://github.com/certd/certd/compare/v1.20.12...v1.20.13) (2024-06-18)
**Note:** Version bump only for package @certd/pipeline **Note:** Version bump only for package @certd/pipeline

View File

@@ -0,0 +1,96 @@
import * as fs from "fs";
import * as path from "path";
// https://gist.github.com/lovasoa/8691344
async function* walk(dir) {
for await (const d of await fs.promises.opendir(dir)) {
const entry = path.join(dir, d.name);
if (d.isDirectory()) {
yield* walk(entry);
} else if (d.isFile()) {
yield entry;
}
}
}
function resolveImportPath(sourceFile, importPath, options) {
const sourceFileAbs = path.resolve(process.cwd(), sourceFile);
const root = path.dirname(sourceFileAbs);
const { moduleFilter = defaultModuleFilter } = options;
if (moduleFilter(importPath)) {
const importPathAbs = path.resolve(root, importPath);
let possiblePath = [path.resolve(importPathAbs, "./index.ts"), path.resolve(importPathAbs, "./index.js"), importPathAbs + ".ts", importPathAbs + ".js"];
if (possiblePath.length) {
for (let i = 0; i < possiblePath.length; i++) {
let entry = possiblePath[i];
if (fs.existsSync(entry)) {
const resolved = path.relative(root, entry.replace(/\.ts$/, ".js"));
if (!resolved.startsWith(".")) {
return "./" + resolved;
}
return resolved;
}
}
}
}
return null;
}
function replace(filePath, outFilePath, options) {
const code = fs.readFileSync(filePath).toString();
const newCode = code.replace(/(import|export) (.+?) from ('[^\n']+'|"[^\n"]+");/gs, function (found, action, imported, from) {
const importPath = from.slice(1, -1);
let resolvedPath = resolveImportPath(filePath, importPath, options);
if (resolvedPath) {
resolvedPath = resolvedPath.replaceAll("\\", "/");
console.log("\t", importPath, resolvedPath);
return `${action} ${imported} from "${resolvedPath}";`;
}
return found;
});
if (code !== newCode) {
fs.writeFileSync(outFilePath, newCode);
}
}
// Then, use it with a simple async for loop
async function run(srcDir, options = defaultOptions) {
const { sourceFileFilter = defaultSourceFileFilter } = options;
for await (const entry of walk(srcDir)) {
if (sourceFileFilter(entry)) {
console.log(entry);
replace(entry, entry, options);
}
}
}
const defaultSourceFileFilter = function (sourceFilePath) {
return /\.(js|ts)$/.test(sourceFilePath) && !/node_modules/.test(sourceFilePath);
};
const defaultModuleFilter = function (importedModule) {
return !path.isAbsolute(importedModule) && !importedModule.startsWith("@") && !importedModule.endsWith(".js");
};
const defaultOptions = {
sourceFileFilter: defaultSourceFileFilter,
moduleFilter: defaultModuleFilter,
};
// Switch this to test on one file or directly run on a directory.
const DEBUG = false;
if (DEBUG) {
replace("./src/index.ts", "./out.ts", defaultOptions);
} else {
await run("./src/", defaultOptions);
}

View File

@@ -1,49 +1,46 @@
{ {
"name": "@certd/pipeline", "name": "@certd/pipeline",
"private": false, "private": false,
"version": "1.20.13", "version": "1.22.2",
"main": "./src/index.ts", "type": "module",
"module": "./src/index.ts", "main": "./dist/index.js",
"types": "./src/index.ts", "types": "./dist/index.d.ts",
"publishConfig": {
"main": "./dist/bundle.js",
"module": "./dist/bundle.mjs",
"types": "./dist/d/index.d.ts"
},
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "rollup -c", "build": "tsc --skipLibCheck",
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build", "build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"axios": "^1.4.0", "@types/lodash-es": "^4.17.12",
"axios": "^1.7.2",
"fix-path": "^4.0.0",
"lodash-es": "^4.17.21",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
"nodemailer": "^6.9.3", "nodemailer": "^6.9.3",
"qs": "^6.11.2" "qs": "^6.11.2"
}, },
"devDependencies": { "devDependencies": {
"@certd/acme-client": "workspace:^1.20.13",
"@rollup/plugin-commonjs": "^23.0.4", "@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-json": "^6.0.0", "@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-terser": "^0.4.3", "@rollup/plugin-terser": "^0.4.3",
"@rollup/plugin-typescript": "^11.0.0", "@rollup/plugin-typescript": "^11.0.0",
"@types/chai": "^4.3.5", "@types/chai": "^4.3.10",
"@types/lodash": "^4.14.194",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@types/node-forge": "^1.3.2", "@types/node-forge": "^1.3.2",
"@types/uuid": "^9.0.2", "@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^5.59.7", "@typescript-eslint/eslint-plugin": "^5.59.7",
"@typescript-eslint/parser": "^5.59.7", "@typescript-eslint/parser": "^5.59.7",
"chai": "^4.3.7", "chai": "4.3.10",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"eslint": "^8.41.0", "eslint": "^8.41.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"lodash": "^4.17.21", "iconv-lite": "^0.6.3",
"log4js": "^6.9.1", "log4js": "^6.9.1",
"mocha": "^10.2.0", "mocha": "^10.2.0",
"prettier": "^2.8.8", "prettier": "^2.8.8",
@@ -52,10 +49,11 @@
"rollup-plugin-typescript2": "^0.34.1", "rollup-plugin-typescript2": "^0.34.1",
"rollup-plugin-visualizer": "^5.8.2", "rollup-plugin-visualizer": "^5.8.2",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tsc-esm-fix": "^3.0.0",
"tslib": "^2.5.2", "tslib": "^2.5.2",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"vite": "^4.3.8", "vite": "^4.3.8",
"vue-tsc": "^1.6.5" "vue-tsc": "^1.6.5"
}, },
"gitHead": "a31f1c7f5e71fa946de9bf0283e11d6ce049b3e9" "gitHead": "47fe3d5826661f678d081ab53e67c847a3239d88"
} }

View File

@@ -1,43 +0,0 @@
const resolve = require("@rollup/plugin-node-resolve");
const commonjs = require("@rollup/plugin-commonjs");
//const Typescript = require("rollup-plugin-typescript2");
const Typescript = require("@rollup/plugin-typescript");
const json = require("@rollup/plugin-json");
const terser = require("@rollup/plugin-terser");
module.exports = {
input: "src/index.ts",
output: {
file: "dist/bundle.js",
format: "cjs",
},
plugins: [
// 解析第三方依赖
resolve(),
// 识别 commonjs 模式第三方依赖
commonjs(),
Typescript({
target: "esnext",
rootDir: "src",
declaration: true,
declarationDir: "dist/d",
exclude: ["./node_modules/**", "./src/**/*.vue"],
allowSyntheticDefaultImports: true,
}),
json(),
terser(),
],
external: [
"vue",
"lodash",
"dayjs",
"@certd/acme-client",
"@certd/pipeline",
"@certd/plugin-cert",
"@certd/plugin-aliyun",
"@certd/plugin-tencent",
"@certd/plugin-huawei",
"@certd/plugin-host",
"@certd/plugin-tencent",
"@certd/plugin-util",
],
};

View File

@@ -1,5 +1,5 @@
import { Registrable } from "../registry"; import { Registrable } from "../registry/index.js";
import { FormItemProps } from "../d.ts"; import { FormItemProps } from "../dt/index.js";
export type AccessInputDefine = FormItemProps & { export type AccessInputDefine = FormItemProps & {
title: string; title: string;

View File

@@ -1,8 +1,8 @@
// src/decorator/memoryCache.decorator.ts // src/decorator/memoryCache.decorator.ts
import { AccessDefine, AccessInputDefine } from "./api"; import { AccessDefine, AccessInputDefine } from "./api.js";
import { Decorator } from "../decorator"; import { Decorator } from "../decorator/index.js";
import _ from "lodash"; import _ from "lodash-es";
import { accessRegistry } from "./registry"; import { accessRegistry } from "./registry.js";
// 提供一个唯一 key // 提供一个唯一 key
export const ACCESS_CLASS_KEY = "pipeline:access"; export const ACCESS_CLASS_KEY = "pipeline:access";

View File

@@ -1,3 +1,3 @@
export * from "./api"; export * from "./api.js";
export * from "./registry"; export * from "./registry.js";
export * from "./decorator"; export * from "./decorator.js";

View File

@@ -1,4 +1,4 @@
import { Registry } from "../registry"; import { Registry } from "../registry/index.js";
// @ts-ignore // @ts-ignore
export const accessRegistry = new Registry("access"); export const accessRegistry = new Registry("access");

View File

@@ -1,5 +1,5 @@
import { AxiosInstance } from "axios"; import { AxiosInstance } from "axios";
import { IContext } from "../core"; import { IContext } from "../core/index.js";
export type HttpClient = AxiosInstance; export type HttpClient = AxiosInstance;
export type UserContext = IContext; export type UserContext = IContext;

View File

@@ -1,4 +1,5 @@
import { IStorage, MemoryStorage } from "./storage"; import { IStorage, MemoryStorage } from "./storage.js";
const CONTEXT_VERSION_KEY = "contextVersion"; const CONTEXT_VERSION_KEY = "contextVersion";
export interface IContext { export interface IContext {
getInt(key: string): Promise<number>; getInt(key: string): Promise<number>;
@@ -20,13 +21,11 @@ export class ContextFactory {
} }
getContext(scope: string, namespace: string): IContext { getContext(scope: string, namespace: string): IContext {
const context = new StorageContext(scope, namespace, this.storage); return new StorageContext(scope, namespace, this.storage);
return context;
} }
getMemoryContext(scope: string, namespace: string): IContext { getMemoryContext(scope: string, namespace: string): IContext {
const context = new StorageContext(scope, namespace, this.memoryStorage); return new StorageContext(scope, namespace, this.memoryStorage);
return context;
} }
} }

View File

@@ -1,18 +1,18 @@
import { ConcurrencyStrategy, NotificationWhen, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../d.ts"; import { ConcurrencyStrategy, NotificationWhen, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../dt/index.js";
import _ from "lodash"; import _ from "lodash-es";
import { RunHistory, RunnableCollection } from "./run-history"; import { RunHistory, RunnableCollection } from "./run-history.js";
import { AbstractTaskPlugin, PluginDefine, pluginRegistry, TaskInstanceContext } from "../plugin"; import { AbstractTaskPlugin, PluginDefine, pluginRegistry, TaskInstanceContext } from "../plugin/index.js";
import { ContextFactory, IContext } from "./context"; import { ContextFactory, IContext } from "./context.js";
import { IStorage } from "./storage"; import { IStorage } from "./storage.js";
import { logger } from "../utils/util.log"; import { logger } from "../utils/util.log.js";
import { Logger } from "log4js"; import { Logger } from "log4js";
import { createAxiosService } from "../utils/util.request"; import { createAxiosService } from "../utils/util.request.js";
import { IAccessService } from "../access"; import { IAccessService } from "../access/index.js";
import { RegistryItem } from "../registry"; import { RegistryItem } from "../registry/index.js";
import { Decorator } from "../decorator"; import { Decorator } from "../decorator/index.js";
import { IEmailService } from "../service"; import { IEmailService } from "../service/index.js";
import { FileStore } from "./file-store"; import { FileStore } from "./file-store.js";
import { TimeoutPromise } from "../utils/util.promise"; // import { TimeoutPromise } from "../utils/util.promise.js";
export type ExecutorOptions = { export type ExecutorOptions = {
userId: any; userId: any;
@@ -33,7 +33,7 @@ export class Executor {
lastRuntime!: RunHistory; lastRuntime!: RunHistory;
options: ExecutorOptions; options: ExecutorOptions;
canceled = false; canceled = false;
onChanged: (history: RunHistory) => void; onChanged: (history: RunHistory) => Promise<void>;
constructor(options: ExecutorOptions) { constructor(options: ExecutorOptions) {
this.options = options; this.options = options;
this.pipeline = _.cloneDeep(options.pipeline); this.pipeline = _.cloneDeep(options.pipeline);
@@ -110,12 +110,13 @@ export class Executor {
const intervalFlushLogId = setInterval(async () => { const intervalFlushLogId = setInterval(async () => {
await this.onChanged(this.runtime); await this.onChanged(this.runtime);
}, 5000); }, 5000);
const timeout = runnable.timeout ?? 20 * 60 * 1000;
// const timeout = runnable.timeout ?? 20 * 60 * 1000;
try { try {
if (this.canceled) { if (this.canceled) {
throw new Error("task canceled"); return ResultType.canceled;
} }
await TimeoutPromise(run, timeout); await run();
this.runtime.success(runnable); this.runtime.success(runnable);
return ResultType.success; return ResultType.success;
} catch (e: any) { } catch (e: any) {
@@ -142,19 +143,25 @@ export class Executor {
async runStage(stage: Stage) { async runStage(stage: Stage) {
const runnerList = []; const runnerList = [];
for (const task of stage.tasks) { for (const task of stage.tasks) {
const runner = this.runWithHistory(task, "task", async () => { const runner = async () => {
await this.runTask(task); return this.runWithHistory(task, "task", async () => {
}); await this.runTask(task);
});
};
runnerList.push(runner); runnerList.push(runner);
} }
let resList: ResultType[] = []; let resList: ResultType[] = [];
if (stage.concurrency === ConcurrencyStrategy.Parallel) { if (stage.concurrency === ConcurrencyStrategy.Parallel) {
resList = await Promise.all(runnerList); const pList = [];
for (const item of runnerList) {
pList.push(item());
}
resList = await Promise.all(pList);
} else { } else {
for (let i = 0; i < runnerList.length; i++) { for (let i = 0; i < runnerList.length; i++) {
const runner = runnerList[i]; const runner = runnerList[i];
resList[i] = await runner; resList[i] = await runner();
} }
} }
return this.compositionResultType(resList); return this.compositionResultType(resList);
@@ -239,7 +246,7 @@ export class Executor {
this.lastStatusMap.clear(); this.lastStatusMap.clear();
} }
//输出到output context //输出到output context
_.forEach(define.output, (item, key) => { _.forEach(define.output, (item: any, key: any) => {
step.status!.output[key] = instance[key]; step.status!.output[key] = instance[key];
const stepOutputKey = `step.${step.id}.${key}`; const stepOutputKey = `step.${step.id}.${key}`;
this.runtime.context[stepOutputKey] = instance[key]; this.runtime.context[stepOutputKey] = instance[key];
@@ -275,12 +282,16 @@ export class Executor {
continue; continue;
} }
if (notification.type === "email") { if (notification.type === "email") {
this.options.emailService?.send({ try {
userId: this.pipeline.userId, await this.options.emailService?.send({
subject, userId: this.pipeline.userId,
content, subject,
receivers: notification.options.receivers, content,
}); receivers: notification.options.receivers,
});
} catch (e) {
logger.error("send email error", e);
}
} }
} }
} }

View File

@@ -1,8 +1,8 @@
import { fileUtils } from "../utils"; import { fileUtils } from "../utils/index.js";
import dayjs from "dayjs"; import dayjs from "dayjs";
import path from "path"; import path from "path";
import fs from "fs"; import fs from "fs";
import { logger } from "../utils"; import { logger } from "../utils/index.js";
export type FileStoreOptions = { export type FileStoreOptions = {
rootDir?: string; rootDir?: string;
@@ -10,6 +10,12 @@ export type FileStoreOptions = {
parent: string; parent: string;
}; };
export interface IFileStore {
readFile(filePath: string): Buffer | null;
writeFile(filename: string, file: Buffer): string;
deleteByParent(scope: string, parent: string): void;
}
export class FileStore { export class FileStore {
rootDir: string; rootDir: string;
scope: string; scope: string;
@@ -35,7 +41,7 @@ export class FileStore {
return localPath; return localPath;
} }
private buildFilePath(filename: string) { protected buildFilePath(filename: string) {
const parentDir = path.join(this.rootDir, this.scope + "", this.parent + "", dayjs().format("YYYY-MM-DD")); const parentDir = path.join(this.rootDir, this.scope + "", this.parent + "", dayjs().format("YYYY-MM-DD"));
if (!fs.existsSync(parentDir)) { if (!fs.existsSync(parentDir)) {
fs.mkdirSync(parentDir, { recursive: true }); fs.mkdirSync(parentDir, { recursive: true });

View File

@@ -1,5 +1,5 @@
export * from "./executor"; export * from "./executor.js";
export * from "./run-history"; export * from "./run-history.js";
export * from "./context"; export * from "./context.js";
export * from "./storage"; export * from "./storage.js";
export * from "./file-store"; export * from "./file-store.js";

View File

@@ -1,6 +1,6 @@
import { Context, HistoryResult, Pipeline, ResultType, Runnable, RunnableMap, Stage, Step, Task } from "../d.ts"; import { Context, HistoryResult, Pipeline, ResultType, Runnable, RunnableMap, Stage, Step, Task } from "../dt/index.js";
import _ from "lodash"; import _ from "lodash-es";
import { buildLogger } from "../utils/util.log"; import { buildLogger } from "../utils/util.log.js";
import { Logger } from "log4js"; import { Logger } from "log4js";
export type HistoryStatus = { export type HistoryStatus = {

View File

@@ -1,6 +1,6 @@
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import { fileUtils } from "../utils/util.file"; import { fileUtils } from "../utils/util.file.js";
export interface IStorage { export interface IStorage {
get(scope: string, namespace: string, version: string, key: string): Promise<string | null>; get(scope: string, namespace: string, version: string, key: string): Promise<string | null>;

View File

@@ -70,6 +70,7 @@ export type Runnable = {
default?: { default?: {
[key: string]: any; [key: string]: any;
}; };
context?: Context;
}; };
export type EmailOptions = { export type EmailOptions = {

View File

@@ -1,4 +1,4 @@
import { Decorator } from "./index"; import { Decorator } from "./index.js";
export type AutowireProp = { export type AutowireProp = {
name?: string; name?: string;

View File

@@ -1,2 +1,2 @@
export * from "./utils"; export * from "./utils.js";
export * from "./common"; export * from "./common.js";

View File

@@ -1,4 +1,4 @@
import _ from "lodash"; import _ from "lodash-es";
const propertyMap: any = {}; const propertyMap: any = {};
function attachProperty(target: any, propertyKey: string | symbol) { function attachProperty(target: any, propertyKey: string | symbol) {
@@ -11,7 +11,11 @@ function attachProperty(target: any, propertyKey: string | symbol) {
} }
function getClassProperties(target: any) { function getClassProperties(target: any) {
return propertyMap[target] || {}; //获取父类
const parent = Object.getPrototypeOf(target);
const parentMap = propertyMap[parent] || {};
const current = propertyMap[target] || {};
return _.merge({}, parentMap, current);
} }
function target(target: any, propertyKey?: string | symbol) { function target(target: any, propertyKey?: string | symbol) {
@@ -25,7 +29,7 @@ function target(target: any, propertyKey?: string | symbol) {
} }
function inject(define: any, instance: any, context: any, preHandler?: (item: any, key: string, instance: any, context: any) => void) { function inject(define: any, instance: any, context: any, preHandler?: (item: any, key: string, instance: any, context: any) => void) {
_.forEach(define, (item, key) => { _.forEach(define, (item: any, key: any) => {
if (preHandler) { if (preHandler) {
preHandler(item, key, instance, context); preHandler(item, key, instance, context);
} }

View File

@@ -0,0 +1,115 @@
/**
* [x]-col的配置
*/
export type ColProps = {
span?: number;
[props: string]: any;
};
export type FormItemProps = {
/**
* 字段label
*/
title?: string;
/**
* 表单字段组件配置
*/
component?: ComponentProps;
/**
* 表单字段 [a|el|n]-col的配置
* 一般用来配置跨列:{span:24} 占满一行
*/
col?: ColProps;
/**
* 默认值
*/
value?: any;
/**
* 帮助提示配置
*/
helper?: string | FormItemHelperProps;
/**
* 排序号
*/
order?: number;
/**
* 是否显示此字段
*/
show?: boolean;
/**
* 是否是空白占位栏
*/
blank?: boolean;
[key: string]: any;
};
/**
* 表单字段帮助说明配置
*/
export type FormItemHelperProps = {
/**
* 自定义渲染帮助说明
* @param scope
*/
render?: (scope: any) => any;
/**
* 帮助文本
*/
text?: string;
/**
* 帮助说明所在的位置,[ undefined | label]
*/
position?: string;
/**
* [a|el|n]-tooltip配置
*/
tooltip?: object;
[key: string]: any;
};
/**
* 组件配置
*/
export type ComponentProps = {
/**
* 组件的名称
*/
name?: string | object;
/**
* vmodel绑定的目标属性名
*/
vModel?: string;
/**
* 当原始组件名的参数被以上属性名占用时,可以配置在这里
* 例如:原始组件有一个叫name的属性你想要配置它则可以按如下配置
* ```
* component:{
* name:"组件的名称"
* props:{
* name:"组件的name属性" <-----------
* }
* }
* ```
*/
props?: {
[key: string]: any;
};
/**
* 组件事件监听
*/
on?: {
[key: string]: (context?: any) => void;
};
/**
* 组件其他参数
* 事件onXxx:(event)=>void 组件原始事件监听
* on.onXxx:(context)=>void 组件事件监听(对原始事件包装)
* 样式style、class等
*/
[key: string]: any;
};

View File

@@ -0,0 +1,2 @@
export * from "./pipeline.js";
export * from "./fast-crud.js";

View File

@@ -0,0 +1,139 @@
export enum RunStrategy {
AlwaysRun,
SkipWhenSucceed,
}
export enum ConcurrencyStrategy {
Serial,
Parallel,
}
export enum NextStrategy {
AllSuccess,
OneSuccess,
}
export enum HandlerType {
//清空后续任务的状态
ClearFollowStatus,
SendEmail,
}
export type EventHandler = {
type: HandlerType;
params: {
[key: string]: any;
};
};
export type RunnableStrategy = {
runStrategy?: RunStrategy;
onSuccess?: EventHandler[];
onError?: EventHandler[];
};
export type Step = Runnable & {
type: string; //插件类型
input: {
[key: string]: any;
};
};
export type Task = Runnable & {
steps: Step[];
};
export type Stage = Runnable & {
tasks: Task[];
concurrency: ConcurrencyStrategy;
next: NextStrategy;
};
export type Trigger = {
id: string;
title: string;
cron: string;
type: string;
};
export type FileItem = {
id: string;
filename: string;
path: string;
};
export type Runnable = {
id: string;
title: string;
strategy?: RunnableStrategy;
runnableType?: string; // pipeline, stage, task , step
status?: HistoryResult;
timeout?: number;
default?: {
[key: string]: any;
};
};
export type EmailOptions = {
receivers: string[];
};
export type NotificationWhen = "error" | "success" | "turnToSuccess" | "start";
export type NotificationType = "email" | "url";
export type Notification = {
type: NotificationType;
when: NotificationWhen[];
options: EmailOptions;
};
export type Pipeline = Runnable & {
version?: number;
userId: any;
stages: Stage[];
triggers: Trigger[];
notifications?: Notification[];
};
export type Context = {
[key: string]: any;
};
export type Log = {
title: string;
time: number;
level: string;
text: string;
};
export enum ResultType {
start = "start",
success = "success",
error = "error",
canceled = "canceled",
skip = "skip",
none = "none",
}
export type HistoryResultGroup = {
[key: string]: {
runnable: Runnable;
res: HistoryResult;
};
};
export type HistoryResult = {
input: any;
output: any;
files?: FileItem[];
/**
* 任务状态
*/
status: ResultType;
startTime: number;
endTime?: number;
/**
* 处理结果
*/
result?: ResultType; //success, error,skip
message?: string;
};
export type RunnableMap = {
[id: string]: Runnable;
};

View File

@@ -1,10 +1,10 @@
import "util"; import "util";
export * from "./core"; export * from "./core/index.js";
export * from "./d.ts"; export * from "./dt/index.js";
export * from "./access"; export * from "./access/index.js";
export * from "./registry"; export * from "./registry/index.js";
export * from "./plugin"; export * from "./plugin/index.js";
export * from "./utils"; export * from "./utils/index.js";
export * from "./context"; export * from "./context/index.js";
export * from "./decorator"; export * from "./decorator/index.js";
export * from "./service"; export * from "./service/index.js";

View File

@@ -1,12 +1,12 @@
import { Registrable } from "../registry"; import { Registrable } from "../registry/index.js";
import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../d.ts"; import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js";
import { FileStore } from "../core/file-store"; import { FileStore } from "../core/file-store.js";
import { Logger } from "log4js"; import { Logger } from "log4js";
import { IAccessService } from "../access"; import { IAccessService } from "../access/index.js";
import { IEmailService } from "../service"; import { IEmailService } from "../service/index.js";
import { IContext } from "../core"; import { IContext } from "../core/index.js";
import { AxiosInstance } from "axios"; import { AxiosInstance } from "axios";
import { logger } from "../utils"; import { logger } from "../utils/index.js";
export enum ContextScope { export enum ContextScope {
global, global,
@@ -23,6 +23,7 @@ export type TaskInputDefine = FormItemProps;
export type PluginDefine = Registrable & { export type PluginDefine = Registrable & {
default?: any; default?: any;
group?: string;
input?: { input?: {
[key: string]: TaskInputDefine; [key: string]: TaskInputDefine;
}; };
@@ -83,7 +84,7 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
return Math.random().toString(36).substring(2, 9); return Math.random().toString(36).substring(2, 9);
} }
linkFile(file: FileItem) { linkFile(file: FileItem) {
this._result.files!.push({ this._result.files?.push({
...file, ...file,
id: this.randomFileId(), id: this.randomFileId(),
}); });
@@ -91,13 +92,20 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
saveFile(filename: string, file: Buffer) { saveFile(filename: string, file: Buffer) {
const filePath = this.ctx.fileStore.writeFile(filename, file); const filePath = this.ctx.fileStore.writeFile(filename, file);
logger.info(`saveFile:${filePath}`); logger.info(`saveFile:${filePath}`);
this._result.files!.push({ this._result.files?.push({
id: this.randomFileId(), id: this.randomFileId(),
filename, filename,
path: filePath, path: filePath,
}); });
} }
extendsFiles() {
if (this._result.files == null) {
this._result.files = [];
}
this._result.files.push(...(this.ctx.lastStatus?.status?.files || []));
}
get pipeline() { get pipeline() {
return this.ctx.pipeline; return this.ctx.pipeline;
} }

View File

@@ -1,8 +1,8 @@
import _ from "lodash"; import _ from "lodash-es";
import { pluginRegistry } from "./registry"; import { pluginRegistry } from "./registry.js";
import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api"; import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api.js";
import { Decorator } from "../decorator"; import { Decorator } from "../decorator/index.js";
import { AUTOWIRE_KEY } from "../decorator"; import { AUTOWIRE_KEY } from "../decorator/index.js";
import "reflect-metadata"; import "reflect-metadata";
// 提供一个唯一 key // 提供一个唯一 key
export const PLUGIN_CLASS_KEY = "pipeline:plugin"; export const PLUGIN_CLASS_KEY = "pipeline:plugin";
@@ -31,7 +31,24 @@ export function IsTaskPlugin(define: PluginDefine): ClassDecorator {
outputs[property] = output; outputs[property] = output;
} }
} }
_.merge(define, { input: inputs, autowire: autowires, output: outputs });
// inputs 转换为array根据order排序然后再转换为map
let inputArray = [];
for (const key in inputs) {
const _input = inputs[key];
if (_input.order == null) {
_input.order = 0;
}
inputArray.push([key, _input]);
}
inputArray = _.sortBy(inputArray, (item: any) => item[1].order);
const inputMap: any = {};
inputArray.forEach((item: any) => {
inputMap[item[0]] = item[1];
});
_.merge(define, { input: inputMap, autowire: autowires, output: outputs });
Reflect.defineMetadata(PLUGIN_CLASS_KEY, define, target); Reflect.defineMetadata(PLUGIN_CLASS_KEY, define, target);

View File

@@ -0,0 +1,25 @@
import { PluginDefine } from "./api";
export class PluginGroup {
key: string;
title: string;
desc?: string;
order: number;
plugins: PluginDefine[];
constructor(key: string, title: string, order = 0, desc = "") {
this.key = key;
this.title = title;
this.order = order;
this.desc = desc;
this.plugins = [];
}
}
export const pluginGroups = {
cert: new PluginGroup("cert", "证书申请", 1),
aliyun: new PluginGroup("aliyun", "阿里云", 2),
huawei: new PluginGroup("huawei", "华为云", 3),
tencent: new PluginGroup("tencent", "腾讯云", 4),
host: new PluginGroup("host", "主机", 5),
other: new PluginGroup("other", "其他", 7),
};

View File

@@ -1,3 +1,4 @@
export * from "./api"; export * from "./api.js";
export * from "./registry"; export * from "./registry.js";
export * from "./decorator"; export * from "./decorator.js";
export * from "./group.js";

View File

@@ -1,4 +1,16 @@
import { Registry } from "../registry"; import { OnRegisterContext, Registry } from "../registry/index.js";
import { AbstractTaskPlugin } from "./api"; import { AbstractTaskPlugin } from "./api.js";
import { pluginGroups } from "./group.js";
export const pluginRegistry = new Registry<AbstractTaskPlugin>("plugin"); const onRegister = ({ key, value }: OnRegisterContext<AbstractTaskPlugin>) => {
const group = value?.define?.group as string;
if (group) {
if (pluginGroups.hasOwnProperty(group)) {
// @ts-ignore
pluginGroups[group].plugins.push(value.define);
} else {
pluginGroups.other.plugins.push(value.define);
}
}
};
export const pluginRegistry = new Registry<AbstractTaskPlugin>("plugin", onRegister);

View File

@@ -1,6 +1,5 @@
import { ITaskPlugin } from "../api"; import { ITaskPlugin } from "../api.js";
import { IsTaskPlugin, TaskInput } from "../decorator"; import { IsTaskPlugin, TaskInput } from "../decorator.js";
import { Autowire } from "../../decorator";
@IsTaskPlugin({ @IsTaskPlugin({
name: "EchoPlugin", name: "EchoPlugin",

View File

@@ -1 +1 @@
export * from "./registry"; export * from "./registry.js";

View File

@@ -1,23 +1,34 @@
import { logger } from "../utils"; import { logger } from "../utils/index.js";
export type Registrable = { export type Registrable = {
name: string; name: string;
title: string; title: string;
desc?: string; desc?: string;
group?: string;
}; };
export type RegistryItem<T> = { export type RegistryItem<T> = {
define: Registrable; define: Registrable;
target: T; target: T;
}; };
export type OnRegisterContext<T> = {
registry: Registry<T>;
key: string;
value: RegistryItem<T>;
};
export type OnRegister<T> = (ctx: OnRegisterContext<T>) => void;
export class Registry<T> { export class Registry<T> {
type = ""; type = "";
storage: { storage: {
[key: string]: RegistryItem<T>; [key: string]: RegistryItem<T>;
} = {}; } = {};
constructor(type: string) { onRegister?: OnRegister<T>;
constructor(type: string, onRegister?: OnRegister<T>) {
this.type = type; this.type = type;
this.onRegister = onRegister;
} }
register(key: string, value: RegistryItem<T>) { register(key: string, value: RegistryItem<T>) {
@@ -25,6 +36,13 @@ export class Registry<T> {
return; return;
} }
this.storage[key] = value; this.storage[key] = value;
if (this.onRegister) {
this.onRegister({
registry: this,
key,
value,
});
}
logger.info(`注册插件:${this.type}:${key}`); logger.info(`注册插件:${this.type}:${key}`);
} }

View File

@@ -1 +1 @@
export * from "./email"; export * from "./email.js";

View File

@@ -1,7 +1,9 @@
import sleep from "./util.sleep"; import sleep from "./util.sleep.js";
import { request } from "./util.request"; import { request } from "./util.request.js";
export * from "./util.log"; export * from "./util.log.js";
export * from "./util.file"; export * from "./util.file.js";
export * from "./util.sp.js";
export * as promises from "./util.promise.js";
export const utils = { export const utils = {
sleep, sleep,
http: request, http: request,

Some files were not shown because too many files have changed in this diff Show More