diff --git a/app/index.html b/app/index.html
index 2466189..e345eee 100644
--- a/app/index.html
+++ b/app/index.html
@@ -40,7 +40,7 @@
-
+
@@ -207,6 +207,7 @@
+
Set this DNS Record
Hostname
loading...
TXT Host
diff --git a/app/js/app.js b/app/js/app.js
index c14d7e3..f32ab86 100644
--- a/app/js/app.js
+++ b/app/js/app.js
@@ -2,7 +2,7 @@
'use strict';
/*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 $qsa = function (s) { return window.document.querySelectorAll(s); };
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 () {
- console.log("supports ECDSA");
+ console.info("[crypto] ECDSA is supported");
BROWSER_SUPPORTS_ECDSA = true;
+ localStorage.setItem('version', '1');
return true;
}).catch(function () {
- console.log("DOES NOT supports ECDSA");
+ console.warn("[crypto] ECDSA is NOT fully supported");
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;
});
- // TODO test RSA support
var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory';
function updateApiType() {
@@ -83,7 +117,10 @@
var j = i;
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) {
@@ -355,8 +392,9 @@
});
});
}).catch(function (err) {
- console.error('Step \'' + i + '\' Error:');
+ console.error('Step \'\' Error:');
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) {
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) {
allsWell = false;
console.warn('BAD POLL STATUS', poll);
+ window.alert("unknown error: " + JSON.stringify(poll, null, 2));
}
// 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
steps[4] = function () {
updateProgress(1);
@@ -460,16 +543,10 @@
function createKeypair() {
var opts;
- if(BROWSER_SUPPORTS_ECDSA) {
- opts = {
- type: 'ECDSA'
- , bitlength: '256'
- };
+ if (BROWSER_SUPPORTS_ECDSA) {
+ opts = { type: 'ECDSA', bitlength: '256' };
} else {
- opts = {
- type: 'RSA'
- , bitlength: '2048'
- };
+ opts = { type: 'RSA', bitlength: '2048' };
}
return BACME.domains.generateKeypair(opts).then(function (serverJwk) {
@@ -524,52 +601,14 @@
$qs("#js-download-fullchain-link").href =
"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 pemName;
if (/^R/.test(info.serverJwk.kty)) {
pemName = 'RSA';
- wcOpts = {
- name: "RSASSA-PKCS1-v1_5"
- , hash: { name: "SHA-256" }
- };
+ wcOpts = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } };
} else {
pemName = 'EC';
- wcOpts = {
- name: "ECDSA"
- , namedCurve: "P-256"
- };
+ wcOpts = { name: "ECDSA", namedCurve: "P-256" };
}
return crypto.subtle.importKey(
"jwk"
@@ -580,15 +619,16 @@
).then(function (privateKey) {
return window.crypto.subtle.exportKey("pkcs8", privateKey);
}).then (function (keydata) {
- var pem = spkiToPEM(keydata);
+ var pem = spkiToPEM(keydata, pemName);
$qs('#js-privkey').innerHTML = pem;
$qs("#js-download-privkey-link").href =
"data:text/octet-stream;base64," + window.btoa(pem);
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.");
});
};
diff --git a/app/js/bacme.js b/app/js/bacme.js
index 978e166..fb63042 100644
--- a/app/js/bacme.js
+++ b/app/js/bacme.js
@@ -38,13 +38,16 @@ BACME._logBody = function (body) {
BACME.directory = function (opts) {
return webFetch(opts.directoryUrl || directoryUrl, { mode: 'cors' }).then(function (resp) {
BACME._logHeaders(resp);
- return resp.json().then(function (body) {
- directory = body;
+ return resp.json().then(function (reply) {
+ 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';
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";
- BACME._logBody(body);
- return body;
+ BACME._logBody(reply);
+ return reply;
});
});
};
@@ -227,18 +230,30 @@ BACME.accounts.sign = function (opts) {
payloadJson
);
- // TODO RSA
var protectedJson =
{ nonce: opts.nonce
, url: accountUrl
, alg: abstractKey.meta.alg
- , jwk: {
- kty: opts.jwk.kty
- , crv: opts.jwk.crv
- , x: opts.jwk.x
- , y: opts.jwk.y
- }
+ , jwk: null
};
+
+ 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(protectedJson);
var protected64 = BACME._jsto64(
@@ -486,9 +501,7 @@ var challengePollUrl;
// { jwk, challengeUrl, accountId (kid) }
BACME.challenges.accept = function (opts) {
- var payload64 = BACME._jsto64(
- {}
- );
+ var payload64 = BACME._jsto64({});
return BACME._importKey(opts.jwk).then(function (abstractKey) {
var protected64 = BACME._jsto64(
@@ -530,6 +543,9 @@ BACME.challenges.check = function (opts) {
BACME._logHeaders(resp);
return resp.json().then(function (reply) {
+ if (/error/.test(reply.type)) {
+ return Promise.reject(new Error(reply.detail || reply.type));
+ }
challengePollUrl = reply.url;
BACME._logBody(reply);
@@ -630,6 +646,9 @@ BACME.orders.finalize = function (opts) {
nonce = resp.headers.get('replay-nonce');
return resp.json().then(function (reply) {
+ if (/error/.test(reply.type)) {
+ return Promise.reject(new Error(reply.detail || reply.type));
+ }
certificateUrl = reply.certificate;
BACME._logBody(reply);
@@ -667,6 +686,9 @@ BACME.orders.check = function (opts) {
BACME._logHeaders(resp);
return resp.json().then(function (reply) {
+ if (/error/.test(reply.type)) {
+ return Promise.reject(new Error(reply.detail || reply.type));
+ }
BACME._logBody(reply);
return reply;