mirror of
				https://github.com/therootcompany/acme.js.git
				synced 2024-11-16 17:29:00 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			274 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # [ACME.js](https://git.rootprojects.org/root/bluecrypt-acme.js)
 | |
| 
 | |
| Free SSL Certificates from Let's Encrypt, for Node.js and Web Browsers
 | |
| 
 | |
| Lightweight. Fast. Modern Crypto. Zero dependecies.
 | |
| 
 | |
| # Features
 | |
| 
 | |
| | 15k gzipped | 55k minified | 88k (2,500 loc) source with comments |
 | |
| 
 | |
| -   [x] Let's Encrypt v2.1+ (November 2019)
 | |
|     -   [x] ACME draft 15 (supports POST-as-GET)
 | |
|     -   [x] Secure support for EC and RSA for account and server keys
 | |
|     -   [x] Simple and lightweight PEM, DER, ASN1, X509, and CSR implementations
 | |
| -   [x] Supports International Domain Names (i.e. `.中国`)
 | |
| -   [x] VanillaJS, Zero External Dependencies
 | |
|     -   [x] Node.js
 | |
|     -   [x] WebPack
 | |
| 
 | |
| # Want Quick and Easy?
 | |
| 
 | |
| ACME.js is a low-level tool for building Let's Encrypt clients in Node and Browsers.
 | |
| 
 | |
| If you're looking for maximum convenience, try
 | |
| [Greenlock.js](https://git.rootprojects.org/root/greenlock-express.js).
 | |
| 
 | |
| -   <https://git.rootprojects.org/root/greenlock-express.js>
 | |
| 
 | |
| # Online Demos
 | |
| 
 | |
| -   Greenlock for the Web <https://greenlock.domains>
 | |
| -   ACME.js Demo <https://rootprojects.org/acme/>
 | |
| 
 | |
| We expect that our hosted versions will meet all of yours needs.
 | |
| If they don't, please open an issue to let us know why.
 | |
| 
 | |
| We'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.
 | |
| 
 | |
| # QuickStart
 | |
| 
 | |
| To make it easy to generate, encode, and decode keys and certificates,
 | |
| ACME.js embeds [Keypairs.js](https://git.rootprojects.org/root/bluecrypt-keypairs.js)
 | |
| and [CSR.js](https://git.rootprojects.org/root/bluecrypt-csr.js)
 | |
| 
 | |
| ## Node.js
 | |
| 
 | |
| ```js
 | |
| var ACME = require('@root/acme');
 | |
| ```
 | |
| 
 | |
| ## WebPack
 | |
| 
 | |
| ```js
 | |
| var ACME = require('@root/acme');
 | |
| ```
 | |
| 
 | |
| ## Vanilla JS
 | |
| 
 | |
| ```js
 | |
| var ACME = window.ACME;
 | |
| ```
 | |
| 
 | |
| `acme.js`
 | |
| 
 | |
| ```html
 | |
| <script src="https://unpkg.com/@root/acme/dist/acme.js"></script>
 | |
| ```
 | |
| 
 | |
| `acme.min.js`
 | |
| 
 | |
| ```html
 | |
| <script src="https://unpkg.com/@root/acme/dist/acme.min.js"></script>
 | |
| ```
 | |
| 
 | |
| ## 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;
 | |
| 		});
 | |
| });
 | |
| ```
 | |
| 
 | |
| ### 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 serverPrivateKey;
 | |
| 
 | |
| Keypairs.generate({ kty: 'EC' }).then(function(pair) {
 | |
| 	serverPrivateKey = pair.private;
 | |
| 
 | |
| 	return acme.certificates
 | |
| 		.create({
 | |
| 			agreeToTerms: function(tos) {
 | |
| 				return tos;
 | |
| 			},
 | |
| 			account: account,
 | |
| 			accountKeypair: { privateKeyJwk: accountPrivateKey },
 | |
| 			serverKeypair: { privateKeyJwk: serverPrivateKey },
 | |
| 			domains: ['example.com', 'www.example.com'],
 | |
| 			challenges: challenges, // must be implemented
 | |
| 			customerEmail: null,
 | |
| 			skipDryRun: true
 | |
| 		})
 | |
| 		.then(function(results) {
 | |
| 			console.log('Got SSL Certificate:');
 | |
| 			console.log(results.expires);
 | |
| 			console.log(results.cert);
 | |
| 			console.log(results.chain);
 | |
| 		});
 | |
| });
 | |
| ```
 | |
| 
 | |
| ### 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();
 | |
| 		},
 | |
| 		remove: function(opts) {
 | |
| 			console.log('http-01 remove challenge:', opts.challengeUrl);
 | |
| 			return Promise.resolve();
 | |
| 		}
 | |
| 	}
 | |
| };
 | |
| ```
 | |
| 
 | |
| # IDN - International Domain Names
 | |
| 
 | |
| Convert domain names to `punycode` before creating the certificate:
 | |
| 
 | |
| ```js
 | |
| var punycode = require('punycode');
 | |
| 
 | |
| acme.certificates.create({
 | |
| 	// ...
 | |
| 	domains: ['example.com', 'www.example.com'].map(function(name) {
 | |
| 		return punycode.toASCII(name);
 | |
| 	})
 | |
| });
 | |
| ```
 | |
| 
 | |
| The punycode library itself is lightweight and dependency-free.
 | |
| It is available both in node and for browsers.
 | |
| 
 | |
| # Full Documentation
 | |
| 
 | |
| See [acme.js](https://git.coolaj86.com/coolaj86/acme-v2.js).
 | |
| 
 | |
| Aside from the loading instructions (`npm` and `require` instead of `script` tags),
 | |
| the usage is identical to the node version.
 | |
| 
 | |
| That said, the two may leap-frog a little from time to time
 | |
| (for example, the browser version is just a touch ahead at the moment).
 | |
| 
 | |
| # 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)
 |