forked from root/acme.js
		
	v1.8: transitional support for v2.0
This commit is contained in:
		
							parent
							
								
									dfbee8aa79
								
							
						
					
					
						commit
						e6497fe34b
					
				
							
								
								
									
										19
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,32 +1,17 @@
 | 
				
			|||||||
 | 
					.env
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*.pem
 | 
					*.pem
 | 
				
			||||||
letsencrypt.work
 | 
					 | 
				
			||||||
letsencrypt.logs
 | 
					 | 
				
			||||||
letsencrypt.config
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Logs
 | 
					# Logs
 | 
				
			||||||
logs
 | 
					logs
 | 
				
			||||||
*.log
 | 
					*.log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Runtime data
 | 
					 | 
				
			||||||
pids
 | 
					 | 
				
			||||||
*.pid
 | 
					 | 
				
			||||||
*.seed
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Directory for instrumented libs generated by jscoverage/JSCover
 | 
					# Directory for instrumented libs generated by jscoverage/JSCover
 | 
				
			||||||
lib-cov
 | 
					lib-cov
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Coverage directory used by tools like istanbul
 | 
					# Coverage directory used by tools like istanbul
 | 
				
			||||||
coverage
 | 
					coverage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
 | 
					 | 
				
			||||||
.grunt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# node-waf configuration
 | 
					 | 
				
			||||||
.lock-wscript
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
 | 
					 | 
				
			||||||
build/Release
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Dependency directory
 | 
					# Dependency directory
 | 
				
			||||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
 | 
					# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
 | 
				
			||||||
node_modules
 | 
					node_modules
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										224
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										224
									
								
								README.md
									
									
									
									
									
								
							@ -2,28 +2,77 @@
 | 
				
			|||||||
| [acme-v2-cli.js](https://git.coolaj86.com/coolaj86/acme-v2-cli.js)
 | 
					| [acme-v2-cli.js](https://git.coolaj86.com/coolaj86/acme-v2-cli.js)
 | 
				
			||||||
| [greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
 | 
					| [greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
 | 
				
			||||||
| [goldilocks.js](https://git.coolaj86.com/coolaj86/goldilocks.js)
 | 
					| [goldilocks.js](https://git.coolaj86.com/coolaj86/goldilocks.js)
 | 
				
			||||||
|
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
| A [Root](https://therootcompany.com) Project
 | 
					# [acme-v2.js](https://git.coolaj86.com/coolaj86/acme-v2.js) | a [Root](https://therootcompany.com) project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# [acme-v2.js](https://git.coolaj86.com/coolaj86/acme-v2.js)
 | 
					A **Zero (External) Dependency**\* library for building
 | 
				
			||||||
 | 
					Let's Encrypt v2 (ACME draft 18) clients and getting Free SSL certificates.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A lightweight, **Low Dependency**\* framework for building
 | 
					The primary goal of this library is to make it easy to
 | 
				
			||||||
Let's Encrypt v2 (ACME draft 12) clients, successor to `le-acme-core.js`.
 | 
					get Accounts and Certificates through Let's Encrypt.
 | 
				
			||||||
Built [by request](https://git.coolaj86.com/coolaj86/greenlock.js/issues/5#issuecomment-8).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
\* <small>although `node-forge` and `ursa` are included as `optionalDependencies`
 | 
					# Features
 | 
				
			||||||
for backwards compatibility with older versions of node, there are no other
 | 
					
 | 
				
			||||||
dependencies except those that I wrote for this (and related) projects.</small>
 | 
					- [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))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					\* <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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Looking for Quick 'n' Easy™?
 | 
					## Looking for Quick 'n' Easy™?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If you're looking to _build a webserver_, try [greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js).
 | 
					If you want something that's more "batteries included" give
 | 
				
			||||||
If you're looking for an _ACME-enabled webserver_, try [goldilocks.js](https://git.coolaj86.com/coolaj86/goldilocks.js).
 | 
					[greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
 | 
				
			||||||
 | 
					a try.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
 | 
					- [greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
 | 
				
			||||||
- [goldilocks.js](https://git.coolaj86.com/coolaj86/goldilocks.js)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.7+: Transitional v2 Support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					By the end of June 2019 we expect to have completed the migration to Let's Encrypt v2.1 (ACME draft 18).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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're taking this **mandatory ACME update** as an opportunity to **clean up** and **greatly simplify**
 | 
				
			||||||
 | 
					the code with a fresh new release.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
					## How to build ACME clients
 | 
				
			||||||
 | 
					
 | 
				
			||||||
As this is intended to build ACME clients, there is not a simple 2-line example
 | 
					As this is intended to build ACME clients, there is not a simple 2-line example
 | 
				
			||||||
@ -63,136 +112,75 @@ examples/https-server.js
 | 
				
			|||||||
examples/http-server.js
 | 
					examples/http-server.js
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Let's Encrypt Directory URLs
 | 
					-->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					## API
 | 
				
			||||||
# Production URL
 | 
					 | 
				
			||||||
https://acme-v02.api.letsencrypt.org/directory
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					Status: Small, but breaking changes coming in v2
 | 
				
			||||||
# Staging URL
 | 
					 | 
				
			||||||
https://acme-staging-v02.api.letsencrypt.org/directory
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Two API versions, Two Implementations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This library (acme-v2.js) supports ACME [_draft 11_](https://tools.ietf.org/html/draft-ietf-acme-acme-11),
 | 
					 | 
				
			||||||
otherwise known as Let's Encrypt v2 (or v02).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- ACME draft 11
 | 
					 | 
				
			||||||
- Let's Encrypt v2
 | 
					 | 
				
			||||||
- Let's Encrypt v02
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The predecessor (le-acme-core) supports Let's Encrypt v1 (or v01), which was a
 | 
					 | 
				
			||||||
[hodge-podge of various drafts](https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md)
 | 
					 | 
				
			||||||
of the ACME spec early on.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- ACME early draft
 | 
					 | 
				
			||||||
- Let's Encrypt v1
 | 
					 | 
				
			||||||
- Let's Encrypt v01
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This library maintains compatibility with le-acme-core so that it can be used as a **drop-in replacement**
 | 
					 | 
				
			||||||
and requires **no changes to existing code**,
 | 
					 | 
				
			||||||
but also provides an updated API more congruent with draft 11.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## le-acme-core-compatible API (recommended)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Status: Stable, Locked, Bugfix-only
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
See Full Documentation at <https://git.coolaj86.com/coolaj86/le-acme-core.js>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```js
 | 
					 | 
				
			||||||
var RSA = require('rsa-compat').RSA;
 | 
					 | 
				
			||||||
var acme = require('acme-v2/compat.js').ACME.create({ RSA: RSA });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Use exactly the same as le-acme-core
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Promise API (dev)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Status: Almost stable, but **not semver locked**
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
This API is a simple evolution of le-acme-core,
 | 
					This API is a simple evolution of le-acme-core,
 | 
				
			||||||
but tries to provide a better mapping to the new draft 11 APIs.
 | 
					but tries to provide a better mapping to the new draft 11 APIs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
// Create Instance (Dependency Injection)
 | 
					 | 
				
			||||||
var ACME = require('acme-v2').ACME.create({
 | 
					var ACME = require('acme-v2').ACME.create({
 | 
				
			||||||
  RSA: require('rsa-compat').RSA
 | 
						// used for overriding the default user-agent
 | 
				
			||||||
 | 
						userAgent: 'My custom UA String',
 | 
				
			||||||
 | 
						getUserAgentString: function(deps) {
 | 
				
			||||||
 | 
							return 'My custom UA String';
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // other overrides
 | 
						// don't try to validate challenges locally
 | 
				
			||||||
, request: require('request')
 | 
						skipChallengeTest: false,
 | 
				
			||||||
, promisify: require('util').promisify
 | 
						skipDryRun: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // used for constructing user-agent
 | 
						// ask if the certificate can be issued up to 10 times before failing
 | 
				
			||||||
, os: require('os')
 | 
						retryPoll: 8,
 | 
				
			||||||
, process: require('process')
 | 
						// ask if the certificate has been validated up to 6 times before cancelling
 | 
				
			||||||
 | 
						retryPending: 4,
 | 
				
			||||||
  // used for overriding the default user-agent
 | 
						// Wait 1000ms between retries
 | 
				
			||||||
, userAgent: 'My custom UA String'
 | 
						retryInterval: 1000,
 | 
				
			||||||
, getUserAgentString: function (deps) { return 'My custom UA String'; }
 | 
						// Wait 10,000ms after deauthorizing a challenge before retrying
 | 
				
			||||||
 | 
						deauthWait: 10 * 1000
 | 
				
			||||||
 | 
					 | 
				
			||||||
  // don't try to validate challenges locally
 | 
					 | 
				
			||||||
, skipChallengeTest: false
 | 
					 | 
				
			||||||
  // 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
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Discover Directory URLs
 | 
					// Discover Directory URLs
 | 
				
			||||||
ACME.init(acmeDirectoryUrl)                   // returns Promise<acmeUrls={keyChange,meta,newAccount,newNonce,newOrder,revokeCert}>
 | 
					ACME.init(acmeDirectoryUrl); // returns Promise<acmeUrls={keyChange,meta,newAccount,newNonce,newOrder,revokeCert}>
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Accounts
 | 
					// Accounts
 | 
				
			||||||
ACME.accounts.create(options)                 // returns Promise<regr> registration data
 | 
					ACME.accounts.create(options); // returns Promise<regr> registration data
 | 
				
			||||||
 | 
					 | 
				
			||||||
    { email: '<email>'                        //    valid email (server checks MX records)
 | 
					 | 
				
			||||||
    , accountKeypair: {                       //    privateKeyPem or privateKeyJwt
 | 
					 | 
				
			||||||
        privateKeyPem: '<ASCII PEM>'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    , agreeToTerms: fn (tosUrl) {}            //    returns Promise with tosUrl
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Registration
 | 
					// Registration
 | 
				
			||||||
ACME.certificates.create(options)             // returns Promise<pems={ privkey (key), cert, chain (ca) }>
 | 
					ACME.certificates.create(options); // returns Promise<pems={ privkey (key), cert, chain (ca) }>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    { newAuthzUrl: '<url>'                    //    specify acmeUrls.newAuthz
 | 
					options = {
 | 
				
			||||||
    , newCertUrl: '<url>'                     //    specify acmeUrls.newCert
 | 
						domainKeypair: {
 | 
				
			||||||
 | 
							privateKeyPem: '<ASCII PEM>'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						accountKeypair: {
 | 
				
			||||||
 | 
							privateKeyPem: '<ASCII PEM>'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						domains: ['example.com'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    , domainKeypair: {
 | 
						getZones: function(opts) {}, // should Promise an array of domain zone names
 | 
				
			||||||
        privateKeyPem: '<ASCII PEM>'
 | 
						setChallenge: function(opts) {}, // should Promise the record id, or name
 | 
				
			||||||
      }
 | 
						removeChallenge: function(opts) {} // should Promise null
 | 
				
			||||||
    , accountKeypair: {
 | 
					};
 | 
				
			||||||
        privateKeyPem: '<ASCII PEM>'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    , domains: [ 'example.com' ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    , setChallenge: fn (hostname, key, val)   // return Promise
 | 
					 | 
				
			||||||
    , removeChallenge: fn (hostname, key)     // return Promise
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Helpers & Stuff
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```javascript
 | 
					 | 
				
			||||||
// Constants
 | 
					 | 
				
			||||||
ACME.challengePrefixes['http-01']; // '/.well-known/acme-challenge'
 | 
					 | 
				
			||||||
ACME.challengePrefixes['dns-01']; // '_acme-challenge'
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Changelog
 | 
					# Changelog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- v1.8
 | 
				
			||||||
 | 
					  - more transitional prepwork for new v2 API
 | 
				
			||||||
 | 
					  - support newer (simpler) dns-01 and http-01 libraries
 | 
				
			||||||
- v1.5
 | 
					- v1.5
 | 
				
			||||||
  - perform full test challenge first (even before nonce)
 | 
					  - perform full test challenge first (even before nonce)
 | 
				
			||||||
- v1.3
 | 
					- v1.3
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										69
									
								
								examples/dns-01-digitalocean.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								examples/dns-01-digitalocean.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					(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);
 | 
				
			||||||
							
								
								
									
										3
									
								
								examples/example.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/example.env
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					ACME_EMAIL=jon.doe@gmail.com
 | 
				
			||||||
 | 
					ACME_DOMAINS=example.com,foo.example.com,*.foo.example.com
 | 
				
			||||||
 | 
					DIGITALOCEAN_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 | 
				
			||||||
							
								
								
									
										451
									
								
								node.js
									
									
									
									
									
								
							
							
						
						
									
										451
									
								
								node.js
									
									
									
									
									
								
							@ -276,7 +276,10 @@ ACME._registerAccount = function(me, options) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			if (1 === options.agreeToTerms.length) {
 | 
								if (1 === options.agreeToTerms.length) {
 | 
				
			||||||
				// newer promise API
 | 
									// newer promise API
 | 
				
			||||||
				return options.agreeToTerms(me._tos).then(agree, reject);
 | 
									return Promise.resolve(options.agreeToTerms(me._tos)).then(
 | 
				
			||||||
 | 
										agree,
 | 
				
			||||||
 | 
										reject
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
			} else if (2 === options.agreeToTerms.length) {
 | 
								} else if (2 === options.agreeToTerms.length) {
 | 
				
			||||||
				// backwards compat cb API
 | 
									// backwards compat cb API
 | 
				
			||||||
				return options.agreeToTerms(me._tos, function(err, tosUrl) {
 | 
									return options.agreeToTerms(me._tos, function(err, tosUrl) {
 | 
				
			||||||
@ -461,6 +464,58 @@ ACME._chooseChallenge = function(options, results) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return challenge;
 | 
						return challenge;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					ACME._getZones = function(me, options, dnsHosts) {
 | 
				
			||||||
 | 
						if ('function' !== typeof options.getZones) {
 | 
				
			||||||
 | 
							options.getZones = function() {
 | 
				
			||||||
 | 
								return Promise.resolve([]);
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return new Promise(function(resolve, reject) {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								if (options.getZones.length <= 1) {
 | 
				
			||||||
 | 
									options
 | 
				
			||||||
 | 
										.getZones({ dnsHosts: dnsHosts })
 | 
				
			||||||
 | 
										.then(resolve)
 | 
				
			||||||
 | 
										.catch(reject);
 | 
				
			||||||
 | 
								} else if (2 === options.getZones.length) {
 | 
				
			||||||
 | 
									options.getZones({ dnsHosts: dnsHosts }, function(err, zonenames) {
 | 
				
			||||||
 | 
										if (err) {
 | 
				
			||||||
 | 
											reject(err);
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											resolve(zonenames);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									throw new Error(
 | 
				
			||||||
 | 
										'options.getZones should accept opts and Promise an array of zone names'
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} catch (e) {
 | 
				
			||||||
 | 
								reject(e);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					function newZoneRegExp(zonename) {
 | 
				
			||||||
 | 
						// (^|\.)example\.com$
 | 
				
			||||||
 | 
						// which matches:
 | 
				
			||||||
 | 
						//  foo.example.com
 | 
				
			||||||
 | 
						//  example.com
 | 
				
			||||||
 | 
						// but not:
 | 
				
			||||||
 | 
						//  fooexample.com
 | 
				
			||||||
 | 
						return new RegExp('(^|\\.)' + zonename.replace(/\./g, '\\.') + '$');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function pluckZone(zonenames, dnsHost) {
 | 
				
			||||||
 | 
						return zonenames
 | 
				
			||||||
 | 
							.filter(function(zonename) {
 | 
				
			||||||
 | 
								// the only character that needs to be escaped for regex
 | 
				
			||||||
 | 
								// and is allowed in a domain name is '.'
 | 
				
			||||||
 | 
								return newZoneRegExp(zonename).test(dnsHost);
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.sort(function(a, b) {
 | 
				
			||||||
 | 
								// longest match first
 | 
				
			||||||
 | 
								return b.length - a.length;
 | 
				
			||||||
 | 
							})[0];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
ACME._challengeToAuth = function(me, options, request, challenge, dryrun) {
 | 
					ACME._challengeToAuth = function(me, options, request, challenge, dryrun) {
 | 
				
			||||||
	// we don't poison the dns cache with our dummy request
 | 
						// we don't poison the dns cache with our dummy request
 | 
				
			||||||
	var dnsPrefix = ACME.challengePrefixes['dns-01'];
 | 
						var dnsPrefix = ACME.challengePrefixes['dns-01'];
 | 
				
			||||||
@ -490,6 +545,7 @@ ACME._challengeToAuth = function(me, options, request, challenge, dryrun) {
 | 
				
			|||||||
		auth[key] = challenge[key];
 | 
							auth[key] = challenge[key];
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var zone = pluckZone(options.zonenames || [], auth.identifier.value);
 | 
				
			||||||
	// batteries-included helpers
 | 
						// batteries-included helpers
 | 
				
			||||||
	auth.hostname = auth.identifier.value;
 | 
						auth.hostname = auth.identifier.value;
 | 
				
			||||||
	// because I'm not 100% clear if the wildcard identifier does or doesn't have the leading *. in all cases
 | 
						// because I'm not 100% clear if the wildcard identifier does or doesn't have the leading *. in all cases
 | 
				
			||||||
@ -511,7 +567,15 @@ ACME._challengeToAuth = function(me, options, request, challenge, dryrun) {
 | 
				
			|||||||
			.update(auth.keyAuthorization)
 | 
								.update(auth.keyAuthorization)
 | 
				
			||||||
			.digest('base64')
 | 
								.digest('base64')
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
						if (zone) {
 | 
				
			||||||
 | 
							auth.dnsZone = zone;
 | 
				
			||||||
 | 
							auth.dnsPrefix = auth.dnsHost
 | 
				
			||||||
 | 
								.replace(newZoneRegExp(zone), '')
 | 
				
			||||||
 | 
								.replace(/\.$/, '');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// for backwards compat
 | 
				
			||||||
 | 
						auth.challenge = auth;
 | 
				
			||||||
	return auth;
 | 
						return auth;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -997,187 +1061,204 @@ ACME._getCertificate = function(me, options) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Do a little dry-run / self-test
 | 
						var dnsHosts = options.domains.map(function(d) {
 | 
				
			||||||
	return ACME._testChallenges(me, options).then(function() {
 | 
							return (
 | 
				
			||||||
		if (me.debug) {
 | 
								require('crypto')
 | 
				
			||||||
			console.debug('[acme-v2] certificates.create');
 | 
									.randomBytes(2)
 | 
				
			||||||
		}
 | 
									.toString('hex') + d
 | 
				
			||||||
		return ACME._getNonce(me).then(function() {
 | 
							);
 | 
				
			||||||
			var body = {
 | 
						});
 | 
				
			||||||
				// raw wildcard syntax MUST be used here
 | 
						return ACME._getZones(me, options, dnsHosts).then(function(zonenames) {
 | 
				
			||||||
				identifiers: options.domains
 | 
							options.zonenames = zonenames;
 | 
				
			||||||
					.sort(function(a, b) {
 | 
							// Do a little dry-run / self-test
 | 
				
			||||||
						// the first in the list will be the subject of the certificate, I believe (and hope)
 | 
							return ACME._testChallenges(me, options).then(function() {
 | 
				
			||||||
						if (!options.subject) {
 | 
					 | 
				
			||||||
							return 0;
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
						if (options.subject === a) {
 | 
					 | 
				
			||||||
							return -1;
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
						if (options.subject === b) {
 | 
					 | 
				
			||||||
							return 1;
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
						return 0;
 | 
					 | 
				
			||||||
					})
 | 
					 | 
				
			||||||
					.map(function(hostname) {
 | 
					 | 
				
			||||||
						return { type: 'dns', value: hostname };
 | 
					 | 
				
			||||||
					})
 | 
					 | 
				
			||||||
				//, "notBefore": "2016-01-01T00:00:00Z"
 | 
					 | 
				
			||||||
				//, "notAfter": "2016-01-08T00:00:00Z"
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			var payload = JSON.stringify(body);
 | 
					 | 
				
			||||||
			// determine the signing algorithm to use in protected header // TODO isn't that handled by the signer?
 | 
					 | 
				
			||||||
			me._kty =
 | 
					 | 
				
			||||||
				(options.accountKeypair.privateKeyJwk &&
 | 
					 | 
				
			||||||
					options.accountKeypair.privateKeyJwk.kty) ||
 | 
					 | 
				
			||||||
				'RSA';
 | 
					 | 
				
			||||||
			me._alg = 'EC' === me._kty ? 'ES256' : 'RS256'; // TODO vary with bitwidth of key (if not handled)
 | 
					 | 
				
			||||||
			var jws = me.RSA.signJws(
 | 
					 | 
				
			||||||
				options.accountKeypair,
 | 
					 | 
				
			||||||
				undefined,
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					nonce: me._nonce,
 | 
					 | 
				
			||||||
					alg: me._alg,
 | 
					 | 
				
			||||||
					url: me._directoryUrls.newOrder,
 | 
					 | 
				
			||||||
					kid: me._kid
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				Buffer.from(payload, 'utf8')
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (me.debug) {
 | 
								if (me.debug) {
 | 
				
			||||||
				console.debug('\n[DEBUG] newOrder\n');
 | 
									console.debug('[acme-v2] certificates.create');
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			me._nonce = null;
 | 
								return ACME._getNonce(me).then(function() {
 | 
				
			||||||
			return me
 | 
									var body = {
 | 
				
			||||||
				._request({
 | 
										// raw wildcard syntax MUST be used here
 | 
				
			||||||
					method: 'POST',
 | 
										identifiers: options.domains
 | 
				
			||||||
					url: me._directoryUrls.newOrder,
 | 
											.sort(function(a, b) {
 | 
				
			||||||
					headers: { 'Content-Type': 'application/jose+json' },
 | 
												// the first in the list will be the subject of the certificate, I believe (and hope)
 | 
				
			||||||
					json: jws
 | 
												if (!options.subject) {
 | 
				
			||||||
				})
 | 
													return 0;
 | 
				
			||||||
				.then(function(resp) {
 | 
					 | 
				
			||||||
					me._nonce = resp.toJSON().headers['replay-nonce'];
 | 
					 | 
				
			||||||
					var location = resp.toJSON().headers.location;
 | 
					 | 
				
			||||||
					var setAuths;
 | 
					 | 
				
			||||||
					var auths = [];
 | 
					 | 
				
			||||||
					if (me.debug) {
 | 
					 | 
				
			||||||
						console.debug(location);
 | 
					 | 
				
			||||||
					} // the account id url
 | 
					 | 
				
			||||||
					if (me.debug) {
 | 
					 | 
				
			||||||
						console.debug(resp.toJSON());
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					me._authorizations = resp.body.authorizations;
 | 
					 | 
				
			||||||
					me._order = location;
 | 
					 | 
				
			||||||
					me._finalize = resp.body.finalize;
 | 
					 | 
				
			||||||
					//if (me.debug) console.debug('[DEBUG] finalize:', me._finalize); return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					if (!me._authorizations) {
 | 
					 | 
				
			||||||
						return Promise.reject(
 | 
					 | 
				
			||||||
							new Error(
 | 
					 | 
				
			||||||
								"[acme-v2.js] authorizations were not fetched for '" +
 | 
					 | 
				
			||||||
									options.domains.join() +
 | 
					 | 
				
			||||||
									"':\n" +
 | 
					 | 
				
			||||||
									JSON.stringify(resp.body)
 | 
					 | 
				
			||||||
							)
 | 
					 | 
				
			||||||
						);
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					if (me.debug) {
 | 
					 | 
				
			||||||
						console.debug('[acme-v2] POST newOrder has authorizations');
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					setAuths = me._authorizations.slice(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					function setNext() {
 | 
					 | 
				
			||||||
						var authUrl = setAuths.shift();
 | 
					 | 
				
			||||||
						if (!authUrl) {
 | 
					 | 
				
			||||||
							return;
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						return ACME._getChallenges(me, options, authUrl).then(function(
 | 
					 | 
				
			||||||
							results
 | 
					 | 
				
			||||||
						) {
 | 
					 | 
				
			||||||
							// var domain = options.domains[i]; // results.identifier.value
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							// If it's already valid, we're golden it regardless
 | 
					 | 
				
			||||||
							if (
 | 
					 | 
				
			||||||
								results.challenges.some(function(ch) {
 | 
					 | 
				
			||||||
									return 'valid' === ch.status;
 | 
					 | 
				
			||||||
								})
 | 
					 | 
				
			||||||
							) {
 | 
					 | 
				
			||||||
								return setNext();
 | 
					 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
 | 
												if (options.subject === a) {
 | 
				
			||||||
							var challenge = ACME._chooseChallenge(options, results);
 | 
													return -1;
 | 
				
			||||||
							if (!challenge) {
 | 
					 | 
				
			||||||
								// For example, wildcards require dns-01 and, if we don't have that, we have to bail
 | 
					 | 
				
			||||||
								return Promise.reject(
 | 
					 | 
				
			||||||
									new Error(
 | 
					 | 
				
			||||||
										"Server didn't offer any challenge we can handle for '" +
 | 
					 | 
				
			||||||
											options.domains.join() +
 | 
					 | 
				
			||||||
											"'."
 | 
					 | 
				
			||||||
									)
 | 
					 | 
				
			||||||
								);
 | 
					 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
 | 
												if (options.subject === b) {
 | 
				
			||||||
							var auth = ACME._challengeToAuth(me, options, results, challenge);
 | 
													return 1;
 | 
				
			||||||
							auths.push(auth);
 | 
					 | 
				
			||||||
							return ACME._setChallenge(me, options, auth).then(setNext);
 | 
					 | 
				
			||||||
						});
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					function challengeNext() {
 | 
					 | 
				
			||||||
						var auth = auths.shift();
 | 
					 | 
				
			||||||
						if (!auth) {
 | 
					 | 
				
			||||||
							return;
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
						return ACME._postChallenge(me, options, auth).then(challengeNext);
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					// First we set every challenge
 | 
					 | 
				
			||||||
					// Then we ask for each challenge to be checked
 | 
					 | 
				
			||||||
					// Doing otherwise would potentially cause us to poison our own DNS cache with misses
 | 
					 | 
				
			||||||
					return setNext()
 | 
					 | 
				
			||||||
						.then(challengeNext)
 | 
					 | 
				
			||||||
						.then(function() {
 | 
					 | 
				
			||||||
							if (me.debug) {
 | 
					 | 
				
			||||||
								console.debug('[getCertificate] next.then');
 | 
					 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
							var validatedDomains = body.identifiers.map(function(ident) {
 | 
												return 0;
 | 
				
			||||||
								return ident.value;
 | 
					 | 
				
			||||||
							});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							return ACME._finalizeOrder(me, options, validatedDomains);
 | 
					 | 
				
			||||||
						})
 | 
											})
 | 
				
			||||||
						.then(function(order) {
 | 
											.map(function(hostname) {
 | 
				
			||||||
							if (me.debug) {
 | 
												return { type: 'dns', value: hostname };
 | 
				
			||||||
								console.debug('acme-v2: order was finalized');
 | 
											})
 | 
				
			||||||
 | 
										//, "notBefore": "2016-01-01T00:00:00Z"
 | 
				
			||||||
 | 
										//, "notAfter": "2016-01-08T00:00:00Z"
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									var payload = JSON.stringify(body);
 | 
				
			||||||
 | 
									// determine the signing algorithm to use in protected header // TODO isn't that handled by the signer?
 | 
				
			||||||
 | 
									me._kty =
 | 
				
			||||||
 | 
										(options.accountKeypair.privateKeyJwk &&
 | 
				
			||||||
 | 
											options.accountKeypair.privateKeyJwk.kty) ||
 | 
				
			||||||
 | 
										'RSA';
 | 
				
			||||||
 | 
									me._alg = 'EC' === me._kty ? 'ES256' : 'RS256'; // TODO vary with bitwidth of key (if not handled)
 | 
				
			||||||
 | 
									var jws = me.RSA.signJws(
 | 
				
			||||||
 | 
										options.accountKeypair,
 | 
				
			||||||
 | 
										undefined,
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											nonce: me._nonce,
 | 
				
			||||||
 | 
											alg: me._alg,
 | 
				
			||||||
 | 
											url: me._directoryUrls.newOrder,
 | 
				
			||||||
 | 
											kid: me._kid
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Buffer.from(payload, 'utf8')
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (me.debug) {
 | 
				
			||||||
 | 
										console.debug('\n[DEBUG] newOrder\n');
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									me._nonce = null;
 | 
				
			||||||
 | 
									return me
 | 
				
			||||||
 | 
										._request({
 | 
				
			||||||
 | 
											method: 'POST',
 | 
				
			||||||
 | 
											url: me._directoryUrls.newOrder,
 | 
				
			||||||
 | 
											headers: { 'Content-Type': 'application/jose+json' },
 | 
				
			||||||
 | 
											json: jws
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
										.then(function(resp) {
 | 
				
			||||||
 | 
											me._nonce = resp.toJSON().headers['replay-nonce'];
 | 
				
			||||||
 | 
											var location = resp.toJSON().headers.location;
 | 
				
			||||||
 | 
											var setAuths;
 | 
				
			||||||
 | 
											var auths = [];
 | 
				
			||||||
 | 
											if (me.debug) {
 | 
				
			||||||
 | 
												console.debug(location);
 | 
				
			||||||
 | 
											} // the account id url
 | 
				
			||||||
 | 
											if (me.debug) {
 | 
				
			||||||
 | 
												console.debug(resp.toJSON());
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											me._authorizations = resp.body.authorizations;
 | 
				
			||||||
 | 
											me._order = location;
 | 
				
			||||||
 | 
											me._finalize = resp.body.finalize;
 | 
				
			||||||
 | 
											//if (me.debug) console.debug('[DEBUG] finalize:', me._finalize); return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (!me._authorizations) {
 | 
				
			||||||
 | 
												return Promise.reject(
 | 
				
			||||||
 | 
													new Error(
 | 
				
			||||||
 | 
														"[acme-v2.js] authorizations were not fetched for '" +
 | 
				
			||||||
 | 
															options.domains.join() +
 | 
				
			||||||
 | 
															"':\n" +
 | 
				
			||||||
 | 
															JSON.stringify(resp.body)
 | 
				
			||||||
 | 
													)
 | 
				
			||||||
 | 
												);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											if (me.debug) {
 | 
				
			||||||
 | 
												console.debug('[acme-v2] POST newOrder has authorizations');
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											setAuths = me._authorizations.slice(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											function setNext() {
 | 
				
			||||||
 | 
												var authUrl = setAuths.shift();
 | 
				
			||||||
 | 
												if (!authUrl) {
 | 
				
			||||||
 | 
													return;
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
							return me
 | 
					
 | 
				
			||||||
								._request({ method: 'GET', url: me._certificate, json: true })
 | 
												return ACME._getChallenges(me, options, authUrl).then(function(
 | 
				
			||||||
								.then(function(resp) {
 | 
													results
 | 
				
			||||||
									if (me.debug) {
 | 
												) {
 | 
				
			||||||
										console.debug('acme-v2: csr submitted and cert received:');
 | 
													// var domain = options.domains[i]; // results.identifier.value
 | 
				
			||||||
									}
 | 
					
 | 
				
			||||||
									// https://github.com/certbot/certbot/issues/5721
 | 
													// If it's already valid, we're golden it regardless
 | 
				
			||||||
									var certsarr = ACME.splitPemChain(
 | 
													if (
 | 
				
			||||||
										ACME.formatPemChain(resp.body || '')
 | 
														results.challenges.some(function(ch) {
 | 
				
			||||||
 | 
															return 'valid' === ch.status;
 | 
				
			||||||
 | 
														})
 | 
				
			||||||
 | 
													) {
 | 
				
			||||||
 | 
														return setNext();
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													var challenge = ACME._chooseChallenge(options, results);
 | 
				
			||||||
 | 
													if (!challenge) {
 | 
				
			||||||
 | 
														// For example, wildcards require dns-01 and, if we don't have that, we have to bail
 | 
				
			||||||
 | 
														return Promise.reject(
 | 
				
			||||||
 | 
															new Error(
 | 
				
			||||||
 | 
																"Server didn't offer any challenge we can handle for '" +
 | 
				
			||||||
 | 
																	options.domains.join() +
 | 
				
			||||||
 | 
																	"'."
 | 
				
			||||||
 | 
															)
 | 
				
			||||||
									);
 | 
														);
 | 
				
			||||||
									//  cert, chain, fullchain, privkey, /*TODO, subject, altnames, issuedAt, expiresAt */
 | 
													}
 | 
				
			||||||
									var certs = {
 | 
					
 | 
				
			||||||
										expires: order.expires,
 | 
													var auth = ACME._challengeToAuth(
 | 
				
			||||||
										identifiers: order.identifiers,
 | 
														me,
 | 
				
			||||||
										//, authorizations: order.authorizations
 | 
														options,
 | 
				
			||||||
										cert: certsarr.shift(),
 | 
														results,
 | 
				
			||||||
										//, privkey: privkeyPem
 | 
														challenge
 | 
				
			||||||
										chain: certsarr.join('\n')
 | 
													);
 | 
				
			||||||
									};
 | 
													auths.push(auth);
 | 
				
			||||||
									if (me.debug) {
 | 
													return ACME._setChallenge(me, options, auth).then(setNext);
 | 
				
			||||||
										console.debug(certs);
 | 
												});
 | 
				
			||||||
									}
 | 
											}
 | 
				
			||||||
									return certs;
 | 
					
 | 
				
			||||||
 | 
											function challengeNext() {
 | 
				
			||||||
 | 
												var auth = auths.shift();
 | 
				
			||||||
 | 
												if (!auth) {
 | 
				
			||||||
 | 
													return;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												return ACME._postChallenge(me, options, auth).then(challengeNext);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// First we set every challenge
 | 
				
			||||||
 | 
											// Then we ask for each challenge to be checked
 | 
				
			||||||
 | 
											// Doing otherwise would potentially cause us to poison our own DNS cache with misses
 | 
				
			||||||
 | 
											return setNext()
 | 
				
			||||||
 | 
												.then(challengeNext)
 | 
				
			||||||
 | 
												.then(function() {
 | 
				
			||||||
 | 
													if (me.debug) {
 | 
				
			||||||
 | 
														console.debug('[getCertificate] next.then');
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
													var validatedDomains = body.identifiers.map(function(ident) {
 | 
				
			||||||
 | 
														return ident.value;
 | 
				
			||||||
								});
 | 
													});
 | 
				
			||||||
						});
 | 
					
 | 
				
			||||||
				});
 | 
													return ACME._finalizeOrder(me, options, validatedDomains);
 | 
				
			||||||
 | 
												})
 | 
				
			||||||
 | 
												.then(function(order) {
 | 
				
			||||||
 | 
													if (me.debug) {
 | 
				
			||||||
 | 
														console.debug('acme-v2: order was finalized');
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
													return me
 | 
				
			||||||
 | 
														._request({ method: 'GET', url: me._certificate, json: true })
 | 
				
			||||||
 | 
														.then(function(resp) {
 | 
				
			||||||
 | 
															if (me.debug) {
 | 
				
			||||||
 | 
																console.debug(
 | 
				
			||||||
 | 
																	'acme-v2: csr submitted and cert received:'
 | 
				
			||||||
 | 
																);
 | 
				
			||||||
 | 
															}
 | 
				
			||||||
 | 
															// https://github.com/certbot/certbot/issues/5721
 | 
				
			||||||
 | 
															var certsarr = ACME.splitPemChain(
 | 
				
			||||||
 | 
																ACME.formatPemChain(resp.body || '')
 | 
				
			||||||
 | 
															);
 | 
				
			||||||
 | 
															//  cert, chain, fullchain, privkey, /*TODO, subject, altnames, issuedAt, expiresAt */
 | 
				
			||||||
 | 
															var certs = {
 | 
				
			||||||
 | 
																expires: order.expires,
 | 
				
			||||||
 | 
																identifiers: order.identifiers,
 | 
				
			||||||
 | 
																//, authorizations: order.authorizations
 | 
				
			||||||
 | 
																cert: certsarr.shift(),
 | 
				
			||||||
 | 
																//, privkey: privkeyPem
 | 
				
			||||||
 | 
																chain: certsarr.join('\n')
 | 
				
			||||||
 | 
															};
 | 
				
			||||||
 | 
															if (me.debug) {
 | 
				
			||||||
 | 
																console.debug(certs);
 | 
				
			||||||
 | 
															}
 | 
				
			||||||
 | 
															return certs;
 | 
				
			||||||
 | 
														});
 | 
				
			||||||
 | 
												});
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -1190,7 +1271,7 @@ ACME.create = function create(me) {
 | 
				
			|||||||
	me.challengePrefixes = ACME.challengePrefixes;
 | 
						me.challengePrefixes = ACME.challengePrefixes;
 | 
				
			||||||
	me.RSA = me.RSA || require('rsa-compat').RSA;
 | 
						me.RSA = me.RSA || require('rsa-compat').RSA;
 | 
				
			||||||
	//me.Keypairs = me.Keypairs || require('keypairs');
 | 
						//me.Keypairs = me.Keypairs || require('keypairs');
 | 
				
			||||||
	me.request = me.request || require('@coolaj86/urequest');
 | 
						me.request = me.request || require('@root/request');
 | 
				
			||||||
	me._dig = function(query) {
 | 
						me._dig = function(query) {
 | 
				
			||||||
		// TODO use digd.js
 | 
							// TODO use digd.js
 | 
				
			||||||
		return new Promise(function(resolve, reject) {
 | 
							return new Promise(function(resolve, reject) {
 | 
				
			||||||
@ -1241,7 +1322,27 @@ ACME.create = function create(me) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	me.init = function(_directoryUrl) {
 | 
						me.init = function(_directoryUrl) {
 | 
				
			||||||
		me.directoryUrl = me.directoryUrl || _directoryUrl;
 | 
							if (_directoryUrl) {
 | 
				
			||||||
 | 
								_directoryUrl = _directoryUrl.directoryUrl || _directoryUrl;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if ('string' === typeof _directoryUrl) {
 | 
				
			||||||
 | 
								me.directoryUrl = _directoryUrl;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (!me.directoryUrl) {
 | 
				
			||||||
 | 
								me.directoryUrl =
 | 
				
			||||||
 | 
									'https://acme-staging-v02.api.letsencrypt.org/directory';
 | 
				
			||||||
 | 
								console.warn();
 | 
				
			||||||
 | 
								console.warn(
 | 
				
			||||||
 | 
									"No ACME `directoryUrl` was specified. Using Let's Encrypt's staging environment as the default, which will issue invalid certs."
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								console.warn('\t' + me.directoryUrl);
 | 
				
			||||||
 | 
								console.warn();
 | 
				
			||||||
 | 
								console.warn(
 | 
				
			||||||
 | 
									"To get valid certificates you will need to switch to a production URL. You might like Let's Encrypt v2:"
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								console.warn('\t' + me.directoryUrl.replace('-staging', ''));
 | 
				
			||||||
 | 
								console.warn();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		return ACME._directory(me).then(function(resp) {
 | 
							return ACME._directory(me).then(function(resp) {
 | 
				
			||||||
			me._directoryUrls = resp.body;
 | 
								me._directoryUrls = resp.body;
 | 
				
			||||||
			me._tos = me._directoryUrls.meta.termsOfService;
 | 
								me._tos = me._directoryUrls.meta.termsOfService;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										22
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										22
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -1,13 +1,19 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	"name": "acme-v2",
 | 
						"name": "acme-v2",
 | 
				
			||||||
	"version": "1.7.6",
 | 
						"version": "1.8.0",
 | 
				
			||||||
	"lockfileVersion": 1,
 | 
						"lockfileVersion": 1,
 | 
				
			||||||
	"requires": true,
 | 
						"requires": true,
 | 
				
			||||||
	"dependencies": {
 | 
						"dependencies": {
 | 
				
			||||||
		"@coolaj86/urequest": {
 | 
							"@root/request": {
 | 
				
			||||||
			"version": "1.3.7",
 | 
								"version": "1.3.11",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/@coolaj86/urequest/-/urequest-1.3.7.tgz",
 | 
								"resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.11.tgz",
 | 
				
			||||||
			"integrity": "sha512-PPrVYra9aWvZjSCKl/x1pJ9ZpXda1652oJrPBYy5rQumJJMkmTBN3ux+sK2xAUwVvv2wnewDlaQaHLxLwSHnIA=="
 | 
								"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==",
 | 
				
			||||||
 | 
								"dev": true
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"eckles": {
 | 
							"eckles": {
 | 
				
			||||||
			"version": "1.4.1",
 | 
								"version": "1.4.1",
 | 
				
			||||||
@ -29,9 +35,9 @@
 | 
				
			|||||||
			"integrity": "sha512-KxtX+/fBk+wM7O3CNgwjSh5elwFilLvqWajhr6wFr2Hd63JnKTTi43Tw+Jb1hxJQWOwoya+NZWR2xztn3hCrTw=="
 | 
								"integrity": "sha512-KxtX+/fBk+wM7O3CNgwjSh5elwFilLvqWajhr6wFr2Hd63JnKTTi43Tw+Jb1hxJQWOwoya+NZWR2xztn3hCrTw=="
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"rsa-compat": {
 | 
							"rsa-compat": {
 | 
				
			||||||
			"version": "2.0.6",
 | 
								"version": "2.0.8",
 | 
				
			||||||
			"resolved": "https://registry.npmjs.org/rsa-compat/-/rsa-compat-2.0.6.tgz",
 | 
								"resolved": "https://registry.npmjs.org/rsa-compat/-/rsa-compat-2.0.8.tgz",
 | 
				
			||||||
			"integrity": "sha512-bQmpscAQec9442RaghDybrHMy1twQ3nUZOgTlqntio1yru+rMnDV64uGRzKp7dJ4VVhNv3mLh3X4MNON+YM0dA==",
 | 
								"integrity": "sha512-BFiiSEbuxzsVdaxpejbxfX07qs+rtous49Y6mL/zw6YHh9cranDvm2BvBmqT3rso84IsxNlP5BXnuNvm1Wn3Tw==",
 | 
				
			||||||
			"requires": {
 | 
								"requires": {
 | 
				
			||||||
				"keypairs": "^1.2.14"
 | 
									"keypairs": "^1.2.14"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										15
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								package.json
									
									
									
									
									
								
							@ -1,11 +1,11 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	"name": "acme-v2",
 | 
						"name": "acme-v2",
 | 
				
			||||||
	"version": "1.7.7",
 | 
						"version": "1.8.0",
 | 
				
			||||||
	"description": "Free SSL. A framework for building Let's Encrypt v2 clients, and other ACME v2 (draft 11) clients. Successor to le-acme-core.js",
 | 
						"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",
 | 
						"homepage": "https://git.coolaj86.com/coolaj86/acme-v2.js",
 | 
				
			||||||
	"main": "node.js",
 | 
						"main": "node.js",
 | 
				
			||||||
	"scripts": {
 | 
						"scripts": {
 | 
				
			||||||
		"test": "echo \"Error: no test specified\" && exit 1"
 | 
							"test": "node ./test.js"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"repository": {
 | 
						"repository": {
 | 
				
			||||||
		"type": "git",
 | 
							"type": "git",
 | 
				
			||||||
@ -23,10 +23,13 @@
 | 
				
			|||||||
		"automated https",
 | 
							"automated https",
 | 
				
			||||||
		"letsencrypt"
 | 
							"letsencrypt"
 | 
				
			||||||
	],
 | 
						],
 | 
				
			||||||
	"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
 | 
						"author": "AJ ONeal <coolaj86@gmail.com> (https://solderjs.com/)",
 | 
				
			||||||
	"license": "MPL-2.0",
 | 
						"license": "MPL-2.0",
 | 
				
			||||||
	"dependencies": {
 | 
						"dependencies": {
 | 
				
			||||||
		"@coolaj86/urequest": "^1.3.6",
 | 
							"@root/request": "^1.3.11",
 | 
				
			||||||
		"rsa-compat": "^2.0.6"
 | 
							"rsa-compat": "^2.0.8"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"devDependencies": {
 | 
				
			||||||
 | 
							"dotenv": "^8.0.0"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user