mirror of
https://github.com/jtesta/ssh-audit.git
synced 2024-11-22 10:31:41 +01:00
Flake8 fixes (#35)
* Apply Flake8 also on `setup.py` modified: tox.ini * Fix W605 - invalid escape syntax modified: packages/setup.py modified: tox.ini * Update comment about Flake8: W504 W503 and W504 are mutual exclusive - so we have to keep one of them. modified: tox.ini * Fix F841 - variable assigned but never used modified: ssh-audit.py modified: tox.ini * Fix E741 - ambiguous variable name 'l' modified: ssh-audit.py modified: tox.ini * Fix E712 - comparison to False should be 'if cond is False' ... and not 'if conf == False'. modified: ssh-audit.py modified: tox.ini * Fix E711 - comparison to None should be 'if cond is not None' ... and not 'if cond != None'. modified: ssh-audit.py modified: tox.ini * Fix E305 - expected 2 blank lines ... after class or function definition, found 1. modified: ssh-audit.py modified: tox.ini * Fix E303 - too many blank lines modified: ssh-audit.py modified: tox.ini * Fix E303 - too many blank lines modified: ssh-audit.py modified: tox.ini * Fix E301 - expected 1 blank line, found 0 No code change necessary, probably fixed by another commit. modified: tox.ini * Fix E265 - block comment should start with '# ' There is lots of commented out code, which usually should be just deleted. I will keep it for now, as I am not yet very familiar with the code base. modified: ssh-audit.py modified: tox.ini * Fix E261 - at least two spaces before inline comment modified: ssh-audit.py modified: tox.ini * Fix E251 - unexpected spaces around keyword / parameter equals modified: packages/setup.py modified: tox.ini * Fix E231 - missing whitespace after ',' No code change necessary, probably fixed by previous commit. modified: tox.ini * Fix E226 - missing whitespace around arithmetic operator modified: ssh-audit.py modified: tox.ini * Fix W293 - blank line contains whitespace modified: ssh-audit.py modified: tox.ini * Fix E221 - multiple spaces before operator modified: ssh-audit.py modified: tox.ini * Update comment about Flake 8 E241 Lots of data is formatted as tables, so this warning is disabled for a good reason. modified: tox.ini * Fix E401 - multiple imports on one line modified: ssh-audit.py modified: tox.ini * Do not ignore Flake8 warning F401 ... as there were no errors in source code anyway. modified: tox.ini * Fix F821 - undefined name modified: ssh-audit.py modified: tox.ini * Reformat ignore section for Flake8 modified: tox.ini * Flake8 test suite modified: test/conftest.py modified: test/test_auditconf.py modified: test/test_banner.py modified: test/test_buffer.py modified: test/test_errors.py modified: test/test_output.py modified: test/test_resolve.py modified: test/test_socket.py modified: test/test_software.py modified: test/test_ssh1.py modified: test/test_ssh2.py modified: test/test_ssh_algorithm.py modified: test/test_utils.py modified: test/test_version_compare.py modified: tox.ini
This commit is contained in:
parent
29d874b450
commit
246a41d46f
@ -5,7 +5,7 @@ import re
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
version = re.search('^VERSION\s*=\s*\'v(\d\.\d\.\d)\'', open('sshaudit/sshaudit.py').read(), re.M).group(1)
|
||||
version = re.search(r'^VERSION\s*=\s*\'v(\d\.\d\.\d)\'', open('sshaudit/sshaudit.py').read(), re.M).group(1)
|
||||
print("\n\nPackaging ssh-audit v%s...\n\n" % version)
|
||||
|
||||
with open("sshaudit/README.md", "rb") as f:
|
||||
@ -13,20 +13,20 @@ with open("sshaudit/README.md", "rb") as f:
|
||||
|
||||
|
||||
setup(
|
||||
name = "ssh-audit",
|
||||
packages = ["sshaudit"],
|
||||
license = 'MIT',
|
||||
entry_points = {
|
||||
name="ssh-audit",
|
||||
packages=["sshaudit"],
|
||||
license='MIT',
|
||||
entry_points={
|
||||
"console_scripts": ['ssh-audit = sshaudit.sshaudit:main']
|
||||
},
|
||||
version = version,
|
||||
description = "An SSH server & client configuration security auditing tool",
|
||||
long_description = long_descr,
|
||||
long_description_content_type = "text/markdown",
|
||||
author = "Joe Testa",
|
||||
author_email = "jtesta@positronsecurity.com",
|
||||
url = "https://github.com/jtesta/ssh-audit",
|
||||
classifiers = [
|
||||
version=version,
|
||||
description="An SSH server & client configuration security auditing tool",
|
||||
long_description=long_descr,
|
||||
long_description_content_type="text/markdown",
|
||||
author="Joe Testa",
|
||||
author_email="jtesta@positronsecurity.com",
|
||||
url="https://github.com/jtesta/ssh-audit",
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Intended Audience :: System Administrators",
|
||||
|
210
ssh-audit.py
210
ssh-audit.py
@ -25,11 +25,24 @@
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
import base64, binascii, errno, hashlib, getopt, io, os, random, re, select, socket, struct, sys, json
|
||||
import base64
|
||||
import binascii
|
||||
import errno
|
||||
import getopt
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import select
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
VERSION = 'v2.2.1-dev'
|
||||
SSH_HEADER = 'SSH-{0}-OpenSSH_8.0' # SSH software to impersonate
|
||||
SSH_HEADER = 'SSH-{0}-OpenSSH_8.0' # SSH software to impersonate
|
||||
|
||||
if sys.version_info.major < 3:
|
||||
print("\n!!!! NOTE: Python 2 is being considered for deprecation. If you have a good reason to need continued Python 2 support, please e-mail jtesta@positronsecurity.com with your rationale.\n\n")
|
||||
@ -41,7 +54,7 @@ if sys.version_info >= (3,): # pragma: nocover
|
||||
else: # pragma: nocover
|
||||
import StringIO as _StringIO # pylint: disable=import-error
|
||||
StringIO = BytesIO = _StringIO.StringIO
|
||||
text_type = unicode # pylint: disable=undefined-variable
|
||||
text_type = unicode # pylint: disable=undefined-variable # noqa: F821
|
||||
binary_type = str
|
||||
try: # pragma: nocover
|
||||
# pylint: disable=unused-import
|
||||
@ -99,7 +112,7 @@ class AuditConf(object):
|
||||
self.ipv4 = False
|
||||
self.ipv6 = False
|
||||
self.timeout = 5.0
|
||||
self.timeout_set = False # Set to True when the user explicitly sets it.
|
||||
self.timeout_set = False # Set to True when the user explicitly sets it.
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
# type: (str, Union[str, int, bool, Sequence[int]]) -> None
|
||||
@ -190,9 +203,9 @@ class AuditConf(object):
|
||||
elif o in ('-t', '--timeout'):
|
||||
aconf.timeout = float(a)
|
||||
aconf.timeout_set = True
|
||||
if len(args) == 0 and aconf.client_audit == False:
|
||||
if len(args) == 0 and aconf.client_audit is False:
|
||||
usage_cb()
|
||||
if aconf.client_audit == False:
|
||||
if aconf.client_audit is False:
|
||||
if oport is not None:
|
||||
host = args[0]
|
||||
else:
|
||||
@ -310,7 +323,7 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
WARN_OPENSSH74_UNSAFE = 'disabled (in client) since OpenSSH 7.4, unsafe algorithm'
|
||||
WARN_OPENSSH72_LEGACY = 'disabled (in client) since OpenSSH 7.2, legacy algorithm'
|
||||
FAIL_OPENSSH70_LEGACY = 'removed since OpenSSH 7.0, legacy algorithm'
|
||||
FAIL_OPENSSH70_WEAK = 'removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm'
|
||||
FAIL_OPENSSH70_WEAK = 'removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm'
|
||||
FAIL_OPENSSH70_LOGJAM = 'disabled (in client) since OpenSSH 7.0, logjam attack'
|
||||
INFO_OPENSSH69_CHACHA = 'default cipher since OpenSSH 6.9.'
|
||||
FAIL_OPENSSH67_UNSAFE = 'removed (in server) since OpenSSH 6.7, unsafe algorithm'
|
||||
@ -319,21 +332,21 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
FAIL_DBEAR67_DISABLED = 'disabled since Dropbear SSH 2015.67'
|
||||
FAIL_DBEAR53_DISABLED = 'disabled since Dropbear SSH 0.53'
|
||||
FAIL_DEPRECATED_CIPHER = 'deprecated cipher'
|
||||
FAIL_WEAK_CIPHER = 'using weak cipher'
|
||||
FAIL_WEAK_ALGORITHM = 'using weak/obsolete algorithm'
|
||||
FAIL_PLAINTEXT = 'no encryption/integrity'
|
||||
FAIL_DEPRECATED_MAC = 'deprecated MAC'
|
||||
WARN_CURVES_WEAK = 'using weak elliptic curves'
|
||||
WARN_RNDSIG_KEY = 'using weak random number generator could reveal the key'
|
||||
WARN_MODULUS_SIZE = 'using small 1024-bit modulus'
|
||||
WARN_HASH_WEAK = 'using weak hashing algorithm'
|
||||
WARN_CIPHER_MODE = 'using weak cipher mode'
|
||||
WARN_BLOCK_SIZE = 'using small 64-bit block size'
|
||||
WARN_CIPHER_WEAK = 'using weak cipher'
|
||||
WARN_ENCRYPT_AND_MAC = 'using encrypt-and-MAC mode'
|
||||
WARN_TAG_SIZE = 'using small 64-bit tag size'
|
||||
WARN_TAG_SIZE_96 = 'using small 96-bit tag size'
|
||||
WARN_EXPERIMENTAL = 'using experimental algorithm'
|
||||
FAIL_WEAK_CIPHER = 'using weak cipher'
|
||||
FAIL_WEAK_ALGORITHM = 'using weak/obsolete algorithm'
|
||||
FAIL_PLAINTEXT = 'no encryption/integrity'
|
||||
FAIL_DEPRECATED_MAC = 'deprecated MAC'
|
||||
WARN_CURVES_WEAK = 'using weak elliptic curves'
|
||||
WARN_RNDSIG_KEY = 'using weak random number generator could reveal the key'
|
||||
WARN_MODULUS_SIZE = 'using small 1024-bit modulus'
|
||||
WARN_HASH_WEAK = 'using weak hashing algorithm'
|
||||
WARN_CIPHER_MODE = 'using weak cipher mode'
|
||||
WARN_BLOCK_SIZE = 'using small 64-bit block size'
|
||||
WARN_CIPHER_WEAK = 'using weak cipher'
|
||||
WARN_ENCRYPT_AND_MAC = 'using encrypt-and-MAC mode'
|
||||
WARN_TAG_SIZE = 'using small 64-bit tag size'
|
||||
WARN_TAG_SIZE_96 = 'using small 96-bit tag size'
|
||||
WARN_EXPERIMENTAL = 'using experimental algorithm'
|
||||
|
||||
ALGORITHMS = {
|
||||
# Format: 'algorithm_name': [['version_first_appeared_in'], [reason_for_failure1, reason_for_failure2, ...], [warning1, warning2, ...]]
|
||||
@ -378,7 +391,7 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
'ecdh-sha2-nistp384': [['5.7,d2013.62'], [WARN_CURVES_WEAK]],
|
||||
'ecdh-sha2-nistp521': [['5.7,d2013.62'], [WARN_CURVES_WEAK]],
|
||||
'ecdh-sha2-nistt571': [[], [WARN_CURVES_WEAK]],
|
||||
'ecdh-sha2-1.3.132.0.10': [[]], # ECDH over secp256k1 (i.e.: the Bitcoin curve)
|
||||
'ecdh-sha2-1.3.132.0.10': [[]], # ECDH over secp256k1 (i.e.: the Bitcoin curve)
|
||||
'curve25519-sha256@libssh.org': [['6.5,d2013.62,l10.6.0']],
|
||||
'curve25519-sha256': [['7.4,d2018.76']],
|
||||
'curve448-sha512': [[]],
|
||||
@ -386,8 +399,8 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
'rsa1024-sha1': [[], [], [WARN_MODULUS_SIZE, WARN_HASH_WEAK]],
|
||||
'rsa2048-sha256': [[]],
|
||||
'sntrup4591761x25519-sha512@tinyssh.org': [['8.0'], [], [WARN_EXPERIMENTAL]],
|
||||
'ext-info-c': [[]], # Extension negotiation (RFC 8308)
|
||||
'ext-info-s': [[]], # Extension negotiation (RFC 8308)
|
||||
'ext-info-c': [[]], # Extension negotiation (RFC 8308)
|
||||
'ext-info-s': [[]], # Extension negotiation (RFC 8308)
|
||||
},
|
||||
'key': {
|
||||
'ssh-rsa1': [[], [FAIL_WEAK_ALGORITHM]],
|
||||
@ -400,7 +413,7 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
'ecdsa-sha2-nistp256': [['5.7,d2013.62,l10.6.4'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
|
||||
'ecdsa-sha2-nistp384': [['5.7,d2013.62,l10.6.4'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
|
||||
'ecdsa-sha2-nistp521': [['5.7,d2013.62,l10.6.4'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
|
||||
'ecdsa-sha2-1.3.132.0.10': [[], [], [WARN_RNDSIG_KEY]], # ECDSA over secp256k1 (i.e.: the Bitcoin curve)
|
||||
'ecdsa-sha2-1.3.132.0.10': [[], [], [WARN_RNDSIG_KEY]], # ECDSA over secp256k1 (i.e.: the Bitcoin curve)
|
||||
'x509v3-sign-dss': [[], [FAIL_OPENSSH70_WEAK], [WARN_MODULUS_SIZE, WARN_RNDSIG_KEY]],
|
||||
'x509v3-sign-rsa': [[], [], [WARN_HASH_WEAK]],
|
||||
'x509v3-sign-rsa-sha256@ssh.com': [[]],
|
||||
@ -416,7 +429,7 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
'rsa-sha2-256-cert-v01@openssh.com': [['7.8']],
|
||||
'rsa-sha2-512-cert-v01@openssh.com': [['7.8']],
|
||||
'ssh-rsa-sha256@ssh.com': [[]],
|
||||
'ecdsa-sha2-1.3.132.0.10': [[], [], [WARN_RNDSIG_KEY]], # ECDSA over secp256k1 (i.e.: the Bitcoin curve)
|
||||
'ecdsa-sha2-1.3.132.0.10': [[], [], [WARN_RNDSIG_KEY]], # ECDSA over secp256k1 (i.e.: the Bitcoin curve)
|
||||
'sk-ecdsa-sha2-nistp256-cert-v01@openssh.com': [['8.2'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
|
||||
'sk-ecdsa-sha2-nistp256@openssh.com': [['8.2'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
|
||||
'sk-ssh-ed25519-cert-v01@openssh.com': [['8.2']],
|
||||
@ -508,20 +521,20 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
'umac-128@openssh.com': [['6.2'], [], [WARN_ENCRYPT_AND_MAC]],
|
||||
'hmac-sha1-etm@openssh.com': [['6.2'], [], [WARN_HASH_WEAK]],
|
||||
'hmac-sha1-96-etm@openssh.com': [['6.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_HASH_WEAK]],
|
||||
'hmac-sha2-256-96-etm@openssh.com': [[], [], [WARN_TAG_SIZE_96]], # Despite the @openssh.com tag, it doesn't appear that this was ever shipped with OpenSSH; it is only implemented in AsyncSSH (?).
|
||||
'hmac-sha2-512-96-etm@openssh.com': [[], [], [WARN_TAG_SIZE_96]], # Despite the @openssh.com tag, it doesn't appear that this was ever shipped with OpenSSH; it is only implemented in AsyncSSH (?).
|
||||
'hmac-sha2-256-96-etm@openssh.com': [[], [], [WARN_TAG_SIZE_96]], # Despite the @openssh.com tag, it doesn't appear that this was ever shipped with OpenSSH; it is only implemented in AsyncSSH (?).
|
||||
'hmac-sha2-512-96-etm@openssh.com': [[], [], [WARN_TAG_SIZE_96]], # Despite the @openssh.com tag, it doesn't appear that this was ever shipped with OpenSSH; it is only implemented in AsyncSSH (?).
|
||||
'hmac-sha2-256-etm@openssh.com': [['6.2']],
|
||||
'hmac-sha2-512-etm@openssh.com': [['6.2']],
|
||||
'hmac-md5-etm@openssh.com': [['6.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_HASH_WEAK]],
|
||||
'hmac-md5-96-etm@openssh.com': [['6.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_HASH_WEAK]],
|
||||
'hmac-ripemd160-etm@openssh.com': [['6.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY]],
|
||||
'umac-32@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]], # Despite having the @openssh.com suffix, this may never have shipped with OpenSSH (!).
|
||||
'umac-32@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]], # Despite having the @openssh.com suffix, this may never have shipped with OpenSSH (!).
|
||||
'umac-64-etm@openssh.com': [['6.2'], [], [WARN_TAG_SIZE]],
|
||||
'umac-96@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]], # Despite having the @openssh.com suffix, this may never have shipped with OpenSSH (!).
|
||||
'umac-96@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]], # Despite having the @openssh.com suffix, this may never have shipped with OpenSSH (!).
|
||||
'umac-128-etm@openssh.com': [['6.2']],
|
||||
'aes128-gcm': [[]],
|
||||
'aes256-gcm': [[]],
|
||||
'chacha20-poly1305@openssh.com': [[]], # Despite the @openssh.com tag, this was never shipped as a MAC in OpenSSH (only as a cipher); it is only implemented as a MAC in Syncplify.
|
||||
'chacha20-poly1305@openssh.com': [[]], # Despite the @openssh.com tag, this was never shipped as a MAC in OpenSSH (only as a cipher); it is only implemented as a MAC in Syncplify.
|
||||
}
|
||||
} # type: Dict[str, Dict[str, List[List[Optional[str]]]]]
|
||||
|
||||
@ -700,7 +713,7 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
'ecdh-sha2-nistp256': KexNISTP256,
|
||||
'ecdh-sha2-nistp384': KexNISTP384,
|
||||
'ecdh-sha2-nistp521': KexNISTP521,
|
||||
#'kexguess2@matt.ucc.asn.au': ???
|
||||
# 'kexguess2@matt.ucc.asn.au': ???
|
||||
}
|
||||
|
||||
# Pick the first kex algorithm that the server supports, which we
|
||||
@ -735,14 +748,14 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
# If the connection is closed, re-open it and get the kex again.
|
||||
if not s.is_connected():
|
||||
s.connect()
|
||||
unused = None # pylint: disable=unused-variable
|
||||
unused = None # pylint: disable=unused-variable
|
||||
unused, unused, err = s.get_banner()
|
||||
if err is not None:
|
||||
s.close()
|
||||
return
|
||||
|
||||
# Parse the server's initial KEX.
|
||||
packet_type = 0 # pylint: disable=unused-variable
|
||||
packet_type = 0 # pylint: disable=unused-variable
|
||||
packet_type, payload = s.read_packet()
|
||||
SSH2.Kex.parse(payload)
|
||||
|
||||
@ -798,7 +811,6 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
else:
|
||||
host_key_types[host_key_type]['parsed'] = True
|
||||
|
||||
|
||||
# Performs DH group exchanges to find what moduli are supported, and checks
|
||||
# their size.
|
||||
class GEXTest(object):
|
||||
@ -811,14 +823,14 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
return
|
||||
|
||||
s.connect()
|
||||
unused = None # pylint: disable=unused-variable
|
||||
unused = None # pylint: disable=unused-variable
|
||||
unused, unused, err = s.get_banner()
|
||||
if err is not None:
|
||||
s.close()
|
||||
return False
|
||||
|
||||
# Parse the server's initial KEX.
|
||||
packet_type = 0 # pylint: disable=unused-variable
|
||||
packet_type = 0 # pylint: disable=unused-variable
|
||||
packet_type, payload = s.read_packet(2)
|
||||
kex = SSH2.Kex.parse(payload)
|
||||
|
||||
@ -865,7 +877,7 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
# got here, doesn't mean the server is vulnerable...
|
||||
smallest_modulus = kex_group.get_dh_modulus_size()
|
||||
|
||||
except Exception as e: # pylint: disable=bare-except
|
||||
except Exception: # pylint: disable=bare-except
|
||||
pass
|
||||
finally:
|
||||
s.close()
|
||||
@ -887,9 +899,9 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
kex_group.send_init_gex(s, bits, bits, bits)
|
||||
kex_group.recv_reply(s, False)
|
||||
smallest_modulus = kex_group.get_dh_modulus_size()
|
||||
except Exception as e: # pylint: disable=bare-except
|
||||
#import traceback
|
||||
#print(traceback.format_exc())
|
||||
except Exception: # pylint: disable=bare-except
|
||||
# import traceback
|
||||
# print(traceback.format_exc())
|
||||
pass
|
||||
finally:
|
||||
# The server is in a state that is not re-testable,
|
||||
@ -897,7 +909,6 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
# connection.
|
||||
s.close()
|
||||
|
||||
|
||||
if smallest_modulus > 0:
|
||||
kex.set_dh_modulus_size(gex_alg, smallest_modulus)
|
||||
|
||||
@ -919,6 +930,7 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
if reconnect_failed:
|
||||
break
|
||||
|
||||
|
||||
class SSH1(object):
|
||||
class CRC32(object):
|
||||
def __init__(self):
|
||||
@ -935,8 +947,8 @@ class SSH1(object):
|
||||
|
||||
def calc(self, v):
|
||||
# type: (binary_type) -> int
|
||||
crc, l = 0, len(v)
|
||||
for i in range(l):
|
||||
crc, length = 0, len(v)
|
||||
for i in range(length):
|
||||
n = ord(v[i:i + 1])
|
||||
n = n ^ (crc & 0xff)
|
||||
crc = (crc >> 8) ^ self._table[n]
|
||||
@ -955,11 +967,11 @@ class SSH1(object):
|
||||
|
||||
class KexDB(object): # pylint: disable=too-few-public-methods
|
||||
# pylint: disable=bad-whitespace
|
||||
FAIL_PLAINTEXT = 'no encryption/integrity'
|
||||
FAIL_PLAINTEXT = 'no encryption/integrity'
|
||||
FAIL_OPENSSH37_REMOVE = 'removed since OpenSSH 3.7'
|
||||
FAIL_NA_BROKEN = 'not implemented in OpenSSH, broken algorithm'
|
||||
FAIL_NA_UNSAFE = 'not implemented in OpenSSH (server), unsafe algorithm'
|
||||
TEXT_CIPHER_IDEA = 'cipher used by commercial SSH'
|
||||
FAIL_NA_BROKEN = 'not implemented in OpenSSH, broken algorithm'
|
||||
FAIL_NA_UNSAFE = 'not implemented in OpenSSH (server), unsafe algorithm'
|
||||
TEXT_CIPHER_IDEA = 'cipher used by commercial SSH'
|
||||
|
||||
ALGORITHMS = {
|
||||
'key': {
|
||||
@ -1189,6 +1201,7 @@ class ReadBuf(object):
|
||||
self._len = 0
|
||||
super(ReadBuf, self).reset()
|
||||
|
||||
|
||||
class WriteBuf(object):
|
||||
def __init__(self, data=None):
|
||||
# type: (Optional[binary_type]) -> None
|
||||
@ -1285,15 +1298,15 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
class Protocol(object): # pylint: disable=too-few-public-methods
|
||||
# pylint: disable=bad-whitespace
|
||||
SMSG_PUBLIC_KEY = 2
|
||||
MSG_DEBUG = 4
|
||||
MSG_KEXINIT = 20
|
||||
MSG_NEWKEYS = 21
|
||||
MSG_KEXDH_INIT = 30
|
||||
MSG_DEBUG = 4
|
||||
MSG_KEXINIT = 20
|
||||
MSG_NEWKEYS = 21
|
||||
MSG_KEXDH_INIT = 30
|
||||
MSG_KEXDH_REPLY = 31
|
||||
MSG_KEXDH_GEX_REQUEST = 34
|
||||
MSG_KEXDH_GEX_GROUP = 31
|
||||
MSG_KEXDH_GEX_INIT = 32
|
||||
MSG_KEXDH_GEX_REPLY = 33
|
||||
MSG_KEXDH_GEX_GROUP = 31
|
||||
MSG_KEXDH_GEX_INIT = 32
|
||||
MSG_KEXDH_GEX_REPLY = 33
|
||||
|
||||
class Product(object): # pylint: disable=too-few-public-methods
|
||||
OpenSSH = 'OpenSSH'
|
||||
@ -1609,7 +1622,7 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
|
||||
def __getitem__(self, product):
|
||||
# type: (str) -> Sequence[Optional[str]]
|
||||
return tuple(self.__storage.get(product, [None]*4))
|
||||
return tuple(self.__storage.get(product, [None] * 4))
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
@ -1640,7 +1653,7 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
ssh_versions[ssh_prod] = ssh_ver
|
||||
for ssh_product, ssh_version in ssh_versions.items():
|
||||
if ssh_product not in self.__storage:
|
||||
self.__storage[ssh_product] = [None]*4
|
||||
self.__storage[ssh_product] = [None] * 4
|
||||
prev = self[ssh_product][pos]
|
||||
if (prev is None or
|
||||
(prev < ssh_version and pos % 2 == 0) or
|
||||
@ -1783,19 +1796,18 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
if software is not None:
|
||||
if software.product not in vproducts:
|
||||
unknown_software = True
|
||||
#
|
||||
# The code below is commented out because it would try to guess what the server is,
|
||||
# usually resulting in wild & incorrect recommendations.
|
||||
#
|
||||
# if software is None:
|
||||
# ssh_timeframe = self.get_ssh_timeframe(for_server)
|
||||
# for product in vproducts:
|
||||
# if product not in ssh_timeframe:
|
||||
# continue
|
||||
# version = ssh_timeframe.get_from(product, for_server)
|
||||
# if version is not None:
|
||||
# software = SSH.Software(None, product, version, None, None)
|
||||
# break
|
||||
|
||||
# The code below is commented out because it would try to guess what the server is,
|
||||
# usually resulting in wild & incorrect recommendations.
|
||||
# if software is None:
|
||||
# ssh_timeframe = self.get_ssh_timeframe(for_server)
|
||||
# for product in vproducts:
|
||||
# if product not in ssh_timeframe:
|
||||
# continue
|
||||
# version = ssh_timeframe.get_from(product, for_server)
|
||||
# if version is not None:
|
||||
# software = SSH.Software(None, product, version, None, None)
|
||||
# break
|
||||
rec = {} # type: Dict[int, Dict[str, Dict[str, Dict[str, int]]]]
|
||||
if software is None:
|
||||
unknown_software = True
|
||||
@ -2057,7 +2069,6 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
self.client_host = None
|
||||
self.client_port = None
|
||||
|
||||
|
||||
def _resolve(self, ipvo):
|
||||
# type: (Sequence[int]) -> Iterable[Tuple[int, Tuple[Any, ...]]]
|
||||
ipvo = tuple([x for x in utils.unique_seq(ipvo) if x in (4, 6)])
|
||||
@ -2081,7 +2092,6 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
out.fail('[exception] {0}'.format(e))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Listens on a server socket and accepts one connection (used for
|
||||
# auditing client connections).
|
||||
def listen_and_accept(self):
|
||||
@ -2093,7 +2103,7 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
s.bind(('0.0.0.0', self.__port))
|
||||
s.listen()
|
||||
self.__sock_map[s.fileno()] = s
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
print("Warning: failed to listen on any IPv4 interfaces.")
|
||||
pass
|
||||
|
||||
@ -2105,7 +2115,7 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
s.bind(('::', self.__port))
|
||||
s.listen()
|
||||
self.__sock_map[s.fileno()] = s
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
print("Warning: failed to listen on any IPv6 interfaces.")
|
||||
pass
|
||||
|
||||
@ -2139,7 +2149,6 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
c.settimeout(self.__timeout)
|
||||
self.__sock = c
|
||||
|
||||
|
||||
def connect(self):
|
||||
# type: () -> None
|
||||
err = None
|
||||
@ -2169,10 +2178,10 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
banner = SSH_HEADER.format('1.5' if sshv == 1 else '2.0')
|
||||
if self.__state < self.SM_BANNER_SENT:
|
||||
self.send_banner(banner)
|
||||
# rto = self.__sock.gettimeout()
|
||||
# self.__sock.settimeout(0.7)
|
||||
# rto = self.__sock.gettimeout()
|
||||
# self.__sock.settimeout(0.7)
|
||||
s, e = self.recv()
|
||||
# self.__sock.settimeout(rto)
|
||||
# self.__sock.settimeout(rto)
|
||||
if s < 0:
|
||||
return self.__banner, self.__header, e
|
||||
e = None
|
||||
@ -2352,12 +2361,11 @@ class KexDH(object): # pragma: nocover
|
||||
self.__hostkey_type = None
|
||||
self.__hostkey_e = 0
|
||||
self.__hostkey_n = 0
|
||||
self.__hostkey_n_len = 0 # Length of the host key modulus.
|
||||
self.__ca_n_len = 0 # Length of the CA key modulus (if hostkey is a cert).
|
||||
self.__hostkey_n_len = 0 # Length of the host key modulus.
|
||||
self.__ca_n_len = 0 # Length of the CA key modulus (if hostkey is a cert).
|
||||
self.__f = 0
|
||||
self.__h_sig = 0
|
||||
|
||||
|
||||
def set_params(self, g, p):
|
||||
self.__g = g
|
||||
self.__p = p
|
||||
@ -2365,7 +2373,6 @@ class KexDH(object): # pragma: nocover
|
||||
self.__x = 0
|
||||
self.__e = 0
|
||||
|
||||
|
||||
def send_init(self, s, init_msg=SSH.Protocol.MSG_KEXDH_INIT):
|
||||
# type: (SSH.Socket) -> None
|
||||
r = random.SystemRandom()
|
||||
@ -2395,17 +2402,16 @@ class KexDH(object): # pragma: nocover
|
||||
return None
|
||||
|
||||
hostkey_len = f_len = h_sig_len = 0 # pylint: disable=unused-variable
|
||||
hostkey_type_len = hostkey_e_len = 0 # pylint: disable=unused-variable
|
||||
key_id_len = principles_len = 0 # pylint: disable=unused-variable
|
||||
critical_options_len = extensions_len = 0 # pylint: disable=unused-variable
|
||||
nonce_len = ca_key_len = ca_key_type_len = 0 # pylint: disable=unused-variable
|
||||
hostkey_type_len = hostkey_e_len = 0 # pylint: disable=unused-variable
|
||||
key_id_len = principles_len = 0 # pylint: disable=unused-variable
|
||||
critical_options_len = extensions_len = 0 # pylint: disable=unused-variable
|
||||
nonce_len = ca_key_len = ca_key_type_len = 0 # pylint: disable=unused-variable
|
||||
ca_key_len = ca_key_type_len = ca_key_e_len = 0 # pylint: disable=unused-variable
|
||||
|
||||
key_id = principles = None # pylint: disable=unused-variable
|
||||
critical_options = extensions = None # pylint: disable=unused-variable
|
||||
valid_after = valid_before = None # pylint: disable=unused-variable
|
||||
key_id = principles = None # pylint: disable=unused-variable
|
||||
critical_options = extensions = None # pylint: disable=unused-variable
|
||||
nonce = ca_key = ca_key_type = None # pylint: disable=unused-variable
|
||||
ca_key_e = ca_key_n = None # pylint: disable=unused-variable
|
||||
ca_key_e = ca_key_n = None # pylint: disable=unused-variable
|
||||
|
||||
# Get the host key blob, F, and signature.
|
||||
ptr = 0
|
||||
@ -2455,12 +2461,10 @@ class KexDH(object): # pragma: nocover
|
||||
# The principles, which are... I don't know what.
|
||||
principles, principles_len, ptr = KexDH.__get_bytes(hostkey, ptr)
|
||||
|
||||
# The timestamp that this certificate is valid after.
|
||||
valid_after = hostkey[ptr:ptr + 8]
|
||||
# Skip over the timestamp that this certificate is valid after.
|
||||
ptr += 8
|
||||
|
||||
# The timestamp that this certificate is valid before.
|
||||
valid_before = hostkey[ptr:ptr + 8]
|
||||
# Skip over the timestamp that this certificate is valid before.
|
||||
ptr += 8
|
||||
|
||||
# TODO: validate the principles, and time range.
|
||||
@ -2895,7 +2899,7 @@ def output_fingerprints(algs, sha256=True):
|
||||
if algs.ssh1kex is not None:
|
||||
name = 'ssh-rsa1'
|
||||
fp = SSH.Fingerprint(algs.ssh1kex.host_key_fingerprint_data)
|
||||
#bits = algs.ssh1kex.host_key_bits
|
||||
# bits = algs.ssh1kex.host_key_bits
|
||||
fps.append((name, fp))
|
||||
if algs.ssh2kex is not None:
|
||||
host_keys = algs.ssh2kex.host_keys()
|
||||
@ -2917,8 +2921,8 @@ def output_fingerprints(algs, sha256=True):
|
||||
for fpp in fps:
|
||||
name, fp = fpp
|
||||
fpo = fp.sha256 if sha256 else fp.md5
|
||||
#p = '' if out.batch else ' ' * (padlen - len(name))
|
||||
#out.good('(fin) {0}{1} -- {2} {3}'.format(name, p, bits, fpo))
|
||||
# p = '' if out.batch else ' ' * (padlen - len(name))
|
||||
# out.good('(fin) {0}{1} -- {2} {3}'.format(name, p, bits, fpo))
|
||||
out.good('(fin) {0}: {1}'.format(name, fpo))
|
||||
if len(obuf) > 0:
|
||||
out.head('# fingerprints')
|
||||
@ -2994,7 +2998,7 @@ def output_recommendations(algs, software, padlen=0):
|
||||
else:
|
||||
title = ''
|
||||
out.head('# algorithm recommendations {0}'.format(title))
|
||||
obuf.flush(True) # Sort the output so that it is always stable (needed for repeatable testing).
|
||||
obuf.flush(True) # Sort the output so that it is always stable (needed for repeatable testing).
|
||||
out.sep()
|
||||
return ret
|
||||
|
||||
@ -3018,7 +3022,7 @@ def output_info(algs, software, client_audit, any_problems, padlen=0):
|
||||
|
||||
def output(banner, header, client_host=None, kex=None, pkm=None):
|
||||
# type: (Optional[SSH.Banner], List[text_type], Optional[SSH2.Kex], Optional[SSH1.PublicKeyMessage]) -> None
|
||||
client_audit = (client_host != None) # If set, this is a client audit.
|
||||
client_audit = client_host is not None # If set, this is a client audit.
|
||||
sshv = 1 if pkm is not None else 2
|
||||
algs = SSH.Algorithms(pkm, kex)
|
||||
with OutputBuffer() as obuf:
|
||||
@ -3077,11 +3081,11 @@ def output(banner, header, client_host=None, kex=None, pkm=None):
|
||||
perfect_config = output_recommendations(algs, software, maxlen)
|
||||
output_info(algs, software, client_audit, not perfect_config)
|
||||
|
||||
|
||||
# If we encountered any unknown algorithms, ask the user to report them.
|
||||
if len(unknown_algorithms) > 0:
|
||||
out.warn("\n\n!!! WARNING: unknown algorithm(s) found!: %s. Please email the full output above to the maintainer (jtesta@positronsecurity.com), or create a Github issue at <https://github.com/jtesta/ssh-audit/issues>.\n" % ','.join(unknown_algorithms))
|
||||
|
||||
|
||||
class Utils(object):
|
||||
@classmethod
|
||||
def _type_err(cls, v, target):
|
||||
@ -3204,6 +3208,7 @@ class Utils(object):
|
||||
except: # pylint: disable=bare-except
|
||||
return -1.0
|
||||
|
||||
|
||||
def build_struct(banner, kex=None, pkm=None, client_host=None):
|
||||
res = {
|
||||
"banner": {
|
||||
@ -3281,6 +3286,7 @@ def build_struct(banner, kex=None, pkm=None, client_host=None):
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def audit(aconf, sshv=None):
|
||||
# type: (AuditConf, Optional[int]) -> None
|
||||
out.batch = aconf.batch
|
||||
@ -3350,9 +3356,11 @@ def audit(aconf, sshv=None):
|
||||
utils = Utils()
|
||||
out = Output()
|
||||
|
||||
|
||||
def main():
|
||||
conf = AuditConf.from_cmdline(sys.argv[1:], usage)
|
||||
audit(conf)
|
||||
|
||||
|
||||
if __name__ == '__main__': # pragma: nocover
|
||||
main()
|
||||
|
@ -52,7 +52,6 @@ class TestErrors(object):
|
||||
assert 'timed out' in lines[-1]
|
||||
|
||||
def test_recv_empty(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
|
@ -25,7 +25,7 @@ class TestResolve(object):
|
||||
conf = self._conf()
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
list(s._resolve(conf.ipvo))
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 1
|
||||
assert 'hostname nor servname provided' in lines[-1]
|
||||
@ -40,7 +40,6 @@ class TestResolve(object):
|
||||
assert len(r) == 0
|
||||
|
||||
def test_resolve_ipv4(self, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
conf = self._conf()
|
||||
conf.ipv4 = True
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
@ -49,7 +48,6 @@ class TestResolve(object):
|
||||
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||
|
||||
def test_resolve_ipv6(self, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
conf.ipv6 = True
|
||||
@ -58,7 +56,6 @@ class TestResolve(object):
|
||||
assert r[0] == (socket.AF_INET6, ('::1', 22))
|
||||
|
||||
def test_resolve_ipv46_both(self, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
@ -67,7 +64,6 @@ class TestResolve(object):
|
||||
assert r[1] == (socket.AF_INET6, ('::1', 22))
|
||||
|
||||
def test_resolve_ipv46_order(self, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
conf.ipv4 = True
|
||||
|
@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import socket
|
||||
import pytest
|
||||
|
||||
|
||||
@ -12,17 +11,17 @@ class TestSocket(object):
|
||||
|
||||
def test_invalid_host(self, virtual_socket):
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket(None, 22)
|
||||
self.ssh.Socket(None, 22)
|
||||
|
||||
def test_invalid_port(self, virtual_socket):
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket('localhost', 'abc')
|
||||
self.ssh.Socket('localhost', 'abc')
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket('localhost', -1)
|
||||
self.ssh.Socket('localhost', -1)
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket('localhost', 0)
|
||||
self.ssh.Socket('localhost', 0)
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket('localhost', 65536)
|
||||
self.ssh.Socket('localhost', 65536)
|
||||
|
||||
def test_not_connected_socket(self, virtual_socket):
|
||||
sock = self.ssh.Socket('localhost', 22)
|
||||
|
@ -95,10 +95,10 @@ class TestSSH1(object):
|
||||
skey, hkey = self._server_key(), self._host_key()
|
||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
|
||||
self._assert_pkm_fields(pkm, skey, hkey)
|
||||
for skey2 in ([], [0], [0,1], [0,1,2,3]):
|
||||
for skey2 in ([], [0], [0, 1], [0, 1, 2, 3]):
|
||||
with pytest.raises(ValueError):
|
||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey2, hkey, pflags, cmask, amask)
|
||||
for hkey2 in ([], [0], [0,1], [0,1,2,3]):
|
||||
for hkey2 in ([], [0], [0, 1], [0, 1, 2, 3]):
|
||||
with pytest.raises(ValueError):
|
||||
print(hkey2)
|
||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey2, pflags, cmask, amask)
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import struct, os
|
||||
import os
|
||||
import struct
|
||||
import pytest
|
||||
|
||||
|
||||
|
@ -105,15 +105,15 @@ class TestVersionCompare(object):
|
||||
versions.append('2015.{0}'.format(i))
|
||||
for i in range(72, 75):
|
||||
versions.append('2016.{0}'.format(i))
|
||||
l = len(versions)
|
||||
for i in range(l):
|
||||
length = len(versions)
|
||||
for i in range(length):
|
||||
v = versions[i]
|
||||
s = self.get_dropbear_software(v)
|
||||
assert s.compare_version(v) == 0
|
||||
if i - 1 >= 0:
|
||||
vbefore = versions[i - 1]
|
||||
assert s.compare_version(vbefore) > 0
|
||||
if i + 1 < l:
|
||||
if i + 1 < length:
|
||||
vnext = versions[i + 1]
|
||||
assert s.compare_version(vnext) < 0
|
||||
|
||||
@ -164,15 +164,15 @@ class TestVersionCompare(object):
|
||||
versions.append('6.{0}'.format(i))
|
||||
for i in range(0, 4):
|
||||
versions.append('7.{0}'.format(i))
|
||||
l = len(versions)
|
||||
for i in range(l):
|
||||
length = len(versions)
|
||||
for i in range(length):
|
||||
v = versions[i]
|
||||
s = self.get_openssh_software(v)
|
||||
assert s.compare_version(v) == 0
|
||||
if i - 1 >= 0:
|
||||
vbefore = versions[i - 1]
|
||||
assert s.compare_version(vbefore) > 0
|
||||
if i + 1 < l:
|
||||
if i + 1 < length:
|
||||
vnext = versions[i + 1]
|
||||
assert s.compare_version(vnext) < 0
|
||||
|
||||
@ -202,14 +202,14 @@ class TestVersionCompare(object):
|
||||
versions.append('0.6.{0}'.format(i))
|
||||
for i in range(0, 5):
|
||||
versions.append('0.7.{0}'.format(i))
|
||||
l = len(versions)
|
||||
for i in range(l):
|
||||
length = len(versions)
|
||||
for i in range(length):
|
||||
v = versions[i]
|
||||
s = self.get_libssh_software(v)
|
||||
assert s.compare_version(v) == 0
|
||||
if i - 1 >= 0:
|
||||
vbefore = versions[i - 1]
|
||||
assert s.compare_version(vbefore) > 0
|
||||
if i + 1 < l:
|
||||
if i + 1 < length:
|
||||
vnext = versions[i + 1]
|
||||
assert s.compare_version(vnext) < 0
|
||||
|
51
tox.ini
51
tox.ini
@ -80,7 +80,7 @@ commands =
|
||||
deps =
|
||||
flake8
|
||||
commands =
|
||||
flake8 {posargs:{env:SSHAUDIT}}
|
||||
flake8 {posargs:{env:SSHAUDIT} {toxinidir}/packages/setup.py {toxinidir}/test} --statistics
|
||||
|
||||
[testenv:vulture]
|
||||
deps =
|
||||
@ -137,42 +137,13 @@ max-module-lines = 2500
|
||||
|
||||
[flake8]
|
||||
ignore =
|
||||
# indentation contains tabs
|
||||
W191,
|
||||
# blank line contains whitespace
|
||||
W293,
|
||||
# indentation contains mixed spaces and tabs
|
||||
E101,
|
||||
# multiple spaces before operator
|
||||
E221,
|
||||
# multiple spaces after operator
|
||||
E241,
|
||||
# multiple imports on one line
|
||||
E401,
|
||||
# line too long
|
||||
E501,
|
||||
# module imported but unused
|
||||
F401,
|
||||
# undefined name
|
||||
F821,
|
||||
# these exceptions should be handled one by one
|
||||
E117, # over-indented
|
||||
E126, # continuation line over-indented for hanging indent
|
||||
E128, # continuation line under-indented for visual indent
|
||||
E226, # missing whitespace around arithmetic operator
|
||||
E231, # missing whitespace after ','
|
||||
E251, # unexpected spaces around keyword / parameter equals
|
||||
E261, # at least two spaces before inline comment
|
||||
E265, # block comment should start with '# '
|
||||
E301, # expected 1 blank line, found 0
|
||||
E302, # expected 2 blank lines, found 1
|
||||
E303, # too many blank lines (2)
|
||||
E305, # expected 2 blank lines after class or function definition, found 1
|
||||
E711, # comparison to None should be 'if cond is not None:'
|
||||
E712, # comparison to False should be 'if cond is False:' or 'if not cond:'
|
||||
E722, # do not use bare 'except'
|
||||
E741, # ambiguous variable name 'l'
|
||||
F601, # dictionary key 'ecdsa-sha2-1.3.132.0.10' repeated with different values
|
||||
F841, # local variable 'e' is assigned to but never used
|
||||
W504, # line break after binary operator
|
||||
W605, # invalid escape sequence '\s'
|
||||
W191, # indentation contains tabs
|
||||
E101, # indentation contains mixed spaces and tabs
|
||||
E241, # multiple spaces after operator; should be kept for tabular data
|
||||
E501, # line too long
|
||||
E117, # over-indented
|
||||
E126, # continuation line over-indented for hanging indent
|
||||
E128, # continuation line under-indented for visual indent
|
||||
E722, # do not use bare 'except'
|
||||
F601, # dictionary key 'ecdsa-sha2-1.3.132.0.10' repeated with different values
|
||||
W504, # line break after binary operator; this (or W503) has to stay
|
||||
|
Loading…
Reference in New Issue
Block a user