tested working in firefox

This commit is contained in:
AJ ONeal 2018-11-10 09:18:59 +00:00
parent 09ff0b3adc
commit 268f83b49e
3 changed files with 136 additions and 73 deletions

View File

@ -207,6 +207,7 @@
<div id="js-acme-ver-uri" class="acme-ver-uri">..loading</div> <div id="js-acme-ver-uri" class="acme-ver-uri">..loading</div>
</div> </div>
<div class="js-acme-verification-dns-01"> <div class="js-acme-verification-dns-01">
<h3>Set this DNS Record</h3>
<div class="acme-ver-dns-label">Hostname</div> <div class="acme-ver-dns-label">Hostname</div>
<div id="js-acme-ver-hostname">loading...</div> <div id="js-acme-ver-hostname">loading...</div>
<div class="acme-ver-dns-label">TXT Host</div> <div class="acme-ver-dns-label">TXT Host</div>

View File

@ -2,7 +2,7 @@
'use strict'; 'use strict';
/*global URLSearchParams,Headers*/ /*global URLSearchParams,Headers*/
var BROWSER_SUPPORTS_ECDSA = navigator.userAgent.toLowerCase().indexOf('firefox') === -1; var BROWSER_SUPPORTS_ECDSA;
var $qs = function (s) { return window.document.querySelector(s); }; var $qs = function (s) { return window.document.querySelector(s); };
var $qsa = function (s) { return window.document.querySelectorAll(s); }; var $qsa = function (s) { return window.document.querySelectorAll(s); };
var info = {}; var info = {};
@ -31,16 +31,50 @@
}); });
}); });
} }
function testRsaSupport() {
var opts = {
type: 'RSA'
, bitlength: '2048'
};
return BACME.accounts.generateKeypair(opts).then(function (jwk) {
return crypto.subtle.importKey(
"jwk"
, jwk
, { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } }
, true
, ["sign"]
).then(function (privateKey) {
return window.crypto.subtle.exportKey("pkcs8", privateKey);
});
});
}
testEcdsaSupport().then(function () { testEcdsaSupport().then(function () {
console.log("supports ECDSA"); console.info("[crypto] ECDSA is supported");
BROWSER_SUPPORTS_ECDSA = true; BROWSER_SUPPORTS_ECDSA = true;
localStorage.setItem('version', '1');
return true; return true;
}).catch(function () { }).catch(function () {
console.log("DOES NOT supports ECDSA"); console.warn("[crypto] ECDSA is NOT fully supported");
BROWSER_SUPPORTS_ECDSA = false; BROWSER_SUPPORTS_ECDSA = false;
// fix previous firefox browsers
if (!localStorage.getItem('version')) {
localStorage.clear();
localStorage.getItem('version', '1');
}
// DO NOT RETURN HERE
testRsaSupport().then(function () {
console.info('[crypto] RSA is supported');
}).catch(function (err) {
console.error('[crypto] could not use either EC nor RSA.');
console.error(err);
window.alert("Your browser is cryptography support (neither RSA or EC is usable). Please use Chrome, Firefox, or Safari.");
});
// RETURN HERE
return false; return false;
}); });
// TODO test RSA support
var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory'; var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory';
function updateApiType() { function updateApiType() {
@ -83,7 +117,10 @@
var j = i; var j = i;
i += 1; i += 1;
steps[j].submit(ev); return PromiseA.resolve(steps[j].submit(ev)).catch(function (err) {
console.error(err);
window.alert.error("Something went wrong. It's our fault not yours. Please email aj@greenlock.domains and let him know that 'step " + j + "' failed.");
});
} }
$qsa('.js-acme-form').forEach(function ($el) { $qsa('.js-acme-form').forEach(function ($el) {
@ -355,8 +392,9 @@
}); });
}); });
}).catch(function (err) { }).catch(function (err) {
console.error('Step \'' + i + '\' Error:'); console.error('Step \'\' Error:');
console.error(err, err.stack); console.error(err, err.stack);
window.alert("An error happened at Step " + i + ", but it's not your fault. Email aj@greenlock.domains and let him know.");
}); });
}; };
@ -421,9 +459,22 @@
if ('pending' === poll.status) { if ('pending' === poll.status) {
return true; return true;
} }
if ('invalid' === poll.status) {
allsWell = false;
window.alert("verification failed:" + poll.error.detail);
return;
}
if (poll.error) {
window.alert("verification failed:" + poll.error.detail);
return;
}
if ('valid' !== poll.status) { if ('valid' !== poll.status) {
allsWell = false; allsWell = false;
console.warn('BAD POLL STATUS', poll); console.warn('BAD POLL STATUS', poll);
window.alert("unknown error: " + JSON.stringify(poll, null, 2));
} }
// TODO show status in HTML // TODO show status in HTML
}); });
@ -444,6 +495,38 @@
}); });
}; };
// https://stackoverflow.com/questions/40314257/export-webcrypto-key-to-pem-format
function spkiToPEM(keydata, pemName){
var keydataS = arrayBufferToString(keydata);
var keydataB64 = window.btoa(keydataS);
var keydataB64Pem = formatAsPem(keydataB64, pemName);
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, pemName) {
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;
}
// spinner // spinner
steps[4] = function () { steps[4] = function () {
updateProgress(1); updateProgress(1);
@ -460,16 +543,10 @@
function createKeypair() { function createKeypair() {
var opts; var opts;
if(BROWSER_SUPPORTS_ECDSA) { if (BROWSER_SUPPORTS_ECDSA) {
opts = { opts = { type: 'ECDSA', bitlength: '256' };
type: 'ECDSA'
, bitlength: '256'
};
} else { } else {
opts = { opts = { type: 'RSA', bitlength: '2048' };
type: 'RSA'
, bitlength: '2048'
};
} }
return BACME.domains.generateKeypair(opts).then(function (serverJwk) { return BACME.domains.generateKeypair(opts).then(function (serverJwk) {
@ -524,52 +601,14 @@
$qs("#js-download-fullchain-link").href = $qs("#js-download-fullchain-link").href =
"data:text/octet-stream;base64," + window.btoa(certs); "data:text/octet-stream;base64," + window.btoa(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 wcOpts;
var pemName; var pemName;
if (/^R/.test(info.serverJwk.kty)) { if (/^R/.test(info.serverJwk.kty)) {
pemName = 'RSA'; pemName = 'RSA';
wcOpts = { wcOpts = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } };
name: "RSASSA-PKCS1-v1_5"
, hash: { name: "SHA-256" }
};
} else { } else {
pemName = 'EC'; pemName = 'EC';
wcOpts = { wcOpts = { name: "ECDSA", namedCurve: "P-256" };
name: "ECDSA"
, namedCurve: "P-256"
};
} }
return crypto.subtle.importKey( return crypto.subtle.importKey(
"jwk" "jwk"
@ -580,15 +619,16 @@
).then(function (privateKey) { ).then(function (privateKey) {
return window.crypto.subtle.exportKey("pkcs8", privateKey); return window.crypto.subtle.exportKey("pkcs8", privateKey);
}).then (function (keydata) { }).then (function (keydata) {
var pem = spkiToPEM(keydata); var pem = spkiToPEM(keydata, pemName);
$qs('#js-privkey').innerHTML = pem; $qs('#js-privkey').innerHTML = pem;
$qs("#js-download-privkey-link").href = $qs("#js-download-privkey-link").href =
"data:text/octet-stream;base64," + window.btoa(pem); "data:text/octet-stream;base64," + window.btoa(pem);
steps[i](); steps[i]();
}).catch(function(err){
console.error(err.toString());
}); });
}); });
}).catch(function (err) {
console.error(err.toString());
window.alert("An error happened in the final step, but it's not your fault. Email aj@greenlock.domains and let him know.");
}); });
}; };

View File

@ -38,13 +38,16 @@ BACME._logBody = function (body) {
BACME.directory = function (opts) { BACME.directory = function (opts) {
return webFetch(opts.directoryUrl || directoryUrl, { mode: 'cors' }).then(function (resp) { return webFetch(opts.directoryUrl || directoryUrl, { mode: 'cors' }).then(function (resp) {
BACME._logHeaders(resp); BACME._logHeaders(resp);
return resp.json().then(function (body) { return resp.json().then(function (reply) {
directory = body; if (/error/.test(reply.type)) {
return Promise.reject(new Error(reply.detail || reply.type));
}
directory = reply;
nonceUrl = directory.newNonce || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce'; nonceUrl = directory.newNonce || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce';
accountUrl = directory.newAccount || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-account'; accountUrl = directory.newAccount || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-account';
orderUrl = directory.newOrder || "https://acme-staging-v02.api.letsencrypt.org/acme/new-order"; orderUrl = directory.newOrder || "https://acme-staging-v02.api.letsencrypt.org/acme/new-order";
BACME._logBody(body); BACME._logBody(reply);
return body; return reply;
}); });
}); });
}; };
@ -227,18 +230,30 @@ BACME.accounts.sign = function (opts) {
payloadJson payloadJson
); );
// TODO RSA
var protectedJson = var protectedJson =
{ nonce: opts.nonce { nonce: opts.nonce
, url: accountUrl , url: accountUrl
, alg: abstractKey.meta.alg , alg: abstractKey.meta.alg
, jwk: { , jwk: null
kty: opts.jwk.kty
, crv: opts.jwk.crv
, x: opts.jwk.x
, y: opts.jwk.y
}
}; };
if (/EC/i.test(opts.jwk.kty)) {
protectedJson.jwk = {
crv: opts.jwk.crv
, kty: opts.jwk.kty
, x: opts.jwk.x
, y: opts.jwk.y
};
} else if (/RS/i.test(opts.jwk.kty)) {
protectedJson.jwk = {
e: opts.jwk.e
, kty: opts.jwk.kty
, n: opts.jwk.n
};
} else {
return Promise.reject(new Error("[acme.accounts.sign] unsupported key type '" + opts.jwk.kty + "'"));
}
console.log('protected:'); console.log('protected:');
console.log(protectedJson); console.log(protectedJson);
var protected64 = BACME._jsto64( var protected64 = BACME._jsto64(
@ -486,9 +501,7 @@ var challengePollUrl;
// { jwk, challengeUrl, accountId (kid) } // { jwk, challengeUrl, accountId (kid) }
BACME.challenges.accept = function (opts) { BACME.challenges.accept = function (opts) {
var payload64 = BACME._jsto64( var payload64 = BACME._jsto64({});
{}
);
return BACME._importKey(opts.jwk).then(function (abstractKey) { return BACME._importKey(opts.jwk).then(function (abstractKey) {
var protected64 = BACME._jsto64( var protected64 = BACME._jsto64(
@ -530,6 +543,9 @@ BACME.challenges.check = function (opts) {
BACME._logHeaders(resp); BACME._logHeaders(resp);
return resp.json().then(function (reply) { return resp.json().then(function (reply) {
if (/error/.test(reply.type)) {
return Promise.reject(new Error(reply.detail || reply.type));
}
challengePollUrl = reply.url; challengePollUrl = reply.url;
BACME._logBody(reply); BACME._logBody(reply);
@ -630,6 +646,9 @@ BACME.orders.finalize = function (opts) {
nonce = resp.headers.get('replay-nonce'); nonce = resp.headers.get('replay-nonce');
return resp.json().then(function (reply) { return resp.json().then(function (reply) {
if (/error/.test(reply.type)) {
return Promise.reject(new Error(reply.detail || reply.type));
}
certificateUrl = reply.certificate; certificateUrl = reply.certificate;
BACME._logBody(reply); BACME._logBody(reply);
@ -667,6 +686,9 @@ BACME.orders.check = function (opts) {
BACME._logHeaders(resp); BACME._logHeaders(resp);
return resp.json().then(function (reply) { return resp.json().then(function (reply) {
if (/error/.test(reply.type)) {
return Promise.reject(new Error(reply.detail || reply.type));
}
BACME._logBody(reply); BACME._logBody(reply);
return reply; return reply;