mirror of
				https://github.com/jtesta/ssh-audit.git
				synced 2025-11-04 11:12:15 +01:00 
			
		
		
		
	Merge develop branch.
This commit is contained in:
		@@ -8,7 +8,11 @@ python:
 | 
				
			|||||||
  - pypy
 | 
					  - pypy
 | 
				
			||||||
  - pypy3
 | 
					  - pypy3
 | 
				
			||||||
install:
 | 
					install:
 | 
				
			||||||
  - pip install pytest
 | 
					  - pip install --upgrade pytest
 | 
				
			||||||
 | 
					  - pip install --upgrade pytest-cov
 | 
				
			||||||
 | 
					  - pip install --upgrade coveralls
 | 
				
			||||||
script:
 | 
					script:
 | 
				
			||||||
  - py.test -v test
 | 
					  - py.test --cov-report= --cov=ssh-audit -v test
 | 
				
			||||||
 | 
					after_success:
 | 
				
			||||||
 | 
					  - coveralls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							@@ -7,10 +7,11 @@
 | 
				
			|||||||
- grab banner, recognize device or software and operating system, detect compression;
 | 
					- grab banner, recognize device or software and operating system, detect compression;
 | 
				
			||||||
- gather key-exchange, host-key, encryption and message authentication code algorithms;
 | 
					- gather key-exchange, host-key, encryption and message authentication code algorithms;
 | 
				
			||||||
- output algorithm information (available since, removed/disabled, unsafe/weak/legacy, etc);
 | 
					- output algorithm information (available since, removed/disabled, unsafe/weak/legacy, etc);
 | 
				
			||||||
 | 
					- output algorithm recommendations (append or remove based on recognized software version);
 | 
				
			||||||
- output security information (related issues, assigned CVE list, etc);
 | 
					- output security information (related issues, assigned CVE list, etc);
 | 
				
			||||||
- analyze SSH version compatibility based on algorithm information;
 | 
					- analyze SSH version compatibility based on algorithm information;
 | 
				
			||||||
- historical information from OpenSSH and Dropbear SSH;
 | 
					- historical information from OpenSSH, Dropbear SSH and libssh;
 | 
				
			||||||
- no dependencies, compatible with Python2 and Python3;
 | 
					- no dependencies, compatible with Python 2.6+, Python 3.x and PyPy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Usage
 | 
					## Usage
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
@@ -28,9 +29,17 @@ usage: ssh-audit.py [-bnv] [-l <level>] <host[:port]>
 | 
				
			|||||||
* verbose flag `-v` will prefix each line with section type and algorithm name.  
 | 
					* verbose flag `-v` will prefix each line with section type and algorithm name.  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### example
 | 
					### example
 | 
				
			||||||
  
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## ChangeLog
 | 
					## ChangeLog
 | 
				
			||||||
 | 
					### v1.6.0 (2016-10-14)
 | 
				
			||||||
 | 
					 - implement algorithm recommendations section (based on recognized software)
 | 
				
			||||||
 | 
					 - implement full libssh support (version history, algorithms, security, etc)
 | 
				
			||||||
 | 
					 - fix SSH-1.99 banner recognition and version comparison functionality
 | 
				
			||||||
 | 
					 - do not output empty algorithms (happens for misconfigured servers)
 | 
				
			||||||
 | 
					 - make consistent output for Python 3.x versions
 | 
				
			||||||
 | 
					 - add a lot more tests (conf, banner, software, SSH1/SSH2, output, etc)
 | 
				
			||||||
 | 
					 - use Travis CI to test for multiple Python versions (2.6-3.5, pypy, pypy3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### v1.5.0 (2016-09-20)
 | 
					### v1.5.0 (2016-09-20)
 | 
				
			||||||
 - create security section for related security information
 | 
					 - create security section for related security information
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										642
									
								
								ssh-audit.py
									
									
									
									
									
								
							
							
						
						
									
										642
									
								
								ssh-audit.py
									
									
									
									
									
								
							@@ -26,13 +26,12 @@
 | 
				
			|||||||
from __future__ import print_function
 | 
					from __future__ import print_function
 | 
				
			||||||
import os, io, sys, socket, struct, random, errno, getopt, re, hashlib, base64
 | 
					import os, io, sys, socket, struct, random, errno, getopt, re, hashlib, base64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VERSION = 'v1.5.0'
 | 
					VERSION = 'v1.5.1.dev'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def usage(err=None):
 | 
					def usage(err=None):
 | 
				
			||||||
 | 
						out = Output()
 | 
				
			||||||
	p = os.path.basename(sys.argv[0])
 | 
						p = os.path.basename(sys.argv[0])
 | 
				
			||||||
	out.batch = False
 | 
					 | 
				
			||||||
	out.minlevel = 'info'
 | 
					 | 
				
			||||||
	out.head('# {0} {1}, moo@arthepsy.eu'.format(p, VERSION))
 | 
						out.head('# {0} {1}, moo@arthepsy.eu'.format(p, VERSION))
 | 
				
			||||||
	if err is not None:
 | 
						if err is not None:
 | 
				
			||||||
		out.fail('\n' + err)
 | 
							out.fail('\n' + err)
 | 
				
			||||||
@@ -49,43 +48,78 @@ def usage(err=None):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AuditConf(object):
 | 
					class AuditConf(object):
 | 
				
			||||||
	def __init__(self):
 | 
						def __init__(self, host=None, port=22):
 | 
				
			||||||
		self.__host = None
 | 
							self.host = host
 | 
				
			||||||
		self.__port = 22
 | 
							self.port = port
 | 
				
			||||||
		self.__ssh1 = False
 | 
							self.ssh1 = True
 | 
				
			||||||
		self.__ssh2 = False
 | 
							self.ssh2 = True
 | 
				
			||||||
 | 
							self.batch = False
 | 
				
			||||||
 | 
							self.colors = True
 | 
				
			||||||
 | 
							self.verbose = False
 | 
				
			||||||
 | 
							self.minlevel = 'info'
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	@property
 | 
						def __setattr__(self, name, value):
 | 
				
			||||||
	def host(self):
 | 
							valid = False
 | 
				
			||||||
		return self.__host
 | 
							if name in ['ssh1', 'ssh2', 'batch', 'colors', 'verbose']:
 | 
				
			||||||
 | 
								valid, value = True, True if value else False
 | 
				
			||||||
 | 
							elif name == 'port':
 | 
				
			||||||
 | 
								valid, port = True, utils.parse_int(value)
 | 
				
			||||||
 | 
								if port < 1 or port > 65535:
 | 
				
			||||||
 | 
									raise ValueError('invalid port: {0}'.format(value))
 | 
				
			||||||
 | 
								value = port
 | 
				
			||||||
 | 
							elif name in ['minlevel']:
 | 
				
			||||||
 | 
								if value not in ('info', 'warn', 'fail'):
 | 
				
			||||||
 | 
									raise ValueError('invalid level: {0}'.format(value))
 | 
				
			||||||
 | 
								valid = True
 | 
				
			||||||
 | 
							elif name == 'host':
 | 
				
			||||||
 | 
								valid = True
 | 
				
			||||||
 | 
							if valid:
 | 
				
			||||||
 | 
								object.__setattr__(self, name, value)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	@host.setter
 | 
						@classmethod
 | 
				
			||||||
	def host(self, v):
 | 
						def from_cmdline(cls, args, usage_cb):
 | 
				
			||||||
		self.__host = v
 | 
							conf = cls()
 | 
				
			||||||
	
 | 
							try:
 | 
				
			||||||
	@property
 | 
								sopts = 'h12bnvl:'
 | 
				
			||||||
	def port(self):
 | 
								lopts = ['help', 'ssh1', 'ssh2', 'batch',
 | 
				
			||||||
		return self.__port
 | 
								         'no-colors', 'verbose', 'level=']
 | 
				
			||||||
	
 | 
								opts, args = getopt.getopt(args, sopts, lopts)
 | 
				
			||||||
	@port.setter
 | 
							except getopt.GetoptError as err:
 | 
				
			||||||
	def port(self, v):
 | 
								usage_cb(str(err))
 | 
				
			||||||
		self.__port = v
 | 
							conf.ssh1, conf.ssh2 = False, False
 | 
				
			||||||
	
 | 
							for o, a in opts:
 | 
				
			||||||
	@property
 | 
								if o in ('-h', '--help'):
 | 
				
			||||||
	def ssh1(self):
 | 
									usage_cb()
 | 
				
			||||||
		return self.__ssh1
 | 
								elif o in ('-1', '--ssh1'):
 | 
				
			||||||
	
 | 
									conf.ssh1 = True
 | 
				
			||||||
	@ssh1.setter
 | 
								elif o in ('-2', '--ssh2'):
 | 
				
			||||||
	def ssh1(self, v):
 | 
									conf.ssh2 = True
 | 
				
			||||||
		self.__ssh1 = v
 | 
								elif o in ('-b', '--batch'):
 | 
				
			||||||
	
 | 
									conf.batch = True
 | 
				
			||||||
	@property
 | 
									conf.verbose = True
 | 
				
			||||||
	def ssh2(self):
 | 
								elif o in ('-n', '--no-colors'):
 | 
				
			||||||
		return self.__ssh2
 | 
									conf.colors = False
 | 
				
			||||||
	
 | 
								elif o in ('-v', '--verbose'):
 | 
				
			||||||
	@ssh2.setter
 | 
									conf.verbose = True
 | 
				
			||||||
	def ssh2(self, v):
 | 
								elif o in ('-l', '--level'):
 | 
				
			||||||
		self.__ssh2 = v
 | 
									if a not in ('info', 'warn', 'fail'):
 | 
				
			||||||
 | 
										usage_cb('level {0} is not valid'.format(a))
 | 
				
			||||||
 | 
									conf.minlevel = a
 | 
				
			||||||
 | 
							if len(args) == 0:
 | 
				
			||||||
 | 
								usage_cb()
 | 
				
			||||||
 | 
							s = args[0].split(':')
 | 
				
			||||||
 | 
							host, port = s[0].strip(), 22
 | 
				
			||||||
 | 
							if len(s) > 1:
 | 
				
			||||||
 | 
								port = utils.parse_int(s[1])
 | 
				
			||||||
 | 
							if not host:
 | 
				
			||||||
 | 
								usage_cb('host is empty')
 | 
				
			||||||
 | 
							if port <= 0 or port > 65535:
 | 
				
			||||||
 | 
								usage_cb('port {0} is not valid'.format(s[1]))
 | 
				
			||||||
 | 
							conf.host = host
 | 
				
			||||||
 | 
							conf.port = port
 | 
				
			||||||
 | 
							if not (conf.ssh1 or conf.ssh2):
 | 
				
			||||||
 | 
								conf.ssh1, conf.ssh2 = True, True
 | 
				
			||||||
 | 
							return conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Output(object):
 | 
					class Output(object):
 | 
				
			||||||
@@ -100,7 +134,9 @@ class Output(object):
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	@property
 | 
						@property
 | 
				
			||||||
	def minlevel(self):
 | 
						def minlevel(self):
 | 
				
			||||||
		return self.__minlevel
 | 
							if self.__minlevel < len(self.LEVELS):
 | 
				
			||||||
 | 
								return self.LEVELS[self.__minlevel]
 | 
				
			||||||
 | 
							return 'unknown'
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	@minlevel.setter
 | 
						@minlevel.setter
 | 
				
			||||||
	def minlevel(self, name):
 | 
						def minlevel(self, name):
 | 
				
			||||||
@@ -122,7 +158,7 @@ class Output(object):
 | 
				
			|||||||
	def __getattr__(self, name):
 | 
						def __getattr__(self, name):
 | 
				
			||||||
		if name == 'head' and self.batch:
 | 
							if name == 'head' and self.batch:
 | 
				
			||||||
			return lambda x: None
 | 
								return lambda x: None
 | 
				
			||||||
		if not self.getlevel(name) >= self.minlevel:
 | 
							if not self.getlevel(name) >= self.__minlevel:
 | 
				
			||||||
			return lambda x: None
 | 
								return lambda x: None
 | 
				
			||||||
		if self.colors and os.name == 'posix' and name in self.COLORS:
 | 
							if self.colors and os.name == 'posix' and name in self.COLORS:
 | 
				
			||||||
			color = u'\033[0;{0}m'.format(self.COLORS[name])
 | 
								color = u'\033[0;{0}m'.format(self.COLORS[name])
 | 
				
			||||||
@@ -133,7 +169,7 @@ class Output(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class OutputBuffer(list):
 | 
					class OutputBuffer(list):
 | 
				
			||||||
	def __enter__(self):
 | 
						def __enter__(self):
 | 
				
			||||||
		self.__buf = io.StringIO()
 | 
							self.__buf = utils.StringIO()
 | 
				
			||||||
		self.__stdout = sys.stdout
 | 
							self.__stdout = sys.stdout
 | 
				
			||||||
		sys.stdout = self.__buf
 | 
							sys.stdout = self.__buf
 | 
				
			||||||
		return self
 | 
							return self
 | 
				
			||||||
@@ -147,39 +183,110 @@ class OutputBuffer(list):
 | 
				
			|||||||
		sys.stdout = self.__stdout
 | 
							sys.stdout = self.__stdout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class KexParty(object):
 | 
					class SSH2(object):
 | 
				
			||||||
	encryption = []
 | 
						class KexParty(object):
 | 
				
			||||||
	mac = []
 | 
							def __init__(self, enc, mac, compression, languages):
 | 
				
			||||||
	compression = []
 | 
								self.__enc = enc
 | 
				
			||||||
	languages = []
 | 
								self.__mac = mac
 | 
				
			||||||
 | 
								self.__compression = compression
 | 
				
			||||||
 | 
								self.__languages = languages
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
 | 
							@property
 | 
				
			||||||
 | 
							def encryption(self):
 | 
				
			||||||
 | 
								return self.__enc
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
class Kex(object):
 | 
							@property
 | 
				
			||||||
	cookie = None
 | 
							def mac(self):
 | 
				
			||||||
	kex_algorithms = []
 | 
								return self.__mac
 | 
				
			||||||
	key_algorithms = []
 | 
							
 | 
				
			||||||
	server = KexParty()
 | 
							@property
 | 
				
			||||||
	client = KexParty()
 | 
							def compression(self):
 | 
				
			||||||
	follows = False
 | 
								return self.__compression
 | 
				
			||||||
	unused = 0
 | 
							
 | 
				
			||||||
 | 
							@property
 | 
				
			||||||
 | 
							def languages(self):
 | 
				
			||||||
 | 
								return self.__languages
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						class Kex(object):
 | 
				
			||||||
 | 
							def __init__(self, cookie, kex_algs, key_algs, cli, srv, follows, unused=0):
 | 
				
			||||||
 | 
								self.__cookie = cookie
 | 
				
			||||||
 | 
								self.__kex_algs = kex_algs
 | 
				
			||||||
 | 
								self.__key_algs = key_algs
 | 
				
			||||||
 | 
								self.__client = cli
 | 
				
			||||||
 | 
								self.__server = srv
 | 
				
			||||||
 | 
								self.__follows = follows
 | 
				
			||||||
 | 
								self.__unused = unused
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							@property
 | 
				
			||||||
 | 
							def cookie(self):
 | 
				
			||||||
 | 
								return self.__cookie
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							@property
 | 
				
			||||||
 | 
							def kex_algorithms(self):
 | 
				
			||||||
 | 
								return self.__kex_algs
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							@property
 | 
				
			||||||
 | 
							def key_algorithms(self):
 | 
				
			||||||
 | 
								return self.__key_algs
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							# client_to_server
 | 
				
			||||||
 | 
							@property
 | 
				
			||||||
 | 
							def client(self):
 | 
				
			||||||
 | 
								return self.__client
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							# server_to_client
 | 
				
			||||||
 | 
							@property
 | 
				
			||||||
 | 
							def server(self):
 | 
				
			||||||
 | 
								return self.__server
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							@property
 | 
				
			||||||
 | 
							def follows(self):
 | 
				
			||||||
 | 
								return self.__follows
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							@property
 | 
				
			||||||
 | 
							def unused(self):
 | 
				
			||||||
 | 
								return self.__unused
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							def write(self, wbuf):
 | 
				
			||||||
 | 
								wbuf.write(self.cookie)
 | 
				
			||||||
 | 
								wbuf.write_list(self.kex_algorithms)
 | 
				
			||||||
 | 
								wbuf.write_list(self.key_algorithms)
 | 
				
			||||||
 | 
								wbuf.write_list(self.client.encryption)
 | 
				
			||||||
 | 
								wbuf.write_list(self.server.encryption)
 | 
				
			||||||
 | 
								wbuf.write_list(self.client.mac)
 | 
				
			||||||
 | 
								wbuf.write_list(self.server.mac)
 | 
				
			||||||
 | 
								wbuf.write_list(self.client.compression)
 | 
				
			||||||
 | 
								wbuf.write_list(self.server.compression)
 | 
				
			||||||
 | 
								wbuf.write_list(self.client.languages)
 | 
				
			||||||
 | 
								wbuf.write_list(self.server.languages)
 | 
				
			||||||
 | 
								wbuf.write_bool(self.follows)
 | 
				
			||||||
 | 
								wbuf.write_int(self.__unused)
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							@property
 | 
				
			||||||
 | 
							def payload(self):
 | 
				
			||||||
 | 
								wbuf = WriteBuf()
 | 
				
			||||||
 | 
								self.write(wbuf)
 | 
				
			||||||
 | 
								return wbuf.write_flush()
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		@classmethod
 | 
							@classmethod
 | 
				
			||||||
		def parse(cls, payload):
 | 
							def parse(cls, payload):
 | 
				
			||||||
		kex = cls()
 | 
					 | 
				
			||||||
			buf = ReadBuf(payload)
 | 
								buf = ReadBuf(payload)
 | 
				
			||||||
		kex.cookie = buf.read(16)
 | 
								cookie = buf.read(16)
 | 
				
			||||||
		kex.kex_algorithms = buf.read_list()
 | 
								kex_algs = buf.read_list()
 | 
				
			||||||
		kex.key_algorithms = buf.read_list()
 | 
								key_algs = buf.read_list()
 | 
				
			||||||
		kex.client.encryption = buf.read_list()
 | 
								cli_enc = buf.read_list()
 | 
				
			||||||
		kex.server.encryption = buf.read_list()
 | 
								srv_enc = buf.read_list()
 | 
				
			||||||
		kex.client.mac = buf.read_list()
 | 
								cli_mac = buf.read_list()
 | 
				
			||||||
		kex.server.mac = buf.read_list()
 | 
								srv_mac = buf.read_list()
 | 
				
			||||||
		kex.client.compression = buf.read_list()
 | 
								cli_compression = buf.read_list()
 | 
				
			||||||
		kex.server.compression = buf.read_list()
 | 
								srv_compression = buf.read_list()
 | 
				
			||||||
		kex.client.languages = buf.read_list()
 | 
								cli_languages = buf.read_list()
 | 
				
			||||||
		kex.server.languages = buf.read_list()
 | 
								srv_languages = buf.read_list()
 | 
				
			||||||
		kex.follows = buf.read_bool()
 | 
								follows = buf.read_bool()
 | 
				
			||||||
		kex.unused = buf.read_int()
 | 
								unused = buf.read_int()
 | 
				
			||||||
 | 
								cli = SSH2.KexParty(cli_enc, cli_mac, cli_compression, cli_languages)
 | 
				
			||||||
 | 
								srv = SSH2.KexParty(srv_enc, srv_mac, srv_compression, srv_languages)
 | 
				
			||||||
 | 
								kex = cls(cookie, kex_algs, key_algs, cli, srv, follows, unused)
 | 
				
			||||||
			return kex
 | 
								return kex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -204,12 +311,14 @@ class SSH1(object):
 | 
				
			|||||||
				crc = (crc >> 8) ^ self._table[n]
 | 
									crc = (crc >> 8) ^ self._table[n]
 | 
				
			||||||
			return crc
 | 
								return crc
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	_crc32 = CRC32()
 | 
						_crc32 = None
 | 
				
			||||||
	CIPHERS = ['none', 'idea', 'des', '3des', 'tss', 'rc4', 'blowfish']
 | 
						CIPHERS = ['none', 'idea', 'des', '3des', 'tss', 'rc4', 'blowfish']
 | 
				
			||||||
	AUTHS = [None, 'rhosts', 'rsa', 'password', 'rhosts_rsa', 'tis', 'kerberos']
 | 
						AUTHS = [None, 'rhosts', 'rsa', 'password', 'rhosts_rsa', 'tis', 'kerberos']
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	@classmethod
 | 
						@classmethod
 | 
				
			||||||
	def crc32(cls, v):
 | 
						def crc32(cls, v):
 | 
				
			||||||
 | 
							if cls._crc32 is None:
 | 
				
			||||||
 | 
								cls._crc32 = cls.CRC32()
 | 
				
			||||||
		return cls._crc32.calc(v)
 | 
							return cls._crc32.calc(v)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	class KexDB(object):
 | 
						class KexDB(object):
 | 
				
			||||||
@@ -315,6 +424,24 @@ class SSH1(object):
 | 
				
			|||||||
					auths.append(SSH1.AUTHS[i])
 | 
										auths.append(SSH1.AUTHS[i])
 | 
				
			||||||
			return auths
 | 
								return auths
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
 | 
							def write(self, wbuf):
 | 
				
			||||||
 | 
								wbuf.write(self.cookie)
 | 
				
			||||||
 | 
								wbuf.write_int(self.server_key_bits)
 | 
				
			||||||
 | 
								wbuf.write_mpint1(self.server_key_public_exponent)
 | 
				
			||||||
 | 
								wbuf.write_mpint1(self.server_key_public_modulus)
 | 
				
			||||||
 | 
								wbuf.write_int(self.host_key_bits)
 | 
				
			||||||
 | 
								wbuf.write_mpint1(self.host_key_public_exponent)
 | 
				
			||||||
 | 
								wbuf.write_mpint1(self.host_key_public_modulus)
 | 
				
			||||||
 | 
								wbuf.write_int(self.protocol_flags)
 | 
				
			||||||
 | 
								wbuf.write_int(self.supported_ciphers_mask)
 | 
				
			||||||
 | 
								wbuf.write_int(self.supported_authentications_mask)
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							@property
 | 
				
			||||||
 | 
							def payload(self):
 | 
				
			||||||
 | 
								wbuf = WriteBuf()
 | 
				
			||||||
 | 
								self.write(wbuf)
 | 
				
			||||||
 | 
								return wbuf.write_flush()
 | 
				
			||||||
 | 
							
 | 
				
			||||||
		@classmethod
 | 
							@classmethod
 | 
				
			||||||
		def parse(cls, payload):
 | 
							def parse(cls, payload):
 | 
				
			||||||
			buf = ReadBuf(payload)
 | 
								buf = ReadBuf(payload)
 | 
				
			||||||
@@ -337,7 +464,7 @@ class SSH1(object):
 | 
				
			|||||||
class ReadBuf(object):
 | 
					class ReadBuf(object):
 | 
				
			||||||
	def __init__(self, data=None):
 | 
						def __init__(self, data=None):
 | 
				
			||||||
		super(ReadBuf, self).__init__()
 | 
							super(ReadBuf, self).__init__()
 | 
				
			||||||
		self._buf = io.BytesIO(data) if data else io.BytesIO()
 | 
							self._buf = utils.BytesIO(data) if data else utils.BytesIO()
 | 
				
			||||||
		self._len = len(data) if data else 0
 | 
							self._len = len(data) if data else 0
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	@property
 | 
						@property
 | 
				
			||||||
@@ -416,7 +543,7 @@ class WriteBuf(object):
 | 
				
			|||||||
		return self.write(v)
 | 
							return self.write(v)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	def write_list(self, v):
 | 
						def write_list(self, v):
 | 
				
			||||||
		self.write_string(u','.join(v))
 | 
							return self.write_string(u','.join(v))
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	@classmethod
 | 
						@classmethod
 | 
				
			||||||
	def _bitlength(cls, n):
 | 
						def _bitlength(cls, n):
 | 
				
			||||||
@@ -454,6 +581,12 @@ class WriteBuf(object):
 | 
				
			|||||||
		data = self._create_mpint(n)
 | 
							data = self._create_mpint(n)
 | 
				
			||||||
		return self.write_string(data)
 | 
							return self.write_string(data)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						def write_line(self, v):
 | 
				
			||||||
 | 
							if not isinstance(v, bytes):
 | 
				
			||||||
 | 
								v = bytes(bytearray(v, 'utf-8'))
 | 
				
			||||||
 | 
							v += b'\r\n'
 | 
				
			||||||
 | 
							return self.write(v)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	def write_flush(self):
 | 
						def write_flush(self):
 | 
				
			||||||
		payload = self._wbuf.getvalue()
 | 
							payload = self._wbuf.getvalue()
 | 
				
			||||||
		self._wbuf.truncate(0)
 | 
							self._wbuf.truncate(0)
 | 
				
			||||||
@@ -472,6 +605,7 @@ class SSH(object):
 | 
				
			|||||||
	class Product(object):
 | 
						class Product(object):
 | 
				
			||||||
		OpenSSH = 'OpenSSH'
 | 
							OpenSSH = 'OpenSSH'
 | 
				
			||||||
		DropbearSSH = 'Dropbear SSH'
 | 
							DropbearSSH = 'Dropbear SSH'
 | 
				
			||||||
 | 
							LibSSH = 'libssh'
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	class Software(object):
 | 
						class Software(object):
 | 
				
			||||||
		def __init__(self, vendor, product, version, patch, os):
 | 
							def __init__(self, vendor, product, version, patch, os):
 | 
				
			||||||
@@ -505,7 +639,7 @@ class SSH(object):
 | 
				
			|||||||
			if other is None:
 | 
								if other is None:
 | 
				
			||||||
				return 1
 | 
									return 1
 | 
				
			||||||
			if isinstance(other, self.__class__):
 | 
								if isinstance(other, self.__class__):
 | 
				
			||||||
				other = '{0}{1}'.format(other.version, other.patch)
 | 
									other = '{0}{1}'.format(other.version, other.patch or '')
 | 
				
			||||||
			else:
 | 
								else:
 | 
				
			||||||
				other = str(other)
 | 
									other = str(other)
 | 
				
			||||||
			mx = re.match(r'^([\d\.]+\d+)(.*)$', other)
 | 
								mx = re.match(r'^([\d\.]+\d+)(.*)$', other)
 | 
				
			||||||
@@ -517,15 +651,15 @@ class SSH(object):
 | 
				
			|||||||
				return -1
 | 
									return -1
 | 
				
			||||||
			elif self.version > oversion:
 | 
								elif self.version > oversion:
 | 
				
			||||||
				return 1
 | 
									return 1
 | 
				
			||||||
			spatch = self.patch
 | 
								spatch = self.patch or ''
 | 
				
			||||||
			if self.product == SSH.Product.DropbearSSH:
 | 
								if self.product == SSH.Product.DropbearSSH:
 | 
				
			||||||
				if not re.match(r'^test\d.*$', opatch):
 | 
									if not re.match(r'^test\d.*$', opatch):
 | 
				
			||||||
					opatch = 'z{0}'.format(opatch)
 | 
										opatch = 'z{0}'.format(opatch)
 | 
				
			||||||
				if not re.match(r'^test\d.*$', self.patch):
 | 
									if not re.match(r'^test\d.*$', spatch):
 | 
				
			||||||
					spatch = 'z{0}'.format(self.patch)
 | 
										spatch = 'z{0}'.format(spatch)
 | 
				
			||||||
			elif self.product == SSH.Product.OpenSSH:
 | 
								elif self.product == SSH.Product.OpenSSH:
 | 
				
			||||||
				mx1 = re.match(r'^p\d(.*)', opatch)
 | 
									mx1 = re.match(r'^p\d(.*)', opatch)
 | 
				
			||||||
				mx2 = re.match(r'^p\d(.*)', self.patch)
 | 
									mx2 = re.match(r'^p\d(.*)', spatch)
 | 
				
			||||||
				if not (mx1 and mx2):
 | 
									if not (mx1 and mx2):
 | 
				
			||||||
					if mx1:
 | 
										if mx1:
 | 
				
			||||||
						opatch = mx1.group(1)
 | 
											opatch = mx1.group(1)
 | 
				
			||||||
@@ -544,25 +678,29 @@ class SSH(object):
 | 
				
			|||||||
				return False
 | 
									return False
 | 
				
			||||||
			return True
 | 
								return True
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		def __str__(self):
 | 
							def display(self, full=True):
 | 
				
			||||||
			out = '{0} '.format(self.vendor) if self.vendor else ''
 | 
								out = '{0} '.format(self.vendor) if self.vendor else ''
 | 
				
			||||||
			out += self.product
 | 
								out += self.product
 | 
				
			||||||
			if self.version:
 | 
								if self.version:
 | 
				
			||||||
				out += ' {0}'.format(self.version)
 | 
									out += ' {0}'.format(self.version)
 | 
				
			||||||
			patch = self.patch
 | 
								if full:
 | 
				
			||||||
 | 
									patch = self.patch or ''
 | 
				
			||||||
				if self.product == SSH.Product.OpenSSH:
 | 
									if self.product == SSH.Product.OpenSSH:
 | 
				
			||||||
				mx = re.match('^(p\d)(.*)$', self.patch)
 | 
										mx = re.match('^(p\d)(.*)$', patch)
 | 
				
			||||||
					if mx is not None:
 | 
										if mx is not None:
 | 
				
			||||||
						out += mx.group(1)
 | 
											out += mx.group(1)
 | 
				
			||||||
						patch = mx.group(2).strip()
 | 
											patch = mx.group(2).strip()
 | 
				
			||||||
				if patch:
 | 
									if patch:
 | 
				
			||||||
				out += ' ({0})'.format(self.patch)
 | 
										out += ' ({0})'.format(patch)
 | 
				
			||||||
				if self.os:
 | 
									if self.os:
 | 
				
			||||||
					out += ' running on {0}'.format(self.os)
 | 
										out += ' running on {0}'.format(self.os)
 | 
				
			||||||
			return out
 | 
								return out
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
 | 
							def __str__(self):
 | 
				
			||||||
 | 
								return self.display()
 | 
				
			||||||
 | 
							
 | 
				
			||||||
		def __repr__(self):
 | 
							def __repr__(self):
 | 
				
			||||||
			out = 'vendor={0} '.format(self.vendor) if self.vendor else ''
 | 
								out = 'vendor={0}'.format(self.vendor) if self.vendor else ''
 | 
				
			||||||
			if self.product:
 | 
								if self.product:
 | 
				
			||||||
				if self.vendor:
 | 
									if self.vendor:
 | 
				
			||||||
					out += ', '
 | 
										out += ', '
 | 
				
			||||||
@@ -577,7 +715,7 @@ class SSH(object):
 | 
				
			|||||||
		
 | 
							
 | 
				
			||||||
		@staticmethod
 | 
							@staticmethod
 | 
				
			||||||
		def _fix_patch(patch):
 | 
							def _fix_patch(patch):
 | 
				
			||||||
			return re.sub(r'^[-_\.]+', '', patch)
 | 
								return re.sub(r'^[-_\.]+', '', patch) or None
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		@staticmethod
 | 
							@staticmethod
 | 
				
			||||||
		def _fix_date(d):
 | 
							def _fix_date(d):
 | 
				
			||||||
@@ -628,6 +766,12 @@ class SSH(object):
 | 
				
			|||||||
				v = None
 | 
									v = None
 | 
				
			||||||
				os = cls._extract_os(banner.comments)
 | 
									os = cls._extract_os(banner.comments)
 | 
				
			||||||
				return cls(v, p, mx.group(1), patch, os)
 | 
									return cls(v, p, mx.group(1), patch, os)
 | 
				
			||||||
 | 
								mx = re.match(r'^libssh-([\d\.]+\d+)(.*)', software)
 | 
				
			||||||
 | 
								if mx:
 | 
				
			||||||
 | 
									patch = cls._fix_patch(mx.group(2))
 | 
				
			||||||
 | 
									v, p = None, SSH.Product.LibSSH
 | 
				
			||||||
 | 
									os = cls._extract_os(banner.comments)
 | 
				
			||||||
 | 
									return cls(v, p, mx.group(1), patch, os)
 | 
				
			||||||
			mx = re.match(r'^RomSShell_([\d\.]+\d+)(.*)', software)
 | 
								mx = re.match(r'^RomSShell_([\d\.]+\d+)(.*)', software)
 | 
				
			||||||
			if mx:
 | 
								if mx:
 | 
				
			||||||
				patch = cls._fix_patch(mx.group(2))
 | 
									patch = cls._fix_patch(mx.group(2))
 | 
				
			||||||
@@ -644,8 +788,8 @@ class SSH(object):
 | 
				
			|||||||
			return None
 | 
								return None
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	class Banner(object):
 | 
						class Banner(object):
 | 
				
			||||||
		_RXP, _RXR = r'SSH-\d\.\s*?\d+', r'(-([^\s]*)(?:\s+(.*))?)?'
 | 
							_RXP, _RXR = r'SSH-\d\.\s*?\d+', r'(-\s*([^\s]*)(?:\s+(.*))?)?'
 | 
				
			||||||
		RX_PROTOCOL = re.compile(_RXP.replace('\d', '(\d)'))
 | 
							RX_PROTOCOL = re.compile(re.sub(r'\\d(\+?)', '(\\d\g<1>)', _RXP))
 | 
				
			||||||
		RX_BANNER = re.compile(r'^({0}(?:(?:-{0})*)){1}$'.format(_RXP, _RXR))
 | 
							RX_BANNER = re.compile(r'^({0}(?:(?:-{0})*)){1}$'.format(_RXP, _RXR))
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		def __init__(self, protocol, software, comments):
 | 
							def __init__(self, protocol, software, comments):
 | 
				
			||||||
@@ -693,6 +837,8 @@ class SSH(object):
 | 
				
			|||||||
			if software is None and (mx.group(2) or '').startswith('-'):
 | 
								if software is None and (mx.group(2) or '').startswith('-'):
 | 
				
			||||||
				software = ''
 | 
									software = ''
 | 
				
			||||||
			comments = (mx.group(4) or '').strip() or None
 | 
								comments = (mx.group(4) or '').strip() or None
 | 
				
			||||||
 | 
								if comments is not None:
 | 
				
			||||||
 | 
									comments = re.sub('\s+', ' ', comments)
 | 
				
			||||||
			return cls(protocol, software, comments)
 | 
								return cls(protocol, software, comments)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	class Fingerprint(object):
 | 
						class Fingerprint(object):
 | 
				
			||||||
@@ -714,21 +860,34 @@ class SSH(object):
 | 
				
			|||||||
	class Security(object):
 | 
						class Security(object):
 | 
				
			||||||
		CVE = {
 | 
							CVE = {
 | 
				
			||||||
			'Dropbear SSH': [
 | 
								'Dropbear SSH': [
 | 
				
			||||||
				['0.44', '2015.71', 1, 'CVE-2016-3116', 5.5, 'bypass command restrictions via xauth command injection.'],
 | 
									['0.44', '2015.71', 1, 'CVE-2016-3116', 5.5, 'bypass command restrictions via xauth command injection'],
 | 
				
			||||||
				['0.28', '2013.58', 1, 'CVE-2013-4434', 5.0, 'discover valid usernames through different time delays.'],
 | 
									['0.28', '2013.58', 1, 'CVE-2013-4434', 5.0, 'discover valid usernames through different time delays'],
 | 
				
			||||||
				['0.28', '2013.58', 1, 'CVE-2013-4421', 5.0, 'cause DoS (memory consumption) via a compressed packet.'],
 | 
									['0.28', '2013.58', 1, 'CVE-2013-4421', 5.0, 'cause DoS (memory consumption) via a compressed packet'],
 | 
				
			||||||
				['0.52', '2011.54', 1, 'CVE-2012-0920', 7.1, 'execute arbitrary code or bypass command restrictions.'],
 | 
									['0.52', '2011.54', 1, 'CVE-2012-0920', 7.1, 'execute arbitrary code or bypass command restrictions'],
 | 
				
			||||||
				['0.40', '0.48.1',  1, 'CVE-2007-1099', 7.5, 'conduct a MitM attack (no warning for hostkey mismatch).'],
 | 
									['0.40', '0.48.1',  1, 'CVE-2007-1099', 7.5, 'conduct a MitM attack (no warning for hostkey mismatch)'],
 | 
				
			||||||
				['0.28', '0.47',    1, 'CVE-2006-1206', 7.5, 'cause DoS (slot exhaustion) via large number of connections.'],
 | 
									['0.28', '0.47',    1, 'CVE-2006-1206', 7.5, 'cause DoS (slot exhaustion) via large number of connections'],
 | 
				
			||||||
				['0.39', '0.47',    1, 'CVE-2006-0225', 4.6, 'execute arbitrary commands via scp with crafted filenames.'],
 | 
									['0.39', '0.47',    1, 'CVE-2006-0225', 4.6, 'execute arbitrary commands via scp with crafted filenames'],
 | 
				
			||||||
				['0.28', '0.46',    1, 'CVE-2005-4178', 6.5, 'execute arbitrary code via buffer overflow vulnerability.'],
 | 
									['0.28', '0.46',    1, 'CVE-2005-4178', 6.5, 'execute arbitrary code via buffer overflow vulnerability'],
 | 
				
			||||||
				['0.28', '0.42',    1, 'CVE-2004-2486', 7.5, 'execute arbitrary code via DSS verification code.'],
 | 
									['0.28', '0.42',    1, 'CVE-2004-2486', 7.5, 'execute arbitrary code via DSS verification code']],
 | 
				
			||||||
			]
 | 
								'libssh': [
 | 
				
			||||||
 | 
									['0.1',   '0.7.2',  1, 'CVE-2016-0739', 4.3, 'conduct a MitM attack (weakness in DH key generation)'],
 | 
				
			||||||
 | 
									['0.5.1', '0.6.4',  1, 'CVE-2015-3146', 5.0, 'cause DoS via kex packets (null pointer dereference)'],
 | 
				
			||||||
 | 
									['0.5.1', '0.6.3',  1, 'CVE-2014-8132', 5.0, 'cause DoS via kex init packet (dangling pointer)'],
 | 
				
			||||||
 | 
									['0.4.7', '0.6.2',  1, 'CVE-2014-0017', 1.9, 'leak data via PRNG state reuse on forking servers'],
 | 
				
			||||||
 | 
									['0.4.7', '0.5.3',  1, 'CVE-2013-0176', 4.3, 'cause DoS via kex packet (null pointer dereference)'],
 | 
				
			||||||
 | 
									['0.4.7', '0.5.2',  1, 'CVE-2012-6063', 7.5, 'cause DoS or execute arbitrary code via sftp (double free)'],
 | 
				
			||||||
 | 
									['0.4.7', '0.5.2',  1, 'CVE-2012-4562', 7.5, 'cause DoS or execute arbitrary code (overflow check)'],
 | 
				
			||||||
 | 
									['0.4.7', '0.5.2',  1, 'CVE-2012-4561', 5.0, 'cause DoS via unspecified vectors (invalid pointer)'],
 | 
				
			||||||
 | 
									['0.4.7', '0.5.2',  1, 'CVE-2012-4560', 7.5, 'cause DoS or execute arbitrary code (buffer overflow)'],
 | 
				
			||||||
 | 
									['0.4.7', '0.5.2',  1, 'CVE-2012-4559', 6.8, 'cause DoS or execute arbitrary code (double free)']]
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		TXT = {
 | 
							TXT = {
 | 
				
			||||||
			'Dropbear SSH': [
 | 
								'Dropbear SSH': [
 | 
				
			||||||
				['0.28', '0.34', 1, 'remote root exploit', 'remote format string buffer overflow exploit (exploit-db#387).'],
 | 
									['0.28', '0.34', 1, 'remote root exploit', 'remote format string buffer overflow exploit (exploit-db#387)']],
 | 
				
			||||||
			]
 | 
								'libssh': [
 | 
				
			||||||
 | 
									['0.3.3', '0.3.3', 1, 'null pointer check', 'missing null pointer check in "crypt_set_algorithms_server"'],
 | 
				
			||||||
 | 
									['0.3.3', '0.3.3', 1, 'integer overflow',   'integer overflow in "buffer_get_data"'],
 | 
				
			||||||
 | 
									['0.3.3', '0.3.3', 3, 'heap overflow',      'heap overflow in "packet_decrypt"']]
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	class Socket(ReadBuf, WriteBuf):
 | 
						class Socket(ReadBuf, WriteBuf):
 | 
				
			||||||
@@ -960,29 +1119,29 @@ class KexDB(object):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	ALGORITHMS = {
 | 
						ALGORITHMS = {
 | 
				
			||||||
		'kex': {
 | 
							'kex': {
 | 
				
			||||||
			'diffie-hellman-group1-sha1': [['2.3.0,d0.28', '6.6', '6.9'], [FAIL_OPENSSH67_UNSAFE, FAIL_OPENSSH70_LOGJAM], [WARN_MODULUS_SIZE, WARN_HASH_WEAK]],
 | 
								'diffie-hellman-group1-sha1': [['2.3.0,d0.28,l10.2', '6.6', '6.9'], [FAIL_OPENSSH67_UNSAFE, FAIL_OPENSSH70_LOGJAM], [WARN_MODULUS_SIZE, WARN_HASH_WEAK]],
 | 
				
			||||||
			'diffie-hellman-group14-sha1': [['3.9,d0.53'], [], [WARN_HASH_WEAK]],
 | 
								'diffie-hellman-group14-sha1': [['3.9,d0.53,l10.6.0'], [], [WARN_HASH_WEAK]],
 | 
				
			||||||
			'diffie-hellman-group14-sha256': [['7.3,d2016.73']],
 | 
								'diffie-hellman-group14-sha256': [['7.3,d2016.73']],
 | 
				
			||||||
			'diffie-hellman-group16-sha512': [['7.3,d2016.73']],
 | 
								'diffie-hellman-group16-sha512': [['7.3,d2016.73']],
 | 
				
			||||||
			'diffie-hellman-group18-sha512': [['7.3']],
 | 
								'diffie-hellman-group18-sha512': [['7.3']],
 | 
				
			||||||
			'diffie-hellman-group-exchange-sha1': [['2.3.0', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_HASH_WEAK]],
 | 
								'diffie-hellman-group-exchange-sha1': [['2.3.0', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_HASH_WEAK]],
 | 
				
			||||||
			'diffie-hellman-group-exchange-sha256': [['4.4'], [], [WARN_MODULUS_CUSTOM]],
 | 
								'diffie-hellman-group-exchange-sha256': [['4.4'], [], [WARN_MODULUS_CUSTOM]],
 | 
				
			||||||
			'ecdh-sha2-nistp256': [['5.7,d2013.62'], [WARN_CURVES_WEAK]],
 | 
								'ecdh-sha2-nistp256': [['5.7,d2013.62,l10.6.0'], [WARN_CURVES_WEAK]],
 | 
				
			||||||
			'ecdh-sha2-nistp384': [['5.7,d2013.62'], [WARN_CURVES_WEAK]],
 | 
								'ecdh-sha2-nistp384': [['5.7,d2013.62'], [WARN_CURVES_WEAK]],
 | 
				
			||||||
			'ecdh-sha2-nistp521': [['5.7,d2013.62'], [WARN_CURVES_WEAK]],
 | 
								'ecdh-sha2-nistp521': [['5.7,d2013.62'], [WARN_CURVES_WEAK]],
 | 
				
			||||||
			'curve25519-sha256@libssh.org': [['6.5,d2013.62']],
 | 
								'curve25519-sha256@libssh.org': [['6.5,d2013.62,l10.6.0']],
 | 
				
			||||||
			'kexguess2@matt.ucc.asn.au': [['d2013.57']],
 | 
								'kexguess2@matt.ucc.asn.au': [['d2013.57']],
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		'key': {
 | 
							'key': {
 | 
				
			||||||
			'rsa-sha2-256': [['7.2']],
 | 
								'rsa-sha2-256': [['7.2']],
 | 
				
			||||||
			'rsa-sha2-512': [['7.2']],
 | 
								'rsa-sha2-512': [['7.2']],
 | 
				
			||||||
			'ssh-ed25519': [['6.5']],
 | 
								'ssh-ed25519': [['6.5,l10.7.0']],
 | 
				
			||||||
			'ssh-ed25519-cert-v01@openssh.com': [['6.5']],
 | 
								'ssh-ed25519-cert-v01@openssh.com': [['6.5']],
 | 
				
			||||||
			'ssh-rsa': [['2.5.0,d0.28']],
 | 
								'ssh-rsa': [['2.5.0,d0.28,l10.2']],
 | 
				
			||||||
			'ssh-dss': [['2.1.0,d0.28', '6.9'], [FAIL_OPENSSH70_WEAK], [WARN_MODULUS_SIZE, WARN_RNDSIG_KEY]],
 | 
								'ssh-dss': [['2.1.0,d0.28,l10.2', '6.9'], [FAIL_OPENSSH70_WEAK], [WARN_MODULUS_SIZE, WARN_RNDSIG_KEY]],
 | 
				
			||||||
			'ecdsa-sha2-nistp256': [['5.7,d2013.62'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
 | 
								'ecdsa-sha2-nistp256': [['5.7,d2013.62,l10.6.4'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
 | 
				
			||||||
			'ecdsa-sha2-nistp384': [['5.7,d2013.62'], [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'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
 | 
								'ecdsa-sha2-nistp521': [['5.7,d2013.62,l10.6.4'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
 | 
				
			||||||
			'ssh-rsa-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_OPENSSH70_LEGACY], []],
 | 
								'ssh-rsa-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_OPENSSH70_LEGACY], []],
 | 
				
			||||||
			'ssh-dss-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_OPENSSH70_LEGACY], [WARN_MODULUS_SIZE, WARN_RNDSIG_KEY]],
 | 
								'ssh-dss-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_OPENSSH70_LEGACY], [WARN_MODULUS_SIZE, WARN_RNDSIG_KEY]],
 | 
				
			||||||
			'ssh-rsa-cert-v01@openssh.com': [['5.6']],
 | 
								'ssh-rsa-cert-v01@openssh.com': [['5.6']],
 | 
				
			||||||
@@ -992,10 +1151,10 @@ class KexDB(object):
 | 
				
			|||||||
			'ecdsa-sha2-nistp521-cert-v01@openssh.com': [['5.7'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
 | 
								'ecdsa-sha2-nistp521-cert-v01@openssh.com': [['5.7'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		'enc': {
 | 
							'enc': {
 | 
				
			||||||
			'none': [['1.2.2,d2013.56'], [FAIL_PLAINTEXT]],
 | 
								'none': [['1.2.2,d2013.56,l10.2'], [FAIL_PLAINTEXT]],
 | 
				
			||||||
			'3des-cbc': [['1.2.2,d0.28', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_WEAK, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
 | 
								'3des-cbc': [['1.2.2,d0.28,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_WEAK, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
 | 
				
			||||||
			'3des-ctr': [['d0.52']],
 | 
								'3des-ctr': [['d0.52']],
 | 
				
			||||||
			'blowfish-cbc': [['1.2.2,d0.28', '6.6,d0.52', '7.1,d0.52'], [FAIL_OPENSSH67_UNSAFE, FAIL_DBEAR53_DISABLED], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
 | 
								'blowfish-cbc': [['1.2.2,d0.28,l10.2', '6.6,d0.52', '7.1,d0.52'], [FAIL_OPENSSH67_UNSAFE, FAIL_DBEAR53_DISABLED], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
 | 
				
			||||||
			'twofish-cbc': [['d0.28', 'd2014.66'], [FAIL_DBEAR67_DISABLED], [WARN_CIPHER_MODE]],
 | 
								'twofish-cbc': [['d0.28', 'd2014.66'], [FAIL_DBEAR67_DISABLED], [WARN_CIPHER_MODE]],
 | 
				
			||||||
			'twofish128-cbc': [['d0.47', 'd2014.66'], [FAIL_DBEAR67_DISABLED], [WARN_CIPHER_MODE]],
 | 
								'twofish128-cbc': [['d0.47', 'd2014.66'], [FAIL_DBEAR67_DISABLED], [WARN_CIPHER_MODE]],
 | 
				
			||||||
			'twofish256-cbc': [['d0.47', 'd2014.66'], [FAIL_DBEAR67_DISABLED], [WARN_CIPHER_MODE]],
 | 
								'twofish256-cbc': [['d0.47', 'd2014.66'], [FAIL_DBEAR67_DISABLED], [WARN_CIPHER_MODE]],
 | 
				
			||||||
@@ -1005,27 +1164,27 @@ class KexDB(object):
 | 
				
			|||||||
			'arcfour': [['2.1.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_WEAK]],
 | 
								'arcfour': [['2.1.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_WEAK]],
 | 
				
			||||||
			'arcfour128': [['4.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_WEAK]],
 | 
								'arcfour128': [['4.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_WEAK]],
 | 
				
			||||||
			'arcfour256': [['4.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_WEAK]],
 | 
								'arcfour256': [['4.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_WEAK]],
 | 
				
			||||||
			'aes128-cbc': [['2.3.0,d0.28', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]],
 | 
								'aes128-cbc': [['2.3.0,d0.28,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]],
 | 
				
			||||||
			'aes192-cbc': [['2.3.0', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]],
 | 
								'aes192-cbc': [['2.3.0,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]],
 | 
				
			||||||
			'aes256-cbc': [['2.3.0,d0.47', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]],
 | 
								'aes256-cbc': [['2.3.0,d0.47,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]],
 | 
				
			||||||
			'rijndael128-cbc': [['2.3.0', '3.0.2'], [FAIL_OPENSSH31_REMOVE], [WARN_CIPHER_MODE]],
 | 
								'rijndael128-cbc': [['2.3.0', '3.0.2'], [FAIL_OPENSSH31_REMOVE], [WARN_CIPHER_MODE]],
 | 
				
			||||||
			'rijndael192-cbc': [['2.3.0', '3.0.2'], [FAIL_OPENSSH31_REMOVE], [WARN_CIPHER_MODE]],
 | 
								'rijndael192-cbc': [['2.3.0', '3.0.2'], [FAIL_OPENSSH31_REMOVE], [WARN_CIPHER_MODE]],
 | 
				
			||||||
			'rijndael256-cbc': [['2.3.0', '3.0.2'], [FAIL_OPENSSH31_REMOVE], [WARN_CIPHER_MODE]],
 | 
								'rijndael256-cbc': [['2.3.0', '3.0.2'], [FAIL_OPENSSH31_REMOVE], [WARN_CIPHER_MODE]],
 | 
				
			||||||
			'rijndael-cbc@lysator.liu.se': [['2.3.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE]],
 | 
								'rijndael-cbc@lysator.liu.se': [['2.3.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE]],
 | 
				
			||||||
			'aes128-ctr': [['3.7,d0.52']],
 | 
								'aes128-ctr': [['3.7,d0.52,l10.4.1']],
 | 
				
			||||||
			'aes192-ctr': [['3.7']],
 | 
								'aes192-ctr': [['3.7,l10.4.1']],
 | 
				
			||||||
			'aes256-ctr': [['3.7,d0.52']],
 | 
								'aes256-ctr': [['3.7,d0.52,l10.4.1']],
 | 
				
			||||||
			'aes128-gcm@openssh.com': [['6.2']],
 | 
								'aes128-gcm@openssh.com': [['6.2']],
 | 
				
			||||||
			'aes256-gcm@openssh.com': [['6.2']],
 | 
								'aes256-gcm@openssh.com': [['6.2']],
 | 
				
			||||||
			'chacha20-poly1305@openssh.com': [['6.5'], [], [], [INFO_OPENSSH69_CHACHA]],
 | 
								'chacha20-poly1305@openssh.com': [['6.5'], [], [], [INFO_OPENSSH69_CHACHA]],
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		'mac': {
 | 
							'mac': {
 | 
				
			||||||
			'none': [['d2013.56'], [FAIL_PLAINTEXT]],
 | 
								'none': [['d2013.56'], [FAIL_PLAINTEXT]],
 | 
				
			||||||
			'hmac-sha1': [['2.1.0,d0.28'], [], [WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
 | 
								'hmac-sha1': [['2.1.0,d0.28,l10.2'], [], [WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
 | 
				
			||||||
			'hmac-sha1-96': [['2.5.0,d0.47', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
 | 
								'hmac-sha1-96': [['2.5.0,d0.47', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
 | 
				
			||||||
			'hmac-sha2-256': [['5.9,d2013.56'], [], [WARN_ENCRYPT_AND_MAC]],
 | 
								'hmac-sha2-256': [['5.9,d2013.56,l10.7.0'], [], [WARN_ENCRYPT_AND_MAC]],
 | 
				
			||||||
			'hmac-sha2-256-96': [['5.9', '6.0'], [FAIL_OPENSSH61_REMOVE], [WARN_ENCRYPT_AND_MAC]],
 | 
								'hmac-sha2-256-96': [['5.9', '6.0'], [FAIL_OPENSSH61_REMOVE], [WARN_ENCRYPT_AND_MAC]],
 | 
				
			||||||
			'hmac-sha2-512': [['5.9,d2013.56'], [], [WARN_ENCRYPT_AND_MAC]],
 | 
								'hmac-sha2-512': [['5.9,d2013.56,l10.7.0'], [], [WARN_ENCRYPT_AND_MAC]],
 | 
				
			||||||
			'hmac-sha2-512-96': [['5.9', '6.0'], [FAIL_OPENSSH61_REMOVE], [WARN_ENCRYPT_AND_MAC]],
 | 
								'hmac-sha2-512-96': [['5.9', '6.0'], [FAIL_OPENSSH61_REMOVE], [WARN_ENCRYPT_AND_MAC]],
 | 
				
			||||||
			'hmac-md5': [['2.1.0,d0.28', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
 | 
								'hmac-md5': [['2.1.0,d0.28', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
 | 
				
			||||||
			'hmac-md5-96': [['2.5.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
 | 
								'hmac-md5-96': [['2.5.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
 | 
				
			||||||
@@ -1049,6 +1208,8 @@ class KexDB(object):
 | 
				
			|||||||
def get_ssh_version(version_desc):
 | 
					def get_ssh_version(version_desc):
 | 
				
			||||||
	if version_desc.startswith('d'):
 | 
						if version_desc.startswith('d'):
 | 
				
			||||||
		return (SSH.Product.DropbearSSH, version_desc[1:])
 | 
							return (SSH.Product.DropbearSSH, version_desc[1:])
 | 
				
			||||||
 | 
						elif version_desc.startswith('l1'):
 | 
				
			||||||
 | 
							return (SSH.Product.LibSSH, version_desc[2:])
 | 
				
			||||||
	else:
 | 
						else:
 | 
				
			||||||
		return (SSH.Product.OpenSSH, version_desc)
 | 
							return (SSH.Product.OpenSSH, version_desc)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1091,8 +1252,10 @@ def get_alg_timeframe(alg_desc, for_server=True, result={}):
 | 
				
			|||||||
def get_ssh_timeframe(alg_pairs, for_server=True):
 | 
					def get_ssh_timeframe(alg_pairs, for_server=True):
 | 
				
			||||||
	timeframe = {}
 | 
						timeframe = {}
 | 
				
			||||||
	for alg_pair in alg_pairs:
 | 
						for alg_pair in alg_pairs:
 | 
				
			||||||
		alg_db, algs = alg_pair
 | 
							sshv, alg_db = alg_pair[0]
 | 
				
			||||||
		for alg_type, alg_list in algs.items():
 | 
							alg_sets = alg_pair[1:]
 | 
				
			||||||
 | 
							for alg_set in alg_sets:
 | 
				
			||||||
 | 
								alg_type, alg_list = alg_set
 | 
				
			||||||
			for alg_name in alg_list:
 | 
								for alg_name in alg_list:
 | 
				
			||||||
				alg_desc = alg_db[alg_type].get(alg_name)
 | 
									alg_desc = alg_db[alg_type].get(alg_name)
 | 
				
			||||||
				if alg_desc is None:
 | 
									if alg_desc is None:
 | 
				
			||||||
@@ -1110,6 +1273,8 @@ def get_alg_since_text(alg_desc):
 | 
				
			|||||||
		ssh_prefix, ssh_version = get_ssh_version(v)
 | 
							ssh_prefix, ssh_version = get_ssh_version(v)
 | 
				
			||||||
		if not ssh_version:
 | 
							if not ssh_version:
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
 | 
							if ssh_prefix in [SSH.Product.LibSSH]:
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
		if ssh_version.endswith('C'):
 | 
							if ssh_version.endswith('C'):
 | 
				
			||||||
			ssh_version = '{0} (client only)'.format(ssh_version[:-1])
 | 
								ssh_version = '{0} (client only)'.format(ssh_version[:-1])
 | 
				
			||||||
		tv.append('{0} {1}'.format(ssh_prefix, ssh_version))
 | 
							tv.append('{0} {1}'.format(ssh_prefix, ssh_version))
 | 
				
			||||||
@@ -1118,6 +1283,117 @@ def get_alg_since_text(alg_desc):
 | 
				
			|||||||
	return 'available since ' + ', '.join(tv).rstrip(', ')
 | 
						return 'available since ' + ', '.join(tv).rstrip(', ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_alg_pairs(kex, pkm):
 | 
				
			||||||
 | 
						alg_pairs = []
 | 
				
			||||||
 | 
						if pkm is not None:
 | 
				
			||||||
 | 
							alg_pairs.append(((1, SSH1.KexDB.ALGORITHMS),
 | 
				
			||||||
 | 
							                  ('key', ['ssh-rsa1']),
 | 
				
			||||||
 | 
							                  ('enc', pkm.supported_ciphers),
 | 
				
			||||||
 | 
							                  ('aut', pkm.supported_authentications)))
 | 
				
			||||||
 | 
						if kex is not None:
 | 
				
			||||||
 | 
							alg_pairs.append(((2, KexDB.ALGORITHMS),
 | 
				
			||||||
 | 
							                  ('kex', kex.kex_algorithms),
 | 
				
			||||||
 | 
							                  ('key', kex.key_algorithms),
 | 
				
			||||||
 | 
							                  ('enc', kex.server.encryption),
 | 
				
			||||||
 | 
							                  ('mac', kex.server.mac)))
 | 
				
			||||||
 | 
						return alg_pairs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_alg_recommendations(software, kex, pkm, for_server=True):
 | 
				
			||||||
 | 
						alg_pairs = get_alg_pairs(kex, pkm)
 | 
				
			||||||
 | 
						vproducts = [SSH.Product.OpenSSH,
 | 
				
			||||||
 | 
						             SSH.Product.DropbearSSH,
 | 
				
			||||||
 | 
						             SSH.Product.LibSSH]
 | 
				
			||||||
 | 
						if software is not None:
 | 
				
			||||||
 | 
							if software.product not in vproducts:
 | 
				
			||||||
 | 
								software = None
 | 
				
			||||||
 | 
						if software is None:
 | 
				
			||||||
 | 
							ssh_timeframe = get_ssh_timeframe(alg_pairs, for_server)
 | 
				
			||||||
 | 
							for product in vproducts:
 | 
				
			||||||
 | 
								if product not in ssh_timeframe:
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								version = ssh_timeframe[product][0]
 | 
				
			||||||
 | 
								if version is not None:
 | 
				
			||||||
 | 
									software = SSH.Software(None, product, version, None, None)
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
						rec = {'.software': software}
 | 
				
			||||||
 | 
						if software is None:
 | 
				
			||||||
 | 
							return rec
 | 
				
			||||||
 | 
						for alg_pair in alg_pairs:
 | 
				
			||||||
 | 
							sshv, alg_db = alg_pair[0]
 | 
				
			||||||
 | 
							alg_sets = alg_pair[1:]
 | 
				
			||||||
 | 
							rec[sshv] = {}
 | 
				
			||||||
 | 
							for alg_set in alg_sets:
 | 
				
			||||||
 | 
								alg_type, alg_list = alg_set
 | 
				
			||||||
 | 
								if alg_type == 'aut':
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								rec[sshv][alg_type] = {'add': [], 'del': {}}
 | 
				
			||||||
 | 
								for n, alg_desc in alg_db[alg_type].items():
 | 
				
			||||||
 | 
									if alg_type == 'key' and '-cert-' in n:
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									versions = alg_desc[0]
 | 
				
			||||||
 | 
									if len(versions) == 0 or versions[0] is None:
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									matches = False
 | 
				
			||||||
 | 
									for v in versions[0].split(','):
 | 
				
			||||||
 | 
										ssh_prefix, ssh_version = get_ssh_version(v)
 | 
				
			||||||
 | 
										if not ssh_version:
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										if ssh_prefix != software.product:
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										if ssh_version.endswith('C'):
 | 
				
			||||||
 | 
											if for_server:
 | 
				
			||||||
 | 
												continue
 | 
				
			||||||
 | 
											ssh_version = ssh_version[:-1]
 | 
				
			||||||
 | 
										if software.compare_version(ssh_version) < 0:
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										matches = True
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									if not matches:
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									adl, faults = len(alg_desc), 0
 | 
				
			||||||
 | 
									for i in range(1, 3):
 | 
				
			||||||
 | 
										if not adl > i:
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										fc = len(alg_desc[i])
 | 
				
			||||||
 | 
										if fc > 0:
 | 
				
			||||||
 | 
											faults += pow(10, 2 - i) * fc
 | 
				
			||||||
 | 
									if n not in alg_list:
 | 
				
			||||||
 | 
										if faults > 0:
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										rec[sshv][alg_type]['add'].append(n)
 | 
				
			||||||
 | 
									else:
 | 
				
			||||||
 | 
										if faults == 0:
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										if n == 'diffie-hellman-group-exchange-sha256':
 | 
				
			||||||
 | 
											if software.compare_version('7.3') < 0:
 | 
				
			||||||
 | 
												continue
 | 
				
			||||||
 | 
										rec[sshv][alg_type]['del'][n] = faults
 | 
				
			||||||
 | 
								add_count = len(rec[sshv][alg_type]['add'])
 | 
				
			||||||
 | 
								del_count = len(rec[sshv][alg_type]['del'])
 | 
				
			||||||
 | 
								new_alg_count = len(alg_list) + add_count - del_count
 | 
				
			||||||
 | 
								if new_alg_count < 1 and del_count > 0:
 | 
				
			||||||
 | 
									mf, new_del = min(rec[sshv][alg_type]['del'].values()), {}
 | 
				
			||||||
 | 
									for k, v in rec[sshv][alg_type]['del'].items():
 | 
				
			||||||
 | 
										if v != mf:
 | 
				
			||||||
 | 
											new_del[k] = v
 | 
				
			||||||
 | 
									if del_count != len(new_del):
 | 
				
			||||||
 | 
										rec[sshv][alg_type]['del'] = new_del
 | 
				
			||||||
 | 
										new_alg_count += del_count - len(new_del)
 | 
				
			||||||
 | 
								if new_alg_count < 1:
 | 
				
			||||||
 | 
									del rec[sshv][alg_type]
 | 
				
			||||||
 | 
								else:
 | 
				
			||||||
 | 
									if add_count == 0:
 | 
				
			||||||
 | 
										del rec[sshv][alg_type]['add']
 | 
				
			||||||
 | 
									if del_count == 0:
 | 
				
			||||||
 | 
										del rec[sshv][alg_type]['del']
 | 
				
			||||||
 | 
									if len(rec[sshv][alg_type]) == 0:
 | 
				
			||||||
 | 
										del rec[sshv][alg_type]
 | 
				
			||||||
 | 
							if len(rec[sshv]) == 0:
 | 
				
			||||||
 | 
								del rec[sshv]
 | 
				
			||||||
 | 
						return rec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def output_algorithms(title, alg_db, alg_type, algorithms, maxlen=0):
 | 
					def output_algorithms(title, alg_db, alg_type, algorithms, maxlen=0):
 | 
				
			||||||
	with OutputBuffer() as obuf:
 | 
						with OutputBuffer() as obuf:
 | 
				
			||||||
		for algorithm in algorithms:
 | 
							for algorithm in algorithms:
 | 
				
			||||||
@@ -1134,6 +1410,8 @@ def output_algorithm(alg_db, alg_type, alg_name, alg_max_len=0):
 | 
				
			|||||||
		alg_max_len = len(alg_name)
 | 
							alg_max_len = len(alg_name)
 | 
				
			||||||
	padding = '' if out.batch else ' ' * (alg_max_len - len(alg_name))
 | 
						padding = '' if out.batch else ' ' * (alg_max_len - len(alg_name))
 | 
				
			||||||
	texts = []
 | 
						texts = []
 | 
				
			||||||
 | 
						if len(alg_name.strip()) == 0:
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	if alg_name in alg_db[alg_type]:
 | 
						if alg_name in alg_db[alg_type]:
 | 
				
			||||||
		alg_desc = alg_db[alg_type][alg_name]
 | 
							alg_desc = alg_db[alg_type][alg_name]
 | 
				
			||||||
		ldesc = len(alg_desc)
 | 
							ldesc = len(alg_desc)
 | 
				
			||||||
@@ -1167,18 +1445,7 @@ def output_algorithm(alg_db, alg_type, alg_name, alg_max_len=0):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def output_compatibility(kex, pkm, for_server=True):
 | 
					def output_compatibility(kex, pkm, for_server=True):
 | 
				
			||||||
	alg_pairs = []
 | 
						alg_pairs = get_alg_pairs(kex, pkm)
 | 
				
			||||||
	if pkm is not None:
 | 
					 | 
				
			||||||
		alg_pairs.append((SSH1.KexDB.ALGORITHMS,
 | 
					 | 
				
			||||||
		                  {'key': ['ssh-rsa1'],
 | 
					 | 
				
			||||||
		                   'enc': pkm.supported_ciphers,
 | 
					 | 
				
			||||||
		                   'aut': pkm.supported_authentications}))
 | 
					 | 
				
			||||||
	if kex is not None:
 | 
					 | 
				
			||||||
		alg_pairs.append((KexDB.ALGORITHMS,
 | 
					 | 
				
			||||||
		                  {'kex': kex.kex_algorithms,
 | 
					 | 
				
			||||||
		                   'key': kex.key_algorithms,
 | 
					 | 
				
			||||||
		                   'enc': kex.server.encryption,
 | 
					 | 
				
			||||||
		                   'mac': kex.server.mac}))
 | 
					 | 
				
			||||||
	ssh_timeframe = get_ssh_timeframe(alg_pairs, for_server)
 | 
						ssh_timeframe = get_ssh_timeframe(alg_pairs, for_server)
 | 
				
			||||||
	vp = 1 if for_server else 2
 | 
						vp = 1 if for_server else 2
 | 
				
			||||||
	comp_text = []
 | 
						comp_text = []
 | 
				
			||||||
@@ -1210,7 +1477,8 @@ def output_security_sub(sub, software, padlen):
 | 
				
			|||||||
			continue
 | 
								continue
 | 
				
			||||||
		target, name = line[2:4]
 | 
							target, name = line[2:4]
 | 
				
			||||||
		is_server, is_client = target & 1 == 1, target & 2 == 2
 | 
							is_server, is_client = target & 1 == 1, target & 2 == 2
 | 
				
			||||||
		if is_client:
 | 
							is_local = target & 4 == 4
 | 
				
			||||||
 | 
							if not is_server:
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		p = '' if out.batch else ' ' * (padlen - len(name))
 | 
							p = '' if out.batch else ' ' * (padlen - len(name))
 | 
				
			||||||
		if sub == 'cve':
 | 
							if sub == 'cve':
 | 
				
			||||||
@@ -1252,6 +1520,38 @@ def output_fingerprint(kex, pkm, sha256=True, padlen=0):
 | 
				
			|||||||
		out.sep()
 | 
							out.sep()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def output_recommendations(software, kex, pkm, padlen=0):
 | 
				
			||||||
 | 
						for_server = True
 | 
				
			||||||
 | 
						with OutputBuffer() as obuf:
 | 
				
			||||||
 | 
							alg_rec = get_alg_recommendations(software, kex, pkm, for_server)
 | 
				
			||||||
 | 
							software = alg_rec['.software']
 | 
				
			||||||
 | 
							for sshv in range(2, 0, -1):
 | 
				
			||||||
 | 
								if sshv not in alg_rec:
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								for alg_type in ['kex', 'key', 'enc', 'mac']:
 | 
				
			||||||
 | 
									if alg_type not in alg_rec[sshv]:
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									for action in ['del', 'add']:
 | 
				
			||||||
 | 
										if action not in alg_rec[sshv][alg_type]:
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										for name in alg_rec[sshv][alg_type][action]:
 | 
				
			||||||
 | 
											p = '' if out.batch else ' ' * (padlen - len(name))
 | 
				
			||||||
 | 
											if action == 'del':
 | 
				
			||||||
 | 
												an, sg, fn = 'remove', '-', out.warn
 | 
				
			||||||
 | 
												if alg_rec[sshv][alg_type][action][name] >= 10:
 | 
				
			||||||
 | 
													fn = out.fail
 | 
				
			||||||
 | 
											else:
 | 
				
			||||||
 | 
												an, sg, fn = 'append', '+', out.good
 | 
				
			||||||
 | 
											b = '(SSH{0})'.format(sshv) if sshv == 1 else ''
 | 
				
			||||||
 | 
											fm = '(rec) {0}{1}{2}-- {3} algorithm to {4} {5}'
 | 
				
			||||||
 | 
											fn(fm.format(sg, name, p, alg_type, an, b))
 | 
				
			||||||
 | 
						if len(obuf) > 0:
 | 
				
			||||||
 | 
							title = '(for {0})'.format(software.display(False)) if software else ''
 | 
				
			||||||
 | 
							out.head('# algorithm recommendations {0}'.format(title))
 | 
				
			||||||
 | 
							obuf.flush()
 | 
				
			||||||
 | 
							out.sep()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def output(banner, header, kex=None, pkm=None):
 | 
					def output(banner, header, kex=None, pkm=None):
 | 
				
			||||||
	sshv = 1 if pkm else 2
 | 
						sshv = 1 if pkm else 2
 | 
				
			||||||
	with OutputBuffer() as obuf:
 | 
						with OutputBuffer() as obuf:
 | 
				
			||||||
@@ -1264,6 +1564,8 @@ def output(banner, header, kex=None, pkm=None):
 | 
				
			|||||||
			software = SSH.Software.parse(banner)
 | 
								software = SSH.Software.parse(banner)
 | 
				
			||||||
			if software is not None:
 | 
								if software is not None:
 | 
				
			||||||
				out.good('(gen) software: {0}'.format(software))
 | 
									out.good('(gen) software: {0}'.format(software))
 | 
				
			||||||
 | 
							else:
 | 
				
			||||||
 | 
								software = None
 | 
				
			||||||
		output_compatibility(kex, pkm)
 | 
							output_compatibility(kex, pkm)
 | 
				
			||||||
		if kex is not None:
 | 
							if kex is not None:
 | 
				
			||||||
			compressions = [x for x in kex.server.compression if x != 'none']
 | 
								compressions = [x for x in kex.server.compression if x != 'none']
 | 
				
			||||||
@@ -1287,6 +1589,7 @@ def output(banner, header, kex=None, pkm=None):
 | 
				
			|||||||
		             ml(kex.server.encryption),
 | 
							             ml(kex.server.encryption),
 | 
				
			||||||
		             ml(kex.server.mac),
 | 
							             ml(kex.server.mac),
 | 
				
			||||||
		             maxlen)
 | 
							             maxlen)
 | 
				
			||||||
 | 
						maxlen += 1
 | 
				
			||||||
	output_security(banner, maxlen)
 | 
						output_security(banner, maxlen)
 | 
				
			||||||
	if pkm is not None:
 | 
						if pkm is not None:
 | 
				
			||||||
		adb = SSH1.KexDB.ALGORITHMS
 | 
							adb = SSH1.KexDB.ALGORITHMS
 | 
				
			||||||
@@ -1308,59 +1611,36 @@ def output(banner, header, kex=None, pkm=None):
 | 
				
			|||||||
		output_algorithms(title, adb, atype, kex.server.encryption, maxlen)
 | 
							output_algorithms(title, adb, atype, kex.server.encryption, maxlen)
 | 
				
			||||||
		title, atype = 'message authentication code algorithms', 'mac'
 | 
							title, atype = 'message authentication code algorithms', 'mac'
 | 
				
			||||||
		output_algorithms(title, adb, atype, kex.server.mac, maxlen)
 | 
							output_algorithms(title, adb, atype, kex.server.mac, maxlen)
 | 
				
			||||||
 | 
						output_recommendations(software, kex, pkm, maxlen)
 | 
				
			||||||
	output_fingerprint(kex, pkm, True, maxlen)
 | 
						output_fingerprint(kex, pkm, True, maxlen)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_int(v):
 | 
					class Utils(object):
 | 
				
			||||||
 | 
						PY2 = sys.version_info[0] == 2
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						@classmethod
 | 
				
			||||||
 | 
						def wrap(cls):
 | 
				
			||||||
 | 
							o = cls()
 | 
				
			||||||
 | 
							if cls.PY2:
 | 
				
			||||||
 | 
								import StringIO
 | 
				
			||||||
 | 
								o.StringIO = o.BytesIO = StringIO.StringIO
 | 
				
			||||||
 | 
							else:
 | 
				
			||||||
 | 
								o.StringIO, o.BytesIO = io.StringIO, io.BytesIO
 | 
				
			||||||
 | 
							return o
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						@staticmethod
 | 
				
			||||||
 | 
						def parse_int(v):
 | 
				
			||||||
		try:
 | 
							try:
 | 
				
			||||||
			return int(v)
 | 
								return int(v)
 | 
				
			||||||
		except:
 | 
							except:
 | 
				
			||||||
			return 0
 | 
								return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse_args():
 | 
					 | 
				
			||||||
	conf = AuditConf()
 | 
					 | 
				
			||||||
	try:
 | 
					 | 
				
			||||||
		sopts = 'h12bnvl:'
 | 
					 | 
				
			||||||
		lopts = ['help', 'ssh1', 'ssh2', 'batch', 'no-colors', 'verbose', 'level=']
 | 
					 | 
				
			||||||
		opts, args = getopt.getopt(sys.argv[1:], sopts, lopts)
 | 
					 | 
				
			||||||
	except getopt.GetoptError as err:
 | 
					 | 
				
			||||||
		usage(str(err))
 | 
					 | 
				
			||||||
	for o, a in opts:
 | 
					 | 
				
			||||||
		if o in ('-h', '--help'):
 | 
					 | 
				
			||||||
			usage()
 | 
					 | 
				
			||||||
		elif o in ('-1', '--ssh1'):
 | 
					 | 
				
			||||||
			conf.ssh1 = True
 | 
					 | 
				
			||||||
		elif o in ('-2', '--ssh2'):
 | 
					 | 
				
			||||||
			conf.ssh2 = True
 | 
					 | 
				
			||||||
		elif o in ('-b', '--batch'):
 | 
					 | 
				
			||||||
			out.batch = True
 | 
					 | 
				
			||||||
			out.verbose = True
 | 
					 | 
				
			||||||
		elif o in ('-n', '--no-colors'):
 | 
					 | 
				
			||||||
			out.colors = False
 | 
					 | 
				
			||||||
		elif o in ('-v', '--verbose'):
 | 
					 | 
				
			||||||
			out.verbose = True
 | 
					 | 
				
			||||||
		elif o in ('-l', '--level'):
 | 
					 | 
				
			||||||
			if a not in ('info', 'warn', 'fail'):
 | 
					 | 
				
			||||||
				usage('level ' + a + ' is not valid')
 | 
					 | 
				
			||||||
			out.minlevel = a
 | 
					 | 
				
			||||||
	if len(args) == 0:
 | 
					 | 
				
			||||||
		usage()
 | 
					 | 
				
			||||||
	s = args[0].split(':')
 | 
					 | 
				
			||||||
	host, port = s[0].strip(), 22
 | 
					 | 
				
			||||||
	if len(s) > 1:
 | 
					 | 
				
			||||||
		port = parse_int(s[1])
 | 
					 | 
				
			||||||
	if not host or port <= 0:
 | 
					 | 
				
			||||||
		usage('port {0} is not valid'.format(port))
 | 
					 | 
				
			||||||
	conf.host = host
 | 
					 | 
				
			||||||
	conf.port = port
 | 
					 | 
				
			||||||
	if not (conf.ssh1 or conf.ssh2):
 | 
					 | 
				
			||||||
		conf.ssh1 = True
 | 
					 | 
				
			||||||
		conf.ssh2 = True
 | 
					 | 
				
			||||||
	return conf
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def audit(conf, sshv=None):
 | 
					def audit(conf, sshv=None):
 | 
				
			||||||
 | 
						out.batch = conf.batch
 | 
				
			||||||
 | 
						out.colors = conf.colors
 | 
				
			||||||
 | 
						out.verbose = conf.verbose
 | 
				
			||||||
 | 
						out.minlevel = conf.minlevel
 | 
				
			||||||
	s = SSH.Socket(conf.host, conf.port)
 | 
						s = SSH.Socket(conf.host, conf.port)
 | 
				
			||||||
	if sshv is None:
 | 
						if sshv is None:
 | 
				
			||||||
		sshv = 2 if conf.ssh2 else 1
 | 
							sshv = 2 if conf.ssh2 else 1
 | 
				
			||||||
@@ -1371,7 +1651,8 @@ def audit(conf, sshv=None):
 | 
				
			|||||||
	if err is None:
 | 
						if err is None:
 | 
				
			||||||
		packet_type, payload = s.read_packet(sshv)
 | 
							packet_type, payload = s.read_packet(sshv)
 | 
				
			||||||
		if packet_type < 0:
 | 
							if packet_type < 0:
 | 
				
			||||||
			if payload == b'Protocol major versions differ.':
 | 
								payload = str(payload).decode('utf-8')
 | 
				
			||||||
 | 
								if payload == u'Protocol major versions differ.':
 | 
				
			||||||
				if sshv == 2 and conf.ssh1:
 | 
									if sshv == 2 and conf.ssh1:
 | 
				
			||||||
					audit(conf, 1)
 | 
										audit(conf, 1)
 | 
				
			||||||
					return
 | 
										return
 | 
				
			||||||
@@ -1393,11 +1674,12 @@ def audit(conf, sshv=None):
 | 
				
			|||||||
		pkm = SSH1.PublicKeyMessage.parse(payload)
 | 
							pkm = SSH1.PublicKeyMessage.parse(payload)
 | 
				
			||||||
		output(banner, header, pkm=pkm)
 | 
							output(banner, header, pkm=pkm)
 | 
				
			||||||
	elif sshv == 2:
 | 
						elif sshv == 2:
 | 
				
			||||||
		kex = Kex.parse(payload)
 | 
							kex = SSH2.Kex.parse(payload)
 | 
				
			||||||
		output(banner, header, kex=kex)
 | 
							output(banner, header, kex=kex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					utils = Utils.wrap()
 | 
				
			||||||
 | 
					out = Output()
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == '__main__':
 | 
				
			||||||
	out = Output()
 | 
						conf = AuditConf.from_cmdline(sys.argv[1:], usage)
 | 
				
			||||||
	conf = parse_args()
 | 
					 | 
				
			||||||
	audit(conf)
 | 
						audit(conf)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,35 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
# -*- coding: utf-8 -*-
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
import os, sys
 | 
					import pytest, os, sys, io
 | 
				
			||||||
import pytest
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if sys.version_info[0] == 2:
 | 
				
			||||||
 | 
						import StringIO
 | 
				
			||||||
 | 
						StringIO = StringIO.StringIO
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
						StringIO = io.StringIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.fixture(scope='module')
 | 
					@pytest.fixture(scope='module')
 | 
				
			||||||
def ssh_audit():
 | 
					def ssh_audit():
 | 
				
			||||||
	__rdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
 | 
						__rdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
 | 
				
			||||||
	sys.path.append(os.path.abspath(__rdir))
 | 
						sys.path.append(os.path.abspath(__rdir))
 | 
				
			||||||
	return __import__('ssh-audit')
 | 
						return __import__('ssh-audit')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _OutputSpy(list):
 | 
				
			||||||
 | 
						def begin(self):
 | 
				
			||||||
 | 
							self.__out = StringIO()
 | 
				
			||||||
 | 
							self.__old_stdout = sys.stdout
 | 
				
			||||||
 | 
							sys.stdout = self.__out
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
						def flush(self):
 | 
				
			||||||
 | 
							lines = self.__out.getvalue().splitlines()
 | 
				
			||||||
 | 
							sys.stdout = self.__old_stdout
 | 
				
			||||||
 | 
							self.__out = None
 | 
				
			||||||
 | 
							return lines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture(scope='module')
 | 
				
			||||||
 | 
					def output_spy():
 | 
				
			||||||
 | 
						return _OutputSpy()
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										115
									
								
								test/test_auditconf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								test/test_auditconf.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestAuditConf(object):
 | 
				
			||||||
 | 
						@pytest.fixture(autouse=True)
 | 
				
			||||||
 | 
						def init(self, ssh_audit):
 | 
				
			||||||
 | 
							self.AuditConf = ssh_audit.AuditConf
 | 
				
			||||||
 | 
							self.usage = ssh_audit.usage
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def _test_conf(self, conf, **kwargs):
 | 
				
			||||||
 | 
							options = {
 | 
				
			||||||
 | 
								'host': None,
 | 
				
			||||||
 | 
								'port': 22,
 | 
				
			||||||
 | 
								'ssh1': True,
 | 
				
			||||||
 | 
								'ssh2': True,
 | 
				
			||||||
 | 
								'batch': False,
 | 
				
			||||||
 | 
								'colors': True,
 | 
				
			||||||
 | 
								'verbose': False,
 | 
				
			||||||
 | 
								'minlevel': 'info'
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for k, v in kwargs.items():
 | 
				
			||||||
 | 
								options[k] = v
 | 
				
			||||||
 | 
							assert conf.host == options['host']
 | 
				
			||||||
 | 
							assert conf.port == options['port']
 | 
				
			||||||
 | 
							assert conf.ssh1 is options['ssh1']
 | 
				
			||||||
 | 
							assert conf.ssh2 is options['ssh2']
 | 
				
			||||||
 | 
							assert conf.batch is options['batch']
 | 
				
			||||||
 | 
							assert conf.colors is options['colors']
 | 
				
			||||||
 | 
							assert conf.verbose is options['verbose']
 | 
				
			||||||
 | 
							assert conf.minlevel == options['minlevel']
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_audit_conf_defaults(self):
 | 
				
			||||||
 | 
							conf = self.AuditConf()
 | 
				
			||||||
 | 
							self._test_conf(conf)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_audit_conf_booleans(self):
 | 
				
			||||||
 | 
							conf = self.AuditConf()
 | 
				
			||||||
 | 
							for p in ['ssh1', 'ssh2', 'batch', 'colors', 'verbose']:
 | 
				
			||||||
 | 
								for v in [True, 1]:
 | 
				
			||||||
 | 
									setattr(conf, p, v)
 | 
				
			||||||
 | 
									assert getattr(conf, p) is True
 | 
				
			||||||
 | 
								for v in [False, 0]:
 | 
				
			||||||
 | 
									setattr(conf, p, v)
 | 
				
			||||||
 | 
									assert getattr(conf, p) is False
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_audit_conf_port(self):
 | 
				
			||||||
 | 
							conf = self.AuditConf()
 | 
				
			||||||
 | 
							for port in [22, 2222]:
 | 
				
			||||||
 | 
								conf.port = port
 | 
				
			||||||
 | 
								assert conf.port == port
 | 
				
			||||||
 | 
							for port in [-1, 0, 65536, 99999]:
 | 
				
			||||||
 | 
								with pytest.raises(ValueError) as excinfo:
 | 
				
			||||||
 | 
									conf.port = port
 | 
				
			||||||
 | 
								excinfo.match(r'.*invalid port.*')
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_audit_conf_minlevel(self):
 | 
				
			||||||
 | 
							conf = self.AuditConf()
 | 
				
			||||||
 | 
							for level in ['info', 'warn', 'fail']:
 | 
				
			||||||
 | 
								conf.minlevel = level
 | 
				
			||||||
 | 
								assert conf.minlevel == level
 | 
				
			||||||
 | 
							for level in ['head', 'good', 'unknown', None]:
 | 
				
			||||||
 | 
								with pytest.raises(ValueError) as excinfo:
 | 
				
			||||||
 | 
									conf.minlevel = level
 | 
				
			||||||
 | 
								excinfo.match(r'.*invalid level.*')
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_audit_conf_cmdline(self):
 | 
				
			||||||
 | 
							c = lambda x: self.AuditConf.from_cmdline(x.split(), self.usage)
 | 
				
			||||||
 | 
							with pytest.raises(SystemExit):
 | 
				
			||||||
 | 
								conf = c('')
 | 
				
			||||||
 | 
							with pytest.raises(SystemExit):
 | 
				
			||||||
 | 
								conf = c('-x')
 | 
				
			||||||
 | 
							with pytest.raises(SystemExit):
 | 
				
			||||||
 | 
								conf = c('-h')
 | 
				
			||||||
 | 
							with pytest.raises(SystemExit):
 | 
				
			||||||
 | 
								conf = c('--help')
 | 
				
			||||||
 | 
							with pytest.raises(SystemExit):
 | 
				
			||||||
 | 
								conf = c(':')
 | 
				
			||||||
 | 
							with pytest.raises(SystemExit):
 | 
				
			||||||
 | 
								conf = c(':22')
 | 
				
			||||||
 | 
							conf = c('localhost')
 | 
				
			||||||
 | 
							self._test_conf(conf, host='localhost')
 | 
				
			||||||
 | 
							conf = c('github.com')
 | 
				
			||||||
 | 
							self._test_conf(conf, host='github.com')
 | 
				
			||||||
 | 
							conf = c('localhost:2222')
 | 
				
			||||||
 | 
							self._test_conf(conf, host='localhost', port=2222)
 | 
				
			||||||
 | 
							with pytest.raises(SystemExit):
 | 
				
			||||||
 | 
								conf = c('localhost:')
 | 
				
			||||||
 | 
							with pytest.raises(SystemExit):
 | 
				
			||||||
 | 
								conf = c('localhost:abc')
 | 
				
			||||||
 | 
							with pytest.raises(SystemExit):
 | 
				
			||||||
 | 
								conf = c('localhost:-22')
 | 
				
			||||||
 | 
							with pytest.raises(SystemExit):
 | 
				
			||||||
 | 
								conf = c('localhost:99999')
 | 
				
			||||||
 | 
							conf = c('-1 localhost')
 | 
				
			||||||
 | 
							self._test_conf(conf, host='localhost', ssh1=True, ssh2=False)
 | 
				
			||||||
 | 
							conf = c('-2 localhost')
 | 
				
			||||||
 | 
							self._test_conf(conf, host='localhost', ssh1=False, ssh2=True)
 | 
				
			||||||
 | 
							conf = c('-12 localhost')
 | 
				
			||||||
 | 
							self._test_conf(conf, host='localhost', ssh1=True, ssh2=True)
 | 
				
			||||||
 | 
							conf = c('-b localhost')
 | 
				
			||||||
 | 
							self._test_conf(conf, host='localhost', batch=True, verbose=True)
 | 
				
			||||||
 | 
							conf = c('-n localhost')
 | 
				
			||||||
 | 
							self._test_conf(conf, host='localhost', colors=False)
 | 
				
			||||||
 | 
							conf = c('-v localhost')
 | 
				
			||||||
 | 
							self._test_conf(conf, host='localhost', verbose=True)
 | 
				
			||||||
 | 
							conf = c('-l info localhost')
 | 
				
			||||||
 | 
							self._test_conf(conf, host='localhost', minlevel='info')
 | 
				
			||||||
 | 
							conf = c('-l warn localhost')
 | 
				
			||||||
 | 
							self._test_conf(conf, host='localhost', minlevel='warn')
 | 
				
			||||||
 | 
							conf = c('-l fail localhost')
 | 
				
			||||||
 | 
							self._test_conf(conf, host='localhost', minlevel='fail')
 | 
				
			||||||
 | 
							with pytest.raises(SystemExit):
 | 
				
			||||||
 | 
								conf = c('-l something localhost')
 | 
				
			||||||
							
								
								
									
										68
									
								
								test/test_banner.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								test/test_banner.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestBanner(object):
 | 
				
			||||||
 | 
						@pytest.fixture(autouse=True)
 | 
				
			||||||
 | 
						def init(self, ssh_audit):
 | 
				
			||||||
 | 
							self.ssh = ssh_audit.SSH
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_simple_banners(self):
 | 
				
			||||||
 | 
							banner = lambda x: self.ssh.Banner.parse(x)
 | 
				
			||||||
 | 
							b = banner('SSH-2.0-OpenSSH_7.3')
 | 
				
			||||||
 | 
							assert b.protocol == (2, 0)
 | 
				
			||||||
 | 
							assert b.software == 'OpenSSH_7.3'
 | 
				
			||||||
 | 
							assert b.comments is None
 | 
				
			||||||
 | 
							assert str(b) == 'SSH-2.0-OpenSSH_7.3'
 | 
				
			||||||
 | 
							b = banner('SSH-1.99-Sun_SSH_1.1.3')
 | 
				
			||||||
 | 
							assert b.protocol == (1, 99)
 | 
				
			||||||
 | 
							assert b.software == 'Sun_SSH_1.1.3'
 | 
				
			||||||
 | 
							assert b.comments is None
 | 
				
			||||||
 | 
							assert str(b) == 'SSH-1.99-Sun_SSH_1.1.3'
 | 
				
			||||||
 | 
							b = banner('SSH-1.5-Cisco-1.25')
 | 
				
			||||||
 | 
							assert b.protocol == (1, 5)
 | 
				
			||||||
 | 
							assert b.software == 'Cisco-1.25'
 | 
				
			||||||
 | 
							assert b.comments is None
 | 
				
			||||||
 | 
							assert str(b) == 'SSH-1.5-Cisco-1.25'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_invalid_banners(self):
 | 
				
			||||||
 | 
							b = lambda x: self.ssh.Banner.parse(x)
 | 
				
			||||||
 | 
							assert b('Something') is None
 | 
				
			||||||
 | 
							assert b('SSH-XXX-OpenSSH_7.3') is None
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_banners_with_spaces(self):
 | 
				
			||||||
 | 
							b = lambda x: self.ssh.Banner.parse(x)
 | 
				
			||||||
 | 
							s = 'SSH-2.0-OpenSSH_4.3p2'
 | 
				
			||||||
 | 
							assert str(b('SSH-2.0-OpenSSH_4.3p2    ')) == s
 | 
				
			||||||
 | 
							assert str(b('SSH-2.0-    OpenSSH_4.3p2')) == s
 | 
				
			||||||
 | 
							assert str(b('SSH-2.0-  OpenSSH_4.3p2  ')) == s
 | 
				
			||||||
 | 
							s = 'SSH-2.0-OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu'
 | 
				
			||||||
 | 
							assert str(b('SSH-2.0-  OpenSSH_4.3p2 Debian-9etch3   on i686-pc-linux-gnu')) == s
 | 
				
			||||||
 | 
							assert str(b('SSH-2.0-OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu  ')) == s
 | 
				
			||||||
 | 
							assert str(b('SSH-2.0-  OpenSSH_4.3p2 Debian-9etch3   on   i686-pc-linux-gnu  ')) == s
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_banners_without_software(self):
 | 
				
			||||||
 | 
							b = lambda x: self.ssh.Banner.parse(x)
 | 
				
			||||||
 | 
							assert b('SSH-2.0').protocol == (2, 0)
 | 
				
			||||||
 | 
							assert b('SSH-2.0').software is None
 | 
				
			||||||
 | 
							assert b('SSH-2.0').comments is None
 | 
				
			||||||
 | 
							assert str(b('SSH-2.0')) == 'SSH-2.0'
 | 
				
			||||||
 | 
							assert b('SSH-2.0-').protocol == (2, 0)
 | 
				
			||||||
 | 
							assert b('SSH-2.0-').software == ''
 | 
				
			||||||
 | 
							assert b('SSH-2.0-').comments is None
 | 
				
			||||||
 | 
							assert str(b('SSH-2.0-')) == 'SSH-2.0-'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_banners_with_comments(self):
 | 
				
			||||||
 | 
							b = lambda x: self.ssh.Banner.parse(x)
 | 
				
			||||||
 | 
							assert repr(b('SSH-2.0-OpenSSH_7.2p2 Ubuntu-1')) == '<Banner(protocol=2.0, software=OpenSSH_7.2p2, comments=Ubuntu-1)>'
 | 
				
			||||||
 | 
							assert repr(b('SSH-1.99-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3')) == '<Banner(protocol=1.99, software=OpenSSH_3.4p1, comments=Debian 1:3.4p1-1.woody.3)>'
 | 
				
			||||||
 | 
							assert repr(b('SSH-1.5-1.3.7 F-SECURE SSH')) == '<Banner(protocol=1.5, software=1.3.7, comments=F-SECURE SSH)>'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_banners_with_multiple_protocols(self):
 | 
				
			||||||
 | 
							b = lambda x: self.ssh.Banner.parse(x)
 | 
				
			||||||
 | 
							assert str(b('SSH-1.99-SSH-1.99-OpenSSH_3.6.1p2')) == 'SSH-1.99-OpenSSH_3.6.1p2'
 | 
				
			||||||
 | 
							assert str(b('SSH-2.0-SSH-2.0-OpenSSH_4.3p2 Debian-9')) == 'SSH-2.0-OpenSSH_4.3p2 Debian-9'
 | 
				
			||||||
 | 
							assert str(b('SSH-1.99-SSH-2.0-dropbear_0.5')) == 'SSH-1.99-dropbear_0.5'
 | 
				
			||||||
 | 
							assert str(b('SSH-2.0-SSH-1.99-OpenSSH_4.2p1 SSH Secure Shell (non-commercial)')) == 'SSH-1.99-OpenSSH_4.2p1 SSH Secure Shell (non-commercial)'
 | 
				
			||||||
 | 
							assert str(b('SSH-1.99-SSH-1.99-SSH-1.99-OpenSSH_3.9p1')) == 'SSH-1.99-OpenSSH_3.9p1'
 | 
				
			||||||
							
								
								
									
										117
									
								
								test/test_buffer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								test/test_buffer.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestBuffer(object):
 | 
				
			||||||
 | 
						@pytest.fixture(autouse=True)
 | 
				
			||||||
 | 
						def init(self, ssh_audit):
 | 
				
			||||||
 | 
							self.rbuf = ssh_audit.ReadBuf
 | 
				
			||||||
 | 
							self.wbuf = ssh_audit.WriteBuf
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def _b(self, v):
 | 
				
			||||||
 | 
							v = re.sub(r'\s', '', v)
 | 
				
			||||||
 | 
							data = [int(v[i * 2:i * 2 + 2], 16) for i in range(len(v) // 2)]
 | 
				
			||||||
 | 
							return bytes(bytearray(data))
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_unread(self):
 | 
				
			||||||
 | 
							w = self.wbuf().write_byte(1).write_int(2).write_flush()
 | 
				
			||||||
 | 
							r = self.rbuf(w)
 | 
				
			||||||
 | 
							assert r.unread_len == 5
 | 
				
			||||||
 | 
							r.read_byte()
 | 
				
			||||||
 | 
							assert r.unread_len == 4
 | 
				
			||||||
 | 
							r.read_int()
 | 
				
			||||||
 | 
							assert r.unread_len == 0
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_byte(self):
 | 
				
			||||||
 | 
							w = lambda x: self.wbuf().write_byte(x).write_flush()
 | 
				
			||||||
 | 
							r = lambda x: self.rbuf(x).read_byte()
 | 
				
			||||||
 | 
							tc = [(0x00, '00'),
 | 
				
			||||||
 | 
							      (0x01, '01'),
 | 
				
			||||||
 | 
							      (0x10, '10'),
 | 
				
			||||||
 | 
							      (0xff, 'ff')]
 | 
				
			||||||
 | 
							for p in tc:
 | 
				
			||||||
 | 
								assert w(p[0]) == self._b(p[1])
 | 
				
			||||||
 | 
								assert r(self._b(p[1])) == p[0]
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_bool(self):
 | 
				
			||||||
 | 
							w = lambda x: self.wbuf().write_bool(x).write_flush()
 | 
				
			||||||
 | 
							r = lambda x: self.rbuf(x).read_bool()
 | 
				
			||||||
 | 
							tc = [(True,  '01'),
 | 
				
			||||||
 | 
							      (False, '00')]
 | 
				
			||||||
 | 
							for p in tc:
 | 
				
			||||||
 | 
								assert w(p[0]) == self._b(p[1])
 | 
				
			||||||
 | 
								assert r(self._b(p[1])) == p[0]
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_int(self):
 | 
				
			||||||
 | 
							w = lambda x: self.wbuf().write_int(x).write_flush()
 | 
				
			||||||
 | 
							r = lambda x: self.rbuf(x).read_int()
 | 
				
			||||||
 | 
							tc = [(0x00,       '00 00 00 00'),
 | 
				
			||||||
 | 
							      (0x01,       '00 00 00 01'),
 | 
				
			||||||
 | 
							      (0xabcd,     '00 00 ab cd'),
 | 
				
			||||||
 | 
							      (0xffffffff, 'ff ff ff ff')]
 | 
				
			||||||
 | 
							for p in tc:
 | 
				
			||||||
 | 
								assert w(p[0]) == self._b(p[1])
 | 
				
			||||||
 | 
								assert r(self._b(p[1])) == p[0]
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_string(self):
 | 
				
			||||||
 | 
							w = lambda x: self.wbuf().write_string(x).write_flush()
 | 
				
			||||||
 | 
							r = lambda x: self.rbuf(x).read_string()
 | 
				
			||||||
 | 
							tc = [(u'abc1',  '00 00 00 04 61 62 63 31'),
 | 
				
			||||||
 | 
							      (b'abc2',  '00 00 00 04 61 62 63 32')]
 | 
				
			||||||
 | 
							for p in tc:
 | 
				
			||||||
 | 
								v = p[0]
 | 
				
			||||||
 | 
								assert w(v) == self._b(p[1])
 | 
				
			||||||
 | 
								if not isinstance(v, bytes):
 | 
				
			||||||
 | 
									v = bytes(bytearray(v, 'utf-8'))
 | 
				
			||||||
 | 
								assert r(self._b(p[1])) == v
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_list(self):
 | 
				
			||||||
 | 
							w = lambda x: self.wbuf().write_list(x).write_flush()
 | 
				
			||||||
 | 
							r = lambda x: self.rbuf(x).read_list()
 | 
				
			||||||
 | 
							tc = [(['d', 'ef', 'ault'], '00 00 00 09 64 2c 65 66 2c 61 75 6c 74')]
 | 
				
			||||||
 | 
							for p in tc:
 | 
				
			||||||
 | 
								assert w(p[0]) == self._b(p[1])
 | 
				
			||||||
 | 
								assert r(self._b(p[1])) == p[0]
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_line(self):
 | 
				
			||||||
 | 
							w = lambda x: self.wbuf().write_line(x).write_flush()
 | 
				
			||||||
 | 
							r = lambda x: self.rbuf(x).read_line()
 | 
				
			||||||
 | 
							tc = [(u'example line', '65 78 61 6d 70 6c 65 20 6c 69 6e 65 0d 0a')]
 | 
				
			||||||
 | 
							for p in tc:
 | 
				
			||||||
 | 
								assert w(p[0]) == self._b(p[1])
 | 
				
			||||||
 | 
								assert r(self._b(p[1])) == p[0]
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_bitlen(self):
 | 
				
			||||||
 | 
							class Py26Int(int):
 | 
				
			||||||
 | 
								def bit_length(self):
 | 
				
			||||||
 | 
									raise AttributeError
 | 
				
			||||||
 | 
							assert self.wbuf._bitlength(42) == 6
 | 
				
			||||||
 | 
							assert self.wbuf._bitlength(Py26Int(42)) == 6
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_mpint1(self):
 | 
				
			||||||
 | 
							mpint1w = lambda x: self.wbuf().write_mpint1(x).write_flush()
 | 
				
			||||||
 | 
							mpint1r = lambda x: self.rbuf(x).read_mpint1()
 | 
				
			||||||
 | 
							tc = [(0x0,     '00 00'),
 | 
				
			||||||
 | 
							      (0x1234,  '00 0d 12 34'),
 | 
				
			||||||
 | 
							      (0x12345, '00 11 01 23 45'),
 | 
				
			||||||
 | 
							      (0xdeadbeef, '00 20 de ad be ef')]
 | 
				
			||||||
 | 
							for p in tc:
 | 
				
			||||||
 | 
								assert mpint1w(p[0]) == self._b(p[1])
 | 
				
			||||||
 | 
								assert mpint1r(self._b(p[1])) == p[0]
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_mpint2(self):
 | 
				
			||||||
 | 
							mpint2w = lambda x: self.wbuf().write_mpint2(x).write_flush()
 | 
				
			||||||
 | 
							mpint2r = lambda x: self.rbuf(x).read_mpint2()
 | 
				
			||||||
 | 
							tc = [(0x0,               '00 00 00 00'),
 | 
				
			||||||
 | 
							      (0x80,              '00 00 00 02 00 80'),
 | 
				
			||||||
 | 
							      (0x9a378f9b2e332a7, '00 00 00 08 09 a3 78 f9 b2 e3 32 a7'),
 | 
				
			||||||
 | 
							      (-0x1234,           '00 00 00 02 ed cc'),
 | 
				
			||||||
 | 
							      (-0xdeadbeef,       '00 00 00 05 ff 21 52 41 11'),
 | 
				
			||||||
 | 
							      (-0x8000,           '00 00 00 02 80 00'),
 | 
				
			||||||
 | 
							      (-0x80,             '00 00 00 01 80')]
 | 
				
			||||||
 | 
							for p in tc:
 | 
				
			||||||
 | 
								assert mpint2w(p[0]) == self._b(p[1])
 | 
				
			||||||
 | 
								assert mpint2r(self._b(p[1])) == p[0]
 | 
				
			||||||
 | 
							assert mpint2r(self._b('00 00 00 02 ff 80')) == -0x80
 | 
				
			||||||
							
								
								
									
										172
									
								
								test/test_output.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								test/test_output.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,172 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					from __future__ import print_function
 | 
				
			||||||
 | 
					import pytest, io, sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestOutput(object):
 | 
				
			||||||
 | 
						@pytest.fixture(autouse=True)
 | 
				
			||||||
 | 
						def init(self, ssh_audit):
 | 
				
			||||||
 | 
							self.Output = ssh_audit.Output
 | 
				
			||||||
 | 
							self.OutputBuffer = ssh_audit.OutputBuffer
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_output_buffer_no_lines(self, output_spy):
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							with self.OutputBuffer() as obuf:
 | 
				
			||||||
 | 
								pass
 | 
				
			||||||
 | 
							assert output_spy.flush() == []
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							with self.OutputBuffer() as obuf:
 | 
				
			||||||
 | 
								pass
 | 
				
			||||||
 | 
							obuf.flush()
 | 
				
			||||||
 | 
							assert output_spy.flush() == []
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_output_buffer_no_flush(self, output_spy):
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							with self.OutputBuffer() as obuf:
 | 
				
			||||||
 | 
								print(u'abc')
 | 
				
			||||||
 | 
							assert output_spy.flush() == []
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_output_buffer_flush(self, output_spy):
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							with self.OutputBuffer() as obuf:
 | 
				
			||||||
 | 
								print(u'abc')
 | 
				
			||||||
 | 
								print()
 | 
				
			||||||
 | 
								print(u'def')
 | 
				
			||||||
 | 
							obuf.flush()
 | 
				
			||||||
 | 
							assert output_spy.flush() == [u'abc', u'', u'def']
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_output_defaults(self):
 | 
				
			||||||
 | 
							out = self.Output()
 | 
				
			||||||
 | 
							# default: on
 | 
				
			||||||
 | 
							assert out.batch is False
 | 
				
			||||||
 | 
							assert out.colors is True
 | 
				
			||||||
 | 
							assert out.minlevel == 'info'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_output_colors(self, output_spy):
 | 
				
			||||||
 | 
							out = self.Output()
 | 
				
			||||||
 | 
							# test without colors
 | 
				
			||||||
 | 
							out.colors = False
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.info('info color')
 | 
				
			||||||
 | 
							assert output_spy.flush() == [u'info color']
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.head('head color')
 | 
				
			||||||
 | 
							assert output_spy.flush() == [u'head color']
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.good('good color')
 | 
				
			||||||
 | 
							assert output_spy.flush() == [u'good color']
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.warn('warn color')
 | 
				
			||||||
 | 
							assert output_spy.flush() == [u'warn color']
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.fail('fail color')
 | 
				
			||||||
 | 
							assert output_spy.flush() == [u'fail color']
 | 
				
			||||||
 | 
							# test with colors
 | 
				
			||||||
 | 
							out.colors = True
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.info('info color')
 | 
				
			||||||
 | 
							assert output_spy.flush() == [u'info color']
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.head('head color')
 | 
				
			||||||
 | 
							assert output_spy.flush() == [u'\x1b[0;36mhead color\x1b[0m']
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.good('good color')
 | 
				
			||||||
 | 
							assert output_spy.flush() == [u'\x1b[0;32mgood color\x1b[0m']
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.warn('warn color')
 | 
				
			||||||
 | 
							assert output_spy.flush() == [u'\x1b[0;33mwarn color\x1b[0m']
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.fail('fail color')
 | 
				
			||||||
 | 
							assert output_spy.flush() == [u'\x1b[0;31mfail color\x1b[0m']
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_output_sep(self, output_spy):
 | 
				
			||||||
 | 
							out = self.Output()
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.sep()
 | 
				
			||||||
 | 
							out.sep()
 | 
				
			||||||
 | 
							out.sep()
 | 
				
			||||||
 | 
							assert output_spy.flush() == [u'', u'', u'']
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_output_levels(self):
 | 
				
			||||||
 | 
							out = self.Output()
 | 
				
			||||||
 | 
							assert out.getlevel('info') == 0
 | 
				
			||||||
 | 
							assert out.getlevel('good') == 0
 | 
				
			||||||
 | 
							assert out.getlevel('warn') == 1
 | 
				
			||||||
 | 
							assert out.getlevel('fail') == 2
 | 
				
			||||||
 | 
							assert out.getlevel('unknown') > 2
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_output_minlevel_property(self):
 | 
				
			||||||
 | 
							out = self.Output()
 | 
				
			||||||
 | 
							out.minlevel = 'info'
 | 
				
			||||||
 | 
							assert out.minlevel == 'info'
 | 
				
			||||||
 | 
							out.minlevel = 'good'
 | 
				
			||||||
 | 
							assert out.minlevel == 'info'
 | 
				
			||||||
 | 
							out.minlevel = 'warn'
 | 
				
			||||||
 | 
							assert out.minlevel == 'warn'
 | 
				
			||||||
 | 
							out.minlevel = 'fail'
 | 
				
			||||||
 | 
							assert out.minlevel == 'fail'
 | 
				
			||||||
 | 
							out.minlevel = 'invalid level'
 | 
				
			||||||
 | 
							assert out.minlevel == 'unknown'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_output_minlevel(self, output_spy):
 | 
				
			||||||
 | 
							out = self.Output()
 | 
				
			||||||
 | 
							# visible: all
 | 
				
			||||||
 | 
							out.minlevel = 'info'
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.info('info color')
 | 
				
			||||||
 | 
							out.head('head color')
 | 
				
			||||||
 | 
							out.good('good color')
 | 
				
			||||||
 | 
							out.warn('warn color')
 | 
				
			||||||
 | 
							out.fail('fail color')
 | 
				
			||||||
 | 
							assert len(output_spy.flush()) == 5
 | 
				
			||||||
 | 
							# visible: head, warn, fail
 | 
				
			||||||
 | 
							out.minlevel = 'warn'
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.info('info color')
 | 
				
			||||||
 | 
							out.head('head color')
 | 
				
			||||||
 | 
							out.good('good color')
 | 
				
			||||||
 | 
							out.warn('warn color')
 | 
				
			||||||
 | 
							out.fail('fail color')
 | 
				
			||||||
 | 
							assert len(output_spy.flush()) == 3
 | 
				
			||||||
 | 
							# visible: head, fail
 | 
				
			||||||
 | 
							out.minlevel = 'fail'
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.info('info color')
 | 
				
			||||||
 | 
							out.head('head color')
 | 
				
			||||||
 | 
							out.good('good color')
 | 
				
			||||||
 | 
							out.warn('warn color')
 | 
				
			||||||
 | 
							out.fail('fail color')
 | 
				
			||||||
 | 
							assert len(output_spy.flush()) == 2
 | 
				
			||||||
 | 
							# visible: head
 | 
				
			||||||
 | 
							out.minlevel = 'invalid level'
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.info('info color')
 | 
				
			||||||
 | 
							out.head('head color')
 | 
				
			||||||
 | 
							out.good('good color')
 | 
				
			||||||
 | 
							out.warn('warn color')
 | 
				
			||||||
 | 
							out.fail('fail color')
 | 
				
			||||||
 | 
							assert len(output_spy.flush()) == 1
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_output_batch(self, output_spy):
 | 
				
			||||||
 | 
							out = self.Output()
 | 
				
			||||||
 | 
							# visible: all
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.minlevel = 'info'
 | 
				
			||||||
 | 
							out.batch = False
 | 
				
			||||||
 | 
							out.info('info color')
 | 
				
			||||||
 | 
							out.head('head color')
 | 
				
			||||||
 | 
							out.good('good color')
 | 
				
			||||||
 | 
							out.warn('warn color')
 | 
				
			||||||
 | 
							out.fail('fail color')
 | 
				
			||||||
 | 
							assert len(output_spy.flush()) == 5
 | 
				
			||||||
 | 
							# visible: all except head
 | 
				
			||||||
 | 
							output_spy.begin()
 | 
				
			||||||
 | 
							out.minlevel = 'info'
 | 
				
			||||||
 | 
							out.batch = True
 | 
				
			||||||
 | 
							out.info('info color')
 | 
				
			||||||
 | 
							out.head('head color')
 | 
				
			||||||
 | 
							out.good('good color')
 | 
				
			||||||
 | 
							out.warn('warn color')
 | 
				
			||||||
 | 
							out.fail('fail color')
 | 
				
			||||||
 | 
							assert len(output_spy.flush()) == 4
 | 
				
			||||||
@@ -1,42 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					 | 
				
			||||||
# -*- coding: utf-8 -*-
 | 
					 | 
				
			||||||
import pytest
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TestProtocol(object):
 | 
					 | 
				
			||||||
	@pytest.fixture(autouse=True)
 | 
					 | 
				
			||||||
	def init(self, ssh_audit):
 | 
					 | 
				
			||||||
		self.rbuf = ssh_audit.ReadBuf
 | 
					 | 
				
			||||||
		self.wbuf = ssh_audit.WriteBuf
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def _b(self, v):
 | 
					 | 
				
			||||||
		v = re.sub(r'\s', '', v)
 | 
					 | 
				
			||||||
		data = [int(v[i * 2:i * 2 + 2], 16) for i in range(len(v) // 2)]
 | 
					 | 
				
			||||||
		return bytes(bytearray(data))
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def test_mpint1(self):
 | 
					 | 
				
			||||||
		mpint1w = lambda x: self.wbuf().write_mpint1(x).write_flush()
 | 
					 | 
				
			||||||
		mpint1r = lambda x: self.rbuf(x).read_mpint1()
 | 
					 | 
				
			||||||
		tc = [(0x0,     '00 00'),
 | 
					 | 
				
			||||||
		      (0x1234,  '00 0d 12 34'),
 | 
					 | 
				
			||||||
		      (0x12345, '00 11 01 23 45'),
 | 
					 | 
				
			||||||
		      (0xdeadbeef, '00 20 de ad be ef')]
 | 
					 | 
				
			||||||
		for p in tc:
 | 
					 | 
				
			||||||
			assert mpint1w(p[0]) == self._b(p[1])
 | 
					 | 
				
			||||||
			assert mpint1r(self._b(p[1])) == p[0]
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def test_mpint2(self):
 | 
					 | 
				
			||||||
		mpint2w = lambda x: self.wbuf().write_mpint2(x).write_flush()
 | 
					 | 
				
			||||||
		mpint2r = lambda x: self.rbuf(x).read_mpint2()
 | 
					 | 
				
			||||||
		tc = [(0x0,               '00 00 00 00'),
 | 
					 | 
				
			||||||
		      (0x80,              '00 00 00 02 00 80'),
 | 
					 | 
				
			||||||
		      (0x9a378f9b2e332a7, '00 00 00 08 09 a3 78 f9 b2 e3 32 a7'),
 | 
					 | 
				
			||||||
		      (-0x1234,           '00 00 00 02 ed cc'),
 | 
					 | 
				
			||||||
		      (-0xdeadbeef,       '00 00 00 05 ff 21 52 41 11'),
 | 
					 | 
				
			||||||
		      (-0x8000,           '00 00 00 02 80 00'),
 | 
					 | 
				
			||||||
		      (-0x80,             '00 00 00 01 80')]
 | 
					 | 
				
			||||||
		for p in tc:
 | 
					 | 
				
			||||||
			assert mpint2w(p[0]) == self._b(p[1])
 | 
					 | 
				
			||||||
			assert mpint2r(self._b(p[1])) == p[0]
 | 
					 | 
				
			||||||
		assert mpint2r(self._b('00 00 00 02 ff 80')) == -0x80
 | 
					 | 
				
			||||||
							
								
								
									
										285
									
								
								test/test_software.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										285
									
								
								test/test_software.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,285 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestSoftware(object):
 | 
				
			||||||
 | 
						@pytest.fixture(autouse=True)
 | 
				
			||||||
 | 
						def init(self, ssh_audit):
 | 
				
			||||||
 | 
							self.ssh = ssh_audit.SSH
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_unknown_software(self):
 | 
				
			||||||
 | 
							ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x))
 | 
				
			||||||
 | 
							assert ps('SSH-1.5') is None
 | 
				
			||||||
 | 
							assert ps('SSH-1.99-AlfaMegaServer') is None
 | 
				
			||||||
 | 
							assert ps('SSH-2.0-BetaMegaServer 0.0.1') is None
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_openssh_software(self):
 | 
				
			||||||
 | 
							ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x))
 | 
				
			||||||
 | 
							# common
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_7.3')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'OpenSSH'
 | 
				
			||||||
 | 
							assert s.version == '7.3'
 | 
				
			||||||
 | 
							assert s.patch is None
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 7.3'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == str(s)
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=7.3)>'
 | 
				
			||||||
 | 
							# common, portable
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_7.2p1')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'OpenSSH'
 | 
				
			||||||
 | 
							assert s.version == '7.2'
 | 
				
			||||||
 | 
							assert s.patch == 'p1'
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 7.2p1'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == 'OpenSSH 7.2'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=7.2, patch=p1)>'
 | 
				
			||||||
 | 
							# dot instead of underline
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH.6.6')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'OpenSSH'
 | 
				
			||||||
 | 
							assert s.version == '6.6'
 | 
				
			||||||
 | 
							assert s.patch is None
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 6.6'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == str(s)
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=6.6)>'
 | 
				
			||||||
 | 
							# dash instead of underline
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH-3.9p1')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'OpenSSH'
 | 
				
			||||||
 | 
							assert s.version == '3.9'
 | 
				
			||||||
 | 
							assert s.patch == 'p1'
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 3.9p1'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == 'OpenSSH 3.9'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=3.9, patch=p1)>'
 | 
				
			||||||
 | 
							# patch prefix with dash
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_7.2-hpn14v5')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'OpenSSH'
 | 
				
			||||||
 | 
							assert s.version == '7.2'
 | 
				
			||||||
 | 
							assert s.patch == 'hpn14v5'
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 7.2 (hpn14v5)'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == 'OpenSSH 7.2'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=7.2, patch=hpn14v5)>'
 | 
				
			||||||
 | 
							# patch prefix with underline
 | 
				
			||||||
 | 
							s = ps('SSH-1.5-OpenSSH_6.6.1_hpn13v11')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'OpenSSH'
 | 
				
			||||||
 | 
							assert s.version == '6.6.1'
 | 
				
			||||||
 | 
							assert s.patch == 'hpn13v11'
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 6.6.1 (hpn13v11)'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == 'OpenSSH 6.6.1'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=6.6.1, patch=hpn13v11)>'
 | 
				
			||||||
 | 
							# patch prefix with dot
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_5.9.CASPUR')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'OpenSSH'
 | 
				
			||||||
 | 
							assert s.version == '5.9'
 | 
				
			||||||
 | 
							assert s.patch == 'CASPUR'
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 5.9 (CASPUR)'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == 'OpenSSH 5.9'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=5.9, patch=CASPUR)>'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_dropbear_software(self):
 | 
				
			||||||
 | 
							ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x))
 | 
				
			||||||
 | 
							# common
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-dropbear_2016.74')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'Dropbear SSH'
 | 
				
			||||||
 | 
							assert s.version == '2016.74'
 | 
				
			||||||
 | 
							assert s.patch is None
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'Dropbear SSH 2016.74'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == str(s)
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=Dropbear SSH, version=2016.74)>'
 | 
				
			||||||
 | 
							# common, patch
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-dropbear_0.44test4')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'Dropbear SSH'
 | 
				
			||||||
 | 
							assert s.version == '0.44'
 | 
				
			||||||
 | 
							assert s.patch == 'test4'
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'Dropbear SSH 0.44 (test4)'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == 'Dropbear SSH 0.44'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=Dropbear SSH, version=0.44, patch=test4)>'
 | 
				
			||||||
 | 
							# patch prefix with dash
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-dropbear_0.44-Freesco-p49')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'Dropbear SSH'
 | 
				
			||||||
 | 
							assert s.version == '0.44'
 | 
				
			||||||
 | 
							assert s.patch == 'Freesco-p49'
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'Dropbear SSH 0.44 (Freesco-p49)'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == 'Dropbear SSH 0.44'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=Dropbear SSH, version=0.44, patch=Freesco-p49)>'
 | 
				
			||||||
 | 
							# patch prefix with underline
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-dropbear_2014.66_agbn_1')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'Dropbear SSH'
 | 
				
			||||||
 | 
							assert s.version == '2014.66'
 | 
				
			||||||
 | 
							assert s.patch == 'agbn_1'
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'Dropbear SSH 2014.66 (agbn_1)'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == 'Dropbear SSH 2014.66'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=Dropbear SSH, version=2014.66, patch=agbn_1)>'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_libssh_software(self):
 | 
				
			||||||
 | 
							ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x))
 | 
				
			||||||
 | 
							# common
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-libssh-0.2')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'libssh'
 | 
				
			||||||
 | 
							assert s.version == '0.2'
 | 
				
			||||||
 | 
							assert s.patch is None
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'libssh 0.2'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == str(s)
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=libssh, version=0.2)>'
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-libssh-0.7.3')
 | 
				
			||||||
 | 
							assert s.vendor is None
 | 
				
			||||||
 | 
							assert s.product == 'libssh'
 | 
				
			||||||
 | 
							assert s.version == '0.7.3'
 | 
				
			||||||
 | 
							assert s.patch is None
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'libssh 0.7.3'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == str(s)
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=libssh, version=0.7.3)>'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_romsshell_software(self):
 | 
				
			||||||
 | 
							ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x))
 | 
				
			||||||
 | 
							# common
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-RomSShell_5.40')
 | 
				
			||||||
 | 
							assert s.vendor == 'Allegro Software'
 | 
				
			||||||
 | 
							assert s.product == 'RomSShell'
 | 
				
			||||||
 | 
							assert s.version == '5.40'
 | 
				
			||||||
 | 
							assert s.patch is None
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'Allegro Software RomSShell 5.40'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == str(s)
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(vendor=Allegro Software, product=RomSShell, version=5.40)>'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_hp_ilo_software(self):
 | 
				
			||||||
 | 
							ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x))
 | 
				
			||||||
 | 
							# common
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-mpSSH_0.2.1')
 | 
				
			||||||
 | 
							assert s.vendor == 'HP'
 | 
				
			||||||
 | 
							assert s.product == 'iLO (Integrated Lights-Out) sshd'
 | 
				
			||||||
 | 
							assert s.version == '0.2.1'
 | 
				
			||||||
 | 
							assert s.patch is None
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'HP iLO (Integrated Lights-Out) sshd 0.2.1'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == str(s)
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(vendor=HP, product=iLO (Integrated Lights-Out) sshd, version=0.2.1)>'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_cisco_software(self):
 | 
				
			||||||
 | 
							ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x))
 | 
				
			||||||
 | 
							# common
 | 
				
			||||||
 | 
							s = ps('SSH-1.5-Cisco-1.25')
 | 
				
			||||||
 | 
							assert s.vendor == 'Cisco'
 | 
				
			||||||
 | 
							assert s.product == 'IOS/PIX sshd'
 | 
				
			||||||
 | 
							assert s.version == '1.25'
 | 
				
			||||||
 | 
							assert s.patch is None
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							assert str(s) == 'Cisco IOS/PIX sshd 1.25'
 | 
				
			||||||
 | 
							assert str(s) == s.display()
 | 
				
			||||||
 | 
							assert s.display(True) == str(s)
 | 
				
			||||||
 | 
							assert s.display(False) == str(s)
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(vendor=Cisco, product=IOS/PIX sshd, version=1.25)>'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_sofware_os(self):
 | 
				
			||||||
 | 
							ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x))
 | 
				
			||||||
 | 
							# unknown
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_3.7.1 MegaOperatingSystem 123')
 | 
				
			||||||
 | 
							assert s.os is None
 | 
				
			||||||
 | 
							# NetBSD
 | 
				
			||||||
 | 
							s = ps('SSH-1.99-OpenSSH_2.5.1 NetBSD_Secure_Shell-20010614')
 | 
				
			||||||
 | 
							assert s.os == 'NetBSD (2001-06-14)'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 2.5.1 running on NetBSD (2001-06-14)'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=2.5.1, os=NetBSD (2001-06-14))>'
 | 
				
			||||||
 | 
							s = ps('SSH-1.99-OpenSSH_5.0 NetBSD_Secure_Shell-20080403+-hpn13v1')
 | 
				
			||||||
 | 
							assert s.os == 'NetBSD (2008-04-03)'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 5.0 running on NetBSD (2008-04-03)'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=5.0, os=NetBSD (2008-04-03))>'
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_6.6.1_hpn13v11 NetBSD-20100308')
 | 
				
			||||||
 | 
							assert s.os == 'NetBSD (2010-03-08)'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 6.6.1 (hpn13v11) running on NetBSD (2010-03-08)'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=6.6.1, patch=hpn13v11, os=NetBSD (2010-03-08))>'
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_4.4 NetBSD')
 | 
				
			||||||
 | 
							assert s.os == 'NetBSD'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 4.4 running on NetBSD'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=4.4, os=NetBSD)>'
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_3.0.2 NetBSD Secure Shell')
 | 
				
			||||||
 | 
							assert s.os == 'NetBSD'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 3.0.2 running on NetBSD'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=3.0.2, os=NetBSD)>'
 | 
				
			||||||
 | 
							# FreeBSD
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_7.2 FreeBSD-20160310')
 | 
				
			||||||
 | 
							assert s.os == 'FreeBSD (2016-03-10)'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 7.2 running on FreeBSD (2016-03-10)'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=7.2, os=FreeBSD (2016-03-10))>'
 | 
				
			||||||
 | 
							s = ps('SSH-1.99-OpenSSH_2.9 FreeBSD localisations 20020307')
 | 
				
			||||||
 | 
							assert s.os == 'FreeBSD (2002-03-07)'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 2.9 running on FreeBSD (2002-03-07)'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=2.9, os=FreeBSD (2002-03-07))>'
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_2.3.0 green@FreeBSD.org 20010321')
 | 
				
			||||||
 | 
							assert s.os == 'FreeBSD (2001-03-21)'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 2.3.0 running on FreeBSD (2001-03-21)'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=2.3.0, os=FreeBSD (2001-03-21))>'
 | 
				
			||||||
 | 
							s = ps('SSH-1.99-OpenSSH_4.4p1 FreeBSD-openssh-portable-overwrite-base-4.4.p1_1,1')
 | 
				
			||||||
 | 
							assert s.os == 'FreeBSD'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 4.4p1 running on FreeBSD'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=4.4, patch=p1, os=FreeBSD)>'
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_7.2-OVH-rescue FreeBSD')
 | 
				
			||||||
 | 
							assert s.os == 'FreeBSD'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 7.2 (OVH-rescue) running on FreeBSD'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=7.2, patch=OVH-rescue, os=FreeBSD)>'
 | 
				
			||||||
 | 
							# Windows
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_3.7.1 in RemotelyAnywhere 5.21.422')
 | 
				
			||||||
 | 
							assert s.os == 'Microsoft Windows (RemotelyAnywhere 5.21.422)'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 3.7.1 running on Microsoft Windows (RemotelyAnywhere 5.21.422)'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=3.7.1, os=Microsoft Windows (RemotelyAnywhere 5.21.422))>'
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_3.8 in DesktopAuthority 7.1.091')
 | 
				
			||||||
 | 
							assert s.os == 'Microsoft Windows (DesktopAuthority 7.1.091)'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 3.8 running on Microsoft Windows (DesktopAuthority 7.1.091)'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=3.8, os=Microsoft Windows (DesktopAuthority 7.1.091))>'
 | 
				
			||||||
 | 
							s = ps('SSH-2.0-OpenSSH_3.8 in RemoteSupportManager 1.0.023')
 | 
				
			||||||
 | 
							assert s.os == 'Microsoft Windows (RemoteSupportManager 1.0.023)'
 | 
				
			||||||
 | 
							assert str(s) == 'OpenSSH 3.8 running on Microsoft Windows (RemoteSupportManager 1.0.023)'
 | 
				
			||||||
 | 
							assert repr(s) == '<Software(product=OpenSSH, version=3.8, os=Microsoft Windows (RemoteSupportManager 1.0.023))>'
 | 
				
			||||||
							
								
								
									
										74
									
								
								test/test_ssh1.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								test/test_ssh1.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestSSH1(object):
 | 
				
			||||||
 | 
						@pytest.fixture(autouse=True)
 | 
				
			||||||
 | 
						def init(self, ssh_audit):
 | 
				
			||||||
 | 
							self.ssh = ssh_audit.SSH
 | 
				
			||||||
 | 
							self.ssh1 = ssh_audit.SSH1
 | 
				
			||||||
 | 
							self.rbuf = ssh_audit.ReadBuf
 | 
				
			||||||
 | 
							self.wbuf = ssh_audit.WriteBuf
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_crc32(self):
 | 
				
			||||||
 | 
							assert self.ssh1.crc32(b'') == 0x00
 | 
				
			||||||
 | 
							assert self.ssh1.crc32(b'The quick brown fox jumps over the lazy dog') == 0xb9c60808
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def _server_key(self):
 | 
				
			||||||
 | 
							return (1024, 0x10001, 0xee6552da432e0ac2c422df1a51287507748bfe3b5e3e4fa989a8f49fdc163a17754939ef18ef8a667ea3b71036a151fcd7f5e01ceef1e4439864baf3ac569047582c69d6c128212e0980dcb3168f00d371004039983f6033cd785b8b8f85096c7d9405cbfdc664e27c966356a6b4eb6ee20ad43414b50de18b22829c1880b551)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def _host_key(self):
 | 
				
			||||||
 | 
							return (2048, 0x10001, 0xdfa20cd2a530ccc8c870aa60d9feb3b35deeab81c3215a96557abbd683d21f4600f38e475d87100da9a4404220eeb3bb5584e5a2b5b48ffda58530ea19104a32577d7459d91e76aa711b241050f4cc6d5327ccce254f371acad3be56d46eb5919b73f20dbdb1177b700f00891c5bf4ed128bb90ed541b778288285bcfa28432ab5cbcb8321b6e24760e998e0daa519f093a631e44276d7dd252ce0c08c75e2ab28a7349ead779f97d0f20a6d413bf3623cd216dc35375f6366690bcc41e3b2d5465840ec7ee0dc7e3f1c101d674a0c7dbccbc3942788b111396add2f8153b46a0e4b50d66e57ee92958f1c860dd97cc0e40e32febff915343ed53573142bdf4b)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def _pkm_payload(self):
 | 
				
			||||||
 | 
							w = self.wbuf()
 | 
				
			||||||
 | 
							w.write(b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff')
 | 
				
			||||||
 | 
							b, e, m = self._server_key()
 | 
				
			||||||
 | 
							w.write_int(b).write_mpint1(e).write_mpint1(m)
 | 
				
			||||||
 | 
							b, e, m = self._host_key()
 | 
				
			||||||
 | 
							w.write_int(b).write_mpint1(e).write_mpint1(m)
 | 
				
			||||||
 | 
							w.write_int(2)
 | 
				
			||||||
 | 
							w.write_int(72)
 | 
				
			||||||
 | 
							w.write_int(36)
 | 
				
			||||||
 | 
							return w.write_flush()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_fingerprint(self):
 | 
				
			||||||
 | 
							b, e, m = self._host_key()
 | 
				
			||||||
 | 
							fpd = self.wbuf._create_mpint(m, False)
 | 
				
			||||||
 | 
							fpd += self.wbuf._create_mpint(e, False)
 | 
				
			||||||
 | 
							fp = self.ssh.Fingerprint(fpd)
 | 
				
			||||||
 | 
							assert fp.md5 == 'MD5:9d:26:f8:39:fc:20:9d:9b:ca:cc:4a:0f:e1:93:f5:96'
 | 
				
			||||||
 | 
							assert fp.sha256 == 'SHA256:vZdx3mhzbvVJmn08t/ruv8WDhJ9jfKYsCTuSzot+QIs'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_pkm_read(self):
 | 
				
			||||||
 | 
							pkm = self.ssh1.PublicKeyMessage.parse(self._pkm_payload())
 | 
				
			||||||
 | 
							assert pkm is not None
 | 
				
			||||||
 | 
							assert pkm.cookie == b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
 | 
				
			||||||
 | 
							b, e, m = self._server_key()
 | 
				
			||||||
 | 
							assert pkm.server_key_bits == b
 | 
				
			||||||
 | 
							assert pkm.server_key_public_exponent == e
 | 
				
			||||||
 | 
							assert pkm.server_key_public_modulus == m
 | 
				
			||||||
 | 
							b, e, m = self._host_key()
 | 
				
			||||||
 | 
							assert pkm.host_key_bits == b
 | 
				
			||||||
 | 
							assert pkm.host_key_public_exponent == e
 | 
				
			||||||
 | 
							assert pkm.host_key_public_modulus == m
 | 
				
			||||||
 | 
							fp = self.ssh.Fingerprint(pkm.host_key_fingerprint_data)
 | 
				
			||||||
 | 
							assert pkm.protocol_flags == 2
 | 
				
			||||||
 | 
							assert pkm.supported_ciphers_mask == 72
 | 
				
			||||||
 | 
							assert pkm.supported_ciphers == ['3des', 'blowfish']
 | 
				
			||||||
 | 
							assert pkm.supported_authentications_mask == 36
 | 
				
			||||||
 | 
							assert pkm.supported_authentications == ['rsa', 'tis']
 | 
				
			||||||
 | 
							assert fp.md5 == 'MD5:9d:26:f8:39:fc:20:9d:9b:ca:cc:4a:0f:e1:93:f5:96'
 | 
				
			||||||
 | 
							assert fp.sha256 == 'SHA256:vZdx3mhzbvVJmn08t/ruv8WDhJ9jfKYsCTuSzot+QIs'
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_pkm_payload(self):
 | 
				
			||||||
 | 
							cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff' 
 | 
				
			||||||
 | 
							skey = self._server_key()
 | 
				
			||||||
 | 
							hkey = self._host_key()
 | 
				
			||||||
 | 
							pflags = 2
 | 
				
			||||||
 | 
							cmask = 72
 | 
				
			||||||
 | 
							amask = 36
 | 
				
			||||||
 | 
							pkm1 = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
 | 
				
			||||||
 | 
							pkm2 = self.ssh1.PublicKeyMessage.parse(self._pkm_payload())
 | 
				
			||||||
 | 
							assert pkm1.payload == pkm2.payload
 | 
				
			||||||
							
								
								
									
										48
									
								
								test/test_ssh2.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								test/test_ssh2.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestSSH2(object):
 | 
				
			||||||
 | 
						@pytest.fixture(autouse=True)
 | 
				
			||||||
 | 
						def init(self, ssh_audit):
 | 
				
			||||||
 | 
							self.ssh = ssh_audit.SSH
 | 
				
			||||||
 | 
							self.ssh2 = ssh_audit.SSH2
 | 
				
			||||||
 | 
							self.rbuf = ssh_audit.ReadBuf
 | 
				
			||||||
 | 
							self.wbuf = ssh_audit.WriteBuf
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def _kex_payload(self):
 | 
				
			||||||
 | 
							w = self.wbuf()
 | 
				
			||||||
 | 
							w.write(b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff')
 | 
				
			||||||
 | 
							w.write_list([u'curve25519-sha256@libssh.org', u'ecdh-sha2-nistp256', u'ecdh-sha2-nistp384', u'ecdh-sha2-nistp521', u'diffie-hellman-group-exchange-sha256', u'diffie-hellman-group14-sha1'])
 | 
				
			||||||
 | 
							w.write_list([u'ssh-rsa', u'rsa-sha2-512', u'rsa-sha2-256', u'ssh-ed25519'])
 | 
				
			||||||
 | 
							w.write_list([u'chacha20-poly1305@openssh.com', u'aes128-ctr', u'aes192-ctr', u'aes256-ctr', u'aes128-gcm@openssh.com', u'aes256-gcm@openssh.com', u'aes128-cbc', u'aes192-cbc', u'aes256-cbc'])
 | 
				
			||||||
 | 
							w.write_list([u'chacha20-poly1305@openssh.com', u'aes128-ctr', u'aes192-ctr', u'aes256-ctr', u'aes128-gcm@openssh.com', u'aes256-gcm@openssh.com', u'aes128-cbc', u'aes192-cbc', u'aes256-cbc'])
 | 
				
			||||||
 | 
							w.write_list([u'umac-64-etm@openssh.com', u'umac-128-etm@openssh.com', u'hmac-sha2-256-etm@openssh.com', u'hmac-sha2-512-etm@openssh.com', u'hmac-sha1-etm@openssh.com', u'umac-64@openssh.com', u'umac-128@openssh.com', u'hmac-sha2-256', u'hmac-sha2-512', u'hmac-sha1'])
 | 
				
			||||||
 | 
							w.write_list([u'umac-64-etm@openssh.com', u'umac-128-etm@openssh.com', u'hmac-sha2-256-etm@openssh.com', u'hmac-sha2-512-etm@openssh.com', u'hmac-sha1-etm@openssh.com', u'umac-64@openssh.com', u'umac-128@openssh.com', u'hmac-sha2-256', u'hmac-sha2-512', u'hmac-sha1'])
 | 
				
			||||||
 | 
							w.write_list([u'none', u'zlib@openssh.com'])
 | 
				
			||||||
 | 
							w.write_list([u'none', u'zlib@openssh.com'])
 | 
				
			||||||
 | 
							w.write_list([u''])
 | 
				
			||||||
 | 
							w.write_list([u''])
 | 
				
			||||||
 | 
							w.write_byte(False)
 | 
				
			||||||
 | 
							w.write_int(0)
 | 
				
			||||||
 | 
							return w.write_flush()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_kex_read(self):
 | 
				
			||||||
 | 
							kex = self.ssh2.Kex.parse(self._kex_payload())
 | 
				
			||||||
 | 
							assert kex is not None
 | 
				
			||||||
 | 
							assert kex.cookie == b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
 | 
				
			||||||
 | 
							assert kex.kex_algorithms == [u'curve25519-sha256@libssh.org', u'ecdh-sha2-nistp256', u'ecdh-sha2-nistp384', u'ecdh-sha2-nistp521', u'diffie-hellman-group-exchange-sha256', u'diffie-hellman-group14-sha1']
 | 
				
			||||||
 | 
							assert kex.key_algorithms == [u'ssh-rsa', u'rsa-sha2-512', u'rsa-sha2-256', u'ssh-ed25519']
 | 
				
			||||||
 | 
							assert kex.client is not None
 | 
				
			||||||
 | 
							assert kex.server is not None
 | 
				
			||||||
 | 
							assert kex.client.encryption == [u'chacha20-poly1305@openssh.com', u'aes128-ctr', u'aes192-ctr', u'aes256-ctr', u'aes128-gcm@openssh.com', u'aes256-gcm@openssh.com', u'aes128-cbc', u'aes192-cbc', u'aes256-cbc']
 | 
				
			||||||
 | 
							assert kex.server.encryption == [u'chacha20-poly1305@openssh.com', u'aes128-ctr', u'aes192-ctr', u'aes256-ctr', u'aes128-gcm@openssh.com', u'aes256-gcm@openssh.com', u'aes128-cbc', u'aes192-cbc', u'aes256-cbc']
 | 
				
			||||||
 | 
							assert kex.client.mac == [u'umac-64-etm@openssh.com', u'umac-128-etm@openssh.com', u'hmac-sha2-256-etm@openssh.com', u'hmac-sha2-512-etm@openssh.com', u'hmac-sha1-etm@openssh.com', u'umac-64@openssh.com', u'umac-128@openssh.com', u'hmac-sha2-256', u'hmac-sha2-512', u'hmac-sha1']
 | 
				
			||||||
 | 
							assert kex.server.mac == [u'umac-64-etm@openssh.com', u'umac-128-etm@openssh.com', u'hmac-sha2-256-etm@openssh.com', u'hmac-sha2-512-etm@openssh.com', u'hmac-sha1-etm@openssh.com', u'umac-64@openssh.com', u'umac-128@openssh.com', u'hmac-sha2-256', u'hmac-sha2-512', u'hmac-sha1']
 | 
				
			||||||
 | 
							assert kex.client.compression == [u'none', u'zlib@openssh.com']
 | 
				
			||||||
 | 
							assert kex.server.compression == [u'none', u'zlib@openssh.com']
 | 
				
			||||||
 | 
							assert kex.client.languages == [u'']
 | 
				
			||||||
 | 
							assert kex.server.languages == [u'']
 | 
				
			||||||
 | 
							assert kex.follows is False
 | 
				
			||||||
 | 
							assert kex.unused == 0
 | 
				
			||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
# -*- coding: utf-8 -*-
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestVersionCompare(object):
 | 
					class TestVersionCompare(object):
 | 
				
			||||||
	@pytest.fixture(autouse=True)
 | 
						@pytest.fixture(autouse=True)
 | 
				
			||||||
	def init(self, ssh_audit):
 | 
						def init(self, ssh_audit):
 | 
				
			||||||
@@ -15,34 +16,69 @@ class TestVersionCompare(object):
 | 
				
			|||||||
		b = self.ssh.Banner.parse('SSH-2.0-OpenSSH_{0}'.format(v))
 | 
							b = self.ssh.Banner.parse('SSH-2.0-OpenSSH_{0}'.format(v))
 | 
				
			||||||
		return self.ssh.Software.parse(b)
 | 
							return self.ssh.Software.parse(b)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						def get_libssh_software(self, v):
 | 
				
			||||||
 | 
							b = self.ssh.Banner.parse('SSH-2.0-libssh-{0}'.format(v))
 | 
				
			||||||
 | 
							return self.ssh.Software.parse(b)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	def test_dropbear_compare_version_pre_years(self):
 | 
						def test_dropbear_compare_version_pre_years(self):
 | 
				
			||||||
		s = self.get_dropbear_software('0.44')
 | 
							s = self.get_dropbear_software('0.44')
 | 
				
			||||||
 | 
							assert s.compare_version(None) == 1
 | 
				
			||||||
 | 
							assert s.compare_version('') == 1
 | 
				
			||||||
		assert s.compare_version('0.43') > 0
 | 
							assert s.compare_version('0.43') > 0
 | 
				
			||||||
		assert s.compare_version('0.44') == 0
 | 
							assert s.compare_version('0.44') == 0
 | 
				
			||||||
 | 
							assert s.compare_version(s) == 0
 | 
				
			||||||
		assert s.compare_version('0.45') < 0
 | 
							assert s.compare_version('0.45') < 0
 | 
				
			||||||
		assert s.between_versions('0.43', '0.45') == True
 | 
							assert s.between_versions('0.43', '0.45')
 | 
				
			||||||
 | 
							assert s.between_versions('0.43', '0.43') is False
 | 
				
			||||||
 | 
							assert s.between_versions('0.45', '0.43') is False
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	def test_dropbear_compare_version_with_years(self):
 | 
						def test_dropbear_compare_version_with_years(self):
 | 
				
			||||||
		s = self.get_dropbear_software('2015.71')
 | 
							s = self.get_dropbear_software('2015.71')
 | 
				
			||||||
		assert s.compare_version('2014.67') > 0
 | 
							assert s.compare_version(None) == 1
 | 
				
			||||||
 | 
							assert s.compare_version('') == 1
 | 
				
			||||||
 | 
							assert s.compare_version('2014.66') > 0
 | 
				
			||||||
		assert s.compare_version('2015.71') == 0
 | 
							assert s.compare_version('2015.71') == 0
 | 
				
			||||||
 | 
							assert s.compare_version(s) == 0
 | 
				
			||||||
		assert s.compare_version('2016.74') < 0
 | 
							assert s.compare_version('2016.74') < 0
 | 
				
			||||||
		assert s.between_versions('2014.67', '2016.74') == True
 | 
							assert s.between_versions('2014.66', '2016.74')
 | 
				
			||||||
 | 
							assert s.between_versions('2014.66', '2015.69') is False
 | 
				
			||||||
 | 
							assert s.between_versions('2016.74', '2014.66') is False
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	def test_dropbear_compare_version_mixed(self):
 | 
						def test_dropbear_compare_version_mixed(self):
 | 
				
			||||||
		s = self.get_dropbear_software('0.53.1')
 | 
							s = self.get_dropbear_software('0.53.1')
 | 
				
			||||||
 | 
							assert s.compare_version(None) == 1
 | 
				
			||||||
 | 
							assert s.compare_version('') == 1
 | 
				
			||||||
		assert s.compare_version('0.53') > 0
 | 
							assert s.compare_version('0.53') > 0
 | 
				
			||||||
		assert s.compare_version('0.53.1') == 0
 | 
							assert s.compare_version('0.53.1') == 0
 | 
				
			||||||
 | 
							assert s.compare_version(s) == 0
 | 
				
			||||||
		assert s.compare_version('2011.54') < 0
 | 
							assert s.compare_version('2011.54') < 0
 | 
				
			||||||
		assert s.between_versions('0.53', '2011.54') == True
 | 
							assert s.between_versions('0.53', '2011.54')
 | 
				
			||||||
 | 
							assert s.between_versions('0.53', '0.53') is False
 | 
				
			||||||
 | 
							assert s.between_versions('2011.54', '0.53') is False
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	def test_dropbear_compare_version_patchlevel(self):
 | 
						def test_dropbear_compare_version_patchlevel(self):
 | 
				
			||||||
		s1 = self.get_dropbear_software('0.44')
 | 
							s1 = self.get_dropbear_software('0.44')
 | 
				
			||||||
		s2 = self.get_dropbear_software('0.44test3')
 | 
							s2 = self.get_dropbear_software('0.44test3')
 | 
				
			||||||
 | 
							assert s1.compare_version(None) == 1
 | 
				
			||||||
 | 
							assert s1.compare_version('') == 1
 | 
				
			||||||
 | 
							assert s1.compare_version('0.44') == 0
 | 
				
			||||||
 | 
							assert s1.compare_version(s1) == 0
 | 
				
			||||||
		assert s1.compare_version('0.43') > 0
 | 
							assert s1.compare_version('0.43') > 0
 | 
				
			||||||
		assert s1.compare_version('0.44test4') > 0
 | 
							assert s1.compare_version('0.44test4') > 0
 | 
				
			||||||
 | 
							assert s1.between_versions('0.44test4', '0.45')
 | 
				
			||||||
 | 
							assert s1.between_versions('0.43', '0.44test4') is False
 | 
				
			||||||
 | 
							assert s1.between_versions('0.45', '0.44test4') is False
 | 
				
			||||||
 | 
							assert s2.compare_version(None) == 1
 | 
				
			||||||
 | 
							assert s2.compare_version('') == 1
 | 
				
			||||||
 | 
							assert s2.compare_version('0.44test3') == 0
 | 
				
			||||||
 | 
							assert s2.compare_version(s2) == 0
 | 
				
			||||||
		assert s2.compare_version('0.44') < 0
 | 
							assert s2.compare_version('0.44') < 0
 | 
				
			||||||
		assert s2.compare_version('0.44test4') < 0
 | 
							assert s2.compare_version('0.44test4') < 0
 | 
				
			||||||
 | 
							assert s2.between_versions('0.43', '0.44')
 | 
				
			||||||
 | 
							assert s2.between_versions('0.43', '0.44test2') is False
 | 
				
			||||||
 | 
							assert s2.between_versions('0.44', '0.43') is False
 | 
				
			||||||
 | 
							assert s1.compare_version(s2) > 0
 | 
				
			||||||
 | 
							assert s2.compare_version(s1) < 0
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	def test_dropbear_compare_version_sequential(self):
 | 
						def test_dropbear_compare_version_sequential(self):
 | 
				
			||||||
		versions = []
 | 
							versions = []
 | 
				
			||||||
@@ -82,20 +118,28 @@ class TestVersionCompare(object):
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	def test_openssh_compare_version_simple(self):
 | 
						def test_openssh_compare_version_simple(self):
 | 
				
			||||||
		s = self.get_openssh_software('3.7.1')
 | 
							s = self.get_openssh_software('3.7.1')
 | 
				
			||||||
 | 
							assert s.compare_version(None) == 1
 | 
				
			||||||
 | 
							assert s.compare_version('') == 1
 | 
				
			||||||
		assert s.compare_version('3.7') > 0
 | 
							assert s.compare_version('3.7') > 0
 | 
				
			||||||
		assert s.compare_version('3.7.1') == 0
 | 
							assert s.compare_version('3.7.1') == 0
 | 
				
			||||||
 | 
							assert s.compare_version(s) == 0
 | 
				
			||||||
		assert s.compare_version('3.8') < 0
 | 
							assert s.compare_version('3.8') < 0
 | 
				
			||||||
		assert s.between_versions('3.7', '3.8') == True
 | 
							assert s.between_versions('3.7', '3.8')
 | 
				
			||||||
		
 | 
							assert s.between_versions('3.6', '3.7') is False
 | 
				
			||||||
 | 
							assert s.between_versions('3.8', '3.7') is False
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	def test_openssh_compare_version_patchlevel(self):
 | 
						def test_openssh_compare_version_patchlevel(self):
 | 
				
			||||||
		s1 = self.get_openssh_software('2.1.1')
 | 
							s1 = self.get_openssh_software('2.1.1')
 | 
				
			||||||
		s2 = self.get_openssh_software('2.1.1p2')
 | 
							s2 = self.get_openssh_software('2.1.1p2')
 | 
				
			||||||
 | 
							assert s1.compare_version(s1) == 0
 | 
				
			||||||
 | 
							assert s2.compare_version(s2) == 0
 | 
				
			||||||
		assert s1.compare_version('2.1.1p1') == 0
 | 
							assert s1.compare_version('2.1.1p1') == 0
 | 
				
			||||||
		assert s1.compare_version('2.1.1p2') == 0
 | 
							assert s1.compare_version('2.1.1p2') == 0
 | 
				
			||||||
		assert s2.compare_version('2.1.1') == 0
 | 
							assert s2.compare_version('2.1.1') == 0
 | 
				
			||||||
		assert s2.compare_version('2.1.1p1') > 0
 | 
							assert s2.compare_version('2.1.1p1') > 0
 | 
				
			||||||
		assert s2.compare_version('2.1.1p3') < 0
 | 
							assert s2.compare_version('2.1.1p3') < 0
 | 
				
			||||||
 | 
							assert s1.compare_version(s2) == 0
 | 
				
			||||||
 | 
							assert s2.compare_version(s1) == 0
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	def test_openbsd_compare_version_sequential(self):
 | 
						def test_openbsd_compare_version_sequential(self):
 | 
				
			||||||
		versions = []
 | 
							versions = []
 | 
				
			||||||
@@ -130,3 +174,41 @@ class TestVersionCompare(object):
 | 
				
			|||||||
			if i + 1 < l:
 | 
								if i + 1 < l:
 | 
				
			||||||
				vnext = versions[i + 1]
 | 
									vnext = versions[i + 1]
 | 
				
			||||||
				assert s.compare_version(vnext) < 0
 | 
									assert s.compare_version(vnext) < 0
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_libssh_compare_version_simple(self):
 | 
				
			||||||
 | 
							s = self.get_libssh_software('0.3')
 | 
				
			||||||
 | 
							assert s.compare_version(None) == 1
 | 
				
			||||||
 | 
							assert s.compare_version('') == 1
 | 
				
			||||||
 | 
							assert s.compare_version('0.2') > 0
 | 
				
			||||||
 | 
							assert s.compare_version('0.3') == 0
 | 
				
			||||||
 | 
							assert s.compare_version(s) == 0
 | 
				
			||||||
 | 
							assert s.compare_version('0.3.1') < 0
 | 
				
			||||||
 | 
							assert s.between_versions('0.2', '0.3.1')
 | 
				
			||||||
 | 
							assert s.between_versions('0.1', '0.2') is False
 | 
				
			||||||
 | 
							assert s.between_versions('0.3.1', '0.2') is False
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						def test_libssh_compare_version_sequential(self):
 | 
				
			||||||
 | 
							versions = []
 | 
				
			||||||
 | 
							for v in ['0.2', '0.3']:
 | 
				
			||||||
 | 
								versions.append(v)
 | 
				
			||||||
 | 
							for i in range(1, 5):
 | 
				
			||||||
 | 
								versions.append('0.3.{0}'.format(i))
 | 
				
			||||||
 | 
							for i in range(0, 9):
 | 
				
			||||||
 | 
								versions.append('0.4.{0}'.format(i))
 | 
				
			||||||
 | 
							for i in range(0, 6):
 | 
				
			||||||
 | 
								versions.append('0.5.{0}'.format(i))
 | 
				
			||||||
 | 
							for i in range(0, 6):
 | 
				
			||||||
 | 
								versions.append('0.6.{0}'.format(i))
 | 
				
			||||||
 | 
							for i in range(0, 4):
 | 
				
			||||||
 | 
								versions.append('0.7.{0}'.format(i))
 | 
				
			||||||
 | 
							l = len(versions)
 | 
				
			||||||
 | 
							for i in range(l):
 | 
				
			||||||
 | 
								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:
 | 
				
			||||||
 | 
									vnext = versions[i + 1]
 | 
				
			||||||
 | 
									assert s.compare_version(vnext) < 0
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user