commit d68230ce101f50e193a0bc10ce37d8cdf51dee3e Author: AJ ONeal Date: Wed Nov 28 02:32:25 2018 -0700 v1.0.0: An ASN.1 packer in <100 lines of Vanilla JavaScript, part of the BlueCrypt suite diff --git a/README.md b/README.md new file mode 100644 index 0000000..097950f --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +# Bluecrypt ASN.1 Packer + +An ASN.1 builder in less than 100 lines of Vanilla JavaScript, +part of the Bluecrypt suite. +
+(< 150 with newlines and comments) + +# Features + +| <100 lines of code | <1k gzipped | 1.8k minified | 3.3k with comments | + +* [x] Complete ASN.1 packer + * [x] Parses x.509 certificates + * [x] PEM (base64-encoded DER) +* [x] VanillaJS, Zero Dependencies + * [x] Browsers (even old ones) + * [x] Online [Demo](https://coolaj86.com/demos/asn1-packer/) + * [ ] Node.js (built, publishing soon) + +# Demo + + + + + +# Usage + +```html + +``` + +```js +'use strict'; + +var ASN1 = window.ASN1 // 62 lines +var Enc = window.Enc // 27 lines +var PEM = window.PEM // 6 lines + +var arr = [ + 0x30, + [ + [ 0x30, [ [ 0x06, "2a8648ce3d0201" ], [ 0x06, "2a8648ce3d030107" ] ] ], + [ 0x03, "04213d5258bc6c69c3e2139675ea3928a409fcffef39acc8e0c82a24ae78c37ede98fd89c0e00e74c997bb0a716ca9e0dca673dbb9b3fa72962255c9debcd218ca" ] + ] +]; + +var hex = ASN1.pack(arr); +var buf = Enc.hexToBuf(hex); +var pem = PEM.packBlock({ type: "PUBLIC KEY", bytes: buf }); + +console.log(pem); +``` + +``` +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIT1SWLxsacPiE5Z16jkopAn8/+85 +rMjgyCokrnjDft6Y/YnA4A50yZe7CnFsqeDcpnPbubP6cpYiVcnevNIYyg== +-----END PUBLIC KEY----- +``` + +### Simple Packing + +All types are handled by the same rules except for Integer (0x02) +and Bit String (0x03), which require special padding. + +This is sufficient for RSA and EC keypairs, and CSRs. + +I don't know of any format for which it is not sufficient, +but if you find one I'd like to know about it. + +### Zero Dependencies + +> A little copying is better than a little dependency - Golang Proverbs by Rob Pike + +Rather than requiring hundreds (or thousands) of lines of dependencies, +this library takes the approach of including from other libraries in its suite +to produce a small, focused file that does exactly what it needs to do. + +# Legal + +[Bluecrypt VanillaJS ASN.1 Packer](https://git.coolaj86.com/coolaj86/asn1-packer.js) | +MPL-2.0 diff --git a/asn1-packer.js b/asn1-packer.js new file mode 100644 index 0000000..a57043c --- /dev/null +++ b/asn1-packer.js @@ -0,0 +1,127 @@ +;(function (exports) { +'use strict'; + +if (!exports.ASN1) { exports.ASN1 = {}; } +if (!exports.Enc) { exports.Enc = {}; } +if (!exports.PEM) { exports.PEM = {}; } + +var ASN1 = exports.ASN1; +var Enc = exports.Enc; +var PEM = exports.PEM; + +// +// Packer +// + +// Almost every ASN.1 type that's important for CSR +// can be represented generically with only a few rules. +exports.ASN1 = function ASN1(/*type, hexstrings...*/) { + var args = Array.prototype.slice.call(arguments); + var typ = args.shift(); + var str = args.join('').replace(/\s+/g, '').toLowerCase(); + var len = (str.length/2); + var lenlen = 0; + var hex = typ; + + // We can't have an odd number of hex chars + if (len !== Math.round(len)) { + throw new Error("invalid hex"); + } + + // The first byte of any ASN.1 sequence is the type (Sequence, Integer, etc) + // The second byte is either the size of the value, or the size of its size + + // 1. If the second byte is < 0x80 (128) it is considered the size + // 2. If it is > 0x80 then it describes the number of bytes of the size + // ex: 0x82 means the next 2 bytes describe the size of the value + // 3. The special case of exactly 0x80 is "indefinite" length (to end-of-file) + + if (len > 127) { + lenlen += 1; + while (len > 255) { + lenlen += 1; + len = len >> 8; + } + } + + if (lenlen) { hex += Enc.numToHex(0x80 + lenlen); } + return hex + Enc.numToHex(str.length/2) + str; +}; + +// The Integer type has some special rules +ASN1.UInt = function UINT() { + var str = Array.prototype.slice.call(arguments).join(''); + var first = parseInt(str.slice(0, 2), 16); + + // If the first byte is 0x80 or greater, the number is considered negative + // Therefore we add a '00' prefix if the 0x80 bit is set + if (0x80 & first) { str = '00' + str; } + + return ASN1('02', str); +}; + +// The Bit String type also has a special rule +ASN1.BitStr = function BITSTR() { + var str = Array.prototype.slice.call(arguments).join(''); + // '00' is a mask of how many bits of the next byte to ignore + return ASN1('03', '00' + str); +}; + +ASN1.pack = function (arr) { + var typ = Enc.numToHex(arr[0]); + var str = ''; + if (Array.isArray(arr[1])) { + arr[1].forEach(function (a) { + str += ASN1.pack(a); + }); + } else if ('string' === typeof arr[1]) { + str = arr[1]; + } else { + throw new Error("unexpected array"); + } + if ('03' === typ) { + return ASN1.BitStr(str); + } else if ('02' === typ) { + return ASN1.UInt(str); + } else { + return ASN1(typ, str); + } +}; +Object.keys(ASN1).forEach(function (k) { + exports.ASN1[k] = ASN1[k]; +}); +ASN1 = exports.ASN1; + +PEM.packBlock = function (opts) { + // TODO allow for headers? + return '-----BEGIN ' + opts.type + '-----\n' + + Enc.bufToBase64(opts.bytes).match(/.{1,64}/g).join('\n') + '\n' + + '-----END ' + opts.type + '-----' + ; +}; + +Enc.bufToBase64 = function (u8) { + var bin = ''; + u8.forEach(function (i) { + bin += String.fromCharCode(i); + }); + return btoa(bin); +}; + +Enc.hexToBuf = function (hex) { + var arr = []; + hex.match(/.{2}/g).forEach(function (h) { + arr.push(parseInt(h, 16)); + }); + return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; +}; + +Enc.numToHex = function (d) { + d = d.toString(16); + if (d.length % 2) { + return '0' + d; + } + return d; +}; + +}('undefined' !== typeof window ? window : module.exports)); diff --git a/index.html b/index.html new file mode 100644 index 0000000..7e32237 --- /dev/null +++ b/index.html @@ -0,0 +1,75 @@ + + + + ASN.1 Packer - Bluecrypt + + + +

Bluecrypt ASN.1 Packer

+ + +
+ + +
 
+ +
 
+ +
+

Made with asn1-packer.js

+ + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..cc5c9d9 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "asn1-packer", + "version": "1.0.0", + "description": "An ASN.1 packer in less than 100 lines of Vanilla JavaScript, part of the Bluecrypt suite.", + "homepage": "https://git.coolaj86.com/coolaj86/asn1-packer.js", + "main": "asn1-packer.js", + "scripts": { + "prepare": "uglifyjs asn1-packer.js > asn1-packer.min.js" + }, + "directories": { + "lib": "lib" + }, + "repository": { + "type": "git", + "url": "https://git.coolaj86.com/coolaj86/asn1-packer.js" + }, + "keywords": [ + "zero-dependency", + "ASN.1", + "X.509", + "PEM", + "DER", + "asn1", + "x509", + "VanillaJS" + ], + "author": "AJ ONeal (https://coolaj86.com/)", + "license": "MPL-2.0" +}