Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit eed49e2

Browse files
Merge pull request #323 from pmazzini/ssh
export to ssh
2 parents fafbcaf + 52e2f06 commit eed49e2

File tree

4 files changed

+147
-8
lines changed

4 files changed

+147
-8
lines changed

‎src/ecdsa/der.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import warnings
66
from itertools import chain
77
from six import int2byte, b, text_type
8-
from ._compat import str_idx_as_int
8+
from ._compat import compat26_str, str_idx_as_int
99

1010

1111
class UnexpectedDER(Exception):
@@ -400,10 +400,10 @@ def unpem(pem):
400400

401401

402402
def topem(der, name):
403-
b64 = base64.b64encode(der)
403+
b64 = base64.b64encode(compat26_str(der))
404404
lines = [("-----BEGIN %s-----\n" % name).encode()]
405405
lines.extend(
406-
[b64[start : start + 64] + b("\n") for start in range(0, len(b64), 64)]
406+
[b64[start : start + 76] + b("\n") for start in range(0, len(b64), 76)]
407407
)
408408
lines.append(("-----END %s-----\n" % name).encode())
409409
return b("").join(lines)

‎src/ecdsa/keys.py‎

Lines changed: 26 additions & 1 deletion
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‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import binascii
2+
from . import der
3+
from ._compat import compat26_str, int_to_bytes
4+
5+
_SSH_ED25519 = b"ssh-ed25519"
6+
_SK_MAGIC = b"openssh-key-v10円"
7+
_NONE = b"none"
8+
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+
17+
class _Serializer:
18+
def __init__(self):
19+
self.bytes = b""
20+
21+
def put_raw(self, val):
22+
self.bytes += val
23+
24+
def put_u32(self, val):
25+
self.bytes += int_to_bytes(val, length=4, byteorder="big")
26+
27+
def put_str(self, val):
28+
self.put_u32(len(val))
29+
self.bytes += val
30+
31+
def put_pad(self, blklen=8):
32+
padlen = blklen - (len(self.bytes) % blklen)
33+
self.put_raw(bytearray(range(1, 1 + padlen)))
34+
35+
def encode(self):
36+
return binascii.b2a_base64(compat26_str(self.bytes))
37+
38+
def tobytes(self):
39+
return self.bytes
40+
41+
def topem(self):
42+
return der.topem(self.bytes, "OPENSSH PRIVATE KEY")
43+
44+
45+
def serialize_public(name, pub):
46+
serial = _Serializer()
47+
ktype = _get_key_type(name)
48+
serial.put_str(ktype)
49+
serial.put_str(pub)
50+
return b" ".join([ktype, serial.encode()])
51+
52+
53+
def serialize_private(name, pub, priv):
54+
# encode public part
55+
spub = _Serializer()
56+
ktype = _get_key_type(name)
57+
spub.put_str(ktype)
58+
spub.put_str(pub)
59+
60+
# encode private part
61+
spriv = _Serializer()
62+
checksum = 0
63+
spriv.put_u32(checksum)
64+
spriv.put_u32(checksum)
65+
spriv.put_raw(spub.tobytes())
66+
spriv.put_str(priv + pub)
67+
comment = b""
68+
spriv.put_str(comment)
69+
spriv.put_pad()
70+
71+
# top-level structure
72+
main = _Serializer()
73+
main.put_raw(_SK_MAGIC)
74+
ciphername = kdfname = _NONE
75+
main.put_str(ciphername)
76+
main.put_str(kdfname)
77+
nokdf = 0
78+
main.put_u32(nokdf)
79+
nkeys = 1
80+
main.put_u32(nkeys)
81+
main.put_str(spub.tobytes())
82+
main.put_str(spriv.tobytes())
83+
return main.topem()

‎src/ecdsa/test_keys.py‎

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,18 @@ def test_export_ed255_to_pem(self):
364364

365365
self.assertEqual(vk_pem, vk.to_pem())
366366

367+
def test_export_ed255_to_ssh(self):
368+
vk_str = (
369+
b"\x23\x00\x50\xd0\xd6\x64\x22\x28\x8e\xe3\x55\x89\x7e\x6e\x41\x57"
370+
b"\x8d\xae\xde\x44\x26\xee\x56\x27\xbc\x85\xe6\x0b\x2f\x2a\xcb\x65"
371+
)
372+
373+
vk = VerifyingKey.from_string(vk_str, Ed25519)
374+
375+
vk_ssh = b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICMAUNDWZCIojuNViX5uQVeNrt5EJu5WJ7yF5gsvKstl\n"
376+
377+
self.assertEqual(vk_ssh, vk.to_ssh())
378+
367379
def test_ed25519_export_import(self):
368380
sk = SigningKey.generate(Ed25519)
369381
vk = sk.verifying_key
@@ -428,8 +440,8 @@ def test_ed448_to_pem(self):
428440

429441
vk_pem = (
430442
b"-----BEGIN PUBLIC KEY-----\n"
431-
b"MEMwBQYDK2VxAzoAeQtetSu7CMEzE+XWB10Bg47LCA0giNikOxHzdp+tZ/eK/En0\n"
432-
b"dTdYD2ll94g58MhSnBiBQB9A1MMA\n"
443+
b"MEMwBQYDK2VxAzoAeQtetSu7CMEzE+XWB10Bg47LCA0giNikOxHzdp+tZ/eK/En0dTdYD2ll94g5\n"
444+
b"8MhSnBiBQB9A1MMA\n"
433445
b"-----END PUBLIC KEY-----\n"
434446
)
435447

@@ -629,6 +641,25 @@ def test_ed25519_to_pem(self):
629641

630642
self.assertEqual(sk.to_pem(format="pkcs8"), pem_str)
631643

644+
def test_ed25519_to_ssh(self):
645+
sk = SigningKey.from_string(
646+
b"\x34\xBA\xC7\xD1\x4E\xD4\xF1\xBC\x4F\x8C\x48\x3E\x0F\x19\x77\x4C"
647+
b"\xFC\xB8\xBE\xAC\x54\x66\x45\x11\x9A\xD7\xD7\xB8\x07\x0B\xF5\xD4",
648+
Ed25519,
649+
)
650+
651+
ssh_str = (
652+
b"-----BEGIN OPENSSH PRIVATE KEY-----\n"
653+
b"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZWQyNTUx\n"
654+
b"OQAAACAjAFDQ1mQiKI7jVYl+bkFXja7eRCbuVie8heYLLyrLZQAAAIgAAAAAAAAAAAAAAAtzc2gt\n"
655+
b"ZWQyNTUxOQAAACAjAFDQ1mQiKI7jVYl+bkFXja7eRCbuVie8heYLLyrLZQAAAEA0usfRTtTxvE+M\n"
656+
b"SD4PGXdM/Li+rFRmRRGa19e4Bwv11CMAUNDWZCIojuNViX5uQVeNrt5EJu5WJ7yF5gsvKstlAAAA\n"
657+
b"AAECAwQF\n"
658+
b"-----END OPENSSH PRIVATE KEY-----\n"
659+
)
660+
661+
self.assertEqual(sk.to_ssh(), ssh_str)
662+
632663
def test_ed25519_to_and_from_pem(self):
633664
sk = SigningKey.generate(Ed25519)
634665

@@ -665,8 +696,8 @@ def test_ed448_to_pem(self):
665696
)
666697
pem_str = (
667698
b"-----BEGIN PRIVATE KEY-----\n"
668-
b"MEcCAQAwBQYDK2VxBDsEOTyFuXqFLXgJlV8uDqcOw9nG4IqzLiZ/i5NfBDoHPzmP\n"
669-
b"OP0JMYaLGlTzwovmvCDJ2zLaezu9NLz9aQ==\n"
699+
b"MEcCAQAwBQYDK2VxBDsEOTyFuXqFLXgJlV8uDqcOw9nG4IqzLiZ/i5NfBDoHPzmPOP0JMYaLGlTz\n"
700+
b"wovmvCDJ2zLaezu9NLz9aQ==\n"
670701
b"-----END PRIVATE KEY-----\n"
671702
)
672703

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /