Browse Source

v1.0.0: just wrap Rasha and Eckles for now

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

1
.gitignore

@ -0,0 +1 @@
node_modules

139
README.md

@ -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.

20
bin/keypairs.js

@ -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);
});

134
browser.js

@ -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

13
convert-from-hex.js

@ -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]);

16
convert-to-der.js

@ -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]);

7
fixtures/privkey-ec-p256.jwk.json

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

5
fixtures/privkey-ec-p256.pkcs8.pem

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

5
fixtures/privkey-ec-p256.sec1.pem

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

7
fixtures/privkey-ec-p384.jwk.json

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

6
fixtures/privkey-ec-p384.pkcs8.pem

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

6
fixtures/privkey-ec-p384.sec1.pem

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

11
fixtures/privkey-rsa-2048.jwk.json

@ -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"
}

27
fixtures/privkey-rsa-2048.pkcs1.pem

@ -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-----

28
fixtures/privkey-rsa-2048.pkcs8.pem

@ -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-----

6
fixtures/pub-ec-p256.jwk.json

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

4
fixtures/pub-ec-p256.spki.pem

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

1
fixtures/pub-ec-p256.ssh.pub

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

6
fixtures/pub-ec-p384.jwk.json

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

5
fixtures/pub-ec-p384.spki.pem

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

1
fixtures/pub-ec-p384.ssh.pub

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

5
fixtures/pub-rsa-2048.jwk.json

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

8
fixtures/pub-rsa-2048.pkcs1.pem

@ -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-----

9
fixtures/pub-rsa-2048.spki.pem

@ -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-----

1
fixtures/pub-rsa-2048.ssh.pub

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

3
index.js

@ -1,3 +0,0 @@
'use strict';
module.exports = require('./lib/keypairs.js');

42
keypairs.js

@ -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);
}
});
};

111
lib/asn1-packer.js

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

147
lib/asn1-parser.js

@ -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));

3
lib/crypto.js

@ -1,3 +0,0 @@
'use strict';
var wrap = module.exports;

63
lib/ec.js

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

493
lib/eckles.js

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

42
lib/encoding.js

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

143
lib/keypairs.js

@ -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 });
};

14
lib/pem-packer.js

@ -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 + '-----'
;
};

21
lib/pem-parser.js

@ -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('')) };
};

28
lib/pem.js

@ -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 + '-----'
;
};

204
lib/rasha.js

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

80
lib/ssh.js

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

111
lib/telemetry.js

@ -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) {
setTimeout(function () {
var req = https.request({
hostname: 'api.therootcompany.com'
, port: 443
, path: '/api/therootcompany.com/public/ping'
, 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 = action;
try {
obj.ppid = ppid(obj.action);
} catch(e) {
// ignore
//console.error(e);
}
req.write(JSON.stringify(obj, 2, null));
req.end();
req.on('error', function (/*e*/) { /*console.error('req.error', e);*/ });
}, 50);
}
// to help identify unique installs without getting
// the personally identifiable info that we don't want
function ppid(action) {
var parts = [ action, data.package, data.version, data.node, data.arch, data.platform, data.release ];
var ifaces = os.networkInterfaces();
Object.keys(ifaces).forEach(function (ifname) {
if (/^en/.test(ifname) || /^eth/.test(ifname) || /^wl/.test(ifname)) {
if (ifaces[ifname] && ifaces[ifname].length) {
parts.push(ifaces[ifname][0].mac);
}
}
});
return crypto.createHash('sha1').update(parts.join(',')).digest('base64');
}
module.exports.ping = ping;
module.exports.joinCommunity = addCommunityMember;
if (require.main === module) {
ping('install');
//addCommunityMember({ name: "AJ ONeal", email: 'coolaj86@gmail.com' });
}

123
lib/x509-ec-parser.js

@ -1,123 +0,0 @@
'use strict';
var Enc = require('./encoding.js');
var x509 = module.exports;
// 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();
x509.parseSec1 = function parseEcOnlyPrivkey(u8, jwk) {
var index = 7;
var len = 32;
var olen = OBJ_ID_EC_256.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)
};
};
x509.parsePkcs8 = function parseEcPkcs8(u8, jwk) {
console.log("here", u8);
var index = 24 + (OBJ_ID_EC_256.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)");
}
var jwk = {};
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)
};
};
x509.parseSpki = function parsePem(u8, jwk) {
var ci = 16 + OBJ_ID_EC_256.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)
};
};
x509.parsePkix = x509.parseSpki;

14
lib/x509-packer.js

@ -1,14 +0,0 @@
'use strict';
var x509 = module.exports;
var RSA = require('./rsa.js');
var EC = require('./ec.js');
x509.pack = function (opts) {
if ('RSA' === opts.jwk.kty) {
return RSA.pack(opts);
} else {
return EC.pack(opts);
}
};

46
lib/x509-parser.js

@ -1,46 +0,0 @@
'use strict';
var x509 = module.exports;
var PEM = require('./pem-parser.js');
var EC = require('./x509-ec-parser.js');
var RSA = require('./x509-rsa-parser.js');
x509.parse = function (opts) {
console.log(opts);
var pem = opts.pem;
var der = opts.der;
if ('string' === opts.key) {
pem = opts.key;
} else if (opts.key && opts.key.length) {
der = opts.key;
}
if (pem) { pem = PEM.parseBlock(pem); }
else { pem = { bytes: der, type: '' }; }
der = pem.bytes;
var typ = pem.type;
var pub = /PUBLIC KEY/.test(typ);
var prv = /PRIVATE KEY/.test(typ);
var ec = /EC P/.test(typ);
var rsa = /RSA P/.test(typ);
// Try EC Private and Public keys
if (!rsa && !pub) {
try { return EC.parsePkcs8(der); } catch(e) { console.error(e); /*ignore*/ }
try { return EC.parseSec1(der); } catch(e) { /*ignore*/ }
} else if (!rsa && !prv) {
try { return EC.parseSpki(der); } catch(e) { /*ignore*/ }
}
// Try RSA Private and Public keys
if (!ec && !pub) {
try { return RSA.parsePkcs8(der); } catch(e) { /*ignore*/ }
try { return RSA.parsePkcs1(der); } catch(e) { /*ignore*/ }
} else if (!ec && !prv) {
try { return RSA.parseSpki(der); } catch(e) { /*ignore*/ }
try { return RSA.parsePublicPkcs1(der); } catch(e) { /*ignore*/ }
}
throw new Error("Invalid or Unsupported key:\n"
+ "Tried ASN.1 PEM/DER PKCS1, SEC1, PKCS8, and SPKI/PKIX (RSA and EC Private and Public)");
};

3
lib/x509-rsa-parser.js

@ -1,3 +0,0 @@
'use strict';
var RSA = module.exports;

153
lib/x509.js

@ -1,153 +0,0 @@
'use strict';
var x509 = module.exports;
var ASN1 = require('./asn1.js');
var Enc = require('./encoding.js');
x509.guess = function (der, asn1) {
// accepting der for compatability with other usages
var meta = { kty: 'RSA', format: 'pkcs1', public: true };
//meta.asn1 = ASN1.parse(u8);
if (asn1.children.every(function(el) {
return 0x02 === el.type;
})) {
if (2 === asn1.children.length) {
// rsa pkcs1 public
return meta;
} else if (asn1.children.length >= 9) {
// the standard allows for "otherPrimeInfos", hence at least 9
meta.public = false;
// rsa pkcs1 private
return meta;
} else {
throw new Error("not an RSA PKCS#1 public or private key (wrong number of ints)");
}
} else {
meta.format = 'pkcs8';
}
return meta;
};
x509.parsePkcs1 = function parseRsaPkcs1(buf, asn1, jwk) {
if (!asn1.children.every(function(el) {
return 0x02 === el.type;
})) {
throw new Error("not an RSA PKCS#1 public or private key (not all ints)");
}
if (2 === asn1.children.length) {
jwk.n = Enc.bufToUrlBase64(asn1.children[0].value);
jwk.e = Enc.bufToUrlBase64(asn1.children[1].value);
return jwk;
} else if (asn1.children.length >= 9) {
// the standard allows for "otherPrimeInfos", hence at least 9
jwk.n = Enc.bufToUrlBase64(asn1.children[1].value);
jwk.e = Enc.bufToUrlBase64(asn1.children[2].value);
jwk.d = Enc.bufToUrlBase64(asn1.children[3].value);
jwk.p = Enc.bufToUrlBase64(asn1.children[4].value);
jwk.q = Enc.bufToUrlBase64(asn1.children[5].value);
jwk.dp = Enc.bufToUrlBase64(asn1.children[6].value);
jwk.dq = Enc.bufToUrlBase64(asn1.children[7].value);
jwk.qi = Enc.bufToUrlBase64(asn1.children[8].value);
return jwk;
} else {
throw new Error("not an RSA PKCS#1 public or private key (wrong number of ints)");
}
};
x509.parsePkcs8 = function parseRsaPkcs8(buf, asn1, jwk) {
if (2 === asn1.children.length
&& 0x03 === asn1.children[1].type
&& 0x30 === asn1.children[1].value[0]) {
asn1 = ASN1.parse(asn1.children[1].value);
jwk.n = Enc.bufToUrlBase64(asn1.children[0].value);
jwk.e = Enc.bufToUrlBase64(asn1.children[1].value);
} else if (3 === asn1.children.length
&& 0x04 === asn1.children[2].type
&& 0x30 === asn1.children[2].children[0].type
&& 0x02 === asn1.children[2].children[0].children[0].type) {
asn1 = asn1.children[2].children[0];
jwk.n = Enc.bufToUrlBase64(asn1.children[1].value);
jwk.e = Enc.bufToUrlBase64(asn1.children[2].value);
jwk.d = Enc.bufToUrlBase64(asn1.children[3].value);
jwk.p = Enc.bufToUrlBase64(asn1.children[4].value);
jwk.q = Enc.bufToUrlBase64(asn1.children[5].value);
jwk.dp = Enc.bufToUrlBase64(asn1.children[6].value);
jwk.dq = Enc.bufToUrlBase64(asn1.children[7].value);
jwk.qi = Enc.bufToUrlBase64(asn1.children[8].value);
} else {
throw new Error("not an RSA PKCS#8 public or private key (wrong format)");
}
return jwk;
};
x509.packPkcs1 = function (jwk) {
var n = ASN1.UInt(Enc.base64ToHex(jwk.n));
var e = ASN1.UInt(Enc.base64ToHex(jwk.e));
if (!jwk.d) {
return Enc.hexToBuf(ASN1('30', n, e));
}
return Enc.hexToBuf(ASN1('30'
, ASN1.UInt('00')
, n
, e
, ASN1.UInt(Enc.base64ToHex(jwk.d))
, ASN1.UInt(Enc.base64ToHex(jwk.p))
, ASN1.UInt(Enc.base64ToHex(jwk.q))
, ASN1.UInt(Enc.base64ToHex(jwk.dp))
, ASN1.UInt(Enc.base64ToHex(jwk.dq))
, ASN1.UInt(Enc.base64ToHex(jwk.qi))
));
};
x509.packPkcs8 = function (jwk) {
if (!jwk.d) {
// Public RSA
return Enc.hexToBuf(ASN1('30'
, ASN1('30'
, ASN1('06', '2a864886f70d010101')
, ASN1('05')
)
, ASN1.BitStr(ASN1('30'
, ASN1.UInt(Enc.base64ToHex(jwk.n))
, ASN1.UInt(Enc.base64ToHex(jwk.e))
))
));
}
// Private RSA
return Enc.hexToBuf(ASN1('30'
, ASN1.UInt('00')
, ASN1('30'
, ASN1('06', '2a864886f70d010101')
, ASN1('05')
)
, ASN1('04'
, ASN1('30'
, ASN1.UInt('00')
, ASN1.UInt(Enc.base64ToHex(jwk.n))
, ASN1.UInt(Enc.base64ToHex(jwk.e))
, ASN1.UInt(Enc.base64ToHex(jwk.d))
, ASN1.UInt(Enc.base64ToHex(jwk.p))
, ASN1.UInt(Enc.base64ToHex(jwk.q))
, ASN1.UInt(Enc.base64ToHex(jwk.dp))
, ASN1.UInt(Enc.base64ToHex(jwk.dq))
, ASN1.UInt(Enc.base64ToHex(jwk.qi))
)
)
));
};
x509.packSpki = x509.packPkcs8;

18
package-lock.json

@ -0,0 +1,18 @@
{
"name": "keypairs",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"eckles": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.0.tgz",
"integrity": "sha512-Bm5dpwhsBuoCHvKCY3gAvP8XFyXH7im8uAu3szykpVNbFBdC+lOuV8vLC8fvTYRZBfFqB+k/P6ud/ZPVO2V2tA=="
},
"rasha": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.1.tgz",
"integrity": "sha512-cs4Hu/rVF3/Qucq+V7lxSz449VfHNMVXJaeajAHno9H5FC1PWlmS4NM6IAX5jPKFF0IC2rOdHdf7iNxQuIWZag=="
}
}
}

15
package.json

@ -1,8 +1,9 @@
{
"name": "keypairs",
"version": "0.0.4",
"description": "Interchangeably use RSA & ECDSA with PEM and JWK for Signing, Verifying, CSR generation and JOSE. Ugh... that was a mouthful.",
"main": "index.js",
"version": "1.0.0",
"description": "Lightweight RSA/ECDSA keypair generation and JWK <-> PEM",
"main": "keypairs.js",
"files": [],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
@ -16,9 +17,11 @@
"ECDSA",
"PEM",
"JWK",
"CSR",
"JOSE"
],
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "(MIT OR Apache-2.0)"
"license": "MPL-2.0",
"dependencies": {
"eckles": "^1.4.0",
"rasha": "^1.2.1"
}
}

23
pubkey-cli.js

@ -1,23 +0,0 @@
'use strict';
var pubkey = require('./pubkey.js');
var pubname = process.argv[2];
var fs = require('fs');
var pem = fs.readFileSync(pubname);
var key = pubkey.parsePem(pem);
if ('RSA' !== key.typ) {
throw new Error(key.typ + " not supported");
}
if (key.pub) {
var pubbuf = pubkey.readPubkey(key.der);
} else {
var pubbuf = pubkey.readPrivkey(key.der);
}
console.log(pubbuf.byteLength, pubkey.toHex(pubbuf));
var der = pubkey.toRsaPub(pubbuf);
var b64 = pubkey.toBase64(der);
var pem = pubkey.formatAsPublicPem(b64);
console.log('Pub:\n');
console.log(pem);

292
pubkey.js

@ -1,292 +0,0 @@
(function (exports) {
'use strict';
// 1.2.840.113549.1.1.1
// rsaEncryption (PKCS #1)
var OBJ_ID_RSA = '06 09 2A864886F70D010101'.replace(/\s+/g, '').toLowerCase();
// 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();
// 30 sequence
// 03 bit string
// 05 null
// 06 object id
function parsePem(pem) {
var typ;
var pub;
var crv;
var der = fromBase64(pem.split(/\n/).filter(function (line, i) {
if (0 === i) {
if (/ PUBLIC /.test(line)) {
pub = true;
} else if (/ PRIVATE /.test(line)) {
pub = false;
}
if (/ RSA /.test(line)) {
typ = 'RSA';
} else if (/ EC/.test(line)) {
typ = 'EC';
}
}
return !/---/.test(line);
}).join(''));
if (!typ || 'EC' === typ) {
var hex = toHex(der).toLowerCase();
// This is the RSA object ID
if (-1 !== hex.indexOf(OBJ_ID_RSA)) {
typ = 'RSA';
} else if (-1 !== hex.indexOf(OBJ_ID_EC)) {
typ = 'EC';
crv = 'P-256';
} else {
// TODO more than just P-256
console.warn("unsupported ec curve");
}
}
return { typ: typ, pub: pub, der: der, crv: crv };
}
function toHex(ab) {
var hex = [];
var u8 = new Uint8Array(ab);
var size = u8.byteLength;
var i;
var h;
for (i = 0; i < size; i += 1) {
h = u8[i].toString(16);
if (2 === h.length) {
hex.push(h);
} else {
hex.push('0' + h);
}
}
return hex.join('');
}
function fromHex(hex) {
if ('undefined' !== typeof Buffer) {
return Buffer.from(hex, 'hex');
}
var ab = new ArrayBuffer(hex.length/2);
var i;
var j;
ab = new Uint8Array(ab);
for (i = 0, j = 0; i < (hex.length/2); i += 1) {
ab[i] = parseInt(hex.slice(j, j+1), 16);
j += 2;
}
return ab.buffer;
}
function readEcPrivkey(der) {
return readEcPubkey(der);
}
function readEcPubkey(der) {
// the key is the last 520 bits of both the private key and the public key
// he 3 bits prior identify the key as
var x, y;
var compressed;
var keylen = 32;
var offset = 64;
var headerSize = 4;
var header = toHex(der.slice(der.byteLength - (offset + headerSize), der.byteLength - offset));
if ('03420004' !== header) {
offset = 32;
header = toHex(der.slice(der.byteLength - (offset + headerSize), der.byteLength - offset));
if ('03420002' !== header) {
throw new Error("not a valid EC P-256 key (expected 0x0342004 or 0x0342002 as pub key preamble, but found " + header + ")");
}
}
console.log('header', header);
console.log('offset', offset);
// The one good thing that came from the b***kchain hysteria: good EC documentation
// https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/
compressed = ('2' === header[header.byteLength -1]);
console.log(der.byteLength - offset, (der.byteLength - offset) + keylen);
x = der.slice(der.byteLength - offset, (der.byteLength - offset) + keylen);
if (!compressed) {
y = der.slice(der.byteLength - keylen, der.byteLength);
}
return {
x: x
, y: y || null
};
}
function readPubkey(der) {
var offset = 28 + 5; // header plus size
var ksBytes = der.slice(30, 32);
// not sure why it shows 257 instead of 256
var keysize = new DataView(ksBytes).getUint16(0, false) - 1;
var pub = der.slice(offset, offset + keysize);
return pub;
}
function readPrivkey(der) {
var offset = 7 + 5; // header plus size
var ksBytes = der.slice(9, 11);
// not sure why it shows 257 instead of 256
var keysize = new DataView(ksBytes).getUint16(0, false) - 1;
var pub = der.slice(offset, offset + keysize);
return pub;
}
// I used OpenSSL to create RSA keys with sizes 2048 and 4096.
// Then I used https://lapo.it/asn1js/ to see which bits changed.
// And I created a template from the bits that do and don't.
// No ASN.1 and X.509 parsers or generators. Yay!
var rsaAsn1Head = (
'30 82 xx 22 30 0D 06 09'
+ '2A 86 48 86 F7 0D 01 01'
+ '01 05 00 03 82 xx 0F 00'
+ '30 82 xx 0A 02 82 xx 01'
+ '00').replace(/\s+/g, '');
var rsaAsn1Foot = ('02 03 01 00 01').replace(/\s+/g, '');
function toRsaPub(pub) {
// 256 // 2048-bit
var len = '0' + (pub.byteLength / 256);
var head = rsaAsn1Head.replace(/xx/g, len);
var headSize = (rsaAsn1Head.length / 2);
var foot = rsaAsn1Foot;
var footSize = (foot.length / 2);
var size = headSize + pub.byteLength + footSize;
var der = new Uint8Array(new ArrayBuffer(size));
var i, j;
for (i = 0, j = 0; i < headSize; i += 1) {
der[i] = parseInt(head.slice(j,j+2), 16);
j += 2;
}
pub = new Uint8Array(pub);
for (i = 0; i < pub.byteLength; i += 1) {
der[headSize + i] = pub[i];
}
for (i = 0, j = 0; i < footSize; i += 1) {
der[headSize + pub.byteLength + i] = parseInt(foot.slice(j,j+2), 16);
j += 2;
}
return der.buffer;
}
function h(d) {
d = d.toString(16);
if (d.length % 2) {
return '0' + d;
}
return d;
}
// I used OpenSSL to create EC keys with the P-256 curve TODO P-384.
// Then I used https://lapo.it/asn1js/ to see which bits changed.
// And I created a template from the bits that do and don't.
// No ASN.1 and X.509 parsers or generators. Yay!
var ecP256Asn1Head = (
'30 {n}' // 0x59 = 89 bytes // sequence
+ '30 _13' // 0x13 = 19 bytes // sequence
+ '06 _07 2A 86 48 CE 3D 02 01' // 0x07 = 7 bytes // 1.2.840.10045.2.1 ecPublicKey (ANSI X9.62 public key type)
+ '06 _08 2A 86 48 CE 3D 03 01 07' // 0x08 = 8 bytes // 1.2.840.10045.3.1.7 prime256v1 (ANSI X9.62 named elliptic curve)
+ '03 {p} 00 {c} {x} {y}' // xlen+ylen+1+1 // bit string
).replace(/[\s_]+/g, '')
;
function toEcPub(x, y) {
// 256 // 2048-bit
var keylen = toHex([ x.byteLength + (y && y.byteLength || 0) ]);
var head = ecP256Asn1Head
.replace(/{n}/, h(0x13 + 2 + 2 + keylen))
.replace(/{p}/, h(2 + keylen))
.replace(/{c}/, y && '04' || '02')
.replace(/{x}/, toHex(x))
.replace(/{y}/, y && toHex(y) || '')
;
return fromHex(head);
}
function formatAsPem(str) {
var finalString = '';
while (str.length > 0) {
finalString += str.substring(0, 64) + '\n';
str = str.substring(64);
}
return finalString;
}
function formatAsPrivatePem(str, privacy, pemName) {
var pemstr = (pemName ? pemName + ' ' : '');
var privstr = (privacy ? privacy + ' ' : '');
var finalString = '-----BEGIN ' + pemstr + privstr + 'KEY-----\n';
finalString += formatAsPem(str);
finalString += '-----END ' + pemstr + privstr + 'KEY-----';
return finalString;
}
function formatAsPublicPem(str) {
var privacy = 'PUBLIC';
var pemName = '';
var pemstr = (pemName ? pemName + ' ' : '');
var privstr = (privacy ? privacy + ' ' : '');
var finalString = '-----BEGIN ' + pemstr + privstr + 'KEY-----\n';
finalString += formatAsPem(str);
finalString += '-----END ' + pemstr + privstr + 'KEY-----';
return finalString;
}
function toBase64(der) {
if ('undefined' === typeof btoa) {
return Buffer.from(der).toString('base64');
}
var chs = [];
der = new Uint8Array(der);
der.forEach(function (b) {
chs.push(String.fromCharCode(b));
});
return btoa(chs.join(''));
}
function fromBase64(b64) {
var buf;
var ab;
if ('undefined' === typeof atob) {
buf = Buffer.from(b64, 'base64');
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
}
buf = atob(b64);
ab = new ArrayBuffer(buf.length);
ab = new Uint8Array(ab);
buf.split('').forEach(function (ch, i) {
ab[i] = ch.charCodeAt(0);
});
return ab.buffer;
}
exports.parsePem = parsePem;
exports.toBase64 = toBase64;
exports.toRsaPub = toRsaPub;
exports.toEcPub = toEcPub;
exports.formatAsPublicPem = formatAsPublicPem;
exports.formatAsPrivatePem = formatAsPrivatePem;
exports.formatAsPem = formatAsPem;
exports.readPubkey = readPubkey;
exports.readEcPubkey = readEcPubkey;
exports.readEcPrivkey = readEcPrivkey;
exports.readPrivkey = readPrivkey;
exports.toHex = toHex;
exports.fromHex = fromHex;
}('undefined' !== typeof module ? module.exports: window));

597
re-sign.js

@ -1,597 +0,0 @@
'use strict';
var crypto = require('crypto');
var fs = require('fs');
var pubkey = require('./pubkey.js');
// these are static ASN.1 segments
// The head specifies that there will be 3 segments and a content length
// (those segments will be content, signature header, and signature)
var csrHead = '30 82 {0seq0len}'.replace(/\s+/g, '');
// The tail specifies the RSA256 signature header (and is followed by the signature
var csrRsaFoot =
( '30 0D'
// 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
+ '06 09 2A 86 48 86 F7 0D 01 01 0B'
+ '05 00'
+ '03 82 01 01 00' // preamble to sig
).replace(/\s+/g, '');
var csrEcFoot =
( '30 0A'
// 1.2.840.10045.4.3.2 ecdsaWithSHA256
// (ANSI X9.62 ECDSA algorithm with SHA256)
+ '06 08 2A 86 48 CE 3D 04 03 02'
+ '03 49 00'
+ '30 46'
+ '02 21 00 {s}' // 32 bytes of sig
+ '02 21 00 {r}' // 32 bytes of sig
).replace(/\s+/g, '');
var csrDomains = '82 {dlen} {domain.tld}'; // 2+n bytes (type 82?)
/*
var csrRsaContent =
'30 82 {1.1.0seqlen}' // 7+n 4 bytes, sequence
+ '02 01 00' // 3 3 bytes, int 0
+ '30 {3.2.0seqlen}' // 13+n 2 bytes, sequence
+ '31 {4.3.0setlen}' // 11+n 2 bytes, set
+ '30 {5.4.0seqlen}' // 9+n 2 bytes, sequence
+ '06 03 55 04 03' // 7+n 5 bytes, object id (commonName)
+ '0C {dlen} {domain.tld}' // 2+n 2+n bytes, utf8string
+ '30 82 {8.2.2seqlen}' // 19 4 bytes, sequence
+ '30 0D' // 15 2 bytes, sequence
+ '06 09 2A 86 48 86 F7 0D 01 01 01' // 13 11 bytes, rsaEncryption (PKCS #1)
+ '05 00' // 2 2 bytes, null (why?)
+ '03 82 {12.3.0bslen} 00' // +18+m 5 bytes, bit string [01 0F]
+ '30 82 {13.4.0seqlen}' // +13+m 4 bytes, sequence
+ '02 82 {klen} 00 {key}' // +9+m 4+1+n bytes, int (RSA Pub Key)
+ '02 03 {mod}' // +5 5 bytes, key and modules [01 00 01]
+ 'A0 {16.2.3ellen}' // 30+n 2 bytes, ?? [4B]
+ '30 {17.3.9seqlen}' // 28+n 2 bytes, sequence
+ '06 09 2A 86 48 86 F7 0D 01 09 0E' // 26+n 11 bytes, object id (extensionRequest (PKCS #9 via CRMF))
+ '31 {19.5.0setlen}' // 15+n 2 bytes, set
+ '30 {20.6.0seqlen}' // 13+n 2 bytes, sequence
+ '30 {21.7.0seqlen}' // 11+n 2 bytes, sequence
+ '06 03 55 1D 11' // 9+n 5 bytes, object id (subjectAltName (X.509 extension))
+ '04 {23.8.0octlen}' // 4+n 2 bytes, octet string
+ '30 {24.9.0seqlen}' // 2+n 2 bytes, sequence
+ '{altnames}' // n n bytes
;
*/
function privateToPub(pem) {
var pubbuf;
var pubxy;
var key = pubkey.parsePem(pem);
var pubder;
if ('RSA' === key.typ) {
if (key.pub) {
pubbuf = pubkey.readPubkey(key.der);
} else {
pubbuf = pubkey.readPrivkey(key.der);
}
pubder = pubkey.toRsaPub(pubbuf);
} else if ('EC' === key.typ) {
if (key.crv && 'P-256' !== key.crv) {
throw new Error("unsupported curve type");
}
if (key.pub) {
pubxy = pubkey.readEcPubkey(key.der);
} else {
pubxy = pubkey.readEcPrivkey(key.der);
}
pubder = pubkey.toEcPub(pubxy.x, pubxy.y);
}
//console.log(pubbuf.byteLength, pubkey.toHex(pubbuf));
var b64 = pubkey.toBase64(pubder);
return pubkey.formatAsPublicPem(b64);
}
function strToHex(str) {
return str.split('').map(function (ch) {
var h = ch.charCodeAt(0).toString(16);
if (2 === h.length) {
return h;
}
return '0' + h;
}).join('');
}
function pubToPem(pubbuf) {
var der = pubkey.toRsaPub(pubbuf);
var b64 = pubkey.toBase64(der);
return pubkey.formatAsPublicPem(b64);
}
function h(d) {
d = d.toString(16);
if (d.length % 2) {
return '0' + d;
}
return d;
}
function fromHex(hex) {
if ('undefined' !== typeof Buffer) {
return Buffer.from(hex, 'hex');
}
var ab = new ArrayBuffer(hex.length/2);
var i;
var j;
ab = new Uint8Array(ab);
for (i = 0, j = 0; i < (hex.length/2); i += 1) {
ab[i] = parseInt(hex.slice(j, j+1), 16);
j += 2;
}
return ab.buffer;
}
function createCsrBodyRsa(domains, csrpub) {
var altnames = domains.map(function (d) {
return csrDomains.replace(/{dlen}/, h(d.length)).replace(/{domain\.tld}/, strToHex(d));
}).join('').replace(/\s+/g, '');
var publen = csrpub.byteLength;
var sublen = domains[0].length;
var sanlen = (altnames.length/2);
var body = [ '30 82 {1.1.0seqlen}' // 4 bytes, sequence
.replace(/{[^}]+}/, h(
3
+ 13 + sublen
+ 38 + publen // Length for RSA-related stuff
+ 30 + sanlen
))
// #0 Total 3
, '02 01 00' // 3 bytes, int 0
// Subject
// #1 Total 2+11+n
, '30 {3.2.0seqlen}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(2+2+5+2+sublen))
, '31 {4.3.0setlen}' // 2 bytes, set
.replace(/{[^}]+}/, h(2+5+2+sublen))
, '30 {5.4.0seqlen}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(5+2+sublen))
, '06 03 55 04 03' // 5 bytes, object id (commonName)
, '0C {dlen} {domain.tld}' // 2+n bytes, utf8string
.replace(/{dlen}/, h(sublen))
.replace(/{domain\.tld}/, strToHex(domains[0]))
// Public Key
// #2 Total 4+28+n+1+5
, '30 82 {8.2.2seqlen}' // 4 bytes, sequence
.replace(/{[^}]+}/, h(2+11+2+4+1+4+4+publen+1+5))
, '30 0D' // 2 bytes, sequence
, '06 09 2A 86 48 86 F7 0D 01 01 01' // 11 bytes, rsaEncryption (PKCS #1)
, '05 00' // 2 bytes, null (why?)
, '03 82 {12.3.0bslen} 00' // 4+1 bytes, bit string [01 0F]
.replace(/{[^}]+}/, h(1+4+4+publen+1+5))
, '30 82 {13.4.0seqlen}' // 4 bytes, sequence
.replace(/{[^}]+}/, h(4+publen+1+5))
, '02 82 {klen} 00 {key}' // 4+n bytes, int (RSA Pub Key)
.replace(/{klen}/, h(publen+1))
.replace(/{key}/, pubkey.toHex(csrpub))
, '02 03 {mod}' // 5 bytes, key and modules [01 00 01]
.replace(/{mod}/, '01 00 01')
// AltNames
// #3 Total 2+28+n
, 'A0 {16.2.3ellen}' // 2 bytes, ?? [4B]
.replace(/{[^}]+}/, h(2+11+2+2+2+5+2+2+sanlen))
, '30 {17.3.9seqlen}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(11+2+2+2+5+2+2+sanlen))
, '06 09 2A 86 48 86 F7 0D 01 09 0E' // 11 bytes, object id (extensionRequest (PKCS #9 via CRMF))
, '31 {19.5.0setlen}' // 2 bytes, set
.replace(/{[^}]+}/, h(2+2+5+2+2+sanlen))
, '30 {20.6.0seqlen}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(2+5+2+2+sanlen))
, '30 {21.7.0seqlen}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(5+2+2+sanlen))
, '06 03 55 1D 11' // 5 bytes, object id (subjectAltName (X.509 extension))
, '04 {23.8.0octlen}' // 2 bytes, octet string
.replace(/{[^}]+}/, h(2+sanlen))
, '30 {24.9.0seqlen}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(sanlen))
, '{altnames}' // n (elements of sequence)
.replace(/{altnames}/, altnames)
];
body = body.join('').replace(/\s+/g, '');
return fromHex(body);
}
function createCsrBodyEc(domains, xy) {
var altnames = domains.map(function (d) {
return csrDomains.replace(/{dlen}/, h(d.length)).replace(/{domain\.tld}/, strToHex(d));
}).join('').replace(/\s+/g, '');
var sublen = domains[0].length;
var sanlen = (altnames.length/2);
var publen = xy.x.byteLength;
var compression = '04';
var hxy = '';
// 04 == x+y, 02 == x-only
if (xy.y) {
publen += xy.y.byteLength;
} else {
// Note: I don't intend to support compression - it isn't used by most
// libraries and it requir more dependencies for bigint ops to deflate.
// This is more just a placeholder. It won't work right now anyway
// because compression requires an exta bit stored (odd vs even), which
// I haven't learned yet, and I'm not sure if it's allowed at all
compression = '02';
}
hxy += pubkey.toHex(xy.x);
if (xy.y) {
hxy += pubkey.toHex(xy.y);
}
console.log('hxy:', hxy);
var body = [ '30 81 {+85+n}' // 4 bytes, sequence
.replace(/{[^}]+}/, h(
3
+ 13 + sublen
+ 27 + publen // Length for EC-related P-256 stuff
+ 30 + sanlen
))
// #0 Total 3
, '02 01 00' // 3 bytes, int 0
// Subject
// #1 Total 2+11+n
, '30 {3.2.0seqlen}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(2+2+5+2+sublen))
, '31 {4.3.0setlen}' // 2 bytes, set
.replace(/{[^}]+}/, h(2+5+2+sublen))
, '30 {5.4.0seqlen}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(5+2+sublen))
, '06 03 55 04 03' // 5 bytes, object id (commonName)
, '0C {dlen} {domain.tld}' // 2+n bytes, utf8string
.replace(/{dlen}/, h(sublen))
.replace(/{domain\.tld}/, strToHex(domains[0]))
// P-256 Public Key
// #2 Total 2+25+xy
, '30 {+25+xy}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(2+9+10+3+1+publen))
, '30 13' // 2 bytes, sequence
// 1.2.840.10045.2.1 ecPublicKey
// (ANSI X9.62 public key type)
, '06 07 2A 86 48 CE 3D 02 01' // 9 bytes, object id
// 1.2.840.10045.3.1.7 prime256v1
// (ANSI X9.62 named elliptic curve)
, '06 08 2A 86 48 CE 3D 03 01 07' // 10 bytes, object id
, '03 {xylen} 00 {xy}' // 3+1+n bytes
.replace(/{xylen}/, h(publen+2))
.replace(/{xy}/, compression + hxy)
// Altnames
// #3 Total 2+28+n
, 'A0 {+28}' // 2 bytes, ?? [4B]
.replace(/{[^}]+}/, h(2+11+2+2+2+5+2+2+sanlen))
, '30 {+26}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(11+2+2+2+5+2+2+sanlen))
// (extensionRequest (PKCS #9 via CRMF))
, '06 09 2A 86 48 86 F7 0D 01 09 0E' // 11 bytes, object id
, '31 {+13}' // 2 bytes, set
.replace(/{[^}]+}/, h(2+2+5+2+2+sanlen))
, '30 {+11}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(2+5+2+2+sanlen))
, '30 {+9}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(5+2+2+sanlen))
// (subjectAltName (X.509 extension))
, '06 03 55 1D 11' // 5 bytes, object id
, '04 {+2}' // 2 bytes, octet string
.replace(/{[^}]+}/, h(2+sanlen))
, '30 {+n}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(sanlen))
, '{altnames}' // n (elements of sequence)
.replace(/{altnames}/, altnames)
];
body = body.join('').replace(/\s+/g, '');
return fromHex(body);
}
// https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
function signEc(keypem, ab) {
var sign = crypto.createSign('SHA256');
sign.write(new Uint8Array(ab));
sign.end();
var sig = sign.sign(keypem);
console.log("");
console.log("SIGNING!!");
console.log('csr body:', pubkey.toHex(new Uint8Array(ab)));
console.log('ec sig:', pubkey.toHex(sig));
console.log("");
sig = new Uint8Array(sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength));
var rlen = new Uint8Array(sig)[3];
var r = sig.slice(4, 4 + rlen);
var slen = sig.slice[5 + rlen];
var s = sig.slice(6 + rlen, 6 + rlen + slen);
// not sure what this is all about...
while(r[0] === 0) {
r = r.slice(1);
}
while(s[0] === 0) {
s = s.slice(1);
}
return { r: r, s: s };
}
function createRsaCsr(domains, keypem, csrpub) {
// TODO get pub from priv
var body = createCsrBodyRsa(domains, csrpub);
var sign = crypto.createSign('SHA256');
sign.write(new Uint8Array(body));
sign.end();
var sig = sign.sign(keypem);
var len = body.byteLength + (csrRsaFoot.length/2) + sig.byteLength;
console.log('headlen', h(len));
var head = csrHead.replace(/{[^}]+}/, h(len));
var ab = new Uint8Array(new ArrayBuffer(4 + len));
var i = 0;
fromHex(head).forEach(function (b) {
ab[i] = b;
i += 1;
});
body.forEach(function (b) {
ab[i] = b;
i += 1;
});
fromHex(csrRsaFoot).forEach(function (b) {
ab[i] = b;
i += 1;
});
new Uint8Array(sig).forEach(function (b) {
ab[i] = b;
i += 1;
});
var pem = pubkey.formatAsPem(pubkey.toBase64(ab));
return pem;
}
function createEcCsr(domains, keypem, pubj) {
// TODO get pub from priv
console.log('[TRACE] createEcCsr');
var body = createCsrBodyEc(domains, pubj);
console.log('csr body:', pubkey.toHex(body))
var sig = signEc(keypem, body);
//var siglen = sig.s.byteLength + sig.r.byteLength;
console.log('r:', pubkey.toHex(sig.r));
console.log('s:', pubkey.toHex(sig.s));
// XXX TODO this is junk, not real
var foot = csrEcFoot
.replace(/{s}/, pubkey.toHex(sig.s))
.replace(/{r}/, pubkey.toHex(sig.r))
;
var len = body.byteLength + (foot.length/2) + sig.r.byteLength + sig.s.byteLength;
console.log('headlen', h(len));
var head = csrHead.replace(/{[^}]+}/, h(len));
var ab = new Uint8Array(new ArrayBuffer(4 + len));
var i = 0;
fromHex(head).forEach(function (b) {
ab[i] = b;
i += 1;
});
body.forEach(function (b) {
ab[i] = b;
i += 1;
});
fromHex(foot).forEach(function (b) {
ab[i] = b;
i += 1;
});
new Uint8Array(sig).forEach(function (b) {
ab[i] = b;
i += 1;
});
var pem = pubkey.formatAsPem(pubkey.toBase64(ab));
return pem;
}
/*
function signagain(csrbody) {
var longEnough = csrbody.byteLength > 256;
if (!longEnough) { return false; }
var domains = [ 'example.com', 'www.example.com', 'api.example.com' ];
var body = createCsrBodyRsa(domains);
var sign = crypto.createSign('SHA256');
sign.write(new Uint8Array(body));
sign.end();
var sig2 = sign.sign(keypem);
var hexsig = pubkey.toHex(sig);
var hexsig2 = pubkey.toHex(sig2);
console.log('hexsig:');
console.log(hexsig);
if (hexsig2 !== hexsig) {
throw new Error("sigs didn't match");
}
console.log('Winner winner!', csrbody.byteLength);
console.log(pubkey.toHex(csrbody));
console.log();
console.log('Test:', body.byteLength);
console.log(pubkey.toHex(body));
console.log();
var len = body.byteLength + (csrRsaFoot.length/2) + sig.byteLength;
console.log('headlen', h(len));
var head = csrHead.replace(/{[^}]+}/, h(len));
var ab = new Uint8Array(new ArrayBuffer(4 + len));
var i = 0;
fromHex(head).forEach(function (b) {
ab[i] = b;
i += 1;
});
body.forEach(function (b) {
ab[i] = b;
i += 1;
});
fromHex(csrRsaFoot).forEach(function (b) {
ab[i] = b;
i += 1;
});
new Uint8Array(sig2).forEach(function (b) {
ab[i] = b;
i += 1;
});
console.log("Whole enchilada:", pubkey.toHex(ab.buffer) === pubkey.toHex(csrFull));
console.log(pubkey.toHex(ab.buffer));
console.log();
// subject + 2-byte altname headers + altnames themselves + public key
//var extralen = domains[0].length + (domains.length * 2) + domains.join('').length + csrpub.byteLength;
//var foot = csrRsaFoot.replace(/xxxx/g, (csrpub.byteLength + 1));
//var head = csrHead.replace(/xxxx/);
return false;
}
*/
function check(domains, keypem, csrFull) {
var pemblock = pubkey.parsePem(keypem);
if ('EC' === pemblock.typ) {
console.log("EC TIME:");
console.log(pemblock);
checkEc(domains, keypem, pemblock, csrFull);
} else if ('RSA' === pemblock.typ) {
console.log("RSA TIME!");
checkRsa(domains, keypem, pemblock, csrFull);
} else {
// TODO try
throw new Error("unknown key type");
}
}
// TODO
function checkEc(domains, keypem, pemblock, csrFull) {
var bodyend = (csrFull.byteLength - (3+2+2+32+2+32));
var sig = csrFull.slice(bodyend);
console.log();
console.log();
console.log('csr (' + csrFull.byteLength + ')');
console.log(pubkey.toHex(csrFull));
console.log();
// First 4 bytes define Segment, segment length, and content length
var csrbody = csrFull.slice(4, bodyend);
console.log();
console.log('csr body (' + csrbody.byteLength + ')');
console.log(pubkey.toHex(csrbody));
console.log();
var cnend = 21 + 2 + domains[0].length + 2 + 2 + 7 + 8;
var csrpub = csrFull.slice(cnend, cnend+2+89);
console.log('csr pub (' + csrpub.byteLength + ')');
console.log(pubkey.toHex(csrpub));
console.log();
console.log('sig (' + sig.byteLength + ')');
console.log(pubkey.toHex(sig));
console.log();
/*
var csrpem = pubToPem(csrpub);
console.log(csrpem);
console.log();
var prvpem = privateToPub(keypem);
console.log(prvpem);
console.log();
if (csrpem === prvpem) {
console.log("Public Keys Match");
} else {
throw new Error("public key read from keyfile doesn't match public key read from CSR");
}
*/
var pubj = pubkey.readEcPubkey(pemblock.der);
console.log('pubj:', pubj);
var pem = createEcCsr(domains, keypem, pubj, csrpub);
console.log('EC CSR PEM:');
console.log(pem);
return;
}
function checkRsa(domains, keypem, pubj, csrFull) {
var sigend = (csrFull.byteLength - (2048 / 8));
var sig = csrFull.slice(sigend);
console.log();
console.log();
console.log('csr (' + csrFull.byteLength + ')');
console.log(pubkey.toHex(csrFull));
console.log();
// First 4 bytes define Segment, segment length, and content length
console.log(sigend, csrRsaFoot, csrRsaFoot.length/2);
var csrbody = csrFull.slice(4, sigend - (csrRsaFoot.length/2));
console.log(pubkey.toHex(csrbody.slice(0, csrbody.byteLength - sig.byteLength)));
console.log();
console.log('csr body (' + csrbody.byteLength + ')');
console.log(pubkey.toHex(csrbody));
console.log();
var csrpub = csrFull.slice(63 + 5, 63 + 5 + 256);
console.log('csr pub (' + csrpub.byteLength + ')');
console.log(pubkey.toHex(csrpub));
console.log();
console.log('sig (' + sig.byteLength + ')');
console.log(pubkey.toHex(sig));
console.log();
var csrpem = pubToPem(csrpub);
console.log(csrpem);
console.log();
var prvpem = privateToPub(keypem);
console.log(prvpem);
console.log();
if (csrpem === prvpem) {
console.log("Public Keys Match");
} else {
throw new Error("public key read from keyfile doesn't match public key read from CSR");
}
var pem = createRsaCsr(domains, keypem, csrpub);
console.log('RSA CSR PEM:');
console.log(pem);
return;
}
var keyname = process.argv[2];
var dername = process.argv[3];
var keypem = fs.readFileSync(keyname, 'ascii');
var csrFull = fs.readFileSync(dername);
var csrFull = csrFull.buffer.slice(csrFull.byteOffset, csrFull.byteOffset + csrFull.byteLength);
console.log();
console.log("CSR");
console.log(pubkey.toHex(csrFull));
console.log();
//check([ 'whatever.net', 'api.whatever.net' ], keypem, csrFull);
check([ 'example.com', 'www.example.com', 'api.example.com' ], keypem, csrFull);
Loading…
Cancel
Save