diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index 7eaedcc1..03deaac3 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -38,14 +38,26 @@ import type_kdf_params from '../type/kdf_params.js'; import type_mpi from '../type/mpi.js'; import type_oid from '../type/oid.js'; -function BigInteger2mpi(bn) { - var mpi = new type_mpi(); - mpi.fromBigInteger(bn); - return mpi; +function createType(data, type) { + switch(type) { + case 'mpi': + return new type_mpi(data); + case 'oid': + return new type_oid(data); + case 'kdf': + return new type_kdf_params(data); + case 'ecdh_symkey': + return new type_ecdh_symkey(data); + default: + return null; + } } -function mapResult(result) { - return result.map(BigInteger2mpi); +function mapResult(result, types) { + for (var i=0; i < result.length; i++) { + result[i] = createType(result[i], types[i]); + } + return result; } export default { @@ -53,38 +65,39 @@ export default { * Encrypts data using the specified public key multiprecision integers * and the specified algorithm. * @param {module:enums.publicKey} algo Algorithm to be used (See {@link http://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1}) - * @param {Array} publicMPIs Algorithm dependent multiprecision integers + * @param {Array} publicParams Algorithm dependent multiprecision integers * @param {module:type/mpi} data Data to be encrypted as MPI * @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, fingerprint) { + publicKeyEncrypt: function(algo, publicParams, data, fingerprint) { + var types = this.getEncSessionKeyParamTypes(algo); var result = (function() { var m; switch (algo) { case 'rsa_encrypt': case 'rsa_encrypt_sign': var rsa = new publicKey.rsa(); - var n = publicMPIs[0].toBigInteger(); - var e = publicMPIs[1].toBigInteger(); + var n = publicParams[0].toBigInteger(); + var e = publicParams[1].toBigInteger(); m = data.toBigInteger(); - return mapResult([rsa.encrypt(m, e, n)]); + return mapResult([rsa.encrypt(m, e, n)], types); case 'elgamal': var elgamal = new publicKey.elgamal(); - var p = publicMPIs[0].toBigInteger(); - var g = publicMPIs[1].toBigInteger(); - var y = publicMPIs[2].toBigInteger(); + var p = publicParams[0].toBigInteger(); + var g = publicParams[1].toBigInteger(); + var y = publicParams[2].toBigInteger(); m = data.toBigInteger(); - return mapResult(elgamal.encrypt(m, g, p, y)); + return mapResult(elgamal.encrypt(m, g, p, y), types); case 'ecdh': var ecdh = publicKey.elliptic.ecdh; - var curve = publicMPIs[0]; - var kdf_params = publicMPIs[2]; - var R = publicMPIs[1].toBigInteger(); + var curve = publicParams[0]; + var kdf_params = publicParams[2]; + var R = publicParams[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)]; + return mapResult([res.V, res.C], types); default: return []; @@ -98,7 +111,7 @@ export default { * Decrypts data using the specified public key multiprecision integers of the private key, * the specified secretMPIs of the private key and the specified algorithm. * @param {module:enums.publicKey} algo Algorithm to be used (See {@link http://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1}) - * @param {Array} publicMPIs Algorithm dependent multiprecision integers + * @param {Array} publicParams Algorithm dependent multiprecision integers * of the public key part of the private key * @param {Array} secretMPIs Algorithm dependent multiprecision integers * of the private key used @@ -146,8 +159,7 @@ export default { } })(); - var result = new type_mpi(); - result.fromBigInteger(bn); + var result = new type_mpi(bn); return result; }, @@ -155,7 +167,7 @@ export default { * @param {String} algo The public key algorithm * @return {Integer} The number of integers. */ - getPrivateMpiCount: function(algo) { + getPrivKeyParamTypes: function(algo) { switch (algo) { case 'rsa_encrypt': case 'rsa_encrypt_sign': @@ -165,27 +177,30 @@ export default { // - MPI of RSA secret prime value p. // - MPI of RSA secret prime value q (p < q). // - MPI of u, the multiplicative inverse of p, mod q. - return 4; + return ['mpi', 'mpi', 'mpi', 'mpi']; case 'elgamal': // Algorithm-Specific Fields for Elgamal secret keys: // - MPI of Elgamal secret exponent x. - return 1; + return ['mpi']; case 'dsa': // Algorithm-Specific Fields for DSA secret keys: // - MPI of DSA secret exponent x. - return 1; + return ['mpi']; case 'ecdh': case 'ecdsa': // Algorithm-Specific Fields for ECDSA or ECDH secret keys: // - MPI of an integer representing the secret key. - return 1; + return ['mpi']; default: throw new Error('Unknown algorithm'); } }, - getPublicMpiCount: function(algo) { - // - A series of multiprecision integers comprising the key material: + getPrivKeyParamCount: function(algo) { + return this.getPrivKeyParamTypes(algo).length; + }, + + getPubKeyParamTypes: function(algo) { // Algorithm-Specific Fields for RSA public keys: // - a multiprecision integer (MPI) of RSA public modulus n; // - an MPI of RSA public encryption exponent e. @@ -193,42 +208,71 @@ export default { case 'rsa_encrypt': case 'rsa_encrypt_sign': case 'rsa_sign': - return 2; - + return ['mpi', 'mpi']; // Algorithm-Specific Fields for Elgamal public keys: // - MPI of Elgamal prime p; // - MPI of Elgamal group generator g; // - MPI of Elgamal public key value y (= g**x mod p where x is secret). case 'elgamal': - return 3; - + return ['mpi', 'mpi', 'mpi']; // Algorithm-Specific Fields for DSA public keys: // - MPI of DSA prime p; // - MPI of DSA group order q (q is a prime divisor of p-1); // - MPI of DSA group generator g; // - MPI of DSA public-key value y (= g**x mod p where x is secret). case 'dsa': - return 4; - + return ['mpi', 'mpi', 'mpi', 'mpi']; // Algorithm-Specific Fields for ECDSA public keys: // - OID of curve; // - MPI of EC point representing public key. case 'ecdsa': - return 2; - + return ['oid', 'mpi']; // 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; + return ['oid', 'mpi', 'kdf']; + default: + throw new Error('Unknown algorithm.'); + } + }, + + getPubKeyParamCount: function(algo) { + return this.getPubKeyParamTypes(algo).length; + }, + + getEncSessionKeyParamTypes: function(algo) { + switch (algo) { + // Algorithm-Specific Fields for RSA encrypted session keys: + // - MPI of RSA encrypted value m**e mod n. + case 'rsa_encrypt': + case 'rsa_encrypt_sign': + return ['mpi']; + + // Algorithm-Specific Fields for Elgamal encrypted session keys: + // - MPI of Elgamal value g**k mod p + // - MPI of Elgamal value m * y**k mod p + case 'elgamal': + return ['mpi', 'mpi']; + + // Algorithm-Specific Fields for ECDH encrypted session keys: + // - MPI containing the ephemeral key used to establish the shared secret + // - EcdhSymmetricKey + case 'ecdh': + return ['mpi', 'ecdh_symkey']; default: throw new Error('Unknown algorithm.'); } }, - generateMpi: function(algo, bits, curve) { + getEncSessionKeyParamCount: function(algo) { + return this.getEncSessionKeyParamTypes(algo).length; + }, + + generateParams: function(algo, bits, curve) { + var types = this.getPubKeyParamTypes(algo).concat(this.getPrivKeyParamTypes(algo)); switch (algo) { case 'rsa_encrypt': case 'rsa_encrypt_sign': @@ -236,33 +280,17 @@ export default { //remember "publicKey" refers to the crypto/public_key dir var rsa = new publicKey.rsa(); return rsa.generate(bits, "10001").then(function(keyObject) { - var output = []; - output.push(keyObject.n); - output.push(keyObject.ee); - output.push(keyObject.d); - output.push(keyObject.p); - output.push(keyObject.q); - output.push(keyObject.u); - return mapResult(output); + return mapResult([keyObject.n, keyObject.ee, keyObject.d, keyObject.p, keyObject.q, keyObject.u], types); }); case 'ecdsa': return publicKey.elliptic.generate(curve).then(function (keyObject) { - return [ - new type_oid(keyObject.oid), - BigInteger2mpi(keyObject.R), - BigInteger2mpi(keyObject.r) - ]; + return mapResult([keyObject.oid, keyObject.R, keyObject.r], types); }); case 'ecdh': return publicKey.elliptic.generate(curve).then(function (keyObject) { - return [ - new type_oid(keyObject.oid), - BigInteger2mpi(keyObject.R), - new type_kdf_params(keyObject.hash, keyObject.cipher), - BigInteger2mpi(keyObject.r) - ]; + return mapResult([keyObject.oid, keyObject.R, [keyObject.hash, keyObject.cipher], keyObject.r], types); }); default: diff --git a/src/crypto/random.js b/src/crypto/random.js index 4e2331a4..54e656ea 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -113,8 +113,7 @@ export default { randomBits.charCodeAt(0)) + randomBits.substring(1); } - var mpi = new type_mpi(); - mpi.fromBytes(randomBits); + var mpi = new type_mpi(randomBits); return mpi.toBigInteger(); }, diff --git a/src/key.js b/src/key.js index 36506c6c..fce34b1a 100644 --- a/src/key.js +++ b/src/key.js @@ -380,7 +380,7 @@ Key.prototype.encrypt = function(passphrase) { var keys = this.getAllKeyPackets(); for (var i = 0; i < keys.length; i++) { keys[i].encrypt(passphrase); - keys[i].clearPrivateMPIs(); + keys[i].clearPrivateParams(); } }; @@ -1279,8 +1279,8 @@ function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { packetlist.push(subkeySignaturePacket); if (!options.unlocked) { - secretKeyPacket.clearPrivateMPIs(); - secretSubkeyPacket.clearPrivateMPIs(); + secretKeyPacket.clearPrivateParams(); + secretSubkeyPacket.clearPrivateParams(); } return new Key(packetlist); diff --git a/src/packet/public_key.js b/src/packet/public_key.js index 8e322248..57b4ca98 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -51,12 +51,8 @@ export default function PublicKey() { /** Key creation date. * @type {Date} */ this.created = new Date(); - /** A list of multiprecision integers - * @type {module:type/mpi} */ - this.mpi = []; - /** Public key algorithm - * @type {module:enums.publicKey} */ - this.algorithm = 'rsa_sign'; + /* Algorithm specific params */ + this.params = []; // time in days (V3 only) this.expirationTimeV3 = 0; /** @@ -97,22 +93,22 @@ PublicKey.prototype.read = function (bytes) { // - A one-octet number denoting the public-key algorithm of this key. this.algorithm = enums.read(enums.publicKey, bytes[pos++]); - var mpicount = crypto.getPublicMpiCount(this.algorithm); - this.mpi = []; + var param_count = crypto.getPubKeyParamCount(this.algorithm); + this.params = []; var bmpi = bytes.subarray(pos, bytes.length); var p = 0; - for (var i = 0; i < mpicount && p < bmpi.length; i++) { + for (var i = 0; i < param_count && p < bmpi.length; i++) { if ((this.algorithm === 'ecdsa' || this.algorithm === 'ecdh') && i === 0) { - this.mpi[i] = new type_oid(); + this.params[i] = new type_oid(); } else if (this.algorithm === 'ecdh' && i === 2) { - this.mpi[i] = new type_kdf_params(); + this.params[i] = new type_kdf_params(); } else { - this.mpi[i] = new type_mpi(); + this.params[i] = new type_mpi(); } - p += this.mpi[i].read(bmpi.subarray(p, bmpi.length)); + p += this.params[i].read(bmpi.subarray(p, bmpi.length)); if (p > bmpi.length) { throw new Error('Error reading MPI @:' + p); @@ -147,10 +143,10 @@ PublicKey.prototype.write = function () { } arr.push(new Uint8Array([enums.write(enums.publicKey, this.algorithm)])); - var mpicount = crypto.getPublicMpiCount(this.algorithm); + var param_count = crypto.getPubKeyParamCount(this.algorithm); - for (var i = 0; i < mpicount; i++) { - arr.push(this.mpi[i].write()); + for (var i = 0; i < param_count; i++) { + arr.push(this.params[i].write()); } return util.concatUint8Array(arr); @@ -183,7 +179,7 @@ PublicKey.prototype.getKeyId = function () { if (this.version === 4) { this.keyid.read(util.str2Uint8Array(util.hex2bin(this.getFingerprint()).substr(12, 8))); } else if (this.version === 3) { - var arr = this.mpi[0].write(); + var arr = this.params[0].write(); this.keyid.read(arr.subarray(arr.length - 8, arr.length)); } return this.keyid; @@ -202,9 +198,9 @@ PublicKey.prototype.getFingerprint = function () { toHash = this.writeOld(); this.fingerprint = util.Uint8Array2str(crypto.hash.sha1(toHash)); } else if (this.version === 3) { - var mpicount = crypto.getPublicMpiCount(this.algorithm); - for (var i = 0; i < mpicount; i++) { - toHash += this.mpi[i].toBytes(); + var param_count = crypto.getPubKeyParamCount(this.algorithm); + for (var i = 0; i < param_count; i++) { + toHash += this.params[i].toBytes(); } this.fingerprint = util.Uint8Array2str(crypto.hash.md5(util.str2Uint8Array(toHash))); } @@ -217,15 +213,15 @@ PublicKey.prototype.getFingerprint = function () { * @return {int} Number of bits */ PublicKey.prototype.getBitSize = function () { - return this.mpi[0].byteLength() * 8; + return this.params[0].byteLength() * 8; }; /** * Fix custom types after cloning */ PublicKey.prototype.postCloneTypeFix = function() { - for (var i = 0; i < this.mpi.length; i++) { - this.mpi[i] = type_mpi.fromClone(this.mpi[i]); + for (var i = 0; i < this.params.length; i++) { + this.params[i] = type_mpi.fromClone(this.params[i]); } if (this.keyid) { this.keyid = type_keyid.fromClone(this.keyid); diff --git a/src/packet/public_key_encrypted_session_key.js b/src/packet/public_key_encrypted_session_key.js index 673ebc3d..9678f479 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -55,10 +55,7 @@ export default function PublicKeyEncryptedSessionKey() { this.version = 3; this.publicKeyId = new type_keyid(); - this.publicKeyAlgorithm = 'rsa_encrypt'; - this.sessionKey = null; - this.sessionKeyAlgorithm = 'aes256'; /** @type {Array} */ this.encrypted = []; @@ -81,34 +78,20 @@ PublicKeyEncryptedSessionKey.prototype.read = function (bytes) { var i = 10; - var integerCount = (function(algo) { - switch (algo) { - case 'rsa_encrypt': - case 'rsa_encrypt_sign': - return 1; + var integerCount = crypto.getEncSessionKeyParamCount(this.publicKeyAlgorithm); - case 'elgamal': - return 2; - - case 'ecdh': - return 2; - - default: - throw new Error("Invalid algorithm."); - } - })(this.publicKeyAlgorithm); this.encrypted = []; for (var j = 0; j < integerCount; j++) { - var mpi; + var param; if (this.publicKeyAlgorithm === 'ecdh' && j === 1) { - mpi = new type_ecdh_symkey(); + param = new type_ecdh_symkey(); } else { - mpi = new type_mpi(); + param = new type_mpi(); } - i += mpi.read(bytes.subarray(i, bytes.length)); - this.encrypted.push(mpi); + i += param.read(bytes.subarray(i, bytes.length)); + this.encrypted.push(param); } }; @@ -136,20 +119,20 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = function (key) { var checksum = util.calc_checksum(this.sessionKey); data += util.Uint8Array2str(util.writeNumber(checksum, 2)); - var mpi; + var param; if (this.publicKeyAlgorithm === 'ecdh') { - mpi = util.str2Uint8Array(crypto.pkcs5.addPadding(data)); + param = util.str2Uint8Array(crypto.pkcs5.addPadding(data)); } else { - mpi = new type_mpi(); - mpi.fromBytes(crypto.pkcs1.eme.encode( + param = new type_mpi(); + param.fromBytes(crypto.pkcs1.eme.encode( data, - key.mpi[0].byteLength())); + key.params[0].byteLength())); } this.encrypted = crypto.publicKeyEncrypt( this.publicKeyAlgorithm, - key.mpi, - mpi, + key.params, + param, key.fingerprint); }; @@ -164,7 +147,7 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = function (key) { PublicKeyEncryptedSessionKey.prototype.decrypt = function (key) { var result = crypto.publicKeyDecrypt( this.publicKeyAlgorithm, - key.mpi, + key.params, this.encrypted, key.fingerprint).toBytes(); diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index 8e525e41..52bfce8f 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -89,7 +89,7 @@ function parse_cleartext_mpi(hash_algorithm, cleartext, algorithm) { return new Error("Hash mismatch."); } - var mpis = crypto.getPrivateMpiCount(algorithm); + var mpis = crypto.getPrivKeyParamCount(algorithm); var j = 0; var mpi = []; @@ -104,7 +104,7 @@ function parse_cleartext_mpi(hash_algorithm, cleartext, algorithm) { function write_cleartext_mpi(hash_algorithm, algorithm, mpi) { var arr = []; - var discard = crypto.getPublicMpiCount(algorithm); + var discard = crypto.getPubKeyParamCount(algorithm); for (var i = discard; i < mpi.length; i++) { arr.push(mpi[i].write()); @@ -147,7 +147,7 @@ SecretKey.prototype.read = function (bytes) { if (parsedMPI instanceof Error) { throw parsedMPI; } - this.mpi = this.mpi.concat(parsedMPI); + this.params = this.params.concat(parsedMPI); this.isDecrypted = true; } @@ -161,7 +161,7 @@ SecretKey.prototype.write = function () { if (!this.encrypted) { arr.push(new Uint8Array([0])); - arr.push(write_cleartext_mpi('mod', this.algorithm, this.mpi)); + arr.push(write_cleartext_mpi('mod', this.algorithm, this.params)); } else { arr.push(this.encrypted); } @@ -188,7 +188,7 @@ SecretKey.prototype.encrypt = function (passphrase) { var s2k = new type_s2k(), symmetric = 'aes256', - cleartext = write_cleartext_mpi('sha1', this.algorithm, this.mpi), + cleartext = write_cleartext_mpi('sha1', this.algorithm, this.params), key = produceEncryptionKey(s2k, passphrase, symmetric), blockLen = crypto.cipher[symmetric].blockSize, iv = crypto.random.getRandomBytes(blockLen); @@ -267,7 +267,7 @@ SecretKey.prototype.decrypt = function (passphrase) { if (parsedMPI instanceof Error) { return false; } - this.mpi = this.mpi.concat(parsedMPI); + this.params = this.params.concat(parsedMPI); this.isDecrypted = true; this.encrypted = null; return true; @@ -276,8 +276,8 @@ SecretKey.prototype.decrypt = function (passphrase) { SecretKey.prototype.generate = function (bits, curve) { var self = this; - return crypto.generateMpi(self.algorithm, bits, curve).then(function(mpi) { - self.mpi = mpi; + return crypto.generateParams(self.algorithm, bits, curve).then(function(params) { + self.params = params; self.isDecrypted = true; }); }; @@ -285,10 +285,10 @@ SecretKey.prototype.generate = function (bits, curve) { /** * Clear private MPIs, return to initial state */ -SecretKey.prototype.clearPrivateMPIs = function () { +SecretKey.prototype.clearPrivateParams = function () { if (!this.encrypted) { throw new Error('If secret key is not encrypted, clearing private MPIs is irreversible.'); } - this.mpi = this.mpi.slice(0, crypto.getPublicMpiCount(this.algorithm)); + this.params = this.params.slice(0, crypto.getPubKeyParamCount(this.algorithm)); this.isDecrypted = false; }; diff --git a/src/packet/signature.js b/src/packet/signature.js index b5f72d69..89d953c4 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -243,7 +243,7 @@ Signature.prototype.sign = function (key, data) { this.signedHashValue = hash.subarray(0, 2); this.signature = crypto.signature.sign(hashAlgorithm, - publicKeyAlgorithm, key.mpi, toHash); + publicKeyAlgorithm, key.params, toHash); }; /** @@ -644,7 +644,7 @@ Signature.prototype.verify = function (key, data) { } this.verified = crypto.signature.verify(publicKeyAlgorithm, - hashAlgorithm, mpi, key.mpi, + hashAlgorithm, mpi, key.params, util.concatUint8Array([bytes, this.signatureData, trailer])); return this.verified; diff --git a/src/type/mpi.js b/src/type/mpi.js index d787b668..11e50504 100644 --- a/src/type/mpi.js +++ b/src/type/mpi.js @@ -42,9 +42,15 @@ import util from '../util.js'; /** * @constructor */ -export default function MPI() { +export default function MPI(data) { /** An implementation dependent integer */ - this.data = null; + if (data instanceof BigInteger) { + this.fromBigInteger(data); + } else if (typeof data === 'string') { + this.fromBytes(data); + } else { + this.data = null; + } } /** diff --git a/test/general/packet.js b/test/general/packet.js index 431e1f3a..95c03d78 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -191,13 +191,13 @@ describe("Packet", function() { enc.publicKeyAlgorithm = 'rsa_encrypt'; enc.sessionKeyAlgorithm = 'aes256'; enc.publicKeyId.bytes = '12345678'; - enc.encrypt({ mpi: mpi }); + enc.encrypt({ params: mpi }); msg.push(enc); msg2.read(msg.write()); - msg2[0].decrypt({ mpi: mpi }); + msg2[0].decrypt({ params: mpi }); expect(stringify(msg2[0].sessionKey)).to.equal(stringify(enc.sessionKey)); expect(msg2[0].sessionKeyAlgorithm).to.equal(enc.sessionKeyAlgorithm); @@ -445,8 +445,8 @@ describe("Packet", function() { return mpi; }); - key[0].mpi = mpi; - + key[0].params = mpi; + key[0].algorithm = "rsa_sign"; key[0].encrypt('hello'); var raw = key.write(); @@ -455,7 +455,7 @@ describe("Packet", function() { key2.read(raw); key2[0].decrypt('hello'); - expect(key[0].mpi.toString()).to.equal(key2[0].mpi.toString()); + expect(key[0].params.toString()).to.equal(key2[0].params.toString()); }); }); @@ -473,7 +473,8 @@ describe("Packet", function() { return mpi; }); - key.mpi = mpi; + key.params = mpi; + key.algorithm = "rsa_sign"; var signed = new openpgp.packet.List(), literal = new openpgp.packet.Literal(),