Repository: pyca/cryptography

Issue:
Exception when getting public_bytes of Ed25519PublicKey
I created an ed25519 key with: `ssh-keygen -t ed25519`.
This one I loaded successfully with cryptography 2.6.1. Dumping the loaded key however creates the following exception:

```
>>> print(key.__class__)
<class 'cryptography.hazmat.backends.openssl.ed25519._Ed25519PublicKey'>

>>> key.public_bytes(Encoding.OpenSSH, PublicFormat.OpenSSH)
Traceback (most recent call last):
  File "/usr/lib/python3.6/code.py", line 91, in runcode
    exec(code, self.locals)
  File "<console>", line 1, in <module>
  File "/home/richard/.local/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/ed25519.py", line 45, in public_bytes
    encoding, format, self, self._evp_pkey, None
  File "/home/richard/.local/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1838, in _public_key_bytes
    return self._openssh_public_key_bytes(key)
  File "/home/richard/.local/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1886, in _openssh_public_key_bytes
    assert isinstance(key, ec.EllipticCurvePublicKey)
AssertionError
```


Discussion:
This behavior seems to be in accordance with the doc found here: 
https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed25519/#cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey.public_bytes

To provide a bit more context: I'd like to compute the ssh fingerprint of the key, hence the `Encoding.OpenSSH`. This works for all other ssh keys, except for this one.
There's really two bugs here:

a) If `OpenSSH` isn't support it should give a proper error, not an `assert` failure.

b) We should support OpenSSH!
are you by chance interested in implementing this? you'd need to add the implementation here https://github.com/pyca/cryptography/blob/master/src/cryptography/hazmat/backends/openssl/backend.py#L1870 and then add tests
I'll look into it.

Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -1885,6 +1885,13 @@ def _openssh_public_key_bytes(self, key):
                 ssh._ssh_write_mpint(parameter_numbers.g) +
                 ssh._ssh_write_mpint(public_numbers.y)
             )
+        elif isinstance(key, ed25519.Ed25519PublicKey):
+            raw_bytes = key.public_bytes(serialization.Encoding.Raw,
+                                         serialization.PublicFormat.Raw)
+            return b"ssh-ed25519 " + base64.b64encode(
+                ssh._ssh_write_string(b"ssh-ed25519") +
+                ssh._ssh_write_string(raw_bytes)
+            )
         else:
             assert isinstance(key, ec.EllipticCurvePublicKey)
             public_numbers = key.public_numbers()


Test patch:
diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py
--- a/tests/hazmat/primitives/test_serialization.py
+++ b/tests/hazmat/primitives/test_serialization.py
@@ -1293,6 +1293,17 @@ def test_load_ssh_public_key(self, backend):
             b"N\x06G\xecV\xbc\x19\xaf\xc6<k\x07[\xc6"
         )
 
+    def test_public_bytes_openssh(self, backend):
+        ssh_key = (
+            b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2fgpmpYO61qeAxGd0wgRaN/E4"
+            b"GR+xWvBmvxjxrB1vG"
+        )
+        key = load_ssh_public_key(ssh_key, backend)
+        assert isinstance(key, ed25519.Ed25519PublicKey)
+        assert key.public_bytes(
+            Encoding.OpenSSH, PublicFormat.OpenSSH
+        ) == ssh_key
+
     def test_load_ssh_public_key_not_32_bytes(self, backend):
         ssh_key = (
             b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI22fgpmpYO61qeAxGd0wgRaN/E4"