From 07f75f8e430c4eec022f7bf8db4bb9af67bd838d Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 4 May 2019 00:59:47 -0600 Subject: [PATCH] working RSA CSR! --- app.js | 158 +++++++++++++++++++++++--------------- index.html | 14 +++- lib/bluecrypt-encoding.js | 2 + lib/csr.js | 78 +++++++------------ 4 files changed, 137 insertions(+), 115 deletions(-) diff --git a/app.js b/app.js index 28c9e63..fc3d9da 100644 --- a/app.js +++ b/app.js @@ -6,7 +6,9 @@ var Rasha = window.Rasha; var Eckles = window.Eckles; var x509 = window.x509; + var CSR = window.CSR; var ACME = window.ACME; + var accountStuff = {}; function $(sel) { return document.querySelector(sel); @@ -15,6 +17,11 @@ return Array.prototype.slice.call(document.querySelectorAll(sel)); } + function checkTos(tos) { + console.log("TODO checkbox for agree to terms"); + return tos; + } + function run() { console.log('hello'); @@ -101,6 +108,9 @@ $$('input').map(function ($el) { $el.disabled = false; }); $$('button').map(function ($el) { $el.disabled = false; }); $('.js-toc-jwk').hidden = false; + + $('.js-create-account').hidden = false; + $('.js-create-csr').hidden = false; }); }); @@ -115,74 +125,17 @@ console.log('acme result', result); var privJwk = JSON.parse($('.js-jwk').innerText).private; var email = $('.js-email').value; - function checkTos(tos) { - console.log("TODO checkbox for agree to terms"); - return tos; - } return acme.accounts.create({ email: email , agreeToTerms: checkTos , accountKeypair: { privateKeyJwk: privJwk } }).then(function (account) { console.log("account created result:", account); - return Keypairs.generate({ - kty: 'RSA' - , modulusLength: 2048 - }).then(function (pair) { - console.log('domain keypair:', pair); - var domains = ($('.js-domains').value||'example.com').split(/[, ]+/g); - return acme.certificates.create({ - accountKeypair: { privateKeyJwk: privJwk } - , account: account - , domainKeypair: { privateKeyJwk: pair.private } - , email: email - , domains: domains - , agreeToTerms: checkTos - , challenges: { - 'dns-01': { - set: function (opts) { - console.info('dns-01 set challenge:'); - console.info('TXT', opts.dnsHost); - console.info(opts.dnsAuthorization); - return new Promise(function (resolve) { - while (!window.confirm("Did you set the challenge?")) {} - resolve(); - }); - } - , remove: function (opts) { - console.log('dns-01 remove challenge:'); - console.info('TXT', opts.dnsHost); - console.info(opts.dnsAuthorization); - return new Promise(function (resolve) { - while (!window.confirm("Did you delete the challenge?")) {} - resolve(); - }); - } - } - , 'http-01': { - set: function (opts) { - console.info('http-01 set challenge:'); - console.info(opts.challengeUrl); - console.info(opts.keyAuthorization); - return new Promise(function (resolve) { - while (!window.confirm("Did you set the challenge?")) {} - resolve(); - }); - } - , remove: function (opts) { - console.log('http-01 remove challenge:'); - console.info(opts.challengeUrl); - console.info(opts.keyAuthorization); - return new Promise(function (resolve) { - while (!window.confirm("Did you delete the challenge?")) {} - resolve(); - }); - } - } - } - , challengeTypes: [$('input[name="acme-challenge-type"]:checked').value] - }); - }); + 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); @@ -191,8 +144,87 @@ }); }); + $('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({ + kty: 'RSA' + , modulusLength: 2048 + }).then(function (pair) { + console.log('domain keypair:', pair); + var domains = ($('.js-domains').value||'example.com').split(/[, ]+/g); + return acme.certificates.create({ + accountKeypair: { privateKeyJwk: privJwk } + , account: account + , domainKeypair: { privateKeyJwk: pair.private } + , email: email + , domains: domains + , agreeToTerms: checkTos + , challenges: { + 'dns-01': { + set: function (opts) { + console.info('dns-01 set challenge:'); + console.info('TXT', opts.dnsHost); + console.info(opts.dnsAuthorization); + return new Promise(function (resolve) { + while (!window.confirm("Did you set the challenge?")) {} + resolve(); + }); + } + , remove: function (opts) { + console.log('dns-01 remove challenge:'); + console.info('TXT', opts.dnsHost); + console.info(opts.dnsAuthorization); + return new Promise(function (resolve) { + while (!window.confirm("Did you delete the challenge?")) {} + resolve(); + }); + } + } + , 'http-01': { + set: function (opts) { + console.info('http-01 set challenge:'); + console.info(opts.challengeUrl); + console.info(opts.keyAuthorization); + return new Promise(function (resolve) { + while (!window.confirm("Did you set the challenge?")) {} + resolve(); + }); + } + , remove: function (opts) { + console.log('http-01 remove challenge:'); + console.info(opts.challengeUrl); + console.info(opts.keyAuthorization); + return new Promise(function (resolve) { + while (!window.confirm("Did you delete the challenge?")) {} + resolve(); + }); + } + } + } + , challengeTypes: [$('input[name="acme-challenge-type"]:checked').value] + }); + }); + }); + $('.js-generate').hidden = false; - $('.js-create-account').hidden = false; } window.addEventListener('load', run); diff --git a/index.html b/index.html index f984d04..4379cdb 100644 --- a/index.html +++ b/index.html @@ -58,15 +58,26 @@
+ + + +

Certificate Signing Request

+

+ +
+ +

ACME Certificate Order

+
+ Challenge type:
- +
@@ -117,6 +128,7 @@ + diff --git a/lib/bluecrypt-encoding.js b/lib/bluecrypt-encoding.js index 7dc1073..c2473a6 100644 --- a/lib/bluecrypt-encoding.js +++ b/lib/bluecrypt-encoding.js @@ -110,6 +110,8 @@ Enc.binToHex = function (bin) { return h; }).join(''); }; +// TODO are there any nuance differences here? +Enc.utf8ToHex = Enc.binToHex; Enc.hexToBase64 = function (hex) { return btoa(Enc.hexToBin(hex)); diff --git a/lib/csr.js b/lib/csr.js index aab4763..bd21fa3 100644 --- a/lib/csr.js +++ b/lib/csr.js @@ -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'; -var crypto = require('crypto'); -var ASN1 = require('./asn1.js'); -var Enc = require('./encoding.js'); -var PEM = require('./pem.js'); -var X509 = require('./x509.js'); -var RSA = {}; +var ASN1 = exports.ASN1; +var Enc = exports.Enc; +var PEM = exports.PEM; +var X509 = exports.x509; +var Keypairs = exports.Keypairs; -/*global Promise*/ -var CSR = module.exports = function rsacsr(opts) { +// TODO find a way that the prior node-ish way of `module.exports = function () {}` isn't broken +var CSR = exports.CSR = function (opts) { // 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 opts = CSR._prepare(opts); @@ -69,11 +73,7 @@ CSR._prepare = function (opts) { opts.jwk = jwk; 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) { if ('der' === (opts.encoding||'').toLowerCase()) { 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) { var hex = CSR.request(opts.jwk, opts.domains); 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) { - var asn1pub = X509.packCsrPublicKey(jwk); - return X509.packCsr(asn1pub, domains); + var asn1pub = X509.packCsrRsaPublicKey(jwk); + 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) { - var keypem = PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: X509.packPkcs1(jwk) }); - return RSA.sign(keypem, Enc.hexToBuf(request)).then(function (sig) { + // Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a + // 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 }); }); }; + + CSR.toDer = function encode(opts) { var sty = ASN1('30' // 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) { return ASN1('30' // Version (0) @@ -211,3 +185,5 @@ X509.packCsrRsaPublicKey = function (jwk) { // Add the CSR pub key header return ASN1('30', ASN1('30', ASN1('06', '2a864886f70d010101'), ASN1('05')), ASN1.BitStr(asn1pub)); }; + +}('undefined' === typeof window ? module.exports : window));