v1.0.0: An insanely small ASN.1 codec for Node.js and Browsers
This commit is contained in:
		
							parent
							
								
									4555c003d7
								
							
						
					
					
						commit
						0c73f3fbbe
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,5 @@ | |||||||
|  | *.gz | ||||||
|  | 
 | ||||||
| # ---> Node | # ---> Node | ||||||
| # Logs | # Logs | ||||||
| logs | logs | ||||||
|  | |||||||
							
								
								
									
										413
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										413
									
								
								README.md
									
									
									
									
									
								
							| @ -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) | ||||||
|  | |||||||
							
								
								
									
										28
									
								
								build.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								build.sh
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||||
							
								
								
									
										518
									
								
								dist/asn1.all.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										518
									
								
								dist/asn1.all.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -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); | ||||||
|  | }; | ||||||
|  | }()); | ||||||
							
								
								
									
										1
									
								
								dist/asn1.all.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								dist/asn1.all.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										319
									
								
								dist/asn1.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								dist/asn1.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -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); | ||||||
|  | }; | ||||||
|  | }()); | ||||||
							
								
								
									
										1
									
								
								dist/asn1.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								dist/asn1.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -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)}})(); | ||||||
							
								
								
									
										8
									
								
								dist/index.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								dist/index.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | <html> | ||||||
|  | 	<head> | ||||||
|  | 		<meta charset="UTF-8" /> | ||||||
|  | 	</head> | ||||||
|  | 	<body> | ||||||
|  | 		<script src="./asn1.all.js"></script> | ||||||
|  | 	</body> | ||||||
|  | </html> | ||||||
							
								
								
									
										11
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							| @ -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]; | ||||||
|  | }); | ||||||
							
								
								
									
										19
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -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 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @ -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" | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										131
									
								
								packer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								packer.js
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||||
|  | }; | ||||||
							
								
								
									
										189
									
								
								parser.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								parser.js
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | }; | ||||||
							
								
								
									
										123
									
								
								tests/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								tests/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user