[Python-modules-commits] [sshpubkeys] 01/02: import sshpubkeys_2.2.0.orig.tar.gz
Vincent Bernat
bernat at moszumanska.debian.org
Thu Nov 23 12:59:54 UTC 2017
This is an automated email from the git hooks/post-receive script.
bernat pushed a commit to annotated tag debian/2.2.0-1
in repository sshpubkeys.
commit 585ed6a652489da4df7c2d9e165808819d5baab1
Author: Vincent Bernat <vincent at bernat.im>
Date: Thu Nov 23 13:41:10 2017 +0100
import sshpubkeys_2.2.0.orig.tar.gz
---
PKG-INFO | 115 +++++++++
README.rst | 91 +++++++
setup.cfg | 8 +
setup.py | 45 ++++
sshpubkeys.egg-info/PKG-INFO | 115 +++++++++
sshpubkeys.egg-info/SOURCES.txt | 10 +
sshpubkeys.egg-info/dependency_links.txt | 1 +
sshpubkeys.egg-info/requires.txt | 6 +
sshpubkeys.egg-info/top_level.txt | 1 +
sshpubkeys/__init__.py | 416 +++++++++++++++++++++++++++++++
sshpubkeys/exceptions.py | 55 ++++
11 files changed, 863 insertions(+)
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..ededb69
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,115 @@
+Metadata-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 at 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 <https://github.com/ojarva/sshpubkeys>`_ and use
+
+ ::
+
+ python setup.py install
+
+ Usage:
+
+ ::
+
+ import sys
+ from sshpubkeys import SSHKey
+
+ ssh = SSHKey("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCxO38tKAJXIs9ivPxt7AY"
+ "dfybgtAR1ow3Qkb9GPQ6wkFHQqcFDe6faKCxH6iDRteo4D8L8B"
+ "xwzN42uZSB0nfmjkIxFTcEU3mFSXEbWByg78aoddMrAAjatyrh"
+ "H1pON6P0= ojarva at 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 at 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/ <https://github.com/ojarva/sshpubkeys/tree/master/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 at jarva.fi, and I'll include it. Alternatively, `create a new issue <https://github.com/ojarva/sshpubkeys/issues/new>`_ or make `a pull request <https://github.com/ojarva/sshpubkeys/compare>`_ 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
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..e49f2a3
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,91 @@
+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 <https://github.com/ojarva/sshpubkeys>`_ and use
+
+::
+
+ python setup.py install
+
+Usage:
+
+::
+
+ import sys
+ from sshpubkeys import SSHKey
+
+ ssh = SSHKey("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCxO38tKAJXIs9ivPxt7AY"
+ "dfybgtAR1ow3Qkb9GPQ6wkFHQqcFDe6faKCxH6iDRteo4D8L8B"
+ "xwzN42uZSB0nfmjkIxFTcEU3mFSXEbWByg78aoddMrAAjatyrh"
+ "H1pON6P0= ojarva at 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 at 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/ <https://github.com/ojarva/sshpubkeys/tree/master/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 at jarva.fi, and I'll include it. Alternatively, `create a new issue <https://github.com/ojarva/sshpubkeys/issues/new>`_ or make `a pull request <https://github.com/ojarva/sshpubkeys/compare>`_ in github.
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..6f08d0e
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,8 @@
+[bdist_wheel]
+universal = 1
+
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..812586e
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,45 @@
+from 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 at 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'],
+ },
+)
diff --git a/sshpubkeys.egg-info/PKG-INFO b/sshpubkeys.egg-info/PKG-INFO
new file mode 100644
index 0000000..ededb69
--- /dev/null
+++ b/sshpubkeys.egg-info/PKG-INFO
@@ -0,0 +1,115 @@
+Metadata-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 at 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 <https://github.com/ojarva/sshpubkeys>`_ and use
+
+ ::
+
+ python setup.py install
+
+ Usage:
+
+ ::
+
+ import sys
+ from sshpubkeys import SSHKey
+
+ ssh = SSHKey("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCxO38tKAJXIs9ivPxt7AY"
+ "dfybgtAR1ow3Qkb9GPQ6wkFHQqcFDe6faKCxH6iDRteo4D8L8B"
+ "xwzN42uZSB0nfmjkIxFTcEU3mFSXEbWByg78aoddMrAAjatyrh"
+ "H1pON6P0= ojarva at 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 at 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/ <https://github.com/ojarva/sshpubkeys/tree/master/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 at jarva.fi, and I'll include it. Alternatively, `create a new issue <https://github.com/ojarva/sshpubkeys/issues/new>`_ or make `a pull request <https://github.com/ojarva/sshpubkeys/compare>`_ 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
diff --git a/sshpubkeys.egg-info/SOURCES.txt b/sshpubkeys.egg-info/SOURCES.txt
new file mode 100644
index 0000000..d08251b
--- /dev/null
+++ b/sshpubkeys.egg-info/SOURCES.txt
@@ -0,0 +1,10 @@
+README.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.txt
\ No newline at end of file
diff --git a/sshpubkeys.egg-info/dependency_links.txt b/sshpubkeys.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/sshpubkeys.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/sshpubkeys.egg-info/requires.txt b/sshpubkeys.egg-info/requires.txt
new file mode 100644
index 0000000..6eb5fd2
--- /dev/null
+++ b/sshpubkeys.egg-info/requires.txt
@@ -0,0 +1,6 @@
+pycrypto>=2.6
+ecdsa>=0.13
+
+[dev]
+twine
+wheel
\ No newline at end of file
diff --git a/sshpubkeys.egg-info/top_level.txt b/sshpubkeys.egg-info/top_level.txt
new file mode 100644
index 0000000..c61c368
--- /dev/null
+++ b/sshpubkeys.egg-info/top_level.txt
@@ -0,0 +1 @@
+sshpubkeys
diff --git a/sshpubkeys/__init__.py b/sshpubkeys/__init__.py
new file mode 100644
index 0000000..45f2f48
--- /dev/null
+++ b/sshpubkeys/__init__.py
@@ -0,0 +1,416 @@
+# 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))
diff --git a/sshpubkeys/exceptions.py b/sshpubkeys/exceptions.py
new file mode 100644
index 0000000..d7f0ccf
--- /dev/null
+++ b/sshpubkeys/exceptions.py
@@ -0,0 +1,55 @@
+# 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
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/sshpubkeys.git
More information about the Python-modules-commits
mailing list