@certd/acme-client 1.39.12 → 1.39.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,28 +1,24 @@
1
+ // @ts-nocheck
1
2
  /**
2
3
  * ACME challenge verification
3
4
  */
4
-
5
- import dnsSdk from "dns"
6
- import https from 'https'
7
- import { log as defaultLog } from './logger.js'
8
- import axios from './axios.js'
9
- import * as util from './util.js'
10
- import { isAlpnCertificateAuthorizationValid } from './crypto/index.js'
11
- import { utils } from '@certd/basic'
12
-
13
- const dns = dnsSdk.promises
14
-
15
- let walkFromAuthoritative = true
5
+ import dnsSdk from "dns";
6
+ import https from 'https';
7
+ import { log as defaultLog } from './logger.js';
8
+ import axios from './axios.js';
9
+ import * as util from './util.js';
10
+ import { isAlpnCertificateAuthorizationValid } from './crypto/index.js';
11
+ import { utils } from '@certd/basic';
12
+ const dns = dnsSdk.promises;
13
+ let walkFromAuthoritative = true;
16
14
  export function setWalkFromAuthoritative(value = true) {
17
- walkFromAuthoritative = value
15
+ walkFromAuthoritative = value;
18
16
  }
19
-
20
17
  export function createChallengeFn(opts = {}) {
21
- const logger = opts?.logger || { info: defaultLog, error: defaultLog, warn: defaultLog, debug: defaultLog }
22
-
18
+ const logger = opts?.logger || { info: defaultLog, error: defaultLog, warn: defaultLog, debug: defaultLog };
23
19
  const log = function (...args) {
24
- logger.info(...args)
25
- }
20
+ logger.info(...args);
21
+ };
26
22
  /**
27
23
  * Verify ACME HTTP challenge
28
24
  *
@@ -34,65 +30,52 @@ export function createChallengeFn(opts = {}) {
34
30
  * @param {string} [suffix] URL suffix
35
31
  * @returns {Promise<boolean>}
36
32
  */
37
-
38
33
  async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix = `/.well-known/acme-challenge/${challenge.token}`) {
39
-
40
34
  async function doQuery(challengeUrl) {
41
- log(`正在测试请求 ${challengeUrl} `)
35
+ log(`正在测试请求 ${challengeUrl} `);
42
36
  // const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
43
37
  // const challengeUrl = `https://${authz.identifier.value}:${httpsPort}${suffix}`;
44
-
45
38
  /* May redirect to HTTPS with invalid/self-signed cert - https://letsencrypt.org/docs/challenge-types/#http-01-challenge */
46
39
  const httpsAgent = new https.Agent({ rejectUnauthorized: false });
47
-
48
40
  log(`Sending HTTP query to ${authz.identifier.value}, suffix: ${suffix}, port: ${httpPort}`);
49
- let data = ""
41
+ let data = "";
50
42
  try {
51
43
  const resp = await axios.get(challengeUrl, { httpsAgent });
52
44
  data = (resp.data || '').replace(/\s+$/, '');
53
- } catch (e) {
45
+ }
46
+ catch (e) {
54
47
  log(`[error] HTTP request error from ${authz.identifier.value}`, e.message);
55
- return false
48
+ return false;
56
49
  }
57
-
58
50
  if (!data || (data !== keyAuthorization)) {
59
51
  log(`[error] Authorization not found in HTTP response from ${authz.identifier.value}`);
60
- return false
52
+ return false;
61
53
  }
62
- return true
63
-
54
+ return true;
64
55
  }
65
-
66
56
  const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
67
57
  let host = authz.identifier.value;
68
58
  if (utils.domain.isIpv6(host)) {
69
59
  host = `[${host}]`;
70
60
  }
71
61
  const challengeUrl = `http://${host}:${httpPort}${suffix}`;
72
-
73
62
  if (!await doQuery(challengeUrl)) {
74
63
  const httpsPort = axios.defaults.acmeSettings.httpsChallengePort || 443;
75
64
  const httpsChallengeUrl = `https://${host}:${httpsPort}${suffix}`;
76
- const res = await doQuery(httpsChallengeUrl)
65
+ const res = await doQuery(httpsChallengeUrl);
77
66
  if (!res) {
78
67
  throw new Error(`[error] 验证失败,请检查以上测试url是否可以正常访问`);
79
68
  }
80
69
  }
81
-
82
-
83
70
  log(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
84
71
  return true;
85
72
  }
86
-
87
73
  /**
88
74
  * Walk DNS until TXT records are found
89
75
  */
90
-
91
76
  async function walkDnsChallengeRecord(recordName, resolver = dns, deep = 0) {
92
-
93
77
  let records = [];
94
-
95
- const isAuthoritative = resolver === dns
78
+ const isAuthoritative = resolver === dns;
96
79
  /* Resolve TXT records */
97
80
  try {
98
81
  log(`检查域名 ${recordName} 的TXT记录(from ${isAuthoritative ? '本地DNS' : '权威DNS服务器'})`);
@@ -102,15 +85,14 @@ export function createChallengeFn(opts = {}) {
102
85
  log(`TXT records: ${JSON.stringify(txtRecords)}`);
103
86
  records = records.concat(...txtRecords);
104
87
  }
105
- } catch (e) {
88
+ }
89
+ catch (e) {
106
90
  log(`解析 TXT 记录出错, ${recordName} :${e.message}`);
107
91
  }
108
-
109
92
  /* Resolve CNAME record first */
110
93
  try {
111
94
  log(`检查是否存在CNAME映射: ${recordName}`);
112
95
  const cnameRecords = await resolver.resolveCname(recordName);
113
-
114
96
  if (cnameRecords.length) {
115
97
  const cnameRecord = cnameRecords[0];
116
98
  log(`已找到${recordName}的CNAME记录,将检查: ${cnameRecord}`);
@@ -119,37 +101,36 @@ export function createChallengeFn(opts = {}) {
119
101
  log(`从CNAME中找到TXT记录: ${JSON.stringify(res)}`);
120
102
  records = records.concat(...res);
121
103
  }
122
- } else {
104
+ }
105
+ else {
123
106
  log(`没有CNAME映射(${recordName})`);
124
107
  }
125
- } catch (e) {
108
+ }
109
+ catch (e) {
126
110
  log(`检查CNAME出错(${recordName}) :${e.message}`);
127
111
  }
128
- return records
112
+ return records;
129
113
  }
130
-
131
114
  async function walkTxtRecord(recordName, deep = 0) {
132
115
  if (deep > 5) {
133
- log(`walkTxtRecord too deep (#${deep}) , skip walk`)
134
- return []
116
+ log(`walkTxtRecord too deep (#${deep}) , skip walk`);
117
+ return [];
135
118
  }
136
-
137
- const txtRecords = []
119
+ const txtRecords = [];
138
120
  try {
139
121
  /* Default DNS resolver first */
140
122
  log('从本地DNS服务器获取TXT解析记录');
141
123
  const res = await walkDnsChallengeRecord(recordName, dns, deep);
142
124
  if (res && res.length > 0) {
143
125
  for (const item of res) {
144
- txtRecords.push(item)
126
+ txtRecords.push(item);
145
127
  }
146
128
  }
147
-
148
- } catch (e) {
149
- log(`本地获取TXT解析记录失败:${e.message}`)
150
129
  }
151
-
152
- if (walkFromAuthoritative !==false) {
130
+ catch (e) {
131
+ log(`本地获取TXT解析记录失败:${e.message}`);
132
+ }
133
+ if (walkFromAuthoritative !== false) {
153
134
  try {
154
135
  /* Authoritative DNS resolver */
155
136
  log(`从域名权威服务器获取TXT解析记录`);
@@ -157,23 +138,22 @@ export function createChallengeFn(opts = {}) {
157
138
  const res = await walkDnsChallengeRecord(recordName, authoritativeResolver, deep);
158
139
  if (res && res.length > 0) {
159
140
  for (const item of res) {
160
- txtRecords.push(item)
141
+ txtRecords.push(item);
161
142
  }
162
143
  }
163
- } catch (e) {
164
- log(`权威服务器获取TXT解析记录失败:${e.message}`)
165
144
  }
166
- }else{
145
+ catch (e) {
146
+ log(`权威服务器获取TXT解析记录失败:${e.message}`);
147
+ }
148
+ }
149
+ else {
167
150
  log(`跳过从权威服务器获取TXT解析记录`);
168
151
  }
169
-
170
-
171
152
  if (txtRecords.length === 0) {
172
153
  throw new Error(`没有找到TXT解析记录(${recordName})`);
173
154
  }
174
155
  return txtRecords;
175
156
  }
176
-
177
157
  /**
178
158
  * Verify ACME DNS challenge
179
159
  *
@@ -185,7 +165,6 @@ export function createChallengeFn(opts = {}) {
185
165
  * @param {string} [prefix] DNS prefix
186
166
  * @returns {Promise<boolean>}
187
167
  */
188
-
189
168
  async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '_acme-challenge.') {
190
169
  const recordName = `${prefix}${authz.identifier.value}`;
191
170
  log(`本地校验TXT记录): ${recordName}`);
@@ -194,14 +173,12 @@ export function createChallengeFn(opts = {}) {
194
173
  recordValues = [...new Set(recordValues)];
195
174
  log(`DNS查询成功, 找到 ${recordValues.length} 条TXT记录:${recordValues}`);
196
175
  if (!recordValues.length || !recordValues.includes(keyAuthorization)) {
197
- const err = `没有找到需要的DNS TXT记录: ${recordName},期望:${keyAuthorization},结果:${recordValues}`
176
+ const err = `没有找到需要的DNS TXT记录: ${recordName},期望:${keyAuthorization},结果:${recordValues}`;
198
177
  throw new Error(err);
199
178
  }
200
-
201
179
  log(`关键授权匹配成功(${challenge.type}/${recordName}):${keyAuthorization},校验成功, ACME challenge verified`);
202
180
  return true;
203
181
  }
204
-
205
182
  /**
206
183
  * Verify ACME TLS ALPN challenge
207
184
  *
@@ -212,23 +189,18 @@ export function createChallengeFn(opts = {}) {
212
189
  * @param {string} keyAuthorization Challenge key authorization
213
190
  * @returns {Promise<boolean>}
214
191
  */
215
-
216
192
  async function verifyTlsAlpnChallenge(authz, challenge, keyAuthorization) {
217
193
  const tlsAlpnPort = axios.defaults.acmeSettings.tlsAlpnChallengePort || 443;
218
194
  const host = authz.identifier.value;
219
195
  log(`Establishing TLS connection with host: ${host}:${tlsAlpnPort}`);
220
-
221
196
  const certificate = await util.retrieveTlsAlpnCertificate(host, tlsAlpnPort);
222
197
  log('Certificate received from server successfully, matching key authorization in ALPN');
223
-
224
198
  if (!isAlpnCertificateAuthorizationValid(certificate, keyAuthorization)) {
225
199
  throw new Error(`Authorization not found in certificate from ${authz.identifier.value}`);
226
200
  }
227
-
228
201
  log(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
229
202
  return true;
230
203
  }
231
-
232
204
  return {
233
205
  challenges: {
234
206
  'http-01': verifyHttpChallenge,
@@ -237,10 +209,6 @@ export function createChallengeFn(opts = {}) {
237
209
  },
238
210
  walkTxtRecord,
239
211
  walkDnsChallengeRecord,
240
- }
241
-
212
+ };
242
213
  }
243
-
244
-
245
-
246
214
  // createChallengeFn({logger:{info:console.log}}).walkDnsChallengeRecord("handsfree.work")
package/dist/wait.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function wait(ms: any): Promise<unknown>;
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  export async function wait(ms) {
2
3
  return new Promise((resolve) => {
3
4
  setTimeout(resolve, ms);
package/package.json CHANGED
@@ -3,22 +3,22 @@
3
3
  "description": "Simple and unopinionated ACME client",
4
4
  "private": false,
5
5
  "author": "nmorsman",
6
- "version": "1.39.12",
6
+ "version": "1.39.13",
7
7
  "type": "module",
8
- "module": "scr/index.js",
9
- "main": "src/index.js",
10
- "types": "types/index.d.ts",
8
+ "module": "./dist/index.js",
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
11
  "license": "MIT",
12
12
  "homepage": "https://github.com/publishlab/node-acme-client",
13
13
  "engines": {
14
14
  "node": ">= 18"
15
15
  },
16
16
  "files": [
17
- "src",
17
+ "dist",
18
18
  "types"
19
19
  ],
20
20
  "dependencies": {
21
- "@certd/basic": "^1.39.12",
21
+ "@certd/basic": "^1.39.13",
22
22
  "@peculiar/x509": "^1.11.0",
23
23
  "asn1js": "^3.0.5",
24
24
  "axios": "^1.9.0",
@@ -35,10 +35,12 @@
35
35
  "@typescript-eslint/parser": "^8.26.1",
36
36
  "chai": "^4.4.1",
37
37
  "chai-as-promised": "^7.1.2",
38
+ "cross-env": "^7.0.3",
38
39
  "eslint": "^8.57.0",
39
40
  "eslint-config-prettier": "^8.5.0",
40
41
  "eslint-plugin-import": "^2.29.1",
41
42
  "eslint-plugin-prettier": "^4.2.1",
43
+ "esmock": "^2.7.5",
42
44
  "jsdoc-to-markdown": "^8.0.1",
43
45
  "mocha": "^10.6.0",
44
46
  "nock": "^13.5.4",
@@ -47,13 +49,17 @@
47
49
  "typescript": "^5.4.2"
48
50
  },
49
51
  "scripts": {
50
- "build-docs": "jsdoc2md src/client.js > docs/client.md && jsdoc2md src/crypto/index.js > docs/crypto.md && jsdoc2md src/crypto/forge.js > docs/forge.md",
51
- "lint": "eslint .",
52
- "lint-types": "tsd",
53
- "prepublishOnly": "npm run build-docs",
52
+ "before-build": "node -e \"const fs=require('fs');fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('tsconfig.tsbuildinfo',{force:true});\"",
53
+ "build": "npm run before-build && tsc --skipLibCheck",
54
+ "build-docs": "jsdoc2md dist/client.js > docs/client.md && jsdoc2md dist/crypto/index.js > docs/crypto.md && jsdoc2md dist/crypto/forge.js > docs/forge.md",
55
+ "lint": "eslint \"src/**/*.ts\" \"types/**/*.ts\"",
56
+ "lint-types": "tsd --files \"types/index.test-d.ts\"",
57
+ "prepublishOnly": "npm run build && npm run build-docs",
54
58
  "test": "mocha -t 60000 \"test/setup.js\" \"test/**/*.spec.js\"",
59
+ "before-test:unit": "node -e \"const fs=require('fs');fs.rmSync('dist-test',{recursive:true,force:true});fs.rmSync('tsconfig.test.tsbuildinfo',{force:true});\"",
60
+ "test:unit": "cross-env NODE_ENV=unittest npm run before-test:unit && cross-env NODE_ENV=unittest tsc -p tsconfig.test.json --skipLibCheck && cross-env NODE_ENV=unittest mocha -t 60000 \"dist-test/**/*.test.js\"",
55
61
  "pub": "npm publish",
56
- "compile": "echo '1'"
62
+ "compile": "tsc --skipLibCheck --watch"
57
63
  },
58
64
  "repository": {
59
65
  "type": "git",
@@ -70,5 +76,5 @@
70
76
  "bugs": {
71
77
  "url": "https://github.com/publishlab/node-acme-client/issues"
72
78
  },
73
- "gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
79
+ "gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3"
74
80
  }
package/types/index.d.ts CHANGED
@@ -4,8 +4,6 @@
4
4
 
5
5
  import { AxiosInstance } from 'axios';
6
6
  import * as rfc8555 from './rfc8555';
7
- import {CancelError} from '../src/error.js'
8
- export * from '../src/error.js'
9
7
 
10
8
  export type PrivateKeyBuffer = Buffer;
11
9
  export type PublicKeyBuffer = Buffer;
@@ -115,6 +113,15 @@ export const directory: {
115
113
  zerossl: {
116
114
  staging: string,
117
115
  production: string
116
+ },
117
+ sslcom: {
118
+ staging: string,
119
+ production: string,
120
+ ec: string
121
+ },
122
+ litessl: {
123
+ staging: string,
124
+ production: string
118
125
  }
119
126
  };
120
127
 
@@ -211,14 +218,16 @@ export const agents: any;
211
218
  * Logger
212
219
  */
213
220
 
221
+ export class CancelError extends Error {
222
+ constructor(message?: string);
223
+ }
224
+
214
225
  export function setLogger(fn: (message: any, ...args: any[]) => void): void;
215
226
 
216
227
  export function createChallengeFn(opts?: {logger?:any}): any;
217
228
  // export function walkTxtRecord(record: any): Promise<string[]>;
218
229
  export function getAuthoritativeDnsResolver(record:string): Promise<any>;
219
230
 
220
- export const CancelError: typeof CancelError;
221
-
222
231
  export function resolveDomainBySoaRecord(domain: string): Promise<string>;
223
232
 
224
- export function setWalkFromAuthoritative(value = true): void;
233
+ export function setWalkFromAuthoritative(value?: boolean): void;
@@ -2,7 +2,7 @@
2
2
  * acme-client type definition tests
3
3
  */
4
4
 
5
- import * as acme from 'acme-client';
5
+ import * as acme from '..';
6
6
 
7
7
  (async () => {
8
8
  /* Client */
@@ -10,6 +10,7 @@ import * as acme from 'acme-client';
10
10
 
11
11
  const client = new acme.Client({
12
12
  accountKey,
13
+ sslProvider: 'letsencrypt',
13
14
  directoryUrl: acme.directory.letsencrypt.staging
14
15
  });
15
16
 
@@ -52,7 +53,10 @@ import * as acme from 'acme-client';
52
53
  /* Auto */
53
54
  await client.auto({
54
55
  csr: certCsr,
55
- challengeCreateFn: async (authz, challenge, keyAuthorization) => {},
56
+ challengeCreateFn: async (authz, keyAuthorization) => ({
57
+ challenge: authz.challenges[0],
58
+ keyAuthorization: await keyAuthorization(authz.challenges[0])
59
+ }),
56
60
  challengeRemoveFn: async (authz, challenge, keyAuthorization) => {}
57
61
  });
58
62
 
@@ -63,7 +67,10 @@ import * as acme from 'acme-client';
63
67
  skipChallengeVerification: false,
64
68
  challengePriority: ['http-01', 'dns-01'],
65
69
  preferredChain: 'DST Root CA X3',
66
- challengeCreateFn: async (authz, challenge, keyAuthorization) => {},
70
+ challengeCreateFn: async (authz, keyAuthorization) => ({
71
+ challenge: authz.challenges[0],
72
+ keyAuthorization: await keyAuthorization(authz.challenges[0])
73
+ }),
67
74
  challengeRemoveFn: async (authz, challenge, keyAuthorization) => {}
68
75
  });
69
76
  })();
package/src/index.js DELETED
@@ -1,106 +0,0 @@
1
- /**
2
- * acme-client
3
- */
4
- import AcmeClinet from './client.js'
5
- export const Client = AcmeClinet
6
-
7
- /**
8
- * Directory URLs
9
- */
10
-
11
- export const directory = {
12
- buypass: {
13
- staging: 'https://api.test4.buypass.no/acme/directory',
14
- production: 'https://api.buypass.com/acme/directory',
15
- },
16
- google: {
17
- staging: 'https://dv.acme-v02.test-api.pki.goog/directory',
18
- production: 'https://dv.acme-v02.api.pki.goog/directory',
19
- },
20
- letsencrypt: {
21
- staging: 'https://acme-staging-v02.api.letsencrypt.org/directory',
22
- production: 'https://acme-v02.api.letsencrypt.org/directory',
23
- },
24
- letsencrypt_staging: {
25
- production: 'https://acme-staging-v02.api.letsencrypt.org/directory',
26
- },
27
- zerossl: {
28
- staging: 'https://acme.zerossl.com/v2/DV90',
29
- production: 'https://acme.zerossl.com/v2/DV90',
30
- },
31
- sslcom:{
32
- staging: 'https://acme.ssl.com/sslcom-dv-rsa',
33
- production: 'https://acme.ssl.com/sslcom-dv-rsa',
34
- ec: 'https://acme.ssl.com/sslcom-dv-ecc',
35
- },
36
- litessl: {
37
- staging: 'https://acme.litessl.com/acme/v2/directory',
38
- production: 'https://acme.litessl.com/acme/v2/directory',
39
- },
40
- };
41
-
42
- export function getDirectoryUrl(opts) {
43
- const {sslProvider, pkType} = opts
44
- const list= directory[sslProvider]
45
- if (!list) {
46
- throw new Error(`sslProvider ${sslProvider} not found`)
47
- }
48
- let pkTypePrefix = pkType || 'rsa'
49
- if (pkType) {
50
- pkTypePrefix = pkType.toLowerCase().split("_")[0]
51
- }
52
-
53
- if (pkTypePrefix && list[pkTypePrefix]) {
54
- return list[pkTypePrefix]
55
- }
56
-
57
- return list.production
58
- }
59
-
60
-
61
- export function getAllSslProviderDomains() {
62
- const list = Object.values(directory).map((item) => {
63
- let url = item.production.replace('https://', '')
64
- url = url.substring(0, url.indexOf('/'))
65
- return url
66
- })
67
- return list
68
- }
69
-
70
- let sslProviderReverseProxies = {}
71
-
72
- function initSslProviderReverseProxies() {
73
- for (const sslProvider of getAllSslProviderDomains()) {
74
- sslProviderReverseProxies[sslProvider] = ""
75
- }
76
- }
77
- initSslProviderReverseProxies()
78
-
79
- export function getSslProviderReverseProxies() {
80
- return sslProviderReverseProxies
81
- }
82
- export function setSslProviderReverseProxies(reverseProxies) {
83
- Object.assign(sslProviderReverseProxies, reverseProxies)
84
- }
85
-
86
- /**
87
- * Crypto
88
- */
89
-
90
- export * as crypto from './crypto/index.js'
91
- export * as forge from './crypto/forge.js'
92
-
93
- /**
94
- * Axios
95
- */
96
-
97
- export * from './axios.js'
98
- /**
99
- * Logger
100
- */
101
-
102
- export * from './logger.js'
103
- export * from './verify.js'
104
- export * from './error.js'
105
-
106
- export * from './util.js'
package/src/logs/app.log DELETED
File without changes