@certd/acme-client 0.2.0 → 0.3.1

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/src/verify.js CHANGED
@@ -2,12 +2,10 @@
2
2
  * ACME challenge verification
3
3
  */
4
4
 
5
- const Promise = require('bluebird');
6
- const dns = Promise.promisifyAll(require('dns'));
7
- const logger = require('./util.log.js');
8
-
9
- const debug = logger.info;
5
+ const dns = require('dns').promises;
6
+ const { log } = require('./logger');
10
7
  const axios = require('./axios');
8
+ const util = require('./util');
11
9
 
12
10
 
13
11
  /**
@@ -26,21 +24,59 @@ async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix =
26
24
  const httpPort = axios.defaults.acmeSettings.httpChallengePort || 80;
27
25
  const challengeUrl = `http://${authz.identifier.value}:${httpPort}${suffix}`;
28
26
 
29
- logger.info(`Sending HTTP query to ${authz.identifier.value}, suffix: ${suffix}, port: ${httpPort}`);
27
+ log(`Sending HTTP query to ${authz.identifier.value}, suffix: ${suffix}, port: ${httpPort}`);
30
28
  const resp = await axios.get(challengeUrl);
31
29
  const data = (resp.data || '').replace(/\s+$/, '');
32
30
 
33
- logger.info(`Query successful, HTTP status code: ${resp.status}`);
31
+ log(`Query successful, HTTP status code: ${resp.status}`);
34
32
 
35
33
  if (!data || (data !== keyAuthorization)) {
36
34
  throw new Error(`Authorization not found in HTTP response from ${authz.identifier.value}`);
37
35
  }
38
36
 
39
- logger.info(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
37
+ log(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
40
38
  return true;
41
39
  }
42
40
 
43
41
 
42
+ /**
43
+ * Walk DNS until TXT records are found
44
+ */
45
+
46
+ async function walkDnsChallengeRecord(recordName, resolver = dns) {
47
+ /* Resolve CNAME record first */
48
+ try {
49
+ log(`Checking name for CNAME records: ${recordName}`);
50
+ const cnameRecords = await resolver.resolveCname(recordName);
51
+
52
+ if (cnameRecords.length) {
53
+ log(`CNAME record found at ${recordName}, new challenge record name: ${cnameRecords[0]}`);
54
+ return walkDnsChallengeRecord(cnameRecords[0]);
55
+ }
56
+ }
57
+ catch (e) {
58
+ log(`No CNAME records found for name: ${recordName}`);
59
+ }
60
+
61
+ /* Resolve TXT records */
62
+ try {
63
+ log(`Checking name for TXT records: ${recordName}`);
64
+ const txtRecords = await resolver.resolveTxt(recordName);
65
+
66
+ if (txtRecords.length) {
67
+ log(`Found ${txtRecords.length} TXT records at ${recordName}`);
68
+ return [].concat(...txtRecords);
69
+ }
70
+ }
71
+ catch (e) {
72
+ log(`No TXT records found for name: ${recordName}`);
73
+ }
74
+
75
+ /* Found nothing */
76
+ throw new Error(`No TXT records found for name: ${recordName}`);
77
+ }
78
+
79
+
44
80
  /**
45
81
  * Verify ACME DNS challenge
46
82
  *
@@ -54,34 +90,29 @@ async function verifyHttpChallenge(authz, challenge, keyAuthorization, suffix =
54
90
  */
55
91
 
56
92
  async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '_acme-challenge.') {
57
- logger.info(`Resolving DNS TXT records for ${authz.identifier.value}, prefix: ${prefix}`);
58
- let challengeRecord = `${prefix}${authz.identifier.value}`;
93
+ let recordValues = [];
94
+ const recordName = `${prefix}${authz.identifier.value}`;
95
+ log(`Resolving DNS TXT from record: ${recordName}`);
59
96
 
60
97
  try {
61
- /* Attempt CNAME record first */
62
- logger.info(`Checking CNAME for record ${challengeRecord}`);
63
- const cnameRecords = await dns.resolveCnameAsync(challengeRecord);
64
-
65
- if (cnameRecords.length) {
66
- logger.info(`CNAME found at ${challengeRecord}, new challenge record: ${cnameRecords[0]}`);
67
- challengeRecord = cnameRecords[0];
68
- }
98
+ /* Default DNS resolver first */
99
+ log('Attempting to resolve TXT with default DNS resolver first');
100
+ recordValues = await walkDnsChallengeRecord(recordName);
69
101
  }
70
102
  catch (e) {
71
- logger.info(`No CNAME found for record ${challengeRecord}`);
103
+ /* Authoritative DNS resolver */
104
+ log(`Error using default resolver, attempting to resolve TXT with authoritative NS: ${e.message}`);
105
+ const authoritativeResolver = await util.getAuthoritativeDnsResolver(recordName);
106
+ recordValues = await walkDnsChallengeRecord(recordName, authoritativeResolver);
72
107
  }
73
108
 
74
- /* Read TXT record */
75
- const result = await dns.resolveTxtAsync(challengeRecord);
76
- const records = [].concat(...result);
77
-
78
- logger.info(`Query successful, found ${records.length} DNS TXT records`);
109
+ log(`DNS query finished successfully, found ${recordValues.length} TXT records`);
79
110
 
80
- if (records.indexOf(keyAuthorization) === -1) {
81
- throw new Error(`Authorization not found in DNS TXT records for ${authz.identifier.value}`);
111
+ if (!recordValues.length || !recordValues.includes(keyAuthorization)) {
112
+ throw new Error(`Authorization not found in DNS TXT record: ${recordName}`);
82
113
  }
83
114
 
84
- logger.info(`Key authorization match for ${challenge.type}/${authz.identifier.value}, ACME challenge verified`);
115
+ log(`Key authorization match for ${challenge.type}/${recordName}, ACME challenge verified`);
85
116
  return true;
86
117
  }
87
118
 
package/types/index.d.ts CHANGED
@@ -37,15 +37,21 @@ export interface ClientOptions {
37
37
  directoryUrl: string;
38
38
  accountKey: PrivateKeyBuffer | PrivateKeyString;
39
39
  accountUrl?: string;
40
+ externalAccountBinding?: ClientExternalAccountBindingOptions;
40
41
  backoffAttempts?: number;
41
42
  backoffMin?: number;
42
43
  backoffMax?: number;
43
44
  }
44
45
 
46
+ export interface ClientExternalAccountBindingOptions {
47
+ kid: string;
48
+ hmacKey: string;
49
+ }
50
+
45
51
  export interface ClientAutoOptions {
46
52
  csr: CsrBuffer | CsrString;
47
53
  challengeCreateFn: (authz: Authorization, challenge: rfc8555.Challenge, keyAuthorization: string) => Promise<any>;
48
- challengeRemoveFn: (authz: Authorization, challenge: rfc8555.Challenge, keyAuthorization: string) => Promise<any>;
54
+ challengeRemoveFn: (authz: Authorization, challenge: rfc8555.Challenge, keyAuthorization: string, recordRes:any) => Promise<any>;
49
55
  email?: string;
50
56
  termsOfServiceAgreed?: boolean;
51
57
  skipChallengeVerification?: boolean;
@@ -69,7 +75,7 @@ export class Client {
69
75
  verifyChallenge(authz: Authorization, challenge: rfc8555.Challenge): Promise<boolean>;
70
76
  completeChallenge(challenge: rfc8555.Challenge): Promise<rfc8555.Challenge>;
71
77
  waitForValidStatus<T = Order | Authorization | rfc8555.Challenge>(item: T): Promise<T>;
72
- getCertificate(order: Order, preferredChain?: string | null): Promise<string>;
78
+ getCertificate(order: Order, preferredChain?: string): Promise<string>;
73
79
  revokeCertificate(cert: CertificateBuffer | CertificateString, data?: rfc8555.CertificateRevocationRequest): Promise<void>;
74
80
  auto(opts: ClientAutoOptions): Promise<string>;
75
81
  }
@@ -80,9 +86,16 @@ export class Client {
80
86
  */
81
87
 
82
88
  export const directory: {
89
+ buypass: {
90
+ staging: string,
91
+ production: string
92
+ },
83
93
  letsencrypt: {
84
94
  staging: string,
85
95
  production: string
96
+ },
97
+ zerossl: {
98
+ production: string
86
99
  }
87
100
  };
88
101
 
@@ -119,7 +132,36 @@ export interface CsrOptions {
119
132
  emailAddress?: string;
120
133
  }
121
134
 
135
+ export interface RsaPublicJwk {
136
+ e: string;
137
+ kty: string;
138
+ n: string;
139
+ }
140
+
141
+ export interface EcdsaPublicJwk {
142
+ crv: string;
143
+ kty: string;
144
+ x: string;
145
+ y: string;
146
+ }
147
+
122
148
  export interface CryptoInterface {
149
+ createPrivateKey(keySize?: number): Promise<PrivateKeyBuffer>;
150
+ createPrivateRsaKey(keySize?: number): Promise<PrivateKeyBuffer>;
151
+ createPrivateEcdsaKey(namedCurve?: 'P-256' | 'P-384' | 'P-521'): Promise<PrivateKeyBuffer>;
152
+ getPublicKey(keyPem: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString): PublicKeyBuffer;
153
+ getJwk(keyPem: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString): RsaPublicJwk | EcdsaPublicJwk;
154
+ splitPemChain(chainPem: CertificateBuffer | CertificateString): string[];
155
+ getPemBodyAsB64u(pem: CertificateBuffer | CertificateString): string;
156
+ readCsrDomains(csrPem: CsrBuffer | CsrString): CertificateDomains;
157
+ readCertificateInfo(certPem: CertificateBuffer | CertificateString): CertificateInfo;
158
+ createCsr(data: CsrOptions, keyPem?: PrivateKeyBuffer | PrivateKeyString): Promise<[PrivateKeyBuffer, CsrBuffer]>;
159
+ }
160
+
161
+ export const crypto: CryptoInterface;
162
+
163
+ /* TODO: LEGACY */
164
+ export interface CryptoLegacyInterface {
123
165
  createPrivateKey(size?: number): Promise<PrivateKeyBuffer>;
124
166
  createPublicKey(key: PrivateKeyBuffer | PrivateKeyString): Promise<PublicKeyBuffer>;
125
167
  getPemBody(str: string): string;
@@ -131,7 +173,7 @@ export interface CryptoInterface {
131
173
  createCsr(data: CsrOptions, key?: PrivateKeyBuffer | PrivateKeyString): Promise<[PrivateKeyBuffer, CsrBuffer]>;
132
174
  }
133
175
 
134
- export const forge: CryptoInterface;
176
+ export const forge: CryptoLegacyInterface;
135
177
 
136
178
 
137
179
  /**
@@ -139,3 +181,10 @@ export const forge: CryptoInterface;
139
181
  */
140
182
 
141
183
  export const axios: AxiosInstance;
184
+
185
+
186
+ /**
187
+ * Logger
188
+ */
189
+
190
+ export function setLogger(fn: (msg: string) => void): void;
package/types/test.ts CHANGED
@@ -7,7 +7,7 @@ import * as acme from 'acme-client';
7
7
 
8
8
  (async () => {
9
9
  /* Client */
10
- const accountKey = await acme.forge.createPrivateKey();
10
+ const accountKey = await acme.crypto.createPrivateKey();
11
11
 
12
12
  const client = new acme.Client({
13
13
  accountKey,
@@ -41,7 +41,7 @@ import * as acme from 'acme-client';
41
41
  await client.waitForValidStatus(challenge);
42
42
 
43
43
  /* Finalize */
44
- const [certKey, certCsr] = await acme.forge.createCsr({
44
+ const [certKey, certCsr] = await acme.crypto.createCsr({
45
45
  commonName: 'example.com',
46
46
  altNames: ['example.com', '*.example.com']
47
47
  });
package/CHANGELOG.md DELETED
@@ -1,152 +0,0 @@
1
- # Changelog
2
-
3
- ## v4.1.2 (2020-11-16)
4
-
5
- * `fixed` Bug when encoding PEM payloads, potentially causing malformed requests
6
-
7
-
8
- ## v4.1.1 (2020-11-13)
9
-
10
- * `fixed` Missing TypeScript definitions
11
-
12
-
13
- ## v4.1.0 (2020-11-12)
14
-
15
- * `added` Option `preferredChain` added to `client.getCertificate()` and `client.auto()` to indicate which certificate chain is preferred if a CA offers multiple
16
- * Related: [https://community.letsencrypt.org/t/transition-to-isrgs-root-delayed-until-jan-11-2021/125516](https://community.letsencrypt.org/t/transition-to-isrgs-root-delayed-until-jan-11-2021/125516)
17
- * `added` Method `client.getOrder()` to refresh order from CA
18
- * `fixed` Upgrade `axios@0.21.0`
19
- * `fixed` Error when attempting to revoke a certificate chain
20
- * `fixed` Missing URL augmentation in `client.finalizeOrder()` and `client.deactivateAuthorization()`
21
- * `fixed` Add certificate issuer to response from `forge.readCertificateInfo()`
22
-
23
-
24
- ## v4.0.2 (2020-10-09)
25
-
26
- * `fixed` Explicitly set default `axios` HTTP adapter - [axios/axios#1180](https://github.com/axios/axios/issues/1180)
27
-
28
-
29
- ## v4.0.1 (2020-09-15)
30
-
31
- * `fixed` Upgrade `node-forge@0.10.0` - [CVE-2020-7720](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-7720)
32
-
33
-
34
- ## v4.0.0 (2020-05-29)
35
-
36
- * `fixed` Incorrect TypeScript `CertificateInfo` definitions
37
- * `fixed` Allow trailing whitespace character in `http-01` challenge response
38
- * `breaking` Remove support for Node v8
39
- * `breaking` Remove deprecated `openssl` crypto module
40
-
41
-
42
- ## v3.3.1 (2020-01-07)
43
-
44
- * `fixed` Improvements to TypeScript definitions
45
-
46
-
47
- ## v3.3.0 (2019-12-19)
48
-
49
- * `added` TypeScript definitions
50
- * `fixed` Allow missing ACME directory meta field - [RFC 8555 Section 7.1.1](https://tools.ietf.org/html/rfc8555#section-7.1.1)
51
-
52
-
53
- ## v3.2.1 (2019-11-14)
54
-
55
- * `added` New option `skipChallengeVerification` added to `client.auto()` to bypass internal challenge verification
56
-
57
-
58
- ## v3.2.0 (2019-08-26)
59
-
60
- * `added` More extensive testing using [letsencrypt/pebble](https://github.com/letsencrypt/pebble)
61
- * `changed` When creating a CSR, `commonName` no longer defaults to `'localhost'`
62
- * This change is not considered breaking since `commonName: 'localhost'` will result in an error when ordering a certificate
63
- * `fixed` Retry signed API requests on `urn:ietf:params:acme:error:badNonce` - [RFC 8555 Section 6.5](https://tools.ietf.org/html/rfc8555#section-6.5)
64
- * `fixed` Minor bugs related to `POST-as-GET` when calling `updateAccount()`
65
- * `fixed` Ensure subject common name is present in SAN when creating a CSR - [CAB v1.2.3 Section 9.2.2](https://cabforum.org/wp-content/uploads/BRv1.2.3.pdf)
66
- * `fixed` Send empty JSON body when responding to challenges - [RFC 8555 Section 7.5.1](https://tools.ietf.org/html/rfc8555#section-7.5.1)
67
-
68
-
69
- ## v2.3.1 (2019-08-26)
70
-
71
- * `backport` Minor bugs related to `POST-as-GET` when calling `client.updateAccount()`
72
- * `backport` Send empty JSON body when responding to challenges
73
-
74
-
75
- ## v3.1.0 (2019-08-21)
76
-
77
- * `added` UTF-8 support when generating a CSR subject using forge - [RFC 5280](https://tools.ietf.org/html/rfc5280)
78
- * `fixed` Implemented `POST-as-GET` for all ACME API requests - [RFC 8555 Section 6.3](https://tools.ietf.org/html/rfc8555#section-6.3)
79
-
80
-
81
- ## v2.3.0 (2019-08-21)
82
-
83
- * `backport` Implemented `POST-as-GET` for all ACME API requests
84
-
85
-
86
- ## v3.0.0 (2019-07-13)
87
-
88
- * `added` Expose `axios` instance to allow manipulating HTTP client defaults
89
- * `breaking` Remove support for Node v4 and v6
90
- * `breaking` Remove Babel transpilation
91
-
92
-
93
- ## v2.2.3 (2019-01-25)
94
-
95
- * `added` DNS CNAME detection when verifying `dns-01` challenges
96
-
97
-
98
- ## v2.2.2 (2019-01-07)
99
-
100
- * `added` Support for `tls-alpn-01` challenge key authorization
101
-
102
-
103
- ## v2.2.1 (2019-01-04)
104
-
105
- * `fixed` Handle and throw errors from OpenSSL process
106
-
107
-
108
- ## v2.2.0 (2018-11-06)
109
-
110
- * `added` New [node-forge](https://www.npmjs.com/package/node-forge) crypto engine, removes OpenSSL CLI dependency
111
- * `added` Support native `crypto.generateKeyPair()` API when generating key pairs
112
-
113
-
114
- ## v2.1.0 (2018-10-21)
115
-
116
- * `added` Ability to set and get current account URL
117
- * `fixed` Replace HTTP client `request` with `axios`
118
- * `fixed` Auto-mode no longer tries to create account when account URL exists
119
-
120
-
121
- ## v2.0.1 (2018-08-17)
122
-
123
- * `fixed` Key rollover in compliance with [draft-ietf-acme-13](https://tools.ietf.org/html/draft-ietf-acme-acme-13)
124
-
125
-
126
- ## v2.0.0 (2018-04-02)
127
-
128
- * `breaking` ACMEv2
129
- * `breaking` API changes
130
- * `breaking` Rewrite to ES6
131
- * `breaking` Promises instead of callbacks
132
-
133
-
134
- ## v1.0.0 (2017-10-20)
135
-
136
- * API stable
137
-
138
-
139
- ## v0.2.1 (2017-09-27)
140
-
141
- * `fixed` Bug causing invalid anti-replay nonce
142
-
143
-
144
- ## v0.2.0 (2017-09-21)
145
-
146
- * `breaking` OpenSSL method `readCsrDomains` and `readCertificateInfo` now return domains as an object
147
- * `fixed` Added and fixed some tests
148
-
149
-
150
- ## v0.1.0 (2017-09-14)
151
-
152
- * `acme-client` released
package/src/util.log.js DELETED
@@ -1,8 +0,0 @@
1
- const log4js = require('log4js');
2
-
3
- log4js.configure({
4
- appenders: { std: { type: 'stdout' } },
5
- categories: { default: { appenders: ['std'], level: 'info' } }
6
- });
7
- const logger = log4js.getLogger('certd');
8
- module.exports = logger;