diff --git a/.gitignore b/.gitignore
index 144585f..be8362d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+*.gz
+
# ---> Node
# Logs
logs
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..7e5d770
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "bracketSpacing": true,
+ "printWidth": 80,
+ "singleQuote": true,
+ "tabWidth": 4,
+ "trailingComma": "none",
+ "useTabs": true
+}
diff --git a/README.md b/README.md
index a22c788..f5c659e 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,187 @@
-# x509.js
+# @root/x509
-VanillaJS, Lightweight, Zero-Dependency, x509 (ASN.1, DER) encoder and decoder for Private / Public Keypairs and CSRs.
\ No newline at end of file
+Built by [The Root Company](https://therootcompany.com)
+for [Greenlock](https://greenlock.domains),
+[ACME.js](https://git.rootprojects.org/root/acme.js),
+and [Keypairs.js](https://git.rootprojects.org/root/keypairs.js)
+
+Lightweight, Zero-Dependency, x509 encoder and decoder for Node.js and Browsers
+
+| 1.6k gzipped
+| 6.9k minified
+| 9.9k pretty
+|
+
+This provides a set ASN.1 / x509 schemas for DER encoding and decoding
+Public / Private Keypairs and CSRs.
+
+- [x] Zero External Dependencies
+- [x] x509 schemas for common crypto
+ - [x] RSA & ECDSA Public/Private Keypairs
+ - PKCS1
+ - PKCS8
+ - SEC1
+ - SPKI
+ - PKIX
+ - [x] Certificate Signing Requests (CSR)
+ - PKCS10
+- [x] Universal Support
+ - [x] Node.js
+ - [x] Browsers
+- [x] Vanilla JS
+
+Looking for **easy**?
+
+You probably just want to use one of these:
+
+- [keypairs.js](https://git.rootprojects.org/root/keypairs.js)
+- [csr.js](https://git.rootprojects.org/root/csr.js)
+
+Looking for a **deep dive**? Well, in addition to x509.js,
+you'll probably also want one of more of these:
+
+- [encoding.js](https://git.rootprojects.org/root/encoding.js)
+- [asn1.js](https://git.rootprojects.org/root/asn1.js)
+- [csr.js](https://git.rootprojects.org/root/csr.js)
+- [pem.js](https://git.rootprojects.org/root/pem.js)
+- [keypairs.js](https://git.rootprojects.org/root/keypairs.js)
+
+Want to [contribute](#contributions)?
+Need [commercial support](#commercial-support)?
+
+# 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)
+
+```bash
+npm install --save @root/x509
+```
+
+```js
+var X509 = require('@root/x509');
+```
+
+```js
+// just the encoders
+var X509 = require('@root/x509/packers');
+```
+
+```js
+// just the decoders
+var X509 = require('@root/x509/parsers');
+```
+
+## Browsers (Vanilla JS)
+
+```html
+
+```
+
+```html
+
+```
+
+```js
+var X509 = window.X509;
+```
+
+# Usage
+
+This is a _very_ tiny, _very_ efficient x509 package.
+
+Rather than implementing full schemas as defined by the RFCs,
+it only implements the parts that are actually used in the wild
+by programs like `openssl`, Let's Encrypt, `ssh-keygen`, etc.
+
+Additionally, rather than always using a full parser,
+it uses happy-path heuristics to quickly and efficiently
+extract the necessary information. It likewise packs very quickly.
+
+## Encoders
+
+The packers encoder JWK as DER.
+
+```js
+X509.packPkcs1(jwk);
+X509.packSec1(jwk);
+X509.packPkcs8(jwk);
+X509.packSpki(jwk);
+X509.packPkix(jwk); // alias of X509.packSpki
+```
+
+There are two special functions specifically for
+embeding keys in CSRs.
+
+```js
+X509.packCsrRsaPublicKey(jwk);
+X509.packCsrEcPublicKey(jwk);
+```
+
+The rest of the CSR code is in [csr.js](https://git.rootprojects.org/root/csr.js).
+
+## Decoders
+
+The keypair format parsers each return a JWK, for convenience.
+To conserve memory, they expect taht you give an empty object
+as the `jwk` parameter.
+
+If you are using `crv: 'P-384'`, you should pass that in as part
+of the otherwise empty JWK.
+
+```js
+X509.parsePkcs1(buf, jwk);
+X509.parseSec1(buf, jwk);
+X509.parsePkcs8(buf, jwk);
+X509.parseSpki(buf, jwk);
+X509.parsePkix(buf, jwk); // aliase of parseSpki
+```
+
+# 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:
+- Patreon:
+
+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 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)
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..3469281
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+set -e
+set -u
+
+# TODO convert to JS
+cat parsers.js packers.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/x509.js
+echo "'use strict';" >> dist/x509.js
+echo "var X509 = window.X509 = {};" >> dist/x509.js
+echo "var ASN1 = window.ASN1;" >> dist/x509.js
+echo "var Enc = window.Encoding;" >> dist/x509.js
+cat all.tmp.js >> dist/x509.js
+rm all.tmp.js
+echo '}());' >> dist/x509.js
+
+rm -f dist/*.gz
+
+uglifyjs dist/x509.js > dist/x509.min.js
+gzip dist/x509.min.js
+uglifyjs dist/x509.js > dist/x509.min.js
+
+cat node_modules/@root/asn1/dist/asn1.all.js >> all.js
+cat dist/x509.js >> all.js
+mv all.js dist/x509.all.js
+
+uglifyjs dist/x509.all.js > dist/x509.all.min.js
+gzip dist/x509.all.min.js
+uglifyjs dist/x509.all.js > dist/x509.all.min.js
diff --git a/dist/x509.all.js b/dist/x509.all.js
new file mode 100644
index 0000000..c84e332
--- /dev/null
+++ b/dist/x509.all.js
@@ -0,0 +1,954 @@
+;(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);
+};
+}());
+;(function () {
+'use strict';
+var X509 = window.X509 = {};
+var ASN1 = window.ASN1;
+var Enc = window.Encoding;
+
+
+// 1.2.840.10045.3.1.7
+// prime256v1 (ANSI X9.62 named elliptic curve)
+var OBJ_ID_EC = '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();
+
+X509.parsePkcs1 = function parseRsaPkcs1(asn1, jwk) {
+ if (!jwk) {
+ jwk = {};
+ }
+
+ // might be a buffer
+ if (!Array.isArray(asn1)) {
+ asn1 = ASN1.parse({ der: asn1, verbose: true, json: false });
+ }
+
+ if (
+ !asn1.children.every(function(el) {
+ return 0x02 === el.type;
+ })
+ ) {
+ throw new Error(
+ 'not an RSA PKCS#1 public or private key (not all ints)'
+ );
+ }
+
+ if (2 === asn1.children.length) {
+ jwk.n = Enc.bufToUrlBase64(asn1.children[0].value);
+ jwk.e = Enc.bufToUrlBase64(asn1.children[1].value);
+ jwk.kty = 'RSA';
+ } else if (asn1.children.length >= 9) {
+ // the standard allows for "otherPrimeInfos", hence at least 9
+
+ jwk.n = Enc.bufToUrlBase64(asn1.children[1].value);
+ jwk.e = Enc.bufToUrlBase64(asn1.children[2].value);
+ jwk.d = Enc.bufToUrlBase64(asn1.children[3].value);
+ jwk.p = Enc.bufToUrlBase64(asn1.children[4].value);
+ jwk.q = Enc.bufToUrlBase64(asn1.children[5].value);
+ jwk.dp = Enc.bufToUrlBase64(asn1.children[6].value);
+ jwk.dq = Enc.bufToUrlBase64(asn1.children[7].value);
+ jwk.qi = Enc.bufToUrlBase64(asn1.children[8].value);
+ jwk.kty = 'RSA';
+ } else {
+ throw new Error(
+ 'not an RSA PKCS#1 public or private key (wrong number of ints)'
+ );
+ }
+
+ return jwk;
+};
+
+X509.parseSec1 = function parseEcOnlyPrivkey(u8, jwk) {
+ var index = 7;
+ var len = 32;
+ var olen = OBJ_ID_EC.length / 2;
+
+ if ('P-384' === jwk.crv) {
+ olen = OBJ_ID_EC_384.length / 2;
+ index = 8;
+ len = 48;
+ }
+ if (len !== u8[index - 1]) {
+ throw new Error('Unexpected bitlength ' + len);
+ }
+
+ // private part is d
+ var d = u8.slice(index, index + len);
+ // compression bit index
+ var ci = index + len + 2 + olen + 2 + 3;
+ var c = u8[ci];
+ var x, y;
+
+ if (0x04 === c) {
+ y = u8.slice(ci + 1 + len, ci + 1 + len + len);
+ } else if (0x02 !== c) {
+ throw new Error('not a supported EC private key');
+ }
+ x = u8.slice(ci + 1, ci + 1 + len);
+
+ return {
+ kty: jwk.kty || 'EC',
+ crv: jwk.crv || 'P-256',
+ d: Enc.bufToUrlBase64(d),
+ //, dh: Enc.bufToHex(d)
+ x: Enc.bufToUrlBase64(x),
+ //, xh: Enc.bufToHex(x)
+ y: Enc.bufToUrlBase64(y)
+ //, yh: Enc.bufToHex(y)
+ };
+};
+
+X509.parsePkcs8 = function(u8, jwk) {
+ try {
+ return X509.parseRsaPkcs8(u8, jwk);
+ } catch (e) {
+ return X509.parseEcPkcs8(u8, jwk);
+ }
+};
+
+X509.parseEcPkcs8 = function parseEcPkcs8(u8, jwk) {
+ var index = 24 + OBJ_ID_EC.length / 2;
+ var len = 32;
+ if ('P-384' === jwk.crv) {
+ index = 24 + OBJ_ID_EC_384.length / 2 + 2;
+ len = 48;
+ }
+
+ if (0x04 !== u8[index]) {
+ throw new Error('privkey not found');
+ }
+ var d = u8.slice(index + 2, index + 2 + len);
+ var ci = index + 2 + len + 5;
+ var xi = ci + 1;
+ var x = u8.slice(xi, xi + len);
+ var yi = xi + len;
+ var y;
+ if (0x04 === u8[ci]) {
+ y = u8.slice(yi, yi + len);
+ } else if (0x02 !== u8[ci]) {
+ throw new Error('invalid compression bit (expected 0x04 or 0x02)');
+ }
+
+ return {
+ kty: jwk.kty || 'EC',
+ crv: jwk.crv || 'P-256',
+ d: Enc.bufToUrlBase64(d),
+ //, dh: Enc.bufToHex(d)
+ x: Enc.bufToUrlBase64(x),
+ //, xh: Enc.bufToHex(x)
+ y: Enc.bufToUrlBase64(y)
+ //, yh: Enc.bufToHex(y)
+ };
+};
+
+X509.parseRsaPkcs8 = function parseRsaPkcs8(asn1, jwk) {
+ if (!jwk) {
+ jwk = {};
+ }
+
+ // might be a buffer
+ if (!Array.isArray(asn1)) {
+ /*
+ console.log(
+ JSON.stringify(ASN1.parse({ der: asn1, verbose: true, json: false }), null, 2)
+ );
+ */
+ asn1 = ASN1.parse({ der: asn1, verbose: true, json: false });
+ }
+ if (
+ 2 === asn1.children.length &&
+ 0x03 === asn1.children[1].type &&
+ 0x30 === asn1.children[1].value[0]
+ ) {
+ asn1 = ASN1.parse({
+ der: asn1.children[1].value,
+ verbose: true,
+ json: false
+ });
+ jwk.n = Enc.bufToUrlBase64(asn1.children[0].value);
+ jwk.e = Enc.bufToUrlBase64(asn1.children[1].value);
+ jwk.kty = 'RSA';
+ } else if (
+ 3 === asn1.children.length &&
+ 0x04 === asn1.children[2].type &&
+ 0x30 === asn1.children[2].children[0].type &&
+ 0x02 === asn1.children[2].children[0].children[0].type
+ ) {
+ asn1 = asn1.children[2].children[0];
+ jwk.n = Enc.bufToUrlBase64(asn1.children[1].value);
+ jwk.e = Enc.bufToUrlBase64(asn1.children[2].value);
+ jwk.d = Enc.bufToUrlBase64(asn1.children[3].value);
+ jwk.p = Enc.bufToUrlBase64(asn1.children[4].value);
+ jwk.q = Enc.bufToUrlBase64(asn1.children[5].value);
+ jwk.dp = Enc.bufToUrlBase64(asn1.children[6].value);
+ jwk.dq = Enc.bufToUrlBase64(asn1.children[7].value);
+ jwk.qi = Enc.bufToUrlBase64(asn1.children[8].value);
+ jwk.kty = 'RSA';
+ } else {
+ throw new Error(
+ 'not an RSA PKCS#8 public or private key (wrong format)'
+ );
+ }
+ return jwk;
+};
+
+X509.parseSpki = function(buf, jwk) {
+ try {
+ return X509.parseRsaPkcs8(buf, jwk);
+ } catch (e) {
+ //console.error(e);
+ return X509.parseEcSpki(buf, jwk);
+ }
+};
+
+X509.parseEcSpki = function(u8, jwk) {
+ var ci = 16 + OBJ_ID_EC.length / 2;
+ var len = 32;
+
+ if ('P-384' === jwk.crv) {
+ ci = 16 + OBJ_ID_EC_384.length / 2;
+ len = 48;
+ }
+
+ var c = u8[ci];
+ var xi = ci + 1;
+ var x = u8.slice(xi, xi + len);
+ var yi = xi + len;
+ var y;
+ if (0x04 === c) {
+ y = u8.slice(yi, yi + len);
+ } else if (0x02 !== c) {
+ throw new Error('not a supported EC private key');
+ }
+
+ return {
+ kty: jwk.kty || 'EC',
+ crv: jwk.crv || 'P-256',
+ x: Enc.bufToUrlBase64(x),
+ //, xh: Enc.bufToHex(x)
+ y: Enc.bufToUrlBase64(y)
+ //, yh: Enc.bufToHex(y)
+ };
+};
+
+X509.parsePkix = X509.parseSpki;
+
+
+// 1.2.840.10045.3.1.7
+// prime256v1 (ANSI X9.62 named elliptic curve)
+var OBJ_ID_EC = '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();
+
+var Asn1 = ASN1.Any;
+var UInt = ASN1.UInt;
+var BitStr = ASN1.BitStr;
+
+X509.packPkcs1 = function(jwk) {
+ var n = UInt(Enc.base64ToHex(jwk.n));
+ var e = UInt(Enc.base64ToHex(jwk.e));
+
+ if (!jwk.d) {
+ return Enc.hexToBuf(Asn1('30', n, e));
+ }
+
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ UInt('00'),
+ n,
+ e,
+ UInt(Enc.base64ToHex(jwk.d)),
+ UInt(Enc.base64ToHex(jwk.p)),
+ UInt(Enc.base64ToHex(jwk.q)),
+ UInt(Enc.base64ToHex(jwk.dp)),
+ UInt(Enc.base64ToHex(jwk.dq)),
+ UInt(Enc.base64ToHex(jwk.qi))
+ )
+ );
+};
+
+X509.packSec1 = function(jwk) {
+ var d = Enc.base64ToHex(jwk.d);
+ var x = Enc.base64ToHex(jwk.x);
+ var y = Enc.base64ToHex(jwk.y);
+ var objId = 'P-256' === jwk.crv ? OBJ_ID_EC : OBJ_ID_EC_384;
+
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ UInt('01'),
+ Asn1('04', d),
+ Asn1('A0', objId),
+ Asn1('A1', BitStr('04' + x + y))
+ )
+ );
+};
+/**
+ * take a private jwk and creates a der from it
+ * @param {*} jwk
+ */
+X509.packPkcs8 = function(jwk) {
+ if (/RSA/.test(jwk.kty)) {
+ return X509.packPkcs8Rsa(jwk);
+ }
+
+ return X509.packPkcs8Ec(jwk);
+};
+X509.packPkcs8Ec = function(jwk) {
+ var d = Enc.base64ToHex(jwk.d);
+ var x = Enc.base64ToHex(jwk.x);
+ var y = Enc.base64ToHex(jwk.y);
+ var objId = 'P-256' === jwk.crv ? OBJ_ID_EC : OBJ_ID_EC_384;
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ UInt('00'),
+ Asn1('30', OBJ_ID_EC_PUB, objId),
+ Asn1(
+ '04',
+ Asn1(
+ '30',
+ UInt('01'),
+ Asn1('04', d),
+ Asn1('A1', BitStr('04' + x + y))
+ )
+ )
+ )
+ );
+};
+
+X509.packPkcs8Rsa = function(jwk) {
+ if (!jwk.d) {
+ // Public RSA
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ Asn1('30', Asn1('06', '2a864886f70d010101'), Asn1('05')),
+ BitStr(
+ Asn1(
+ '30',
+ UInt(Enc.base64ToHex(jwk.n)),
+ UInt(Enc.base64ToHex(jwk.e))
+ )
+ )
+ )
+ );
+ }
+
+ // Private RSA
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ UInt('00'),
+ Asn1('30', Asn1('06', '2a864886f70d010101'), Asn1('05')),
+ Asn1(
+ '04',
+ Asn1(
+ '30',
+ UInt('00'),
+ UInt(Enc.base64ToHex(jwk.n)),
+ UInt(Enc.base64ToHex(jwk.e)),
+ UInt(Enc.base64ToHex(jwk.d)),
+ UInt(Enc.base64ToHex(jwk.p)),
+ UInt(Enc.base64ToHex(jwk.q)),
+ UInt(Enc.base64ToHex(jwk.dp)),
+ UInt(Enc.base64ToHex(jwk.dq)),
+ UInt(Enc.base64ToHex(jwk.qi))
+ )
+ )
+ )
+ );
+};
+X509.packSpkiEc = function(jwk) {
+ var x = Enc.base64ToHex(jwk.x);
+ var y = Enc.base64ToHex(jwk.y);
+ var objId = 'P-256' === jwk.crv ? OBJ_ID_EC : OBJ_ID_EC_384;
+ return Enc.hexToBuf(
+ Asn1('30', Asn1('30', OBJ_ID_EC_PUB, objId), BitStr('04' + x + y))
+ );
+};
+X509.packSpki = function(jwk) {
+ if (/RSA/i.test(jwk.kty)) {
+ return X509.packPkcs8Rsa(jwk);
+ }
+ return X509.packSpkiEc(jwk);
+};
+X509.packPkix = X509.packSpki;
+
+X509.packCsrRsaPublicKey = function(jwk) {
+ // Sequence the key
+ var n = UInt(Enc.base64ToHex(jwk.n));
+ var e = UInt(Enc.base64ToHex(jwk.e));
+ var asn1pub = Asn1('30', n, e);
+
+ // Add the CSR pub key header
+ return Asn1(
+ '30',
+ Asn1('30', Asn1('06', '2a864886f70d010101'), Asn1('05')),
+ BitStr(asn1pub)
+ );
+};
+
+X509.packCsrEcPublicKey = function(jwk) {
+ var ecOid = X509._oids[jwk.crv];
+ if (!ecOid) {
+ throw new Error(
+ "Unsupported namedCurve '" +
+ jwk.crv +
+ "'. Supported types are " +
+ Object.keys(X509._oids)
+ );
+ }
+ var cmp = '04'; // 04 == x+y, 02 == x-only
+ var hxy = '';
+ // Placeholder. I'm not even sure if compression should be supported.
+ if (!jwk.y) {
+ cmp = '02';
+ }
+ hxy += Enc.base64ToHex(jwk.x);
+ if (jwk.y) {
+ hxy += Enc.base64ToHex(jwk.y);
+ }
+
+ // 1.2.840.10045.2.1 ecPublicKey
+ return Asn1(
+ '30',
+ Asn1('30', Asn1('06', '2a8648ce3d0201'), Asn1('06', ecOid)),
+ BitStr(cmp + hxy)
+ );
+};
+
+X509._oids = {
+ // 1.2.840.10045.3.1.7 prime256v1
+ // (ANSI X9.62 named elliptic curve) (06 08 - 2A 86 48 CE 3D 03 01 07)
+ 'P-256': '2a8648ce3d030107',
+ // 1.3.132.0.34 P-384 (06 05 - 2B 81 04 00 22)
+ // (SEC 2 recommended EC domain secp256r1)
+ 'P-384': '2b81040022'
+ // 1.3.132.0.35 P-521 (06 05 - 2B 81 04 00 23)
+ // (SEC 2 alternate P-521)
+ //, 'P-521': '2B 81 04 00 23'
+};
+}());
diff --git a/dist/x509.all.min.js b/dist/x509.all.min.js
new file mode 100644
index 0000000..bdacf55
--- /dev/null
+++ b/dist/x509.all.min.js
@@ -0,0 +1 @@
+(function(){"use strict";var Enc=window.Encoding={};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))};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))};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))};Enc.bufToHex=function(u8){var hex=[];var i,h;var len=u8.byteLength||u8.length;for(i=0;i=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(iters2+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)}})();(function(){"use strict";var X509=window.X509={};var ASN1=window.ASN1;var Enc=window.Encoding;var OBJ_ID_EC="06 08 2A8648CE3D030107".replace(/\s+/g,"").toLowerCase();var OBJ_ID_EC_384="06 05 2B81040022".replace(/\s+/g,"").toLowerCase();X509.parsePkcs1=function parseRsaPkcs1(asn1,jwk){if(!jwk){jwk={}}if(!Array.isArray(asn1)){asn1=ASN1.parse({der:asn1,verbose:true,json:false})}if(!asn1.children.every(function(el){return 2===el.type})){throw new Error("not an RSA PKCS#1 public or private key (not all ints)")}if(2===asn1.children.length){jwk.n=Enc.bufToUrlBase64(asn1.children[0].value);jwk.e=Enc.bufToUrlBase64(asn1.children[1].value);jwk.kty="RSA"}else if(asn1.children.length>=9){jwk.n=Enc.bufToUrlBase64(asn1.children[1].value);jwk.e=Enc.bufToUrlBase64(asn1.children[2].value);jwk.d=Enc.bufToUrlBase64(asn1.children[3].value);jwk.p=Enc.bufToUrlBase64(asn1.children[4].value);jwk.q=Enc.bufToUrlBase64(asn1.children[5].value);jwk.dp=Enc.bufToUrlBase64(asn1.children[6].value);jwk.dq=Enc.bufToUrlBase64(asn1.children[7].value);jwk.qi=Enc.bufToUrlBase64(asn1.children[8].value);jwk.kty="RSA"}else{throw new Error("not an RSA PKCS#1 public or private key (wrong number of ints)")}return jwk};X509.parseSec1=function parseEcOnlyPrivkey(u8,jwk){var index=7;var len=32;var olen=OBJ_ID_EC.length/2;if("P-384"===jwk.crv){olen=OBJ_ID_EC_384.length/2;index=8;len=48}if(len!==u8[index-1]){throw new Error("Unexpected bitlength "+len)}var d=u8.slice(index,index+len);var ci=index+len+2+olen+2+3;var c=u8[ci];var x,y;if(4===c){y=u8.slice(ci+1+len,ci+1+len+len)}else if(2!==c){throw new Error("not a supported EC private key")}x=u8.slice(ci+1,ci+1+len);return{kty:jwk.kty||"EC",crv:jwk.crv||"P-256",d:Enc.bufToUrlBase64(d),x:Enc.bufToUrlBase64(x),y:Enc.bufToUrlBase64(y)}};X509.parsePkcs8=function(u8,jwk){try{return X509.parseRsaPkcs8(u8,jwk)}catch(e){return X509.parseEcPkcs8(u8,jwk)}};X509.parseEcPkcs8=function parseEcPkcs8(u8,jwk){var index=24+OBJ_ID_EC.length/2;var len=32;if("P-384"===jwk.crv){index=24+OBJ_ID_EC_384.length/2+2;len=48}if(4!==u8[index]){throw new Error("privkey not found")}var d=u8.slice(index+2,index+2+len);var ci=index+2+len+5;var xi=ci+1;var x=u8.slice(xi,xi+len);var yi=xi+len;var y;if(4===u8[ci]){y=u8.slice(yi,yi+len)}else if(2!==u8[ci]){throw new Error("invalid compression bit (expected 0x04 or 0x02)")}return{kty:jwk.kty||"EC",crv:jwk.crv||"P-256",d:Enc.bufToUrlBase64(d),x:Enc.bufToUrlBase64(x),y:Enc.bufToUrlBase64(y)}};X509.parseRsaPkcs8=function parseRsaPkcs8(asn1,jwk){if(!jwk){jwk={}}if(!Array.isArray(asn1)){asn1=ASN1.parse({der:asn1,verbose:true,json:false})}if(2===asn1.children.length&&3===asn1.children[1].type&&48===asn1.children[1].value[0]){asn1=ASN1.parse({der:asn1.children[1].value,verbose:true,json:false});jwk.n=Enc.bufToUrlBase64(asn1.children[0].value);jwk.e=Enc.bufToUrlBase64(asn1.children[1].value);jwk.kty="RSA"}else if(3===asn1.children.length&&4===asn1.children[2].type&&48===asn1.children[2].children[0].type&&2===asn1.children[2].children[0].children[0].type){asn1=asn1.children[2].children[0];jwk.n=Enc.bufToUrlBase64(asn1.children[1].value);jwk.e=Enc.bufToUrlBase64(asn1.children[2].value);jwk.d=Enc.bufToUrlBase64(asn1.children[3].value);jwk.p=Enc.bufToUrlBase64(asn1.children[4].value);jwk.q=Enc.bufToUrlBase64(asn1.children[5].value);jwk.dp=Enc.bufToUrlBase64(asn1.children[6].value);jwk.dq=Enc.bufToUrlBase64(asn1.children[7].value);jwk.qi=Enc.bufToUrlBase64(asn1.children[8].value);jwk.kty="RSA"}else{throw new Error("not an RSA PKCS#8 public or private key (wrong format)")}return jwk};X509.parseSpki=function(buf,jwk){try{return X509.parseRsaPkcs8(buf,jwk)}catch(e){return X509.parseEcSpki(buf,jwk)}};X509.parseEcSpki=function(u8,jwk){var ci=16+OBJ_ID_EC.length/2;var len=32;if("P-384"===jwk.crv){ci=16+OBJ_ID_EC_384.length/2;len=48}var c=u8[ci];var xi=ci+1;var x=u8.slice(xi,xi+len);var yi=xi+len;var y;if(4===c){y=u8.slice(yi,yi+len)}else if(2!==c){throw new Error("not a supported EC private key")}return{kty:jwk.kty||"EC",crv:jwk.crv||"P-256",x:Enc.bufToUrlBase64(x),y:Enc.bufToUrlBase64(y)}};X509.parsePkix=X509.parseSpki;var OBJ_ID_EC="06 08 2A8648CE3D030107".replace(/\s+/g,"").toLowerCase();var OBJ_ID_EC_384="06 05 2B81040022".replace(/\s+/g,"").toLowerCase();var OBJ_ID_EC_PUB="06 07 2A8648CE3D0201".replace(/\s+/g,"").toLowerCase();var Asn1=ASN1.Any;var UInt=ASN1.UInt;var BitStr=ASN1.BitStr;X509.packPkcs1=function(jwk){var n=UInt(Enc.base64ToHex(jwk.n));var e=UInt(Enc.base64ToHex(jwk.e));if(!jwk.d){return Enc.hexToBuf(Asn1("30",n,e))}return Enc.hexToBuf(Asn1("30",UInt("00"),n,e,UInt(Enc.base64ToHex(jwk.d)),UInt(Enc.base64ToHex(jwk.p)),UInt(Enc.base64ToHex(jwk.q)),UInt(Enc.base64ToHex(jwk.dp)),UInt(Enc.base64ToHex(jwk.dq)),UInt(Enc.base64ToHex(jwk.qi))))};X509.packSec1=function(jwk){var d=Enc.base64ToHex(jwk.d);var x=Enc.base64ToHex(jwk.x);var y=Enc.base64ToHex(jwk.y);var objId="P-256"===jwk.crv?OBJ_ID_EC:OBJ_ID_EC_384;return Enc.hexToBuf(Asn1("30",UInt("01"),Asn1("04",d),Asn1("A0",objId),Asn1("A1",BitStr("04"+x+y))))};X509.packPkcs8=function(jwk){if(/RSA/.test(jwk.kty)){return X509.packPkcs8Rsa(jwk)}return X509.packPkcs8Ec(jwk)};X509.packPkcs8Ec=function(jwk){var d=Enc.base64ToHex(jwk.d);var x=Enc.base64ToHex(jwk.x);var y=Enc.base64ToHex(jwk.y);var objId="P-256"===jwk.crv?OBJ_ID_EC:OBJ_ID_EC_384;return Enc.hexToBuf(Asn1("30",UInt("00"),Asn1("30",OBJ_ID_EC_PUB,objId),Asn1("04",Asn1("30",UInt("01"),Asn1("04",d),Asn1("A1",BitStr("04"+x+y))))))};X509.packPkcs8Rsa=function(jwk){if(!jwk.d){return Enc.hexToBuf(Asn1("30",Asn1("30",Asn1("06","2a864886f70d010101"),Asn1("05")),BitStr(Asn1("30",UInt(Enc.base64ToHex(jwk.n)),UInt(Enc.base64ToHex(jwk.e))))))}return Enc.hexToBuf(Asn1("30",UInt("00"),Asn1("30",Asn1("06","2a864886f70d010101"),Asn1("05")),Asn1("04",Asn1("30",UInt("00"),UInt(Enc.base64ToHex(jwk.n)),UInt(Enc.base64ToHex(jwk.e)),UInt(Enc.base64ToHex(jwk.d)),UInt(Enc.base64ToHex(jwk.p)),UInt(Enc.base64ToHex(jwk.q)),UInt(Enc.base64ToHex(jwk.dp)),UInt(Enc.base64ToHex(jwk.dq)),UInt(Enc.base64ToHex(jwk.qi))))))};X509.packSpkiEc=function(jwk){var x=Enc.base64ToHex(jwk.x);var y=Enc.base64ToHex(jwk.y);var objId="P-256"===jwk.crv?OBJ_ID_EC:OBJ_ID_EC_384;return Enc.hexToBuf(Asn1("30",Asn1("30",OBJ_ID_EC_PUB,objId),BitStr("04"+x+y)))};X509.packSpki=function(jwk){if(/RSA/i.test(jwk.kty)){return X509.packPkcs8Rsa(jwk)}return X509.packSpkiEc(jwk)};X509.packPkix=X509.packSpki;X509.packCsrRsaPublicKey=function(jwk){var n=UInt(Enc.base64ToHex(jwk.n));var e=UInt(Enc.base64ToHex(jwk.e));var asn1pub=Asn1("30",n,e);return Asn1("30",Asn1("30",Asn1("06","2a864886f70d010101"),Asn1("05")),BitStr(asn1pub))};X509.packCsrEcPublicKey=function(jwk){var ecOid=X509._oids[jwk.crv];if(!ecOid){throw new Error("Unsupported namedCurve '"+jwk.crv+"'. Supported types are "+Object.keys(X509._oids))}var cmp="04";var hxy="";if(!jwk.y){cmp="02"}hxy+=Enc.base64ToHex(jwk.x);if(jwk.y){hxy+=Enc.base64ToHex(jwk.y)}return Asn1("30",Asn1("30",Asn1("06","2a8648ce3d0201"),Asn1("06",ecOid)),BitStr(cmp+hxy))};X509._oids={"P-256":"2a8648ce3d030107","P-384":"2b81040022"}})();
diff --git a/dist/x509.js b/dist/x509.js
new file mode 100644
index 0000000..601a7fe
--- /dev/null
+++ b/dist/x509.js
@@ -0,0 +1,436 @@
+;(function () {
+'use strict';
+var X509 = window.X509 = {};
+var ASN1 = window.ASN1;
+var Enc = window.Encoding;
+
+
+// 1.2.840.10045.3.1.7
+// prime256v1 (ANSI X9.62 named elliptic curve)
+var OBJ_ID_EC = '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();
+
+X509.parsePkcs1 = function parseRsaPkcs1(asn1, jwk) {
+ if (!jwk) {
+ jwk = {};
+ }
+
+ // might be a buffer
+ if (!Array.isArray(asn1)) {
+ asn1 = ASN1.parse({ der: asn1, verbose: true, json: false });
+ }
+
+ if (
+ !asn1.children.every(function(el) {
+ return 0x02 === el.type;
+ })
+ ) {
+ throw new Error(
+ 'not an RSA PKCS#1 public or private key (not all ints)'
+ );
+ }
+
+ if (2 === asn1.children.length) {
+ jwk.n = Enc.bufToUrlBase64(asn1.children[0].value);
+ jwk.e = Enc.bufToUrlBase64(asn1.children[1].value);
+ jwk.kty = 'RSA';
+ } else if (asn1.children.length >= 9) {
+ // the standard allows for "otherPrimeInfos", hence at least 9
+
+ jwk.n = Enc.bufToUrlBase64(asn1.children[1].value);
+ jwk.e = Enc.bufToUrlBase64(asn1.children[2].value);
+ jwk.d = Enc.bufToUrlBase64(asn1.children[3].value);
+ jwk.p = Enc.bufToUrlBase64(asn1.children[4].value);
+ jwk.q = Enc.bufToUrlBase64(asn1.children[5].value);
+ jwk.dp = Enc.bufToUrlBase64(asn1.children[6].value);
+ jwk.dq = Enc.bufToUrlBase64(asn1.children[7].value);
+ jwk.qi = Enc.bufToUrlBase64(asn1.children[8].value);
+ jwk.kty = 'RSA';
+ } else {
+ throw new Error(
+ 'not an RSA PKCS#1 public or private key (wrong number of ints)'
+ );
+ }
+
+ return jwk;
+};
+
+X509.parseSec1 = function parseEcOnlyPrivkey(u8, jwk) {
+ var index = 7;
+ var len = 32;
+ var olen = OBJ_ID_EC.length / 2;
+
+ if ('P-384' === jwk.crv) {
+ olen = OBJ_ID_EC_384.length / 2;
+ index = 8;
+ len = 48;
+ }
+ if (len !== u8[index - 1]) {
+ throw new Error('Unexpected bitlength ' + len);
+ }
+
+ // private part is d
+ var d = u8.slice(index, index + len);
+ // compression bit index
+ var ci = index + len + 2 + olen + 2 + 3;
+ var c = u8[ci];
+ var x, y;
+
+ if (0x04 === c) {
+ y = u8.slice(ci + 1 + len, ci + 1 + len + len);
+ } else if (0x02 !== c) {
+ throw new Error('not a supported EC private key');
+ }
+ x = u8.slice(ci + 1, ci + 1 + len);
+
+ return {
+ kty: jwk.kty || 'EC',
+ crv: jwk.crv || 'P-256',
+ d: Enc.bufToUrlBase64(d),
+ //, dh: Enc.bufToHex(d)
+ x: Enc.bufToUrlBase64(x),
+ //, xh: Enc.bufToHex(x)
+ y: Enc.bufToUrlBase64(y)
+ //, yh: Enc.bufToHex(y)
+ };
+};
+
+X509.parsePkcs8 = function(u8, jwk) {
+ try {
+ return X509.parseRsaPkcs8(u8, jwk);
+ } catch (e) {
+ return X509.parseEcPkcs8(u8, jwk);
+ }
+};
+
+X509.parseEcPkcs8 = function parseEcPkcs8(u8, jwk) {
+ var index = 24 + OBJ_ID_EC.length / 2;
+ var len = 32;
+ if ('P-384' === jwk.crv) {
+ index = 24 + OBJ_ID_EC_384.length / 2 + 2;
+ len = 48;
+ }
+
+ if (0x04 !== u8[index]) {
+ throw new Error('privkey not found');
+ }
+ var d = u8.slice(index + 2, index + 2 + len);
+ var ci = index + 2 + len + 5;
+ var xi = ci + 1;
+ var x = u8.slice(xi, xi + len);
+ var yi = xi + len;
+ var y;
+ if (0x04 === u8[ci]) {
+ y = u8.slice(yi, yi + len);
+ } else if (0x02 !== u8[ci]) {
+ throw new Error('invalid compression bit (expected 0x04 or 0x02)');
+ }
+
+ return {
+ kty: jwk.kty || 'EC',
+ crv: jwk.crv || 'P-256',
+ d: Enc.bufToUrlBase64(d),
+ //, dh: Enc.bufToHex(d)
+ x: Enc.bufToUrlBase64(x),
+ //, xh: Enc.bufToHex(x)
+ y: Enc.bufToUrlBase64(y)
+ //, yh: Enc.bufToHex(y)
+ };
+};
+
+X509.parseRsaPkcs8 = function parseRsaPkcs8(asn1, jwk) {
+ if (!jwk) {
+ jwk = {};
+ }
+
+ // might be a buffer
+ if (!Array.isArray(asn1)) {
+ /*
+ console.log(
+ JSON.stringify(ASN1.parse({ der: asn1, verbose: true, json: false }), null, 2)
+ );
+ */
+ asn1 = ASN1.parse({ der: asn1, verbose: true, json: false });
+ }
+ if (
+ 2 === asn1.children.length &&
+ 0x03 === asn1.children[1].type &&
+ 0x30 === asn1.children[1].value[0]
+ ) {
+ asn1 = ASN1.parse({
+ der: asn1.children[1].value,
+ verbose: true,
+ json: false
+ });
+ jwk.n = Enc.bufToUrlBase64(asn1.children[0].value);
+ jwk.e = Enc.bufToUrlBase64(asn1.children[1].value);
+ jwk.kty = 'RSA';
+ } else if (
+ 3 === asn1.children.length &&
+ 0x04 === asn1.children[2].type &&
+ 0x30 === asn1.children[2].children[0].type &&
+ 0x02 === asn1.children[2].children[0].children[0].type
+ ) {
+ asn1 = asn1.children[2].children[0];
+ jwk.n = Enc.bufToUrlBase64(asn1.children[1].value);
+ jwk.e = Enc.bufToUrlBase64(asn1.children[2].value);
+ jwk.d = Enc.bufToUrlBase64(asn1.children[3].value);
+ jwk.p = Enc.bufToUrlBase64(asn1.children[4].value);
+ jwk.q = Enc.bufToUrlBase64(asn1.children[5].value);
+ jwk.dp = Enc.bufToUrlBase64(asn1.children[6].value);
+ jwk.dq = Enc.bufToUrlBase64(asn1.children[7].value);
+ jwk.qi = Enc.bufToUrlBase64(asn1.children[8].value);
+ jwk.kty = 'RSA';
+ } else {
+ throw new Error(
+ 'not an RSA PKCS#8 public or private key (wrong format)'
+ );
+ }
+ return jwk;
+};
+
+X509.parseSpki = function(buf, jwk) {
+ try {
+ return X509.parseRsaPkcs8(buf, jwk);
+ } catch (e) {
+ //console.error(e);
+ return X509.parseEcSpki(buf, jwk);
+ }
+};
+
+X509.parseEcSpki = function(u8, jwk) {
+ var ci = 16 + OBJ_ID_EC.length / 2;
+ var len = 32;
+
+ if ('P-384' === jwk.crv) {
+ ci = 16 + OBJ_ID_EC_384.length / 2;
+ len = 48;
+ }
+
+ var c = u8[ci];
+ var xi = ci + 1;
+ var x = u8.slice(xi, xi + len);
+ var yi = xi + len;
+ var y;
+ if (0x04 === c) {
+ y = u8.slice(yi, yi + len);
+ } else if (0x02 !== c) {
+ throw new Error('not a supported EC private key');
+ }
+
+ return {
+ kty: jwk.kty || 'EC',
+ crv: jwk.crv || 'P-256',
+ x: Enc.bufToUrlBase64(x),
+ //, xh: Enc.bufToHex(x)
+ y: Enc.bufToUrlBase64(y)
+ //, yh: Enc.bufToHex(y)
+ };
+};
+
+X509.parsePkix = X509.parseSpki;
+
+
+// 1.2.840.10045.3.1.7
+// prime256v1 (ANSI X9.62 named elliptic curve)
+var OBJ_ID_EC = '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();
+
+var Asn1 = ASN1.Any;
+var UInt = ASN1.UInt;
+var BitStr = ASN1.BitStr;
+
+X509.packPkcs1 = function(jwk) {
+ var n = UInt(Enc.base64ToHex(jwk.n));
+ var e = UInt(Enc.base64ToHex(jwk.e));
+
+ if (!jwk.d) {
+ return Enc.hexToBuf(Asn1('30', n, e));
+ }
+
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ UInt('00'),
+ n,
+ e,
+ UInt(Enc.base64ToHex(jwk.d)),
+ UInt(Enc.base64ToHex(jwk.p)),
+ UInt(Enc.base64ToHex(jwk.q)),
+ UInt(Enc.base64ToHex(jwk.dp)),
+ UInt(Enc.base64ToHex(jwk.dq)),
+ UInt(Enc.base64ToHex(jwk.qi))
+ )
+ );
+};
+
+X509.packSec1 = function(jwk) {
+ var d = Enc.base64ToHex(jwk.d);
+ var x = Enc.base64ToHex(jwk.x);
+ var y = Enc.base64ToHex(jwk.y);
+ var objId = 'P-256' === jwk.crv ? OBJ_ID_EC : OBJ_ID_EC_384;
+
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ UInt('01'),
+ Asn1('04', d),
+ Asn1('A0', objId),
+ Asn1('A1', BitStr('04' + x + y))
+ )
+ );
+};
+/**
+ * take a private jwk and creates a der from it
+ * @param {*} jwk
+ */
+X509.packPkcs8 = function(jwk) {
+ if (/RSA/.test(jwk.kty)) {
+ return X509.packPkcs8Rsa(jwk);
+ }
+
+ return X509.packPkcs8Ec(jwk);
+};
+X509.packPkcs8Ec = function(jwk) {
+ var d = Enc.base64ToHex(jwk.d);
+ var x = Enc.base64ToHex(jwk.x);
+ var y = Enc.base64ToHex(jwk.y);
+ var objId = 'P-256' === jwk.crv ? OBJ_ID_EC : OBJ_ID_EC_384;
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ UInt('00'),
+ Asn1('30', OBJ_ID_EC_PUB, objId),
+ Asn1(
+ '04',
+ Asn1(
+ '30',
+ UInt('01'),
+ Asn1('04', d),
+ Asn1('A1', BitStr('04' + x + y))
+ )
+ )
+ )
+ );
+};
+
+X509.packPkcs8Rsa = function(jwk) {
+ if (!jwk.d) {
+ // Public RSA
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ Asn1('30', Asn1('06', '2a864886f70d010101'), Asn1('05')),
+ BitStr(
+ Asn1(
+ '30',
+ UInt(Enc.base64ToHex(jwk.n)),
+ UInt(Enc.base64ToHex(jwk.e))
+ )
+ )
+ )
+ );
+ }
+
+ // Private RSA
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ UInt('00'),
+ Asn1('30', Asn1('06', '2a864886f70d010101'), Asn1('05')),
+ Asn1(
+ '04',
+ Asn1(
+ '30',
+ UInt('00'),
+ UInt(Enc.base64ToHex(jwk.n)),
+ UInt(Enc.base64ToHex(jwk.e)),
+ UInt(Enc.base64ToHex(jwk.d)),
+ UInt(Enc.base64ToHex(jwk.p)),
+ UInt(Enc.base64ToHex(jwk.q)),
+ UInt(Enc.base64ToHex(jwk.dp)),
+ UInt(Enc.base64ToHex(jwk.dq)),
+ UInt(Enc.base64ToHex(jwk.qi))
+ )
+ )
+ )
+ );
+};
+X509.packSpkiEc = function(jwk) {
+ var x = Enc.base64ToHex(jwk.x);
+ var y = Enc.base64ToHex(jwk.y);
+ var objId = 'P-256' === jwk.crv ? OBJ_ID_EC : OBJ_ID_EC_384;
+ return Enc.hexToBuf(
+ Asn1('30', Asn1('30', OBJ_ID_EC_PUB, objId), BitStr('04' + x + y))
+ );
+};
+X509.packSpki = function(jwk) {
+ if (/RSA/i.test(jwk.kty)) {
+ return X509.packPkcs8Rsa(jwk);
+ }
+ return X509.packSpkiEc(jwk);
+};
+X509.packPkix = X509.packSpki;
+
+X509.packCsrRsaPublicKey = function(jwk) {
+ // Sequence the key
+ var n = UInt(Enc.base64ToHex(jwk.n));
+ var e = UInt(Enc.base64ToHex(jwk.e));
+ var asn1pub = Asn1('30', n, e);
+
+ // Add the CSR pub key header
+ return Asn1(
+ '30',
+ Asn1('30', Asn1('06', '2a864886f70d010101'), Asn1('05')),
+ BitStr(asn1pub)
+ );
+};
+
+X509.packCsrEcPublicKey = function(jwk) {
+ var ecOid = X509._oids[jwk.crv];
+ if (!ecOid) {
+ throw new Error(
+ "Unsupported namedCurve '" +
+ jwk.crv +
+ "'. Supported types are " +
+ Object.keys(X509._oids)
+ );
+ }
+ var cmp = '04'; // 04 == x+y, 02 == x-only
+ var hxy = '';
+ // Placeholder. I'm not even sure if compression should be supported.
+ if (!jwk.y) {
+ cmp = '02';
+ }
+ hxy += Enc.base64ToHex(jwk.x);
+ if (jwk.y) {
+ hxy += Enc.base64ToHex(jwk.y);
+ }
+
+ // 1.2.840.10045.2.1 ecPublicKey
+ return Asn1(
+ '30',
+ Asn1('30', Asn1('06', '2a8648ce3d0201'), Asn1('06', ecOid)),
+ BitStr(cmp + hxy)
+ );
+};
+
+X509._oids = {
+ // 1.2.840.10045.3.1.7 prime256v1
+ // (ANSI X9.62 named elliptic curve) (06 08 - 2A 86 48 CE 3D 03 01 07)
+ 'P-256': '2a8648ce3d030107',
+ // 1.3.132.0.34 P-384 (06 05 - 2B 81 04 00 22)
+ // (SEC 2 recommended EC domain secp256r1)
+ 'P-384': '2b81040022'
+ // 1.3.132.0.35 P-521 (06 05 - 2B 81 04 00 23)
+ // (SEC 2 alternate P-521)
+ //, 'P-521': '2B 81 04 00 23'
+};
+}());
diff --git a/dist/x509.min.js b/dist/x509.min.js
new file mode 100644
index 0000000..b22584e
--- /dev/null
+++ b/dist/x509.min.js
@@ -0,0 +1 @@
+(function(){"use strict";var X509=window.X509={};var ASN1=window.ASN1;var Enc=window.Encoding;var OBJ_ID_EC="06 08 2A8648CE3D030107".replace(/\s+/g,"").toLowerCase();var OBJ_ID_EC_384="06 05 2B81040022".replace(/\s+/g,"").toLowerCase();X509.parsePkcs1=function parseRsaPkcs1(asn1,jwk){if(!jwk){jwk={}}if(!Array.isArray(asn1)){asn1=ASN1.parse({der:asn1,verbose:true,json:false})}if(!asn1.children.every(function(el){return 2===el.type})){throw new Error("not an RSA PKCS#1 public or private key (not all ints)")}if(2===asn1.children.length){jwk.n=Enc.bufToUrlBase64(asn1.children[0].value);jwk.e=Enc.bufToUrlBase64(asn1.children[1].value);jwk.kty="RSA"}else if(asn1.children.length>=9){jwk.n=Enc.bufToUrlBase64(asn1.children[1].value);jwk.e=Enc.bufToUrlBase64(asn1.children[2].value);jwk.d=Enc.bufToUrlBase64(asn1.children[3].value);jwk.p=Enc.bufToUrlBase64(asn1.children[4].value);jwk.q=Enc.bufToUrlBase64(asn1.children[5].value);jwk.dp=Enc.bufToUrlBase64(asn1.children[6].value);jwk.dq=Enc.bufToUrlBase64(asn1.children[7].value);jwk.qi=Enc.bufToUrlBase64(asn1.children[8].value);jwk.kty="RSA"}else{throw new Error("not an RSA PKCS#1 public or private key (wrong number of ints)")}return jwk};X509.parseSec1=function parseEcOnlyPrivkey(u8,jwk){var index=7;var len=32;var olen=OBJ_ID_EC.length/2;if("P-384"===jwk.crv){olen=OBJ_ID_EC_384.length/2;index=8;len=48}if(len!==u8[index-1]){throw new Error("Unexpected bitlength "+len)}var d=u8.slice(index,index+len);var ci=index+len+2+olen+2+3;var c=u8[ci];var x,y;if(4===c){y=u8.slice(ci+1+len,ci+1+len+len)}else if(2!==c){throw new Error("not a supported EC private key")}x=u8.slice(ci+1,ci+1+len);return{kty:jwk.kty||"EC",crv:jwk.crv||"P-256",d:Enc.bufToUrlBase64(d),x:Enc.bufToUrlBase64(x),y:Enc.bufToUrlBase64(y)}};X509.parsePkcs8=function(u8,jwk){try{return X509.parseRsaPkcs8(u8,jwk)}catch(e){return X509.parseEcPkcs8(u8,jwk)}};X509.parseEcPkcs8=function parseEcPkcs8(u8,jwk){var index=24+OBJ_ID_EC.length/2;var len=32;if("P-384"===jwk.crv){index=24+OBJ_ID_EC_384.length/2+2;len=48}if(4!==u8[index]){throw new Error("privkey not found")}var d=u8.slice(index+2,index+2+len);var ci=index+2+len+5;var xi=ci+1;var x=u8.slice(xi,xi+len);var yi=xi+len;var y;if(4===u8[ci]){y=u8.slice(yi,yi+len)}else if(2!==u8[ci]){throw new Error("invalid compression bit (expected 0x04 or 0x02)")}return{kty:jwk.kty||"EC",crv:jwk.crv||"P-256",d:Enc.bufToUrlBase64(d),x:Enc.bufToUrlBase64(x),y:Enc.bufToUrlBase64(y)}};X509.parseRsaPkcs8=function parseRsaPkcs8(asn1,jwk){if(!jwk){jwk={}}if(!Array.isArray(asn1)){asn1=ASN1.parse({der:asn1,verbose:true,json:false})}if(2===asn1.children.length&&3===asn1.children[1].type&&48===asn1.children[1].value[0]){asn1=ASN1.parse({der:asn1.children[1].value,verbose:true,json:false});jwk.n=Enc.bufToUrlBase64(asn1.children[0].value);jwk.e=Enc.bufToUrlBase64(asn1.children[1].value);jwk.kty="RSA"}else if(3===asn1.children.length&&4===asn1.children[2].type&&48===asn1.children[2].children[0].type&&2===asn1.children[2].children[0].children[0].type){asn1=asn1.children[2].children[0];jwk.n=Enc.bufToUrlBase64(asn1.children[1].value);jwk.e=Enc.bufToUrlBase64(asn1.children[2].value);jwk.d=Enc.bufToUrlBase64(asn1.children[3].value);jwk.p=Enc.bufToUrlBase64(asn1.children[4].value);jwk.q=Enc.bufToUrlBase64(asn1.children[5].value);jwk.dp=Enc.bufToUrlBase64(asn1.children[6].value);jwk.dq=Enc.bufToUrlBase64(asn1.children[7].value);jwk.qi=Enc.bufToUrlBase64(asn1.children[8].value);jwk.kty="RSA"}else{throw new Error("not an RSA PKCS#8 public or private key (wrong format)")}return jwk};X509.parseSpki=function(buf,jwk){try{return X509.parseRsaPkcs8(buf,jwk)}catch(e){return X509.parseEcSpki(buf,jwk)}};X509.parseEcSpki=function(u8,jwk){var ci=16+OBJ_ID_EC.length/2;var len=32;if("P-384"===jwk.crv){ci=16+OBJ_ID_EC_384.length/2;len=48}var c=u8[ci];var xi=ci+1;var x=u8.slice(xi,xi+len);var yi=xi+len;var y;if(4===c){y=u8.slice(yi,yi+len)}else if(2!==c){throw new Error("not a supported EC private key")}return{kty:jwk.kty||"EC",crv:jwk.crv||"P-256",x:Enc.bufToUrlBase64(x),y:Enc.bufToUrlBase64(y)}};X509.parsePkix=X509.parseSpki;var OBJ_ID_EC="06 08 2A8648CE3D030107".replace(/\s+/g,"").toLowerCase();var OBJ_ID_EC_384="06 05 2B81040022".replace(/\s+/g,"").toLowerCase();var OBJ_ID_EC_PUB="06 07 2A8648CE3D0201".replace(/\s+/g,"").toLowerCase();var Asn1=ASN1.Any;var UInt=ASN1.UInt;var BitStr=ASN1.BitStr;X509.packPkcs1=function(jwk){var n=UInt(Enc.base64ToHex(jwk.n));var e=UInt(Enc.base64ToHex(jwk.e));if(!jwk.d){return Enc.hexToBuf(Asn1("30",n,e))}return Enc.hexToBuf(Asn1("30",UInt("00"),n,e,UInt(Enc.base64ToHex(jwk.d)),UInt(Enc.base64ToHex(jwk.p)),UInt(Enc.base64ToHex(jwk.q)),UInt(Enc.base64ToHex(jwk.dp)),UInt(Enc.base64ToHex(jwk.dq)),UInt(Enc.base64ToHex(jwk.qi))))};X509.packSec1=function(jwk){var d=Enc.base64ToHex(jwk.d);var x=Enc.base64ToHex(jwk.x);var y=Enc.base64ToHex(jwk.y);var objId="P-256"===jwk.crv?OBJ_ID_EC:OBJ_ID_EC_384;return Enc.hexToBuf(Asn1("30",UInt("01"),Asn1("04",d),Asn1("A0",objId),Asn1("A1",BitStr("04"+x+y))))};X509.packPkcs8=function(jwk){if(/RSA/.test(jwk.kty)){return X509.packPkcs8Rsa(jwk)}return X509.packPkcs8Ec(jwk)};X509.packPkcs8Ec=function(jwk){var d=Enc.base64ToHex(jwk.d);var x=Enc.base64ToHex(jwk.x);var y=Enc.base64ToHex(jwk.y);var objId="P-256"===jwk.crv?OBJ_ID_EC:OBJ_ID_EC_384;return Enc.hexToBuf(Asn1("30",UInt("00"),Asn1("30",OBJ_ID_EC_PUB,objId),Asn1("04",Asn1("30",UInt("01"),Asn1("04",d),Asn1("A1",BitStr("04"+x+y))))))};X509.packPkcs8Rsa=function(jwk){if(!jwk.d){return Enc.hexToBuf(Asn1("30",Asn1("30",Asn1("06","2a864886f70d010101"),Asn1("05")),BitStr(Asn1("30",UInt(Enc.base64ToHex(jwk.n)),UInt(Enc.base64ToHex(jwk.e))))))}return Enc.hexToBuf(Asn1("30",UInt("00"),Asn1("30",Asn1("06","2a864886f70d010101"),Asn1("05")),Asn1("04",Asn1("30",UInt("00"),UInt(Enc.base64ToHex(jwk.n)),UInt(Enc.base64ToHex(jwk.e)),UInt(Enc.base64ToHex(jwk.d)),UInt(Enc.base64ToHex(jwk.p)),UInt(Enc.base64ToHex(jwk.q)),UInt(Enc.base64ToHex(jwk.dp)),UInt(Enc.base64ToHex(jwk.dq)),UInt(Enc.base64ToHex(jwk.qi))))))};X509.packSpkiEc=function(jwk){var x=Enc.base64ToHex(jwk.x);var y=Enc.base64ToHex(jwk.y);var objId="P-256"===jwk.crv?OBJ_ID_EC:OBJ_ID_EC_384;return Enc.hexToBuf(Asn1("30",Asn1("30",OBJ_ID_EC_PUB,objId),BitStr("04"+x+y)))};X509.packSpki=function(jwk){if(/RSA/i.test(jwk.kty)){return X509.packPkcs8Rsa(jwk)}return X509.packSpkiEc(jwk)};X509.packPkix=X509.packSpki;X509.packCsrRsaPublicKey=function(jwk){var n=UInt(Enc.base64ToHex(jwk.n));var e=UInt(Enc.base64ToHex(jwk.e));var asn1pub=Asn1("30",n,e);return Asn1("30",Asn1("30",Asn1("06","2a864886f70d010101"),Asn1("05")),BitStr(asn1pub))};X509.packCsrEcPublicKey=function(jwk){var ecOid=X509._oids[jwk.crv];if(!ecOid){throw new Error("Unsupported namedCurve '"+jwk.crv+"'. Supported types are "+Object.keys(X509._oids))}var cmp="04";var hxy="";if(!jwk.y){cmp="02"}hxy+=Enc.base64ToHex(jwk.x);if(jwk.y){hxy+=Enc.base64ToHex(jwk.y)}return Asn1("30",Asn1("30",Asn1("06","2a8648ce3d0201"),Asn1("06",ecOid)),BitStr(cmp+hxy))};X509._oids={"P-256":"2a8648ce3d030107","P-384":"2b81040022"}})();
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..7bf24f3
--- /dev/null
+++ b/index.js
@@ -0,0 +1,11 @@
+'use strict';
+
+var X509 = module.exports;
+var packer = require('./packers');
+var parser = require('./parsers');
+Object.keys(parser).forEach(function(key) {
+ X509[key] = parser[key];
+});
+Object.keys(packer).forEach(function(key) {
+ X509[key] = packer[key];
+});
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..ed85199
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,34 @@
+{
+ "name": "@root/x509",
+ "version": "0.7.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@root/asn1": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@root/asn1/-/asn1-1.0.0.tgz",
+ "integrity": "sha512-0lfZNuOULKJDJmdIkP8V9RnbV3XaK6PAHD3swnFy4tZwtlMDzLKoM/dfNad7ut8Hu3r91wy9uK0WA/9zym5mig==",
+ "requires": {
+ "@root/encoding": "^1.0.1"
+ },
+ "dependencies": {
+ "@root/encoding": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz",
+ "integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ=="
+ }
+ }
+ },
+ "@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.4",
+ "resolved": "https://registry.npmjs.org/@root/pem/-/pem-1.0.4.tgz",
+ "integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA==",
+ "dev": true
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..4846515
--- /dev/null
+++ b/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "@root/x509",
+ "version": "0.7.0",
+ "description": "VanillaJS, Lightweight, Zero-Dependency, X509 schema 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/x509.js.git"
+ },
+ "keywords": [
+ "x509",
+ "PEM",
+ "ASN.1",
+ "PKIX",
+ "SPKI",
+ "SEC1",
+ "PKCS1",
+ "PKCS8",
+ "PKCS10",
+ "asn1"
+ ],
+ "author": "AJ ONeal (https://coolaj86.com/)",
+ "license": "MPL-2.0",
+ "devDependencies": {
+ "@root/pem": "^1.0.3"
+ },
+ "dependencies": {
+ "@root/asn1": "^1.0.0",
+ "@root/encoding": "^1.0.1"
+ }
+}
diff --git a/packers.js b/packers.js
new file mode 100644
index 0000000..592ff22
--- /dev/null
+++ b/packers.js
@@ -0,0 +1,207 @@
+'use strict';
+
+var X509 = module.exports;
+
+// 1.2.840.10045.3.1.7
+// prime256v1 (ANSI X9.62 named elliptic curve)
+var OBJ_ID_EC = '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();
+
+var Enc = require('@root/encoding');
+var ASN1 = require('@root/asn1/packer');
+var Asn1 = ASN1.Any;
+var UInt = ASN1.UInt;
+var BitStr = ASN1.BitStr;
+
+X509.packPkcs1 = function(jwk) {
+ var n = UInt(Enc.base64ToHex(jwk.n));
+ var e = UInt(Enc.base64ToHex(jwk.e));
+
+ if (!jwk.d) {
+ return Enc.hexToBuf(Asn1('30', n, e));
+ }
+
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ UInt('00'),
+ n,
+ e,
+ UInt(Enc.base64ToHex(jwk.d)),
+ UInt(Enc.base64ToHex(jwk.p)),
+ UInt(Enc.base64ToHex(jwk.q)),
+ UInt(Enc.base64ToHex(jwk.dp)),
+ UInt(Enc.base64ToHex(jwk.dq)),
+ UInt(Enc.base64ToHex(jwk.qi))
+ )
+ );
+};
+
+X509.packSec1 = function(jwk) {
+ var d = Enc.base64ToHex(jwk.d);
+ var x = Enc.base64ToHex(jwk.x);
+ var y = Enc.base64ToHex(jwk.y);
+ var objId = 'P-256' === jwk.crv ? OBJ_ID_EC : OBJ_ID_EC_384;
+
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ UInt('01'),
+ Asn1('04', d),
+ Asn1('A0', objId),
+ Asn1('A1', BitStr('04' + x + y))
+ )
+ );
+};
+/**
+ * take a private jwk and creates a der from it
+ * @param {*} jwk
+ */
+X509.packPkcs8 = function(jwk) {
+ if (/RSA/.test(jwk.kty)) {
+ return X509.packPkcs8Rsa(jwk);
+ }
+
+ return X509.packPkcs8Ec(jwk);
+};
+X509.packPkcs8Ec = function(jwk) {
+ var d = Enc.base64ToHex(jwk.d);
+ var x = Enc.base64ToHex(jwk.x);
+ var y = Enc.base64ToHex(jwk.y);
+ var objId = 'P-256' === jwk.crv ? OBJ_ID_EC : OBJ_ID_EC_384;
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ UInt('00'),
+ Asn1('30', OBJ_ID_EC_PUB, objId),
+ Asn1(
+ '04',
+ Asn1(
+ '30',
+ UInt('01'),
+ Asn1('04', d),
+ Asn1('A1', BitStr('04' + x + y))
+ )
+ )
+ )
+ );
+};
+
+X509.packPkcs8Rsa = function(jwk) {
+ if (!jwk.d) {
+ // Public RSA
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ Asn1('30', Asn1('06', '2a864886f70d010101'), Asn1('05')),
+ BitStr(
+ Asn1(
+ '30',
+ UInt(Enc.base64ToHex(jwk.n)),
+ UInt(Enc.base64ToHex(jwk.e))
+ )
+ )
+ )
+ );
+ }
+
+ // Private RSA
+ return Enc.hexToBuf(
+ Asn1(
+ '30',
+ UInt('00'),
+ Asn1('30', Asn1('06', '2a864886f70d010101'), Asn1('05')),
+ Asn1(
+ '04',
+ Asn1(
+ '30',
+ UInt('00'),
+ UInt(Enc.base64ToHex(jwk.n)),
+ UInt(Enc.base64ToHex(jwk.e)),
+ UInt(Enc.base64ToHex(jwk.d)),
+ UInt(Enc.base64ToHex(jwk.p)),
+ UInt(Enc.base64ToHex(jwk.q)),
+ UInt(Enc.base64ToHex(jwk.dp)),
+ UInt(Enc.base64ToHex(jwk.dq)),
+ UInt(Enc.base64ToHex(jwk.qi))
+ )
+ )
+ )
+ );
+};
+X509.packSpkiEc = function(jwk) {
+ var x = Enc.base64ToHex(jwk.x);
+ var y = Enc.base64ToHex(jwk.y);
+ var objId = 'P-256' === jwk.crv ? OBJ_ID_EC : OBJ_ID_EC_384;
+ return Enc.hexToBuf(
+ Asn1('30', Asn1('30', OBJ_ID_EC_PUB, objId), BitStr('04' + x + y))
+ );
+};
+X509.packSpki = function(jwk) {
+ if (/RSA/i.test(jwk.kty)) {
+ return X509.packPkcs8Rsa(jwk);
+ }
+ return X509.packSpkiEc(jwk);
+};
+X509.packPkix = X509.packSpki;
+
+X509.packCsrRsaPublicKey = function(jwk) {
+ // Sequence the key
+ var n = UInt(Enc.base64ToHex(jwk.n));
+ var e = UInt(Enc.base64ToHex(jwk.e));
+ var asn1pub = Asn1('30', n, e);
+
+ // Add the CSR pub key header
+ return Asn1(
+ '30',
+ Asn1('30', Asn1('06', '2a864886f70d010101'), Asn1('05')),
+ BitStr(asn1pub)
+ );
+};
+
+X509.packCsrEcPublicKey = function(jwk) {
+ var ecOid = X509._oids[jwk.crv];
+ if (!ecOid) {
+ throw new Error(
+ "Unsupported namedCurve '" +
+ jwk.crv +
+ "'. Supported types are " +
+ Object.keys(X509._oids)
+ );
+ }
+ var cmp = '04'; // 04 == x+y, 02 == x-only
+ var hxy = '';
+ // Placeholder. I'm not even sure if compression should be supported.
+ if (!jwk.y) {
+ cmp = '02';
+ }
+ hxy += Enc.base64ToHex(jwk.x);
+ if (jwk.y) {
+ hxy += Enc.base64ToHex(jwk.y);
+ }
+
+ // 1.2.840.10045.2.1 ecPublicKey
+ return Asn1(
+ '30',
+ Asn1('30', Asn1('06', '2a8648ce3d0201'), Asn1('06', ecOid)),
+ BitStr(cmp + hxy)
+ );
+};
+
+X509._oids = {
+ // 1.2.840.10045.3.1.7 prime256v1
+ // (ANSI X9.62 named elliptic curve) (06 08 - 2A 86 48 CE 3D 03 01 07)
+ 'P-256': '2a8648ce3d030107',
+ // 1.3.132.0.34 P-384 (06 05 - 2B 81 04 00 22)
+ // (SEC 2 recommended EC domain secp256r1)
+ 'P-384': '2b81040022'
+ // requires more logic and isn't a recommended standard
+ // 1.3.132.0.35 P-521 (06 05 - 2B 81 04 00 23)
+ // (SEC 2 alternate P-521)
+ //, 'P-521': '2B 81 04 00 23'
+};
diff --git a/parsers.js b/parsers.js
new file mode 100644
index 0000000..92837b7
--- /dev/null
+++ b/parsers.js
@@ -0,0 +1,221 @@
+'use strict';
+
+var X509 = module.exports;
+var Enc = require('@root/encoding');
+var ASN1 = require('@root/asn1/parser');
+
+// 1.2.840.10045.3.1.7
+// prime256v1 (ANSI X9.62 named elliptic curve)
+var OBJ_ID_EC = '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();
+
+X509.parsePkcs1 = function parseRsaPkcs1(asn1, jwk) {
+ if (!jwk) {
+ jwk = {};
+ }
+
+ // might be a buffer
+ if (!Array.isArray(asn1)) {
+ asn1 = ASN1.parse({ der: asn1, verbose: true, json: false });
+ }
+
+ if (
+ !asn1.children.every(function(el) {
+ return 0x02 === el.type;
+ })
+ ) {
+ throw new Error(
+ 'not an RSA PKCS#1 public or private key (not all ints)'
+ );
+ }
+
+ if (2 === asn1.children.length) {
+ jwk.n = Enc.bufToUrlBase64(asn1.children[0].value);
+ jwk.e = Enc.bufToUrlBase64(asn1.children[1].value);
+ jwk.kty = 'RSA';
+ } else if (asn1.children.length >= 9) {
+ // the standard allows for "otherPrimeInfos", hence at least 9
+
+ jwk.n = Enc.bufToUrlBase64(asn1.children[1].value);
+ jwk.e = Enc.bufToUrlBase64(asn1.children[2].value);
+ jwk.d = Enc.bufToUrlBase64(asn1.children[3].value);
+ jwk.p = Enc.bufToUrlBase64(asn1.children[4].value);
+ jwk.q = Enc.bufToUrlBase64(asn1.children[5].value);
+ jwk.dp = Enc.bufToUrlBase64(asn1.children[6].value);
+ jwk.dq = Enc.bufToUrlBase64(asn1.children[7].value);
+ jwk.qi = Enc.bufToUrlBase64(asn1.children[8].value);
+ jwk.kty = 'RSA';
+ } else {
+ throw new Error(
+ 'not an RSA PKCS#1 public or private key (wrong number of ints)'
+ );
+ }
+
+ return jwk;
+};
+
+X509.parseSec1 = function parseEcOnlyPrivkey(u8, jwk) {
+ var index = 7;
+ var len = 32;
+ var olen = OBJ_ID_EC.length / 2;
+
+ if ('P-384' === jwk.crv) {
+ olen = OBJ_ID_EC_384.length / 2;
+ index = 8;
+ len = 48;
+ }
+ if (len !== u8[index - 1]) {
+ throw new Error('Unexpected bitlength ' + len);
+ }
+
+ // private part is d
+ var d = u8.slice(index, index + len);
+ // compression bit index
+ var ci = index + len + 2 + olen + 2 + 3;
+ var c = u8[ci];
+ var x, y;
+
+ if (0x04 === c) {
+ y = u8.slice(ci + 1 + len, ci + 1 + len + len);
+ } else if (0x02 !== c) {
+ throw new Error('not a supported EC private key');
+ }
+ x = u8.slice(ci + 1, ci + 1 + len);
+
+ return {
+ kty: jwk.kty || 'EC',
+ crv: jwk.crv || 'P-256',
+ d: Enc.bufToUrlBase64(d),
+ //, dh: Enc.bufToHex(d)
+ x: Enc.bufToUrlBase64(x),
+ //, xh: Enc.bufToHex(x)
+ y: Enc.bufToUrlBase64(y)
+ //, yh: Enc.bufToHex(y)
+ };
+};
+
+X509.parsePkcs8 = function(u8, jwk) {
+ try {
+ return X509.parseRsaPkcs8(u8, jwk);
+ } catch (e) {
+ return X509.parseEcPkcs8(u8, jwk);
+ }
+};
+
+X509.parseEcPkcs8 = function parseEcPkcs8(u8, jwk) {
+ var index = 24 + OBJ_ID_EC.length / 2;
+ var len = 32;
+ if ('P-384' === jwk.crv) {
+ index = 24 + OBJ_ID_EC_384.length / 2 + 2;
+ len = 48;
+ }
+
+ if (0x04 !== u8[index]) {
+ throw new Error('privkey not found');
+ }
+ var d = u8.slice(index + 2, index + 2 + len);
+ var ci = index + 2 + len + 5;
+ var xi = ci + 1;
+ var x = u8.slice(xi, xi + len);
+ var yi = xi + len;
+ var y;
+ if (0x04 === u8[ci]) {
+ y = u8.slice(yi, yi + len);
+ } else if (0x02 !== u8[ci]) {
+ throw new Error('invalid compression bit (expected 0x04 or 0x02)');
+ }
+
+ return {
+ kty: jwk.kty || 'EC',
+ crv: jwk.crv || 'P-256',
+ d: Enc.bufToUrlBase64(d),
+ //, dh: Enc.bufToHex(d)
+ x: Enc.bufToUrlBase64(x),
+ //, xh: Enc.bufToHex(x)
+ y: Enc.bufToUrlBase64(y)
+ //, yh: Enc.bufToHex(y)
+ };
+};
+
+X509.parseRsaPkcs8 = function parseRsaPkcs8(asn1, jwk) {
+ if (!jwk) {
+ jwk = {};
+ }
+
+ // might be a buffer
+ if (!Array.isArray(asn1)) {
+ asn1 = ASN1.parse({ der: asn1, verbose: true, json: false });
+ }
+ if (
+ 2 === asn1.children.length &&
+ 0x03 === asn1.children[1].type // && 2 === asn1.children[1].children.length
+ ) {
+ asn1 = asn1.children[1].children[0];
+ jwk.n = Enc.bufToUrlBase64(asn1.children[0].value);
+ jwk.e = Enc.bufToUrlBase64(asn1.children[1].value);
+ jwk.kty = 'RSA';
+ } else if (
+ 3 === asn1.children.length &&
+ 0x04 === asn1.children[2].type &&
+ 0x30 === asn1.children[2].children[0].type &&
+ 0x02 === asn1.children[2].children[0].children[0].type
+ ) {
+ asn1 = asn1.children[2].children[0];
+ jwk.n = Enc.bufToUrlBase64(asn1.children[1].value);
+ jwk.e = Enc.bufToUrlBase64(asn1.children[2].value);
+ jwk.d = Enc.bufToUrlBase64(asn1.children[3].value);
+ jwk.p = Enc.bufToUrlBase64(asn1.children[4].value);
+ jwk.q = Enc.bufToUrlBase64(asn1.children[5].value);
+ jwk.dp = Enc.bufToUrlBase64(asn1.children[6].value);
+ jwk.dq = Enc.bufToUrlBase64(asn1.children[7].value);
+ jwk.qi = Enc.bufToUrlBase64(asn1.children[8].value);
+ jwk.kty = 'RSA';
+ } else {
+ throw new Error(
+ 'not an RSA PKCS#8 public or private key (wrong format)'
+ );
+ }
+ return jwk;
+};
+
+X509.parseSpki = function(buf, jwk) {
+ try {
+ return X509.parseRsaPkcs8(buf, jwk);
+ } catch (e) {
+ return X509.parseEcSpki(buf, jwk);
+ }
+};
+
+X509.parseEcSpki = function(u8, jwk) {
+ var ci = 16 + OBJ_ID_EC.length / 2;
+ var len = 32;
+
+ if ('P-384' === jwk.crv) {
+ ci = 16 + OBJ_ID_EC_384.length / 2;
+ len = 48;
+ }
+
+ var c = u8[ci];
+ var xi = ci + 1;
+ var x = u8.slice(xi, xi + len);
+ var yi = xi + len;
+ var y;
+ if (0x04 === c) {
+ y = u8.slice(yi, yi + len);
+ } else if (0x02 !== c) {
+ throw new Error('not a supported EC private key');
+ }
+
+ return {
+ kty: jwk.kty || 'EC',
+ crv: jwk.crv || 'P-256',
+ x: Enc.bufToUrlBase64(x),
+ //, xh: Enc.bufToHex(x)
+ y: Enc.bufToUrlBase64(y)
+ //, yh: Enc.bufToHex(y)
+ };
+};
+
+X509.parsePkix = X509.parseSpki;
diff --git a/tests/index.js b/tests/index.js
new file mode 100644
index 0000000..fcfefb5
--- /dev/null
+++ b/tests/index.js
@@ -0,0 +1,201 @@
+'use strict';
+
+var X509 = require('../');
+var PEM = require('@root/pem');
+
+var ec = {
+ crv: 'P-256',
+ d: 'LImWxqqTHbP3LHQfqscDSUzf_uNePGqf9U6ETEcO5Ho',
+ kty: 'EC',
+ x: 'vdjQ3T6VBX82LIKDzepYgRsz3HgRwp83yPuonu6vqos',
+ y: 'IUkEXtAMnppnV1A19sE2bJhUo4WPbq6EYgWxma4oGyg',
+ kid: 'MnfJYyS9W5gUjrJLdn8ePMzik8ZJz2qc-VZmKOs_oCw'
+};
+var ecPub = {
+ crv: 'P-256',
+ kty: 'EC',
+ x: 'vdjQ3T6VBX82LIKDzepYgRsz3HgRwp83yPuonu6vqos',
+ y: 'IUkEXtAMnppnV1A19sE2bJhUo4WPbq6EYgWxma4oGyg',
+ kid: 'MnfJYyS9W5gUjrJLdn8ePMzik8ZJz2qc-VZmKOs_oCw'
+};
+
+var rsa = {
+ alg: 'RS256',
+ d:
+ 'ETFW_Kx2yUf9DHZaYkYinydcgURY1DA1umkcAwcVVGA7PQtEFXp4Z7EhhA9KZb7CxX0SFALh5PLZLesmkm3oBVonlGeS2kArNHEIAzzGQQzalTjPwfYtIxKsLDUxad05XL0MmGPKAKK8kgLq75EWzvRqkSd2tpfPRzWhu4tOEaaN3zrCklUIG6wueDgxZ0lq3zPzbabKUPzHtKrD_K2CmAs1e0Uh31IwUdAVFeV0cznr6_g2mK-bplSa8pX0nfWKA9J33U43y_XqWINslofBNKqI4XSdTizydGG5K8OTr15LZ8dZzkyE24SwzL5TdwXU084hdxGy2Q2k3Hpe3FatVQ',
+ dp:
+ 'pJ6Vhwkwgb5otzqlM0nPBwuSconUxAScT7xFpcPVR1PnV0f8tFVOYIgdBRbMwsP2bsSkt0bzpHPwMlPgbqDSDPGnMOV0ORyp8rDS3FdjusLSh74448dWRGf-jV9SiKQQV26-wosXASEHVE-DvkXL97oZ3s4hKDlnZBcTtFl50Ik',
+ dq:
+ 'evjNO5Tt1RYad3BoYvQyOsIUpXemEO4ehLwya5OR5a5Bn65ebQOqlCa946AAoifIzHJxIFYohfIpptrzdCSAlJA2Hi_ZxRcMzsi_k9mLLKQA7xZSblwuDzk5G7OrwdqC05YUYH-LmH7QklgLsg7CP-BmSb2_hTKVCghImY6SPQk',
+ e: 'AQAB',
+ ext: true,
+ key_ops: ['sign'],
+ kty: 'RSA',
+ n:
+ 'uQ5a9uJ-UBCUWlQ67lo-o0e0F_WTsbbcETtE8trAuz-2jzRh9BqGfKoJqEP2quXGeOSfBEbB4d02nem3RBR-plC-fHrdluj31mK4GrCA5t9OJ2Q5K9xEH3vHkicFy0gbE0NmuSkFSLHmZ7lSD2kTOhAP3_aTjvaxMsv6ShA76XRuwhRwbUj931eOdsBzDBkmqeotu9eNQnUeq9qJsJDp8EgPaKUcKjz3yb8hCWo5LNIpuTLAV9nxNVRCo64WAotY6FSWO_Tb_pfy8YiN0nnFNg_0K2aU9BaRvb0EISA7KTq4IZzXPZGLwPopwmH8I4GB9IF3k0_ohjJBLiAaokkmUQ',
+ p:
+ '42hRRgBoWqXjY1wR5131O-vOIVPcoiX37ephSoyYOqwgkPTJcpG62p5kwwNCJ0QB9RLJS1aJRcpYVcEu4js1kiJy_jkIGN6t_LZquASThLP0cdbiqsEVCE-0e-zgN4zf-h_IqjkvXilWGJQGa5AIcpoZPKQVUPy00b9ao9SxLKU',
+ q:
+ '0FLZ2MKPc6G2bMhEa2QF3otpOgKJuYoTGnlo_WfqdksW5U4xEga_PbjxXWI-TAy5lFEjtmj1FP6v5Y-ZZeutGsCwfPnEIdVewoIjym7f1ztDL9a3s_hn8R6PrsUq-718kPP-7or3MvunH31VXWn5B4xQ57AfkjfkV-n-qYglBz0',
+ qi:
+ 'XFSbj_yuhDgDzgIi4uPbCIhIOj9H5gbYv7gDPWsNJEavcZ1gImOrIfFHunOekWuUHl6C_5ARNLf0cGUBJy9Mqj0QSr7L8R04YQUHg4Z9aJ-ISY6Yd_0BdVU1TwtXg0MiClrJQe7KFachIEa7XJO27D6xb0ubI6ZzB1V9VrmmmWY',
+ kid: 'MKyL9gVM1vW-dmDkvgyzZxeQr6OlEVj_uX6v0Owe0BQ'
+};
+var rsaPub = {
+ alg: 'RS256',
+ e: 'AQAB',
+ ext: true,
+ key_ops: ['sign'],
+ kty: 'RSA',
+ n:
+ 'uQ5a9uJ-UBCUWlQ67lo-o0e0F_WTsbbcETtE8trAuz-2jzRh9BqGfKoJqEP2quXGeOSfBEbB4d02nem3RBR-plC-fHrdluj31mK4GrCA5t9OJ2Q5K9xEH3vHkicFy0gbE0NmuSkFSLHmZ7lSD2kTOhAP3_aTjvaxMsv6ShA76XRuwhRwbUj931eOdsBzDBkmqeotu9eNQnUeq9qJsJDp8EgPaKUcKjz3yb8hCWo5LNIpuTLAV9nxNVRCo64WAotY6FSWO_Tb_pfy8YiN0nnFNg_0K2aU9BaRvb0EISA7KTq4IZzXPZGLwPopwmH8I4GB9IF3k0_ohjJBLiAaokkmUQ',
+ kid: 'MKyL9gVM1vW-dmDkvgyzZxeQr6OlEVj_uX6v0Owe0BQ'
+};
+
+function log(pem) {
+ //console.log(pem);
+}
+
+var rsaPrivPkcs1 = PEM.packBlock({
+ type: 'RSA PRIVATE KEY',
+ bytes: X509.packPkcs1(rsa)
+});
+log(rsaPrivPkcs1);
+
+var rsaPubPkcs1 = PEM.packBlock({
+ type: 'RSA PUBLIC KEY',
+ bytes: X509.packPkcs1(rsaPub)
+});
+log(rsaPubPkcs1);
+
+var ecPrivSec1 = PEM.packBlock({
+ type: 'EC PRIVATE KEY',
+ bytes: X509.packSec1(ec)
+});
+log(ecPrivSec1);
+
+var rsaPrivPkcs8 = PEM.packBlock({
+ type: 'PRIVATE KEY (RSA)',
+ bytes: X509.packPkcs8(rsa)
+});
+log(rsaPrivPkcs8);
+
+var ecPrivPkcs8 = PEM.packBlock({
+ type: 'PRIVATE KEY (EC)',
+ bytes: X509.packPkcs8(ec)
+});
+log(ecPrivPkcs8);
+
+var rsaPubSpki = PEM.packBlock({
+ type: 'PUBLIC KEY (RSA)',
+ bytes: X509.packSpki(rsaPub)
+});
+log(rsaPubSpki);
+
+var ecPubSpki = PEM.packBlock({
+ type: 'PUBLIC KEY (EC)',
+ bytes: X509.packSpki(ecPub)
+});
+log(ecPubSpki);
+
+var rsaPubCsr = PEM.packBlock({
+ type: '(CSR-Embeddable) PUBLIC KEY (RSA)',
+ bytes: X509.packCsrRsaPublicKey(rsaPub)
+});
+log(rsaPubCsr);
+
+var ecPubCsr = ecPubCsr;
+log(
+ PEM.packBlock({
+ type: '(CSR-Embeddable) PUBLIC KEY (EC)',
+ bytes: X509.packCsrEcPublicKey(ecPub)
+ })
+);
+
+console.info('PASS: Packed all possible formats');
+
+if (
+ rsaPrivPkcs1 !==
+ PEM.packBlock({
+ type: 'RSA PRIVATE KEY',
+ bytes: X509.packPkcs1(
+ X509.parsePkcs1(PEM.parseBlock(rsaPrivPkcs1).bytes, {})
+ )
+ })
+) {
+ throw new Error('Fail packPrivPkcs1 Private');
+}
+
+if (
+ rsaPubPkcs1 !==
+ PEM.packBlock({
+ type: 'RSA PUBLIC KEY',
+ bytes: X509.packPkcs1(
+ X509.parsePkcs1(PEM.parseBlock(rsaPubPkcs1).bytes, {})
+ )
+ })
+) {
+ throw new Error('Fail packPubPkcs1 Private');
+}
+
+if (
+ ecPrivSec1 !==
+ PEM.packBlock({
+ type: 'EC PRIVATE KEY',
+ bytes: X509.packSec1(
+ X509.parseSec1(PEM.parseBlock(ecPrivSec1).bytes, {})
+ )
+ })
+) {
+ throw new Error('FAIL packSec1');
+}
+
+if (
+ rsaPrivPkcs8 !==
+ PEM.packBlock({
+ type: 'PRIVATE KEY (RSA)',
+ bytes: X509.packPkcs8(
+ X509.parsePkcs8(PEM.parseBlock(rsaPrivPkcs8).bytes, {})
+ )
+ })
+) {
+ throw new Error('FAIL packRsaPrivPkcs8');
+}
+
+if (
+ ecPrivPkcs8 !==
+ PEM.packBlock({
+ type: 'PRIVATE KEY (EC)',
+ bytes: X509.packPkcs8(
+ X509.parsePkcs8(PEM.parseBlock(ecPrivPkcs8).bytes, {})
+ )
+ })
+) {
+ throw new Error('FAIL packEcPrivPkcs8');
+}
+
+if (
+ rsaPubSpki !==
+ PEM.packBlock({
+ type: 'PUBLIC KEY (RSA)',
+ bytes: X509.packSpki(
+ X509.parseSpki(PEM.parseBlock(rsaPubSpki).bytes, {})
+ )
+ })
+) {
+ throw new Error('FAIL packRsaPubSpki');
+}
+
+if (
+ ecPubSpki !==
+ PEM.packBlock({
+ type: 'PUBLIC KEY (EC)',
+ bytes: X509.packSpki(
+ X509.parseSpki(PEM.parseBlock(ecPubSpki).bytes, {})
+ )
+ })
+) {
+ throw new Error('FAIL packEcPubSpki');
+}
+
+console.info('PASS: Parsed all possible formats');