diff options
Diffstat (limited to 'paramiko/pkey.py')
-rw-r--r-- | paramiko/pkey.py | 69 |
1 files changed, 65 insertions, 4 deletions
diff --git a/paramiko/pkey.py b/paramiko/pkey.py index c0844407..02ae9ed5 100644 --- a/paramiko/pkey.py +++ b/paramiko/pkey.py @@ -59,9 +59,22 @@ def _unpad_openssh(data): return data[:-padding_length] +class UnknownKeyType(Exception): + """ + An unknown public/private key algorithm was attempted to be read. + """ + + def __init__(self, key_type, key_bytes): + self.key_type = key_type + self.key_bytes = key_bytes + + class PKey: """ Base class for public keys. + + Also includes some "meta" level convenience constructors such as + `.from_type_string`. """ # known encryption types for private key files: @@ -92,6 +105,46 @@ class PKey: ) END_TAG = re.compile(r"^-{5}END (RSA|DSA|EC|OPENSSH) PRIVATE KEY-{5}\s*$") + @staticmethod + def from_type_string(key_type, key_bytes): + """ + Given type `str` & raw `bytes`, return a `PKey` subclass instance. + + For example, ``PKey.from_type_string("ssh-ed25519", <public bytes>)`` + will (if successful) return a new `.Ed25519Key`. + + :param str key_type: + The key type, eg ``"ssh-ed25519"``. + :param bytes key_bytes: + The raw byte data forming the key material, as expected by + subclasses' ``data`` parameter. + + :returns: + A `PKey` subclass instance. + + :raises: + `UnknownKeyType`, if no registered classes knew about this type. + + .. versionadded:: 3.2 + """ + from paramiko import key_classes + + for key_class in key_classes: + if key_type in key_class.identifiers(): + return key_class(data=key_bytes) + raise UnknownKeyType(key_type=key_type, key_bytes=key_bytes) + + @classmethod + def identifiers(cls): + """ + returns an iterable of key format/name strings this class can handle. + + Most classes only have a single identifier, and thus this default + implementation suffices; see `.ECDSAKey` for one example of an + override. + """ + return [cls.name] + def __init__(self, msg=None, data=None): """ Create a new instance of this public key type. If ``msg`` is given, @@ -150,11 +203,17 @@ class PKey: Similar to `get_name`, but aimed at pure algorithm name instead of SSH protocol field value. """ + # Nuke the leading 'ssh-' # TODO in Python 3.9: use .removeprefix() - no_prefix = self.get_name().replace("ssh-", "") - # Mostly for ECDSA's sake; OpenSSH does basically this too. - no_suffix = no_prefix.split("-")[0] - return no_suffix.upper() + name = self.get_name().replace("ssh-", "") + # Trim any cert suffix (but leave the -cert, as OpenSSH does) + cert_tail = "-cert-v01@openssh.com" + if cert_tail in name: + name = name.replace(cert_tail, "-cert") + # Nuke any eg ECDSA suffix, OpenSSH does basically this too. + else: + name = name.split("-")[0] + return name.upper() def get_bits(self): """ @@ -163,6 +222,8 @@ class PKey: :return: bits in the key (as an `int`) """ + # TODO 4.0: raise NotImplementedError, 0 is unlikely to ever be + # _correct_ and nothing in the critical path seems to use this. return 0 def can_sign(self): |