WIP Building out all features necessary for Let's Encrypt #6

Closed
coolaj86 wants to merge 17 commits from more-acme into master
4 changed files with 137 additions and 115 deletions
Showing only changes of commit 07f75f8e43 - Show all commits

54
app.js
View File

@ -6,7 +6,9 @@
var Rasha = window.Rasha; var Rasha = window.Rasha;
var Eckles = window.Eckles; var Eckles = window.Eckles;
var x509 = window.x509; var x509 = window.x509;
var CSR = window.CSR;
var ACME = window.ACME; var ACME = window.ACME;
var accountStuff = {};
function $(sel) { function $(sel) {
return document.querySelector(sel); return document.querySelector(sel);
@ -15,6 +17,11 @@
return Array.prototype.slice.call(document.querySelectorAll(sel)); return Array.prototype.slice.call(document.querySelectorAll(sel));
} }
function checkTos(tos) {
console.log("TODO checkbox for agree to terms");
return tos;
}
function run() { function run() {
console.log('hello'); console.log('hello');
@ -101,6 +108,9 @@
$$('input').map(function ($el) { $el.disabled = false; }); $$('input').map(function ($el) { $el.disabled = false; });
$$('button').map(function ($el) { $el.disabled = false; }); $$('button').map(function ($el) { $el.disabled = false; });
$('.js-toc-jwk').hidden = false; $('.js-toc-jwk').hidden = false;
$('.js-create-account').hidden = false;
$('.js-create-csr').hidden = false;
}); });
}); });
@ -115,16 +125,45 @@
console.log('acme result', result); console.log('acme result', result);
var privJwk = JSON.parse($('.js-jwk').innerText).private; var privJwk = JSON.parse($('.js-jwk').innerText).private;
var email = $('.js-email').value; var email = $('.js-email').value;
function checkTos(tos) {
console.log("TODO checkbox for agree to terms");
return tos;
}
return acme.accounts.create({ return acme.accounts.create({
email: email email: email
, agreeToTerms: checkTos , agreeToTerms: checkTos
, accountKeypair: { privateKeyJwk: privJwk } , accountKeypair: { privateKeyJwk: privJwk }
}).then(function (account) { }).then(function (account) {
console.log("account created result:", account); console.log("account created result:", account);
accountStuff.account = account;
accountStuff.privateJwk = privJwk;
accountStuff.email = email;
accountStuff.acme = acme;
$('.js-create-order').hidden = false;
}).catch(function (err) {
console.error("A bad thing happened:");
console.error(err);
window.alert(err.message || JSON.stringify(err, null, 2));
});
});
});
$('form.js-csr').addEventListener('submit', function (ev) {
ev.preventDefault();
ev.stopPropagation();
var domains = ($('.js-domains').value||'example.com').split(/[, ]+/g);
var privJwk = JSON.parse($('.js-jwk').innerText).private;
return CSR({ jwk: privJwk, domains: domains }).then(function (web64) {
// Verify with https://www.sslshopper.com/csr-decoder.html
console.log('urlBase64 CSR:');
console.log(web64);
});
});
$('form.js-acme-order').addEventListener('submit', function (ev) {
ev.preventDefault();
ev.stopPropagation();
var account = accountStuff.account;
var privJwk = accountStuff.privateJwk;
var email = accountStuff.email;
var acme = accountStuff.acme;
return Keypairs.generate({ return Keypairs.generate({
kty: 'RSA' kty: 'RSA'
, modulusLength: 2048 , modulusLength: 2048
@ -183,16 +222,9 @@
, challengeTypes: [$('input[name="acme-challenge-type"]:checked').value] , challengeTypes: [$('input[name="acme-challenge-type"]:checked').value]
}); });
}); });
}).catch(function (err) {
console.error("A bad thing happened:");
console.error(err);
window.alert(err.message || JSON.stringify(err, null, 2));
});
});
}); });
$('.js-generate').hidden = false; $('.js-generate').hidden = false;
$('.js-create-account').hidden = false;
} }
window.addEventListener('load', run); window.addEventListener('load', run);

View File

@ -58,15 +58,26 @@
<label for="-acmeEmail">Email:</label> <label for="-acmeEmail">Email:</label>
<input class="js-email" type="email" id="-acmeEmail"> <input class="js-email" type="email" id="-acmeEmail">
<br> <br>
<button class="js-create-account" hidden>Create Account</button>
</form>
<h2>Certificate Signing Request</h2>
<form class="js-csr">
<label for="-acmeDomains">Domains:</label> <label for="-acmeDomains">Domains:</label>
<input class="js-domains" type="text" id="-acmeDomains"> <input class="js-domains" type="text" id="-acmeDomains">
<br> <br>
<button class="js-create-csr" hidden>Create CSR</button>
</form>
<h2>ACME Certificate Order</h2>
<form class="js-acme-order">
Challenge type:
<label for="-http01"><input type="radio" id="-http01" <label for="-http01"><input type="radio" id="-http01"
name="acme-challenge-type" value="http-01" checked>http-01</label> name="acme-challenge-type" value="http-01" checked>http-01</label>
<label for="-dns01"><input type="radio" id="-dns01" <label for="-dns01"><input type="radio" id="-dns01"
name="acme-challenge-type" value="dns-01">dns-01</label> name="acme-challenge-type" value="dns-01">dns-01</label>
<br> <br>
<button class="js-create-account" hidden>Create Account</button> <button class="js-create-order" hidden>Create Order</button>
</form> </form>
<div class="js-loading" hidden>Loading</div> <div class="js-loading" hidden>Loading</div>
@ -117,6 +128,7 @@
<script src="./lib/ecdsa.js"></script> <script src="./lib/ecdsa.js"></script>
<script src="./lib/rsa.js"></script> <script src="./lib/rsa.js"></script>
<script src="./lib/keypairs.js"></script> <script src="./lib/keypairs.js"></script>
<script src="./lib/csr.js"></script>
<script src="./lib/acme.js"></script> <script src="./lib/acme.js"></script>
<script src="./app.js"></script> <script src="./app.js"></script>
</body> </body>

View File

@ -110,6 +110,8 @@ Enc.binToHex = function (bin) {
return h; return h;
}).join(''); }).join('');
}; };
// TODO are there any nuance differences here?
Enc.utf8ToHex = Enc.binToHex;
Enc.hexToBase64 = function (hex) { Enc.hexToBase64 = function (hex) {
return btoa(Enc.hexToBin(hex)); return btoa(Enc.hexToBin(hex));

View File

@ -1,14 +1,18 @@
// Copyright 2018-present AJ ONeal. All rights reserved
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
(function (exports) {
'use strict'; 'use strict';
var crypto = require('crypto'); var ASN1 = exports.ASN1;
var ASN1 = require('./asn1.js'); var Enc = exports.Enc;
var Enc = require('./encoding.js'); var PEM = exports.PEM;
var PEM = require('./pem.js'); var X509 = exports.x509;
var X509 = require('./x509.js'); var Keypairs = exports.Keypairs;
var RSA = {};
/*global Promise*/ // TODO find a way that the prior node-ish way of `module.exports = function () {}` isn't broken
var CSR = module.exports = function rsacsr(opts) { var CSR = exports.CSR = function (opts) {
// We're using a Promise here to be compatible with the browser version // We're using a Promise here to be compatible with the browser version
// which will probably use the webcrypto API for some of the conversions // which will probably use the webcrypto API for some of the conversions
opts = CSR._prepare(opts); opts = CSR._prepare(opts);
@ -69,11 +73,7 @@ CSR._prepare = function (opts) {
opts.jwk = jwk; opts.jwk = jwk;
return opts; return opts;
}; };
CSR.sync = function (opts) {
opts = CSR._prepare(opts);
var bytes = CSR.createSync(opts);
return CSR._encode(opts, bytes);
};
CSR._encode = function (opts, bytes) { CSR._encode = function (opts, bytes) {
if ('der' === (opts.encoding||'').toLowerCase()) { if ('der' === (opts.encoding||'').toLowerCase()) {
return bytes; return bytes;
@ -84,11 +84,6 @@ CSR._encode = function (opts, bytes) {
}); });
}; };
CSR.createSync = function createCsr(opts) {
var hex = CSR.request(opts.jwk, opts.domains);
var csr = CSR.signSync(opts.jwk, hex);
return Enc.hexToBuf(csr);
};
CSR.create = function createCsr(opts) { CSR.create = function createCsr(opts) {
var hex = CSR.request(opts.jwk, opts.domains); var hex = CSR.request(opts.jwk, opts.domains);
return CSR.sign(opts.jwk, hex).then(function (csr) { return CSR.sign(opts.jwk, hex).then(function (csr) {
@ -96,22 +91,25 @@ CSR.create = function createCsr(opts) {
}); });
}; };
//
// RSA
//
//
CSR.request = function createCsrBodyEc(jwk, domains) { CSR.request = function createCsrBodyEc(jwk, domains) {
var asn1pub = X509.packCsrPublicKey(jwk); var asn1pub = X509.packCsrRsaPublicKey(jwk);
return X509.packCsr(asn1pub, domains); return X509.packCsrRsa(asn1pub, domains);
}; };
CSR.signSync = function csrEcSig(jwk, request) {
var keypem = PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: X509.packPkcs1(jwk) });
var sig = RSA.signSync(keypem, Enc.hexToBuf(request));
return CSR.toDer({ request: request, signature: sig });
};
CSR.sign = function csrEcSig(jwk, request) { CSR.sign = function csrEcSig(jwk, request) {
var keypem = PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: X509.packPkcs1(jwk) }); // Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
return RSA.sign(keypem, Enc.hexToBuf(request)).then(function (sig) { // TODO will have to convert web ECDSA signatures to PEM ECDSA signatures (but RSA should be the same)
// TODO have a consistent non-private way to sign
return Keypairs._sign({ jwk: jwk }, Enc.hexToBuf(request)).then(function (sig) {
return CSR.toDer({ request: request, signature: sig }); return CSR.toDer({ request: request, signature: sig });
}); });
}; };
CSR.toDer = function encode(opts) { CSR.toDer = function encode(opts) {
var sty = ASN1('30' var sty = ASN1('30'
// 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1) // 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
@ -128,30 +126,6 @@ CSR.toDer = function encode(opts) {
); );
}; };
//
// RSA
//
// Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
RSA.signSync = function signRsaSync(keypem, ab) {
// Signer is a stream
var sign = crypto.createSign('SHA256');
sign.write(new Uint8Array(ab));
sign.end();
// The signature is ASN1 encoded, as it turns out
var sig = sign.sign(keypem);
// Convert to a JavaScript ArrayBuffer just because
return new Uint8Array(sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength));
};
RSA.sign = function signRsa(keypem, ab) {
return Promise.resolve().then(function () {
return RSA.signSync(keypem, ab);
});
};
X509.packCsrRsa = function (asn1pubkey, domains) { X509.packCsrRsa = function (asn1pubkey, domains) {
return ASN1('30' return ASN1('30'
// Version (0) // Version (0)
@ -211,3 +185,5 @@ X509.packCsrRsaPublicKey = function (jwk) {
// Add the CSR pub key header // Add the CSR pub key header
return ASN1('30', ASN1('30', ASN1('06', '2a864886f70d010101'), ASN1('05')), ASN1.BitStr(asn1pub)); return ASN1('30', ASN1('30', ASN1('06', '2a864886f70d010101'), ASN1('05')), ASN1.BitStr(asn1pub));
}; };
}('undefined' === typeof window ? module.exports : window));