v1.0.0: An ASN.1 packer in <100 lines of Vanilla JavaScript, part of the BlueCrypt suite
This commit is contained in:
commit
d68230ce10
|
@ -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.
|
||||||
|
<br>
|
||||||
|
<small>(< 150 with newlines and comments)</small>
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
<https://coolaj86.com/demos/asn1-packer/>
|
||||||
|
|
||||||
|
<img border="1" src="https://i.imgur.com/phGeuMw.png" />
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://git.coolaj86.com/coolaj86/asn1-packer.js/raw/branch/master/asn1-packer.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
```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
|
|
@ -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));
|
|
@ -0,0 +1,75 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>ASN.1 Packer - Bluecrypt</title>
|
||||||
|
<style>
|
||||||
|
textarea {
|
||||||
|
width: 42em;
|
||||||
|
height: 10em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Bluecrypt ASN.1 Packer</h1>
|
||||||
|
|
||||||
|
<input type="text" class="js-type" placeholder="PEM header (i.e. PUBLIC KEY)" value="PUBLIC KEY">
|
||||||
|
<br>
|
||||||
|
<textarea class="js-input" placeholder="Paste a PEM here">[
|
||||||
|
48,
|
||||||
|
[
|
||||||
|
[
|
||||||
|
48,
|
||||||
|
[
|
||||||
|
[
|
||||||
|
6,
|
||||||
|
"2a8648ce3d0201"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
6,
|
||||||
|
"2a8648ce3d030107"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3,
|
||||||
|
"04213d5258bc6c69c3e2139675ea3928a409fcffef39acc8e0c82a24ae78c37ede98fd89c0e00e74c997bb0a716ca9e0dca673dbb9b3fa72962255c9debcd218ca"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]</textarea>
|
||||||
|
|
||||||
|
<pre><code class="js-hex"> </code></pre>
|
||||||
|
|
||||||
|
<pre><code class="js-pem"> </code></pre>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<p>Made with <a href="https://git.coolaj86.com/coolaj86/asn1-packer/">asn1-packer.js</a></p>
|
||||||
|
|
||||||
|
<script src="./asn1-packer.js"></script>
|
||||||
|
<script>
|
||||||
|
var $input = document.querySelector('.js-input');
|
||||||
|
|
||||||
|
function convert() {
|
||||||
|
console.log('keyup');
|
||||||
|
var json;
|
||||||
|
|
||||||
|
try {
|
||||||
|
var typ = document.querySelector('.js-type').value;
|
||||||
|
var text = document.querySelector('.js-input').value;
|
||||||
|
var arr = JSON.parse(text);
|
||||||
|
var hex = ASN1.pack(arr);
|
||||||
|
var buf = Enc.hexToBuf(hex);
|
||||||
|
var pem = PEM.packBlock({ type: typ, bytes: buf });
|
||||||
|
document.querySelector('.js-hex').innerText = hex
|
||||||
|
.match(/.{2}/g).join(' ').match(/.{1,24}/g).join(' ').match(/.{1,50}/g).join('\n');
|
||||||
|
document.querySelector('.js-pem').innerText = pem;
|
||||||
|
} catch(e) {
|
||||||
|
pem = { error: { message: e.message } };
|
||||||
|
document.querySelector('.js-pem').innerText = JSON.stringify(pem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$input.addEventListener('keyup', convert);
|
||||||
|
convert();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -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 <coolaj86@gmail.com> (https://coolaj86.com/)",
|
||||||
|
"license": "MPL-2.0"
|
||||||
|
}
|
Loading…
Reference in New Issue