v1.0.1: 💯 ECDSA Command line tools

This commit is contained in:
AJ ONeal 2018-11-24 22:50:10 -07:00
commit 0b21269c95
7 changed files with 461 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

162
README.md Normal file
View File

@ -0,0 +1,162 @@
[Eckles CLI](https://git.coolaj86.com/coolaj86/eckles-cli.js)
=========
Sponsored by [Root](https://therootcompany.com).
Built for [ACME.js](https://git.coolaj86.com/coolaj86/acme.js)
and [Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock.js)
ECDSA (elliptic curve) tools.
## Install
```bash
npm install -g eckles
```
Table of Contents
=================
* [x] Generate EC Keys
* [x] PEM to JWK
* [x] JWK to PEM
* [x] SSH "pub" format
* [ ] RSA
* **Need RSA tools?** Check out [Rasha.js](https://git.coolaj86.com/coolaj86/rasha.js)
## Generate EC (ECDSA/ECDH) Keypair
```
eckles [format] [curve|encoding]
```
#### Generate ECDSA JWK
```
eckles [jwk] [P-256|P-384]
```
```bash
# Default P-256 (prime256v1, secp256r1)
eckles jwk
# Use P-384 (secp384r1)
eckles jwk P-384
```
#### Generate ECDSA PEM
```
eckles [sec1|pkcs8|ssh] [P-256|P-384]
```
```bash
eckles sec1
eckles pkcs8 P-256
eckles ssh P-384
```
#### Generate ECDSA DER
```
eckles [sec1|pkcs8] [der]
```
```bash
eckles sec1 der > privkey.ec.sec1.der 2> pub.ec.spki.der
eckles pkcs8 der > privkey.ec.pkcs8.der 2> pub.ec.spki.der
```
## Convert ECDSA PEM to JWK
```
eckles [pemfile] [public]
```
```bash
eckles privkey.pem > privkey.jwk.json
eckles pub.pem > pub.jwk.json
eckles privkey.pem public > pub.jwk.json
```
```bash
eckles id_rsa > privkey.jwk.json
eckles id_rsa public > pub.jwk.json
eckles id_rsa.pub > pub.jwk.json
```
## Convert ECDSA JWK to PEM
```
eckles [jwk-keyfile] [format]
```
```bash
eckles privkey.jwk.json sec1 > privkey.pem
eckles privkey.jwk.json pkcs8 > privkey.pem
eckles privkey.jwk.json spki > pub.pem
eckles privkey.jwk.json ssh > id_rsa.pub
```
```bash
eckles pub.jwk.json spki > id_rsa.pub
eckles pub.jwk.json ssh > id_rsa.pub
```
## Convert ECDSA PEM to SSH
This is a two-step process, at the moment.
Only public keys are necessary, but private keys may be used.
```bash
eckles privkey.pem > privkey.jwk.json
eckles privkey.jwk.json pkcs8 > id_rsa
eckles privkey.jwk.json ssh > id_rsa.pub
```
```bash
eckles pub.pem > pub.jwk.json
eckles pub.jwk.json ssh > id_rsa.pub
```
#### Convert ECDSA SSH to PEM
This is a two-step process, at the moment.
Only public keys are necessary, but private keys may be used.
```bash
eckles id_rsa > privkey.jwk.json
eckles privkey.jwk.json sec1 > privkey.pem
eckles privkey.jwk.json pkcs8 > privkey.pem
```
```bash
eckles id_rsa.pub > pub.jwk.json
eckles privkey.jwk.json spki > pub.pem
```
Legal
-----
[Eckles CLI](https://git.coolaj86.com/coolaj86/eckles-cli.js) |
MPL-2.0 |
[Terms of Use](https://therootcompany.com/legal/#terms) |
[Privacy Policy](https://therootcompany.com/legal/#privacy)

80
bin/eckles.js Executable file
View File

@ -0,0 +1,80 @@
#!/usr/bin/env node
'use strict';
var fs = require('fs');
var Eckles = require('eckles');
var ecdsacsr = require('ecdsa-csr');
var infile = process.argv[2];
var format = process.argv[3];
var domains = process.argv[3];
var key;
function errout(err) {
console.error(err);
process.exit(1);
}
if (!infile) {
infile = 'jwk';
}
if (-1 !== [ 'jwk', 'pem', 'json', 'der', 'sec1', 'pkcs8', 'spki', 'ssh' ].indexOf(infile)) {
return Eckles.generate({
format: infile
, namedCurve: format === 'P-384' ? 'P-384' : 'P-256'
, encoding: format === 'der' ? 'der' : 'pem'
}).then(function (key) {
if ('der' === infile || 'der' === format) {
key.private = key.private.toString('binary');
key.public = key.public.toString('binary');
}
if ('jwk' === infile) {
key.private = JSON.stringify(key.private, null, 2);
key.public = JSON.stringify(key.public, null, 2);
}
console.info(key.private);
// so that the pub key can be directed separately
console.error(key.public);
process.exit(0);
}).catch(errout);
}
if ('csr' === infile) {
key = fs.readFileSync(format, 'ascii');
} else {
key = fs.readFileSync(infile, 'ascii');
}
try {
key = JSON.parse(key);
} catch(e) {
// ignore
}
if ('csr' === infile) {
return ecdsacsr({
// don't remember which it was... whatever
pem: key
, key: key
, jwk: key
, domains: domains.split(/,/)
}).then(function (csr) {
console.info(csr);
}).catch(errout);
}
console.log(typeof key, key);
if ('string' === typeof key) {
var pub = (-1 !== [ 'public', 'spki', 'pkix' ].indexOf(format));
return Eckles.import({ pem: key, public: (pub || format) }).then(function (jwk) {
console.info(JSON.stringify(jwk, null, 2));
}).catch(errout);
} else {
var pub = (-1 !== [ 'public', 'spki', 'pkix', 'ssh' ].indexOf(format));
if ('public' === format) { format = 'spki'; }
return Eckles.export({ jwk: key, format: format, public: pub }).then(function (pem) {
console.info(pem);
}).catch(errout);
}

111
lib/telemetry.js Normal file
View File

@ -0,0 +1,111 @@
'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' });
}

18
package-lock.json generated Normal file
View File

@ -0,0 +1,18 @@
{
"name": "eckles-cli",
"version": "1.3.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"ecdsa-csr": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ecdsa-csr/-/ecdsa-csr-1.1.1.tgz",
"integrity": "sha512-j0ssynuUi2ZPzkzEhUwPN5h5nOzJak7kExju29fEOVvIXvtU9o97puMNVQnEAM4uAM3u4G2Wp0YcusJKaSecCQ=="
},
"eckles": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/eckles/-/eckles-1.3.2.tgz",
"integrity": "sha512-UBpiRqM/YqpuWSyQucnwmjWvd/umb1WvH2GEkdWiDRGzU6DLsjk8kHitmud4VtHXkggZXY8Oy+2zeMlqwYfIgw=="
}
}
}

43
package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "eckles-cli",
"version": "1.0.1",
"description": "Command line ECDSA tools to generating key pairs and converting between JWK, various PEM formats, and SSH",
"homepage": "https://git.coolaj86.com/coolaj86/eckles-cli.js",
"main": "bin/eckles.js",
"bin": {
"eckles": "bin/eckles.js"
},
"files": [
"bin",
"fixtures",
"lib",
"test.sh"
],
"directories": {
"lib": "lib"
},
"scripts": {
"postinstall": "node lib/telemetry.js event:install",
"test": "bash test.sh"
},
"repository": {
"type": "git",
"url": "https://git.coolaj86.com/coolaj86/eckles-cli.js"
},
"keywords": [
"EC",
"ECDSA",
"PEM",
"JWK",
"SSH",
"tested",
"working",
"complete"
],
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "MPL-2.0",
"dependencies": {
"ecdsa-csr": "^1.1.1",
"eckles": "^1.3.2"
}
}

46
test.sh Normal file
View File

@ -0,0 +1,46 @@
#!/bin/bash
set -e
echo ""
echo "Generate"
node bin/eckles.js jwk > privkey.1.jwk.json 2> pub.1.jwk.json
node bin/eckles.js sec1 > privkey.2.sec1.pem 2> pub.2.spki.pem
node bin/eckles.js pkcs8 > privkey.3.pkcs8.pem 2> pub.3.spki.pem
node bin/eckles.js ssh > id_rsa 2> id_rsa.pub
echo "PASS"
# JWK
echo ""
echo "Read JWK"
node bin/eckles.js privkey.1.jwk.json > /dev/null
node bin/eckles.js privkey.1.jwk.json public > /dev/null
node bin/eckles.js pub.1.jwk.json > /dev/null
echo "PASS"
# SEC1 + SPKI
echo ""
echo "Read SEC1"
node bin/eckles.js privkey.2.sec1.pem > /dev/null
node bin/eckles.js privkey.2.sec1.pem public > /dev/null
node bin/eckles.js pub.2.spki.pem > /dev/null
echo "PASS"
# PKCS8 (SPKI already tested)
echo ""
echo "Read PKCS8"
node bin/eckles.js privkey.3.pkcs8.pem > /dev/null
node bin/eckles.js privkey.3.pkcs8.pem public > /dev/null
echo "PASS"
# SSH (PKCS8 + PUB)
echo ""
echo "Read SSH"
node bin/eckles.js privkey.3.pkcs8.pem > /dev/null
node bin/eckles.js id_rsa > /dev/null
node bin/eckles.js id_rsa public > /dev/null
node bin/eckles.js id_rsa.pub > /dev/null
echo "PASS"
echo ""
echo ""
echo "Passed all tests"