From 510a36713530124508a6174271d9f74937bae39e Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 4 May 2018 11:10:43 +0000 Subject: [PATCH] appears to work --- index.html | 13 +++++---- install.sh | 2 +- js/app.js | 81 +++++++++++++++++++++++++++++++++++++++++++++++------ js/bacme.js | 67 +++++++++++++++++++++++++++++++++----------- 4 files changed, 132 insertions(+), 31 deletions(-) diff --git a/index.html b/index.html index 92a4bbb..d4b51bf 100644 --- a/index.html +++ b/index.html @@ -120,19 +120,22 @@ - +
- + - + + diff --git a/install.sh b/install.sh index e674dc5..968f06a 100644 --- a/install.sh +++ b/install.sh @@ -10,5 +10,5 @@ popd mkdir -p js/browser-csr/v1.0.0-alpha/ pushd js/browser-csr/v1.0.0-alpha/ - wget -c https://git.coolaj86.com/coolaj86/browser-csr.js/raw/commit/c513a862a4e016794da800f0c2eec858b80837ab/csr.js + wget -c https://git.coolaj86.com/coolaj86/browser-csr.js/raw/commit/01cdc0e91b5bf03f12e1b25b4129e3cde927987c/csr.js popd diff --git a/js/app.js b/js/app.js index 75c8e08..c1308d9 100644 --- a/js/app.js +++ b/js/app.js @@ -30,7 +30,9 @@ }); }); function updateChallengeType() { - var input = this || $qs('.js-acme-challenge-type'); + var input = this || Array.prototype.filter.call( + $qsa('.js-acme-challenge-type'), function ($el) { return $el.checked; } + )[0]; console.log('ch type radio:', input.value); $qs('.js-acme-table-wildcard').hidden = true; $qs('.js-acme-table-http-01').hidden = true; @@ -200,7 +202,6 @@ , dnsAnswer: dnsAuth.answer }; - obj[c.type].push(data); console.log(''); console.log('CHALLENGE'); console.log(claim); @@ -258,9 +259,6 @@ $qs('.js-acme-form-challenges').hidden = false; }; steps[3].submit = function () { - // 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) { @@ -283,6 +281,7 @@ }); }); }); + console.log("INFO.challenges !!!!!", info.challenges); var results = []; function nextChallenge() { @@ -294,6 +293,7 @@ }); } + // for now just show the next page immediately (its a spinner) steps[i](); return nextChallenge().then(function (results) { console.log('challenge status:', results); @@ -366,6 +366,7 @@ return p.then(function (_serverJwk) { serverJwk = _serverJwk; + info.serverJwk = serverJwk; // { serverJwk, domains } return BACME.orders.generateCsr({ serverJwk: serverJwk @@ -373,7 +374,7 @@ return ident.value; }) }).then(function (csrweb64) { - return BACME.order.finalize({ + return BACME.orders.finalize({ csr: csrweb64 , jwk: info.jwk , finalizeUrl: info.finalizeUrl @@ -384,7 +385,7 @@ return new Promise(function (resolve) { setTimeout(resolve, 1000); }).then(function () { - return BACME.order.check({ orderUrl: info.orderUrl }); + return BACME.orders.check({ orderUrl: info.orderUrl }); }).then(function (reply) { if ('processing' === reply) { return checkCert(); @@ -395,10 +396,74 @@ return checkCert(); }).then(function (reply) { - return BACME.order.receive({ certificateUrl: reply.certificate }); + return BACME.orders.receive({ certificateUrl: reply.certificate }); }).then(function (certs) { console.log('WINNING!'); console.log(certs); + $qs('.js-fullchain').value = certs; + + // https://stackoverflow.com/questions/40314257/export-webcrypto-key-to-pem-format + function spkiToPEM(keydata){ + var keydataS = arrayBufferToString(keydata); + var keydataB64 = window.btoa(keydataS); + var keydataB64Pem = formatAsPem(keydataB64); + return keydataB64Pem; + } + + function arrayBufferToString( buffer ) { + var binary = ''; + var bytes = new Uint8Array( buffer ); + var len = bytes.byteLength; + for (var i = 0; i < len; i++) { + binary += String.fromCharCode( bytes[ i ] ); + } + return binary; + } + + + function formatAsPem(str) { + var finalString = '-----BEGIN ' + pemName + ' PRIVATE KEY-----\n'; + + while(str.length > 0) { + finalString += str.substring(0, 64) + '\n'; + str = str.substring(64); + } + + finalString = finalString + '-----END ' + pemName + ' PRIVATE KEY-----'; + + return finalString; + } + + var wcOpts; + var pemName; + if (/^R/.test(info.serverJwk.kty)) { + pemName = 'RSA'; + wcOpts = { + name: "RSASSA-PKCS1-v1_5" + , hash: { name: "SHA-256" } + }; + } else { + pemName = 'EC'; + wcOpts = { + name: "ECDSA" + , namedCurve: "P-256" + } + } + return crypto.subtle.importKey( + "jwk" + , info.serverJwk + , wcOpts + , true + , ["sign"] + ).then(function (privateKey) { + return window.crypto.subtle.exportKey("pkcs8", privateKey); + }).then (function (keydata) { + var pem = spkiToPEM(keydata); + $qs('.js-privkey').value = pem; + steps[i](); + }).catch(function(err){ + console.error(err); + }); }); }); }; diff --git a/js/bacme.js b/js/bacme.js index 3eceed1..5673646 100644 --- a/js/bacme.js +++ b/js/bacme.js @@ -129,13 +129,24 @@ var textEncoder = new TextEncoder(); BACME._importKey = function (jwk) { var alg; // I think the 256 refers to the hash var wcOpts = {}; - var extractable = false; + var extractable = true; // TODO make optionally false? + var priv = jwk; + var pub; // ECDSA if (/^EC/i.test(jwk.kty)) { wcOpts.name = 'ECDSA'; wcOpts.namedCurve = jwk.crv; alg = 'ES256'; + pub = { + crv: priv.crv + , kty: priv.kty + , x: priv.x + , y: priv.y + }; + if (!priv.d) { + priv = null; + } } // RSA @@ -143,28 +154,50 @@ BACME._importKey = function (jwk) { wcOpts.name = 'RSASSA-PKCS1-v1_5'; wcOpts.hash = { name: "SHA-256" }; alg = 'RS256'; + pub = { + e: priv.e + , kty: priv.kty + , n: priv.n + } + if (!priv.p) { + priv = null; + } } return window.crypto.subtle.importKey( "jwk" - , jwk + , pub , wcOpts , extractable - , [ "sign"/*, "verify"*/ ] - ).then(function (keypair) { - return { - wcKey: keypair - , meta: { - alg: alg - , name: wcOpts.name - , hash: wcOpts.hash - } - , jwk: jwk - }; + , [ "verify" ] + ).then(function (publicKey) { + function give(privateKey) { + return { + wcPub: publicKey + , wcKey: privateKey + , wcKeypair: { publicKey: publicKey, privateKey: privateKey } + , meta: { + alg: alg + , name: wcOpts.name + , hash: wcOpts.hash + } + , jwk: jwk + }; + } + if (!priv) { + return give(); + } + return window.crypto.subtle.importKey( + "jwk" + , priv + , wcOpts + , extractable + , [ "sign"/*, "verify"*/ ] + ).then(give); }); }; BACME._sign = function (opts) { - var wcPrivKey = opts.abstractKey.wcKey; + var wcPrivKey = opts.abstractKey.wcKeypair.privateKey; var wcOpts = opts.abstractKey.meta; var alg = opts.abstractKey.meta.alg; // I think the 256 refers to the hash var signHash; @@ -508,6 +541,7 @@ BACME.challenges.accept = function (opts) { ).then(function (resp) { BACME._logHeaders(resp); nonce = resp.headers.get('replay-nonce'); + console.log("ACCEPT NONCE:", nonce); return resp.json().then(function (reply) { challengePollUrl = reply.url; @@ -523,7 +557,6 @@ BACME.challenges.accept = function (opts) { BACME.challenges.check = function (opts) { return window.fetch(opts.challengePollUrl, { mode: 'cors' }).then(function (resp) { BACME._logHeaders(resp); - nonce = resp.headers.get('replay-nonce'); return resp.json().then(function (reply) { challengePollUrl = reply.url; @@ -566,7 +599,7 @@ BACME.domains.generateKeypair = function () { // { 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 })); + return Promise.resolve(CSR.generate({ keypair: abstractKey.wcKeypair, domains: opts.domains })); }); }; @@ -621,7 +654,7 @@ BACME.orders.receive = function (opts) { BACME._logHeaders(resp); nonce = resp.headers.get('replay-nonce'); - return resp.json().then(function (reply) { + return resp.text().then(function (reply) { BACME._logBody(reply); return reply;