Skip to content

Commit 79d6798

Browse files
Merge pull request #519 from c00kiemon5ter/fix-aes-ctr-ecb-CVE-2017-1000246
Fix AES IV reuse - drop support for CTR and ECB - address CVE-2017-1000246
2 parents d5e4e1b + 0e6aab4 commit 79d6798

File tree

4 files changed

+83
-52
lines changed

4 files changed

+83
-52
lines changed

src/saml2/aes.py

Lines changed: 8 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,27 @@
1111
POSTFIX_MODE = {
1212
'cbc': modes.CBC,
1313
'cfb': modes.CFB,
14-
'ecb': modes.ECB,
1514
}
1615

1716
AES_BLOCK_SIZE = int(algorithms.AES.block_size / 8)
1817

1918

2019
class AESCipher(object):
21-
def __init__(self, key, iv=None):
20+
def __init__(self, key):
2221
"""
2322
:param key: The encryption key
24-
:param iv: Init vector
2523
:return: AESCipher instance
2624
"""
2725
self.key = key
28-
self.iv = iv
2926

30-
def build_cipher(self, iv=None, alg='aes_128_cbc'):
27+
def build_cipher(self, alg='aes_128_cbc'):
3128
"""
32-
:param iv: init vector
3329
:param alg: cipher algorithm
3430
:return: A Cipher instance
3531
"""
3632
typ, bits, cmode = alg.lower().split('_')
3733
bits = int(bits)
38-
39-
if not iv:
40-
if self.iv:
41-
iv = self.iv
42-
else:
43-
iv = os.urandom(AES_BLOCK_SIZE)
34+
iv = os.urandom(AES_BLOCK_SIZE)
4435

4536
if len(iv) != AES_BLOCK_SIZE:
4637
raise Exception('Wrong iv size: {}'.format(len(iv)))
@@ -63,11 +54,10 @@ def build_cipher(self, iv=None, alg='aes_128_cbc'):
6354

6455
return cipher, iv
6556

66-
def encrypt(self, msg, iv=None, alg='aes_128_cbc', padding='PKCS#7',
67-
b64enc=True, block_size=AES_BLOCK_SIZE):
57+
def encrypt(self, msg, alg='aes_128_cbc', padding='PKCS#7', b64enc=True,
58+
block_size=AES_BLOCK_SIZE):
6859
"""
6960
:param key: The encryption key
70-
:param iv: init vector
7161
:param msg: Message to be encrypted
7262
:param padding: Which padding that should be used
7363
:param b64enc: Whether the result should be base64encoded
@@ -87,7 +77,7 @@ def encrypt(self, msg, iv=None, alg='aes_128_cbc', padding='PKCS#7',
8777
c = chr(plen).encode()
8878
msg += c * plen
8979

90-
cipher, iv = self.build_cipher(iv, alg)
80+
cipher, iv = self.build_cipher(alg)
9181
encryptor = cipher.encryptor()
9282
cmsg = iv + encryptor.update(msg) + encryptor.finalize()
9383

@@ -98,48 +88,18 @@ def encrypt(self, msg, iv=None, alg='aes_128_cbc', padding='PKCS#7',
9888

9989
return enc_msg
10090

101-
def decrypt(self, msg, iv=None, alg='aes_128_cbc', padding='PKCS#7',
102-
b64dec=True):
91+
def decrypt(self, msg, alg='aes_128_cbc', padding='PKCS#7', b64dec=True):
10392
"""
10493
:param key: The encryption key
105-
:param iv: init vector
10694
:param msg: Base64 encoded message to be decrypted
10795
:return: The decrypted message
10896
"""
10997
data = b64decode(msg) if b64dec else msg
11098

111-
_iv = data[:AES_BLOCK_SIZE]
112-
if iv:
113-
assert iv == _iv
114-
cipher, iv = self.build_cipher(iv, alg=alg)
99+
cipher, iv = self.build_cipher(alg=alg)
115100
decryptor = cipher.decryptor()
116101
res = decryptor.update(data)[AES_BLOCK_SIZE:] + decryptor.finalize()
117102
if padding in ['PKCS#5', 'PKCS#7']:
118103
idx = bytearray(res)[-1]
119104
res = res[:-idx]
120105
return res
121-
122-
123-
def run_test():
124-
key = b'1234523451234545' # 16 byte key
125-
iv = os.urandom(AES_BLOCK_SIZE)
126-
# Iff padded, the message doesn't have to be multiple of 16 in length
127-
original_msg = b'ToBeOrNotTobe W.S.'
128-
aes = AESCipher(key)
129-
130-
encrypted_msg = aes.encrypt(original_msg, iv)
131-
decrypted_msg = aes.decrypt(encrypted_msg, iv)
132-
assert decrypted_msg == original_msg
133-
134-
encrypted_msg = aes.encrypt(original_msg)
135-
decrypted_msg = aes.decrypt(encrypted_msg)
136-
assert decrypted_msg == original_msg
137-
138-
aes = AESCipher(key, iv)
139-
encrypted_msg = aes.encrypt(original_msg)
140-
decrypted_msg = aes.decrypt(encrypted_msg)
141-
assert decrypted_msg == original_msg
142-
143-
144-
if __name__ == '__main__':
145-
run_test()

src/saml2/authn.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def __init__(self, srv, mako_template, template_lookup, pwd, return_to):
120120
self.return_to = return_to
121121
self.active = {}
122122
self.query_param = "upm_answer"
123-
self.aes = AESCipher(self.srv.symkey.encode(), srv.iv)
123+
self.aes = AESCipher(self.srv.symkey.encode())
124124

125125
def __call__(self, cookie=None, policy_url=None, logo_url=None,
126126
query="", **kwargs):

src/saml2/server.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,9 @@ def __init__(self, config_file="", config=None, cache=None, stype="idp",
8383
self.init_config(stype)
8484
self.cache = cache
8585
self.ticket = {}
86-
#
8786
self.session_db = self.choose_session_storage()
88-
# Needed for
8987
self.symkey = symkey
9088
self.seed = rndstr()
91-
self.iv = os.urandom(16)
9289
self.lock = threading.Lock()
9390

9491
def getvalid_certificate_str(self):

tests/test_92_aes.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import os
2+
3+
import saml2.aes
4+
5+
6+
class TestAES():
7+
def test_aes_defaults(self):
8+
original_msg = b'ToBeOrNotTobe W.S.'
9+
key = os.urandom(16)
10+
aes = saml2.aes.AESCipher(key)
11+
12+
encrypted_msg = aes.encrypt(original_msg)
13+
decrypted_msg = aes.decrypt(encrypted_msg)
14+
assert decrypted_msg == original_msg
15+
16+
def test_aes_128_cbc(self):
17+
original_msg = b'ToBeOrNotTobe W.S.'
18+
key = os.urandom(16)
19+
aes = saml2.aes.AESCipher(key)
20+
alg = 'aes_128_cbc'
21+
22+
encrypted_msg = aes.encrypt(original_msg, alg=alg)
23+
decrypted_msg = aes.decrypt(encrypted_msg, alg=alg)
24+
assert decrypted_msg == original_msg
25+
26+
def test_aes_128_cfb(self):
27+
original_msg = b'ToBeOrNotTobe W.S.'
28+
key = os.urandom(16)
29+
aes = saml2.aes.AESCipher(key)
30+
alg = 'aes_128_cfb'
31+
32+
encrypted_msg = aes.encrypt(original_msg, alg=alg)
33+
decrypted_msg = aes.decrypt(encrypted_msg, alg=alg)
34+
assert decrypted_msg == original_msg
35+
36+
def test_aes_192_cbc(self):
37+
original_msg = b'ToBeOrNotTobe W.S.'
38+
key = os.urandom(24)
39+
aes = saml2.aes.AESCipher(key)
40+
alg = 'aes_192_cbc'
41+
42+
encrypted_msg = aes.encrypt(original_msg, alg=alg)
43+
decrypted_msg = aes.decrypt(encrypted_msg, alg=alg)
44+
assert decrypted_msg == original_msg
45+
46+
def test_aes_192_cfb(self):
47+
original_msg = b'ToBeOrNotTobe W.S.'
48+
key = os.urandom(24)
49+
aes = saml2.aes.AESCipher(key)
50+
alg = 'aes_192_cfb'
51+
52+
encrypted_msg = aes.encrypt(original_msg, alg=alg)
53+
decrypted_msg = aes.decrypt(encrypted_msg, alg=alg)
54+
assert decrypted_msg == original_msg
55+
56+
def test_aes_256_cbc(self):
57+
original_msg = b'ToBeOrNotTobe W.S.'
58+
key = os.urandom(32)
59+
aes = saml2.aes.AESCipher(key)
60+
alg = 'aes_256_cbc'
61+
62+
encrypted_msg = aes.encrypt(original_msg, alg=alg)
63+
decrypted_msg = aes.decrypt(encrypted_msg, alg=alg)
64+
assert decrypted_msg == original_msg
65+
66+
def test_aes_256_cfb(self):
67+
original_msg = b'ToBeOrNotTobe W.S.'
68+
key = os.urandom(32)
69+
aes = saml2.aes.AESCipher(key)
70+
alg = 'aes_256_cfb'
71+
72+
encrypted_msg = aes.encrypt(original_msg, alg=alg)
73+
decrypted_msg = aes.decrypt(encrypted_msg, alg=alg)
74+
assert decrypted_msg == original_msg

0 commit comments

Comments
 (0)