Skip to content

Commit

Permalink
export to ssh
Browse files Browse the repository at this point in the history
  • Loading branch information
pmazzini committed Sep 24, 2023
1 parent 5db1d2d commit f273a51
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 1 deletion.
27 changes: 26 additions & 1 deletion src/ecdsa/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import os
from six import PY2, b
from . import ecdsa, eddsa
from . import der
from . import der, ssh
from . import rfc6979
from . import ellipticcurve
from .curves import NIST192p, Curve, Ed25519, Ed448
Expand Down Expand Up @@ -614,6 +614,18 @@ def to_der(
der.encode_bitstring(point_str, 0),
)

def to_ssh(self):
"""
Convert the public key to the SSH format.
:return: SSH encoding of the public key
:rtype: bytes
"""
return ssh.serialize_public(
self.curve.name,
self.to_string(),
)

def verify(
self,
signature,
Expand Down Expand Up @@ -1281,6 +1293,19 @@ def to_der(
der.encode_octet_string(ec_private_key),
)

def to_ssh(self):
"""
Convert the private key to the SSH format.
:return: SSH encoded private key
:rtype: bytes
"""
return ssh.serialize_private(
self.curve.name,
self.verifying_key.to_string(),
self.to_string(),
)

def get_verifying_key(self):
"""
Return the VerifyingKey associated with this private key.
Expand Down
80 changes: 80 additions & 0 deletions src/ecdsa/ssh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import base64
import binascii

_SSH_ED25519 = b"ssh-ed25519"
_SK_MAGIC = b"openssh-key-v1\0"
_SK_START = b"-----BEGIN OPENSSH PRIVATE KEY-----\n"
_SK_END = b"-----END OPENSSH PRIVATE KEY-----\n"
_NONE = b"none"

def _get_key_type(name):
if name == "Ed25519":
return _SSH_ED25519
else:
raise ValueError("Unsupported key type")

class _Serializer:
def __init__(self):
self.bytes = b""

def put_raw(self, val):
self.bytes += val

def put_u32(self, val):
self.bytes += val.to_bytes(length=4, byteorder="big")

def put_str(self, val):
self.put_u32(len(val))
self.bytes += val

def put_pad(self, blklen=8):
padlen = blklen - (len(self.bytes) % blklen)
self.put_raw(bytearray(range(1, 1 + padlen)))

def encode(self):
return binascii.b2a_base64(self.bytes)

def tobytes(self):
return self.bytes

def topem(self):
return b"".join([_SK_START, base64.encodebytes(self.bytes), _SK_END])

def serialize_public(name, pub):
serial = _Serializer()
ktype = _get_key_type(name)
serial.put_str(ktype)
serial.put_str(pub)
return b" ".join([ktype, serial.encode()])

def serialize_private(name, pub, priv):
# encode public part
spub = _Serializer()
ktype = _get_key_type(name)
spub.put_str(ktype)
spub.put_str(pub)

# encode private part
spriv = _Serializer()
checksum = 0
spriv.put_u32(checksum)
spriv.put_u32(checksum)
spriv.put_raw(spub.tobytes())
spriv.put_str(priv + pub)
comment = b""
spriv.put_str(comment)
spriv.put_pad()

# top-level structure
main = _Serializer()
main.put_raw(_SK_MAGIC)
ciphername = kdfname = _NONE
main.put_str(ciphername)
main.put_str(kdfname)
nokdf = 0
main.put_u32(nokdf)
nkeys = 1
main.put_u32(nkeys)
main.put_str(spub.tobytes())
main.put_str(spriv.tobytes())
return main.topem()

0 comments on commit f273a51

Please sign in to comment.