sshpubkeys-2.2.0/0000755000076500000240000000000012755063302014323 5ustar ojarvastaff00000000000000sshpubkeys-2.2.0/PKG-INFO0000644000076500000240000001217412755063302015425 0ustar ojarvastaff00000000000000Metadata-Version: 1.1 Name: sshpubkeys Version: 2.2.0 Summary: SSH public key parser Home-page: https://github.com/ojarva/python-sshpubkeys Author: Olli Jarva Author-email: olli@jarva.fi License: BSD Description: OpenSSH Public Key Parser for Python ==================================== .. image:: https://travis-ci.org/ojarva/python-sshpubkeys.svg?branch=master :target: https://travis-ci.org/ojarva/python-sshpubkeys .. image:: https://pypip.in/v/sshpubkeys/badge.png :target: https://pypi.python.org/pypi/sshpubkeys Native implementation for validating OpenSSH public keys. Currently ssh-rsa, ssh-dss (DSA), ssh-ed25519 and ecdsa keys with NIST curves are supported. Installation: :: pip install sshpubkeys or clone the `repository `_ and use :: python setup.py install Usage: :: import sys from sshpubkeys import SSHKey ssh = SSHKey("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCxO38tKAJXIs9ivPxt7AY" "dfybgtAR1ow3Qkb9GPQ6wkFHQqcFDe6faKCxH6iDRteo4D8L8B" "xwzN42uZSB0nfmjkIxFTcEU3mFSXEbWByg78aoddMrAAjatyrh" "H1pON6P0= ojarva@ojar-laptop", strict_mode=True) try: ssh.parse() except InvalidKeyException as err: print("Invalid key:", err) sys.exit(1) except NotImplementedError as err: print("Invalid key type:", err) sys.exit(1) print(ssh.bits) # 768 print(ssh.hash_md5()) # 56:84:1e:90:08:3b:60:c7:29:70:5f:5e:25:a6:3b:86 print(ssh.hash_sha256()) # SHA256:xk3IEJIdIoR9MmSRXTP98rjDdZocmXJje/28ohMQEwM print(ssh.hash_sha512()) # SHA512:1C3lNBhjpDVQe39hnyy+xvlZYU3IPwzqK1rVneGavy6O3/ebjEQSFvmeWoyMTplIanmUK1hmr9nA8Skmj516HA print(ssh.comment) # ojar@ojar-laptop print(ssh.options_raw) # None (string of optional options at the beginning of public key) print(ssh.options) # None (options as a dictionary, parsed and validated) Options ------- Set options in constructor as a keywords (i.e., `SSHKey(None, strict_mode=False)`) - strict_mode: defaults to True. Disallows keys OpenSSH's ssh-keygen refuses to create. For instance, this includes DSA keys where length != 1024 bits and RSA keys shorter than 1024-bit. If set to False, tries to allow all keys OpenSSH accepts, including highly insecure 1-bit DSA keys. - skip_option_parsing: if set to True, options string is not parsed (ssh.options_raw is populated, but ssh.options is not). Exceptions ---------- - NotImplementedError if invalid ecdsa curve or unknown key type is encountered. - InvalidKeyException if any other error is encountered: - TooShortKeyException if key is too short (<768 bits for RSA, <1024 for DSA, <256 for ED25519) - TooLongKeyException if key is too long (>16384 for RSA, >1024 for DSA, >256 for ED25519) - InvalidTypeException if key type ("ssh-rsa" in above example) does not match to what is included in base64 encoded data. - MalformedDataException if decoding and extracting the data fails. - InvalidOptionsException if options string is invalid. - InvalidOptionNameException if option name contains invalid characters. - UnknownOptionNameException if option name is not recognized. - MissingMandatoryOptionValueException if option needs to have parameter, but it is absent. Tests ----- See "`tests/ `_" folder for unit tests. Use :: python setup.py test or :: python3 setup.py test to run test suite. If you have keys that are not parsed properly, or malformed keys that raise incorrect exception, please send your *public key* to olli@jarva.fi, and I'll include it. Alternatively, `create a new issue `_ or make `a pull request `_ in github. Keywords: ssh pubkey public key openssh ssh-rsa ssh-dss ssh-ed25519 Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Topic :: Security Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation :: PyPy sshpubkeys-2.2.0/README.rst0000644000076500000240000000677012755061601016024 0ustar ojarvastaff00000000000000OpenSSH Public Key Parser for Python ==================================== .. image:: https://travis-ci.org/ojarva/python-sshpubkeys.svg?branch=master :target: https://travis-ci.org/ojarva/python-sshpubkeys .. image:: https://pypip.in/v/sshpubkeys/badge.png :target: https://pypi.python.org/pypi/sshpubkeys Native implementation for validating OpenSSH public keys. Currently ssh-rsa, ssh-dss (DSA), ssh-ed25519 and ecdsa keys with NIST curves are supported. Installation: :: pip install sshpubkeys or clone the `repository `_ and use :: python setup.py install Usage: :: import sys from sshpubkeys import SSHKey ssh = SSHKey("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCxO38tKAJXIs9ivPxt7AY" "dfybgtAR1ow3Qkb9GPQ6wkFHQqcFDe6faKCxH6iDRteo4D8L8B" "xwzN42uZSB0nfmjkIxFTcEU3mFSXEbWByg78aoddMrAAjatyrh" "H1pON6P0= ojarva@ojar-laptop", strict_mode=True) try: ssh.parse() except InvalidKeyException as err: print("Invalid key:", err) sys.exit(1) except NotImplementedError as err: print("Invalid key type:", err) sys.exit(1) print(ssh.bits) # 768 print(ssh.hash_md5()) # 56:84:1e:90:08:3b:60:c7:29:70:5f:5e:25:a6:3b:86 print(ssh.hash_sha256()) # SHA256:xk3IEJIdIoR9MmSRXTP98rjDdZocmXJje/28ohMQEwM print(ssh.hash_sha512()) # SHA512:1C3lNBhjpDVQe39hnyy+xvlZYU3IPwzqK1rVneGavy6O3/ebjEQSFvmeWoyMTplIanmUK1hmr9nA8Skmj516HA print(ssh.comment) # ojar@ojar-laptop print(ssh.options_raw) # None (string of optional options at the beginning of public key) print(ssh.options) # None (options as a dictionary, parsed and validated) Options ------- Set options in constructor as a keywords (i.e., `SSHKey(None, strict_mode=False)`) - strict_mode: defaults to True. Disallows keys OpenSSH's ssh-keygen refuses to create. For instance, this includes DSA keys where length != 1024 bits and RSA keys shorter than 1024-bit. If set to False, tries to allow all keys OpenSSH accepts, including highly insecure 1-bit DSA keys. - skip_option_parsing: if set to True, options string is not parsed (ssh.options_raw is populated, but ssh.options is not). Exceptions ---------- - NotImplementedError if invalid ecdsa curve or unknown key type is encountered. - InvalidKeyException if any other error is encountered: - TooShortKeyException if key is too short (<768 bits for RSA, <1024 for DSA, <256 for ED25519) - TooLongKeyException if key is too long (>16384 for RSA, >1024 for DSA, >256 for ED25519) - InvalidTypeException if key type ("ssh-rsa" in above example) does not match to what is included in base64 encoded data. - MalformedDataException if decoding and extracting the data fails. - InvalidOptionsException if options string is invalid. - InvalidOptionNameException if option name contains invalid characters. - UnknownOptionNameException if option name is not recognized. - MissingMandatoryOptionValueException if option needs to have parameter, but it is absent. Tests ----- See "`tests/ `_" folder for unit tests. Use :: python setup.py test or :: python3 setup.py test to run test suite. If you have keys that are not parsed properly, or malformed keys that raise incorrect exception, please send your *public key* to olli@jarva.fi, and I'll include it. Alternatively, `create a new issue `_ or make `a pull request `_ in github. sshpubkeys-2.2.0/setup.cfg0000644000076500000240000000013012755063302016136 0ustar ojarvastaff00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 sshpubkeys-2.2.0/setup.py0000644000076500000240000000257612755063206016052 0ustar ojarvastaff00000000000000from setuptools import setup from codecs import open as codecs_open from os import path here = path.abspath(path.dirname(__file__)) with codecs_open(path.join(here, 'README.rst'), encoding='utf-8') as f: long_description = f.read() setup( name='sshpubkeys', version='2.2.0', description='SSH public key parser', long_description=long_description, url='https://github.com/ojarva/python-sshpubkeys', author='Olli Jarva', author_email='olli@jarva.fi', license='BSD', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Topic :: Security', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: Implementation :: PyPy', ], keywords='ssh pubkey public key openssh ssh-rsa ssh-dss ssh-ed25519', packages=["sshpubkeys"], test_suite="tests", install_requires=['pycrypto>=2.6', 'ecdsa>=0.13'], extras_require={ 'dev': ['twine', 'wheel'], }, ) sshpubkeys-2.2.0/sshpubkeys/0000755000076500000240000000000012755063302016523 5ustar ojarvastaff00000000000000sshpubkeys-2.2.0/sshpubkeys/__init__.py0000644000076500000240000003770312755061601020646 0ustar ojarvastaff00000000000000# pylint:disable=line-too-long """ Parser for ssh public keys. Currently supports ssh-rsa, ssh-dsa, ssh-ed25519 and ssh-dss keys. import sys key_data = open("ssh-pubkey-file.pem").read() ssh_key = SSHKey(key_data) try: ssh_key.parse() except InvalidKeyException: print("Invalid key") sys.exit(1) print(ssh_key.bits) """ import base64 import binascii import hashlib import re import struct import sys import warnings import ecdsa from Crypto.PublicKey import RSA, DSA from .exceptions import * # pylint:disable=wildcard-import __all__ = ["SSHKey"] class SSHKey(object): # pylint:disable=too-many-instance-attributes """ ssh_key = SSHKey(key_data, strict=True) ssh_key.parse() strict=True (default) only allows keys ssh-keygen generates. Setting strict mode to false allows all keys OpenSSH actually accepts, including highly insecure ones. For example, OpenSSH accepts 512-bit DSA keys and 64-bit RSA keys which are highly insecure. """ DSA_MIN_LENGTH_STRICT = 1024 DSA_MAX_LENGTH_STRICT = 1024 DSA_MIN_LENGTH_LOOSE = 1 DSA_MAX_LENGTH_LOOSE = 16384 DSA_N_LENGTH = 160 ECDSA_CURVE_DATA = { b"nistp256": (ecdsa.curves.NIST256p, hashlib.sha256), b"nistp192": (ecdsa.curves.NIST192p, hashlib.sha256), b"nistp224": (ecdsa.curves.NIST224p, hashlib.sha256), b"nistp384": (ecdsa.curves.NIST384p, hashlib.sha384), b"nistp521": (ecdsa.curves.NIST521p, hashlib.sha512), } RSA_MIN_LENGTH_STRICT = 1024 RSA_MAX_LENGTH_STRICT = 16384 RSA_MIN_LENGTH_LOOSE = 768 RSA_MAX_LENGTH_LOOSE = 16384 # Valid as of OpenSSH_6.9p1 # argument name, value is mandatory. Options are case-insensitive, but this list must be in lowercase. OPTIONS_SPEC = [ ("agent-forwarding", False), ("cert-authority", False), ("command", True), ("environment", True), ("from", True), ("no-agent-forwarding", False), ("no-port-forwarding", False), ("no-pty", False), ("no-user-rc", False), ("no-x11-forwarding", False), ("permitopen", True), ("port-forwarding", False), ("principals", True), ("pty", False), ("restrict", False), ("tunnel", True), ("user-rc", False), ("x11-forwarding", False), ] OPTION_NAME_RE = re.compile("^[A-Za-z0-9-]+$") INT_LEN = 4 FIELDS = ["rsa", "dsa", "ecdsa", "bits", "comment", "options", "options_raw", "key_type"] def __init__(self, keydata=None, **kwargs): self.keydata = keydata self._decoded_key = None self.rsa = None self.dsa = None self.ecdsa = None self.bits = None self.comment = None self.options = None self.options_raw = None self.key_type = None self.strict_mode = bool(kwargs.get("strict", True)) self.skip_option_parsing = bool(kwargs.get("skip_option_parsing", False)) if keydata: try: self.parse(keydata) except (InvalidKeyException, NotImplementedError): pass def reset(self): """ Reset all data fields """ for field in self.FIELDS: setattr(self, field, None) def hash(self): """ Calculate md5 fingerprint. Deprecated, use .hash_md5() instead. """ warnings.warn("hash() is deprecated. Use hash_md5(), hash_sha256() or hash_sha512() instead.") return self.hash_md5().replace(b"MD5:", b"") def hash_md5(self): """ Calculate md5 fingerprint. Shamelessly copied from http://stackoverflow.com/questions/6682815/deriving-an-ssh-fingerprint-from-a-public-key-in-python For specification, see RFC4716, section 4. """ fp_plain = hashlib.md5(self._decoded_key).hexdigest() return "MD5:" + ':'.join(a + b for a, b in zip(fp_plain[::2], fp_plain[1::2])) def hash_sha256(self): """ Calculate sha256 fingerprint. """ fp_plain = hashlib.sha256(self._decoded_key).digest() return (b"SHA256:" + base64.b64encode(fp_plain).replace(b"=", b"")).decode("utf-8") def hash_sha512(self): """ Calculates sha512 fingerprint. """ fp_plain = hashlib.sha512(self._decoded_key).digest() return (b"SHA512:" + base64.b64encode(fp_plain).replace(b"=", b"")).decode("utf-8") def _unpack_by_int(self, data, current_position): """ Returns a tuple with (location of next data field, contents of requested data field). """ # Unpack length of data field try: requested_data_length = struct.unpack('>I', data[current_position:current_position + self.INT_LEN])[0] except struct.error: raise MalformedDataException("Unable to unpack %s bytes from the data" % self.INT_LEN) # Move pointer to the beginning of the data field current_position += self.INT_LEN remaining_data_length = len(data[current_position:]) if remaining_data_length < requested_data_length: raise MalformedDataException("Requested %s bytes, but only %s bytes available." % (requested_data_length, remaining_data_length)) next_data = data[current_position:current_position + requested_data_length] # Move pointer to the end of the data field current_position += requested_data_length return current_position, next_data @classmethod def _parse_long(cls, data): """ Calculate two's complement """ if sys.version < '3': ret = long(0) for byte in data: ret = (ret << 8) + ord(byte) else: ret = 0 # pylint:disable=redefined-variable-type for byte in data: ret = (ret << 8) + byte return ret def _split_key(self, data): options_raw = None # Terribly inefficient way to remove options, but hey, it works. if not data.startswith("ssh-") and not data.startswith("ecdsa-"): quote_open = False for i, character in enumerate(data): if character == '"': # only double quotes are allowed, no need to care about single quotes quote_open = not quote_open if quote_open: continue if character == " ": # Data begins after the first space options_raw = data[:i] data = data[i + 1:] break else: raise MalformedDataException("Couldn't find beginning of the key data") key_parts = data.strip().split(None, 2) if len(key_parts) < 2: # Key type and content are mandatory fields. raise InvalidKeyException("Unexpected key format: at least type and base64 encoded value is required") if len(key_parts) == 3: self.comment = key_parts[2] key_parts = key_parts[0:2] if options_raw: # Populate and parse options field. self.options_raw = options_raw if not self.skip_option_parsing: self.options = self.parse_options(self.options_raw) else: # Set empty defaults for fields self.options_raw = None self.options = {} return key_parts @classmethod def decode_key(cls, pubkey_content): """ Decode base64 coded part of the key. """ try: decoded_key = base64.b64decode(pubkey_content.encode("ascii")) except (TypeError, binascii.Error): raise MalformedDataException("Unable to decode the key") return decoded_key @classmethod def _bits_in_number(cls, number): return len(format(number, "b")) def parse_options(self, options): """ Parses ssh options string """ quote_open = False parsed_options = {} def parse_add_single_option(opt): """ Parses and validates a single option, and adds it to parsed_options field. """ if "=" in opt: opt_name, opt_value = opt.split("=", 1) opt_value = opt_value.replace('"', '') else: opt_name = opt opt_value = True if " " in opt_name or not self.OPTION_NAME_RE.match(opt_name): raise InvalidOptionNameException("%s is not valid option name." % opt_name) if self.strict_mode: for valid_opt_name, value_required in self.OPTIONS_SPEC: if opt_name.lower() == valid_opt_name: if value_required and opt_value is True: raise MissingMandatoryOptionValueException("%s is missing mandatory value." % opt_name) break else: raise UnknownOptionNameException("%s is unrecognized option name." % opt_name) if opt_name not in parsed_options: parsed_options[opt_name] = [] parsed_options[opt_name].append(opt_value) start_of_current_opt = 0 i = 1 # Need to be set for empty options strings for i, character in enumerate(options): if character == '"': # only double quotes are allowed, no need to care about single quotes quote_open = not quote_open if quote_open: continue if character == ",": opt = options[start_of_current_opt:i] parse_add_single_option(opt) start_of_current_opt = i + 1 # Data begins after the first space if start_of_current_opt + 1 != i: opt = options[start_of_current_opt:] parse_add_single_option(opt) if quote_open: raise InvalidOptionsException("Unbalanced quotes.") return parsed_options def _process_ssh_rsa(self, data): """ Parses ssh-rsa public keys """ current_position, raw_e = self._unpack_by_int(data, 0) current_position, raw_n = self._unpack_by_int(data, current_position) unpacked_e = self._parse_long(raw_e) unpacked_n = self._parse_long(raw_n) self.rsa = RSA.construct((unpacked_n, unpacked_e)) self.bits = self.rsa.size() + 1 if self.strict_mode: min_length = self.RSA_MIN_LENGTH_STRICT max_length = self.RSA_MAX_LENGTH_STRICT else: min_length = self.RSA_MIN_LENGTH_LOOSE max_length = self.RSA_MAX_LENGTH_LOOSE if self.bits < min_length: raise TooShortKeyException("%s key data can not be shorter than %s bits (was %s)" % (self.key_type, min_length, self.bits)) if self.bits > max_length: raise TooLongKeyException("%s key data can not be longer than %s bits (was %s)" % (self.key_type, max_length, self.bits)) return current_position def _process_ssh_dss(self, data): """ Parses ssh-dsa public keys """ data_fields = {} current_position = 0 for item in ("p", "q", "g", "y"): current_position, value = self._unpack_by_int(data, current_position) data_fields[item] = self._parse_long(value) self.dsa = DSA.construct((data_fields["y"], data_fields["g"], data_fields["p"], data_fields["q"])) self.bits = self.dsa.size() + 1 q_bits = self._bits_in_number(data_fields["q"]) if q_bits != self.DSA_N_LENGTH: raise InvalidKeyException("Incorrect DSA key parameters: bits(p)=%s, q=%s" % (self.bits, q_bits)) if self.strict_mode: min_length = self.DSA_MIN_LENGTH_STRICT max_length = self.DSA_MAX_LENGTH_STRICT else: min_length = self.DSA_MIN_LENGTH_LOOSE max_length = self.DSA_MAX_LENGTH_LOOSE if self.bits < min_length: raise TooShortKeyException("%s key can not be shorter than %s bits (was %s)" % (self.key_type, min_length, self.bits)) if self.bits > max_length: raise TooLongKeyException("%s key data can not be longer than %s bits (was %s)" % (self.key_type, max_length, self.bits)) return current_position def _process_ecdsa_sha(self, data): """ Parses ecdsa-sha public keys """ current_position, curve_information = self._unpack_by_int(data, 0) if curve_information not in self.ECDSA_CURVE_DATA: raise NotImplementedError("Invalid curve type: %s" % curve_information) curve, hash_algorithm = self.ECDSA_CURVE_DATA[curve_information] current_position, key_data = self._unpack_by_int(data, current_position) try: # data starts with \x04, which should be discarded. ecdsa_key = ecdsa.VerifyingKey.from_string(key_data[1:], curve, hash_algorithm) except AssertionError: raise InvalidKeyException("Invalid ecdsa key") self.bits = int(curve_information.replace(b"nistp", b"")) self.ecdsa = ecdsa_key return current_position def _process_ed25516(self, data): """ Parses ed25516 keys. There is no (apparent) way to validate ed25519 keys. This only checks data length (256 bits), but does not try to validate the key in any way. """ current_position, verifying_key = self._unpack_by_int(data, 0) verifying_key_length = len(verifying_key) * 8 verifying_key = self._parse_long(verifying_key) if verifying_key < 0: raise InvalidKeyException("ed25519 verifying key must be >0.") self.bits = verifying_key_length if self.bits != 256: raise InvalidKeyLengthException("ed25519 keys must be 256 bits (was %s bits)" % self.bits) return current_position def _process_key(self, data): if self.key_type == b"ssh-rsa": return self._process_ssh_rsa(data) elif self.key_type == b"ssh-dss": return self._process_ssh_dss(data) elif self.key_type.strip().startswith(b"ecdsa-sha"): return self._process_ecdsa_sha(data) elif self.key_type == b"ssh-ed25519": return self._process_ed25516(data) else: raise NotImplementedError("Invalid key type: %s" % self.key_type) def parse(self, keydata=None): """ Validates SSH public key Throws exception for invalid keys. Otherwise returns None. Populates key_type, bits and bits fields. For rsa keys, see field "rsa" for raw public key data. For dsa keys, see field "dsa". For ecdsa keys, see field "ecdsa". """ if keydata is None: if self.keydata is None: raise ValueError("Key data must be supplied either in constructor or to parse()") keydata = self.keydata else: self.reset() self.keydata = keydata if keydata.startswith("---- BEGIN SSH2 PUBLIC KEY ----"): # SSH2 key format key_type = None # There is no redundant key-type field - skip comparing plain-text and encoded data. pubkey_content = "" for line in keydata.split("\n"): if ":" in line: # key-value lines continue if "----" in line: # begin/end lines continue pubkey_content += line else: key_parts = self._split_key(keydata) key_type = key_parts[0] pubkey_content = key_parts[1] self._decoded_key = self.decode_key(pubkey_content) # Check key type current_position, unpacked_key_type = self._unpack_by_int(self._decoded_key, 0) if key_type is not None and key_type != unpacked_key_type.decode(): raise InvalidTypeException("Keytype mismatch: %s != %s" % (key_type, unpacked_key_type)) self.key_type = unpacked_key_type key_data_length = self._process_key(self._decoded_key[current_position:]) current_position = current_position + key_data_length if current_position != len(self._decoded_key): raise MalformedDataException("Leftover data: %s bytes" % (len(self._decoded_key) - current_position)) sshpubkeys-2.2.0/sshpubkeys/exceptions.py0000644000076500000240000000304312755061601021256 0ustar ojarvastaff00000000000000# pylint:disable=line-too-long """ Exceptions for sshpubkeys """ class InvalidKeyException(Exception): """ Invalid key - something is wrong with the key, and it should not be accepted, as OpenSSH will not work with it. """ pass class InvalidKeyLengthException(InvalidKeyException): """ Invalid key length - either too short or too long. See also TooShortKeyException and TooLongKeyException """ pass class TooShortKeyException(InvalidKeyLengthException): """ Key is shorter than what the specification allows """ pass class TooLongKeyException(InvalidKeyLengthException): """ Key is longer than what the specification allows """ pass class InvalidTypeException(InvalidKeyException): """ Key type is invalid or unrecognized """ pass class MalformedDataException(InvalidKeyException): """ The key is invalid - unable to parse the data. The data may be corrupted, truncated, or includes extra content that is not allowed. """ pass class InvalidOptionsException(MalformedDataException): """ Options string is invalid: it contains invalid characters, unrecognized options, or is otherwise malformed. """ pass class InvalidOptionNameException(InvalidOptionsException): """ Invalid option name (contains disallowed characters, or is unrecognized.) """ pass class UnknownOptionNameException(InvalidOptionsException): """ Unrecognized option name. """ pass class MissingMandatoryOptionValueException(InvalidOptionsException): """ Mandatory option value is missing. """ pass sshpubkeys-2.2.0/sshpubkeys.egg-info/0000755000076500000240000000000012755063302020215 5ustar ojarvastaff00000000000000sshpubkeys-2.2.0/sshpubkeys.egg-info/dependency_links.txt0000644000076500000240000000000112755063300024261 0ustar ojarvastaff00000000000000 sshpubkeys-2.2.0/sshpubkeys.egg-info/PKG-INFO0000644000076500000240000001217412755063300021315 0ustar ojarvastaff00000000000000Metadata-Version: 1.1 Name: sshpubkeys Version: 2.2.0 Summary: SSH public key parser Home-page: https://github.com/ojarva/python-sshpubkeys Author: Olli Jarva Author-email: olli@jarva.fi License: BSD Description: OpenSSH Public Key Parser for Python ==================================== .. image:: https://travis-ci.org/ojarva/python-sshpubkeys.svg?branch=master :target: https://travis-ci.org/ojarva/python-sshpubkeys .. image:: https://pypip.in/v/sshpubkeys/badge.png :target: https://pypi.python.org/pypi/sshpubkeys Native implementation for validating OpenSSH public keys. Currently ssh-rsa, ssh-dss (DSA), ssh-ed25519 and ecdsa keys with NIST curves are supported. Installation: :: pip install sshpubkeys or clone the `repository `_ and use :: python setup.py install Usage: :: import sys from sshpubkeys import SSHKey ssh = SSHKey("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCxO38tKAJXIs9ivPxt7AY" "dfybgtAR1ow3Qkb9GPQ6wkFHQqcFDe6faKCxH6iDRteo4D8L8B" "xwzN42uZSB0nfmjkIxFTcEU3mFSXEbWByg78aoddMrAAjatyrh" "H1pON6P0= ojarva@ojar-laptop", strict_mode=True) try: ssh.parse() except InvalidKeyException as err: print("Invalid key:", err) sys.exit(1) except NotImplementedError as err: print("Invalid key type:", err) sys.exit(1) print(ssh.bits) # 768 print(ssh.hash_md5()) # 56:84:1e:90:08:3b:60:c7:29:70:5f:5e:25:a6:3b:86 print(ssh.hash_sha256()) # SHA256:xk3IEJIdIoR9MmSRXTP98rjDdZocmXJje/28ohMQEwM print(ssh.hash_sha512()) # SHA512:1C3lNBhjpDVQe39hnyy+xvlZYU3IPwzqK1rVneGavy6O3/ebjEQSFvmeWoyMTplIanmUK1hmr9nA8Skmj516HA print(ssh.comment) # ojar@ojar-laptop print(ssh.options_raw) # None (string of optional options at the beginning of public key) print(ssh.options) # None (options as a dictionary, parsed and validated) Options ------- Set options in constructor as a keywords (i.e., `SSHKey(None, strict_mode=False)`) - strict_mode: defaults to True. Disallows keys OpenSSH's ssh-keygen refuses to create. For instance, this includes DSA keys where length != 1024 bits and RSA keys shorter than 1024-bit. If set to False, tries to allow all keys OpenSSH accepts, including highly insecure 1-bit DSA keys. - skip_option_parsing: if set to True, options string is not parsed (ssh.options_raw is populated, but ssh.options is not). Exceptions ---------- - NotImplementedError if invalid ecdsa curve or unknown key type is encountered. - InvalidKeyException if any other error is encountered: - TooShortKeyException if key is too short (<768 bits for RSA, <1024 for DSA, <256 for ED25519) - TooLongKeyException if key is too long (>16384 for RSA, >1024 for DSA, >256 for ED25519) - InvalidTypeException if key type ("ssh-rsa" in above example) does not match to what is included in base64 encoded data. - MalformedDataException if decoding and extracting the data fails. - InvalidOptionsException if options string is invalid. - InvalidOptionNameException if option name contains invalid characters. - UnknownOptionNameException if option name is not recognized. - MissingMandatoryOptionValueException if option needs to have parameter, but it is absent. Tests ----- See "`tests/ `_" folder for unit tests. Use :: python setup.py test or :: python3 setup.py test to run test suite. If you have keys that are not parsed properly, or malformed keys that raise incorrect exception, please send your *public key* to olli@jarva.fi, and I'll include it. Alternatively, `create a new issue `_ or make `a pull request `_ in github. Keywords: ssh pubkey public key openssh ssh-rsa ssh-dss ssh-ed25519 Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Topic :: Security Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation :: PyPy sshpubkeys-2.2.0/sshpubkeys.egg-info/requires.txt0000644000076500000240000000005412755063300022612 0ustar ojarvastaff00000000000000pycrypto>=2.6 ecdsa>=0.13 [dev] twine wheelsshpubkeys-2.2.0/sshpubkeys.egg-info/SOURCES.txt0000644000076500000240000000036612755063302022106 0ustar ojarvastaff00000000000000README.rst setup.cfg setup.py sshpubkeys/__init__.py sshpubkeys/exceptions.py sshpubkeys.egg-info/PKG-INFO sshpubkeys.egg-info/SOURCES.txt sshpubkeys.egg-info/dependency_links.txt sshpubkeys.egg-info/requires.txt sshpubkeys.egg-info/top_level.txtsshpubkeys-2.2.0/sshpubkeys.egg-info/top_level.txt0000644000076500000240000000001312755063300022737 0ustar ojarvastaff00000000000000sshpubkeys