From aee8974ef513a700f358a588bffbe1b88bf19b9f Mon Sep 17 00:00:00 2001 From: Mahrud Sayrafi Date: Tue, 13 Feb 2018 00:38:35 -0800 Subject: [PATCH] RSA signatures now use asmcrypto.js; various fixes and tweaks --- src/crypto/crypto.js | 10 +++-- src/crypto/pkcs1.js | 12 +++--- src/crypto/public_key/rsa.js | 34 ++++++++--------- src/crypto/signature.js | 47 +++++++++++------------ src/packet/signature.js | 6 +-- src/util.js | 5 ++- test/crypto/crypto.js | 73 ++++++++++++++++++++---------------- test/general/x25519.js | 12 +++--- 8 files changed, 103 insertions(+), 96 deletions(-) diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index 4cee1f7e..33926c35 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -82,7 +82,9 @@ export default { const curve = publicParams[0]; const kdf_params = publicParams[2]; const R = publicParams[1].toBigInteger(); - const res = await ecdh.encrypt(curve.oid, kdf_params.cipher, kdf_params.hash, data, R, fingerprint); + const res = await ecdh.encrypt( + curve.oid, kdf_params.cipher, kdf_params.hash, data, R, fingerprint + ); return constructParams(types, [res.V, res.C]); } default: @@ -261,7 +263,9 @@ export default { //remember "publicKey" refers to the crypto/public_key dir const rsa = new publicKey.rsa(); return rsa.generate(bits, "10001").then(function(keyObject) { - return constructParams(types, [keyObject.n, keyObject.ee, keyObject.d, keyObject.p, keyObject.q, keyObject.u]); + return constructParams( + types, [keyObject.n, keyObject.ee, keyObject.d, keyObject.p, keyObject.q, keyObject.u] + ); }); } case 'ecdsa': @@ -269,12 +273,10 @@ export default { return publicKey.elliptic.generate(curve).then(function (keyObject) { return constructParams(types, [keyObject.oid, keyObject.Q, keyObject.d]); }); - case 'ecdh': return publicKey.elliptic.generate(curve).then(function (keyObject) { return constructParams(types, [keyObject.oid, keyObject.Q, [keyObject.hash, keyObject.cipher], keyObject.d]); }); - default: throw new Error('Unsupported algorithm for key generation.'); } diff --git a/src/crypto/pkcs1.js b/src/crypto/pkcs1.js index 1acb15d4..f1c194e6 100644 --- a/src/crypto/pkcs1.js +++ b/src/crypto/pkcs1.js @@ -17,18 +17,15 @@ /** * PKCS1 encoding - * @requires crypto/crypto - * @requires crypto/hash - * @requires crypto/public_key/jsbn * @requires crypto/random + * @requires crypto/hash * @requires util * @module crypto/pkcs1 */ -import random from './random.js'; -import util from '../util.js'; -import BigInteger from './public_key/jsbn.js'; +import random from './random'; import hash from './hash'; +import util from '../util'; /** * ASN1 object identifiers for hashes (See {@link https://tools.ietf.org/html/rfc4880#section-5.2.2}) @@ -98,6 +95,7 @@ export default { * @return {String} message, an octet string */ decode: function(EM) { + // FIXME // leading zeros truncated by jsbn if (EM.charCodeAt(0) !== 0) { EM = String.fromCharCode(0) + EM; @@ -158,7 +156,7 @@ export default { PS + String.fromCharCode(0x00) + T; - return new BigInteger(util.hexstrdump(EM), 16); + return util.hexstrdump(EM); } } }; diff --git a/src/crypto/public_key/rsa.js b/src/crypto/public_key/rsa.js index 834220e3..c0fc860b 100644 --- a/src/crypto/public_key/rsa.js +++ b/src/crypto/public_key/rsa.js @@ -18,16 +18,20 @@ // RSA implementation /** + * @requires asmcrypto.js * @requires crypto/public_key/jsbn * @requires crypto/random + * @requires config * @requires util * @module crypto/public_key/rsa */ -import BigInteger from './jsbn.js'; -import util from '../../util.js'; -import random from '../random.js'; + +import { RSA_RAW } from 'asmcrypto.js'; +import BigInteger from './jsbn'; +import random from '../random'; import config from '../../config'; +import util from '../../util'; function SecureRandom() { function nextBytes(byteArray) { @@ -58,20 +62,13 @@ function unblind(t, n) { export default function RSA() { /** * This function uses jsbn Big Num library to decrypt RSA - * @param m - * message - * @param n - * RSA public modulus n as BigInteger - * @param e - * RSA public exponent as BigInteger - * @param d - * RSA d as BigInteger - * @param p - * RSA p as BigInteger - * @param q - * RSA q as BigInteger - * @param u - * RSA u as BigInteger + * @param m message + * @param n RSA public modulus n as BigInteger + * @param e RSA public exponent as BigInteger + * @param d RSA d as BigInteger + * @param p RSA p as BigInteger + * @param q RSA q as BigInteger + * @param u RSA u as BigInteger * @return {BigInteger} The decrypted value of the message */ function decrypt(m, n, e, d, p, q, u) { @@ -91,6 +88,7 @@ export default function RSA() { t = t.multiply(u).mod(q); } t = t.multiply(p).add(xp); + if (config.rsa_blinding) { t = unblind(t, n); } @@ -111,10 +109,12 @@ export default function RSA() { /* Sign and Verify */ function sign(m, d, n) { return m.modPow(d, n); +// return RSA_RAW.sign(m, [n, e, d]); } function verify(x, e, n) { return x.modPowInt(e, n); +// return RSA_RAW.verify(x, [n, e]); } // "empty" RSA key constructor diff --git a/src/crypto/signature.js b/src/crypto/signature.js index be045470..96a9061c 100644 --- a/src/crypto/signature.js +++ b/src/crypto/signature.js @@ -1,13 +1,15 @@ /** - * @requires util - * @requires crypto/hash - * @requires crypto/pkcs1 + * @requires asmcrypto.js * @requires crypto/public_key + * @requires crypto/pkcs1 + * @requires util * @module crypto/signature */ -import util from '../util'; + +import { RSA_RAW } from 'asmcrypto.js' import publicKey from './public_key'; -import pkcs1 from './pkcs1.js'; +import pkcs1 from './pkcs1'; +import util from '../util'; export default { /** @@ -35,14 +37,13 @@ export default { // RSA Encrypt-Only [HAC] case 3: { // RSA Sign-Only [HAC] - const rsa = new publicKey.rsa(); - const n = publickey_MPIs[0].toBigInteger(); + const n = util.str2Uint8Array(publickey_MPIs[0].toBytes()); const k = publickey_MPIs[0].byteLength(); - const e = publickey_MPIs[1].toBigInteger(); - m = msg_MPIs[0].toBigInteger(); - const EM = rsa.verify(m, e, n); + const e = util.str2Uint8Array(publickey_MPIs[1].toBytes()); + m = msg_MPIs[0].write().slice(2); // FIXME + const EM = RSA_RAW.verify(m, [n, e]); const EM2 = pkcs1.emsa.encode(hash_algo, data, k); - return EM.compareTo(EM2) === 0; + return util.hexidump(EM) === EM2; } case 16: { // Elgamal (Encrypt-Only) [ELGAMAL] [HAC] @@ -88,13 +89,14 @@ export default { /** * Create a signature on data using the specified algorithm - * @param {module:enums.hash} hash_algo hash Algorithm to use (See {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4}) * @param {module:enums.publicKey} algo Asymmetric cipher algorithm to use (See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1}) + * @param {module:enums.hash} hash_algo hash Algorithm to use (See {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4}) * @param {Array} keyIntegers Public followed by Private key multiprecision algorithm-specific parameters * @param {Uint8Array} data Data to be signed * @return {Array} */ - sign: async function(hash_algo, algo, keyIntegers, data) { + sign: async function(algo, hash_algo, keyIntegers, data) { + data = util.Uint8Array2str(data); let m; @@ -109,15 +111,14 @@ export default { // RSA Encrypt-Only [HAC] case 3: { // RSA Sign-Only [HAC] - const rsa = new publicKey.rsa(); - d = keyIntegers[2].toBigInteger(); - const n = keyIntegers[0].toBigInteger(); - m = pkcs1.emsa.encode( - hash_algo, - data, keyIntegers[0].byteLength() + const n = util.str2Uint8Array(keyIntegers[0].toBytes()); + const k = keyIntegers[0].byteLength(); + const e = util.str2Uint8Array(keyIntegers[1].toBytes()); + d = util.str2Uint8Array(keyIntegers[2].toBytes()); + m = util.hex2Uint8Array( + '00'+pkcs1.emsa.encode(hash_algo, data, k) // FIXME ); - return util.str2Uint8Array(rsa.sign(m, d, n).toMPI()); - } + return util.Uint8Array2MPI(RSA_RAW.sign(m, [n, e, d])); case 17: { // DSA (Digital Signature Algorithm) [FIPS186] [HAC] const dsa = new publicKey.dsa(); @@ -150,10 +151,10 @@ export default { d = keyIntegers[2].toBigInteger(); m = data; signature = await eddsa.sign(curve.oid, hash_algo, m, d); - return new Uint8Array([].concat( + return util.concatUint8Array([ util.Uint8Array2MPI(signature.R.toArrayLike(Uint8Array, 'le', 32)), util.Uint8Array2MPI(signature.S.toArrayLike(Uint8Array, 'le', 32)) - )); + ]); } default: throw new Error('Invalid signature algorithm.'); diff --git a/src/packet/signature.js b/src/packet/signature.js index d1fa0f5b..015672f0 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -245,8 +245,7 @@ Signature.prototype.sign = async function (key, data) { this.signedHashValue = hash.subarray(0, 2); this.signature = await crypto.signature.sign( - hashAlgorithm, - publicKeyAlgorithm, key.params, toHash + publicKeyAlgorithm, hashAlgorithm, key.params, toHash ); }; @@ -652,8 +651,7 @@ Signature.prototype.verify = async function (key, data) { } this.verified = await crypto.signature.verify( - publicKeyAlgorithm, - hashAlgorithm, mpi, key.params, + publicKeyAlgorithm, hashAlgorithm, mpi, key.params, util.concatUint8Array([bytes, this.signatureData, trailer]) ); diff --git a/src/util.js b/src/util.js index 12fc4e69..8af4d619 100644 --- a/src/util.js +++ b/src/util.js @@ -326,11 +326,12 @@ export default { * Convert a Uint8Array to an MPI array. * @function module:util.Uint8Array2MPI * @param {Uint8Array} bin An array of (binary) integers to convert - * @return {Array} MPI-formatted array + * @return {Uint8Array} MPI-formatted Uint8Array */ Uint8Array2MPI: function (bin) { const size = (bin.length - 1) * 8 + this.nbits(bin[0]); - return [(size & 0xFF00) >> 8, size & 0xFF].concat(Array.from(bin)); + const prefix = Uint8Array.from([(size & 0xFF00) >> 8, size & 0xFF]); + return this.concatUint8Array([prefix, bin]); }, /** diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index 69556544..5be914fe 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -8,6 +8,7 @@ const expect = chai.expect; describe('API functional testing', function() { const util = openpgp.util; + const crypto = openpgp.crypto; const RSApubMPIstrs = [ new Uint8Array([0x08,0x00,0xac,0x15,0xb3,0xd6,0xd2,0x0f,0xf0,0x7a,0xdd,0x21,0xb7, 0xbf,0x61,0xfa,0xca,0x93,0x86,0xc8,0x55,0x5a,0x4b,0xa6,0xa4,0x1a, @@ -235,14 +236,15 @@ describe('API functional testing', function() { describe('Sign and verify', function () { it('RSA', function () { + // FIXME //Originally we passed public and secret MPI separately, now they are joined. Is this what we want to do long term? // RSA - return openpgp.crypto.signature.sign( - 2, 1, RSApubMPIs.concat(RSAsecMPIs), data + return crypto.signature.sign( + 1, 2, RSApubMPIs.concat(RSAsecMPIs), data ).then(RSAsignedData => { const RSAsignedDataMPI = new openpgp.MPI(); RSAsignedDataMPI.read(RSAsignedData); - return openpgp.crypto.signature.verify( + return crypto.signature.verify( 1, 2, [RSAsignedDataMPI], RSApubMPIs, data ).then(success => { return expect(success).to.be.true; @@ -252,8 +254,8 @@ describe('API functional testing', function() { it('DSA', function () { // DSA - return openpgp.crypto.signature.sign( - 2, 17, DSApubMPIs.concat(DSAsecMPIs), data + return crypto.signature.sign( + 17, 2, DSApubMPIs.concat(DSAsecMPIs), data ).then(DSAsignedData => { DSAsignedData = util.Uint8Array2str(DSAsignedData); const DSAmsgMPIs = []; @@ -261,7 +263,7 @@ describe('API functional testing', function() { DSAmsgMPIs[1] = new openpgp.MPI(); DSAmsgMPIs[0].read(DSAsignedData.substring(0,34)); DSAmsgMPIs[1].read(DSAsignedData.substring(34,68)); - return openpgp.crypto.signature.verify( + return crypto.signature.verify( 17, 2, DSAmsgMPIs, DSApubMPIs, data ).then(success => { return expect(success).to.be.true; @@ -278,9 +280,9 @@ describe('API functional testing', function() { function testCFB(plaintext, resync) { symmAlgos.forEach(function(algo) { - const symmKey = openpgp.crypto.generateSessionKey(algo); - const symmencData = openpgp.crypto.cfb.encrypt(openpgp.crypto.getPrefixRandom(algo), algo, util.str2Uint8Array(plaintext), symmKey, resync); - const text = util.Uint8Array2str(openpgp.crypto.cfb.decrypt(algo, symmKey, symmencData, resync)); + const symmKey = crypto.generateSessionKey(algo); + const symmencData = crypto.cfb.encrypt(crypto.getPrefixRandom(algo), algo, util.str2Uint8Array(plaintext), symmKey, resync); + const text = util.Uint8Array2str(crypto.cfb.decrypt(algo, symmKey, symmencData, resync)); expect(text).to.equal(plaintext); }); } @@ -288,16 +290,17 @@ describe('API functional testing', function() { function testAESCFB(plaintext) { symmAlgos.forEach(function(algo) { if(algo.substr(0,3) === 'aes') { - const symmKey = openpgp.crypto.generateSessionKey(algo); - const rndm = openpgp.crypto.getPrefixRandom(algo); + const symmKey = crypto.generateSessionKey(algo); + const rndm = crypto.getPrefixRandom(algo); + const repeat = new Uint8Array([rndm[rndm.length - 2], rndm[rndm.length - 1]]); const prefix = util.concatUint8Array([rndm, repeat]); - const symmencData = openpgp.crypto.cfb.encrypt(rndm, algo, util.str2Uint8Array(plaintext), symmKey, false); + const symmencData = crypto.cfb.encrypt(rndm, algo, util.str2Uint8Array(plaintext), symmKey, false); const symmencData2 = asmCrypto.AES_CFB.encrypt(util.concatUint8Array([prefix, util.str2Uint8Array(plaintext)]), symmKey); let decrypted = asmCrypto.AES_CFB.decrypt(symmencData, symmKey); - decrypted = decrypted.subarray(openpgp.crypto.cipher[algo].blockSize + 2, decrypted.length); + decrypted = decrypted.subarray(crypto.cipher[algo].blockSize + 2, decrypted.length); expect(util.Uint8Array2str(symmencData)).to.equal(util.Uint8Array2str(symmencData2)); const text = util.Uint8Array2str(decrypted); @@ -309,16 +312,17 @@ describe('API functional testing', function() { function testAESGCM(plaintext) { symmAlgos.forEach(function(algo) { if(algo.substr(0,3) === 'aes') { - it(algo, function(done) { - const key = openpgp.crypto.generateSessionKey(algo); - const iv = openpgp.crypto.random.getRandomValues(new Uint8Array(openpgp.crypto.gcm.ivLength)); + it(algo, function() { + const key = crypto.generateSessionKey(algo); + const iv = crypto.random.getRandomValues(new Uint8Array(crypto.gcm.ivLength)); - openpgp.crypto.gcm.encrypt(algo, util.str2Uint8Array(plaintext), key, iv).then(function(ciphertext) { - return openpgp.crypto.gcm.decrypt(algo, ciphertext, key, iv); + return crypto.gcm.encrypt( + algo, util.str2Uint8Array(plaintext), key, iv + ).then(function(ciphertext) { + return crypto.gcm.decrypt(algo, ciphertext, key, iv); }).then(function(decrypted) { const decryptedStr = util.Uint8Array2str(decrypted); expect(decryptedStr).to.equal(plaintext); - done(); }); }); } @@ -372,40 +376,43 @@ describe('API functional testing', function() { testAESGCM("12345678901234567890123456789012345678901234567890"); }); - it('Asymmetric using RSA with eme_pkcs1 padding', function (done) { - const symmKey = util.Uint8Array2str(openpgp.crypto.generateSessionKey('aes256')); + it('Asymmetric using RSA with eme_pkcs1 padding', function () { + const symmKey = util.Uint8Array2str(crypto.generateSessionKey('aes256')); const RSAUnencryptedData = new openpgp.MPI(); - RSAUnencryptedData.fromBytes(openpgp.crypto.pkcs1.eme.encode(symmKey, RSApubMPIs[0].byteLength())); - openpgp.crypto.publicKeyEncrypt( + RSAUnencryptedData.fromBytes(crypto.pkcs1.eme.encode(symmKey, RSApubMPIs[0].byteLength())); + return crypto.publicKeyEncrypt( "rsa_encrypt_sign", RSApubMPIs, RSAUnencryptedData ).then(RSAEncryptedData => { - openpgp.crypto.publicKeyDecrypt("rsa_encrypt_sign", RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData).then(data => { + return crypto.publicKeyDecrypt( + "rsa_encrypt_sign", RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData + ).then(data => { data = data.write(); data = util.Uint8Array2str(data.subarray(2, data.length)); - const result = openpgp.crypto.pkcs1.eme.decode(data, RSApubMPIs[0].byteLength()); + const result = crypto.pkcs1.eme.decode(data, RSApubMPIs[0].byteLength()); expect(result).to.equal(symmKey); - done(); }); }); }); - it('Asymmetric using Elgamal with eme_pkcs1 padding', function (done) { - const symmKey = util.Uint8Array2str(openpgp.crypto.generateSessionKey('aes256')); + it('Asymmetric using Elgamal with eme_pkcs1 padding', function () { + const symmKey = util.Uint8Array2str(crypto.generateSessionKey('aes256')); const ElgamalUnencryptedData = new openpgp.MPI(); - ElgamalUnencryptedData.fromBytes(openpgp.crypto.pkcs1.eme.encode(symmKey, ElgamalpubMPIs[0].byteLength())); - openpgp.crypto.publicKeyEncrypt( + ElgamalUnencryptedData.fromBytes(crypto.pkcs1.eme.encode(symmKey, ElgamalpubMPIs[0].byteLength())); + + return crypto.publicKeyEncrypt( "elgamal", ElgamalpubMPIs, ElgamalUnencryptedData ).then(ElgamalEncryptedData => { - const data = openpgp.crypto.publicKeyDecrypt("elgamal", ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData).then(data => { + return crypto.publicKeyDecrypt( + "elgamal", ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData + ).then(data => { data = data.write(); data = util.Uint8Array2str(data.subarray(2, data.length)); - const result = openpgp.crypto.pkcs1.eme.decode(data, ElgamalpubMPIs[0].byteLength()); + const result = crypto.pkcs1.eme.decode(data, ElgamalpubMPIs[0].byteLength()); expect(result).to.equal(symmKey); - done(); }); }); }); diff --git a/test/general/x25519.js b/test/general/x25519.js index 53f4341f..7ed5f337 100644 --- a/test/general/x25519.js +++ b/test/general/x25519.js @@ -318,7 +318,7 @@ describe('X25519 Cryptography', function () { describe('Ed25519 Test Vectors from RFC8032', function () { // https://tools.ietf.org/html/rfc8032#section-7.1 - const crypto = openpgp.crypto.signature; + const signature = openpgp.crypto.signature; const curve = openpgp.crypto.publicKey.elliptic.get('ed25519'); const util = openpgp.util; function testVector(vector) { @@ -336,12 +336,12 @@ describe('X25519 Cryptography', function () { new openpgp.MPI(util.Uint8Array2str(util.hex2Uint8Array(vector.SIGNATURE.S).reverse())) ]; return Promise.all([ - crypto.sign(undefined, 22, keyIntegers, data).then(signature => { - const len = ((signature[0] << 8 | signature[1]) + 7) / 8; - expect(util.hex2Uint8Array(vector.SIGNATURE.R)).to.deep.eq(signature.slice(2, 2 + len)); - expect(util.hex2Uint8Array(vector.SIGNATURE.S)).to.deep.eq(signature.slice(4 + len)); + signature.sign(22, undefined, keyIntegers, data).then(signed => { + const len = ((signed[0] << 8| signed[1]) + 7) / 8; + expect(util.hex2Uint8Array(vector.SIGNATURE.R)).to.deep.eq(signed.slice(2, 2 + len)); + expect(util.hex2Uint8Array(vector.SIGNATURE.S)).to.deep.eq(signed.slice(4 + len)); }), - crypto.verify(22, undefined, msg_MPIs, keyIntegers, data).then(result => { + signature.verify(22, undefined, msg_MPIs, keyIntegers, data).then(result => { expect(result).to.be.true; }) ]);