forked from root/acme.js
		
	merge unrelated v2 (historical) and v3 (new from scratch)
This commit is contained in:
		
						commit
						ad42d34587
					
				
							
								
								
									
										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
 | 
			
		||||
==================================
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										498
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										498
									
								
								README.md
									
									
									
									
									
								
							@ -1,239 +1,355 @@
 | 
			
		||||
# ACME.js v3 on its way (Nov 1st, 2019)
 | 
			
		||||
# [ACME.js](https://git.rootprojects.org/root/acme.js) v3
 | 
			
		||||
 | 
			
		||||
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 Draft 12
 | 
			
		||||
  - [ ] (in-progress) Let's Encrypt™ v2.1 / ACME Draft 18
 | 
			
		||||
-   [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
 | 
			
		||||
  - [x] Zero External Dependencies
 | 
			
		||||
-   [x] VanillaJS, Zero External Dependencies
 | 
			
		||||
    -   [x] Safe, Efficient, Maintained
 | 
			
		||||
  - [x] Works in Node v6+
 | 
			
		||||
  - [ ] (v2) Works in Web Browsers (See [Demo](https://greenlock.domains))
 | 
			
		||||
    -   [x] Node.js\* (v6+)
 | 
			
		||||
    -   [x] WebPack
 | 
			
		||||
-   [x] Online Demo
 | 
			
		||||
    -   See 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>
 | 
			
		||||
\* 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.
 | 
			
		||||
 | 
			
		||||
## Looking for Quick 'n' Easy™?
 | 
			
		||||
# Want Quick and Easy?
 | 
			
		||||
 | 
			
		||||
If you want something that's more "batteries included" give
 | 
			
		||||
[greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
 | 
			
		||||
a try.
 | 
			
		||||
ACME.js is a low-level tool for building Let's Encrypt clients in Node and Browsers.
 | 
			
		||||
 | 
			
		||||
- [greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
 | 
			
		||||
If you're looking for maximum convenience, try
 | 
			
		||||
[Greenlock.js](https://git.rootprojects.org/root/greenlock-express.js).
 | 
			
		||||
 | 
			
		||||
## v1.7+: Transitional v2 Support
 | 
			
		||||
-   <https://git.rootprojects.org/root/greenlock-express.js>
 | 
			
		||||
 | 
			
		||||
By the end of June 2019 we expect to have completed the migration to Let's Encrypt v2.1 (ACME draft 18).
 | 
			
		||||
# Online Demos
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
-   Greenlock for the Web <https://greenlock.domains>
 | 
			
		||||
-   ACME.js Demo <https://rootprojects.org/acme/>
 | 
			
		||||
 | 
			
		||||
We're taking this **mandatory ACME update** as an opportunity to **clean up** and **greatly simplify**
 | 
			
		||||
the code with a fresh new release.
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
## Recommended Example
 | 
			
		||||
# QuickStart
 | 
			
		||||
 | 
			
		||||
Due to the upcoming changes we've removed the old documentation.
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
## Node.js
 | 
			
		||||
 | 
			
		||||
```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';
 | 
			
		||||
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"`)
 | 
			
		||||
 | 
			
		||||
`acme.js`
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<script src="https://unpkg.com/@root/acme@3.0.0/dist/acme.js"></script>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
`acme.min.js`
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<script src="https://unpkg.com/@root/acme@3.0.0/dist/acme.min.js"></script>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Use
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
var ACME = window['@root/acme'];
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 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.
 | 
			
		||||
 | 
			
		||||
### 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 accountPrivateKey;
 | 
			
		||||
var account;
 | 
			
		||||
 | 
			
		||||
Keypairs.generate({ kty: 'EC' }).then(function(pair) {
 | 
			
		||||
	accountPrivateKey = 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);
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			accountKeypair: { privateKeyJwk: pair.private },
 | 
			
		||||
			subscriberEmail: $('.js-email-input').value
 | 
			
		||||
		})
 | 
			
		||||
		.then(function(_account) {
 | 
			
		||||
			account = _account;
 | 
			
		||||
		});
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
	// don't try to validate challenges locally
 | 
			
		||||
	skipChallengeTest: false,
 | 
			
		||||
	skipDryRun: false,
 | 
			
		||||
### Generate a Certificate Private Key
 | 
			
		||||
 | 
			
		||||
	// 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
 | 
			
		||||
```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>'
 | 
			
		||||
The easiest way to generate a Certificate Signing Request will be either with `openssl` or with `@root/CSR`.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
var CSR = require('@root/csr');
 | 
			
		||||
var Enc = require('@root/encoding');
 | 
			
		||||
 | 
			
		||||
// 'subject' should be first in list
 | 
			
		||||
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);
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 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({
 | 
			
		||||
	agreeToTerms: function(tos) {
 | 
			
		||||
		return tos;
 | 
			
		||||
	},
 | 
			
		||||
	agreeToTerms: function(tosUrl) {} //    should Promise the same `tosUrl` back
 | 
			
		||||
};
 | 
			
		||||
	account: account,
 | 
			
		||||
	accountKeypair: { privateKeyJwk: accountPrivateKey },
 | 
			
		||||
	csr: csr,
 | 
			
		||||
	domains: sortedDomains,
 | 
			
		||||
	challenges: challenges, // must be implemented
 | 
			
		||||
	customerEmail: null,
 | 
			
		||||
	skipChallengeTests: false,
 | 
			
		||||
	skipDryRun: false
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Registration
 | 
			
		||||
ACME.certificates.create(options); // returns Promise<pems={ privkey (key), cert, chain (ca) }>
 | 
			
		||||
console.log('Got SSL Certificate:');
 | 
			
		||||
console.log(results.expires);
 | 
			
		||||
 | 
			
		||||
options = {
 | 
			
		||||
	domainKeypair: {
 | 
			
		||||
		privateKeyPem: '<ASCII PEM>'
 | 
			
		||||
// 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.')
 | 
			
		||||
			) {}
 | 
			
		||||
			return Promise.resolve();
 | 
			
		||||
		},
 | 
			
		||||
	accountKeypair: {
 | 
			
		||||
		privateKeyPem: '<ASCII PEM>'
 | 
			
		||||
	},
 | 
			
		||||
	domains: ['example.com'],
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
		remove: function(opts) {
 | 
			
		||||
			console.log('http-01 remove challenge:', opts.challengeUrl);
 | 
			
		||||
			return Promise.resolve();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# Changelog
 | 
			
		||||
# IDN - International Domain Names
 | 
			
		||||
 | 
			
		||||
- 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
 | 
			
		||||
Convert domain names to `punycode` before creating the certificate:
 | 
			
		||||
 | 
			
		||||
# Legal
 | 
			
		||||
```js
 | 
			
		||||
var punycode = require('punycode');
 | 
			
		||||
 | 
			
		||||
[acme-v2.js](https://git.coolaj86.com/coolaj86/acme-v2.js) |
 | 
			
		||||
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/bluecrypt-acme.js
 | 
			
		||||
pushd bluecrypt-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/bluecrypt-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)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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)
 | 
			
		||||
	);
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										50
									
								
								browser.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								browser.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
'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 new 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 new me.request({
 | 
			
		||||
		url: me._baseUrl + '/api/http?url=' + url
 | 
			
		||||
	}).then(function(resp) {
 | 
			
		||||
		return resp.body;
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										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;
 | 
			
		||||
							
								
								
									
										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);
 | 
			
		||||
})();
 | 
			
		||||
@ -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"}'
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,10 @@ if (!fs.existsSync(__dirname + '/../tests/account.privkey.pem')) {
 | 
			
		||||
		var privkeyPem = RSA.exportPrivatePem(keypair);
 | 
			
		||||
		console.log(privkeyPem);
 | 
			
		||||
 | 
			
		||||
		fs.writeFileSync(__dirname + '/../tests/account.privkey.pem', privkeyPem);
 | 
			
		||||
		fs.writeFileSync(
 | 
			
		||||
			__dirname + '/../tests/account.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());
 | 
			
		||||
	});
 | 
			
		||||
							
								
								
									
										231
									
								
								examples/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								examples/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,231 @@
 | 
			
		||||
<html>
 | 
			
		||||
	<head>
 | 
			
		||||
		<title>Bluecrypt ACME - 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>
 | 
			
		||||
			@bluecrypt/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://rootprojects.org/acme/bluecrypt-acme.js">https://rootprojects.org/acme/bluecrypt-acme.js</a>"></script></code></pre>
 | 
			
		||||
		<pre><code><script src="<a href="https://rootprojects.org/acme/bluecrypt-acme.min.js">https://rootprojects.org/acme/bluecrypt-acme.min.js</a>"></script></code></pre>
 | 
			
		||||
		<a href="https://git.rootprojects.org/root/bluecrypt-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>
 | 
			
		||||
			Bluecrypt™ is 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/bluecrypt-acme.js"
 | 
			
		||||
				>@bluecrypt/acme</a
 | 
			
		||||
			>
 | 
			
		||||
		</footer>
 | 
			
		||||
 | 
			
		||||
		<script src="./app.js"></script>
 | 
			
		||||
		<!-- script src="../dist/acme.js"></script -->
 | 
			
		||||
		<!-- script src="../dist/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"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								fixtures/account.registration.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								fixtures/account.registration.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
{
 | 
			
		||||
	"key": {
 | 
			
		||||
		"kty": "EC",
 | 
			
		||||
		"crv": "P-256",
 | 
			
		||||
		"x": "uLh0RLpAmKyyHCf2zOaF18IIuBiJEiZ8Mu3xPZ7ZxN8",
 | 
			
		||||
		"y": "vVl_cCXK0_GlCaCT5Yg750LUd8eRU6tySEdQFLM62NQ",
 | 
			
		||||
		"kid": "https://acme-staging-v02.api.letsencrypt.org/acme/acct/11265299"
 | 
			
		||||
	},
 | 
			
		||||
	"contact": [],
 | 
			
		||||
	"initialIp": "66.219.236.169",
 | 
			
		||||
	"createdAt": "2019-10-04T22:54:28.569489074Z",
 | 
			
		||||
	"status": "valid"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								lib/browser/http.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								lib/browser/http.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var http = module.exports;
 | 
			
		||||
 | 
			
		||||
http.request = function(opts) {
 | 
			
		||||
	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);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										19
									
								
								lib/node/http.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								lib/node/http.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var http = module.exports;
 | 
			
		||||
var promisify = require('util').promisify;
 | 
			
		||||
var request = promisify(require('@root/request'));
 | 
			
		||||
 | 
			
		||||
http.request = function(opts) {
 | 
			
		||||
	if (!opts.headers) {
 | 
			
		||||
		opts.headers = {};
 | 
			
		||||
	}
 | 
			
		||||
	if (
 | 
			
		||||
		!Object.keys(opts.headers).some(function(key) {
 | 
			
		||||
			return 'user-agent' === key.toLowerCase();
 | 
			
		||||
		})
 | 
			
		||||
	) {
 | 
			
		||||
		// TODO opts.headers['User-Agent'] = 'TODO';
 | 
			
		||||
	}
 | 
			
		||||
	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();
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										33
									
								
								native.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								native.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var native = module.exports;
 | 
			
		||||
var promisify = require('util').promisify;
 | 
			
		||||
var resolveTxt = promisify(require('dns').resolveTxt);
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										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-wip.4",
 | 
			
		||||
	"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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										61
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								package.json
									
									
									
									
									
								
							@ -1,41 +1,60 @@
 | 
			
		||||
{
 | 
			
		||||
	"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-wip.4",
 | 
			
		||||
	"description": "Free SSL certificates for Node.js and Browsers. Issued via Let's Encrypt",
 | 
			
		||||
	"homepage": "https://rootprojects.org/acme/",
 | 
			
		||||
	"main": "acme.js",
 | 
			
		||||
	"browser": {
 | 
			
		||||
		"./native.js": "./browser.js",
 | 
			
		||||
		"./lib/node/sha2.js": "./lib/browser/sha2.js",
 | 
			
		||||
		"./lib/node/http.js": "./lib/browser/http.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);
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										15
									
								
								tests/generate-cert-key.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								tests/generate-cert-key.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
async function run() {
 | 
			
		||||
	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'
 | 
			
		||||
	});
 | 
			
		||||
	console.log(pem);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
run();
 | 
			
		||||
							
								
								
									
										225
									
								
								tests/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								tests/index.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,225 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
require('dotenv').config();
 | 
			
		||||
 | 
			
		||||
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 acme = ACME.create({
 | 
			
		||||
	// debug: true
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// TODO exec npm install --save-dev CHALLENGE_MODULE
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
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 (config.debug) {
 | 
			
		||||
		console.info('Account Key Created');
 | 
			
		||||
		console.info(JSON.stringify(accountKeypair, null, 2));
 | 
			
		||||
		console.info();
 | 
			
		||||
		console.info();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var account = await acme.accounts.create({
 | 
			
		||||
		agreeToTerms: agree,
 | 
			
		||||
		// TODO detect jwk/pem/der?
 | 
			
		||||
		accountKeypair: { privateKeyJwk: accountKeypair.private },
 | 
			
		||||
		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,
 | 
			
		||||
		accountKeypair: { privateKeyJwk: accountKeypair.private },
 | 
			
		||||
		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() {
 | 
			
		||||
		// Now try RSA + EC
 | 
			
		||||
		rnd = random();
 | 
			
		||||
		return happyPath('RSA', 'EC', rnd).then(function() {
 | 
			
		||||
			console.info('success');
 | 
			
		||||
		});
 | 
			
		||||
	})
 | 
			
		||||
	.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) + '例'
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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);
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										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