AJ ONeal
5 years ago
commit
d68230ce10
4 changed files with 313 additions and 0 deletions
@ -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