diff --git a/js/app.js b/js/app.js index 18daa54..75c8e08 100644 --- a/js/app.js +++ b/js/app.js @@ -19,8 +19,9 @@ } function submitForm(ev) { - steps[i].submit(ev); + var j = i; i += 1; + steps[j].submit(ev); } $qsa('.js-acme-form').forEach(function ($el) { $el.addEventListener('submit', function (ev) { @@ -51,7 +52,12 @@ }; steps[1].submit = function () { info.identifiers = $qs('.js-acme-domains').value.split(/\s*,\s*/g).map(function (hostname) { - return { type: 'dns', value: hostname.trim() }; + return { type: 'dns', value: hostname.toLowerCase().trim() }; + }); + info.identifiers.sort(function (a, b) { + if (a === b) { return 0; } + if (a < b) { return 1; } + if (a > b) { return -1; } }); return BACME.directory($qs('.js-acme-directory-url').value).then(function (directory) { @@ -59,6 +65,7 @@ return BACME.nonce().then(function (_nonce) { nonce = _nonce; + console.log("MAGIC STEP NUMBER in 1 is:", i); steps[i](); }); }); @@ -147,6 +154,7 @@ signedOrder: signedOrder }).then(function (order) { info.finalizeUrl = order.finalize; + info.orderUrl = order.url; // from header Location ??? return BACME.thumbprint({ jwk: jwk }).then(function (thumbprint) { return BACME.challenges.all().then(function (claims) { console.log('claims:'); @@ -176,7 +184,7 @@ , challengeDomain: hostname }); return BACME.challenges['dns-01']({ - keyAuth: keyAuth + keyAuth: keyAuth.value , challengeDomain: hostname }).then(function (dnsAuth) { var data = { @@ -230,6 +238,7 @@ updateChallengeType(); + console.log("MAGIC STEP NUMBER in 2 is:", i); steps[i](); }); @@ -249,23 +258,44 @@ $qs('.js-acme-form-challenges').hidden = false; }; steps[3].submit = function () { - var chType = $qs('.js-acme-challenge-type').value; - var ps = []; + // for now just show the next page immediately (its a spinner) + console.log("MAGIC STEP NUMBER is:", i); + + var chType; + Array.prototype.some.call($qsa('.js-acme-challenge-type'), function ($el) { + if ($el.checked) { + chType = $el.value; + return true; + } + }); + console.log('chType is:', chType); + var chs = []; // do each wildcard, if any // do each challenge, by selected type only [ 'wildcard', chType].forEach(function (typ) { info.challenges[typ].forEach(function (ch) { // { jwk, challengeUrl, accountId (kid) } - ps.push(BACME.challenges.accept({ + chs.push({ jwk: info.jwk , challengeUrl: ch.url , accountId: info.kid - })); + }); }); }); - return Promise.all(ps).then(function (results) { + var results = []; + function nextChallenge() { + var ch = chs.pop(); + if (!ch) { return results; } + return BACME.challenges.accept(ch).then(function (result) { + results.push(result); + return nextChallenge(); + }); + } + + steps[i](); + return nextChallenge().then(function (results) { console.log('challenge status:', results); var polls = results.slice(0); var allsWell = true; @@ -276,7 +306,9 @@ }).then(function () { return Promise.all(polls.map(function (poll) { return BACME.challenges.check({ challengePollUrl: poll.url }); - })).then(function () { + })).then(function (polls) { + console.log(polls); + polls = polls.filter(function (poll) { //return 'valid' !== poll.status && 'invalid' !== poll.status; if ('pending' === poll.status) { @@ -312,7 +344,63 @@ } steps[4].submit = function () { console.log('Congrats! Auto advancing...'); - return BACME.order + var key = info.identifiers.map(function (ident) { return ident.value; }).join(','); + var serverJwk = JSON.parse(localStorage.getItem('server:' + key) || 'null'); + var p; + + function createKeypair() { + return BACME.accounts.generateKeypair({ + type: 'ECDSA' + , bitlength: '256' + }).then(function (serverJwk) { + localStorage.setItem('server:' + key, JSON.stringify(serverJwk)); + return serverJwk; + }) + } + + if (serverJwk) { + p = Promise.resolve(serverJwk); + } else { + p = createKeypair(); + } + + return p.then(function (_serverJwk) { + serverJwk = _serverJwk; + // { serverJwk, domains } + return BACME.orders.generateCsr({ + serverJwk: serverJwk + , domains: info.identifiers.map(function (ident) { + return ident.value; + }) + }).then(function (csrweb64) { + return BACME.order.finalize({ + csr: csrweb64 + , jwk: info.jwk + , finalizeUrl: info.finalizeUrl + , accountId: info.kid + }); + }).then(function () { + function checkCert() { + return new Promise(function (resolve) { + setTimeout(resolve, 1000); + }).then(function () { + return BACME.order.check({ orderUrl: info.orderUrl }); + }).then(function (reply) { + if ('processing' === reply) { + return checkCert(); + } + return reply; + }); + } + + return checkCert(); + }).then(function (reply) { + return BACME.order.receive({ certificateUrl: reply.certificate }); + }).then(function (certs) { + console.log('WINNING!'); + console.log(certs); + }); + }); }; steps[5] = function () { diff --git a/js/bacme.js b/js/bacme.js index 18b31ef..3eceed1 100644 --- a/js/bacme.js +++ b/js/bacme.js @@ -102,6 +102,8 @@ BACME.accounts.generateKeypair = function (opts) { console.log('private jwk:'); console.log(JSON.stringify(privJwk, null, 2)); + return privJwk; + /* return webCrypto.subtle.exportKey( "pkcs8" , result.privateKey @@ -112,6 +114,7 @@ BACME.accounts.generateKeypair = function (opts) { return privJwk; //return accountKeypair; }); + */ }) }); }; @@ -335,6 +338,7 @@ BACME.orders.create = function (opts) { finalizeUrl = result.finalize; BACME._logBody(result); + result.url = currentOrderUrl; return result; }); }); @@ -404,9 +408,9 @@ BACME.thumbprint = function (opts) { var keys; if (/^EC/i.test(opts.jwk.kty)) { - keys = [ 'e', 'kty', 'n' ]; - } else if (/^RS/i.test(opts.jwk.kty)) { keys = [ 'crv', 'kty', 'x', 'y' ]; + } else if (/^RS/i.test(opts.jwk.kty)) { + keys = [ 'e', 'kty', 'n' ]; } var accountPublicStr = '{' + keys.map(function (key) { @@ -416,12 +420,14 @@ BACME.thumbprint = function (opts) { return window.crypto.subtle.digest( { name: "SHA-256" } // SHA-256 is spec'd, non-optional , textEncoder.encode(accountPublicStr) - ).then(function(hash){ + ).then(function (hash) { thumbprint = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) { return String.fromCharCode(ch); }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); console.log('Thumbprint:'); + console.log(opts); + console.log(accountPublicStr); console.log(thumbprint); return thumbprint; @@ -446,10 +452,12 @@ BACME.challenges['http-01'] = function (opts) { // { keyAuth } BACME.challenges['dns-01'] = function (opts) { + console.log('opts.keyAuth for DNS:'); + console.log(opts.keyAuth); return window.crypto.subtle.digest( { name: "SHA-256", } , textEncoder.encode(opts.keyAuth) - ).then(function(hash){ + ).then(function (hash) { dnsAuth = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) { return String.fromCharCode(ch); }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); @@ -478,8 +486,7 @@ BACME.challenges.accept = function (opts) { {} ); - nonce = null; - return BACME._import(opts.jwk).then(function (abstractKey) { + return BACME._importKey(opts.jwk).then(function (abstractKey) { var protected64 = BACME._jsto64( { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.challengeUrl, kid: opts.accountId } ); @@ -490,6 +497,7 @@ BACME.challenges.accept = function (opts) { }); }).then(function (signedAccept) { + nonce = null; return window.fetch( opts.challengeUrl , { mode: 'cors' @@ -555,8 +563,11 @@ BACME.domains.generateKeypair = function () { }); }; -BACME.orders.generateCsr = function (keypair, domains) { - return Promise.resolve(CSR.generate(keypair, domains)); +// { serverJwk, domains } +BACME.orders.generateCsr = function (opts) { + return BACME._importKey(opts.serverJwk).then(function (abstractKey) { + return Promise.resolve(CSR.generate({ keypair: abstractKey.wcKey, domains: opts.domains })); + }); }; var certificateUrl; @@ -567,8 +578,7 @@ BACME.orders.finalize = function (opts) { { csr: opts.csr } ); - nonce = null; - return BACME._import(opts.jwk).then(function (abstractKey) { + return BACME._importKey(opts.jwk).then(function (abstractKey) { var protected64 = BACME._jsto64( { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.finalizeUrl, kid: opts.accountId } ); @@ -579,8 +589,9 @@ BACME.orders.finalize = function (opts) { }); }).then(function (signedFinal) { + nonce = null; return window.fetch( - finalizeUrl + opts.finalizeUrl , { mode: 'cors' , method: 'POST' , headers: { 'Content-Type': 'application/jose+json' } @@ -600,4 +611,39 @@ BACME.orders.finalize = function (opts) { }); }; +BACME.orders.receive = function (opts) { + return window.fetch( + opts.certificateUrl + , { mode: 'cors' + , method: 'GET' + } + ).then(function (resp) { + BACME._logHeaders(resp); + nonce = resp.headers.get('replay-nonce'); + + return resp.json().then(function (reply) { + BACME._logBody(reply); + + return reply; + }); + }); +}; + +BACME.orders.check = function (opts) { + return window.fetch( + opts.orderUrl + , { mode: 'cors' + , method: 'GET' + } + ).then(function (resp) { + BACME._logHeaders(resp); + + return resp.json().then(function (reply) { + BACME._logBody(reply); + + return reply; + }); + }); +}; + }(window));