Browse Source

wip

tags/v1.0.0
AJ ONeal 11 months ago
parent
commit
b9b403ef4f
6 changed files with 434 additions and 132 deletions
  1. 134
    0
      browser.js
  2. 1
    132
      index.js
  3. 62
    0
      lib/ec.js
  4. 104
    0
      lib/keypairs.js
  5. 14
    0
      lib/x509-packer.js
  6. 119
    0
      lib/x509-parser.js

+ 134
- 0
browser.js View File

@@ -0,0 +1,134 @@
1
+;(function (exports) {
2
+'use strict';
3
+
4
+var PromiseA;
5
+try {
6
+  /*global Promise*/
7
+  PromiseA = Promise;
8
+} catch(e) {
9
+  PromiseA = require('bluebird');
10
+}
11
+
12
+
13
+// https://stackoverflow.com/questions/40314257/export-webcrypto-key-to-pem-format
14
+function derToPem(keydata, pemName, privacy){
15
+  var keydataS = arrayBufferToString(keydata);
16
+  var keydataB64 = window.btoa(keydataS);
17
+  var keydataB64Pem = formatAsPem(keydataB64, pemName, privacy);
18
+  return keydataB64Pem;
19
+}
20
+
21
+function arrayBufferToString( buffer ) {
22
+  var binary = [];
23
+  var bytes = new Uint8Array( buffer );
24
+  var len = bytes.byteLength;
25
+  for (var i = 0; i < len; i++) {
26
+      binary.push(String.fromCharCode( bytes[ i ] ));
27
+  }
28
+  return binary.join('');
29
+}
30
+
31
+
32
+function formatAsPem(str, pemName, privacy) {
33
+  var privstr = (privacy ? privacy + ' ' : '');
34
+  var finalString = '-----BEGIN ' + pemName + ' ' + privstr + 'KEY-----\n';
35
+
36
+  while (str.length > 0) {
37
+      finalString += str.substring(0, 64) + '\n';
38
+      str = str.substring(64);
39
+  }
40
+
41
+  finalString = finalString + '-----END ' + pemName + ' ' + privstr + 'KEY-----';
42
+
43
+  return finalString;
44
+}
45
+
46
+var Keypairs = exports.Keypairs = {
47
+  generate: function(opts) {
48
+    if (!opts) { opts = {}; }
49
+    if (!opts.type) { opts.type = 'EC'; }
50
+
51
+    var supported = [ 'EC', 'RSA' ];
52
+    if (-1 === supported.indexOf(opts.type)) {
53
+      return PromiseA.reject(new Error("'" + opts.type + "' not implemented. Try one of " + supported.join(', ')));
54
+    }
55
+
56
+    if ('EC' === opts.type) {
57
+      return Keypairs._generateEc(opts);
58
+    }
59
+    if ('RSA' === opts.type) {
60
+      return Keypairs._generateRsa(opts);
61
+    }
62
+  }
63
+, _generateEc: function (opts) {
64
+    if (!opts.namedCurve) { opts.namedCurve = 'P-256'; }
65
+    if ('P-256' !== opts.namedCurve) {
66
+      console.warn("'" + opts.namedCurve + "' is not supported, but it _might_ happen to work anyway.");
67
+    }
68
+
69
+    // https://github.com/diafygi/webcrypto-examples#ecdsa---generatekey
70
+    var extractable = true;
71
+
72
+    return window.crypto.subtle.generateKey(
73
+      { name: "ECDSA", namedCurve: opts.namedCurve }
74
+    , extractable
75
+    , [ 'sign', 'verify' ]
76
+    ).then(function (result) {
77
+      return window.crypto.subtle.exportKey(
78
+        "jwk"
79
+      , result.privateKey
80
+      ).then(function (jwk) {
81
+        return window.crypto.subtle.exportKey(
82
+          "pkcs8"
83
+        , result.privateKey
84
+        ).then(function (keydata) {
85
+          return {
86
+            type: 'EC'
87
+          , privateJwk: jwk
88
+          , privatePem: derToPem(keydata, 'EC', 'PRIVATE')
89
+          };
90
+        });
91
+      });
92
+    });
93
+  }
94
+, _generateRsa: function (opts) {
95
+    if (!opts.bitlength) { opts.bitlength = 2048; }
96
+    if (-1 === [ 2048, 4096 ].indexOf(opts.bitlength)) {
97
+      return PromiseA.reject("opts.bitlength = (" + typeof opts.bitlength + ") " + opts.bitlength + ": Are you serious?");
98
+    }
99
+
100
+    // https://github.com/diafygi/webcrypto-examples#rsa---generatekey
101
+    var extractable = true;
102
+
103
+    return window.crypto.subtle.generateKey(
104
+      { name: "RSASSA-PKCS1-v1_5"
105
+      , modulusLength: opts.bitlength
106
+      , publicExponent: new Uint8Array([0x01, 0x00, 0x01])
107
+      , hash: { name: "SHA-256" }
108
+      }
109
+    , extractable
110
+    , [ 'sign', 'verify' ]
111
+    ).then(function (result) {
112
+      return window.crypto.subtle.exportKey(
113
+        "jwk"
114
+      , result.privateKey
115
+      ).then(function (jwk) {
116
+        return window.crypto.subtle.exportKey(
117
+          "pkcs8"
118
+        , result.privateKey
119
+        ).then(function (keydata) {
120
+          return {
121
+            type: 'RSA'
122
+          , privateJwk: jwk
123
+          , privatePem: derToPem(keydata, 'RSA', 'PRIVATE')
124
+          };
125
+        });
126
+      });
127
+    });
128
+  }
129
+};
130
+
131
+}('undefined' === typeof module ? window : module.exports));
132
+
133
+// How we might use this
134
+// var Keypairs = require('keypairs').Keypairs

+ 1
- 132
index.js View File

@@ -1,134 +1,3 @@
1
-;(function (exports) {
2 1
 'use strict';
3 2
 
4
-var PromiseA;
5
-try {
6
-  /*global Promise*/
7
-  PromiseA = Promise;
8
-} catch(e) {
9
-  PromiseA = require('bluebird');
10
-}
11
-
12
-
13
-// https://stackoverflow.com/questions/40314257/export-webcrypto-key-to-pem-format
14
-function derToPem(keydata, pemName, privacy){
15
-  var keydataS = arrayBufferToString(keydata);
16
-  var keydataB64 = window.btoa(keydataS);
17
-  var keydataB64Pem = formatAsPem(keydataB64, pemName, privacy);
18
-  return keydataB64Pem;
19
-}
20
-
21
-function arrayBufferToString( buffer ) {
22
-  var binary = [];
23
-  var bytes = new Uint8Array( buffer );
24
-  var len = bytes.byteLength;
25
-  for (var i = 0; i < len; i++) {
26
-      binary.push(String.fromCharCode( bytes[ i ] ));
27
-  }
28
-  return binary.join('');
29
-}
30
-
31
-
32
-function formatAsPem(str, pemName, privacy) {
33
-  var privstr = (privacy ? privacy + ' ' : '');
34
-  var finalString = '-----BEGIN ' + pemName + ' ' + privstr + 'KEY-----\n';
35
-
36
-  while (str.length > 0) {
37
-      finalString += str.substring(0, 64) + '\n';
38
-      str = str.substring(64);
39
-  }
40
-
41
-  finalString = finalString + '-----END ' + pemName + ' ' + privstr + 'KEY-----';
42
-
43
-  return finalString;
44
-}
45
-
46
-var Keypairs = exports.Keypairs = {
47
-  generate: function(opts) {
48
-    if (!opts) { opts = {}; }
49
-    if (!opts.type) { opts.type = 'EC'; }
50
-
51
-    var supported = [ 'EC', 'RSA' ];
52
-    if (-1 === supported.indexOf(opts.type)) {
53
-      return PromiseA.reject(new Error("'" + opts.type + "' not implemented. Try one of " + supported.join(', ')));
54
-    }
55
-
56
-    if ('EC' === opts.type) {
57
-      return Keypairs._generateEc(opts);
58
-    }
59
-    if ('RSA' === opts.type) {
60
-      return Keypairs._generateRsa(opts);
61
-    }
62
-  }
63
-, _generateEc: function (opts) {
64
-    if (!opts.namedCurve) { opts.namedCurve = 'P-256'; }
65
-    if ('P-256' !== opts.namedCurve) {
66
-      console.warn("'" + opts.namedCurve + "' is not supported, but it _might_ happen to work anyway.");
67
-    }
68
-
69
-    // https://github.com/diafygi/webcrypto-examples#ecdsa---generatekey
70
-    var extractable = true;
71
-
72
-    return window.crypto.subtle.generateKey(
73
-      { name: "ECDSA", namedCurve: opts.namedCurve }
74
-    , extractable
75
-    , [ 'sign', 'verify' ]
76
-    ).then(function (result) {
77
-      return window.crypto.subtle.exportKey(
78
-        "jwk"
79
-      , result.privateKey
80
-      ).then(function (jwk) {
81
-        return window.crypto.subtle.exportKey(
82
-          "pkcs8"
83
-        , result.privateKey
84
-        ).then(function (keydata) {
85
-          return {
86
-            type: 'EC'
87
-          , privateJwk: jwk
88
-          , privatePem: derToPem(keydata, 'EC', 'PRIVATE')
89
-          };
90
-        });
91
-      });
92
-    });
93
-  }
94
-, _generateRsa: function (opts) {
95
-    if (!opts.bitlength) { opts.bitlength = 2048; }
96
-    if (-1 === [ 2048, 4096 ].indexOf(opts.bitlength)) {
97
-      return PromiseA.reject("opts.bitlength = (" + typeof opts.bitlength + ") " + opts.bitlength + ": Are you serious?");
98
-    }
99
-
100
-    // https://github.com/diafygi/webcrypto-examples#rsa---generatekey
101
-    var extractable = true;
102
-
103
-    return window.crypto.subtle.generateKey(
104
-      { name: "RSASSA-PKCS1-v1_5"
105
-      , modulusLength: opts.bitlength
106
-      , publicExponent: new Uint8Array([0x01, 0x00, 0x01])
107
-      , hash: { name: "SHA-256" }
108
-      }
109
-    , extractable
110
-    , [ 'sign', 'verify' ]
111
-    ).then(function (result) {
112
-      return window.crypto.subtle.exportKey(
113
-        "jwk"
114
-      , result.privateKey
115
-      ).then(function (jwk) {
116
-        return window.crypto.subtle.exportKey(
117
-          "pkcs8"
118
-        , result.privateKey
119
-        ).then(function (keydata) {
120
-          return {
121
-            type: 'RSA'
122
-          , privateJwk: jwk
123
-          , privatePem: derToPem(keydata, 'RSA', 'PRIVATE')
124
-          };
125
-        });
126
-      });
127
-    });
128
-  }
129
-};
130
-
131
-}('undefined' === typeof module ? window : module.exports));
132
-
133
-// How we might use this
134
-// var Keypairs = require('keypairs').Keypairs
3
+module.exports = require('./lib/keypairs.js');

+ 62
- 0
lib/ec.js View File

@@ -0,0 +1,62 @@
1
+'use strict';
2
+
3
+var ASN1 = require('./asn1-packer.js');
4
+var Enc = require('./encoding.js');
5
+var x509 = module.exports;
6
+
7
+// 1.2.840.10045.3.1.7
8
+// prime256v1 (ANSI X9.62 named elliptic curve)
9
+var OBJ_ID_EC  = '06 08 2A8648CE3D030107'.replace(/\s+/g, '').toLowerCase();
10
+// 1.3.132.0.34
11
+// secp384r1 (SECG (Certicom) named elliptic curve)
12
+var OBJ_ID_EC_384 = '06 05 2B81040022'.replace(/\s+/g, '').toLowerCase();
13
+// 1.2.840.10045.2.1
14
+// ecPublicKey (ANSI X9.62 public key type)
15
+var OBJ_ID_EC_PUB = '06 07 2A8648CE3D0201'.replace(/\s+/g, '').toLowerCase();
16
+
17
+x509.packSec1 = function (jwk) {
18
+  var d = Enc.base64ToHex(jwk.d);
19
+  var x = Enc.base64ToHex(jwk.x);
20
+  var y = Enc.base64ToHex(jwk.y);
21
+  var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384;
22
+  return Enc.hexToUint8(
23
+    ASN1('30'
24
+    , ASN1.UInt('01')
25
+    , ASN1('04', d)
26
+    , ASN1('A0', objId)
27
+    , ASN1('A1', ASN1.BitStr('04' + x + y)))
28
+  );
29
+};
30
+x509.packPkcs8 = function (jwk) {
31
+  var d = Enc.base64ToHex(jwk.d);
32
+  var x = Enc.base64ToHex(jwk.x);
33
+  var y = Enc.base64ToHex(jwk.y);
34
+  var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384;
35
+  return Enc.hexToUint8(
36
+    ASN1('30'
37
+    , ASN1.UInt('00')
38
+    , ASN1('30'
39
+      , OBJ_ID_EC_PUB
40
+      , objId
41
+      )
42
+    , ASN1('04'
43
+      , ASN1('30'
44
+        , ASN1.UInt('01')
45
+        , ASN1('04', d)
46
+        , ASN1('A1', ASN1.BitStr('04' + x + y)))))
47
+  );
48
+};
49
+x509.packSpki = function (jwk) {
50
+  var x = Enc.base64ToHex(jwk.x);
51
+  var y = Enc.base64ToHex(jwk.y);
52
+  var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC : OBJ_ID_EC_384;
53
+  return Enc.hexToUint8(
54
+    ASN1('30'
55
+    , ASN1('30'
56
+      , OBJ_ID_EC_PUB
57
+      , objId
58
+      )
59
+    , ASN1.BitStr('04' + x + y))
60
+  );
61
+};
62
+x509.packPkix = x509.packSpki;

+ 104
- 0
lib/keypairs.js View File

@@ -0,0 +1,104 @@
1
+'use strict';
2
+
3
+/*global Promise*/
4
+var keypairs = module.exports;
5
+
6
+var PEM = require('./pem-parser.js');
7
+PEM.packBlock = require('./pem-packer.js').packBlock;
8
+
9
+var ASN1 = require('./asn1-parser.js');
10
+ASN1.pack = require('./asn1-packer.js').pack;
11
+
12
+var x509 = require('./x509-parser.js');
13
+
14
+var SSH = require('./ssh-parser.js');
15
+SSH.pack = require('./ssh-packer.js').pack;
16
+
17
+// sign, signJws, signJwt
18
+var JWS = require('./jws.js');
19
+var JWT = require('./jwt.js');
20
+
21
+var RSA = require('./rsa.js');
22
+var EC = require('./ec.js');
23
+
24
+keypairs.import = function (opts) {
25
+  return Promise.resolve().then(function () {
26
+    var jwk = opts.jwk;
27
+    var pem;
28
+    var der;
29
+    var typ;
30
+
31
+    if (opts.pem) {
32
+      pem = PEM.parseBlock(opts.pem);
33
+      if (/OPENSSH/.test(pem.type)) {
34
+        jwk = SSH.parse(pem);
35
+      } else {
36
+        der = pem.bytes;
37
+        jwk = x509.parse(der);
38
+      }
39
+    }
40
+    if (opts.ssh) {
41
+      jwk = SSH.parse(opts.ssh);
42
+    }
43
+    if (jwk) {
44
+      // Both RSA and EC use 'd' as part of the private key
45
+      if (jwk.d) {
46
+        typ = 'PRIVATE KEY';
47
+        der = x509.pack({ jwk: jwk, format: 'pkcs8', encoding: 'pem' });
48
+      } else {
49
+        typ = 'PUBLIC KEY';
50
+        der = x509.pack({ jwk: jwk, format: 'spki', encoding: 'pem' });
51
+      }
52
+      pem = PEM.packBlock({ type: typ, bytes: der });
53
+    }
54
+
55
+    return { pem: pem, jwk: jwk };
56
+  });
57
+};
58
+
59
+keypairs.export = function (opts) {
60
+  // { pem, jwk, format, encoding }
61
+  var format = opts.format;
62
+  var encoding = opts.encoding;
63
+  var jwk = opts.jwk;
64
+  var pem = opts.pem;
65
+  var der = opts.der;
66
+  var pub = opts.public;
67
+
68
+  if (opts.key) {
69
+    if ('string' === typeof opts.key) {
70
+      pem = opts.key;
71
+    } else if (opts.key.d) {
72
+      jwk = opts.key;
73
+    } else if (opts.key.length) {
74
+      der = opts.der;
75
+    } else {
76
+      throw new Error("'key' must be of type 'string' (PEM), 'object' (JWK), Buffer, or Array (DER)");
77
+    }
78
+  }
79
+  if (!format) { format = 'jwk'; }
80
+
81
+  if (!jwk) {
82
+    jwk = keypairs.import({ pem: pem }).jwk;
83
+  }
84
+  if (pub) {
85
+    if ('RSA' === jwk.kty) {
86
+      jwk = { kty: jwk.kty, n: jwk.n, e: jwk.e };
87
+    } else {
88
+      jwk = { kty: jwk.kty, x: jwk.x, y: jwk.y };
89
+    }
90
+  }
91
+  if ('jwk' === format) {
92
+    if (encoding && 'json' !== encoding) {
93
+      throw new Error("'encoding' must be 'json' for 'jwk'");
94
+    }
95
+    return jwk;
96
+  }
97
+
98
+  if ('openssh' === format || 'ssh' === format) {
99
+    // TODO if ('ssh' === format) { format = 'pkcs8'; }
100
+    // TODO 'ssh2' public key is a special variant of pkcs8
101
+    return SSH.pack({ jwk: jwk, public: opts.public });
102
+  }
103
+  return x509.pack({ jwk: jwk, format: opts.format, encoding: opts.encoding, public: opts.public });
104
+};

+ 14
- 0
lib/x509-packer.js View File

@@ -0,0 +1,14 @@
1
+'use strict';
2
+
3
+var x509 = module.exports;
4
+
5
+var RSA = require('./rsa.js');
6
+var EC = require('./ec.js');
7
+
8
+x509.pack = function (opts) {
9
+  if ('RSA' === opts.jwk.kty) {
10
+    return RSA.pack(opts);
11
+  } else {
12
+    return EC.pack(opts);
13
+  }
14
+};

+ 119
- 0
lib/x509-parser.js View File

@@ -0,0 +1,119 @@
1
+'use strict';
2
+
3
+var Enc = require('./encoding.js');
4
+var x509 = module.exports;
5
+
6
+// 1.2.840.10045.3.1.7
7
+// prime256v1 (ANSI X9.62 named elliptic curve)
8
+var OBJ_ID_EC  = '06 08 2A8648CE3D030107'.replace(/\s+/g, '').toLowerCase();
9
+// 1.3.132.0.34
10
+// secp384r1 (SECG (Certicom) named elliptic curve)
11
+var OBJ_ID_EC_384 = '06 05 2B81040022'.replace(/\s+/g, '').toLowerCase();
12
+
13
+x509.parseSec1 = function parseEcOnlyPrivkey(u8, jwk) {
14
+  var index = 7;
15
+  var len = 32;
16
+  var olen = OBJ_ID_EC.length/2;
17
+
18
+  if ("P-384" === jwk.crv) {
19
+    olen = OBJ_ID_EC_384.length/2;
20
+    index = 8;
21
+    len = 48;
22
+  }
23
+  if (len !== u8[index - 1]) {
24
+    throw new Error("Unexpected bitlength " + len);
25
+  }
26
+
27
+  // private part is d
28
+  var d = u8.slice(index, index + len);
29
+  // compression bit index
30
+  var ci = index + len + 2 + olen + 2 + 3;
31
+  var c = u8[ci];
32
+  var x, y;
33
+
34
+  if (0x04 === c) {
35
+    y = u8.slice(ci + 1 + len, ci + 1 + len + len);
36
+  } else if (0x02 !== c) {
37
+    throw new Error("not a supported EC private key");
38
+  }
39
+  x = u8.slice(ci + 1, ci + 1 + len);
40
+
41
+  return {
42
+    kty: jwk.kty
43
+  , crv: jwk.crv
44
+  , d: Enc.bufToUrlBase64(d)
45
+  //, dh: Enc.bufToHex(d)
46
+  , x: Enc.bufToUrlBase64(x)
47
+  //, xh: Enc.bufToHex(x)
48
+  , y: Enc.bufToUrlBase64(y)
49
+  //, yh: Enc.bufToHex(y)
50
+  };
51
+};
52
+
53
+x509.parsePkcs8 = function parseEcPkcs8(u8, jwk) {
54
+  var index = 24 + (OBJ_ID_EC.length/2);
55
+  var len = 32;
56
+  if ("P-384" === jwk.crv) {
57
+    index = 24 + (OBJ_ID_EC_384.length/2) + 2;
58
+    len = 48;
59
+  }
60
+
61
+  //console.log(index, u8.slice(index));
62
+  if (0x04 !== u8[index]) {
63
+    //console.log(jwk);
64
+    throw new Error("privkey not found");
65
+  }
66
+  var d = u8.slice(index+2, index+2+len);
67
+  var ci = index+2+len+5;
68
+  var xi = ci+1;
69
+  var x = u8.slice(xi, xi + len);
70
+  var yi = xi+len;
71
+  var y;
72
+  if (0x04 === u8[ci]) {
73
+    y = u8.slice(yi, yi + len);
74
+  } else if (0x02 !== u8[ci]) {
75
+    throw new Error("invalid compression bit (expected 0x04 or 0x02)");
76
+  }
77
+
78
+  return {
79
+    kty: jwk.kty
80
+  , crv: jwk.crv
81
+  , d: Enc.bufToUrlBase64(d)
82
+  //, dh: Enc.bufToHex(d)
83
+  , x: Enc.bufToUrlBase64(x)
84
+  //, xh: Enc.bufToHex(x)
85
+  , y: Enc.bufToUrlBase64(y)
86
+  //, yh: Enc.bufToHex(y)
87
+  };
88
+};
89
+
90
+x509.parseSpki = function parsePem(u8, jwk) {
91
+  var ci = 16 + OBJ_ID_EC.length/2;
92
+  var len = 32;
93
+
94
+  if ("P-384" === jwk.crv) {
95
+    ci = 16 + OBJ_ID_EC_384.length/2;
96
+    len = 48;
97
+  }
98
+
99
+  var c = u8[ci];
100
+  var xi = ci + 1;
101
+  var x = u8.slice(xi, xi + len);
102
+  var yi = xi + len;
103
+  var y;
104
+  if (0x04 === c) {
105
+    y = u8.slice(yi, yi + len);
106
+  } else if (0x02 !== c) {
107
+    throw new Error("not a supported EC private key");
108
+  }
109
+
110
+  return {
111
+    kty: jwk.kty
112
+  , crv: jwk.crv
113
+  , x: Enc.bufToUrlBase64(x)
114
+  //, xh: Enc.bufToHex(x)
115
+  , y: Enc.bufToUrlBase64(y)
116
+  //, yh: Enc.bufToHex(y)
117
+  };
118
+};
119
+x509.parsePkix = x509.parseSpki;

Loading…
Cancel
Save