141 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # μASN1.js
 | |
| 
 | |
| An insanely minimal ASN.1 builder for X.509 common schemas,
 | |
| specifically SEC1/X9.62 PKCS#8, SPKI/PKIX, PKCS#1 and CSR.
 | |
| 
 | |
| Created for [ECDSA-CSR](https://git.coolaj86.com/coolaj86/ecdsa-csr.js)
 | |
| and [eckles.js](https://git.coolaj86.com/coolaj86/eckles.js) (PEM-to-JWK and JWK-to-PEM).
 | |
| 
 | |
| Optimal for the times you want lightweight ASN.1 support
 | |
| and it's reasonable to build concise specific functions for
 | |
| a bounded number of supported schemas rather than a generic
 | |
| parser that supports _all_ schemas.
 | |
| 
 | |
| Works exclusively in hexidecimal for simplicity and ease-of-use.
 | |
| 
 | |
| ```js
 | |
| var ASN1 = require('uasn1');
 | |
| ```
 | |
| 
 | |
| # API
 | |
| 
 | |
| The ASN.1 standard is actually pretty simple and fairly consistent,
 | |
| but it's a little tedius to construct due to how sizes are calculated
 | |
| with nested structures.
 | |
| 
 | |
| There are only 3 methods needed to support all of the X.509 schemas
 | |
| that most of us care about, and so that's all this library has:
 | |
| 
 | |
| ```js
 | |
| ASN1(type, hex1, hex2, ...)
 | |
| ASN1.UInt(hex1, hex2, ...)
 | |
| ASN1.BitStr(hex1, hex2, ...)
 | |
| 
 | |
| /*helper*/
 | |
| ASN1.numToHex(num)
 | |
| ```
 | |
| 
 | |
| Most ASN.1 types follow the same rules:
 | |
| 
 | |
|   * Type byte goes first
 | |
|   * Length Info byte goes next
 | |
|     * for numbers < 128 length info is read as the length
 | |
|     * for numbers > 128 length info is size of the length (and the next bytes are the length)
 | |
|     * 128 is a special case which essentially means "read to the end of the file"
 | |
|   * The value bytes go next
 | |
| 
 | |
| The tedius part is just cascading the lengths.
 | |
| 
 | |
| Integer values are different.
 | |
| They must have a leading '0' if the first byte is > 127,
 | |
| if the number is positive (otherwise it will be considered negative).
 | |
| 
 | |
| Bit Strings are also different.
 | |
| The first byte is used to tell how many of the next bytes are used for alignment.
 | |
| For the purposes of all X509 schemas I've seen, that means it's just '0'.
 | |
| 
 | |
| As far as I've been able to tell, that's all that matters.
 | |
| 
 | |
| # Examples
 | |
| 
 | |
| * EC SEC1/X9.62
 | |
| * EC PKCS#8
 | |
| * EC SPKI/PKIX
 | |
| 
 | |
| First, some CONSTANTs:
 | |
| 
 | |
| ```js
 | |
| // 1.2.840.10045.3.1.7
 | |
| // prime256v1 (ANSI X9.62 named elliptic curve)
 | |
| var OBJ_ID_EC_256 = '06 08 2A8648CE3D030107'.replace(/\s+/g, '').toLowerCase();
 | |
| 
 | |
| // 1.3.132.0.34
 | |
| // secp384r1 (SECG (Certicom) named elliptic curve)
 | |
| var OBJ_ID_EC_384 = '06 05 2B81040022'.replace(/\s+/g, '').toLowerCase();
 | |
| 
 | |
| // 1.2.840.10045.2.1
 | |
| // ecPublicKey (ANSI X9.62 public key type)
 | |
| var OBJ_ID_EC_PUB = '06 07 2A8648CE3D0201'.replace(/\s+/g, '').toLowerCase();
 | |
| ```
 | |
| 
 | |
| ## EC sec1
 | |
| 
 | |
| ```js
 | |
| function packEcSec1(jwk) {
 | |
|   var d = toHex(base64ToUint8(urlBase64ToBase64(jwk.d)));
 | |
|   var x = toHex(base64ToUint8(urlBase64ToBase64(jwk.x)));
 | |
|   var y = toHex(base64ToUint8(urlBase64ToBase64(jwk.y)));
 | |
|   var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC_256 : OBJ_ID_EC_384;
 | |
|   return hexToUint8(
 | |
|     ASN1('30'                                 // Sequence
 | |
|     , ASN1.UInt('01')                         // Integer (Version 1)
 | |
|     , ASN1('04', d)                           // Octet String
 | |
|     , ASN1('A0', objId)                       // [0] Object ID
 | |
|     , ASN1('A1', ASN1.BitStr('04' + x + y)))  // [1] Embedded EC/ASN1 public key
 | |
|   );
 | |
| }
 | |
| ```
 | |
| 
 | |
| ## EC pkcs8
 | |
| 
 | |
| ```js
 | |
| function packEcPkcs8(jwk) {
 | |
|   var d = toHex(base64ToUint8(urlBase64ToBase64(jwk.d)));
 | |
|   var x = toHex(base64ToUint8(urlBase64ToBase64(jwk.x)));
 | |
|   var y = toHex(base64ToUint8(urlBase64ToBase64(jwk.y)));
 | |
|   var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC_256 : OBJ_ID_EC_384;
 | |
|   return hexToUint8(
 | |
|     ASN1('30'
 | |
|     , ASN1.UInt('00')
 | |
|     , ASN1('30'
 | |
|       , OBJ_ID_EC_PUB
 | |
|       , objId
 | |
|       )
 | |
|     , ASN1('04'
 | |
|       , ASN1('30'
 | |
|         , ASN1.UInt('01')
 | |
|         , ASN1('04', d)
 | |
|         , ASN1('A1', ASN1.BitStr('04' + x + y)))))
 | |
|   );
 | |
| }
 | |
| ```
 | |
| 
 | |
| ## EC spki/pkix
 | |
| 
 | |
| ```js
 | |
| function packEcSpki(jwk) {
 | |
|   var x = toHex(base64ToUint8(urlBase64ToBase64(jwk.x)));
 | |
|   var y = toHex(base64ToUint8(urlBase64ToBase64(jwk.y)));
 | |
|   var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC_256 : OBJ_ID_EC_384;
 | |
|   return hexToUint8(
 | |
|     ASN1('30'
 | |
|     , ASN1('30'
 | |
|       , OBJ_ID_EC_PUB
 | |
|       , objId
 | |
|       )
 | |
|     , ASN1.BitStr('04' + x + y))
 | |
|   );
 | |
| }
 | |
| var packPkix = packSpki;
 | |
| ```
 |