aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2016-06-03 13:04:26 -0700
committerPaul Kehrer <paul.l.kehrer@gmail.com>2016-06-03 13:04:26 -0700
commit949892938735c0cf14a6689d68779c2ce2410585 (patch)
tree17077680d9c0583458d21662904fdc48dc3f4f5b /src
parent6eeaf0bd76f5d40e9fbd9bc17b1b2fd08df186c4 (diff)
downloadcryptography-949892938735c0cf14a6689d68779c2ce2410585.tar.gz
cryptography-949892938735c0cf14a6689d68779c2ce2410585.tar.bz2
cryptography-949892938735c0cf14a6689d68779c2ce2410585.zip
SSH serialization for public keys (#2957)
* SSH serialization for public keys * name errors ahoy! * id, ego, superego * dsa support * EC support * Don't keyerror * Documentation OpenSSH * flake8 * fix * bytes bytes bytes * skip curve unsupported * bytes! * Move a function * reorganize code for coverage
Diffstat (limited to 'src')
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py55
-rw-r--r--src/cryptography/hazmat/backends/openssl/dsa.py1
-rw-r--r--src/cryptography/hazmat/backends/openssl/ec.py1
-rw-r--r--src/cryptography/hazmat/backends/openssl/rsa.py1
-rw-r--r--src/cryptography/hazmat/primitives/serialization.py37
5 files changed, 81 insertions, 14 deletions
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index d8a681e6..126a881a 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -4,6 +4,7 @@
from __future__ import absolute_import, division, print_function
+import base64
import calendar
import collections
import itertools
@@ -1698,11 +1699,23 @@ class Backend(object):
self.openssl_assert(res == 1)
return self._read_mem_bio(bio)
- def _public_key_bytes(self, encoding, format, evp_pkey, cdata):
+ def _public_key_bytes(self, encoding, format, key, evp_pkey, cdata):
if not isinstance(encoding, serialization.Encoding):
raise TypeError("encoding must be an item from the Encoding enum")
- if format is serialization.PublicFormat.SubjectPublicKeyInfo:
+ if (
+ format is serialization.PublicFormat.OpenSSH or
+ encoding is serialization.Encoding.OpenSSH
+ ):
+ if (
+ format is not serialization.PublicFormat.OpenSSH or
+ encoding is not serialization.Encoding.OpenSSH
+ ):
+ raise ValueError(
+ "OpenSSH format must be used with OpenSSH encoding"
+ )
+ return self._openssh_public_key_bytes(key)
+ elif format is serialization.PublicFormat.SubjectPublicKeyInfo:
if encoding is serialization.Encoding.PEM:
write_bio = self._lib.PEM_write_bio_PUBKEY
else:
@@ -1732,6 +1745,44 @@ class Backend(object):
self.openssl_assert(res == 1)
return self._read_mem_bio(bio)
+ def _openssh_public_key_bytes(self, key):
+ if isinstance(key, rsa.RSAPublicKey):
+ public_numbers = key.public_numbers()
+ return b"ssh-rsa " + base64.b64encode(
+ serialization._ssh_write_string(b"ssh-rsa") +
+ serialization._ssh_write_mpint(public_numbers.e) +
+ serialization._ssh_write_mpint(public_numbers.n)
+ )
+ elif isinstance(key, dsa.DSAPublicKey):
+ public_numbers = key.public_numbers()
+ parameter_numbers = public_numbers.parameter_numbers
+ return b"ssh-dss " + base64.b64encode(
+ serialization._ssh_write_string(b"ssh-dss") +
+ serialization._ssh_write_mpint(parameter_numbers.p) +
+ serialization._ssh_write_mpint(parameter_numbers.q) +
+ serialization._ssh_write_mpint(parameter_numbers.g) +
+ serialization._ssh_write_mpint(public_numbers.y)
+ )
+ else:
+ assert isinstance(key, ec.EllipticCurvePublicKey)
+ public_numbers = key.public_numbers()
+ try:
+ curve_name = {
+ ec.SECP256R1: b"nistp256",
+ ec.SECP384R1: b"nistp384",
+ ec.SECP521R1: b"nistp521",
+ }[type(public_numbers.curve)]
+ except KeyError:
+ raise ValueError(
+ "Only SECP256R1, SECP384R1, and SECP521R1 curves are "
+ "supported by the SSH public key format"
+ )
+ return b"ecdsa-sha2-" + curve_name + b" " + base64.b64encode(
+ serialization._ssh_write_string(b"ecdsa-sha2-" + curve_name) +
+ serialization._ssh_write_string(curve_name) +
+ serialization._ssh_write_string(public_numbers.encode_point())
+ )
+
class GetCipherByName(object):
def __init__(self, fmt):
diff --git a/src/cryptography/hazmat/backends/openssl/dsa.py b/src/cryptography/hazmat/backends/openssl/dsa.py
index 5abc3da9..1608df04 100644
--- a/src/cryptography/hazmat/backends/openssl/dsa.py
+++ b/src/cryptography/hazmat/backends/openssl/dsa.py
@@ -297,6 +297,7 @@ class _DSAPublicKey(object):
return self._backend._public_key_bytes(
encoding,
format,
+ self,
self._evp_pkey,
None
)
diff --git a/src/cryptography/hazmat/backends/openssl/ec.py b/src/cryptography/hazmat/backends/openssl/ec.py
index aa4a7a35..2f476031 100644
--- a/src/cryptography/hazmat/backends/openssl/ec.py
+++ b/src/cryptography/hazmat/backends/openssl/ec.py
@@ -299,6 +299,7 @@ class _EllipticCurvePublicKey(object):
return self._backend._public_key_bytes(
encoding,
format,
+ self,
self._evp_pkey,
None
)
diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py
index d8458ccc..920bae06 100644
--- a/src/cryptography/hazmat/backends/openssl/rsa.py
+++ b/src/cryptography/hazmat/backends/openssl/rsa.py
@@ -642,6 +642,7 @@ class _RSAPublicKey(object):
return self._backend._public_key_bytes(
encoding,
format,
+ self,
self._evp_pkey,
self._rsa_cdata
)
diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py
index d848e5d4..992fd42f 100644
--- a/src/cryptography/hazmat/primitives/serialization.py
+++ b/src/cryptography/hazmat/primitives/serialization.py
@@ -59,7 +59,7 @@ def load_ssh_public_key(data, backend):
except TypeError:
raise ValueError('Key is not in the proper format.')
- inner_key_type, rest = _read_next_string(decoded_data)
+ inner_key_type, rest = _ssh_read_next_string(decoded_data)
if inner_key_type != key_type:
raise ValueError(
@@ -70,8 +70,8 @@ def load_ssh_public_key(data, backend):
def _load_ssh_rsa_public_key(key_type, decoded_data, backend):
- e, rest = _read_next_mpint(decoded_data)
- n, rest = _read_next_mpint(rest)
+ e, rest = _ssh_read_next_mpint(decoded_data)
+ n, rest = _ssh_read_next_mpint(rest)
if rest:
raise ValueError('Key body contains extra bytes.')
@@ -80,10 +80,10 @@ def _load_ssh_rsa_public_key(key_type, decoded_data, backend):
def _load_ssh_dss_public_key(key_type, decoded_data, backend):
- p, rest = _read_next_mpint(decoded_data)
- q, rest = _read_next_mpint(rest)
- g, rest = _read_next_mpint(rest)
- y, rest = _read_next_mpint(rest)
+ p, rest = _ssh_read_next_mpint(decoded_data)
+ q, rest = _ssh_read_next_mpint(rest)
+ g, rest = _ssh_read_next_mpint(rest)
+ y, rest = _ssh_read_next_mpint(rest)
if rest:
raise ValueError('Key body contains extra bytes.')
@@ -95,8 +95,8 @@ def _load_ssh_dss_public_key(key_type, decoded_data, backend):
def _load_ssh_ecdsa_public_key(expected_key_type, decoded_data, backend):
- curve_name, rest = _read_next_string(decoded_data)
- data, rest = _read_next_string(rest)
+ curve_name, rest = _ssh_read_next_string(decoded_data)
+ data, rest = _ssh_read_next_string(rest)
if expected_key_type != b"ecdsa-sha2-" + curve_name:
raise ValueError(
@@ -121,7 +121,7 @@ def _load_ssh_ecdsa_public_key(expected_key_type, decoded_data, backend):
return numbers.public_key(backend)
-def _read_next_string(data):
+def _ssh_read_next_string(data):
"""
Retrieves the next RFC 4251 string value from the data.
@@ -137,22 +137,34 @@ def _read_next_string(data):
return data[4:4 + str_len], data[4 + str_len:]
-def _read_next_mpint(data):
+def _ssh_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)
+ mpint_data, rest = _ssh_read_next_string(data)
return (
utils.int_from_bytes(mpint_data, byteorder='big', signed=False), rest
)
+def _ssh_write_string(data):
+ return struct.pack(">I", len(data)) + data
+
+
+def _ssh_write_mpint(value):
+ data = utils.int_to_bytes(value)
+ if six.indexbytes(data, 0) & 0x80:
+ data = b"\x00" + data
+ return _ssh_write_string(data)
+
+
class Encoding(Enum):
PEM = "PEM"
DER = "DER"
+ OpenSSH = "OpenSSH"
class PrivateFormat(Enum):
@@ -163,6 +175,7 @@ class PrivateFormat(Enum):
class PublicFormat(Enum):
SubjectPublicKeyInfo = "X.509 subjectPublicKeyInfo with PKCS#1"
PKCS1 = "Raw PKCS#1"
+ OpenSSH = "OpenSSH"
@six.add_metaclass(abc.ABCMeta)