Last active
December 12, 2015 09:28
-
-
Save brianloveswords/4751447 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This is a proof-of-concept to show that, despite being undocumented, | |
node can successfully sign and verify using Elliptic Curve keys and | |
SHA2 hashing. | |
I originally thought using sha2 with ecdsa in node wasn't possible | |
because node only accepts algorithms defined as an openssl message | |
digest algorithm, and even the latest version of openssl only lists | |
"ecdsa-with-sha1". | |
This test aims to prove that it's possible to use ECDSA with SHA2 by | |
lying to node by specifying "RSA-SHA(bits)" as the algorithm then | |
passing an Elliptic Curve key. | |
Usage: | |
node ec-poc.js | |
Methodology: | |
I test interoperability by generating a signature with | |
`crypto.createSign` and verifying that signature with `openssl dgst -verify`. | |
Then I do the reverse, generating a signature with `openssl dgst -sign` and | |
verifying it with `crypto.createVerify`. | |
Personal Results: | |
I tested this with positive results against node v0.8.18 and | |
OpenSSL version 1.0.1c. | |
*/ | |
const crypto = require('crypto'); | |
const fs = require('fs'); | |
const path = require('path'); | |
const spawn = require('child_process').spawn; | |
const INPUT = fs.readFileSync(__filename); | |
OpenSSL_keygen(function (keys) { | |
OpenSSL_verify(keys, function (verifyTestPassed) { | |
OpenSSL_sign(keys, function (signTestPassed) { | |
console.log('\nresults'); | |
console.log('\tverify test:', verifyTestPassed); | |
console.log('\tsign test:', signTestPassed); | |
cleanup(keys); | |
}); | |
}); | |
}); | |
/** | |
Generate a signature with `crypto.createSign` and attempt to verify it | |
with `openssl dgst -verify`. | |
`callback` should be in the form of `function (success) { }` | |
`keys` should be a keys object as generated by `OpenSSL_keygen` | |
*/ | |
function OpenSSL_verify(keys, callback) { | |
const signer = crypto.createSign('RSA-SHA256'); | |
const signature = (signer.update(INPUT), signer.sign(keys.private.data)); | |
const signatureFilePath = path.join(__dirname, keys.prefix+'.signature.txt'); | |
fs.writeFileSync(signatureFilePath, Buffer(signature, 'binary')); | |
const verify = spawn('openssl', [ | |
'dgst', | |
'-sha256', | |
'-verify', keys.public.file, | |
'-signature', signatureFilePath, | |
]); | |
verify.stdin.end(INPUT); | |
verify.stderr.pipe(process.stderr, {end: false}); | |
verify.on('exit', function (code) { | |
if (code === 0) | |
return callback(true); | |
return callback(false); | |
}); | |
} | |
/** | |
Generate a signature with `openssl dgst -sign` and attempt to verify | |
it with `crypto.createVerify`. | |
`callback` should be in the form of `function (success) { }` | |
`keys` should be a keys object as generated by `OpenSSL_keygen` | |
*/ | |
function OpenSSL_sign(keys, callback) { | |
var signature = Buffer(0); | |
const sign = spawn('openssl', [ | |
'dgst', | |
'-sha256', | |
'-sign', keys.private.file, | |
]); | |
sign.stdin.end(INPUT); | |
sign.stdout.on('data', function (buf) { | |
signature = Buffer.concat([signature, buf]); | |
}); | |
sign.stderr.pipe(process.stderr, {end: false}); | |
sign.on('exit', function (code) { | |
if (code !== 0) | |
throw Error('openssl error'); | |
const verifier = crypto.createVerify('RSA-SHA256'); | |
const valid = ( | |
verifier.update(INPUT), | |
verifier.verify(keys.public.data, signature) | |
); | |
return callback(valid); | |
}); | |
} | |
/** | |
Generate an EC keypair. Spawns `openssl ecparam` and `openssl ec` | |
`callback` should be in form of `function(keys) { ... }` | |
`keys` will contain three properies: `prefix`, `private` and `public`. | |
`prefix` is the prefix used when generating the key files. | |
`private` and `public` each contain two properties: | |
`data` is the raw key data | |
`file` is the path to the file on disk | |
*/ | |
function OpenSSL_keygen(callback) { | |
const prefix = (Math.random() * 0x100000000).toString(32); | |
const privateKeyPath = path.join(__dirname, prefix+'.private.pem'); | |
const publicKeyPath = path.join(__dirname, prefix+'.public.pem'); | |
var privateKey = Buffer(0); | |
var publicKey = Buffer(0); | |
var privgen, pubgen, error; | |
privgen = spawn('openssl', [ | |
'ecparam', | |
'-name', 'secp256k1', | |
'-genkey' | |
]); | |
privgen.stderr.pipe(process.stderr, {end: false}); | |
privgen.stdout.on('data', function (buf) { | |
privateKey = Buffer.concat([privateKey, buf]); | |
}); | |
privgen.on('exit', function (code) { | |
if (code !== 0) | |
throw new Error('openssl error') | |
error = ''; | |
pubgen = spawn('openssl', [ | |
'ec', | |
'-pubout' | |
]); | |
pubgen.stderr.pipe(process.stderr, {end: false}); | |
pubgen.stdout.on('data', function (buf) { | |
publicKey = Buffer.concat([publicKey, buf]); | |
}); | |
pubgen.stdin.end(privateKey); | |
pubgen.on('exit', function (code) { | |
if (code !== 0) | |
throw new Error('openssl error') | |
fs.writeFileSync(privateKeyPath, privateKey); | |
fs.writeFileSync(publicKeyPath, publicKey); | |
return callback({ | |
"prefix": prefix, | |
"private": { | |
data: privateKey, | |
file: privateKeyPath, | |
}, | |
"public": { | |
data: publicKey, | |
file: publicKeyPath | |
}, | |
}); | |
}); | |
}); | |
} | |
/** | |
Remove all the generated files | |
*/ | |
function cleanup(keys) { | |
const prefix = keys.prefix; | |
fs.unlinkSync(path.join(__dirname, prefix+'.private.pem')); | |
fs.unlinkSync(path.join(__dirname, prefix+'.public.pem')); | |
fs.unlinkSync(path.join(__dirname, prefix+'.signature.txt')); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment