@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.
- package/dist/api.d.ts +151 -0
- package/{src → dist}/api.js +2 -38
- package/dist/auto.d.ts +9 -0
- package/{src → dist}/auto.js +45 -79
- package/dist/axios.d.ts +8 -0
- package/{src → dist}/axios.js +3 -31
- package/dist/client.d.ts +396 -0
- package/{src → dist}/client.js +16 -108
- package/dist/crypto/forge.d.ts +174 -0
- package/{src → dist}/crypto/forge.js +7 -63
- package/dist/crypto/index.d.ts +215 -0
- package/{src → dist}/crypto/index.js +2 -87
- package/dist/error.d.ts +3 -0
- package/{src → dist}/error.js +1 -3
- package/dist/http.d.ts +135 -0
- package/{src → dist}/http.js +3 -65
- package/dist/index.d.ts +58 -0
- package/dist/index.js +90 -0
- package/dist/logger.d.ts +15 -0
- package/{src → dist}/logger.js +4 -9
- package/dist/rfc8555.d.ts +106 -0
- package/dist/rfc8555.js +25 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.js +1 -0
- package/dist/util.d.ts +85 -0
- package/{src → dist}/util.js +11 -78
- package/dist/verify.d.ts +14 -0
- package/{src → dist}/verify.js +46 -78
- package/dist/wait.d.ts +1 -0
- package/{src → dist}/wait.js +1 -0
- package/package.json +18 -12
- package/types/index.d.ts +14 -5
- package/types/index.test-d.ts +10 -3
- package/src/index.js +0 -106
- package/src/logs/app.log +0 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type * as rfc8555 from "./rfc8555.js";
|
|
2
|
+
import type { Challenge } from "./rfc8555.js";
|
|
3
|
+
export type * from "./rfc8555.js";
|
|
4
|
+
export type PrivateKeyBuffer = Buffer;
|
|
5
|
+
export type PublicKeyBuffer = Buffer;
|
|
6
|
+
export type CertificateBuffer = Buffer;
|
|
7
|
+
export type CsrBuffer = Buffer;
|
|
8
|
+
export type PrivateKeyString = string;
|
|
9
|
+
export type PublicKeyString = string;
|
|
10
|
+
export type CertificateString = string;
|
|
11
|
+
export type CsrString = string;
|
|
12
|
+
export interface Order extends rfc8555.Order {
|
|
13
|
+
url: string;
|
|
14
|
+
}
|
|
15
|
+
export interface Authorization extends rfc8555.Authorization {
|
|
16
|
+
url: string;
|
|
17
|
+
}
|
|
18
|
+
export type UrlMapping = {
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
mappings: Record<string, string>;
|
|
21
|
+
};
|
|
22
|
+
export interface ClientExternalAccountBindingOptions {
|
|
23
|
+
kid: string;
|
|
24
|
+
hmacKey: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ClientOptions {
|
|
27
|
+
sslProvider: string;
|
|
28
|
+
directoryUrl: string;
|
|
29
|
+
accountKey: PrivateKeyBuffer | PrivateKeyString;
|
|
30
|
+
accountUrl?: string;
|
|
31
|
+
externalAccountBinding?: ClientExternalAccountBindingOptions;
|
|
32
|
+
backoffAttempts?: number;
|
|
33
|
+
backoffMin?: number;
|
|
34
|
+
backoffMax?: number;
|
|
35
|
+
urlMapping?: UrlMapping;
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
logger?: any;
|
|
38
|
+
}
|
|
39
|
+
export interface ClientAutoOptions {
|
|
40
|
+
csr: CsrBuffer | CsrString;
|
|
41
|
+
challengeCreateFn: (authz: Authorization, keyAuthorization: (challenge: Challenge) => Promise<string>) => Promise<{
|
|
42
|
+
recordReq?: any;
|
|
43
|
+
recordRes?: any;
|
|
44
|
+
dnsProvider?: any;
|
|
45
|
+
challenge: Challenge;
|
|
46
|
+
keyAuthorization: string;
|
|
47
|
+
}>;
|
|
48
|
+
challengeRemoveFn: (authz: Authorization, challenge: Challenge, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider: any, httpUploader: any) => Promise<any>;
|
|
49
|
+
email?: string;
|
|
50
|
+
termsOfServiceAgreed?: boolean;
|
|
51
|
+
skipChallengeVerification?: boolean;
|
|
52
|
+
challengePriority?: string[];
|
|
53
|
+
preferredChain?: string;
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
profile?: string;
|
|
56
|
+
waitDnsDiffuseTime?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface CertificateDomains {
|
|
59
|
+
commonName: string;
|
|
60
|
+
altNames: string[];
|
|
61
|
+
}
|
|
62
|
+
export interface CertificateIssuer {
|
|
63
|
+
commonName: string;
|
|
64
|
+
}
|
|
65
|
+
export interface CertificateInfo {
|
|
66
|
+
issuer: CertificateIssuer;
|
|
67
|
+
domains: CertificateDomains;
|
|
68
|
+
notAfter: Date;
|
|
69
|
+
notBefore: Date;
|
|
70
|
+
}
|
|
71
|
+
export interface CsrOptions {
|
|
72
|
+
keySize?: number;
|
|
73
|
+
commonName?: string;
|
|
74
|
+
altNames?: string[];
|
|
75
|
+
country?: string;
|
|
76
|
+
state?: string;
|
|
77
|
+
locality?: string;
|
|
78
|
+
organization?: string;
|
|
79
|
+
organizationUnit?: string;
|
|
80
|
+
emailAddress?: string;
|
|
81
|
+
}
|
|
82
|
+
export interface RsaPublicJwk {
|
|
83
|
+
e: string;
|
|
84
|
+
kty: string;
|
|
85
|
+
n: string;
|
|
86
|
+
}
|
|
87
|
+
export interface EcdsaPublicJwk {
|
|
88
|
+
crv: string;
|
|
89
|
+
kty: string;
|
|
90
|
+
x: string;
|
|
91
|
+
y: string;
|
|
92
|
+
}
|
|
93
|
+
export interface CryptoInterface {
|
|
94
|
+
createPrivateKey(keySize?: number, encodingType?: string): Promise<PrivateKeyBuffer>;
|
|
95
|
+
createPrivateRsaKey(keySize?: number, encodingType?: string): Promise<PrivateKeyBuffer>;
|
|
96
|
+
createPrivateEcdsaKey(namedCurve?: "P-256" | "P-384" | "P-521", encodingType?: string): Promise<PrivateKeyBuffer>;
|
|
97
|
+
getPublicKey(keyPem: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString): PublicKeyBuffer;
|
|
98
|
+
getJwk(keyPem: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString): RsaPublicJwk | EcdsaPublicJwk;
|
|
99
|
+
splitPemChain(chainPem: CertificateBuffer | CertificateString): string[];
|
|
100
|
+
getPemBodyAsB64u(pem: CertificateBuffer | CertificateString): string;
|
|
101
|
+
readCsrDomains(csrPem: CsrBuffer | CsrString): CertificateDomains;
|
|
102
|
+
readCertificateInfo(certPem: CertificateBuffer | CertificateString): CertificateInfo;
|
|
103
|
+
createCsr(data: CsrOptions, keyPem?: PrivateKeyBuffer | PrivateKeyString, encodingType?: string): Promise<[PrivateKeyBuffer, CsrBuffer]>;
|
|
104
|
+
createAlpnCertificate(authz: Authorization, keyAuthorization: string, keyPem?: PrivateKeyBuffer | PrivateKeyString): Promise<[PrivateKeyBuffer, CertificateBuffer]>;
|
|
105
|
+
isAlpnCertificateAuthorizationValid(certPem: CertificateBuffer | CertificateString, keyAuthorization: string): boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface CryptoLegacyInterface {
|
|
108
|
+
createPrivateKey(size?: number): Promise<PrivateKeyBuffer>;
|
|
109
|
+
createPublicKey(key: PrivateKeyBuffer | PrivateKeyString): Promise<PublicKeyBuffer>;
|
|
110
|
+
getPemBody(str: string): string;
|
|
111
|
+
splitPemChain(str: string): string[];
|
|
112
|
+
getModulus(input: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString | CertificateBuffer | CertificateString | CsrBuffer | CsrString): Promise<Buffer>;
|
|
113
|
+
getPublicExponent(input: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString | CertificateBuffer | CertificateString | CsrBuffer | CsrString): Promise<Buffer>;
|
|
114
|
+
readCsrDomains(csr: CsrBuffer | CsrString): Promise<CertificateDomains>;
|
|
115
|
+
readCertificateInfo(cert: CertificateBuffer | CertificateString): Promise<CertificateInfo>;
|
|
116
|
+
createCsr(data: CsrOptions, key?: PrivateKeyBuffer | PrivateKeyString): Promise<[PrivateKeyBuffer, CsrBuffer]>;
|
|
117
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility methods
|
|
3
|
+
*/
|
|
4
|
+
import dnsSdk from 'dns';
|
|
5
|
+
/**
|
|
6
|
+
* Retry promise
|
|
7
|
+
*
|
|
8
|
+
* @param {function} fn Function returning promise that should be retried
|
|
9
|
+
* @param {object} [backoffOpts] Backoff options
|
|
10
|
+
* @param {number} [backoffOpts.attempts] Maximum number of attempts, default: `5`
|
|
11
|
+
* @param {number} [backoffOpts.min] Minimum attempt delay in milliseconds, default: `5000`
|
|
12
|
+
* @param {number} [backoffOpts.max] Maximum attempt delay in milliseconds, default: `30000`
|
|
13
|
+
* @returns {Promise}
|
|
14
|
+
*/
|
|
15
|
+
declare function retry(fn: any, { attempts, min, max }?: {
|
|
16
|
+
attempts?: number;
|
|
17
|
+
min?: number;
|
|
18
|
+
max?: number;
|
|
19
|
+
}, logger?: (...msg: any[]) => void): Promise<any>;
|
|
20
|
+
/**
|
|
21
|
+
* Parse URLs from Link header
|
|
22
|
+
*
|
|
23
|
+
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.4.2
|
|
24
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link
|
|
25
|
+
*
|
|
26
|
+
* @param {string} header Header contents
|
|
27
|
+
* @param {string} rel Link relation, default: `alternate`
|
|
28
|
+
* @returns {string[]} Array of URLs
|
|
29
|
+
*/
|
|
30
|
+
declare function parseLinkHeader(header: any, rel?: string): any;
|
|
31
|
+
/**
|
|
32
|
+
* Parse date or duration from Retry-After header
|
|
33
|
+
*
|
|
34
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
|
35
|
+
*
|
|
36
|
+
* @param {string} header Header contents
|
|
37
|
+
* @returns {number} Retry duration in seconds
|
|
38
|
+
*/
|
|
39
|
+
declare function parseRetryAfterHeader(header: any): number;
|
|
40
|
+
/**
|
|
41
|
+
* Find certificate chain with preferred issuer common name
|
|
42
|
+
* - If issuer is found in multiple chains, the closest to root wins
|
|
43
|
+
* - If issuer can not be located, the first chain will be returned
|
|
44
|
+
*
|
|
45
|
+
* @param {string[]} certificates Array of PEM encoded certificate chains
|
|
46
|
+
* @param {string} issuer Preferred certificate issuer
|
|
47
|
+
* @returns {string} PEM encoded certificate chain
|
|
48
|
+
*/
|
|
49
|
+
declare function findCertificateChainForIssuer(chains: any, issuer: any): any;
|
|
50
|
+
/**
|
|
51
|
+
* Find and format error in response object
|
|
52
|
+
*
|
|
53
|
+
* @param {object} resp HTTP response
|
|
54
|
+
* @returns {string} Error message
|
|
55
|
+
*/
|
|
56
|
+
declare function formatResponseError(resp: any): any;
|
|
57
|
+
/**
|
|
58
|
+
* Resolve root domain name by looking for SOA record
|
|
59
|
+
*
|
|
60
|
+
* @param {string} recordName DNS record name
|
|
61
|
+
* @returns {Promise<string>} Root domain name
|
|
62
|
+
*/
|
|
63
|
+
declare function resolveDomainBySoaRecord(recordName: any, logger?: (...msg: any[]) => void): Promise<any>;
|
|
64
|
+
/**
|
|
65
|
+
* Get DNS resolver using domains authoritative NS records
|
|
66
|
+
*
|
|
67
|
+
* @param {string} recordName DNS record name
|
|
68
|
+
* @returns {Promise<dns.Resolver>} DNS resolver
|
|
69
|
+
*/
|
|
70
|
+
declare function getAuthoritativeDnsResolver(recordName: any, logger?: (...msg: any[]) => void): Promise<dnsSdk.promises.Resolver>;
|
|
71
|
+
/**
|
|
72
|
+
* Attempt to retrieve TLS ALPN certificate from peer
|
|
73
|
+
*
|
|
74
|
+
* https://nodejs.org/api/tls.html#tlsconnectoptions-callback
|
|
75
|
+
*
|
|
76
|
+
* @param {string} host Host the TLS client should connect to
|
|
77
|
+
* @param {number} port Port the client should connect to
|
|
78
|
+
* @param {string} servername Server name for the SNI (Server Name Indication)
|
|
79
|
+
* @returns {Promise<string>} PEM encoded certificate
|
|
80
|
+
*/
|
|
81
|
+
declare function retrieveTlsAlpnCertificate(host: any, port: any, timeout?: number): Promise<unknown>;
|
|
82
|
+
/**
|
|
83
|
+
* Export utils
|
|
84
|
+
*/
|
|
85
|
+
export { retry, parseLinkHeader, parseRetryAfterHeader, findCertificateChainForIssuer, formatResponseError, getAuthoritativeDnsResolver, retrieveTlsAlpnCertificate, resolveDomainBySoaRecord };
|
package/{src → dist}/util.js
RENAMED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
/**
|
|
2
3
|
* Utility methods
|
|
3
4
|
*/
|
|
4
|
-
|
|
5
5
|
import tls from 'tls';
|
|
6
6
|
import dnsSdk from 'dns';
|
|
7
|
-
import { readCertificateInfo, splitPemChain }from './crypto/index.js'
|
|
8
|
-
import { log } from './logger.js'
|
|
9
|
-
|
|
7
|
+
import { readCertificateInfo, splitPemChain } from './crypto/index.js';
|
|
8
|
+
import { log } from './logger.js';
|
|
10
9
|
const dns = dnsSdk.promises;
|
|
11
10
|
/**
|
|
12
11
|
* Exponential backoff
|
|
@@ -18,27 +17,23 @@ const dns = dnsSdk.promises;
|
|
|
18
17
|
* @param {number} [opts.min] Minimum backoff duration in ms
|
|
19
18
|
* @param {number} [opts.max] Maximum backoff duration in ms
|
|
20
19
|
*/
|
|
21
|
-
|
|
22
20
|
class Backoff {
|
|
23
21
|
constructor({ min = 100, max = 10000 } = {}) {
|
|
24
22
|
this.min = min;
|
|
25
23
|
this.max = max;
|
|
26
24
|
this.attempts = 0;
|
|
27
25
|
}
|
|
28
|
-
|
|
29
26
|
/**
|
|
30
27
|
* Get backoff duration
|
|
31
28
|
*
|
|
32
29
|
* @returns {number} Backoff duration in ms
|
|
33
30
|
*/
|
|
34
|
-
|
|
35
31
|
duration() {
|
|
36
32
|
const ms = this.min * (2 ** this.attempts);
|
|
37
33
|
this.attempts += 1;
|
|
38
34
|
return Math.min(ms, this.max);
|
|
39
35
|
}
|
|
40
36
|
}
|
|
41
|
-
|
|
42
37
|
/**
|
|
43
38
|
* Retry promise
|
|
44
39
|
*
|
|
@@ -47,37 +42,32 @@ class Backoff {
|
|
|
47
42
|
* @param {Backoff} backoff Backoff instance
|
|
48
43
|
* @returns {Promise}
|
|
49
44
|
*/
|
|
50
|
-
|
|
51
45
|
async function retryPromise(fn, attempts, backoff, logger = log) {
|
|
52
46
|
let aborted = false;
|
|
53
47
|
let abortedFromUser = false;
|
|
54
|
-
|
|
55
48
|
try {
|
|
56
|
-
const setAbort = (fromUser = false) => { aborted = true; abortedFromUser = fromUser; }
|
|
49
|
+
const setAbort = (fromUser = false) => { aborted = true; abortedFromUser = fromUser; };
|
|
57
50
|
const data = await fn(setAbort);
|
|
58
51
|
return data;
|
|
59
52
|
}
|
|
60
53
|
catch (e) {
|
|
61
|
-
if (aborted){
|
|
62
|
-
if (abortedFromUser){
|
|
54
|
+
if (aborted) {
|
|
55
|
+
if (abortedFromUser) {
|
|
63
56
|
logger(`用户取消重试`);
|
|
64
57
|
}
|
|
65
58
|
throw e;
|
|
66
59
|
}
|
|
67
|
-
if (
|
|
60
|
+
if (((backoff.attempts + 1) >= attempts)) {
|
|
68
61
|
logger(`重试次数超过${attempts}次`);
|
|
69
62
|
throw e;
|
|
70
63
|
}
|
|
71
|
-
|
|
72
64
|
logger(`Promise rejected: ${e.message}`);
|
|
73
65
|
const duration = backoff.duration();
|
|
74
66
|
logger(`Promise rejected attempt #${backoff.attempts}, ${duration}ms 后重试: ${e.message}`);
|
|
75
|
-
|
|
76
67
|
await new Promise((resolve) => { setTimeout(resolve, duration); });
|
|
77
68
|
return retryPromise(fn, attempts, backoff, logger);
|
|
78
69
|
}
|
|
79
70
|
}
|
|
80
|
-
|
|
81
71
|
/**
|
|
82
72
|
* Retry promise
|
|
83
73
|
*
|
|
@@ -88,12 +78,10 @@ async function retryPromise(fn, attempts, backoff, logger = log) {
|
|
|
88
78
|
* @param {number} [backoffOpts.max] Maximum attempt delay in milliseconds, default: `30000`
|
|
89
79
|
* @returns {Promise}
|
|
90
80
|
*/
|
|
91
|
-
|
|
92
81
|
function retry(fn, { attempts = 5, min = 5000, max = 30000 } = {}, logger = log) {
|
|
93
82
|
const backoff = new Backoff({ min, max });
|
|
94
83
|
return retryPromise(fn, attempts, backoff, logger);
|
|
95
84
|
}
|
|
96
|
-
|
|
97
85
|
/**
|
|
98
86
|
* Parse URLs from Link header
|
|
99
87
|
*
|
|
@@ -104,18 +92,14 @@ function retry(fn, { attempts = 5, min = 5000, max = 30000 } = {}, logger = log)
|
|
|
104
92
|
* @param {string} rel Link relation, default: `alternate`
|
|
105
93
|
* @returns {string[]} Array of URLs
|
|
106
94
|
*/
|
|
107
|
-
|
|
108
95
|
function parseLinkHeader(header, rel = 'alternate') {
|
|
109
96
|
const relRe = new RegExp(`\\s*rel\\s*=\\s*"?${rel}"?`, 'i');
|
|
110
|
-
|
|
111
97
|
const results = (header || '').split(/,\s*</).map((link) => {
|
|
112
98
|
const [, linkUrl, linkParts] = link.match(/<?([^>]*)>;(.*)/) || [];
|
|
113
99
|
return (linkUrl && linkParts && linkParts.match(relRe)) ? linkUrl : null;
|
|
114
100
|
});
|
|
115
|
-
|
|
116
101
|
return results.filter((r) => r);
|
|
117
102
|
}
|
|
118
|
-
|
|
119
103
|
/**
|
|
120
104
|
* Parse date or duration from Retry-After header
|
|
121
105
|
*
|
|
@@ -124,29 +108,23 @@ function parseLinkHeader(header, rel = 'alternate') {
|
|
|
124
108
|
* @param {string} header Header contents
|
|
125
109
|
* @returns {number} Retry duration in seconds
|
|
126
110
|
*/
|
|
127
|
-
|
|
128
111
|
function parseRetryAfterHeader(header) {
|
|
129
112
|
const sec = parseInt(header, 10);
|
|
130
113
|
const date = new Date(header);
|
|
131
|
-
|
|
132
114
|
/* Seconds into the future */
|
|
133
115
|
if (Number.isSafeInteger(sec) && (sec > 0)) {
|
|
134
116
|
return sec;
|
|
135
117
|
}
|
|
136
|
-
|
|
137
118
|
/* Future date string */
|
|
138
119
|
if (date instanceof Date && !Number.isNaN(date)) {
|
|
139
120
|
const now = new Date();
|
|
140
121
|
const diff = Math.ceil((date.getTime() - now.getTime()) / 1000);
|
|
141
|
-
|
|
142
122
|
if (diff > 0) {
|
|
143
123
|
return diff;
|
|
144
124
|
}
|
|
145
125
|
}
|
|
146
|
-
|
|
147
126
|
return 0;
|
|
148
127
|
}
|
|
149
|
-
|
|
150
128
|
/**
|
|
151
129
|
* Find certificate chain with preferred issuer common name
|
|
152
130
|
* - If issuer is found in multiple chains, the closest to root wins
|
|
@@ -156,23 +134,19 @@ function parseRetryAfterHeader(header) {
|
|
|
156
134
|
* @param {string} issuer Preferred certificate issuer
|
|
157
135
|
* @returns {string} PEM encoded certificate chain
|
|
158
136
|
*/
|
|
159
|
-
|
|
160
137
|
function findCertificateChainForIssuer(chains, issuer) {
|
|
161
138
|
log(`Attempting to find match for issuer="${issuer}" in ${chains.length} certificate chains`);
|
|
162
139
|
let bestMatch = null;
|
|
163
140
|
let bestDistance = null;
|
|
164
|
-
|
|
165
141
|
chains.forEach((chain) => {
|
|
166
142
|
/* Look up all issuers */
|
|
167
143
|
const certs = splitPemChain(chain);
|
|
168
144
|
const infoCollection = certs.map((c) => readCertificateInfo(c));
|
|
169
145
|
const issuerCollection = infoCollection.map((i) => i.issuer.commonName);
|
|
170
|
-
|
|
171
146
|
/* Found issuer match, get distance from root - lower is better */
|
|
172
147
|
if (issuerCollection.includes(issuer)) {
|
|
173
148
|
const distance = (issuerCollection.length - issuerCollection.indexOf(issuer));
|
|
174
149
|
log(`Found matching chain for preferred issuer="${issuer}" distance=${distance} issuers=${JSON.stringify(issuerCollection)}`);
|
|
175
|
-
|
|
176
150
|
/* Chain wins, use it */
|
|
177
151
|
if (!bestDistance || (distance < bestDistance)) {
|
|
178
152
|
log(`Issuer is closer to root than previous match, using it (${distance} < ${bestDistance || 'undefined'})`);
|
|
@@ -185,27 +159,22 @@ function findCertificateChainForIssuer(chains, issuer) {
|
|
|
185
159
|
log(`Unable to match certificate for preferred issuer="${issuer}", issuers=${JSON.stringify(issuerCollection)}`);
|
|
186
160
|
}
|
|
187
161
|
});
|
|
188
|
-
|
|
189
162
|
/* Return found match */
|
|
190
163
|
if (bestMatch) {
|
|
191
164
|
return bestMatch;
|
|
192
165
|
}
|
|
193
|
-
|
|
194
166
|
/* No chains matched, return default */
|
|
195
167
|
log(`Found no match in ${chains.length} certificate chains for preferred issuer="${issuer}", returning default certificate chain`);
|
|
196
168
|
return chains[0];
|
|
197
169
|
}
|
|
198
|
-
|
|
199
170
|
/**
|
|
200
171
|
* Find and format error in response object
|
|
201
172
|
*
|
|
202
173
|
* @param {object} resp HTTP response
|
|
203
174
|
* @returns {string} Error message
|
|
204
175
|
*/
|
|
205
|
-
|
|
206
176
|
function formatResponseError(resp) {
|
|
207
177
|
let result;
|
|
208
|
-
|
|
209
178
|
if (resp.data) {
|
|
210
179
|
if (resp.data.error) {
|
|
211
180
|
result = resp.data.error.detail || resp.data.error;
|
|
@@ -214,17 +183,14 @@ function formatResponseError(resp) {
|
|
|
214
183
|
result = resp.data.detail || JSON.stringify(resp.data);
|
|
215
184
|
}
|
|
216
185
|
}
|
|
217
|
-
|
|
218
186
|
return (result || '').replace(/\n/g, '');
|
|
219
187
|
}
|
|
220
|
-
|
|
221
188
|
/**
|
|
222
189
|
* Resolve root domain name by looking for SOA record
|
|
223
190
|
*
|
|
224
191
|
* @param {string} recordName DNS record name
|
|
225
192
|
* @returns {Promise<string>} Root domain name
|
|
226
193
|
*/
|
|
227
|
-
|
|
228
194
|
async function resolveDomainBySoaRecord(recordName, logger = log) {
|
|
229
195
|
try {
|
|
230
196
|
await dns.resolveSoa(recordName);
|
|
@@ -234,41 +200,33 @@ async function resolveDomainBySoaRecord(recordName, logger = log) {
|
|
|
234
200
|
catch (e) {
|
|
235
201
|
logger(`找不到${recordName}的SOA记录,继续往主域名查找`);
|
|
236
202
|
const parentRecordName = recordName.split('.').slice(1).join('.');
|
|
237
|
-
|
|
238
203
|
if (!parentRecordName.includes('.')) {
|
|
239
204
|
throw new Error('SOA record查找失败');
|
|
240
205
|
}
|
|
241
|
-
|
|
242
|
-
return resolveDomainBySoaRecord(parentRecordName,logger);
|
|
206
|
+
return resolveDomainBySoaRecord(parentRecordName, logger);
|
|
243
207
|
}
|
|
244
208
|
}
|
|
245
|
-
|
|
246
209
|
/**
|
|
247
210
|
* Get DNS resolver using domains authoritative NS records
|
|
248
211
|
*
|
|
249
212
|
* @param {string} recordName DNS record name
|
|
250
213
|
* @returns {Promise<dns.Resolver>} DNS resolver
|
|
251
214
|
*/
|
|
252
|
-
|
|
253
215
|
async function getAuthoritativeDnsResolver(recordName, logger = log) {
|
|
254
216
|
logger(`获取域名${recordName}的权威NS服务器: `);
|
|
255
|
-
const resolver = new dns.Resolver({timeout: 2000,tries: 2});
|
|
256
|
-
|
|
217
|
+
const resolver = new dns.Resolver({ timeout: 2000, tries: 2 });
|
|
257
218
|
try {
|
|
258
219
|
/* Resolve root domain by SOA */
|
|
259
|
-
const domain = await resolveDomainBySoaRecord(recordName,logger);
|
|
260
|
-
|
|
220
|
+
const domain = await resolveDomainBySoaRecord(recordName, logger);
|
|
261
221
|
/* Resolve authoritative NS addresses */
|
|
262
222
|
logger(`获取到权威NS服务器name: ${domain}`);
|
|
263
223
|
const nsRecords = await dns.resolveNs(domain);
|
|
264
224
|
logger(`域名权威NS服务器:${nsRecords}`);
|
|
265
225
|
const nsAddrArray = await Promise.all(nsRecords.map(async (r) => dns.resolve4(r)));
|
|
266
226
|
const nsAddresses = [].concat(...nsAddrArray).filter((a) => a);
|
|
267
|
-
|
|
268
227
|
if (!nsAddresses.length) {
|
|
269
228
|
throw new Error(`Unable to locate any valid authoritative NS addresses for domain(获取权威服务器IP失败): ${domain}`);
|
|
270
229
|
}
|
|
271
|
-
|
|
272
230
|
/* Authoritative NS success */
|
|
273
231
|
logger(`Found ${nsAddresses.length} authoritative NS addresses for domain: ${domain}`);
|
|
274
232
|
resolver.setServers(nsAddresses);
|
|
@@ -276,14 +234,11 @@ async function getAuthoritativeDnsResolver(recordName, logger = log) {
|
|
|
276
234
|
catch (e) {
|
|
277
235
|
logger(`Authoritative NS lookup error(获取权威NS服务器地址失败): ${e.message}`);
|
|
278
236
|
}
|
|
279
|
-
|
|
280
237
|
/* Return resolver */
|
|
281
238
|
const addresses = resolver.getServers();
|
|
282
239
|
logger(`DNS resolver addresses(域名的权威NS服务器地址): ${addresses.join(', ')}`);
|
|
283
|
-
|
|
284
240
|
return resolver;
|
|
285
241
|
}
|
|
286
|
-
|
|
287
242
|
/**
|
|
288
243
|
* Attempt to retrieve TLS ALPN certificate from peer
|
|
289
244
|
*
|
|
@@ -294,11 +249,9 @@ async function getAuthoritativeDnsResolver(recordName, logger = log) {
|
|
|
294
249
|
* @param {string} servername Server name for the SNI (Server Name Indication)
|
|
295
250
|
* @returns {Promise<string>} PEM encoded certificate
|
|
296
251
|
*/
|
|
297
|
-
|
|
298
252
|
async function retrieveTlsAlpnCertificate(host, port, timeout = 30000) {
|
|
299
253
|
return new Promise((resolve, reject) => {
|
|
300
254
|
let result;
|
|
301
|
-
|
|
302
255
|
/* TLS connection */
|
|
303
256
|
const socket = tls.connect({
|
|
304
257
|
host,
|
|
@@ -307,50 +260,30 @@ async function retrieveTlsAlpnCertificate(host, port, timeout = 30000) {
|
|
|
307
260
|
rejectUnauthorized: false,
|
|
308
261
|
ALPNProtocols: ['acme-tls/1'],
|
|
309
262
|
});
|
|
310
|
-
|
|
311
263
|
socket.setTimeout(timeout);
|
|
312
264
|
socket.setEncoding('utf-8');
|
|
313
|
-
|
|
314
265
|
/* Grab certificate once connected and close */
|
|
315
266
|
socket.on('secureConnect', () => {
|
|
316
267
|
result = socket.getPeerX509Certificate();
|
|
317
268
|
socket.end();
|
|
318
269
|
});
|
|
319
|
-
|
|
320
270
|
/* Errors */
|
|
321
271
|
socket.on('error', (err) => {
|
|
322
272
|
reject(err);
|
|
323
273
|
});
|
|
324
|
-
|
|
325
274
|
socket.on('timeout', () => {
|
|
326
275
|
socket.destroy(new Error('TLS ALPN certificate lookup request timed out'));
|
|
327
276
|
});
|
|
328
|
-
|
|
329
277
|
/* Done, return cert as PEM if found */
|
|
330
278
|
socket.on('end', () => {
|
|
331
279
|
if (result) {
|
|
332
280
|
return resolve(result.toString());
|
|
333
281
|
}
|
|
334
|
-
|
|
335
282
|
return reject(new Error('TLS ALPN lookup failed to retrieve certificate'));
|
|
336
283
|
});
|
|
337
284
|
});
|
|
338
285
|
}
|
|
339
|
-
|
|
340
286
|
/**
|
|
341
287
|
* Export utils
|
|
342
288
|
*/
|
|
343
|
-
|
|
344
|
-
export {
|
|
345
|
-
retry,
|
|
346
|
-
parseLinkHeader,
|
|
347
|
-
parseRetryAfterHeader,
|
|
348
|
-
findCertificateChainForIssuer,
|
|
349
|
-
formatResponseError,
|
|
350
|
-
getAuthoritativeDnsResolver,
|
|
351
|
-
retrieveTlsAlpnCertificate,
|
|
352
|
-
resolveDomainBySoaRecord
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
289
|
+
export { retry, parseLinkHeader, parseRetryAfterHeader, findCertificateChainForIssuer, formatResponseError, getAuthoritativeDnsResolver, retrieveTlsAlpnCertificate, resolveDomainBySoaRecord };
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACME challenge verification
|
|
3
|
+
*/
|
|
4
|
+
import dnsSdk from "dns";
|
|
5
|
+
export declare function setWalkFromAuthoritative(value?: boolean): void;
|
|
6
|
+
export declare function createChallengeFn(opts?: {}): {
|
|
7
|
+
challenges: {
|
|
8
|
+
'http-01': (authz: any, challenge: any, keyAuthorization: any, suffix?: string) => Promise<boolean>;
|
|
9
|
+
'dns-01': (authz: any, challenge: any, keyAuthorization: any, prefix?: string) => Promise<boolean>;
|
|
10
|
+
'tls-alpn-01': (authz: any, challenge: any, keyAuthorization: any) => Promise<boolean>;
|
|
11
|
+
};
|
|
12
|
+
walkTxtRecord: (recordName: any, deep?: number) => Promise<any[]>;
|
|
13
|
+
walkDnsChallengeRecord: (recordName: any, resolver?: typeof dnsSdk.promises, deep?: number) => Promise<any[]>;
|
|
14
|
+
};
|