diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index d877ea63..88849808 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -21,6 +21,7 @@ * @requires crypto/cipher * @requires crypto/public_key * @requires crypto/random + * @requires type/ecdh_symkey * @requires type/mpi * @module crypto/crypto */ @@ -30,8 +31,19 @@ import random from './random.js'; import cipher from './cipher'; import publicKey from './public_key'; +import type_ecdh_symkey from '../type/ecdh_symkey.js'; import type_mpi from '../type/mpi.js'; +function BigInteger2mpi(bn) { + var mpi = new type_mpi(); + mpi.fromBigInteger(bn); + return mpi; +} + +function mapResult(result) { + return result.map(BigInteger2mpi); +} + export default { /** * Encrypts data using the specified public key multiprecision integers @@ -42,7 +54,7 @@ export default { * @return {Array} if RSA an module:type/mpi; * if elgamal encryption an array of two module:type/mpi is returned; otherwise null */ - publicKeyEncrypt: function(algo, publicMPIs, data) { + publicKeyEncrypt: function(algo, publicMPIs, data, fingerprint) { var result = (function() { var m; switch (algo) { @@ -52,7 +64,7 @@ export default { var n = publicMPIs[0].toBigInteger(); var e = publicMPIs[1].toBigInteger(); m = data.toBigInteger(); - return [rsa.encrypt(m, e, n)]; + return mapResult([rsa.encrypt(m, e, n)]); case 'elgamal': var elgamal = new publicKey.elgamal(); @@ -60,18 +72,22 @@ export default { var g = publicMPIs[1].toBigInteger(); var y = publicMPIs[2].toBigInteger(); m = data.toBigInteger(); - return elgamal.encrypt(m, g, p, y); + return mapResult(elgamal.encrypt(m, g, p, y)); + + case 'ecdh': + var ecdh = publicKey.elliptic.ecdh; + var curve = publicMPIs[0]; + var kdf_params = publicMPIs[2]; + var R = publicMPIs[1].toBigInteger(); + var res = ecdh.encrypt(curve.oid, kdf_params.cipher, kdf_params.hash, data, R, fingerprint); + return [BigInteger2mpi(res.V), new type_ecdh_symkey(res.C)]; default: return []; } })(); - return result.map(function(bn) { - var mpi = new type_mpi(); - mpi.fromBigInteger(bn); - return mpi; - }); + return result; }, /** @@ -86,7 +102,7 @@ export default { * @return {module:type/mpi} returns a big integer containing the decrypted data; otherwise null */ - publicKeyDecrypt: function(algo, keyIntegers, dataIntegers) { + publicKeyDecrypt: function(algo, keyIntegers, dataIntegers, fingerprint) { var p; var bn = (function() { @@ -111,6 +127,16 @@ export default { var c2 = dataIntegers[1].toBigInteger(); p = keyIntegers[0].toBigInteger(); return elgamal.decrypt(c1, c2, p, x); + + case 'ecdh': + var ecdh = publicKey.elliptic.ecdh; + var curve = keyIntegers[0]; + var kdf_params = keyIntegers[2]; + var V = dataIntegers[0].toBigInteger(); + var C = dataIntegers[1].data; + var r = keyIntegers[3].toBigInteger(); + return ecdh.decrypt(curve.oid, kdf_params.cipher, kdf_params.hash, V, C, r, fingerprint); + default: return null; } @@ -144,8 +170,9 @@ export default { // Algorithm-Specific Fields for DSA secret keys: // - MPI of DSA secret exponent x. return 1; + case 'ecdh': case 'ecdsa': - // Algorithm-Specific Fields for ECDSA secret keys: + // Algorithm-Specific Fields for ECDSA or ECDH secret keys: // - MPI of an integer representing the secret key. return 1; default: @@ -185,6 +212,13 @@ export default { case 'ecdsa': return 2; + // Algorithm-Specific Fields for ECDH public keys: + // - OID of curve; + // - MPI of EC point representing public key. + // - variable-length field containing KDF parameters. + case 'ecdh': + return 3; + default: throw new Error('Unknown algorithm.'); } @@ -210,14 +244,6 @@ export default { default: throw new Error('Unsupported algorithm for key generation.'); } - - function mapResult(result) { - return result.map(function(bn) { - var mpi = new type_mpi(); - mpi.fromBigInteger(bn); - return mpi; - }); - } }, diff --git a/src/packet/public_key.js b/src/packet/public_key.js index 9a083350..8e322248 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -24,6 +24,7 @@ * major versions. Consequently, this section is complex. * @requires crypto * @requires enums + * @requires type/kdf_params * @requires type/keyid * @requires type/mpi * @requires type/oid @@ -35,6 +36,7 @@ import util from '../util.js'; import type_mpi from '../type/mpi.js'; +import type_kdf_params from '../type/kdf_params.js'; import type_keyid from '../type/keyid.js'; import type_oid from '../type/oid.js'; import enums from '../enums.js'; @@ -102,8 +104,10 @@ PublicKey.prototype.read = function (bytes) { var p = 0; for (var i = 0; i < mpicount && p < bmpi.length; i++) { - if (this.algorithm === 'ecdsa' && i === 0) { + if ((this.algorithm === 'ecdsa' || this.algorithm === 'ecdh') && i === 0) { this.mpi[i] = new type_oid(); + } else if (this.algorithm === 'ecdh' && i === 2) { + this.mpi[i] = new type_kdf_params(); } else { this.mpi[i] = new type_mpi(); } diff --git a/src/packet/public_key_encrypted_session_key.js b/src/packet/public_key_encrypted_session_key.js index fcdd71d0..673ebc3d 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -31,6 +31,7 @@ * decrypt the message. * @requires crypto * @requires enums + * @requires type/ecdh_symkey * @requires type/keyid * @requires type/mpi * @requires util @@ -41,6 +42,7 @@ import type_keyid from '../type/keyid.js'; import util from '../util.js'; +import type_ecdh_symkey from '../type/ecdh_symkey.js'; import type_mpi from '../type/mpi.js'; import enums from '../enums.js'; import crypto from '../crypto'; @@ -88,6 +90,9 @@ PublicKeyEncryptedSessionKey.prototype.read = function (bytes) { case 'elgamal': return 2; + case 'ecdh': + return 2; + default: throw new Error("Invalid algorithm."); } @@ -96,7 +101,12 @@ PublicKeyEncryptedSessionKey.prototype.read = function (bytes) { this.encrypted = []; for (var j = 0; j < integerCount; j++) { - var mpi = new type_mpi(); + var mpi; + if (this.publicKeyAlgorithm === 'ecdh' && j === 1) { + mpi = new type_ecdh_symkey(); + } else { + mpi = new type_mpi(); + } i += mpi.read(bytes.subarray(i, bytes.length)); this.encrypted.push(mpi); } @@ -126,15 +136,21 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = function (key) { var checksum = util.calc_checksum(this.sessionKey); data += util.Uint8Array2str(util.writeNumber(checksum, 2)); - var mpi = new type_mpi(); - mpi.fromBytes(crypto.pkcs1.eme.encode( - data, - key.mpi[0].byteLength())); + var mpi; + if (this.publicKeyAlgorithm === 'ecdh') { + mpi = util.str2Uint8Array(crypto.pkcs5.addPadding(data)); + } else { + mpi = new type_mpi(); + mpi.fromBytes(crypto.pkcs1.eme.encode( + data, + key.mpi[0].byteLength())); + } this.encrypted = crypto.publicKeyEncrypt( this.publicKeyAlgorithm, key.mpi, - mpi); + mpi, + key.fingerprint); }; /** @@ -149,11 +165,18 @@ PublicKeyEncryptedSessionKey.prototype.decrypt = function (key) { var result = crypto.publicKeyDecrypt( this.publicKeyAlgorithm, key.mpi, - this.encrypted).toBytes(); + this.encrypted, + key.fingerprint).toBytes(); - var checksum = util.readNumber(util.str2Uint8Array(result.substr(result.length - 2))); - - var decoded = crypto.pkcs1.eme.decode(result); + var checksum; + var decoded; + if (this.publicKeyAlgorithm === 'ecdh') { + decoded = crypto.pkcs5.removePadding(result); + checksum = util.readNumber(util.str2Uint8Array(decoded.substr(decoded.length - 2))); + } else { + decoded = crypto.pkcs1.eme.decode(result); + checksum = util.readNumber(util.str2Uint8Array(result.substr(result.length - 2))); + } key = util.str2Uint8Array(decoded.substring(1, decoded.length - 2));