From 9901b69c8e372f39d6214fc0254b6465c4f329dc Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 10 Oct 2012 15:44:47 -0700 Subject: [PATCH] crypto: Move encoding logic to JS, default=buffer crypto: Hash and Hmac default to buffers crypto: Move Cipher encoding logic to JS crypto: Move Cipheriv encoding logic to JS crypto: Move Decipher encoding logic to JS crypto: Move Decipheriv into JS, default to buffers crypto: Move Sign class to JS crypto: Better encoding handling in Hash.update crypto: Move Verify class to JS crypto: Move DiffieHellman to JS, default to buffers crypto: Move DiffieHellmanGroup to JS, default to buffers Also, create a test for this feature --- doc/api/crypto.markdown | 47 ++-- lib/crypto.js | 294 +++++++++++++++++++--- test/simple/test-crypto-padding-aes256.js | 2 +- test/simple/test-crypto.js | 34 ++- 4 files changed, 304 insertions(+), 73 deletions(-) diff --git a/doc/api/crypto.markdown b/doc/api/crypto.markdown index 275e4b717b..e3ce72a96d 100644 --- a/doc/api/crypto.markdown +++ b/doc/api/crypto.markdown @@ -87,14 +87,15 @@ Returned by `crypto.createHash`. Updates the hash content with the given `data`, the encoding of which is given in `input_encoding` and can be `'buffer'`, `'utf8'`, `'ascii'` or `'binary'`. -Defaults to `'binary'`. +Defaults to `'buffer'`. + This can be called many times with new data as it is streamed. ### hash.digest([encoding]) Calculates the digest of all of the passed data to be hashed. The `encoding` can be `'buffer'`, `'hex'`, `'binary'` or `'base64'`. -Defaults to `'binary'`. +Defaults to `'buffer'`. Note: `hash` object can not be used after `digest()` method been called. @@ -121,7 +122,7 @@ This can be called many times with new data as it is streamed. Calculates the digest of all of the passed data to the hmac. The `encoding` can be `'buffer'`, `'hex'`, `'binary'` or `'base64'`. -Defaults to `'binary'`. +Defaults to `'buffer'`. Note: `hmac` object can not be used after `digest()` method been called. @@ -157,17 +158,18 @@ Returned by `crypto.createCipher` and `crypto.createCipheriv`. Updates the cipher with `data`, the encoding of which is given in `input_encoding` and can be `'buffer'`, `'utf8'`, `'ascii'` or `'binary'`. -Defaults to `'binary'`. +Defaults to `'buffer'`. The `output_encoding` specifies the output format of the enciphered data, -and can be `'buffer'`, `'binary'`, `'base64'` or `'hex'`. Defaults to `'binary'`. +and can be `'buffer'`, `'binary'`, `'base64'` or `'hex'`. Defaults to +`'buffer'`. Returns the enciphered contents, and can be called many times with new data as it is streamed. ### cipher.final([output_encoding]) Returns any remaining enciphered contents, with `output_encoding` being one of: -`'buffer'`, `'binary'`, `'base64'` or `'hex'`. Defaults to `'binary'`. +`'buffer'`, `'binary'`, `'base64'` or `'hex'`. Defaults to `'buffer'`. Note: `cipher` object can not be used after `final()` method been called. @@ -197,18 +199,18 @@ Returned by `crypto.createDecipher` and `crypto.createDecipheriv`. ### decipher.update(data, [input_encoding], [output_encoding]) Updates the decipher with `data`, which is encoded in `'buffer'`, `'binary'`, -`'base64'` or `'hex'`. Defaults to `'binary'`. +`'base64'` or `'hex'`. Defaults to `'buffer'`. The `output_decoding` specifies in what format to return the deciphered plaintext: `'buffer'`, `'binary'`, `'ascii'` or `'utf8'`. -Defaults to `'binary'`. +Defaults to `'buffer'`. ### decipher.final([output_encoding]) Returns any remaining plaintext which is deciphered, with `output_encoding` being one of: `'buffer'`, `'binary'`, `'ascii'` or `'utf8'`. -Defaults to `'binary'`. +Defaults to `'buffer'`. Note: `decipher` object can not be used after `final()` method been called. @@ -241,7 +243,7 @@ Calculates the signature on all the updated data passed through the signer. `private_key` is a string containing the PEM encoded private key for signing. Returns the signature in `output_format` which can be `'buffer'`, `'binary'`, -`'hex'` or `'base64'`. Defaults to `'binary'`. +`'hex'` or `'base64'`. Defaults to `'buffer'`. Note: `signer` object can not be used after `sign()` method been called. @@ -267,7 +269,7 @@ Verifies the signed data by using the `object` and `signature`. `object` is a string containing a PEM encoded object, which can be one of RSA public key, DSA public key, or X.509 certificate. `signature` is the previously calculated signature for the data, in the `signature_format` which can be `'buffer'`, -`'binary'`, `'hex'` or `'base64'`. Defaults to `'binary'`. +`'binary'`, `'hex'` or `'base64'`. Defaults to `'buffer'`. Returns true or false depending on the validity of the signature for the data and public key. @@ -283,7 +285,7 @@ given bit length. The generator used is `2`. Creates a Diffie-Hellman key exchange object using the supplied prime. The generator used is `2`. Encoding can be `'buffer'`, `'binary'`, `'hex'`, or `'base64'`. -Defaults to `'binary'`. +Defaults to `'buffer'`. ## Class: DiffieHellman @@ -296,7 +298,7 @@ Returned by `crypto.createDiffieHellman`. Generates private and public Diffie-Hellman key values, and returns the public key in the specified encoding. This key should be transferred to the other party. Encoding can be `'binary'`, `'hex'`, or `'base64'`. -Defaults to `'binary'`. +Defaults to `'buffer'`. ### diffieHellman.computeSecret(other_public_key, [input_encoding], [output_encoding]) @@ -304,38 +306,39 @@ Computes the shared secret using `other_public_key` as the other party's public key and returns the computed shared secret. Supplied key is interpreted using specified `input_encoding`, and secret is encoded using specified `output_encoding`. Encodings can be `'buffer'`, `'binary'`, `'hex'`, -or `'base64'`. The input encoding defaults to `'binary'`. +or `'base64'`. The input encoding defaults to `'buffer'`. If no output encoding is given, the input encoding is used as output encoding. ### diffieHellman.getPrime([encoding]) Returns the Diffie-Hellman prime in the specified encoding, which can be -`'buffer'`, `'binary'`, `'hex'`, or `'base64'`. Defaults to `'binary'`. +`'buffer'`, `'binary'`, `'hex'`, or `'base64'`. Defaults to `'buffer'`. ### diffieHellman.getGenerator([encoding]) Returns the Diffie-Hellman prime in the specified encoding, which can be -`'buffer'`, `'binary'`, `'hex'`, or `'base64'`. Defaults to `'binary'`. +`'buffer'`, `'binary'`, `'hex'`, or `'base64'`. Defaults to `'buffer'`. ### diffieHellman.getPublicKey([encoding]) Returns the Diffie-Hellman public key in the specified encoding, which can -be `'binary'`, `'hex'`, or `'base64'`. Defaults to `'binary'`. +be `'binary'`, `'hex'`, or `'base64'`. Defaults to `'buffer'`. ### diffieHellman.getPrivateKey([encoding]) Returns the Diffie-Hellman private key in the specified encoding, which can -be `'buffer'`, `'binary'`, `'hex'`, or `'base64'`. Defaults to `'binary'`. +be `'buffer'`, `'binary'`, `'hex'`, or `'base64'`. Defaults to +`'buffer'`. ### diffieHellman.setPublicKey(public_key, [encoding]) Sets the Diffie-Hellman public key. Key encoding can be `'buffer', ``'binary'`, -`'hex'` or `'base64'`. Defaults to `'binary'`. +`'hex'` or `'base64'`. Defaults to `'buffer'`. ### diffieHellman.setPrivateKey(public_key, [encoding]) Sets the Diffie-Hellman private key. Key encoding can be `'buffer'`, `'binary'`, -`'hex'` or `'base64'`. Defaults to `'binary'`. +`'hex'` or `'base64'`. Defaults to `'buffer'`. ## crypto.getDiffieHellman(group_name) @@ -361,8 +364,8 @@ Example (obtaining a shared secret): alice.generateKeys(); bob.generateKeys(); - var alice_secret = alice.computeSecret(bob.getPublicKey(), 'binary', 'hex'); - var bob_secret = bob.computeSecret(alice.getPublicKey(), 'binary', 'hex'); + var alice_secret = alice.computeSecret(bob.getPublicKey(), null, 'hex'); + var bob_secret = bob.computeSecret(alice.getPublicKey(), null, 'hex'); /* alice_secret and bob_secret should be the same */ console.log(alice_secret == bob_secret); diff --git a/lib/crypto.js b/lib/crypto.js index 3d3affa1e6..50e6af58de 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -23,14 +23,6 @@ try { var binding = process.binding('crypto'); var SecureContext = binding.SecureContext; - var Hmac = binding.Hmac; - var Hash = binding.Hash; - var Cipher = binding.Cipher; - var Decipher = binding.Decipher; - var Sign = binding.Sign; - var Verify = binding.Verify; - var DiffieHellman = binding.DiffieHellman; - var DiffieHellmanGroup = binding.DiffieHellmanGroup; var PBKDF2 = binding.PBKDF2; var randomBytes = binding.randomBytes; var pseudoRandomBytes = binding.pseudoRandomBytes; @@ -42,6 +34,8 @@ try { var crypto = false; } +var assert = require('assert'); +var StringDecoder = require('string_decoder').StringDecoder; function Credentials(secureProtocol, flags, context) { if (!(this instanceof Credentials)) { @@ -129,65 +123,285 @@ exports.createCredentials = function(options, context) { }; -exports.Hash = Hash; -exports.createHash = function(hash) { - return new Hash(hash); +exports.createHash = exports.Hash = Hash; +function Hash(algorithm) { + if (!(this instanceof Hash)) + return new Hash(algorithm); + this._binding = new binding.Hash(algorithm); +} + +Hash.prototype.update = function(data, encoding) { + if (encoding === 'buffer') + encoding = null; + if (encoding || typeof data === 'string') + data = new Buffer(data, encoding); + this._binding.update(data); + return this; +}; + +Hash.prototype.digest = function(outputEncoding) { + var result = this._binding.digest('buffer'); + if (outputEncoding && outputEncoding !== 'buffer') + result = result.toString(outputEncoding); + return result; }; -exports.Hmac = Hmac; -exports.createHmac = function(hmac, key) { - return (new Hmac).init(hmac, key); +exports.createHmac = exports.Hmac = Hmac; + +function Hmac(hmac, key) { + if (!(this instanceof Hmac)) + return new Hmac(hmac, key); + this._binding = new binding.Hmac(); + this._binding.init(hmac, key); }; +Hmac.prototype.update = Hash.prototype.update; +Hmac.prototype.digest = Hash.prototype.digest; + + +function getDecoder(decoder, encoding) { + decoder = decoder || new StringDecoder(encoding); + assert(decoder.encoding === encoding, 'Cannot change encoding'); + return decoder; +} + -exports.Cipher = Cipher; -exports.createCipher = function(cipher, password) { - return (new Cipher).init(cipher, password); +exports.createCipher = exports.Cipher = Cipher; +function Cipher(cipher, password) { + if (!(this instanceof Cipher)) + return new Cipher(cipher, password); + this._binding = new binding.Cipher; + this._binding.init(cipher, password); + this._decoder = null; }; +Cipher.prototype.update = function(data, inputEncoding, outputEncoding) { + if (inputEncoding && inputEncoding !== 'buffer') + data = new Buffer(data, inputEncoding); + + var ret = this._binding.update(data, 'buffer', 'buffer'); + + if (outputEncoding && outputEncoding !== 'buffer') { + this._decoder = getDecoder(this._decoder, outputEncoding); + ret = this._decoder.write(ret); + } -exports.createCipheriv = function(cipher, key, iv) { - return (new Cipher).initiv(cipher, key, iv); + return ret; }; +Cipher.prototype.final = function(outputEncoding) { + var ret = this._binding.final('buffer'); + + if (outputEncoding && outputEncoding !== 'buffer') { + this._decoder = getDecoder(this._decoder, outputEncoding); + ret = this._decoder.write(ret); + } + + return ret; +}; -exports.Decipher = Decipher; -exports.createDecipher = function(cipher, password) { - return (new Decipher).init(cipher, password); +Cipher.prototype.setAutoPadding = function(ap) { + this._binding.setAutoPadding(ap); + return this; }; -exports.createDecipheriv = function(cipher, key, iv) { - return (new Decipher).initiv(cipher, key, iv); + +exports.createCipheriv = exports.Cipheriv = Cipheriv; +function Cipheriv(cipher, key, iv) { + if (!(this instanceof Cipheriv)) + return new Cipheriv(cipher, key, iv); + this._binding = new binding.Cipher(); + this._binding.initiv(cipher, key, iv); + this._decoder = null; +} + +Cipheriv.prototype.update = Cipher.prototype.update; +Cipheriv.prototype.final = Cipher.prototype.final; +Cipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; + + +exports.createDecipher = exports.Decipher = Decipher; +function Decipher(cipher, password) { + if (!(this instanceof Decipher)) + return new Decipher(cipher, password); + this._binding = new binding.Decipher + this._binding.init(cipher, password); + this._decoder = null; }; +Decipher.prototype.update = Cipher.prototype.update; +Decipher.prototype.final = Cipher.prototype.final; +Decipher.prototype.finaltol = Cipher.prototype.final; +Decipher.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; + -exports.Sign = Sign; -exports.createSign = function(algorithm) { - return (new Sign).init(algorithm); +exports.createDecipheriv = exports.Decipheriv = Decipheriv; +function Decipheriv(cipher, key, iv) { + if (!(this instanceof Decipheriv)) + return new Decipheriv(cipher, key, iv); + this._binding = new binding.Decipher; + this._binding.initiv(cipher, key, iv); + this._decoder = null; }; -exports.Verify = Verify; -exports.createVerify = function(algorithm) { - return (new Verify).init(algorithm); +Decipheriv.prototype.update = Cipher.prototype.update; +Decipheriv.prototype.final = Cipher.prototype.final; +Decipheriv.prototype.finaltol = Cipher.prototype.final; +Decipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; + + +exports.createSign = exports.Sign = Sign; +function Sign(algorithm) { + if (!(this instanceof Sign)) + return new Sign(algorithm); + this._binding = new binding.Sign(); + this._binding.init(algorithm); }; -exports.DiffieHellman = DiffieHellman; -exports.createDiffieHellman = function(size_or_key, enc) { - if (!size_or_key) { - return new DiffieHellman(); - } else if (!enc) { - return new DiffieHellman(size_or_key); - } else { - return new DiffieHellman(size_or_key, enc); +Sign.prototype.update = Hash.prototype.update; + +Sign.prototype.sign = function(key, encoding) { + var ret = this._binding.sign(key, 'buffer'); + if (encoding && encoding !== 'buffer') + ret = ret.toString(encoding); + return ret; +}; + + +exports.createVerify = exports.Verify = Verify; +function Verify(algorithm) { + if (!(this instanceof Verify)) + return new Verify(algorithm); + + this._binding = new binding.Verify; + this._binding.init(algorithm); +} + +Verify.prototype.update = Hash.prototype.update; + +Verify.prototype.verify = function(object, signature, sigEncoding) { + if (sigEncoding === 'buffer') + sigEncoding = null; + if (sigEncoding || typeof signature === 'string') + signature = new Buffer(signature, sigEncoding); + return this._binding.verify(object, signature, 'buffer'); +}; + +exports.createDiffieHellman = exports.DiffieHellman = DiffieHellman; + +function DiffieHellman(sizeOrKey, encoding) { + if (!(this instanceof DiffieHellman)) + return new DiffieHellman(sizeOrKey, encoding); + + if (!sizeOrKey) + this._binding = new binding.DiffieHellman(); + else { + if (encoding === 'buffer') + encoding = null; + if (encoding || typeof sizeOrKey === 'string') + sizeOrKey = new Buffer(sizeOrKey, encoding); + this._binding = new binding.DiffieHellman(sizeOrKey, 'buffer'); } +} + +DiffieHellman.prototype.generateKeys = function(encoding) { + var keys = this._binding.generateKeys('buffer'); + if (encoding) + keys = keys.toString(encoding); + return keys; +}; + +DiffieHellman.prototype.computeSecret = function(key, inEnc, outEnc) { + if (inEnc === 'buffer') + inEnc = null; + if (outEnc === 'buffer') + outEnc = null; + if (inEnc || typeof key === 'string') + key = new Buffer(key, inEnc); + var ret = this._binding.computeSecret(key, 'buffer', 'buffer'); + if (outEnc) + ret = ret.toString(outEnc); + return ret; +}; + +DiffieHellman.prototype.getPrime = function(encoding) { + var prime = this._binding.getPrime('buffer'); + if (encoding && encoding !== 'buffer') + prime = prime.toString(encoding); + return prime; +}; + +DiffieHellman.prototype.getGenerator = function(encoding) { + var generator = this._binding.getGenerator('buffer'); + if (encoding && encoding !== 'buffer') + generator = generator.toString(encoding); + return generator; +}; +DiffieHellman.prototype.getPublicKey = function(encoding) { + var key = this._binding.getPublicKey('buffer'); + if (encoding && encoding !== 'buffer') + key = key.toString(encoding); + return key; }; -exports.getDiffieHellman = function(group_name) { - return new DiffieHellmanGroup(group_name); + +DiffieHellman.prototype.getPrivateKey = function(encoding) { + var key = this._binding.getPrivateKey('buffer'); + if (encoding && encoding !== 'buffer') + key = key.toString(encoding); + return key; }; +DiffieHellman.prototype.setPublicKey = function(key, encoding) { + if (encoding === 'buffer') + encoding = null; + if (encoding || typeof key === 'string') + key = new Buffer(key, encoding); + this._binding.setPublicKey(key, 'buffer'); + return this; +}; + +DiffieHellman.prototype.setPrivateKey = function(key, encoding) { + if (encoding === 'buffer') + encoding = null; + if (encoding || typeof key === 'string') + key = new Buffer(key, encoding); + this._binding.setPrivateKey(key, 'buffer'); + return this; +}; + + + +exports.DiffieHellmanGroup = + exports.createDiffieHellmanGroup = + exports.getDiffieHellman = DiffieHellmanGroup; + +function DiffieHellmanGroup(name) { + if (!(this instanceof DiffieHellmanGroup)) + return new DiffieHellmanGroup(name); + this._binding = new binding.DiffieHellmanGroup(name); +}; + +DiffieHellmanGroup.prototype.generateKeys = + DiffieHellman.prototype.generateKeys; + +DiffieHellmanGroup.prototype.computeSecret = + DiffieHellman.prototype.computeSecret; + +DiffieHellmanGroup.prototype.getPrime = + DiffieHellman.prototype.getPrime; + +DiffieHellmanGroup.prototype.getGenerator = + DiffieHellman.prototype.getGenerator; + +DiffieHellmanGroup.prototype.getPublicKey = + DiffieHellman.prototype.getPublicKey; + +DiffieHellmanGroup.prototype.getPrivateKey = + DiffieHellman.prototype.getPrivateKey; + exports.pbkdf2 = PBKDF2; exports.randomBytes = randomBytes; diff --git a/test/simple/test-crypto-padding-aes256.js b/test/simple/test-crypto-padding-aes256.js index e3b5518a8d..20574eccb4 100644 --- a/test/simple/test-crypto-padding-aes256.js +++ b/test/simple/test-crypto-padding-aes256.js @@ -43,7 +43,7 @@ function aes256(decipherFinal) { function decrypt(val, pad) { var c = crypto.createDecipheriv('aes256', key, iv); c.setAutoPadding(pad); - return c.update(val, 'binary', 'binary') + c[decipherFinal]('utf8'); + return c.update(val, 'binary', 'utf8') + c[decipherFinal]('utf8'); } // echo 0123456789abcdef0123456789abcdef \ diff --git a/test/simple/test-crypto.js b/test/simple/test-crypto.js index bbc35ef36c..0b70ece400 100644 --- a/test/simple/test-crypto.js +++ b/test/simple/test-crypto.js @@ -376,12 +376,16 @@ assert.equal(a1, 'h\u00ea\u00cb\u0097\u00d8o\fF!\u00fa+\u000e\u0017\u00ca' + '\u00bd\u008c', 'Test MD5 as binary'); assert.equal(a2, '2bX1jws4GYKTlxhloUB09Z66PoJZW+y+hq5R8dnx9l4=', 'Test SHA256 as base64'); -assert.equal(a3, '\u00c1(4\u00f1\u0003\u001fd\u0097!O\'\u00d4C/&Qz\u00d4' + - '\u0094\u0015l\u00b8\u008dQ+\u00db\u001d\u00c4\u00b5}\u00b2' + - '\u00d6\u0092\u00a3\u00df\u00a2i\u00a1\u009b\n\n*\u000f' + - '\u00d7\u00d6\u00a2\u00a8\u0085\u00e3<\u0083\u009c\u0093' + - '\u00c2\u0006\u00da0\u00a1\u00879(G\u00ed\'', - 'Test SHA512 as assumed binary'); +assert.deepEqual( + a3, + new Buffer( + '\u00c1(4\u00f1\u0003\u001fd\u0097!O\'\u00d4C/&Qz\u00d4' + + '\u0094\u0015l\u00b8\u008dQ+\u00db\u001d\u00c4\u00b5}\u00b2' + + '\u00d6\u0092\u00a3\u00df\u00a2i\u00a1\u009b\n\n*\u000f' + + '\u00d7\u00d6\u00a2\u00a8\u0085\u00e3<\u0083\u009c\u0093' + + '\u00c2\u0006\u00da0\u00a1\u00879(G\u00ed\'', + 'binary'), + 'Test SHA512 as assumed buffer'); assert.deepEqual(a4, new Buffer('8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'hex'), 'Test SHA1'); @@ -554,10 +558,10 @@ var privkey1 = dh1.getPrivateKey(); dh3.setPublicKey(key1); dh3.setPrivateKey(privkey1); -assert.equal(dh1.getPrime(), dh3.getPrime()); -assert.equal(dh1.getGenerator(), dh3.getGenerator()); -assert.equal(dh1.getPublicKey(), dh3.getPublicKey()); -assert.equal(dh1.getPrivateKey(), dh3.getPrivateKey()); +assert.deepEqual(dh1.getPrime(), dh3.getPrime()); +assert.deepEqual(dh1.getGenerator(), dh3.getGenerator()); +assert.deepEqual(dh1.getPublicKey(), dh3.getPublicKey()); +assert.deepEqual(dh1.getPrivateKey(), dh3.getPrivateKey()); var secret3 = dh3.computeSecret(key2, 'hex', 'base64'); @@ -567,6 +571,16 @@ assert.throws(function() { dh3.computeSecret(''); }, /key is too small/i); +// Create a shared using a DH group. +var alice = crypto.createDiffieHellmanGroup('modp5'); +var bob = crypto.createDiffieHellmanGroup('modp5'); +alice.generateKeys(); +bob.generateKeys(); +var aSecret = alice.computeSecret(bob.getPublicKey()).toString('hex'); +var bSecret = bob.computeSecret(alice.getPublicKey()).toString('hex'); +assert.equal(aSecret, bSecret); + + // https://github.com/joyent/node/issues/2338 assert.throws(function() { var p = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' +