diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index 9626a693..dff3ea7a 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -36,13 +36,12 @@ import publicKey from './public_key'; import cipher from './cipher'; import random from './random'; import type_ecdh_symkey from '../type/ecdh_symkey'; -import type_kdf_params from '../type/kdf_params'; +import KDFParams from '../type/kdf_params'; import type_mpi from '../type/mpi'; -import type_oid from '../type/oid'; import enums from '../enums'; import util from '../util'; -import pkcs1 from './pkcs1'; -import pkcs5 from './pkcs5'; +import OID from '../type/oid'; +import Curve from './public_key/elliptic/curves'; function constructParams(types, data) { return types.map(function(type, i) { @@ -57,40 +56,30 @@ export default { /** * Encrypts data using specified algorithm and public key parameters. * See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} for public key algorithms. - * @param {module:enums.publicKey} algo Public key algorithm - * @param {Array} pub_params Algorithm-specific public key parameters - * @param {Uint8Array} data Data to be encrypted - * @param {Uint8Array} fingerprint Recipient fingerprint + * @param {module:enums.publicKey} algo Public key algorithm + * @param {Object} pubParams Algorithm-specific public key parameters + * @param {Uint8Array} data Data to be encrypted + * @param {Uint8Array} fingerprint Recipient fingerprint * @returns {Array} encrypted session key parameters * @async */ - publicKeyEncrypt: async function(algo, pub_params, data, fingerprint) { + publicKeyEncrypt: async function(algo, publicParams, data, fingerprint) { const types = this.getEncSessionKeyParamTypes(algo); switch (algo) { case enums.publicKey.rsaEncrypt: case enums.publicKey.rsaEncryptSign: { - const n = pub_params[0].toUint8Array(); - const e = pub_params[1].toUint8Array(); + const { n, e } = publicParams; const res = await publicKey.rsa.encrypt(data, n, e); return constructParams(types, [res]); } case enums.publicKey.elgamal: { - data = new type_mpi(await pkcs1.eme.encode(data, pub_params[0].byteLength())); - const m = await data.toBigInteger(); - const p = await pub_params[0].toBigInteger(); - const g = await pub_params[1].toBigInteger(); - const y = await pub_params[2].toBigInteger(); - const res = await publicKey.elgamal.encrypt(m, p, g, y); + const { p, g, y } = publicParams; + const res = await publicKey.elgamal.encrypt(data, p, g, y); return constructParams(types, [res.c1, res.c2]); } case enums.publicKey.ecdh: { - data = new type_mpi(pkcs5.encode(data)); - const oid = pub_params[0]; - const Q = pub_params[1].toUint8Array(); - const kdfParams = pub_params[2]; + const { oid, Q, kdfParams } = publicParams; const { publicKey: V, wrappedKey: C } = await publicKey.elliptic.ecdh.encrypt( oid, kdfParams, data, Q, fingerprint); return constructParams(types, [V, C]); @@ -104,125 +93,133 @@ export default { * Decrypts data using specified algorithm and private key parameters. * See {@link https://tools.ietf.org/html/rfc4880#section-5.5.3|RFC 4880 5.5.3} * @param {module:enums.publicKey} algo Public key algorithm - * @param {Array} key_params Algorithm-specific public, private key parameters + * @param {Object} publicKeyParams Algorithm-specific public key parameters + * @param {Object} privateKeyParams Algorithm-specific private key parameters * @param {Array} data_params encrypted session key parameters - * @param {String} fingerprint Recipient fingerprint - * @returns {String} String containing the decrypted data + * @param {Uint8Array} fingerprint Recipient fingerprint + * @returns {Uint8Array} decrypted data * @async */ - publicKeyDecrypt: async function(algo, key_params, data_params, fingerprint) { + publicKeyDecrypt: async function(algo, publicKeyParams, privateKeyParams, data_params, fingerprint) { switch (algo) { case enums.publicKey.rsaEncryptSign: case enums.publicKey.rsaEncrypt: { const c = data_params[0].toUint8Array(); - const n = key_params[0].toUint8Array(); // n = pq - const e = key_params[1].toUint8Array(); - const d = key_params[2].toUint8Array(); // de = 1 mod (p-1)(q-1) - const p = key_params[3].toUint8Array(); - const q = key_params[4].toUint8Array(); - const u = key_params[5].toUint8Array(); // p^-1 mod q + const { n, e } = publicKeyParams; + const { d, p, q, u } = privateKeyParams; return publicKey.rsa.decrypt(c, n, e, d, p, q, u); } case enums.publicKey.elgamal: { - const c1 = await data_params[0].toBigInteger(); - const c2 = await data_params[1].toBigInteger(); - const p = await key_params[0].toBigInteger(); - const x = await key_params[3].toBigInteger(); - const result = new type_mpi(await publicKey.elgamal.decrypt(c1, c2, p, x)); - return pkcs1.eme.decode(result.toUint8Array('be', p.byteLength())); + const c1 = data_params[0].toUint8Array(); + const c2 = data_params[1].toUint8Array(); + const p = publicKeyParams.p; + const x = privateKeyParams.x; + return publicKey.elgamal.decrypt(c1, c2, p, x); } case enums.publicKey.ecdh: { - const oid = key_params[0]; - const kdfParams = key_params[2]; + const { oid, Q, kdfParams } = publicKeyParams; + const { d } = privateKeyParams; const V = data_params[0].toUint8Array(); const C = data_params[1].data; - const Q = key_params[1].toUint8Array(); - const d = key_params[3].toUint8Array(); - const result = new type_mpi(await publicKey.elliptic.ecdh.decrypt( - oid, kdfParams, V, C, Q, d, fingerprint)); - return pkcs5.decode(result.toUint8Array()); + return publicKey.elliptic.ecdh.decrypt( + oid, kdfParams, V, C, Q, d, fingerprint); } default: throw new Error('Invalid public key encryption algorithm.'); } }, - /** Returns the types comprising the private key of an algorithm - * @param {module:enums.publicKey} algo The public key algorithm - * @returns {Array} The array of types + /** + * Parse public key material in binary form to get the key parameters + * @param {module:enums.publicKey} algo The key algorithm + * @param {Uint8Array} bytes The key material to parse + * @returns {Object} key parameters referenced by name + * @returns { read: Number, publicParams: Object } number of read bytes plus key parameters referenced by name */ - getPrivKeyParamTypes: function(algo) { + parsePublicKeyParams: function(algo, bytes) { + let read = 0; switch (algo) { - // Algorithm-Specific Fields for RSA secret keys: - // - multiprecision integer (MPI) of RSA secret exponent d. - // - 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. case enums.publicKey.rsaEncrypt: case enums.publicKey.rsaEncryptSign: - case enums.publicKey.rsaSign: - return [type_mpi, type_mpi, type_mpi, type_mpi]; - // Algorithm-Specific Fields for Elgamal secret keys: - // - MPI of Elgamal secret exponent x. - case enums.publicKey.elgamal: - return [type_mpi]; - // Algorithm-Specific Fields for DSA secret keys: - // - MPI of DSA secret exponent x. - case enums.publicKey.dsa: - return [type_mpi]; - // Algorithm-Specific Fields for ECDSA or ECDH secret keys: - // - MPI of an integer representing the secret key. - case enums.publicKey.ecdh: - case enums.publicKey.ecdsa: - case enums.publicKey.eddsa: - return [type_mpi]; + case enums.publicKey.rsaSign: { + let read = 0; + const n = util.readMPI(bytes.subarray(read)); read += n.length + 2; + const e = util.readMPI(bytes.subarray(read)); read += e.length + 2; + return { read, publicParams: { n, e } }; + } + case enums.publicKey.dsa: { + const p = util.readMPI(bytes.subarray(read)); read += p.length + 2; + const q = util.readMPI(bytes.subarray(read)); read += q.length + 2; + const g = util.readMPI(bytes.subarray(read)); read += g.length + 2; + const y = util.readMPI(bytes.subarray(read)); read += y.length + 2; + return { read, publicParams: { p, q, g, y } }; + } + case enums.publicKey.elgamal: { + const p = util.readMPI(bytes.subarray(read)); read += p.length + 2; + const g = util.readMPI(bytes.subarray(read)); read += g.length + 2; + const y = util.readMPI(bytes.subarray(read)); read += y.length + 2; + return { read, publicParams: { p, g, y } }; + } + case enums.publicKey.ecdsa: { + const oid = new OID(); read += oid.read(bytes); + const Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2; + return { read: read, publicParams: { oid, Q } }; + } + case enums.publicKey.eddsa: { + const oid = new OID(); read += oid.read(bytes); + let Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2; + Q = util.padToLength(Q, 33); + return { read: read, publicParams: { oid, Q } }; + } + case enums.publicKey.ecdh: { + const oid = new OID(); read += oid.read(bytes); + const Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2; + const kdfParams = new KDFParams(); read += kdfParams.read(bytes.subarray(read)); + return { read: read, publicParams: { oid, Q, kdfParams } }; + } default: throw new Error('Invalid public key encryption algorithm.'); } }, - /** Returns the types comprising the public key of an algorithm - * @param {module:enums.publicKey} algo The public key algorithm - * @returns {Array} The array of types + /** + * Parse private key material in binary form to get the key parameters + * @param {module:enums.publicKey} algo The key algorithm + * @param {Uint8Array} bytes The key material to parse + * @param {Object} publicParams (ECC only) public params, needed to format some private params + * @returns { read: Number, privateParams: Object } number of read bytes plus the key parameters referenced by name */ - getPubKeyParamTypes: function(algo) { + parsePrivateKeyParams: function(algo, bytes, publicParams) { + let read = 0; switch (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. case enums.publicKey.rsaEncrypt: case enums.publicKey.rsaEncryptSign: - case enums.publicKey.rsaSign: - return [type_mpi, type_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 enums.publicKey.elgamal: - return [type_mpi, type_mpi, type_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 enums.publicKey.rsaSign: { + const d = util.readMPI(bytes.subarray(read)); read += d.length + 2; + const p = util.readMPI(bytes.subarray(read)); read += p.length + 2; + const q = util.readMPI(bytes.subarray(read)); read += q.length + 2; + const u = util.readMPI(bytes.subarray(read)); read += u.length + 2; + return { read, privateParams: { d, p, q, u } }; + } case enums.publicKey.dsa: - return [type_mpi, type_mpi, type_mpi, type_mpi]; - // Algorithm-Specific Fields for ECDSA/EdDSA public keys: - // - OID of curve; - // - MPI of EC point representing public key. + case enums.publicKey.elgamal: { + const x = util.readMPI(bytes.subarray(read)); read += x.length + 2; + return { read, privateParams: { x } }; + } case enums.publicKey.ecdsa: - case enums.publicKey.eddsa: - return [type_oid, type_mpi]; - // Algorithm-Specific Fields for ECDH public keys: - // - OID of curve; - // - MPI of EC point representing public key. - // - KDF: variable-length field containing KDF parameters. - case enums.publicKey.ecdh: - return [type_oid, type_mpi, type_kdf_params]; + case enums.publicKey.ecdh: { + const curve = new Curve(publicParams.oid); + let d = util.readMPI(bytes.subarray(read)); read += d.length + 2; + d = util.padToLength(d, curve.payloadSize); + return { read, privateParams: { d } }; + } + case enums.publicKey.eddsa: { + let seed = util.readMPI(bytes.subarray(read)); read += seed.length + 2; + seed = util.padToLength(seed, 32); + return { read, privateParams: { seed } }; + } default: throw new Error('Invalid public key encryption algorithm.'); } @@ -255,42 +252,60 @@ export default { } }, - /** Generate algorithm-specific key parameters + /** + * Convert params to MPI and serializes them in the proper order + * @param {module:enums.publicKey} algo The public key algorithm + * @param {Object} params The key parameters indexed by name + * @returns {Uint8Array} The array containing the MPIs + */ + serializeKeyParams: function(algo, params) { + const orderedParams = Object.keys(params).map(name => { + const param = params[name]; + return util.isUint8Array(param) ? util.uint8ArrayToMpi(param) : param.write(); + }); + return util.concatUint8Array(orderedParams); + }, + + /** + * Generate algorithm-specific key parameters * @param {module:enums.publicKey} algo The public key algorithm * @param {Integer} bits Bit length for RSA keys * @param {module:type/oid} oid Object identifier for ECC keys - * @returns {Array} The array of parameters + * @returns { publicParams, privateParams: {Object} } The parameters referenced by name * @async */ generateParams: function(algo, bits, oid) { - const types = [].concat(this.getPubKeyParamTypes(algo), this.getPrivKeyParamTypes(algo)); switch (algo) { case enums.publicKey.rsaEncrypt: case enums.publicKey.rsaEncryptSign: case enums.publicKey.rsaSign: { - return publicKey.rsa.generate(bits, 65537).then(function(keyObject) { - return constructParams( - types, [keyObject.n, keyObject.e, keyObject.d, keyObject.p, keyObject.q, keyObject.u] - ); - }); + return publicKey.rsa.generate(bits, 65537).then(({ n, e, d, p, q, u }) => ({ + privateParams: { d, p, q, u }, + publicParams: { n, e } + })); } + case enums.publicKey.ecdsa: + return publicKey.elliptic.generate(oid).then(({ oid, Q, secret }) => ({ + privateParams: { d: secret }, + publicParams: { oid: new OID(oid), Q } + })); + case enums.publicKey.eddsa: + return publicKey.elliptic.generate(oid).then(({ oid, Q, secret }) => ({ + privateParams: { seed: secret }, + publicParams: { oid: new OID(oid), Q } + })); + case enums.publicKey.ecdh: + return publicKey.elliptic.generate(oid).then(({ oid, Q, secret, hash, cipher }) => ({ + privateParams: { d: secret }, + publicParams: { + oid: new OID(oid), + Q, + kdfParams: new KDFParams({ hash, cipher }) + } + })); case enums.publicKey.dsa: case enums.publicKey.elgamal: throw new Error('Unsupported algorithm for key generation.'); - case enums.publicKey.ecdsa: - case enums.publicKey.eddsa: - return publicKey.elliptic.generate(oid).then(function (keyObject) { - return constructParams(types, [keyObject.oid, keyObject.Q, keyObject.d]); - }); - case enums.publicKey.ecdh: - return publicKey.elliptic.generate(oid).then(function (keyObject) { - return constructParams(types, [ - keyObject.oid, - keyObject.Q, - { hash: keyObject.hash, cipher: keyObject.cipher }, - keyObject.d - ]); - }); default: throw new Error('Invalid public key algorithm.'); } @@ -299,65 +314,43 @@ export default { /** * Validate algorithm-specific key parameters * @param {module:enums.publicKey} algo The public key algorithm - * @param {Array} params The array of parameters - * @returns {Promise} whether the parameters are valid + * @param {Object} publicParams Algorithm-specific public key parameters + * @param {Object} privateParams Algorithm-specific private key parameters + * @returns {Promise} whether the parameters are valid * @async */ - validateParams: async function(algo, params) { + validateParams: async function(algo, publicParams, privateParams) { + if (!publicParams || !privateParams) { + throw new Error('Missing key parameters'); + } switch (algo) { case enums.publicKey.rsaEncrypt: case enums.publicKey.rsaEncryptSign: case enums.publicKey.rsaSign: { - if (params.length < 6) { - throw new Error('Missing key parameters'); - } - const n = params[0].toUint8Array(); - const e = params[1].toUint8Array(); - const d = params[2].toUint8Array(); - const p = params[3].toUint8Array(); - const q = params[4].toUint8Array(); - const u = params[5].toUint8Array(); + const { n, e } = publicParams; + const { d, p, q, u } = privateParams; return publicKey.rsa.validateParams(n, e, d, p, q, u); } case enums.publicKey.dsa: { - if (params.length < 5) { - throw new Error('Missing key parameters'); - } - const p = params[0].toUint8Array(); - const q = params[1].toUint8Array(); - const g = params[2].toUint8Array(); - const y = params[3].toUint8Array(); - const x = params[4].toUint8Array(); + const { p, q, g, y } = publicParams; + const { x } = privateParams; return publicKey.dsa.validateParams(p, q, g, y, x); } case enums.publicKey.elgamal: { - if (params.length < 4) { - throw new Error('Missing key parameters'); - } - const p = params[0].toUint8Array(); - const g = params[1].toUint8Array(); - const y = params[2].toUint8Array(); - const x = params[3].toUint8Array(); + const { p, g, y } = publicParams; + const { x } = privateParams; return publicKey.elgamal.validateParams(p, g, y, x); } case enums.publicKey.ecdsa: case enums.publicKey.ecdh: { - const expectedLen = algo === enums.publicKey.ecdh ? 3 : 2; - if (params.length < expectedLen) { - throw new Error('Missing key parameters'); - } - const algoModule = publicKey.elliptic[enums.read(enums.publicKey, algo)]; - const { oid, Q, d } = algoModule.parseParams(params); + const { oid, Q } = publicParams; + const { d } = privateParams; return algoModule.validateParams(oid, Q, d); } case enums.publicKey.eddsa: { - const expectedLen = 3; - if (params.length < expectedLen) { - throw new Error('Missing key parameters'); - } - - const { oid, Q, seed } = publicKey.elliptic.eddsa.parseParams(params); + const { oid, Q } = publicParams; + const { seed } = privateParams; return publicKey.elliptic.eddsa.validateParams(oid, Q, seed); } default: diff --git a/src/crypto/public_key/dsa.js b/src/crypto/public_key/dsa.js index 1a712a82..83dca363 100644 --- a/src/crypto/public_key/dsa.js +++ b/src/crypto/public_key/dsa.js @@ -36,16 +36,21 @@ export default { * DSA Sign function * @param {Integer} hash_algo * @param {Uint8Array} hashed - * @param {BigInteger} g - * @param {BigInteger} p - * @param {BigInteger} q - * @param {BigInteger} x - * @returns {{ r: BigInteger, s: BigInteger }} + * @param {Uint8Array} g + * @param {Uint8Array} p + * @param {Uint8Array} q + * @param {Uint8Array} x + * @returns {{ r: Uint8Array, s: Uint8Array }} * @async */ sign: async function(hash_algo, hashed, g, p, q, x) { const BigInteger = await util.getBigInteger(); const one = new BigInteger(1); + p = new BigInteger(p); + q = new BigInteger(q); + g = new BigInteger(g); + x = new BigInteger(x); + let k; let r; let s; @@ -87,30 +92,37 @@ export default { /** * DSA Verify function * @param {Integer} hash_algo - * @param {BigInteger} r - * @param {BigInteger} s + * @param {Uint8Array} r + * @param {Uint8Array} s * @param {Uint8Array} hashed - * @param {BigInteger} g - * @param {BigInteger} p - * @param {BigInteger} q - * @param {BigInteger} y + * @param {Uint8Array} g + * @param {Uint8Array} p + * @param {Uint8Array} q + * @param {Uint8Array} y * @returns {boolean} * @async */ verify: async function(hash_algo, r, s, hashed, g, p, q, y) { const BigInteger = await util.getBigInteger(); const zero = new BigInteger(0); + r = new BigInteger(r); + s = new BigInteger(s); + + p = new BigInteger(p); + q = new BigInteger(q); + g = new BigInteger(g); + y = new BigInteger(y); if (r.lte(zero) || r.gte(q) || s.lte(zero) || s.gte(q)) { util.printDebug("invalid DSA Signature"); - return null; + return false; } const h = new BigInteger(hashed.subarray(0, q.byteLength())).imod(q); const w = s.modInv(q); // s**-1 mod q if (w.isZero()) { util.printDebug("invalid DSA Signature"); - return null; + return false; } g = g.mod(p); diff --git a/src/crypto/public_key/elgamal.js b/src/crypto/public_key/elgamal.js index 30c7cc0a..9e611cc0 100644 --- a/src/crypto/public_key/elgamal.js +++ b/src/crypto/public_key/elgamal.js @@ -24,38 +24,55 @@ import util from '../../util'; import random from '../random'; +import pkcs1 from '../pkcs1'; export default { /** * ElGamal Encryption function - * @param {BigInteger} m - * @param {BigInteger} p - * @param {BigInteger} g - * @param {BigInteger} y - * @returns {{ c1: BigInteger, c2: BigInteger }} + * Note that in OpenPGP, the message needs to be padded with PKCS#1 (same as RSA) + * @param {Uint8Array} data to be padded and encrypted + * @param {Uint8Array} p + * @param {Uint8Array} g + * @param {Uint8Array} y + * @returns {{ c1: Uint8Array, c2: Uint8Array }} * @async */ - encrypt: async function(m, p, g, y) { + encrypt: async function(data, p, g, y) { const BigInteger = await util.getBigInteger(); - // See Section 11.5 here: https://crypto.stanford.edu/~dabo/cryptobook/BonehShoup_0_4.pdf - const k = await random.getRandomBigInteger(new BigInteger(0), p); // returns in [0, p-1] + p = new BigInteger(p); + g = new BigInteger(g); + y = new BigInteger(y); + + const padded = await pkcs1.eme.encode(data, p.byteLength()); + const m = new BigInteger(padded); + + // OpenPGP uses a "special" version of ElGamal where g is generator of the full group Z/pZ* + // hence g has order p-1, and to avoid that k = 0 mod p-1, we need to pick k in [1, p-2] + const k = await random.getRandomBigInteger(new BigInteger(1), p.dec()); return { - c1: g.modExp(k, p), - c2: y.modExp(k, p).imul(m).imod(p) + c1: g.modExp(k, p).toUint8Array(), + c2: y.modExp(k, p).imul(m).imod(p).toUint8Array() }; }, /** * ElGamal Encryption function - * @param {BigInteger} c1 - * @param {BigInteger} c2 - * @param {BigInteger} p - * @param {BigInteger} x - * @returns BigInteger + * @param {Uint8Array} c1 + * @param {Uint8Array} c2 + * @param {Uint8Array} p + * @param {Uint8Array} x + * @returns {Uint8Array} unpadded message * @async */ decrypt: async function(c1, c2, p, x) { - return c1.modExp(x, p).modInv(p).imul(c2).imod(p); + const BigInteger = await util.getBigInteger(); + c1 = new BigInteger(c1); + c2 = new BigInteger(c2); + p = new BigInteger(p); + x = new BigInteger(x); + + const padded = c1.modExp(x, p).modInv(p).imul(c2).imod(p); + return pkcs1.eme.decode(padded.toUint8Array('be', p.byteLength())); }, /** diff --git a/src/crypto/public_key/elliptic/curves.js b/src/crypto/public_key/elliptic/curves.js index 40fc2cc4..f99c9a6a 100644 --- a/src/crypto/public_key/elliptic/curves.js +++ b/src/crypto/public_key/elliptic/curves.js @@ -214,10 +214,12 @@ async function generate(curve) { curve = new Curve(curve); const keyPair = await curve.genKeyPair(); + const Q = new BigInteger(keyPair.publicKey).toUint8Array(); + const secret = new BigInteger(keyPair.privateKey).toUint8Array('be', curve.payloadSize); return { oid: curve.oid, - Q: new BigInteger(keyPair.publicKey), - d: new BigInteger(keyPair.privateKey), + Q, + secret, hash: curve.hash, cipher: curve.cipher }; diff --git a/src/crypto/public_key/elliptic/ecdh.js b/src/crypto/public_key/elliptic/ecdh.js index 990d318d..3f4a2be0 100644 --- a/src/crypto/public_key/elliptic/ecdh.js +++ b/src/crypto/public_key/elliptic/ecdh.js @@ -37,6 +37,8 @@ import random from '../../random'; import hash from '../../hash'; import enums from '../../../enums'; import util from '../../../util'; +import pkcs5 from '../../pkcs5'; +import MPI from '../../../type/mpi'; import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey'; const webCrypto = util.getWebCrypto(); @@ -65,31 +67,6 @@ function buildEcdhParam(public_algo, oid, kdfParams, fingerprint) { ]); } -/** - * Parses MPI params and returns them as byte arrays of fixed length - * @param {Array} params key parameters - * @returns {Object} parameters in the form - * { oid, kdfParams, d: Uint8Array, Q: Uint8Array } - */ -function parseParams(params) { - if (params.length < 3 || params.length > 4) { - throw new Error('Unexpected number of parameters'); - } - - const oid = params[0]; - const curve = new Curve(oid); - const parsedParams = { oid }; - // The public point never has leading zeros, as it is prefixed by 0x40 or 0x04 - parsedParams.Q = params[1].toUint8Array(); - parsedParams.kdfParams = params[2]; - - if (params.length === 4) { - parsedParams.d = params[3].toUint8Array('be', curve.payloadSize); - } - - return parsedParams; -} - // Key Derivation Function (RFC 6637) async function kdf(hash_algo, X, length, param, stripLeading = false, stripTrailing = false) { // Note: X is little endian for Curve25519, big-endian for all others. @@ -151,13 +128,15 @@ async function genPublicEphemeralKey(curve, Q) { * * @param {module:type/oid} oid Elliptic curve object identifier * @param {module:type/kdf_params} kdfParams KDF params including cipher and algorithm to use - * @param {module:type/mpi} m Value derived from session key (RFC 6637) + * @param {Uint8Array} data Unpadded session key data * @param {Uint8Array} Q Recipient public key * @param {Uint8Array} fingerprint Recipient fingerprint * @returns {Promise<{publicKey: Uint8Array, wrappedKey: Uint8Array}>} * @async */ -async function encrypt(oid, kdfParams, m, Q, fingerprint) { +async function encrypt(oid, kdfParams, data, Q, fingerprint) { + const m = new MPI(pkcs5.encode(data)); + const curve = new Curve(oid); const { publicKey, sharedKey } = await genPublicEphemeralKey(curve, Q); const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint); @@ -214,12 +193,10 @@ async function genPrivateEphemeralKey(curve, V, Q, d) { * @param {Uint8Array} Q Recipient public key * @param {Uint8Array} d Recipient private key * @param {Uint8Array} fingerprint Recipient fingerprint - * @returns {Promise} Value derived from session key + * @returns {Promise} Value derived from session key * @async */ async function decrypt(oid, kdfParams, V, C, Q, d, fingerprint) { - const BigInteger = await util.getBigInteger(); - const curve = new Curve(oid); const { sharedKey } = await genPrivateEphemeralKey(curve, V, Q, d); const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint); @@ -229,7 +206,7 @@ async function decrypt(oid, kdfParams, V, C, Q, d, fingerprint) { try { // Work around old go crypto bug and old OpenPGP.js bug, respectively. const Z = await kdf(kdfParams.hash, sharedKey, cipher[cipher_algo].keySize, param, i === 1, i === 2); - return new BigInteger(aes_kw.unwrap(Z, C)); + return pkcs5.decode(aes_kw.unwrap(Z, C)); } catch (e) { err = e; } @@ -411,4 +388,4 @@ async function nodePublicEphemeralKey(curve, Q) { return { publicKey, sharedKey }; } -export default { encrypt, decrypt, genPublicEphemeralKey, genPrivateEphemeralKey, buildEcdhParam, kdf, webPublicEphemeralKey, webPrivateEphemeralKey, ellipticPublicEphemeralKey, ellipticPrivateEphemeralKey, nodePublicEphemeralKey, nodePrivateEphemeralKey, validateParams, parseParams }; +export default { encrypt, decrypt, genPublicEphemeralKey, genPrivateEphemeralKey, buildEcdhParam, kdf, webPublicEphemeralKey, webPrivateEphemeralKey, ellipticPublicEphemeralKey, ellipticPrivateEphemeralKey, nodePublicEphemeralKey, nodePrivateEphemeralKey, validateParams }; diff --git a/src/crypto/public_key/elliptic/ecdsa.js b/src/crypto/public_key/elliptic/ecdsa.js index 845450d9..6efeebc8 100644 --- a/src/crypto/public_key/elliptic/ecdsa.js +++ b/src/crypto/public_key/elliptic/ecdsa.js @@ -152,31 +152,7 @@ async function validateParams(oid, Q, d) { } } -/** - * Parses MPI params and returns them as byte arrays of fixed length - * @param {Array} params key parameters - * @returns {Object} parameters in the form - * { oid, d: Uint8Array, Q: Uint8Array } - */ -function parseParams(params) { - if (params.length < 2 || params.length > 3) { - throw new Error('Unexpected number of parameters'); - } - - const oid = params[0]; - const curve = new Curve(oid); - const parsedParams = { oid }; - // The public point never has leading zeros, as it is prefixed by 0x40 or 0x04 - parsedParams.Q = params[1].toUint8Array(); - if (params.length === 3) { - parsedParams.d = params[2].toUint8Array('be', curve.payloadSize); - } - - return parsedParams; -} - - -export default { sign, verify, ellipticVerify, ellipticSign, validateParams, parseParams }; +export default { sign, verify, ellipticVerify, ellipticSign, validateParams }; ////////////////////////// diff --git a/src/crypto/public_key/elliptic/eddsa.js b/src/crypto/public_key/elliptic/eddsa.js index 7dc158ec..01cde9ee 100644 --- a/src/crypto/public_key/elliptic/eddsa.js +++ b/src/crypto/public_key/elliptic/eddsa.js @@ -92,28 +92,4 @@ async function validateParams(oid, Q, k) { return util.equalsUint8Array(Q, dG); } -/** - * Parses MPI params and returns them as byte arrays of fixed length - * @param {Array} params key parameters - * @returns {Object} parameters in the form - * { oid, seed: Uint8Array, Q: Uint8Array } - */ -function parseParams(params) { - if (params.length < 2 || params.length > 3) { - throw new Error('Unexpected number of parameters'); - } - - const parsedParams = { - oid: params[0], - Q: params[1].toUint8Array('be', 33) - }; - - if (params.length === 3) { - parsedParams.seed = params[2].toUint8Array('be', 32); - } - - return parsedParams; -} - - -export default { sign, verify, validateParams, parseParams }; +export default { sign, verify, validateParams }; diff --git a/src/crypto/public_key/rsa.js b/src/crypto/public_key/rsa.js index b3b10790..daa55cf9 100644 --- a/src/crypto/public_key/rsa.js +++ b/src/crypto/public_key/rsa.js @@ -171,14 +171,13 @@ export default { * @param {Integer} bits RSA bit length * @param {Integer} e RSA public exponent * @returns {{n, e, d, - * p, q ,u: BigInteger}} RSA public modulus, RSA public exponent, RSA private exponent, - * RSA private prime p, RSA private prime q, u = q ** 1 mod p + * p, q ,u: Uint8Array}} RSA public modulus, RSA public exponent, RSA private exponent, + * RSA private prime p, RSA private prime q, u = p ** -1 mod q * @async */ generate: async function(bits, e) { const BigInteger = await util.getBigInteger(); - let key; e = new BigInteger(e); // Native RSA keygen using Web Crypto @@ -221,17 +220,17 @@ export default { if (jwk instanceof ArrayBuffer) { jwk = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(jwk))); } - // map JWK parameters to BN - key = {}; - key.n = new BigInteger(util.b64ToUint8Array(jwk.n)); - key.e = e; - key.d = new BigInteger(util.b64ToUint8Array(jwk.d)); - // switch p and q - key.p = new BigInteger(util.b64ToUint8Array(jwk.q)); - key.q = new BigInteger(util.b64ToUint8Array(jwk.p)); - // Since p and q are switched in places, we could keep u - key.u = new BigInteger(util.b64ToUint8Array(jwk.qi)); - return key; + // map JWK parameters to corresponding OpenPGP names + return { + n: util.b64ToUint8Array(jwk.n), + e: e.toUint8Array(), + d: util.b64ToUint8Array(jwk.d), + // switch p and q + p: util.b64ToUint8Array(jwk.q), + q: util.b64ToUint8Array(jwk.p), + // Since p and q are switched in places, u is the inverse of jwk.q + u: util.b64ToUint8Array(jwk.qi) + }; } else if (util.getNodeCrypto() && nodeCrypto.generateKeyPair && RSAPrivateKey) { const opts = { modulusLength: bits, @@ -246,19 +245,20 @@ export default { resolve(RSAPrivateKey.decode(der, 'der')); } })); - /** PGP spec differs from DER spec, DER: `(inverse of q) mod p`, PGP: `(inverse of p) mod q`. + /** + * OpenPGP spec differs from DER spec, DER: `u = (inverse of q) mod p`, OpenPGP: `u = (inverse of p) mod q`. * @link https://tools.ietf.org/html/rfc3447#section-3.2 * @link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-08#section-5.6.1 */ return { - n: prv.modulus, - e: prv.publicExponent, - d: prv.privateExponent, + n: prv.modulus.toArrayLike(Uint8Array), + e: prv.publicExponent.toArrayLike(Uint8Array), + d: prv.privateExponent.toArrayLike(Uint8Array), // switch p and q - p: prv.prime2, - q: prv.prime1, - // Since p and q are switched in places, we could keep u - u: prv.coefficient // PGP type of u + p: prv.prime2.toArrayLike(Uint8Array), + q: prv.prime1.toArrayLike(Uint8Array), + // Since p and q are switched in places, we can keep u as defined by DER + u: prv.coefficient.toArrayLike(Uint8Array) }; } @@ -271,17 +271,16 @@ export default { if (q.lt(p)) { [p, q] = [q, p]; } - const phi = p.dec().imul(q.dec()); return { - n: p.mul(q), - e, - d: e.modInv(phi), - p: p, - q: q, + n: p.mul(q).toUint8Array(), + e: e.toUint8Array(), + d: e.modInv(phi).toUint8Array(), + p: p.toUint8Array(), + q: q.toUint8Array(), // dp: d.mod(p.subn(1)), // dq: d.mod(q.subn(1)), - u: p.modInv(q) + u: p.modInv(q).toUint8Array() }; }, diff --git a/src/crypto/signature.js b/src/crypto/signature.js index eb8f763c..1681c1a2 100644 --- a/src/crypto/signature.js +++ b/src/crypto/signature.js @@ -18,45 +18,37 @@ export default { * See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} * and {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4} * for public key and hash algorithms. - * @param {module:enums.publicKey} algo Public key algorithm - * @param {module:enums.hash} hash_algo Hash algorithm - * @param {Array} msg_MPIs Algorithm-specific signature parameters - * @param {Array} pub_MPIs Algorithm-specific public key parameters - * @param {Uint8Array} data Data for which the signature was created - * @param {Uint8Array} hashed The hashed data - * @returns {Boolean} True if signature is valid + * @param {module:enums.publicKey} algo Public key algorithm + * @param {module:enums.hash} hash_algo Hash algorithm + * @param {Array} msg_MPIs Algorithm-specific signature parameters + * @param {Object} publicParams Algorithm-specific public key parameters + * @param {Uint8Array} data Data for which the signature was created + * @param {Uint8Array} hashed The hashed data + * @returns {Boolean} True if signature is valid * @async */ - verify: async function(algo, hash_algo, msg_MPIs, pub_MPIs, data, hashed) { - const types = crypto.getPubKeyParamTypes(algo); - if (pub_MPIs.length < types.length) { - throw new Error('Missing public key parameters'); - } + verify: async function(algo, hash_algo, msg_MPIs, publicParams, data, hashed) { switch (algo) { case enums.publicKey.rsaEncryptSign: case enums.publicKey.rsaEncrypt: case enums.publicKey.rsaSign: { - const n = pub_MPIs[0].toUint8Array(); - const e = pub_MPIs[1].toUint8Array(); + const { n, e } = publicParams; const m = msg_MPIs[0].toUint8Array('be', n.length); return publicKey.rsa.verify(hash_algo, data, m, n, e, hashed); } case enums.publicKey.dsa: { - const r = await msg_MPIs[0].toBigInteger(); - const s = await msg_MPIs[1].toBigInteger(); - const p = await pub_MPIs[0].toBigInteger(); - const q = await pub_MPIs[1].toBigInteger(); - const g = await pub_MPIs[2].toBigInteger(); - const y = await pub_MPIs[3].toBigInteger(); + const r = await msg_MPIs[0].toUint8Array(); + const s = await msg_MPIs[1].toUint8Array(); + const { g, p, q, y } = publicParams; return publicKey.dsa.verify(hash_algo, r, s, hashed, g, p, q, y); } case enums.publicKey.ecdsa: { - const { oid, Q } = publicKey.elliptic.ecdsa.parseParams(pub_MPIs); + const { oid, Q } = publicParams; const signature = { r: msg_MPIs[0].toUint8Array(), s: msg_MPIs[1].toUint8Array() }; return publicKey.elliptic.ecdsa.verify(oid, hash_algo, signature, data, Q, hashed); } case enums.publicKey.eddsa: { - const { oid, Q } = publicKey.elliptic.eddsa.parseParams(pub_MPIs); + const { oid, Q } = publicParams; // EdDSA signature params are expected in little-endian format const signature = { R: msg_MPIs[0].toUint8Array('le', 32), @@ -74,37 +66,31 @@ export default { * See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} * and {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4} * for public key and hash algorithms. - * @param {module:enums.publicKey} algo Public key algorithm - * @param {module:enums.hash} hash_algo Hash algorithm - * @param {Array} key_params Algorithm-specific public and private key parameters - * @param {Uint8Array} data Data to be signed - * @param {Uint8Array} hashed The hashed data - * @returns {Uint8Array} Signature + * @param {module:enums.publicKey} algo Public key algorithm + * @param {module:enums.hash} hash_algo Hash algorithm + * @param {Object} publicKeyParams Algorithm-specific public and private key parameters + * @param {Object} privateKeyParams Algorithm-specific public and private key parameters + * @param {Uint8Array} data Data to be signed + * @param {Uint8Array} hashed The hashed data + * @returns {Uint8Array} Signature * @async */ - sign: async function(algo, hash_algo, key_params, data, hashed) { - const types = [].concat(crypto.getPubKeyParamTypes(algo), crypto.getPrivKeyParamTypes(algo)); - if (key_params.length < types.length) { - throw new Error('Missing private key parameters'); + sign: async function(algo, hash_algo, publicKeyParams, privateKeyParams, data, hashed) { + if (!publicKeyParams || !privateKeyParams) { + throw new Error('Missing key parameters'); } switch (algo) { case enums.publicKey.rsaEncryptSign: case enums.publicKey.rsaEncrypt: case enums.publicKey.rsaSign: { - const n = key_params[0].toUint8Array(); - const e = key_params[1].toUint8Array(); - const d = key_params[2].toUint8Array(); - const p = key_params[3].toUint8Array(); - const q = key_params[4].toUint8Array(); - const u = key_params[5].toUint8Array(); + const { n, e } = publicKeyParams; + const { d, p, q, u } = privateKeyParams; const signature = await publicKey.rsa.sign(hash_algo, data, n, e, d, p, q, u, hashed); return util.uint8ArrayToMpi(signature); } case enums.publicKey.dsa: { - const p = await key_params[0].toBigInteger(); - const q = await key_params[1].toBigInteger(); - const g = await key_params[2].toBigInteger(); - const x = await key_params[4].toBigInteger(); + const { g, p, q } = publicKeyParams; + const { x } = privateKeyParams; const signature = await publicKey.dsa.sign(hash_algo, hashed, g, p, q, x); return util.concatUint8Array([ util.uint8ArrayToMpi(signature.r), @@ -115,7 +101,8 @@ export default { throw new Error('Signing with Elgamal is not defined in the OpenPGP standard.'); } case enums.publicKey.ecdsa: { - const { oid, Q, d } = publicKey.elliptic.ecdsa.parseParams(key_params); + const { oid, Q } = publicKeyParams; + const { d } = privateKeyParams; const signature = await publicKey.elliptic.ecdsa.sign(oid, hash_algo, data, Q, d, hashed); return util.concatUint8Array([ util.uint8ArrayToMpi(signature.r), @@ -123,7 +110,8 @@ export default { ]); } case enums.publicKey.eddsa: { - const { oid, Q, seed } = publicKey.elliptic.eddsa.parseParams(key_params); + const { oid, Q } = publicKeyParams; + const { seed } = privateKeyParams; const signature = await publicKey.elliptic.eddsa.sign(oid, hash_algo, data, Q, seed, hashed); return util.concatUint8Array([ util.uint8ArrayToMpi(signature.R), diff --git a/src/key/helper.js b/src/key/helper.js index 640fcb2f..6fa49059 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -151,7 +151,7 @@ export async function getPreferredHashAlgo(key, keyPacket, date = new Date(), us case 'ecdh': case 'ecdsa': case 'eddsa': - pref_algo = crypto.publicKey.elliptic.getPreferredHashAlgo(keyPacket.params[0]); + pref_algo = crypto.publicKey.elliptic.getPreferredHashAlgo(keyPacket.publicParams.oid); } } return crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ? diff --git a/src/packet/public_key.js b/src/packet/public_key.js index 0a8e7faf..8d6542ca 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -29,7 +29,6 @@ import { Sha1 } from 'asmcrypto.js/dist_es8/hash/sha1/sha1'; import { Sha256 } from 'asmcrypto.js/dist_es8/hash/sha256/sha256'; import type_keyid from '../type/keyid'; -import type_mpi from '../type/mpi'; import config from '../config'; import crypto from '../crypto'; import enums from '../enums'; @@ -70,10 +69,10 @@ class PublicKeyPacket { */ this.algorithm = null; /** - * Algorithm specific params - * @type {Array} + * Algorithm specific public params + * @type {Object} */ - this.params = []; + this.publicParams = null; /** * Time until expiration in days (V3 only) * @type {Integer} @@ -116,16 +115,13 @@ class PublicKeyPacket { pos += 4; } - // - A series of values comprising the key material. This is - // algorithm-specific and described in section XXXX. - const types = crypto.getPubKeyParamTypes(algo); - this.params = crypto.constructParams(types); - - for (let i = 0; i < types.length && pos < bytes.length; i++) { - pos += this.params[i].read(bytes.subarray(pos, bytes.length)); - if (pos > bytes.length) { - throw new Error('Error reading MPI @:' + pos); - } + // - A series of values comprising the key material. + try { + const { read, publicParams } = crypto.parsePublicKeyParams(algo, bytes.subarray(pos)); + this.publicParams = publicParams; + pos += read; + } catch (err) { + throw new Error('Error reading MPIs'); } return pos; @@ -134,9 +130,8 @@ class PublicKeyPacket { } /** - * Same as write_private_key, but has less information because of - * public key. - * @returns {Uint8Array} OpenPGP packet body contents, + * Creates an OpenPGP public key packet for the given key. + * @returns {Uint8Array} Bytes encoding the public key OpenPGP packet */ write() { const arr = []; @@ -147,8 +142,7 @@ class PublicKeyPacket { const algo = enums.write(enums.publicKey, this.algorithm); arr.push(new Uint8Array([algo])); - const paramCount = crypto.getPubKeyParamTypes(algo).length; - const params = util.concatUint8Array(this.params.slice(0, paramCount).map(param => param.write())); + const params = crypto.serializeKeyParams(algo, this.publicParams); if (this.version === 5) { // A four-octet scalar octet count for the following key material arr.push(util.writeNumber(params.length, 4)); @@ -243,11 +237,11 @@ class PublicKeyPacket { getAlgorithmInfo() { const result = {}; result.algorithm = this.algorithm; - if (this.params[0] instanceof type_mpi) { - result.rsaBits = this.params[0].byteLength() * 8; + if (this.publicParams.n) { + result.rsaBits = this.publicParams.n.length * 8; result.bits = result.rsaBits; // Deprecated. } else { - result.curve = this.params[0].getName(); + result.curve = this.publicParams.oid.getName(); } return result; } diff --git a/src/packet/public_key_encrypted_session_key.js b/src/packet/public_key_encrypted_session_key.js index 519816e6..ddc29615 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -110,7 +110,7 @@ class PublicKeyEncryptedSessionKeyPacket { ]); const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm); this.encrypted = await crypto.publicKeyEncrypt( - algo, key.params, data, key.getFingerprintBytes()); + algo, key.publicParams, data, key.getFingerprintBytes()); return true; } @@ -130,7 +130,7 @@ class PublicKeyEncryptedSessionKeyPacket { if (algo !== keyAlgo) { throw new Error('Decryption error'); } - const decoded = await crypto.publicKeyDecrypt(algo, key.params, this.encrypted, key.getFingerprintBytes()); + const decoded = await crypto.publicKeyDecrypt(algo, key.publicParams, key.privateParams, this.encrypted, key.getFingerprintBytes()); const checksum = decoded.subarray(decoded.length - 2); const sessionKey = decoded.subarray(1, decoded.length - 2); if (!util.equalsUint8Array(checksum, util.writeChecksum(sessionKey))) { diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index 6b283f8c..8ccd3dbe 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -73,6 +73,11 @@ class SecretKeyPacket extends PublicKeyPacket { * @type {String} */ this.aead = null; + /** + * Decrypted private parameters, referenced by name + * @type {Object} + */ + this.privateParams = null; } // 5.5.3. Secret-Key Packet Formats @@ -154,8 +159,13 @@ class SecretKeyPacket extends PublicKeyPacket { if (!util.equalsUint8Array(util.writeChecksum(cleartext), this.keyMaterial.subarray(-2))) { throw new Error('Key checksum mismatch'); } - const privParams = parse_cleartext_params(cleartext, this.algorithm); - this.params = this.params.concat(privParams); + try { + const algo = enums.write(enums.publicKey, this.algorithm); + const { privateParams } = crypto.parsePrivateKeyParams(algo, cleartext, this.publicParams); + this.privateParams = privateParams; + } catch (err) { + throw new Error('Error reading MPIs'); + } } } @@ -200,7 +210,8 @@ class SecretKeyPacket extends PublicKeyPacket { if (!this.isDummy()) { if (!this.s2k_usage) { - const cleartextParams = write_cleartext_params(this.params, this.algorithm); + const algo = enums.write(enums.publicKey, this.algorithm); + const cleartextParams = crypto.serializeKeyParams(algo, this.privateParams); this.keyMaterial = util.concatUint8Array([ cleartextParams, util.writeChecksum(cleartextParams) @@ -282,7 +293,8 @@ class SecretKeyPacket extends PublicKeyPacket { this.s2k = new type_s2k(); this.s2k.salt = await crypto.random.getRandomBytes(8); - const cleartext = write_cleartext_params(this.params, this.algorithm); + const algo = enums.write(enums.publicKey, this.algorithm); + const cleartext = crypto.serializeKeyParams(algo, this.privateParams); this.symmetric = 'aes256'; const key = await produceEncryptionKey(this.s2k, passphrase, this.symmetric); const blockLen = crypto.cipher[this.symmetric].blockSize; @@ -354,8 +366,13 @@ class SecretKeyPacket extends PublicKeyPacket { } } - const privParams = parse_cleartext_params(cleartext, this.algorithm); - this.params = this.params.concat(privParams); + try { + const algo = enums.write(enums.publicKey, this.algorithm); + const { privateParams } = crypto.parsePrivateKeyParams(algo, cleartext, this.publicParams); + this.privateParams = privateParams; + } catch (err) { + throw new Error('Error reading MPIs'); + } this.isEncrypted = false; this.keyMaterial = null; this.s2k_usage = 0; @@ -378,7 +395,14 @@ class SecretKeyPacket extends PublicKeyPacket { } const algo = enums.write(enums.publicKey, this.algorithm); - const validParams = await crypto.validateParams(algo, this.params); + + let validParams; + try { + // this can throw if some parameters are undefined + validParams = await crypto.validateParams(algo, this.publicParams, this.privateParams); + } catch (_) { + validParams = false; + } if (!validParams) { throw new Error('Key is invalid'); } @@ -387,7 +411,9 @@ class SecretKeyPacket extends PublicKeyPacket { async generate(bits, curve) { const algo = enums.write(enums.publicKey, this.algorithm); - this.params = await crypto.generateParams(algo, bits, curve); + const { privateParams, publicParams } = await crypto.generateParams(algo, bits, curve); + this.privateParams = privateParams; + this.publicParams = publicParams; this.isEncrypted = false; } @@ -400,47 +426,16 @@ class SecretKeyPacket extends PublicKeyPacket { return; } - const algo = enums.write(enums.publicKey, this.algorithm); - const publicParamCount = crypto.getPubKeyParamTypes(algo).length; - this.params.slice(publicParamCount).forEach(param => { - param.data.fill(0); + Object.keys(this.privateParams).forEach(name => { + const param = this.privateParams[name]; + param.fill(0); + delete this.privateParams[name]; }); - this.params.length = publicParamCount; + this.privateParams = null; this.isEncrypted = true; } } -// Helper function - -function parse_cleartext_params(cleartext, algorithm) { - const algo = enums.write(enums.publicKey, algorithm); - const types = crypto.getPrivKeyParamTypes(algo); - const params = crypto.constructParams(types); - let p = 0; - - for (let i = 0; i < types.length && p < cleartext.length; i++) { - p += params[i].read(cleartext.subarray(p, cleartext.length)); - if (p > cleartext.length) { - throw new Error('Error reading param @:' + p); - } - } - - return params; -} - -function write_cleartext_params(params, algorithm) { - const arr = []; - const algo = enums.write(enums.publicKey, algorithm); - const numPublicParams = crypto.getPubKeyParamTypes(algo).length; - - for (let i = numPublicParams; i < params.length; i++) { - arr.push(params[i].write()); - } - - return util.concatUint8Array(arr); -} - - async function produceEncryptionKey(s2k, passphrase, algorithm) { return s2k.produce_key( passphrase, diff --git a/src/packet/signature.js b/src/packet/signature.js index 68196cc9..7ca56314 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -177,9 +177,8 @@ class SignaturePacket { const hash = await this.hash(signatureType, data, toHash, detached); this.signedHashValue = stream.slice(stream.clone(hash), 0, 2); - const params = key.params; const signed = async () => crypto.signature.sign( - publicKeyAlgorithm, hashAlgorithm, params, toHash, await stream.readToEnd(hash) + publicKeyAlgorithm, hashAlgorithm, key.publicParams, key.privateParams, toHash, await stream.readToEnd(hash) ); if (streaming) { this.signature = stream.fromAsync(signed); @@ -714,7 +713,7 @@ class SignaturePacket { i += mpi[j].read(this.signature.subarray(i, this.signature.length), endian); } const verified = await crypto.signature.verify( - publicKeyAlgorithm, hashAlgorithm, mpi, key.params, + publicKeyAlgorithm, hashAlgorithm, mpi, key.publicParams, toHash, hash ); if (!verified) { diff --git a/src/util.js b/src/util.js index 33c2e229..ffb4fcfe 100644 --- a/src/util.js +++ b/src/util.js @@ -167,6 +167,31 @@ export default { return str; }, + /** + * Read one MPI from bytes in input + * @param {Uint8Array} bytes input data to parse + * @returns {Uint8Array} parsed MPI + */ + readMPI: function (bytes) { + const bits = (bytes[0] << 8) | bytes[1]; + const bytelen = (bits + 7) >>> 3; + return bytes.subarray(2, 2 + bytelen); + }, + + /** + * Pad Uint8Array to length by adding 0x0 bytes + * @param {Uint8Array} bytes data to pad + * @param {Number} length padded length + * @param {'be'|'le'} endianess endianess of input data + * @return {Uint8Array} padded bytes + */ + padToLength(bytes, length, endianess = 'be') { + const padded = new Uint8Array(length); + const offset = (endianess === 'be') ? 0 : (length - bytes.length); + padded.set(bytes, offset); + return padded; + }, + /** * Convert a Uint8Array to an MPI-formatted Uint8Array. * Note: the output is **not** an MPI object. diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index 508a86eb..63171ba1 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -8,7 +8,7 @@ const expect = chai.expect; module.exports = () => describe('API functional testing', function() { const util = openpgp.util; const crypto = openpgp.crypto; - const RSApubMPIstrs = [ + const RSAPublicKeyMaterial = util.concatUint8Array([ 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, 0x60,0xa2,0x3a,0x37,0x06,0x08,0xd8,0x15,0x8e,0x85,0x45,0xaa,0xb7, @@ -30,8 +30,8 @@ module.exports = () => describe('API functional testing', function() { 0xee,0xc9,0xa4,0xcd,0x15,0xdc,0x1b,0x8d,0x64,0xc1,0x36,0x17,0xc4, 0x8d,0x5e,0x99,0x7a,0x5b,0x9f,0x39,0xd0,0x00,0x6e,0xf9]), new Uint8Array([0x00,0x11,0x01,0x00,0x01]) - ]; - const RSAsecMPIstrs = [ + ]); + const RSAPrivateKeyMaterial = util.concatUint8Array([ new Uint8Array([0x07,0xfe,0x23,0xff,0xce,0x45,0x6c,0x60,0x65,0x40,0x6e,0xae,0x35, 0x10,0x56,0x60,0xee,0xab,0xfa,0x10,0x42,0xba,0xc7,0x04,0xaf,0x63, 0xcd,0x3f,0x62,0xca,0x4b,0xfa,0xe1,0xa9,0x70,0xcd,0x34,0x8b,0xc8, @@ -82,9 +82,9 @@ module.exports = () => describe('API functional testing', function() { 0x51,0xe0,0x22,0xf0,0xff,0xa7,0x42,0xd4,0xde,0x0b,0x47,0x8f,0x2b, 0xf5,0x4d,0x04,0x32,0x91,0x89,0x4b,0x0e,0x05,0x8d,0x70,0xf9,0xbb, 0xe7,0xd6,0x76,0xea,0x0e,0x1a,0x90,0x30,0xf5,0x98,0x01,0xc5,0x73]) - ]; + ]); - const DSApubMPIstrs = [ + const DSAPublicKeyMaterial = util.concatUint8Array([ new Uint8Array([0x08,0x00,0xa8,0x85,0x5c,0x28,0x05,0x94,0x03,0xbe,0x07,0x6c,0x13,0x3e,0x65, 0xfb,0xb5,0xe1,0x99,0x7c,0xfa,0x84,0xe3,0xac,0x47,0xa5,0xc4,0x46,0xd8,0x5f, 0x44,0xe9,0xc1,0x6b,0x69,0xf7,0x10,0x76,0x49,0xa7,0x25,0x85,0xf4,0x1b,0xed, @@ -142,14 +142,14 @@ module.exports = () => describe('API functional testing', function() { 0x67,0x8d,0x9d,0x14,0xb6,0x9d,0x32,0x82,0xd0,0xb5,0xc6,0x57,0xf0,0x91,0xd9, 0xc3,0x26,0xae,0x9f,0xa9,0x67,0x49,0x96,0x5c,0x07,0x3e,0x47,0x5c,0xed,0x60, 0x07,0xac,0x6a]) - ]; - const DSAsecMPIstrs = [ + ]); + const DSAPrivateKeyMaterial = util.concatUint8Array([ new Uint8Array([0x01,0x00,0x9b,0x58,0xa8,0xf4,0x04,0xb1,0xd5,0x14,0x09,0xe1,0xe1,0xa1,0x8a, 0x0b,0xa3,0xc3,0xa3,0x66,0xaa,0x27,0x99,0x50,0x1c,0x4d,0xba,0x24,0xee,0xdf, 0xdf,0xb8,0x8e,0x8e]) - ]; + ]); - const ElgamalpubMPIstrs = [ + const elGamalPublicKeyMaterial = util.concatUint8Array([ new Uint8Array([0x08,0x00,0xea,0xcc,0xbe,0xe2,0xe4,0x5a,0x51,0x18,0x93,0xa1,0x12,0x2f,0x00, 0x99,0x42,0xd8,0x5c,0x1c,0x2f,0xb6,0x3c,0xd9,0x94,0x61,0xb4,0x55,0x8d,0x4e, 0x73,0xe6,0x69,0xbc,0x1d,0x33,0xe3,0x2d,0x91,0x23,0x69,0x95,0x98,0xd7,0x18, @@ -187,64 +187,37 @@ module.exports = () => describe('API functional testing', function() { 0xda,0xba,0x19,0xf3,0xcb,0x10,0xa0,0x6b,0xd0,0x2d,0xbe,0x40,0x42,0x7b,0x9b, 0x15,0xa4,0x2d,0xec,0xcf,0x09,0xd6,0xe3,0x92,0xc3,0x8d,0x65,0x6b,0x60,0x97, 0xda,0x6b,0xca]) - ]; + ]); - const ElgamalsecMPIstrs = [ + const elGamalPrivateKeyMaterial = util.concatUint8Array([ new Uint8Array([0x01,0x52,0x02,0x80,0x87,0xf6,0xe4,0x49,0xd7,0x2e,0x3e,0xfe,0x60,0xb9,0xa3, 0x2a,0xf0,0x67,0x58,0xe9,0xf6,0x47,0x83,0xde,0x7e,0xfb,0xbb,0xbd,0xdf,0x48, 0x12,0x1b,0x06,0x7d,0x13,0xbc,0x3b,0x49,0xf9,0x86,0xd4,0x53,0xed,0x2d,0x68]) - ]; + ]); - const RSApubMPIs = []; - let i; - for (i = 0; i < 2; i++) { - RSApubMPIs[i] = new openpgp.MPI(); - RSApubMPIs[i].read(RSApubMPIstrs[i]); - } + const algoRSA = openpgp.enums.publicKey.rsaEncryptSign; + const RSAPublicParams = crypto.parsePublicKeyParams(algoRSA, RSAPublicKeyMaterial).publicParams; + const RSAPrivateParams = crypto.parsePrivateKeyParams(algoRSA, RSAPrivateKeyMaterial).privateParams; - const RSAsecMPIs = []; - for (i = 0; i < 4; i++) { - RSAsecMPIs[i] = new openpgp.MPI(); - RSAsecMPIs[i].read(RSAsecMPIstrs[i]); - } + const algoDSA = openpgp.enums.publicKey.dsa; + const DSAPublicParams = crypto.parsePublicKeyParams(algoDSA, DSAPublicKeyMaterial).publicParams; + const DSAPrivateParams = crypto.parsePrivateKeyParams(algoDSA, DSAPrivateKeyMaterial).privateParams; - const DSAsecMPIs = []; - for (i = 0; i < 1; i++) { - DSAsecMPIs[i] = new openpgp.MPI(); - DSAsecMPIs[i].read(DSAsecMPIstrs[i]); - } - - const DSApubMPIs = []; - for (i = 0; i < 4; i++) { - DSApubMPIs[i] = new openpgp.MPI(); - DSApubMPIs[i].read(DSApubMPIstrs[i]); - } - const ElgamalsecMPIs = []; - for (i = 0; i < 1; i++) { - ElgamalsecMPIs[i] = new openpgp.MPI(); - ElgamalsecMPIs[i].read(ElgamalsecMPIstrs[i]); - } - - const ElgamalpubMPIs = []; - for (i = 0; i < 3; i++) { - ElgamalpubMPIs[i] = new openpgp.MPI(); - ElgamalpubMPIs[i].read(ElgamalpubMPIstrs[i]); - } + const algoElGamal = openpgp.enums.publicKey.elgamal; + const elGamalPublicParams = crypto.parsePublicKeyParams(algoElGamal, elGamalPublicKeyMaterial).publicParams; + const elGamalPrivateParams = crypto.parsePrivateKeyParams(algoElGamal, elGamalPrivateKeyMaterial).privateParams; const data = util.strToUint8Array("foobar"); describe('Sign and verify', function () { it('RSA', async 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 crypto.signature.sign( - 1, 2, RSApubMPIs.concat(RSAsecMPIs), data, await crypto.hash.digest(2, data) + 1, 2, RSAPublicParams, RSAPrivateParams, data, await crypto.hash.digest(2, data) ).then(async RSAsignedData => { const RSAsignedDataMPI = new openpgp.MPI(); RSAsignedDataMPI.read(RSAsignedData); return crypto.signature.verify( - 1, 2, [RSAsignedDataMPI], RSApubMPIs, data, await crypto.hash.digest(2, data) + 1, 2, [RSAsignedDataMPI], RSAPublicParams, data, await crypto.hash.digest(2, data) ).then(success => { return expect(success).to.be.true; }); @@ -252,9 +225,8 @@ module.exports = () => describe('API functional testing', function() { }); it('DSA', async function () { - // DSA return crypto.signature.sign( - 17, 2, DSApubMPIs.concat(DSAsecMPIs), data, await crypto.hash.digest(2, data) + 17, 2, DSAPublicParams, DSAPrivateParams, data, await crypto.hash.digest(2, data) ).then(async DSAsignedData => { DSAsignedData = util.uint8ArrayToStr(DSAsignedData); const DSAmsgMPIs = []; @@ -263,7 +235,7 @@ module.exports = () => describe('API functional testing', function() { DSAmsgMPIs[0].read(DSAsignedData.substring(0,34)); DSAmsgMPIs[1].read(DSAsignedData.substring(34,68)); return crypto.signature.verify( - 17, 2, DSAmsgMPIs, DSApubMPIs, data, await crypto.hash.digest(2, data) + 17, 2, DSAmsgMPIs, DSAPublicParams, data, await crypto.hash.digest(2, data) ).then(success => { return expect(success).to.be.true; }); @@ -356,9 +328,9 @@ module.exports = () => describe('API functional testing', function() { it('Asymmetric using RSA with eme_pkcs1 padding', async function () { const symmKey = await crypto.generateSessionKey('aes256'); - return crypto.publicKeyEncrypt(1, RSApubMPIs, symmKey).then(RSAEncryptedData => { + return crypto.publicKeyEncrypt(algoRSA, RSAPublicParams, symmKey).then(RSAEncryptedData => { return crypto.publicKeyDecrypt( - 1, RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData + algoRSA, RSAPublicParams, RSAPrivateParams, RSAEncryptedData ).then(data => { expect(data).to.deep.equal(symmKey); }); @@ -367,9 +339,9 @@ module.exports = () => describe('API functional testing', function() { it('Asymmetric using Elgamal with eme_pkcs1 padding', async function () { const symmKey = await crypto.generateSessionKey('aes256'); - return crypto.publicKeyEncrypt(16, ElgamalpubMPIs, symmKey).then(ElgamalEncryptedData => { + return crypto.publicKeyEncrypt(algoElGamal, elGamalPublicParams, symmKey).then(ElgamalEncryptedData => { return crypto.publicKeyDecrypt( - 16, ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData + algoElGamal, elGamalPublicParams, elGamalPrivateParams, ElgamalEncryptedData ).then(data => { expect(data).to.deep.equal(symmKey); }); diff --git a/test/crypto/rsa.js b/test/crypto/rsa.js index 51962e15..672c889f 100644 --- a/test/crypto/rsa.js +++ b/test/crypto/rsa.js @@ -22,16 +22,11 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra it('sign and verify using generated key params', async function() { const bits = openpgp.util.getWebCryptoAll() ? 2048 : 1024; - const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); + const { publicParams, privateParams } = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); const message = await openpgp.crypto.random.getRandomBytes(64); const hash_algo = openpgp.enums.write(openpgp.enums.hash, 'sha256'); const hashed = await openpgp.crypto.hash.digest(hash_algo, message); - const n = keyParams[0].toUint8Array(); - const e = keyParams[1].toUint8Array(); - const d = keyParams[2].toUint8Array(); - const p = keyParams[3].toUint8Array(); - const q = keyParams[4].toUint8Array(); - const u = keyParams[5].toUint8Array(); + const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const signature = await openpgp.crypto.publicKey.rsa.sign(hash_algo, message, n, e, d, p, q, u, hashed); expect(signature).to.exist; const verify = await openpgp.crypto.publicKey.rsa.verify(hash_algo, message, signature, n, e, hashed); @@ -40,13 +35,8 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra it('encrypt and decrypt using generated key params', async function() { const bits = openpgp.util.getWebCryptoAll() ? 2048 : 1024; - const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); - const n = keyParams[0].toUint8Array(); - const e = keyParams[1].toUint8Array(); - const d = keyParams[2].toUint8Array(); - const p = keyParams[3].toUint8Array(); - const q = keyParams[4].toUint8Array(); - const u = keyParams[5].toUint8Array(); + const { publicParams, privateParams } = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); + const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const message = await openpgp.crypto.generateSessionKey('aes256'); const encrypted = await openpgp.crypto.publicKey.rsa.encrypt(message, n, e); const result = new openpgp.MPI(encrypted); @@ -59,13 +49,8 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra this.skip(); } const bits = 1024; - const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); - const n = keyParams[0].toUint8Array(); - const e = keyParams[1].toUint8Array(); - const d = keyParams[2].toUint8Array(); - const p = keyParams[3].toUint8Array(); - const q = keyParams[4].toUint8Array(); - const u = keyParams[5].toUint8Array(); + const { publicParams, privateParams } = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); + const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const message = await openpgp.crypto.generateSessionKey('aes256'); const encryptedBn = await openpgp.crypto.publicKey.rsa.bnEncrypt(message, n, e); const decrypted1 = await openpgp.crypto.publicKey.rsa.nodeDecrypt(encryptedBn, n, e, d, p, q, u); @@ -80,13 +65,8 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra this.skip(); } const bits = openpgp.util.getWebCrypto() ? 2048 : 1024; - const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); - const n = keyParams[0].toUint8Array(); - const e = keyParams[1].toUint8Array(); - const d = keyParams[2].toUint8Array(); - const p = keyParams[3].toUint8Array(); - const q = keyParams[4].toUint8Array(); - const u = keyParams[5].toUint8Array(); + const { publicParams, privateParams } = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); + const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const message = await openpgp.crypto.random.getRandomBytes(64); const hashName = 'sha256'; const hash_algo = openpgp.enums.write(openpgp.enums.hash, hashName); @@ -107,13 +87,8 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra this.skip(); } const bits = openpgp.util.getWebCrypto() ? 2048 : 1024; - const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); - const n = keyParams[0].toUint8Array(); - const e = keyParams[1].toUint8Array(); - const d = keyParams[2].toUint8Array(); - const p = keyParams[3].toUint8Array(); - const q = keyParams[4].toUint8Array(); - const u = keyParams[5].toUint8Array(); + const { publicParams, privateParams } = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); + const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const message = await openpgp.crypto.random.getRandomBytes(64); const hashName = 'sha256'; const hash_algo = openpgp.enums.write(openpgp.enums.hash, hashName); @@ -137,13 +112,8 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra this.skip(); } const bits = 1024; - const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); - const n = keyParams[0].toUint8Array(); - const e = keyParams[1].toUint8Array(); - const d = keyParams[2].toUint8Array(); - const p = keyParams[3].toUint8Array(); - const q = keyParams[4].toUint8Array(); - const u = keyParams[5].toUint8Array(); + const { publicParams, privateParams } = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); + const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const message = await openpgp.crypto.random.getRandomBytes(64); const hashName = 'sha256'; const hash_algo = openpgp.enums.write(openpgp.enums.hash, hashName); @@ -158,13 +128,8 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra this.skip(); } const bits = openpgp.util.getWebCrypto() ? 2048 : 1024; - const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); - const n = keyParams[0].toUint8Array(); - const e = keyParams[1].toUint8Array(); - const d = keyParams[2].toUint8Array(); - const p = keyParams[3].toUint8Array(); - const q = keyParams[4].toUint8Array(); - const u = keyParams[5].toUint8Array(); + const { publicParams, privateParams } = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); + const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const message = await openpgp.crypto.random.getRandomBytes(64); const hashName = 'sha256'; const hash_algo = openpgp.enums.write(openpgp.enums.hash, hashName); diff --git a/test/crypto/validate.js b/test/crypto/validate.js index b19f1766..84c24c1b 100644 --- a/test/crypto/validate.js +++ b/test/crypto/validate.js @@ -93,12 +93,12 @@ module.exports = () => { it('detect invalid edDSA Q', async function() { const eddsaKeyPacket = cloneKeyPacket(eddsaKey); - const Q = eddsaKeyPacket.params[1]; - Q.data[0]++; + const Q = eddsaKeyPacket.publicParams.Q; + Q[0]++; await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); - const infQ = new Uint8Array(Q.data.length); - eddsaKeyPacket.params[1] = new openpgp.MPI(infQ); + const infQ = new Uint8Array(Q.length); + eddsaKeyPacket.publicParams.Q = infQ; await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); }); @@ -114,86 +114,78 @@ module.exports = () => { }); it('EdDSA params are not valid for ECDH', async function() { - const oid = eddsaKey.keyPacket.params[0]; - const Q = eddsaKey.keyPacket.params[1]; - const seed = eddsaKey.keyPacket.params[2]; + const { oid, Q } = eddsaKey.keyPacket.publicParams; + const { seed } = eddsaKey.keyPacket.privateParams; const ecdhKeyPacket = cloneKeyPacket(ecdhKey); - const ecdhOID = ecdhKeyPacket.params[0]; + const ecdhOID = ecdhKeyPacket.publicParams.oid; - ecdhKeyPacket.params[0] = oid; + ecdhKeyPacket.publicParams.oid = oid; await expect(ecdhKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); - ecdhKeyPacket.params[0] = ecdhOID; - ecdhKeyPacket.params[1] = Q; - ecdhKeyPacket.params[3] = seed; + ecdhKeyPacket.publicParams.oid = ecdhOID; + ecdhKeyPacket.publicParams.Q = Q; + ecdhKeyPacket.privateParams.d = seed; await expect(ecdhKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); it('EdDSA params are not valid for EcDSA', async function() { - const oid = eddsaKey.keyPacket.params[0]; - const Q = eddsaKey.keyPacket.params[1]; - const seed = eddsaKey.keyPacket.params[2]; + const { oid, Q } = eddsaKey.keyPacket.publicParams; + const { seed } = eddsaKey.keyPacket.privateParams; const ecdsaKeyPacket = cloneKeyPacket(ecdsaKey); - const ecdsaOID = ecdsaKeyPacket.params[0]; - ecdsaKeyPacket.params[0] = oid; + const ecdsaOID = ecdsaKeyPacket.publicParams.oid; + ecdsaKeyPacket.publicParams.oid = oid; await expect(ecdsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); - ecdsaKeyPacket.params[0] = ecdsaOID; - ecdsaKeyPacket.params[1] = Q; - ecdsaKeyPacket.params[2] = seed; + ecdsaKeyPacket.publicParams.oid = ecdsaOID; + ecdsaKeyPacket.publicParams.Q = Q; + ecdsaKeyPacket.privateParams.d = seed; await expect(ecdsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); it('ECDH x25519 params are not valid for EcDSA', async function() { - const ecdh25519KeyPacket = ecdhKey.keyPacket; - const oid = ecdh25519KeyPacket.params[0]; - const Q = ecdh25519KeyPacket.params[1]; - const d = ecdh25519KeyPacket.params[3]; + const { oid, Q } = ecdhKey.keyPacket.publicParams; + const { d } = ecdhKey.keyPacket.privateParams; const ecdsaKeyPacket = cloneKeyPacket(ecdsaKey); - ecdsaKeyPacket.params[0] = oid; - ecdsaKeyPacket.params[1] = Q; - ecdsaKeyPacket.params[2] = d; + ecdsaKeyPacket.publicParams.oid = oid; + ecdsaKeyPacket.publicParams.Q = Q; + ecdsaKeyPacket.privateParams.d = d; await expect(ecdsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); it('EcDSA params are not valid for EdDSA', async function() { - const oid = ecdsaKey.keyPacket.params[0]; - const Q = ecdsaKey.keyPacket.params[1]; - const d = ecdsaKey.keyPacket.params[2]; + const { oid, Q } = ecdsaKey.keyPacket.publicParams; + const { d } = ecdsaKey.keyPacket.privateParams; const eddsaKeyPacket = cloneKeyPacket(eddsaKey); - const eddsaOID = eddsaKeyPacket.params[0]; - eddsaKeyPacket.params[0] = oid; + const eddsaOID = eddsaKeyPacket.publicParams.oid; + eddsaKeyPacket.publicParams.oid = oid; await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); - eddsaKeyPacket.params[0] = eddsaOID; - eddsaKeyPacket.params[1] = Q; - eddsaKeyPacket.params[2] = d; + eddsaKeyPacket.publicParams.oid = eddsaOID; + eddsaKeyPacket.publicParams.Q = Q; + eddsaKeyPacket.privateParams.seed = d; await expect(eddsaKeyPacket.validate()).to.be.rejected; }); it('ECDH x25519 params are not valid for EdDSA', async function() { - const ecdh25519KeyPacket = ecdhKey.keyPacket; - const oid = ecdh25519KeyPacket.params[0]; - const Q = ecdh25519KeyPacket.params[1]; - const d = ecdh25519KeyPacket.params[3]; + const { oid, Q } = ecdhKey.keyPacket.publicParams; + const { d } = ecdhKey.keyPacket.privateParams; const eddsaKeyPacket = cloneKeyPacket(eddsaKey); - const eddsaOID = eddsaKeyPacket.params[0]; - eddsaKeyPacket.params[0] = oid; + const eddsaOID = eddsaKeyPacket.publicParams.oid; + eddsaKeyPacket.publicParams.oid = oid; await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); - eddsaKeyPacket.params[0] = eddsaOID; - eddsaKeyPacket.params[1] = Q; - eddsaKeyPacket.params[2] = d; + eddsaKeyPacket.publicParams.oid = eddsaOID; + eddsaKeyPacket.publicParams.Q = Q; + eddsaKeyPacket.privateParams.seed = d; await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); }); - const curves = ['curve25519', 'p256', 'p384', 'p521', 'secp256k1', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1']; curves.forEach(curve => { describe(`ECC ${curve} parameter validation`, () => { @@ -216,12 +208,12 @@ module.exports = () => { it('detect invalid EcDSA Q', async function() { const keyPacket = cloneKeyPacket(ecdsaKey); - const Q = keyPacket.params[1]; - Q.data[0]++; + const Q = keyPacket.publicParams.Q; + Q[0]++; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - const infQ = new Uint8Array(Q.data.length); - keyPacket.params[1] = new openpgp.MPI(infQ); + const infQ = new Uint8Array(Q.length); + keyPacket.publicParams.Q = infQ; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); } @@ -232,12 +224,12 @@ module.exports = () => { it('detect invalid ECDH Q', async function() { const keyPacket = cloneKeyPacket(ecdhKey); - const Q = keyPacket.params[1]; - Q.data[16]++; + const Q = keyPacket.publicParams.Q; + Q[16]++; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - const infQ = new Uint8Array(Q.data.length); - keyPacket.params[1] = new openpgp.MPI(infQ); + const infQ = new Uint8Array(Q.length); + keyPacket.publicParams.Q = infQ; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); }); @@ -255,15 +247,15 @@ module.exports = () => { it('detect invalid RSA n', async function() { const keyPacket = cloneKeyPacket(rsaKey); - const n = keyPacket.params[0]; - n.data[0]++; + const n = keyPacket.publicParams.n; + n[0]++; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); it('detect invalid RSA e', async function() { const keyPacket = cloneKeyPacket(rsaKey); - const e = keyPacket.params[1]; - e.data[0]++; + const e = keyPacket.publicParams.e; + e[0]++; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); }); @@ -280,28 +272,27 @@ module.exports = () => { it('detect invalid DSA p', async function() { const keyPacket = cloneKeyPacket(dsaKey); - const p = keyPacket.params[0]; - p.data[0]++; + const p = keyPacket.publicParams.p; + p[0]++; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); it('detect invalid DSA y', async function() { const keyPacket = cloneKeyPacket(dsaKey); - const y = keyPacket.params[3]; + const y = keyPacket.publicParams.y; - y.data[0]++; + y[0]++; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); it('detect invalid DSA g', async function() { const keyPacket = cloneKeyPacket(dsaKey); - const g = keyPacket.params[2]; + const g = keyPacket.publicParams.g; - g.data[0]++; + g[0]++; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - const gOne = new openpgp.MPI(new Uint8Array([1])); - keyPacket.params[2] = gOne; + keyPacket.publicParams.g = new Uint8Array([1]); await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); }); @@ -318,40 +309,39 @@ module.exports = () => { it('detect invalid p', async function() { const keyPacket = cloneKeyPacket(egKey); - const p = keyPacket.params[0]; - p.data[0]++; + const p = keyPacket.publicParams.p; + p[0]++; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); it('detect invalid y', async function() { const keyPacket = cloneKeyPacket(egKey); - const y = keyPacket.params[2]; - y.data[0]++; + const y = keyPacket.publicParams.y; + y[0]++; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); it('detect invalid g', async function() { const keyPacket = cloneKeyPacket(egKey); - const g = keyPacket.params[1]; + const g = keyPacket.publicParams.g; - g.data[0]++; + g[0]++; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - const gOne = new openpgp.MPI(new Uint8Array([1])); - keyPacket.params[1] = gOne; + keyPacket.publicParams.g = new Uint8Array([1]); await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); it('detect g with small order', async function() { const keyPacket = cloneKeyPacket(egKey); - const p = keyPacket.params[0].toUint8Array(); - const g = keyPacket.params[1].toUint8Array(); + const p = keyPacket.publicParams.p; + const g = keyPacket.publicParams.g; const pBN = new BN(p); const gModP = new BN(g).toRed(new BN.red(pBN)); // g**(p-1)/2 has order 2 const gOrd2 = gModP.redPow(pBN.subn(1).shrn(1)); - keyPacket.params[1] = new openpgp.MPI(gOrd2.toArrayLike(Uint8Array, 'be')); + keyPacket.publicParams.g = gOrd2.toArrayLike(Uint8Array, 'be'); await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); }); diff --git a/test/general/key.js b/test/general/key.js index 2ed28d6e..20c33ee1 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -2584,6 +2584,17 @@ function versionSpecificTests() { } module.exports = () => describe('Key', function() { + async function deepCopyKeyParams(params) { + const paramsCopy = {}; + Object.keys(params).forEach(name => { + const param = params[name]; + const copy = new Uint8Array(param.length); + copy.set(param); + paramsCopy[name] = copy; + }); + return paramsCopy; + } + let rsaGenStub; let v5KeysVal; let aeadProtectVal; @@ -2594,8 +2605,9 @@ module.exports = () => describe('Key', function() { }; beforeEach(function() { + // We fake the generation function to speed up the tests rsaGenStub = stub(openpgp.crypto.publicKey.rsa, 'generate'); - rsaGenStub.callsFake(N => rsaGenValue[N]); + rsaGenStub.callsFake(async N => deepCopyKeyParams(await rsaGenValue[N])); }); afterEach(function() { @@ -2882,7 +2894,7 @@ module.exports = () => describe('Key', function() { key.primaryKey.makeDummy(); expect(key.primaryKey.isDummy()).to.be.true; await key.validate(); - await expect(openpgp.reformatKey({ privateKey: key, userIds: 'test2 ' })).to.be.rejectedWith(/Missing private key parameters/); + await expect(openpgp.reformatKey({ privateKey: key, userIds: 'test2 ' })).to.be.rejectedWith(/Missing key parameters/); }); it('makeDummy() - subkeys of the converted key can still sign', async function() { @@ -2905,25 +2917,28 @@ module.exports = () => describe('Key', function() { const key = await openpgp.key.readArmored(priv_key_rsa); await key.decrypt('hello world'); const signingKeyPacket = key.subKeys[0].keyPacket; - const params = signingKeyPacket.params; + const privateParams = signingKeyPacket.privateParams; await key.clearPrivateParams(); key.primaryKey.isEncrypted = false; - key.primaryKey.params = params; + key.primaryKey.privateParams = privateParams; key.subKeys[0].keyPacket.isEncrypted = false; - key.subKeys[0].keyPacket.params = params; - await expect(key.validate()).to.be.rejectedWith('Missing key parameters'); + key.subKeys[0].keyPacket.privateParams = privateParams; + await expect(key.validate()).to.be.rejectedWith('Key is invalid'); }); it('clearPrivateParams() - detect that private key parameters were zeroed out', async function() { const key = await openpgp.key.readArmored(priv_key_rsa); await key.decrypt('hello world'); const signingKeyPacket = key.subKeys[0].keyPacket; - const params = signingKeyPacket.params.slice(); + const privateParams = {}; + Object.entries(signingKeyPacket.privateParams).forEach(([name, value]) => { + privateParams[name] = value; + }); await key.clearPrivateParams(); key.primaryKey.isEncrypted = false; - key.primaryKey.params = params; + key.primaryKey.privateParams = privateParams; key.subKeys[0].keyPacket.isEncrypted = false; - key.subKeys[0].keyPacket.params = params; + key.subKeys[0].keyPacket.privateParams = privateParams; await expect(key.validate()).to.be.rejectedWith('Key is invalid'); }); @@ -3398,9 +3413,9 @@ VYGdb3eNlV8CfoEC const subKey = newPrivateKey.subKeys[total]; expect(subKey).to.exist; expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); - const subkeyN = subKey.keyPacket.params[0]; - const pkN = privateKey.primaryKey.params[0]; - expect(subkeyN.byteLength()).to.be.equal(rsaBits ? (rsaBits / 8) : pkN.byteLength()); + const subkeyN = subKey.keyPacket.publicParams.n; + const pkN = privateKey.primaryKey.publicParams.n; + expect(subkeyN.length).to.be.equal(rsaBits ? (rsaBits / 8) : pkN.length); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); expect(subKey.getAlgorithmInfo().rsaBits).to.be.equal(rsaBits || privateKey.getAlgorithmInfo().rsaBits); await subKey.verify(newPrivateKey.primaryKey); @@ -3446,8 +3461,8 @@ VYGdb3eNlV8CfoEC expect(subKey1.getKeyId().toHex()).to.be.equal(subKey.getKeyId().toHex()); expect(subKey).to.exist; expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); - const subkeyOid = subKey.keyPacket.params[0]; - const pkOid = privateKey.primaryKey.params[0]; + const subkeyOid = subKey.keyPacket.publicParams.oid; + const pkOid = privateKey.primaryKey.publicParams.oid; expect(subkeyOid.getName()).to.be.equal(pkOid.getName()); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); await subKey.verify(privateKey.primaryKey); @@ -3464,7 +3479,7 @@ VYGdb3eNlV8CfoEC const subKey = newPrivateKey.subKeys[total]; expect(subKey).to.exist; expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); - expect(subKey.keyPacket.params[0].getName()).to.be.equal(openpgp.enums.curve.curve25519); + expect(subKey.keyPacket.publicParams.oid.getName()).to.be.equal(openpgp.enums.curve.curve25519); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdh'); await subKey.verify(privateKey.primaryKey); }); @@ -3479,8 +3494,8 @@ VYGdb3eNlV8CfoEC const armoredKey = newPrivateKey.armor(); newPrivateKey = await openpgp.key.readArmored(armoredKey); const subKey = newPrivateKey.subKeys[total]; - const subkeyOid = subKey.keyPacket.params[0]; - const pkOid = newPrivateKey.primaryKey.params[0]; + const subkeyOid = subKey.keyPacket.publicParams.oid; + const pkOid = newPrivateKey.primaryKey.publicParams.oid; expect(subkeyOid.getName()).to.be.equal(pkOid.getName()); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); await subKey.verify(newPrivateKey.primaryKey); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 75731fc0..826cb03c 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -549,13 +549,24 @@ function withCompression(tests) { } module.exports = () => describe('OpenPGP.js public api tests', function() { + async function deepCopyKeyParams(params) { + const paramsCopy = {}; + Object.keys(params).forEach(name => { + const param = params[name]; + const copy = new Uint8Array(param.length); + copy.set(param); + paramsCopy[name] = copy; + }); + return paramsCopy; + } let rsaGenStub; - let rsaGenValue = openpgp.crypto.publicKey.rsa.generate(openpgp.util.getWebCryptoAll() ? 2048 : 512, 65537); + const rsaGenValue = openpgp.crypto.publicKey.rsa.generate(openpgp.util.getWebCryptoAll() ? 2048 : 512, 65537); beforeEach(function() { + // We fake the generation function to speed up the tests rsaGenStub = stub(openpgp.crypto.publicKey.rsa, 'generate'); - rsaGenStub.returns(rsaGenValue); + rsaGenStub.returns(async () => deepCopyKeyParams(await rsaGenValue())); }); afterEach(function() { @@ -2329,7 +2340,7 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { const pubKeyDE = await openpgp.key.readArmored(pub_key_de); const privKeyDE = await openpgp.key.readArmored(priv_key_de); // corrupt the public key params - privKeyDE.subKeys[0].keyPacket.params[0].data[0]++; + privKeyDE.subKeys[0].keyPacket.publicParams.p[0]++; // validation will not check the decryption subkey and will succeed await privKeyDE.decrypt(passphrase); const encrypted = await openpgp.encrypt({ diff --git a/test/general/packet.js b/test/general/packet.js index c120f471..ed082aac 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -6,6 +6,7 @@ chai.use(require('chai-as-promised')); const { expect } = chai; const input = require('./testInputs.js'); +const { PacketList } = require('../../dist/node/openpgp.min'); function stringify(array) { if (openpgp.util.isStream(array)) { @@ -315,16 +316,10 @@ module.exports = () => describe("Packet", function() { }); it('Public key encrypted symmetric key packet', function() { - const rsa = openpgp.crypto.publicKey.rsa; + const rsa = openpgp.enums.publicKey.rsaEncryptSign; const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys - return rsa.generate(keySize, 65537).then(function(mpiGen) { - - let mpi = [mpiGen.n, mpiGen.e, mpiGen.d, mpiGen.p, mpiGen.q, mpiGen.u]; - mpi = mpi.map(function(k) { - return new openpgp.MPI(k); - }); - + return openpgp.crypto.generateParams(rsa, keySize, 65537).then(function({ publicParams, privateParams }) { const enc = new openpgp.PublicKeyEncryptedSessionKeyPacket(); const msg = new openpgp.PacketList(); const msg2 = new openpgp.PacketList(); @@ -333,14 +328,12 @@ module.exports = () => describe("Packet", function() { enc.publicKeyAlgorithm = 'rsaEncryptSign'; enc.sessionKeyAlgorithm = 'aes256'; enc.publicKeyId.bytes = '12345678'; - return enc.encrypt({ params: mpi, getFingerprintBytes() {} }).then(async () => { + return enc.encrypt({ publicParams, getFingerprintBytes() {} }).then(async () => { msg.push(enc); - await msg2.read(msg.write(), openpgp); - return msg2[0].decrypt({ algorithm: 'rsaEncryptSign', params: mpi, getFingerprintBytes() {} }).then(() => { - + return msg2[0].decrypt({ algorithm: 'rsaEncryptSign', publicParams, privateParams, getFingerprintBytes() {} }).then(() => { expect(stringify(msg2[0].sessionKey)).to.equal(stringify(enc.sessionKey)); expect(msg2[0].sessionKeyAlgorithm).to.equal(enc.sessionKeyAlgorithm); }); @@ -841,82 +834,67 @@ V+HOQJQxXJkVRYa3QrFUehiMzTeqqMdgC6ZqJy7+ expect(rawNotations[1].humanReadable).to.equal(true); }); - it('Writing and encryption of a secret key packet.', function() { - const key = new openpgp.PacketList(); - key.push(new openpgp.SecretKeyPacket()); - - const rsa = openpgp.crypto.publicKey.rsa; + it('Writing and encryption of a secret key packet (AEAD)', async function() { + const rsa = openpgp.enums.publicKey.rsaEncryptSign; const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys + const { privateParams, publicParams } = await openpgp.crypto.generateParams(rsa, keySize, 65537); - return rsa.generate(keySize, 65537).then(async function(mpiGen) { - let mpi = [mpiGen.n, mpiGen.e, mpiGen.d, mpiGen.p, mpiGen.q, mpiGen.u]; - mpi = mpi.map(function(k) { - return new openpgp.MPI(k); - }); + const secretKeyPacket = new openpgp.SecretKeyPacket(); + secretKeyPacket.privateParams = privateParams; + secretKeyPacket.publicParams = publicParams; + secretKeyPacket.algorithm = "rsaSign"; + secretKeyPacket.isEncrypted = false; + await secretKeyPacket.encrypt('hello'); - key[0].params = mpi; - key[0].algorithm = "rsaSign"; - key[0].isEncrypted = false; - await key[0].encrypt('hello'); + const raw = new openpgp.PacketList(); + raw.push(secretKeyPacket); + const packetList = new openpgp.PacketList(); + await packetList.read(raw.write(), openpgp); + const secretKeyPacket2 = packetList[0]; + await secretKeyPacket2.decrypt('hello'); - const raw = key.write(); - - const key2 = new openpgp.PacketList(); - await key2.read(raw, openpgp); - await key2[0].decrypt('hello'); - - expect(key[0].params.toString()).to.equal(key2[0].params.toString()); - }); + expect(secretKeyPacket2.privateParams).to.deep.equal(secretKeyPacket.privateParams); + expect(secretKeyPacket2.publicParams).to.deep.equal(secretKeyPacket.publicParams); }); - it('Writing and encryption of a secret key packet. (AEAD)', async function() { - let aeadProtectVal = openpgp.config.aeadProtect; - openpgp.config.aeadProtect = true; + it('Writing and encryption of a secret key packet (CFB)', async function() { + const aeadProtectVal = openpgp.config.aeadProtect; + openpgp.config.aeadProtect = false; - const key = new openpgp.PacketList(); - key.push(new openpgp.SecretKeyPacket()); - - const rsa = openpgp.crypto.publicKey.rsa; + const rsa = openpgp.enums.publicKey.rsaEncryptSign; const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys try { - const mpiGen = await rsa.generate(keySize, 65537); - let mpi = [mpiGen.n, mpiGen.e, mpiGen.d, mpiGen.p, mpiGen.q, mpiGen.u]; - mpi = mpi.map(function(k) { - return new openpgp.MPI(k); - }); + const { privateParams, publicParams } = await openpgp.crypto.generateParams(rsa, keySize, 65537); + const secretKeyPacket = new openpgp.SecretKeyPacket(); + secretKeyPacket.privateParams = privateParams; + secretKeyPacket.publicParams = publicParams; + secretKeyPacket.algorithm = "rsaSign"; + secretKeyPacket.isEncrypted = false; + await secretKeyPacket.encrypt('hello'); - key[0].params = mpi; - key[0].algorithm = "rsaSign"; - key[0].isEncrypted = false; - await key[0].encrypt('hello'); - - const raw = key.write(); - - const key2 = new openpgp.PacketList(); - await key2.read(raw, openpgp); - await key2[0].decrypt('hello'); - - expect(key[0].params.toString()).to.equal(key2[0].params.toString()); + const raw = new openpgp.PacketList(); + raw.push(secretKeyPacket); + const packetList = new openpgp.PacketList(); + await packetList.read(raw.write(), openpgp); + const secretKeyPacket2 = packetList[0]; + await secretKeyPacket2.decrypt('hello'); } finally { openpgp.config.aeadProtect = aeadProtectVal; } }); - it('Writing and verification of a signature packet.', function() { + it('Writing and verification of a signature packet', function() { const key = new openpgp.SecretKeyPacket(); - const rsa = openpgp.crypto.publicKey.rsa; + const rsa = openpgp.enums.publicKey.rsaEncryptSign; const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys - return rsa.generate(keySize, 65537).then(function(mpiGen) { - let mpi = [mpiGen.n, mpiGen.e, mpiGen.d, mpiGen.p, mpiGen.q, mpiGen.u]; - mpi = mpi.map(function(k) { - return new openpgp.MPI(k); - }); + return openpgp.crypto.generateParams(rsa, keySize, 65537).then(function({ privateParams, publicParams }) { const testText = input.createSomeMessage(); - key.params = mpi; + key.publicParams = publicParams; + key.privateParams = privateParams; key.algorithm = "rsaSign"; const signed = new openpgp.PacketList(); diff --git a/test/general/signature.js b/test/general/signature.js index 455ba029..1334aa74 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -877,9 +877,9 @@ hUhMKMuiM3pRwdIyDOItkUWQmjEEw7/XmhgInkXsCw== expect(msg.signatures).to.have.length(1); expect(msg.signatures[0].valid).to.be.true; expect(msg.signatures[0].signature.packets.length).to.equal(1); - await expect(openpgp.sign({ message: openpgp.message.fromText('test'), privateKeys: [priv_key_gnupg_ext] })).to.eventually.be.rejectedWith('Missing private key parameters'); - await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext })).to.eventually.be.rejectedWith('Missing private key parameters'); - await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext_2, passphrase: 'test' })).to.eventually.be.rejectedWith('Missing private key parameters'); + await expect(openpgp.sign({ message: openpgp.message.fromText('test'), privateKeys: [priv_key_gnupg_ext] })).to.eventually.be.rejectedWith(/Missing key parameters/); + await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext })).to.eventually.be.rejectedWith(/Missing key parameters/); + await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext_2, passphrase: 'test' })).to.eventually.be.rejectedWith(/Missing key parameters/); await priv_key_gnupg_ext.encrypt("abcd"); expect(priv_key_gnupg_ext.isDecrypted()).to.be.false; const primaryKey_packet2 = priv_key_gnupg_ext.primaryKey.write(); diff --git a/test/general/testInputs.js b/test/general/testInputs.js index 8a12cee5..6bcb9e1d 100644 --- a/test/general/testInputs.js +++ b/test/general/testInputs.js @@ -1,18 +1,17 @@ - /** * Generates a 64 character long javascript string out of the whole utf-8 range. */ function createSomeMessage(){ - let arr = []; - for (let i = 0; i < 30; i++) { - arr.push(Math.floor(Math.random() * 10174) + 1); - } - for (let i = 0; i < 10; i++) { - arr.push(0x1F600 + Math.floor(Math.random() * (0x1F64F - 0x1F600)) + 1); - } - return '  \t' + String.fromCodePoint(...arr).replace(/\r/g, '\n') + '  \t\n한국어/조선말'; + const arr = []; + for (let i = 0; i < 30; i++) { + arr.push(Math.floor(Math.random() * 10174) + 1); + } + for (let i = 0; i < 10; i++) { + arr.push(0x1F600 + Math.floor(Math.random() * (0x1F64F - 0x1F600)) + 1); + } + return '  \t' + String.fromCodePoint(...arr).replace(/\r/g, '\n') + '  \t\n한국어/조선말'; } module.exports = { - createSomeMessage: createSomeMessage + createSomeMessage: createSomeMessage }; diff --git a/test/general/x25519.js b/test/general/x25519.js index 6ca608fc..bc6f5573 100644 --- a/test/general/x25519.js +++ b/test/general/x25519.js @@ -219,22 +219,24 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr const { publicKey } = openpgp.crypto.publicKey.nacl.sign.keyPair.fromSeed(openpgp.util.hexToUint8Array(vector.SECRET_KEY)); expect(publicKey).to.deep.equal(openpgp.util.hexToUint8Array(vector.PUBLIC_KEY)); const data = util.strToUint8Array(vector.MESSAGE); - const keyIntegers = [ - new openpgp.OID(curve.oid), - new openpgp.MPI(util.hexToStr('40'+vector.PUBLIC_KEY)), - new openpgp.MPI(util.hexToStr(vector.SECRET_KEY)) - ]; + const privateParams = { + seed: util.hexToUint8Array(vector.SECRET_KEY) + }; + const publicParams = { + oid: new openpgp.OID(curve.oid), + Q: util.hexToUint8Array('40' + vector.PUBLIC_KEY) + }; const msg_MPIs = [ new openpgp.MPI(util.uint8ArrayToStr(util.hexToUint8Array(vector.SIGNATURE.R).reverse())), new openpgp.MPI(util.uint8ArrayToStr(util.hexToUint8Array(vector.SIGNATURE.S).reverse())) ]; return Promise.all([ - signature.sign(22, undefined, keyIntegers, undefined, data).then(signed => { - const len = ((signed[0] << 8| signed[1]) + 7) / 8; + signature.sign(22, undefined, publicParams, privateParams, undefined, data).then(signed => { + const len = (((signed[0] << 8) | signed[1]) + 7) / 8; expect(util.hexToUint8Array(vector.SIGNATURE.R)).to.deep.eq(signed.slice(2, 2 + len)); expect(util.hexToUint8Array(vector.SIGNATURE.S)).to.deep.eq(signed.slice(4 + len)); }), - signature.verify(22, undefined, msg_MPIs, keyIntegers, undefined, data).then(result => { + signature.verify(22, undefined, msg_MPIs, publicParams, undefined, data).then(result => { expect(result).to.be.true; }) ]); @@ -242,63 +244,44 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr it('Signature of empty string', function () { return testVector({ - SECRET_KEY: - ['9d61b19deffd5a60ba844af492ec2cc4', - '4449c5697b326919703bac031cae7f60'].join(''), - PUBLIC_KEY: - ['d75a980182b10ab7d54bfed3c964073a', - '0ee172f3daa62325af021a68f707511a'].join(''), + SECRET_KEY: '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60', + PUBLIC_KEY: 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a', MESSAGE: '', - SIGNATURE: - { R: ['e5564300c360ac729086e2cc806e828a', - '84877f1eb8e5d974d873e06522490155'].join(''), - S: ['5fb8821590a33bacc61e39701cf9b46b', - 'd25bf5f0595bbe24655141438e7a100b'].join('') } + SIGNATURE: { + R: 'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155', + S: '5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b' + } }); }); it('Signature of single byte', function () { return testVector({ - SECRET_KEY: - ['4ccd089b28ff96da9db6c346ec114e0f', - '5b8a319f35aba624da8cf6ed4fb8a6fb'].join(''), - PUBLIC_KEY: - ['3d4017c3e843895a92b70aa74d1b7ebc', - '9c982ccf2ec4968cc0cd55f12af4660c'].join(''), + SECRET_KEY: '4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb', + PUBLIC_KEY: '3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c', MESSAGE: util.hexToStr('72'), - SIGNATURE: - { R: ['92a009a9f0d4cab8720e820b5f642540', - 'a2b27b5416503f8fb3762223ebdb69da'].join(''), - S: ['085ac1e43e15996e458f3613d0f11d8c', - '387b2eaeb4302aeeb00d291612bb0c00'].join('') } + SIGNATURE: { + R: '92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da', + S: '085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00' + } }); }); it('Signature of two bytes', function () { return testVector({ - SECRET_KEY: - ['c5aa8df43f9f837bedb7442f31dcb7b1', - '66d38535076f094b85ce3a2e0b4458f7'].join(''), - PUBLIC_KEY: - ['fc51cd8e6218a1a38da47ed00230f058', - '0816ed13ba3303ac5deb911548908025'].join(''), + SECRET_KEY: 'c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7', + PUBLIC_KEY: 'fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025', MESSAGE: util.hexToStr('af82'), - SIGNATURE: - { R: ['6291d657deec24024827e69c3abe01a3', - '0ce548a284743a445e3680d7db5ac3ac'].join(''), - S: ['18ff9b538d16f290ae67f760984dc659', - '4a7c15e9716ed28dc027beceea1ec40a'].join('') } + SIGNATURE: { + R: '6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac', + S: '18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a' + } }); }); it('Signature of 1023 bytes', function () { return testVector({ - SECRET_KEY: - ['f5e5767cf153319517630f226876b86c', - '8160cc583bc013744c6bf255f5cc0ee5'].join(''), - PUBLIC_KEY: - ['278117fc144c72340f67d0f2316e8386', - 'ceffbf2b2428c9c51fef7c597f1d426e'].join(''), + SECRET_KEY: 'f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5', + PUBLIC_KEY: '278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e', MESSAGE: util.hexToStr([ '08b8b2b733424243760fe426a4b54908', '632110a66c2f6591eabd3345e3e4eb98', @@ -365,39 +348,32 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr '0618983f8741c5ef68d3a101e8a3b8ca', 'c60c905c15fc910840b94c00a0b9d0' ].join('')), - SIGNATURE: - { R: ['0aab4c900501b3e24d7cdf4663326a3a', - '87df5e4843b2cbdb67cbf6e460fec350'].join(''), - S: ['aa5371b1508f9f4528ecea23c436d94b', - '5e8fcd4f681e30a6ac00a9704a188a03'].join('') } + SIGNATURE: { + R: '0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350', + S: 'aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03' + } }); }); it('Signature of SHA(abc)', function () { return testVector({ - SECRET_KEY: - ['833fe62409237b9d62ec77587520911e', - '9a759cec1d19755b7da901b96dca3d42'].join(''), - PUBLIC_KEY: - ['ec172b93ad5e563bf4932c70e1245034', - 'c35467ef2efd4d64ebf819683467e2bf'].join(''), + SECRET_KEY: '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42', + PUBLIC_KEY: 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf', MESSAGE: util.hexToStr([ 'ddaf35a193617abacc417349ae204131', '12e6fa4e89a97ea20a9eeee64b55d39a', '2192992a274fc1a836ba3c23a3feebbd', '454d4423643ce80e2a9ac94fa54ca49f' ].join('')), - SIGNATURE: - { R: ['dc2a4459e7369633a52b1bf277839a00', - '201009a3efbf3ecb69bea2186c26b589'].join(''), - S: ['09351fc9ac90b3ecfdfbc7c66431e030', - '3dca179c138ac17ad9bef1177331a704'].join('') } + SIGNATURE: { + R: 'dc2a4459e7369633a52b1bf277839a00201009a3efbf3ecb69bea2186c26b589', + S: '09351fc9ac90b3ecfdfbc7c66431e0303dca179c138ac17ad9bef1177331a704' + } }); }); }); -/* TODO how does GPG2 accept this? - it('Should handle little-endian parameters in EdDSA', function () { + it('Should handle little-endian parameters in EdDSA', async function () { const pubKey = [ '-----BEGIN PGP PUBLIC KEY BLOCK-----', 'Version: OpenPGP.js v3.0.0', @@ -412,19 +388,18 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr 'FQIbDAAAhNQBAKmy4gPorjbwTwy5usylHttP28XnTdaGkZ1E7Rc3G9luAQCs', 'Gbm1oe83ZB+0aSp5m34YkpHQNb80y8PGFy7nIexiAA==', '=xeG/', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + '-----END PGP PUBLIC KEY BLOCK-----' + ].join('\n'); const hi = await openpgp.key.readArmored(pubKey); - const results = hi.getPrimaryUser(); + const results = await hi.getPrimaryUser(); + // console.log(results); expect(results).to.exist; expect(results.user).to.exist; const user = results.user; - expect(user.selfCertifications[0].verify( - hi.primaryKey, {userId: user.userId, key: hi.primaryKey} - )).to.eventually.be.true; await user.verifyCertificate( hi.primaryKey, user.selfCertifications[0], [hi] ); - }); */ + }); describe('X25519 Omnibus Tests', omnibus); });