display all ASN.1/x509 formats

This commit is contained in:
AJ ONeal 2019-04-27 00:34:49 -06:00
parent b2174e3923
commit 76621560cb
5 changed files with 209 additions and 126 deletions

69
app.js
View File

@ -2,6 +2,9 @@
'use strict'; 'use strict';
var Keypairs = window.Keypairs; var Keypairs = window.Keypairs;
var Rasha = window.Rasha;
var Eckles = window.Eckles;
var x509 = window.x509;
function $(sel) { function $(sel) {
return document.querySelector(sel); return document.querySelector(sel);
@ -35,9 +38,10 @@
$('.js-loading').hidden = false; $('.js-loading').hidden = false;
$('.js-jwk').hidden = true; $('.js-jwk').hidden = true;
$('.js-toc-der-public').hidden = true; $('.js-toc-der-public').hidden = true;
$('.js-toc-pem-public').hidden = true;
$('.js-toc-der-private').hidden = true; $('.js-toc-der-private').hidden = true;
$('.js-toc-pem-private').hidden = true; $$('.js-toc-pem').forEach(function ($el) {
$el.hidden = true;
});
$$('input').map(function ($el) { $el.disabled = true; }); $$('input').map(function ($el) { $el.disabled = true; });
$$('button').map(function ($el) { $el.disabled = true; }); $$('button').map(function ($el) { $el.disabled = true; });
var opts = { var opts = {
@ -47,32 +51,47 @@
}; };
console.log('opts', opts); console.log('opts', opts);
Keypairs.generate(opts).then(function (results) { Keypairs.generate(opts).then(function (results) {
var der_public, der_private; var pubDer;
if (opts.kty == 'EC') { var privDer;
der_public = x509.packSpki(results.public); if (/EC/i.test(opts.kty)) {
der_private = x509.packPkcs8(results.private); privDer = x509.packPkcs8(results.private);
var pem_private = Eckles.export({ jwk: results.private }) pubDer = x509.packSpki(results.public);
var pem_public = Eckles.export({ jwk: results.public, public: true }) Eckles.export({ jwk: results.private, format: 'sec1' }).then(function (pem) {
$('.js-input-pem-public').innerText = pem_public; $('.js-input-pem-sec1-private').innerText = pem;
$('.js-toc-pem-public').hidden = false; $('.js-toc-pem-sec1-private').hidden = false;
$('.js-input-pem-private').innerText = pem_private; });
$('.js-toc-pem-private').hidden = false; Eckles.export({ jwk: results.private, format: 'pkcs8' }).then(function (pem) {
$('.js-input-pem-pkcs8-private').innerText = pem;
$('.js-toc-pem-pkcs8-private').hidden = false;
});
Eckles.export({ jwk: results.public, public: true }).then(function (pem) {
$('.js-input-pem-spki-public').innerText = pem;
$('.js-toc-pem-spki-public').hidden = false;
});
} else { } else {
der_private = x509.packPkcs8(results.private); privDer = x509.packPkcs8(results.private);
der_public = x509.packPkcs8(results.public); pubDer = x509.packSpki(results.public);
Rasha.pack({ jwk: results.private }).then(function (pem) { Rasha.export({ jwk: results.private, format: 'pkcs1' }).then(function (pem) {
$('.js-input-pem-private').innerText = pem; $('.js-input-pem-pkcs1-private').innerText = pem;
$('.js-toc-pem-private').hidden = false; $('.js-toc-pem-pkcs1-private').hidden = false;
}) });
Rasha.pack({ jwk: results.public }).then(function (pem) { Rasha.export({ jwk: results.private, format: 'pkcs8' }).then(function (pem) {
$('.js-input-pem-public').innerText = pem; $('.js-input-pem-pkcs8-private').innerText = pem;
$('.js-toc-pem-public').hidden = false; $('.js-toc-pem-pkcs8-private').hidden = false;
}) });
Rasha.export({ jwk: results.public, format: 'pkcs1' }).then(function (pem) {
$('.js-input-pem-pkcs1-public').innerText = pem;
$('.js-toc-pem-pkcs1-public').hidden = false;
});
Rasha.export({ jwk: results.public, format: 'spki' }).then(function (pem) {
$('.js-input-pem-spki-public').innerText = pem;
$('.js-toc-pem-spki-public').hidden = false;
});
} }
$('.js-der-public').innerText = der_public; $('.js-der-public').innerText = pubDer;
$('.js-toc-der-public').hidden = false; $('.js-toc-der-public').hidden = false;
$('.js-der-private').innerText = der_private; $('.js-der-private').innerText = privDer;
$('.js-toc-der-private').hidden = false; $('.js-toc-der-private').hidden = false;
$('.js-jwk').innerText = JSON.stringify(results, null, 2); $('.js-jwk').innerText = JSON.stringify(results, null, 2);
$('.js-loading').hidden = true; $('.js-loading').hidden = true;
@ -87,7 +106,7 @@
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
$('.js-loading').hidden = false; $('.js-loading').hidden = false;
ACME.accounts.create //ACME.accounts.create
}); });
$('.js-generate').hidden = false; $('.js-generate').hidden = false;

View File

@ -8,10 +8,10 @@
} }
/* need to word wrap the binary no space der */ /* need to word wrap the binary no space der */
.js-der-public, .js-der-private{ .js-der-public, .js-der-private{
white-space: pre-wrap; /* CSS3 */ white-space: pre-wrap; /* CSS3 */
white-space: -moz-pre-wrap; /* Firefox */ white-space: -moz-pre-wrap; /* Firefox */
white-space: -pre-wrap; /* Opera <7 */ white-space: -pre-wrap; /* Opera <7 */
white-space: -o-pre-wrap; /* Opera 7 */ white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* IE */ word-wrap: break-word; /* IE */
} }
</style> </style>
@ -80,13 +80,25 @@
<summary>DER Public Binary</summary> <summary>DER Public Binary</summary>
<pre><code class="js-der-public">&nbsp;</code></pre> <pre><code class="js-der-public">&nbsp;</code></pre>
</details> </details>
<details class="js-toc-pem-private" hidden> <details class="js-toc-pem js-toc-pem-pkcs1-private" hidden>
<summary>PEM Private (base64-encoded DER)</summary> <summary>PEM Private (base64-encoded PKCS1 DER)</summary>
<pre><code class="js-input-pem-private" ></code></pre> <pre><code class="js-input-pem-pkcs1-private" ></code></pre>
</details> </details>
<details class="js-toc-pem-public" hidden> <details class="js-toc-pem js-toc-pem-sec1-private" hidden>
<summary>PEM Public (base64-encoded DER)</summary> <summary>PEM Private (base64-encoded SEC1 DER)</summary>
<pre><code class="js-input-pem-public" ></code></pre> <pre><code class="js-input-pem-sec1-private" ></code></pre>
</details>
<details class="js-toc-pem js-toc-pem-pkcs8-private" hidden>
<summary>PEM Private (base64-encoded PKCS8 DER)</summary>
<pre><code class="js-input-pem-pkcs8-private" ></code></pre>
</details>
<details class="js-toc-pem js-toc-pem-pkcs1-public" hidden>
<summary>PEM Public (base64-encoded PKCS1 DER)</summary>
<pre><code class="js-input-pem-pkcs1-public" ></code></pre>
</details>
<details class="js-toc-pem js-toc-pem-spki-public" hidden>
<summary>PEM Public (base64-encoded SPKI/PKIX DER)</summary>
<pre><code class="js-input-pem-spki-public" ></code></pre>
</details> </details>
<details class="js-toc-acme-account-request" hidden> <details class="js-toc-acme-account-request" hidden>
<summary>ACME Account Request</summary> <summary>ACME Account Request</summary>
@ -97,9 +109,9 @@
<pre><code class="js-acme-account-response">&nbsp;</code></pre> <pre><code class="js-acme-account-response">&nbsp;</code></pre>
</details> </details>
<script src="./lib/bluecrypt-encoding.js"></script> <script src="./lib/bluecrypt-encoding.js"></script>
<script src="./lib/ecdsa.js"></script>
<script src="./lib/asn1-packer.js"></script> <script src="./lib/asn1-packer.js"></script>
<script src="./lib/x509.js"></script> <script src="./lib/x509.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/acme.js"></script> <script src="./lib/acme.js"></script>

View File

@ -3,7 +3,10 @@
'use strict'; 'use strict';
var EC = exports.Eckles = {}; var EC = exports.Eckles = {};
var x509 = exports.x509;
if ('undefined' !== typeof module) { module.exports = EC; } if ('undefined' !== typeof module) { module.exports = EC; }
var PEM = exports.PEM;
var SSH = exports.SSH;
var Enc = {}; var Enc = {};
var textEncoder = new TextEncoder(); var textEncoder = new TextEncoder();
@ -32,7 +35,7 @@ EC.generate = function (opts) {
+ " Please choose either 'P-256' or 'P-384'. " + " Please choose either 'P-256' or 'P-384'. "
+ EC._stance)); + EC._stance));
} }
var extractable = true; var extractable = true;
return window.crypto.subtle.generateKey( return window.crypto.subtle.generateKey(
wcOpts wcOpts
@ -52,51 +55,53 @@ EC.generate = function (opts) {
}; };
EC.export = function (opts) { EC.export = function (opts) {
if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) { return Promise.resolve().then(function () {
throw new Error("must pass { jwk: jwk } as a JSON object"); if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) {
} throw new Error("must pass { jwk: jwk } as a JSON object");
var jwk = JSON.parse(JSON.stringify(opts.jwk)); }
var format = opts.format; var jwk = JSON.parse(JSON.stringify(opts.jwk));
if (opts.public || -1 !== [ 'spki', 'pkix', 'ssh', 'rfc4716' ].indexOf(format)) { var format = opts.format;
jwk.d = null; if (opts.public || -1 !== [ 'spki', 'pkix', 'ssh', 'rfc4716' ].indexOf(format)) {
} jwk.d = null;
if ('EC' !== jwk.kty) { }
throw new Error("options.jwk.kty must be 'EC' for EC keys"); if ('EC' !== jwk.kty) {
} throw new Error("options.jwk.kty must be 'EC' for EC keys");
if (!jwk.d) { }
if (!format || -1 !== [ 'spki', 'pkix' ].indexOf(format)) { if (!jwk.d) {
format = 'spki'; if (!format || -1 !== [ 'spki', 'pkix' ].indexOf(format)) {
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) { format = 'spki';
format = 'ssh'; } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
format = 'ssh';
} else {
throw new Error("options.format must be 'spki' or 'ssh' for public EC keys, not ("
+ typeof format + ") " + format);
}
} else { } else {
throw new Error("options.format must be 'spki' or 'ssh' for public EC keys, not (" if (!format || 'sec1' === format) {
+ typeof format + ") " + format); format = 'sec1';
} else if ('pkcs8' !== format) {
throw new Error("options.format must be 'sec1' or 'pkcs8' for private EC keys, not '" + format + "'");
}
} }
} else { if (-1 === [ 'P-256', 'P-384' ].indexOf(jwk.crv)) {
if (!format || 'sec1' === format) { throw new Error("options.jwk.crv must be either P-256 or P-384 for EC keys, not '" + jwk.crv + "'");
format = 'sec1'; }
} else if ('pkcs8' !== format) { if (!jwk.y) {
throw new Error("options.format must be 'sec1' or 'pkcs8' for private EC keys, not '" + format + "'"); throw new Error("options.jwk.y must be a urlsafe base64-encoded either P-256 or P-384");
} }
}
if (-1 === [ 'P-256', 'P-384' ].indexOf(jwk.crv)) {
throw new Error("options.jwk.crv must be either P-256 or P-384 for EC keys, not '" + jwk.crv + "'");
}
if (!jwk.y) {
throw new Error("options.jwk.y must be a urlsafe base64-encoded either P-256 or P-384");
}
if ('sec1' === format) { if ('sec1' === format) {
return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: x509.packSec1(jwk) }); return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: x509.packSec1(jwk) });
} else if ('pkcs8' === format) { } else if ('pkcs8' === format) {
return PEM.packBlock({ type: "PRIVATE KEY", bytes: x509.packPkcs8(jwk) }); return PEM.packBlock({ type: "PRIVATE KEY", bytes: x509.packPkcs8(jwk) });
} else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) { } else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) {
return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) }); return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) });
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) { } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
return SSH.packSsh(jwk); return SSH.packSsh(jwk);
} else { } else {
throw new Error("Sanity Error: reached unreachable code block with format: " + format); throw new Error("Sanity Error: reached unreachable code block with format: " + format);
} }
});
}; };
EC.pack = function (opts) { EC.pack = function (opts) {
return Promise.resolve().then(function () { return Promise.resolve().then(function () {

View File

@ -5,6 +5,8 @@
var RSA = exports.Rasha = {}; var RSA = exports.Rasha = {};
var x509 = exports.x509; var x509 = exports.x509;
if ('undefined' !== typeof module) { module.exports = RSA; } if ('undefined' !== typeof module) { module.exports = RSA; }
var PEM = exports.PEM;
var SSH = exports.SSH;
var Enc = {}; var Enc = {};
var textEncoder = new TextEncoder(); var textEncoder = new TextEncoder();
@ -108,55 +110,57 @@ RSA.thumbprint = function (opts) {
}; };
RSA.export = function (opts) { RSA.export = function (opts) {
if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) { return Promise.resolve().then(function () {
throw new Error("must pass { jwk: jwk }"); if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) {
} throw new Error("must pass { jwk: jwk }");
var jwk = JSON.parse(JSON.stringify(opts.jwk)); }
var format = opts.format; var jwk = JSON.parse(JSON.stringify(opts.jwk));
var pub = opts.public; var format = opts.format;
if (pub || -1 !== [ 'spki', 'pkix', 'ssh', 'rfc4716' ].indexOf(format)) { var pub = opts.public;
jwk = RSA.nueter(jwk); if (pub || -1 !== [ 'spki', 'pkix', 'ssh', 'rfc4716' ].indexOf(format)) {
} jwk = RSA.neuter({ jwk: jwk });
if ('RSA' !== jwk.kty) { }
throw new Error("options.jwk.kty must be 'RSA' for RSA keys"); if ('RSA' !== jwk.kty) {
} throw new Error("options.jwk.kty must be 'RSA' for RSA keys");
if (!jwk.p) { }
// TODO test for n and e if (!jwk.p) {
pub = true; // TODO test for n and e
if (!format || 'pkcs1' === format) { pub = true;
format = 'pkcs1'; if (!format || 'pkcs1' === format) {
} else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) { format = 'pkcs1';
format = 'spki'; } else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) {
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) { format = 'spki';
format = 'ssh'; } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
format = 'ssh';
} else {
throw new Error("options.format must be 'spki', 'pkcs1', or 'ssh' for public RSA keys, not ("
+ typeof format + ") " + format);
}
} else { } else {
throw new Error("options.format must be 'spki', 'pkcs1', or 'ssh' for public RSA keys, not (" // TODO test for all necessary keys (d, p, q ...)
+ typeof format + ") " + format); if (!format || 'pkcs1' === format) {
format = 'pkcs1';
} else if ('pkcs8' !== format) {
throw new Error("options.format must be 'pkcs1' or 'pkcs8' for private RSA keys");
}
} }
} else {
// TODO test for all necessary keys (d, p, q ...)
if (!format || 'pkcs1' === format) {
format = 'pkcs1';
} else if ('pkcs8' !== format) {
throw new Error("options.format must be 'pkcs1' or 'pkcs8' for private RSA keys");
}
}
if ('pkcs1' === format) { if ('pkcs1' === format) {
if (jwk.d) { if (jwk.d) {
return PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: x509.packPkcs1(jwk) }); return PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: x509.packPkcs1(jwk) });
} else {
return PEM.packBlock({ type: "RSA PUBLIC KEY", bytes: x509.packPkcs1(jwk) });
}
} else if ('pkcs8' === format) {
return PEM.packBlock({ type: "PRIVATE KEY", bytes: x509.packPkcs8(jwk) });
} else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) {
return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) });
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
return SSH.pack({ jwk: jwk, comment: opts.comment });
} else { } else {
return PEM.packBlock({ type: "RSA PUBLIC KEY", bytes: x509.packPkcs1(jwk) }); throw new Error("Sanity Error: reached unreachable code block with format: " + format);
} }
} else if ('pkcs8' === format) { });
return PEM.packBlock({ type: "PRIVATE KEY", bytes: x509.packPkcs8(jwk) });
} else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) {
return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) });
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
return SSH.pack({ jwk: jwk, comment: opts.comment });
} else {
throw new Error("Sanity Error: reached unreachable code block with format: " + format);
}
}; };
RSA.pack = function (opts) { RSA.pack = function (opts) {
// wrapped in a promise for API compatibility // wrapped in a promise for API compatibility

View File

@ -58,11 +58,11 @@
x509.packPkcs1 = function (jwk) { x509.packPkcs1 = function (jwk) {
var n = ASN1.UInt(Enc.base64ToHex(jwk.n)); var n = ASN1.UInt(Enc.base64ToHex(jwk.n));
var e = ASN1.UInt(Enc.base64ToHex(jwk.e)); var e = ASN1.UInt(Enc.base64ToHex(jwk.e));
if (!jwk.d) { if (!jwk.d) {
return Enc.hexToBuf(ASN1('30', n, e)); return Enc.hexToBuf(ASN1('30', n, e));
} }
return Enc.hexToBuf(ASN1('30' return Enc.hexToBuf(ASN1('30'
, ASN1.UInt('00') , ASN1.UInt('00')
, n , n
@ -159,10 +159,10 @@
}; };
/** /**
* take a private jwk and creates a der from it * take a private jwk and creates a der from it
* @param {*} jwk * @param {*} jwk
*/ */
x509.packPkcs8 = function (jwk) { x509.packPkcs8 = function (jwk) {
if (jwk.kty == 'RSA') { if ('RSA' === jwk.kty) {
if (!jwk.d) { if (!jwk.d) {
// Public RSA // Public RSA
return Enc.hexToBuf(ASN1('30' return Enc.hexToBuf(ASN1('30'
@ -219,6 +219,49 @@
); );
}; };
x509.packSpki = function (jwk) { x509.packSpki = function (jwk) {
if (/EC/i.test(jwk.kty)) {
return x509.packSpkiEc(jwk);
}
return x509.packSpkiRsa(jwk);
};
x509.packSpkiRsa = function (jwk) {
if (!jwk.d) {
// Public RSA
return Enc.hexToBuf(ASN1('30'
, ASN1('30'
, ASN1('06', '2a864886f70d010101')
, ASN1('05')
)
, ASN1.BitStr(ASN1('30'
, ASN1.UInt(Enc.base64ToHex(jwk.n))
, ASN1.UInt(Enc.base64ToHex(jwk.e))
))
));
}
// Private RSA
return Enc.hexToBuf(ASN1('30'
, ASN1.UInt('00')
, ASN1('30'
, ASN1('06', '2a864886f70d010101')
, ASN1('05')
)
, ASN1('04'
, ASN1('30'
, ASN1.UInt('00')
, ASN1.UInt(Enc.base64ToHex(jwk.n))
, ASN1.UInt(Enc.base64ToHex(jwk.e))
, ASN1.UInt(Enc.base64ToHex(jwk.d))
, ASN1.UInt(Enc.base64ToHex(jwk.p))
, ASN1.UInt(Enc.base64ToHex(jwk.q))
, ASN1.UInt(Enc.base64ToHex(jwk.dp))
, ASN1.UInt(Enc.base64ToHex(jwk.dq))
, ASN1.UInt(Enc.base64ToHex(jwk.qi))
)
)
));
};
x509.packSpkiEc = function (jwk) {
var x = Enc.base64ToHex(jwk.x); var x = Enc.base64ToHex(jwk.x);
var y = Enc.base64ToHex(jwk.y); var y = Enc.base64ToHex(jwk.y);
var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384; var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384;