Skip to content

Commit ea854f1

Browse files
committed
rsa: consistent handling of zero-padded ciphertext
1 parent 66878a9 commit ea854f1

File tree

2 files changed

+93
-4
lines changed

2 files changed

+93
-4
lines changed

tlslite/utils/openssl_rsakey.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ def password_callback(v, prompt1='Enter private key passphrase:',
3030

3131

3232
if m2cryptoLoaded:
33+
import M2Crypto
34+
3335
class OpenSSL_RSAKey(RSAKey):
3436
def __init__(self, n=0, e=0, key_type="rsa"):
3537
self.rsa = None
@@ -69,8 +71,9 @@ def _rawPrivateKeyOp(self, message):
6971
return ciphertext
7072

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

7578
def _rawPublicKeyOp(self, ciphertext):
7679
data = numberToByteArray(ciphertext, numBytes(self.n))
@@ -79,9 +82,16 @@ def _rawPublicKeyOp(self, ciphertext):
7982
message = bytesToNumber(bytearray(string))
8083
return message
8184

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

8696
def acceptsPassword(self): return True
8797

unit_tests/test_tlslite_utils_rsakey.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2051,6 +2051,85 @@ def test_negative_11_byte_long_wrong_type_byte(self):
20512051
self.assertNotEqual(msg, b'lorem ipsum')
20522052
self.assertEqual(msg, plaintext)
20532053

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

0 commit comments

Comments
 (0)