mirror of
https://github.com/therootcompany/acme.js.git
synced 2024-11-16 17:29:00 +00:00
Compare commits
73 Commits
9139d89143
...
e0bec09e43
Author | SHA1 | Date | |
---|---|---|---|
e0bec09e43 | |||
148846b18a | |||
b1c591b6ed | |||
4e7ff0d9e8 | |||
b39a3763cf | |||
54cda5a888 | |||
90c7154a24 | |||
161e9183c6 | |||
7f868f350b | |||
30f4306c05 | |||
0efa94eeb0 | |||
f05e9db38e | |||
7e6a66c1d8 | |||
b1046222dc | |||
d25fa6756c | |||
c89e5b7882 | |||
4b79b0bb3a | |||
ad42d34587 | |||
d7b3e2e1db | |||
f363f5ef02 | |||
1f2169c78c | |||
c5757d2650 | |||
f7fd435443 | |||
20c11d5df7 | |||
21f7e87606 | |||
5623ed1914 | |||
cd35f26e95 | |||
83cf96f074 | |||
edc830696e | |||
d17a373a89 | |||
0d26a42bc7 | |||
8e2763ecd6 | |||
76f98f7c7e | |||
080497bf4c | |||
2b0fce0869 | |||
96b491a9c0 | |||
499ac7f8ea | |||
24c3633d75 | |||
e75c503356 | |||
6c11446e2f | |||
87a12c36b0 | |||
bc08fb0b97 | |||
b1ccb95601 | |||
5a188577c0 | |||
7186df6760 | |||
806b7e6a91 | |||
32321ea87d | |||
e543911c39 | |||
b324afe6d6 | |||
14c24e3aea | |||
b902907a7c | |||
f1e11f1be7 | |||
0ce04b7466 | |||
7f0a5fb28a | |||
7385dd8580 | |||
488067ec20 | |||
76621560cb | |||
b2174e3923 | |||
fbe30dbfbc | |||
36f5337f7d | |||
70103de28d | |||
f0166afeeb | |||
4b44a576c1 | |||
735ec948da | |||
1b01c2c413 | |||
803fd8e4f1 | |||
2e0549af5a | |||
10f817a51c | |||
3156229e2c | |||
959d2ff009 | |||
66e2cb70a8 | |||
692301e37d | |||
bfc4ab6795 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +1,7 @@
|
||||
.env
|
||||
*.gz
|
||||
.*.sw*
|
||||
.ignore
|
||||
|
||||
*.pem
|
||||
|
||||
@ -14,4 +17,5 @@ coverage
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
|
||||
node_modules
|
||||
|
@ -2,7 +2,7 @@
|
||||
"bracketSpacing": true,
|
||||
"printWidth": 80,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "none",
|
||||
"useTabs": true
|
||||
}
|
||||
|
53
CHANGELOG.md
Normal file
53
CHANGELOG.md
Normal file
@ -0,0 +1,53 @@
|
||||
# Changelog
|
||||
|
||||
- v3 (Oct 2019)
|
||||
- Add POST-as-GET for Let's Encrypt v2 release 2 (ACME / RFC 8555)
|
||||
- Jump to v3 for parity with Greenlock
|
||||
- Merge browser and node.js versions in one
|
||||
- Drop all backwards-compat complexity
|
||||
- Move to zero-external deps, using @root packages only
|
||||
- v1.8
|
||||
- more transitional prepwork for new v2 API
|
||||
- support newer (simpler) dns-01 and http-01 libraries
|
||||
- v1.5
|
||||
- perform full test challenge first (even before nonce)
|
||||
- v1.3
|
||||
- Use node RSA keygen by default
|
||||
- No non-optional external deps!
|
||||
- v1.2
|
||||
- fix some API out-of-specness
|
||||
- doc some magic numbers (status)
|
||||
- updated deps
|
||||
- v1.1.0
|
||||
- reduce dependencies (use lightweight @coolaj86/request instead of request)
|
||||
- v1.0.5 - cleanup logging
|
||||
- v1.0.4 - v6- compat use `promisify` from node's util or bluebird
|
||||
- v1.0.3 - documentation cleanup
|
||||
- v1.0.2
|
||||
- use `options.contact` to provide raw contact array
|
||||
- made `options.email` optional
|
||||
- file cleanup
|
||||
- v1.0.1
|
||||
- Compat API is ready for use
|
||||
- Eliminate debug logging
|
||||
- Apr 10, 2018 - tested backwards-compatibility using greenlock.js
|
||||
- Apr 5, 2018 - export http and dns challenge tests
|
||||
- Apr 5, 2018 - test http and dns challenges (success and failure)
|
||||
- Apr 5, 2018 - test subdomains and its wildcard
|
||||
- Apr 5, 2018 - test two subdomains
|
||||
- Apr 5, 2018 - test wildcard
|
||||
- Apr 5, 2018 - completely match api for acme v1 (le-acme-core.js)
|
||||
- Mar 21, 2018 - _mostly_ matches le-acme-core.js API
|
||||
- Mar 21, 2018 - can now accept values (not hard coded)
|
||||
- Mar 20, 2018 - SUCCESS - got a test certificate (hard-coded)
|
||||
- Mar 20, 2018 - download certificate
|
||||
- Mar 20, 2018 - poll for status
|
||||
- Mar 20, 2018 - finalize order (submit csr)
|
||||
- Mar 20, 2018 - generate domain keypair
|
||||
- Mar 20, 2018 - respond to challenges
|
||||
- Mar 16, 2018 - get challenges
|
||||
- Mar 16, 2018 - new order
|
||||
- Mar 15, 2018 - create account
|
||||
- Mar 15, 2018 - generate account keypair
|
||||
- Mar 15, 2018 - get nonce
|
||||
- Mar 15, 2018 - get directory
|
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
||||
Copyright 2018 AJ ONeal
|
||||
Copyright 2015-2019 AJ ONeal
|
||||
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
595
README.md
595
README.md
@ -1,239 +1,434 @@
|
||||
# ACME.js v3 on its way (Nov 1st, 2019)
|
||||
# [ACME.js](https://git.rootprojects.org/root/acme.js) (RFC 8555 / November 2019)
|
||||
|
||||
ACME.js v3 is in private beta and will be available by Nov 1st.
|
||||
| Built by [Root](https://therootcompany.com) for [Greenlock](https://greenlock.domains)
|
||||
|
||||
Follow the updates on the [campaign page](https://indiegogo.com/at/greenlock),
|
||||
and contribute to support the project and get beta access now.
|
||||
Free SSL Certificates from Let's Encrypt, for Node.js and Web Browsers
|
||||
|
||||
| **acme-v2.js** ([npm](https://www.npmjs.com/package/acme-v2))
|
||||
| [acme-v2-cli.js](https://git.coolaj86.com/coolaj86/acme-v2-cli.js)
|
||||
| [greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
|
||||
| [goldilocks.js](https://git.coolaj86.com/coolaj86/goldilocks.js)
|
||||
Lightweight. Fast. Modern Crypto. Zero external dependecies.
|
||||
|
||||
# [acme-v2.js](https://git.coolaj86.com/coolaj86/acme-v2.js) | a [Root](https://therootcompany.com) project
|
||||
# Features
|
||||
|
||||
A **Zero (External) Dependency**\* library for building
|
||||
Let's Encrypt v2 (ACME draft 18) clients and getting Free SSL certificates.
|
||||
| 15k gzipped | 55k minified | 88k (2,500 loc) source with comments |
|
||||
|
||||
The primary goal of this library is to make it easy to
|
||||
get Accounts and Certificates through Let's Encrypt.
|
||||
|
||||
# Features
|
||||
- [x] Let's Encrypt v2 / ACME RFC 8555 (November 2019)
|
||||
- [x] POST-as-GET support
|
||||
- [x] Secure support for EC and RSA for account and server keys
|
||||
- [x] Simple and lightweight PEM, DER, ASN1, X509, and CSR implementations
|
||||
- [ ] (in-progress) StartTLS Everywhere™
|
||||
- [x] Supports International Domain Names (i.e. `.中国`)
|
||||
- [x] Works with any [generic ACME challenge handler](https://git.rootprojects.org/root/acme-challenge-test.js)
|
||||
- [x] **http-01** for single or multiple domains per certificate
|
||||
- [x] **dns-01** for wildcards, localhost, private networks, etc
|
||||
- [x] VanillaJS, Zero External Dependencies
|
||||
- [x] Safe, Efficient, Maintained
|
||||
- [x] Node.js\* (v6+)
|
||||
- [x] WebPack
|
||||
- [x] Online Demo
|
||||
- See https://greenlock.domains
|
||||
|
||||
- [x] Let's Encrypt™ v2 / ACME Draft 12
|
||||
- [ ] (in-progress) Let's Encrypt™ v2.1 / ACME Draft 18
|
||||
- [ ] (in-progress) StartTLS Everywhere™
|
||||
- [x] Works with any [generic ACME challenge handler](https://git.rootprojects.org/root/acme-challenge-test.js)
|
||||
- [x] **http-01** for single or multiple domains per certificate
|
||||
- [x] **dns-01** for wildcards, localhost, private networks, etc
|
||||
- [x] VanillaJS
|
||||
- [x] Zero External Dependencies
|
||||
- [x] Safe, Efficient, Maintained
|
||||
- [x] Works in Node v6+
|
||||
- [ ] (v2) Works in Web Browsers (See [Demo](https://greenlock.domains))
|
||||
\* Although we use `async/await` in the examples, the code is written in CommonJS,
|
||||
with Promises, so you can use it in Node.js and Browsers without transpiling.
|
||||
|
||||
\* <small>The only required dependencies were built by us, specifically for this and related libraries.
|
||||
There are some, truly optional, backwards-compatibility dependencies for node v6.</small>
|
||||
# Want Quick and Easy?
|
||||
|
||||
## Looking for Quick 'n' Easy™?
|
||||
ACME.js is a low-level tool for building Let's Encrypt clients in Node and Browsers.
|
||||
|
||||
If you want something that's more "batteries included" give
|
||||
[greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
|
||||
a try.
|
||||
If you're looking for maximum convenience, try
|
||||
[Greenlock.js](https://git.rootprojects.org/root/greenlock-express.js).
|
||||
|
||||
- [greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
|
||||
- <https://git.rootprojects.org/root/greenlock-express.js>
|
||||
|
||||
## v1.7+: Transitional v2 Support
|
||||
# Online Demos
|
||||
|
||||
By the end of June 2019 we expect to have completed the migration to Let's Encrypt v2.1 (ACME draft 18).
|
||||
- Greenlock for the Web <https://greenlock.domains>
|
||||
- ACME.js Demo <https://rootprojects.org/acme/>
|
||||
|
||||
Although the draft 18 changes themselves don't requiring breaking the API,
|
||||
we've been keeping backwards compatibility for a long time and the API has become messy.
|
||||
We expect that our hosted versions will meet all of yours needs.
|
||||
If they don't, please open an issue to let us know why.
|
||||
|
||||
We're taking this **mandatory ACME update** as an opportunity to **clean up** and **greatly simplify**
|
||||
the code with a fresh new release.
|
||||
We'd much rather improve the app than have a hundred different versions running in the wild.
|
||||
However, in keeping to our values we've made the source visible for others to inspect, improve, and modify.
|
||||
|
||||
As of **v1.7** we started adding **transitional support** for the **next major version**, v2.0 of acme-v2.js.
|
||||
We've been really good about backwards compatibility for
|
||||
|
||||
## Recommended Example
|
||||
|
||||
Due to the upcoming changes we've removed the old documentation.
|
||||
|
||||
Instead we recommend that you take a look at the
|
||||
[Digital Ocean DNS-01 Example](https://git.rootprojects.org/root/acme-v2.js/src/branch/master/examples/dns-01-digitalocean.js)
|
||||
|
||||
- [examples/dns-01-digitalocean.js](https://git.rootprojects.org/root/acme-v2.js/src/branch/master/examples/dns-01-digitalocean.js)
|
||||
|
||||
That's not exactly the new API, but it's close.
|
||||
|
||||
## Let's Encrypt v02 Directory URLs
|
||||
|
||||
```
|
||||
# Production URL
|
||||
https://acme-v02.api.letsencrypt.org/directory
|
||||
```
|
||||
|
||||
```
|
||||
# Staging URL
|
||||
https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
```
|
||||
|
||||
<!--
|
||||
## How to build ACME clients
|
||||
|
||||
As this is intended to build ACME clients, there is not a simple 2-line example
|
||||
(and if you want that, see [greenlock-express.js](https://git.coolaj86.com/coolaj86/greenlock-express.js)).
|
||||
|
||||
I'd recommend first running the example CLI client with a test domain and then investigating the files used for that example:
|
||||
|
||||
```bash
|
||||
node examples/cli.js
|
||||
```
|
||||
|
||||
The example cli has the following prompts:
|
||||
|
||||
```
|
||||
What web address(es) would you like to get certificates for? (ex: example.com,*.example.com)
|
||||
What challenge will you be testing today? http-01 or dns-01? [http-01]
|
||||
What email should we use? (optional)
|
||||
What API style would you like to test? v1-compat or promise? [v1-compat]
|
||||
|
||||
Put the string 'mBfh0SqaAV3MOK3B6cAhCbIReAyDuwuxlO1Sl70x6bM.VNAzCR4THe4czVzo9piNn73B1ZXRLaB2CESwJfKkvRM' into a file at 'example.com/.well-known/acme-challenge/mBfh0SqaAV3MOK3B6cAhCbIReAyDuwuxlO1Sl70x6bM'
|
||||
|
||||
echo 'mBfh0SqaAV3MOK3B6cAhCbIReAyDuwuxlO1Sl70x6bM.VNAzCR4THe4czVzo9piNn73B1ZXRLaB2CESwJfKkvRM' > 'example.com/.well-known/acme-challenge/mBfh0SqaAV3MOK3B6cAhCbIReAyDuwuxlO1Sl70x6bM'
|
||||
|
||||
Then hit the 'any' key to continue...
|
||||
```
|
||||
|
||||
When you've completed the challenge you can hit a key to continue the process.
|
||||
|
||||
If you place the certificate you receive back in `tests/fullchain.pem`
|
||||
you can then test it with `examples/https-server.js`.
|
||||
|
||||
```
|
||||
examples/cli.js
|
||||
examples/genkeypair.js
|
||||
tests/compat.js
|
||||
examples/https-server.js
|
||||
examples/http-server.js
|
||||
```
|
||||
|
||||
-->
|
||||
|
||||
## API
|
||||
|
||||
Status: Small, but breaking changes coming in v2
|
||||
|
||||
This API is a simple evolution of le-acme-core,
|
||||
but tries to provide a better mapping to the new draft 11 APIs.
|
||||
# API Overview
|
||||
|
||||
```js
|
||||
var ACME = require('acme-v2').ACME.create({
|
||||
// used for overriding the default user-agent
|
||||
userAgent: 'My custom UA String',
|
||||
getUserAgentString: function(deps) {
|
||||
return 'My custom UA String';
|
||||
},
|
||||
ACME.create({ maintainerEmail, packageAgent, notify });
|
||||
acme.init(directoryUrl);
|
||||
acme.accounts.create({ subscriberEmail, agreeToTerms, accountKey });
|
||||
acme.certificates.create({
|
||||
customerEmail, // do not use
|
||||
account,
|
||||
accountKey,
|
||||
csr,
|
||||
domains,
|
||||
challenges
|
||||
});
|
||||
```
|
||||
|
||||
// don't try to validate challenges locally
|
||||
skipChallengeTest: false,
|
||||
skipDryRun: false,
|
||||
| Parameter | Description |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| account | an object containing the Let's Encrypt Account ID as "kid" (misnomer, not actually a key id/thumbprint) |
|
||||
| accountKey | an RSA or EC public/private keypair in JWK format |
|
||||
| agreeToTerms | set to `true` to agree to the Let's Encrypt Subscriber Agreement |
|
||||
| challenges | the 'http-01', 'alpn-01', and/or 'dns-01' challenge plugins (`get`, `set`, and `remove` callbacks) to use |
|
||||
| csr | a Certificate Signing Request (CSR), which may be generated with csr.js, openssl, or another |
|
||||
| customerEmail | Don't use this. Given as an example to differentiate between Maintainer, Subscriber, and End-User |
|
||||
| directoryUrl | should be the Let's Encrypt Directory URL<br>`https://acme-staging-v02.api.letsencrypt.org/directory` |
|
||||
| domains | the list of altnames (subject first) that are listed in the CSR and will be listed on the certificate |
|
||||
| notify | all callback for logging events and errors in the form `function (ev, args) { ... }` |
|
||||
| maintainerEmail | should be a contact for the author of the code to receive critical bug and security notices |
|
||||
| packageAgent | should be an RFC72321-style user-agent string to append to the ACME client (ex: mypackage/v1.1.1) |
|
||||
| subscriberEmail | should be a contact for the service provider to receive renewal failure notices and manage the ACME account |
|
||||
|
||||
// ask if the certificate can be issued up to 10 times before failing
|
||||
retryPoll: 8,
|
||||
// ask if the certificate has been validated up to 6 times before cancelling
|
||||
retryPending: 4,
|
||||
// Wait 1000ms between retries
|
||||
retryInterval: 1000,
|
||||
// Wait 10,000ms after deauthorizing a challenge before retrying
|
||||
deauthWait: 10 * 1000
|
||||
Helper Functions
|
||||
|
||||
```js
|
||||
ACME.computeChallenge({
|
||||
accountKey: jwk,
|
||||
hostname: 'example.com',
|
||||
challenge: { type: 'dns-01', token: 'xxxx' }
|
||||
});
|
||||
```
|
||||
|
||||
# Install
|
||||
|
||||
To make it easy to generate, encode, and decode keys and certificates,
|
||||
ACME.js uses [Keypairs.js](https://git.rootprojects.org/root/keypairs.js)
|
||||
and [CSR.js](https://git.rootprojects.org/root/csr.js)
|
||||
|
||||
## Node.js
|
||||
|
||||
```js
|
||||
npm install --save @root/acme
|
||||
```
|
||||
|
||||
```js
|
||||
var ACME = require('@root/acme');
|
||||
```
|
||||
|
||||
## WebPack
|
||||
|
||||
```html
|
||||
<meta charset="UTF-8" />
|
||||
```
|
||||
|
||||
(necessary in case the webserver headers don't specify `plain/text; charset="UTF-8"`)
|
||||
|
||||
```js
|
||||
var ACME = require('@root/acme');
|
||||
```
|
||||
|
||||
## Vanilla JS
|
||||
|
||||
```html
|
||||
<meta charset="UTF-8" />
|
||||
```
|
||||
|
||||
(necessary in case the webserver headers don't specify `plain/text; charset="UTF-8"`)
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@root/acme@3.0.0/dist/acme.all.js"></script>
|
||||
```
|
||||
|
||||
`acme.min.js`
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@root/acme@3.0.0/dist/acme.all.min.js"></script>
|
||||
```
|
||||
|
||||
Use
|
||||
|
||||
```js
|
||||
var ACME = window['@root/acme'];
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
You can see `tests/index.js`, `examples/index.html`, `examples/app.js` in the repo for full example usage.
|
||||
|
||||
### Emails: Maintainer vs Subscriber vs Customer
|
||||
|
||||
- `maintainerEmail` should be the email address of the **author of the code**.
|
||||
This person will receive critical security and API change notifications.
|
||||
- `subscriberEmail` should be the email of the **admin of the hosting service**.
|
||||
This person agrees to the Let's Encrypt Terms of Service and will be notified
|
||||
when a certificate fails to renew.
|
||||
- `customerEmail` should be the email of individual who owns the domain.
|
||||
This is optional (not currently implemented).
|
||||
|
||||
Generally speaking **YOU** are the _maintainer_ and you **or your employer** is the _subscriber_.
|
||||
|
||||
If you (or your employer) is running any type of service
|
||||
you **SHOULD NOT** pass the _customer_ email as the subscriber email.
|
||||
|
||||
If you are not running a service (you may be building a CLI, for example),
|
||||
then you should prompt the user for their email address, and they are the subscriber.
|
||||
|
||||
### Overview
|
||||
|
||||
1. Create an instance of ACME.js
|
||||
2. Create and SAVE a Subscriber Account private key
|
||||
3. Retrieve the Let's Encrypt Subscriber account (with the key)
|
||||
- the account will be created if it doesn't exist
|
||||
4. Create a Server Key
|
||||
- this should be per-server, or perhaps per-end-user
|
||||
5. Create a Certificate Signing Request
|
||||
- International Domain Names must be converted with `punycode`
|
||||
6. Create an ACME Order
|
||||
- use a challenge plugin for HTTP-01 or DNS-01 challenges
|
||||
|
||||
### Instantiate ACME.js
|
||||
|
||||
Although built for Let's Encrypt, ACME.js will work with any server
|
||||
that supports draft-15 of the ACME spec (includes POST-as-GET support).
|
||||
|
||||
The `init()` method takes a _directory url_ and initializes internal state according to its response.
|
||||
|
||||
```js
|
||||
var acme = ACME.create({
|
||||
maintainerEmail: 'jon@example.com'
|
||||
});
|
||||
acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(
|
||||
function() {
|
||||
// Ready to use, show page
|
||||
$('body').hidden = false;
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Create ACME Account with Let's Encrypt
|
||||
|
||||
ACME Accounts are key and device based, with an email address as a backup identifier.
|
||||
|
||||
A public account key must be registered before an SSL certificate can be requested.
|
||||
|
||||
```js
|
||||
var accountPrivateJwk;
|
||||
var account;
|
||||
|
||||
Keypairs.generate({ kty: 'EC' }).then(function(pair) {
|
||||
accountPrivateJwk = pair.private;
|
||||
|
||||
return acme.accounts
|
||||
.create({
|
||||
agreeToTerms: function(tos) {
|
||||
if (
|
||||
window.confirm(
|
||||
"Do you agree to the ACME.js and Let's Encrypt Terms of Service?"
|
||||
)
|
||||
) {
|
||||
return Promise.resolve(tos);
|
||||
}
|
||||
},
|
||||
accountKey: pair.private,
|
||||
subscriberEmail: $('.js-email-input').value
|
||||
})
|
||||
.then(function(_account) {
|
||||
account = _account;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Generate a Certificate Private Key
|
||||
|
||||
```js
|
||||
var certKeypair = await Keypairs.generate({ kty: 'RSA' });
|
||||
var pem = await Keypairs.export({
|
||||
jwk: certKeypair.private,
|
||||
encoding: 'pem'
|
||||
});
|
||||
|
||||
// Discover Directory URLs
|
||||
ACME.init(acmeDirectoryUrl); // returns Promise<acmeUrls={keyChange,meta,newAccount,newNonce,newOrder,revokeCert}>
|
||||
// This should be saved as `privkey.pem`
|
||||
console.log(pem);
|
||||
```
|
||||
|
||||
// Accounts
|
||||
ACME.accounts.create(options); // returns Promise<regr> registration data
|
||||
### Generate a CSR
|
||||
|
||||
options = {
|
||||
email: '<email>', // valid email (server checks MX records)
|
||||
accountKeypair: {
|
||||
// privateKeyPem or privateKeyJwt
|
||||
privateKeyPem: '<ASCII PEM>'
|
||||
},
|
||||
agreeToTerms: function(tosUrl) {} // should Promise the same `tosUrl` back
|
||||
};
|
||||
The easiest way to generate a Certificate Signing Request will be either with `openssl` or with `@root/CSR`.
|
||||
|
||||
// Registration
|
||||
ACME.certificates.create(options); // returns Promise<pems={ privkey (key), cert, chain (ca) }>
|
||||
```js
|
||||
var CSR = require('@root/csr');
|
||||
var Enc = require('@root/encoding');
|
||||
|
||||
options = {
|
||||
domainKeypair: {
|
||||
privateKeyPem: '<ASCII PEM>'
|
||||
},
|
||||
accountKeypair: {
|
||||
privateKeyPem: '<ASCII PEM>'
|
||||
},
|
||||
domains: ['example.com'],
|
||||
// 'subject' should be first in list
|
||||
// the domains may be in any order, but it should be consistent
|
||||
var sortedDomains = ['example.com', 'www.example.com'];
|
||||
var csr = await CSR.csr({
|
||||
jwk: certKeypair.private,
|
||||
domains: sortedDomains,
|
||||
encoding: 'der'
|
||||
}).then(function(der) {
|
||||
return Enc.bufToUrlBase64(der);
|
||||
});
|
||||
```
|
||||
|
||||
getZones: function(opts) {}, // should Promise an array of domain zone names
|
||||
setChallenge: function(opts) {}, // should Promise the record id, or name
|
||||
removeChallenge: function(opts) {} // should Promise null
|
||||
### Get Free 90-day SSL Certificate
|
||||
|
||||
Creating an ACME "order" for a 90-day SSL certificate requires use of the account private key,
|
||||
the names of domains to be secured, and a distinctly separate server private key.
|
||||
|
||||
A domain ownership verification "challenge" (uploading a file to an unsecured HTTP url or setting a DNS record)
|
||||
is a required part of the process, which requires `set` and `remove` callbacks/promises.
|
||||
|
||||
```js
|
||||
var certinfo = await acme.certificates.create({
|
||||
account: account,
|
||||
accountKey: accountPrivateJwk,
|
||||
csr: csr,
|
||||
domains: sortedDomains,
|
||||
challenges: challenges, // must be implemented
|
||||
customerEmail: null,
|
||||
skipChallengeTests: false,
|
||||
skipDryRun: false
|
||||
});
|
||||
|
||||
console.log('Got SSL Certificate:');
|
||||
console.log(results.expires);
|
||||
|
||||
// This should be saved as `fullchain.pem`
|
||||
console.log([results.cert, results.chain].join('\n'));
|
||||
```
|
||||
|
||||
### Example "Challenge" Implementation
|
||||
|
||||
Typically here you're just presenting some sort of dialog to the user to ask them to
|
||||
upload a file or set a DNS record.
|
||||
|
||||
It may be possible to do something fancy like using OAuth2 to login to Google Domanis
|
||||
to set a DNS address, etc, but it seems like that sort of fanciness is probably best
|
||||
reserved for server-side plugins.
|
||||
|
||||
```js
|
||||
var challenges = {
|
||||
'http-01': {
|
||||
set: function(opts) {
|
||||
console.info('http-01 set challenge:');
|
||||
console.info(opts.challengeUrl);
|
||||
console.info(opts.keyAuthorization);
|
||||
while (
|
||||
!window.confirm('Upload the challenge file before continuing.')
|
||||
) {
|
||||
// spin and wait for the user to upload the challenge file
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
remove: function(opts) {
|
||||
console.log('http-01 remove challenge:', opts.challengeUrl);
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# Changelog
|
||||
Many challenge plugins are already available for popular platforms.
|
||||
|
||||
- v1.8
|
||||
- more transitional prepwork for new v2 API
|
||||
- support newer (simpler) dns-01 and http-01 libraries
|
||||
- v1.5
|
||||
- perform full test challenge first (even before nonce)
|
||||
- v1.3
|
||||
- Use node RSA keygen by default
|
||||
- No non-optional external deps!
|
||||
- v1.2
|
||||
- fix some API out-of-specness
|
||||
- doc some magic numbers (status)
|
||||
- updated deps
|
||||
- v1.1.0
|
||||
- reduce dependencies (use lightweight @coolaj86/request instead of request)
|
||||
- v1.0.5 - cleanup logging
|
||||
- v1.0.4 - v6- compat use `promisify` from node's util or bluebird
|
||||
- v1.0.3 - documentation cleanup
|
||||
- v1.0.2
|
||||
- use `options.contact` to provide raw contact array
|
||||
- made `options.email` optional
|
||||
- file cleanup
|
||||
- v1.0.1
|
||||
- Compat API is ready for use
|
||||
- Eliminate debug logging
|
||||
- Apr 10, 2018 - tested backwards-compatibility using greenlock.js
|
||||
- Apr 5, 2018 - export http and dns challenge tests
|
||||
- Apr 5, 2018 - test http and dns challenges (success and failure)
|
||||
- Apr 5, 2018 - test subdomains and its wildcard
|
||||
- Apr 5, 2018 - test two subdomains
|
||||
- Apr 5, 2018 - test wildcard
|
||||
- Apr 5, 2018 - completely match api for acme v1 (le-acme-core.js)
|
||||
- Mar 21, 2018 - _mostly_ matches le-acme-core.js API
|
||||
- Mar 21, 2018 - can now accept values (not hard coded)
|
||||
- Mar 20, 2018 - SUCCESS - got a test certificate (hard-coded)
|
||||
- Mar 20, 2018 - download certificate
|
||||
- Mar 20, 2018 - poll for status
|
||||
- Mar 20, 2018 - finalize order (submit csr)
|
||||
- Mar 20, 2018 - generate domain keypair
|
||||
- Mar 20, 2018 - respond to challenges
|
||||
- Mar 16, 2018 - get challenges
|
||||
- Mar 16, 2018 - new order
|
||||
- Mar 15, 2018 - create account
|
||||
- Mar 15, 2018 - generate account keypair
|
||||
- Mar 15, 2018 - get nonce
|
||||
- Mar 15, 2018 - get directory
|
||||
Search `acme-http-01-` or `acme-dns-01-` on npm to find more.
|
||||
|
||||
# Legal
|
||||
- [x] DNS-01 Challenges
|
||||
- CloudFlare
|
||||
- [Digital Ocean](https://git.rootprojects.org/root/acme-dns-01-digitalocean.js)
|
||||
- [DNSimple](https://git.rootprojects.org/root/acme-dns-01-dnsimple.js)
|
||||
- [DuckDNS](https://git.rootprojects.org/root/acme-dns-01-duckdns.js)
|
||||
- [GoDaddy](https://git.rootprojects.org/root/acme-dns-01-godaddy.js)
|
||||
- [Gandi](https://git.rootprojects.org/root/acme-dns-01-gandi.js)
|
||||
- [NameCheap](https://git.rootprojects.org/root/acme-dns-01-namecheap.js)
|
||||
- [Name.com](https://git.rootprojects.org/root/acme-dns-01-namedotcom.js)
|
||||
- Route53 (AWS)
|
||||
- [Vultr](https://git.rootprojects.org/root/acme-dns-01-vultr.js)
|
||||
- Build your own
|
||||
- [x] HTTP-01 Challenges
|
||||
- [In-Memory](https://git.rootprojects.org/root/acme-http-01-standalone.js) (Standalone)
|
||||
- [FileSystem](https://git.rootprojects.org/root/acme-http-01-webroot.js) (WebRoot)
|
||||
- S3 (AWS, Digital Ocean, etc)
|
||||
- [x] TLS-ALPN-01 Challenges
|
||||
- Contact us to learn about Greenlock Pro
|
||||
|
||||
[acme-v2.js](https://git.coolaj86.com/coolaj86/acme-v2.js) |
|
||||
# IDN - International Domain Names
|
||||
|
||||
Convert domain names to `punycode` before creating the certificate:
|
||||
|
||||
```js
|
||||
var punycode = require('punycode');
|
||||
|
||||
acme.certificates.create({
|
||||
// ...
|
||||
domains: ['example.com', 'www.example.com'].map(function(name) {
|
||||
return punycode.toASCII(name);
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
The punycode library itself is lightweight and dependency-free.
|
||||
It is available both in node and for browsers.
|
||||
|
||||
# Testing
|
||||
|
||||
You will need to use one of the [`acme-dns-01-*` plugins](https://www.npmjs.com/search?q=acme-dns-01-)
|
||||
to run the test locally.
|
||||
|
||||
You'll also need a `.env` that looks something like the one in `examples/example.env`:
|
||||
|
||||
```bash
|
||||
ENV=DEV
|
||||
SUBSCRIBER_EMAIL=letsencrypt+staging@example.com
|
||||
BASE_DOMAIN=test.example.com
|
||||
CHALLENGE_TYPE=dns-01
|
||||
CHALLENGE_PLUGIN=acme-dns-01-digitalocean
|
||||
CHALLENGE_OPTIONS='{"token":"xxxxxxxxxxxx"}'
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
# Get the repo and change directories into it
|
||||
git clone https://git.rootprojects.org/root/acme.js
|
||||
pushd acme.js/
|
||||
|
||||
# Install the challenge plugin you'll use for the tests
|
||||
npm install --save-dev acme-dns-01-digitalocean
|
||||
|
||||
# Copy the sample .env file
|
||||
rsync -av examples/example.env .env
|
||||
|
||||
# Edit the config file to use a domain in your account, and your API token
|
||||
#vim .env
|
||||
code .env
|
||||
|
||||
# Run the tests
|
||||
node tests/index.js
|
||||
```
|
||||
|
||||
# Developing
|
||||
|
||||
You can see `<script>` tags in the `index.html` in the repo, which references the original
|
||||
source files.
|
||||
|
||||
Join `@rootprojects` `#general` on [Keybase](https://keybase.io) if you'd like to chat with us.
|
||||
|
||||
# Commercial Support
|
||||
|
||||
We have both commercial support and commercial licensing available.
|
||||
|
||||
You're welcome to [contact us](mailto:aj@therootcompany.com) in regards to IoT, On-Prem,
|
||||
Enterprise, and Internal installations, integrations, and deployments.
|
||||
|
||||
We also offer consulting for all-things-ACME and Let's Encrypt.
|
||||
|
||||
# Legal & Rules of the Road
|
||||
|
||||
Greenlock™ is a [trademark](https://rootprojects.org/legal/#trademark) of AJ ONeal
|
||||
|
||||
The rule of thumb is "attribute, but don't confuse". For example:
|
||||
|
||||
> Built with [ACME.js](https://git.rootprojects.org/root/acme.js) (a [Root](https://rootprojects.org) project).
|
||||
|
||||
Please [contact us](mailto:aj@therootcompany.com) if have any questions in regards to our trademark,
|
||||
attribution, and/or visible source policies. We want to build great software and a great community.
|
||||
|
||||
[ACME.js](https://git.rootprojects.org/root/acme.js) |
|
||||
MPL-2.0 |
|
||||
[Terms of Use](https://therootcompany.com/legal/#terms) |
|
||||
[Privacy Policy](https://therootcompany.com/legal/#privacy)
|
||||
|
148
account.js
Normal file
148
account.js
Normal file
@ -0,0 +1,148 @@
|
||||
'use strict';
|
||||
|
||||
var A = module.exports;
|
||||
var U = require('./utils.js');
|
||||
|
||||
var Keypairs = require('@root/keypairs');
|
||||
var Enc = require('@root/encoding/bytes');
|
||||
|
||||
A._getAccountKid = function(me, options) {
|
||||
// It's just fine if there's no account, we'll go get the key id we need via the existing key
|
||||
var kid =
|
||||
options.kid ||
|
||||
(options.account && (options.account.key && options.account.key.kid));
|
||||
|
||||
if (kid) {
|
||||
return Promise.resolve(kid);
|
||||
}
|
||||
|
||||
//return Promise.reject(new Error("must include KeyID"));
|
||||
// This is an idempotent request. It'll return the same account for the same public key.
|
||||
return A._registerAccount(me, options).then(function(account) {
|
||||
return account.key.kid;
|
||||
});
|
||||
};
|
||||
|
||||
// ACME RFC Section 7.3 Account Creation
|
||||
/*
|
||||
{
|
||||
"protected": base64url({
|
||||
"alg": "ES256",
|
||||
"jwk": {...},
|
||||
"nonce": "6S8IqOGY7eL2lsGoTZYifg",
|
||||
"url": "https://example.com/acme/new-account"
|
||||
}),
|
||||
"payload": base64url({
|
||||
"termsOfServiceAgreed": true,
|
||||
"onlyReturnExisting": false,
|
||||
"contact": [
|
||||
"mailto:cert-admin@example.com",
|
||||
"mailto:admin@example.com"
|
||||
]
|
||||
}),
|
||||
"signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I"
|
||||
}
|
||||
*/
|
||||
A._registerAccount = function(me, options) {
|
||||
//#console.debug('[ACME.js] accounts.create');
|
||||
|
||||
function agree(tosUrl) {
|
||||
var err;
|
||||
if (me._tos !== tosUrl) {
|
||||
err = new Error("must agree to '" + tosUrl + "'");
|
||||
err.code = 'E_AGREE_TOS';
|
||||
throw err;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function getAccount() {
|
||||
return U._importKeypair(options.accountKey).then(function(pair) {
|
||||
var contact;
|
||||
if (options.contact) {
|
||||
contact = options.contact.slice(0);
|
||||
} else if (options.subscriberEmail) {
|
||||
contact = ['mailto:' + options.subscriberEmail];
|
||||
}
|
||||
|
||||
var accountRequest = {
|
||||
termsOfServiceAgreed: true,
|
||||
onlyReturnExisting: false,
|
||||
contact: contact
|
||||
};
|
||||
|
||||
var pub = pair.public;
|
||||
return attachExtAcc(pub, accountRequest).then(function(accReq) {
|
||||
var payload = JSON.stringify(accReq);
|
||||
return U._jwsRequest(me, {
|
||||
accountKey: options.accountKey,
|
||||
url: me._directoryUrls.newAccount,
|
||||
protected: { kid: false, jwk: pair.public },
|
||||
payload: Enc.strToBuf(payload)
|
||||
}).then(function(resp) {
|
||||
var account = resp.body;
|
||||
|
||||
if (resp.statusCode < 200 || resp.statusCode >= 300) {
|
||||
if ('string' !== typeof account) {
|
||||
account = JSON.stringify(account);
|
||||
}
|
||||
throw new Error(
|
||||
'account error: ' +
|
||||
resp.statusCode +
|
||||
' ' +
|
||||
account +
|
||||
'\n' +
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
// the account id url is the "kid"
|
||||
var kid = resp.headers.location;
|
||||
if (!account) {
|
||||
account = { _emptyResponse: true };
|
||||
}
|
||||
if (!account.key) {
|
||||
account.key = {};
|
||||
}
|
||||
account.key.kid = kid;
|
||||
return account;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// for external accounts (probably useless, but spec'd)
|
||||
function attachExtAcc(pubkey, accountRequest) {
|
||||
if (!options.externalAccount) {
|
||||
return Promise.resolve(accountRequest);
|
||||
}
|
||||
|
||||
return Keypairs.signJws({
|
||||
// TODO is HMAC the standard, or is this arbitrary?
|
||||
secret: options.externalAccount.secret,
|
||||
protected: {
|
||||
alg: options.externalAccount.alg || 'HS256',
|
||||
kid: options.externalAccount.id,
|
||||
url: me._directoryUrls.newAccount
|
||||
},
|
||||
payload: Enc.strToBuf(JSON.stringify(pubkey))
|
||||
}).then(function(jws) {
|
||||
accountRequest.externalAccountBinding = jws;
|
||||
return accountRequest;
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
.then(function() {
|
||||
//#console.debug('[ACME.js] agreeToTerms');
|
||||
var agreeToTerms = options.agreeToTerms;
|
||||
if (true === agreeToTerms) {
|
||||
agreeToTerms = function(tos) {
|
||||
return tos;
|
||||
};
|
||||
}
|
||||
return agreeToTerms(me._tos);
|
||||
})
|
||||
.then(agree)
|
||||
.then(getAccount);
|
||||
};
|
60
bin/bundle.js
Normal file
60
bin/bundle.js
Normal file
@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env node
|
||||
(async function() {
|
||||
'use strict';
|
||||
|
||||
var UglifyJS = require('uglify-js');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var promisify = require('util').promisify;
|
||||
var readFile = promisify(fs.readFile);
|
||||
var writeFile = promisify(fs.writeFile);
|
||||
var gzip = promisify(require('zlib').gzip);
|
||||
|
||||
// The order is specific, and it matters
|
||||
var files = await Promise.all(
|
||||
[
|
||||
'../lib/encoding.js',
|
||||
'../lib/asn1-packer.js',
|
||||
'../lib/x509.js',
|
||||
'../lib/ecdsa.js',
|
||||
'../lib/rsa.js',
|
||||
'../lib/keypairs.js',
|
||||
'../lib/asn1-parser.js',
|
||||
'../lib/csr.js',
|
||||
'../lib/acme.js'
|
||||
].map(async function(file) {
|
||||
return (await readFile(path.join(__dirname, file), 'utf8')).trim();
|
||||
})
|
||||
);
|
||||
|
||||
var header =
|
||||
[
|
||||
'// Copyright 2015-2019 AJ ONeal. All rights reserved',
|
||||
'/* This Source Code Form is subject to the terms of the Mozilla Public',
|
||||
' * License, v. 2.0. If a copy of the MPL was not distributed with this',
|
||||
' * file, You can obtain one at http://mozilla.org/MPL/2.0/. */'
|
||||
].join('\n') + '\n';
|
||||
|
||||
var file = header + files.join('\n') + '\n';
|
||||
await writeFile(path.join(__dirname, '../dist', 'acme.js'), file);
|
||||
await writeFile(
|
||||
path.join(__dirname, '../dist', 'acme.js.gz'),
|
||||
await gzip(file)
|
||||
);
|
||||
|
||||
// TODO source maps?
|
||||
var result = UglifyJS.minify(file, {
|
||||
compress: true,
|
||||
// mangling doesn't save significant
|
||||
mangle: false
|
||||
});
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
file = header + result.code;
|
||||
await writeFile(path.join(__dirname, '../dist', 'acme.min.js'), file);
|
||||
await writeFile(
|
||||
path.join(__dirname, '../dist', 'acme.min.js.gz'),
|
||||
await gzip(file)
|
||||
);
|
||||
})();
|
94
compat.js
94
compat.js
@ -1,94 +0,0 @@
|
||||
// Copyright 2018 AJ ONeal. All rights reserved
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
/* global Promise */
|
||||
|
||||
var ACME2 = require('./').ACME;
|
||||
|
||||
function resolveFn(cb) {
|
||||
return function(val) {
|
||||
// nextTick to get out of Promise chain
|
||||
process.nextTick(function() {
|
||||
cb(null, val);
|
||||
});
|
||||
};
|
||||
}
|
||||
function rejectFn(cb) {
|
||||
return function(err) {
|
||||
console.error('[acme-v2] handled(?) rejection as errback:');
|
||||
console.error(err.stack);
|
||||
|
||||
// nextTick to get out of Promise chain
|
||||
process.nextTick(function() {
|
||||
cb(err);
|
||||
});
|
||||
|
||||
// do not resolve promise further
|
||||
return new Promise(function() {});
|
||||
};
|
||||
}
|
||||
|
||||
function create(deps) {
|
||||
deps.LeCore = {};
|
||||
var acme2 = ACME2.create(deps);
|
||||
acme2.registerNewAccount = function(options, cb) {
|
||||
acme2.accounts.create(options).then(resolveFn(cb), rejectFn(cb));
|
||||
};
|
||||
acme2.getCertificate = function(options, cb) {
|
||||
options.agreeToTerms =
|
||||
options.agreeToTerms ||
|
||||
function(tos) {
|
||||
return Promise.resolve(tos);
|
||||
};
|
||||
acme2.certificates.create(options).then(function(certs) {
|
||||
var privkeyPem = acme2.RSA.exportPrivatePem(options.domainKeypair);
|
||||
certs.privkey = privkeyPem;
|
||||
resolveFn(cb)(certs);
|
||||
}, rejectFn(cb));
|
||||
};
|
||||
acme2.getAcmeUrls = function(options, cb) {
|
||||
acme2.init(options).then(resolveFn(cb), rejectFn(cb));
|
||||
};
|
||||
acme2.getOptions = function() {
|
||||
var defs = {};
|
||||
|
||||
Object.keys(module.exports.defaults).forEach(function(key) {
|
||||
defs[key] = defs[deps] || module.exports.defaults[key];
|
||||
});
|
||||
|
||||
return defs;
|
||||
};
|
||||
acme2.stagingServerUrl = module.exports.defaults.stagingServerUrl;
|
||||
acme2.productionServerUrl = module.exports.defaults.productionServerUrl;
|
||||
acme2.acmeChallengePrefix = module.exports.defaults.acmeChallengePrefix;
|
||||
return acme2;
|
||||
}
|
||||
|
||||
module.exports.ACME = {};
|
||||
module.exports.defaults = {
|
||||
productionServerUrl: 'https://acme-v02.api.letsencrypt.org/directory',
|
||||
stagingServerUrl: 'https://acme-staging-v02.api.letsencrypt.org/directory',
|
||||
knownEndpoints: [
|
||||
'keyChange',
|
||||
'meta',
|
||||
'newAccount',
|
||||
'newNonce',
|
||||
'newOrder',
|
||||
'revokeCert'
|
||||
],
|
||||
challengeTypes: ['http-01', 'dns-01'],
|
||||
challengeType: 'http-01',
|
||||
//, keyType: 'rsa' // ecdsa
|
||||
//, keySize: 2048 // 256
|
||||
rsaKeySize: 2048, // 256
|
||||
acmeChallengePrefix: '/.well-known/acme-challenge/'
|
||||
};
|
||||
Object.keys(module.exports.defaults).forEach(function(key) {
|
||||
module.exports.ACME[key] = module.exports.defaults[key];
|
||||
});
|
||||
Object.keys(ACME2).forEach(function(key) {
|
||||
module.exports.ACME[key] = ACME2[key];
|
||||
});
|
||||
module.exports.ACME.create = create;
|
81
errors.js
Normal file
81
errors.js
Normal file
@ -0,0 +1,81 @@
|
||||
'use strict';
|
||||
|
||||
var E = module.exports;
|
||||
|
||||
E.NO_SUITABLE_CHALLENGE = function(domain, challenges, presenters) {
|
||||
// Bail with a descriptive message if no usable challenge could be selected
|
||||
// For example, wildcards require dns-01 and, if we don't have that, we have to bail
|
||||
var enabled = presenters.join(', ') || 'none';
|
||||
var suitable =
|
||||
challenges
|
||||
.map(function(r) {
|
||||
return r.type;
|
||||
})
|
||||
.join(', ') || 'none';
|
||||
return new Error(
|
||||
"None of the challenge types that you've enabled ( " +
|
||||
enabled +
|
||||
' )' +
|
||||
" are suitable for validating the domain you've selected (" +
|
||||
domain +
|
||||
').' +
|
||||
' You must enable one of ( ' +
|
||||
suitable +
|
||||
' ).'
|
||||
);
|
||||
};
|
||||
E.UNHANDLED_ORDER_STATUS = function(options, domains, resp) {
|
||||
return new Error(
|
||||
"Didn't finalize order: Unhandled status '" +
|
||||
resp.body.status +
|
||||
"'." +
|
||||
' This is not one of the known statuses...\n' +
|
||||
"Requested: '" +
|
||||
options.domains.join(', ') +
|
||||
"'\n" +
|
||||
"Validated: '" +
|
||||
domains.join(', ') +
|
||||
"'\n" +
|
||||
JSON.stringify(resp.body, null, 2) +
|
||||
'\n\n' +
|
||||
'Please open an issue at https://git.rootprojects.org/root/acme.js'
|
||||
);
|
||||
};
|
||||
E.DOUBLE_READY_ORDER = function(options, domains, resp) {
|
||||
return new Error(
|
||||
"Did not finalize order: status 'ready'." +
|
||||
" Hmmm... this state shouldn't be possible here. That was the last state." +
|
||||
" This one should at least be 'processing'.\n" +
|
||||
"Requested: '" +
|
||||
options.domains.join(', ') +
|
||||
"'\n" +
|
||||
"Validated: '" +
|
||||
domains.join(', ') +
|
||||
"'\n" +
|
||||
JSON.stringify(resp.body, null, 2) +
|
||||
'\n\n' +
|
||||
'Please open an issue at https://git.rootprojects.org/root/acme.js'
|
||||
);
|
||||
};
|
||||
E.ORDER_INVALID = function(options, domains, resp) {
|
||||
return new Error(
|
||||
"Did not finalize order: status 'invalid'." +
|
||||
' Best guess: One or more of the domain challenges could not be verified' +
|
||||
' (or the order was canceled).\n' +
|
||||
"Requested: '" +
|
||||
options.domains.join(', ') +
|
||||
"'\n" +
|
||||
"Validated: '" +
|
||||
domains.join(', ') +
|
||||
"'\n" +
|
||||
JSON.stringify(resp.body, null, 2)
|
||||
);
|
||||
};
|
||||
E.NO_AUTHORIZATIONS = function(options, resp) {
|
||||
return new Error(
|
||||
"[acme-v2.js] authorizations were not fetched for '" +
|
||||
options.domains.join() +
|
||||
"':\n" +
|
||||
JSON.stringify(resp.body)
|
||||
);
|
||||
};
|
340
examples/app.js
Normal file
340
examples/app.js
Normal file
@ -0,0 +1,340 @@
|
||||
/*global Promise*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var Keypairs = require('@root/keypairs');
|
||||
var Rasha = require('@root/acme/rsa');
|
||||
var Eckles = require('@root/acme/ecdsa');
|
||||
var x509 = require('@root/acme/x509');
|
||||
var CSR = require('@root/csr');
|
||||
var ACME = require('@root/acme');
|
||||
var accountStuff = {};
|
||||
|
||||
function $(sel) {
|
||||
return document.querySelector(sel);
|
||||
}
|
||||
function $$(sel) {
|
||||
return Array.prototype.slice.call(document.querySelectorAll(sel));
|
||||
}
|
||||
|
||||
function checkTos(tos) {
|
||||
if ($('input[name="tos"]:checked')) {
|
||||
return tos;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function run() {
|
||||
console.log('hello');
|
||||
|
||||
// Show different options for ECDSA vs RSA
|
||||
$$('input[name="kty"]').forEach(function($el) {
|
||||
$el.addEventListener('change', function(ev) {
|
||||
console.log(this);
|
||||
console.log(ev);
|
||||
if ('RSA' === ev.target.value) {
|
||||
$('.js-rsa-opts').hidden = false;
|
||||
$('.js-ec-opts').hidden = true;
|
||||
} else {
|
||||
$('.js-rsa-opts').hidden = true;
|
||||
$('.js-ec-opts').hidden = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Generate a key on submit
|
||||
$('form.js-keygen').addEventListener('submit', function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
$('.js-loading').hidden = false;
|
||||
$('.js-jwk').hidden = true;
|
||||
$('.js-toc-der-public').hidden = true;
|
||||
$('.js-toc-der-private').hidden = true;
|
||||
$$('.js-toc-pem').forEach(function($el) {
|
||||
$el.hidden = true;
|
||||
});
|
||||
$$('input').map(function($el) {
|
||||
$el.disabled = true;
|
||||
});
|
||||
$$('button').map(function($el) {
|
||||
$el.disabled = true;
|
||||
});
|
||||
var opts = {
|
||||
kty: $('input[name="kty"]:checked').value,
|
||||
namedCurve: $('input[name="ec-crv"]:checked').value,
|
||||
modulusLength: $('input[name="rsa-len"]:checked').value
|
||||
};
|
||||
var then = Date.now();
|
||||
console.log('opts', opts);
|
||||
Keypairs.generate(opts).then(function(results) {
|
||||
console.log('Key generation time:', Date.now() - then + 'ms');
|
||||
var pubDer;
|
||||
var privDer;
|
||||
if (/EC/i.test(opts.kty)) {
|
||||
privDer = x509.packPkcs8(results.private);
|
||||
pubDer = x509.packSpki(results.public);
|
||||
Eckles.export({
|
||||
jwk: results.private,
|
||||
format: 'sec1'
|
||||
}).then(function(pem) {
|
||||
$('.js-input-pem-sec1-private').innerText = pem;
|
||||
$('.js-toc-pem-sec1-private').hidden = false;
|
||||
});
|
||||
Eckles.export({
|
||||
jwk: results.private,
|
||||
format: 'pkcs8'
|
||||
}).then(function(pem) {
|
||||
$('.js-input-pem-pkcs8-private').innerText = pem;
|
||||
$('.js-toc-pem-pkcs8-private').hidden = false;
|
||||
});
|
||||
Eckles.export({ jwk: results.public, public: true }).then(
|
||||
function(pem) {
|
||||
$('.js-input-pem-spki-public').innerText = pem;
|
||||
$('.js-toc-pem-spki-public').hidden = false;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
privDer = x509.packPkcs8(results.private);
|
||||
pubDer = x509.packSpki(results.public);
|
||||
Rasha.export({
|
||||
jwk: results.private,
|
||||
format: 'pkcs1'
|
||||
}).then(function(pem) {
|
||||
$('.js-input-pem-pkcs1-private').innerText = pem;
|
||||
$('.js-toc-pem-pkcs1-private').hidden = false;
|
||||
});
|
||||
Rasha.export({
|
||||
jwk: results.private,
|
||||
format: 'pkcs8'
|
||||
}).then(function(pem) {
|
||||
$('.js-input-pem-pkcs8-private').innerText = pem;
|
||||
$('.js-toc-pem-pkcs8-private').hidden = false;
|
||||
});
|
||||
Rasha.export({ jwk: results.public, format: 'pkcs1' }).then(
|
||||
function(pem) {
|
||||
$('.js-input-pem-pkcs1-public').innerText = pem;
|
||||
$('.js-toc-pem-pkcs1-public').hidden = false;
|
||||
}
|
||||
);
|
||||
Rasha.export({ jwk: results.public, format: 'spki' }).then(
|
||||
function(pem) {
|
||||
$('.js-input-pem-spki-public').innerText = pem;
|
||||
$('.js-toc-pem-spki-public').hidden = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$('.js-der-public').innerText = pubDer;
|
||||
$('.js-toc-der-public').hidden = false;
|
||||
$('.js-der-private').innerText = privDer;
|
||||
$('.js-toc-der-private').hidden = false;
|
||||
$('.js-jwk').innerText = JSON.stringify(results, null, 2);
|
||||
$('.js-loading').hidden = true;
|
||||
$('.js-jwk').hidden = false;
|
||||
$$('input').map(function($el) {
|
||||
$el.disabled = false;
|
||||
});
|
||||
$$('button').map(function($el) {
|
||||
$el.disabled = false;
|
||||
});
|
||||
$('.js-toc-jwk').hidden = false;
|
||||
|
||||
$('.js-create-account').hidden = false;
|
||||
$('.js-create-csr').hidden = false;
|
||||
});
|
||||
});
|
||||
|
||||
$('form.js-acme-account').addEventListener('submit', function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
$('.js-loading').hidden = false;
|
||||
var acme = ACME.create({
|
||||
Keypairs: Keypairs,
|
||||
CSR: CSR
|
||||
});
|
||||
acme.init(
|
||||
'https://acme-staging-v02.api.letsencrypt.org/directory'
|
||||
).then(function(result) {
|
||||
console.log('acme result', result);
|
||||
var privJwk = JSON.parse($('.js-jwk').innerText).private;
|
||||
var email = $('.js-email').value;
|
||||
return acme.accounts
|
||||
.create({
|
||||
email: email,
|
||||
agreeToTerms: checkTos,
|
||||
accountKeypair: { privateKeyJwk: privJwk }
|
||||
})
|
||||
.then(function(account) {
|
||||
console.log('account created result:', account);
|
||||
accountStuff.account = account;
|
||||
accountStuff.privateJwk = privJwk;
|
||||
accountStuff.email = email;
|
||||
accountStuff.acme = acme;
|
||||
$('.js-create-order').hidden = false;
|
||||
$('.js-toc-acme-account-response').hidden = false;
|
||||
$(
|
||||
'.js-acme-account-response'
|
||||
).innerText = JSON.stringify(account, null, 2);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('A bad thing happened:');
|
||||
console.error(err);
|
||||
window.alert(
|
||||
err.message || JSON.stringify(err, null, 2)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('form.js-csr').addEventListener('submit', function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
generateCsr();
|
||||
});
|
||||
|
||||
$('form.js-acme-order').addEventListener('submit', function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var account = accountStuff.account;
|
||||
var privJwk = accountStuff.privateJwk;
|
||||
var email = accountStuff.email;
|
||||
var acme = accountStuff.acme;
|
||||
|
||||
var domains = ($('.js-domains').value || 'example.com').split(
|
||||
/[, ]+/g
|
||||
);
|
||||
return getDomainPrivkey().then(function(domainPrivJwk) {
|
||||
console.log('Has CSR already?');
|
||||
console.log(accountStuff.csr);
|
||||
return acme.certificates
|
||||
.create({
|
||||
accountKeypair: { privateKeyJwk: privJwk },
|
||||
account: account,
|
||||
serverKeypair: { privateKeyJwk: domainPrivJwk },
|
||||
csr: accountStuff.csr,
|
||||
domains: domains,
|
||||
skipDryRun:
|
||||
$('input[name="skip-dryrun"]:checked') && true,
|
||||
agreeToTerms: checkTos,
|
||||
challenges: {
|
||||
'dns-01': {
|
||||
set: function(opts) {
|
||||
console.info('dns-01 set challenge:');
|
||||
console.info('TXT', opts.dnsHost);
|
||||
console.info(opts.dnsAuthorization);
|
||||
return new Promise(function(resolve) {
|
||||
while (
|
||||
!window.confirm(
|
||||
'Did you set the challenge?'
|
||||
)
|
||||
) {}
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
remove: function(opts) {
|
||||
console.log('dns-01 remove challenge:');
|
||||
console.info('TXT', opts.dnsHost);
|
||||
console.info(opts.dnsAuthorization);
|
||||
return new Promise(function(resolve) {
|
||||
while (
|
||||
!window.confirm(
|
||||
'Did you delete the challenge?'
|
||||
)
|
||||
) {}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
},
|
||||
'http-01': {
|
||||
set: function(opts) {
|
||||
console.info('http-01 set challenge:');
|
||||
console.info(opts.challengeUrl);
|
||||
console.info(opts.keyAuthorization);
|
||||
return new Promise(function(resolve) {
|
||||
while (
|
||||
!window.confirm(
|
||||
'Did you set the challenge?'
|
||||
)
|
||||
) {}
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
remove: function(opts) {
|
||||
console.log('http-01 remove challenge:');
|
||||
console.info(opts.challengeUrl);
|
||||
console.info(opts.keyAuthorization);
|
||||
return new Promise(function(resolve) {
|
||||
while (
|
||||
!window.confirm(
|
||||
'Did you delete the challenge?'
|
||||
)
|
||||
) {}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
challengeTypes: [
|
||||
$('input[name="acme-challenge-type"]:checked').value
|
||||
]
|
||||
})
|
||||
.then(function(results) {
|
||||
console.log('Got Certificates:');
|
||||
console.log(results);
|
||||
$('.js-toc-acme-order-response').hidden = false;
|
||||
$('.js-acme-order-response').innerText = JSON.stringify(
|
||||
results,
|
||||
null,
|
||||
2
|
||||
);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('challenge failed:');
|
||||
console.error(err);
|
||||
window.alert(
|
||||
'failed! ' + err.message || JSON.stringify(err)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('.js-generate').hidden = false;
|
||||
}
|
||||
|
||||
function getDomainPrivkey() {
|
||||
if (accountStuff.domainPrivateJwk) {
|
||||
return Promise.resolve(accountStuff.domainPrivateJwk);
|
||||
}
|
||||
return Keypairs.generate({
|
||||
kty: $('input[name="kty"]:checked').value,
|
||||
namedCurve: $('input[name="ec-crv"]:checked').value,
|
||||
modulusLength: $('input[name="rsa-len"]:checked').value
|
||||
}).then(function(pair) {
|
||||
console.log('domain keypair:', pair);
|
||||
accountStuff.domainPrivateJwk = pair.private;
|
||||
return pair.private;
|
||||
});
|
||||
}
|
||||
|
||||
function generateCsr() {
|
||||
var domains = ($('.js-domains').value || 'example.com').split(/[, ]+/g);
|
||||
//var privJwk = JSON.parse($('.js-jwk').innerText).private;
|
||||
return getDomainPrivkey().then(function(privJwk) {
|
||||
accountStuff.domainPrivateJwk = privJwk;
|
||||
return CSR({ jwk: privJwk, domains: domains }).then(function(pem) {
|
||||
// Verify with https://www.sslshopper.com/csr-decoder.html
|
||||
accountStuff.csr = pem;
|
||||
console.log('Created CSR:');
|
||||
console.log(pem);
|
||||
|
||||
console.log('CSR info:');
|
||||
console.log(CSR._info(pem));
|
||||
|
||||
return pem;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('load', run);
|
||||
})();
|
131
examples/cli.js
131
examples/cli.js
@ -1,131 +0,0 @@
|
||||
// Copyright 2018 AJ ONeal. All rights reserved
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
|
||||
var RSA = require('rsa-compat').RSA;
|
||||
var readline = require('readline');
|
||||
var rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
require('./genkeypair.js');
|
||||
|
||||
function getWeb() {
|
||||
rl.question(
|
||||
'What web address(es) would you like to get certificates for? (ex: example.com,*.example.com) ',
|
||||
function(web) {
|
||||
web = (web || '').trim().split(/,/g);
|
||||
if (!web[0]) {
|
||||
getWeb();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
web.some(function(w) {
|
||||
return '*' === w[0];
|
||||
})
|
||||
) {
|
||||
console.log('Wildcard domains must use dns-01');
|
||||
getEmail(web, 'dns-01');
|
||||
} else {
|
||||
getChallengeType(web);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getChallengeType(web) {
|
||||
rl.question(
|
||||
'What challenge will you be testing today? http-01 or dns-01? [http-01] ',
|
||||
function(chType) {
|
||||
chType = (chType || '').trim();
|
||||
if (!chType) {
|
||||
chType = 'http-01';
|
||||
}
|
||||
|
||||
getEmail(web, chType);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getEmail(web, chType) {
|
||||
rl.question('What email should we use? (optional) ', function(email) {
|
||||
email = (email || '').trim();
|
||||
if (!email) {
|
||||
email = null;
|
||||
}
|
||||
|
||||
getApiStyle(web, chType, email);
|
||||
});
|
||||
}
|
||||
|
||||
function getApiStyle(web, chType, email) {
|
||||
var defaultStyle = 'compat';
|
||||
rl.question(
|
||||
'What API style would you like to test? v1-compat or promise? [v1-compat] ',
|
||||
function(apiStyle) {
|
||||
apiStyle = (apiStyle || '').trim();
|
||||
if (!apiStyle) {
|
||||
apiStyle = 'v1-compat';
|
||||
}
|
||||
|
||||
rl.close();
|
||||
|
||||
var RSA = require('rsa-compat').RSA;
|
||||
var accountKeypair = RSA.import({
|
||||
privateKeyPem: require('fs').readFileSync(
|
||||
__dirname + '/../tests/account.privkey.pem'
|
||||
)
|
||||
});
|
||||
var domainKeypair = RSA.import({
|
||||
privateKeyPem: require('fs').readFileSync(
|
||||
__dirname + '/../tests/privkey.pem'
|
||||
)
|
||||
});
|
||||
var directoryUrl =
|
||||
'https://acme-staging-v02.api.letsencrypt.org/directory';
|
||||
|
||||
if ('promise' === apiStyle) {
|
||||
require('../tests/promise.js').run(
|
||||
directoryUrl,
|
||||
RSA,
|
||||
web,
|
||||
chType,
|
||||
email,
|
||||
accountKeypair,
|
||||
domainKeypair
|
||||
);
|
||||
} else if ('cb' === apiStyle) {
|
||||
require('../tests/cb.js').run(
|
||||
directoryUrl,
|
||||
RSA,
|
||||
web,
|
||||
chType,
|
||||
email,
|
||||
accountKeypair,
|
||||
domainKeypair
|
||||
);
|
||||
} else {
|
||||
if ('v1-compat' !== apiStyle) {
|
||||
console.warn(
|
||||
"Didn't understand '" + apiStyle + "', using 'v1-compat' instead..."
|
||||
);
|
||||
}
|
||||
require('../tests/compat.js').run(
|
||||
directoryUrl,
|
||||
RSA,
|
||||
web,
|
||||
chType,
|
||||
email,
|
||||
accountKeypair,
|
||||
domainKeypair
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getWeb();
|
@ -1,69 +0,0 @@
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
// node[0] ./test.js[1] jon.doe@gmail.com[2] example.com,*.example.com[3] xxxxxx[4]
|
||||
var email = process.argv[2] || process.env.ACME_EMAIL;
|
||||
var domains = (process.argv[3] || process.env.ACME_DOMAINS).split(/[,\s]+/);
|
||||
var token = process.argv[4] || process.env.DIGITALOCEAN_API_KEY;
|
||||
|
||||
// git clone https://git.rootprojects.org/root/acme-dns-01-digitalocean.js node_modules/acme-dns-01-digitalocean
|
||||
var dns01 = require('acme-dns-01-digitalocean').create({
|
||||
//baseUrl: 'https://api.digitalocean.com/v2/domains',
|
||||
token: token
|
||||
});
|
||||
|
||||
// This will be replaced with Keypairs.js in the next version
|
||||
var promisify = require('util').promisify;
|
||||
var generateKeypair = promisify(require('rsa-compat').RSA.generateKeypair);
|
||||
|
||||
//var ACME = exports.ACME || require('acme').ACME;
|
||||
var ACME = exports.ACME || require('../').ACME;
|
||||
var acme = ACME.create({});
|
||||
acme
|
||||
.init({
|
||||
//directoryUrl: 'https://acme-staging-v02.api.letsencrypt.org/directory'
|
||||
})
|
||||
.then(function() {
|
||||
return generateKeypair(null).then(function(accountPair) {
|
||||
return generateKeypair(null).then(function(serverPair) {
|
||||
return acme.accounts
|
||||
.create({
|
||||
// valid email (server checks MX records)
|
||||
email: email,
|
||||
accountKeypair: accountPair,
|
||||
agreeToTerms: function(tosUrl) {
|
||||
// ask user (if user is the host)
|
||||
return tosUrl;
|
||||
}
|
||||
})
|
||||
.then(function(account) {
|
||||
console.info('Created Account:');
|
||||
console.info(account);
|
||||
|
||||
return acme.certificates
|
||||
.create({
|
||||
domains: domains,
|
||||
challenges: { 'dns-01': dns01 },
|
||||
domainKeypair: serverPair,
|
||||
accountKeypair: accountPair,
|
||||
|
||||
// v2 will be directly compatible with the new ACME modules,
|
||||
// whereas this version needs a shim
|
||||
getZones: dns01.zones,
|
||||
setChallenge: dns01.set,
|
||||
removeChallenge: dns01.remove
|
||||
})
|
||||
.then(function(certs) {
|
||||
console.info('Secured SSL Certificates');
|
||||
console.info(certs);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function(e) {
|
||||
console.error('Something went wrong:');
|
||||
console.error(e);
|
||||
process.exit(500);
|
||||
});
|
||||
})('undefined' === typeof module ? window : module.exports);
|
@ -1,3 +1,6 @@
|
||||
ACME_EMAIL=jon.doe@gmail.com
|
||||
ACME_DOMAINS=example.com,foo.example.com,*.foo.example.com
|
||||
DIGITALOCEAN_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
ENV=DEV
|
||||
SUBSCRIBER_EMAIL=letsencrypt+staging@example.com
|
||||
BASE_DOMAIN=test.example.com
|
||||
CHALLENGE_TYPE=dns-01
|
||||
CHALLENGE_PLUGIN=acme-dns-01-digitalocean
|
||||
CHALLENGE_OPTIONS='{"token":"xxxxxxxxxxxx"}'
|
||||
|
@ -1,26 +0,0 @@
|
||||
// Copyright 2018 AJ ONeal. All rights reserved
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
var RSA = require('rsa-compat').RSA;
|
||||
var fs = require('fs');
|
||||
|
||||
if (!fs.existsSync(__dirname + '/../tests/account.privkey.pem')) {
|
||||
RSA.generateKeypair(2048, 65537, {}, function(err, keypair) {
|
||||
console.log(keypair);
|
||||
var privkeyPem = RSA.exportPrivatePem(keypair);
|
||||
console.log(privkeyPem);
|
||||
|
||||
fs.writeFileSync(__dirname + '/../tests/account.privkey.pem', privkeyPem);
|
||||
});
|
||||
}
|
||||
|
||||
if (!fs.existsSync(__dirname + '/../tests/privkey.pem')) {
|
||||
RSA.generateKeypair(2048, 65537, {}, function(err, keypair) {
|
||||
console.log(keypair);
|
||||
var privkeyPem = RSA.exportPrivatePem(keypair);
|
||||
console.log(privkeyPem);
|
||||
|
||||
fs.writeFileSync(__dirname + '/../tests/privkey.pem', privkeyPem);
|
||||
});
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
// Copyright 2018 AJ ONeal. All rights reserved
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
|
||||
var http = require('http');
|
||||
var express = require('express');
|
||||
var server = http
|
||||
.createServer(express.static('../tests'))
|
||||
.listen(80, function() {
|
||||
console.log('Listening on', this.address());
|
||||
});
|
@ -1,20 +0,0 @@
|
||||
// Copyright 2018 AJ ONeal. All rights reserved
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
|
||||
var https = require('https');
|
||||
var server = https
|
||||
.createServer(
|
||||
{
|
||||
key: require('fs').readFileSync('../tests/privkey.pem'),
|
||||
cert: require('fs').readFileSync('../tests/fullchain.pem')
|
||||
},
|
||||
function(req, res) {
|
||||
res.end('Hello, World!');
|
||||
}
|
||||
)
|
||||
.listen(443, function() {
|
||||
console.log('Listening on', this.address());
|
||||
});
|
228
examples/index.html
Normal file
228
examples/index.html
Normal file
@ -0,0 +1,228 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>ACME.js - A Root Project</title>
|
||||
<meta charset="UTF-8" />
|
||||
<style>
|
||||
textarea {
|
||||
width: 42em;
|
||||
height: 10em;
|
||||
}
|
||||
/* need to word wrap the binary no space der */
|
||||
.js-der-public,
|
||||
.js-der-private {
|
||||
white-space: pre-wrap; /* CSS3 */
|
||||
white-space: -moz-pre-wrap; /* Firefox */
|
||||
white-space: -pre-wrap; /* Opera <7 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* IE */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>
|
||||
@root/acme: Let's Encrypt for the Browser
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
This is intended to be explored with your JavaScript console open.
|
||||
</p>
|
||||
<pre><code><script src="<a href="https://unpkg.com/@root/acme@3.0.0/dist/acme.js">https://unpkg.com/@root/acme@3.0.0/dist/acme.js</a>"></script></code></pre>
|
||||
<pre><code><script src="<a href="https://unpkg.com/@root/acme@3.0.0/dist/acme.min.js">https://unpkg.com/@root/acme@3.0.0/dist/acme.min.js</a>"></script></code></pre>
|
||||
<a href="https://git.rootprojects.org/root/acme.js">Documentation</a>
|
||||
|
||||
<h2>1. Keypair Generation</h2>
|
||||
<form class="js-keygen">
|
||||
<p>Key Type:</p>
|
||||
<div>
|
||||
<input type="radio" id="-ktyEC" name="kty" value="EC" checked />
|
||||
<label for="-ktyEC">ECDSA</label>
|
||||
<input type="radio" id="-ktyRSA" name="kty" value="RSA" />
|
||||
<label for="-ktyRSA">RSA</label>
|
||||
</div>
|
||||
<div class="js-ec-opts">
|
||||
<p>EC Options:</p>
|
||||
<label for="-crv2"
|
||||
><input
|
||||
type="radio"
|
||||
id="-crv2"
|
||||
name="ec-crv"
|
||||
value="P-256"
|
||||
checked
|
||||
/>P-256</label
|
||||
>
|
||||
<label for="-crv3"
|
||||
><input
|
||||
type="radio"
|
||||
id="-crv3"
|
||||
name="ec-crv"
|
||||
value="P-384"
|
||||
/>P-384</label
|
||||
>
|
||||
<!-- label for="-crv5"><input type="radio" id="-crv5"
|
||||
name="ec-crv" value="P-521">P-521</label -->
|
||||
</div>
|
||||
<div class="js-rsa-opts" hidden>
|
||||
<p>RSA Options:</p>
|
||||
<label for="-modlen2"
|
||||
><input
|
||||
type="radio"
|
||||
id="-modlen2"
|
||||
name="rsa-len"
|
||||
value="2048"
|
||||
checked
|
||||
/>2048</label
|
||||
>
|
||||
<label for="-modlen3"
|
||||
><input
|
||||
type="radio"
|
||||
id="-modlen3"
|
||||
name="rsa-len"
|
||||
value="3072"
|
||||
/>3072</label
|
||||
>
|
||||
<label for="-modlen5"
|
||||
><input
|
||||
type="radio"
|
||||
id="-modlen5"
|
||||
name="rsa-len"
|
||||
value="4096"
|
||||
/>4096</label
|
||||
>
|
||||
</div>
|
||||
<button class="js-generate" hidden>Generate</button>
|
||||
</form>
|
||||
|
||||
<h2>2. ACME Account</h2>
|
||||
<form class="js-acme-account">
|
||||
<label for="-acmeEmail">Email:</label>
|
||||
<input
|
||||
class="js-email"
|
||||
type="email"
|
||||
id="-acmeEmail"
|
||||
value="john.doe@gmail.com"
|
||||
/>
|
||||
<br />
|
||||
<label for="-acmeTos"
|
||||
><input
|
||||
class="js-tos"
|
||||
name="tos"
|
||||
type="checkbox"
|
||||
id="-acmeTos"
|
||||
checked
|
||||
/>
|
||||
Agree to Let's Encrypt Terms of Service</label
|
||||
>
|
||||
<br />
|
||||
<button class="js-create-account" hidden>Create Account</button>
|
||||
</form>
|
||||
|
||||
<h2>3. (optional) Certificate Signing Request</h2>
|
||||
<form class="js-csr">
|
||||
<label for="-acmeDomains">Domains:</label>
|
||||
<input
|
||||
class="js-domains"
|
||||
type="text"
|
||||
id="-acmeDomains"
|
||||
value="example.com www.example.com"
|
||||
/>
|
||||
<br />
|
||||
<button class="js-create-csr" hidden>Create CSR</button>
|
||||
</form>
|
||||
|
||||
<h2>4. ACME Certificate Order</h2>
|
||||
<form class="js-acme-order">
|
||||
Challenge type:
|
||||
<label for="-http01"
|
||||
><input
|
||||
type="radio"
|
||||
id="-http01"
|
||||
name="acme-challenge-type"
|
||||
value="http-01"
|
||||
checked
|
||||
/>http-01</label
|
||||
>
|
||||
<label for="-dns01"
|
||||
><input
|
||||
type="radio"
|
||||
id="-dns01"
|
||||
name="acme-challenge-type"
|
||||
value="dns-01"
|
||||
/>dns-01</label
|
||||
>
|
||||
<br />
|
||||
<label for="-skipDryrun"
|
||||
><input
|
||||
class="js-skip-dryrun"
|
||||
name="skip-dryrun"
|
||||
type="checkbox"
|
||||
id="-skipDryrun"
|
||||
checked
|
||||
/>
|
||||
Skip dry-run challenge</label
|
||||
>
|
||||
<br />
|
||||
<button class="js-create-order" hidden>Create Order</button>
|
||||
</form>
|
||||
|
||||
<div class="js-loading" hidden>Loading</div>
|
||||
|
||||
<details class="js-toc-jwk" hidden>
|
||||
<summary>JWK Keypair</summary>
|
||||
<pre><code class="js-jwk"> </code></pre>
|
||||
</details>
|
||||
<details class="js-toc-der-private" hidden>
|
||||
<summary>DER Private Binary</summary>
|
||||
<pre><code class="js-der-private"> </code></pre>
|
||||
</details>
|
||||
<details class="js-toc-der-public" hidden>
|
||||
<summary>DER Public Binary</summary>
|
||||
<pre><code class="js-der-public"> </code></pre>
|
||||
</details>
|
||||
<details class="js-toc-pem js-toc-pem-pkcs1-private" hidden>
|
||||
<summary>PEM Private (base64-encoded PKCS1 DER)</summary>
|
||||
<pre><code class="js-input-pem-pkcs1-private" ></code></pre>
|
||||
</details>
|
||||
<details class="js-toc-pem js-toc-pem-sec1-private" hidden>
|
||||
<summary>PEM Private (base64-encoded SEC1 DER)</summary>
|
||||
<pre><code class="js-input-pem-sec1-private" ></code></pre>
|
||||
</details>
|
||||
<details class="js-toc-pem js-toc-pem-pkcs8-private" hidden>
|
||||
<summary>PEM Private (base64-encoded PKCS8 DER)</summary>
|
||||
<pre><code class="js-input-pem-pkcs8-private" ></code></pre>
|
||||
</details>
|
||||
<details class="js-toc-pem js-toc-pem-pkcs1-public" hidden>
|
||||
<summary>PEM Public (base64-encoded PKCS1 DER)</summary>
|
||||
<pre><code class="js-input-pem-pkcs1-public" ></code></pre>
|
||||
</details>
|
||||
<details class="js-toc-pem js-toc-pem-spki-public" hidden>
|
||||
<summary>PEM Public (base64-encoded SPKI/PKIX DER)</summary>
|
||||
<pre><code class="js-input-pem-spki-public" ></code></pre>
|
||||
</details>
|
||||
<details class="js-toc-acme-account-response" hidden>
|
||||
<summary>ACME Account Request</summary>
|
||||
<pre><code class="js-acme-account-response"> </code></pre>
|
||||
</details>
|
||||
<details class="js-toc-acme-order-response" hidden>
|
||||
<summary>ACME Order Response</summary>
|
||||
<pre><code class="js-acme-order-response"> </code></pre>
|
||||
</details>
|
||||
|
||||
<br />
|
||||
<p>
|
||||
[Root](https://rootprojects.org) has built a collection of
|
||||
lightweight, zero-dependency, libraries written in VanillaJS. They
|
||||
are fast, tiny, and secure, using the native features of modern
|
||||
browsers where possible. This means it's easy-to-use crypto in
|
||||
kilobytes, not megabytes.
|
||||
</p>
|
||||
<br />
|
||||
<footer>
|
||||
View (git) source
|
||||
<a href="https://git.rootprojects.org/root/acme.js">@root/acme</a>
|
||||
</footer>
|
||||
|
||||
<!-- script src="../dist/acme.js"></script -->
|
||||
<!-- script src="../dist/app.js"></script -->
|
||||
<script src="./app.js"></script>
|
||||
</body>
|
||||
</html>
|
174
examples/server.js
Normal file
174
examples/server.js
Normal file
@ -0,0 +1,174 @@
|
||||
'use strict';
|
||||
|
||||
var crypto = require('crypto');
|
||||
//var dnsjs = require('dns-suite');
|
||||
var dig = require('dig.js/dns-request');
|
||||
var request = require('util').promisify(require('@root/request'));
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
var nameservers = require('dns').getServers();
|
||||
var index = crypto.randomBytes(2).readUInt16BE(0) % nameservers.length;
|
||||
var nameserver = nameservers[index];
|
||||
|
||||
app.use('/', express.static(__dirname));
|
||||
app.use('/api', express.json());
|
||||
app.get('/api/dns/:domain', function(req, res, next) {
|
||||
var domain = req.params.domain;
|
||||
var casedDomain = domain
|
||||
.toLowerCase()
|
||||
.split('')
|
||||
.map(function(ch) {
|
||||
// dns0x20 takes advantage of the fact that the binary operation for toUpperCase is
|
||||
// ch = ch | 0x20;
|
||||
return Math.round(Math.random()) % 2 ? ch : ch.toUpperCase();
|
||||
})
|
||||
.join('');
|
||||
var typ = req.query.type;
|
||||
var query = {
|
||||
header: {
|
||||
id: crypto.randomBytes(2).readUInt16BE(0),
|
||||
qr: 0,
|
||||
opcode: 0,
|
||||
aa: 0, // Authoritative-Only
|
||||
tc: 0, // NA
|
||||
rd: 1, // Recurse
|
||||
ra: 0, // NA
|
||||
rcode: 0 // NA
|
||||
},
|
||||
question: [
|
||||
{
|
||||
name: casedDomain,
|
||||
//, type: typ || 'A'
|
||||
typeName: typ || 'A',
|
||||
className: 'IN'
|
||||
}
|
||||
]
|
||||
};
|
||||
var opts = {
|
||||
onError: function(err) {
|
||||
next(err);
|
||||
},
|
||||
onMessage: function(packet) {
|
||||
var fail0x20;
|
||||
|
||||
if (packet.id !== query.id) {
|
||||
console.error(
|
||||
"[SECURITY] ignoring packet for '" +
|
||||
packet.question[0].name +
|
||||
"' due to mismatched id"
|
||||
);
|
||||
console.error(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
packet.question.forEach(function(q) {
|
||||
// if (-1 === q.name.lastIndexOf(cli.casedQuery))
|
||||
if (q.name !== casedDomain) {
|
||||
fail0x20 = q.name;
|
||||
}
|
||||
});
|
||||
|
||||
['question', 'answer', 'authority', 'additional'].forEach(function(
|
||||
group
|
||||
) {
|
||||
(packet[group] || []).forEach(function(a) {
|
||||
var an = a.name;
|
||||
var i = domain
|
||||
.toLowerCase()
|
||||
.lastIndexOf(a.name.toLowerCase()); // answer is something like ExAMPle.cOM and query was wWw.ExAMPle.cOM
|
||||
var j = a.name
|
||||
.toLowerCase()
|
||||
.lastIndexOf(domain.toLowerCase()); // answer is something like www.ExAMPle.cOM and query was ExAMPle.cOM
|
||||
|
||||
// it's important to note that these should only relpace changes in casing that we expected
|
||||
// any abnormalities should be left intact to go "huh?" about
|
||||
// TODO detect abnormalities?
|
||||
if (-1 !== i) {
|
||||
// "EXamPLE.cOm".replace("wWw.EXamPLE.cOm".substr(4), "www.example.com".substr(4))
|
||||
a.name = a.name.replace(
|
||||
casedDomain.substr(i),
|
||||
domain.substr(i)
|
||||
);
|
||||
} else if (-1 !== j) {
|
||||
// "www.example.com".replace("EXamPLE.cOm", "example.com")
|
||||
a.name =
|
||||
a.name.substr(0, j) +
|
||||
a.name.substr(j).replace(casedDomain, domain);
|
||||
}
|
||||
|
||||
// NOTE: right now this assumes that anything matching the query matches all the way to the end
|
||||
// it does not handle the case of a record for example.com.uk being returned in response to a query for www.example.com correctly
|
||||
// (but I don't think it should need to)
|
||||
if (a.name.length !== an.length) {
|
||||
console.error(
|
||||
"[ERROR] question / answer mismatch: '" +
|
||||
an +
|
||||
"' != '" +
|
||||
a.length +
|
||||
"'"
|
||||
);
|
||||
console.error(a);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (fail0x20) {
|
||||
console.warn(
|
||||
";; Warning: DNS 0x20 security not implemented (or packet spoofed). Queried '" +
|
||||
casedDomain +
|
||||
"' but got response for '" +
|
||||
fail0x20 +
|
||||
"'."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
res.send({
|
||||
header: packet.header,
|
||||
question: packet.question,
|
||||
answer: packet.answer,
|
||||
authority: packet.authority,
|
||||
additional: packet.additional,
|
||||
edns_options: packet.edns_options
|
||||
});
|
||||
},
|
||||
onListening: function() {},
|
||||
onSent: function(/*res*/) {},
|
||||
onTimeout: function(res) {
|
||||
console.error('dns timeout:', res);
|
||||
next(new Error('DNS timeout - no response'));
|
||||
},
|
||||
onClose: function() {},
|
||||
//, mdns: cli.mdns
|
||||
nameserver: nameserver,
|
||||
port: 53,
|
||||
timeout: 2000
|
||||
};
|
||||
|
||||
dig.resolveJson(query, opts);
|
||||
});
|
||||
app.get('/api/http', function(req, res) {
|
||||
var url = req.query.url;
|
||||
return request({ method: 'GET', url: url }).then(function(resp) {
|
||||
res.send(resp.body);
|
||||
});
|
||||
});
|
||||
app.get('/api/_acme_api_', function(req, res) {
|
||||
res.send({ success: true });
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
if (require.main === module) {
|
||||
// curl -L http://localhost:3000/api/dns/example.com?type=A
|
||||
console.info('Listening on localhost:3000');
|
||||
app.listen(3000);
|
||||
console.info('Try this:');
|
||||
console.info("\tcurl -L 'http://localhost:3000/api/_acme_api_/'");
|
||||
console.info(
|
||||
"\tcurl -L 'http://localhost:3000/api/dns/example.com?type=A'"
|
||||
);
|
||||
console.info(
|
||||
"\tcurl -L 'http://localhost:3000/api/http/?url=https://example.com'"
|
||||
);
|
||||
}
|
17
fixtures/account.jwk.json
Normal file
17
fixtures/account.jwk.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"private": {
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
"d": "HB1OvdHfLnIy2mYYO9cLU4BqP36CeyS8OsDf3OnYP-M",
|
||||
"x": "uLh0RLpAmKyyHCf2zOaF18IIuBiJEiZ8Mu3xPZ7ZxN8",
|
||||
"y": "vVl_cCXK0_GlCaCT5Yg750LUd8eRU6tySEdQFLM62NQ",
|
||||
"kid": "UuuZa_56jCM2douUq1riGyRphPtRvCPkxtkg0bP-pNs"
|
||||
},
|
||||
"public": {
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
"x": "uLh0RLpAmKyyHCf2zOaF18IIuBiJEiZ8Mu3xPZ7ZxN8",
|
||||
"y": "vVl_cCXK0_GlCaCT5Yg750LUd8eRU6tySEdQFLM62NQ",
|
||||
"kid": "UuuZa_56jCM2douUq1riGyRphPtRvCPkxtkg0bP-pNs"
|
||||
}
|
||||
}
|
14
fixtures/account.registration.headers.json
Normal file
14
fixtures/account.registration.headers.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:19:57 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "341",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11407977",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\", <https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf>;rel=\"terms-of-service\"",
|
||||
"location": "https://acme-staging-v02.api.letsencrypt.org/acme/acct/11407977",
|
||||
"replay-nonce": "0001pgbsovQitzg1gDmvpxu18MOh_lsxRyV8cDC19YozinE",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
}
|
12
fixtures/account.registration.json
Normal file
12
fixtures/account.registration.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"key": {
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
"x": "9JZE7ZMAAQ-26oP-_pzd9gy2CbuEvgvrB42R1rP2Pb0",
|
||||
"y": "8yvSYK5sAx30upYpqVknnPPQlK1T3zGTLbJRC-DH_qw"
|
||||
},
|
||||
"contact": ["mailto:letsencrypt+staging@therootcompany.com"],
|
||||
"initialIp": "66.219.236.169",
|
||||
"createdAt": "2019-10-24T23:19:57.480171297Z",
|
||||
"status": "valid"
|
||||
}
|
15
fixtures/account.request.json
Normal file
15
fixtures/account.request.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/new-acct",
|
||||
"json": {
|
||||
"protected": "eyJqd2siOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJCa0ZsYVBQUi1JTmJfcHVvWHNlbkpGUWJLcFJQM2RraUJuQ0Y4TlNNX3lZIiwieSI6IlZCMEhjM2JoYXlJS2s4QlFiRGJSTDBJZC1LS1hoVkFhRFhLd0RENk1EMjgifSwibm9uY2UiOiIwMDAxSVBlQzN0YV91S29lLTVHanBxUVlGUjFDLVFjS0pzVFVac0daTVFPSzY5ZyIsInVybCI6Imh0dHBzOi8vYWNtZS1zdGFnaW5nLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvbmV3LWFjY3QiLCJhbGciOiJFUzI1NiJ9",
|
||||
"payload": "eyJ0ZXJtc09mU2VydmljZUFncmVlZCI6dHJ1ZSwib25seVJldHVybkV4aXN0aW5nIjpmYWxzZSwiY29udGFjdCI6WyJtYWlsdG86bGV0c2VuY3J5cHQrc3RhZ2luZ0B0aGVyb290Y29tcGFueS5jb20iXX0",
|
||||
"signature": "nuwft1-d349OZoQOH5lsgWCCFYsbciUFrGspiYkd630z_AZU_z0BdNXU5oT2NdaFJJXdqOJkePvEtmTFhAPCEg"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJqd2siOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJCa0ZsYVBQUi1JTmJfcHVvWHNlbkpGUWJLcFJQM2RraUJuQ0Y4TlNNX3lZIiwieSI6IlZCMEhjM2JoYXlJS2s4QlFiRGJSTDBJZC1LS1hoVkFhRFhLd0RENk1EMjgifSwibm9uY2UiOiIwMDAxSVBlQzN0YV91S29lLTVHanBxUVlGUjFDLVFjS0pzVFVac0daTVFPSzY5ZyIsInVybCI6Imh0dHBzOi8vYWNtZS1zdGFnaW5nLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvbmV3LWFjY3QiLCJhbGciOiJFUzI1NiJ9\",\"payload\":\"eyJ0ZXJtc09mU2VydmljZUFncmVlZCI6dHJ1ZSwib25seVJldHVybkV4aXN0aW5nIjpmYWxzZSwiY29udGFjdCI6WyJtYWlsdG86bGV0c2VuY3J5cHQrc3RhZ2luZ0B0aGVyb290Y29tcGFueS5jb20iXX0\",\"signature\":\"nuwft1-d349OZoQOH5lsgWCCFYsbciUFrGspiYkd630z_AZU_z0BdNXU5oT2NdaFJJXdqOJkePvEtmTFhAPCEg\"}",
|
||||
"method": "POST"
|
||||
}
|
14
fixtures/account.response.headers.json
Normal file
14
fixtures/account.response.headers.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:24 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "340",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11408075",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\", <https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf>;rel=\"terms-of-service\"",
|
||||
"location": "https://acme-staging-v02.api.letsencrypt.org/acme/acct/11408075",
|
||||
"replay-nonce": "0002O1dowqaEQWEHtP2Cz9BYJuOU91uRvRM1uPFbcdwaj-0",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
}
|
12
fixtures/account.response.json
Normal file
12
fixtures/account.response.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"key": {
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
"x": "BkFlaPPR-INb_puoXsenJFQbKpRP3dkiBnCF8NSM_yY",
|
||||
"y": "VB0Hc3bhayIKk8BQbDbRL0Id-KKXhVAaDXKwDD6MD28"
|
||||
},
|
||||
"contact": ["mailto:letsencrypt+staging@therootcompany.com"],
|
||||
"initialIp": "66.219.236.169",
|
||||
"createdAt": "2019-10-24T23:41:24.38248946Z",
|
||||
"status": "valid"
|
||||
}
|
177
fixtures/authorization.other.json
Normal file
177
fixtures/authorization.other.json
Normal file
@ -0,0 +1,177 @@
|
||||
[
|
||||
[
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603342",
|
||||
"json": {
|
||||
"protected": "eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDFvU05Bd25ZVjJRWlB0cGNCZHlNUWd1cXB4MFI1SzhFd0txYzJPeWxVYm5vIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9hdXRoei12My8xNjYwMzM0MiIsImFsZyI6IkVTMjU2In0",
|
||||
"payload": "",
|
||||
"signature": "mgxpomAxc-a2zEbVuyDxncZvoJTbEWwSRb3aE9W-d8TU_9iIK7jKo6RTL6jTZfgM4ToUET7F19NIqWMnQmoREw"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDFvU05Bd25ZVjJRWlB0cGNCZHlNUWd1cXB4MFI1SzhFd0txYzJPeWxVYm5vIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9hdXRoei12My8xNjYwMzM0MiIsImFsZyI6IkVTMjU2In0\",\"payload\":\"\",\"signature\":\"mgxpomAxc-a2zEbVuyDxncZvoJTbEWwSRb3aE9W-d8TU_9iIK7jKo6RTL6jTZfgM4ToUET7F19NIqWMnQmoREw\"}",
|
||||
"method": "POST"
|
||||
},
|
||||
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:32 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "838",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11408075",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\"",
|
||||
"replay-nonce": "0002t2JSKyWPm0PEBFrttckiXqIrSEf0PoLdhv24P_QGbrw",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
},
|
||||
{
|
||||
"identifier": {
|
||||
"type": "dns",
|
||||
"value": "xn--bar-acmejs-2ea4-zk8x.test.utahrust.com"
|
||||
},
|
||||
"status": "pending",
|
||||
"expires": "2019-10-31T23:41:32Z",
|
||||
"challenges": [
|
||||
{
|
||||
"type": "http-01",
|
||||
"status": "pending",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603342/SX06Rw",
|
||||
"token": "NsJOLEHJONiQqkADO_lecQ2J0u-g6I2tvkgqevUBbUA"
|
||||
},
|
||||
{
|
||||
"type": "dns-01",
|
||||
"status": "pending",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603342/I4uhGQ",
|
||||
"token": "NsJOLEHJONiQqkADO_lecQ2J0u-g6I2tvkgqevUBbUA"
|
||||
},
|
||||
{
|
||||
"type": "tls-alpn-01",
|
||||
"status": "pending",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603342/E-EFfg",
|
||||
"token": "NsJOLEHJONiQqkADO_lecQ2J0u-g6I2tvkgqevUBbUA"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603343",
|
||||
"json": {
|
||||
"protected": "eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDJ0MkpTS3lXUG0wUEVCRnJ0dGNraVhxSXJTRWYwUG9MZGh2MjRQX1FHYnJ3IiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9hdXRoei12My8xNjYwMzM0MyIsImFsZyI6IkVTMjU2In0",
|
||||
"payload": "",
|
||||
"signature": "equGw3S_17IjiavHk25D3l3g48nE6kIhcN6bvgUdBofh1kfsc-kpPVwkZrBMndqWTh-_WHmQtfg01fkP3xzVGg"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDJ0MkpTS3lXUG0wUEVCRnJ0dGNraVhxSXJTRWYwUG9MZGh2MjRQX1FHYnJ3IiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9hdXRoei12My8xNjYwMzM0MyIsImFsZyI6IkVTMjU2In0\",\"payload\":\"\",\"signature\":\"equGw3S_17IjiavHk25D3l3g48nE6kIhcN6bvgUdBofh1kfsc-kpPVwkZrBMndqWTh-_WHmQtfg01fkP3xzVGg\"}",
|
||||
"method": "POST"
|
||||
},
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:32 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "838",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11408075",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\"",
|
||||
"replay-nonce": "0002quWdcKvS2smvRV2Dl98tTHjPUS9sRC4ZDzjXpuyeGhc",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
},
|
||||
{
|
||||
"identifier": {
|
||||
"type": "dns",
|
||||
"value": "xn--baz-acmejs-2ea4-zk8x.test.utahrust.com"
|
||||
},
|
||||
"status": "pending",
|
||||
"expires": "2019-10-31T23:41:32Z",
|
||||
"challenges": [
|
||||
{
|
||||
"type": "http-01",
|
||||
"status": "pending",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603343/bSRwrg",
|
||||
"token": "Cc3I3F1Pvc_aweOeRdtzR1h2C_uhseAbiWMQkwb6Kf8"
|
||||
},
|
||||
{
|
||||
"type": "dns-01",
|
||||
"status": "pending",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603343/Umv_5w",
|
||||
"token": "Cc3I3F1Pvc_aweOeRdtzR1h2C_uhseAbiWMQkwb6Kf8"
|
||||
},
|
||||
{
|
||||
"type": "tls-alpn-01",
|
||||
"status": "pending",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603343/awV7qQ",
|
||||
"token": "Cc3I3F1Pvc_aweOeRdtzR1h2C_uhseAbiWMQkwb6Kf8"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603344",
|
||||
"json": {
|
||||
"protected": "eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDJxdVdkY0t2UzJzbXZSVjJEbDk4dFRIalBVUzlzUkM0WkR6alhwdXllR2hjIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9hdXRoei12My8xNjYwMzM0NCIsImFsZyI6IkVTMjU2In0",
|
||||
"payload": "",
|
||||
"signature": "UzOSs2HvxN_mErU-wjrffbFp3JZOu6Earsq3ssj49Qcw3Bf5uyXPKO5DF7iseuL2Qammqofvh70pCka6tD_knQ"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDJxdVdkY0t2UzJzbXZSVjJEbDk4dFRIalBVUzlzUkM0WkR6alhwdXllR2hjIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9hdXRoei12My8xNjYwMzM0NCIsImFsZyI6IkVTMjU2In0\",\"payload\":\"\",\"signature\":\"UzOSs2HvxN_mErU-wjrffbFp3JZOu6Earsq3ssj49Qcw3Bf5uyXPKO5DF7iseuL2Qammqofvh70pCka6tD_knQ\"}",
|
||||
"method": "POST"
|
||||
},
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:32 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "838",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11408075",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\"",
|
||||
"replay-nonce": "0001kREyyuaaIacPhD7-j73BHzyQnhfPiBM3PEwnXDFVgTc",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
},
|
||||
{
|
||||
"identifier": {
|
||||
"type": "dns",
|
||||
"value": "xn--foo-acmejs-2ea4-zk8x.test.utahrust.com"
|
||||
},
|
||||
"status": "pending",
|
||||
"expires": "2019-10-31T23:41:32Z",
|
||||
"challenges": [
|
||||
{
|
||||
"type": "http-01",
|
||||
"status": "pending",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603344/usH89w",
|
||||
"token": "uv7D1YC7oWvMY8-EC2blKxmSFExYHwjCcKjGpuodwWs"
|
||||
},
|
||||
{
|
||||
"type": "dns-01",
|
||||
"status": "pending",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603344/Hqvliw",
|
||||
"token": "uv7D1YC7oWvMY8-EC2blKxmSFExYHwjCcKjGpuodwWs"
|
||||
},
|
||||
{
|
||||
"type": "tls-alpn-01",
|
||||
"status": "pending",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603344/6C26qQ",
|
||||
"token": "uv7D1YC7oWvMY8-EC2blKxmSFExYHwjCcKjGpuodwWs"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
15
fixtures/authorization.post.json
Normal file
15
fixtures/authorization.post.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603341",
|
||||
"json": {
|
||||
"protected": "eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDFqNEF6c2Qwa2s2aTYwTlN6Um9aY3ZMaWRtTG81QjBzRzFsTUtUcVdyMzg4IiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9hdXRoei12My8xNjYwMzM0MSIsImFsZyI6IkVTMjU2In0",
|
||||
"payload": "",
|
||||
"signature": "qjrQyqKRskdhF7DVUymZdHhm9neC9vgH9UUc6D-vtXtS8T2QW9C82qsyghZdGGJLWeKeZLRsADjmZSh5XCAa4g"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDFqNEF6c2Qwa2s2aTYwTlN6Um9aY3ZMaWRtTG81QjBzRzFsTUtUcVdyMzg4IiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9hdXRoei12My8xNjYwMzM0MSIsImFsZyI6IkVTMjU2In0\",\"payload\":\"\",\"signature\":\"qjrQyqKRskdhF7DVUymZdHhm9neC9vgH9UUc6D-vtXtS8T2QW9C82qsyghZdGGJLWeKeZLRsADjmZSh5XCAa4g\"}",
|
||||
"method": "POST"
|
||||
}
|
13
fixtures/authorization.response.headers.json
Normal file
13
fixtures/authorization.response.headers.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:32 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "420",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11408075",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\"",
|
||||
"replay-nonce": "0001oSNAwnYV2QZPtpcBdyMQguqpx0R5K8EwKqc2OylUbno",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
}
|
17
fixtures/authorization.response.json
Normal file
17
fixtures/authorization.response.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"identifier": {
|
||||
"type": "dns",
|
||||
"value": "xn--baz-acmejs-2ea4-zk8x.test.utahrust.com"
|
||||
},
|
||||
"status": "pending",
|
||||
"expires": "2019-10-31T23:41:32Z",
|
||||
"challenges": [
|
||||
{
|
||||
"type": "dns-01",
|
||||
"status": "pending",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603341/yCthUw",
|
||||
"token": "DiO9DFHuFTpNsJxIbOxfVCSPVkpe4lJUjozeSyzkMjI"
|
||||
}
|
||||
],
|
||||
"wildcard": true
|
||||
}
|
15
fixtures/cert.request.json
Normal file
15
fixtures/cert.request.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/cert/fa78326c21c0c7f06c03931900bead4fe3ee",
|
||||
"json": {
|
||||
"protected": "eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDExLW5qUV91MWp4N1dqVEdfY1Blam05UUxLZWxFcUVFdEpEa3JlVHJ5OVI4IiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jZXJ0L2ZhNzgzMjZjMjFjMGM3ZjA2YzAzOTMxOTAwYmVhZDRmZTNlZSIsImFsZyI6IkVTMjU2In0",
|
||||
"payload": "",
|
||||
"signature": "639Q5Eo2_xWh3ylRy3olXJVXz_4JTrpVFkUmz9-h1l8Hrsmg47I0HFgMrHslfKEJfj86zGUh9XY-VtBF2IFcIQ"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDExLW5qUV91MWp4N1dqVEdfY1Blam05UUxLZWxFcUVFdEpEa3JlVHJ5OVI4IiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jZXJ0L2ZhNzgzMjZjMjFjMGM3ZjA2YzAzOTMxOTAwYmVhZDRmZTNlZSIsImFsZyI6IkVTMjU2In0\",\"payload\":\"\",\"signature\":\"639Q5Eo2_xWh3ylRy3olXJVXz_4JTrpVFkUmz9-h1l8Hrsmg47I0HFgMrHslfKEJfj86zGUh9XY-VtBF2IFcIQ\"}",
|
||||
"method": "POST"
|
||||
}
|
12
fixtures/cert.response.headers.json
Normal file
12
fixtures/cert.response.headers.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:44 GMT",
|
||||
"content-type": "application/pem-certificate-chain",
|
||||
"content-length": "3806",
|
||||
"connection": "close",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\"",
|
||||
"replay-nonce": "0002vmpuKxQvokCGu5-cbVhsXkBHweBkdFnNrIpufnVn8mc",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
}
|
64
fixtures/cert.response.txt
Normal file
64
fixtures/cert.response.txt
Normal file
@ -0,0 +1,64 @@
|
||||
// Note: I may have added or truncated a beginning or ending
|
||||
// newline here in the process of copy/paste
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIF9TCCBN2gAwIBAgITAPp4MmwhwMfwbAOTGQC+rU/j7jANBgkqhkiG9w0BAQsF
|
||||
ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xOTEwMjQy
|
||||
MjQxNDRaFw0yMDAxMjIyMjQxNDRaMDUxMzAxBgNVBAMTKnhuLS1mb28tYWNtZWpz
|
||||
LTJlYTQtems4eC50ZXN0LnV0YWhydXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBAOXgIzVvJzQRuGkomoKQzswNyMaFB7MmCHNOW98yYxfHpLqj
|
||||
KKddplJpvHQ/R8I15+38QfqT9kvj9vQ7i3gU6AUya56Sg6TSSmUE5PBP7WfEn/2O
|
||||
+iHzZ/Devq/Oq0fHQoF+TtEFgnMVZZL4gnEyciSzQs5ftn+HejLGYmBH5uJlPGCp
|
||||
9lMOe+ziweWKbmZYDu4Qrqf3TEHbFOpBPgJUna4tz0xmISdxzuR9Q/tie3a+cCjV
|
||||
4xtxCblN9W37KC1VnEkLtQwgm6zjZAVSUWOLZUqMVL2H+/jR5Z9r1XYevEDlAl35
|
||||
sW0kaEf/FdLfr8tfbbnPUsVvRL5I5gdLmyonJccCAwEAAaOCAw8wggMLMA4GA1Ud
|
||||
DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T
|
||||
AQH/BAIwADAdBgNVHQ4EFgQUJqGfhDoxM99m3HZUhlME4JMg+zQwHwYDVR0jBBgw
|
||||
FoAUwMwDRrlYIMxccnDz4S7LIKb1aDowdwYIKwYBBQUHAQEEazBpMDIGCCsGAQUF
|
||||
BzABhiZodHRwOi8vb2NzcC5zdGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZzAzBggr
|
||||
BgEFBQcwAoYnaHR0cDovL2NlcnQuc3RnLWludC14MS5sZXRzZW5jcnlwdC5vcmcv
|
||||
MIG9BgNVHREEgbUwgbKCLCoueG4tLWJhei1hY21lanMtMmVhNC16azh4LnRlc3Qu
|
||||
dXRhaHJ1c3QuY29tgip4bi0tYmFyLWFjbWVqcy0yZWE0LXprOHgudGVzdC51dGFo
|
||||
cnVzdC5jb22CKnhuLS1iYXotYWNtZWpzLTJlYTQtems4eC50ZXN0LnV0YWhydXN0
|
||||
LmNvbYIqeG4tLWZvby1hY21lanMtMmVhNC16azh4LnRlc3QudXRhaHJ1c3QuY29t
|
||||
MEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEBMCgwJgYIKwYBBQUH
|
||||
AgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3JnMIIBAwYKKwYBBAHWeQIEAgSB
|
||||
9ASB8QDvAHYAxj8iGMN9VqaqBrWW2o5T1NcVbR6brI5E0iAt5k1p2dwAAAFuACW/
|
||||
/QAABAMARzBFAiB/xTPuBFV2+yfovKBiru29WQ+j3wjTGE1Urcn1Rn+5nQIhALH+
|
||||
5N4A0TiK04romA8Nb/R5X0sNM68HGK/KRCICdYOxAHUAsMyD5aX5fWuvfAnMKEkE
|
||||
hyrH6IsTLGNQt8b9JuFsbHcAAAFuACW//gAABAMARjBEAiAcL3cjhbwAOV34v3vK
|
||||
svbb9yIK36vRucq3hu/Vs1B3ZAIgfTwjAHDE6GqfZEW2e9MjuULEvMdF2QHVh7WB
|
||||
Bp5A48wwDQYJKoZIhvcNAQELBQADggEBAFxbkUt0QOZNAKnTqdYnBP2FlxezjFPq
|
||||
P4pD/G2/JFKi86VDg2vLVfPMGd7jv+e8Ao0+G9rgC3vtQE817T5d9XFlJ8p7dMjK
|
||||
TbTmSlKHxM9Dal8fqC7kbqqx/gdpzzPyBoDYlKWvhr3qXsxB/hGI3OX+d42R1wsr
|
||||
zcQKaG2HpJcerZ1au2Jm/YOCJPpDHMAFKK5wuCmOIBfNQ+ULyStPZLQWPdMI04S2
|
||||
Y8eIQgS6q9OX1CtvuehVFwyO8TNi53do88wFDdHF7lNZEjz7NvpNqi3qeZgSRuAb
|
||||
/fTMCULMjDghh+xpTLRzSROB6YJbU8uXtSZ6Xn04SZ6ZSuvbCYmHlsU=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw
|
||||
GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2
|
||||
MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0
|
||||
8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym
|
||||
oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0
|
||||
ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN
|
||||
xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56
|
||||
dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9
|
||||
AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
|
||||
HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0
|
||||
BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu
|
||||
b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu
|
||||
Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF
|
||||
UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9
|
||||
AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp
|
||||
DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7
|
||||
IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf
|
||||
zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI
|
||||
PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w
|
||||
SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em
|
||||
2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0
|
||||
WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt
|
||||
n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU=
|
||||
-----END CERTIFICATE-----
|
121
fixtures/challenge.others.json
Normal file
121
fixtures/challenge.others.json
Normal file
@ -0,0 +1,121 @@
|
||||
[
|
||||
[
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603342/I4uhGQ",
|
||||
"json": {
|
||||
"protected": "eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDIybXdZUUhpR0NMMVRacUViYkNBZ1N1djJYMXctSGhkMWR0TV9zRllXRGlNIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jaGFsbC12My8xNjYwMzM0Mi9JNHVoR1EiLCJhbGciOiJFUzI1NiJ9",
|
||||
"payload": "e30",
|
||||
"signature": "90XygqCrKMhqsoFD4-J56yYgEKuevnw7V-4MaP_lZKzMn9vnhK_CtWh0k5kRuePhJzopTRrWkRzXz9OExlt9WQ"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDIybXdZUUhpR0NMMVRacUViYkNBZ1N1djJYMXctSGhkMWR0TV9zRllXRGlNIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jaGFsbC12My8xNjYwMzM0Mi9JNHVoR1EiLCJhbGciOiJFUzI1NiJ9\",\"payload\":\"e30\",\"signature\":\"90XygqCrKMhqsoFD4-J56yYgEKuevnw7V-4MaP_lZKzMn9vnhK_CtWh0k5kRuePhJzopTRrWkRzXz9OExlt9WQ\"}",
|
||||
"method": "POST"
|
||||
},
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:42 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "292",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11408075",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\", <https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603342>;rel=\"up\"",
|
||||
"location": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603342/I4uhGQ",
|
||||
"replay-nonce": "0001XZufnGiSHfABU10B8FWCxHzvqPN991zSEO3-uQnNZqI",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
},
|
||||
{
|
||||
"type": "dns-01",
|
||||
"status": "valid",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603342/I4uhGQ",
|
||||
"token": "NsJOLEHJONiQqkADO_lecQ2J0u-g6I2tvkgqevUBbUA",
|
||||
"validationRecord": [
|
||||
{ "hostname": "xn--bar-acmejs-2ea4-zk8x.test.utahrust.com" }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
[
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603343/Umv_5w",
|
||||
"json": {
|
||||
"protected": "eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDFYWnVmbkdpU0hmQUJVMTBCOEZXQ3hIenZxUE45OTF6U0VPMy11UW5OWnFJIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jaGFsbC12My8xNjYwMzM0My9VbXZfNXciLCJhbGciOiJFUzI1NiJ9",
|
||||
"payload": "e30",
|
||||
"signature": "I5p1OLU52W7m-oHeRWAuZQyf5saBlm1Mv5UV8kqRLVxxt-kMEJLXwKgP0kgfz-rXjnZheYnrKiKERZX1wt7RdA"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDFYWnVmbkdpU0hmQUJVMTBCOEZXQ3hIenZxUE45OTF6U0VPMy11UW5OWnFJIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jaGFsbC12My8xNjYwMzM0My9VbXZfNXciLCJhbGciOiJFUzI1NiJ9\",\"payload\":\"e30\",\"signature\":\"I5p1OLU52W7m-oHeRWAuZQyf5saBlm1Mv5UV8kqRLVxxt-kMEJLXwKgP0kgfz-rXjnZheYnrKiKERZX1wt7RdA\"}",
|
||||
"method": "POST"
|
||||
},
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:43 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "292",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11408075",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\", <https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603343>;rel=\"up\"",
|
||||
"location": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603343/Umv_5w",
|
||||
"replay-nonce": "00012YkSGH0-3llPNZT_hV8Ovw11jJU9YyppuJ--gJldLTo",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
},
|
||||
{
|
||||
"type": "dns-01",
|
||||
"status": "pending",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603343/Umv_5w",
|
||||
"token": "Cc3I3F1Pvc_aweOeRdtzR1h2C_uhseAbiWMQkwb6Kf8"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603344/Hqvliw",
|
||||
"json": {
|
||||
"protected": "eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDJxT0s4WGhNcWtCVWgzYk1LUV9ZMUo2QXJUbEVOR01BTUQ4bHc3WjNtT2JvIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jaGFsbC12My8xNjYwMzM0NC9IcXZsaXciLCJhbGciOiJFUzI1NiJ9",
|
||||
"payload": "e30",
|
||||
"signature": "ltAp1E52XSMMZpleycguLlo4Hii0FxAbiXcmZBdA-vTjqJb8S1X4CVYQ-qebmYFlCipRhe9Juaj6zpvX7UbTnQ"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDJxT0s4WGhNcWtCVWgzYk1LUV9ZMUo2QXJUbEVOR01BTUQ4bHc3WjNtT2JvIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jaGFsbC12My8xNjYwMzM0NC9IcXZsaXciLCJhbGciOiJFUzI1NiJ9\",\"payload\":\"e30\",\"signature\":\"ltAp1E52XSMMZpleycguLlo4Hii0FxAbiXcmZBdA-vTjqJb8S1X4CVYQ-qebmYFlCipRhe9Juaj6zpvX7UbTnQ\"}",
|
||||
"method": "POST"
|
||||
},
|
||||
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:44 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "292",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11408075",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\", <https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603344>;rel=\"up\"",
|
||||
"location": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603344/Hqvliw",
|
||||
"replay-nonce": "0001RZo7OXhCjsG_9mtrLylmz443TVc9FOsyhfergGWmkDM",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
},
|
||||
{
|
||||
"type": "dns-01",
|
||||
"status": "valid",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603344/Hqvliw",
|
||||
"token": "uv7D1YC7oWvMY8-EC2blKxmSFExYHwjCcKjGpuodwWs",
|
||||
"validationRecord": [
|
||||
{ "hostname": "xn--foo-acmejs-2ea4-zk8x.test.utahrust.com" }
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
15
fixtures/challenge.pending.json
Normal file
15
fixtures/challenge.pending.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603341/yCthUw",
|
||||
"json": {
|
||||
"protected": "eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDFrUkV5eXVhYUlhY1BoRDctajczQkh6eVFuaGZQaUJNM1BFd25YREZWZ1RjIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jaGFsbC12My8xNjYwMzM0MS95Q3RoVXciLCJhbGciOiJFUzI1NiJ9",
|
||||
"payload": "e30",
|
||||
"signature": "QZKdMroSf-qrno2UBHf_L2nL9VrvDtDEb0uLL2fp1yKkwX8u0sELLOYfIu8YqeSwcmPZ1LQHWbXLx5SQ0Lv3Pw"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDFrUkV5eXVhYUlhY1BoRDctajczQkh6eVFuaGZQaUJNM1BFd25YREZWZ1RjIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jaGFsbC12My8xNjYwMzM0MS95Q3RoVXciLCJhbGciOiJFUzI1NiJ9\",\"payload\":\"e30\",\"signature\":\"QZKdMroSf-qrno2UBHf_L2nL9VrvDtDEb0uLL2fp1yKkwX8u0sELLOYfIu8YqeSwcmPZ1LQHWbXLx5SQ0Lv3Pw\"}",
|
||||
"method": "POST"
|
||||
}
|
14
fixtures/challenge.pending.response.headers.json
Normal file
14
fixtures/challenge.pending.response.headers.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:39 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "190",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11408075",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\", <https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603341>;rel=\"up\"",
|
||||
"location": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603341/yCthUw",
|
||||
"replay-nonce": "0001In5LKCnj27k3uNTzl19vqQ5oHlroIJJI-U1daaxNd-Y",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
}
|
6
fixtures/challenge.pending.response.json
Normal file
6
fixtures/challenge.pending.response.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "dns-01",
|
||||
"status": "pending",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603341/yCthUw",
|
||||
"token": "DiO9DFHuFTpNsJxIbOxfVCSPVkpe4lJUjozeSyzkMjI"
|
||||
}
|
15
fixtures/challenge.valid.json
Normal file
15
fixtures/challenge.valid.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603341/yCthUw",
|
||||
"json": {
|
||||
"protected": "eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDFJbjVMS0NuajI3azN1TlR6bDE5dnFRNW9IbHJvSUpKSS1VMWRhYXhOZC1ZIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jaGFsbC12My8xNjYwMzM0MS95Q3RoVXciLCJhbGciOiJFUzI1NiJ9",
|
||||
"payload": "e30",
|
||||
"signature": "3SVtWvRXGFirW198sM4bWErA5M_GplWkI_duSKLHtdGLe-R2D2r0VK1_Xn4exfk6MGIBSkaeeYV6RJfnsLgYLg"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDFJbjVMS0NuajI3azN1TlR6bDE5dnFRNW9IbHJvSUpKSS1VMWRhYXhOZC1ZIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9jaGFsbC12My8xNjYwMzM0MS95Q3RoVXciLCJhbGciOiJFUzI1NiJ9\",\"payload\":\"e30\",\"signature\":\"3SVtWvRXGFirW198sM4bWErA5M_GplWkI_duSKLHtdGLe-R2D2r0VK1_Xn4exfk6MGIBSkaeeYV6RJfnsLgYLg\"}",
|
||||
"method": "POST"
|
||||
}
|
9
fixtures/challenge.valid.response.headers.json
Normal file
9
fixtures/challenge.valid.response.headers.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "dns-01",
|
||||
"status": "valid",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603341/yCthUw",
|
||||
"token": "DiO9DFHuFTpNsJxIbOxfVCSPVkpe4lJUjozeSyzkMjI",
|
||||
"validationRecord": [
|
||||
{ "hostname": "xn--baz-acmejs-2ea4-zk8x.test.utahrust.com" }
|
||||
]
|
||||
}
|
14
fixtures/challenge.valid.response.json
Normal file
14
fixtures/challenge.valid.response.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:40 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "292",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11408075",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\", <https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603341>;rel=\"up\"",
|
||||
"location": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/16603341/yCthUw",
|
||||
"replay-nonce": "0001P9ksMrD-4xaHyRPUVR2pq6PMQSG7T-ELjWBWXsLROv0",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
}
|
9
fixtures/directory.request.json
Normal file
9
fixtures/directory.request.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"method": "GET",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/directory",
|
||||
"json": true,
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
}
|
10
fixtures/directory.response.headers.json
Normal file
10
fixtures/directory.response.headers.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:24 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "724",
|
||||
"connection": "close",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
}
|
13
fixtures/directory.response.json
Normal file
13
fixtures/directory.response.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"Uw5jwSdQL_Q": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
|
||||
"keyChange": "https://acme-staging-v02.api.letsencrypt.org/acme/key-change",
|
||||
"meta": {
|
||||
"caaIdentities": ["letsencrypt.org"],
|
||||
"termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf",
|
||||
"website": "https://letsencrypt.org/docs/staging-environment/"
|
||||
},
|
||||
"newAccount": "https://acme-staging-v02.api.letsencrypt.org/acme/new-acct",
|
||||
"newNonce": "https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce",
|
||||
"newOrder": "https://acme-staging-v02.api.letsencrypt.org/acme/new-order",
|
||||
"revokeCert": "https://acme-staging-v02.api.letsencrypt.org/acme/revoke-cert"
|
||||
}
|
15
fixtures/finalize.valid.json
Normal file
15
fixtures/finalize.valid.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/finalize/11408075/57799471",
|
||||
"json": {
|
||||
"protected": "eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDFSWm83T1hoQ2pzR185bXRyTHlsbXo0NDNUVmM5Rk9zeWhmZXJnR1dta0RNIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9maW5hbGl6ZS8xMTQwODA3NS81Nzc5OTQ3MSIsImFsZyI6IkVTMjU2In0",
|
||||
"payload": "eyJjc3IiOiJNSUlEVHpDQ0FqY0NBUUF3TlRFek1ERUdBMVVFQXd3cWVHNHRMV1p2YnkxaFkyMWxhbk10TW1WaE5DMTZhemg0TG5SbGMzUXVkWFJoYUhKMWMzUXVZMjl0TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUE1ZUFqTlc4bk5CRzRhU2lhZ3BET3pBM0l4b1VIc3lZSWMwNWIzekpqRjhla3VxTW9wMTJtVW1tOGREOUh3alhuN2Z4Qi1wUDJTLVAyOUR1TGVCVG9CVEpybnBLRHBOSktaUVRrOEVfdFo4U2ZfWTc2SWZObjhONi1yODZyUjhkQ2dYNU8wUVdDY3hWbGt2aUNjVEp5SkxOQ3psLTJmNGQ2TXNaaVlFZm00bVU4WUtuMlV3NTc3T0xCNVlwdVpsZ083aEN1cF9kTVFkc1U2a0UtQWxTZHJpM1BUR1loSjNITzVIMUQtMko3ZHI1d0tOWGpHM0VKdVUzMWJmc29MVldjU1F1MURDQ2JyT05rQlZKUlk0dGxTb3hVdllmNy1OSGxuMnZWZGg2OFFPVUNYZm14YlNSb1JfOFYwdC12eTE5dHVjOVN4VzlFdmtqbUIwdWJLaWNseHdJREFRQUJvSUhVTUlIUkJna3Foa2lHOXcwQkNRNHhnY013Z2NBd2diMEdBMVVkRVFTQnRUQ0Jzb0lxZUc0dExXWnZieTFoWTIxbGFuTXRNbVZoTkMxNmF6aDRMblJsYzNRdWRYUmhhSEoxYzNRdVkyOXRnaXA0YmkwdFltRnlMV0ZqYldWcWN5MHlaV0UwTFhwck9IZ3VkR1Z6ZEM1MWRHRm9jblZ6ZEM1amIyMkNMQ291ZUc0dExXSmhlaTFoWTIxbGFuTXRNbVZoTkMxNmF6aDRMblJsYzNRdWRYUmhhSEoxYzNRdVkyOXRnaXA0YmkwdFltRjZMV0ZqYldWcWN5MHlaV0UwTFhwck9IZ3VkR1Z6ZEM1MWRHRm9jblZ6ZEM1amIyMHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBTV9idU84N0YtMkd2aThKZmlaZ3ZYNGNvUnllLVhSSDVnbTJ6enRjNW1KS0ZxRmRBdkV5Z0IxbE82NmJaTG5uZjk5bWRROFk5UnQ0R1RiU3N5N1djQ1NMVF91MVNGX0h1REU5SnZ2ek43MnU1VmtlLW1KelB0cG1OcTlRODZpRWNVQnVEMmNfVVVCQ0Y2ZEFsTHhUZmRQRkJWdXBPSnVCRmQ4azdBNlhhbTl0UjFKV3p4RGdrSHM1cTdmSWo1dXVLcmdjSlhWc19lWHA0QkNONEcyM2hKX01YR1RidDhqeHU1MTFOaDE0Z18wT3JlWkw1bHd5MWR5ZE9mN0pLdGpUdmtyQWE1YjJDVXlLa293NHlaLTNoUmVRcHZjVnIzcnRaTWtKdndMMHI5WjcxcENHRjViUVEweDBIVk04VzYtVkotTWJpLVlhTC04TjNyNEpTbWdDN09VIn0",
|
||||
"signature": "_X0X-Wg86dr5mF0eS0GOYNSmO0HCenlIGQeMygRVoH7BpYO0AMK_mgRQlNR3MWNMULC_aQ-oEMtsXGMXrTa7VA"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDFSWm83T1hoQ2pzR185bXRyTHlsbXo0NDNUVmM5Rk9zeWhmZXJnR1dta0RNIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9maW5hbGl6ZS8xMTQwODA3NS81Nzc5OTQ3MSIsImFsZyI6IkVTMjU2In0\",\"payload\":\"eyJjc3IiOiJNSUlEVHpDQ0FqY0NBUUF3TlRFek1ERUdBMVVFQXd3cWVHNHRMV1p2YnkxaFkyMWxhbk10TW1WaE5DMTZhemg0TG5SbGMzUXVkWFJoYUhKMWMzUXVZMjl0TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUE1ZUFqTlc4bk5CRzRhU2lhZ3BET3pBM0l4b1VIc3lZSWMwNWIzekpqRjhla3VxTW9wMTJtVW1tOGREOUh3alhuN2Z4Qi1wUDJTLVAyOUR1TGVCVG9CVEpybnBLRHBOSktaUVRrOEVfdFo4U2ZfWTc2SWZObjhONi1yODZyUjhkQ2dYNU8wUVdDY3hWbGt2aUNjVEp5SkxOQ3psLTJmNGQ2TXNaaVlFZm00bVU4WUtuMlV3NTc3T0xCNVlwdVpsZ083aEN1cF9kTVFkc1U2a0UtQWxTZHJpM1BUR1loSjNITzVIMUQtMko3ZHI1d0tOWGpHM0VKdVUzMWJmc29MVldjU1F1MURDQ2JyT05rQlZKUlk0dGxTb3hVdllmNy1OSGxuMnZWZGg2OFFPVUNYZm14YlNSb1JfOFYwdC12eTE5dHVjOVN4VzlFdmtqbUIwdWJLaWNseHdJREFRQUJvSUhVTUlIUkJna3Foa2lHOXcwQkNRNHhnY013Z2NBd2diMEdBMVVkRVFTQnRUQ0Jzb0lxZUc0dExXWnZieTFoWTIxbGFuTXRNbVZoTkMxNmF6aDRMblJsYzNRdWRYUmhhSEoxYzNRdVkyOXRnaXA0YmkwdFltRnlMV0ZqYldWcWN5MHlaV0UwTFhwck9IZ3VkR1Z6ZEM1MWRHRm9jblZ6ZEM1amIyMkNMQ291ZUc0dExXSmhlaTFoWTIxbGFuTXRNbVZoTkMxNmF6aDRMblJsYzNRdWRYUmhhSEoxYzNRdVkyOXRnaXA0YmkwdFltRjZMV0ZqYldWcWN5MHlaV0UwTFhwck9IZ3VkR1Z6ZEM1MWRHRm9jblZ6ZEM1amIyMHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBTV9idU84N0YtMkd2aThKZmlaZ3ZYNGNvUnllLVhSSDVnbTJ6enRjNW1KS0ZxRmRBdkV5Z0IxbE82NmJaTG5uZjk5bWRROFk5UnQ0R1RiU3N5N1djQ1NMVF91MVNGX0h1REU5SnZ2ek43MnU1VmtlLW1KelB0cG1OcTlRODZpRWNVQnVEMmNfVVVCQ0Y2ZEFsTHhUZmRQRkJWdXBPSnVCRmQ4azdBNlhhbTl0UjFKV3p4RGdrSHM1cTdmSWo1dXVLcmdjSlhWc19lWHA0QkNONEcyM2hKX01YR1RidDhqeHU1MTFOaDE0Z18wT3JlWkw1bHd5MWR5ZE9mN0pLdGpUdmtyQWE1YjJDVXlLa293NHlaLTNoUmVRcHZjVnIzcnRaTWtKdndMMHI5WjcxcENHRjViUVEweDBIVk04VzYtVkotTWJpLVlhTC04TjNyNEpTbWdDN09VIn0\",\"signature\":\"_X0X-Wg86dr5mF0eS0GOYNSmO0HCenlIGQeMygRVoH7BpYO0AMK_mgRQlNR3MWNMULC_aQ-oEMtsXGMXrTa7VA\"}",
|
||||
"method": "POST"
|
||||
}
|
14
fixtures/finalize.valid.response.headers.json
Normal file
14
fixtures/finalize.valid.response.headers.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:44 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "993",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11408075",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\"",
|
||||
"location": "https://acme-staging-v02.api.letsencrypt.org/acme/order/11408075/57799471",
|
||||
"replay-nonce": "00011-njQ_u1jx7WjTG_cPejm9QLKelEqEEtJDkreTry9R8",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
}
|
27
fixtures/finalize.valid.response.json
Normal file
27
fixtures/finalize.valid.response.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"status": "valid",
|
||||
"expires": "2019-10-31T23:41:32Z",
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "dns",
|
||||
"value": "*.xn--baz-acmejs-2ea4-zk8x.test.utahrust.com"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"value": "xn--bar-acmejs-2ea4-zk8x.test.utahrust.com"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"value": "xn--baz-acmejs-2ea4-zk8x.test.utahrust.com"
|
||||
},
|
||||
{ "type": "dns", "value": "xn--foo-acmejs-2ea4-zk8x.test.utahrust.com" }
|
||||
],
|
||||
"authorizations": [
|
||||
"https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603341",
|
||||
"https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603342",
|
||||
"https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603343",
|
||||
"https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603344"
|
||||
],
|
||||
"finalize": "https://acme-staging-v02.api.letsencrypt.org/acme/finalize/11408075/57799471",
|
||||
"certificate": "https://acme-staging-v02.api.letsencrypt.org/acme/cert/fa78326c21c0c7f06c03931900bead4fe3ee"
|
||||
}
|
7
fixtures/nonce.request.json
Normal file
7
fixtures/nonce.request.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"method": "HEAD",
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce",
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 Darwin darwin/17.7.0 Darwin/x64"
|
||||
}
|
||||
}
|
10
fixtures/nonce.response.headers.json
Normal file
10
fixtures/nonce.response.headers.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:24 GMT",
|
||||
"connection": "close",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\"",
|
||||
"replay-nonce": "0001IPeC3ta_uKoe-5GjpqQYFR1C-QcKJsTUZsGZMQOK69g",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
}
|
1
fixtures/nonce.response.txt
Normal file
1
fixtures/nonce.response.txt
Normal file
@ -0,0 +1 @@
|
||||
// there is no nonce response body, see the headers
|
15
fixtures/order.request.json
Normal file
15
fixtures/order.request.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/new-order",
|
||||
"json": {
|
||||
"protected": "eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDJPMWRvd3FhRVFXRUh0UDJDejlCWUp1T1U5MXVSdlJNMXVQRmJjZHdhai0wIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9uZXctb3JkZXIiLCJhbGciOiJFUzI1NiJ9",
|
||||
"payload": "eyJpZGVudGlmaWVycyI6W3sidHlwZSI6ImRucyIsInZhbHVlIjoieG4tLWZvby1hY21lanMtMmVhNC16azh4LnRlc3QudXRhaHJ1c3QuY29tIn0seyJ0eXBlIjoiZG5zIiwidmFsdWUiOiJ4bi0tYmFyLWFjbWVqcy0yZWE0LXprOHgudGVzdC51dGFocnVzdC5jb20ifSx7InR5cGUiOiJkbnMiLCJ2YWx1ZSI6IioueG4tLWJhei1hY21lanMtMmVhNC16azh4LnRlc3QudXRhaHJ1c3QuY29tIn0seyJ0eXBlIjoiZG5zIiwidmFsdWUiOiJ4bi0tYmF6LWFjbWVqcy0yZWE0LXprOHgudGVzdC51dGFocnVzdC5jb20ifV19",
|
||||
"signature": "Bw8cjSwQj_rFooUFL61gqiuLXec-8x4anHNF1ueVt_LvoCO70bYt0fM26W4hOJ9Es6fibmYazFKSTPwdgnLm2Q"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "ACME.js/v3 node/v10.13.0 darwin/17.7.0 Darwin/x64",
|
||||
"Content-Type": "application/jose+json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"body": "{\"protected\":\"eyJraWQiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTE0MDgwNzUiLCJub25jZSI6IjAwMDJPMWRvd3FhRVFXRUh0UDJDejlCWUp1T1U5MXVSdlJNMXVQRmJjZHdhai0wIiwidXJsIjoiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9uZXctb3JkZXIiLCJhbGciOiJFUzI1NiJ9\",\"payload\":\"eyJpZGVudGlmaWVycyI6W3sidHlwZSI6ImRucyIsInZhbHVlIjoieG4tLWZvby1hY21lanMtMmVhNC16azh4LnRlc3QudXRhaHJ1c3QuY29tIn0seyJ0eXBlIjoiZG5zIiwidmFsdWUiOiJ4bi0tYmFyLWFjbWVqcy0yZWE0LXprOHgudGVzdC51dGFocnVzdC5jb20ifSx7InR5cGUiOiJkbnMiLCJ2YWx1ZSI6IioueG4tLWJhei1hY21lanMtMmVhNC16azh4LnRlc3QudXRhaHJ1c3QuY29tIn0seyJ0eXBlIjoiZG5zIiwidmFsdWUiOiJ4bi0tYmF6LWFjbWVqcy0yZWE0LXprOHgudGVzdC51dGFocnVzdC5jb20ifV19\",\"signature\":\"Bw8cjSwQj_rFooUFL61gqiuLXec-8x4anHNF1ueVt_LvoCO70bYt0fM26W4hOJ9Es6fibmYazFKSTPwdgnLm2Q\"}",
|
||||
"method": "POST"
|
||||
}
|
14
fixtures/order.response.headers.json
Normal file
14
fixtures/order.response.headers.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"server": "nginx",
|
||||
"date": "Thu, 24 Oct 2019 23:41:32 GMT",
|
||||
"content-type": "application/json",
|
||||
"content-length": "893",
|
||||
"connection": "close",
|
||||
"boulder-requester": "11408075",
|
||||
"cache-control": "public, max-age=0, no-cache",
|
||||
"link": "<https://acme-staging-v02.api.letsencrypt.org/directory>;rel=\"index\"",
|
||||
"location": "https://acme-staging-v02.api.letsencrypt.org/acme/order/11408075/57799471",
|
||||
"replay-nonce": "0001j4Azsd0kk6i60NSzRoZcvLidmLo5B0sG1lMKTqWr388",
|
||||
"x-frame-options": "DENY",
|
||||
"strict-transport-security": "max-age=604800"
|
||||
}
|
26
fixtures/order.response.json
Normal file
26
fixtures/order.response.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"status": "pending",
|
||||
"expires": "2019-10-31T23:41:32.669736375Z",
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "dns",
|
||||
"value": "*.xn--baz-acmejs-2ea4-zk8x.test.utahrust.com"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"value": "xn--bar-acmejs-2ea4-zk8x.test.utahrust.com"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"value": "xn--baz-acmejs-2ea4-zk8x.test.utahrust.com"
|
||||
},
|
||||
{ "type": "dns", "value": "xn--foo-acmejs-2ea4-zk8x.test.utahrust.com" }
|
||||
],
|
||||
"authorizations": [
|
||||
"https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603341",
|
||||
"https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603342",
|
||||
"https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603343",
|
||||
"https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/16603344"
|
||||
],
|
||||
"finalize": "https://acme-staging-v02.api.letsencrypt.org/acme/finalize/11408075/57799471"
|
||||
}
|
20
fixtures/server.jwk.json
Normal file
20
fixtures/server.jwk.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"private": {
|
||||
"kty": "RSA",
|
||||
"n": "ud6agEF9P6H66ciYgvZ_FakyZKossq5i6J2D4wIcJBnem5X63t7u3E7Rpc7rgVB5MElUNZmBoVO3VbaVJpiG0tS5zxkOZcj_k6C_5LXBdTHinG0bFZHtV6Wapf5fJ4PXNp71AHWv09qz4swJzz6_Rp_7ovNpivVsdVHfd8g9HqH3sjouwfIGfo-1LLm0F4NM12AJZISFt_03knhbvtd5x4ASorBiENPPnv2s7SA5kFT1Seeu-iUCq8PlKi-HMbNrLeM2E3wYySQPSSDt6UXRTvIzW_8upXRvaVThJk3wWjx-qt1CUIFoZBh2RsmiujWFFc6ORXb3GlF3U4LaMt3YEw",
|
||||
"e": "AQAB",
|
||||
"d": "YCzN9yVr4Jw5D_UK7WEMuzGUcMAZZs-TQFgY4UK7Ovbj18_QQrhKElb6Zfhepcf1HUYkO6PVjpuZ1tEl9hWgVcFa781AROyvSj04beiaVMDeSCCwjgW3MM3w6olnxTOUDaBMl9NNiqq0v9riDImkQbAQbe3To-KAH2ig4AMNlSZJAhmI2zAMiJhQE_pAcCxc-bQ5oNO-WSU0GRHWdMJSXp9mFgoBhVPDYGW-dmnoFzuNWssxlSqGXY-8a2YOuiunK6XM5_80c1eQqmy-k1InUIViR_wljskc8UiH6xa8BCznZYacgSz4PnvKsiKWKQQ1eliIucV3MC6BzMD3N8EWqQ",
|
||||
"p": "8NUtOIglu0dvDGmEB7QC5eC02Y2jZKnoxHSPKMAEPxQ0131_2aL49IzADWoTvae3NBPzU7ol3RwJo_GvS967OysfOr6Od699p1FSLwLfK89aql7_uVPJh4Q43H-W_NtRHKUkv0OmkDiwa4WqBQTVfREdPQ3NJT7vIY-cqH_AMRc",
|
||||
"q": "xZNIl9NRl3b0_V8Y-7_6_foIu9Sx5ILv2XV7WONDx2jp4vuT7byLm1UWdYPBbxLyd5TAvWqtyvaRtVNyplrD0PyyPK3NxqVJde0uzScAU-bf25DeK30V22Xo7IEZiPZoizrjtzGnS6VVNJmZ-Ictz3xmWIudw5d5XDH12fFRlmU",
|
||||
"dp": "F1Ld9UqiNNf_NjmF0uUpHrA7c5JXD6mw5E3Ri4XFI4LGd1QtLJuu9qgm9WWfkc-LW5zPBP3TKu3LNThz3KougdV0SdEopQi255xllC34BRso0bUvmPg3XUt94kTtD4ICAf8wZuGbYP5Mf61LQP8t2dXtefs7Me89Y4ewCVWN_HM",
|
||||
"dq": "oPuT35lgVtCnZ7dPrPjNMpnC-gCg_fcuJPqTiWaLuHQkdjzUWJYTDnqy9Qdo2e8PPx4mOXAtsT1clekrdp5oBOWQ-N4I172fcIXUZ3ZKzxJD_iw4yih-YajUs7exLabQoflWx9KeZIWPOm-ZRCYoznGnFqiT4GWQje1rS6xT9P0",
|
||||
"qi": "aXkK-w4Npw0BpUEzQ1PURVGm5y5cKIdd-CfEYwub19rronI9EEvuQHoqR7ODtZ_mlIIffHmHaM3ug50fJDB9QDOG4Ioc5S4YxVURT58Ps8at-dQAAP1UgSlV3vhXh4WZRaDECUI_728U3fxQqH78bJsy81mU8MtGU8LR_eTMXx8",
|
||||
"kid": "1hxSLs31DwbGo532keMUL9eY8L6gWyYlbcr0TtiV7qk"
|
||||
},
|
||||
"public": {
|
||||
"kty": "RSA",
|
||||
"n": "ud6agEF9P6H66ciYgvZ_FakyZKossq5i6J2D4wIcJBnem5X63t7u3E7Rpc7rgVB5MElUNZmBoVO3VbaVJpiG0tS5zxkOZcj_k6C_5LXBdTHinG0bFZHtV6Wapf5fJ4PXNp71AHWv09qz4swJzz6_Rp_7ovNpivVsdVHfd8g9HqH3sjouwfIGfo-1LLm0F4NM12AJZISFt_03knhbvtd5x4ASorBiENPPnv2s7SA5kFT1Seeu-iUCq8PlKi-HMbNrLeM2E3wYySQPSSDt6UXRTvIzW_8upXRvaVThJk3wWjx-qt1CUIFoZBh2RsmiujWFFc6ORXb3GlF3U4LaMt3YEw",
|
||||
"e": "AQAB",
|
||||
"kid": "1hxSLs31DwbGo532keMUL9eY8L6gWyYlbcr0TtiV7qk"
|
||||
}
|
||||
}
|
54
lib/browser.js
Normal file
54
lib/browser.js
Normal file
@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
var native = module.exports;
|
||||
|
||||
native._canCheck = function(me) {
|
||||
me._canCheck = {};
|
||||
return me
|
||||
.request({ url: me._baseUrl + '/api/_acme_api_/' })
|
||||
.then(function(resp) {
|
||||
if (resp.body.success) {
|
||||
me._canCheck['http-01'] = true;
|
||||
me._canCheck['dns-01'] = true;
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
// ignore
|
||||
});
|
||||
};
|
||||
|
||||
native._dns01 = function(me, ch) {
|
||||
return me
|
||||
.request({
|
||||
url: me._baseUrl + '/api/dns/' + ch.dnsHost + '?type=TXT'
|
||||
})
|
||||
.then(function(resp) {
|
||||
var err;
|
||||
if (!resp.body || !Array.isArray(resp.body.answer)) {
|
||||
err = new Error('failed to get DNS response');
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
if (!resp.body.answer.length) {
|
||||
err = new Error('failed to get DNS answer record in response');
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
return {
|
||||
answer: resp.body.answer.map(function(ans) {
|
||||
return { data: ans.data, ttl: ans.ttl };
|
||||
})
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
native._http01 = function(me, ch) {
|
||||
var url = encodeURIComponent(ch.challengeUrl);
|
||||
return me
|
||||
.request({
|
||||
url: me._baseUrl + '/api/http?url=' + url
|
||||
})
|
||||
.then(function(resp) {
|
||||
return resp.body;
|
||||
});
|
||||
};
|
6
lib/browser/client-user-agent.js
Normal file
6
lib/browser/client-user-agent.js
Normal file
@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
var UserAgent = module.exports;
|
||||
UserAgent.get = function() {
|
||||
return false;
|
||||
};
|
33
lib/browser/http.js
Normal file
33
lib/browser/http.js
Normal file
@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
var http = module.exports;
|
||||
|
||||
http.request = function(opts) {
|
||||
opts.cors = true;
|
||||
return window.fetch(opts.url, opts).then(function(resp) {
|
||||
var headers = {};
|
||||
var result = {
|
||||
statusCode: resp.status,
|
||||
headers: headers,
|
||||
toJSON: function() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
Array.from(resp.headers.entries()).forEach(function(h) {
|
||||
headers[h[0]] = h[1];
|
||||
});
|
||||
if (!headers['content-type']) {
|
||||
return result;
|
||||
}
|
||||
if (/json/.test(headers['content-type'])) {
|
||||
return resp.json().then(function(json) {
|
||||
result.body = json;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
return resp.text().then(function(txt) {
|
||||
result.body = txt;
|
||||
return result;
|
||||
});
|
||||
});
|
||||
};
|
13
lib/browser/sha2.js
Normal file
13
lib/browser/sha2.js
Normal file
@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
var sha2 = module.exports;
|
||||
|
||||
var encoder = new TextEncoder();
|
||||
sha2.sum = function(alg, str) {
|
||||
var data = str;
|
||||
if ('string' === typeof data) {
|
||||
data = encoder.encode(str);
|
||||
}
|
||||
var sha = 'SHA-' + String(alg).replace(/^sha-?/i, '');
|
||||
return window.crypto.subtle.digest(sha, data);
|
||||
};
|
88
lib/native.js
Normal file
88
lib/native.js
Normal file
@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
|
||||
var native = module.exports;
|
||||
var promisify = require('util').promisify;
|
||||
var resolveTxt = promisify(require('dns').resolveTxt);
|
||||
var crypto = require('crypto');
|
||||
|
||||
native._canCheck = function(me) {
|
||||
me._canCheck = {};
|
||||
me._canCheck['http-01'] = true;
|
||||
me._canCheck['dns-01'] = true;
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
native._dns01 = function(me, ch) {
|
||||
// TODO use digd.js
|
||||
return resolveTxt(ch.dnsHost).then(function(records) {
|
||||
return {
|
||||
answer: records.map(function(rr) {
|
||||
return {
|
||||
data: rr
|
||||
};
|
||||
})
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
native._http01 = function(me, ch) {
|
||||
return new me.request({
|
||||
url: ch.challengeUrl
|
||||
}).then(function(resp) {
|
||||
return resp.body;
|
||||
});
|
||||
};
|
||||
|
||||
// the hashcash here is for browser parity only
|
||||
// basically we ask the client to find a needle in a haystack
|
||||
// (very similar to CloudFlare's api protection)
|
||||
native._hashcash = function(ch) {
|
||||
if (!ch || !ch.nonce) {
|
||||
ch = { nonce: 'xxx' };
|
||||
}
|
||||
return Promise.resolve()
|
||||
.then(function() {
|
||||
// only get easy answers
|
||||
var len = ch.needle.length;
|
||||
var start = ch.start || 0;
|
||||
var end = ch.end || Math.ceil(len / 2);
|
||||
var window = parseInt(end - start, 10) || 0;
|
||||
|
||||
var maxLen = 6;
|
||||
var maxTries = Math.pow(2, maxLen * 8);
|
||||
if (
|
||||
len > maxLen ||
|
||||
window < Math.ceil(len / 2) ||
|
||||
ch.needle.toLowerCase() !== ch.needle ||
|
||||
ch.alg !== 'SHA-256'
|
||||
) {
|
||||
// bail unless the server is issuing very easy challenges
|
||||
throw new Error('possible and easy answers only, please');
|
||||
}
|
||||
|
||||
var haystack;
|
||||
var i;
|
||||
var answer;
|
||||
var needle = Buffer.from(ch.needle, 'hex');
|
||||
for (i = 0; i < maxTries; i += 1) {
|
||||
answer = i.toString(16);
|
||||
if (answer.length % 2) {
|
||||
answer = '0' + answer;
|
||||
}
|
||||
haystack = crypto
|
||||
.createHash('sha256')
|
||||
.update(Buffer.from(ch.nonce + answer, 'hex'))
|
||||
.digest()
|
||||
.slice(ch.start, ch.end);
|
||||
if (-1 !== haystack.indexOf(needle)) {
|
||||
return ch.nonce + ':' + answer;
|
||||
}
|
||||
}
|
||||
return ch.nonce + ':xxx';
|
||||
})
|
||||
.catch(function() {
|
||||
//console.log('[debug]', err);
|
||||
// ignore any error
|
||||
return ch.nonce + ':xxx';
|
||||
});
|
||||
};
|
37
lib/node/client-user-agent.js
Normal file
37
lib/node/client-user-agent.js
Normal file
@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
var os = require('os');
|
||||
var ver = require('../../package.json').version;
|
||||
|
||||
var UserAgent = module.exports;
|
||||
UserAgent.get = function(me) {
|
||||
// ACME clients MUST have an RFC7231-compliant User-Agent
|
||||
// ex: Greenlock/v3 ACME.js/v3 node/v12.0.0 darwin/17.7.0 Darwin/x64
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc8555#section-6.1
|
||||
// And https://tools.ietf.org/html/rfc7231#section-5.5.3
|
||||
// And https://community.letsencrypt.org/t/user-agent-flag-explained/3843/2
|
||||
|
||||
var ua =
|
||||
'ACME.js/' +
|
||||
ver +
|
||||
' ' +
|
||||
process.release.name +
|
||||
'/' +
|
||||
process.version +
|
||||
' ' +
|
||||
os.platform() +
|
||||
'/' +
|
||||
os.release() +
|
||||
' ' +
|
||||
os.type() +
|
||||
'/' +
|
||||
process.arch;
|
||||
|
||||
var pkg = me.packageAgent;
|
||||
if (pkg) {
|
||||
ua = pkg + ' ' + ua;
|
||||
}
|
||||
|
||||
return ua;
|
||||
};
|
9
lib/node/http.js
Normal file
9
lib/node/http.js
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
var http = module.exports;
|
||||
var promisify = require('util').promisify;
|
||||
var request = promisify(require('@root/request'));
|
||||
|
||||
http.request = function(opts) {
|
||||
return request(opts);
|
||||
};
|
17
lib/node/sha2.js
Normal file
17
lib/node/sha2.js
Normal file
@ -0,0 +1,17 @@
|
||||
/* global Promise */
|
||||
'use strict';
|
||||
|
||||
var sha2 = module.exports;
|
||||
var crypto = require('crypto');
|
||||
|
||||
sha2.sum = function(alg, str) {
|
||||
return Promise.resolve().then(function() {
|
||||
var sha = 'sha' + String(alg).replace(/^sha-?/i, '');
|
||||
// utf8 is the default for strings
|
||||
var buf = Buffer.from(str);
|
||||
return crypto
|
||||
.createHash(sha)
|
||||
.update(buf)
|
||||
.digest();
|
||||
});
|
||||
};
|
85
maintainers.js
Normal file
85
maintainers.js
Normal file
@ -0,0 +1,85 @@
|
||||
'use strict';
|
||||
|
||||
var M = module.exports;
|
||||
var native = require('./lib/native.js');
|
||||
|
||||
// Keep track of active maintainers so that we know who to inform if
|
||||
// something breaks or has a serious bug or flaw.
|
||||
|
||||
var oldCollegeTries = {};
|
||||
M.init = function(me) {
|
||||
if (oldCollegeTries[me.maintainerEmail]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tz = '';
|
||||
try {
|
||||
// Use timezone to stagger messages to maintainers
|
||||
tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
} catch (e) {
|
||||
// ignore node versions with no or incomplete Intl
|
||||
}
|
||||
|
||||
// Use locale to know what language to use
|
||||
var env = process.env;
|
||||
var locale = env.LC_ALL || env.LC_MESSAGES || env.LANG || env.LANGUAGE;
|
||||
|
||||
try {
|
||||
M._init(me, tz, locale);
|
||||
} catch (e) {
|
||||
//console.log(e);
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
M._init = function(me, tz, locale) {
|
||||
// prevent a stampede from misconfigured clients in an eternal loop
|
||||
setTimeout(function() {
|
||||
me.request({
|
||||
method: 'GET',
|
||||
url: 'https://api.rootprojects.org/api/nonce',
|
||||
json: true
|
||||
})
|
||||
.then(function(resp) {
|
||||
// in the browser this will work until solved, but in
|
||||
// node this will bail unless the challenge is trivial
|
||||
return native._hashcash(resp.body || {});
|
||||
})
|
||||
.then(function(hashcash) {
|
||||
var req = {
|
||||
headers: {
|
||||
'x-root-nonce-v1': hashcash
|
||||
},
|
||||
method: 'POST',
|
||||
url:
|
||||
'https://api.rootprojects.org/api/projects/ACME.js/dependents',
|
||||
json: {
|
||||
maintainer: me.maintainerEmail,
|
||||
tz: tz,
|
||||
locale: locale
|
||||
}
|
||||
};
|
||||
return me
|
||||
.request(req)
|
||||
.catch(function(err) {
|
||||
if (true || me.debug) {
|
||||
console.error(err);
|
||||
}
|
||||
})
|
||||
.then(function(/*resp*/) {
|
||||
oldCollegeTries[me.maintainerEmail] = true;
|
||||
//console.log(resp);
|
||||
});
|
||||
});
|
||||
}, me.__timeout || 3000);
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
var ACME = require('./');
|
||||
var acme = ACME.create({
|
||||
maintainerEmail: 'aj+acme-test@rootprojects.org',
|
||||
packageAgent: 'test/v0',
|
||||
__timeout: 100
|
||||
});
|
||||
M.init(acme);
|
||||
}
|
241
package-lock.json
generated
241
package-lock.json
generated
@ -1,46 +1,227 @@
|
||||
{
|
||||
"name": "acme-v2",
|
||||
"version": "1.8.6",
|
||||
"name": "@root/acme",
|
||||
"version": "3.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@root/asn1": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@root/asn1/-/asn1-1.0.0.tgz",
|
||||
"integrity": "sha512-0lfZNuOULKJDJmdIkP8V9RnbV3XaK6PAHD3swnFy4tZwtlMDzLKoM/dfNad7ut8Hu3r91wy9uK0WA/9zym5mig==",
|
||||
"requires": {
|
||||
"@root/encoding": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@root/csr": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@root/csr/-/csr-0.8.1.tgz",
|
||||
"integrity": "sha512-hKl0VuE549TK6SnS2Yn9nRvKbFZXn/oAg+dZJU/tlKl/f/0yRXeuUzf8akg3JjtJq+9E592zDqeXZ7yyrg8fSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@root/asn1": "^1.0.0",
|
||||
"@root/pem": "^1.0.4",
|
||||
"@root/x509": "^0.7.2"
|
||||
}
|
||||
},
|
||||
"@root/encoding": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz",
|
||||
"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ=="
|
||||
},
|
||||
"@root/keypairs": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.9.0.tgz",
|
||||
"integrity": "sha512-NXE2L9Gv7r3iC4kB/gTPZE1vO9Ox/p14zDzAJ5cGpTpytbWOlWF7QoHSJbtVX4H7mRG/Hp7HR3jWdWdb2xaaXg==",
|
||||
"requires": {
|
||||
"@root/encoding": "^1.0.1",
|
||||
"@root/pem": "^1.0.4",
|
||||
"@root/x509": "^0.7.2"
|
||||
}
|
||||
},
|
||||
"@root/pem": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@root/pem/-/pem-1.0.4.tgz",
|
||||
"integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA=="
|
||||
},
|
||||
"@root/request": {
|
||||
"version": "1.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.11.tgz",
|
||||
"integrity": "sha512-3a4Eeghcjsfe6zh7EJ+ni1l8OK9Fz2wL1OjP4UCa0YdvtH39kdXB9RGWuzyNv7dZi0+Ffkc83KfH0WbPMiuJFw=="
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.0.0.tgz",
|
||||
"integrity": "sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg==",
|
||||
"@root/x509": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@root/x509/-/x509-0.7.2.tgz",
|
||||
"integrity": "sha512-ENq3LGYORK5NiMFHEVeNMt+fTXaC7DTS6sQXoqV+dFdfT0vmiL5cDLjaXQhaklJQq0NiwicZegzJRl1ZOTp3WQ==",
|
||||
"requires": {
|
||||
"@root/asn1": "^1.0.0",
|
||||
"@root/encoding": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"eckles": {
|
||||
"bluebird": {
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz",
|
||||
"integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz",
|
||||
"integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"exit": "0.1.2",
|
||||
"glob": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"dig.js": {
|
||||
"version": "1.3.9",
|
||||
"resolved": "https://registry.npmjs.org/dig.js/-/dig.js-1.3.9.tgz",
|
||||
"integrity": "sha512-O/tSWZuW7AwpjsgePPmTanwvSDL9xF+FzLTJD9byN3C6lk79iMejC/Ahz9CERAXTW4e2TXL1vtqh3T0Ug79ocA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cli": "^1.0.1",
|
||||
"dns-suite": "git+https://git.coolaj86.com/coolaj86/dns-suite.js#v1.2",
|
||||
"hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"dns-suite": {
|
||||
"version": "git+https://git.coolaj86.com/coolaj86/dns-suite.js#092008f766540909d27c934211495c9e03705bf3",
|
||||
"from": "git+https://git.coolaj86.com/coolaj86/dns-suite.js#v1.2",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird": "^3.5.0",
|
||||
"hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dns-suite": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/dns-suite/-/dns-suite-1.2.13.tgz",
|
||||
"integrity": "sha512-veYKPHUc2RfRCe7c4G/iKxhRv0S4InJ3JsW8tEhW6Yb7dn3ac34iozC6cNX0uzHYZUw0BG5V9Fu65L1bx1GeBg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@root/hexdump": "^1.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@root/hexdump": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@root/hexdump/-/hexdump-1.1.1.tgz",
|
||||
"integrity": "sha512-AmrmLOutlzctR599ittO06lINOco1TIqb0c1wu83fP2Eoi5iSvx7kVWC4mDufze8rxPewC+aQOx4e6Pw7izV4A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
|
||||
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==",
|
||||
"dev": true
|
||||
},
|
||||
"exit": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
|
||||
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
|
||||
"dev": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz",
|
||||
"integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"hexdump.js": {
|
||||
"version": "git+https://git.coolaj86.com/coolaj86/hexdump.js#222fa7de5036a16397de2fe703c35ac54a3d8d0c",
|
||||
"from": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4",
|
||||
"dev": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"punycode": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.1.tgz",
|
||||
"integrity": "sha512-auWyk/k8oSkVHaD4RxkPadKsLUcIwKgr/h8F7UZEueFDBO7BsE4y+H6IMUDbfqKIFPg/9MxV6KcBdJCmVVcxSA=="
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
|
||||
"dev": true
|
||||
},
|
||||
"keypairs": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/keypairs/-/keypairs-1.2.14.tgz",
|
||||
"integrity": "sha512-ZoZfZMygyB0QcjSlz7Rh6wT2CJasYEHBPETtmHZEfxuJd7bnsOG5AdtPZqHZBT+hoHvuWCp/4y8VmvTvH0Y9uA==",
|
||||
"requires": {
|
||||
"eckles": "^1.4.1",
|
||||
"rasha": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"rasha": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.5.tgz",
|
||||
"integrity": "sha512-KxtX+/fBk+wM7O3CNgwjSh5elwFilLvqWajhr6wFr2Hd63JnKTTi43Tw+Jb1hxJQWOwoya+NZWR2xztn3hCrTw=="
|
||||
},
|
||||
"rsa-compat": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/rsa-compat/-/rsa-compat-2.0.8.tgz",
|
||||
"integrity": "sha512-BFiiSEbuxzsVdaxpejbxfX07qs+rtous49Y6mL/zw6YHh9cranDvm2BvBmqT3rso84IsxNlP5BXnuNvm1Wn3Tw==",
|
||||
"requires": {
|
||||
"keypairs": "^1.2.14"
|
||||
}
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
62
package.json
62
package.json
@ -1,41 +1,61 @@
|
||||
{
|
||||
"name": "acme-v2",
|
||||
"version": "1.8.6",
|
||||
"description": "A lightweight library for getting Free SSL certifications through Let's Encrypt, using the ACME protocol.",
|
||||
"homepage": "https://git.coolaj86.com/coolaj86/acme-v2.js",
|
||||
"main": "index.js",
|
||||
"name": "@root/acme",
|
||||
"version": "3.0.0",
|
||||
"description": "Free SSL certificates for Node.js and Browsers. Issued via Let's Encrypt",
|
||||
"homepage": "https://rootprojects.org/acme/",
|
||||
"main": "acme.js",
|
||||
"browser": {
|
||||
"./lib/native.js": "./lib/browser.js",
|
||||
"./lib/node/sha2.js": "./lib/browser/sha2.js",
|
||||
"./lib/node/http.js": "./lib/browser/http.js",
|
||||
"./lib/node/client-user-agent.js": "./lib/browser/client-user-agent.js"
|
||||
},
|
||||
"files": [
|
||||
"compat.js",
|
||||
"*.js",
|
||||
"lib",
|
||||
"scripts"
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "node_xxx bin/bundle.js",
|
||||
"lint": "jshint lib bin",
|
||||
"postinstall": "node scripts/postinstall",
|
||||
"test": "node ./test.js"
|
||||
"test": "node server.js",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.coolaj86.com/coolaj86/acme-v2.js.git"
|
||||
"url": "https://git.rootprojects.org/root/acme.js.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Let's Encrypt",
|
||||
"ACME",
|
||||
"v02",
|
||||
"v2",
|
||||
"draft-11",
|
||||
"draft-12",
|
||||
"free ssl",
|
||||
"tls",
|
||||
"automated https",
|
||||
"letsencrypt"
|
||||
"Let's Encrypt",
|
||||
"EC",
|
||||
"RSA",
|
||||
"CSR",
|
||||
"browser",
|
||||
"greenlock",
|
||||
"VanillaJS",
|
||||
"ZeroSSL"
|
||||
],
|
||||
"author": "AJ ONeal <coolaj86@gmail.com> (https://solderjs.com/)",
|
||||
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@root/encoding": "^1.0.1",
|
||||
"@root/keypairs": "^0.9.0",
|
||||
"@root/pem": "^1.0.4",
|
||||
"@root/request": "^1.3.11",
|
||||
"rsa-compat": "^2.0.8"
|
||||
"@root/x509": "^0.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "^8.0.0"
|
||||
"@root/csr": "^0.8.1",
|
||||
"dig.js": "^1.3.9",
|
||||
"dns-suite": "^1.2.13",
|
||||
"dotenv": "^8.1.0",
|
||||
"punycode": "^1.4.1"
|
||||
},
|
||||
"trulyOptionalDependencies": {
|
||||
"eslint": "^6.5.1",
|
||||
"webpack": "^4.41.0",
|
||||
"webpack-cli": "^3.3.9"
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
// BG WH \u001b[47m
|
||||
// BOLD \u001b[1m
|
||||
// RED \u001b[31m
|
||||
// GREEN \u001b[32m
|
||||
// RESET \u001b[0m
|
||||
|
||||
setTimeout(function() {
|
||||
[
|
||||
'',
|
||||
'\u001b[31mGreenlock and ACME.js v3 are on the way!\u001b[0m',
|
||||
'Watch for updates at https://indiegogo.com/at/greenlock',
|
||||
''
|
||||
]
|
||||
.forEach(function(line) {
|
||||
console.info(line);
|
||||
});
|
||||
}, 300);
|
||||
|
||||
setTimeout(function() {
|
||||
// give time to read
|
||||
}, 1500);
|
||||
// TODO put postinstall back
|
||||
|
3
test.js
3
test.js
@ -1,3 +0,0 @@
|
||||
'use strict';
|
||||
require('dotenv').config();
|
||||
require('./examples/dns-01-digitalocean.js');
|
118
tests/cb.js
118
tests/cb.js
@ -1,118 +0,0 @@
|
||||
// Copyright 2018 AJ ONeal. All rights reserved
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
|
||||
module.exports.run = function run(
|
||||
directoryUrl,
|
||||
RSA,
|
||||
web,
|
||||
chType,
|
||||
email,
|
||||
accountKeypair,
|
||||
domainKeypair
|
||||
) {
|
||||
// [ 'test.ppl.family' ] 'coolaj86@gmail.com''http-01'
|
||||
var acme2 = require('../').ACME.create({ RSA: RSA });
|
||||
acme2.init(directoryUrl).then(function() {
|
||||
var options = {
|
||||
agreeToTerms: function(tosUrl, agree) {
|
||||
agree(null, tosUrl);
|
||||
},
|
||||
setChallenge: function(opts, cb) {
|
||||
var pathname;
|
||||
|
||||
console.log('');
|
||||
console.log('identifier:');
|
||||
console.log(opts.identifier);
|
||||
console.log('hostname:');
|
||||
console.log(opts.hostname);
|
||||
console.log('type:');
|
||||
console.log(opts.type);
|
||||
console.log('token:');
|
||||
console.log(opts.token);
|
||||
console.log('thumbprint:');
|
||||
console.log(opts.thumbprint);
|
||||
console.log('keyAuthorization:');
|
||||
console.log(opts.keyAuthorization);
|
||||
console.log('dnsAuthorization:');
|
||||
console.log(opts.dnsAuthorization);
|
||||
console.log('');
|
||||
|
||||
if ('http-01' === opts.type) {
|
||||
pathname =
|
||||
opts.hostname +
|
||||
acme2.challengePrefixes['http-01'] +
|
||||
'/' +
|
||||
opts.token;
|
||||
console.log(
|
||||
"Put the string '" +
|
||||
opts.keyAuthorization +
|
||||
"' into a file at '" +
|
||||
pathname +
|
||||
"'"
|
||||
);
|
||||
console.log(
|
||||
"echo '" + opts.keyAuthorization + "' > '" + pathname + "'"
|
||||
);
|
||||
} else if ('dns-01' === opts.type) {
|
||||
pathname =
|
||||
acme2.challengePrefixes['dns-01'] +
|
||||
'.' +
|
||||
opts.hostname.replace(/^\*\./, '');
|
||||
console.log(
|
||||
"Put the string '" +
|
||||
opts.dnsAuthorization +
|
||||
"' into the TXT record '" +
|
||||
pathname +
|
||||
"'"
|
||||
);
|
||||
console.log(
|
||||
'ddig TXT ' + pathname + " '" + opts.dnsAuthorization + "'"
|
||||
);
|
||||
} else {
|
||||
cb(new Error('[acme-v2] unrecognized challenge type'));
|
||||
return;
|
||||
}
|
||||
console.log("\nThen hit the 'any' key to continue...");
|
||||
|
||||
function onAny() {
|
||||
console.log("'any' key was hit");
|
||||
process.stdin.pause();
|
||||
process.stdin.removeListener('data', onAny);
|
||||
process.stdin.setRawMode(false);
|
||||
cb();
|
||||
}
|
||||
|
||||
process.stdin.setRawMode(true);
|
||||
process.stdin.resume();
|
||||
process.stdin.on('data', onAny);
|
||||
},
|
||||
removeChallenge: function(opts, cb) {
|
||||
// hostname, key
|
||||
console.log(
|
||||
'[acme-v2] remove challenge',
|
||||
opts.hostname,
|
||||
opts.keyAuthorization
|
||||
);
|
||||
setTimeout(cb, 1 * 1000);
|
||||
},
|
||||
challengeType: chType,
|
||||
email: email,
|
||||
accountKeypair: accountKeypair,
|
||||
domainKeypair: domainKeypair,
|
||||
domains: web
|
||||
};
|
||||
|
||||
acme2.accounts.create(options).then(function(account) {
|
||||
console.log('[acme-v2] account:');
|
||||
console.log(account);
|
||||
|
||||
acme2.certificates.create(options).then(function(fullchainPem) {
|
||||
console.log('[acme-v2] fullchain.pem:');
|
||||
console.log(fullchainPem);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
106
tests/compat.js
106
tests/compat.js
@ -1,106 +0,0 @@
|
||||
// Copyright 2018 AJ ONeal. All rights reserved
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
|
||||
module.exports.run = function(
|
||||
directoryUrl,
|
||||
RSA,
|
||||
web,
|
||||
chType,
|
||||
email,
|
||||
accountKeypair,
|
||||
domainKeypair
|
||||
) {
|
||||
console.log('[DEBUG] run', web, chType, email);
|
||||
|
||||
var acme2 = require('../compat.js').ACME.create({ RSA: RSA });
|
||||
acme2.getAcmeUrls(acme2.stagingServerUrl, function(err /*, directoryUrls*/) {
|
||||
if (err) {
|
||||
console.log('err 1');
|
||||
throw err;
|
||||
}
|
||||
|
||||
var options = {
|
||||
agreeToTerms: function(tosUrl, agree) {
|
||||
agree(null, tosUrl);
|
||||
},
|
||||
setChallenge: function(hostname, token, val, cb) {
|
||||
var pathname;
|
||||
|
||||
if ('http-01' === cb.type) {
|
||||
pathname = hostname + acme2.acmeChallengePrefix + token;
|
||||
console.log(
|
||||
"Put the string '" +
|
||||
val /*keyAuthorization*/ +
|
||||
"' into a file at '" +
|
||||
pathname +
|
||||
"'"
|
||||
);
|
||||
console.log(
|
||||
"echo '" + val /*keyAuthorization*/ + "' > '" + pathname + "'"
|
||||
);
|
||||
console.log("\nThen hit the 'any' key to continue...");
|
||||
} else if ('dns-01' === cb.type) {
|
||||
// forwards-backwards compat
|
||||
pathname =
|
||||
acme2.challengePrefixes['dns-01'] +
|
||||
'.' +
|
||||
hostname.replace(/^\*\./, '');
|
||||
console.log(
|
||||
"Put the string '" +
|
||||
cb.dnsAuthorization +
|
||||
"' into the TXT record '" +
|
||||
pathname +
|
||||
"'"
|
||||
);
|
||||
console.log('dig TXT ' + pathname + " '" + cb.dnsAuthorization + "'");
|
||||
console.log("\nThen hit the 'any' key to continue...");
|
||||
} else {
|
||||
cb(new Error('[acme-v2] unrecognized challenge type: ' + cb.type));
|
||||
return;
|
||||
}
|
||||
|
||||
function onAny() {
|
||||
console.log("'any' key was hit");
|
||||
process.stdin.pause();
|
||||
process.stdin.removeListener('data', onAny);
|
||||
process.stdin.setRawMode(false);
|
||||
cb();
|
||||
}
|
||||
|
||||
process.stdin.setRawMode(true);
|
||||
process.stdin.resume();
|
||||
process.stdin.on('data', onAny);
|
||||
},
|
||||
removeChallenge: function(hostname, key, cb) {
|
||||
console.log('[DEBUG] remove challenge', hostname, key);
|
||||
setTimeout(cb, 1 * 1000);
|
||||
},
|
||||
challengeType: chType,
|
||||
email: email,
|
||||
accountKeypair: accountKeypair,
|
||||
domainKeypair: domainKeypair,
|
||||
domains: web
|
||||
};
|
||||
|
||||
acme2.registerNewAccount(options, function(err, account) {
|
||||
if (err) {
|
||||
console.log('err 2');
|
||||
throw err;
|
||||
}
|
||||
if (options.debug) console.debug('account:');
|
||||
if (options.debug) console.log(account);
|
||||
|
||||
acme2.getCertificate(options, function(err, fullchainPem) {
|
||||
if (err) {
|
||||
console.log('err 3');
|
||||
throw err;
|
||||
}
|
||||
console.log('[acme-v2] A fullchain.pem:');
|
||||
console.log(fullchainPem);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
111
tests/compute-authorization-response.js
Normal file
111
tests/compute-authorization-response.js
Normal file
@ -0,0 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
var ACME = require('../');
|
||||
var accountKey = require('../fixtures/account.jwk.json').private;
|
||||
|
||||
var authorization = {
|
||||
identifier: {
|
||||
type: 'dns',
|
||||
value: 'example.com'
|
||||
},
|
||||
status: 'pending',
|
||||
expires: '2018-04-25T00:23:57Z',
|
||||
challenges: [
|
||||
{
|
||||
type: 'dns-01',
|
||||
status: 'pending',
|
||||
url:
|
||||
'https://acme-staging-v02.api.letsencrypt.org/acme/challenge/cMkwXI8pIeKN04Ynfem8ErHK3GeqAPdSt2x6q7PvVGU/118755342',
|
||||
token: 'LZdlUiZ-kWPs6q5WTmQFYQHZKpz9szn2vxEUu0XhyyM'
|
||||
},
|
||||
{
|
||||
type: 'http-01',
|
||||
status: 'pending',
|
||||
url:
|
||||
'https://acme-staging-v02.api.letsencrypt.org/acme/challenge/cMkwXI8pIeKN04Ynfem8ErHK3GeqAPdSt2x6q7PvVGU/118755343',
|
||||
token: '1S4zBG5YVhwSBaIY4ksI_KNMRrSmH0DZfNM9v7PYjDU'
|
||||
}
|
||||
]
|
||||
};
|
||||
var expectedChallengeUrl =
|
||||
'http://example.com/.well-known/acme-challenge/1S4zBG5YVhwSBaIY4ksI_KNMRrSmH0DZfNM9v7PYjDU';
|
||||
var expectedKeyAuth =
|
||||
'1S4zBG5YVhwSBaIY4ksI_KNMRrSmH0DZfNM9v7PYjDU.UuuZa_56jCM2douUq1riGyRphPtRvCPkxtkg0bP-pNs';
|
||||
var expectedKeyAuthDigest = 'iQiMcQUDiAeD0TJV1RHJuGnI5D2-PuSpxKz9JqUaZ2M';
|
||||
var expectedDnsHost = '_test-challenge.example.com';
|
||||
|
||||
async function main() {
|
||||
console.info('\n[Test] computing challenge authorizatin responses');
|
||||
var challenges = authorization.challenges.slice(0);
|
||||
|
||||
function next() {
|
||||
var ch = challenges.shift();
|
||||
if (!ch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var hostname = authorization.identifier.value;
|
||||
return ACME.computeChallenge({
|
||||
accountKey: accountKey,
|
||||
hostname: hostname,
|
||||
challenge: ch,
|
||||
dnsPrefix: '_test-challenge'
|
||||
})
|
||||
.then(function(auth) {
|
||||
if ('dns-01' === ch.type) {
|
||||
if (auth.keyAuthorizationDigest !== expectedKeyAuthDigest) {
|
||||
console.error('[keyAuthorizationDigest]');
|
||||
console.error(auth.keyAuthorizationDigest);
|
||||
console.error(expectedKeyAuthDigest);
|
||||
throw new Error('bad keyAuthDigest');
|
||||
}
|
||||
if (auth.dnsHost !== expectedDnsHost) {
|
||||
console.error('[dnsHost]');
|
||||
console.error(auth.dnsHost);
|
||||
console.error(expectedDnsHost);
|
||||
throw new Error('bad dnsHost');
|
||||
}
|
||||
} else if ('http-01' === ch.type) {
|
||||
if (auth.challengeUrl !== expectedChallengeUrl) {
|
||||
console.error('[challengeUrl]');
|
||||
console.error(auth.challengeUrl);
|
||||
console.error(expectedChallengeUrl);
|
||||
throw new Error('bad challengeUrl');
|
||||
}
|
||||
if (auth.challengeUrl !== expectedChallengeUrl) {
|
||||
console.error('[keyAuthorization]');
|
||||
console.error(auth.keyAuthorization);
|
||||
console.error(expectedKeyAuth);
|
||||
throw new Error('bad keyAuth');
|
||||
}
|
||||
} else {
|
||||
throw new Error('bad authorization inputs');
|
||||
}
|
||||
console.info('PASS', hostname, ch.type);
|
||||
return next();
|
||||
})
|
||||
.catch(function(err) {
|
||||
err.message =
|
||||
'Error computing ' +
|
||||
ch.type +
|
||||
' for ' +
|
||||
hostname +
|
||||
':' +
|
||||
err.message;
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
module.exports = function() {
|
||||
return main(authorization)
|
||||
.then(function() {
|
||||
console.info('PASS');
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
};
|
@ -35,55 +35,46 @@ var tests = [
|
||||
'----\nxxxx\nyyyy\n----\r\n----\nxxxx\ryyyy\n----\n'
|
||||
];
|
||||
|
||||
function formatPemChain(str) {
|
||||
return (
|
||||
str
|
||||
.trim()
|
||||
.replace(/[\r\n]+/g, '\n')
|
||||
.replace(/\-\n\-/g, '-\n\n-') + '\n'
|
||||
);
|
||||
}
|
||||
function splitPemChain(str) {
|
||||
return str
|
||||
.trim()
|
||||
.split(/[\r\n]{2,}/g)
|
||||
.map(function(str) {
|
||||
return str + '\n';
|
||||
});
|
||||
}
|
||||
var ACME = require('../');
|
||||
|
||||
tests.forEach(function(str) {
|
||||
var actual = formatPemChain(str);
|
||||
if (expected !== actual) {
|
||||
console.error('input: ', JSON.stringify(str));
|
||||
console.error('expected:', JSON.stringify(expected));
|
||||
console.error('actual: ', JSON.stringify(actual));
|
||||
throw new Error('did not pass');
|
||||
module.exports = function() {
|
||||
console.info('\n[Test] can split and format PEM chain properly');
|
||||
|
||||
tests.forEach(function(str) {
|
||||
var actual = ACME.formatPemChain(str);
|
||||
if (expected !== actual) {
|
||||
console.error('input: ', JSON.stringify(str));
|
||||
console.error('expected:', JSON.stringify(expected));
|
||||
console.error('actual: ', JSON.stringify(actual));
|
||||
throw new Error('did not pass');
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
'----\nxxxx\nyyyy\n----\n' !==
|
||||
ACME.formatPemChain('\n\n----\r\nxxxx\r\nyyyy\r\n----\n\n')
|
||||
) {
|
||||
throw new Error('Not proper for single cert in chain');
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
'----\nxxxx\nyyyy\n----\n' !==
|
||||
formatPemChain('\n\n----\r\nxxxx\r\nyyyy\r\n----\n\n')
|
||||
) {
|
||||
throw new Error('Not proper for single cert in chain');
|
||||
}
|
||||
|
||||
if (
|
||||
'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n' !==
|
||||
formatPemChain(
|
||||
'\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n'
|
||||
)
|
||||
) {
|
||||
throw new Error('Not proper for three certs in chain');
|
||||
}
|
||||
|
||||
splitPemChain(
|
||||
'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n'
|
||||
).forEach(function(str) {
|
||||
if ('--B--\nxxxx\nyyyy\n--E--\n' !== str) {
|
||||
throw new Error('bad thingy');
|
||||
if (
|
||||
'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n' !==
|
||||
ACME.formatPemChain(
|
||||
'\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n--B--\nxxxx\nyyyy\n--E--\n\n\n'
|
||||
)
|
||||
) {
|
||||
throw new Error('Not proper for three certs in chain');
|
||||
}
|
||||
});
|
||||
|
||||
console.info('PASS');
|
||||
ACME.splitPemChain(
|
||||
'--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n\n--B--\nxxxx\nyyyy\n--E--\n'
|
||||
).forEach(function(str) {
|
||||
if ('--B--\nxxxx\nyyyy\n--E--\n' !== str) {
|
||||
throw new Error('bad thingy');
|
||||
}
|
||||
});
|
||||
|
||||
console.info('PASS');
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
27
tests/generate-cert-key.js
Normal file
27
tests/generate-cert-key.js
Normal file
@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = async function() {
|
||||
console.log('[Test] can generate, export, and import key');
|
||||
var Keypairs = require('@root/keypairs');
|
||||
|
||||
var certKeypair = await Keypairs.generate({ kty: 'RSA' });
|
||||
//console.log(certKeypair);
|
||||
var pem = await Keypairs.export({
|
||||
jwk: certKeypair.private,
|
||||
encoding: 'pem'
|
||||
});
|
||||
var jwk = await Keypairs.import({
|
||||
pem: pem
|
||||
});
|
||||
['kty', 'd', 'n', 'e'].forEach(function(k) {
|
||||
if (!jwk[k] || jwk[k] !== certKeypair.private[k]) {
|
||||
throw new Error('bad export/import');
|
||||
}
|
||||
});
|
||||
//console.log(pem);
|
||||
console.log('PASS');
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
module.exports();
|
||||
}
|
10
tests/index.js
Normal file
10
tests/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
async function main() {
|
||||
await require('./generate-cert-key.js')();
|
||||
await require('./format-pem-chains.js')();
|
||||
await require('./compute-authorization-response.js')();
|
||||
await require('./issue-certificates.js')();
|
||||
}
|
||||
|
||||
main();
|
260
tests/issue-certificates.js
Normal file
260
tests/issue-certificates.js
Normal file
@ -0,0 +1,260 @@
|
||||
'use strict';
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
var pkg = require('../package.json');
|
||||
var CSR = require('@root/csr');
|
||||
var Enc = require('@root/encoding/base64');
|
||||
var PEM = require('@root/pem');
|
||||
var punycode = require('punycode');
|
||||
var ACME = require('../acme.js');
|
||||
var Keypairs = require('@root/keypairs');
|
||||
var ecJwk = require('../fixtures/account.jwk.json');
|
||||
|
||||
// TODO exec npm install --save-dev CHALLENGE_MODULE
|
||||
if (!process.env.CHALLENGE_OPTIONS) {
|
||||
console.error(
|
||||
'Please create a .env in the format of examples/example.env to run the tests'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var config = {
|
||||
env: process.env.ENV,
|
||||
email: process.env.SUBSCRIBER_EMAIL,
|
||||
domain: process.env.BASE_DOMAIN,
|
||||
challengeType: process.env.CHALLENGE_TYPE,
|
||||
challengeModule: process.env.CHALLENGE_PLUGIN,
|
||||
challengeOptions: JSON.parse(process.env.CHALLENGE_OPTIONS)
|
||||
};
|
||||
//config.debug = !/^PROD/i.test(config.env);
|
||||
var pluginPrefix = 'acme-' + config.challengeType + '-';
|
||||
var pluginName = config.challengeModule;
|
||||
var plugin;
|
||||
|
||||
module.exports = function() {
|
||||
console.info('\n[Test] end-to-end issue certificates');
|
||||
|
||||
var acme = ACME.create({
|
||||
// debug: true
|
||||
maintainerEmail: config.email,
|
||||
packageAgent: 'test-' + pkg.name + '/' + pkg.version,
|
||||
notify: function(ev, params) {
|
||||
console.info(
|
||||
'\t' + ev,
|
||||
params.subject || params.altname || params.domain || '',
|
||||
params.status || ''
|
||||
);
|
||||
if ('error' === ev) {
|
||||
console.error(params.action || params.type || '');
|
||||
console.error(params);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function badPlugin(err) {
|
||||
if ('MODULE_NOT_FOUND' !== err.code) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
console.error("Couldn't find '" + pluginName + "'. Is it installed?");
|
||||
console.error("\tnpm install --save-dev '" + pluginName + "'");
|
||||
}
|
||||
try {
|
||||
plugin = require(pluginName);
|
||||
} catch (err) {
|
||||
if (
|
||||
'MODULE_NOT_FOUND' !== err.code ||
|
||||
0 === pluginName.indexOf(pluginPrefix)
|
||||
) {
|
||||
badPlugin(err);
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
pluginName = pluginPrefix + pluginName;
|
||||
plugin = require(pluginName);
|
||||
} catch (e) {
|
||||
badPlugin(e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
config.challenger = plugin.create(config.challengeOptions);
|
||||
if (!config.challengeType || !config.domain) {
|
||||
console.error(
|
||||
new Error('Missing config variables. Check you .env and the docs')
|
||||
.message
|
||||
);
|
||||
console.error(config);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var challenges = {};
|
||||
challenges[config.challengeType] = config.challenger;
|
||||
|
||||
async function happyPath(accKty, srvKty, rnd) {
|
||||
var agreed = false;
|
||||
var metadata = await acme.init(
|
||||
'https://acme-staging-v02.api.letsencrypt.org/directory'
|
||||
);
|
||||
|
||||
// Ready to use, show page
|
||||
if (config.debug) {
|
||||
console.info('ACME.js initialized');
|
||||
console.info(metadata);
|
||||
console.info();
|
||||
console.info();
|
||||
}
|
||||
|
||||
var accountKeypair = await Keypairs.generate({ kty: accKty });
|
||||
if (/EC/i.test(accKty)) {
|
||||
// to test that an existing account gets back data
|
||||
accountKeypair = ecJwk;
|
||||
}
|
||||
var accountKey = accountKeypair.private;
|
||||
if (config.debug) {
|
||||
console.info('Account Key Created');
|
||||
console.info(JSON.stringify(accountKey, null, 2));
|
||||
console.info();
|
||||
console.info();
|
||||
}
|
||||
|
||||
var account = await acme.accounts.create({
|
||||
agreeToTerms: agree,
|
||||
// TODO detect jwk/pem/der?
|
||||
accountKey: accountKey,
|
||||
subscriberEmail: config.email
|
||||
});
|
||||
|
||||
// TODO top-level agree
|
||||
function agree(tos) {
|
||||
if (config.debug) {
|
||||
console.info('Agreeing to Terms of Service:');
|
||||
console.info(tos);
|
||||
console.info();
|
||||
console.info();
|
||||
}
|
||||
agreed = true;
|
||||
return Promise.resolve(tos);
|
||||
}
|
||||
if (config.debug) {
|
||||
console.info('New Subscriber Account');
|
||||
console.info(JSON.stringify(account, null, 2));
|
||||
console.info();
|
||||
console.info();
|
||||
}
|
||||
if (!agreed) {
|
||||
throw new Error('Failed to ask the user to agree to terms');
|
||||
}
|
||||
|
||||
var certKeypair = await Keypairs.generate({ kty: srvKty });
|
||||
var pem = await Keypairs.export({
|
||||
jwk: certKeypair.private,
|
||||
encoding: 'pem'
|
||||
});
|
||||
if (config.debug) {
|
||||
console.info('Server Key Created');
|
||||
console.info('privkey.jwk.json');
|
||||
console.info(JSON.stringify(certKeypair, null, 2));
|
||||
// This should be saved as `privkey.pem`
|
||||
console.info();
|
||||
console.info('privkey.' + srvKty.toLowerCase() + '.pem:');
|
||||
console.info(pem);
|
||||
console.info();
|
||||
}
|
||||
|
||||
// 'subject' should be first in list
|
||||
var domains = randomDomains(rnd);
|
||||
if (config.debug) {
|
||||
console.info('Get certificates for random domains:');
|
||||
console.info(
|
||||
domains
|
||||
.map(function(puny) {
|
||||
var uni = punycode.toUnicode(puny);
|
||||
if (puny !== uni) {
|
||||
return puny + ' (' + uni + ')';
|
||||
}
|
||||
return puny;
|
||||
})
|
||||
.join('\n')
|
||||
);
|
||||
console.info();
|
||||
}
|
||||
|
||||
// Create CSR
|
||||
var csrDer = await CSR.csr({
|
||||
jwk: certKeypair.private,
|
||||
domains: domains,
|
||||
encoding: 'der'
|
||||
});
|
||||
var csr = Enc.bufToUrlBase64(csrDer);
|
||||
var csrPem = PEM.packBlock({
|
||||
type: 'CERTIFICATE REQUEST',
|
||||
bytes: csrDer /* { jwk: jwk, domains: opts.domains } */
|
||||
});
|
||||
if (config.debug) {
|
||||
console.info('Certificate Signing Request');
|
||||
console.info(csrPem);
|
||||
console.info();
|
||||
}
|
||||
|
||||
var results = await acme.certificates.create({
|
||||
account: account,
|
||||
accountKey: accountKey,
|
||||
csr: csr,
|
||||
domains: domains,
|
||||
challenges: challenges, // must be implemented
|
||||
customerEmail: null
|
||||
});
|
||||
|
||||
if (config.debug) {
|
||||
console.info('Got SSL Certificate:');
|
||||
console.info(Object.keys(results));
|
||||
console.info(results.expires);
|
||||
console.info(results.cert);
|
||||
console.info(results.chain);
|
||||
console.info();
|
||||
console.info();
|
||||
}
|
||||
}
|
||||
|
||||
// Try EC + RSA
|
||||
var rnd = random();
|
||||
happyPath('EC', 'RSA', rnd)
|
||||
.then(function() {
|
||||
console.info('PASS: ECDSA account key with RSA server key');
|
||||
// Now try RSA + EC
|
||||
rnd = random();
|
||||
return happyPath('RSA', 'EC', rnd).then(function() {
|
||||
console.info('PASS: RSA account key with ECDSA server key');
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
console.info('PASS');
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Error:');
|
||||
console.error(err.stack);
|
||||
});
|
||||
|
||||
function randomDomains(rnd) {
|
||||
return ['foo-acmejs', 'bar-acmejs', '*.baz-acmejs', 'baz-acmejs'].map(
|
||||
function(pre) {
|
||||
return punycode.toASCII(pre + '-' + rnd + '.' + config.domain);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function random() {
|
||||
return (
|
||||
parseInt(
|
||||
Math.random()
|
||||
.toString()
|
||||
.slice(2, 99),
|
||||
10
|
||||
)
|
||||
.toString(16)
|
||||
.slice(0, 4) + '例'
|
||||
);
|
||||
}
|
||||
};
|
74
tests/maintainer.js
Normal file
74
tests/maintainer.js
Normal file
@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
var native = require('../lib/native.js');
|
||||
var crypto = require('crypto');
|
||||
|
||||
native
|
||||
._hashcash({
|
||||
alg: 'SHA-256',
|
||||
nonce: '00',
|
||||
needle: '0000',
|
||||
start: 0,
|
||||
end: 2
|
||||
})
|
||||
.then(function(hashcash) {
|
||||
if ('00:76de' !== hashcash) {
|
||||
throw new Error('hashcash algorthim changed');
|
||||
}
|
||||
console.info('PASS: known hash solves correctly');
|
||||
|
||||
return native
|
||||
._hashcash({
|
||||
alg: 'SHA-256',
|
||||
nonce: '10',
|
||||
needle: '',
|
||||
start: 0,
|
||||
end: 2
|
||||
})
|
||||
.then(function(hashcash) {
|
||||
if ('10:00' !== hashcash) {
|
||||
throw new Error('hashcash algorthim changed');
|
||||
}
|
||||
console.info('PASS: empty hash solves correctly');
|
||||
|
||||
var now = Date.now();
|
||||
var nonce = '20';
|
||||
var needle = crypto
|
||||
.randomBytes(3)
|
||||
.toString('hex')
|
||||
.slice(0, 5);
|
||||
native
|
||||
._hashcash({
|
||||
alg: 'SHA-256',
|
||||
nonce: nonce,
|
||||
needle: needle,
|
||||
start: 0,
|
||||
end: Math.ceil(needle.length / 2)
|
||||
})
|
||||
.then(function(hashcash) {
|
||||
var later = Date.now();
|
||||
var parts = hashcash.split(':');
|
||||
var answer = parts[1];
|
||||
if (parts[0] !== nonce) {
|
||||
throw new Error('incorrect nonce');
|
||||
}
|
||||
var haystack = crypto
|
||||
.createHash('sha256')
|
||||
.update(Buffer.from(nonce + answer, 'hex'))
|
||||
.digest()
|
||||
.slice(0, Math.ceil(needle.length / 2));
|
||||
if (
|
||||
-1 === haystack.indexOf(Buffer.from(needle, 'hex'))
|
||||
) {
|
||||
throw new Error('incorrect solution');
|
||||
}
|
||||
if (later - now > 2000) {
|
||||
throw new Error('took too long to solve');
|
||||
}
|
||||
console.info(
|
||||
'PASS: rando hash solves correctly (and in good time - %dms)',
|
||||
later - now
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
124
tests/promise.js
124
tests/promise.js
@ -1,124 +0,0 @@
|
||||
// Copyright 2018 AJ ONeal. All rights reserved
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
'use strict';
|
||||
|
||||
/* global Promise */
|
||||
module.exports.run = function run(
|
||||
directoryUrl,
|
||||
RSA,
|
||||
web,
|
||||
chType,
|
||||
email,
|
||||
accountKeypair,
|
||||
domainKeypair
|
||||
) {
|
||||
var acme2 = require('../').ACME.create({ RSA: RSA });
|
||||
// [ 'test.ppl.family' ] 'coolaj86@gmail.com''http-01'
|
||||
acme2.init(directoryUrl).then(function() {
|
||||
var options = {
|
||||
agreeToTerms: function(tosUrl) {
|
||||
return Promise.resolve(tosUrl);
|
||||
},
|
||||
setChallenge: function(opts) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var pathname;
|
||||
|
||||
console.log('');
|
||||
console.log('identifier:');
|
||||
console.log(opts.identifier);
|
||||
console.log('hostname:');
|
||||
console.log(opts.hostname);
|
||||
console.log('type:');
|
||||
console.log(opts.type);
|
||||
console.log('token:');
|
||||
console.log(opts.token);
|
||||
console.log('thumbprint:');
|
||||
console.log(opts.thumbprint);
|
||||
console.log('keyAuthorization:');
|
||||
console.log(opts.keyAuthorization);
|
||||
console.log('dnsAuthorization:');
|
||||
console.log(opts.dnsAuthorization);
|
||||
console.log('');
|
||||
|
||||
if ('http-01' === opts.type) {
|
||||
pathname =
|
||||
opts.hostname +
|
||||
acme2.challengePrefixes['http-01'] +
|
||||
'/' +
|
||||
opts.token;
|
||||
console.log(
|
||||
"Put the string '" +
|
||||
opts.keyAuthorization +
|
||||
"' into a file at '" +
|
||||
pathname +
|
||||
"'"
|
||||
);
|
||||
console.log(
|
||||
"echo '" + opts.keyAuthorization + "' > '" + pathname + "'"
|
||||
);
|
||||
} else if ('dns-01' === opts.type) {
|
||||
pathname =
|
||||
acme2.challengePrefixes['dns-01'] +
|
||||
'.' +
|
||||
opts.hostname.replace(/^\*\./, '');
|
||||
console.log(
|
||||
"Put the string '" +
|
||||
opts.dnsAuthorization +
|
||||
"' into the TXT record '" +
|
||||
pathname +
|
||||
"'"
|
||||
);
|
||||
console.log(
|
||||
'dig TXT ' + pathname + " '" + opts.dnsAuthorization + "'"
|
||||
);
|
||||
} else {
|
||||
reject(new Error('[acme-v2] unrecognized challenge type'));
|
||||
return;
|
||||
}
|
||||
console.log("\nThen hit the 'any' key to continue...");
|
||||
|
||||
function onAny() {
|
||||
console.log("'any' key was hit");
|
||||
process.stdin.pause();
|
||||
process.stdin.removeListener('data', onAny);
|
||||
process.stdin.setRawMode(false);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
process.stdin.setRawMode(true);
|
||||
process.stdin.resume();
|
||||
process.stdin.on('data', onAny);
|
||||
});
|
||||
},
|
||||
removeChallenge: function(opts) {
|
||||
console.log(
|
||||
'[acme-v2] remove challenge',
|
||||
opts.hostname,
|
||||
opts.keyAuthorization
|
||||
);
|
||||
return new Promise(function(resolve) {
|
||||
// hostname, key
|
||||
setTimeout(resolve, 1 * 1000);
|
||||
});
|
||||
},
|
||||
challengeType: chType,
|
||||
email: email,
|
||||
accountKeypair: accountKeypair,
|
||||
domainKeypair: domainKeypair,
|
||||
domains: web
|
||||
};
|
||||
|
||||
acme2.accounts.create(options).then(function(account) {
|
||||
console.log('[acme-v2] account:');
|
||||
console.log(account);
|
||||
|
||||
acme2.certificates.create(options).then(function(fullchainPem) {
|
||||
console.log('[acme-v2] fullchain.pem:');
|
||||
console.log(fullchainPem);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
173
utils.js
Normal file
173
utils.js
Normal file
@ -0,0 +1,173 @@
|
||||
'use strict';
|
||||
|
||||
var U = module.exports;
|
||||
|
||||
var Keypairs = require('@root/keypairs');
|
||||
var UserAgent = require('./lib/node/client-user-agent.js');
|
||||
|
||||
// Handle nonce, signing, and request altogether
|
||||
U._jwsRequest = function(me, bigopts) {
|
||||
return U._getNonce(me).then(function(nonce) {
|
||||
bigopts.protected.nonce = nonce;
|
||||
bigopts.protected.url = bigopts.url;
|
||||
// protected.alg: added by Keypairs.signJws
|
||||
if (!bigopts.protected.jwk) {
|
||||
// protected.kid must be overwritten due to ACME's interpretation of the spec
|
||||
if (!('kid' in bigopts.protected)) {
|
||||
bigopts.protected.kid = bigopts.kid;
|
||||
}
|
||||
}
|
||||
|
||||
// this will shasum the thumbprint the 2nd time
|
||||
return Keypairs.signJws({
|
||||
jwk: bigopts.accountKey,
|
||||
protected: bigopts.protected,
|
||||
payload: bigopts.payload
|
||||
})
|
||||
.then(function(jws) {
|
||||
//#console.debug('[ACME.js] url: ' + bigopts.url + ':');
|
||||
//#console.debug(jws);
|
||||
return U._request(me, { url: bigopts.url, json: jws });
|
||||
})
|
||||
.catch(function(e) {
|
||||
if (/badNonce$/.test(e.urn)) {
|
||||
// retry badNonces
|
||||
var retryable = bigopts._retries >= 2;
|
||||
if (!retryable) {
|
||||
bigopts._retries = (bigopts._retries || 0) + 1;
|
||||
return U._jwsRequest(me, bigopts);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
U._getNonce = function(me) {
|
||||
var nonce;
|
||||
while (true) {
|
||||
nonce = me._nonces.shift();
|
||||
if (!nonce) {
|
||||
break;
|
||||
}
|
||||
if (Date.now() - nonce.createdAt > 15 * 60 * 1000) {
|
||||
nonce = null;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nonce) {
|
||||
return Promise.resolve(nonce.nonce);
|
||||
}
|
||||
|
||||
// HEAD-as-HEAD ok
|
||||
return U._request(me, {
|
||||
method: 'HEAD',
|
||||
url: me._directoryUrls.newNonce
|
||||
}).then(function(resp) {
|
||||
return resp.headers['replay-nonce'];
|
||||
});
|
||||
};
|
||||
|
||||
// Handle some ACME-specific defaults
|
||||
U._request = function(me, opts) {
|
||||
// no-op on browser
|
||||
var ua = UserAgent.get(me, opts);
|
||||
|
||||
// Note: the required User-Agent string will be set in node, but not browsers
|
||||
if (!opts.headers) {
|
||||
opts.headers = {};
|
||||
}
|
||||
|
||||
if (ua && !opts.headers['User-Agent']) {
|
||||
opts.headers['User-Agent'] = ua;
|
||||
}
|
||||
if (opts.json) {
|
||||
opts.headers.Accept = 'application/json';
|
||||
if (true !== opts.json) {
|
||||
opts.body = JSON.stringify(opts.json);
|
||||
}
|
||||
if (/*opts.jose ||*/ opts.json.protected) {
|
||||
opts.headers['Content-Type'] = 'application/jose+json';
|
||||
}
|
||||
}
|
||||
if (!opts.method) {
|
||||
opts.method = 'GET';
|
||||
if (opts.body) {
|
||||
opts.method = 'POST';
|
||||
}
|
||||
}
|
||||
|
||||
//console.log('\n[debug] REQUEST');
|
||||
//console.log(opts);
|
||||
return me.__request(opts).then(function(resp) {
|
||||
if (resp.toJSON) {
|
||||
resp = resp.toJSON();
|
||||
}
|
||||
if (resp.headers['replay-nonce']) {
|
||||
U._setNonce(me, resp.headers['replay-nonce']);
|
||||
}
|
||||
//console.log('[debug] RESPONSE:');
|
||||
//console.log(resp.headers);
|
||||
//console.log(resp.body);
|
||||
|
||||
var e;
|
||||
var err;
|
||||
if (resp.body) {
|
||||
err = resp.body.error;
|
||||
e = new Error('');
|
||||
if (400 === resp.body.status) {
|
||||
err = { type: resp.body.type, detail: resp.body.detail };
|
||||
}
|
||||
if (err) {
|
||||
e.status = resp.body.status;
|
||||
e.code = 'E_ACME';
|
||||
if (e.status) {
|
||||
e.message = '[' + e.status + '] ';
|
||||
}
|
||||
e.detail = err.detail;
|
||||
e.message += err.detail || JSON.stringify(err);
|
||||
e.urn = err.type;
|
||||
e.uri = resp.body.url;
|
||||
e._rawError = err;
|
||||
e._rawBody = resp.body;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return resp;
|
||||
});
|
||||
};
|
||||
|
||||
U._setNonce = function(me, nonce) {
|
||||
me._nonces.unshift({ nonce: nonce, createdAt: Date.now() });
|
||||
};
|
||||
|
||||
U._importKeypair = function(key) {
|
||||
var p;
|
||||
var pub;
|
||||
|
||||
if (key && key.kty) {
|
||||
// nix the browser jwk extras
|
||||
key.key_ops = undefined;
|
||||
key.ext = undefined;
|
||||
pub = Keypairs.neuter({ jwk: key });
|
||||
p = Promise.resolve({
|
||||
private: key,
|
||||
public: pub
|
||||
});
|
||||
} else if ('string' === typeof key) {
|
||||
p = Keypairs.import({ pem: key });
|
||||
} else {
|
||||
throw new Error('no private key given');
|
||||
}
|
||||
|
||||
return p.then(function(pair) {
|
||||
if (pair.public.kid) {
|
||||
pair = JSON.parse(JSON.stringify(pair));
|
||||
delete pair.public.kid;
|
||||
delete pair.private.kid;
|
||||
}
|
||||
return pair;
|
||||
});
|
||||
};
|
20
webpack.config.js
Normal file
20
webpack.config.js
Normal file
@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: './examples/app.js',
|
||||
//entry: './acme.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'app.js'
|
||||
//filename: 'acme.js',
|
||||
//library: '@root/acme',
|
||||
//libraryTarget: 'umd'
|
||||
//globalObject: "typeof self !== 'undefined' ? self : this"
|
||||
},
|
||||
resolve: {
|
||||
aliasFields: ['webpack', 'browser'],
|
||||
mainFields: ['browser', 'main']
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user