Skip to content

Commit 98b3821

Browse files
authored
Merge pull request #448 from tlsfuzzer/zero-padding
rsa: consistent handling of zero-padded ciphertext
2 parents 25749b6 + 00c0390 commit 98b3821

File tree

3 files changed

+114
-11
lines changed

3 files changed

+114
-11
lines changed

tlslite/utils/compat.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ def compatAscii2Bytes(val):
3838
return bytes(val, 'ascii')
3939
return val
4040

41+
def compat_b2a(val):
42+
"""Convert an ASCII bytes string to string."""
43+
return str(val, 'ascii')
44+
4145
def raw_input(s):
4246
return input(s)
4347

@@ -141,6 +145,10 @@ def compatAscii2Bytes(val):
141145
"""Convert ASCII string to bytes."""
142146
return val
143147

148+
def compat_b2a(val):
149+
"""Convert an ASCII bytes string to string."""
150+
return str(val)
151+
144152
# So, python 2.6 requires strings, python 3 requires 'bytes',
145153
# and python 2.7 can handle bytearrays...
146154
def compatHMAC(x): return compat26Str(x)

tlslite/utils/openssl_rsakey.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77

88
from .rsakey import *
99
from .python_rsakey import Python_RSAKey
10-
from .compat import compatAscii2Bytes
11-
import sys
10+
from .compat import compatAscii2Bytes, compat_b2a
1211

1312
#copied from M2Crypto.util.py, so when we load the local copy of m2
1413
#we can still use it
@@ -30,6 +29,8 @@ def password_callback(v, prompt1='Enter private key passphrase:',
3029

3130

3231
if m2cryptoLoaded:
32+
import M2Crypto
33+
3334
class OpenSSL_RSAKey(RSAKey):
3435
def __init__(self, n=0, e=0, key_type="rsa"):
3536
self.rsa = None
@@ -69,8 +70,9 @@ def _rawPrivateKeyOp(self, message):
6970
return ciphertext
7071

7172
def _raw_private_key_op_bytes(self, message):
72-
return bytearray(m2.rsa_private_encrypt(self.rsa, bytes(message),
73-
m2.no_padding))
73+
return self._call_m2crypto(
74+
m2.rsa_private_encrypt, message,
75+
"Bad parameters to private key operation")
7476

7577
def _rawPublicKeyOp(self, ciphertext):
7678
data = numberToByteArray(ciphertext, numBytes(self.n))
@@ -79,9 +81,16 @@ def _rawPublicKeyOp(self, ciphertext):
7981
message = bytesToNumber(bytearray(string))
8082
return message
8183

84+
def _call_m2crypto(self, method, param, err_msg):
85+
try:
86+
return bytearray(method(self.rsa, bytes(param), m2.no_padding))
87+
except M2Crypto.RSA.RSAError:
88+
raise ValueError(err_msg)
89+
8290
def _raw_public_key_op_bytes(self, ciphertext):
83-
return bytearray(m2.rsa_public_decrypt(self.rsa, bytes(ciphertext),
84-
m2.no_padding))
91+
return self._call_m2crypto(
92+
m2.rsa_public_decrypt, ciphertext,
93+
"Bad parameters to public key operation")
8594

8695
def acceptsPassword(self): return True
8796

@@ -109,6 +118,9 @@ def f():pass
109118
key.rsa = m2.rsa_generate_key(bits, 3, f)
110119
key._hasPrivateKey = True
111120
key.key_type = key_type
121+
b64_key = compat_b2a(key.write())
122+
py_key = Python_RSAKey.parsePEM(b64_key)
123+
key.d = py_key.d
112124
return key
113125

114126
@staticmethod
@@ -161,10 +173,7 @@ def f():pass
161173
else:
162174
raise SyntaxError()
163175
if key._hasPrivateKey:
164-
if sys.version_info < (3, 0):
165-
b64_key = str(key.write())
166-
else:
167-
b64_key = str(key.write(), "ascii")
176+
b64_key = compat_b2a(key.write())
168177
py_key = Python_RSAKey.parsePEM(b64_key)
169178
key.d = py_key.d
170179
return key

unit_tests/test_tlslite_utils_rsakey.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from tlslite.utils.python_rsakey import Python_RSAKey
1414
from tlslite.utils.cryptomath import *
1515
from tlslite.errors import *
16-
from tlslite.utils.keyfactory import parsePEMKey
16+
from tlslite.utils.keyfactory import parsePEMKey, generateRSAKey
1717
from tlslite.utils.compat import a2b_hex, remove_whitespace
1818
try:
1919
import mock
@@ -1229,6 +1229,7 @@ def m(leght):
12291229
signed = self.rsa.RSASSA_PSS_sign(mHash, 'sha512', 10)
12301230
self.assertEqual(signed, intendedS)
12311231

1232+
12321233
class TestEncryptDecrypt(unittest.TestCase):
12331234
n = int("a8d68acd413c5e195d5ef04e1b4faaf242365cb450196755e92e1215ba59802aa"
12341235
"fbadbf2564dd550956abb54f8b1c917844e5f36195d1088c600e07cada5c080ed"
@@ -1267,6 +1268,12 @@ def test_invalid_init(self):
12671268
with self.assertRaises(ValueError):
12681269
Python_RSAKey(self.n, self.e, self.d, self.p)
12691270

1271+
def test_with_generated_key(self):
1272+
key = generateRSAKey(1024)
1273+
1274+
txt = bytearray(b"test string")
1275+
self.assertEqual(txt, key.decrypt(key.encrypt(txt)))
1276+
12701277

12711278
class TestRSAPKCS1(unittest.TestCase):
12721279
n = int("a8d68acd413c5e195d5ef04e1b4faaf242365cb450196755e92e1215ba59802aa"
@@ -2051,6 +2058,85 @@ def test_negative_11_byte_long_wrong_type_byte(self):
20512058
self.assertNotEqual(msg, b'lorem ipsum')
20522059
self.assertEqual(msg, plaintext)
20532060

2061+
def test_negative_11_bytes_long_with_null_padded_ciphertext(self):
2062+
# an invalid ciphertext, with a zero byte in first byte of
2063+
# ciphertext, decrypts to a random 11 byte long synthethic
2064+
# plaintext
2065+
ciphertext = a2b_hex(remove_whitespace("""
2066+
0096136621faf36d5290b16bd26295de27f895d1faa51c800dafce73d001d60796cd4e2ac3f
2067+
a2162131d859cd9da5a0c8a42281d9a63e5f353971b72e36b5722e4ac444d77f892a5443deb
2068+
3dca49fa732fe855727196e23c26eeac55eeced8267a209ebc0f92f4656d64a6c13f7f7ce54
2069+
4ebeb0f668fe3a6c0f189e4bcd5ea12b73cf63e0c8350ee130dd62f01e5c97a1e13f52fde96
2070+
a9a1bc9936ce734fdd61f27b18216f1d6de87f49cf4f2ea821fb8efd1f92cdad529baf7e31a
2071+
ff9bff4074f2cad2b4243dd15a711adcf7de900851fbd6bcb53dac399d7c880531d06f25f70
2072+
02e1aaf1722765865d2c2b902c7736acd27bc6cbd3e38b560e2eecf7d4b576
2073+
"""))
2074+
self.assertEqual(len(ciphertext), numBytes(self.pub_key.n))
2075+
2076+
# sanity check that the decrypted ciphertext is invalid
2077+
dec = self.priv_key._raw_private_key_op_bytes(ciphertext)
2078+
self.assertEqual(dec[0:3], b'\x00\x02\x00')
2079+
self.assertNotEqual(dec[-12:-11], b'\x00')
2080+
2081+
plaintext = a2b_hex(remove_whitespace("ba27b1842e7c21c0e7ef6a"))
2082+
2083+
msg = self.priv_key.decrypt(ciphertext)
2084+
2085+
self.assertEqual(msg, plaintext)
2086+
2087+
def test_negative_11_bytes_long_with_null_truncated_ciphertext(self):
2088+
# same as test_negative_11_bytes_long_with_null_padded_ciphertext
2089+
# but with the zero bytes at the beginning removed
2090+
ciphertext = a2b_hex(remove_whitespace("""
2091+
96136621faf36d5290b16bd26295de27f895d1faa51c800dafce73d001d60796cd4e2ac3f
2092+
a2162131d859cd9da5a0c8a42281d9a63e5f353971b72e36b5722e4ac444d77f892a5443deb
2093+
3dca49fa732fe855727196e23c26eeac55eeced8267a209ebc0f92f4656d64a6c13f7f7ce54
2094+
4ebeb0f668fe3a6c0f189e4bcd5ea12b73cf63e0c8350ee130dd62f01e5c97a1e13f52fde96
2095+
a9a1bc9936ce734fdd61f27b18216f1d6de87f49cf4f2ea821fb8efd1f92cdad529baf7e31a
2096+
ff9bff4074f2cad2b4243dd15a711adcf7de900851fbd6bcb53dac399d7c880531d06f25f70
2097+
02e1aaf1722765865d2c2b902c7736acd27bc6cbd3e38b560e2eecf7d4b576
2098+
"""))
2099+
self.assertEqual(len(ciphertext), numBytes(self.pub_key.n)-1)
2100+
2101+
# sanity check that the decrypted ciphertext is invalid
2102+
dec = self.priv_key._raw_private_key_op_bytes(b"\x00" + ciphertext)
2103+
self.assertEqual(dec[0:3], b'\x00\x02\x00')
2104+
self.assertNotEqual(dec[-12:-11], b'\x00')
2105+
2106+
plaintext = a2b_hex(remove_whitespace("ba27b1842e7c21c0e7ef6a"))
2107+
2108+
msg = self.priv_key.decrypt(ciphertext)
2109+
2110+
# tlslite-ng considers a too short ciphertext a publicly known error
2111+
# so it returns an error (None)
2112+
self.assertEqual(msg, None)
2113+
2114+
def test_negative_11_byte_long_with_double_null_padded_ciphertext(self):
2115+
# an invalid ciphertext, with two zero byte in first bytes of
2116+
# ciphertext, that decrypts to a random 11 byte long synthethic
2117+
# plaintext
2118+
ciphertext = a2b_hex(remove_whitespace("""
2119+
0000587cccc6b264bdfe0dc2149a988047fa921801f3502ea64624c510c6033d2f427e3f136
2120+
c26e88ea9f6519e86a542cec96aad1e5e9013c3cc203b6de15a69183050813af5c9ad797031
2121+
36d4b92f50ce171eefc6aa7988ecf02f319ffc5eafd6ee7a137f8fce64b255bb1b8dd19cfe7
2122+
67d64fdb468b9b2e9e7a0c24dae03239c8c714d3f40b7ee9c4e59ac15b17e4d328f1100756b
2123+
ce17133e8e7493b54e5006c3cbcdacd134130c5132a1edebdbd01a0c41452d16ed7a0788003
2124+
c34730d0808e7e14c797a21f2b45a8aa1644357fd5e988f99b017d9df37563a354c788dc0e2
2125+
f9466045622fa3f3e17db63414d27761f57392623a2bef6467501c63e8d645"""))
2126+
self.assertEqual(len(ciphertext), numBytes(self.pub_key.n))
2127+
2128+
# sanity check that the decrypted ciphertext is invalid
2129+
dec = self.priv_key._raw_private_key_op_bytes(ciphertext)
2130+
self.assertEqual(dec[0:3], b'\x00\x02\x00')
2131+
self.assertNotEqual(dec[-12:-11], b'\x00')
2132+
2133+
plaintext = a2b_hex(remove_whitespace("d5cf555b1d6151029a429a"))
2134+
2135+
msg = self.priv_key.decrypt(ciphertext)
2136+
2137+
self.assertEqual(msg, plaintext)
2138+
2139+
20542140
def test_negative_11_byte_long_null_type_byte(self):
20552141
# an otherwise correct plaintext, but with wrong second byte
20562142
# (0x00 instead of 0x02), and a 0x02 on third position, generates a

0 commit comments

Comments
 (0)