WIP get challenges
This commit is contained in:
		
							parent
							
								
									488067ec20
								
							
						
					
					
						commit
						7385dd8580
					
				
							
								
								
									
										52
									
								
								app.js
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								app.js
									
									
									
									
									
								
							@ -1,3 +1,4 @@
 | 
			
		||||
/*global Promise*/
 | 
			
		||||
(function () {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
@ -112,15 +113,56 @@
 | 
			
		||||
      });
 | 
			
		||||
      acme.init('https://acme-staging-v02.api.letsencrypt.org/directory').then(function (result) {
 | 
			
		||||
        console.log('acme result', result);
 | 
			
		||||
        return acme.accounts.create({
 | 
			
		||||
          email: $('.js-email').innerText
 | 
			
		||||
        , agreeToTerms: function (tos) {
 | 
			
		||||
        var privJwk = JSON.parse($('.js-jwk').innerText).private;
 | 
			
		||||
        var email = $('.js-email').innerText;
 | 
			
		||||
        function checkTos(tos) {
 | 
			
		||||
          console.log("TODO checkbox for agree to terms");
 | 
			
		||||
          return tos;
 | 
			
		||||
        }
 | 
			
		||||
        , accountKeypair: {
 | 
			
		||||
            privateKeyJwk: JSON.parse($('.js-jwk').innerText).private
 | 
			
		||||
        return acme.accounts.create({
 | 
			
		||||
          email: email
 | 
			
		||||
        , agreeToTerms: checkTos
 | 
			
		||||
        , accountKeypair: { privateKeyJwk: privJwk }
 | 
			
		||||
        }).then(function (account) {
 | 
			
		||||
          console.log("account created result:", account);
 | 
			
		||||
          return Keypairs.generate({
 | 
			
		||||
            kty: 'RSA'
 | 
			
		||||
          , modulusLength: 2048
 | 
			
		||||
          }).then(function (pair) {
 | 
			
		||||
            console.log('domain keypair:', pair);
 | 
			
		||||
            var domains = ($('.js-domains').innerText||'example.com').split(/[, ]+/g);
 | 
			
		||||
            return acme.certificates.create({
 | 
			
		||||
              accountKeypair: { privateKeyJwk: privJwk }
 | 
			
		||||
            , account: account
 | 
			
		||||
            , domainKeypair: { privateKeyJwk: pair.private }
 | 
			
		||||
            , email: email
 | 
			
		||||
            , domains: domains
 | 
			
		||||
            , agreeToTerms: checkTos
 | 
			
		||||
            , challenges: {
 | 
			
		||||
                'dns-01': {
 | 
			
		||||
                  set: function (opts) {
 | 
			
		||||
                    console.log('dns-01 set challenge:');
 | 
			
		||||
                    console.log(JSON.stringify(opts, null, 2));
 | 
			
		||||
                    return new Promise(function (resolve) {
 | 
			
		||||
                      while (!window.confirm("Did you set the challenge?")) {}
 | 
			
		||||
                      resolve();
 | 
			
		||||
                    });
 | 
			
		||||
                  }
 | 
			
		||||
                , remove: function (opts) {
 | 
			
		||||
                    console.log('dns-01 remove challenge:');
 | 
			
		||||
                    console.log(JSON.stringify(opts, null, 2));
 | 
			
		||||
                    return new Promise(function (resolve) {
 | 
			
		||||
                      while (!window.confirm("Did you delete the challenge?")) {}
 | 
			
		||||
                      resolve();
 | 
			
		||||
                    });
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
          });
 | 
			
		||||
        }).catch(function (err) {
 | 
			
		||||
          console.error("A bad thing happened:");
 | 
			
		||||
          console.error(err);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -63,6 +63,10 @@
 | 
			
		||||
    <form class="js-acme-account">
 | 
			
		||||
      <label for="-acmeEmail">Email:</label>
 | 
			
		||||
      <input class="js-email" type="email" id="-acmeEmail">
 | 
			
		||||
      <br>
 | 
			
		||||
      <label for="-acmeDomains">Domains:</label>
 | 
			
		||||
      <input class="js-domains" type="text" id="-acmeDomains">
 | 
			
		||||
      <br>
 | 
			
		||||
      <button class="js-create-account" hidden>Create Account</button>
 | 
			
		||||
    </form>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										100
									
								
								lib/acme.js
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								lib/acme.js
									
									
									
									
									
								
							@ -7,7 +7,7 @@
 | 
			
		||||
/* globals Promise */
 | 
			
		||||
 | 
			
		||||
var ACME = exports.ACME = {};
 | 
			
		||||
var Keypairs = exports.Keypairs || {};
 | 
			
		||||
//var Keypairs = exports.Keypairs || {};
 | 
			
		||||
var Enc = exports.Enc || {};
 | 
			
		||||
var Crypto = exports.Crypto || {};
 | 
			
		||||
 | 
			
		||||
@ -90,7 +90,7 @@ ACME._getNonce = function (me) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (nonce) { return Promise.resolve(nonce); }
 | 
			
		||||
  if (nonce) { return Promise.resolve(nonce.nonce); }
 | 
			
		||||
  return me.request({ method: 'HEAD', url: me._directoryUrls.newNonce }).then(function (resp) {
 | 
			
		||||
    return resp.headers['replay-nonce'];
 | 
			
		||||
  });
 | 
			
		||||
@ -132,26 +132,7 @@ ACME._registerAccount = function (me, options) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      var jwk = options.accountKeypair.privateKeyJwk;
 | 
			
		||||
      var p;
 | 
			
		||||
      if (jwk) {
 | 
			
		||||
        // nix the browser jwk extras
 | 
			
		||||
        jwk.key_ops = undefined;
 | 
			
		||||
        jwk.ext = undefined;
 | 
			
		||||
        p = Promise.resolve({ private: jwk, public: Keypairs.neuter({ jwk: jwk }) });
 | 
			
		||||
      } else {
 | 
			
		||||
        p = Keypairs.import({ pem: options.accountKeypair.privateKeyPem });
 | 
			
		||||
      }
 | 
			
		||||
      return p.then(function (pair) {
 | 
			
		||||
        options.accountKeypair.privateKeyJwk = pair.private;
 | 
			
		||||
        options.accountKeypair.publicKeyJwk = pair.public;
 | 
			
		||||
        if (pair.public.kid) {
 | 
			
		||||
          pair = JSON.parse(JSON.stringify(pair));
 | 
			
		||||
          delete pair.public.kid;
 | 
			
		||||
          delete pair.private.kid;
 | 
			
		||||
        }
 | 
			
		||||
        return pair;
 | 
			
		||||
      }).then(function (pair) {
 | 
			
		||||
      return ACME._importKeypair(me, options.accountKeypair).then(function (pair) {
 | 
			
		||||
        var contact;
 | 
			
		||||
        if (options.contact) {
 | 
			
		||||
          contact = options.contact.slice(0);
 | 
			
		||||
@ -209,7 +190,7 @@ ACME._registerAccount = function (me, options) {
 | 
			
		||||
              status: 'valid'
 | 
			
		||||
            }
 | 
			
		||||
            */
 | 
			
		||||
            if (!account) { account = { _emptyResponse: true, key: {} }; }
 | 
			
		||||
            if (!account) { account = { _emptyResponse: true }; }
 | 
			
		||||
            // https://git.coolaj86.com/coolaj86/acme-v2.js/issues/8
 | 
			
		||||
            if (!account.key) { account.key = {}; }
 | 
			
		||||
            account.key.kid = options._kid;
 | 
			
		||||
@ -346,11 +327,12 @@ ACME._testChallenges = function (me, options) {
 | 
			
		||||
      , wildcard: identifierValue.includes('*.') || undefined
 | 
			
		||||
      };
 | 
			
		||||
      var dryrun = true;
 | 
			
		||||
      var auth = ACME._challengeToAuth(me, options, results, challenge, dryrun);
 | 
			
		||||
      return ACME._challengeToAuth(me, options, results, challenge, dryrun).then(function (auth) {
 | 
			
		||||
        return ACME._setChallenge(me, options, auth).then(function () {
 | 
			
		||||
          return auth;
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  })).then(function (auths) {
 | 
			
		||||
    return ACME._wait(CHECK_DELAY).then(function () {
 | 
			
		||||
      return Promise.all(auths.map(function (auth) {
 | 
			
		||||
@ -402,7 +384,8 @@ ACME._challengeToAuth = function (me, options, request, challenge, dryrun) {
 | 
			
		||||
  auth.hostname = auth.identifier.value;
 | 
			
		||||
  // because I'm not 100% clear if the wildcard identifier does or doesn't have the leading *. in all cases
 | 
			
		||||
  auth.altname = ACME._untame(auth.identifier.value, auth.wildcard);
 | 
			
		||||
  return me.Keypairs.thumbprint({ jwk: options.accountKeypair.publicKeyJwk }).then(function (thumb) {
 | 
			
		||||
  return ACME._importKeypair(me, options.accountKeypair).then(function (pair) {
 | 
			
		||||
    return me.Keypairs.thumbprint({ jwk: pair.public }).then(function (thumb) {
 | 
			
		||||
      auth.thumbprint = thumb;
 | 
			
		||||
      //   keyAuthorization = token || '.' || base64url(JWK_Thumbprint(accountKey))
 | 
			
		||||
      auth.keyAuthorization = challenge.token + '.' + auth.thumbprint;
 | 
			
		||||
@ -415,6 +398,7 @@ ACME._challengeToAuth = function (me, options, request, challenge, dryrun) {
 | 
			
		||||
        return auth;
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ACME._untame = function (name, wild) {
 | 
			
		||||
@ -542,15 +526,20 @@ ACME._postChallenge = function (me, options, auth) {
 | 
			
		||||
  return respondToChallenge();
 | 
			
		||||
};
 | 
			
		||||
ACME._setChallenge = function (me, options, auth) {
 | 
			
		||||
  console.log('challenge auth:', auth);
 | 
			
		||||
  console.log('challenges:', options.challenges);
 | 
			
		||||
  return new Promise(function (resolve, reject) {
 | 
			
		||||
    var challengers = options.challenges || {};
 | 
			
		||||
    var challenger = (challengers[auth.type] && challengers[auth.type].set) || options.setChallenge;
 | 
			
		||||
    try {
 | 
			
		||||
      if (1 === options.setChallenge.length) {
 | 
			
		||||
        options.setChallenge(auth).then(resolve).catch(reject);
 | 
			
		||||
      } else if (2 === options.setChallenge.length) {
 | 
			
		||||
        options.setChallenge(auth, function (err) {
 | 
			
		||||
      if (1 === challenger.length) {
 | 
			
		||||
        challenger(auth).then(resolve).catch(reject);
 | 
			
		||||
      } else if (2 === challenger.length) {
 | 
			
		||||
        challenger(auth, function (err) {
 | 
			
		||||
          if(err) { reject(err); } else { resolve(); }
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        // TODO remove this old backwards-compat
 | 
			
		||||
        var challengeCb = function(err) {
 | 
			
		||||
          if(err) { reject(err); } else { resolve(); }
 | 
			
		||||
        };
 | 
			
		||||
@ -563,7 +552,7 @@ ACME._setChallenge = function (me, options, auth) {
 | 
			
		||||
          console.warn("The API has been changed for compatibility with all ACME / Let's Encrypt challenge types.");
 | 
			
		||||
          ACME._setChallengeWarn = true;
 | 
			
		||||
        }
 | 
			
		||||
        options.setChallenge(auth.identifier.value, auth.token, auth.keyAuthorization, challengeCb);
 | 
			
		||||
        challenger(auth.identifier.value, auth.token, auth.keyAuthorization, challengeCb);
 | 
			
		||||
      }
 | 
			
		||||
    } catch(e) {
 | 
			
		||||
      reject(e);
 | 
			
		||||
@ -577,7 +566,7 @@ ACME._setChallenge = function (me, options, auth) {
 | 
			
		||||
};
 | 
			
		||||
ACME._finalizeOrder = function (me, options, validatedDomains) {
 | 
			
		||||
  if (me.debug) { console.debug('finalizeOrder:'); }
 | 
			
		||||
  var csr = me.Keypairs.generateCsrWeb64(options.domainKeypair, validatedDomains);
 | 
			
		||||
  return ACME._generateCsrWeb64(me, options, validatedDomains).then(function (csr) {
 | 
			
		||||
    var body = { csr: csr };
 | 
			
		||||
    var payload = JSON.stringify(body);
 | 
			
		||||
 | 
			
		||||
@ -652,6 +641,7 @@ ACME._finalizeOrder = function (me, options, validatedDomains) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return pollCert();
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
// _kid
 | 
			
		||||
// registerAccount
 | 
			
		||||
@ -686,16 +676,18 @@ ACME._getCertificate = function (me, options) {
 | 
			
		||||
  }
 | 
			
		||||
  if (!(options.domains && options.domains.length)) {
 | 
			
		||||
    return Promise.reject(new Error("options.domains must be a list of string domain names,"
 | 
			
		||||
    + " with the first being the subject of the domain (or options.subject must specified)."));
 | 
			
		||||
    + " with the first being the subject of the certificate (or options.subject must specified)."));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // It's just fine if there's no account, we'll go get the key id we need via the public key
 | 
			
		||||
  if (options.accountKid || options.account && options.account.kid) {
 | 
			
		||||
    options._kid = options.accountKid || options.account.kid;
 | 
			
		||||
  } else {
 | 
			
		||||
  // 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.reject(new Error("must include KeyID"));
 | 
			
		||||
    // This is an idempotent request. It'll return the same account for the same public key.
 | 
			
		||||
    return ACME._registerAccount(me, options).then(function () {
 | 
			
		||||
    return ACME._registerAccount(me, options).then(function (account) {
 | 
			
		||||
      options._kid = account.key.kid;
 | 
			
		||||
      // start back from the top
 | 
			
		||||
      return ACME._getCertificate(me, options);
 | 
			
		||||
    });
 | 
			
		||||
@ -720,9 +712,6 @@ ACME._getCertificate = function (me, options) {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    var payload = JSON.stringify(body);
 | 
			
		||||
    // determine the signing algorithm to use in protected header // TODO isn't that handled by the signer?
 | 
			
		||||
    options._kty = (options.accountKeypair.privateKeyJwk && options.accountKeypair.privateKeyJwk.kty || 'RSA');
 | 
			
		||||
    options._alg = ('EC' === options._kty) ? 'ES256' : 'RS256'; // TODO vary with bitwidth of key (if not handled)
 | 
			
		||||
    if (me.debug) { console.debug('\n[DEBUG] newOrder\n'); }
 | 
			
		||||
    return ACME._jwsRequest({
 | 
			
		||||
      options: options
 | 
			
		||||
@ -815,6 +804,13 @@ ACME._getCertificate = function (me, options) {
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
ACME._generateCsrWeb64 = function (me, options, validatedDomains) {
 | 
			
		||||
  return ACME._importKeypair(me, options.domainKeypair).then(function (/*pair*/) {
 | 
			
		||||
    return me.Keypairs.generateCsr(options.domainKeypair, validatedDomains).then(function (der) {
 | 
			
		||||
      return Enc.bufToUrlBase64(der);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ACME.create = function create(me) {
 | 
			
		||||
  if (!me) { me = {}; }
 | 
			
		||||
@ -942,6 +938,30 @@ ACME._defaultRequest = function (opts) {
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ACME._importKeypair = function (me, kp) {
 | 
			
		||||
  var jwk = kp.privateKeyJwk;
 | 
			
		||||
  var p;
 | 
			
		||||
  if (jwk) {
 | 
			
		||||
    // nix the browser jwk extras
 | 
			
		||||
    jwk.key_ops = undefined;
 | 
			
		||||
    jwk.ext = undefined;
 | 
			
		||||
    p = Promise.resolve({ private: jwk, public: me.Keypairs.neuter({ jwk: jwk }) });
 | 
			
		||||
  } else {
 | 
			
		||||
    p = me.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;
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
TODO
 | 
			
		||||
Per-Order State Params
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user