From 78a7d1c4c63737c4eae0c22207a00141a44402d3 Mon Sep 17 00:00:00 2001 From: Mark Adams Date: Fri, 12 Dec 2014 23:13:12 -0600 Subject: Added load_ssh_rsa_public_key to hazmat.primitives.serialization to allow for loading of OpenSSH RSA public keys Also added load_ssh_public_key as a generic method that can be later extended to support more public key algorithms. --- .../hazmat/primitives/serialization.py | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'src') diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py index b9cf5967..0f07e41f 100644 --- a/src/cryptography/hazmat/primitives/serialization.py +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -4,9 +4,13 @@ from __future__ import absolute_import, division, print_function +import base64 +import struct import warnings from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers def load_pem_traditional_openssl_private_key(data, password, backend): @@ -39,3 +43,46 @@ def load_pem_private_key(data, password, backend): def load_pem_public_key(data, backend): return backend.load_pem_public_key(data) + + +def load_ssh_public_key(data, backend): + if not data.startswith(b'ssh-'): + raise ValueError('SSH-formatted keys must begin with ssh-') + + if not data.startswith(b'ssh-rsa'): + raise UnsupportedAlgorithm('Only RSA keys are currently supported.') + + return load_ssh_rsa_public_key(data, backend) + + +def load_ssh_rsa_public_key(data, backend): + if not data.startswith(b'ssh-rsa '): + raise ValueError('SSH-formatted RSA keys must begin with ssh-rsa') + + parts = data.split(b' ') + data = base64.b64decode(parts[1]) + + cert_data = [] + + while len(data) > 0: + str_len = struct.unpack('>I', data[0:4])[0] + cert_data.append(data[4:4 + str_len]) + data = data[4 + str_len:] + + e = _bytes_to_int(cert_data[1]) + n = _bytes_to_int(cert_data[2]) + return backend.load_rsa_public_numbers(RSAPublicNumbers(e, n)) + + +def _bytes_to_int(data): + if len(data) % 4 != 0: + # Pad the bytes with 0x00 to a block size of 4 + data = (b'\x00' * (4 - (len(data) % 4))) + data + + result = 0 + + while len(data) > 0: + result = (result << 32) + struct.unpack('>I', data[0:4])[0] + data = data[4:] + + return result -- cgit v1.2.3 From b7b91179a5b1d4c28643f9e59bb46e52144a7de3 Mon Sep 17 00:00:00 2001 From: Mark Adams Date: Sat, 13 Dec 2014 10:43:01 -0600 Subject: Privatized the load_ssh_rsa_public_key function and fixed some coverage issues on test_serialization. --- src/cryptography/hazmat/primitives/serialization.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py index 0f07e41f..e1ffab9e 100644 --- a/src/cryptography/hazmat/primitives/serialization.py +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -52,10 +52,10 @@ def load_ssh_public_key(data, backend): if not data.startswith(b'ssh-rsa'): raise UnsupportedAlgorithm('Only RSA keys are currently supported.') - return load_ssh_rsa_public_key(data, backend) + return _load_ssh_rsa_public_key(data, backend) -def load_ssh_rsa_public_key(data, backend): +def _load_ssh_rsa_public_key(data, backend): if not data.startswith(b'ssh-rsa '): raise ValueError('SSH-formatted RSA keys must begin with ssh-rsa') -- cgit v1.2.3 From dfa57bf7821a63c65ef0f83234c79f611fab46db Mon Sep 17 00:00:00 2001 From: Mark Adams Date: Sat, 13 Dec 2014 23:13:52 -0600 Subject: Removed redundant tests for _load_ssh_rsa_public_key since it is now a non-public part of the API and made a number of minor changes to tests and documentation --- src/cryptography/hazmat/primitives/serialization.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py index e1ffab9e..38c541cb 100644 --- a/src/cryptography/hazmat/primitives/serialization.py +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -56,8 +56,7 @@ def load_ssh_public_key(data, backend): def _load_ssh_rsa_public_key(data, backend): - if not data.startswith(b'ssh-rsa '): - raise ValueError('SSH-formatted RSA keys must begin with ssh-rsa') + assert data.startswith(b'ssh-rsa ') parts = data.split(b' ') data = base64.b64decode(parts[1]) -- cgit v1.2.3 From 4724d61be546f900298c7594d3bdb942b39a919f Mon Sep 17 00:00:00 2001 From: Mark Adams Date: Sun, 14 Dec 2014 00:16:03 -0600 Subject: Added better parsing for RFC 4251 string and mpint values. Also moved several of the SSH key splitting and validation checks up into the load_ssh_public_key method since they will apply to more than just RSA. Added additional checks to make sure the key doesn't contain extraneous data --- .../hazmat/primitives/serialization.py | 62 ++++++++++++++-------- 1 file changed, 40 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py index 38c541cb..f20d9f56 100644 --- a/src/cryptography/hazmat/primitives/serialization.py +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -46,42 +46,60 @@ def load_pem_public_key(data, backend): def load_ssh_public_key(data, backend): - if not data.startswith(b'ssh-'): - raise ValueError('SSH-formatted keys must begin with ssh-') + key_parts = data.split(b' ') - if not data.startswith(b'ssh-rsa'): + if len(key_parts) < 2 or len(key_parts) > 3: + raise ValueError( + 'Key is not in the proper format or contains extra data.') + + key_type = key_parts[0] + key_body = key_parts[1] + + if not key_type.startswith(b'ssh-'): + raise ValueError('SSH-formatted keys must begin with \'ssh-\'.') + + if not key_type.startswith(b'ssh-rsa'): raise UnsupportedAlgorithm('Only RSA keys are currently supported.') - return _load_ssh_rsa_public_key(data, backend) + return _load_ssh_rsa_public_key(key_type, key_body, backend) + +def _load_ssh_rsa_public_key(key_type, key_body, backend): + assert key_type == b'ssh-rsa' -def _load_ssh_rsa_public_key(data, backend): - assert data.startswith(b'ssh-rsa ') + data = base64.b64decode(key_body) - parts = data.split(b' ') - data = base64.b64decode(parts[1]) + key_body_type, rest = _read_next_string(data) + e, rest = _read_next_mpint(rest) + n, rest = _read_next_mpint(rest) - cert_data = [] + if key_type != key_body_type: + raise ValueError( + 'Key header and key body contain different key type values.') - while len(data) > 0: - str_len = struct.unpack('>I', data[0:4])[0] - cert_data.append(data[4:4 + str_len]) - data = data[4 + str_len:] + if len(rest) != 0: + raise ValueError('Key body contains extra bytes.') - e = _bytes_to_int(cert_data[1]) - n = _bytes_to_int(cert_data[2]) return backend.load_rsa_public_numbers(RSAPublicNumbers(e, n)) -def _bytes_to_int(data): - if len(data) % 4 != 0: +def _read_next_string(data): + """Retrieves the next RFC 4251 string value from the data.""" + str_len = struct.unpack('>I', data[0:4])[0] + return data[4:4 + str_len], data[4 + str_len:] + + +def _read_next_mpint(data): + mpint_data, rest = _read_next_string(data) + + if len(mpint_data) % 4 != 0: # Pad the bytes with 0x00 to a block size of 4 - data = (b'\x00' * (4 - (len(data) % 4))) + data + mpint_data = (b'\x00' * (4 - (len(mpint_data) % 4))) + mpint_data result = 0 - while len(data) > 0: - result = (result << 32) + struct.unpack('>I', data[0:4])[0] - data = data[4:] + while len(mpint_data) > 0: + result = (result << 32) + struct.unpack('>I', mpint_data[0:4])[0] + mpint_data = mpint_data[4:] - return result + return result, rest -- cgit v1.2.3 From c3e8b8890585d82bf19ac642756c5c4baac74237 Mon Sep 17 00:00:00 2001 From: Mark Adams Date: Sun, 14 Dec 2014 08:11:48 -0600 Subject: Made a couple of minor tweaks to clean up _read_next_string and _read_next_mpint --- src/cryptography/hazmat/primitives/serialization.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py index f20d9f56..455c8a91 100644 --- a/src/cryptography/hazmat/primitives/serialization.py +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -65,8 +65,6 @@ def load_ssh_public_key(data, backend): def _load_ssh_rsa_public_key(key_type, key_body, backend): - assert key_type == b'ssh-rsa' - data = base64.b64decode(key_body) key_body_type, rest = _read_next_string(data) @@ -85,7 +83,7 @@ def _load_ssh_rsa_public_key(key_type, key_body, backend): def _read_next_string(data): """Retrieves the next RFC 4251 string value from the data.""" - str_len = struct.unpack('>I', data[0:4])[0] + str_len, = struct.unpack('>I', data[0:4]) return data[4:4 + str_len], data[4 + str_len:] -- cgit v1.2.3 From cbddc9897b579f1b44e0c4cd5cd868fa7c6dd06d Mon Sep 17 00:00:00 2001 From: Mark Adams Date: Sun, 14 Dec 2014 22:31:29 -0600 Subject: Added optimization for Python 3 to use int.from_bytes instead of Python code --- src/cryptography/hazmat/primitives/serialization.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py index 455c8a91..8a4c8bd8 100644 --- a/src/cryptography/hazmat/primitives/serialization.py +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -6,6 +6,7 @@ from __future__ import absolute_import, division, print_function import base64 import struct +import sys import warnings from cryptography import utils @@ -88,10 +89,15 @@ def _read_next_string(data): def _read_next_mpint(data): + """Reads the next mpint from the data. Currently, all mpints are + interpreted as unsigned.""" mpint_data, rest = _read_next_string(data) + if sys.version_info >= (3, 2): + # If we're using >= 3.2, use int.from_bytes for identical results. + return int.from_bytes(mpint_data, byteorder='big', signed=False), rest + if len(mpint_data) % 4 != 0: - # Pad the bytes with 0x00 to a block size of 4 mpint_data = (b'\x00' * (4 - (len(mpint_data) % 4))) + mpint_data result = 0 -- cgit v1.2.3 From 993b85ad6f3ebe5db6a24c1649d28f8cf45095ea Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 15 Dec 2014 10:42:45 -0800 Subject: A handful of tiny fixes --- .../hazmat/primitives/serialization.py | 49 +++++++++++++--------- src/cryptography/utils.py | 7 ++-- 2 files changed, 34 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py index 8a4c8bd8..858ec043 100644 --- a/src/cryptography/hazmat/primitives/serialization.py +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -49,7 +49,7 @@ def load_pem_public_key(data, backend): def load_ssh_public_key(data, backend): key_parts = data.split(b' ') - if len(key_parts) < 2 or len(key_parts) > 3: + if len(key_parts) != 2 and len(key_parts) != 3: raise ValueError( 'Key is not in the proper format or contains extra data.') @@ -62,21 +62,21 @@ def load_ssh_public_key(data, backend): if not key_type.startswith(b'ssh-rsa'): raise UnsupportedAlgorithm('Only RSA keys are currently supported.') - return _load_ssh_rsa_public_key(key_type, key_body, backend) + return _load_ssh_rsa_public_key(key_body, backend) -def _load_ssh_rsa_public_key(key_type, key_body, backend): +def _load_ssh_rsa_public_key(key_body, backend): data = base64.b64decode(key_body) - key_body_type, rest = _read_next_string(data) + key_type, rest = _read_next_string(data) e, rest = _read_next_mpint(rest) n, rest = _read_next_mpint(rest) - if key_type != key_body_type: + if key_type != b'ssh-rsa': raise ValueError( 'Key header and key body contain different key type values.') - if len(rest) != 0: + if rest: raise ValueError('Key body contains extra bytes.') return backend.load_rsa_public_numbers(RSAPublicNumbers(e, n)) @@ -84,26 +84,37 @@ def _load_ssh_rsa_public_key(key_type, key_body, backend): def _read_next_string(data): """Retrieves the next RFC 4251 string value from the data.""" - str_len, = struct.unpack('>I', data[0:4]) + str_len, = struct.unpack('>I', data[:4]) return data[4:4 + str_len], data[4 + str_len:] def _read_next_mpint(data): - """Reads the next mpint from the data. Currently, all mpints are - interpreted as unsigned.""" + """ + Reads the next mpint from the data. + + Currently, all mpints are interpreted as unsigned. + """ mpint_data, rest = _read_next_string(data) - if sys.version_info >= (3, 2): - # If we're using >= 3.2, use int.from_bytes for identical results. - return int.from_bytes(mpint_data, byteorder='big', signed=False), rest + return _int_from_bytes(mpint_data, byteorder='big', signed=False), rest + + + +if hasattr(int, "from_bytes"): + _int_from_bytes = int.from_bytes +else: + def _int_from_bytes(data, byteorder, signed=False): + assert byteorder == 'big' + assert not signed - if len(mpint_data) % 4 != 0: - mpint_data = (b'\x00' * (4 - (len(mpint_data) % 4))) + mpint_data + if len(data) % 4 != 0: + data = (b'\x00' * (4 - (len(data) % 4))) + data - result = 0 + result = 0 - while len(mpint_data) > 0: - result = (result << 32) + struct.unpack('>I', mpint_data[0:4])[0] - mpint_data = mpint_data[4:] + while len(data) > 0: + digit, = struct.unpack('>I', data[:4]) + result = (result << 32) + digit + data = data[4:] - return result, rest + return result diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 63464dfa..78f73464 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -48,8 +48,9 @@ def verify_interface(iface, klass): ) -def bit_length(x): - if sys.version_info >= (2, 7): +if sys.version_info >= (2, 7): + def bit_length(x): return x.bit_length() - else: +else: + def bit_length(x): return len(bin(x)) - (2 + (x <= 0)) -- cgit v1.2.3 From 004b3ad14d691311d6dbb48aa05901e01d681ea2 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 15 Dec 2014 11:19:05 -0800 Subject: two flake8 fixes --- src/cryptography/hazmat/primitives/serialization.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py index 858ec043..0dbbc85c 100644 --- a/src/cryptography/hazmat/primitives/serialization.py +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -6,7 +6,6 @@ from __future__ import absolute_import, division, print_function import base64 import struct -import sys import warnings from cryptography import utils @@ -99,7 +98,6 @@ def _read_next_mpint(data): return _int_from_bytes(mpint_data, byteorder='big', signed=False), rest - if hasattr(int, "from_bytes"): _int_from_bytes = int.from_bytes else: -- cgit v1.2.3