tested working in firefox
This commit is contained in:
parent
136c9c06fe
commit
df964d76c6
|
@ -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>
|
||||||
|
|
154
app/js/app.js
154
app/js/app.js
|
@ -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);
|
||||||
|
@ -461,15 +544,9 @@
|
||||||
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) {
|
}).catch(function (err) {
|
||||||
console.error(err.toString());
|
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.");
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
if (/EC/i.test(opts.jwk.kty)) {
|
||||||
|
protectedJson.jwk = {
|
||||||
|
crv: opts.jwk.crv
|
||||||
|
, kty: opts.jwk.kty
|
||||||
, x: opts.jwk.x
|
, x: opts.jwk.x
|
||||||
, y: opts.jwk.y
|
, 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;
|
||||||
|
|
Loading…
Reference in New Issue