10 changed files with 947 additions and 725 deletions
@ -1,2 +1,2 @@ |
|||
js/pkijs.org |
|||
js/browser-csr |
|||
app/js/pkijs.org |
|||
app/js/browser-csr |
|||
|
@ -0,0 +1,237 @@ |
|||
<html> |
|||
<head> |
|||
<title>Greenlock™</title> |
|||
<meta property="og:image" content="https://greenlock.ppl.family/img/greenlock-mark-400x400.png" /> |
|||
<link href="style/main.css" rel="stylesheet"> |
|||
</head> |
|||
<body hidden> |
|||
<div class="column-container wide"> |
|||
<div class="column-row"> |
|||
<img src="img/greenlock-146.png"> |
|||
</div> |
|||
<div class="column-row"> |
|||
<h1>Get the green lock for your website</h1> |
|||
|
|||
<!-- Step 1 Choose Domain(s) --> |
|||
<form class="js-acme-form js-acme-form-domains"> |
|||
<h1><label>What's your domain?</label></h1> |
|||
<h4>Certificates are valid for 90 days. Renewal is free :)</h4> |
|||
<input class="js-acme-domains" type="text" placeholder="example.com,*.example.com" required> |
|||
<br> |
|||
<button type="submit">Next</button> |
|||
|
|||
<br> |
|||
<br> |
|||
<br> |
|||
<label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="v02" checked required> |
|||
Production</label> |
|||
<label><input class="js-acme-api-type" name="acme-api-type" type="radio" value="staging-v02" required> |
|||
Testing</label> |
|||
<br> |
|||
<input class="js-acme-directory-url" type="url" placeholder="ACME directory url"> |
|||
</form> |
|||
|
|||
<!-- Step 2 Create Account --> |
|||
<form class="js-acme-form js-acme-form-account"> |
|||
<h1><label>What's your email?</label></h1> |
|||
<input class="js-acme-account-email" type="email" placeholder="john@doe.family" required> |
|||
<br> |
|||
<br> |
|||
<label><input class="js-acme-account-tos" type="checkbox" required> |
|||
Agree to <a class="js-acme-tos-url" target="acme-tos">Let's Encrypt™ Terms of Service</a>?</label> |
|||
<br> |
|||
<br> |
|||
<label><input class="js-greenlock-account-tos" type="checkbox" required> |
|||
Agree to <a class="js-gl-tos" target="_blank" href="./legal.html">Greenlock™ Terms of Service</a>?</label> |
|||
<br> |
|||
<br> |
|||
<!-- |
|||
<a href="#">advanced (use existing account)</a> |
|||
<br> |
|||
<br> |
|||
--> |
|||
<button type="submit">Next</button> |
|||
</form> |
|||
|
|||
<!-- Step 3 Set Challanges --> |
|||
<form class="js-acme-form js-acme-form-challenges"> |
|||
|
|||
<h1>How will you validate your domain?</h1> |
|||
<br> |
|||
<label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="http-01" checked required> |
|||
File Upload to HTTP Web Server</label> |
|||
<br> |
|||
<label><input class="js-acme-challenge-type" name="acme-challenge-type" type="radio" value="dns-01" required> |
|||
TXT Records on DNS Name Server</label> |
|||
<br> |
|||
|
|||
<div class="js-acme-challenges"> |
|||
|
|||
<h2>Verify Domains & Sub-Domains</h2> |
|||
|
|||
<table class="js-acme-table-http-01"> |
|||
<thead> |
|||
<tr> |
|||
<th>Hostname</th> |
|||
<th>File Location</th> |
|||
<th>File Contents</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td>example.com</td> |
|||
<td>.well-known/acme-challenge/xxx</td> |
|||
<td>sec.ret</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
|
|||
<table class="js-acme-table-dns-01"> |
|||
<thead> |
|||
<tr> |
|||
<th>Hostname</th> |
|||
<th>TXT Host</th> |
|||
<th>TXT Value</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td>example.com</td> |
|||
<td>_acme-challenge.example.com</td> |
|||
<td>4A54</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
|
|||
<div class="js-acme-wildcard"> |
|||
<h2>Verify Wildcard Domains</h2> |
|||
|
|||
<table class="js-acme-table-wildcard"> |
|||
<thead> |
|||
<tr> |
|||
<th>Hostname</th> |
|||
<th>TXT Host</th> |
|||
<th>TXT Value</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td>example.com</td> |
|||
<td>_acme-challenge.example.com</td> |
|||
<td>4A54</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
|
|||
<button type="submit">Next</button> |
|||
</form> |
|||
|
|||
<!-- Step 4 Process Challanges --> |
|||
<form class="js-acme-form js-acme-form-poll"> |
|||
Verifying Domains... (give us 5 seconds or so...) |
|||
|
|||
<!-- |
|||
<table class="js-acme-table-verifying"> |
|||
<thead> |
|||
<tr> |
|||
<th>Hostname</th> |
|||
<th>Type</th> |
|||
<th>Pass</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td>example.com</td> |
|||
<td>http-01</td> |
|||
<td>-</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
|
|||
<a href="#">advanced (use existing keypair for domain)</a> |
|||
|
|||
<button type="submit">Next</button> |
|||
--> |
|||
</form> |
|||
|
|||
<!-- Step 5 Get Certs --> |
|||
<form class="js-acme-form js-acme-form-download"> |
|||
<div> |
|||
<h2><label>privkey.pem</label></h2> |
|||
<textarea cols="80" rows="10" class="js-privkey">-</textarea> |
|||
</div> |
|||
|
|||
<div> |
|||
<h2><label>fullchain.pem</label></h2> |
|||
<textarea cols="80" rows="60" class="js-fullchain">-</textarea> |
|||
</div> |
|||
|
|||
<div> |
|||
<h3>node.js https server example</h3> |
|||
<pre><code>'use strict'; |
|||
|
|||
var https = require('https'); |
|||
var server = https.createServer({ |
|||
key: require('fs').readFileSync('./privkey.pem') |
|||
, cert: require('fs').readFileSync('./fullchain.pem') |
|||
}, function (req, res) { |
|||
res.end("Hello, World!"); |
|||
}).listen(443, function () { |
|||
console.log('Listening on', this.address()); |
|||
}) |
|||
</code></pre> |
|||
</div> |
|||
|
|||
<!-- |
|||
TODO |
|||
<label>cert.pem</label> |
|||
<textarea class="js-cert">-</textarea> |
|||
|
|||
<label>chain.pem</label> |
|||
<textarea class="js-chain">-</textarea> |
|||
|
|||
<button type="button">Download SSL Certificates</button> |
|||
<br> |
|||
<a href="#">Advanced (copy and paste)</a> |
|||
<br> |
|||
<button type="submit">Start Over</button> |
|||
--> |
|||
</form> |
|||
|
|||
<br> |
|||
<br> |
|||
<br> |
|||
<div><small> |
|||
<h3></h3> |
|||
<a href="https://git.coolaj86.com/coolaj86/greenlock.html">View Source</a> (git) |
|||
<!-- or |
|||
<pre><code>git clone https://git.coolaj86.com/coolaj86/greenlock.html.git</code></pre> |
|||
Or view the live site code (same as live-site branch): |
|||
<pre><code>wget https://greenlock.ppl.family --mirror --convert-links --adjust-extension --page-requisites --no-parent</code></pre> |
|||
--> |
|||
</small></div> |
|||
|
|||
<script src="./js/bacme.js"></script> |
|||
<script src="./js/app.js"></script> |
|||
|
|||
<script src="./js/pkijs.org/v1.3.33/common.js"></script> |
|||
<script src="./js/pkijs.org/v1.3.33/asn1.js"></script> |
|||
<script src="./js/pkijs.org/v1.3.33/x509_schema.js"></script> |
|||
<script src="./js/pkijs.org/v1.3.33/x509_simpl.js"></script> |
|||
<script src="./js/browser-csr/v1.0.0-alpha/csr.js"></script> |
|||
|
|||
<!-- Global site tag (gtag.js) - Google Analytics --> |
|||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-118745161-2"></script> |
|||
<script> |
|||
window.dataLayer = window.dataLayer || []; |
|||
function gtag(){dataLayer.push(arguments);} |
|||
gtag('js', new Date()); |
|||
|
|||
gtag('config', 'UA-118745161-2'); |
|||
</script> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
</html> |
@ -0,0 +1,514 @@ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
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 apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory'; |
|||
function updateApiType() { |
|||
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 submitForm(ev) { |
|||
var j = i; |
|||
i += 1; |
|||
steps[j].submit(ev); |
|||
} |
|||
$qsa('.js-acme-form').forEach(function ($el) { |
|||
$el.addEventListener('submit', function (ev) { |
|||
ev.preventDefault(); |
|||
submitForm(ev); |
|||
}); |
|||
}); |
|||
function updateChallengeType() { |
|||
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; |
|||
$qs('.js-acme-table-dns-01').hidden = true; |
|||
if (info.challenges.wildcard) { |
|||
$qs('.js-acme-table-wildcard').hidden = false; |
|||
} |
|||
if (info.challenges[input.value]) { |
|||
$qs('.js-acme-table-' + 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.ppl.family/api/ppl.family/public/list', { |
|||
method: 'POST' |
|||
, cors: true |
|||
, headers: new Headers({ 'Content-Type': 'application/json' }) |
|||
, body: JSON.stringify({ address: email, comment: 'greenlock sub for ' + domains.join(',') }) |
|||
}).then(function (resp) { |
|||
return resp.json().then(function (data) { |
|||
/* |
|||
if (data.error) { |
|||
window.alert("Couldn't save your contact. Email coolaj86@gmail.com instead."); |
|||
return; |
|||
} |
|||
*/ |
|||
}); |
|||
}, function () { |
|||
/* |
|||
window.alert("Didn't get your contact. Bad network connection? Email coolaj86@gmail.com instead."); |
|||
*/ |
|||
}); |
|||
} |
|||
|
|||
steps[1] = function () { |
|||
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() }; |
|||
}); |
|||
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 () { |
|||
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() { |
|||
return BACME.accounts.generateKeypair({ |
|||
type: 'ECDSA' |
|||
, bitlength: '256' |
|||
}).then(function (jwk) { |
|||
localStorage.setItem('account:' + email, JSON.stringify(jwk)); |
|||
return jwk; |
|||
}) |
|||
} |
|||
|
|||
if (jwk) { |
|||
p = Promise.resolve(jwk); |
|||
} else { |
|||
p = 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 = Promise.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': [] }; |
|||
var map = { |
|||
'http-01': '.js-acme-table-http-01' |
|||
, 'dns-01': '.js-acme-table-dns-01' |
|||
, 'wildcard': '.js-acme-table-wildcard' |
|||
} |
|||
var tpls = {}; |
|||
info.challenges = obj; |
|||
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 Promise.all(claims.map(function (claim) { |
|||
var hostname = claim.identifier.value; |
|||
return Promise.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); |
|||
$qs(map.wildcard).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.dnsHost + '</td><td>' + data.dnsAnswer + '</td></tr>'; |
|||
} else if(obj[data.type]) { |
|||
|
|||
obj[data.type].push(data); |
|||
if ('dns-01' === data.type) { |
|||
$qs(map[data.type]).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.dnsHost + '</td><td>' + data.dnsAnswer + '</td></tr>'; |
|||
} else if ('http-01' === data.type) { |
|||
$qs(map[data.type]).innerHTML += '<tr><td>' + data.hostname + '</td><td>' + data.httpPath + '</td><td>' + data.httpAuth + '</td></tr>'; |
|||
} |
|||
} |
|||
|
|||
}); |
|||
|
|||
})); |
|||
})).then(function () { |
|||
|
|||
// hide wildcard if no wildcard
|
|||
// hide http-01 and dns-01 if only wildcard
|
|||
if (!obj.wildcard.length) { |
|||
$qs('.js-acme-wildcard').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 \'' + i + '\' Error:'); |
|||
console.error(err); |
|||
}); |
|||
}; |
|||
|
|||
steps[3] = function () { |
|||
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 Promise(function (resolve) { |
|||
setTimeout(resolve, 1000); |
|||
}).then(function () { |
|||
return Promise.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 ('valid' !== poll.status) { |
|||
allsWell = false; |
|||
console.warn('BAD POLL STATUS', poll); |
|||
} |
|||
// TODO show status in HTML
|
|||
}); |
|||
|
|||
if (polls.length) { |
|||
return checkPolls(); |
|||
} |
|||
return true; |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
return checkPolls().then(function () { |
|||
if (allsWell) { |
|||
return submitForm(); |
|||
} |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
// spinner
|
|||
steps[4] = function () { |
|||
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() { |
|||
return BACME.accounts.generateKeypair({ |
|||
type: 'ECDSA' |
|||
, bitlength: '256' |
|||
}).then(function (serverJwk) { |
|||
localStorage.setItem('server:' + key, JSON.stringify(serverJwk)); |
|||
return serverJwk; |
|||
}) |
|||
} |
|||
|
|||
if (serverJwk) { |
|||
p = Promise.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 Promise(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').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); |
|||
}); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
steps[5] = function () { |
|||
hideForms(); |
|||
$qs('.js-acme-form-download').hidden = false; |
|||
} |
|||
|
|||
steps[1](); |
|||
|
|||
$qs('body').hidden = false; |
|||
}()); |
Binary file not shown.
Binary file not shown.
@ -1,14 +1,14 @@ |
|||
#!/bin/bash |
|||
|
|||
mkdir -p js/pkijs.org/v1.3.33/ |
|||
pushd js/pkijs.org/v1.3.33/ |
|||
mkdir -p app/js/pkijs.org/v1.3.33/ |
|||
pushd app/js/pkijs.org/v1.3.33/ |
|||
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/common.js |
|||
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_schema.js |
|||
wget -c https://raw.githubusercontent.com/PeculiarVentures/PKI.js/41b63af760cacb565dd850fb3466ada4ca163eff/org/pkijs/x509_simpl.js |
|||
wget -c https://raw.githubusercontent.com/PeculiarVentures/ASN1.js/f7181c21c61e53a940ea24373ab489ad86d51bc1/org/pkijs/asn1.js |
|||
popd |
|||
|
|||
mkdir -p js/browser-csr/v1.0.0-alpha/ |
|||
pushd js/browser-csr/v1.0.0-alpha/ |
|||
mkdir -p app/js/browser-csr/v1.0.0-alpha/ |
|||
pushd app/js/browser-csr/v1.0.0-alpha/ |
|||
wget -c https://git.coolaj86.com/coolaj86/browser-csr.js/raw/commit/01cdc0e91b5bf03f12e1b25b4129e3cde927987c/csr.js |
|||
popd |
|||
|