7 измењених фајлова са 25 додато и 1383 уклоњено
@ -1,670 +0,0 @@ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
/*global URLSearchParams,Headers*/ |
|||
var BROWSER_SUPPORTS_ECDSA; |
|||
var $qs = function (s) { return window.document.querySelector(s); }; |
|||
var $qsa = function (s) { return window.document.querySelectorAll(s); }; |
|||
var info = {}; |
|||
var steps = {}; |
|||
var nonce; |
|||
var kid; |
|||
var i = 1; |
|||
var BACME = window.BACME; |
|||
var PromiseA = window.Promise; |
|||
var crypto = window.crypto; |
|||
|
|||
function testEcdsaSupport() { |
|||
var opts = { |
|||
type: 'ECDSA' |
|||
, bitlength: '256' |
|||
}; |
|||
return BACME.accounts.generateKeypair(opts).then(function (jwk) { |
|||
return crypto.subtle.importKey( |
|||
"jwk" |
|||
, jwk |
|||
, { name: "ECDSA", namedCurve: "P-256" } |
|||
, true |
|||
, ["sign"] |
|||
).then(function (privateKey) { |
|||
return window.crypto.subtle.exportKey("pkcs8", privateKey); |
|||
}); |
|||
}); |
|||
} |
|||
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); |
|||
}); |
|||
}); |
|||
} |
|||
function testKeypairSupport() { |
|||
return testEcdsaSupport().then(function () { |
|||
console.info("[crypto] ECDSA is supported"); |
|||
BROWSER_SUPPORTS_ECDSA = true; |
|||
localStorage.setItem('version', '1'); |
|||
return true; |
|||
}).catch(function () { |
|||
console.warn("[crypto] ECDSA is NOT fully supported"); |
|||
BROWSER_SUPPORTS_ECDSA = false; |
|||
|
|||
// fix previous firefox browsers
|
|||
if (!localStorage.getItem('version')) { |
|||
localStorage.clear(); |
|||
localStorage.setItem('version', '1'); |
|||
} |
|||
|
|||
return false; |
|||
}); |
|||
} |
|||
testKeypairSupport().then(function (ecdsaSupport) { |
|||
if (ecdsaSupport) { |
|||
return true; |
|||
} |
|||
|
|||
return 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."); |
|||
}); |
|||
}); |
|||
|
|||
var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory'; |
|||
function updateApiType() { |
|||
console.log("type updated"); |
|||
/*jshint validthis: true */ |
|||
var input = this || Array.prototype.filter.call( |
|||
$qsa('.js-acme-api-type'), function ($el) { return $el.checked; } |
|||
)[0]; |
|||
console.log('ACME api type radio:', input.value); |
|||
$qs('.js-acme-directory-url').value = apiUrl.replace(/{{env}}/g, input.value); |
|||
} |
|||
$qsa('.js-acme-api-type').forEach(function ($el) { |
|||
$el.addEventListener('change', updateApiType); |
|||
}); |
|||
updateApiType(); |
|||
|
|||
function hideForms() { |
|||
$qsa('.js-acme-form').forEach(function (el) { |
|||
el.hidden = true; |
|||
}); |
|||
} |
|||
|
|||
function updateProgress(currentStep) { |
|||
var progressSteps = $qs("#js-progress-bar").children; |
|||
for(var j = 0; j < progressSteps.length; j++) { |
|||
if(j < currentStep) { |
|||
progressSteps[j].classList.add("js-progress-step-complete"); |
|||
progressSteps[j].classList.remove("js-progress-step-started"); |
|||
} else if(j === currentStep) { |
|||
progressSteps[j].classList.remove("js-progress-step-complete"); |
|||
progressSteps[j].classList.add("js-progress-step-started"); |
|||
} else { |
|||
progressSteps[j].classList.remove("js-progress-step-complete"); |
|||
progressSteps[j].classList.remove("js-progress-step-started"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
function submitForm(ev) { |
|||
var j = i; |
|||
i += 1; |
|||
|
|||
return PromiseA.resolve(steps[j].submit(ev)).catch(function (err) { |
|||
console.error(err); |
|||
window.alert("Something went wrong. It's our fault not yours. Please email aj@rootprojects.org and let him know that 'step " + j + "' failed."); |
|||
}); |
|||
} |
|||
|
|||
$qsa('.js-acme-form').forEach(function ($el) { |
|||
$el.addEventListener('submit', function (ev) { |
|||
ev.preventDefault(); |
|||
submitForm(ev); |
|||
}); |
|||
}); |
|||
|
|||
function updateChallengeType() { |
|||
/*jshint validthis: true*/ |
|||
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-verification-wildcard').hidden = true; |
|||
$qs('.js-acme-verification-http-01').hidden = true; |
|||
$qs('.js-acme-verification-dns-01').hidden = true; |
|||
if (info.challenges.wildcard) { |
|||
$qs('.js-acme-verification-wildcard').hidden = false; |
|||
} |
|||
if (info.challenges[input.value]) { |
|||
$qs('.js-acme-verification-' + input.value).hidden = false; |
|||
} |
|||
} |
|||
$qsa('.js-acme-challenge-type').forEach(function ($el) { |
|||
$el.addEventListener('change', updateChallengeType); |
|||
}); |
|||
|
|||
function saveContact(email, domains) { |
|||
// to be used for good, not evil
|
|||
return window.fetch('https://api.rootprojects.org/api/rootprojects.org/public/community', { |
|||
method: 'POST' |
|||
, cors: true |
|||
, headers: new Headers({ 'Content-Type': 'application/json' }) |
|||
, body: JSON.stringify({ |
|||
address: email |
|||
, project: 'greenlock-domains@rootprojects.org' |
|||
, domain: domains.join(',') |
|||
}) |
|||
}).catch(function (err) { |
|||
console.error(err); |
|||
}); |
|||
} |
|||
|
|||
steps[1] = function () { |
|||
updateProgress(0); |
|||
hideForms(); |
|||
$qs('.js-acme-form-domains').hidden = false; |
|||
}; |
|||
steps[1].submit = function () { |
|||
info.identifiers = $qs('.js-acme-domains').value.split(/\s*,\s*/g).map(function (hostname) { |
|||
return { type: 'dns', value: hostname.toLowerCase().trim() }; |
|||
}).slice(0,1); //Disable multiple values for now. We'll just take the first and work with it.
|
|||
info.identifiers.sort(function (a, b) { |
|||
if (a === b) { return 0; } |
|||
if (a < b) { return 1; } |
|||
if (a > b) { return -1; } |
|||
}); |
|||
|
|||
return BACME.directory({ directoryUrl: $qs('.js-acme-directory-url').value }).then(function (directory) { |
|||
$qs('.js-acme-tos-url').href = directory.meta.termsOfService; |
|||
return BACME.nonce().then(function (_nonce) { |
|||
nonce = _nonce; |
|||
|
|||
console.log("MAGIC STEP NUMBER in 1 is:", i); |
|||
steps[i](); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
steps[2] = function () { |
|||
updateProgress(0); |
|||
hideForms(); |
|||
$qs('.js-acme-form-account').hidden = false; |
|||
}; |
|||
steps[2].submit = function () { |
|||
var email = $qs('.js-acme-account-email').value.toLowerCase().trim(); |
|||
|
|||
info.contact = [ 'mailto:' + email ]; |
|||
info.agree = $qs('.js-acme-account-tos').checked; |
|||
info.greenlockAgree = $qs('.js-gl-tos').checked; |
|||
// TODO
|
|||
// options for
|
|||
// * regenerate key
|
|||
// * ECDSA / RSA / bitlength
|
|||
|
|||
// TODO ping with version and account creation
|
|||
setTimeout(saveContact, 100, email, info.identifiers.map(function (ident) { return ident.value; })); |
|||
|
|||
var jwk = JSON.parse(localStorage.getItem('account:' + email) || 'null'); |
|||
var p; |
|||
|
|||
function createKeypair() { |
|||
var opts; |
|||
|
|||
if(BROWSER_SUPPORTS_ECDSA) { |
|||
opts = { |
|||
type: 'ECDSA' |
|||
, bitlength: '256' |
|||
}; |
|||
} else { |
|||
opts = { |
|||
type: 'RSA' |
|||
, bitlength: '2048' |
|||
}; |
|||
} |
|||
|
|||
return BACME.accounts.generateKeypair(opts).then(function (jwk) { |
|||
localStorage.setItem('account:' + email, JSON.stringify(jwk)); |
|||
return jwk; |
|||
}); |
|||
} |
|||
|
|||
if (jwk) { |
|||
p = PromiseA.resolve(jwk); |
|||
} else { |
|||
p = testKeypairSupport().then(createKeypair); |
|||
} |
|||
|
|||
function createAccount(jwk) { |
|||
console.log('account jwk:'); |
|||
console.log(jwk); |
|||
delete jwk.key_ops; |
|||
info.jwk = jwk; |
|||
return BACME.accounts.sign({ |
|||
jwk: jwk |
|||
, contacts: [ 'mailto:' + email ] |
|||
, agree: info.agree |
|||
, nonce: nonce |
|||
, kid: kid |
|||
}).then(function (signedAccount) { |
|||
return BACME.accounts.set({ |
|||
signedAccount: signedAccount |
|||
}).then(function (account) { |
|||
console.log('account:'); |
|||
console.log(account); |
|||
kid = account.kid; |
|||
return kid; |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
return p.then(function (_jwk) { |
|||
jwk = _jwk; |
|||
kid = JSON.parse(localStorage.getItem('account-kid:' + email) || 'null'); |
|||
var p2; |
|||
|
|||
// TODO save account id rather than always retrieving it
|
|||
if (kid) { |
|||
p2 = PromiseA.resolve(kid); |
|||
} else { |
|||
p2 = createAccount(jwk); |
|||
} |
|||
|
|||
return p2.then(function (_kid) { |
|||
kid = _kid; |
|||
info.kid = kid; |
|||
return BACME.orders.sign({ |
|||
jwk: jwk |
|||
, identifiers: info.identifiers |
|||
, kid: kid |
|||
}).then(function (signedOrder) { |
|||
return BACME.orders.create({ |
|||
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:'); |
|||
console.log(claims); |
|||
var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] }; |
|||
info.challenges = obj; |
|||
var map = { |
|||
'http-01': '.js-acme-verification-http-01' |
|||
, 'dns-01': '.js-acme-verification-dns-01' |
|||
, 'wildcard': '.js-acme-verification-wildcard' |
|||
}; |
|||
|
|||
/* |
|||
var tpls = {}; |
|||
Object.keys(map).forEach(function (k) { |
|||
var sel = map[k] + ' tbody'; |
|||
console.log(sel); |
|||
tpls[k] = $qs(sel).innerHTML; |
|||
$qs(map[k] + ' tbody').innerHTML = ''; |
|||
}); |
|||
*/ |
|||
|
|||
// TODO make Promise-friendly
|
|||
return PromiseA.all(claims.map(function (claim) { |
|||
var hostname = claim.identifier.value; |
|||
return PromiseA.all(claim.challenges.map(function (c) { |
|||
var keyAuth = BACME.challenges['http-01']({ |
|||
token: c.token |
|||
, thumbprint: thumbprint |
|||
, challengeDomain: hostname |
|||
}); |
|||
return BACME.challenges['dns-01']({ |
|||
keyAuth: keyAuth.value |
|||
, challengeDomain: hostname |
|||
}).then(function (dnsAuth) { |
|||
var data = { |
|||
type: c.type |
|||
, hostname: hostname |
|||
, url: c.url |
|||
, token: c.token |
|||
, keyAuthorization: keyAuth |
|||
, httpPath: keyAuth.path |
|||
, httpAuth: keyAuth.value |
|||
, dnsType: dnsAuth.type |
|||
, dnsHost: dnsAuth.host |
|||
, dnsAnswer: dnsAuth.answer |
|||
}; |
|||
|
|||
console.log(''); |
|||
console.log('CHALLENGE'); |
|||
console.log(claim); |
|||
console.log(c); |
|||
console.log(data); |
|||
console.log(''); |
|||
|
|||
if (claim.wildcard) { |
|||
obj.wildcard.push(data); |
|||
let verification = $qs(".js-acme-verification-wildcard"); |
|||
verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname; |
|||
verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost; |
|||
verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer; |
|||
|
|||
} else if(obj[data.type]) { |
|||
|
|||
obj[data.type].push(data); |
|||
|
|||
if ('dns-01' === data.type) { |
|||
let verification = $qs(".js-acme-verification-dns-01"); |
|||
verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname; |
|||
verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost; |
|||
verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer; |
|||
} else if ('http-01' === data.type) { |
|||
$qs(".js-acme-ver-file-location").innerHTML = data.httpPath.split("/").slice(-1); |
|||
$qs(".js-acme-ver-content").innerHTML = data.httpAuth; |
|||
$qs(".js-acme-ver-uri").innerHTML = data.httpPath; |
|||
$qs(".js-download-verify-link").href = |
|||
"data:text/octet-stream;base64," + window.btoa(data.httpAuth); |
|||
$qs(".js-download-verify-link").download = data.httpPath.split("/").slice(-1); |
|||
} |
|||
} |
|||
|
|||
}); |
|||
|
|||
})); |
|||
})).then(function () { |
|||
|
|||
// hide wildcard if no wildcard
|
|||
// hide http-01 and dns-01 if only wildcard
|
|||
if (!obj.wildcard.length) { |
|||
$qs('.js-acme-wildcard-challenges').hidden = true; |
|||
} |
|||
if (!obj['http-01'].length) { |
|||
$qs('.js-acme-challenges').hidden = true; |
|||
} |
|||
|
|||
updateChallengeType(); |
|||
|
|||
console.log("MAGIC STEP NUMBER in 2 is:", i); |
|||
steps[i](); |
|||
}); |
|||
|
|||
}); |
|||
}); |
|||
}); |
|||
}); |
|||
}); |
|||
}).catch(function (err) { |
|||
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@rootprojects.org and let him know."); |
|||
}); |
|||
}; |
|||
|
|||
steps[3] = function () { |
|||
updateProgress(1); |
|||
hideForms(); |
|||
$qs('.js-acme-form-challenges').hidden = false; |
|||
}; |
|||
steps[3].submit = function () { |
|||
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) }
|
|||
chs.push({ |
|||
jwk: info.jwk |
|||
, challengeUrl: ch.url |
|||
, accountId: info.kid |
|||
}); |
|||
}); |
|||
}); |
|||
console.log("INFO.challenges !!!!!", info.challenges); |
|||
|
|||
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(); |
|||
}); |
|||
} |
|||
|
|||
// for now just show the next page immediately (its a spinner)
|
|||
steps[i](); |
|||
return nextChallenge().then(function (results) { |
|||
console.log('challenge status:', results); |
|||
var polls = results.slice(0); |
|||
var allsWell = true; |
|||
|
|||
function checkPolls() { |
|||
return new PromiseA(function (resolve) { |
|||
setTimeout(resolve, 1000); |
|||
}).then(function () { |
|||
return PromiseA.all(polls.map(function (poll) { |
|||
return BACME.challenges.check({ challengePollUrl: poll.url }); |
|||
})).then(function (polls) { |
|||
console.log(polls); |
|||
|
|||
polls = polls.filter(function (poll) { |
|||
//return 'valid' !== poll.status && 'invalid' !== poll.status;
|
|||
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
|
|||
}); |
|||
|
|||
if (polls.length) { |
|||
return checkPolls(); |
|||
} |
|||
return true; |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
return checkPolls().then(function () { |
|||
if (allsWell) { |
|||
return submitForm(); |
|||
} |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
// 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); |
|||
hideForms(); |
|||
$qs('.js-acme-form-poll').hidden = false; |
|||
}; |
|||
steps[4].submit = function () { |
|||
console.log('Congrats! Auto advancing...'); |
|||
|
|||
var key = info.identifiers.map(function (ident) { return ident.value; }).join(','); |
|||
var serverJwk = JSON.parse(localStorage.getItem('server:' + key) || 'null'); |
|||
var p; |
|||
|
|||
function createKeypair() { |
|||
var opts; |
|||
|
|||
if (BROWSER_SUPPORTS_ECDSA) { |
|||
opts = { type: 'ECDSA', bitlength: '256' }; |
|||
} else { |
|||
opts = { type: 'RSA', bitlength: '2048' }; |
|||
} |
|||
|
|||
return BACME.domains.generateKeypair(opts).then(function (serverJwk) { |
|||
localStorage.setItem('server:' + key, JSON.stringify(serverJwk)); |
|||
return serverJwk; |
|||
}); |
|||
} |
|||
|
|||
if (serverJwk) { |
|||
p = PromiseA.resolve(serverJwk); |
|||
} else { |
|||
p = createKeypair(); |
|||
} |
|||
|
|||
return p.then(function (_serverJwk) { |
|||
serverJwk = _serverJwk; |
|||
info.serverJwk = serverJwk; |
|||
// { serverJwk, domains }
|
|||
return BACME.orders.generateCsr({ |
|||
serverJwk: serverJwk |
|||
, domains: info.identifiers.map(function (ident) { |
|||
return ident.value; |
|||
}) |
|||
}).then(function (csrweb64) { |
|||
return BACME.orders.finalize({ |
|||
csr: csrweb64 |
|||
, jwk: info.jwk |
|||
, finalizeUrl: info.finalizeUrl |
|||
, accountId: info.kid |
|||
}); |
|||
}).then(function () { |
|||
function checkCert() { |
|||
return new PromiseA(function (resolve) { |
|||
setTimeout(resolve, 1000); |
|||
}).then(function () { |
|||
return BACME.orders.check({ orderUrl: info.orderUrl }); |
|||
}).then(function (reply) { |
|||
if ('processing' === reply) { |
|||
return checkCert(); |
|||
} |
|||
return reply; |
|||
}); |
|||
} |
|||
|
|||
return checkCert(); |
|||
}).then(function (reply) { |
|||
return BACME.orders.receive({ certificateUrl: reply.certificate }); |
|||
}).then(function (certs) { |
|||
console.log('WINNING!'); |
|||
console.log(certs); |
|||
$qs('#js-fullchain').innerHTML = certs; |
|||
$qs("#js-download-fullchain-link").href = |
|||
"data:text/octet-stream;base64," + window.btoa(certs); |
|||
|
|||
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, 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()); |
|||
window.alert("An error happened in the final step, but it's not your fault. Email aj@rootprojects.org and let him know."); |
|||
}); |
|||
}; |
|||
|
|||
steps[5] = function () { |
|||
updateProgress(2); |
|||
hideForms(); |
|||
$qs('.js-acme-form-download').hidden = false; |
|||
}; |
|||
steps[1](); |
|||
|
|||
var params = new URLSearchParams(window.location.search); |
|||
var apiType = params.get('acme-api-type') || "staging-v02"; |
|||
|
|||
if(params.has('acme-domains')) { |
|||
console.log("acme-domains param: ", params.get('acme-domains')); |
|||
$qs('.js-acme-domains').value = params.get('acme-domains'); |
|||
|
|||
$qsa('.js-acme-api-type').forEach(function(ele) { |
|||
if(ele.value === apiType) { |
|||
ele.checked = true; |
|||
} |
|||
}); |
|||
|
|||
updateApiType(); |
|||
steps[2](); |
|||
submitForm(); |
|||
} |
|||
|
|||
$qs('body').hidden = false; |
|||
}()); |
@ -1,699 +0,0 @@ |
|||
/*global CSR*/ |
|||
// CSR takes a while to load after the page load
|
|||
(function (exports) { |
|||
'use strict'; |
|||
|
|||
var BACME = exports.BACME = {}; |
|||
var webFetch = exports.fetch; |
|||
var webCrypto = exports.crypto; |
|||
var Promise = exports.Promise; |
|||
|
|||
var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory'; |
|||
var directory; |
|||
|
|||
var nonceUrl; |
|||
var nonce; |
|||
|
|||
var accountKeypair; |
|||
var accountJwk; |
|||
|
|||
var accountUrl; |
|||
|
|||
BACME.challengePrefixes = { |
|||
'http-01': '/.well-known/acme-challenge' |
|||
, 'dns-01': '_acme-challenge' |
|||
}; |
|||
|
|||
BACME._logHeaders = function (resp) { |
|||
console.log('Headers:'); |
|||
Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); }); |
|||
}; |
|||
|
|||
BACME._logBody = function (body) { |
|||
console.log('Body:'); |
|||
console.log(JSON.stringify(body, null, 2)); |
|||
console.log(''); |
|||
}; |
|||
|
|||
BACME.directory = function (opts) { |
|||
return webFetch(opts.directoryUrl || directoryUrl, { mode: 'cors' }).then(function (resp) { |
|||
BACME._logHeaders(resp); |
|||
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(reply); |
|||
return reply; |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
BACME.nonce = function () { |
|||
return webFetch(nonceUrl, { mode: 'cors' }).then(function (resp) { |
|||
BACME._logHeaders(resp); |
|||
nonce = resp.headers.get('replay-nonce'); |
|||
console.log('Nonce:', nonce); |
|||
// resp.body is empty
|
|||
return resp.headers.get('replay-nonce'); |
|||
}); |
|||
}; |
|||
|
|||
BACME.accounts = {}; |
|||
|
|||
// type = ECDSA
|
|||
// bitlength = 256
|
|||
BACME.accounts.generateKeypair = function (opts) { |
|||
return BACME.generateKeypair(opts).then(function (result) { |
|||
accountKeypair = result; |
|||
|
|||
return webCrypto.subtle.exportKey( |
|||
"jwk" |
|||
, result.privateKey |
|||
).then(function (privJwk) { |
|||
|
|||
accountJwk = privJwk; |
|||
console.log('private jwk:'); |
|||
console.log(JSON.stringify(privJwk, null, 2)); |
|||
|
|||
return privJwk; |
|||
/* |
|||
return webCrypto.subtle.exportKey( |
|||
"pkcs8" |
|||
, result.privateKey |
|||
).then(function (keydata) { |
|||
console.log('pkcs8:'); |
|||
console.log(Array.from(new Uint8Array(keydata))); |
|||
|
|||
return privJwk; |
|||
//return accountKeypair;
|
|||
}); |
|||
*/ |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
// json to url-safe base64
|
|||
BACME._jsto64 = function (json) { |
|||
return btoa(JSON.stringify(json)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); |
|||
}; |
|||
|
|||
var textEncoder = new TextEncoder(); |
|||
|
|||
BACME._importKey = function (jwk) { |
|||
var alg; // I think the 256 refers to the hash
|
|||
var wcOpts = {}; |
|||
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
|
|||
if (/^RS/i.test(jwk.kty)) { |
|||
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" |
|||
, pub |
|||
, wcOpts |
|||
, extractable |
|||
, [ "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.wcKeypair.privateKey; |
|||
var wcOpts = opts.abstractKey.meta; |
|||
var alg = opts.abstractKey.meta.alg; // I think the 256 refers to the hash
|
|||
var signHash; |
|||
|
|||
console.log('kty', opts.abstractKey.jwk.kty); |
|||
signHash = { name: "SHA-" + alg.replace(/[a-z]+/ig, '') }; |
|||
|
|||
var msg = textEncoder.encode(opts.protected64 + '.' + opts.payload64); |
|||
console.log('msg:', msg); |
|||
return window.crypto.subtle.sign( |
|||
{ name: wcOpts.name, hash: signHash } |
|||
, wcPrivKey |
|||
, msg |
|||
).then(function (signature) { |
|||
//console.log('sig1:', signature);
|
|||
//console.log('sig2:', new Uint8Array(signature));
|
|||
//console.log('sig3:', Array.prototype.slice.call(new Uint8Array(signature)));
|
|||
// convert buffer to urlsafe base64
|
|||
var sig64 = btoa(Array.prototype.map.call(new Uint8Array(signature), function (ch) { |
|||
return String.fromCharCode(ch); |
|||
}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); |
|||
|
|||
console.log('[1] URL-safe Base64 Signature:'); |
|||
console.log(sig64); |
|||
|
|||
var signedMsg = { |
|||
protected: opts.protected64 |
|||
, payload: opts.payload64 |
|||
, signature: sig64 |
|||
}; |
|||
|
|||
console.log('Signed Base64 Msg:'); |
|||
console.log(JSON.stringify(signedMsg, null, 2)); |
|||
|
|||
return signedMsg; |
|||
}); |
|||
}; |
|||
// email = john.doe@gmail.com
|
|||
// jwk = { ... }
|
|||
// agree = true
|
|||
BACME.accounts.sign = function (opts) { |
|||
|
|||
return BACME._importKey(opts.jwk).then(function (abstractKey) { |
|||
|
|||
var payloadJson = |
|||
{ termsOfServiceAgreed: opts.agree |
|||
, onlyReturnExisting: false |
|||
, contact: opts.contacts || [ 'mailto:' + opts.email ] |
|||
}; |
|||
console.log('payload:'); |
|||
console.log(payloadJson); |
|||
var payload64 = BACME._jsto64( |
|||
payloadJson |
|||
); |
|||
|
|||
var protectedJson = |
|||
{ nonce: opts.nonce |
|||
, url: accountUrl |
|||
, alg: abstractKey.meta.alg |
|||
, 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( |
|||
protectedJson |
|||
); |
|||
|
|||
// Note: this function hashes before signing so send data, not the hash
|
|||
return BACME._sign({ |
|||
abstractKey: abstractKey |
|||
, payload64: payload64 |
|||
, protected64: protected64 |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
var accountId; |
|||
|
|||
BACME.accounts.set = function (opts) { |
|||
nonce = null; |
|||
return window.fetch(accountUrl, { |
|||
mode: 'cors' |
|||
, method: 'POST' |
|||
, headers: { 'Content-Type': 'application/jose+json' } |
|||
, body: JSON.stringify(opts.signedAccount) |
|||
}).then(function (resp) { |
|||
BACME._logHeaders(resp); |
|||
nonce = resp.headers.get('replay-nonce'); |
|||
accountId = resp.headers.get('location'); |
|||
console.log('Next nonce:', nonce); |
|||
console.log('Location/kid:', accountId); |
|||
|
|||
if (!resp.headers.get('content-type')) { |
|||
console.log('Body: <none>'); |
|||
|
|||
return { kid: accountId }; |
|||
} |
|||
|
|||
return resp.json().then(function (result) { |
|||
if (/^Error/i.test(result.detail)) { |
|||
return Promise.reject(new Error(result.detail)); |
|||
} |
|||
result.kid = accountId; |
|||
BACME._logBody(result); |
|||
|
|||
return result; |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
var orderUrl; |
|||
|
|||
BACME.orders = {}; |
|||
|
|||
// identifiers = [ { type: 'dns', value: 'example.com' }, { type: 'dns', value: '*.example.com' } ]
|
|||
// signedAccount
|
|||
BACME.orders.sign = function (opts) { |
|||
var payload64 = BACME._jsto64({ identifiers: opts.identifiers }); |
|||
|
|||
return BACME._importKey(opts.jwk).then(function (abstractKey) { |
|||
var protected64 = BACME._jsto64( |
|||
{ nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: orderUrl, kid: opts.kid } |
|||
); |
|||
console.log('abstractKey:'); |
|||
console.log(abstractKey); |
|||
return BACME._sign({ |
|||
abstractKey: abstractKey |
|||
, payload64: payload64 |
|||
, protected64: protected64 |
|||
}).then(function (sig) { |
|||
if (!sig) { |
|||
throw new Error('sig is undefined... nonsense!'); |
|||
} |
|||
console.log('newsig', sig); |
|||
return sig; |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
var currentOrderUrl; |
|||
var authorizationUrls; |
|||
var finalizeUrl; |
|||
|
|||
BACME.orders.create = function (opts) { |
|||
nonce = null; |
|||
return window.fetch(orderUrl, { |
|||
mode: 'cors' |
|||
, method: 'POST' |
|||
, headers: { 'Content-Type': 'application/jose+json' } |
|||
, body: JSON.stringify(opts.signedOrder) |
|||
}).then(function (resp) { |
|||
BACME._logHeaders(resp); |
|||
currentOrderUrl = resp.headers.get('location'); |
|||
nonce = resp.headers.get('replay-nonce'); |
|||
console.log('Next nonce:', nonce); |
|||
|
|||
return resp.json().then(function (result) { |
|||
if (/^Error/i.test(result.detail)) { |
|||
return Promise.reject(new Error(result.detail)); |
|||
} |
|||
authorizationUrls = result.authorizations; |
|||
finalizeUrl = result.finalize; |
|||
BACME._logBody(result); |
|||
|
|||
result.url = currentOrderUrl; |
|||
return result; |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
BACME.challenges = {}; |
|||
BACME.challenges.all = function () { |
|||
var challenges = []; |
|||
|
|||
function next() { |
|||
if (!authorizationUrls.length) { |
|||
return challenges; |
|||
} |
|||
|
|||
return BACME.challenges.view().then(function (challenge) { |
|||
challenges.push(challenge); |
|||
return next(); |
|||
}); |
|||
} |
|||
|
|||
return next(); |
|||
}; |
|||
BACME.challenges.view = function () { |
|||
var authzUrl = authorizationUrls.pop(); |
|||
var token; |
|||
var challengeDomain; |
|||
var challengeUrl; |
|||
|
|||
return window.fetch(authzUrl, { |
|||
mode: 'cors' |
|||
}).then(function (resp) { |
|||
BACME._logHeaders(resp); |
|||
|
|||
return resp.json().then(function (result) { |
|||
// Note: select the challenge you wish to use
|
|||
var challenge = result.challenges.slice(0).pop(); |
|||
token = challenge.token; |
|||
challengeUrl = challenge.url; |
|||
challengeDomain = result.identifier.value; |
|||
|
|||
BACME._logBody(result); |
|||
|
|||
return { |
|||
challenges: result.challenges |
|||
, expires: result.expires |
|||
, identifier: result.identifier |
|||
, status: result.status |
|||
, wildcard: result.wildcard |
|||
//, token: challenge.token
|
|||
//, url: challenge.url
|
|||
//, domain: result.identifier.value,
|
|||
}; |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
var thumbprint; |
|||
var keyAuth; |
|||
var httpPath; |
|||
var dnsAuth; |
|||
var dnsRecord; |
|||
|
|||
BACME.thumbprint = function (opts) { |
|||
// https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
|
|||
|
|||
var accountJwk = opts.jwk; |
|||
var keys; |
|||
|
|||
if (/^EC/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) { |
|||
return '"' + key + '":"' + accountJwk[key] + '"'; |
|||
}).join(',') + '}'; |
|||
|
|||
return window.crypto.subtle.digest( |
|||
{ name: "SHA-256" } // SHA-256 is spec'd, non-optional
|
|||
, textEncoder.encode(accountPublicStr) |
|||
).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; |
|||
}); |
|||
}; |
|||
|
|||
// { token, thumbprint, challengeDomain }
|
|||
BACME.challenges['http-01'] = function (opts) { |
|||
// The contents of the key authorization file
|
|||
keyAuth = opts.token + '.' + opts.thumbprint; |
|||
|
|||
// Where the key authorization file goes
|
|||
httpPath = 'http://' + opts.challengeDomain + '/.well-known/acme-challenge/' + opts.token; |
|||
|
|||
console.log("echo '" + keyAuth + "' > '" + httpPath + "'"); |
|||
|
|||
return { |
|||
path: httpPath |
|||
, value: keyAuth |
|||
}; |
|||
}; |
|||
|
|||
// { 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) { |
|||
dnsAuth = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) { |
|||
return String.fromCharCode(ch); |
|||
}).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); |
|||
|
|||
dnsRecord = '_acme-challenge.' + opts.challengeDomain; |
|||
|
|||
console.log('DNS TXT Auth:'); |
|||
// The name of the record
|
|||
console.log(dnsRecord); |
|||
// The TXT record value
|
|||
console.log(dnsAuth); |
|||
|
|||
return { |
|||
type: 'TXT' |
|||
, host: dnsRecord |
|||
, answer: dnsAuth |
|||
}; |
|||
}); |
|||
}; |
|||
|
|||
var challengePollUrl; |
|||
|
|||
// { jwk, challengeUrl, accountId (kid) }
|
|||
BACME.challenges.accept = function (opts) { |
|||
var payload64 = BACME._jsto64({}); |
|||
|
|||
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 } |
|||
); |
|||
return BACME._sign({ |
|||
abstractKey: abstractKey |
|||
, payload64: payload64 |
|||
, protected64: protected64 |
|||
}); |
|||
}).then(function (signedAccept) { |
|||
|
|||
nonce = null; |
|||
return window.fetch( |
|||
opts.challengeUrl |
|||
, { mode: 'cors' |
|||
, method: 'POST' |
|||
, headers: { 'Content-Type': 'application/jose+json' } |
|||
, body: JSON.stringify(signedAccept) |
|||
} |
|||
).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; |
|||
|
|||
console.log('Challenge ACK:'); |
|||
console.log(JSON.stringify(reply)); |
|||
return reply; |
|||
}); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
BACME.challenges.check = function (opts) { |
|||
return window.fetch(opts.challengePollUrl, { mode: 'cors' }).then(function (resp) { |
|||
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); |
|||
|
|||
return reply; |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
var domainKeypair; |
|||
var domainJwk; |
|||
|
|||
BACME.generateKeypair = function (opts) { |
|||
var wcOpts = {}; |
|||
|
|||
// ECDSA has only the P curves and an associated bitlength
|
|||
if (/^EC/i.test(opts.type)) { |
|||
wcOpts.name = 'ECDSA'; |
|||
if (/256/.test(opts.bitlength)) { |
|||
wcOpts.namedCurve = 'P-256'; |
|||
} |
|||
} |
|||
|
|||
// RSA-PSS is another option, but I don't think it's used for Let's Encrypt
|
|||
// I think the hash is only necessary for signing, not generation or import
|
|||
if (/^RS/i.test(opts.type)) { |
|||
wcOpts.name = 'RSASSA-PKCS1-v1_5'; |
|||
wcOpts.modulusLength = opts.bitlength; |
|||
if (opts.bitlength < 2048) { |
|||
wcOpts.modulusLength = opts.bitlength * 8; |
|||
} |
|||
wcOpts.publicExponent = new Uint8Array([0x01, 0x00, 0x01]); |
|||
wcOpts.hash = { name: "SHA-256" }; |
|||
} |
|||
var extractable = true; |
|||
return window.crypto.subtle.generateKey( |
|||
wcOpts |
|||
, extractable |
|||
, [ 'sign', 'verify' ] |
|||
); |
|||
}; |
|||
BACME.domains = {}; |
|||
// TODO factor out from BACME.accounts.generateKeypair even more
|
|||
BACME.domains.generateKeypair = function (opts) { |
|||
return BACME.generateKeypair(opts).then(function (result) { |
|||
domainKeypair = result; |
|||
|
|||
return window.crypto.subtle.exportKey( |
|||
"jwk" |
|||
, result.privateKey |
|||
).then(function (privJwk) { |
|||
|
|||
domainJwk = privJwk; |
|||
console.log('private jwk:'); |
|||
console.log(JSON.stringify(privJwk, null, 2)); |
|||
|
|||
return privJwk; |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
// { serverJwk, domains }
|
|||
BACME.orders.generateCsr = function (opts) { |
|||
return BACME._importKey(opts.serverJwk).then(function (abstractKey) { |
|||
return Promise.resolve(CSR.generate({ keypair: abstractKey.wcKeypair, domains: opts.domains })); |
|||
}); |
|||
}; |
|||
|
|||
var certificateUrl; |
|||
|
|||
// { csr, jwk, finalizeUrl, accountId }
|
|||
BACME.orders.finalize = function (opts) { |
|||
var payload64 = BACME._jsto64( |
|||
{ csr: opts.csr } |
|||
); |
|||
|
|||
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 } |
|||
); |
|||
return BACME._sign({ |
|||
abstractKey: abstractKey |
|||
, payload64: payload64 |
|||
, protected64: protected64 |
|||
}); |
|||
}).then(function (signedFinal) { |
|||
|
|||
nonce = null; |
|||
return window.fetch( |
|||
opts.finalizeUrl |
|||
, { mode: 'cors' |
|||
, method: 'POST' |
|||
, headers: { 'Content-Type': 'application/jose+json' } |
|||
, body: JSON.stringify(signedFinal) |
|||
} |
|||
).then(function (resp) { |
|||
BACME._logHeaders(resp); |
|||
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); |
|||
|
|||
return reply; |
|||
}); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
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.text().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) { |
|||
if (/error/.test(reply.type)) { |
|||
return Promise.reject(new Error(reply.detail || reply.type)); |
|||
} |
|||
BACME._logBody(reply); |
|||
|
|||
return reply; |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
}(window)); |
Loading…
Reference in new issue