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