Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions src/cryptography/hazmat/primitives/serialization/ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)))

Expand Down Expand Up @@ -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")
Expand Down
25 changes: 25 additions & 0 deletions tests/hazmat/primitives/test_ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down