@certd/acme-client 1.37.6 → 1.37.7

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.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Simple and unopinionated ACME client",
4
4
  "private": false,
5
5
  "author": "nmorsman",
6
- "version": "1.37.6",
6
+ "version": "1.37.7",
7
7
  "type": "module",
8
8
  "module": "scr/index.js",
9
9
  "main": "src/index.js",
@@ -18,7 +18,7 @@
18
18
  "types"
19
19
  ],
20
20
  "dependencies": {
21
- "@certd/basic": "^1.37.6",
21
+ "@certd/basic": "^1.37.7",
22
22
  "@peculiar/x509": "^1.11.0",
23
23
  "asn1js": "^3.0.5",
24
24
  "axios": "^1.7.2",
@@ -70,5 +70,5 @@
70
70
  "bugs": {
71
71
  "url": "https://github.com/publishlab/node-acme-client/issues"
72
72
  },
73
- "gitHead": "9fcdeca6920fc7d465e2443dab4f246d279f108b"
73
+ "gitHead": "55d2a1f09b617bc73bd81a65796446c4602ed1b2"
74
74
  }
package/src/auto.js CHANGED
@@ -4,6 +4,9 @@
4
4
  import { readCsrDomains } from "./crypto/index.js";
5
5
  import { wait } from "./wait.js";
6
6
  import { CancelError } from "./error.js";
7
+ import { domainUtils } from '@certd/basic';
8
+
9
+
7
10
 
8
11
 
9
12
  const defaultOpts = {
@@ -65,7 +68,7 @@ export default async (client, userOpts) => {
65
68
  * Parse domains from CSR
66
69
  */
67
70
 
68
- log("[auto] Parsing domains from Certificate Signing Request ");
71
+ log("[auto] Parsing domains from Certificate Signing Request");
69
72
  const { commonName, altNames } = readCsrDomains(opts.csr);
70
73
  const uniqueDomains = Array.from(new Set([commonName].concat(altNames).filter((d) => d)));
71
74
 
@@ -76,9 +79,21 @@ export default async (client, userOpts) => {
76
79
  */
77
80
 
78
81
  log("[auto] Placing new certificate order with ACME provider");
79
- const orderPayload = { identifiers: uniqueDomains.map((d) => ({ type: "dns", value: d })) };
80
- if (opts.profile && client.sslProvider === 'letsencrypt' ){
82
+
83
+ let hasIp = false
84
+ const orderPayload = { identifiers: uniqueDomains.map((d) =>{
85
+ // 判断是否为IP(v4或v6),否则按域名处理
86
+ const type = domainUtils.isIp(d) ? 'ip' : 'dns';
87
+ if(type === 'ip'){
88
+ hasIp = true
89
+ }
90
+ return { type, value: d }
91
+ }) };
92
+ if (opts.profile && client.sslProvider.startsWith("letsencrypt") ){
81
93
  orderPayload.profile = opts.profile;
94
+ if(hasIp){
95
+ orderPayload.profile = "shortlived"
96
+ }
82
97
  }
83
98
  const order = await client.createOrder(orderPayload);
84
99
  const authorizations = await client.getAuthorizations(order);
package/src/client.js CHANGED
@@ -7,7 +7,7 @@ import { createHash } from 'crypto';
7
7
  import { getPemBodyAsB64u } from './crypto/index.js';
8
8
  import HttpClient from './http.js';
9
9
  import AcmeApi from './api.js';
10
- import verify from './verify.js';
10
+ import {createChallengeFn} from './verify.js';
11
11
  import * as util from './util.js';
12
12
  import auto from './auto.js';
13
13
  import { CancelError } from './error.js';
@@ -492,6 +492,9 @@ class AcmeClient {
492
492
  throw new Error('Unable to verify ACME challenge, URL not found');
493
493
  }
494
494
 
495
+ const {challenges} = createChallengeFn({logger:this.logger});
496
+
497
+ const verify = challenges
495
498
  if (typeof verify[challenge.type] === 'undefined') {
496
499
  throw new Error(`Unable to verify ACME challenge, unknown type: ${challenge.type}`);
497
500
  }
@@ -507,7 +510,12 @@ class AcmeClient {
507
510
  };
508
511
 
509
512
  this.log('Waiting for ACME challenge verification(等待ACME检查验证)');
510
- return util.retry(verifyFn, this.backoffOpts);
513
+
514
+
515
+ const log = (...args)=>{
516
+ this.logger.info(...args)
517
+ }
518
+ return util.retry(verifyFn, this.backoffOpts,log);
511
519
  }
512
520
 
513
521
  /**
package/src/util.js CHANGED
@@ -48,7 +48,7 @@ class Backoff {
48
48
  * @returns {Promise}
49
49
  */
50
50
 
51
- async function retryPromise(fn, attempts, backoff) {
51
+ async function retryPromise(fn, attempts, backoff, logger = log) {
52
52
  let aborted = false;
53
53
 
54
54
  try {
@@ -60,12 +60,12 @@ async function retryPromise(fn, attempts, backoff) {
60
60
  throw e;
61
61
  }
62
62
 
63
- log(`Promise rejected: ${e.message}`);
63
+ logger(`Promise rejected: ${e.message}`);
64
64
  const duration = backoff.duration();
65
- log(`Promise rejected attempt #${backoff.attempts}, ${duration}ms 后重试: ${e.message}`);
65
+ logger(`Promise rejected attempt #${backoff.attempts}, ${duration}ms 后重试: ${e.message}`);
66
66
 
67
67
  await new Promise((resolve) => { setTimeout(resolve, duration); });
68
- return retryPromise(fn, attempts, backoff);
68
+ return retryPromise(fn, attempts, backoff, logger);
69
69
  }
70
70
  }
71
71
 
@@ -80,9 +80,9 @@ async function retryPromise(fn, attempts, backoff) {
80
80
  * @returns {Promise}
81
81
  */
82
82
 
83
- function retry(fn, { attempts = 5, min = 5000, max = 30000 } = {}) {
83
+ function retry(fn, { attempts = 5, min = 5000, max = 30000 } = {}, logger = log) {
84
84
  const backoff = new Backoff({ min, max });
85
- return retryPromise(fn, attempts, backoff);
85
+ return retryPromise(fn, attempts, backoff, logger);
86
86
  }
87
87
 
88
88
  /**
@@ -216,21 +216,21 @@ function formatResponseError(resp) {
216
216
  * @returns {Promise<string>} Root domain name
217
217
  */
218
218
 
219
- async function resolveDomainBySoaRecord(recordName) {
219
+ async function resolveDomainBySoaRecord(recordName, logger = log) {
220
220
  try {
221
221
  await dns.resolveSoa(recordName);
222
- log(`找到${recordName}的SOA记录`);
222
+ logger(`找到${recordName}的SOA记录`);
223
223
  return recordName;
224
224
  }
225
225
  catch (e) {
226
- log(`找不到${recordName}的SOA记录,继续往主域名查找`);
226
+ logger(`找不到${recordName}的SOA记录,继续往主域名查找`);
227
227
  const parentRecordName = recordName.split('.').slice(1).join('.');
228
228
 
229
229
  if (!parentRecordName.includes('.')) {
230
230
  throw new Error('SOA record查找失败');
231
231
  }
232
232
 
233
- return resolveDomainBySoaRecord(parentRecordName);
233
+ return resolveDomainBySoaRecord(parentRecordName,logger);
234
234
  }
235
235
  }
236
236
 
@@ -241,18 +241,18 @@ async function resolveDomainBySoaRecord(recordName) {
241
241
  * @returns {Promise<dns.Resolver>} DNS resolver
242
242
  */
243
243
 
244
- async function getAuthoritativeDnsResolver(recordName) {
245
- log(`获取域名${recordName}的权威NS服务器: `);
244
+ async function getAuthoritativeDnsResolver(recordName, logger = log) {
245
+ logger(`获取域名${recordName}的权威NS服务器: `);
246
246
  const resolver = new dns.Resolver();
247
247
 
248
248
  try {
249
249
  /* Resolve root domain by SOA */
250
- const domain = await resolveDomainBySoaRecord(recordName);
250
+ const domain = await resolveDomainBySoaRecord(recordNam,logger);
251
251
 
252
252
  /* Resolve authoritative NS addresses */
253
- log(`获取到权威NS服务器name: ${domain}`);
253
+ logger(`获取到权威NS服务器name: ${domain}`);
254
254
  const nsRecords = await dns.resolveNs(domain);
255
- log(`域名权威NS服务器:${nsRecords}`);
255
+ logger(`域名权威NS服务器:${nsRecords}`);
256
256
  const nsAddrArray = await Promise.all(nsRecords.map(async (r) => dns.resolve4(r)));
257
257
  const nsAddresses = [].concat(...nsAddrArray).filter((a) => a);
258
258
 
@@ -261,16 +261,16 @@ async function getAuthoritativeDnsResolver(recordName) {
261
261
  }
262
262
 
263
263
  /* Authoritative NS success */
264
- log(`Found ${nsAddresses.length} authoritative NS addresses for domain: ${domain}`);
264
+ logger(`Found ${nsAddresses.length} authoritative NS addresses for domain: ${domain}`);
265
265
  resolver.setServers(nsAddresses);
266
266
  }
267
267
  catch (e) {
268
- log(`Authoritative NS lookup error(获取权威NS服务器地址失败): ${e.message}`);
268
+ logger(`Authoritative NS lookup error(获取权威NS服务器地址失败): ${e.message}`);
269
269
  }
270
270
 
271
271
  /* Return resolver */
272
272
  const addresses = resolver.getServers();
273
- log(`DNS resolver addresses(域名的权威NS服务器地址): ${addresses.join(', ')}`);
273
+ logger(`DNS resolver addresses(域名的权威NS服务器地址): ${addresses.join(', ')}`);
274
274
 
275
275
  return resolver;
276
276
  }
package/src/verify.js CHANGED
@@ -4,14 +4,22 @@
4
4
 
5
5
  import dnsSdk from "dns"
6
6
  import https from 'https'
7
- import {log} from './logger.js'
7
+ import {log as defaultLog} from './logger.js'
8
8
  import axios from './axios.js'
9
9
  import * as util from './util.js'
10
10
  import {isAlpnCertificateAuthorizationValid} from './crypto/index.js'
11
11
 
12
12
 
13
13
  const dns = dnsSdk.promises
14
- /**
14
+
15
+
16
+ export function createChallengeFn(opts = {}){
17
+ const logger = opts?.logger || {info:defaultLog,error:defaultLog,warn:defaultLog,debug:defaultLog}
18
+
19
+ const log = function(...args){
20
+ logger.info(...args)
21
+ }
22
+ /**
15
23
  * Verify ACME HTTP challenge
16
24
  *
17
25
  * https://datatracker.ietf.org/doc/html/rfc8555#section-8.3
@@ -112,7 +120,7 @@ async function walkDnsChallengeRecord(recordName, resolver = dns,deep = 0) {
112
120
  return records
113
121
  }
114
122
 
115
- export async function walkTxtRecord(recordName,deep = 0) {
123
+ async function walkTxtRecord(recordName,deep = 0) {
116
124
  if(deep >5){
117
125
  log(`walkTxtRecord too deep (#${deep}) , skip walk`)
118
126
  return []
@@ -136,7 +144,7 @@ export async function walkTxtRecord(recordName,deep = 0) {
136
144
  try{
137
145
  /* Authoritative DNS resolver */
138
146
  log(`从域名权威服务器获取TXT解析记录`);
139
- const authoritativeResolver = await util.getAuthoritativeDnsResolver(recordName);
147
+ const authoritativeResolver = await util.getAuthoritativeDnsResolver(recordName,log);
140
148
  const res = await walkDnsChallengeRecord(recordName, authoritativeResolver,deep);
141
149
  if (res && res.length > 0) {
142
150
  for (const item of res) {
@@ -173,7 +181,8 @@ async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '
173
181
  recordValues = [...new Set(recordValues)];
174
182
  log(`DNS查询成功, 找到 ${recordValues.length} 条TXT记录:${recordValues}`);
175
183
  if (!recordValues.length || !recordValues.includes(keyAuthorization)) {
176
- throw new Error(`没有找到需要的DNS TXT记录: ${recordName},期望:${keyAuthorization},结果:${recordValues}`);
184
+ const err = `没有找到需要的DNS TXT记录: ${recordName},期望:${keyAuthorization},结果:${recordValues}`
185
+ throw new Error(err);
177
186
  }
178
187
 
179
188
  log(`关键授权匹配成功(${challenge.type}/${recordName}):${keyAuthorization},校验成功, ACME challenge verified`);
@@ -207,12 +216,13 @@ async function verifyTlsAlpnChallenge(authz, challenge, keyAuthorization) {
207
216
  return true;
208
217
  }
209
218
 
210
- /**
211
- * Export API
212
- */
219
+ return {
220
+ challenges:{
221
+ 'http-01': verifyHttpChallenge,
222
+ 'dns-01': verifyDnsChallenge,
223
+ 'tls-alpn-01': verifyTlsAlpnChallenge,
224
+ },
225
+ walkTxtRecord,
226
+ }
213
227
 
214
- export default {
215
- 'http-01': verifyHttpChallenge,
216
- 'dns-01': verifyDnsChallenge,
217
- 'tls-alpn-01': verifyTlsAlpnChallenge,
218
- };
228
+ }
package/types/index.d.ts CHANGED
@@ -207,7 +207,8 @@ export const agents: any;
207
207
 
208
208
  export function setLogger(fn: (message: any, ...args: any[]) => void): void;
209
209
 
210
- export function walkTxtRecord(record: any): Promise<string[]>;
210
+ export function createChallengeFn(opts?: {logger?:any}): any;
211
+ // export function walkTxtRecord(record: any): Promise<string[]>;
211
212
  export function getAuthoritativeDnsResolver(record:string): Promise<any>;
212
213
 
213
214
  export const CancelError: typeof CancelError;