forked from root/acme.js
		
	backport all the things
This commit is contained in:
		
							parent
							
								
									7e6a66c1d8
								
							
						
					
					
						commit
						f05e9db38e
					
				
							
								
								
									
										161
									
								
								account.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								account.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,161 @@
 | 
				
			|||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var A = module.exports;
 | 
				
			||||||
 | 
					var U = require('./utils.js');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Keypairs = require('@root/keypairs');
 | 
				
			||||||
 | 
					var Enc = require('@root/encoding/bytes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A._getAccountKid = function(me, options) {
 | 
				
			||||||
 | 
						// It's just fine if there's no account, we'll go get the key id we need via the existing key
 | 
				
			||||||
 | 
						options._kid =
 | 
				
			||||||
 | 
							options._kid ||
 | 
				
			||||||
 | 
							options.accountKid ||
 | 
				
			||||||
 | 
							(options.account &&
 | 
				
			||||||
 | 
								(options.account.kid ||
 | 
				
			||||||
 | 
									(options.account.key && options.account.key.kid)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (options._kid) {
 | 
				
			||||||
 | 
							return Promise.resolve(options._kid);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//return Promise.reject(new Error("must include KeyID"));
 | 
				
			||||||
 | 
						// This is an idempotent request. It'll return the same account for the same public key.
 | 
				
			||||||
 | 
						return A._registerAccount(me, options).then(function(account) {
 | 
				
			||||||
 | 
							options._kid = account.key.kid;
 | 
				
			||||||
 | 
							// start back from the top
 | 
				
			||||||
 | 
							return options._kid;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ACME RFC Section 7.3 Account Creation
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 {
 | 
				
			||||||
 | 
					   "protected": base64url({
 | 
				
			||||||
 | 
					     "alg": "ES256",
 | 
				
			||||||
 | 
					     "jwk": {...},
 | 
				
			||||||
 | 
					     "nonce": "6S8IqOGY7eL2lsGoTZYifg",
 | 
				
			||||||
 | 
					     "url": "https://example.com/acme/new-account"
 | 
				
			||||||
 | 
					   }),
 | 
				
			||||||
 | 
					   "payload": base64url({
 | 
				
			||||||
 | 
					     "termsOfServiceAgreed": true,
 | 
				
			||||||
 | 
					     "onlyReturnExisting": false,
 | 
				
			||||||
 | 
					     "contact": [
 | 
				
			||||||
 | 
					       "mailto:cert-admin@example.com",
 | 
				
			||||||
 | 
					       "mailto:admin@example.com"
 | 
				
			||||||
 | 
					     ]
 | 
				
			||||||
 | 
					   }),
 | 
				
			||||||
 | 
					   "signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I"
 | 
				
			||||||
 | 
					 }
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					A._registerAccount = function(me, options) {
 | 
				
			||||||
 | 
						//#console.debug('[ACME.js] accounts.create');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						function agree(tosUrl) {
 | 
				
			||||||
 | 
							var err;
 | 
				
			||||||
 | 
							if (me._tos !== tosUrl) {
 | 
				
			||||||
 | 
								err = new Error("You must agree to the ToS at '" + me._tos + "'");
 | 
				
			||||||
 | 
								err.code = 'E_AGREE_TOS';
 | 
				
			||||||
 | 
								throw err;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return U._importKeypair(
 | 
				
			||||||
 | 
								me,
 | 
				
			||||||
 | 
								options.accountKey || options.accountKeypair
 | 
				
			||||||
 | 
							).then(function(pair) {
 | 
				
			||||||
 | 
								var contact;
 | 
				
			||||||
 | 
								if (options.contact) {
 | 
				
			||||||
 | 
									contact = options.contact.slice(0);
 | 
				
			||||||
 | 
								} else if (options.subscriberEmail || options.email) {
 | 
				
			||||||
 | 
									contact = [
 | 
				
			||||||
 | 
										'mailto:' + (options.subscriberEmail || options.email)
 | 
				
			||||||
 | 
									];
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								var accountRequest = {
 | 
				
			||||||
 | 
									termsOfServiceAgreed: tosUrl === me._tos,
 | 
				
			||||||
 | 
									onlyReturnExisting: false,
 | 
				
			||||||
 | 
									contact: contact
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								var pExt;
 | 
				
			||||||
 | 
								if (options.externalAccount) {
 | 
				
			||||||
 | 
									pExt = Keypairs.signJws({
 | 
				
			||||||
 | 
										// TODO is HMAC the standard, or is this arbitrary?
 | 
				
			||||||
 | 
										secret: options.externalAccount.secret,
 | 
				
			||||||
 | 
										protected: {
 | 
				
			||||||
 | 
											alg: options.externalAccount.alg || 'HS256',
 | 
				
			||||||
 | 
											kid: options.externalAccount.id,
 | 
				
			||||||
 | 
											url: me._directoryUrls.newAccount
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										payload: Enc.strToBuf(JSON.stringify(pair.public))
 | 
				
			||||||
 | 
									}).then(function(jws) {
 | 
				
			||||||
 | 
										accountRequest.externalAccountBinding = jws;
 | 
				
			||||||
 | 
										return accountRequest;
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									pExt = Promise.resolve(accountRequest);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return pExt.then(function(accountRequest) {
 | 
				
			||||||
 | 
									var payload = JSON.stringify(accountRequest);
 | 
				
			||||||
 | 
									return U._jwsRequest(me, {
 | 
				
			||||||
 | 
										options: options,
 | 
				
			||||||
 | 
										url: me._directoryUrls.newAccount,
 | 
				
			||||||
 | 
										protected: { kid: false, jwk: pair.public },
 | 
				
			||||||
 | 
										payload: Enc.strToBuf(payload)
 | 
				
			||||||
 | 
									}).then(function(resp) {
 | 
				
			||||||
 | 
										var account = resp.body;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (resp.statusCode < 200 || resp.statusCode >= 300) {
 | 
				
			||||||
 | 
											if ('string' !== typeof account) {
 | 
				
			||||||
 | 
												account = JSON.stringify(account);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											throw new Error(
 | 
				
			||||||
 | 
												'account error: ' +
 | 
				
			||||||
 | 
													resp.statusCode +
 | 
				
			||||||
 | 
													' ' +
 | 
				
			||||||
 | 
													account +
 | 
				
			||||||
 | 
													'\n' +
 | 
				
			||||||
 | 
													payload
 | 
				
			||||||
 | 
											);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										var location = resp.headers.location;
 | 
				
			||||||
 | 
										// the account id url
 | 
				
			||||||
 | 
										options._kid = location;
 | 
				
			||||||
 | 
										//#console.debug('[DEBUG] new account location:');
 | 
				
			||||||
 | 
										//#console.debug(location);
 | 
				
			||||||
 | 
										//#console.debug(resp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										/*
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              contact: ["mailto:jon@example.com"],
 | 
				
			||||||
 | 
					              orders: "https://some-url",
 | 
				
			||||||
 | 
					              status: 'valid'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            */
 | 
				
			||||||
 | 
										if (!account) {
 | 
				
			||||||
 | 
											account = { _emptyResponse: true };
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										// https://git.rootprojects.org/root/acme.js/issues/8
 | 
				
			||||||
 | 
										if (!account.key) {
 | 
				
			||||||
 | 
											account.key = {};
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										account.key.kid = options._kid;
 | 
				
			||||||
 | 
										return account;
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Promise.resolve()
 | 
				
			||||||
 | 
							.then(function() {
 | 
				
			||||||
 | 
								//#console.debug('[ACME.js] agreeToTerms');
 | 
				
			||||||
 | 
								var agreeToTerms = options.agreeToTerms;
 | 
				
			||||||
 | 
								if (true === agreeToTerms) {
 | 
				
			||||||
 | 
									agreeToTerms = function(tos) {
 | 
				
			||||||
 | 
										return tos;
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return agreeToTerms(me._tos);
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.then(agree);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -8,9 +8,6 @@ var PEM = require('@root/pem');
 | 
				
			|||||||
var punycode = require('punycode');
 | 
					var punycode = require('punycode');
 | 
				
			||||||
var ACME = require('../acme.js');
 | 
					var ACME = require('../acme.js');
 | 
				
			||||||
var Keypairs = require('@root/keypairs');
 | 
					var Keypairs = require('@root/keypairs');
 | 
				
			||||||
var acme = ACME.create({
 | 
					 | 
				
			||||||
	// debug: true
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO exec npm install --save-dev CHALLENGE_MODULE
 | 
					// TODO exec npm install --save-dev CHALLENGE_MODULE
 | 
				
			||||||
if (!process.env.CHALLENGE_OPTIONS) {
 | 
					if (!process.env.CHALLENGE_OPTIONS) {
 | 
				
			||||||
@ -33,6 +30,22 @@ var pluginPrefix = 'acme-' + config.challengeType + '-';
 | 
				
			|||||||
var pluginName = config.challengeModule;
 | 
					var pluginName = config.challengeModule;
 | 
				
			||||||
var plugin;
 | 
					var plugin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var acme = ACME.create({
 | 
				
			||||||
 | 
						// debug: true
 | 
				
			||||||
 | 
						maintainerEmail: config.email,
 | 
				
			||||||
 | 
						notify: function(ev, params) {
 | 
				
			||||||
 | 
							console.info(
 | 
				
			||||||
 | 
								ev,
 | 
				
			||||||
 | 
								params.subject || params.altname || params.domain,
 | 
				
			||||||
 | 
								params.status
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
							if ('error' === ev) {
 | 
				
			||||||
 | 
								console.error(params);
 | 
				
			||||||
 | 
								console.error(params.error);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function badPlugin(err) {
 | 
					function badPlugin(err) {
 | 
				
			||||||
	if ('MODULE_NOT_FOUND' !== err.code) {
 | 
						if ('MODULE_NOT_FOUND' !== err.code) {
 | 
				
			||||||
		console.error(err);
 | 
							console.error(err);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										155
									
								
								utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								utils.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,155 @@
 | 
				
			|||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var U = module.exports;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Keypairs = require('@root/keypairs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Handle nonce, signing, and request altogether
 | 
				
			||||||
 | 
					U._jwsRequest = function(me, bigopts) {
 | 
				
			||||||
 | 
						return U._getNonce(me).then(function(nonce) {
 | 
				
			||||||
 | 
							bigopts.protected.nonce = nonce;
 | 
				
			||||||
 | 
							bigopts.protected.url = bigopts.url;
 | 
				
			||||||
 | 
							// protected.alg: added by Keypairs.signJws
 | 
				
			||||||
 | 
							if (!bigopts.protected.jwk) {
 | 
				
			||||||
 | 
								// protected.kid must be overwritten due to ACME's interpretation of the spec
 | 
				
			||||||
 | 
								if (!bigopts.protected.kid) {
 | 
				
			||||||
 | 
									bigopts.protected.kid = bigopts.options._kid;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// this will shasum the thumbprint the 2nd time
 | 
				
			||||||
 | 
							return Keypairs.signJws({
 | 
				
			||||||
 | 
								jwk:
 | 
				
			||||||
 | 
									bigopts.options.accountKey ||
 | 
				
			||||||
 | 
									bigopts.options.accountKeypair.privateKeyJwk,
 | 
				
			||||||
 | 
								protected: bigopts.protected,
 | 
				
			||||||
 | 
								payload: bigopts.payload
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
								.then(function(jws) {
 | 
				
			||||||
 | 
									//#console.debug('[ACME.js] url: ' + bigopts.url + ':');
 | 
				
			||||||
 | 
									//#console.debug(jws);
 | 
				
			||||||
 | 
									return U._request(me, { url: bigopts.url, json: jws });
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								.catch(function(e) {
 | 
				
			||||||
 | 
									if (/badNonce$/.test(e.urn)) {
 | 
				
			||||||
 | 
										// retry badNonces
 | 
				
			||||||
 | 
										var retryable = bigopts._retries >= 2;
 | 
				
			||||||
 | 
										if (!retryable) {
 | 
				
			||||||
 | 
											bigopts._retries = (bigopts._retries || 0) + 1;
 | 
				
			||||||
 | 
											return U._jwsRequest(me, bigopts);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									throw e;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					U._getNonce = function(me) {
 | 
				
			||||||
 | 
						var nonce;
 | 
				
			||||||
 | 
						while (true) {
 | 
				
			||||||
 | 
							nonce = me._nonces.shift();
 | 
				
			||||||
 | 
							if (!nonce) {
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (Date.now() - nonce.createdAt > 15 * 60 * 1000) {
 | 
				
			||||||
 | 
								nonce = null;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (nonce) {
 | 
				
			||||||
 | 
							return Promise.resolve(nonce.nonce);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// HEAD-as-HEAD ok
 | 
				
			||||||
 | 
						return U._request(me, {
 | 
				
			||||||
 | 
							method: 'HEAD',
 | 
				
			||||||
 | 
							url: me._directoryUrls.newNonce
 | 
				
			||||||
 | 
						}).then(function(resp) {
 | 
				
			||||||
 | 
							return resp.headers['replay-nonce'];
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Handle some ACME-specific defaults
 | 
				
			||||||
 | 
					U._request = function(me, opts) {
 | 
				
			||||||
 | 
						if (!opts.headers) {
 | 
				
			||||||
 | 
							opts.headers = {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (opts.json && true !== opts.json) {
 | 
				
			||||||
 | 
							opts.headers['Content-Type'] = 'application/jose+json';
 | 
				
			||||||
 | 
							opts.body = JSON.stringify(opts.json);
 | 
				
			||||||
 | 
							if (!opts.method) {
 | 
				
			||||||
 | 
								opts.method = 'POST';
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return me.request(opts).then(function(resp) {
 | 
				
			||||||
 | 
							if (resp.toJSON) {
 | 
				
			||||||
 | 
								resp = resp.toJSON();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (resp.headers['replay-nonce']) {
 | 
				
			||||||
 | 
								U._setNonce(me, resp.headers['replay-nonce']);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var e;
 | 
				
			||||||
 | 
							var err;
 | 
				
			||||||
 | 
							if (resp.body) {
 | 
				
			||||||
 | 
								err = resp.body.error;
 | 
				
			||||||
 | 
								e = new Error('');
 | 
				
			||||||
 | 
								if (400 === resp.body.status) {
 | 
				
			||||||
 | 
									err = { type: resp.body.type, detail: resp.body.detail };
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if (err) {
 | 
				
			||||||
 | 
									e.status = resp.body.status;
 | 
				
			||||||
 | 
									e.code = 'E_ACME';
 | 
				
			||||||
 | 
									if (e.status) {
 | 
				
			||||||
 | 
										e.message = '[' + e.status + '] ';
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									e.detail = err.detail;
 | 
				
			||||||
 | 
									e.message += err.detail || JSON.stringify(err);
 | 
				
			||||||
 | 
									e.urn = err.type;
 | 
				
			||||||
 | 
									e.uri = resp.body.url;
 | 
				
			||||||
 | 
									e._rawError = err;
 | 
				
			||||||
 | 
									e._rawBody = resp.body;
 | 
				
			||||||
 | 
									throw e;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return resp;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					U._setNonce = function(me, nonce) {
 | 
				
			||||||
 | 
						me._nonces.unshift({ nonce: nonce, createdAt: Date.now() });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					U._importKeypair = function(me, kp) {
 | 
				
			||||||
 | 
						var jwk = kp.privateKeyJwk;
 | 
				
			||||||
 | 
						if (kp.kty) {
 | 
				
			||||||
 | 
							jwk = kp;
 | 
				
			||||||
 | 
							kp = {};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var pub;
 | 
				
			||||||
 | 
						var p;
 | 
				
			||||||
 | 
						if (jwk) {
 | 
				
			||||||
 | 
							// nix the browser jwk extras
 | 
				
			||||||
 | 
							jwk.key_ops = undefined;
 | 
				
			||||||
 | 
							jwk.ext = undefined;
 | 
				
			||||||
 | 
							pub = Keypairs.neuter({ jwk: jwk });
 | 
				
			||||||
 | 
							p = Promise.resolve({
 | 
				
			||||||
 | 
								private: jwk,
 | 
				
			||||||
 | 
								public: pub
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							p = Keypairs.import({ pem: kp.privateKeyPem });
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return p.then(function(pair) {
 | 
				
			||||||
 | 
							kp.privateKeyJwk = pair.private;
 | 
				
			||||||
 | 
							kp.publicKeyJwk = pair.public;
 | 
				
			||||||
 | 
							if (pair.public.kid) {
 | 
				
			||||||
 | 
								pair = JSON.parse(JSON.stringify(pair));
 | 
				
			||||||
 | 
								delete pair.public.kid;
 | 
				
			||||||
 | 
								delete pair.private.kid;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return pair;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user