v1.0.0: initial commit

This commit is contained in:
AJ ONeal 2018-11-18 03:13:17 -07:00
commit ef282b6dcd
6 changed files with 792 additions and 0 deletions

170
README.md Normal file
View File

@ -0,0 +1,170 @@
ECDSA-CSR.js
=========
Sponsored by [Root](https://therootcompany.com)
A focused, **zero-dependency** library that can do exactly one thing really, really well:
* Generate a Certificate Signing Requests (CSR), and sign it!
Features
========
* [x] Universal ECDSA and CSR support that Just Works™
* NIST P-256 (also known as prime256v1 or secp256r1)
* PEM, DER, ASN.1, PKCS8 (Private Key), PKIX/SPKI (Public Key)
* Common Name (CN) Subject
* Subject Alternative Names (SANs / altnames)
* [x] Zero Dependencies
* (no ASN1.js, PKI.js, forge, jrsasign - not even elliptic.js!)
* [x] Quality
* Focused
* Lightweight
* Well-Commented, Well-Documented
* Secure
* [x] Vanilla Node.js
* no school like the old school
* easy to read and understand
Usage
-----
Given an array of domains it uses the first for the Common Name (CN),
also known as Subject, and all of them as the Subject Alternative Names (SANs or altnames).
```js
'use strict';
var edsacsr = require('ecdsa-csr');
var domains = [ 'example.com', 'www.example.com', 'api.example.com' ];
return ecdsacsr({ key: key, domains: domains }).then(function (csr) {
console.log('CSR PEM:', csr);
});
```
### Options
* `key` must be a PEM or DER
* PEM may be a plain string or a Buffer*
* DER must be a Buffer*
* `domains` must be a list of strings representing domain names
* must be plain oldl utf8, not punycode
* "Buffer" can be a node Buffer, a JavaScript Uint8Array,
or a JavaScript Array which contains only numbers between 0 and 255.
Other useful options:
* `der: true` output a der instead of a PEM
* `format: string|Array|Buffer|Uint8Array` output the PEM or DER in the desired format.
### Verifying
You can double check that the CSR you get out is actually valid:
```bash
# Generate a key
openssl ecparam -genkey -name prime256v1 -noout -out ./privkey-ec-p256.pem
# Create a CSR
npx ecdsa-csr ./privkey-ec-p256.pem example.com,www.example.com > csr.pem
# Verify
openssl req -text -noout -verify -in csr.pem
```
Known Issues
------------
I've learned to be careful about talking about the future,
however, I literally just published this tonight (2018-11-17)
and there are a few things I plan to address but haven't yet:
* JWK not yet supported
* non-ascii domains, such as 例.中国 not yet supported
* total domains length over 127 characters not yet supported
New to Crypto?
--------------
Just a heads up in case you have no idea what you're doing:
First of all, [don't panic](https://coolaj86.com/articles/dont-panic.html).
Next:
* EC stands for _Elliptic Curve_.
* DSA stands for _Digital Signing Algorithm_.
* EC, ECDSA, and ECDH all belong to the same suite of tools.
* Private keys are actually keypairs (they contain the public key)
* NIST P-256, prime256v1, and secp256r1 are all aliases of the same thing
In many cases the terms get used (and misused) interchangably,
which can be confusing. You'll survive, I promise.
* PEM is just a Base64-encoded DER (think JSON as hex or base64)
* DER is an binary _object notation_ for ASN.1 (think actual stringified JSON or XML)
* ASN.1 is _object notation_ standard (think JSON, the standard)
* X.509 is a suite of schemas (think XLST or json-schema.org)
* PKCS#8, PKIK, SPKI are all X.509 schemas (think defining `firstName` vs `first_name` vs `firstname`)
Now forget about all that and just know this:
**This library solves your problem if** you need EC _something-or-other_ and CSR _something-or-other_
in order to deal with SSL certificates in an internal organization.
If that's not what you're doing, you may want HTTPS and SSL through
[Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock-express.js),
or you may be looking for something else entirely.
Goals vs Non-Goals
-----
This was built for use by [ACME.js](https://git.coolaj86.com/coolaj86/acme.js)
and [Greenlock.js](https://git.coolaj86.com/coolaj86/greenlock-express.js).
Rather than trying to make a generic implementation that works with everything under the sun,
this library is intentionally focused on around the use case of generating certificates for
ACME services (such as Let's Encrypt).
The primary goal of this project is for this code to do exactly (and all of)
what it needs to do - No more, no less.
* ECDSA support EC (named curve P-256), also known as:
* NIST P-256
* prime256v1
* secp256r1
* PEM, DER, and JWK
* Support both ASN.1 private key formats (one supported now)
* Support both ASN.1 public key formats (one supported now)
* Vanilla node.js (ECMAScript 5.1)
* No babel
* No dependencies
However, there are a few areas where I'd be willing to stretch:
* Support other universally accepted EC standards
* (the 384 variety is the only one that comes to mind)
* Support other PEM formats
* (the EC-only format comes to mind)
* Type definition files for altscript languages
It is not a goal of this project to support any EC profiles
except those that are universally supported by browsers and
are sufficiently secure (overkill is overkill).
> A little copying is better than a little dependency. - [Go Proverbs](https://go-proverbs.github.io) by Rob Pike
This code is considered small and focused enough that,
rather than making it a dependency in other small projects,
I personally just copy over the code.
Hence, all of these projects are MPL-2.0 licensed.
Legal
-----
MPL-2.0
[Terms of Use](https://therootcompany.com/legal/#terms) |
[Privacy Policy](https://therootcompany.com/legal/#privacy)

17
bin/ecdsacsr.js Executable file
View File

@ -0,0 +1,17 @@
#!/bin/env node
'use strict';
var fs = require('fs');
var ecdsacsr = require('../index.js');
var keyname = process.argv[2];
var domains = process.argv[3].split(/,/);
var keypem = fs.readFileSync(keyname, 'ascii');
ecdsacsr({ key: keypem, domains: domains }).then(function (csr) {
console.error("CN=" + domains[0]);
console.error("subjectAltName=" + domains.join(','));
console.error();
console.log(csr);
});

2
index.js Normal file
View File

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

463
lib/ecdsacsr.js Normal file
View File

@ -0,0 +1,463 @@
'use strict';
var crypto = require('crypto');
// 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();
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;
}
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 (/ EC/.test(line)) {
typ = 'EC';
}
}
return !/---/.test(line);
}).join(''));
if (!typ || 'EC' === typ) {
var hex = toHex(der).toLowerCase();
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 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 + ")");
}
}
// 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]);
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 formatAsPem(str) {
var finalString = '';
while (str.length > 0) {
finalString += str.substring(0, 64) + '\n';
str = str.substring(64);
}
return finalString;
}
function toBase64(der) {
if ('undefined' === typeof btoa) {
return Buffer.from(der).toString('base64');
}
var chs = [];
der.forEach(function (b) {
chs.push(String.fromCharCode(b));
});
return btoa(chs.join(''));
}
// 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 ES256 signature header (and is followed by the signature
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 {len} 00' // bit stream (why??)
+ '30 {rslen}' // sequence
+ '02 {rlen} {r}' // integer r
+ '02 {slen} {s}' // integer s
).replace(/\s+/g, '');
var csrDomains = '82 {dlen} {domain.tld}'; // 2+n bytes (type 82?)
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 numToHex(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 createCsrBodyEc(domains, xy) {
var altnames = domains.map(function (d) {
return csrDomains.replace(/{dlen}/, numToHex(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 += toHex(xy.x);
if (xy.y) {
hxy += toHex(xy.y);
}
var body = [ '30 81 {+85+n}' // 4 bytes, sequence
.replace(/{[^}]+}/, numToHex(
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(/{[^}]+}/, numToHex(2+2+5+2+sublen))
, '31 {4.3.0setlen}' // 2 bytes, set
.replace(/{[^}]+}/, numToHex(2+5+2+sublen))
, '30 {5.4.0seqlen}' // 2 bytes, sequence
.replace(/{[^}]+}/, numToHex(5+2+sublen))
, '06 03 55 04 03' // 5 bytes, object id (commonName)
, '0C {dlen} {domain.tld}' // 2+n bytes, utf8string
.replace(/{dlen}/, numToHex(sublen))
.replace(/{domain\.tld}/, strToHex(domains[0]))
// P-256 Public Key
// #2 Total 2+25+xy
, '30 {+25+xy}' // 2 bytes, sequence
.replace(/{[^}]+}/, numToHex(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}/, numToHex(publen+2))
.replace(/{xy}/, compression + hxy)
// Altnames
// #3 Total 2+28+n
, 'A0 {+28}' // 2 bytes, ?? [4B]
.replace(/{[^}]+}/, numToHex(2+11+2+2+2+5+2+2+sanlen))
, '30 {+26}' // 2 bytes, sequence
.replace(/{[^}]+}/, numToHex(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(/{[^}]+}/, numToHex(2+2+5+2+2+sanlen))
, '30 {+11}' // 2 bytes, sequence
.replace(/{[^}]+}/, numToHex(2+5+2+2+sanlen))
, '30 {+9}' // 2 bytes, sequence
.replace(/{[^}]+}/, numToHex(5+2+2+sanlen))
// (subjectAltName (X.509 extension))
, '06 03 55 1D 11' // 5 bytes, object id
, '04 {+2}' // 2 bytes, octet string
.replace(/{[^}]+}/, numToHex(2+sanlen))
, '30 {+n}' // 2 bytes, sequence
.replace(/{[^}]+}/, numToHex(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) {
// Signer is a stream
var sign = crypto.createSign('SHA256');
sign.write(new Uint8Array(ab));
sign.end();
// The signature is ASN1 encoded
var sig = sign.sign(keypem);
// Convert to a JavaScript ArrayBuffer just because
sig = new Uint8Array(sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength));
// The first two bytes '30 xx' signify SEQUENCE and LENGTH
// The sequence length byte will be a single byte because the signature is less that 128 bytes (0x80, 1024-bit)
// (this would not be true for P-521, but I'm not supporting that yet)
// The 3rd byte will be '02', signifying INTEGER
// The 4th byte will tell us the length of 'r' (which, on occassion, will be less than the full 255 bytes)
var rIndex = 3;
var rLen = sig[rIndex];
var rEnd = rIndex + 1 + rLen;
var sIndex = rEnd + 1;
var sLen = sig[sIndex];
var sEnd = sIndex + 1 + sLen;
var r = sig.slice(rIndex + 1, rEnd);
var s = sig.slice(sIndex + 1, sEnd); // this should be end-of-file
// ASN1 INTEGER types use the high-order bit to signify a negative number,
// hence a leading '00' is used for numbers that begin with '80' or greater
// which is why r length is sometimes a byte longer than its bit length
if (0 === s[0]) { s = s.slice(1); }
if (0 === r[0]) { r = r.slice(1); }
return { raw: sig.buffer, r: r.buffer, s: s.buffer };
}
function createEcCsr(domains, keypem, ecpub) {
// TODO get pub from priv
var csrBody = createCsrBodyEc(domains, ecpub);
var sig = signEc(keypem, csrBody);
var rLen = sig.r.byteLength;
var rc = '';
var sLen = sig.s.byteLength;
var sc = '';
if (0x80 & new Uint8Array(sig.r)[0]) { rc = '00'; rLen += 1; }
if (0x80 & new Uint8Array(sig.s)[0]) { sc = '00'; sLen += 1; }
var csrSig = csrEcFoot
.replace(/{len}/, numToHex(1 + 2 + 2 + 2 + rLen + sLen))
.replace(/{rslen}/, numToHex(2 + 2 + rLen + sLen))
.replace(/{rlen}/, numToHex(rLen))
.replace(/{r}/, rc + toHex(sig.r))
.replace(/{slen}/, numToHex(sLen))
.replace(/{s}/, sc + toHex(sig.s))
;
// Note: If we supported P-521 a number of the lengths would change
// by one byte and that would be... annoying to update
var len = csrBody.byteLength + (csrSig.length/2);
/*
console.log('sig:', sig.raw.byteLength, toHex(sig.raw));
console.log('r:', sig.r.byteLength, toHex(sig.r));
console.log('s:', sig.s.byteLength, toHex(sig.s));
console.log('csr sig:', csrSig.length / 2, csrSig);
console.log('csrBodyLen + csrSigLen', numToHex(len));
*/
var head = csrHead.replace(/{[^}]+}/, numToHex(len));
var ab = new Uint8Array(new ArrayBuffer((head.length/2) + len));
var i = 0;
fromHex(head).forEach(function (b) {
ab[i] = b;
i += 1;
});
csrBody.forEach(function (b) {
ab[i] = b;
i += 1;
});
fromHex(csrSig).forEach(function (b) {
ab[i] = b;
i += 1;
});
return ab;
}
function createEcCsrPem(domains, keypem) {
var pemblock = parsePem(keypem);
var ecpub = readEcPubkey(pemblock.der);
var ab = createEcCsr(domains, keypem, ecpub);
var pem = formatAsPem(toBase64(ab));
return '-----BEGIN CERTIFICATE REQUEST-----\n' + pem + '-----END CERTIFICATE REQUEST-----';
}
// Taken from Unibabel
// https://git.coolaj86.com/coolaj86/unibabel.js#readme
// https://coolaj86.com/articles/base64-unicode-utf-8-javascript-and-you/
function utf8ToUint8Array(str) {
var escstr = encodeURIComponent(str);
// replaces any uri escape sequence, such as %0A,
// with binary escape, such as 0x0A
var binstr = escstr.replace(/%([0-9A-F]{2})/g, function(match, p1) {
return String.fromCharCode('0x' + p1);
});
var buf = new Uint8Array(binstr.length);
binstr.split('').forEach(function (ch, i) {
buf[i] = ch.charCodeAt(0);
});
return buf;
}
function ensurePem(key) {
if (!key) { throw new Error("no private key given"); }
// whether PEM or DER, convert to Uint8Array
if ('string' === typeof key) { key = utf8ToUint8Array(key); }
// for consistency
if (key instanceof Buffer) { key = new Uint8Array(key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength)); }
// just as a sanity check
if (key instanceof Array) {
key = Uint8Array.from(key);
if (!key.every(function (el) {
return ('number' === typeof el) && (el >= 0) && (el <= 255);
})) {
throw new Error("key was an array, but not an array of ints between 0 and 255");
}
}
// no matter which path we take, we should arrive at a Uint8Array
if (!(key instanceof Uint8Array)) {
throw new Error("typeof key is '" + typeof key + "', not any of the supported types: utf8 string,"
+ " binary string, node Buffer, Uint8Array, or Array of ints between 0 and 255");
}
// if DER, convert to PEM
if ((0x30 === key[0]) && (0x80 & key[1])) {
key = toBase64(key);
}
key = [].map.call(key, function (i) {
return String.fromCharCode(i);
}).join('');
if ('M' === key[0]) {
key = '-----BEGIN EC PRIVATE KEY-----\n' + key + '-----END EC PRIVATE KEY-----';
}
if ('-' === key[0]) {
return key;
} else {
throw new Error("key does not appear to be in PEM formt (does not begin with either '-' or 'M'),"
+ " nor DER format (does not begin with 0x308X)");
}
}
/*global Promise*/
module.exports = function (opts) {
// We're using a Promise here to be compatible with the browser version
// which uses the webcrypto API for some of the conversions
return Promise.resolve().then(function () {
// We do a bit of extra error checking for user convenience
if (!opts) { throw new Error("You must pass options with key and domains to ecdsacsr"); }
if (!Array.isArray(opts.domains) || 0 === opts.domains.length) {
new Error("You must pass options.domains as a non-empty array");
}
// I need to check that 例.中国 is a valid domain name
if (!opts.domains.every(function (d) {
// allow punycode? xn--
if ('string' === typeof d /*&& /\./.test(d) && !/--/.test(d)*/) {
return true;
}
})) {
throw new Error("You must pass options.domains as utf8 strings (not punycode)");
}
var key = ensurePem(opts.key);
return createEcCsrPem(opts.domains, key);
});
};

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

29
package.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "ecdsa-csr",
"version": "1.0.0",
"description": "A focused, zero-dependency library to generate a Certificate Signing Request (CSR) and sign it!",
"main": "index.js",
"bin": {
"ecdsa-csr": "ecdsacsr.js"
},
"directories": {
"lib": "lib"
},
"scripts": {
"postinstall": "node lib/telemetry.js install",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://git.coolaj86.com/coolaj86/ecdsa-csr.js"
},
"keywords": [
"zero-dependency",
"csr",
"ecdsa",
"ec",
"pem"
],
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "MPL-2.0"
}