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;
|
|
```
|