mirror of
https://github.com/certd/certd.git
synced 2026-04-14 12:30:54 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5edfbfa6d | ||
|
|
86e64af35c | ||
|
|
162e10909b | ||
|
|
0f1ae6ccd9 | ||
|
|
c9d5cda953 |
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,14 @@ 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
|
||||||
|
|
||||||
# Config
|
# Config
|
||||||
sed -i 's/test\/certs\/localhost/\/etc\/pebble/' /etc/pebble/pebble.json
|
sed -i 's#test/certs/localhost#/etc/pebble#' /etc/pebble/pebble.json
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
---
|
|
||||||
name: test
|
name: test
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
@@ -12,7 +11,6 @@ jobs:
|
|||||||
node: [16, 18, 20]
|
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
|
||||||
#
|
#
|
||||||
|
|||||||
1
packages/core/acme-client/.gitignore
vendored
1
packages/core/acme-client/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
.actrc
|
||||||
.vscode/
|
.vscode/
|
||||||
node_modules/
|
node_modules/
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v5.4.0 (2024-07-16)
|
||||||
|
|
||||||
|
* `added` Directory URLs for [Google](https://cloud.google.com/certificate-manager/docs/overview) ACME provider
|
||||||
|
* `fixed` Invalidate ACME provider directory cache after 24 hours
|
||||||
|
* `fixed` Retry HTTP requests on server errors or when rate limited - [#89](https://github.com/publishlab/node-acme-client/issues/89)
|
||||||
|
|
||||||
|
## v5.3.1 (2024-05-22)
|
||||||
|
|
||||||
|
* `fixed` Allow `client.auto()` being called with an empty CSR common name
|
||||||
|
* `fixed` Bug when calling `updateAccountKey()` with external account binding
|
||||||
|
|
||||||
## v5.3.0 (2024-02-05)
|
## v5.3.0 (2024-02-05)
|
||||||
|
|
||||||
* `added` Support and tests for satisfying `tls-alpn-01` challenges
|
* `added` Support and tests for satisfying `tls-alpn-01` challenges
|
||||||
* `changed` Replace `jsrsasign` with `@peculiar/x509` for certificate and CSR generation and parsing
|
* `changed` Replace `jsrsasign` with `@peculiar/x509` for certificate and CSR handling
|
||||||
* `changed` Method `getChallengeKeyAuthorization()` now returns `$token.$thumbprint` when called with a `tls-alpn-01` challenge
|
* `changed` Method `getChallengeKeyAuthorization()` now returns `$token.$thumbprint` when called with a `tls-alpn-01` challenge
|
||||||
* Previously returned base64url encoded SHA256 digest of `$token.$thumbprint` erroneously
|
* Previously returned base64url encoded SHA256 digest of `$token.$thumbprint` erroneously
|
||||||
* This change is not considered breaking since the previous behavior was incorrect
|
* This change is not considered breaking since the previous behavior was incorrect
|
||||||
@@ -32,7 +43,7 @@
|
|||||||
* `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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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.<Array.<buffer>></code>
|
## createAlpnCertificate(authz, keyAuthorization, [keyPem]) ⇒ <code>Promise.<Array.<buffer>></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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
@@ -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, () => {
|
||||||
|
|||||||
@@ -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, () => {
|
||||||
|
|||||||
@@ -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, () => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "acme-client",
|
"name": "acme-client",
|
||||||
"description": "Simple and unopinionated ACME client",
|
"description": "Simple and unopinionated ACME client",
|
||||||
"author": "nmorsman",
|
"author": "nmorsman",
|
||||||
"version": "5.3.0",
|
"version": "5.4.0",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"types": "types/index.d.ts",
|
"types": "types/index.d.ts",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -15,23 +15,23 @@
|
|||||||
"types"
|
"types"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@peculiar/x509": "^1.9.7",
|
"@peculiar/x509": "^1.11.0",
|
||||||
"asn1js": "^3.0.5",
|
"asn1js": "^3.0.5",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.7.2",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.3.5",
|
||||||
"node-forge": "^1.3.1"
|
"node-forge": "^1.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.11.5",
|
"@types/node": "^20.14.10",
|
||||||
"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.6.0",
|
||||||
"nock": "^13.5.0",
|
"nock": "^13.5.4",
|
||||||
"tsd": "^0.30.4"
|
"tsd": "^0.31.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"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",
|
"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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -13,10 +13,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 +24,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)) {
|
||||||
@@ -37,7 +36,6 @@ module.exports = async function(client, userOpts) {
|
|||||||
accountPayload.contact = [`mailto:${opts.email}`];
|
accountPayload.contact = [`mailto:${opts.email}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register account
|
* Register account
|
||||||
*/
|
*/
|
||||||
@@ -53,19 +51,16 @@ module.exports = async function(client, userOpts) {
|
|||||||
await client.createAccount(accountPayload);
|
await client.createAccount(accountPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse domains from CSR
|
* Parse domains from CSR
|
||||||
*/
|
*/
|
||||||
|
|
||||||
log('[auto] Parsing domains from Certificate Signing Request');
|
log('[auto] Parsing domains from Certificate Signing Request');
|
||||||
const csrDomains = readCsrDomains(opts.csr);
|
const { commonName, altNames } = readCsrDomains(opts.csr);
|
||||||
const domains = [csrDomains.commonName].concat(csrDomains.altNames);
|
const uniqueDomains = Array.from(new Set([commonName].concat(altNames).filter((d) => d)));
|
||||||
const uniqueDomains = Array.from(new Set(domains));
|
|
||||||
|
|
||||||
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
|
||||||
*/
|
*/
|
||||||
@@ -77,7 +72,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
|
||||||
*/
|
*/
|
||||||
@@ -165,7 +159,6 @@ module.exports = async function(client, userOpts) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for all challenge promises to settle
|
* Wait for all challenge promises to settle
|
||||||
*/
|
*/
|
||||||
@@ -179,7 +172,6 @@ module.exports = async function(client, userOpts) {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finalize order and download certificate
|
* Finalize order and download certificate
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,11 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
const { parseRetryAfterHeader } = require('./util');
|
||||||
|
const { log } = require('./logger');
|
||||||
const pkg = require('./../package.json');
|
const pkg = require('./../package.json');
|
||||||
|
|
||||||
|
const { AxiosError } = axios;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instance
|
* Defaults
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const instance = axios.create();
|
const instance = axios.create();
|
||||||
@@ -19,9 +22,11 @@ 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,
|
||||||
};
|
|
||||||
|
|
||||||
|
retryMaxAttempts: 5,
|
||||||
|
retryDefaultDelay: 5,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Explicitly set Node as default HTTP adapter
|
* Explicitly set Node as default HTTP adapter
|
||||||
@@ -32,6 +37,84 @@ instance.defaults.acmeSettings = {
|
|||||||
|
|
||||||
instance.defaults.adapter = 'http';
|
instance.defaults.adapter = 'http';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry requests on server errors or when rate limited
|
||||||
|
*
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-6.6
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isRetryableError(error) {
|
||||||
|
return (error.code !== 'ECONNABORTED')
|
||||||
|
&& (error.code !== 'ERR_NOCK_NO_MATCH')
|
||||||
|
&& (!error.response
|
||||||
|
|| (error.response.status === 429)
|
||||||
|
|| ((error.response.status >= 500) && (error.response.status <= 599)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://github.com/axios/axios/blob/main/lib/core/settle.js */
|
||||||
|
function validateStatus(response) {
|
||||||
|
const validator = response.config.retryValidateStatus;
|
||||||
|
|
||||||
|
if (!response.status || !validator || validator(response.status)) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AxiosError(
|
||||||
|
`Request failed with status code ${response.status}`,
|
||||||
|
(Math.floor(response.status / 100) === 4) ? AxiosError.ERR_BAD_REQUEST : AxiosError.ERR_BAD_RESPONSE,
|
||||||
|
response.config,
|
||||||
|
response.request,
|
||||||
|
response,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pass all responses through the error interceptor */
|
||||||
|
instance.interceptors.request.use((config) => {
|
||||||
|
if (!('retryValidateStatus' in config)) {
|
||||||
|
config.retryValidateStatus = config.validateStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.validateStatus = () => false;
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Handle request retries if applicable */
|
||||||
|
instance.interceptors.response.use(null, async (error) => {
|
||||||
|
const { config, response } = error;
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pick up errors we want to retry */
|
||||||
|
if (isRetryableError(error)) {
|
||||||
|
const { retryMaxAttempts, retryDefaultDelay } = instance.defaults.acmeSettings;
|
||||||
|
config.retryAttempt = ('retryAttempt' in config) ? (config.retryAttempt + 1) : 1;
|
||||||
|
|
||||||
|
if (config.retryAttempt <= retryMaxAttempts) {
|
||||||
|
const code = response ? `HTTP ${response.status}` : error.code;
|
||||||
|
log(`Caught ${code}, retry attempt ${config.retryAttempt}/${retryMaxAttempts} to URL ${config.url}`);
|
||||||
|
|
||||||
|
/* Attempt to parse Retry-After header, fallback to default delay */
|
||||||
|
let retryAfter = response ? parseRetryAfterHeader(response.headers['retry-after']) : 0;
|
||||||
|
|
||||||
|
if (retryAfter > 0) {
|
||||||
|
log(`Found retry-after response header with value: ${response.headers['retry-after']}, waiting ${retryAfter} seconds`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
retryAfter = (retryDefaultDelay * config.retryAttempt);
|
||||||
|
log(`Unable to locate or parse retry-after response header, waiting ${retryAfter} seconds`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait and retry the request */
|
||||||
|
await new Promise((resolve) => { setTimeout(resolve, (retryAfter * 1000)); });
|
||||||
|
return instance(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate and return response */
|
||||||
|
return validateStatus(response);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export instance
|
* Export instance
|
||||||
|
|||||||
@@ -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
|
||||||
*
|
*
|
||||||
@@ -261,7 +252,7 @@ class AcmeClient {
|
|||||||
const accountUrl = this.api.getAccountUrl();
|
const accountUrl = this.api.getAccountUrl();
|
||||||
|
|
||||||
/* Create new HTTP and API clients using new key */
|
/* Create new HTTP and API clients using new key */
|
||||||
const newHttpClient = new HttpClient(this.opts.directoryUrl, newAccountKey);
|
const newHttpClient = new HttpClient(this.opts.directoryUrl, newAccountKey, this.opts.externalAccountBinding);
|
||||||
const newApiClient = new AcmeApi(newHttpClient, accountUrl);
|
const newApiClient = new AcmeApi(newHttpClient, accountUrl);
|
||||||
|
|
||||||
/* Get old JWK */
|
/* Get old JWK */
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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),
|
||||||
}]
|
}],
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ const { getJwk } = require('./crypto');
|
|||||||
const { log } = require('./logger');
|
const { log } = require('./logger');
|
||||||
const axios = require('./axios');
|
const axios = require('./axios');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ACME HTTP client
|
* ACME HTTP client
|
||||||
*
|
*
|
||||||
@@ -26,10 +25,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
|
||||||
@@ -60,17 +61,20 @@ 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 now = Math.floor(Date.now() / 1000);
|
||||||
|
const age = (now - this.directoryTimestamp);
|
||||||
|
|
||||||
|
if (!this.directoryCache || (age > this.directoryMaxAge)) {
|
||||||
|
log(`Refreshing ACME directory, age: ${age}`);
|
||||||
const resp = await this.request(this.directoryUrl, 'get');
|
const resp = await this.request(this.directoryUrl, 'get');
|
||||||
|
|
||||||
if (resp.status >= 400) {
|
if (resp.status >= 400) {
|
||||||
@@ -81,10 +85,12 @@ 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;
|
||||||
|
this.directoryTimestamp = now;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
return this.directoryCache;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get JSON Web Key
|
* Get JSON Web Key
|
||||||
@@ -100,13 +106,12 @@ class HttpClient {
|
|||||||
return this.jwk;
|
return this.jwk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get nonce from directory API endpoint
|
* Get nonce from directory API endpoint
|
||||||
*
|
*
|
||||||
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.2
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.2
|
||||||
*
|
*
|
||||||
* @returns {Promise<string>} nonce
|
* @returns {Promise<string>} Nonce
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async getNonce() {
|
async getNonce() {
|
||||||
@@ -120,7 +125,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
|
||||||
*
|
*
|
||||||
@@ -129,16 +133,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
|
||||||
*
|
*
|
||||||
@@ -147,16 +150,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
|
||||||
*
|
*
|
||||||
@@ -189,11 +191,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
|
||||||
*
|
*
|
||||||
@@ -216,7 +217,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
|
||||||
*
|
*
|
||||||
@@ -257,13 +257,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
|
||||||
*
|
*
|
||||||
@@ -299,7 +298,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;
|
||||||
@@ -313,6 +312,5 @@ class HttpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Export client */
|
/* Export client */
|
||||||
module.exports = HttpClient;
|
module.exports = HttpClient;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
exports.Client = require('./client');
|
exports.Client = require('./client');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directory URLs
|
* Directory URLs
|
||||||
*/
|
*/
|
||||||
@@ -12,18 +11,21 @@ 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'
|
production: 'https://acme.zerossl.com/v2/DV90',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crypto
|
* Crypto
|
||||||
*/
|
*/
|
||||||
@@ -31,14 +33,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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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,11 +83,13 @@ 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
|
||||||
*
|
*
|
||||||
* @param {string} header Link header contents
|
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.4.2
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link
|
||||||
|
*
|
||||||
|
* @param {string} header Header contents
|
||||||
* @param {string} rel Link relation, default: `alternate`
|
* @param {string} rel Link relation, default: `alternate`
|
||||||
* @returns {string[]} Array of URLs
|
* @returns {string[]} Array of URLs
|
||||||
*/
|
*/
|
||||||
@@ -107,6 +105,36 @@ function parseLinkHeader(header, rel = 'alternate') {
|
|||||||
return results.filter((r) => r);
|
return results.filter((r) => r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse date or duration from Retry-After header
|
||||||
|
*
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
||||||
|
*
|
||||||
|
* @param {string} header Header contents
|
||||||
|
* @returns {number} Retry duration in seconds
|
||||||
|
*/
|
||||||
|
|
||||||
|
function parseRetryAfterHeader(header) {
|
||||||
|
const sec = parseInt(header, 10);
|
||||||
|
const date = new Date(header);
|
||||||
|
|
||||||
|
/* Seconds into the future */
|
||||||
|
if (Number.isSafeInteger(sec) && (sec > 0)) {
|
||||||
|
return sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Future date string */
|
||||||
|
if (date instanceof Date && !Number.isNaN(date)) {
|
||||||
|
const now = new Date();
|
||||||
|
const diff = Math.ceil((date.getTime() - now.getTime()) / 1000);
|
||||||
|
|
||||||
|
if (diff > 0) {
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find certificate chain with preferred issuer common name
|
* Find certificate chain with preferred issuer common name
|
||||||
@@ -157,7 +185,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
|
||||||
*
|
*
|
||||||
@@ -168,17 +195,18 @@ function findCertificateChainForIssuer(chains, issuer) {
|
|||||||
function formatResponseError(resp) {
|
function formatResponseError(resp) {
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
if (resp.data.error) {
|
if (resp.data) {
|
||||||
result = resp.data.error.detail || resp.data.error;
|
if (resp.data.error) {
|
||||||
}
|
result = resp.data.error.detail || resp.data.error;
|
||||||
else {
|
}
|
||||||
result = resp.data.detail || JSON.stringify(resp.data);
|
else {
|
||||||
|
result = resp.data.detail || JSON.stringify(resp.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +232,6 @@ async function resolveDomainBySoaRecord(recordName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get DNS resolver using domains authoritative NS records
|
* Get DNS resolver using domains authoritative NS records
|
||||||
*
|
*
|
||||||
@@ -245,7 +272,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 +293,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 +325,6 @@ async function retrieveTlsAlpnCertificate(host, port, timeout = 30000) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export utils
|
* Export utils
|
||||||
*/
|
*/
|
||||||
@@ -307,8 +332,9 @@ async function retrieveTlsAlpnCertificate(host, port, timeout = 30000) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
retry,
|
retry,
|
||||||
parseLinkHeader,
|
parseLinkHeader,
|
||||||
|
parseRetryAfterHeader,
|
||||||
findCertificateChainForIssuer,
|
findCertificateChainForIssuer,
|
||||||
formatResponseError,
|
formatResponseError,
|
||||||
getAuthoritativeDnsResolver,
|
getAuthoritativeDnsResolver,
|
||||||
retrieveTlsAlpnCertificate
|
retrieveTlsAlpnCertificate,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,7 +42,6 @@ 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
|
||||||
*/
|
*/
|
||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,41 +9,17 @@ 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;
|
||||||
|
|
||||||
|
const endpoint = `http://${uuid()}.example.com`;
|
||||||
const defaultUserAgent = `node-${pkg.name}/${pkg.version}`;
|
const defaultUserAgent = `node-${pkg.name}/${pkg.version}`;
|
||||||
const customUserAgent = 'custom-ua-123';
|
const customUserAgent = 'custom-ua-123';
|
||||||
|
|
||||||
const primaryEndpoint = `http://${uuid()}.example.com`;
|
afterEach(() => {
|
||||||
const defaultUaEndpoint = `http://${uuid()}.example.com`;
|
nock.cleanAll();
|
||||||
const customUaEndpoint = `http://${uuid()}.example.com`;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP mocking
|
|
||||||
*/
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
const defaultUaOpts = { reqheaders: { 'User-Agent': defaultUserAgent } };
|
|
||||||
const customUaOpts = { reqheaders: { 'User-Agent': customUserAgent } };
|
|
||||||
|
|
||||||
nock(primaryEndpoint)
|
|
||||||
.persist().get('/').reply(200, 'ok');
|
|
||||||
|
|
||||||
nock(defaultUaEndpoint, defaultUaOpts)
|
|
||||||
.persist().get('/').reply(200, 'ok');
|
|
||||||
|
|
||||||
nock(customUaEndpoint, customUaOpts)
|
|
||||||
.persist().get('/').reply(200, 'ok');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
after(() => {
|
|
||||||
axios.defaults.headers.common['User-Agent'] = defaultUserAgent;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize
|
* Initialize
|
||||||
*/
|
*/
|
||||||
@@ -52,47 +28,94 @@ describe('http', () => {
|
|||||||
testClient = new HttpClient();
|
testClient = new HttpClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP verbs
|
* HTTP verbs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
it('should http get', async () => {
|
it('should http get', async () => {
|
||||||
const resp = await testClient.request(primaryEndpoint, 'get');
|
nock(endpoint).get('/').reply(200, 'ok');
|
||||||
|
const resp = await testClient.request(endpoint, 'get');
|
||||||
|
|
||||||
assert.isObject(resp);
|
assert.isObject(resp);
|
||||||
assert.strictEqual(resp.status, 200);
|
assert.strictEqual(resp.status, 200);
|
||||||
assert.strictEqual(resp.data, 'ok');
|
assert.strictEqual(resp.data, 'ok');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User-Agent
|
* User-Agent
|
||||||
*/
|
*/
|
||||||
|
|
||||||
it('should request using default user-agent', async () => {
|
it('should request using default user-agent', async () => {
|
||||||
const resp = await testClient.request(defaultUaEndpoint, 'get');
|
nock(endpoint).matchHeader('user-agent', defaultUserAgent).get('/').reply(200, 'ok');
|
||||||
|
axios.defaults.headers.common['User-Agent'] = defaultUserAgent;
|
||||||
|
const resp = await testClient.request(endpoint, 'get');
|
||||||
|
|
||||||
assert.isObject(resp);
|
assert.isObject(resp);
|
||||||
assert.strictEqual(resp.status, 200);
|
assert.strictEqual(resp.status, 200);
|
||||||
assert.strictEqual(resp.data, 'ok');
|
assert.strictEqual(resp.data, 'ok');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not request using custom user-agent', async () => {
|
it('should reject using custom user-agent', async () => {
|
||||||
await assert.isRejected(testClient.request(customUaEndpoint, 'get'));
|
nock(endpoint).matchHeader('user-agent', defaultUserAgent).get('/').reply(200, 'ok');
|
||||||
|
axios.defaults.headers.common['User-Agent'] = customUserAgent;
|
||||||
|
await assert.isRejected(testClient.request(endpoint, 'get'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should request using custom user-agent', async () => {
|
it('should request using custom user-agent', async () => {
|
||||||
|
nock(endpoint).matchHeader('user-agent', customUserAgent).get('/').reply(200, 'ok');
|
||||||
axios.defaults.headers.common['User-Agent'] = customUserAgent;
|
axios.defaults.headers.common['User-Agent'] = customUserAgent;
|
||||||
const resp = await testClient.request(customUaEndpoint, 'get');
|
const resp = await testClient.request(endpoint, 'get');
|
||||||
|
|
||||||
assert.isObject(resp);
|
assert.isObject(resp);
|
||||||
assert.strictEqual(resp.status, 200);
|
assert.strictEqual(resp.status, 200);
|
||||||
assert.strictEqual(resp.data, 'ok');
|
assert.strictEqual(resp.data, 'ok');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not request using default user-agent', async () => {
|
it('should reject using default user-agent', async () => {
|
||||||
axios.defaults.headers.common['User-Agent'] = customUserAgent;
|
nock(endpoint).matchHeader('user-agent', customUserAgent).get('/').reply(200, 'ok');
|
||||||
await assert.isRejected(testClient.request(defaultUaEndpoint, 'get'));
|
axios.defaults.headers.common['User-Agent'] = defaultUserAgent;
|
||||||
|
await assert.isRejected(testClient.request(endpoint, 'get'));
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry on HTTP errors
|
||||||
|
*/
|
||||||
|
|
||||||
|
it('should retry on 429 rate limit', async () => {
|
||||||
|
let rateLimitCount = 0;
|
||||||
|
|
||||||
|
nock(endpoint).persist().get('/').reply(() => {
|
||||||
|
rateLimitCount += 1;
|
||||||
|
|
||||||
|
if (rateLimitCount < 3) {
|
||||||
|
return [429, 'Rate Limit Exceeded', { 'Retry-After': 1 }];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [200, 'ok'];
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(rateLimitCount, 0);
|
||||||
|
const resp = await testClient.request(endpoint, 'get');
|
||||||
|
|
||||||
|
assert.isObject(resp);
|
||||||
|
assert.strictEqual(resp.status, 200);
|
||||||
|
assert.strictEqual(resp.data, 'ok');
|
||||||
|
assert.strictEqual(rateLimitCount, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retry on 5xx server error', async () => {
|
||||||
|
let serverErrorCount = 0;
|
||||||
|
|
||||||
|
nock(endpoint).persist().get('/').reply(() => {
|
||||||
|
serverErrorCount += 1;
|
||||||
|
return [500, 'Internal Server Error', { 'Retry-After': 1 }];
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(serverErrorCount, 0);
|
||||||
|
const resp = await testClient.request(endpoint, 'get');
|
||||||
|
|
||||||
|
assert.isObject(resp);
|
||||||
|
assert.strictEqual(resp.status, 500);
|
||||||
|
assert.strictEqual(serverErrorCount, 4);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
145
packages/core/acme-client/test/10-util.spec.js
Normal file
145
packages/core/acme-client/test/10-util.spec.js
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* Utility method tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
const dns = require('dns').promises;
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
const path = require('path');
|
||||||
|
const { assert } = require('chai');
|
||||||
|
const util = require('./../src/util');
|
||||||
|
const { readCertificateInfo } = require('./../src/crypto');
|
||||||
|
|
||||||
|
describe('util', () => {
|
||||||
|
const testCertPath1 = path.join(__dirname, 'fixtures', 'certificate.crt');
|
||||||
|
const testCertPath2 = path.join(__dirname, 'fixtures', 'letsencrypt.crt');
|
||||||
|
|
||||||
|
it('retry()', async () => {
|
||||||
|
let attempts = 0;
|
||||||
|
const backoffOpts = {
|
||||||
|
min: 100,
|
||||||
|
max: 500,
|
||||||
|
};
|
||||||
|
|
||||||
|
await assert.isRejected(util.retry(() => {
|
||||||
|
throw new Error('oops');
|
||||||
|
}, backoffOpts));
|
||||||
|
|
||||||
|
const r = await util.retry(() => {
|
||||||
|
attempts += 1;
|
||||||
|
|
||||||
|
if (attempts < 3) {
|
||||||
|
throw new Error('oops');
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'abc';
|
||||||
|
}, backoffOpts);
|
||||||
|
|
||||||
|
assert.strictEqual(r, 'abc');
|
||||||
|
assert.strictEqual(attempts, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parseLinkHeader()', () => {
|
||||||
|
const r1 = util.parseLinkHeader('<https://example.com/a>;rel="alternate"');
|
||||||
|
assert.isArray(r1);
|
||||||
|
assert.strictEqual(r1.length, 1);
|
||||||
|
assert.strictEqual(r1[0], 'https://example.com/a');
|
||||||
|
|
||||||
|
const r2 = util.parseLinkHeader('<https://example.com/b>;rel="test"');
|
||||||
|
assert.isArray(r2);
|
||||||
|
assert.strictEqual(r2.length, 0);
|
||||||
|
|
||||||
|
const r3 = util.parseLinkHeader('<http://example.com/c>; rel="test"', 'test');
|
||||||
|
assert.isArray(r3);
|
||||||
|
assert.strictEqual(r3.length, 1);
|
||||||
|
assert.strictEqual(r3[0], 'http://example.com/c');
|
||||||
|
|
||||||
|
const r4 = util.parseLinkHeader(`<https://example.com/a>; rel="alternate",
|
||||||
|
<https://example.com/x>; rel="nope",
|
||||||
|
<https://example.com/b>;rel="alternate",
|
||||||
|
<https://example.com/c>; rel="alternate"`);
|
||||||
|
assert.isArray(r4);
|
||||||
|
assert.strictEqual(r4.length, 3);
|
||||||
|
assert.strictEqual(r4[0], 'https://example.com/a');
|
||||||
|
assert.strictEqual(r4[1], 'https://example.com/b');
|
||||||
|
assert.strictEqual(r4[2], 'https://example.com/c');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parseRetryAfterHeader()', () => {
|
||||||
|
const r1 = util.parseRetryAfterHeader('');
|
||||||
|
assert.strictEqual(r1, 0);
|
||||||
|
|
||||||
|
const r2 = util.parseRetryAfterHeader('abcdef');
|
||||||
|
assert.strictEqual(r2, 0);
|
||||||
|
|
||||||
|
const r3 = util.parseRetryAfterHeader('123');
|
||||||
|
assert.strictEqual(r3, 123);
|
||||||
|
|
||||||
|
const r4 = util.parseRetryAfterHeader('123.456');
|
||||||
|
assert.strictEqual(r4, 123);
|
||||||
|
|
||||||
|
const r5 = util.parseRetryAfterHeader('-555');
|
||||||
|
assert.strictEqual(r5, 0);
|
||||||
|
|
||||||
|
const r6 = util.parseRetryAfterHeader('Wed, 21 Oct 2015 07:28:00 GMT');
|
||||||
|
assert.strictEqual(r6, 0);
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const future = new Date(now.getTime() + 123000);
|
||||||
|
const r7 = util.parseRetryAfterHeader(future.toUTCString());
|
||||||
|
assert.isTrue(r7 > 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('findCertificateChainForIssuer()', async () => {
|
||||||
|
const certs = [
|
||||||
|
(await fs.readFile(testCertPath1)).toString(),
|
||||||
|
(await fs.readFile(testCertPath2)).toString(),
|
||||||
|
];
|
||||||
|
|
||||||
|
const r1 = util.findCertificateChainForIssuer(certs, 'abc123');
|
||||||
|
const r2 = util.findCertificateChainForIssuer(certs, 'example.com');
|
||||||
|
const r3 = util.findCertificateChainForIssuer(certs, 'E6');
|
||||||
|
|
||||||
|
[r1, r2, r3].forEach((r) => {
|
||||||
|
assert.isString(r);
|
||||||
|
assert.isNotEmpty(r);
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(readCertificateInfo(r1).issuer.commonName, 'example.com');
|
||||||
|
assert.strictEqual(readCertificateInfo(r2).issuer.commonName, 'example.com');
|
||||||
|
assert.strictEqual(readCertificateInfo(r3).issuer.commonName, 'E6');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('formatResponseError()', () => {
|
||||||
|
const e1 = util.formatResponseError({ data: { error: 'aaa' } });
|
||||||
|
assert.strictEqual(e1, 'aaa');
|
||||||
|
|
||||||
|
const e2 = util.formatResponseError({ data: { error: { detail: 'bbb' } } });
|
||||||
|
assert.strictEqual(e2, 'bbb');
|
||||||
|
|
||||||
|
const e3 = util.formatResponseError({ data: { detail: 'ccc' } });
|
||||||
|
assert.strictEqual(e3, 'ccc');
|
||||||
|
|
||||||
|
const e4 = util.formatResponseError({ data: { a: 123 } });
|
||||||
|
assert.strictEqual(e4, '{"a":123}');
|
||||||
|
|
||||||
|
const e5 = util.formatResponseError({});
|
||||||
|
assert.isString(e5);
|
||||||
|
assert.isEmpty(e5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getAuthoritativeDnsResolver()', async () => {
|
||||||
|
/* valid domain - should not use global default */
|
||||||
|
const r1 = await util.getAuthoritativeDnsResolver('example.com');
|
||||||
|
assert.instanceOf(r1, dns.Resolver);
|
||||||
|
assert.isNotEmpty(r1.getServers());
|
||||||
|
assert.notDeepEqual(r1.getServers(), dns.getServers());
|
||||||
|
|
||||||
|
/* invalid domain - fallback to global default */
|
||||||
|
const r2 = await util.getAuthoritativeDnsResolver('invalid.xtldx');
|
||||||
|
assert.instanceOf(r2, dns.Resolver);
|
||||||
|
assert.deepStrictEqual(r2.getServers(), dns.getServers());
|
||||||
|
});
|
||||||
|
|
||||||
|
/* TODO: Figure out how to test this */
|
||||||
|
it('retrieveTlsAlpnCertificate()');
|
||||||
|
});
|
||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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,15 +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({
|
||||||
commonName: testSanDomains[0],
|
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);
|
||||||
@@ -316,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);
|
||||||
@@ -333,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({
|
||||||
@@ -341,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({
|
||||||
@@ -362,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();
|
||||||
@@ -372,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({
|
||||||
@@ -386,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();
|
||||||
@@ -395,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
|
||||||
*/
|
*/
|
||||||
@@ -403,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
|
||||||
*/
|
*/
|
||||||
@@ -426,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]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -434,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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -442,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}`]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
23
packages/core/acme-client/test/fixtures/letsencrypt.crt
vendored
Normal file
23
packages/core/acme-client/test/fixtures/letsencrypt.crt
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDzzCCA1WgAwIBAgISA0ghDoSv5DpT3Pd3lqwjbVDDMAoGCCqGSM49BAMDMDIx
|
||||||
|
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF
|
||||||
|
NjAeFw0yNDA2MTAxNzEyMjZaFw0yNDA5MDgxNzEyMjVaMBQxEjAQBgNVBAMTCWxl
|
||||||
|
bmNyLm9yZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEHJ3DjN7pYV3mftHzaP
|
||||||
|
V/WI0RhOJnSI5AIFEPFHDi8UowOINRGIfm9FHGIDqrb4Rmyvr9JrrqBdFGDen8BW
|
||||||
|
6OGjggJnMIICYzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG
|
||||||
|
CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFIdCTnxqmpOELDyzPaEM
|
||||||
|
seB36lUOMB8GA1UdIwQYMBaAFJMnRpgDqVFojpjWxEJI2yO/WJTSMFUGCCsGAQUF
|
||||||
|
BwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL2U2Lm8ubGVuY3Iub3JnMCIGCCsG
|
||||||
|
AQUFBzAChhZodHRwOi8vZTYuaS5sZW5jci5vcmcvMG8GA1UdEQRoMGaCCWxlbmNy
|
||||||
|
Lm9yZ4IPbGV0c2VuY3J5cHQuY29tgg9sZXRzZW5jcnlwdC5vcmeCDXd3dy5sZW5j
|
||||||
|
ci5vcmeCE3d3dy5sZXRzZW5jcnlwdC5jb22CE3d3dy5sZXRzZW5jcnlwdC5vcmcw
|
||||||
|
EwYDVR0gBAwwCjAIBgZngQwBAgEwggEFBgorBgEEAdZ5AgQCBIH2BIHzAPEAdgA/
|
||||||
|
F0tP1yJHWJQdZRyEvg0S7ZA3fx+FauvBvyiF7PhkbgAAAZADWfneAAAEAwBHMEUC
|
||||||
|
IGlp+dPU2hLT2suTMYkYMlt/xbzSnKLZDA/wYSsPACP7AiEAxbAzx6mkzn0cs0hh
|
||||||
|
ti6sLf0pcbmDhxHdlJRjuo6SQZEAdwDf4VbrqgWvtZwPhnGNqMAyTq5W2W6n9aVq
|
||||||
|
AdHBO75SXAAAAZADWfqrAAAEAwBIMEYCIQCrAmDUrlX3oGhri1qCIb65Cuf8h2GR
|
||||||
|
LC1VfXBenX7dCAIhALXwbhCQ1vO1WLv4CqyihMHOwFaICYqN/N6ylaBlVAM4MAoG
|
||||||
|
CCqGSM49BAMDA2gAMGUCMFdgjOXGl+hE2ABDsAeuNq8wi34yTMUHk0KMTOjRAfy9
|
||||||
|
rOCGQqvP0myoYlyzXOH9uQIxAMdkG1ZWBZS1dHavbPf1I/MjYpzX6gy0jVHIXXu5
|
||||||
|
aYWylBi/Uf2RPj0LWFZh8tNa1Q==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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,6 +29,12 @@ if (process.env.ACME_TLSALPN_PORT) {
|
|||||||
axios.defaults.acmeSettings.tlsAlpnChallengePort = process.env.ACME_TLSALPN_PORT;
|
axios.defaults.acmeSettings.tlsAlpnChallengePort = process.env.ACME_TLSALPN_PORT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Greatly reduce retry duration while testing
|
||||||
|
*/
|
||||||
|
|
||||||
|
axios.defaults.acmeSettings.retryMaxAttempts = 3;
|
||||||
|
axios.defaults.acmeSettings.retryDefaultDelay = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* External account binding
|
* External account binding
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
10
packages/core/acme-client/types/index.d.ts
vendored
10
packages/core/acme-client/types/index.d.ts
vendored
@@ -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,6 +87,10 @@ 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
|
||||||
@@ -99,7 +100,6 @@ export const directory: {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crypto
|
* Crypto
|
||||||
*/
|
*/
|
||||||
@@ -177,14 +177,12 @@ export interface CryptoLegacyInterface {
|
|||||||
|
|
||||||
export const forge: CryptoLegacyInterface;
|
export const forge: CryptoLegacyInterface;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Axios
|
* Axios
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const axios: AxiosInstance;
|
export const axios: AxiosInstance;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logger
|
* Logger
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
4
packages/core/acme-client/types/rfc8555.d.ts
vendored
4
packages/core/acme-client/types/rfc8555.d.ts
vendored
@@ -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
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user