Skip to content

Commit f273a51

Browse files
committed
export to ssh
1 parent 5db1d2d commit f273a51

File tree

2 files changed

+106
-1
lines changed

2 files changed

+106
-1
lines changed

src/ecdsa/keys.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import os
88
from six import PY2, b
99
from . import ecdsa, eddsa
10-
from . import der
10+
from . import der, ssh
1111
from . import rfc6979
1212
from . import ellipticcurve
1313
from .curves import NIST192p, Curve, Ed25519, Ed448
@@ -614,6 +614,18 @@ def to_der(
614614
der.encode_bitstring(point_str, 0),
615615
)
616616

617+
def to_ssh(self):
618+
"""
619+
Convert the public key to the SSH format.
620+
621+
:return: SSH encoding of the public key
622+
:rtype: bytes
623+
"""
624+
return ssh.serialize_public(
625+
self.curve.name,
626+
self.to_string(),
627+
)
628+
617629
def verify(
618630
self,
619631
signature,
@@ -1281,6 +1293,19 @@ def to_der(
12811293
der.encode_octet_string(ec_private_key),
12821294
)
12831295

1296+
def to_ssh(self):
1297+
"""
1298+
Convert the private key to the SSH format.
1299+
1300+
:return: SSH encoded private key
1301+
:rtype: bytes
1302+
"""
1303+
return ssh.serialize_private(
1304+
self.curve.name,
1305+
self.verifying_key.to_string(),
1306+
self.to_string(),
1307+
)
1308+
12841309
def get_verifying_key(self):
12851310
"""
12861311
Return the VerifyingKey associated with this private key.

src/ecdsa/ssh.py

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import base64
2+
import binascii
3+
4+
_SSH_ED25519 = b"ssh-ed25519"
5+
_SK_MAGIC = b"openssh-key-v1\0"
6+
_SK_START = b"-----BEGIN OPENSSH PRIVATE KEY-----\n"
7+
_SK_END = b"-----END OPENSSH PRIVATE KEY-----\n"
8+
_NONE = b"none"
9+
10+
def _get_key_type(name):
11+
if name == "Ed25519":
12+
return _SSH_ED25519
13+
else:
14+
raise ValueError("Unsupported key type")
15+
16+
class _Serializer:
17+
def __init__(self):
18+
self.bytes = b""
19+
20+
def put_raw(self, val):
21+
self.bytes += val
22+
23+
def put_u32(self, val):
24+
self.bytes += val.to_bytes(length=4, byteorder="big")
25+
26+
def put_str(self, val):
27+
self.put_u32(len(val))
28+
self.bytes += val
29+
30+
def put_pad(self, blklen=8):
31+
padlen = blklen - (len(self.bytes) % blklen)
32+
self.put_raw(bytearray(range(1, 1 + padlen)))
33+
34+
def encode(self):
35+
return binascii.b2a_base64(self.bytes)
36+
37+
def tobytes(self):
38+
return self.bytes
39+
40+
def topem(self):
41+
return b"".join([_SK_START, base64.encodebytes(self.bytes), _SK_END])
42+
43+
def serialize_public(name, pub):
44+
serial = _Serializer()
45+
ktype = _get_key_type(name)
46+
serial.put_str(ktype)
47+
serial.put_str(pub)
48+
return b" ".join([ktype, serial.encode()])
49+
50+
def serialize_private(name, pub, priv):
51+
# encode public part
52+
spub = _Serializer()
53+
ktype = _get_key_type(name)
54+
spub.put_str(ktype)
55+
spub.put_str(pub)
56+
57+
# encode private part
58+
spriv = _Serializer()
59+
checksum = 0
60+
spriv.put_u32(checksum)
61+
spriv.put_u32(checksum)
62+
spriv.put_raw(spub.tobytes())
63+
spriv.put_str(priv + pub)
64+
comment = b""
65+
spriv.put_str(comment)
66+
spriv.put_pad()
67+
68+
# top-level structure
69+
main = _Serializer()
70+
main.put_raw(_SK_MAGIC)
71+
ciphername = kdfname = _NONE
72+
main.put_str(ciphername)
73+
main.put_str(kdfname)
74+
nokdf = 0
75+
main.put_u32(nokdf)
76+
nkeys = 1
77+
main.put_u32(nkeys)
78+
main.put_str(spub.tobytes())
79+
main.put_str(spriv.tobytes())
80+
return main.topem()

0 commit comments

Comments
 (0)