From f273a51ba158561aac78a995083c6526598dcf86 Mon Sep 17 00:00:00 2001 From: Pablo Mazzini Date: Sun, 24 Sep 2023 21:40:48 +0100 Subject: [PATCH] export to ssh --- src/ecdsa/keys.py | 27 +++++++++++++++- src/ecdsa/ssh.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/ecdsa/ssh.py diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 2b7d3168..d77f4fc4 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -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 @@ -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, @@ -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. diff --git a/src/ecdsa/ssh.py b/src/ecdsa/ssh.py new file mode 100644 index 00000000..ec540266 --- /dev/null +++ b/src/ecdsa/ssh.py @@ -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()