mirror of
https://github.com/azlux/pymumble
synced 2024-11-23 13:56:26 +00:00
192 lines
5.9 KiB
Python
192 lines
5.9 KiB
Python
'''
|
|
Ported from Mumble src/tests/TestCrypt/TestCrypt.cpp
|
|
|
|
=============================================================
|
|
|
|
Copyright 2005-2020 The Mumble Developers. All rights reserved.
|
|
Use of this source code is governed by a BSD-style license
|
|
that can be found in the LICENSE file at the root of the
|
|
Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
|
'''
|
|
|
|
import pytest
|
|
|
|
from .crypto import CryptStateOCB2, AES_BLOCK_SIZE, EncryptFailedException, DecryptFailedException
|
|
from .crypto import ocb_encrypt, ocb_decrypt
|
|
|
|
|
|
@pytest.fixture()
|
|
def rawkey():
|
|
return bytes((0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f))
|
|
|
|
|
|
@pytest.fixture()
|
|
def nonce():
|
|
return bytes((0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00))
|
|
|
|
|
|
def test_reverserecovery():
|
|
enc = CryptStateOCB2()
|
|
dec = CryptStateOCB2()
|
|
enc.gen_key()
|
|
|
|
# For our testcase, we're going to FORCE iv
|
|
enc.encrypt_iv = bytes((0x55,) * AES_BLOCK_SIZE)
|
|
dec.set_key(enc.raw_key, enc.decrypt_iv, enc.encrypt_iv)
|
|
|
|
secret = "abcdefghi".encode('ascii')
|
|
|
|
crypted = [enc.encrypt(secret) for _ in range(128)]
|
|
|
|
i = 0
|
|
for crypt in reversed(crypted[-30:]):
|
|
print(i)
|
|
i += 1
|
|
dec.decrypt(crypt, len(secret))
|
|
for crypt in reversed(crypted[:-30]):
|
|
with pytest.raises(DecryptFailedException):
|
|
dec.decrypt(crypt, len(secret))
|
|
for crypt in reversed(crypted[-30:]):
|
|
with pytest.raises(DecryptFailedException):
|
|
dec.decrypt(crypt, len(secret))
|
|
|
|
# Extensive replay attack test
|
|
crypted = [enc.encrypt(secret) for _ in range(512)]
|
|
|
|
for crypt in crypted:
|
|
decr = dec.decrypt(crypt, len(secret))
|
|
for crypt in crypted:
|
|
with pytest.raises(DecryptFailedException):
|
|
dec.decrypt(crypt, len(secret))
|
|
|
|
|
|
def test_ivrecovery():
|
|
enc = CryptStateOCB2()
|
|
dec = CryptStateOCB2()
|
|
enc.gen_key()
|
|
|
|
# For our testcase, we're going to FORCE iv
|
|
enc.encrypt_iv = bytes((0x55,) * AES_BLOCK_SIZE)
|
|
dec.set_key(enc.raw_key, enc.decrypt_iv, enc.encrypt_iv)
|
|
|
|
secret = "abcdefghi".encode('ascii')
|
|
|
|
crypted = enc.encrypt(secret)
|
|
|
|
# Can decrypt
|
|
decr = dec.decrypt(crypted, len(secret))
|
|
# ... correctly.
|
|
assert secret == decr
|
|
|
|
# But will refuse to reuse same IV.
|
|
with pytest.raises(DecryptFailedException):
|
|
dec.decrypt(crypted, len(secret))
|
|
|
|
# Recover from lost packet.
|
|
for i in range(16):
|
|
crypted = enc.encrypt(secret)
|
|
decr = dec.decrypt(crypted, len(secret))
|
|
|
|
# Wraparound.
|
|
for i in range(128):
|
|
dec.uiLost = 0
|
|
for j in range(15):
|
|
crypted = enc.encrypt(secret)
|
|
decr = dec.decrypt(crypted, len(secret))
|
|
assert dec.uiLost == 14
|
|
|
|
assert enc.encrypt_iv == dec.decrypt_iv
|
|
|
|
# Wrap too far
|
|
for i in range(257):
|
|
crypted = enc.encrypt(secret);
|
|
|
|
with pytest.raises(DecryptFailedException):
|
|
dec.decrypt(crypted, len(secret))
|
|
|
|
# Sync it
|
|
dec.decrypt_iv = enc.encrypt_iv
|
|
crypted = enc.encrypt(secret)
|
|
|
|
decr = dec.decrypt(crypted, len(secret))
|
|
|
|
def test_testvectors(rawkey):
|
|
# Test vectors are from draft-krovetz-ocb-00.txt
|
|
cs = CryptStateOCB2()
|
|
cs.set_key(rawkey, rawkey, rawkey)
|
|
|
|
_, tag = ocb_encrypt(cs._aes, bytes(), rawkey)
|
|
|
|
blanktag = bytes((0xBF,0x31,0x08,0x13,0x07,0x73,0xAD,0x5E,0xC7,0x0E,0xC6,0x9E,0x78,0x75,0xA7,0xB0))
|
|
assert len(blanktag) == AES_BLOCK_SIZE
|
|
|
|
assert tag == blanktag
|
|
|
|
source = bytes(range(40))
|
|
crypt, tag = ocb_encrypt(cs._aes, source, rawkey)
|
|
longtag = bytes((0x9D,0xB0,0xCD,0xF8,0x80,0xF7,0x3E,0x3E,0x10,0xD4,0xEB,0x32,0x17,0x76,0x66,0x88))
|
|
crypted = bytes((0xF7,0x5D,0x6B,0xC8,0xB4,0xDC,0x8D,0x66,0xB8,0x36,0xA2,0xB0,0x8B,0x32,0xA6,0x36,0x9F,0x1C,0xD3,0xC5,0x22,0x8D,0x79,0xFD,
|
|
0x6C,0x26,0x7F,0x5F,0x6A,0xA7,0xB2,0x31,0xC7,0xDF,0xB9,0xD5,0x99,0x51,0xAE,0x9C))
|
|
|
|
assert tag == longtag
|
|
assert crypt[:len(crypted)] == crypted
|
|
|
|
|
|
def test_authcrypt(rawkey, nonce):
|
|
cs = CryptStateOCB2()
|
|
for ll in range(128):
|
|
cs.set_key(rawkey, nonce, nonce)
|
|
src = bytes((i + 1 for i in range(ll)))
|
|
|
|
encrypted, enctag = ocb_encrypt(cs._aes, src, nonce)
|
|
decrypted, dectag = ocb_decrypt(cs._aes, encrypted, nonce, len(src))
|
|
|
|
assert enctag == dectag
|
|
assert src == decrypted
|
|
|
|
|
|
def test_xexstarAttack(rawkey, nonce):
|
|
''' Test prevention of the attack described in section 4.1 of https://eprint.iacr.org/2019/311 '''
|
|
cs = CryptStateOCB2()
|
|
cs.set_key(rawkey, nonce, nonce)
|
|
|
|
# Set first block to `len(secondBlock)`
|
|
# Set second block to arbitrary value
|
|
src = bytearray(AES_BLOCK_SIZE) + bytearray([42] * AES_BLOCK_SIZE)
|
|
src[AES_BLOCK_SIZE - 1] = AES_BLOCK_SIZE * 8
|
|
print(src)
|
|
|
|
with pytest.raises(EncryptFailedException):
|
|
ocb_encrypt(cs._aes, src, nonce)
|
|
encrypted, enctag = ocb_encrypt(cs._aes, src, nonce, insecure=True)
|
|
|
|
# Perform the attack
|
|
encrypted = bytearray(encrypted)
|
|
enctag = bytearray(enctag)
|
|
encrypted[AES_BLOCK_SIZE - 1] ^= AES_BLOCK_SIZE * 8
|
|
for i in range(AES_BLOCK_SIZE):
|
|
enctag[i] = src[AES_BLOCK_SIZE + i] ^ encrypted[AES_BLOCK_SIZE + i]
|
|
|
|
with pytest.raises(DecryptFailedException):
|
|
dc, dct = ocb_decrypt(cs._aes, encrypted, nonce, AES_BLOCK_SIZE)
|
|
print(dc, dct, enctag == dct)
|
|
decrypted, dectag = ocb_decrypt(cs._aes, encrypted, nonce, AES_BLOCK_SIZE, insecure=True)
|
|
|
|
# Verify forged tag (should match if attack is properly implemented)
|
|
assert enctag == dectag
|
|
|
|
|
|
def test_tamper(rawkey, nonce):
|
|
cs = CryptStateOCB2()
|
|
cs.set_key(rawkey, nonce, nonce)
|
|
|
|
msg = "It was a funky funky town!".encode('ascii')
|
|
encrypted = bytearray(cs.encrypt(msg))
|
|
|
|
for i in range(len(msg) * 8):
|
|
encrypted[i // 8] ^= 1 << (i % 8)
|
|
with pytest.raises(DecryptFailedException):
|
|
cs.decrypt(encrypted, len(msg))
|
|
encrypted[i // 8] ^= 1 << (i % 8)
|
|
decrypted = cs.decrypt(encrypted, len(msg))
|