Skip to content

Commit

Permalink
FIX(ocb2): Work around packet loss due to OCB2 XEX* mitigation
Browse files Browse the repository at this point in the history
The mitigation for vulnerabilities discovered in
OCB2 (https://eprint.iacr.org/2019/311, called XEX* attack, or XEXStarAttack, in code)
introduced in be97594 (mumble-voip#4227) willingly allowed for some packets with specific
characteristics to be dropped during encryption to prevent the vulnerability
from being exploited.
It was assumed that the chance of such packets was sufficiently small (given
we are dealing with compressed audio) that such loss was acceptable.

It was however discovered that digital silence (as produced by e.g. a noise
gate) will cause Opus to emit almost exclusively such packets, leading to strong
artifacts on the receiving end. See mumble-voip#4385.

This commit tries to work around the issue by modifying such packets in a way
which will no longer require them to be dropped, and yet produce the expected
output on the receiver side.
As far as I understand [Opus] (specifically section 4.1, 4.3.0 and 4.3.3), the
0s are simply unused bits and are only there because we running Opus in constant
bitrate mode. So, flipping one of them should have no effect on the resulting
audio.

[Opus]: https://tools.ietf.org/html/rfc6716

Fixes mumble-voip#4719
  • Loading branch information
Johni0702 committed Feb 4, 2021
1 parent bc6b6a3 commit 8990f5a
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 11 deletions.
34 changes: 26 additions & 8 deletions src/crypto/CryptStateOCB2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ static void inline ZERO(keyblock &block) {
AES_decrypt(reinterpret_cast< const unsigned char * >(src), reinterpret_cast< unsigned char * >(dst), key);

bool CryptStateOCB2::ocb_encrypt(const unsigned char *plain, unsigned char *encrypted, unsigned int len,
const unsigned char *nonce, unsigned char *tag) {
const unsigned char *nonce, unsigned char *tag, bool modifyPlainOnXEXStarAttack) {
keyblock checksum, delta, tmp, pad;
bool success = true;

Expand All @@ -270,21 +270,39 @@ bool CryptStateOCB2::ocb_encrypt(const unsigned char *plain, unsigned char *encr
ZERO(checksum);

while (len > AES_BLOCK_SIZE) {
S2(delta);
XOR(tmp, delta, reinterpret_cast< const subblock * >(plain));
AESencrypt(tmp, tmp, &encrypt_key);
XOR(reinterpret_cast< subblock * >(encrypted), delta, tmp);
XOR(checksum, checksum, reinterpret_cast< const subblock * >(plain));

// Counter-cryptanalysis described in section 9 of https://eprint.iacr.org/2019/311
// For an attack, the second to last block (i.e. the last iteration of this loop)
// must be all 0 except for the last byte (which may be 0 - 128).
bool flipABit = false; // *plain is const, so we can't directly modify it
if (len - AES_BLOCK_SIZE <= AES_BLOCK_SIZE) {
unsigned char sum = 0;
for (int i = 0; i < AES_BLOCK_SIZE - 1; ++i) {
sum |= plain[i];
}
success &= sum != 0;
if (sum == 0) {
if (modifyPlainOnXEXStarAttack) {
// The assumption that critical packets do not turn up by pure chance turned out to be incorrect
// since digital silence appears to produce them in mass.
// So instead we now modify the packet in a way which should not affect the audio but will
// prevent the attack.
flipABit = true;
} else {
// This option still exists but only to allow us to test ocb_decrypt's detection.
success = false;
}
}
}

S2(delta);
XOR(tmp, delta, reinterpret_cast< const subblock * >(plain));
if (flipABit) {
*reinterpret_cast< unsigned char * >(tmp) ^= 1;
}
AESencrypt(tmp, tmp, &encrypt_key);
XOR(reinterpret_cast< subblock * >(encrypted), delta, tmp);
XOR(checksum, checksum, reinterpret_cast< const subblock * >(plain));
if (flipABit) {
*reinterpret_cast< unsigned char * >(checksum) ^= 1;
}

len -= AES_BLOCK_SIZE;
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/CryptStateOCB2.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class CryptStateOCB2 : public CryptState {
virtual bool encrypt(const unsigned char *source, unsigned char *dst, unsigned int plain_length) Q_DECL_OVERRIDE;

bool ocb_encrypt(const unsigned char *plain, unsigned char *encrypted, unsigned int len, const unsigned char *nonce,
unsigned char *tag);
unsigned char *tag, bool modifyPlainOnXEXStarAttack = true);
bool ocb_decrypt(const unsigned char *encrypted, unsigned char *plain, unsigned int len, const unsigned char *nonce,
unsigned char *tag);

Expand Down
20 changes: 18 additions & 2 deletions src/tests/TestCrypt/TestCrypt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,9 @@ void TestCrypt::xexstarAttack() {
unsigned char enctag[AES_BLOCK_SIZE];
unsigned char dectag[AES_BLOCK_SIZE];
STACKVAR(unsigned char, encrypted, 2 * AES_BLOCK_SIZE);
STACKVAR(unsigned char, decrypted, 1 * AES_BLOCK_SIZE);
STACKVAR(unsigned char, decrypted, 2 * AES_BLOCK_SIZE);

const bool failed_encrypt = !cs.ocb_encrypt(src, encrypted, 2 * AES_BLOCK_SIZE, nonce, enctag);
const bool failed_encrypt = !cs.ocb_encrypt(src, encrypted, 2 * AES_BLOCK_SIZE, nonce, enctag, false);

// Perform the attack
encrypted[AES_BLOCK_SIZE - 1] ^= AES_BLOCK_SIZE * 8;
Expand All @@ -251,6 +251,22 @@ void TestCrypt::xexstarAttack() {
// Make sure we detected the attack
QVERIFY(failed_encrypt);
QVERIFY(failed_decrypt);

// The assumption that critical packets do not turn up by pure chance turned out to be incorrect
// since digital silence appears to produce them in mass.
// So instead we now modify the packet in a way which should not affect the audio but will
// prevent the attack.
QVERIFY(cs.ocb_encrypt(src, encrypted, 2 * AES_BLOCK_SIZE, nonce, enctag));
QVERIFY(cs.ocb_decrypt(encrypted, decrypted, 2 * AES_BLOCK_SIZE, nonce, dectag));

// Tags should match
for (int i = 0; i < AES_BLOCK_SIZE; ++i) {
QCOMPARE(enctag[i], dectag[i]);
}

// Actual content should have been changed such that the critical block is no longer all 0.
QCOMPARE(src[0], static_cast<unsigned char>(0));
QCOMPARE(decrypted[0], static_cast<unsigned char>(1));
}

void TestCrypt::tamper() {
Expand Down

0 comments on commit 8990f5a

Please sign in to comment.