diff --git a/securesystemslib/convert/ssh.py b/securesystemslib/convert/ssh.py new file mode 100644 index 000000000..902dd0d27 --- /dev/null +++ b/securesystemslib/convert/ssh.py @@ -0,0 +1,48 @@ +import re +import typing + +from cryptography.hazmat.primitives.serialization.ssh import ( + load_ssh_private_key, + load_ssh_public_key, +) + +from .hazmat import import_hazmat_key + +openssh_text_format_marker_re = re.compile( + b"^-{2,}BEGIN OPENSSH PRIVATE KEY-{2,}$" +) + + +def import_ssh_key( + key: typing.Union[str, bytes], password: typing.Optional[bytes] = None +): + """ + + Imports either a public or a private key in OpenSSH format + + + key: + A string in OpenSSH format, usually Base64-encoded. + + + securesystemslib.exceptions.FormatError, if the arguments are improperly + formatted. + + securesystemslib.exceptions.UnsupportedAlgorithmError, if 'pem' specifies + an unsupported key type. + + + None. + + + A dictionary containing the keys, conforming to 'securesystemslib.formats.KEY_SCHEMA'. + """ + + if isinstance(key, str): + key = key.encode("utf-8") + + firstLine, _ = key.split(b"\n", 1) + if openssh_text_format_marker_re.match(firstLine): + return import_hazmat_key(load_ssh_private_key(key, None)) + else: + return import_hazmat_key(load_ssh_public_key(key)) diff --git a/tests/data/ssh/ecdsa b/tests/data/ssh/ecdsa new file mode 100644 index 000000000..5ca9f7651 --- /dev/null +++ b/tests/data/ssh/ecdsa @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQRIz8F5gDezWgzdlRn5Upj7B1NoK43R +wP1d/oiCtNKSYDdql9ds3d+5zzEhnhWvdjGOn1sKwAFlbkz+aueExAkvAAAAqGkmJORpJi +TkAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEjPwXmAN7NaDN2V +GflSmPsHU2grjdHA/V3+iIK00pJgN2qX12zd37nPMSGeFa92MY6fWwrAAWVuTP5q54TECS +8AAAAhAMfqco7t7Cdz3rXsPcUwOINw4CKTqyTpOCFTzjULEB6AAAAACWVjZHNhIGtleQEC +AwQFBg== +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/data/ssh/ecdsa.pub b/tests/data/ssh/ecdsa.pub new file mode 100644 index 000000000..c154e3e0b --- /dev/null +++ b/tests/data/ssh/ecdsa.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEjPwXmAN7NaDN2VGflSmPsHU2grjdHA/V3+iIK00pJgN2qX12zd37nPMSGeFa92MY6fWwrAAWVuTP5q54TECS8= ecdsa key diff --git a/tests/data/ssh/ed25519 b/tests/data/ssh/ed25519 new file mode 100644 index 000000000..42367f759 --- /dev/null +++ b/tests/data/ssh/ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAUMmZQY7F/6Pvhj+LQn6HLke1TI0L1qvVImeBCxKjFuAAAAJAh8wZwIfMG +cAAAAAtzc2gtZWQyNTUxOQAAACAUMmZQY7F/6Pvhj+LQn6HLke1TI0L1qvVImeBCxKjFuA +AAAED6uVRyKJZMvslMIFpm4nbqMcRlzza7VwJobiRcbLNwSBQyZlBjsX/o++GP4tCfocuR +7VMjQvWq9UiZ4ELEqMW4AAAAC2VkMjU1MTkga2V5AQI= +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/data/ssh/ed25519.pub b/tests/data/ssh/ed25519.pub new file mode 100644 index 000000000..b9a878dd0 --- /dev/null +++ b/tests/data/ssh/ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBQyZlBjsX/o++GP4tCfocuR7VMjQvWq9UiZ4ELEqMW4 ed25519 key diff --git a/tests/data/ssh/generate.sh b/tests/data/ssh/generate.sh new file mode 100755 index 000000000..498832993 --- /dev/null +++ b/tests/data/ssh/generate.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env sh + +types="ecdsa ed25519 rsa" # dsa + +for t in $types; do + yes | ssh-keygen -t $t -C "$t key" -N "" -f ./$t; +done diff --git a/tests/data/ssh/rsa b/tests/data/ssh/rsa new file mode 100644 index 000000000..a52326394 --- /dev/null +++ b/tests/data/ssh/rsa @@ -0,0 +1,38 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +fyozEzAWaCa90AAAAHcnNhIGtleQECAwQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/data/ssh/rsa.pub b/tests/data/ssh/rsa.pub new file mode 100644 index 000000000..b260788f9 --- /dev/null +++ b/tests/data/ssh/rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzzjkzJGZjENWuFFpjhhK7y+YpMMST18lCK28dlG3QbqbpVJggC+3DcpD4GfFVHTEUpJWplatF4nlpHrzQTc/8RhJzVg+FY3hZXRdxX5J5zJ5vW1aTQdduj0AGMQ0RRaFQo3o5fGMQdrthDUW1/9ybGF0sv4DlTH0rtR8yCvXpl0LmaCIn0MireG8SqsaxdYDb8KdbOHL8Ezo2UmqZg0+svwkphEFoFzqQYVNAhgG4+/5Yotl2PBxZrSUWEUApqvWSS5BeyT/cTnf/8XDGV64H2Qmw9nZhRTIan6GgTgzPOhaX3yUxXS4ZI5rnWRQJuOk/J6FVKsJFNVA+/BAsxjcbUpms+PUtrXe+mHMLcb9FvQAiR9oteNMWUM3X1H8hVRZ20ETTzZVXM44mbSJ+ZvBEcpsGo9RmaMnvsc+4QI9ak/wEojPGN1msNXh/R8RWnhG+sCkbM9Btf2wf1RUyxtpkpYksbcDVyQEl+Rr6x2M+sJH9KZCzS9OFL8hZHKsCAks= rsa key diff --git a/tests/test_openssh.py b/tests/test_openssh.py new file mode 100644 index 000000000..fb0f851ff --- /dev/null +++ b/tests/test_openssh.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +""" + + test_openssh.py + + + KOLANICH + + + Nov 15, 2017 + + + See LICENSE for licensing information. + + + Test OpenSSH-related functions. + +""" + +import sys +import unittest +from pathlib import Path + +this_dir = Path(__file__).resolve().absolute().parent +keys_dir = this_dir / "data" / "ssh" + +# pylint: disable=wrong-import-position +from securesystemslib.convert.ssh import import_ssh_key +from securesystemslib.keys import create_signature, verify_signature + +TEST_DATA = b"test" + + +class TestOpenSSH(unittest.TestCase): + def test_openssh_import_and_sign_and_verify(self): + files = sorted(set(keys_dir.glob("*.pub"))) + for pub_f in files: + sec_f = pub_f.parent / pub_f.stem + with self.subTest(pub_f=pub_f, sec_f=sec_f): + pub = import_ssh_key(pub_f.read_text(), None) + sec = import_ssh_key(sec_f.read_text(), None) + signature = create_signature(sec, TEST_DATA) + self.assertTrue(verify_signature(pub, signature, TEST_DATA)) + + +if __name__ == "__main__": + unittest.main()