Kaynağa Gözat

v1.3.0: integrate node v6 support (rsa-compat backport)

tags/v1.3.0
AJ ONeal 5 ay önce
ebeveyn
işleme
1c67c47131

+ 1
- 1
index.js Dosyayı Görüntüle

@@ -1,2 +1,2 @@
1 1
 'use strict';
2
-module.exports = require('./lib/rasha.js');
2
+module.exports = require('./lib/rsa.js');

+ 51
- 0
lib/crypto.js Dosyayı Görüntüle

@@ -0,0 +1,51 @@
1
+'use strict';
2
+/*global Promise*/
3
+
4
+var PEM = require('./pem.js');
5
+var x509 = require('./x509.js');
6
+var ASN1 = require('./asn1.js');
7
+
8
+// Hacky-do, wrappy-do
9
+module.exports.generate = function (opts) {
10
+  if (!opts) { opts = {}; }
11
+  return new Promise(function (resolve, reject) {
12
+    try {
13
+      var modlen = opts.modulusLength || 2048;
14
+      var exp = opts.publicExponent || 0x10001;
15
+      var pair = require('./generate-privkey.js')(modlen,exp);
16
+      if (pair.private) { resolve(pair); return; }
17
+      pair = toJwks(pair);
18
+      resolve({ private: pair.private , public: pair.public });
19
+    } catch(e) {
20
+      reject(e);
21
+    }
22
+  });
23
+};
24
+
25
+// PKCS1 to JWK only
26
+function toJwks(oldpair) {
27
+  var block = PEM.parseBlock(oldpair.privateKeyPem);
28
+  var asn1 = ASN1.parse(block.bytes);
29
+  var jwk = { kty: 'RSA', n: null, e: null };
30
+  jwk = x509.parsePkcs1(block.bytes, asn1, jwk);
31
+  return { private: jwk, public: neuter(jwk) };
32
+}
33
+
34
+// Copied from rsa.js to prevent circular dep
35
+var privates = [ 'p', 'q', 'd', 'dp', 'dq', 'qi' ];
36
+function neuter(priv) {
37
+  var pub = {};
38
+  Object.keys(priv).forEach(function (key) {
39
+    if (!privates.includes(key)) {
40
+      pub[key] = priv[key];
41
+    }
42
+  });
43
+  return pub;
44
+}
45
+
46
+if (require.main === module) {
47
+  module.exports.generate().then(function (pair) {
48
+    console.info(JSON.stringify(pair.private, null, 2));
49
+    console.info(JSON.stringify(pair.public, null, 2));
50
+  });
51
+}

+ 53
- 0
lib/generate-privkey-forge.js Dosyayı Görüntüle

@@ -0,0 +1,53 @@
1
+// Copyright 2016-2018 AJ ONeal. All rights reserved
2
+/* This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
+'use strict';
6
+
7
+module.exports = function (bitlen, exp) {
8
+  var k = require('node-forge').pki.rsa
9
+    .generateKeyPair({ bits: bitlen || 2048, e: exp || 0x10001 }).privateKey;
10
+  var jwk = {
11
+    kty: "RSA"
12
+  , n: _toUrlBase64(k.n)
13
+  , e: _toUrlBase64(k.e)
14
+  , d: _toUrlBase64(k.d)
15
+  , p: _toUrlBase64(k.p)
16
+  , q: _toUrlBase64(k.q)
17
+  , dp: _toUrlBase64(k.dP)
18
+  , dq: _toUrlBase64(k.dQ)
19
+  , qi: _toUrlBase64(k.qInv)
20
+  };
21
+  return {
22
+    private: jwk
23
+  , public: {
24
+      kty: jwk.kty
25
+    , n: jwk.n
26
+    , e: jwk.e
27
+    }
28
+  };
29
+};
30
+
31
+function _toUrlBase64(fbn) {
32
+  var hex = fbn.toRadix(16);
33
+  if (hex.length % 2) {
34
+    // Invalid hex string
35
+    hex = '0' + hex;
36
+  }
37
+  while ('00' === hex.slice(0, 2)) {
38
+    hex = hex.slice(2);
39
+  }
40
+  return Buffer.from(hex, 'hex').toString('base64')
41
+    .replace(/\+/g, "-")
42
+    .replace(/\//g, "_")
43
+    .replace(/=/g,"")
44
+  ;
45
+}
46
+
47
+if (require.main === module) {
48
+  var keypair = module.exports(2048, 0x10001);
49
+  console.info(keypair.private);
50
+  console.warn(keypair.public);
51
+  //console.info(keypair.privateKeyJwk);
52
+  //console.warn(keypair.publicKeyJwk);
53
+}

+ 26
- 0
lib/generate-privkey-node.js Dosyayı Görüntüle

@@ -0,0 +1,26 @@
1
+// Copyright 2016-2018 AJ ONeal. All rights reserved
2
+/* This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
+'use strict';
6
+
7
+module.exports = function (bitlen, exp) {
8
+  var keypair = require('crypto').generateKeyPairSync(
9
+    'rsa'
10
+  , { modulusLength: bitlen
11
+    , publicExponent: exp
12
+    , privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
13
+    , publicKeyEncoding: { type: 'pkcs1', format: 'pem' }
14
+    }
15
+  );
16
+  var result = { privateKeyPem: keypair.privateKey.trim() };
17
+  return result;
18
+};
19
+
20
+if (require.main === module) {
21
+  var keypair = module.exports(2048, 0x10001);
22
+  console.info(keypair.privateKeyPem);
23
+  console.warn(keypair.publicKeyPem);
24
+  //console.info(keypair.privateKeyJwk);
25
+  //console.warn(keypair.publicKeyJwk);
26
+}

+ 25
- 0
lib/generate-privkey-ursa.js Dosyayı Görüntüle

@@ -0,0 +1,25 @@
1
+// Copyright 2016-2018 AJ ONeal. All rights reserved
2
+/* This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
+'use strict';
6
+
7
+module.exports = function (bitlen, exp) {
8
+  var ursa;
9
+  try {
10
+    ursa = require('ursa');
11
+  } catch(e) {
12
+    ursa = require('ursa-optional');
13
+  }
14
+  var keypair = ursa.generatePrivateKey(bitlen, exp);
15
+  var result = { privateKeyPem: keypair.toPrivatePem().toString('ascii').trim() };
16
+  return result;
17
+};
18
+
19
+if (require.main === module) {
20
+  var keypair = module.exports(2048, 0x10001);
21
+  console.info(keypair.privateKeyPem);
22
+  console.warn(keypair.publicKeyPem);
23
+  //console.info(keypair.privateKeyJwk);
24
+  //console.warn(keypair.publicKeyJwk);
25
+}

+ 67
- 0
lib/generate-privkey.js Dosyayı Görüntüle

@@ -0,0 +1,67 @@
1
+// Copyright 2016-2018 AJ ONeal. All rights reserved
2
+/* This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
+'use strict';
6
+
7
+var oldver = false;
8
+
9
+module.exports = function (bitlen, exp) {
10
+  bitlen = parseInt(bitlen, 10) || 2048;
11
+  exp = parseInt(exp, 10) || 65537;
12
+
13
+  try {
14
+    return require('./generate-privkey-node.js')(bitlen, exp);
15
+  } catch(e) {
16
+    if (!/generateKeyPairSync is not a function/.test(e.message)) {
17
+      throw e;
18
+    }
19
+    try {
20
+      return require('./generate-privkey-ursa.js')(bitlen, exp);
21
+    } catch(e) {
22
+      if (e.code !== 'MODULE_NOT_FOUND') {
23
+        console.error("[rsa-compat] Unexpected error when using 'ursa':");
24
+        console.error(e);
25
+      }
26
+      if (!oldver) {
27
+        oldver = true;
28
+        console.warn("[WARN] rsa-compat: Your version of node does not have crypto.generateKeyPair()");
29
+        console.warn("[WARN] rsa-compat: Please update to node >= v10.12 or 'npm install --save ursa node-forge'");
30
+        console.warn("[WARN] rsa-compat: Using node-forge as a fallback may be unacceptably slow.");
31
+        if (/arm|mips/i.test(require('os').arch)) {
32
+          console.warn("================================================================");
33
+          console.warn("                         WARNING");
34
+          console.warn("================================================================");
35
+          console.warn("");
36
+          console.warn("WARNING: You are generating an RSA key using pure JavaScript on");
37
+          console.warn("         a VERY SLOW cpu. This could take DOZENS of minutes!");
38
+          console.warn("");
39
+          console.warn("         We recommend installing node >= v10.12, or 'gcc' and 'ursa'");
40
+          console.warn("");
41
+          console.warn("EXAMPLE:");
42
+          console.warn("");
43
+          console.warn("        sudo apt-get install build-essential && npm install ursa");
44
+          console.warn("");
45
+          console.warn("================================================================");
46
+        }
47
+      }
48
+      try {
49
+        return require('./generate-privkey-forge.js')(bitlen, exp);
50
+      } catch(e) {
51
+        if (e.code !== 'MODULE_NOT_FOUND') {
52
+          throw e;
53
+        }
54
+        console.error("[ERROR] rsa-compat: could not generate a private key.");
55
+        console.error("None of crypto.generateKeyPair, ursa, nor node-forge are present");
56
+      }
57
+    }
58
+  }
59
+};
60
+
61
+if (require.main === module) {
62
+  var keypair = module.exports(2048, 0x10001);
63
+  console.info(keypair.privateKeyPem);
64
+  console.warn(keypair.publicKeyPem);
65
+  //console.info(keypair.privateKeyJwk);
66
+  //console.warn(keypair.publicKeyJwk);
67
+}

lib/rasha.js → lib/rsa.js Dosyayı Görüntüle

@@ -6,19 +6,30 @@ var PEM = require('./pem.js');
6 6
 var x509 = require('./x509.js');
7 7
 var ASN1 = require('./asn1.js');
8 8
 var Enc = require('./encoding.js');
9
+var Crypto = require('./crypto.js');
9 10
 
10 11
 /*global Promise*/
11 12
 RSA.generate = function (opts) {
12
-  return Promise.resolve().then(function () {
13
-    var typ = 'rsa';
13
+  opts.kty = "RSA";
14
+  return Crypto.generate(opts).then(function (pair) {
14 15
     var format = opts.format;
15 16
     var encoding = opts.encoding;
16
-    var priv;
17
-    var pub;
18 17
 
19
-    if (!format) {
18
+    // The easy way
19
+    if ('json' === format && !encoding) {
20 20
       format = 'jwk';
21
+      encoding = 'json';
22
+    }
23
+    if (('jwk' === format || !format) && ('json' === encoding || !encoding)) { return pair; }
24
+    if ('jwk' === format || 'json' === encoding) {
25
+      throw new Error("format '" + format + "' is incompatible with encoding '" + encoding + "'");
21 26
     }
27
+
28
+    // The... less easy way
29
+    /*
30
+    var priv;
31
+    var pub;
32
+
22 33
     if ('spki' === format || 'pkcs8' === format) {
23 34
       format = 'pkcs8';
24 35
       pub = 'spki';
@@ -32,13 +43,8 @@ RSA.generate = function (opts) {
32 43
       encoding = 'der';
33 44
     }
34 45
 
35
-    if ('jwk' === format || 'json' === format) {
36
-      format = 'jwk';
37
-      encoding = 'json';
38
-    } else {
39
-      priv = format;
40
-      pub = pub || format;
41
-    }
46
+    priv = format;
47
+    pub = pub || format;
42 48
 
43 49
     if (!encoding) {
44 50
       encoding = 'pem';
@@ -52,29 +58,19 @@ RSA.generate = function (opts) {
52 58
       priv = { type: 'pkcs1', format: 'pem' };
53 59
       pub = { type: 'pkcs1', format: 'pem' };
54 60
     }
61
+    */
62
+    if (('pem' === format || 'der' === format) && !encoding) {
63
+      encoding = format;
64
+      format = 'pkcs1';
65
+    }
55 66
 
56
-    return new Promise(function (resolve, reject) {
57
-      return require('crypto').generateKeyPair(typ, {
58
-        modulusLength: opts.modulusLength || 2048
59
-      , publicExponent: opts.publicExponent || 0x10001
60
-      , privateKeyEncoding: priv
61
-      , publicKeyEncoding: pub
62
-      }, function (err, pubkey, privkey) {
63
-        if (err) { reject(err); }
64
-        resolve({
65
-          private: privkey
66
-        , public: pubkey
67
-        });
67
+    var exOpts = { jwk: pair.private, format: format, encoding: encoding };
68
+    return RSA.export(exOpts).then(function (priv) {
69
+      exOpts.public = true;
70
+      if ('pkcs8' === exOpts.format) { exOpts.format = 'spki'; }
71
+      return RSA.export(exOpts).then(function (pub) {
72
+        return { private: priv, public: pub };
68 73
       });
69
-    }).then(function (keypair) {
70
-      if ('jwk' !== format) {
71
-        return keypair;
72
-      }
73
-
74
-      return {
75
-        private: RSA.importSync({ pem: keypair.private, format: priv.type })
76
-      , public: RSA.importSync({ pem: keypair.public, format: pub.type, public: true })
77
-      };
78 74
     });
79 75
   });
80 76
 };
@@ -102,7 +98,7 @@ RSA.importSync = function (opts) {
102 98
   }
103 99
 
104 100
   if (opts.public) {
105
-    jwk = RSA.nueter(jwk);
101
+    jwk = RSA.neuter(jwk);
106 102
   }
107 103
   return jwk;
108 104
 };
@@ -139,7 +135,7 @@ RSA.exportSync = function (opts) {
139 135
   var format = opts.format;
140 136
   var pub = opts.public;
141 137
   if (pub || -1 !== [ 'spki', 'pkix', 'ssh', 'rfc4716' ].indexOf(format)) {
142
-    jwk = RSA.nueter(jwk);
138
+    jwk = RSA.neuter(jwk);
143 139
   }
144 140
   if ('RSA' !== jwk.kty) {
145 141
     throw new Error("options.jwk.kty must be 'RSA' for RSA keys");
@@ -193,15 +189,16 @@ RSA.pack = function (opts) {
193 189
 RSA.toPem = RSA.export = RSA.pack;
194 190
 
195 191
 // snip the _private_ parts... hAHAHAHA!
196
-RSA.nueter = function (jwk) {
197
-  // (snip rather than new object to keep potential extra data)
198
-  // otherwise we could just do this:
199
-  // return { kty: jwk.kty, n: jwk.n, e: jwk.e };
200
-  [ 'p', 'q', 'd', 'dp', 'dq', 'qi' ].forEach(function (key) {
201
-    if (key in jwk) { jwk[key] = undefined; }
202
-    return jwk;
192
+var privates = [ 'p', 'q', 'd', 'dp', 'dq', 'qi' ];
193
+// fix misspelling without breaking the API
194
+RSA.neuter = RSA.nueter = function (priv) {
195
+  var pub = {};
196
+  Object.keys(priv).forEach(function (key) {
197
+    if (!privates.includes(key)) {
198
+      pub[key] = priv[key];
199
+    }
203 200
   });
204
-  return jwk;
201
+  return pub;
205 202
 };
206 203
 
207 204
 RSA.__thumbprint = function (jwk) {

+ 5
- 2
package.json Dosyayı Görüntüle

@@ -1,6 +1,6 @@
1 1
 {
2 2
   "name": "rasha",
3
-  "version": "1.2.5",
3
+  "version": "1.3.0",
4 4
   "description": "💯 PEM-to-JWK and JWK-to-PEM for RSA keys in a lightweight, zero-dependency library focused on perfect universal compatibility.",
5 5
   "homepage": "https://git.coolaj86.com/coolaj86/rasha.js",
6 6
   "main": "index.js",
@@ -35,5 +35,8 @@
35 35
     "PEM-to-SSH"
36 36
   ],
37 37
   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
38
-  "license": "MPL-2.0"
38
+  "license": "MPL-2.0",
39
+  "trulyOptionalDependencies": {
40
+    "node-forge": "^0.8.2"
41
+  }
39 42
 }

+ 8
- 0
test.sh Dosyayı Görüntüle

@@ -123,13 +123,21 @@ jwktopem ""
123 123
 
124 124
 echo ""
125 125
 echo "testing node key generation"
126
+echo "defaults"
126 127
 node bin/rasha.js > /dev/null
128
+echo "jwk"
127 129
 node bin/rasha.js jwk > /dev/null
130
+echo "json 2048"
128 131
 node bin/rasha.js json 2048 > /dev/null
132
+echo "der"
129 133
 node bin/rasha.js der > /dev/null
134
+echo "pkcs8 der"
130 135
 node bin/rasha.js pkcs8 der > /dev/null
136
+echo "pem"
131 137
 node bin/rasha.js pem > /dev/null
138
+echo "pkcs1"
132 139
 node bin/rasha.js pkcs1 pem > /dev/null
140
+echo "spki"
133 141
 node bin/rasha.js spki > /dev/null
134 142
 echo "PASS"
135 143
 

Loading…
İptal
Kaydet