From 286d99126565504786b96819a9eb543f8db7623d Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Wed, 16 Dec 2020 17:38:21 +0100 Subject: [PATCH] Fix parsing of short P-521 keys and EdDSA, RSA signatures (#1185) Also, strip leading zeros when serializing MPIs, as per the spec. --- src/crypto/crypto.js | 6 +- src/crypto/public_key/elliptic/ecdsa.js | 6 +- src/crypto/signature.js | 28 +++-- src/util.js | 17 +-- test/crypto/validate.js | 38 ++++--- test/general/key.js | 33 ++++++ test/general/openpgp.js | 13 ++- test/general/signature.js | 131 ++++++++++++++++++++++++ test/general/util.js | 25 +++++ test/general/x25519.js | 27 ----- test/worker/application_worker.js | 1 + 11 files changed, 258 insertions(+), 67 deletions(-) diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index e1558b3d..f0da2898 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -149,7 +149,7 @@ export function parsePublicKeyParams(algo, bytes) { 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); + Q = util.leftPad(Q, 33); return { read: read, publicParams: { oid, Q } }; } case enums.publicKey.ecdh: { @@ -191,12 +191,12 @@ export function parsePrivateKeyParams(algo, bytes, publicParams) { 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); + d = util.leftPad(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); + seed = util.leftPad(seed, 32); return { read, privateParams: { seed } }; } default: diff --git a/src/crypto/public_key/elliptic/ecdsa.js b/src/crypto/public_key/elliptic/ecdsa.js index 063bbd5e..6dacca49 100644 --- a/src/crypto/public_key/elliptic/ecdsa.js +++ b/src/crypto/public_key/elliptic/ecdsa.js @@ -207,7 +207,6 @@ async function webSign(curve, hash_algo, message, keyPair) { } async function webVerify(curve, hash_algo, { r, s }, message, publicKey) { - const len = curve.payloadSize; const jwk = rawPublicToJwk(curve.payloadSize, webCurves[curve.name], publicKey); const key = await webCrypto.importKey( "jwk", @@ -221,10 +220,7 @@ async function webVerify(curve, hash_algo, { r, s }, message, publicKey) { ["verify"] ); - const signature = util.concatUint8Array([ - new Uint8Array(len - r.length), r, - new Uint8Array(len - s.length), s - ]).buffer; + const signature = util.concatUint8Array([r, s]).buffer; return webCrypto.verify( { diff --git a/src/crypto/signature.js b/src/crypto/signature.js index fc42f2ba..39946b7b 100644 --- a/src/crypto/signature.js +++ b/src/crypto/signature.js @@ -10,9 +10,10 @@ import publicKey from './public_key'; import enums from '../enums'; import util from '../util'; - /** * Parse signature in binary form to get the parameters. + * The returned values are only padded for EdDSA, since in the other cases their expected length + * depends on the key params, hence we delegate the padding to the signature verification function. * See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} * See {@link https://tools.ietf.org/html/rfc4880#section-5.2.2|RFC 4880 5.2.2.} * @param {module:enums.publicKey} algo Public key algorithm @@ -29,6 +30,8 @@ export function parseSignatureParams(algo, signature) { case enums.publicKey.rsaEncrypt: case enums.publicKey.rsaSign: { const s = util.readMPI(signature.subarray(read)); + // The signature needs to be the same length as the public key modulo n. + // We pad s on signature verification, where we have access to n. return { s }; } // Algorithm-Specific Fields for DSA or ECDSA signatures: @@ -43,12 +46,14 @@ export function parseSignatureParams(algo, signature) { } // Algorithm-Specific Fields for EdDSA signatures: // - MPI of an EC point r. - // - EdDSA value s, in MPI, in the little endian representation. - // EdDSA signature parameters are encoded in little-endian format - // https://tools.ietf.org/html/rfc8032#section-5.1.2 + // - EdDSA value s, in MPI, in the little endian representation case enums.publicKey.eddsa: { - const r = util.padToLength(util.readMPI(signature.subarray(read)), 32, 'le'); read += r.length + 2; - const s = util.padToLength(util.readMPI(signature.subarray(read)), 32, 'le'); + // When parsing little-endian MPI data, we always need to left-pad it, as done with big-endian values: + // https://www.ietf.org/archive/id/draft-ietf-openpgp-rfc4880bis-10.html#section-3.2-9 + let r = util.readMPI(signature.subarray(read)); read += r.length + 2; + r = util.leftPad(r, 32); + let s = util.readMPI(signature.subarray(read)); + s = util.leftPad(s, 32); return { r, s }; } default: @@ -76,20 +81,25 @@ export async function verify(algo, hashAlgo, signature, publicParams, data, hash case enums.publicKey.rsaEncrypt: case enums.publicKey.rsaSign: { const { n, e } = publicParams; - const { s } = signature; + const s = util.leftPad(signature.s, n.length); // padding needed for webcrypto and node crypto return publicKey.rsa.verify(hashAlgo, data, s, n, e, hashed); } case enums.publicKey.dsa: { const { g, p, q, y } = publicParams; - const { r, s } = signature; + const { r, s } = signature; // no need to pad, since we always handle them as BigIntegers return publicKey.dsa.verify(hashAlgo, r, s, hashed, g, p, q, y); } case enums.publicKey.ecdsa: { const { oid, Q } = publicParams; - return publicKey.elliptic.ecdsa.verify(oid, hashAlgo, signature, data, Q, hashed); + const curveSize = new publicKey.elliptic.Curve(oid).payloadSize; + // padding needed for webcrypto + const r = util.leftPad(signature.r, curveSize); + const s = util.leftPad(signature.s, curveSize); + return publicKey.elliptic.ecdsa.verify(oid, hashAlgo, { r, s }, data, Q, hashed); } case enums.publicKey.eddsa: { const { oid, Q } = publicParams; + // signature already padded on parsing return publicKey.elliptic.eddsa.verify(oid, hashAlgo, signature, data, Q, hashed); } default: diff --git a/src/util.js b/src/util.js index 1a3a14de..5c037ec1 100644 --- a/src/util.js +++ b/src/util.js @@ -178,15 +178,14 @@ export default { }, /** - * Pad Uint8Array to length by adding 0x0 bytes + * Left-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') { + leftPad(bytes, length) { const padded = new Uint8Array(length); - const offset = (endianess === 'be') ? 0 : (length - bytes.length); + const offset = length - bytes.length; padded.set(bytes, offset); return padded; }, @@ -197,9 +196,15 @@ export default { * @returns {Uint8Array} MPI-formatted Uint8Array */ uint8ArrayToMpi: function (bin) { - const size = (bin.length - 1) * 8 + util.nbits(bin[0]); + let i; // index of leading non-zero byte + for (i = 0; i < bin.length; i++) if (bin[i] !== 0) break; + if (i === bin.length) { + throw new Error('Zero MPI'); + } + const stripped = bin.subarray(i); + const size = (stripped.length - 1) * 8 + util.nbits(stripped[0]); const prefix = Uint8Array.from([(size & 0xFF00) >> 8, size & 0xFF]); - return util.concatUint8Array([prefix, bin]); + return util.concatUint8Array([prefix, stripped]); }, /** diff --git a/test/crypto/validate.js b/test/crypto/validate.js index bfe3c883..7e70fe57 100644 --- a/test/crypto/validate.js +++ b/test/crypto/validate.js @@ -80,6 +80,7 @@ function cloneKeyPacket(key) { return keyPacket; } +/* eslint-disable no-invalid-this */ module.exports = () => { describe('EdDSA parameter validation', function() { let eddsaKey; @@ -201,28 +202,32 @@ module.exports = () => { } }); - if (ecdsaKey) { - it(`EcDSA ${curve} params should be valid`, async function() { - await expect(ecdsaKey.keyPacket.validate()).to.not.be.rejected; - }); + it(`EcDSA ${curve} params should be valid`, async function() { + if (!ecdsaKey) { + this.skip(); + } + await expect(ecdsaKey.keyPacket.validate()).to.not.be.rejected; + }); - it('detect invalid EcDSA Q', async function() { - const keyPacket = cloneKeyPacket(ecdsaKey); - const Q = keyPacket.publicParams.Q; - Q[0]++; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - - const infQ = new Uint8Array(Q.length); - keyPacket.publicParams.Q = infQ; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); - } + it(`ECDSA ${curve} - detect invalid Q`, async function() { + if (!ecdsaKey) { + this.skip(); + } + const keyPacket = cloneKeyPacket(ecdsaKey); + const Q = keyPacket.publicParams.Q; + Q[16]++; + await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); + const infQ = new Uint8Array(Q.length); + infQ[0] = 4; + keyPacket.publicParams.Q = infQ; + await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); + }); it(`ECDH ${curve} params should be valid`, async function() { await expect(ecdhKey.keyPacket.validate()).to.not.be.rejected; }); - it('detect invalid ECDH Q', async function() { + it(`ECDH ${curve} - detect invalid Q`, async function() { const keyPacket = cloneKeyPacket(ecdhKey); const Q = keyPacket.publicParams.Q; Q[16]++; @@ -230,6 +235,7 @@ module.exports = () => { const infQ = new Uint8Array(Q.length); keyPacket.publicParams.Q = infQ; + infQ[0] = 4; await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); }); }); diff --git a/test/general/key.js b/test/general/key.js index bc8dbecd..72480f61 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -2020,6 +2020,31 @@ EBeLgD8oZHVsH3NLjPakPw== =STqy -----END PGP MESSAGE-----`; +const shortP521Key = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xcAiBV/Pa+4TAAAAjQUrgQQAIwQjBADY+IGvRpeUzbT0+YRUe0KCMxAZmDY1 +KjDzULlmOJOS0bfZfqd4HsUF2hRoU/rg1gu1ju/Nt/18db0SJExOqVB9CgA0 +ZYiYyJhGYDOVg/bD54E7a3txWuDPB1DzkGWJH8PkqGNzU0BJpZcUVA6Th09s +YeO7rx5jSoyWNXjUYKwc24trFAAAAAAARQIItVgIiTWNT+QEVnZqDKKTIOUU +XEetkjCjPed1RiSchiZpwq+Bvx5hWGsbV5Pjj0S6EuH/ca5w+2ZyITLWZjr1 +LP8eP80UVGVzdCA8dGVzdEB0ZXN0LmNvbT7CwBUFEBMKACEFAl/Pa+4ECwkH +CAMVCAoEFgIBAAIZAQIbAwIeBwMiAQIAIyIhBbDerrGG7kdy9vbLYvb/j6TC +53fuQgK9Gtt1xng5MgpSUX0CCJZB+Ppt8yG5hBzwiGz5ZRpPVBFihEftaTOO +tKUuYRpWlvgA/XV11DgL6KZWRwu4C2venydBW987CVXCbRp4r18FAgkBjTR1 +AXHEstTVwJYj8mWkOZrz+Bfqvu6pWPmVYclgHJK2sSWizakvX/DtX/LFgTJL +UoASOVvu1hYHDsCO7vWWC/bHwCYFX89r7hIAAACRBSuBBAAjBCMEAULqNQ3L +CcdVlpIHiq4Xb+elTEdu2oDDA+FCbwroX3wvMatrH6GodxCcrjQKUrfVNiiI +cvj+r6SE/pRDnxsvW/JSAWUz3XKfVXccb0PYf0ikkTmb8UW33AaNYX6Srk0W +iamEmEzUpCMiiyXiYe+fp9JD63rKLXBbvLCT2mHuYO/hOikKAwEKCQAAAAAA +RQIDBONtE8bb3Yr2otNhdR67lg529mm3rSRsyWwMBVUPwX0RTTZ/bejq7XP5 +fuXV8QSEjWdOdPBARGw9jhw51D1XWl8gFsK9BRgTCgAJBQJfz2vuAhsMACMi +IQWw3q6xhu5Hcvb2y2L2/4+kwud37kICvRrbdcZ4OTIKUiWwAgkBdH+OZHBt +D2Yx2xKVPqDGJgMa5Ta8GmQZOFnoC2SpB6i9hIOfwiNjNLs+bv+kTxZ09nzf +3ZUGYi5Ei70hLrDAy7UCCNQNObtPmUYaUTtRzj3S9jUohbIpQxcfyoCMh6aP +usLw5q4tc+I5gdq57aiulJ8r4Jj9rdzsZFA7PzNJ9WPGVYJ3 +=GSXO +-----END PGP PRIVATE KEY BLOCK-----`; + function versionSpecificTests() { it('Preferences of generated key', function() { const testPref = function(key) { @@ -2741,6 +2766,14 @@ module.exports = () => describe('Key', function() { await expect(pubKey.getEncryptionKey()).to.be.rejectedWith('Could not find valid encryption key packet in key c076e634d32b498d'); }); + it('should pad an ECDSA P-521 key with shorter secret key', async function() { + const key = await openpgp.readArmoredKey(shortP521Key); + // secret key should be padded + expect(key.keyPacket.privateParams.d.length === 66); + // sanity check + await expect(key.validate()).to.be.fulfilled; + }); + it('should not decrypt using a sign-only RSA key, unless explicitly configured', async function () { const allowSigningKeyDecryption = openpgp.config.allowInsecureDecryptionWithSigningKeys; const key = await openpgp.readArmoredKey(rsaSignOnly); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 23c6d81c..8ed2c0a7 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -2457,7 +2457,19 @@ amnR6g== }); expect(util.decodeUtf8(decrypted.data)).to.equal('"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\nUID:123\r\nDTSTART:20191211T121212Z\r\nDTEND:20191212T121212Z\r\nEND:VEVENT\r\nEND:VCALENDAR"'); }); + }); + describe('Sign and verify with each curve', function() { + const curves = ['secp256k1' , 'p256', 'p384', 'p521', 'curve25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1']; + curves.forEach(curve => { + it(`sign/verify with ${curve}`, async function() { + const plaintext = 'short message'; + const key = (await openpgp.generateKey({ curve, userIds: 'Alice ' })).key; + const signed = await openpgp.sign({ privateKeys:[key], message: openpgp.CleartextMessage.fromText(plaintext) }); + const verified = await openpgp.verify({ publicKeys:[key], message: await openpgp.readArmoredCleartextMessage(signed) }); + expect(verified.signatures[0].valid).to.be.true; + }); + }); }); describe('Errors', function() { @@ -2474,7 +2486,6 @@ amnR6g== }); }); - }); }); diff --git a/test/general/signature.js b/test/general/signature.js index b0958be8..11d723a7 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -1698,4 +1698,135 @@ iTuGu4fEU1UligAXSrZmCdE= } }); + it('should verify a shorter RSA signature', async function () { + const encrypted = `-----BEGIN PGP MESSAGE----- + +wYwD4IT3RGwgLJcBBACmH+a2c2yieZJ3wFchKeTVqzWkoltiidWgHHNE5v5x +8aZGNzZFBd02v80VS23P9oxeJOpqKX2IZyuD36SniNoi+eXdT3zraqIe9x5p +0RY9OrTP9pl58iogFBi1ARls41j7ui8KKDt2/iyQDCWHW1LoOVstiEb5/Xi3 +EWI+34EbNNTBMgEJAQAwEXImkOPmhYhE7bB3FnXe9rb7Fo3GZYA4/8B9YVf7 +GGZRLGwbICGu8E0MolmzLYW9hRThEfusAsNPGSgB+Yaqp0drsk01N4JJj3FT +RKEUvd5EcL3u+Z5EoUUW6GpUL5p8Hvy2thqQfeem7XUbDBY6V3wqydOjbN9u +c4CWB5Zu3GjDGDOvXFsy6cgdQvd/B9xbugKvUbAIsecTPlLtjZwfQklIu63T +DA/3Pz/+zTAknBCsuIM0m7U/ZP3N6AGQIp4To7RJk0I6AxthHF5LbU11MjDZ +iB7+vmhqlrPyIS11g25UNijottJm13f84glVwBdWTJCiEqjh3KbcnTQCckCY +V39DDLtbZG/XIx1ktqp765O9D/9xp2IA4zTyZzH4TuDbYs1j+JRdMsAq254k +1m+wtW5gxJGcD5nh2T2T+ABL0n3jW0G504kR0LNBAQOZhVSKnSLn+F0GkjmI +iGw8+BOy8p2pX/WCLOf776ppSL77TpzhpG6wSE2oQxDrudazmRgVkZpyGzFE +fDjspLTJHOhZ5zlLuoiKS9qEARGp39ysQnElR4dsx7tyVZz0uJvIrVzrQBlB +ekoD0DH0bhfqiwDrqeTJT2ORk8I/Q3jWnhQ3MnRN+q9d0yf1LWApMCwA7xU2 +C4KUFRC/wuF2TR9NvA== +=v3WS +-----END PGP MESSAGE-----`; + const armoredKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +oaBUyhCKt8tz6Q== +=52k1 +-----END PGP PRIVATE KEY BLOCK-----`; + const key = await openpgp.readArmoredKey(armoredKey); + const decrypted = await openpgp.decrypt({ + message: await openpgp.readArmoredMessage(encrypted), + publicKeys: key, + privateKeys: key + }); + expect(decrypted.signatures[0].valid).to.be.true; + }); + + it('should verify a shorter EdDSA signature', async function() { + const key = await openpgp.readArmoredKey(`-----BEGIN PGP PRIVATE KEY BLOCK----- + +xVgEX8+jfBYJKwYBBAHaRw8BAQdA9GbdDjprR0sWf0R5a5IpulUauc0FsmzJ +mOYCfoowt8EAAP9UwaqC0LWWQ5RlX7mps3728vFa/If1KBVwAjk7Uqhi2BKL +zQ90ZXN0MiA8YkBhLmNvbT7CjAQQFgoAHQUCX8+jfAQLCQcIAxUICgQWAgEA +AhkBAhsDAh4BACEJEG464aV2od77FiEEIcg441MtKnyJnPDRbjrhpXah3vuR +gQD+Il6Gw2oIok4/ANyDDLBYZtKqRrMv4NcfF9DHYuAFcP4BAPhFOffyP3qU +AEZb7QPrWdLfhn8/FeSFZxJvnmupQ9sDx10EX8+jfBIKKwYBBAGXVQEFAQEH +QOSzo9cX1U2esGFClprOt0QWXNJ97228R5tKFxo6/0NoAwEIBwAA/0n4sq2i +N6/jE+6rVO4o/7LW0xahxpV1tTA6qv1Op9TwFIDCeAQYFggACQUCX8+jfAIb +DAAhCRBuOuGldqHe+xYhBCHIOONTLSp8iZzw0W464aV2od773XcA/jlmX8/c +1/zIotEkyMZB4mI+GAg3FQ6bIACFBH1sz0MzAP9Snri0P4FRZ8D5THRCJoUm +GBgpBmrf6IVv484jBswGDA== +=8rBO +-----END PGP PRIVATE KEY BLOCK-----`); + const encrypted = `-----BEGIN PGP MESSAGE----- + +wV4DWlRRjuYiLSsSAQdAWwDKQLN4ZUS5fqiwFtAMrRfZZe9J4SgClhG6avEe +AEowkSZwWRT+8Hy8aBIb4oPehYUFXXZ7BtlJCyd7LOTUtqyc00OE0721PC3M +v0+zird60sACATlDmTwweR5GFtEAjHVheIL5rbkOBRD+oSqB8z+IovNg83Pz +FVwsFZnCLtECoYgpF2MJdopuC/bPHcrvf4ndwmD11uXtms4Rq4y25QyqApbn +Hj/hljufk0OkavUXxrNKjGQtxLHMpa3Nsi0MHWY8JguxOKFKpAIMP32CD1e+ +j+GItrR+QbbN13ODlcR3hf66cwjLLsJCx5VcBaRspKF05O3ix/u9KVjJqtbi +Ie6jnY0zP2ldtS4JmhKBa43qmOHCxHc= +=7B58 +-----END PGP MESSAGE-----`; + const decrypted = await openpgp.decrypt({ message: await openpgp.readArmoredMessage(encrypted), privateKeys: key, publicKeys: key.toPublic() }); + expect(decrypted.signatures[0].valid).to.be.true; + }); + + it('should verify a shorter ECDSA signature', async function() { + const key = await openpgp.readArmoredKey(`-----BEGIN PGP PRIVATE KEY BLOCK----- + +xYAFX9JrLRMAAABMCCqGSM49AwEHAgMErtQdX4vh7ng/ut+k1mooYNh3Ywqt +wr0tSS8hxZMvQRIFQ53Weq0e97ioZKXGimprEL571yvAN7I19wtQtqi61AAA +AAAAJAEAjWdW+qlMFaKwXCls3O/X8I1rbZ0OdFgeE3TnRP3YETAP5s0KYSA8 +YUBhLml0PsKSBRATCAAhBQJf0mstBAsJBwgDFQgKBBYCAQACGQECGwMCHgcD +IgECACMiIQUee6Tb+GlhTk/ozKrt7RhInCyR6w3OJb/tYAN1+qbIoYUqAP9S +XmJCmSMrq6KfAD1aWSTBhtmujh+6y/pYTaf6VJVBYQEAt18zK0tw5EihHASY +FXbfdFHBzrMmPJ4UV6UiBvH6k2zHhAVf0mstEgAAAFAIKoZIzj0DAQcCAwQx +qnVPmWex365Nx8X8BGuMNI2TITXzTh9+AuPftZjPm09dhxdT9xmrCstPu/U1 +cpacIp0LIq13ngLgeZWcGFcnAwEIBwAAAAAAJAEAsTvBsKk/XoCz2mi8sz5q +EYaN9YdDOU2jF+HOaSNaJAsPF8J6BRgTCAAJBQJf0mstAhsMACMiIQUee6Tb ++GlhTk/ozKrt7RhInCyR6w3OJb/tYAN1+qbIoVutAP9GHPLn7D9Uahm81lhK +AcvDfr9a0Cp4WAVzKDKLUzrRMgEAozi0VyjiBo1U2LcwTPJkA4PEQqQRVW1D +KZTMSAH7JEo= +=tqWy +-----END PGP PRIVATE KEY BLOCK-----`); + const signed = `-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA256 + +short message +-----BEGIN PGP SIGNATURE----- + +wnYFARMIAAYFAl/Say0AIyIhBR57pNv4aWFOT+jMqu3tGEicLJHrDc4lv+1g +A3X6psihFkcA+Nuog2qpAq20Zc2lzVjDZzQosb8MLvKMg3UFCX12Oc0BAJwd +JImeZLY02MctIpGZULbqgcUGK0P/yqrPL8Pe4lQM +=Pacb +-----END PGP SIGNATURE-----`; + const message = await openpgp.readArmoredCleartextMessage(signed); + const verified = await openpgp.verify({ publicKeys: key, message }); + expect(verified.signatures[0].valid).to.be.true; + }); }); diff --git a/test/general/util.js b/test/general/util.js index 305486f8..68648bb0 100644 --- a/test/general/util.js +++ b/test/general/util.js @@ -82,6 +82,31 @@ module.exports = () => describe('Util unit tests', function() { }); }); + describe('leftPad', function() { + it('should not change the input if the length is correct', function() { + const bytes = new Uint8Array([2, 1]); + const padded = util.leftPad(bytes, 2); + expect(padded).to.deep.equal(bytes); + }); + it('should add leading zeros to input array', function() { + const bytes = new Uint8Array([1, 2]); + const padded = util.leftPad(bytes, 5); + expect(padded).to.deep.equal(new Uint8Array([0, 0, 0, 1, 2])); + }); + }); + + describe('uint8ArrayToMpi', function() { + it('should strip leading zeros', function() { + const bytes = new Uint8Array([0, 0, 1, 2]); + const mpi = util.uint8ArrayToMpi(bytes); + expect(mpi).to.deep.equal(new Uint8Array([0, 9, 1, 2])); + }); + it('should throw on array of all zeros', function() { + const bytes = new Uint8Array([0, 0]); + expect(() => util.uint8ArrayToMpi(bytes)).to.throw('Zero MPI'); + }); + }); + describe('isEmailAddress', function() { it('should return true for valid email address', function() { const data = 'test@example.com'; diff --git a/test/general/x25519.js b/test/general/x25519.js index 7910b4ae..f5bb8221 100644 --- a/test/general/x25519.js +++ b/test/general/x25519.js @@ -372,33 +372,6 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr }); }); - it('Should handle little-endian parameters in EdDSA', async function () { - const pubKey = [ - '-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: OpenPGP.js v3.0.0', - 'Comment: https://openpgpjs.org', - '', - 'xjMEWnRgnxYJKwYBBAHaRw8BAQdAZ8gxxCdUxIv4tBwhfUMW2uoEb1KvOfP8', - 'D+0ObBtsLnfNDkhpIDxoaUBoZWwubG8+wnYEEBYKACkFAlp0YJ8GCwkHCAMC', - 'CRDAYsFlymHCFQQVCAoCAxYCAQIZAQIbAwIeAQAAswsA/3qNZnwBn/ef4twv', - 'uvmFicYK//DDX1jIkpDiQ+/okLUEAPdAr3J/Z2WA7OD0d36trHNB06WLXJUu', - 'aCVm1TwoJHcNzjgEWnRgnxIKKwYBBAGXVQEFAQEHQPBVH+skap0NHMBw2HMe', - 'xWYUQ67I9Did3KoJuuEJ/ctQAwEIB8JhBBgWCAATBQJadGCfCRDAYsFlymHC', - 'FQIbDAAAhNQBAKmy4gPorjbwTwy5usylHttP28XnTdaGkZ1E7Rc3G9luAQCs', - 'Gbm1oe83ZB+0aSp5m34YkpHQNb80y8PGFy7nIexiAA==', - '=xeG/', - '-----END PGP PUBLIC KEY BLOCK-----' - ].join('\n'); - const hi = await openpgp.readArmoredKey(pubKey); - const results = await hi.getPrimaryUser(); - expect(results).to.exist; - expect(results.user).to.exist; - const user = results.user; - await user.verifyCertificate( - hi.primaryKey, user.selfCertifications[0], [hi] - ); - }); - describe('X25519 Omnibus Tests', omnibus); }); diff --git a/test/worker/application_worker.js b/test/worker/application_worker.js index 7cdbb78b..574e7c93 100644 --- a/test/worker/application_worker.js +++ b/test/worker/application_worker.js @@ -4,6 +4,7 @@ const chai = require('chai'); const { expect } = chai; +/* eslint-disable no-invalid-this */ module.exports = () => tryTests('Application Worker', tests, { if: typeof window !== 'undefined' && window.Worker && window.MessageChannel });