diff --git a/src/cryptography/hazmat/primitives/serialization/ssh.py b/src/cryptography/hazmat/primitives/serialization/ssh.py index 230e14799a08..0b2526d30bc1 100644 --- a/src/cryptography/hazmat/primitives/serialization/ssh.py +++ b/src/cryptography/hazmat/primitives/serialization/ssh.py @@ -81,9 +81,6 @@ def _bcrypt_kdf( _DEFAULT_CIPHER = b"aes256-ctr" _DEFAULT_ROUNDS = 16 -# re is only way to work on bytes-like data -_PEM_RC = re.compile(_SK_START + b"(.*?)" + _SK_END, re.DOTALL) - # padding for max blocksize _PADDING = memoryview(bytearray(range(1, 1 + 16))) @@ -684,11 +681,19 @@ def load_ssh_private_key( if password is not None: utils._check_bytes("password", password) - m = _PEM_RC.search(data) - if not m: + # The base64 body lies between the first opening marker and the first + # closing marker after it. Two linear scans find them; anchoring the + # second scan past the opening marker keeps the total work linear in the + # length of the input even when there is no closing marker to find. + # bytes.find does not accept a memoryview, which _check_byteslike permits. + buf = data if isinstance(data, (bytes, bytearray)) else bytes(data) + p1 = buf.find(_SK_START) + if p1 == -1: + raise ValueError("Not OpenSSH private key format") + p1 += len(_SK_START) + p2 = buf.find(_SK_END, p1) + if p2 == -1: raise ValueError("Not OpenSSH private key format") - p1 = m.start(1) - p2 = m.end(1) data = binascii.a2b_base64(memoryview(data)[p1:p2]) if not data.startswith(_SK_MAGIC): raise ValueError("Not OpenSSH private key format") diff --git a/tests/hazmat/primitives/test_ssh.py b/tests/hazmat/primitives/test_ssh.py index 2a319185b2bc..bf316759a3cb 100644 --- a/tests/hazmat/primitives/test_ssh.py +++ b/tests/hazmat/primitives/test_ssh.py @@ -485,6 +485,31 @@ def test_load_ssh_private_key_errors(self): with pytest.raises(ValueError): load_ssh_private_key(data, None) + def test_load_ssh_private_key_missing_footer(self): + # An opening marker with no closing marker is rejected, in linear + # time. + data = self.make_file(footer=b"") + with pytest.raises(ValueError): + load_ssh_private_key(data, password=None) + + # Many opening markers with no closing marker: the worst case for + # locating the markers. + data = b"-----BEGIN OPENSSH PRIVATE KEY-----" * 100000 + with pytest.raises(ValueError): + load_ssh_private_key(data, password=None) + + def test_load_ssh_private_key_surrounding_data(self): + # Data before the opening marker and after the closing marker is + # ignored. + body = self.make_file() + for wrapped in [ + b"leading junk\n" + body, + body + b"trailing junk\n", + b"leading\n" + body + b"trailing\n", + ]: + key = load_ssh_private_key(wrapped, password=None) + assert isinstance(key, ec.EllipticCurvePrivateKey) + def test_ssh_errors_bad_values(self): # bad curve data = self.make_file(pub_type=b"ecdsa-sha2-nistp444")