Browse Source

v1.0.0: just wrap Rasha and Eckles for now

tags/v1.0.0
AJ ONeal 1 year ago
parent
commit
265977e2c0
50 changed files with 126 additions and 3134 deletions
  1. +1
    -0
      .gitignore
  2. +56
    -83
      README.md
  3. +0
    -20
      bin/keypairs.js
  4. +0
    -134
      browser.js
  5. +0
    -13
      convert-from-hex.js
  6. +0
    -16
      convert-to-der.js
  7. +0
    -7
      fixtures/privkey-ec-p256.jwk.json
  8. +0
    -5
      fixtures/privkey-ec-p256.pkcs8.pem
  9. +0
    -5
      fixtures/privkey-ec-p256.sec1.pem
  10. +0
    -7
      fixtures/privkey-ec-p384.jwk.json
  11. +0
    -6
      fixtures/privkey-ec-p384.pkcs8.pem
  12. +0
    -6
      fixtures/privkey-ec-p384.sec1.pem
  13. +0
    -11
      fixtures/privkey-rsa-2048.jwk.json
  14. +0
    -27
      fixtures/privkey-rsa-2048.pkcs1.pem
  15. +0
    -28
      fixtures/privkey-rsa-2048.pkcs8.pem
  16. +0
    -6
      fixtures/pub-ec-p256.jwk.json
  17. +0
    -4
      fixtures/pub-ec-p256.spki.pem
  18. +0
    -1
      fixtures/pub-ec-p256.ssh.pub
  19. +0
    -6
      fixtures/pub-ec-p384.jwk.json
  20. +0
    -5
      fixtures/pub-ec-p384.spki.pem
  21. +0
    -1
      fixtures/pub-ec-p384.ssh.pub
  22. +0
    -5
      fixtures/pub-rsa-2048.jwk.json
  23. +0
    -8
      fixtures/pub-rsa-2048.pkcs1.pem
  24. +0
    -9
      fixtures/pub-rsa-2048.spki.pem
  25. +0
    -1
      fixtures/pub-rsa-2048.ssh.pub
  26. +0
    -3
      index.js
  27. +42
    -0
      keypairs.js
  28. +0
    -111
      lib/asn1-packer.js
  29. +0
    -147
      lib/asn1-parser.js
  30. +0
    -3
      lib/crypto.js
  31. +0
    -63
      lib/ec.js
  32. +0
    -493
      lib/eckles.js
  33. +0
    -42
      lib/encoding.js
  34. +0
    -143
      lib/keypairs.js
  35. +0
    -14
      lib/pem-packer.js
  36. +0
    -21
      lib/pem-parser.js
  37. +0
    -28
      lib/pem.js
  38. +0
    -204
      lib/rasha.js
  39. +0
    -80
      lib/ssh.js
  40. +0
    -111
      lib/telemetry.js
  41. +0
    -123
      lib/x509-ec-parser.js
  42. +0
    -14
      lib/x509-packer.js
  43. +0
    -46
      lib/x509-parser.js
  44. +0
    -3
      lib/x509-rsa-parser.js
  45. +0
    -153
      lib/x509.js
  46. +18
    -0
      package-lock.json
  47. +9
    -6
      package.json
  48. +0
    -23
      pubkey-cli.js
  49. +0
    -292
      pubkey.js
  50. +0
    -597
      re-sign.js

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
node_modules

+ 56
- 83
README.md View File

@@ -1,106 +1,79 @@
This is being ported from code from rsa-compat.js, greenlock.html (bacme.js), and others.
# Keypairs for node.js

This was intended to be just a weekend project, but it's grown a bit.
Lightweight JavaScript RSA and ECDSA utils that work on Windows, Mac, and Linux
using modern node.js APIs (no need for C compiler).

* 2018-10-10 (Saturday) work has begun
* 2018-10-11 (Sunday) W00T! got a CSR generated for RSA with VanillaJS ArrayBuffer
* 2018-10-12 (Monday) Figuring out ECDSA CSRs right now
* 2018-10-15 (Thursday) ECDSA is a trixy hobbit... but I think I've got it...
* 2018-12-02 (Sunday) Been mostly done for a while, individually, slowly merging everything together
* [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js) (RSA utils)
* [Eckles.js](https://git.coolaj86.com/coolaj86/eckles.js) (EC utils)
A thin wrapper around [Eckles.js (ECDSA)](https://git.coolaj86.com/coolaj86/eckles.js/)
and [Rasha.js (RSA)](https://git.coolaj86.com/coolaj86/rasha.js/).

# Features

* [x] Generate keypairs
* [x] RSA
* [x] ECDSA (P-256, P-384)
* [x] PEM-to-JWK
* [x] JWK-to-PEM
* [x] SHA256 JWK Thumbprints
* [ ] JWK fetching. See [Keyfetch.js](https://npmjs.com/packages/keyfetch/)
* [ ] OIDC
* [ ] Auth0

<!--
Keypairs&trade; for node.js
===========================

JavaScript RSA and ECDSA utils that work on Windows, Mac, and Linux with or without C compiler.
* [ ] sign JWS
* [ ] generate CSR (DER as PEM or base64url)

-->

# Usage

There are many different RSA and ECDSA libraries for node and it seems like they're
all incompatible in different ways. This isn't [yet another library](https://xkcd.com/927/),
but rather [one to rule them all and bind them](https://en.wikipedia.org/wiki/One_Ring).
A brief (albeit somewhat nonsensical) introduction to the APIs:

Features
========
```
Keypairs.generate().then(function (jwk) {
return Keypairs.export({ jwk: jwk }).then(function (pem) {
return Keypairs.import({ pem: pem }).then(function (jwk) {
return Keypairs.thumbprint({ jwk: jwk }).then(function (thumb) {
console.log(thumb);
});
});
});
});
```

* [x] RSA
* [ ] ECDSA (in-progress)
* [x] generate keypair
* [x] export to JWK
* [x] import from JWK
* [x] export to PEM
* [x] import from PEM
* [x] sign JWS
* [x] generate CSR (DER as PEM or base64url)
By default ECDSA keys will be used since they've had native support in node
_much_ longer than RSA has, and they're smaller, and faster to generate.

API
===
## API

Each of these return a Promise.

* `Keypairs.generate(options)`
* options example `{ type: 'RSA' || 'ECDSA', bitlength: 2048 || 256 }`
* options example `{ kty: 'RSA', modulusLength: 2048 }`
* options example `{ kty: 'ECDSA', namedCurve: 'P-256' }`
* `Keypairs.import(options)`
* options example `{ pem: '...', crv: 'P-256' || 'ECC', bitlength: 2048 || 256 }`
* options example `{ pem: '...' }`
* `Keypairs.export(options)`
* options example `{ private: true || false, pem: true || false }`
* options example `{ jwk: jwk }`
* options example `{ jwk: jwk, public: true }`
* `Keypairs.thumbprint({ jwk: jwk })`

<!--

* `Keypairs.jws.sign(options)`
* options example `{ keypair, header, protected, payload }`
* `Keypairs.csr.generate(options)`
* options example `{ keypair, [ 'example.com' ] }`

`keypair` can be any object with
any of these keys `publicKeyPem, privateKeyPem, publicKeyJwk, privateKeyJwk`.

Examples
========

These are quick examples of how to use the library.
If you have a specific question, please open an issue.

Keypairs.generate(options)
-------------------

Simple RSA

```js
return Keypairs.generate({
type: 'RSA'
, bitlength: 2048
}).then(function (keypair) {

// we won't bother describing this object
// because it's only useful once exported

});
```

Advanced RSA

```js
return Keypairs.generate({
type: 'RSA'
, bitlength: 2048 // or 4096
, exponent: 65537 // don't change this
, public: true // pre-cache public key
, pem: true // pre-export the PEM
, internal: true // pre-cache internal representations
}).then(function (keypair) {

// we won't bother describing this object
// because it's only useful once exported

});
```
-->

Keypairs.export(options)
-------------------
# Full Documentation

Keypairs.import(options)
-------------------
Keypairs.js provides a 1-to-1 mapping to the Rasha.js and Eckles.js APIs.

Keypairs.jws.sign(options)
-------------------
The full RSA documentation is at [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js/)

Keypairs.csr.generate(options)
-------------------
The full ECDSA documentation is at [Eckles.js](https://git.coolaj86.com/coolaj86/eckles.js/)

-->
Any option you pass to Keypairs will be passed directly to the corresponding API
of either Rasha or Eckles.

+ 0
- 20
bin/keypairs.js View File

@@ -1,20 +0,0 @@
'use strict';

var keypairs = require('../');

var infile = process.argv[2];
var key = require('fs').readFileSync(infile).toString('ascii');
var pem;
var jwk;

try {
jwk = JSON.parse(key);
} catch(e) {
pem = key;
}

keypairs.import({ jwk: jwk, pem: pem }).then(function (keypair) {
console.log(keypair);
}).catch(function (e) {
console.error(e);
});

+ 0
- 134
browser.js View File

@@ -1,134 +0,0 @@
;(function (exports) {
'use strict';

var PromiseA;
try {
/*global Promise*/
PromiseA = Promise;
} catch(e) {
PromiseA = require('bluebird');
}


// https://stackoverflow.com/questions/40314257/export-webcrypto-key-to-pem-format
function derToPem(keydata, pemName, privacy){
var keydataS = arrayBufferToString(keydata);
var keydataB64 = window.btoa(keydataS);
var keydataB64Pem = formatAsPem(keydataB64, pemName, privacy);
return keydataB64Pem;
}

function arrayBufferToString( buffer ) {
var binary = [];
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary.push(String.fromCharCode( bytes[ i ] ));
}
return binary.join('');
}


function formatAsPem(str, pemName, privacy) {
var privstr = (privacy ? privacy + ' ' : '');
var finalString = '-----BEGIN ' + pemName + ' ' + privstr + 'KEY-----\n';

while (str.length > 0) {
finalString += str.substring(0, 64) + '\n';
str = str.substring(64);
}

finalString = finalString + '-----END ' + pemName + ' ' + privstr + 'KEY-----';

return finalString;
}

var Keypairs = exports.Keypairs = {
generate: function(opts) {
if (!opts) { opts = {}; }
if (!opts.type) { opts.type = 'EC'; }

var supported = [ 'EC', 'RSA' ];
if (-1 === supported.indexOf(opts.type)) {
return PromiseA.reject(new Error("'" + opts.type + "' not implemented. Try one of " + supported.join(', ')));
}

if ('EC' === opts.type) {
return Keypairs._generateEc(opts);
}
if ('RSA' === opts.type) {
return Keypairs._generateRsa(opts);
}
}
, _generateEc: function (opts) {
if (!opts.namedCurve) { opts.namedCurve = 'P-256'; }
if ('P-256' !== opts.namedCurve) {
console.warn("'" + opts.namedCurve + "' is not supported, but it _might_ happen to work anyway.");
}

// https://github.com/diafygi/webcrypto-examples#ecdsa---generatekey
var extractable = true;

return window.crypto.subtle.generateKey(
{ name: "ECDSA", namedCurve: opts.namedCurve }
, extractable
, [ 'sign', 'verify' ]
).then(function (result) {
return window.crypto.subtle.exportKey(
"jwk"
, result.privateKey
).then(function (jwk) {
return window.crypto.subtle.exportKey(
"pkcs8"
, result.privateKey
).then(function (keydata) {
return {
type: 'EC'
, privateJwk: jwk
, privatePem: derToPem(keydata, 'EC', 'PRIVATE')
};
});
});
});
}
, _generateRsa: function (opts) {
if (!opts.bitlength) { opts.bitlength = 2048; }
if (-1 === [ 2048, 4096 ].indexOf(opts.bitlength)) {
return PromiseA.reject("opts.bitlength = (" + typeof opts.bitlength + ") " + opts.bitlength + ": Are you serious?");
}

// https://github.com/diafygi/webcrypto-examples#rsa---generatekey
var extractable = true;

return window.crypto.subtle.generateKey(
{ name: "RSASSA-PKCS1-v1_5"
, modulusLength: opts.bitlength
, publicExponent: new Uint8Array([0x01, 0x00, 0x01])
, hash: { name: "SHA-256" }
}
, extractable
, [ 'sign', 'verify' ]
).then(function (result) {
return window.crypto.subtle.exportKey(
"jwk"
, result.privateKey
).then(function (jwk) {
return window.crypto.subtle.exportKey(
"pkcs8"
, result.privateKey
).then(function (keydata) {
return {
type: 'RSA'
, privateJwk: jwk
, privatePem: derToPem(keydata, 'RSA', 'PRIVATE')
};
});
});
});
}
};

}('undefined' === typeof module ? window : module.exports));

// How we might use this
// var Keypairs = require('keypairs').Keypairs

+ 0
- 13
convert-from-hex.js View File

@@ -1,13 +0,0 @@
'use strict';

var fs = require('fs');
var path = require('path');

function convert(name) {
var ext = path.extname(name);
var csr = fs.readFileSync(name, 'ascii').replace(/\s\+/g, '');
var bin = Buffer.from(csr, 'hex');
fs.writeFileSync(name.replace(new RegExp('\\' + ext + '$'), '') + '.bin', bin);
}

convert(process.argv[2]);

+ 0
- 16
convert-to-der.js View File

@@ -1,16 +0,0 @@
'use strict';

var fs = require('fs');
var path = require('path');

function convert(name) {
var ext = path.extname(name);
var csr = fs.readFileSync(name, 'ascii').split(/\n/).filter(function (line) {
return !/---/.test(line);
}).join('');
console.log(csr);
var der = Buffer.from(csr, 'base64');
fs.writeFileSync(name.replace(new RegExp('\\' + ext + '$'), '') + '.der', der);
}

convert(process.argv[2]);

+ 0
- 7
fixtures/privkey-ec-p256.jwk.json View File

@@ -1,7 +0,0 @@
{
"kty": "EC",
"crv": "P-256",
"d": "iYydo27aNGO9DBUWeGEPD8oNi1LZDqfxPmQlieLBjVQ",
"x": "IT1SWLxsacPiE5Z16jkopAn8_-85rMjgyCokrnjDft4",
"y": "mP2JwOAOdMmXuwpxbKng3KZz27mz-nKWIlXJ3rzSGMo"
}

+ 0
- 5
fixtures/privkey-ec-p256.pkcs8.pem View File

@@ -1,5 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgiYydo27aNGO9DBUW
eGEPD8oNi1LZDqfxPmQlieLBjVShRANCAAQhPVJYvGxpw+ITlnXqOSikCfz/7zms
yODIKiSueMN+3pj9icDgDnTJl7sKcWyp4Nymc9u5s/pyliJVyd680hjK
-----END PRIVATE KEY-----

+ 0
- 5
fixtures/privkey-ec-p256.sec1.pem View File

@@ -1,5 +0,0 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIImMnaNu2jRjvQwVFnhhDw/KDYtS2Q6n8T5kJYniwY1UoAoGCCqGSM49
AwEHoUQDQgAEIT1SWLxsacPiE5Z16jkopAn8/+85rMjgyCokrnjDft6Y/YnA4A50
yZe7CnFsqeDcpnPbubP6cpYiVcnevNIYyg==
-----END EC PRIVATE KEY-----

+ 0
- 7
fixtures/privkey-ec-p384.jwk.json View File

@@ -1,7 +0,0 @@
{
"kty": "EC",
"crv": "P-384",
"d": "XlyuCEWSTTS8U79O_Mz05z18vh4kb10szvu_7pdXuGWV6lfEyPExyUYWsA6A2kdV",
"x": "2zEU0bKCa7ejKLIJ8oPGnLhqhxyiv4_w38K2a0SPC6dsSd9_glNJ8lcqv0sff5Gb",
"y": "VD4jnu83S6scn6_TeAj3EZOREGbOs6dzoVpaugn-XQMMyC9O4VLbDDFGBZTJlMsb"
}

+ 0
- 6
fixtures/privkey-ec-p384.pkcs8.pem View File

@@ -1,6 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBeXK4IRZJNNLxTv078
zPTnPXy+HiRvXSzO+7/ul1e4ZZXqV8TI8THJRhawDoDaR1WhZANiAATbMRTRsoJr
t6Mosgnyg8acuGqHHKK/j/DfwrZrRI8Lp2xJ33+CU0nyVyq/Sx9/kZtUPiOe7zdL
qxyfr9N4CPcRk5EQZs6zp3OhWlq6Cf5dAwzIL07hUtsMMUYFlMmUyxs=
-----END PRIVATE KEY-----

+ 0
- 6
fixtures/privkey-ec-p384.sec1.pem View File

@@ -1,6 +0,0 @@
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDBeXK4IRZJNNLxTv078zPTnPXy+HiRvXSzO+7/ul1e4ZZXqV8TI8THJ
RhawDoDaR1WgBwYFK4EEACKhZANiAATbMRTRsoJrt6Mosgnyg8acuGqHHKK/j/Df
wrZrRI8Lp2xJ33+CU0nyVyq/Sx9/kZtUPiOe7zdLqxyfr9N4CPcRk5EQZs6zp3Oh
Wlq6Cf5dAwzIL07hUtsMMUYFlMmUyxs=
-----END EC PRIVATE KEY-----

+ 0
- 11
fixtures/privkey-rsa-2048.jwk.json View File

@@ -1,11 +0,0 @@
{
"kty": "RSA",
"n": "m2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJefLukC-xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57qAZM6-I70on0_iDZm7-jcqOPgADAmbWHhy67BXkk4yy_YzD4yOGZFXZcNp915_TW5bRd__AKPHUHxJasPiyEFqlNKBR2DSD-LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0Qi49OykUCfNZeQlEz7UNNj9RGps_50-CNw",
"e": "AQAB",
"d": "Cpfo7Mm9Nu8YMC_xrZ54W9mKHPkCG9rZ93Ds9PNp-RXUgb-ljTbFPZWsYxGNKLllFz8LNosr1pT2ZDMrwNk0Af1iWNvD6gkyXaiQdCyiDPSBsJyNv2LJZon-e85X74nv53UlIkmo9SYxdLz2JaJ-iIWEe8Qh-7llLktrTJV_xr98_tbhgSppz_IeOymq3SEZaQHM8pTU7w7XvCj2pb9r8fN0M0XcgWZIaf3LGEfkhF_WtX67XJ0C6-LbkT51jtlLRNGX6haGdscXS0OWWjKOJzKGuV-NbthEn5rmRtVnjRZ3yaxQ0ud8vC-NONn7yvGUlOur1IdDzJ_YfHPt9sHMQQ",
"p": "ynG-t9HwKCN3MWRYFdnFzi9-02Qcy3p8B5pu3ary2E70hYn2pHlUG2a9BNE8c5xHQ3Hx43WoWf6s0zOunPV1G28LkU_UYEbAtPv_PxSmzpQp9n9XnYvBLBF8Y3z7gxgLn1vVFNARrQdRtj87qY3aw7E9S4DsGcAarIuOT2TsTCE",
"q": "xIkAjgUzB1zaUzJtW2Zgvp9cYYr1DmpH30ePZl3c_8397_DZDDo46fnFYjs6uPa03HpmKUnbjwr14QHlfXlntJBEuXxcqLjkdKdJ4ob7xueLTK4suo9V8LSrkLChVxlZQwnFD2E5ll0sVeeDeMJHQw38ahSrBFEVnxjpnPh1Q1c",
"dp": "tzDGjECFOU0ehqtuqhcuT63a7h8hj19-7MJqoFwY9HQ-ALkfXyYLXeBSGxHbyiIYuodZg6LsfMNgUJ3r3Eyhc_nAVfYPEC_2IdAG4WYmq7iXYF9LQV09qEsKbFykm7QekE3hO7wswo5k-q2tp3ieBYdVGAXJoGOdv5VpaZ7B1QE",
"dq": "kh5dyDk7YCz7sUFbpsmuAeuPjoH2ghooh2u3xN7iUVmAg-ToKjwbVnG5-7eXiC779rQVwnrD_0yh1AFJ8wjRPqDIR7ObXGHikIxT1VSQWqiJm6AfZzDsL0LUD4YS3iPdhob7-NxLKWzqao_u4lhnDQaX9PKa12HFlny6K1daL48",
"qi": "AlHWbx1gp6Z9pbw_1hlS7HuXAgWoX7IjbTUelldf4gkriDWLOrj3QCZcO4ZvZvEwJhVlsny9LO8IkbwGJEL6cXraK08ByVS2mwQyflgTgGNnpzixyEUL_mrQLx6y145FHcxfeqNInMhep-0Mxn1D5nlhmIOgRApS0t9VoXtHhFU"
}

+ 0
- 27
fixtures/privkey-rsa-2048.pkcs1.pem View File

@@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAm2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhD
NzUJefLukC+xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ
38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57
qAZM6+I70on0/iDZm7+jcqOPgADAmbWHhy67BXkk4yy/YzD4yOGZFXZcNp915/TW
5bRd//AKPHUHxJasPiyEFqlNKBR2DSD+LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By
5AB0Qi49OykUCfNZeQlEz7UNNj9RGps/50+CNwIDAQABAoIBAAqX6OzJvTbvGDAv
8a2eeFvZihz5Ahva2fdw7PTzafkV1IG/pY02xT2VrGMRjSi5ZRc/CzaLK9aU9mQz
K8DZNAH9Yljbw+oJMl2okHQsogz0gbCcjb9iyWaJ/nvOV++J7+d1JSJJqPUmMXS8
9iWifoiFhHvEIfu5ZS5La0yVf8a/fP7W4YEqac/yHjspqt0hGWkBzPKU1O8O17wo
9qW/a/HzdDNF3IFmSGn9yxhH5IRf1rV+u1ydAuvi25E+dY7ZS0TRl+oWhnbHF0tD
lloyjicyhrlfjW7YRJ+a5kbVZ40Wd8msUNLnfLwvjTjZ+8rxlJTrq9SHQ8yf2Hxz
7fbBzEECgYEAynG+t9HwKCN3MWRYFdnFzi9+02Qcy3p8B5pu3ary2E70hYn2pHlU
G2a9BNE8c5xHQ3Hx43WoWf6s0zOunPV1G28LkU/UYEbAtPv/PxSmzpQp9n9XnYvB
LBF8Y3z7gxgLn1vVFNARrQdRtj87qY3aw7E9S4DsGcAarIuOT2TsTCECgYEAxIkA
jgUzB1zaUzJtW2Zgvp9cYYr1DmpH30ePZl3c/8397/DZDDo46fnFYjs6uPa03Hpm
KUnbjwr14QHlfXlntJBEuXxcqLjkdKdJ4ob7xueLTK4suo9V8LSrkLChVxlZQwnF
D2E5ll0sVeeDeMJHQw38ahSrBFEVnxjpnPh1Q1cCgYEAtzDGjECFOU0ehqtuqhcu
T63a7h8hj19+7MJqoFwY9HQ+ALkfXyYLXeBSGxHbyiIYuodZg6LsfMNgUJ3r3Eyh
c/nAVfYPEC/2IdAG4WYmq7iXYF9LQV09qEsKbFykm7QekE3hO7wswo5k+q2tp3ie
BYdVGAXJoGOdv5VpaZ7B1QECgYEAkh5dyDk7YCz7sUFbpsmuAeuPjoH2ghooh2u3
xN7iUVmAg+ToKjwbVnG5+7eXiC779rQVwnrD/0yh1AFJ8wjRPqDIR7ObXGHikIxT
1VSQWqiJm6AfZzDsL0LUD4YS3iPdhob7+NxLKWzqao/u4lhnDQaX9PKa12HFlny6
K1daL48CgYACUdZvHWCnpn2lvD/WGVLse5cCBahfsiNtNR6WV1/iCSuINYs6uPdA
Jlw7hm9m8TAmFWWyfL0s7wiRvAYkQvpxetorTwHJVLabBDJ+WBOAY2enOLHIRQv+
atAvHrLXjkUdzF96o0icyF6n7QzGfUPmeWGYg6BEClLS31Whe0eEVQ==
-----END RSA PRIVATE KEY-----

+ 0
- 28
fixtures/privkey-rsa-2048.pkcs8.pem View File

@@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCba21UHE+VbDTp
mYYFZUOV+OQ8AngOCdjROsPC0KiEfMvEaEM3NQl58u6QL7G7QsErKViiNPm9OTFo
6HF5JijfWzK7haHFuRMEsgI4VwIYyhvqlJDfw/wt0AiVvSmoMfEQn1p1aiaO4V/R
JSE3Vw/uz2bxiT22uSkSqOyShyfYE6dMHnuoBkzr4jvSifT+INmbv6Nyo4+AAMCZ
tYeHLrsFeSTjLL9jMPjI4ZkVdlw2n3Xn9NbltF3/8Ao8dQfElqw+LIQWqU0oFHYN
IP4ttfl5ObMKHaKSvBMyNruZR0El/ZsrcHLkAHRCLj07KRQJ81l5CUTPtQ02P1Ea
mz/nT4I3AgMBAAECggEACpfo7Mm9Nu8YMC/xrZ54W9mKHPkCG9rZ93Ds9PNp+RXU
gb+ljTbFPZWsYxGNKLllFz8LNosr1pT2ZDMrwNk0Af1iWNvD6gkyXaiQdCyiDPSB
sJyNv2LJZon+e85X74nv53UlIkmo9SYxdLz2JaJ+iIWEe8Qh+7llLktrTJV/xr98
/tbhgSppz/IeOymq3SEZaQHM8pTU7w7XvCj2pb9r8fN0M0XcgWZIaf3LGEfkhF/W
tX67XJ0C6+LbkT51jtlLRNGX6haGdscXS0OWWjKOJzKGuV+NbthEn5rmRtVnjRZ3
yaxQ0ud8vC+NONn7yvGUlOur1IdDzJ/YfHPt9sHMQQKBgQDKcb630fAoI3cxZFgV
2cXOL37TZBzLenwHmm7dqvLYTvSFifakeVQbZr0E0TxznEdDcfHjdahZ/qzTM66c
9XUbbwuRT9RgRsC0+/8/FKbOlCn2f1edi8EsEXxjfPuDGAufW9UU0BGtB1G2Pzup
jdrDsT1LgOwZwBqsi45PZOxMIQKBgQDEiQCOBTMHXNpTMm1bZmC+n1xhivUOakff
R49mXdz/zf3v8NkMOjjp+cViOzq49rTcemYpSduPCvXhAeV9eWe0kES5fFyouOR0
p0nihvvG54tMriy6j1XwtKuQsKFXGVlDCcUPYTmWXSxV54N4wkdDDfxqFKsEURWf
GOmc+HVDVwKBgQC3MMaMQIU5TR6Gq26qFy5PrdruHyGPX37swmqgXBj0dD4AuR9f
Jgtd4FIbEdvKIhi6h1mDoux8w2BQnevcTKFz+cBV9g8QL/Yh0AbhZiaruJdgX0tB
XT2oSwpsXKSbtB6QTeE7vCzCjmT6ra2neJ4Fh1UYBcmgY52/lWlpnsHVAQKBgQCS
Hl3IOTtgLPuxQVumya4B64+OgfaCGiiHa7fE3uJRWYCD5OgqPBtWcbn7t5eILvv2
tBXCesP/TKHUAUnzCNE+oMhHs5tcYeKQjFPVVJBaqImboB9nMOwvQtQPhhLeI92G
hvv43EspbOpqj+7iWGcNBpf08prXYcWWfLorV1ovjwKBgAJR1m8dYKemfaW8P9YZ
Uux7lwIFqF+yI201HpZXX+IJK4g1izq490AmXDuGb2bxMCYVZbJ8vSzvCJG8BiRC
+nF62itPAclUtpsEMn5YE4BjZ6c4schFC/5q0C8esteORR3MX3qjSJzIXqftDMZ9
Q+Z5YZiDoEQKUtLfVaF7R4RV
-----END PRIVATE KEY-----

+ 0
- 6
fixtures/pub-ec-p256.jwk.json View File

@@ -1,6 +0,0 @@
{
"kty": "EC",
"crv": "P-256",
"x": "IT1SWLxsacPiE5Z16jkopAn8_-85rMjgyCokrnjDft4",
"y": "mP2JwOAOdMmXuwpxbKng3KZz27mz-nKWIlXJ3rzSGMo"
}

+ 0
- 4
fixtures/pub-ec-p256.spki.pem View File

@@ -1,4 +0,0 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIT1SWLxsacPiE5Z16jkopAn8/+85
rMjgyCokrnjDft6Y/YnA4A50yZe7CnFsqeDcpnPbubP6cpYiVcnevNIYyg==
-----END PUBLIC KEY-----

+ 0
- 1
fixtures/pub-ec-p256.ssh.pub View File

@@ -1 +0,0 @@
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCE9Uli8bGnD4hOWdeo5KKQJ/P/vOazI4MgqJK54w37emP2JwOAOdMmXuwpxbKng3KZz27mz+nKWIlXJ3rzSGMo= P-256@localhost

+ 0
- 6
fixtures/pub-ec-p384.jwk.json View File

@@ -1,6 +0,0 @@
{
"kty": "EC",
"crv": "P-384",
"x": "2zEU0bKCa7ejKLIJ8oPGnLhqhxyiv4_w38K2a0SPC6dsSd9_glNJ8lcqv0sff5Gb",
"y": "VD4jnu83S6scn6_TeAj3EZOREGbOs6dzoVpaugn-XQMMyC9O4VLbDDFGBZTJlMsb"
}

+ 0
- 5
fixtures/pub-ec-p384.spki.pem View File

@@ -1,5 +0,0 @@
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE2zEU0bKCa7ejKLIJ8oPGnLhqhxyiv4/w
38K2a0SPC6dsSd9/glNJ8lcqv0sff5GbVD4jnu83S6scn6/TeAj3EZOREGbOs6dz
oVpaugn+XQMMyC9O4VLbDDFGBZTJlMsb
-----END PUBLIC KEY-----

+ 0
- 1
fixtures/pub-ec-p384.ssh.pub View File

@@ -1 +0,0 @@
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBNsxFNGygmu3oyiyCfKDxpy4aoccor+P8N/CtmtEjwunbEnff4JTSfJXKr9LH3+Rm1Q+I57vN0urHJ+v03gI9xGTkRBmzrOnc6FaWroJ/l0DDMgvTuFS2wwxRgWUyZTLGw== P-384@localhost

+ 0
- 5
fixtures/pub-rsa-2048.jwk.json View File

@@ -1,5 +0,0 @@
{
"kty": "RSA",
"n": "m2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJefLukC-xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57qAZM6-I70on0_iDZm7-jcqOPgADAmbWHhy67BXkk4yy_YzD4yOGZFXZcNp915_TW5bRd__AKPHUHxJasPiyEFqlNKBR2DSD-LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0Qi49OykUCfNZeQlEz7UNNj9RGps_50-CNw",
"e": "AQAB"
}

+ 0
- 8
fixtures/pub-rsa-2048.pkcs1.pem View File

@@ -1,8 +0,0 @@
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAm2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJ
efLukC+xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ38P8
LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57qAZM
6+I70on0/iDZm7+jcqOPgADAmbWHhy67BXkk4yy/YzD4yOGZFXZcNp915/TW5bRd
//AKPHUHxJasPiyEFqlNKBR2DSD+LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0
Qi49OykUCfNZeQlEz7UNNj9RGps/50+CNwIDAQAB
-----END RSA PUBLIC KEY-----

+ 0
- 9
fixtures/pub-rsa-2048.spki.pem View File

@@ -1,9 +0,0 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm2ttVBxPlWw06ZmGBWVD
lfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJefLukC+xu0LBKylYojT5vTkxaOhxeSYo
31syu4WhxbkTBLICOFcCGMob6pSQ38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP
7s9m8Yk9trkpEqjskocn2BOnTB57qAZM6+I70on0/iDZm7+jcqOPgADAmbWHhy67
BXkk4yy/YzD4yOGZFXZcNp915/TW5bRd//AKPHUHxJasPiyEFqlNKBR2DSD+LbX5
eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0Qi49OykUCfNZeQlEz7UNNj9RGps/50+C
NwIDAQAB
-----END PUBLIC KEY-----

+ 0
- 1
fixtures/pub-rsa-2048.ssh.pub View File

@@ -1 +0,0 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCba21UHE+VbDTpmYYFZUOV+OQ8AngOCdjROsPC0KiEfMvEaEM3NQl58u6QL7G7QsErKViiNPm9OTFo6HF5JijfWzK7haHFuRMEsgI4VwIYyhvqlJDfw/wt0AiVvSmoMfEQn1p1aiaO4V/RJSE3Vw/uz2bxiT22uSkSqOyShyfYE6dMHnuoBkzr4jvSifT+INmbv6Nyo4+AAMCZtYeHLrsFeSTjLL9jMPjI4ZkVdlw2n3Xn9NbltF3/8Ao8dQfElqw+LIQWqU0oFHYNIP4ttfl5ObMKHaKSvBMyNruZR0El/ZsrcHLkAHRCLj07KRQJ81l5CUTPtQ02P1Eamz/nT4I3 rsa@localhost

+ 0
- 3
index.js View File

@@ -1,3 +0,0 @@
'use strict';

module.exports = require('./lib/keypairs.js');

+ 42
- 0
keypairs.js View File

@@ -0,0 +1,42 @@
'use strict';

var Eckles = require('eckles');
var Rasha = require('rasha');
var Keypairs = {};

/*global Promise*/

Keypairs.generate = function (opts) {
opts = opts || {};
var kty = opts.kty || opts.type;
if ('RSA' === kty) {
return Rasha.generate(opts);
}
return Eckles.generate(opts);
};

Keypairs.import = function (opts) {
return Eckles.import(opts.pem).catch(function () {
return Rasha.import(opts.pem);
});
};

Keypairs.export = function (opts) {
return Promise.resolve().then(function () {
if ('RSA' === opts.jwk.kty) {
return Rasha.export(opts);
} else {
return Eckles.export(opts);
}
});
};

Keypairs.thumbprint = function (opts) {
return Promise.resolve().then(function () {
if ('RSA' === opts.jwk.kty) {
return Rasha.thumbprint(opts);
} else {
return Eckles.thumbprint(opts);
}
});
};

+ 0
- 111
lib/asn1-packer.js View File

@@ -1,111 +0,0 @@
'use strict';

var ASN1 = module.exports;
var Enc = require('./encoding.js');

//
// Packer
//

// Almost every ASN.1 type that's important for CSR
// can be represented generically with only a few rules.
ASN1.Any = function (/*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;

// 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;
};

// 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 ASN1.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 ASN1.Any('03', '00' + str);
};

ASN1._pack = function (arr) {
var 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 {
throw new Error("unexpected array");
}
if ('03' === typ) {
return ASN1.BitStr(str);
} else if ('02' === typ) {
return ASN1.UInt(str);
} else {
return ASN1.Any(typ, str);
}
};
ASN1.pack = function (arr) {
return Enc.hexToBuf(ASN1._pack(arr));
};

Enc.bufToBase64 = function (u8) {
var bin = '';
u8.forEach(function (i) {
bin += String.fromCharCode(i);
});
return Buffer.from(bin, 'binary').toString('base64');
//return btoa(bin);
};

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.numToHex = function (d) {
d = d.toString(16);
if (d.length % 2) {
return '0' + d;
}
return d;
};

+ 0
- 147
lib/asn1-parser.js View File

@@ -1,147 +0,0 @@
// Copyright 2016-2018 AJ ONeal. All rights reserved
// https://git.coolaj86.com/coolaj86/asn1-parser.js
/* 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/. */
;(function (exports) {
'use strict';

if (!exports.ASN1) { exports.ASN1 = {}; }
if (!exports.Enc) { exports.Enc = {}; }
if (!exports.PEM) { exports.PEM = {}; }

var ASN1 = exports.ASN1;
var Enc = require('./encoding.js');
var PEM = exports.PEM;

//
// Parser
//

ASN1.ELOOPN = 20; // I've seen 9 max in https certificates
ASN1.ELOOP = "uASN1.js Error: iterated over " + ASN1.ELOOPN + "+ elements (probably a malformed file)";
ASN1.EDEEPN = 60; // I've seen 29 deep in https certificates
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.parse = function parseAsn1Helper(buf) {
//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 (-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._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;
};

// don't replace the full parseBlock, if it exists
PEM.parseBlock = PEM.parseBlock || function (str) {
var b64 = str.split(/\n/).filter(function (line) {
return !/-----/.test(line);
}).join('');
var der = Enc.base64ToBuf(b64);
return { bytes: der };
};

Enc.base64ToBuf = function (b64) {
return Buffer.from(b64, 'base64');
};
Enc.binToBuf = function (bin) {
return Buffer.from(bin, 'binary');
};
Enc.bufToHex = function (u8) {
return Buffer.from(u8).toString('hex');
};
Enc.numToHex = function (d) {
d = d.toString(16);
if (d.length % 2) {
return '0' + d;
}
return d;
};

}('undefined' !== typeof window ? window : module.exports));

+ 0
- 3
lib/crypto.js View File

@@ -1,3 +0,0 @@
'use strict';

var wrap = module.exports;

+ 0
- 63
lib/ec.js View File

@@ -1,63 +0,0 @@
'use strict';

var x509 = module.exports;

var ASN1 = require('./asn1-packer.js');
var Enc = require('./encoding.js');

// 1.2.840.10045.3.1.7
// prime256v1 (ANSI X9.62 named elliptic curve)
var OBJ_ID_EC_256 = '06 08 2A8648CE3D030107'.replace(/\s+/g, '').toLowerCase();
// 1.3.132.0.34
// secp384r1 (SECG (Certicom) named elliptic curve)
var OBJ_ID_EC_384 = '06 05 2B81040022'.replace(/\s+/g, '').toLowerCase();
// 1.2.840.10045.2.1
// ecPublicKey (ANSI X9.62 public key type)
var OBJ_ID_EC_PUB = '06 07 2A8648CE3D0201'.replace(/\s+/g, '').toLowerCase();

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_256 : OBJ_ID_EC_384;
return Enc.hexToUint8(
ASN1('30'
, ASN1.UInt('01')
, ASN1('04', d)
, ASN1('A0', objId)
, ASN1('A1', ASN1.BitStr('04' + x + y)))
);
};
x509.packPkcs8 = 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_256 : OBJ_ID_EC_384;
return Enc.hexToUint8(
ASN1('30'
, ASN1.UInt('00')
, ASN1('30'
, OBJ_ID_EC_PUB
, objId
)
, ASN1('04'
, ASN1('30'
, ASN1.UInt('01')
, ASN1('04', d)
, ASN1('A1', ASN1.BitStr('04' + x + y)))))
);
};
x509.packSpki = function (jwk) {
var x = Enc.base64ToHex(jwk.x);
var y = Enc.base64ToHex(jwk.y);
var objId = ('P-256' === jwk.crv) ? OBJ_ID_EC_256 : OBJ_ID_EC_384;
return Enc.hexToUint8(
ASN1('30'
, ASN1('30'
, OBJ_ID_EC_PUB
, objId
)
, ASN1.BitStr('04' + x + y))
);
};
x509.packPkix = x509.packSpki;

+ 0
- 493
lib/eckles.js View File

@@ -1,493 +0,0 @@
'use strict';

var EC = module.exports;

var Enc = require('./encoding.js');
var ASN1;
var PEM = require('./pem.js');

// 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();

// 19 e c d s a - s h a 2 - n i s t p 2 5 6
var SSH_EC_P256 = '00000013 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 36'
.replace(/\s+/g, '').toLowerCase();

// 19 e c d s a - s h a 2 - n i s t p 3 8 4
var SSH_EC_P384 = '00000013 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34'
.replace(/\s+/g, '').toLowerCase();

// The one good thing that came from the b***kchain hysteria: good EC documentation
// https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/

EC.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
, crv: jwk.crv
, d: Enc.bufToUrlBase64(d)
//, dh: Enc.bufToHex(d)
, x: Enc.bufToUrlBase64(x)
//, xh: Enc.bufToHex(x)
, y: Enc.bufToUrlBase64(y)
//, yh: Enc.bufToHex(y)
};
};

EC.parsePkcs8 = 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;
}

//console.log(index, u8.slice(index));
if (0x04 !== u8[index]) {
//console.log(jwk);
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
, crv: jwk.crv
, d: Enc.bufToUrlBase64(d)
//, dh: Enc.bufToHex(d)
, x: Enc.bufToUrlBase64(x)
//, xh: Enc.bufToHex(x)
, y: Enc.bufToUrlBase64(y)
//, yh: Enc.bufToHex(y)
};
};

EC.parseSpki = function parsePem(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
, crv: jwk.crv
, x: Enc.bufToUrlBase64(x)
//, xh: Enc.bufToHex(x)
, y: Enc.bufToUrlBase64(y)
//, yh: Enc.bufToHex(y)
};
};
EC.parsePkix = EC.parseSpki;

EC.parseSsh = function (pem) {
var jwk = { kty: 'EC', crv: null, x: null, y: null };
var b64 = pem.split(/\s+/g)[1];
var buf = Buffer.from(b64, 'base64');
var hex = Enc.bufToHex(buf);
var index = 40;
var len;
if (0 === hex.indexOf(SSH_EC_P256)) {
jwk.crv = 'P-256';
len = 32;
} else if (0 === hex.indexOf(SSH_EC_P384)) {
jwk.crv = 'P-384';
len = 48;
}
var x = buf.slice(index, index + len);
var y = buf.slice(index + len, index + len + len);
jwk.x = Enc.bufToUrlBase64(x);
jwk.y = Enc.bufToUrlBase64(y);
return jwk;
};

/*global Promise*/
EC.generate = function (opts) {
return Promise.resolve().then(function () {
var typ = 'ec';
var format = opts.format;
var encoding = opts.encoding;
var priv;
var pub = 'spki';

if (!format) {
format = 'jwk';
}
if (-1 !== [ 'spki', 'pkcs8', 'ssh' ].indexOf(format)) {
format = 'pkcs8';
}

if ('pem' === format) {
format = 'sec1';
encoding = 'pem';
} else if ('der' === format) {
format = 'sec1';
encoding = 'der';
}

if ('jwk' === format || 'json' === format) {
format = 'jwk';
encoding = 'json';
} else {
priv = format;
}

if (!encoding) {
encoding = 'pem';
}

if (priv) {
priv = { type: priv, format: encoding };
pub = { type: pub, format: encoding };
} else {
// jwk
priv = { type: 'sec1', format: 'pem' };
pub = { type: 'spki', format: 'pem' };
}

return new Promise(function (resolve, reject) {
return require('crypto').generateKeyPair(typ, {
namedCurve: opts.crv || opts.namedCurve || 'P-256'
, privateKeyEncoding: priv
, publicKeyEncoding: pub
}, function (err, pubkey, privkey) {
if (err) { reject(err); }
resolve({
private: privkey
, public: pubkey
});
});
}).then(function (keypair) {
if ('jwk' === format) {
return {
private: EC.importSync({ pem: keypair.private, format: priv.type })
, public: EC.importSync({ pem: keypair.public, format: pub.type, public: true })
};
}

if ('ssh' !== opts.format) {
return keypair;
}

return {
private: keypair.private
, public: EC.exportSync({ jwk: EC.importSync({
pem: keypair.public, format: format, public: true
}), format: opts.format, public: true })
};
});
});
};

EC.importSync = function importEcSync(opts) {
if (!opts || !opts.pem || 'string' !== typeof opts.pem) {
throw new Error("must pass { pem: pem } as a string");
}
if (0 === opts.pem.indexOf('ecdsa-sha2-')) {
return EC.parseSsh(opts.pem);
}
var pem = opts.pem;
var u8 = PEM.parseBlock(pem).bytes;
var hex = Enc.bufToHex(u8);
var jwk = { kty: 'EC', crv: null, x: null, y: null };

//console.log();
if (-1 !== hex.indexOf(OBJ_ID_EC)) {
jwk.crv = "P-256";

// PKCS8
if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) {
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
jwk = EC.parsePkcs8(u8, jwk);
// EC-only
} else if (0x02 === u8[2] && 0x04 === u8[5] && 0xA0 === u8[39]) {
//console.log("EC---", u8[2].toString(16), u8[5].toString(16), u8[39].toString(16));
jwk = EC.parseSec1(u8, jwk);
// SPKI/PKIK (Public)
} else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) {
//console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
jwk = EC.parseSpki(u8, jwk);
// Error
} else {
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
//console.log("EC---", u8[2].toString(16), u8[5].toString(16), u8[39].toString(16));
//console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
throw new Error("unrecognized key format");
}
} else if (-1 !== hex.indexOf(OBJ_ID_EC_384)) {
jwk.crv = "P-384";

// PKCS8
if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) {
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
jwk = EC.parsePkcs8(u8, jwk);
// EC-only
} else if (0x02 === u8[3] && 0x04 === u8[6] && 0xA0 === u8[56]) {
//console.log("EC---", u8[3].toString(16), u8[6].toString(16), u8[56].toString(16));
jwk = EC.parseSec1(u8, jwk);
// SPKI/PKIK (Public)
} else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) {
//console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
jwk = EC.parseSpki(u8, jwk);
// Error
} else {
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
//console.log("EC---", u8[3].toString(16), u8[6].toString(16), u8[56].toString(16));
//console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
throw new Error("unrecognized key format");
}
} else {
throw new Error("Supported key types are P-256 and P-384");
}
if (opts.public) {
if (true !== opts.public) {
throw new Error("options.public must be either `true` or `false` not ("
+ typeof opts.public + ") '" + opts.public + "'");
}
delete jwk.d;
}
return jwk;
};
EC.parse = function parseEc(opts) {
return Promise.resolve().then(function () {
return EC.importSync(opts);
});
};
EC.toJwk = EC.import = EC.parse;

EC.exportSync = function (opts) {
if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) {
throw new Error("must pass { jwk: jwk } as a JSON object");
}
var jwk = JSON.parse(JSON.stringify(opts.jwk));
var format = opts.format;
if (opts.public || -1 !== [ 'spki', 'pkix', 'ssh', 'rfc4716' ].indexOf(format)) {
jwk.d = null;
}
if ('EC' !== jwk.kty) {
throw new Error("options.jwk.kty must be 'EC' for EC keys");
}
if (!jwk.d) {
if (!format || -1 !== [ 'spki', 'pkix' ].indexOf(format)) {
format = 'spki';
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
format = 'ssh';
} else {
throw new Error("options.format must be 'spki' or 'ssh' for public EC keys, not ("
+ typeof format + ") " + format);
}
} else {
if (!format || 'sec1' === format) {
format = 'sec1';
} else if ('pkcs8' !== format) {
throw new Error("options.format must be 'sec1' or 'pkcs8' for private EC keys, not '" + format + "'");
}
}
if (-1 === [ 'P-256', 'P-384' ].indexOf(jwk.crv)) {
throw new Error("options.jwk.crv must be either P-256 or P-384 for EC keys, not '" + jwk.crv + "'");
}
if (!jwk.y) {
throw new Error("options.jwk.y must be a urlsafe base64-encoded either P-256 or P-384");
}

if ('sec1' === format) {
return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: EC.packSec1(jwk) });
} else if ('pkcs8' === format) {
return PEM.packBlock({ type: "PRIVATE KEY", bytes: EC.packPkcs8(jwk) });
} else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) {
return PEM.packBlock({ type: "PUBLIC KEY", bytes: EC.packSpki(jwk) });
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
return EC.packSsh(jwk);
} else {
throw new Error("Sanity Error: reached unreachable code block with format: " + format);
}
};
EC.pack = function (opts) {
return Promise.resolve().then(function () {
return EC.exportSync(opts);
});
};

EC.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.hexToUint8(
ASN1('30'
, ASN1.UInt('01')
, ASN1('04', d)
, ASN1('A0', objId)
, ASN1('A1', ASN1.BitStr('04' + x + y)))
);
};
EC.packPkcs8 = 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.hexToUint8(
ASN1('30'
, ASN1.UInt('00')
, ASN1('30'
, OBJ_ID_EC_PUB
, objId
)
, ASN1('04'
, ASN1('30'
, ASN1.UInt('01')
, ASN1('04', d)
, ASN1('A1', ASN1.BitStr('04' + x + y)))))
);
};
EC.packSpki = 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.hexToUint8(
ASN1('30'
, ASN1('30'
, OBJ_ID_EC_PUB
, objId
)
, ASN1.BitStr('04' + x + y))
);
};
EC.packPkix = EC.packSpki;
EC.packSsh = function (jwk) {
// Custom SSH format
var typ = 'ecdsa-sha2-nistp256';
var a = '32 35 36';
var b = '41';
var comment = jwk.crv + '@localhost';
if ('P-256' !== jwk.crv) {
typ = 'ecdsa-sha2-nistp384';
a = '33 38 34';
b = '61';
}
var x = Enc.base64ToHex(jwk.x);
var y = Enc.base64ToHex(jwk.y);
var ssh = Enc.hexToUint8(
('00 00 00 13 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70'
+ a + '00 00 00 08 6e 69 73 74 70' + a + '00 00 00' + b
+ '04' + x + y).replace(/\s+/g, '').toLowerCase()
);

return typ + ' ' + Enc.bufToBase64(ssh) + ' ' + comment;
};

//
// A dumbed-down, minimal ASN.1 packer
//

// Almost every ASN.1 type that's important for CSR
// can be represented generically with only a few rules.
ASN1 = function ASN1(/*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;

// 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;
};

// 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 ASN1('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 ASN1('03', '00' + str);
};

EC.toPem = EC.export = EC.pack;

+ 0
- 42
lib/encoding.js View File

@@ -1,42 +0,0 @@
'use strict';

var Enc = module.exports;

Enc.base64ToBuf = function (str) {
// node handles both base64 and urlBase64 equally
return Buffer.from(str, 'base64');
};

Enc.base64ToHex = function (b64) {
return Enc.bufToHex(Enc.base64ToBuf(b64));
};

Enc.bufToBase64 = function (u8) {
// Ensure a node buffer, even if TypedArray
return Buffer.from(u8).toString('base64');
};

Enc.bufToHex = function (u8) {
// Ensure a node buffer, even if TypedArray
return Buffer.from(u8).toString('hex');
};

Enc.bufToUrlBase64 = function (u8) {
return Enc.bufToBase64(u8)
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
};

Enc.hexToUint8 = function (hex) {
// TODO: I don't remember why I chose Uint8Array over Buffer...
var buf = Buffer.from(hex, 'hex');
var ab = buf.buffer.slice(buf.offset, buf.offset + buf.byteLength);
return new Uint8Array(ab);
};

Enc.numToHex = function (d) {
d = d.toString(16);
if (d.length % 2) {
return '0' + d;
}
return d;
};

+ 0
- 143
lib/keypairs.js View File

@@ -1,143 +0,0 @@
'use strict';

/*global Promise*/
var keypairs = module.exports;

var PEM = require('./pem-parser.js');
PEM.packBlock = require('./pem-packer.js').packBlock;

var crypto = require('./crypto.js');
var Enc = require('./encoding.js');

var ASN1 = require('./asn1-parser.js');
ASN1.pack = require('./asn1-packer.js').pack;

var x509 = require('./x509-parser.js');
//x509 = require('./x509-packer.js').pack;

var SSH = require('./ssh-parser.js');
SSH.pack = require('./ssh-packer.js').pack;

// sign, signJws, signJwt
/*
var JWS = require('./jws.js');
var JWT = require('./jwt.js');
*/

keypairs.signJws = function (opts) {
opts = JSON.stringify(JSON.parse(opts));
if (!opts.header) { opts.header = {}; }
if (!opts.protected) { opts.protected = {}; }
if (!opts.payload) { opts.payload = {}; }
var protect = Enc.binToBase64(JSON.stringify(opts.protected));
var payload = Enc.binToBase64(JSON.stringify(opts.payload));
if (!opts.jwt) { opts.jwt = keypairs.import(opts).jwt; }
opts.header.typ = 'JWT';
opts.header.alg = ('RSA' === opts.jwk) ? 'RS256' : 'ES256';
// key, jwk, pem, der
return crypto.sign(opts, Enc.binToBuf(protect + '.' + payload), 'SHA256').then(function (sig) {
return {
header: opts.header
, protected: protect
, payload: payload
, signature: sig
};
});
};

keypairs.signJwt = function (opts) {
opts = JSON.stringify(JSON.parse(opts));
if (!opts.header) { opts.header = {}; }
if (!opts.payload) { opts.payload = {}; }
var protect = Enc.binToBase64(JSON.stringify(opts.header)) + '.'
+ Enc.binToBase64(JSON.stringify(opts.payload));
if (!opts.jwt) { opts.jwt = keypairs.import(opts).jwt; }
opts.header.alg = ('RSA' === opts.jwk) ? 'RS256' : 'ES256';
// key, jwk, pem, der
return crypto.sign(opts, Enc.binToBuf(protect), 'SHA256').then(function (sig) {
return protect + '.' + sig;
});
};

keypairs.import = function (opts) {
return Promise.resolve().then(function () {
var jwk = opts.jwk;
var pem;
var der;
var typ;

if (opts.pem) {
pem = PEM.parseBlock(opts.pem);
if ('OPENSSH PRIVATE KEY' === pem.type) {
jwk = SSH.parse(pem);
} else {
der = pem.bytes;
jwk = x509.parse({ der: der });
}
}
if (opts.ssh) {
jwk = SSH.parse(opts.ssh);
}
// TODO re-export to PKCS8 just because
if (jwk && !pem) {
// Both RSA and EC use 'd' as part of the private key
if (jwk.d) {
typ = 'PRIVATE KEY';
der = x509.pack({ jwk: jwk, format: 'pkcs8', encoding: 'pem' });
} else {
typ = 'PUBLIC KEY';
der = x509.pack({ jwk: jwk, format: 'spki', encoding: 'pem' });
}
pem = PEM.packBlock({ type: typ, bytes: der });
}

return { pem: pem, jwk: jwk };
});
};

keypairs.export = function (opts) {
// { pem, jwk, format, encoding }
var format = opts.format;
var encoding = opts.encoding;
var jwk = opts.jwk;
var pem = opts.pem;
var der = opts.der;
var pub = opts.public;

if (opts.key) {
if ('string' === typeof opts.key) {
pem = opts.key;
} else if (opts.key.d) {
jwk = opts.key;
} else if (opts.key.length) {
der = opts.der;
} else {
throw new Error("'key' must be of type 'string' (PEM), 'object' (JWK), Buffer, or Array (DER)");
}
}
if (!format) { format = 'jwk'; }

if (!jwk) {
jwk = keypairs.import({ pem: pem }).jwk;
}
if (pub) {
if ('RSA' === jwk.kty) {
jwk = { kty: jwk.kty, n: jwk.n, e: jwk.e };
} else {
jwk = { kty: jwk.kty, x: jwk.x, y: jwk.y };
}
}
if ('jwk' === format) {
if (encoding && 'json' !== encoding) {
throw new Error("'encoding' must be 'json' for 'jwk'");
}
return jwk;
}

if ('openssh' === format || 'ssh' === format) {
// TODO if ('ssh' === format) { format = 'pkcs8'; }
// TODO 'ssh2' public key is a special variant of pkcs8
return SSH.pack({ jwk: jwk, public: opts.public });
}
return x509.pack({ jwk: jwk, format: opts.format, encoding: opts.encoding, public: opts.public });
};

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

@@ -1,14 +0,0 @@
'use strict';

var PEM = module.exports;
var Enc = require('./encoding.js');

PEM.packBlock = function (opts) {
// TODO allow for headers?
var n = opts.lineLength || 64;
var re = new RegExp('.{1,' + n + '}', 'g');
return '-----BEGIN ' + opts.type + '-----\n'
+ Enc.bufToBase64(opts.bytes).match(re).join('\n') + '\n'
+ '-----END ' + opts.type + '-----'
;
};

+ 0
- 21
lib/pem-parser.js View File

@@ -1,21 +0,0 @@
'use strict';

var PEM = module.exports;
var Enc = require('./encoding.js');

PEM.parseBlock = function pemToDer(pem) {
var lines = pem.trim().split(/\n/);
var end = lines.length - 1;
var head = lines[0].match(/-----BEGIN (.*)-----/);
var foot = lines[end].match(/-----END (.*)-----/);

if (head) {
lines = lines.slice(1, end);
head = head[1];
if (head !== foot[1]) {
throw new Error("headers and footers do not match");
}
}

return { type: head, bytes: Enc.base64ToBuf(lines.join('')) };
};

+ 0
- 28
lib/pem.js View File

@@ -1,28 +0,0 @@
'use strict';

var PEM = module.exports;
var Enc = require('./encoding.js');

PEM.parseBlock = function pemToDer(pem) {
var lines = pem.trim().split(/\n/);
var end = lines.length - 1;
var head = lines[0].match(/-----BEGIN (.*)-----/);
var foot = lines[end].match(/-----END (.*)-----/);

if (head) {
lines = lines.slice(1, end);
head = head[1];
if (head !== foot[1]) {
throw new Error("headers and footers do not match");
}
}

return { type: head, bytes: Enc.base64ToBuf(lines.join('')) };
};

PEM.packBlock = function (opts) {
return '-----BEGIN ' + opts.type + '-----\n'
+ Enc.bufToBase64(opts.bytes).match(/.{1,64}/g).join('\n') + '\n'
+ '-----END ' + opts.type + '-----'
;
};

+ 0
- 204
lib/rasha.js View File

@@ -1,204 +0,0 @@
'use strict';

var RSA = module.exports;
var SSH = require('./ssh.js');
var PEM = require('./pem.js');
var x509 = require('./x509.js');
var ASN1 = require('./asn1.js');

/*global Promise*/
RSA.generate = function (opts) {
return Promise.resolve().then(function () {
var typ = 'rsa';
var format = opts.format;
var encoding = opts.encoding;
var priv;
var pub;

if (!format) {
format = 'jwk';
}
if ('spki' === format || 'pkcs8' === format) {
format = 'pkcs8';
pub = 'spki';
}

if ('pem' === format) {
format = 'pkcs1';
encoding = 'pem';
} else if ('der' === format) {
format = 'pkcs1';
encoding = 'der';
}

if ('jwk' === format || 'json' === format) {
format = 'jwk';
encoding = 'json';
} else {
priv = format;
pub = pub || format;
}

if (!encoding) {
encoding = 'pem';
}

if (priv) {
priv = { type: priv, format: encoding };
pub = { type: pub, format: encoding };
} else {
// jwk
priv = { type: 'pkcs1', format: 'pem' };
pub = { type: 'pkcs1', format: 'pem' };
}

return new Promise(function (resolve, reject) {
return require('crypto').generateKeyPair(typ, {
modulusLength: opts.modulusLength || 2048
, publicExponent: opts.publicExponent || 0x10001
, privateKeyEncoding: priv
, publicKeyEncoding: pub
}, function (err, pubkey, privkey) {
if (err) { reject(err); }
resolve({
private: privkey
, public: pubkey
});
});
}).then(function (keypair) {
if ('jwk' !== format) {
return keypair;
}

return {
private: RSA.importSync({ pem: keypair.private, format: priv.type })
, public: RSA.importSync({ pem: keypair.public, format: pub.type, public: true })
};
});
});
};

RSA.importSync = function (opts) {
if (!opts || !opts.pem || 'string' !== typeof opts.pem) {
throw new Error("must pass { pem: pem } as a string");
}

var jwk = { kty: 'RSA', n: null, e: null };
if (0 === opts.pem.indexOf('ssh-rsa ')) {
return SSH.parse(opts.pem, jwk);
}
var pem = opts.pem;
var block = PEM.parseBlock(pem);
//var hex = toHex(u8);
var asn1 = ASN1.parse(block.bytes);

var meta = x509.guess(block.bytes, asn1);

if ('pkcs1' === meta.format) {
jwk = x509.parsePkcs1(block.bytes, asn1, jwk);
} else {
jwk = x509.parsePkcs8(block.bytes, asn1, jwk);
}

if (opts.public) {
jwk = RSA.nueter(jwk);
}
return jwk;
};
RSA.parse = function parseRsa(opts) {
// wrapped in a promise for API compatibility
// with the forthcoming browser version
// (and potential future native node capability)
return Promise.resolve().then(function () {
return RSA.importSync(opts);
});
};
RSA.toJwk = RSA.import = RSA.parse;

/*
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
*/

RSA.exportSync = function (opts) {
if (!opts || !opts.jwk || 'object' !== typeof opts.jwk) {
throw new Error("must pass { jwk: jwk }");
}
var jwk = JSON.parse(JSON.stringify(opts.jwk));
var format = opts.format;
var pub = opts.public;
if (pub || -1 !== [ 'spki', 'pkix', 'ssh', 'rfc4716' ].indexOf(format)) {
jwk = RSA.nueter(jwk);
}
if ('RSA' !== jwk.kty) {
throw new Error("options.jwk.kty must be 'RSA' for RSA keys");
}
if (!jwk.p) {
// TODO test for n and e
pub = true;
if (!format || 'pkcs1' === format) {
format = 'pkcs1';
} else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) {
format = 'spki';
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
format = 'ssh';
} else {
throw new Error("options.format must be 'spki', 'pkcs1', or 'ssh' for public RSA keys, not ("
+ typeof format + ") " + format);
}
} else {
// TODO test for all necessary keys (d, p, q ...)
if (!format || 'pkcs1' === format) {
format = 'pkcs1';
} else if ('pkcs8' !== format) {
throw new Error("options.format must be 'pkcs1' or 'pkcs8' for private RSA keys");
}
}

if ('pkcs1' === format) {
if (jwk.d) {
return PEM.packBlock({ type: "RSA PRIVATE KEY", bytes: x509.packPkcs1(jwk) });
} else {
return PEM.packBlock({ type: "RSA PUBLIC KEY", bytes: x509.packPkcs1(jwk) });
}
} else if ('pkcs8' === format) {
return PEM.packBlock({ type: "PRIVATE KEY", bytes: x509.packPkcs8(jwk) });
} else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) {
return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) });
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
return SSH.pack({ jwk: jwk, comment: opts.comment });
} else {
throw new Error("Sanity Error: reached unreachable code block with format: " + format);
}
};
RSA.pack = function (opts) {
// wrapped in a promise for API compatibility
// with the forthcoming browser version
// (and potential future native node capability)
return Promise.resolve().then(function () {
return RSA.exportSync(opts);
});
};
RSA.toPem = RSA.export = RSA.pack;

// snip the _private_ parts... hAHAHAHA!
RSA.nueter = function (jwk) {
// (snip rather than new object to keep potential extra data)
// otherwise we could just do this:
// return { kty: jwk.kty, n: jwk.n, e: jwk.e };
[ 'p', 'q', 'd', 'dp', 'dq', 'qi' ].forEach(function (key) {
if (key in jwk) { jwk[key] = undefined; }
return jwk;
});
return jwk;
};

+ 0
- 80
lib/ssh.js View File

@@ -1,80 +0,0 @@
'use strict';

var SSH = module.exports;
var Enc = require('./encoding.js');

// 7 s s h - r s a
SSH.RSA = '00000007 73 73 68 2d 72 73 61'.replace(/\s+/g, '').toLowerCase();

SSH.parse = function (pem, jwk) {

var parts = pem.split(/\s+/);
var buf = Enc.base64ToBuf(parts[1]);
var els = [];
var index = 0;
var len;
var i = 0;
var offset = (buf.byteOffset || 0);
// using dataview to be browser-compatible (I do want _some_ code reuse)
var dv = new DataView(buf.buffer.slice(offset, offset + buf.byteLength));
var el;

if (SSH.RSA !== Enc.bufToHex(buf.slice(0, SSH.RSA.length/2))) {
throw new Error("does not lead with ssh header");
}

while (index < buf.byteLength) {
i += 1;
if (i > 3) { throw new Error("15+ elements, probably not a public ssh key"); }
len = dv.getUint32(index, false);
index += 4;
el = buf.slice(index, index + len);
// remove BigUInt '00' prefix
if (0x00 === el[0]) {
el = el.slice(1);
}
els.push(el);
index += len;
}

jwk.n = Enc.bufToUrlBase64(els[2]);
jwk.e = Enc.bufToUrlBase64(els[1]);

return jwk;
};

SSH.pack = function (opts) {
var jwk = opts.jwk;
var header = 'ssh-rsa';
var comment = opts.comment || 'rsa@localhost';
var e = SSH._padHexInt(Enc.base64ToHex(jwk.e));
var n = SSH._padHexInt(Enc.base64ToHex(jwk.n));
var hex = [
SSH._numToUint32Hex(header.length)
, Enc.strToHex(header)
, SSH._numToUint32Hex(e.length/2)
, e
, SSH._numToUint32Hex(n.length/2)
, n
].join('');
return [ header, Enc.hexToBase64(hex), comment ].join(' ');
};

SSH._numToUint32Hex = function (num) {
var hex = num.toString(16);
while (hex.length < 8) {
hex = '0' + hex;
}
return hex;
};

SSH._padHexInt = function (hex) {
// BigInt is negative if the high order bit 0x80 is set,
// so ASN1, SSH, and many other formats pad with '0x00'
// to signifiy a positive number.
var i = parseInt(hex.slice(0, 2), 16);
if (0x80 & i) {
return '00' + hex;
}
return hex;
};

+ 0
- 111
lib/telemetry.js View File

@@ -1,111 +0,0 @@
'use strict';

// We believe in a proactive approach to sustainable open source.
// As part of that we make it easy for you to opt-in to following our progress
// and we also stay up-to-date on telemetry such as operating system and node
// version so that we can focus our efforts where they'll have the greatest impact.
//
// Want to learn more about our Terms, Privacy Policy, and Mission?
// Check out https://therootcompany.com/legal/

var os = require('os');
var crypto = require('crypto');
var https = require('https');
var pkg = require('../package.json');

// to help focus our efforts in the right places
var data = {
package: pkg.name
, version: pkg.version
, node: process.version
, arch: process.arch || os.arch()
, platform: process.platform || os.platform()
, release: os.release()
};

function addCommunityMember(opts) {
setTimeout(function () {
var req = https.request({
hostname: 'api.therootcompany.com'
, port: 443
, path: '/api/therootcompany.com/public/community'
, method: 'POST'
, headers: { 'Content-Type': 'application/json' }
}, function (resp) {
// let the data flow, so we can ignore it
resp.on('data', function () {});
//resp.on('data', function (chunk) { console.log(chunk.toString()); });
resp.on('error', function () { /*ignore*/ });
//resp.on('error', function (err) { console.error(err); });
});
var obj = JSON.parse(JSON.stringify(data));
obj.action = 'updates';
try {
obj.ppid = ppid(obj.action);
} catch(e) {
// ignore
//console.error(e);
}
obj.name = opts.name || undefined;
obj.address = opts.email;
obj.community = 'node.js@therootcompany.com';

req.write(JSON.stringify(obj, 2, null));
req.end();
req.on('error', function () { /*ignore*/ });
//req.on('error', function (err) { console.error(err); });
}, 50);
}

function ping(action) {