AJ ONeal
5 years ago
14 changed files with 1797 additions and 2 deletions
@ -1,3 +1,412 @@ |
|||
# asn1.js |
|||
# @root/asn1 |
|||
|
|||
Lightweight, Zero-Dependency ASN.1 encoder and decoder in less than 200 lines of vanilla JavaScript |
|||
Built by [The Root Company](https://therootcompany.com) |
|||
for [Greenlock](https://greenlock.domains) |
|||
and [ACME.js](https://git.rootprojects.org/root/acme.js) |
|||
|
|||
Lightweight, Zero-Dependency ASN.1 encoder and decoder for Node.js and Browsers, |
|||
in less than 300 lines of vanilla JavaScript |
|||
|
|||
| 1.6k gzipped |
|||
| 4.2k minified |
|||
| 8.4k pretty |
|||
| |
|||
|
|||
- [x] Zero External Dependencies |
|||
- [x] Universal Support |
|||
- [x] Node.js |
|||
- [x] Browsers |
|||
- [x] Vanilla JS |
|||
|
|||
This ASN.1 codec is built for simplicity. It encodes into DER format |
|||
and decodes into a simple, classless Array of Arrays and values. |
|||
|
|||
Most people don't actually want to work with ANS.1 directly, |
|||
but rather intend to work with pre-defined x509 schemas. |
|||
|
|||
If you're **most people**, you're actually looking for one or more of these: |
|||
|
|||
- [pem.js](https://git.rootprojects.org/root/pem.js) |
|||
- [x509.js](https://git.rootprojects.org/root/x509.js) |
|||
- [csr.js](https://git.rootprojects.org/root/csr.js) |
|||
- [keypairs.js](https://git.rootprojects.org/root/keypairs.js) |
|||
- [encoding.js](https://git.rootprojects.org/root/encoding.js) |
|||
|
|||
Want to [contribute](#contributions)? |
|||
Need [commercial support](#commercial-support)? |
|||
|
|||
# Usage |
|||
|
|||
ASN.1 DER consists values which have |
|||
|
|||
- a type (2-bit class, 6-bit tag) |
|||
- a coded length |
|||
- zero or more values |
|||
|
|||
Common types include: |
|||
|
|||
```txt |
|||
0x30 SEQUENCE |
|||
0x02 INTEGER* |
|||
0x03 BIT STRING** |
|||
0x04 OCTET STRING |
|||
0x05 NULL |
|||
0x06 OBJECT IDENTIFIER |
|||
0x0C UTF8String |
|||
0x16 IA5String (ASCII) |
|||
0x17 UTCTime |
|||
0x31 SET |
|||
0xA0 context-specific*** |
|||
0xA3 context-specific*** |
|||
``` |
|||
|
|||
<small>\* INTEGERS are always BigInt-encoded (a leading '00' for positive numbers with a 1 in the most-significant-bit position)</small> |
|||
|
|||
<small>\*\*BIT STRINGS have a leading "bit mask" which, for all practical purposes, is actually _always_ '00'</small> |
|||
|
|||
<small>\*\*\* See <https://stackoverflow.com/a/15071901/151312></small> |
|||
|
|||
The core value in this library is that it: |
|||
|
|||
- correctly sums the byte length of children elements |
|||
- correctly encodes BigInts |
|||
|
|||
## Parser Usage |
|||
|
|||
There are three options: |
|||
|
|||
- `der` (required) - the input bytes as a buffer |
|||
- `json` (default) - returns hex strings for values, rather than buffers |
|||
- `verbose` - returns a more human-friendly object that is useful for debugging |
|||
|
|||
```js |
|||
ASN1.parse({ der: `<Buffer>`, json: true, verbose: true }); |
|||
``` |
|||
|
|||
Default (hex) output: |
|||
|
|||
```js |
|||
[ |
|||
'30', |
|||
[ |
|||
['02', '01'], |
|||
['04', '2c8996...'], |
|||
['a0', [['06', '2a8648...']]], |
|||
['a1', [['03', '04bdd8...']]] |
|||
] |
|||
]; |
|||
``` |
|||
|
|||
Verbose output: |
|||
|
|||
```js |
|||
{ type: 48, |
|||
lengthSize: 0, |
|||
length: 119, |
|||
children: |
|||
[ { type: 2, lengthSize: 0, length: 1, value: <Buffer 01> }, |
|||
{ type: 4, |
|||
lengthSize: 0, |
|||
length: 32, |
|||
value: |
|||
<Buffer 2c 89 96 ...>, |
|||
children: [] }, |
|||
{ type: 160, lengthSize: 0, length: 10, children: [Array] }, |
|||
{ type: 161, lengthSize: 0, length: 68, children: [Array] } ] } |
|||
``` |
|||
|
|||
## Packer Usage |
|||
|
|||
You can use either of two syntaxes. One is much easier to read than the other. |
|||
|
|||
Ironically, hex strings are used in place of buffers for efficiency. |
|||
|
|||
```js |
|||
ASN1.Any(hexType, hexBytes1, hexBytes2, ...); |
|||
ASN1.UInt(hexBigInt); |
|||
ASN1.BitStr(hexBitStream); |
|||
``` |
|||
|
|||
In practice, you'll be cascading the objects into a final hex string: |
|||
|
|||
``` |
|||
// result is a hex-encoded DER |
|||
var der = hexToBuf( |
|||
ASN1.Any('30' // Sequence |
|||
, ASN1.UInt('01') // Integer (Version 1) |
|||
, ASN1.Any('04', '07CAD7...') // Octet String |
|||
, ASN1.Any('A0', '06082A...') // [0] Object ID (context-specific) |
|||
, ASN1.Any('A1', // [1] (context-specific value) |
|||
ASN1.BitStr('04BDD8...') |
|||
) |
|||
) |
|||
); |
|||
``` |
|||
|
|||
Alternatively you can pack either the sparse array or verbose object, using hex strings or buffers: |
|||
|
|||
- `json` when set to true will return a hex-encoded DER rather than a DER buffer |
|||
|
|||
```js |
|||
var buf = Uint8Array.from([0x01]); |
|||
|
|||
ASN1.pack( |
|||
[ |
|||
'30', |
|||
[ |
|||
['02', buf], |
|||
['04', '07CAD7...'], |
|||
['A0', '06082A...'], |
|||
['A1', ['03', '04BDD8...']] |
|||
] |
|||
], |
|||
{ json: false } |
|||
); |
|||
``` |
|||
|
|||
```js |
|||
var buf = Uint8Array.from([0x01]); |
|||
|
|||
ASN1.pack( |
|||
{ |
|||
type: 48, |
|||
children: [ |
|||
{ type: 2, value: '01' }, |
|||
{ type: 4, value: '2c 89 96 ...', children: [] }, |
|||
{ type: 160, children: [...] }, |
|||
{ type: 161, children: [...] } |
|||
] |
|||
}, |
|||
{ json: false } |
|||
); |
|||
``` |
|||
|
|||
# Install |
|||
|
|||
This package contains both node-specific and browser-specific code, |
|||
and the `package.json#browser` field ensures that your package manager |
|||
will automatically choose the correct code for your environment. |
|||
|
|||
## Node (and Webpack) |
|||
|
|||
```js |
|||
npm install -g @root/asn1 |
|||
``` |
|||
|
|||
```js |
|||
var asn1 = require('@root/asn1'); |
|||
``` |
|||
|
|||
```js |
|||
// just the packer |
|||
var asn1 = require('@root/asn1/packer'); |
|||
|
|||
// just the parser |
|||
var asn1 = require('@root/asn1/parser'); |
|||
``` |
|||
|
|||
## Browsers (Vanilla JS) |
|||
|
|||
```html |
|||
<script src="https://unpkg.com/@root/asn1/dist/asn1.all.js"></script> |
|||
``` |
|||
|
|||
```html |
|||
<script src="https://unpkg.com/@root/asn1/dist/asn1.all.min.js"></script> |
|||
``` |
|||
|
|||
```js |
|||
var ASN1 = window.ASN1; |
|||
``` |
|||
|
|||
# Examples |
|||
|
|||
## Decoding DER to JSON-ASN.1 |
|||
|
|||
```js |
|||
var PEM = require('@root/pem/packer'); |
|||
var Enc = require('@root/encoding'); |
|||
var ASN1 = require('@root/asn1/parser'); |
|||
``` |
|||
|
|||
```js |
|||
var pem = [ |
|||
'-----BEGIN EC PRIVATE KEY-----', |
|||
'MHcCAQEEICyJlsaqkx2z9yx0H6rHA0lM3/7jXjxqn/VOhExHDuR6oAoGCCqGSM49', |
|||
'AwEHoUQDQgAEvdjQ3T6VBX82LIKDzepYgRsz3HgRwp83yPuonu6vqoshSQRe0Aye', |
|||
'mmdXUDX2wTZsmFSjhY9uroRiBbGZrigbKA==', |
|||
'-----END EC PRIVATE KEY-----' |
|||
].join('\n'); |
|||
``` |
|||
|
|||
```js |
|||
var der = PEM.parseBlock(pem).bytes; |
|||
var asn1 = ASN1.parse({ der: der, json: true, verbose: false }); |
|||
``` |
|||
|
|||
```json |
|||
[ |
|||
"30", |
|||
[ |
|||
["02", "01"], |
|||
[ |
|||
"04", |
|||
"2c8996c6aa931db3f72c741faac703494cdffee35e3c6a9ff54e844c470ee47a" |
|||
], |
|||
["a0", [["06", "2a8648ce3d030107"]]], |
|||
[ |
|||
"a1", |
|||
[ |
|||
[ |
|||
"03", |
|||
"04bdd8d0dd3e95057f362c8283cdea58811b33dc7811c29f37c8fba89eeeafaa8b2149045ed00c9e9a67575035f6c1366c9854a3858f6eae846205b199ae281b28" |
|||
] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
``` |
|||
|
|||
```json |
|||
{ |
|||
"type": 48, |
|||
"lengthSize": 0, |
|||
"length": 119, |
|||
"children": [ |
|||
{ "type": 2, "lengthSize": 0, "length": 1, "value": "01" }, |
|||
{ |
|||
"type": 4, |
|||
"lengthSize": 0, |
|||
"length": 32, |
|||
"value": "2c8996c6aa931db3f72c741faac703494cdffee35e3c6a9ff54e844c470ee47a", |
|||
"children": [] |
|||
}, |
|||
{ |
|||
"type": 160, |
|||
"lengthSize": 0, |
|||
"length": 10, |
|||
"children": [ |
|||
{ |
|||
"type": 6, |
|||
"lengthSize": 0, |
|||
"length": 8, |
|||
"value": "2a8648ce3d030107" |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
"type": 161, |
|||
"lengthSize": 0, |
|||
"length": 68, |
|||
"children": [ |
|||
{ |
|||
"type": 3, |
|||
"lengthSize": 0, |
|||
"length": 66, |
|||
"value": "04bdd8d0dd3e95057f362c8283cdea58811b33dc7811c29f37c8fba89eeeafaa8b2149045ed00c9e9a67575035f6c1366c9854a3858f6eae846205b199ae281b28", |
|||
"children": [] |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
## Encoding ASN.1 to DER |
|||
|
|||
Here's an example of an SEC1-encoded EC P-256 Public/Private Keypair: |
|||
|
|||
```js |
|||
var ASN1 = require('@root/asn1/packer'); |
|||
var Enc = require('@root/encoding'); |
|||
var PEM = require('@root/pem/packer'); |
|||
``` |
|||
|
|||
```js |
|||
// 1.2.840.10045.3.1.7 |
|||
// prime256v1 (ANSI X9.62 named elliptic curve) |
|||
var OBJ_ID_EC_256 = '06 08 2A8648CE3D030107'; |
|||
``` |
|||
|
|||
```js |
|||
var jwk = { |
|||
crv: 'P-256', |
|||
d: 'LImWxqqTHbP3LHQfqscDSUzf_uNePGqf9U6ETEcO5Ho', |
|||
kty: 'EC', |
|||
x: 'vdjQ3T6VBX82LIKDzepYgRsz3HgRwp83yPuonu6vqos', |
|||
y: 'IUkEXtAMnppnV1A19sE2bJhUo4WPbq6EYgWxma4oGyg', |
|||
kid: 'MnfJYyS9W5gUjrJLdn8ePMzik8ZJz2qc-VZmKOs_oCw' |
|||
}; |
|||
var d = Enc.base64ToHex(jwk.d); |
|||
var x = Enc.base64ToHex(jwk.x); |
|||
var y = Enc.base64ToHex(jwk.y); |
|||
``` |
|||
|
|||
``` |
|||
var der = Enc.hexToBuf( |
|||
ASN1.Any('30' // Sequence |
|||
, ASN1.UInt('01') // Integer (Version 1) |
|||
, ASN1.Any('04', d) // Octet String |
|||
, ASN1.Any('A0', OBJ_ID_EC_256) // [0] Object ID |
|||
, ASN1.Any('A1', // [1] Embedded EC/ASN1 public key |
|||
ASN1.BitStr('04' + x + y) |
|||
) |
|||
) |
|||
); |
|||
|
|||
var pem = PEM.packBlock({ |
|||
type: 'EC PRIVATE KEY', |
|||
bytes: der |
|||
}); |
|||
``` |
|||
|
|||
# Disabiguation |
|||
|
|||
`ASN1.Any(typ, hexVal, ...)` |
|||
|
|||
There was once an actual ASN.1 type with the literal name 'Any'. |
|||
It was deprecated in 1994 and the `Any` in this API simply means "give any value" |
|||
|
|||
# Contributions |
|||
|
|||
Did this project save you some time? Maybe make your day? Even save the day? |
|||
|
|||
Please say "thanks" via Paypal or Patreon: |
|||
|
|||
- Paypal: [\$5](https://paypal.me/rootprojects/5) | [\$10](https://paypal.me/rootprojects/10) | Any amount: <paypal@therootcompany.com> |
|||
- Patreon: <https://patreon.com/rootprojects> |
|||
|
|||
Where does your contribution go? |
|||
|
|||
[Root](https://therootcompany.com) is a collection of experts |
|||
who trust each other and enjoy working together on deep-tech, |
|||
Indie Web projects. |
|||
|
|||
Our goal is to operate as a sustainable community. |
|||
|
|||
Your contributions - both in code and _especially_ monetarily - |
|||
help to not just this project, but also our broader work |
|||
of [projects](https://rootprojects.org) that fuel the **Indie Web**. |
|||
|
|||
Also, we chat on [Keybase](https://keybase.io) |
|||
in [#rootprojects](https://keybase.io/team/rootprojects) |
|||
|
|||
# Commercial Support |
|||
|
|||
Do you need... |
|||
|
|||
- more features? |
|||
- bugfixes, on _your_ timeline? |
|||
- custom code, built by experts? |
|||
- commercial support and licensing? |
|||
|
|||
Contact <aj@therootcompany.com> for support options. |
|||
|
|||
# Legal |
|||
|
|||
Copyright [AJ ONeal](https://coolaj86.com), |
|||
[Root](https://therootcompany.com) 2018-2019 |
|||
|
|||
MPL-2.0 | |
|||
[Terms of Use](https://therootcompany.com/legal/#terms) | |
|||
[Privacy Policy](https://therootcompany.com/legal/#privacy) |
|||
|
@ -0,0 +1,28 @@ |
|||
#!/bin/bash |
|||
|
|||
# TODO convert to JS |
|||
cat parser.js packer.js > all.tmp.js |
|||
sed -i '' '/use strict/d' all.tmp.js |
|||
sed -i '' '/require/d' all.tmp.js |
|||
sed -i '' '/exports/d' all.tmp.js |
|||
|
|||
echo ';(function () {' > dist/asn1.js |
|||
echo "'use strict';" >> dist/asn1.js |
|||
echo "var ASN1 = window.ASN1 = {};" >> dist/asn1.js |
|||
echo "var Enc = window.Encoding;" >> dist/asn1.js |
|||
cat all.tmp.js >> dist/asn1.js |
|||
rm all.tmp.js |
|||
echo '}());' >> dist/asn1.js |
|||
|
|||
rm dist/*.gz |
|||
|
|||
cat node_modules/@root/encoding/dist/encoding.all.js > all.js |
|||
cat dist/asn1.js >> all.js |
|||
uglifyjs dist/asn1.js > dist/asn1.min.js |
|||
gzip dist/asn1.min.js |
|||
uglifyjs dist/asn1.js > dist/asn1.min.js |
|||
|
|||
mv all.js dist/asn1.all.js |
|||
uglifyjs dist/asn1.all.js > dist/asn1.all.min.js |
|||
gzip dist/asn1.all.min.js |
|||
uglifyjs dist/asn1.all.js > dist/asn1.all.min.js |
@ -0,0 +1,518 @@ |
|||
;(function () { |
|||
'use strict'; |
|||
var Enc = window.Encoding = {}; |
|||
|
|||
|
|||
// To Base64
|
|||
|
|||
Enc.bufToBase64 = function(u8) { |
|||
var bin = ''; |
|||
u8.forEach(function(i) { |
|||
bin += String.fromCharCode(i); |
|||
}); |
|||
return btoa(bin); |
|||
}; |
|||
|
|||
Enc.strToBase64 = function(str) { |
|||
return btoa(Enc.strToBin(str)); |
|||
}; |
|||
|
|||
// From Base64
|
|||
|
|||
function _base64ToBin(b64) { |
|||
return atob(Enc.urlBase64ToBase64(b64)); |
|||
} |
|||
|
|||
Enc._base64ToBin = _base64ToBin; |
|||
|
|||
Enc.base64ToBuf = function(b64) { |
|||
return Enc.binToBuf(_base64ToBin(b64)); |
|||
}; |
|||
|
|||
Enc.base64ToStr = function(b64) { |
|||
return Enc.binToStr(_base64ToBin(b64)); |
|||
}; |
|||
|
|||
// URL Safe Base64
|
|||
|
|||
Enc.urlBase64ToBase64 = function(u64) { |
|||
var r = u64 % 4; |
|||
if (2 === r) { |
|||
u64 += '=='; |
|||
} else if (3 === r) { |
|||
u64 += '='; |
|||
} |
|||
return u64.replace(/-/g, '+').replace(/_/g, '/'); |
|||
}; |
|||
|
|||
Enc.base64ToUrlBase64 = function(b64) { |
|||
return b64 |
|||
.replace(/\+/g, '-') |
|||
.replace(/\//g, '_') |
|||
.replace(/=/g, ''); |
|||
}; |
|||
|
|||
Enc.bufToUrlBase64 = function(buf) { |
|||
return Enc.base64ToUrlBase64(Enc.bufToBase64(buf)); |
|||
}; |
|||
|
|||
Enc.strToUrlBase64 = function(str) { |
|||
return Enc.bufToUrlBase64(Enc.strToBuf(str)); |
|||
}; |
|||
|
|||
|
|||
|
|||
// To Hex
|
|||
|
|||
Enc.bufToHex = function(u8) { |
|||
var hex = []; |
|||
var i, h; |
|||
var len = u8.byteLength || u8.length; |
|||
|
|||
for (i = 0; i < len; i += 1) { |
|||
h = u8[i].toString(16); |
|||
if (2 !== h.length) { |
|||
h = '0' + h; |
|||
} |
|||
hex.push(h); |
|||
} |
|||
|
|||
return hex.join('').toLowerCase(); |
|||
}; |
|||
|
|||
Enc.numToHex = function(d) { |
|||
d = d.toString(16); // .padStart(2, '0');
|
|||
if (d.length % 2) { |
|||
return '0' + d; |
|||
} |
|||
return d; |
|||
}; |
|||
|
|||
Enc.strToHex = function(str) { |
|||
return Enc._binToHex(Enc.strToBin(str)); |
|||
}; |
|||
|
|||
Enc._binToHex = function(bin) { |
|||
return bin |
|||
.split('') |
|||
.map(function(ch) { |
|||
var h = ch.charCodeAt(0).toString(16); |
|||
if (2 !== h.length) { |
|||
h = '0' + h; |
|||
} |
|||
return h; |
|||
}) |
|||
.join(''); |
|||
}; |
|||
|
|||
// From Hex
|
|||
|
|||
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.hexToStr = function(hex) { |
|||
return Enc.binToStr(_hexToBin(hex)); |
|||
}; |
|||
|
|||
function _hexToBin(hex) { |
|||
return hex.replace(/([0-9A-F]{2})/gi, function(_, p1) { |
|||
return String.fromCharCode('0x' + p1); |
|||
}); |
|||
} |
|||
|
|||
Enc._hexToBin = _hexToBin; |
|||
|
|||
|
|||
|
|||
// to Binary String
|
|||
|
|||
Enc.bufToBin = function(buf) { |
|||
var bin = ''; |
|||
// cannot use .map() because Uint8Array would return only 0s
|
|||
buf.forEach(function(ch) { |
|||
bin += String.fromCharCode(ch); |
|||
}); |
|||
return bin; |
|||
}; |
|||
|
|||
Enc.strToBin = function(str) { |
|||
// Note: TextEncoder might be faster (or it might be slower, I don't know),
|
|||
// but it doesn't solve the double-utf8 problem and MS Edge still has users without it
|
|||
var escstr = encodeURIComponent(str); |
|||
// replaces any uri escape sequence, such as %0A,
|
|||
// with binary escape, such as 0x0A
|
|||
var binstr = escstr.replace(/%([0-9A-F]{2})/g, function(_, p1) { |
|||
return String.fromCharCode('0x' + p1); |
|||
}); |
|||
return binstr; |
|||
}; |
|||
|
|||
// to Buffer
|
|||
|
|||
Enc.binToBuf = function(bin) { |
|||
var arr = bin.split('').map(function(ch) { |
|||
return ch.charCodeAt(0); |
|||
}); |
|||
return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; |
|||
}; |
|||
|
|||
Enc.strToBuf = function(str) { |
|||
return Enc.binToBuf(Enc.strToBin(str)); |
|||
}; |
|||
|
|||
// to Unicode String
|
|||
|
|||
Enc.binToStr = function(binstr) { |
|||
var escstr = binstr.replace(/(.)/g, function(m, p) { |
|||
var code = p |
|||
.charCodeAt(0) |
|||
.toString(16) |
|||
.toUpperCase(); |
|||
if (code.length < 2) { |
|||
code = '0' + code; |
|||
} |
|||
return '%' + code; |
|||
}); |
|||
|
|||
return decodeURIComponent(escstr); |
|||
}; |
|||
|
|||
Enc.bufToStr = function(buf) { |
|||
return Enc.binToStr(Enc.bufToBin(buf)); |
|||
}; |
|||
|
|||
// Base64 + Hex
|
|||
|
|||
Enc.base64ToHex = function(b64) { |
|||
return Enc.bufToHex(Enc.base64ToBuf(b64)); |
|||
}; |
|||
|
|||
Enc.hexToBase64 = function(hex) { |
|||
return btoa(Enc._hexToBin(hex)); |
|||
}; |
|||
|
|||
}()); |
|||
;(function () { |
|||
'use strict'; |
|||
var ASN1 = window.ASN1 = {}; |
|||
var Enc = window.Encoding; |
|||
// Copyright 2018 AJ ONeal. All rights reserved
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public |
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|||
|
|||
|
|||
//
|
|||
// Parser
|
|||
//
|
|||
|
|||
// Although I've only seen 9 max in https certificates themselves,
|
|||
// but each domain list could have up to 100
|
|||
ASN1.ELOOPN = 102; |
|||
ASN1.ELOOP = |
|||
'uASN1.js Error: iterated over ' + |
|||
ASN1.ELOOPN + |
|||
'+ elements (probably a malformed file)'; |
|||
// I've seen https certificates go 29 deep
|
|||
ASN1.EDEEPN = 60; |
|||
ASN1.EDEEP = |
|||
'uASN1.js Error: element nested ' + |
|||
ASN1.EDEEPN + |
|||
'+ layers deep (probably a malformed file)'; |
|||
// Container Types are Sequence 0x30, Container Array? (0xA0, 0xA1)
|
|||
// Value Types are Boolean 0x01, Integer 0x02, Null 0x05, Object ID 0x06, String 0x0C, 0x16, 0x13, 0x1e Value Array? (0x82)
|
|||
// Bit String (0x03) and Octet String (0x04) may be values or containers
|
|||
// Sometimes Bit String is used as a container (RSA Pub Spki)
|
|||
ASN1.CTYPES = [0x30, 0x31, 0xa0, 0xa1]; |
|||
ASN1.VTYPES = [0x01, 0x02, 0x05, 0x06, 0x0c, 0x82]; |
|||
ASN1.parseVerbose = function parseAsn1Helper(buf, opts) { |
|||
if (!opts) { |
|||
opts = {}; |
|||
} |
|||
//var ws = ' ';
|
|||
function parseAsn1(buf, depth, eager) { |
|||
if (depth.length >= ASN1.EDEEPN) { |
|||
throw new Error(ASN1.EDEEP); |
|||
} |
|||
|
|||
var index = 2; // we know, at minimum, data starts after type (0) and lengthSize (1)
|
|||
var asn1 = { type: buf[0], lengthSize: 0, length: buf[1] }; |
|||
var child; |
|||
var iters = 0; |
|||
var adjust = 0; |
|||
var adjustedLen; |
|||
|
|||
// Determine how many bytes the length uses, and what it is
|
|||
if (0x80 & asn1.length) { |
|||
asn1.lengthSize = 0x7f & asn1.length; |
|||
// I think that buf->hex->int solves the problem of Endianness... not sure
|
|||
asn1.length = parseInt( |
|||
Enc.bufToHex(buf.slice(index, index + asn1.lengthSize)), |
|||
16 |
|||
); |
|||
index += asn1.lengthSize; |
|||
} |
|||
|
|||
// High-order bit Integers have a leading 0x00 to signify that they are positive.
|
|||
// Bit Streams use the first byte to signify padding, which x.509 doesn't use.
|
|||
if (0x00 === buf[index] && (0x02 === asn1.type || 0x03 === asn1.type)) { |
|||
// However, 0x00 on its own is a valid number
|
|||
if (asn1.length > 1) { |
|||
index += 1; |
|||
adjust = -1; |
|||
} |
|||
} |
|||
adjustedLen = asn1.length + adjust; |
|||
|
|||
//console.warn(depth.join(ws) + '0x' + Enc.numToHex(asn1.type), index, 'len:', asn1.length, asn1);
|
|||
|
|||
function parseChildren(eager) { |
|||
asn1.children = []; |
|||
//console.warn('1 len:', (2 + asn1.lengthSize + asn1.length), 'idx:', index, 'clen:', 0);
|
|||
while ( |
|||
iters < ASN1.ELOOPN && |
|||
index < 2 + asn1.length + asn1.lengthSize |
|||
) { |
|||
iters += 1; |
|||
depth.length += 1; |
|||
child = parseAsn1( |
|||
buf.slice(index, index + adjustedLen), |
|||
depth, |
|||
eager |
|||
); |
|||
depth.length -= 1; |
|||
// The numbers don't match up exactly and I don't remember why...
|
|||
// probably something with adjustedLen or some such, but the tests pass
|
|||
index += 2 + child.lengthSize + child.length; |
|||
//console.warn('2 len:', (2 + asn1.lengthSize + asn1.length), 'idx:', index, 'clen:', (2 + child.lengthSize + child.length));
|
|||
if (index > 2 + asn1.lengthSize + asn1.length) { |
|||
if (!eager) { |
|||
console.error(JSON.stringify(asn1, ASN1._replacer, 2)); |
|||
} |
|||
throw new Error( |
|||
'Parse error: child value length (' + |
|||
child.length + |
|||
') is greater than remaining parent length (' + |
|||
(asn1.length - index) + |
|||
' = ' + |
|||
asn1.length + |
|||
' - ' + |
|||
index + |
|||
')' |
|||
); |
|||
} |
|||
asn1.children.push(child); |
|||
//console.warn(depth.join(ws) + '0x' + Enc.numToHex(asn1.type), index, 'len:', asn1.length, asn1);
|
|||
} |
|||
if (index !== 2 + asn1.lengthSize + asn1.length) { |
|||
//console.warn('index:', index, 'length:', (2 + asn1.lengthSize + asn1.length));
|
|||
throw new Error('premature end-of-file'); |
|||
} |
|||
if (iters >= ASN1.ELOOPN) { |
|||
throw new Error(ASN1.ELOOP); |
|||
} |
|||
|
|||
delete asn1.value; |
|||
return asn1; |
|||
} |
|||
|
|||
// Recurse into types that are _always_ containers
|
|||
if (-1 !== ASN1.CTYPES.indexOf(asn1.type)) { |
|||
return parseChildren(eager); |
|||
} |
|||
|
|||
// Return types that are _always_ values
|
|||
asn1.value = buf.slice(index, index + adjustedLen); |
|||
if (opts.json) { |
|||
asn1.value = Enc.bufToHex(asn1.value); |
|||
} |
|||
if (-1 !== ASN1.VTYPES.indexOf(asn1.type)) { |
|||
return asn1; |
|||
} |
|||
|
|||
// For ambigious / unknown types, recurse and return on failure
|
|||
// (and return child array size to zero)
|
|||
try { |
|||
return parseChildren(true); |
|||
} catch (e) { |
|||
asn1.children.length = 0; |
|||
return asn1; |
|||
} |
|||
} |
|||
|
|||
var asn1 = parseAsn1(buf, []); |
|||
var len = buf.byteLength || buf.length; |
|||
if (len !== 2 + asn1.lengthSize + asn1.length) { |
|||
throw new Error( |
|||
'Length of buffer does not match length of ASN.1 sequence.' |
|||
); |
|||
} |
|||
return asn1; |
|||
}; |
|||
ASN1._toArray = function toArray(next, opts) { |
|||
var typ = opts.json ? Enc.numToHex(next.type) : next.type; |
|||
var val = next.value; |
|||
if (val) { |
|||
if ('string' !== typeof val && opts.json) { |
|||
val = Enc.bufToHex(val); |
|||
} |
|||
return [typ, val]; |
|||
} |
|||
return [ |
|||
typ, |
|||
next.children.map(function(child) { |
|||
return toArray(child, opts); |
|||
}) |
|||
]; |
|||
}; |
|||
ASN1.parse = function(opts) { |
|||
var opts2 = { json: false !== opts.json }; |
|||
var verbose = ASN1.parseVerbose(opts.der, opts2); |
|||
if (opts.verbose) { |
|||
return verbose; |
|||
} |
|||
return ASN1._toArray(verbose, opts2); |
|||
}; |
|||
ASN1._replacer = function(k, v) { |
|||
if ('type' === k) { |
|||
return '0x' + Enc.numToHex(v); |
|||
} |
|||
if (v && 'value' === k) { |
|||
return '0x' + Enc.bufToHex(v.data || v); |
|||
} |
|||
return v; |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Packer
|
|||
//
|
|||
|
|||
// Almost every ASN.1 type that's important for CSR
|
|||
// can be represented generically with only a few rules.
|
|||
function Any(/*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; |
|||
if ('number' === typeof hex) { |
|||
hex = Enc.numToHex(hex); |
|||
} |
|||
|
|||
// 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; |
|||
} |
|||
ASN1.Any = Any; |
|||
|
|||
// 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 Any('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 Any('03', '00' + str); |
|||
}; |
|||
|
|||
ASN1._toArray = function toArray(next, opts) { |
|||
var typ = opts.json ? Enc.numToHex(next.type) : next.type; |
|||
var val = next.value; |
|||
if (val) { |
|||
if ('string' !== typeof val && opts.json) { |
|||
val = Enc.bufToHex(val); |
|||
} |
|||
return [typ, val]; |
|||
} |
|||
return [ |
|||
typ, |
|||
next.children.map(function(child) { |
|||
return toArray(child, opts); |
|||
}) |
|||
]; |
|||
}; |
|||
|
|||
ASN1._pack = function(arr) { |
|||
var typ = arr[0]; |
|||
if ('number' === typeof arr[0]) { |
|||
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 if (arr[1].byteLength) { |
|||
str = Enc.bufToHex(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 Any(typ, str); |
|||
} |
|||
}; |
|||
|
|||
// TODO should this return a buffer?
|
|||
ASN1.pack = function(asn1, opts) { |
|||
if (!opts) { |
|||
opts = {}; |
|||
} |
|||
if (!Array.isArray(asn1)) { |
|||
asn1 = ASN1._toArray(asn1, { json: true }); |
|||
} |
|||
var result = ASN1._pack(asn1); |
|||
if (opts.json) { |
|||
return result; |
|||
} |
|||
return Enc.hexToBuf(result); |
|||
}; |
|||
}()); |
File diff suppressed because one or more lines are too long
@ -0,0 +1,319 @@ |
|||
;(function () { |
|||
'use strict'; |
|||
var ASN1 = window.ASN1 = {}; |
|||
var Enc = window.Encoding; |
|||
// Copyright 2018 AJ ONeal. All rights reserved
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public |
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|||
|
|||
|
|||
//
|
|||
// Parser
|
|||
//
|
|||
|
|||
// Although I've only seen 9 max in https certificates themselves,
|
|||
// but each domain list could have up to 100
|
|||
ASN1.ELOOPN = 102; |
|||
ASN1.ELOOP = |
|||
'uASN1.js Error: iterated over ' + |
|||
ASN1.ELOOPN + |
|||
'+ elements (probably a malformed file)'; |
|||
// I've seen https certificates go 29 deep
|
|||
ASN1.EDEEPN = 60; |
|||
ASN1.EDEEP = |
|||
'uASN1.js Error: element nested ' + |
|||
ASN1.EDEEPN + |
|||
'+ layers deep (probably a malformed file)'; |
|||
// Container Types are Sequence 0x30, Container Array? (0xA0, 0xA1)
|
|||
// Value Types are Boolean 0x01, Integer 0x02, Null 0x05, Object ID 0x06, String 0x0C, 0x16, 0x13, 0x1e Value Array? (0x82)
|
|||
// Bit String (0x03) and Octet String (0x04) may be values or containers
|
|||
// Sometimes Bit String is used as a container (RSA Pub Spki)
|
|||
ASN1.CTYPES = [0x30, 0x31, 0xa0, 0xa1]; |
|||
ASN1.VTYPES = [0x01, 0x02, 0x05, 0x06, 0x0c, 0x82]; |
|||
ASN1.parseVerbose = function parseAsn1Helper(buf, opts) { |
|||
if (!opts) { |
|||
opts = {}; |
|||
} |
|||
//var ws = ' ';
|
|||
function parseAsn1(buf, depth, eager) { |
|||
if (depth.length >= ASN1.EDEEPN) { |
|||
throw new Error(ASN1.EDEEP); |
|||
} |
|||
|
|||
var index = 2; // we know, at minimum, data starts after type (0) and lengthSize (1)
|
|||
var asn1 = { type: buf[0], lengthSize: 0, length: buf[1] }; |
|||
var child; |
|||
var iters = 0; |
|||
var adjust = 0; |
|||
var adjustedLen; |
|||
|
|||
// Determine how many bytes the length uses, and what it is
|
|||
if (0x80 & asn1.length) { |
|||
asn1.lengthSize = 0x7f & asn1.length; |
|||
// I think that buf->hex->int solves the problem of Endianness... not sure
|
|||
asn1.length = parseInt( |
|||
Enc.bufToHex(buf.slice(index, index + asn1.lengthSize)), |
|||
16 |
|||
); |
|||
index += asn1.lengthSize; |
|||
} |
|||
|
|||
// High-order bit Integers have a leading 0x00 to signify that they are positive.
|
|||
// Bit Streams use the first byte to signify padding, which x.509 doesn't use.
|
|||
if (0x00 === buf[index] && (0x02 === asn1.type || 0x03 === asn1.type)) { |
|||
// However, 0x00 on its own is a valid number
|
|||
if (asn1.length > 1) { |
|||
index += 1; |
|||
adjust = -1; |
|||
} |
|||
} |
|||
adjustedLen = asn1.length + adjust; |
|||
|
|||
//console.warn(depth.join(ws) + '0x' + Enc.numToHex(asn1.type), index, 'len:', asn1.length, asn1);
|
|||
|
|||
function parseChildren(eager) { |
|||
asn1.children = []; |
|||
//console.warn('1 len:', (2 + asn1.lengthSize + asn1.length), 'idx:', index, 'clen:', 0);
|
|||
while ( |
|||
iters < ASN1.ELOOPN && |
|||
index < 2 + asn1.length + asn1.lengthSize |
|||
) { |
|||
iters += 1; |
|||
depth.length += 1; |
|||
child = parseAsn1( |
|||
buf.slice(index, index + adjustedLen), |
|||
depth, |
|||
eager |
|||
); |
|||
depth.length -= 1; |
|||
// The numbers don't match up exactly and I don't remember why...
|
|||
// probably something with adjustedLen or some such, but the tests pass
|
|||
index += 2 + child.lengthSize + child.length; |
|||
//console.warn('2 len:', (2 + asn1.lengthSize + asn1.length), 'idx:', index, 'clen:', (2 + child.lengthSize + child.length));
|
|||
if (index > 2 + asn1.lengthSize + asn1.length) { |
|||
if (!eager) { |
|||
console.error(JSON.stringify(asn1, ASN1._replacer, 2)); |
|||
} |
|||
throw new Error( |
|||
'Parse error: child value length (' + |
|||
child.length + |
|||
') is greater than remaining parent length (' + |
|||
(asn1.length - index) + |
|||
' = ' + |
|||
asn1.length + |
|||
' - ' + |
|||
index + |
|||
')' |
|||
); |
|||
} |
|||
asn1.children.push(child); |
|||
//console.warn(depth.join(ws) + '0x' + Enc.numToHex(asn1.type), index, 'len:', asn1.length, asn1);
|
|||
} |
|||
if (index !== 2 + asn1.lengthSize + asn1.length) { |
|||
//console.warn('index:', index, 'length:', (2 + asn1.lengthSize + asn1.length));
|
|||
throw new Error('premature end-of-file'); |
|||
} |
|||
if (iters >= ASN1.ELOOPN) { |
|||
throw new Error(ASN1.ELOOP); |
|||
} |
|||
|
|||
delete asn1.value; |
|||
return asn1; |
|||
} |
|||
|
|||
// Recurse into types that are _always_ containers
|
|||
if (-1 !== ASN1.CTYPES.indexOf(asn1.type)) { |
|||
return parseChildren(eager); |
|||
} |
|||
|
|||
// Return types that are _always_ values
|
|||
asn1.value = buf.slice(index, index + adjustedLen); |
|||
if (opts.json) { |
|||
asn1.value = Enc.bufToHex(asn1.value); |
|||
} |
|||
if (-1 !== ASN1.VTYPES.indexOf(asn1.type)) { |
|||
return asn1; |
|||
} |
|||
|
|||
// For ambigious / unknown types, recurse and return on failure
|
|||
// (and return child array size to zero)
|
|||
try { |
|||
return parseChildren(true); |
|||
} catch (e) { |
|||
asn1.children.length = 0; |
|||
return asn1; |
|||
} |
|||
} |
|||
|
|||
var asn1 = parseAsn1(buf, []); |
|||
var len = buf.byteLength || buf.length; |
|||
if (len !== 2 + asn1.lengthSize + asn1.length) { |
|||
throw new Error( |
|||
'Length of buffer does not match length of ASN.1 sequence.' |
|||
); |
|||
} |
|||
return asn1; |
|||
}; |
|||
ASN1._toArray = function toArray(next, opts) { |
|||
var typ = opts.json ? Enc.numToHex(next.type) : next.type; |
|||
var val = next.value; |
|||
if (val) { |
|||
if ('string' !== typeof val && opts.json) { |
|||
val = Enc.bufToHex(val); |
|||
} |
|||
return [typ, val]; |
|||
} |
|||
return [ |
|||
typ, |
|||
next.children.map(function(child) { |
|||
return toArray(child, opts); |
|||
}) |
|||
]; |
|||
}; |
|||
ASN1.parse = function(opts) { |
|||
var opts2 = { json: false !== opts.json }; |
|||
var verbose = ASN1.parseVerbose(opts.der, opts2); |
|||
if (opts.verbose) { |
|||
return verbose; |
|||
} |
|||
return ASN1._toArray(verbose, opts2); |
|||
}; |
|||
ASN1._replacer = function(k, v) { |
|||
if ('type' === k) { |
|||
return '0x' + Enc.numToHex(v); |
|||
} |
|||
if (v && 'value' === k) { |
|||
return '0x' + Enc.bufToHex(v.data || v); |
|||
} |
|||
return v; |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Packer
|
|||
//
|
|||
|
|||
// Almost every ASN.1 type that's important for CSR
|
|||
// can be represented generically with only a few rules.
|
|||
function Any(/*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; |
|||
if ('number' === typeof hex) { |
|||
hex = Enc.numToHex(hex); |
|||
} |
|||
|
|||
// 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; |
|||
} |
|||
ASN1.Any = Any; |
|||
|
|||
// 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 Any('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 Any('03', '00' + str); |
|||
}; |
|||
|
|||
ASN1._toArray = function toArray(next, opts) { |
|||
var typ = opts.json ? Enc.numToHex(next.type) : next.type; |
|||
var val = next.value; |
|||
if (val) { |
|||
if ('string' !== typeof val && opts.json) { |
|||
val = Enc.bufToHex(val); |
|||
} |
|||
return [typ, val]; |
|||
} |
|||
return [ |
|||
typ, |
|||
next.children.map(function(child) { |
|||
return toArray(child, opts); |
|||
}) |
|||
]; |
|||
}; |
|||
|
|||
ASN1._pack = function(arr) { |
|||
var typ = arr[0]; |
|||
if ('number' === typeof arr[0]) { |
|||
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 if (arr[1].byteLength) { |
|||
str = Enc.bufToHex(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 Any(typ, str); |
|||
} |
|||
}; |
|||
|
|||
// TODO should this return a buffer?
|
|||
ASN1.pack = function(asn1, opts) { |
|||
if (!opts) { |
|||
opts = {}; |
|||
} |
|||
if (!Array.isArray(asn1)) { |
|||
asn1 = ASN1._toArray(asn1, { json: true }); |
|||
} |
|||
var result = ASN1._pack(asn1); |
|||
if (opts.json) { |
|||
return result; |
|||
} |
|||
return Enc.hexToBuf(result); |
|||
}; |
|||
}()); |
@ -0,0 +1 @@ |
|||
(function(){"use strict";var ASN1=window.ASN1={};var Enc=window.Encoding;ASN1.ELOOPN=102;ASN1.ELOOP="uASN1.js Error: iterated over "+ASN1.ELOOPN+"+ elements (probably a malformed file)";ASN1.EDEEPN=60;ASN1.EDEEP="uASN1.js Error: element nested "+ASN1.EDEEPN+"+ layers deep (probably a malformed file)";ASN1.CTYPES=[48,49,160,161];ASN1.VTYPES=[1,2,5,6,12,130];ASN1.parseVerbose=function parseAsn1Helper(buf,opts){if(!opts){opts={}}function parseAsn1(buf,depth,eager){if(depth.length>=ASN1.EDEEPN){throw new Error(ASN1.EDEEP)}var index=2;var asn1={type:buf[0],lengthSize:0,length:buf[1]};var child;var iters=0;var adjust=0;var adjustedLen;if(128&asn1.length){asn1.lengthSize=127&asn1.length;asn1.length=parseInt(Enc.bufToHex(buf.slice(index,index+asn1.lengthSize)),16);index+=asn1.lengthSize}if(0===buf[index]&&(2===asn1.type||3===asn1.type)){if(asn1.length>1){index+=1;adjust=-1}}adjustedLen=asn1.length+adjust;function parseChildren(eager){asn1.children=[];while(iters<ASN1.ELOOPN&&index<2+asn1.length+asn1.lengthSize){iters+=1;depth.length+=1;child=parseAsn1(buf.slice(index,index+adjustedLen),depth,eager);depth.length-=1;index+=2+child.lengthSize+child.length;if(index>2+asn1.lengthSize+asn1.length){if(!eager){console.error(JSON.stringify(asn1,ASN1._replacer,2))}throw new Error("Parse error: child value length ("+child.length+") is greater than remaining parent length ("+(asn1.length-index)+" = "+asn1.length+" - "+index+")")}asn1.children.push(child)}if(index!==2+asn1.lengthSize+asn1.length){throw new Error("premature end-of-file")}if(iters>=ASN1.ELOOPN){throw new Error(ASN1.ELOOP)}delete asn1.value;return asn1}if(-1!==ASN1.CTYPES.indexOf(asn1.type)){return parseChildren(eager)}asn1.value=buf.slice(index,index+adjustedLen);if(opts.json){asn1.value=Enc.bufToHex(asn1.value)}if(-1!==ASN1.VTYPES.indexOf(asn1.type)){return asn1}try{return parseChildren(true)}catch(e){asn1.children.length=0;return asn1}}var asn1=parseAsn1(buf,[]);var len=buf.byteLength||buf.length;if(len!==2+asn1.lengthSize+asn1.length){throw new Error("Length of buffer does not match length of ASN.1 sequence.")}return asn1};ASN1._toArray=function toArray(next,opts){var typ=opts.json?Enc.numToHex(next.type):next.type;var val=next.value;if(val){if("string"!==typeof val&&opts.json){val=Enc.bufToHex(val)}return[typ,val]}return[typ,next.children.map(function(child){return toArray(child,opts)})]};ASN1.parse=function(opts){var opts2={json:false!==opts.json};var verbose=ASN1.parseVerbose(opts.der,opts2);if(opts.verbose){return verbose}return ASN1._toArray(verbose,opts2)};ASN1._replacer=function(k,v){if("type"===k){return"0x"+Enc.numToHex(v)}if(v&&"value"===k){return"0x"+Enc.bufToHex(v.data||v)}return v};function Any(){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;if("number"===typeof hex){hex=Enc.numToHex(hex)}if(len!==Math.round(len)){throw new Error("invalid hex")}if(len>127){lenlen+=1;while(len>255){lenlen+=1;len=len>>8}}if(lenlen){hex+=Enc.numToHex(128+lenlen)}return hex+Enc.numToHex(str.length/2)+str}ASN1.Any=Any;ASN1.UInt=function UINT(){var str=Array.prototype.slice.call(arguments).join("");var first=parseInt(str.slice(0,2),16);if(128&first){str="00"+str}return Any("02",str)};ASN1.BitStr=function BITSTR(){var str=Array.prototype.slice.call(arguments).join("");return Any("03","00"+str)};ASN1._toArray=function toArray(next,opts){var typ=opts.json?Enc.numToHex(next.type):next.type;var val=next.value;if(val){if("string"!==typeof val&&opts.json){val=Enc.bufToHex(val)}return[typ,val]}return[typ,next.children.map(function(child){return toArray(child,opts)})]};ASN1._pack=function(arr){var typ=arr[0];if("number"===typeof arr[0]){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 if(arr[1].byteLength){str=Enc.bufToHex(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 Any(typ,str)}};ASN1.pack=function(asn1,opts){if(!opts){opts={}}if(!Array.isArray(asn1)){asn1=ASN1._toArray(asn1,{json:true})}var result=ASN1._pack(asn1);if(opts.json){return result}return Enc.hexToBuf(result)}})(); |
@ -0,0 +1,8 @@ |
|||
<html> |
|||
<head> |
|||
<meta charset="UTF-8" /> |
|||
</head> |
|||
<body> |
|||
<script src="./asn1.all.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,11 @@ |
|||
'use strict'; |
|||
|
|||
var ASN1 = module.exports; |
|||
var packer = require('./packer.js'); |
|||
var parser = require('./parser.js'); |
|||
Object.keys(parser).forEach(function(key) { |
|||
ASN1[key] = parser[key]; |
|||
}); |
|||
Object.keys(packer).forEach(function(key) { |
|||
ASN1[key] = packer[key]; |
|||
}); |
@ -0,0 +1,19 @@ |
|||
{ |
|||
"name": "@root/asn1", |
|||
"version": "1.0.0", |
|||
"lockfileVersion": 1, |
|||
"requires": true, |
|||
"dependencies": { |
|||
"@root/encoding": { |
|||
"version": "1.0.1", |
|||
"resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz", |
|||
"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ==" |
|||
}, |
|||
"@root/pem": { |
|||
"version": "1.0.3", |
|||
"resolved": "https://registry.npmjs.org/@root/pem/-/pem-1.0.3.tgz", |
|||
"integrity": "sha512-6iFwsbwm6YzWdfjogHzLTYkA1KWdeEkutVX2BBVfhyWoE9q0vp89G7mAcLIhi0QTRd199AMOacHWFq+gTyQkVA==", |
|||
"dev": true |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,36 @@ |
|||
{ |
|||
"name": "@root/asn1", |
|||
"version": "1.0.0", |
|||
"description": "VanillaJS, Lightweight, Zero-Dependency, ASN.1 encoder and decoder.", |
|||
"main": "index.js", |
|||
"browser": { |
|||
"./node/native.js": "./browser/native.js" |
|||
}, |
|||
"files": [ |
|||
"*.js", |
|||
"node", |
|||
"browser", |
|||
"dist" |
|||
], |
|||
"scripts": { |
|||
"test": "node tests" |
|||
}, |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "https://git.rootprojects.org/root/asn1.js.git" |
|||
}, |
|||
"keywords": [ |
|||
"ASN.1", |
|||
"asn1", |
|||
"x509", |
|||
"PEM" |
|||
], |
|||
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", |
|||
"license": "MPL-2.0", |
|||
"devDependencies": { |
|||
"@root/pem": "^1.0.3" |
|||
}, |
|||
"dependencies": { |
|||
"@root/encoding": "^1.0.1" |
|||
} |
|||
} |
@ -0,0 +1,131 @@ |
|||
'use strict'; |
|||
|
|||
var ASN1 = module.exports; |
|||
var Enc = require('@root/encoding/hex'); |
|||
|
|||
//
|
|||
// Packer
|
|||
//
|
|||
|
|||
// Almost every ASN.1 type that's important for CSR
|
|||
// can be represented generically with only a few rules.
|
|||
function Any(/*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; |
|||
if ('number' === typeof hex) { |
|||
hex = Enc.numToHex(hex); |
|||
} |
|||
|
|||
// 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; |
|||
} |
|||
ASN1.Any = Any; |
|||
|
|||
// 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 Any('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 Any('03', '00' + str); |
|||
}; |
|||
|
|||
ASN1._toArray = function toArray(next, opts) { |
|||
var typ = opts.json ? Enc.numToHex(next.type) : next.type; |
|||
var val = next.value; |
|||
if (val) { |
|||
if ('string' !== typeof val && opts.json) { |
|||
val = Enc.bufToHex(val); |
|||
} |
|||
return [typ, val]; |
|||
} |
|||
return [ |
|||
typ, |
|||
next.children.map(function(child) { |
|||
return toArray(child, opts); |
|||
}) |
|||
]; |
|||
}; |
|||
|
|||
ASN1._pack = function(arr) { |
|||
var typ = arr[0]; |
|||
if ('number' === typeof arr[0]) { |
|||
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 if (arr[1].byteLength) { |
|||
str = Enc.bufToHex(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 Any(typ, str); |
|||
} |
|||
}; |
|||
|
|||
// TODO should this return a buffer?
|
|||
ASN1.pack = function(asn1, opts) { |
|||
if (!opts) { |
|||
opts = {}; |
|||
} |
|||
if (!Array.isArray(asn1)) { |
|||
asn1 = ASN1._toArray(asn1, { json: true }); |
|||
} |
|||
var result = ASN1._pack(asn1); |
|||
if (opts.json) { |
|||
return result; |
|||
} |
|||
return Enc.hexToBuf(result); |
|||
}; |
@ -0,0 +1,189 @@ |
|||
// Copyright 2018 AJ ONeal. All rights reserved
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public |
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|||
'use strict'; |
|||
|
|||
var ASN1 = module.exports; |
|||
var Enc = require('@root/encoding/hex'); |
|||
|
|||
//
|
|||
// Parser
|
|||
//
|
|||
|
|||
// Although I've only seen 9 max in https certificates themselves,
|
|||
// but each domain list could have up to 100
|
|||
ASN1.ELOOPN = 102; |
|||
ASN1.ELOOP = |
|||
'uASN1.js Error: iterated over ' + |
|||
ASN1.ELOOPN + |
|||
'+ elements (probably a malformed file)'; |
|||
// I've seen https certificates go 29 deep
|
|||
ASN1.EDEEPN = 60; |
|||
ASN1.EDEEP = |
|||
'uASN1.js Error: element nested ' + |
|||
ASN1.EDEEPN + |
|||
'+ layers deep (probably a malformed file)'; |
|||
// Container Types are Sequence 0x30, Container Array? (0xA0, 0xA1)
|
|||
// Value Types are Boolean 0x01, Integer 0x02, Null 0x05, Object ID 0x06, String 0x0C, 0x16, 0x13, 0x1e Value Array? (0x82)
|
|||
// Bit String (0x03) and Octet String (0x04) may be values or containers
|
|||
// Sometimes Bit String is used as a container (RSA Pub Spki)
|
|||
ASN1.CTYPES = [0x30, 0x31, 0xa0, 0xa1]; |
|||
ASN1.VTYPES = [0x01, 0x02, 0x05, 0x06, 0x0c, 0x82]; |
|||
ASN1.parseVerbose = function parseAsn1Helper(buf, opts) { |
|||
if (!opts) { |
|||
opts = {}; |
|||
} |
|||
//var ws = ' ';
|
|||
function parseAsn1(buf, depth, eager) { |
|||
if (depth.length >= ASN1.EDEEPN) { |
|||
throw new Error(ASN1.EDEEP); |
|||
} |
|||
|
|||
var index = 2; // we know, at minimum, data starts after type (0) and lengthSize (1)
|
|||
var asn1 = { type: buf[0], lengthSize: 0, length: buf[1] }; |
|||
var child; |
|||
var iters = 0; |
|||
var adjust = 0; |
|||
var adjustedLen; |
|||
|
|||
// Determine how many bytes the length uses, and what it is
|
|||
if (0x80 & asn1.length) { |
|||
asn1.lengthSize = 0x7f & asn1.length; |
|||
// I think that buf->hex->int solves the problem of Endianness... not sure
|
|||
asn1.length = parseInt( |
|||
Enc.bufToHex(buf.slice(index, index + asn1.lengthSize)), |
|||
16 |
|||
); |
|||
index += asn1.lengthSize; |
|||
} |
|||
|
|||
// High-order bit Integers have a leading 0x00 to signify that they are positive.
|
|||
// Bit Streams use the first byte to signify padding, which x.509 doesn't use.
|
|||
if (0x00 === buf[index] && (0x02 === asn1.type || 0x03 === asn1.type)) { |
|||
// However, 0x00 on its own is a valid number
|
|||
if (asn1.length > 1) { |
|||
index += 1; |
|||
adjust = -1; |
|||
} |
|||
} |
|||
adjustedLen = asn1.length + adjust; |
|||
|
|||
//console.warn(depth.join(ws) + '0x' + Enc.numToHex(asn1.type), index, 'len:', asn1.length, asn1);
|
|||
|
|||
function parseChildren(eager) { |
|||
asn1.children = []; |
|||
//console.warn('1 len:', (2 + asn1.lengthSize + asn1.length), 'idx:', index, 'clen:', 0);
|
|||
while ( |
|||
iters < ASN1.ELOOPN && |
|||
index < 2 + asn1.length + asn1.lengthSize |
|||
) { |
|||
iters += 1; |
|||
depth.length += 1; |
|||
child = parseAsn1( |
|||
buf.slice(index, index + adjustedLen), |
|||
depth, |
|||
eager |
|||
); |
|||
depth.length -= 1; |
|||
// The numbers don't match up exactly and I don't remember why...
|
|||
// probably something with adjustedLen or some such, but the tests pass
|
|||
index += 2 + child.lengthSize + child.length; |
|||
//console.warn('2 len:', (2 + asn1.lengthSize + asn1.length), 'idx:', index, 'clen:', (2 + child.lengthSize + child.length));
|
|||
if (index > 2 + asn1.lengthSize + asn1.length) { |
|||
if (!eager) { |
|||
console.error(JSON.stringify(asn1, ASN1._replacer, 2)); |
|||
} |
|||
throw new Error( |
|||
'Parse error: child value length (' + |
|||
child.length + |
|||
') is greater than remaining parent length (' + |
|||
(asn1.length - index) + |
|||
' = ' + |
|||
asn1.length + |
|||
' - ' + |
|||
index + |
|||
')' |
|||
); |
|||
} |
|||
asn1.children.push(child); |
|||
//console.warn(depth.join(ws) + '0x' + Enc.numToHex(asn1.type), index, 'len:', asn1.length, asn1);
|
|||
} |
|||
if (index !== 2 + asn1.lengthSize + asn1.length) { |
|||
//console.warn('index:', index, 'length:', (2 + asn1.lengthSize + asn1.length));
|
|||
throw new Error('premature end-of-file'); |
|||
} |
|||
if (iters >= ASN1.ELOOPN) { |
|||
throw new Error(ASN1.ELOOP); |
|||
} |
|||
|
|||
delete asn1.value; |
|||
return asn1; |
|||
} |
|||
|
|||
// Recurse into types that are _always_ containers
|
|||
if (-1 !== ASN1.CTYPES.indexOf(asn1.type)) { |
|||
return parseChildren(eager); |
|||
} |
|||
|
|||
// Return types that are _always_ values
|
|||
asn1.value = buf.slice(index, index + adjustedLen); |
|||
if (opts.json) { |
|||
asn1.value = Enc.bufToHex(asn1.value); |
|||
} |
|||
if (-1 !== ASN1.VTYPES.indexOf(asn1.type)) { |
|||
return asn1; |
|||
} |
|||
|
|||
// For ambigious / unknown types, recurse and return on failure
|
|||
// (and return child array size to zero)
|
|||
try { |
|||
return parseChildren(true); |
|||
} catch (e) { |
|||
asn1.children.length = 0; |
|||
return asn1; |
|||
} |
|||
} |
|||
|
|||
var asn1 = parseAsn1(buf, []); |
|||
var len = buf.byteLength || buf.length; |
|||
if (len !== 2 + asn1.lengthSize + asn1.length) { |
|||
throw new Error( |
|||
'Length of buffer does not match length of ASN.1 sequence.' |
|||
); |
|||
} |
|||
return asn1; |
|||
}; |
|||
ASN1._toArray = function toArray(next, opts) { |
|||
var typ = opts.json ? Enc.numToHex(next.type) : next.type; |
|||
var val = next.value; |
|||
if (val) { |
|||
if ('string' !== typeof val && opts.json) { |
|||
val = Enc.bufToHex(val); |
|||
} |
|||
return [typ, val]; |
|||
} |
|||
return [ |
|||
typ, |
|||
next.children.map(function(child) { |
|||
return toArray(child, opts); |
|||
}) |
|||
]; |
|||
}; |
|||
ASN1.parse = function(opts) { |
|||
var opts2 = { json: false !== opts.json }; |
|||
var verbose = ASN1.parseVerbose(opts.der, opts2); |
|||
if (opts.verbose) { |
|||
return verbose; |
|||
} |
|||
return ASN1._toArray(verbose, opts2); |
|||
}; |
|||
ASN1._replacer = function(k, v) { |
|||
if ('type' === k) { |
|||
return '0x' + Enc.numToHex(v); |
|||
} |
|||
if (v && 'value' === k) { |
|||
return '0x' + Enc.bufToHex(v.data || v); |
|||
} |
|||
return v; |
|||
}; |
@ -0,0 +1,123 @@ |
|||
'use strict'; |
|||
|
|||
var ASN1 = require('../'); |
|||
var Enc = require('@root/encoding'); |
|||
var PEM = require('@root/pem'); |
|||
|
|||
// 1.2.840.10045.3.1.7
|
|||
// prime256v1 (ANSI X9.62 named elliptic curve)
|
|||
var OBJ_ID_EC_256 = '06 08 2A8648CE3D030107'; |
|||
|
|||
var jwk = { |
|||
crv: 'P-256', |
|||
d: 'LImWxqqTHbP3LHQfqscDSUzf_uNePGqf9U6ETEcO5Ho', |
|||
kty: 'EC', |
|||
x: 'vdjQ3T6VBX82LIKDzepYgRsz3HgRwp83yPuonu6vqos', |
|||
y: 'IUkEXtAMnppnV1A19sE2bJhUo4WPbq6EYgWxma4oGyg', |
|||
kid: 'MnfJYyS9W5gUjrJLdn8ePMzik8ZJz2qc-VZmKOs_oCw' |
|||
}; |
|||
var d = Enc.base64ToHex(jwk.d); |
|||
var x = Enc.base64ToHex(jwk.x); |
|||
var y = Enc.base64ToHex(jwk.y); |
|||
|
|||
var der = Enc.hexToBuf( |
|||
ASN1.Any( |
|||
'30', // Sequence
|
|||
ASN1.UInt('01'), // Integer (Version 1)
|
|||
ASN1.Any('04', d), // Octet String
|
|||
ASN1.Any('A0', OBJ_ID_EC_256), // [0] Object ID
|
|||
ASN1.Any( |
|||
'A1', // [1] Embedded EC/ASN1 public key
|
|||
ASN1.BitStr('04' + x + y) |
|||
) |
|||
) |
|||
); |
|||
|
|||
var pem1 = PEM.packBlock({ |
|||
type: 'EC PRIVATE KEY', |
|||
bytes: der |
|||
}); |
|||
|
|||
var expected = [ |
|||
'-----BEGIN EC PRIVATE KEY-----', |
|||
'MHcCAQEEICyJlsaqkx2z9yx0H6rHA0lM3/7jXjxqn/VOhExHDuR6oAoGCCqGSM49', |
|||
'AwEHoUQDQgAEvdjQ3T6VBX82LIKDzepYgRsz3HgRwp83yPuonu6vqoshSQRe0Aye', |
|||
'mmdXUDX2wTZsmFSjhY9uroRiBbGZrigbKA==', |
|||
'-----END EC PRIVATE KEY-----' |
|||
].join('\n'); |
|||
|
|||
if (pem1 !== expected) { |
|||
throw new Error('Did not correctly Cascade pack EC P-256 JWK to DER'); |
|||
} else { |
|||
console.info('PASS: packed cascaded ASN1'); |
|||
} |
|||
|
|||
// Mix and match hex ints, hex strings, and byte arrays
|
|||
var asn1Arr = [ |
|||
'30', // Sequence
|
|||
[ |
|||
[0x02, '01'], // Integer (Version 1)
|
|||
[0x04, Buffer.from(d, 'hex')], // Octet String
|
|||
['a0', OBJ_ID_EC_256], // [0] Object ID
|
|||
[ |
|||
0xa1, // [1] Embedded EC/ASN1 public key
|
|||
ASN1.BitStr('04' + x + y) |
|||
] |
|||
] |
|||
]; |
|||
|
|||
var der2 = ASN1.pack(asn1Arr); |
|||
var pem2 = PEM.packBlock({ |
|||
type: 'EC PRIVATE KEY', |
|||
bytes: der2 |
|||
}); |
|||
|
|||
if (pem2 !== expected) { |
|||
console.log(pem2); |
|||
console.log(expected); |
|||
throw new Error('Did not correctly Array pack EC P-256 JWK to DER'); |
|||
} else { |
|||
console.info('PASS: packed array-style ASN1'); |
|||
} |
|||
|
|||
var block = PEM.parseBlock(expected); |
|||
var arr1 = ASN1.parse({ der: block.bytes }); |
|||
//console.log(JSON.stringify(arr1));
|
|||
var arr2 = ASN1.parse({ der: block.bytes, verbose: false, json: false }); |
|||
var obj3 = ASN1.parse({ der: block.bytes, verbose: true, json: true }); |
|||
//console.log(obj3);
|
|||
|
|||
function eq(b1, b2) { |
|||
if (b1.byteLength !== b2.byteLength) { |
|||
return false; |
|||
} |
|||
return b1.every(function(b, i) { |
|||
return b === b2[i]; |
|||
}); |
|||
} |
|||
|
|||
if (!eq(block.bytes, ASN1.pack(arr1))) { |
|||
throw new Error('packing hex array resulted in different bytes'); |
|||
} else { |
|||
console.log('PASS: packs parsed (hex) array'); |
|||
} |
|||
|
|||
if (!eq(block.bytes, ASN1.pack(arr2))) { |
|||
throw new Error('packing array with bytes resulted in different bytes'); |
|||
} else { |
|||
console.log('PASS: packs parsed array (with bytes)'); |
|||
} |
|||
|
|||
if (!eq(block.bytes, ASN1.pack(obj3))) { |
|||
console.log(block.bytes.toString('hex')); |
|||
console.log(ASN1.pack(obj3)); |
|||
throw new Error('packing verbose object resulted in different bytes'); |
|||
} else { |
|||
console.log('PASS: packs parsed verbose object'); |
|||
} |
|||
|
|||
if (block.bytes.toString('hex') !== ASN1.pack(obj3, { json: true })) { |
|||
throw new Error('packing to hex resulted in different bytes'); |
|||
} else { |
|||
console.log('PASS: packs as hex when json: true'); |
|||
} |
Loading…
Reference in new issue