v1.0.1: 💯 ECDSA Command line tools
This commit is contained in:
commit
0b21269c95
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
node_modules
|
162
README.md
Normal file
162
README.md
Normal 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
80
bin/eckles.js
Executable 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
111
lib/telemetry.js
Normal 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
18
package-lock.json
generated
Normal 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
43
package.json
Normal 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
46
test.sh
Normal 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"
|
Loading…
x
Reference in New Issue
Block a user