mirror of
https://github.com/jtesta/ssh-audit.git
synced 2025-07-06 05:57:50 -05:00
Compare commits
68 Commits
Author | SHA1 | Date | |
---|---|---|---|
eb1588ddc7 | |||
b7d698d743 | |||
b0c00749a6 | |||
6e3e8bac74 | |||
632adc076a | |||
13b065b316 | |||
a7581e07dc | |||
4cae6aff43 | |||
3e20f7c622 | |||
1123ac718c | |||
6d84cfdc31 | |||
c7ad1828d8 | |||
86cb453928 | |||
0c00b37328 | |||
936acfa37d | |||
b5d7f73125 | |||
6a7bed06d7 | |||
41e69dd6f2 | |||
25faeb4c59 | |||
8051078524 | |||
cf815a6652 | |||
2d4eb7da28 | |||
68a420ff00 | |||
17f5eb0b38 | |||
b95969bbc0 | |||
00ce44e728 | |||
8fb07edafd | |||
b27d768c79 | |||
cb54c2bf33 | |||
85f14720cb | |||
1410894f45 | |||
381ba1a660 | |||
8e3f3c6044 | |||
f80e3f22ce | |||
49bd2c96a8 | |||
103b8fb934 | |||
1faa24ad86 | |||
adc1007d7d | |||
8a406dd9d2 | |||
d717f86238 | |||
bf1fbbfa43 | |||
282770e698 | |||
01ec6b0b37 | |||
30f2b7690a | |||
cabbe717d3 | |||
d5ef967758 | |||
dd44e2f010 | |||
8e71c2d66b | |||
da31c19d38 | |||
a75be9ab41 | |||
d168524a5d | |||
1f48e7c92b | |||
12f811cb5c | |||
ec1dda8d7f | |||
42fecf83e6 | |||
9463aab4f7 | |||
22ac41bfb8 | |||
246a41d46f | |||
29d874b450 | |||
bbc4ab542d | |||
edc363db60 | |||
4b314a55ef | |||
4ffae85325 | |||
2c4fb971cd | |||
1ac4041c09 | |||
b70f4061cc | |||
c3aaf6e2a7 | |||
f35c7dbee7 |
@ -1,4 +1,4 @@
|
||||
version: '1.7.1.dev.{build}'
|
||||
version: 'v2.2.1-dev.{build}'
|
||||
|
||||
build: off
|
||||
branches:
|
||||
@ -8,18 +8,14 @@ branches:
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- PYTHON: "C:\\Python26"
|
||||
- PYTHON: "C:\\Python26-x64"
|
||||
- PYTHON: "C:\\Python27"
|
||||
- PYTHON: "C:\\Python27-x64"
|
||||
- PYTHON: "C:\\Python33"
|
||||
- PYTHON: "C:\\Python33-x64"
|
||||
- PYTHON: "C:\\Python34"
|
||||
- PYTHON: "C:\\Python34-x64"
|
||||
- PYTHON: "C:\\Python35"
|
||||
- PYTHON: "C:\\Python35-x64"
|
||||
- PYTHON: "C:\\Python36"
|
||||
- PYTHON: "C:\\Python36-x64"
|
||||
- PYTHON: "C:\\Python37"
|
||||
- PYTHON: "C:\\Python37-x64"
|
||||
- PYTHON: "C:\\Python38"
|
||||
- PYTHON: "C:\\Python38-x64"
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
|
8
.deepsource.toml
Normal file
8
.deepsource.toml
Normal file
@ -0,0 +1,8 @@
|
||||
version = 1
|
||||
|
||||
[[analyzers]]
|
||||
name = "python"
|
||||
enabled = true
|
||||
|
||||
[analyzers.meta]
|
||||
runtime_version = "3.x.x"
|
12
.gitignore
vendored
12
.gitignore
vendored
@ -4,10 +4,16 @@
|
||||
*.asc
|
||||
venv*/
|
||||
.cache/
|
||||
.mypy_cache/
|
||||
.tox
|
||||
.coverage*
|
||||
reports/
|
||||
.scannerwork/
|
||||
pypi/sshaudit/LICENSE
|
||||
pypi/sshaudit/README.md
|
||||
pypi/sshaudit/sshaudit.py
|
||||
packages/sshaudit/LICENSE
|
||||
packages/sshaudit/README.md
|
||||
packages/sshaudit/sshaudit.py
|
||||
packages/parts/
|
||||
packages/prime/
|
||||
packages/snap/
|
||||
packages/stage/
|
||||
packages/ssh-audit_*.snap
|
||||
|
79
.travis.yml
79
.travis.yml
@ -1,80 +1,19 @@
|
||||
language: python
|
||||
sudo: false
|
||||
matrix:
|
||||
include:
|
||||
# (default)
|
||||
- os: linux
|
||||
python: 2.6
|
||||
- os: linux
|
||||
python: 2.7
|
||||
env: SQ=1
|
||||
- os: linux
|
||||
python: 3.3
|
||||
- os: linux
|
||||
python: 3.4
|
||||
- os: linux
|
||||
python: 3.5
|
||||
- os: linux
|
||||
python: 3.6
|
||||
- os: linux
|
||||
python: pypy
|
||||
- os: linux
|
||||
python: pypy3
|
||||
- os: linux
|
||||
python: 3.7-dev
|
||||
# Ubuntu 12.04
|
||||
- os: linux
|
||||
dist: precise
|
||||
language: generic
|
||||
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3 PY_ORIGIN=pyenv
|
||||
# Ubuntu 14.04
|
||||
- os: linux
|
||||
dist: trusty
|
||||
language: generic
|
||||
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3 PY_ORIGIN=pyenv
|
||||
# macOS 10.12 Sierra
|
||||
- os: osx
|
||||
osx_image: xcode8.3
|
||||
language: generic
|
||||
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3
|
||||
# Mac OS X 10.11 El Capitan
|
||||
- os: osx
|
||||
osx_image: xcode7.3
|
||||
language: generic
|
||||
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3
|
||||
# Mac OS X 10.10 Yosemite
|
||||
- os: osx
|
||||
osx_image: xcode6.4
|
||||
language: generic
|
||||
env: PY_VER=py26,py27,py33,py34,py35,py36,pypy,pypy3
|
||||
allow_failures:
|
||||
# PyPy3 on Travis CI is out of date
|
||||
- python: pypy3
|
||||
# Python nightly could fail
|
||||
- python: 3.7-dev
|
||||
- env: PY_VER=py37
|
||||
- env: PY_VER=py37/pyenv
|
||||
- env: PY_VER=py37 PY_ORIGIN=pyenv
|
||||
fast_finish: true
|
||||
|
||||
python:
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
|
||||
cache:
|
||||
- pip
|
||||
- directories:
|
||||
- $HOME/.pyenv.cache
|
||||
- $HOME/.bin
|
||||
|
||||
before_install:
|
||||
- source test/tools/ci-linux.sh
|
||||
- ci_step_before_install
|
||||
|
||||
install:
|
||||
- ci_step_install
|
||||
- pip install -U pip tox tox-travis coveralls codecov
|
||||
|
||||
script:
|
||||
- ci_step_script
|
||||
- tox
|
||||
|
||||
after_success:
|
||||
- ci_step_success
|
||||
|
||||
after_failure:
|
||||
- ci_step_failure
|
||||
- codecov
|
||||
|
26
CONTRIBUTING.md
Normal file
26
CONTRIBUTING.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Contributing to ssh-audit
|
||||
|
||||
We are very much open to receiving patches from the community! To encourage participation, passing Travis tests, unit tests, etc., *is OPTIONAL*. As long as the patch works properly, it can be merged (please submit pull requests to the `dev` branch).
|
||||
|
||||
However, if you can submit patches that pass all of our automated tests, then you'll lighten the load for the project maintainer (who already has enough to do!). This document describes what tests are done and what documentation is maintained.
|
||||
|
||||
*Anything extra you can do is appreciated!*
|
||||
|
||||
|
||||
## Tox Tests
|
||||
|
||||
Tox is used to do unit testing, linting with [pylint](http://pylint.pycqa.org/en/latest/) & [flake8](https://flake8.pycqa.org/en/latest/), and static type-checking with [mypy](https://mypy.readthedocs.io/en/stable/).
|
||||
|
||||
Install tox with `apt install tox`, then simply run `tox` in the top-level directory. Look for any error messages in the (verbose) output.
|
||||
|
||||
|
||||
## Docker Tests
|
||||
|
||||
Docker is used to run ssh-audit against various real SSH servers (OpenSSH, Dropbear, and TinySSH). The output is then diff'ed against the expected result. Any differences result in failure.
|
||||
|
||||
The docker tests are run with `./docker_test.sh`. The first time it is run, it will download and compile the SSH servers; this may take awhile. Subsequent runs, however, will take only a minute to complete, as the docker image will already be up-to-date.
|
||||
|
||||
|
||||
## Man Page
|
||||
|
||||
The `ssh-audit.1` man page documents the various features of ssh-audit. If features are added, or significant behavior is modified, the man page needs to be updated.
|
2
LICENSE
2
LICENSE
@ -1,7 +1,7 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
|
||||
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
|
||||
Copyright (C) 2017-2019 Joe Testa (jtesta@positronsecurity.com)
|
||||
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
121
README.md
121
README.md
@ -7,6 +7,8 @@
|
||||
-->
|
||||
**ssh-audit** is a tool for ssh server & client configuration auditing.
|
||||
|
||||
[jtesta/ssh-audit](https://github.com/jtesta/ssh-audit/) (v2.0+) is the updated and maintained version of ssh-audit forked from [arthepsy/ssh-audit](https://github.com/arthepsy/ssh-audit) (v1.x) due to inactivity.
|
||||
|
||||
## Features
|
||||
- SSH1 and SSH2 protocol server support;
|
||||
- analyze SSH client configuration;
|
||||
@ -17,45 +19,150 @@
|
||||
- output security information (related issues, assigned CVE list, etc);
|
||||
- analyze SSH version compatibility based on algorithm information;
|
||||
- historical information from OpenSSH, Dropbear SSH and libssh;
|
||||
- policy scans to ensure adherence to a hardened/standard configuration;
|
||||
- runs on Linux and Windows;
|
||||
- no dependencies
|
||||
|
||||
## Usage
|
||||
```
|
||||
usage: ssh-audit.py [-1246pbcnjvlt] <host>
|
||||
usage: ssh-audit.py [options] <host>
|
||||
|
||||
-h, --help print this help
|
||||
-1, --ssh1 force ssh version 1 only
|
||||
-2, --ssh2 force ssh version 2 only
|
||||
-4, --ipv4 enable IPv4 (order of precedence)
|
||||
-6, --ipv6 enable IPv6 (order of precedence)
|
||||
-p, --port=<port> port to connect
|
||||
-b, --batch batch output
|
||||
-c, --client-audit starts a server on port 2222 to audit client
|
||||
software config (use -p to change port;
|
||||
use -t to change timeout)
|
||||
-n, --no-colors disable colors
|
||||
-j, --json JSON output
|
||||
-v, --verbose verbose output
|
||||
-l, --level=<level> minimum output level (info|warn|fail)
|
||||
-L, --list-policies list all the official, built-in policies
|
||||
--lookup=<alg1,alg2,...> looks up an algorithm(s) without
|
||||
connecting to a server
|
||||
-M, --make-policy=<policy.txt> creates a policy based on the target server
|
||||
(i.e.: the target server has the ideal
|
||||
configuration that other servers should
|
||||
adhere to)
|
||||
-n, --no-colors disable colors
|
||||
-p, --port=<port> port to connect
|
||||
-P, --policy=<policy.txt> run a policy test using the specified policy
|
||||
-t, --timeout=<secs> timeout (in seconds) for connection and reading
|
||||
(default: 5)
|
||||
-T, --targets=<hosts.txt> a file containing a list of target hosts (one
|
||||
per line, format HOST[:PORT])
|
||||
-v, --verbose verbose output
|
||||
```
|
||||
* if both IPv4 and IPv6 are used, order of precedence can be set by using either `-46` or `-64`.
|
||||
* batch flag `-b` will output sections without header and without empty lines (implies verbose flag).
|
||||
* verbose flag `-v` will prefix each line with section type and algorithm name.
|
||||
* an exit code of 0 is returned when all algorithms are considered secure (for a standard audit), or when a policy check passes (for a policy audit).
|
||||
|
||||
### Server Audit Example
|
||||
Below is a screen shot of the server-auditing output when connecting to an unhardened OpenSSH v5.3 service:
|
||||
Basic server auditing:
|
||||
```
|
||||
ssh-audit localhost
|
||||
ssh-audit 127.0.0.1
|
||||
ssh-audit 127.0.0.1:222
|
||||
ssh-audit ::1
|
||||
ssh-audit [::1]:222
|
||||
```
|
||||
|
||||
To run a standard audit against many servers (place targets into servers.txt, one on each line in the format of `HOST[:PORT]`):
|
||||
|
||||
```
|
||||
ssh-audit -T servers.txt
|
||||
```
|
||||
|
||||
To audit a client configuration (listens on port 2222 by default; connect using `ssh anything@localhost`):
|
||||
|
||||
```
|
||||
ssh-audit -c
|
||||
```
|
||||
|
||||
To audit a client configuration, with a listener on port 4567:
|
||||
```
|
||||
ssh-audit -c -p 4567
|
||||
```
|
||||
|
||||
To list all official built-in policies (hint: use resulting file paths with `-P`/`--policy`):
|
||||
```
|
||||
ssh-audit -L
|
||||
```
|
||||
|
||||
To run a policy audit against a server:
|
||||
```
|
||||
ssh-audit -P path/to/server_policy targetserver
|
||||
```
|
||||
|
||||
To run a policy audit against a client:
|
||||
```
|
||||
ssh-audit -c -P path/to/client_policy
|
||||
```
|
||||
|
||||
To run a policy audit against many servers:
|
||||
```
|
||||
ssh-audit -T servers.txt -P path/to/server_policy
|
||||
```
|
||||
|
||||
To create a policy based on a target server (which can be manually edited; see official built-in policies for syntax examples):
|
||||
```
|
||||
ssh-audit -M new_policy.txt targetserver
|
||||
```
|
||||
|
||||
### Server Standard Audit Example
|
||||
Below is a screen shot of the standard server-auditing output when connecting to an unhardened OpenSSH v5.3 service:
|
||||

|
||||
|
||||
### Client Audit Example
|
||||
### Server Policy Audit Example
|
||||
Below is a screen shot of the policy auditing output when connecting to an un-hardened Ubuntu Server 20.04 machine:
|
||||

|
||||
|
||||
After applying the steps in the hardening guide (see below), the output changes to the following:
|
||||

|
||||
|
||||
### Client Standard Audit Example
|
||||
Below is a screen shot of the client-auditing output when an unhardened OpenSSH v7.2 client connects:
|
||||

|
||||
|
||||
### Hardening Guides
|
||||
Guides to harden server & client configuration can be found here: [https://www.ssh-audit.com/hardening_guides.html](https://www.ssh-audit.com/hardening_guides.html)
|
||||
|
||||
### Pre-Built Packages
|
||||
Pre-built packages are available for Windows (see the releases page), on PyPI, Snap, and Homebrew.
|
||||
|
||||
To install from PyPI:
|
||||
```
|
||||
$ pip3 install ssh-audit
|
||||
```
|
||||
|
||||
To install the Snap package:
|
||||
```
|
||||
$ snap install ssh-audit
|
||||
```
|
||||
|
||||
To install on Homebrew:
|
||||
```
|
||||
$ brew install ssh-audit
|
||||
```
|
||||
|
||||
### Web Front-End
|
||||
For convenience, a web front-end on top of the command-line tool is available at [https://www.ssh-audit.com/](https://www.ssh-audit.com/).
|
||||
|
||||
## ChangeLog
|
||||
### v2.3.0 (2020-09-27)
|
||||
- Added new policy auditing functionality to test adherence to a hardening guide/standard configuration (see `-L`/`--list-policies`, `-M`/`--make-policy` and `-P`/`--policy`). For an in-depth tutorial, see <https://www.positronsecurity.com/blog/2020-09-27-ssh-policy-configuration-checks-with-ssh-audit/>.
|
||||
- Created new man page (see `ssh-audit.1` file).
|
||||
- 1024-bit moduli upgraded from warnings to failures.
|
||||
- Many Python 2 code clean-ups, testing framework improvements, pylint & flake8 fixes, and mypy type comments; credit [Jürgen Gmach](https://github.com/jugmac00).
|
||||
- Added feature to look up algorithms in internal database (see `--lookup`); credit [Adam Russell](https://github.com/thecliguy).
|
||||
- Suppress recommendation of token host key types.
|
||||
- Added check for use-after-free vulnerability in PuTTY v0.73.
|
||||
- Added 11 new host key types: `ssh-rsa1`, `ssh-dss-sha256@ssh.com`, `ssh-gost2001`, `ssh-gost2012-256`, `ssh-gost2012-512`, `spki-sign-rsa`, `ssh-ed448`, `x509v3-ecdsa-sha2-nistp256`, `x509v3-ecdsa-sha2-nistp384`, `x509v3-ecdsa-sha2-nistp521`, `x509v3-rsa2048-sha256`.
|
||||
- Added 8 new key exchanges: `diffie-hellman-group1-sha256`, `kexAlgoCurve25519SHA256`, `Curve25519SHA256`, `gss-group14-sha256-`, `gss-group15-sha512-`, `gss-group16-sha512-`, `gss-nistp256-sha256-`, `gss-curve25519-sha256-`.
|
||||
- Added 5 new ciphers: `blowfish`, `AEAD_AES_128_GCM`, `AEAD_AES_256_GCM`, `crypticore128@ssh.com`, `seed-cbc@ssh.com`.
|
||||
- Added 3 new MACs: `chacha20-poly1305@openssh.com`, `hmac-sha3-224`, `crypticore-mac@ssh.com`.
|
||||
|
||||
### v2.2.0 (2020-03-11)
|
||||
- Marked host key type `ssh-rsa` as weak due to [practical SHA-1 collisions](https://eprint.iacr.org/2020/014.pdf).
|
||||
- Added Windows builds.
|
||||
|
156
docker_test.sh
156
docker_test.sh
@ -28,6 +28,12 @@ GREEN="\033[0;32m"
|
||||
REDB="\033[1;31m" # Red + bold
|
||||
GREENB="\033[1;32m" # Green + bold
|
||||
|
||||
# Program return values.
|
||||
PROGRAM_RETVAL_FAILURE=3
|
||||
PROGRAM_RETVAL_WARNING=2
|
||||
PROGRAM_RETVAL_CONNECTION_ERROR=1
|
||||
PROGRAM_RETVAL_GOOD=0
|
||||
|
||||
|
||||
# Returns 0 if current docker image exists.
|
||||
function check_if_docker_image_exists {
|
||||
@ -353,8 +359,9 @@ function run_dropbear_test {
|
||||
dropbear_version=$1
|
||||
test_number=$2
|
||||
options=$3
|
||||
expected_retval=$4
|
||||
|
||||
run_test 'Dropbear' $dropbear_version $test_number "$options"
|
||||
run_test 'Dropbear' $dropbear_version $test_number "$options" $expected_retval
|
||||
}
|
||||
|
||||
|
||||
@ -363,8 +370,9 @@ function run_dropbear_test {
|
||||
function run_openssh_test {
|
||||
openssh_version=$1
|
||||
test_number=$2
|
||||
expected_retval=$3
|
||||
|
||||
run_test 'OpenSSH' $openssh_version $test_number ''
|
||||
run_test 'OpenSSH' $openssh_version $test_number '' $expected_retval
|
||||
}
|
||||
|
||||
|
||||
@ -373,8 +381,9 @@ function run_openssh_test {
|
||||
function run_tinyssh_test {
|
||||
tinyssh_version=$1
|
||||
test_number=$2
|
||||
expected_retval=$3
|
||||
|
||||
run_test 'TinySSH' $tinyssh_version $test_number ''
|
||||
run_test 'TinySSH' $tinyssh_version $test_number '' $expected_retval
|
||||
}
|
||||
|
||||
|
||||
@ -383,6 +392,7 @@ function run_test {
|
||||
version=$2
|
||||
test_number=$3
|
||||
options=$4
|
||||
expected_retval=$5
|
||||
|
||||
server_exec=
|
||||
test_result_stdout=
|
||||
@ -421,15 +431,17 @@ function run_test {
|
||||
fi
|
||||
|
||||
./ssh-audit.py localhost:2222 > $test_result_stdout
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${REDB}Failed to run ssh-audit.py! (exit code: $?)${CLR}"
|
||||
actual_retval=$?
|
||||
if [[ $actual_retval != $expected_retval ]]; then
|
||||
echo -e "${REDB}Unexpected return value. Expected: ${expected_retval}; Actual: ${actual_retval}${CLR}"
|
||||
docker container stop -t 0 $cid > /dev/null
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./ssh-audit.py -j localhost:2222 > $test_result_json
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${REDB}Failed to run ssh-audit.py! (exit code: $?)${CLR}"
|
||||
actual_retval=$?
|
||||
if [[ $actual_retval != $expected_retval ]]; then
|
||||
echo -e "${REDB}Unexpected return value. Expected: ${expected_retval}; Actual: ${actual_retval}${CLR}"
|
||||
docker container stop -t 0 $cid > /dev/null
|
||||
exit 1
|
||||
fi
|
||||
@ -465,6 +477,81 @@ function run_test {
|
||||
}
|
||||
|
||||
|
||||
function run_policy_test {
|
||||
config_number=$1 # The configuration number to use.
|
||||
test_number=$2 # The policy test number to run.
|
||||
expected_exit_code=$3 # The expected exit code of ssh-audit.py.
|
||||
|
||||
version=
|
||||
config=
|
||||
if [[ ${config_number} == 'config1' ]]; then
|
||||
version='5.6p1'
|
||||
config='sshd_config-5.6p1_test1'
|
||||
elif [[ ${config_number} == 'config2' ]]; then
|
||||
version='8.0p1'
|
||||
config='sshd_config-8.0p1_test1'
|
||||
elif [[ ${config_number} == 'config3' ]]; then
|
||||
version='5.6p1'
|
||||
config='sshd_config-5.6p1_test4'
|
||||
fi
|
||||
|
||||
server_exec="/openssh/sshd-${version} -D -f /etc/ssh/${config}"
|
||||
policy_path="test/docker/policies/policy_${test_number}.txt"
|
||||
test_result_stdout="${TEST_RESULT_DIR}/openssh_${version}_policy_${test_number}.txt"
|
||||
test_result_json="${TEST_RESULT_DIR}/openssh_${version}_policy_${test_number}.json"
|
||||
expected_result_stdout="test/docker/expected_results/openssh_${version}_policy_${test_number}.txt"
|
||||
expected_result_json="test/docker/expected_results/openssh_${version}_policy_${test_number}.json"
|
||||
test_name="OpenSSH ${version} policy ${test_number}"
|
||||
|
||||
#echo "Running: docker run -d -p 2222:22 ${IMAGE_NAME}:${IMAGE_VERSION} ${server_exec}"
|
||||
cid=`docker run -d -p 2222:22 ${IMAGE_NAME}:${IMAGE_VERSION} ${server_exec}`
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${REDB}Failed to run docker image! (exit code: $?)${CLR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#echo "Running: ./ssh-audit.py -P ${policy_path} localhost:2222 > ${test_result_stdout}"
|
||||
./ssh-audit.py -P ${policy_path} localhost:2222 > ${test_result_stdout}
|
||||
actual_exit_code=$?
|
||||
if [[ ${actual_exit_code} != ${expected_exit_code} ]]; then
|
||||
echo -e "${test_name} ${REDB}FAILED${CLR} (expected exit code: ${expected_exit_code}; actual exit code: ${actual_exit_code}\n"
|
||||
cat ${test_result_stdout}
|
||||
docker container stop -t 0 $cid > /dev/null
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#echo "Running: ./ssh-audit.py -P ${policy_path} -j localhost:2222 > ${test_result_json}"
|
||||
./ssh-audit.py -P ${policy_path} -j localhost:2222 > ${test_result_json}
|
||||
actual_exit_code=$?
|
||||
if [[ ${actual_exit_code} != ${expected_exit_code} ]]; then
|
||||
echo -e "${test_name} ${REDB}FAILED${CLR} (expected exit code: ${expected_exit_code}; actual exit code: ${actual_exit_code}\n"
|
||||
cat ${test_result_json}
|
||||
docker container stop -t 0 $cid > /dev/null
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker container stop -t 0 $cid > /dev/null
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${REDB}Failed to stop docker container ${cid}! (exit code: $?)${CLR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
diff=`diff -u ${expected_result_stdout} ${test_result_stdout}`
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${test_name} ${REDB}FAILED${CLR}.\n\n${diff}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
diff=`diff -u ${expected_result_json} ${test_result_json}`
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${test_name} ${REDB}FAILED${CLR}.\n\n${diff}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${test_name} ${GREEN}passed${CLR}."
|
||||
}
|
||||
|
||||
|
||||
# First check if docker is functional.
|
||||
docker version > /dev/null
|
||||
if [[ $? != 0 ]]; then
|
||||
@ -487,21 +574,54 @@ TEST_RESULT_DIR=`mktemp -d /tmp/ssh-audit_test-results_XXXXXXXXXX`
|
||||
|
||||
# Now run all the tests.
|
||||
echo -e "\nRunning tests..."
|
||||
run_openssh_test '4.0p1' 'test1'
|
||||
run_openssh_test '4.0p1' 'test1' $PROGRAM_RETVAL_FAILURE
|
||||
echo
|
||||
run_openssh_test '5.6p1' 'test1'
|
||||
run_openssh_test '5.6p1' 'test2'
|
||||
run_openssh_test '5.6p1' 'test3'
|
||||
run_openssh_test '5.6p1' 'test4'
|
||||
run_openssh_test '5.6p1' 'test5'
|
||||
run_openssh_test '5.6p1' 'test1' $PROGRAM_RETVAL_FAILURE
|
||||
run_openssh_test '5.6p1' 'test2' $PROGRAM_RETVAL_FAILURE
|
||||
run_openssh_test '5.6p1' 'test3' $PROGRAM_RETVAL_FAILURE
|
||||
run_openssh_test '5.6p1' 'test4' $PROGRAM_RETVAL_FAILURE
|
||||
run_openssh_test '5.6p1' 'test5' $PROGRAM_RETVAL_FAILURE
|
||||
echo
|
||||
run_openssh_test '8.0p1' 'test1'
|
||||
run_openssh_test '8.0p1' 'test2'
|
||||
run_openssh_test '8.0p1' 'test3'
|
||||
run_openssh_test '8.0p1' 'test1' $PROGRAM_RETVAL_FAILURE
|
||||
run_openssh_test '8.0p1' 'test2' $PROGRAM_RETVAL_FAILURE
|
||||
run_openssh_test '8.0p1' 'test3' $PROGRAM_RETVAL_GOOD
|
||||
echo
|
||||
run_dropbear_test '2019.78' 'test1' '-r /etc/dropbear/dropbear_rsa_host_key_1024 -r /etc/dropbear/dropbear_dss_host_key -r /etc/dropbear/dropbear_ecdsa_host_key'
|
||||
run_dropbear_test '2019.78' 'test1' '-r /etc/dropbear/dropbear_rsa_host_key_1024 -r /etc/dropbear/dropbear_dss_host_key -r /etc/dropbear/dropbear_ecdsa_host_key' 3
|
||||
echo
|
||||
run_tinyssh_test '20190101' 'test1'
|
||||
run_tinyssh_test '20190101' 'test1' $PROGRAM_RETVAL_WARNING
|
||||
echo
|
||||
echo
|
||||
run_policy_test 'config1' 'test1' $PROGRAM_RETVAL_GOOD
|
||||
run_policy_test 'config1' 'test2' $PROGRAM_RETVAL_FAILURE
|
||||
run_policy_test 'config1' 'test3' $PROGRAM_RETVAL_FAILURE
|
||||
run_policy_test 'config1' 'test4' $PROGRAM_RETVAL_FAILURE
|
||||
run_policy_test 'config1' 'test5' $PROGRAM_RETVAL_FAILURE
|
||||
run_policy_test 'config2' 'test6' $PROGRAM_RETVAL_GOOD
|
||||
|
||||
# Passing test with host key certificate and CA key certificates.
|
||||
run_policy_test 'config3' 'test7' $PROGRAM_RETVAL_GOOD
|
||||
|
||||
# Failing test with host key certificate and non-compliant CA key length.
|
||||
run_policy_test 'config3' 'test8' $PROGRAM_RETVAL_FAILURE
|
||||
|
||||
# Failing test with non-compliant host key certificate and CA key certificate.
|
||||
run_policy_test 'config3' 'test9' $PROGRAM_RETVAL_FAILURE
|
||||
|
||||
# Failing test with non-compliant host key certificate and non-compliant CA key certificate.
|
||||
run_policy_test 'config3' 'test10' $PROGRAM_RETVAL_FAILURE
|
||||
|
||||
# Passing test with host key size check.
|
||||
run_policy_test 'config2' 'test11' $PROGRAM_RETVAL_GOOD
|
||||
|
||||
# Failing test with non-compliant host key size check.
|
||||
run_policy_test 'config2' 'test12' $PROGRAM_RETVAL_FAILURE
|
||||
|
||||
# Passing test with DH modulus test.
|
||||
run_policy_test 'config2' 'test13' $PROGRAM_RETVAL_GOOD
|
||||
|
||||
# Failing test with DH modulus test.
|
||||
run_policy_test 'config2' 'test14' $PROGRAM_RETVAL_FAILURE
|
||||
|
||||
|
||||
# The test functions above will terminate the script on failure, so if we reached here,
|
||||
# all tests are successful.
|
||||
|
@ -11,4 +11,4 @@ uploadprod:
|
||||
twine upload dist/*
|
||||
|
||||
clean:
|
||||
rm -rf build/ dist/ *.egg-info/ sshaudit/sshaudit.py sshaudit/LICENSE sshaudit/README.md
|
||||
rm -rf parts/ prime/ snap/ stage/ build/ dist/ *.egg-info/ sshaudit/sshaudit.py sshaudit/LICENSE sshaudit/README.md ssh-audit*.snap
|
8
packages/Makefile.snap
Normal file
8
packages/Makefile.snap
Normal file
@ -0,0 +1,8 @@
|
||||
all:
|
||||
cp ../ssh-audit.py sshaudit/sshaudit.py
|
||||
cp ../README.md sshaudit/README.md
|
||||
echo -e "\n\nDid you remember to bump the version number in snapcraft.yaml?\n\n"
|
||||
snapcraft
|
||||
|
||||
clean:
|
||||
rm -rf parts/ prime/ snap/ stage/ build/ dist/ *.egg-info/ sshaudit/sshaudit.py sshaudit/LICENSE sshaudit/README.md ssh-audit*.snap
|
41
packages/notes.txt
Normal file
41
packages/notes.txt
Normal file
@ -0,0 +1,41 @@
|
||||
= PyPI =
|
||||
|
||||
To create package and upload to test server:
|
||||
|
||||
# apt install virtualenv
|
||||
$ virtualenv -p /usr/bin/python3 /tmp/pypi_upload
|
||||
$ cd /tmp/pypi_upload; source bin/activate
|
||||
$ pip3 install twine
|
||||
$ cp -R path/to/ssh-audit .
|
||||
$ cd ssh-audit/packages
|
||||
$ make -f Makefile.pypi
|
||||
$ make -f Makefile.pypi uploadtest
|
||||
|
||||
|
||||
To download from test server and verify:
|
||||
|
||||
$ virtualenv -p /usr/bin/python3 /tmp/pypi_test
|
||||
$ cd /tmp/pypi_test; source bin/activate
|
||||
$ pip3 install --index-url https://test.pypi.org/simple ssh-audit
|
||||
|
||||
|
||||
To upload to production server:
|
||||
|
||||
$ cd /tmp/pypi_upload; source bin/activate
|
||||
$ cd ssh-audit/pypi
|
||||
$ make -f Makefile.pypi uploadprod
|
||||
|
||||
|
||||
To download from production server and verify:
|
||||
|
||||
$ virtualenv -p /usr/bin/python3 /tmp/pypi_prod
|
||||
$ cd /tmp/pypi_prod; source bin/activate
|
||||
$ pip3 install ssh-audit
|
||||
|
||||
----
|
||||
|
||||
= Snap =
|
||||
|
||||
To create the snap package, simply run:
|
||||
|
||||
$ make -f Makefile.snap
|
58
packages/setup.py
Normal file
58
packages/setup.py
Normal file
@ -0,0 +1,58 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import re
|
||||
import sys
|
||||
from setuptools import setup
|
||||
|
||||
print_warning = False
|
||||
m = re.search(r'^VERSION\s*=\s*\'v(\d\.\d\.\d)\'', open('sshaudit/sshaudit.py').read(), re.M)
|
||||
if m is None:
|
||||
# If we failed to parse the stable version, see if this is the development version.
|
||||
m = re.search(r'^VERSION\s*=\s*\'v(\d\.\d\.\d-dev)\'', open('sshaudit/sshaudit.py').read(), re.M)
|
||||
if m is None:
|
||||
print("Error: could not parse VERSION variable from ssh-audit.py.")
|
||||
sys.exit(1)
|
||||
else: # Continue with the development version, but print a warning later.
|
||||
print_warning = True
|
||||
|
||||
version = m.group(1)
|
||||
print("\n\nPackaging ssh-audit v%s...\n\n" % version)
|
||||
|
||||
with open("sshaudit/README.md", "rb") as f:
|
||||
long_descr = f.read().decode("utf-8")
|
||||
|
||||
|
||||
setup(
|
||||
name="ssh-audit",
|
||||
packages=["sshaudit"],
|
||||
license='MIT',
|
||||
entry_points={
|
||||
"console_scripts": ['ssh-audit = sshaudit.sshaudit:main']
|
||||
},
|
||||
version=version,
|
||||
description="An SSH server & client configuration security auditing tool",
|
||||
long_description=long_descr,
|
||||
long_description_content_type="text/markdown",
|
||||
author="Joe Testa",
|
||||
author_email="jtesta@positronsecurity.com",
|
||||
url="https://github.com/jtesta/ssh-audit",
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Intended Audience :: System Administrators",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
"Topic :: Security",
|
||||
"Topic :: Security :: Cryptography"
|
||||
])
|
||||
|
||||
if print_warning:
|
||||
print("\n\n !!! WARNING: development version detected (%s). Are you sure you want to package this version? Probably not...\n" % version)
|
21
packages/snapcraft.yaml
Normal file
21
packages/snapcraft.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
name: ssh-audit
|
||||
version: '2.2.0-1'
|
||||
license: 'MIT'
|
||||
summary: ssh-audit
|
||||
description: |
|
||||
SSH server and client security configuration auditor. Official repository: <https://github.com/jtesta/ssh-audit>
|
||||
|
||||
base: core18
|
||||
grade: stable
|
||||
confinement: strict
|
||||
|
||||
apps:
|
||||
ssh-audit:
|
||||
command: bin/ssh-audit
|
||||
plugs: [network,network-bind]
|
||||
|
||||
parts:
|
||||
ssh-audit:
|
||||
plugin: python
|
||||
python-version: python3
|
||||
source: .
|
@ -12,6 +12,6 @@ On a Windows machine, do the following:
|
||||
|
||||
3.) Create the executable with:
|
||||
|
||||
pyinstaller -F --icon windows_icon.ico ssh-audit.py
|
||||
pyinstaller -F --icon packages\windows_icon.ico ssh-audit.py
|
||||
|
||||
4.) The 'dist' folder will have the resulting ssh-audit.exe.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
24
policies/openssh_7_7.txt
Normal file
24
policies/openssh_7_7.txt
Normal file
@ -0,0 +1,24 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH v7.7.
|
||||
#
|
||||
|
||||
name = "Hardened OpenSSH v7.7"
|
||||
version = 1
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-ed25519
|
||||
|
||||
# Host key types that may optionally appear.
|
||||
optional host keys = ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
24
policies/openssh_7_8.txt
Normal file
24
policies/openssh_7_8.txt
Normal file
@ -0,0 +1,24 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH v7.8.
|
||||
#
|
||||
|
||||
name = "Hardened OpenSSH v7.8"
|
||||
version = 1
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-ed25519
|
||||
|
||||
# Host key types that may optionally appear.
|
||||
optional host keys = ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
24
policies/openssh_7_9.txt
Normal file
24
policies/openssh_7_9.txt
Normal file
@ -0,0 +1,24 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH v7.9.
|
||||
#
|
||||
|
||||
name = "Hardened OpenSSH v7.9"
|
||||
version = 1
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-ed25519
|
||||
|
||||
# Host key types that may optionally appear.
|
||||
optional host keys = ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
24
policies/openssh_8_0.txt
Normal file
24
policies/openssh_8_0.txt
Normal file
@ -0,0 +1,24 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH v8.0.
|
||||
#
|
||||
|
||||
name = "Hardened OpenSSH v8.0"
|
||||
version = 1
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-ed25519
|
||||
|
||||
# Host key types that may optionally appear.
|
||||
optional host keys = ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
24
policies/openssh_8_1.txt
Normal file
24
policies/openssh_8_1.txt
Normal file
@ -0,0 +1,24 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH v8.1.
|
||||
#
|
||||
|
||||
name = "Hardened OpenSSH v8.1"
|
||||
version = 1
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-ed25519
|
||||
|
||||
# Host key types that may optionally appear.
|
||||
optional host keys = sk-ssh-ed25519@openssh.com, ssh-ed25519-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
28
policies/openssh_8_2.txt
Normal file
28
policies/openssh_8_2.txt
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH v8.2.
|
||||
#
|
||||
|
||||
name = "Hardened OpenSSH v8.2"
|
||||
version = 1
|
||||
|
||||
# RSA host key sizes.
|
||||
hostkey_size_rsa-sha2-256 = 4096
|
||||
hostkey_size_rsa-sha2-512 = 4096
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519
|
||||
|
||||
# Host key types that may optionally appear.
|
||||
optional host keys = sk-ssh-ed25519@openssh.com, ssh-ed25519-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
28
policies/openssh_8_3.txt
Normal file
28
policies/openssh_8_3.txt
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH v8.3.
|
||||
#
|
||||
|
||||
name = "Hardened OpenSSH v8.3"
|
||||
version = 1
|
||||
|
||||
# RSA host key sizes.
|
||||
hostkey_size_rsa-sha2-256 = 4096
|
||||
hostkey_size_rsa-sha2-512 = 4096
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519
|
||||
|
||||
# Host key types that may optionally appear.
|
||||
optional host keys = sk-ssh-ed25519@openssh.com, ssh-ed25519-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
28
policies/openssh_8_4.txt
Normal file
28
policies/openssh_8_4.txt
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH v8.4.
|
||||
#
|
||||
|
||||
name = "Hardened OpenSSH v8.4"
|
||||
version = 1
|
||||
|
||||
# RSA host key sizes.
|
||||
hostkey_size_rsa-sha2-256 = 4096
|
||||
hostkey_size_rsa-sha2-512 = 4096
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519
|
||||
|
||||
# Host key types that may optionally appear.
|
||||
optional host keys = sk-ssh-ed25519@openssh.com, ssh-ed25519-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
19
policies/ubuntu_client_16_04.txt
Normal file
19
policies/ubuntu_client_16_04.txt
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH on Ubuntu 16.04 LTS.
|
||||
#
|
||||
|
||||
client policy = true
|
||||
name = "Hardened Ubuntu Client 16.04 LTS"
|
||||
version = 1
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-ed25519, ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256, rsa-sha2-512, ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256@libssh.org, diffie-hellman-group-exchange-sha256, ext-info-c
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
19
policies/ubuntu_client_18_04.txt
Normal file
19
policies/ubuntu_client_18_04.txt
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH on Ubuntu 18.04 LTS.
|
||||
#
|
||||
|
||||
client policy = true
|
||||
name = "Hardened Ubuntu Client 18.04 LTS"
|
||||
version = 1
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-ed25519, ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256, rsa-sha2-512, ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256, ext-info-c
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
19
policies/ubuntu_client_20_04.txt
Normal file
19
policies/ubuntu_client_20_04.txt
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH on Ubuntu 20.04 LTS.
|
||||
#
|
||||
|
||||
client policy = true
|
||||
name = "Hardened Ubuntu Client 20.04 LTS"
|
||||
version = 1
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-ed25519, ssh-ed25519-cert-v01@openssh.com, sk-ssh-ed25519@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512, rsa-sha2-512-cert-v01@openssh.com, ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256, ext-info-c
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
24
policies/ubuntu_server_16_04.txt
Normal file
24
policies/ubuntu_server_16_04.txt
Normal file
@ -0,0 +1,24 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH on Ubuntu Server 16.04 LTS.
|
||||
#
|
||||
|
||||
name = "Hardened Ubuntu Server 16.04 LTS"
|
||||
version = 1
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-ed25519
|
||||
|
||||
# Host key types that may optionally appear.
|
||||
optional host keys = ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256@libssh.org, diffie-hellman-group-exchange-sha256
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
24
policies/ubuntu_server_18_04.txt
Normal file
24
policies/ubuntu_server_18_04.txt
Normal file
@ -0,0 +1,24 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH on Ubuntu Server 18.04 LTS.
|
||||
#
|
||||
|
||||
name = "Hardened Ubuntu Server 18.04 LTS"
|
||||
version = 1
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-ed25519
|
||||
|
||||
# Host key types that may optionally appear.
|
||||
optional host keys = ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
28
policies/ubuntu_server_20_04.txt
Normal file
28
policies/ubuntu_server_20_04.txt
Normal file
@ -0,0 +1,28 @@
|
||||
#
|
||||
# Official policy for hardened OpenSSH on Ubuntu Server 20.04 LTS.
|
||||
#
|
||||
|
||||
name = "Hardened Ubuntu Server 20.04 LTS"
|
||||
version = 1
|
||||
|
||||
# RSA host key sizes.
|
||||
hostkey_size_rsa-sha2-256 = 4096
|
||||
hostkey_size_rsa-sha2-512 = 4096
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519
|
||||
|
||||
# Host key types that may optionally appear.
|
||||
optional host keys = sk-ssh-ed25519@openssh.com, ssh-ed25519-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com
|
@ -1,17 +0,0 @@
|
||||
To create package and upload to test server:
|
||||
|
||||
# apt install virtualenv
|
||||
$ virtualenv -p /usr/bin/python3 /tmp/pypi_upload
|
||||
$ cd /tmp/pypi_upload; source bin/activate
|
||||
$ pip3 install twine
|
||||
$ cp -R path/to/ssh-audit .
|
||||
$ cd ssh-audit/pypi
|
||||
$ make
|
||||
$ make uploadtest
|
||||
|
||||
|
||||
To download from test server and verify:
|
||||
|
||||
$ virtualenv -p /usr/bin/python3 /tmp/pypi_test
|
||||
$ cd /tmp/pypi_test; source bin/activate
|
||||
$ pip3 install --index-url https://test.pypi.org/simple ssh-audit
|
@ -1,38 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import re
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
version = re.search('^VERSION\s*=\s*\'v(\d\.\d\.\d)\'', open('sshaudit/sshaudit.py').read(), re.M).group(1)
|
||||
print("\n\nPackaging ssh-audit v%s...\n\n" % version)
|
||||
|
||||
with open("sshaudit/README.md", "rb") as f:
|
||||
long_descr = f.read().decode("utf-8")
|
||||
|
||||
|
||||
setup(
|
||||
name = "ssh-audit",
|
||||
packages = ["sshaudit"],
|
||||
license = 'MIT',
|
||||
entry_points = {
|
||||
"console_scripts": ['ssh-audit = sshaudit.sshaudit:main']
|
||||
},
|
||||
version = version,
|
||||
description = "An SSH server & client configuration security auditing tool",
|
||||
long_description = long_descr,
|
||||
long_description_content_type = "text/markdown",
|
||||
author = "Joe Testa",
|
||||
author_email = "jtesta@positronsecurity.com",
|
||||
url = "https://github.com/jtesta/ssh-audit",
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Intended Audience :: System Administrators",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Topic :: Security",
|
||||
"Topic :: Security :: Cryptography"
|
||||
])
|
221
ssh-audit.1
Normal file
221
ssh-audit.1
Normal file
@ -0,0 +1,221 @@
|
||||
.TH SSH-AUDIT 1 "July 16, 2020"
|
||||
.SH NAME
|
||||
\fBssh-audit\fP \- SSH server & client configuration auditor
|
||||
.SH SYNOPSIS
|
||||
.B ssh-audit
|
||||
.RI [ options ] " <target_host>"
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
\fBssh-audit\fP analyzes the configuration of SSH servers & clients, then warns the user of weak, obsolete, and/or un-tested cryptographic primitives. It is very useful for hardening SSH tunnels, which by default tend to be optimized for compatibility, not security.
|
||||
.PP
|
||||
See <https://www.ssh\-audit.com/> for official hardening guides for common platforms.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B -h, \-\-help
|
||||
.br
|
||||
Print short summary of options.
|
||||
|
||||
.TP
|
||||
.B -1, \-\-ssh1
|
||||
.br
|
||||
Only perform an audit using SSH protocol version 1.
|
||||
|
||||
.TP
|
||||
.B -2, \-\-ssh2
|
||||
.br
|
||||
Only perform an audit using SSH protocol version 2.
|
||||
|
||||
.TP
|
||||
.B -4, \-\-ipv4
|
||||
.br
|
||||
Prioritize the usage of IPv4.
|
||||
|
||||
.TP
|
||||
.B -6, \-\-ipv6
|
||||
.br
|
||||
Prioritize the usage of IPv6.
|
||||
|
||||
.TP
|
||||
.B -b, \-\-batch
|
||||
.br
|
||||
Enables grepable output.
|
||||
|
||||
.TP
|
||||
.B -c, \-\-client\-audit
|
||||
.br
|
||||
Starts a server on port 2222 to audit client software configuration. Use -p/--port=<port> to change port and -t/--timeout=<secs> to change listen timeout.
|
||||
|
||||
.TP
|
||||
.B -j, \-\-json
|
||||
.br
|
||||
Output results in JSON format.
|
||||
|
||||
.TP
|
||||
.B -l, \-\-level=<info|warn|fail>
|
||||
.br
|
||||
Specify the minimum output level. Default is info.
|
||||
|
||||
.TP
|
||||
.B -L, \-\-list-policies
|
||||
.br
|
||||
List all official, built-in policies for common systems. Their file paths can then be provided using -P/--policy=<path/to/policy.txt>.
|
||||
|
||||
.TP
|
||||
.B \-\-lookup=<alg1,alg2,...>
|
||||
.br
|
||||
Look up the security information of an algorithm(s) in the internal database. Does not connect to a server.
|
||||
|
||||
.TP
|
||||
.B -M, \-\-make-policy=<policy.txt>
|
||||
.br
|
||||
Creates a policy based on the target server. Useful when other servers should be compared to the target server's custom configuration (i.e.: a cluster environment). Note that the resulting policy can be edited manually.
|
||||
|
||||
.TP
|
||||
.B -n, \-\-no-colors
|
||||
.br
|
||||
Disable color output.
|
||||
|
||||
.TP
|
||||
.B -p, \-\-port=<port>
|
||||
.br
|
||||
The TCP port to connect to when auditing a server, or the port to listen on when auditing a client.
|
||||
|
||||
.TP
|
||||
.B -P, \-\-policy=<policy.txt>
|
||||
.br
|
||||
Runs a policy audit against a target using the specified policy (see \fBPOLICY AUDIT\fP section for detailed description of this mode of operation). Combine with -c/--client-audit to audit a client configuration instead of a server. Use -L/--list-policies to list all official, built-in policies for common systems.
|
||||
|
||||
.TP
|
||||
.B -t, \-\-timeout=<secs>
|
||||
.br
|
||||
The timeout, in seconds, for creating connections and reading data from the socket. Default is 5.
|
||||
|
||||
.TP
|
||||
.B -T, \-\-targets=<hosts.txt>
|
||||
.br
|
||||
A file containing a list of target hosts. Each line must have one host, in the format of HOST[:PORT].
|
||||
|
||||
.TP
|
||||
.B -v, \-\-verbose
|
||||
.br
|
||||
Enable verbose output.
|
||||
|
||||
|
||||
.SH STANDARD AUDIT
|
||||
.PP
|
||||
By default, \fBssh-audit\fP performs a standard audit. That is, it enumerates all host key types, key exchanges, ciphers, MACs, and other information, then color-codes them in output to the user. Cryptographic primitives with potential issues are displayed in yellow; primitives with serious flaws are displayed in red.
|
||||
|
||||
|
||||
.SH POLICY AUDIT
|
||||
.PP
|
||||
When the -P/--policy=<policy.txt> option is used, \fBssh-audit\fP performs a policy audit. The target's host key types, key exchanges, ciphers, MACs, and other information is compared to a set of expected values defined in the specified policy file. If everything matches, only a short message stating a passing result is reported. Otherwise, the field(s) that did not match are reported.
|
||||
|
||||
.PP
|
||||
Policy auditing is helpful for ensuring a group of related servers are properly hardened to an exact specification.
|
||||
|
||||
.PP
|
||||
The set of official built-in policies can be viewed with -L/--list-policies. Multiple servers can be audited with -T/--targets=<servers.txt>. Custom policies can be made from an ideal target server with -M/--make-policy=<custom_policy.txt>.
|
||||
|
||||
|
||||
.SH EXAMPLES
|
||||
.LP
|
||||
Basic server auditing:
|
||||
.RS
|
||||
.nf
|
||||
ssh-audit localhost
|
||||
ssh-audit 127.0.0.1
|
||||
ssh-audit 127.0.0.1:222
|
||||
ssh-audit ::1
|
||||
ssh-audit [::1]:222
|
||||
.fi
|
||||
.RE
|
||||
|
||||
.LP
|
||||
To run a standard audit against many servers (place targets into servers.txt, one on each line in the format of HOST[:PORT]):
|
||||
.RS
|
||||
.nf
|
||||
ssh-audit -T servers.txt
|
||||
.fi
|
||||
.RE
|
||||
|
||||
.LP
|
||||
To audit a client configuration (listens on port 2222 by default; connect using "ssh anything@localhost"):
|
||||
.RS
|
||||
.nf
|
||||
ssh-audit -c
|
||||
.fi
|
||||
.RE
|
||||
|
||||
.LP
|
||||
To audit a client configuration, with a listener on port 4567:
|
||||
.RS
|
||||
.nf
|
||||
ssh-audit -c -p 4567
|
||||
.fi
|
||||
.RE
|
||||
|
||||
.LP
|
||||
To list all official built-in policies (hint: use resulting file paths with -P/--policy):
|
||||
.RS
|
||||
.nf
|
||||
ssh-audit -L
|
||||
.fi
|
||||
.RE
|
||||
|
||||
.LP
|
||||
To run a policy audit against a server:
|
||||
.RS
|
||||
.nf
|
||||
ssh-audit -P path/to/server_policy targetserver
|
||||
.fi
|
||||
.RE
|
||||
|
||||
.LP
|
||||
To run a policy audit against a client:
|
||||
.RS
|
||||
.nf
|
||||
ssh-audit -c -P path/to/client_policy
|
||||
.fi
|
||||
.RE
|
||||
|
||||
.LP
|
||||
To run a policy audit against many servers:
|
||||
.RS
|
||||
.nf
|
||||
ssh-audit -T servers.txt -P path/to/server_policy
|
||||
.fi
|
||||
.RE
|
||||
|
||||
.LP
|
||||
To create a policy based on a target server (which can be manually edited; see official built-in policies for syntax examples):
|
||||
.RS
|
||||
.nf
|
||||
ssh-audit -M new_policy.txt targetserver
|
||||
.fi
|
||||
.RE
|
||||
|
||||
.SH RETURN VALUES
|
||||
When a successful connection is made and all algorithms are rated as "good", \fBssh-audit\fP returns 0. Other possible return values are:
|
||||
|
||||
.RS
|
||||
.nf
|
||||
1 = connection error
|
||||
2 = at least one algorithm warning was found
|
||||
3 = at least one algorithm failure was found
|
||||
<any other non-zero value> = unknown error
|
||||
.fi
|
||||
.RE
|
||||
|
||||
.SH SSH HARDENING GUIDES
|
||||
Hardening guides for common platforms can be found at: <https://www.ssh\-audit.com/>
|
||||
|
||||
.SH BUG REPORTS
|
||||
Please file bug reports as a Github Issue at: <https://github.com/jtesta/ssh\-audit/issues>
|
||||
|
||||
.SH AUTHOR
|
||||
.LP
|
||||
\fBssh-audit\fP was originally written by Andris Raugulis <moo@arthepsy.eu>, and maintained from 2015 to 2017.
|
||||
.br
|
||||
.LP
|
||||
Maintainership was assumed and development was resumed in 2017 by Joe Testa <jtesta@positronsecurity.com>.
|
6934
ssh-audit.py
6934
ssh-audit.py
File diff suppressed because it is too large
Load Diff
221
test/conftest.py
221
test/conftest.py
@ -1,5 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import io
|
||||
import sys
|
||||
@ -7,150 +5,143 @@ import socket
|
||||
import pytest
|
||||
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
import StringIO # pylint: disable=import-error
|
||||
StringIO = StringIO.StringIO
|
||||
else:
|
||||
StringIO = io.StringIO
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def ssh_audit():
|
||||
__rdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
|
||||
sys.path.append(os.path.abspath(__rdir))
|
||||
return __import__('ssh-audit')
|
||||
__rdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
|
||||
sys.path.append(os.path.abspath(__rdir))
|
||||
return __import__('ssh-audit')
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
class _OutputSpy(list):
|
||||
def begin(self):
|
||||
self.__out = StringIO()
|
||||
self.__old_stdout = sys.stdout
|
||||
sys.stdout = self.__out
|
||||
def begin(self):
|
||||
self.__out = io.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
|
||||
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()
|
||||
return _OutputSpy()
|
||||
|
||||
|
||||
class _VirtualGlobalSocket(object):
|
||||
def __init__(self, vsocket):
|
||||
self.vsocket = vsocket
|
||||
self.addrinfodata = {}
|
||||
class _VirtualGlobalSocket:
|
||||
def __init__(self, vsocket):
|
||||
self.vsocket = vsocket
|
||||
self.addrinfodata = {}
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def create_connection(self, address, timeout=0, source_address=None):
|
||||
# pylint: disable=protected-access
|
||||
return self.vsocket._connect(address, True)
|
||||
# pylint: disable=unused-argument
|
||||
def create_connection(self, address, timeout=0, source_address=None):
|
||||
# pylint: disable=protected-access
|
||||
return self.vsocket._connect(address, True)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def socket(self,
|
||||
family=socket.AF_INET,
|
||||
socktype=socket.SOCK_STREAM,
|
||||
proto=0,
|
||||
fileno=None):
|
||||
return self.vsocket
|
||||
# pylint: disable=unused-argument
|
||||
def socket(self,
|
||||
family=socket.AF_INET,
|
||||
socktype=socket.SOCK_STREAM,
|
||||
proto=0,
|
||||
fileno=None):
|
||||
return self.vsocket
|
||||
|
||||
def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0):
|
||||
key = '{0}#{1}'.format(host, port)
|
||||
if key in self.addrinfodata:
|
||||
data = self.addrinfodata[key]
|
||||
if isinstance(data, Exception):
|
||||
raise data
|
||||
return data
|
||||
if host == 'localhost':
|
||||
r = []
|
||||
if family in (0, socket.AF_INET):
|
||||
r.append((socket.AF_INET, 1, 6, '', ('127.0.0.1', port)))
|
||||
if family in (0, socket.AF_INET6):
|
||||
r.append((socket.AF_INET6, 1, 6, '', ('::1', port)))
|
||||
return r
|
||||
return []
|
||||
def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0):
|
||||
key = '{}#{}'.format(host, port)
|
||||
if key in self.addrinfodata:
|
||||
data = self.addrinfodata[key]
|
||||
if isinstance(data, Exception):
|
||||
raise data
|
||||
return data
|
||||
if host == 'localhost':
|
||||
r = []
|
||||
if family in (0, socket.AF_INET):
|
||||
r.append((socket.AF_INET, 1, 6, '', ('127.0.0.1', port)))
|
||||
if family in (0, socket.AF_INET6):
|
||||
r.append((socket.AF_INET6, 1, 6, '', ('::1', port)))
|
||||
return r
|
||||
return []
|
||||
|
||||
|
||||
class _VirtualSocket(object):
|
||||
def __init__(self):
|
||||
self.sock_address = ('127.0.0.1', 0)
|
||||
self.peer_address = None
|
||||
self._connected = False
|
||||
self.timeout = -1.0
|
||||
self.rdata = []
|
||||
self.sdata = []
|
||||
self.errors = {}
|
||||
self.gsock = _VirtualGlobalSocket(self)
|
||||
class _VirtualSocket:
|
||||
def __init__(self):
|
||||
self.sock_address = ('127.0.0.1', 0)
|
||||
self.peer_address = None
|
||||
self._connected = False
|
||||
self.timeout = -1.0
|
||||
self.rdata = []
|
||||
self.sdata = []
|
||||
self.errors = {}
|
||||
self.gsock = _VirtualGlobalSocket(self)
|
||||
|
||||
def _check_err(self, method):
|
||||
method_error = self.errors.get(method)
|
||||
if method_error:
|
||||
raise method_error
|
||||
def _check_err(self, method):
|
||||
method_error = self.errors.get(method)
|
||||
if method_error:
|
||||
raise method_error
|
||||
|
||||
def connect(self, address):
|
||||
return self._connect(address, False)
|
||||
def connect(self, address):
|
||||
return self._connect(address, False)
|
||||
|
||||
def _connect(self, address, ret=True):
|
||||
self.peer_address = address
|
||||
self._connected = True
|
||||
self._check_err('connect')
|
||||
return self if ret else None
|
||||
def _connect(self, address, ret=True):
|
||||
self.peer_address = address
|
||||
self._connected = True
|
||||
self._check_err('connect')
|
||||
return self if ret else None
|
||||
|
||||
def settimeout(self, timeout):
|
||||
self.timeout = timeout
|
||||
def settimeout(self, timeout):
|
||||
self.timeout = timeout
|
||||
|
||||
def gettimeout(self):
|
||||
return self.timeout
|
||||
def gettimeout(self):
|
||||
return self.timeout
|
||||
|
||||
def getpeername(self):
|
||||
if self.peer_address is None or not self._connected:
|
||||
raise socket.error(57, 'Socket is not connected')
|
||||
return self.peer_address
|
||||
def getpeername(self):
|
||||
if self.peer_address is None or not self._connected:
|
||||
raise OSError(57, 'Socket is not connected')
|
||||
return self.peer_address
|
||||
|
||||
def getsockname(self):
|
||||
return self.sock_address
|
||||
def getsockname(self):
|
||||
return self.sock_address
|
||||
|
||||
def bind(self, address):
|
||||
self.sock_address = address
|
||||
def bind(self, address):
|
||||
self.sock_address = address
|
||||
|
||||
def listen(self, backlog):
|
||||
pass
|
||||
def listen(self, backlog):
|
||||
pass
|
||||
|
||||
def accept(self):
|
||||
# pylint: disable=protected-access
|
||||
conn = _VirtualSocket()
|
||||
conn.sock_address = self.sock_address
|
||||
conn.peer_address = ('127.0.0.1', 0)
|
||||
conn._connected = True
|
||||
return conn, conn.peer_address
|
||||
def accept(self):
|
||||
# pylint: disable=protected-access
|
||||
conn = _VirtualSocket()
|
||||
conn.sock_address = self.sock_address
|
||||
conn.peer_address = ('127.0.0.1', 0)
|
||||
conn._connected = True
|
||||
return conn, conn.peer_address
|
||||
|
||||
def recv(self, bufsize, flags=0):
|
||||
# pylint: disable=unused-argument
|
||||
if not self._connected:
|
||||
raise socket.error(54, 'Connection reset by peer')
|
||||
if not len(self.rdata) > 0:
|
||||
return b''
|
||||
data = self.rdata.pop(0)
|
||||
if isinstance(data, Exception):
|
||||
raise data
|
||||
return data
|
||||
def recv(self, bufsize, flags=0):
|
||||
# pylint: disable=unused-argument
|
||||
if not self._connected:
|
||||
raise OSError(54, 'Connection reset by peer')
|
||||
if not len(self.rdata) > 0:
|
||||
return b''
|
||||
data = self.rdata.pop(0)
|
||||
if isinstance(data, Exception):
|
||||
raise data
|
||||
return data
|
||||
|
||||
def send(self, data):
|
||||
if self.peer_address is None or not self._connected:
|
||||
raise socket.error(32, 'Broken pipe')
|
||||
self._check_err('send')
|
||||
self.sdata.append(data)
|
||||
def send(self, data):
|
||||
if self.peer_address is None or not self._connected:
|
||||
raise OSError(32, 'Broken pipe')
|
||||
self._check_err('send')
|
||||
self.sdata.append(data)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def virtual_socket(monkeypatch):
|
||||
vsocket = _VirtualSocket()
|
||||
gsock = vsocket.gsock
|
||||
monkeypatch.setattr(socket, 'create_connection', gsock.create_connection)
|
||||
monkeypatch.setattr(socket, 'socket', gsock.socket)
|
||||
monkeypatch.setattr(socket, 'getaddrinfo', gsock.getaddrinfo)
|
||||
return vsocket
|
||||
vsocket = _VirtualSocket()
|
||||
gsock = vsocket.gsock
|
||||
monkeypatch.setattr(socket, 'create_connection', gsock.create_connection)
|
||||
monkeypatch.setattr(socket, 'socket', gsock.socket)
|
||||
monkeypatch.setattr(socket, 'getaddrinfo', gsock.getaddrinfo)
|
||||
return vsocket
|
||||
|
@ -25,8 +25,8 @@
|
||||
[0;31m(key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm[0m
|
||||
[0;33m `- [warn] using small 1024-bit modulus[0m
|
||||
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
|
||||
[0;31m(key) ssh-dss -- [fail] removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm[0m
|
||||
[0;33m `- [warn] using small 1024-bit modulus[0m
|
||||
[0;31m(key) ssh-dss -- [fail] using small 1024-bit modulus[0m
|
||||
[0;31m `- [fail] removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm[0m
|
||||
[0;33m `- [warn] using weak random number generator could reveal the key[0m
|
||||
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
|
||||
|
||||
|
@ -31,9 +31,9 @@
|
||||
`- [info] available since OpenSSH 2.3.0
|
||||
[0;33m(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm[0m
|
||||
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
|
||||
[0;31m(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm[0m
|
||||
[0;31m(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus[0m
|
||||
[0;31m `- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm[0m
|
||||
[0;31m `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack[0m
|
||||
[0;33m `- [warn] using small 1024-bit modulus[0m
|
||||
[0;33m `- [warn] using weak hashing algorithm[0m
|
||||
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
|
||||
|
||||
@ -41,8 +41,8 @@
|
||||
[0;31m(key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm[0m
|
||||
[0;33m `- [warn] using small 1024-bit modulus[0m
|
||||
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
|
||||
[0;31m(key) ssh-dss -- [fail] removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm[0m
|
||||
[0;33m `- [warn] using small 1024-bit modulus[0m
|
||||
[0;31m(key) ssh-dss -- [fail] using small 1024-bit modulus[0m
|
||||
[0;31m `- [fail] removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm[0m
|
||||
[0;33m `- [warn] using weak random number generator could reveal the key[0m
|
||||
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
{"errors": [], "host": "localhost", "passed": true, "policy": "Docker policy: test1 (version 1)"}
|
@ -0,0 +1,3 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker policy: test1 (version 1)
|
||||
Result: [0;32m✔ Passed[0m
|
@ -0,0 +1 @@
|
||||
{"errors": [{"actual": ["3072"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "RSA host key (ssh-rsa-cert-v01@openssh.com) sizes"}, {"actual": ["1024"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "RSA CA key (ssh-rsa-cert-v01@openssh.com) sizes"}], "host": "localhost", "passed": false, "policy": "Docker poliicy: test10 (version 1)"}
|
@ -0,0 +1,7 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker poliicy: test10 (version 1)
|
||||
Result: [0;31m❌ Failed![0m
|
||||
[0;33m
|
||||
Errors:
|
||||
* RSA CA key (ssh-rsa-cert-v01@openssh.com) sizes did not match. Expected: 4096; Actual: 1024
|
||||
* RSA host key (ssh-rsa-cert-v01@openssh.com) sizes did not match. Expected: 4096; Actual: 3072[0m
|
@ -0,0 +1 @@
|
||||
{"errors": [{"actual": ["diffie-hellman-group-exchange-sha256", "diffie-hellman-group-exchange-sha1", "diffie-hellman-group14-sha1", "diffie-hellman-group1-sha1"], "expected_optional": [""], "expected_required": ["kex_alg1", "kex_alg2"], "mismatched_field": "Key exchanges"}], "host": "localhost", "passed": false, "policy": "Docker policy: test2 (version 1)"}
|
@ -0,0 +1,6 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker policy: test2 (version 1)
|
||||
Result: [0;31m❌ Failed![0m
|
||||
[0;33m
|
||||
Errors:
|
||||
* Key exchanges did not match. Expected: ['kex_alg1', 'kex_alg2']; Actual: ['diffie-hellman-group-exchange-sha256', 'diffie-hellman-group-exchange-sha1', 'diffie-hellman-group14-sha1', 'diffie-hellman-group1-sha1'][0m
|
@ -0,0 +1 @@
|
||||
{"errors": [{"actual": ["ssh-rsa", "ssh-dss"], "expected_optional": [""], "expected_required": ["ssh-rsa", "ssh-dss", "key_alg1"], "mismatched_field": "Host keys"}], "host": "localhost", "passed": false, "policy": "Docker policy: test3 (version 1)"}
|
@ -0,0 +1,6 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker policy: test3 (version 1)
|
||||
Result: [0;31m❌ Failed![0m
|
||||
[0;33m
|
||||
Errors:
|
||||
* Host keys did not match. Expected: ['ssh-rsa', 'ssh-dss', 'key_alg1']; Actual: ['ssh-rsa', 'ssh-dss'][0m
|
@ -0,0 +1 @@
|
||||
{"errors": [{"actual": ["aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour", "rijndael-cbc@lysator.liu.se"], "expected_optional": [""], "expected_required": ["cipher_alg1", "cipher_alg2"], "mismatched_field": "Ciphers"}], "host": "localhost", "passed": false, "policy": "Docker policy: test4 (version 1)"}
|
@ -0,0 +1,6 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker policy: test4 (version 1)
|
||||
Result: [0;31m❌ Failed![0m
|
||||
[0;33m
|
||||
Errors:
|
||||
* Ciphers did not match. Expected: ['cipher_alg1', 'cipher_alg2']; Actual: ['aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'arcfour256', 'arcfour128', 'aes128-cbc', '3des-cbc', 'blowfish-cbc', 'cast128-cbc', 'aes192-cbc', 'aes256-cbc', 'arcfour', 'rijndael-cbc@lysator.liu.se'][0m
|
@ -0,0 +1 @@
|
||||
{"errors": [{"actual": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"], "expected_optional": [""], "expected_required": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac_alg1", "hmac-md5-96"], "mismatched_field": "MACs"}], "host": "localhost", "passed": false, "policy": "Docker policy: test5 (version 1)"}
|
@ -0,0 +1,6 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker policy: test5 (version 1)
|
||||
Result: [0;31m❌ Failed![0m
|
||||
[0;33m
|
||||
Errors:
|
||||
* MACs did not match. Expected: ['hmac-md5', 'hmac-sha1', 'umac-64@openssh.com', 'hmac-ripemd160', 'hmac-ripemd160@openssh.com', 'hmac_alg1', 'hmac-md5-96']; Actual: ['hmac-md5', 'hmac-sha1', 'umac-64@openssh.com', 'hmac-ripemd160', 'hmac-ripemd160@openssh.com', 'hmac-sha1-96', 'hmac-md5-96'][0m
|
@ -0,0 +1 @@
|
||||
{"errors": [], "host": "localhost", "passed": true, "policy": "Docker poliicy: test7 (version 1)"}
|
@ -0,0 +1,3 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker poliicy: test7 (version 1)
|
||||
Result: [0;32m✔ Passed[0m
|
@ -0,0 +1 @@
|
||||
{"errors": [{"actual": ["1024"], "expected_optional": [""], "expected_required": ["2048"], "mismatched_field": "RSA CA key (ssh-rsa-cert-v01@openssh.com) sizes"}], "host": "localhost", "passed": false, "policy": "Docker poliicy: test8 (version 1)"}
|
@ -0,0 +1,6 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker poliicy: test8 (version 1)
|
||||
Result: [0;31m❌ Failed![0m
|
||||
[0;33m
|
||||
Errors:
|
||||
* RSA CA key (ssh-rsa-cert-v01@openssh.com) sizes did not match. Expected: 2048; Actual: 1024[0m
|
@ -0,0 +1 @@
|
||||
{"errors": [{"actual": ["3072"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "RSA host key (ssh-rsa-cert-v01@openssh.com) sizes"}], "host": "localhost", "passed": false, "policy": "Docker poliicy: test9 (version 1)"}
|
@ -0,0 +1,6 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker poliicy: test9 (version 1)
|
||||
Result: [0;31m❌ Failed![0m
|
||||
[0;33m
|
||||
Errors:
|
||||
* RSA host key (ssh-rsa-cert-v01@openssh.com) sizes did not match. Expected: 4096; Actual: 3072[0m
|
@ -25,9 +25,9 @@
|
||||
`- [info] available since OpenSSH 2.3.0
|
||||
[0;33m(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm[0m
|
||||
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
|
||||
[0;31m(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm[0m
|
||||
[0;31m(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus[0m
|
||||
[0;31m `- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm[0m
|
||||
[0;31m `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack[0m
|
||||
[0;33m `- [warn] using small 1024-bit modulus[0m
|
||||
[0;33m `- [warn] using weak hashing algorithm[0m
|
||||
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
|
||||
|
||||
@ -35,8 +35,8 @@
|
||||
[0;31m(key) ssh-rsa (1024-bit) -- [fail] using weak hashing algorithm[0m
|
||||
[0;33m `- [warn] using small 1024-bit modulus[0m
|
||||
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
|
||||
[0;31m(key) ssh-dss -- [fail] removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm[0m
|
||||
[0;33m `- [warn] using small 1024-bit modulus[0m
|
||||
[0;31m(key) ssh-dss -- [fail] using small 1024-bit modulus[0m
|
||||
[0;31m `- [fail] removed (in server) and disabled (in client) since OpenSSH 7.0, weak algorithm[0m
|
||||
[0;33m `- [warn] using weak random number generator could reveal the key[0m
|
||||
`- [info] available since OpenSSH 2.1.0, Dropbear SSH 0.28
|
||||
|
||||
|
@ -25,9 +25,9 @@
|
||||
`- [info] available since OpenSSH 2.3.0
|
||||
[0;33m(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm[0m
|
||||
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
|
||||
[0;31m(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm[0m
|
||||
[0;31m(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus[0m
|
||||
[0;31m `- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm[0m
|
||||
[0;31m `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack[0m
|
||||
[0;33m `- [warn] using small 1024-bit modulus[0m
|
||||
[0;33m `- [warn] using weak hashing algorithm[0m
|
||||
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
|
||||
|
||||
|
@ -25,9 +25,9 @@
|
||||
`- [info] available since OpenSSH 2.3.0
|
||||
[0;33m(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm[0m
|
||||
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
|
||||
[0;31m(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm[0m
|
||||
[0;31m(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus[0m
|
||||
[0;31m `- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm[0m
|
||||
[0;31m `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack[0m
|
||||
[0;33m `- [warn] using small 1024-bit modulus[0m
|
||||
[0;33m `- [warn] using weak hashing algorithm[0m
|
||||
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
|
||||
|
||||
|
@ -25,9 +25,9 @@
|
||||
`- [info] available since OpenSSH 2.3.0
|
||||
[0;33m(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm[0m
|
||||
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
|
||||
[0;31m(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm[0m
|
||||
[0;31m(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus[0m
|
||||
[0;31m `- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm[0m
|
||||
[0;31m `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack[0m
|
||||
[0;33m `- [warn] using small 1024-bit modulus[0m
|
||||
[0;33m `- [warn] using weak hashing algorithm[0m
|
||||
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
|
||||
|
||||
|
@ -25,9 +25,9 @@
|
||||
`- [info] available since OpenSSH 2.3.0
|
||||
[0;33m(kex) diffie-hellman-group14-sha1 -- [warn] using weak hashing algorithm[0m
|
||||
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
|
||||
[0;31m(kex) diffie-hellman-group1-sha1 -- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm[0m
|
||||
[0;31m(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus[0m
|
||||
[0;31m `- [fail] removed (in server) since OpenSSH 6.7, unsafe algorithm[0m
|
||||
[0;31m `- [fail] disabled (in client) since OpenSSH 7.0, logjam attack[0m
|
||||
[0;33m `- [warn] using small 1024-bit modulus[0m
|
||||
[0;33m `- [warn] using weak hashing algorithm[0m
|
||||
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
{"errors": [], "host": "localhost", "passed": true, "policy": "Docker policy: test11 (version 1)"}
|
@ -0,0 +1,3 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker policy: test11 (version 1)
|
||||
Result: [0;32m✔ Passed[0m
|
@ -0,0 +1 @@
|
||||
{"errors": [{"actual": ["3072"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "RSA host key (rsa-sha2-256) sizes"}, {"actual": ["3072"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "RSA host key (rsa-sha2-512) sizes"}, {"actual": ["3072"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "RSA host key (ssh-rsa) sizes"}], "host": "localhost", "passed": false, "policy": "Docker policy: test12 (version 1)"}
|
@ -0,0 +1,8 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker policy: test12 (version 1)
|
||||
Result: [0;31m❌ Failed![0m
|
||||
[0;33m
|
||||
Errors:
|
||||
* RSA host key (rsa-sha2-256) sizes did not match. Expected: 4096; Actual: 3072
|
||||
* RSA host key (rsa-sha2-512) sizes did not match. Expected: 4096; Actual: 3072
|
||||
* RSA host key (ssh-rsa) sizes did not match. Expected: 4096; Actual: 3072[0m
|
@ -0,0 +1 @@
|
||||
{"errors": [], "host": "localhost", "passed": true, "policy": "Docker policy: test13 (version 1)"}
|
@ -0,0 +1,3 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker policy: test13 (version 1)
|
||||
Result: [0;32m✔ Passed[0m
|
@ -0,0 +1 @@
|
||||
{"errors": [{"actual": ["2048"], "expected_optional": [""], "expected_required": ["4096"], "mismatched_field": "Group exchange (diffie-hellman-group-exchange-sha256) modulus sizes"}], "host": "localhost", "passed": false, "policy": "Docker policy: test14 (version 1)"}
|
@ -0,0 +1,6 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker policy: test14 (version 1)
|
||||
Result: [0;31m❌ Failed![0m
|
||||
[0;33m
|
||||
Errors:
|
||||
* Group exchange (diffie-hellman-group-exchange-sha256) modulus sizes did not match. Expected: 4096; Actual: 2048[0m
|
@ -0,0 +1 @@
|
||||
{"errors": [], "host": "localhost", "passed": true, "policy": "Docker policy: test6 (version 1)"}
|
@ -0,0 +1,3 @@
|
||||
Host: localhost:2222
|
||||
Policy: Docker policy: test6 (version 1)
|
||||
Result: [0;32m✔ Passed[0m
|
10
test/docker/policies/policy_test1.txt
Normal file
10
test/docker/policies/policy_test1.txt
Normal file
@ -0,0 +1,10 @@
|
||||
#
|
||||
# Docker policy: test1
|
||||
#
|
||||
|
||||
name = "Docker policy: test1"
|
||||
version = 1
|
||||
host keys = ssh-rsa, ssh-dss
|
||||
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
|
||||
ciphers = aes128-ctr, aes192-ctr, aes256-ctr, arcfour256, arcfour128, aes128-cbc, 3des-cbc, blowfish-cbc, cast128-cbc, aes192-cbc, aes256-cbc, arcfour, rijndael-cbc@lysator.liu.se
|
||||
macs = hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac-sha1-96, hmac-md5-96
|
39
test/docker/policies/policy_test10.txt
Normal file
39
test/docker/policies/policy_test10.txt
Normal file
@ -0,0 +1,39 @@
|
||||
#
|
||||
# Docker policy: test10
|
||||
#
|
||||
|
||||
# The name of this policy (displayed in the output during scans). Must be in quotes.
|
||||
name = "Docker poliicy: test10"
|
||||
|
||||
# The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings.
|
||||
version = 1
|
||||
|
||||
# The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal.
|
||||
# banner = "SSH-2.0-OpenSSH_5.6"
|
||||
|
||||
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
|
||||
# header = "[]"
|
||||
|
||||
# The compression options that must match exactly (order matters). Commented out to ignore by default.
|
||||
# compressions = none, zlib@openssh.com
|
||||
|
||||
# RSA host key sizes.
|
||||
hostkey_size_rsa-sha2-256 = 3072
|
||||
hostkey_size_rsa-sha2-512 = 3072
|
||||
hostkey_size_ssh-rsa = 3072
|
||||
hostkey_size_ssh-rsa-cert-v01@openssh.com = 4096
|
||||
|
||||
# RSA CA key sizes.
|
||||
cakey_size_ssh-rsa-cert-v01@openssh.com = 4096
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-rsa, ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = aes128-ctr, aes192-ctr, aes256-ctr, arcfour256, arcfour128, aes128-cbc, 3des-cbc, blowfish-cbc, cast128-cbc, aes192-cbc, aes256-cbc, arcfour, rijndael-cbc@lysator.liu.se
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac-sha1-96, hmac-md5-96
|
35
test/docker/policies/policy_test11.txt
Normal file
35
test/docker/policies/policy_test11.txt
Normal file
@ -0,0 +1,35 @@
|
||||
#
|
||||
# Docker policy: test11
|
||||
#
|
||||
|
||||
# The name of this policy (displayed in the output during scans). Must be in quotes.
|
||||
name = "Docker policy: test11"
|
||||
|
||||
# The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings.
|
||||
version = 1
|
||||
|
||||
# The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal.
|
||||
# banner = "SSH-2.0-OpenSSH_8.0"
|
||||
|
||||
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
|
||||
# header = "[]"
|
||||
|
||||
# The compression options that must match exactly (order matters). Commented out to ignore by default.
|
||||
# compressions = none, zlib@openssh.com
|
||||
|
||||
# RSA host key sizes.
|
||||
hostkey_size_rsa-sha2-256 = 3072
|
||||
hostkey_size_rsa-sha2-512 = 3072
|
||||
hostkey_size_ssh-rsa = 3072
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, umac-128@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1
|
35
test/docker/policies/policy_test12.txt
Normal file
35
test/docker/policies/policy_test12.txt
Normal file
@ -0,0 +1,35 @@
|
||||
#
|
||||
# Docker policy: test12
|
||||
#
|
||||
|
||||
# The name of this policy (displayed in the output during scans). Must be in quotes.
|
||||
name = "Docker policy: test12"
|
||||
|
||||
# The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings.
|
||||
version = 1
|
||||
|
||||
# The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal.
|
||||
# banner = "SSH-2.0-OpenSSH_8.0"
|
||||
|
||||
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
|
||||
# header = "[]"
|
||||
|
||||
# The compression options that must match exactly (order matters). Commented out to ignore by default.
|
||||
# compressions = none, zlib@openssh.com
|
||||
|
||||
# RSA host key sizes.
|
||||
hostkey_size_rsa-sha2-256 = 4096
|
||||
hostkey_size_rsa-sha2-512 = 4096
|
||||
hostkey_size_ssh-rsa = 4096
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, umac-128@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1
|
38
test/docker/policies/policy_test13.txt
Normal file
38
test/docker/policies/policy_test13.txt
Normal file
@ -0,0 +1,38 @@
|
||||
#
|
||||
# Docker policy: test13
|
||||
#
|
||||
|
||||
# The name of this policy (displayed in the output during scans). Must be in quotes.
|
||||
name = "Docker policy: test13"
|
||||
|
||||
# The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings.
|
||||
version = 1
|
||||
|
||||
# The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal.
|
||||
# banner = "SSH-2.0-OpenSSH_8.0"
|
||||
|
||||
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
|
||||
# header = "[]"
|
||||
|
||||
# The compression options that must match exactly (order matters). Commented out to ignore by default.
|
||||
# compressions = none, zlib@openssh.com
|
||||
|
||||
# RSA host key sizes.
|
||||
hostkey_size_rsa-sha2-256 = 3072
|
||||
hostkey_size_rsa-sha2-512 = 3072
|
||||
hostkey_size_ssh-rsa = 3072
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, umac-128@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1
|
38
test/docker/policies/policy_test14.txt
Normal file
38
test/docker/policies/policy_test14.txt
Normal file
@ -0,0 +1,38 @@
|
||||
#
|
||||
# Docker policy: test14
|
||||
#
|
||||
|
||||
# The name of this policy (displayed in the output during scans). Must be in quotes.
|
||||
name = "Docker policy: test14"
|
||||
|
||||
# The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings.
|
||||
version = 1
|
||||
|
||||
# The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal.
|
||||
# banner = "SSH-2.0-OpenSSH_8.0"
|
||||
|
||||
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
|
||||
# header = "[]"
|
||||
|
||||
# The compression options that must match exactly (order matters). Commented out to ignore by default.
|
||||
# compressions = none, zlib@openssh.com
|
||||
|
||||
# RSA host key sizes.
|
||||
hostkey_size_rsa-sha2-256 = 3072
|
||||
hostkey_size_rsa-sha2-512 = 3072
|
||||
hostkey_size_ssh-rsa = 3072
|
||||
|
||||
# Group exchange DH modulus sizes.
|
||||
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 4096
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, umac-128@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1
|
10
test/docker/policies/policy_test2.txt
Normal file
10
test/docker/policies/policy_test2.txt
Normal file
@ -0,0 +1,10 @@
|
||||
#
|
||||
# Docker policy: test2
|
||||
#
|
||||
|
||||
name = "Docker policy: test2"
|
||||
version = 1
|
||||
host keys = ssh-rsa, ssh-dss
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = aes128-ctr, aes192-ctr, aes256-ctr, arcfour256, arcfour128, aes128-cbc, 3des-cbc, blowfish-cbc, cast128-cbc, aes192-cbc, aes256-cbc, arcfour, rijndael-cbc@lysator.liu.se
|
||||
macs = hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac-sha1-96, hmac-md5-96
|
10
test/docker/policies/policy_test3.txt
Normal file
10
test/docker/policies/policy_test3.txt
Normal file
@ -0,0 +1,10 @@
|
||||
#
|
||||
# Docker policy: test3
|
||||
#
|
||||
|
||||
name = "Docker policy: test3"
|
||||
version = 1
|
||||
host keys = ssh-rsa, ssh-dss, key_alg1
|
||||
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
|
||||
ciphers = aes128-ctr, aes192-ctr, aes256-ctr, arcfour256, arcfour128, aes128-cbc, 3des-cbc, blowfish-cbc, cast128-cbc, aes192-cbc, aes256-cbc, arcfour, rijndael-cbc@lysator.liu.se
|
||||
macs = hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac-sha1-96, hmac-md5-96
|
10
test/docker/policies/policy_test4.txt
Normal file
10
test/docker/policies/policy_test4.txt
Normal file
@ -0,0 +1,10 @@
|
||||
#
|
||||
# Docker policy: test4
|
||||
#
|
||||
|
||||
name = "Docker policy: test4"
|
||||
version = 1
|
||||
host keys = ssh-rsa, ssh-dss
|
||||
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
|
||||
ciphers = cipher_alg1, cipher_alg2
|
||||
macs = hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac-sha1-96, hmac-md5-96
|
10
test/docker/policies/policy_test5.txt
Normal file
10
test/docker/policies/policy_test5.txt
Normal file
@ -0,0 +1,10 @@
|
||||
#
|
||||
# Docker policy: test5
|
||||
#
|
||||
|
||||
name = "Docker policy: test5"
|
||||
version = 1
|
||||
host keys = ssh-rsa, ssh-dss
|
||||
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
|
||||
ciphers = aes128-ctr, aes192-ctr, aes256-ctr, arcfour256, arcfour128, aes128-cbc, 3des-cbc, blowfish-cbc, cast128-cbc, aes192-cbc, aes256-cbc, arcfour, rijndael-cbc@lysator.liu.se
|
||||
macs = hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac_alg1, hmac-md5-96
|
12
test/docker/policies/policy_test6.txt
Normal file
12
test/docker/policies/policy_test6.txt
Normal file
@ -0,0 +1,12 @@
|
||||
#
|
||||
# Docker policy: test6
|
||||
#
|
||||
|
||||
name = "Docker policy: test6"
|
||||
version = 1
|
||||
banner = "SSH-2.0-OpenSSH_8.0"
|
||||
compressions = none, zlib@openssh.com
|
||||
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519
|
||||
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
|
||||
ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
|
||||
macs = umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, umac-128@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1
|
39
test/docker/policies/policy_test7.txt
Normal file
39
test/docker/policies/policy_test7.txt
Normal file
@ -0,0 +1,39 @@
|
||||
#
|
||||
# Docker policy: test7
|
||||
#
|
||||
|
||||
# The name of this policy (displayed in the output during scans). Must be in quotes.
|
||||
name = "Docker poliicy: test7"
|
||||
|
||||
# The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings.
|
||||
version = 1
|
||||
|
||||
# The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal.
|
||||
# banner = "SSH-2.0-OpenSSH_5.6"
|
||||
|
||||
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
|
||||
# header = "[]"
|
||||
|
||||
# The compression options that must match exactly (order matters). Commented out to ignore by default.
|
||||
# compressions = none, zlib@openssh.com
|
||||
|
||||
# RSA host key sizes.
|
||||
hostkey_size_rsa-sha2-256 = 3072
|
||||
hostkey_size_rsa-sha2-512 = 3072
|
||||
hostkey_size_ssh-rsa = 3072
|
||||
hostkey_size_ssh-rsa-cert-v01@openssh.com = 3072
|
||||
|
||||
# RSA CA key sizes.
|
||||
cakey_size_ssh-rsa-cert-v01@openssh.com = 1024
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-rsa, ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = aes128-ctr, aes192-ctr, aes256-ctr, arcfour256, arcfour128, aes128-cbc, 3des-cbc, blowfish-cbc, cast128-cbc, aes192-cbc, aes256-cbc, arcfour, rijndael-cbc@lysator.liu.se
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac-sha1-96, hmac-md5-96
|
39
test/docker/policies/policy_test8.txt
Normal file
39
test/docker/policies/policy_test8.txt
Normal file
@ -0,0 +1,39 @@
|
||||
#
|
||||
# Docker policy: test8
|
||||
#
|
||||
|
||||
# The name of this policy (displayed in the output during scans). Must be in quotes.
|
||||
name = "Docker poliicy: test8"
|
||||
|
||||
# The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings.
|
||||
version = 1
|
||||
|
||||
# The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal.
|
||||
# banner = "SSH-2.0-OpenSSH_5.6"
|
||||
|
||||
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
|
||||
# header = "[]"
|
||||
|
||||
# The compression options that must match exactly (order matters). Commented out to ignore by default.
|
||||
# compressions = none, zlib@openssh.com
|
||||
|
||||
# RSA host key sizes.
|
||||
hostkey_size_rsa-sha2-256 = 3072
|
||||
hostkey_size_rsa-sha2-512 = 3072
|
||||
hostkey_size_ssh-rsa = 3072
|
||||
hostkey_size_ssh-rsa-cert-v01@openssh.com = 3072
|
||||
|
||||
# RSA CA key sizes.
|
||||
cakey_size_ssh-rsa-cert-v01@openssh.com = 2048
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-rsa, ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = aes128-ctr, aes192-ctr, aes256-ctr, arcfour256, arcfour128, aes128-cbc, 3des-cbc, blowfish-cbc, cast128-cbc, aes192-cbc, aes256-cbc, arcfour, rijndael-cbc@lysator.liu.se
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac-sha1-96, hmac-md5-96
|
39
test/docker/policies/policy_test9.txt
Normal file
39
test/docker/policies/policy_test9.txt
Normal file
@ -0,0 +1,39 @@
|
||||
#
|
||||
# Docker policy: test9
|
||||
#
|
||||
|
||||
# The name of this policy (displayed in the output during scans). Must be in quotes.
|
||||
name = "Docker poliicy: test9"
|
||||
|
||||
# The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings.
|
||||
version = 1
|
||||
|
||||
# The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal.
|
||||
# banner = "SSH-2.0-OpenSSH_5.6"
|
||||
|
||||
# The header that must match exactly. Commented out to ignore headers, since variability in the header is sometimes normal.
|
||||
# header = "[]"
|
||||
|
||||
# The compression options that must match exactly (order matters). Commented out to ignore by default.
|
||||
# compressions = none, zlib@openssh.com
|
||||
|
||||
# RSA host key sizes.
|
||||
hostkey_size_rsa-sha2-256 = 3072
|
||||
hostkey_size_rsa-sha2-512 = 3072
|
||||
hostkey_size_ssh-rsa = 3072
|
||||
hostkey_size_ssh-rsa-cert-v01@openssh.com = 4096
|
||||
|
||||
# RSA CA key sizes.
|
||||
cakey_size_ssh-rsa-cert-v01@openssh.com = 1024
|
||||
|
||||
# The host key types that must match exactly (order matters).
|
||||
host keys = ssh-rsa, ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# The key exchange algorithms that must match exactly (order matters).
|
||||
key exchanges = diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1
|
||||
|
||||
# The ciphers that must match exactly (order matters).
|
||||
ciphers = aes128-ctr, aes192-ctr, aes256-ctr, arcfour256, arcfour128, aes128-cbc, 3des-cbc, blowfish-cbc, cast128-cbc, aes192-cbc, aes256-cbc, arcfour, rijndael-cbc@lysator.liu.se
|
||||
|
||||
# The MACs that must match exactly (order matters).
|
||||
macs = hmac-md5, hmac-sha1, umac-64@openssh.com, hmac-ripemd160, hmac-ripemd160@openssh.com, hmac-sha1-96, hmac-md5-96
|
@ -1,200 +1,196 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
class TestAuditConf(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.AuditConf = ssh_audit.AuditConf
|
||||
self.usage = ssh_audit.usage
|
||||
class TestAuditConf:
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.AuditConf = ssh_audit.AuditConf
|
||||
self.usage = ssh_audit.usage
|
||||
|
||||
@staticmethod
|
||||
def _test_conf(conf, **kwargs):
|
||||
options = {
|
||||
'host': None,
|
||||
'port': 22,
|
||||
'ssh1': True,
|
||||
'ssh2': True,
|
||||
'batch': False,
|
||||
'colors': True,
|
||||
'verbose': False,
|
||||
'level': 'info',
|
||||
'ipv4': True,
|
||||
'ipv6': True,
|
||||
'ipvo': ()
|
||||
}
|
||||
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.level == options['level']
|
||||
assert conf.ipv4 == options['ipv4']
|
||||
assert conf.ipv6 == options['ipv6']
|
||||
assert conf.ipvo == options['ipvo']
|
||||
@staticmethod
|
||||
def _test_conf(conf, **kwargs):
|
||||
options = {
|
||||
'host': '',
|
||||
'port': 22,
|
||||
'ssh1': True,
|
||||
'ssh2': True,
|
||||
'batch': False,
|
||||
'colors': True,
|
||||
'verbose': False,
|
||||
'level': 'info',
|
||||
'ipv4': True,
|
||||
'ipv6': True,
|
||||
'ipvo': ()
|
||||
}
|
||||
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.level == options['level']
|
||||
assert conf.ipv4 == options['ipv4']
|
||||
assert conf.ipv6 == options['ipv6']
|
||||
assert conf.ipvo == options['ipvo']
|
||||
|
||||
def test_audit_conf_defaults(self):
|
||||
conf = self.AuditConf()
|
||||
self._test_conf(conf)
|
||||
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_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_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_ipvo(self):
|
||||
# ipv4-only
|
||||
conf = self.AuditConf()
|
||||
conf.ipv4 = True
|
||||
assert conf.ipv4 is True
|
||||
assert conf.ipv6 is False
|
||||
assert conf.ipvo == (4,)
|
||||
# ipv6-only
|
||||
conf = self.AuditConf()
|
||||
conf.ipv6 = True
|
||||
assert conf.ipv4 is False
|
||||
assert conf.ipv6 is True
|
||||
assert conf.ipvo == (6,)
|
||||
# ipv4-only (by removing ipv6)
|
||||
conf = self.AuditConf()
|
||||
conf.ipv6 = False
|
||||
assert conf.ipv4 is True
|
||||
assert conf.ipv6 is False
|
||||
assert conf.ipvo == (4, )
|
||||
# ipv6-only (by removing ipv4)
|
||||
conf = self.AuditConf()
|
||||
conf.ipv4 = False
|
||||
assert conf.ipv4 is False
|
||||
assert conf.ipv6 is True
|
||||
assert conf.ipvo == (6, )
|
||||
# ipv4-preferred
|
||||
conf = self.AuditConf()
|
||||
conf.ipv4 = True
|
||||
conf.ipv6 = True
|
||||
assert conf.ipv4 is True
|
||||
assert conf.ipv6 is True
|
||||
assert conf.ipvo == (4, 6)
|
||||
# ipv6-preferred
|
||||
conf = self.AuditConf()
|
||||
conf.ipv6 = True
|
||||
conf.ipv4 = True
|
||||
assert conf.ipv4 is True
|
||||
assert conf.ipv6 is True
|
||||
assert conf.ipvo == (6, 4)
|
||||
# ipvo empty
|
||||
conf = self.AuditConf()
|
||||
conf.ipvo = ()
|
||||
assert conf.ipv4 is True
|
||||
assert conf.ipv6 is True
|
||||
assert conf.ipvo == ()
|
||||
# ipvo validation
|
||||
conf = self.AuditConf()
|
||||
conf.ipvo = (1, 2, 3, 4, 5, 6)
|
||||
assert conf.ipvo == (4, 6)
|
||||
conf.ipvo = (4, 4, 4, 6, 6)
|
||||
assert conf.ipvo == (4, 6)
|
||||
def test_audit_conf_ipvo(self):
|
||||
# ipv4-only
|
||||
conf = self.AuditConf()
|
||||
conf.ipv4 = True
|
||||
assert conf.ipv4 is True
|
||||
assert conf.ipv6 is False
|
||||
assert conf.ipvo == (4,)
|
||||
# ipv6-only
|
||||
conf = self.AuditConf()
|
||||
conf.ipv6 = True
|
||||
assert conf.ipv4 is False
|
||||
assert conf.ipv6 is True
|
||||
assert conf.ipvo == (6,)
|
||||
# ipv4-only (by removing ipv6)
|
||||
conf = self.AuditConf()
|
||||
conf.ipv6 = False
|
||||
assert conf.ipv4 is True
|
||||
assert conf.ipv6 is False
|
||||
assert conf.ipvo == (4, )
|
||||
# ipv6-only (by removing ipv4)
|
||||
conf = self.AuditConf()
|
||||
conf.ipv4 = False
|
||||
assert conf.ipv4 is False
|
||||
assert conf.ipv6 is True
|
||||
assert conf.ipvo == (6, )
|
||||
# ipv4-preferred
|
||||
conf = self.AuditConf()
|
||||
conf.ipv4 = True
|
||||
conf.ipv6 = True
|
||||
assert conf.ipv4 is True
|
||||
assert conf.ipv6 is True
|
||||
assert conf.ipvo == (4, 6)
|
||||
# ipv6-preferred
|
||||
conf = self.AuditConf()
|
||||
conf.ipv6 = True
|
||||
conf.ipv4 = True
|
||||
assert conf.ipv4 is True
|
||||
assert conf.ipv6 is True
|
||||
assert conf.ipvo == (6, 4)
|
||||
# ipvo empty
|
||||
conf = self.AuditConf()
|
||||
conf.ipvo = ()
|
||||
assert conf.ipv4 is True
|
||||
assert conf.ipv6 is True
|
||||
assert conf.ipvo == ()
|
||||
# ipvo validation
|
||||
conf = self.AuditConf()
|
||||
conf.ipvo = (1, 2, 3, 4, 5, 6)
|
||||
assert conf.ipvo == (4, 6)
|
||||
conf.ipvo = (4, 4, 4, 6, 6)
|
||||
assert conf.ipvo == (4, 6)
|
||||
|
||||
def test_audit_conf_level(self):
|
||||
conf = self.AuditConf()
|
||||
for level in ['info', 'warn', 'fail']:
|
||||
conf.level = level
|
||||
assert conf.level == level
|
||||
for level in ['head', 'good', 'unknown', None]:
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
conf.level = level
|
||||
excinfo.match(r'.*invalid level.*')
|
||||
def test_audit_conf_level(self):
|
||||
conf = self.AuditConf()
|
||||
for level in ['info', 'warn', 'fail']:
|
||||
conf.level = level
|
||||
assert conf.level == level
|
||||
for level in ['head', 'good', 'unknown', None]:
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
conf.level = level
|
||||
excinfo.match(r'.*invalid level.*')
|
||||
|
||||
def test_audit_conf_cmdline(self):
|
||||
# pylint: disable=too-many-statements
|
||||
c = lambda x: self.AuditConf.from_cmdline(x.split(), self.usage) # noqa
|
||||
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)
|
||||
conf = c('-p 2222 localhost')
|
||||
self._test_conf(conf, host='localhost', port=2222)
|
||||
conf = c('2001:4860:4860::8888')
|
||||
self._test_conf(conf, host='2001:4860:4860::8888')
|
||||
conf = c('[2001:4860:4860::8888]:22')
|
||||
self._test_conf(conf, host='2001:4860:4860::8888')
|
||||
conf = c('[2001:4860:4860::8888]:2222')
|
||||
self._test_conf(conf, host='2001:4860:4860::8888', port=2222)
|
||||
conf = c('-p 2222 2001:4860:4860::8888')
|
||||
self._test_conf(conf, host='2001:4860:4860::8888', port=2222)
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('localhost:')
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('localhost:abc')
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('-p abc localhost')
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('localhost:-22')
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('-p -22 localhost')
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('localhost:99999')
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('-p 99999 localhost')
|
||||
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('-4 localhost')
|
||||
self._test_conf(conf, host='localhost', ipv4=True, ipv6=False, ipvo=(4,))
|
||||
conf = c('-6 localhost')
|
||||
self._test_conf(conf, host='localhost', ipv4=False, ipv6=True, ipvo=(6,))
|
||||
conf = c('-46 localhost')
|
||||
self._test_conf(conf, host='localhost', ipv4=True, ipv6=True, ipvo=(4, 6))
|
||||
conf = c('-64 localhost')
|
||||
self._test_conf(conf, host='localhost', ipv4=True, ipv6=True, ipvo=(6, 4))
|
||||
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', level='info')
|
||||
conf = c('-l warn localhost')
|
||||
self._test_conf(conf, host='localhost', level='warn')
|
||||
conf = c('-l fail localhost')
|
||||
self._test_conf(conf, host='localhost', level='fail')
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('-l something localhost')
|
||||
def test_audit_conf_cmdline(self):
|
||||
# pylint: disable=too-many-statements
|
||||
c = lambda x: self.AuditConf.from_cmdline(x.split(), self.usage) # noqa
|
||||
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)
|
||||
conf = c('-p 2222 localhost')
|
||||
self._test_conf(conf, host='localhost', port=2222)
|
||||
conf = c('2001:4860:4860::8888')
|
||||
self._test_conf(conf, host='2001:4860:4860::8888')
|
||||
conf = c('[2001:4860:4860::8888]:22')
|
||||
self._test_conf(conf, host='2001:4860:4860::8888')
|
||||
conf = c('[2001:4860:4860::8888]:2222')
|
||||
self._test_conf(conf, host='2001:4860:4860::8888', port=2222)
|
||||
conf = c('-p 2222 2001:4860:4860::8888')
|
||||
self._test_conf(conf, host='2001:4860:4860::8888', port=2222)
|
||||
with pytest.raises(ValueError):
|
||||
conf = c('localhost:abc')
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('-p abc localhost')
|
||||
with pytest.raises(ValueError):
|
||||
conf = c('localhost:-22')
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('-p -22 localhost')
|
||||
with pytest.raises(ValueError):
|
||||
conf = c('localhost:99999')
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('-p 99999 localhost')
|
||||
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('-4 localhost')
|
||||
self._test_conf(conf, host='localhost', ipv4=True, ipv6=False, ipvo=(4,))
|
||||
conf = c('-6 localhost')
|
||||
self._test_conf(conf, host='localhost', ipv4=False, ipv6=True, ipvo=(6,))
|
||||
conf = c('-46 localhost')
|
||||
self._test_conf(conf, host='localhost', ipv4=True, ipv6=True, ipvo=(4, 6))
|
||||
conf = c('-64 localhost')
|
||||
self._test_conf(conf, host='localhost', ipv4=True, ipv6=True, ipvo=(6, 4))
|
||||
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', level='info')
|
||||
conf = c('-l warn localhost')
|
||||
self._test_conf(conf, host='localhost', level='warn')
|
||||
conf = c('-l fail localhost')
|
||||
self._test_conf(conf, host='localhost', level='fail')
|
||||
with pytest.raises(SystemExit):
|
||||
conf = c('-l something localhost')
|
||||
|
@ -1,69 +1,67 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=line-too-long,attribute-defined-outside-init
|
||||
class TestBanner(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.ssh = ssh_audit.SSH
|
||||
class TestBanner:
|
||||
@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) # noqa
|
||||
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_simple_banners(self):
|
||||
banner = lambda x: self.ssh.Banner.parse(x) # noqa
|
||||
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) # noqa
|
||||
assert b('Something') is None
|
||||
assert b('SSH-XXX-OpenSSH_7.3') is None
|
||||
def test_invalid_banners(self):
|
||||
b = lambda x: self.ssh.Banner.parse(x) # noqa
|
||||
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) # noqa
|
||||
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_with_spaces(self):
|
||||
b = lambda x: self.ssh.Banner.parse(x) # noqa
|
||||
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) # noqa
|
||||
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_without_software(self):
|
||||
b = lambda x: self.ssh.Banner.parse(x) # noqa
|
||||
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) # noqa
|
||||
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_comments(self):
|
||||
b = lambda x: self.ssh.Banner.parse(x) # noqa
|
||||
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) # noqa
|
||||
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'
|
||||
def test_banners_with_multiple_protocols(self):
|
||||
b = lambda x: self.ssh.Banner.parse(x) # noqa
|
||||
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'
|
||||
|
@ -1,133 +1,131 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init,bad-whitespace
|
||||
class TestBuffer(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.rbuf = ssh_audit.ReadBuf
|
||||
self.wbuf = ssh_audit.WriteBuf
|
||||
self.utf8rchar = b'\xef\xbf\xbd'
|
||||
class TestBuffer:
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.rbuf = ssh_audit.ReadBuf
|
||||
self.wbuf = ssh_audit.WriteBuf
|
||||
self.utf8rchar = b'\xef\xbf\xbd'
|
||||
|
||||
@classmethod
|
||||
def _b(cls, 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))
|
||||
@classmethod
|
||||
def _b(cls, 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_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() # noqa
|
||||
r = lambda x: self.rbuf(x).read_byte() # noqa
|
||||
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_byte(self):
|
||||
w = lambda x: self.wbuf().write_byte(x).write_flush() # noqa
|
||||
r = lambda x: self.rbuf(x).read_byte() # noqa
|
||||
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() # noqa
|
||||
r = lambda x: self.rbuf(x).read_bool() # noqa
|
||||
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_bool(self):
|
||||
w = lambda x: self.wbuf().write_bool(x).write_flush() # noqa
|
||||
r = lambda x: self.rbuf(x).read_bool() # noqa
|
||||
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() # noqa
|
||||
r = lambda x: self.rbuf(x).read_int() # noqa
|
||||
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_int(self):
|
||||
w = lambda x: self.wbuf().write_int(x).write_flush() # noqa
|
||||
r = lambda x: self.rbuf(x).read_int() # noqa
|
||||
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() # noqa
|
||||
r = lambda x: self.rbuf(x).read_string() # noqa
|
||||
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_string(self):
|
||||
w = lambda x: self.wbuf().write_string(x).write_flush() # noqa
|
||||
r = lambda x: self.rbuf(x).read_string() # noqa
|
||||
tc = [('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() # noqa
|
||||
r = lambda x: self.rbuf(x).read_list() # noqa
|
||||
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_list(self):
|
||||
w = lambda x: self.wbuf().write_list(x).write_flush() # noqa
|
||||
r = lambda x: self.rbuf(x).read_list() # noqa
|
||||
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_list_nonutf8(self):
|
||||
r = lambda x: self.rbuf(x).read_list() # noqa
|
||||
src = self._b('00 00 00 04 de ad be ef')
|
||||
dst = [(b'\xde\xad' + self.utf8rchar + self.utf8rchar).decode('utf-8')]
|
||||
assert r(src) == dst
|
||||
def test_list_nonutf8(self):
|
||||
r = lambda x: self.rbuf(x).read_list() # noqa
|
||||
src = self._b('00 00 00 04 de ad be ef')
|
||||
dst = [(b'\xde\xad' + self.utf8rchar + self.utf8rchar).decode('utf-8')]
|
||||
assert r(src) == dst
|
||||
|
||||
def test_line(self):
|
||||
w = lambda x: self.wbuf().write_line(x).write_flush() # noqa
|
||||
r = lambda x: self.rbuf(x).read_line() # noqa
|
||||
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_line(self):
|
||||
w = lambda x: self.wbuf().write_line(x).write_flush() # noqa
|
||||
r = lambda x: self.rbuf(x).read_line() # noqa
|
||||
tc = [('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_line_nonutf8(self):
|
||||
r = lambda x: self.rbuf(x).read_line() # noqa
|
||||
src = self._b('de ad be af')
|
||||
dst = (b'\xde\xad' + self.utf8rchar + self.utf8rchar).decode('utf-8')
|
||||
assert r(src) == dst
|
||||
def test_line_nonutf8(self):
|
||||
r = lambda x: self.rbuf(x).read_line() # noqa
|
||||
src = self._b('de ad be af')
|
||||
dst = (b'\xde\xad' + self.utf8rchar + self.utf8rchar).decode('utf-8')
|
||||
assert r(src) == dst
|
||||
|
||||
def test_bitlen(self):
|
||||
# pylint: disable=protected-access
|
||||
class Py26Int(int):
|
||||
def bit_length(self):
|
||||
raise AttributeError
|
||||
assert self.wbuf._bitlength(42) == 6
|
||||
assert self.wbuf._bitlength(Py26Int(42)) == 6
|
||||
def test_bitlen(self):
|
||||
# pylint: disable=protected-access
|
||||
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() # noqa
|
||||
mpint1r = lambda x: self.rbuf(x).read_mpint1() # noqa
|
||||
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_mpint1(self):
|
||||
mpint1w = lambda x: self.wbuf().write_mpint1(x).write_flush() # noqa
|
||||
mpint1r = lambda x: self.rbuf(x).read_mpint1() # noqa
|
||||
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() # noqa
|
||||
mpint2r = lambda x: self.rbuf(x).read_mpint2() # noqa
|
||||
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
|
||||
def test_mpint2(self):
|
||||
mpint2w = lambda x: self.wbuf().write_mpint2(x).write_flush() # noqa
|
||||
mpint2r = lambda x: self.rbuf(x).read_mpint2() # noqa
|
||||
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
|
||||
|
41
test/test_build_struct.py
Normal file
41
test/test_build_struct.py
Normal file
@ -0,0 +1,41 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def kex(ssh_audit):
|
||||
kex_algs, key_algs = [], []
|
||||
enc, mac, compression, languages = [], [], ['none'], []
|
||||
cli = ssh_audit.SSH2.KexParty(enc, mac, compression, languages)
|
||||
enc, mac, compression, languages = [], [], ['none'], []
|
||||
srv = ssh_audit.SSH2.KexParty(enc, mac, compression, languages)
|
||||
cookie = os.urandom(16)
|
||||
kex = ssh_audit.SSH2.Kex(cookie, kex_algs, key_algs, cli, srv, 0)
|
||||
return kex
|
||||
|
||||
|
||||
def test_prevent_runtime_error_regression(ssh_audit, kex):
|
||||
"""Prevent a regression of https://github.com/jtesta/ssh-audit/issues/41
|
||||
|
||||
The following test setup does not contain any sensible data.
|
||||
It was made up to reproduce a situation when there are several host
|
||||
keys, and an error occurred when iterating and modifying them at the
|
||||
same time.
|
||||
"""
|
||||
kex.set_host_key("ssh-rsa", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
|
||||
kex.set_host_key("ssh-rsa1", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
|
||||
kex.set_host_key("ssh-rsa2", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
|
||||
kex.set_host_key("ssh-rsa3", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
|
||||
kex.set_host_key("ssh-rsa4", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
|
||||
kex.set_host_key("ssh-rsa5", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
|
||||
kex.set_host_key("ssh-rsa6", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
|
||||
kex.set_host_key("ssh-rsa7", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
|
||||
kex.set_host_key("ssh-rsa8", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00")
|
||||
|
||||
rv = ssh_audit.build_struct(banner=None, kex=kex)
|
||||
|
||||
assert len(rv["fingerprints"]) == 9
|
||||
|
||||
for key in ['banner', 'compression', 'enc', 'fingerprints', 'kex', 'key', 'mac']:
|
||||
assert key in rv
|
@ -1,162 +1,162 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import socket
|
||||
import errno
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
class TestErrors(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.AuditConf = ssh_audit.AuditConf
|
||||
self.audit = ssh_audit.audit
|
||||
class TestErrors:
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.AuditConf = ssh_audit.AuditConf
|
||||
self.audit = ssh_audit.audit
|
||||
|
||||
def _conf(self):
|
||||
conf = self.AuditConf('localhost', 22)
|
||||
conf.colors = False
|
||||
conf.batch = True
|
||||
return conf
|
||||
def _conf(self):
|
||||
conf = self.AuditConf('localhost', 22)
|
||||
conf.colors = False
|
||||
conf.batch = True
|
||||
return conf
|
||||
|
||||
def _audit(self, spy, conf=None, sysexit=True):
|
||||
if conf is None:
|
||||
conf = self._conf()
|
||||
spy.begin()
|
||||
if sysexit:
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(conf)
|
||||
else:
|
||||
self.audit(conf)
|
||||
lines = spy.flush()
|
||||
return lines
|
||||
def _audit(self, spy, conf=None, exit_expected=False):
|
||||
if conf is None:
|
||||
conf = self._conf()
|
||||
spy.begin()
|
||||
|
||||
def test_connection_unresolved(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.gsock.addrinfodata['localhost#22'] = []
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'has no DNS records' in lines[-1]
|
||||
if exit_expected:
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(conf)
|
||||
else:
|
||||
ret = self.audit(conf)
|
||||
assert ret != 0
|
||||
|
||||
def test_connection_refused(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.errors['connect'] = socket.error(errno.ECONNREFUSED, 'Connection refused')
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'Connection refused' in lines[-1]
|
||||
lines = spy.flush()
|
||||
return lines
|
||||
|
||||
def test_connection_timeout(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.errors['connect'] = socket.timeout('timed out')
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'timed out' in lines[-1]
|
||||
def test_connection_unresolved(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.gsock.addrinfodata['localhost#22'] = []
|
||||
lines = self._audit(output_spy, exit_expected=True)
|
||||
assert len(lines) == 1
|
||||
assert 'has no DNS records' in lines[-1]
|
||||
|
||||
def test_recv_empty(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
def test_connection_refused(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.errors['connect'] = socket.error(errno.ECONNREFUSED, 'Connection refused')
|
||||
lines = self._audit(output_spy, exit_expected=True)
|
||||
assert len(lines) == 1
|
||||
assert 'Connection refused' in lines[-1]
|
||||
|
||||
def test_recv_timeout(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.timeout('timed out'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'timed out' in lines[-1]
|
||||
def test_connection_timeout(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.errors['connect'] = socket.timeout('timed out')
|
||||
lines = self._audit(output_spy, exit_expected=True)
|
||||
assert len(lines) == 1
|
||||
assert 'timed out' in lines[-1]
|
||||
|
||||
def test_recv_retry_till_timeout(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.EWOULDBLOCK, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.timeout('timed out'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'timed out' in lines[-1]
|
||||
def test_recv_empty(self, output_spy, virtual_socket):
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
|
||||
def test_recv_retry_till_reset(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.EWOULDBLOCK, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'reset by peer' in lines[-1]
|
||||
def test_recv_timeout(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.timeout('timed out'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'timed out' in lines[-1]
|
||||
|
||||
def test_connection_closed_before_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'reset by peer' in lines[-1]
|
||||
def test_recv_retry_till_timeout(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.EWOULDBLOCK, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.timeout('timed out'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'timed out' in lines[-1]
|
||||
|
||||
def test_connection_closed_after_header(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'header line 1\n')
|
||||
vsocket.rdata.append(b'\n')
|
||||
vsocket.rdata.append(b'header line 2\n')
|
||||
vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 3
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'reset by peer' in lines[-1]
|
||||
def test_recv_retry_till_reset(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.EWOULDBLOCK, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.EAGAIN, 'Resource temporarily unavailable'))
|
||||
vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'reset by peer' in lines[-1]
|
||||
|
||||
def test_connection_closed_after_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(socket.error(54, 'Connection reset by peer'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 2
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'reset by peer' in lines[-1]
|
||||
def test_connection_closed_before_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 1
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'reset by peer' in lines[-1]
|
||||
|
||||
def test_empty_data_after_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 2
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'empty' in lines[-1]
|
||||
def test_connection_closed_after_header(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'header line 1\n')
|
||||
vsocket.rdata.append(b'\n')
|
||||
vsocket.rdata.append(b'header line 2\n')
|
||||
vsocket.rdata.append(socket.error(errno.ECONNRESET, 'Connection reset by peer'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 3
|
||||
assert 'did not receive banner' in lines[-1]
|
||||
assert 'reset by peer' in lines[-1]
|
||||
|
||||
def test_wrong_data_after_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(b'xxx\n')
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 2
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'xxx' in lines[-1]
|
||||
def test_connection_closed_after_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(socket.error(54, 'Connection reset by peer'))
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 2
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'reset by peer' in lines[-1]
|
||||
|
||||
def test_non_ascii_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\xc3\xbc\r\n')
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 3
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'ASCII' in lines[-2]
|
||||
assert lines[-3].endswith('SSH-2.0-ssh-audit-test?')
|
||||
def test_empty_data_after_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 2
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'empty' in lines[-1]
|
||||
|
||||
def test_nonutf8_data_after_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(b'\x81\xff\n')
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 2
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert '\\x81\\xff' in lines[-1]
|
||||
def test_wrong_data_after_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(b'xxx\n')
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 2
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'xxx' in lines[-1]
|
||||
|
||||
def test_protocol_mismatch_by_conf(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-1.3-ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(b'Protocol major versions differ.\n')
|
||||
conf = self._conf()
|
||||
conf.ssh1, conf.ssh2 = True, False
|
||||
lines = self._audit(output_spy, conf)
|
||||
assert len(lines) == 3
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'major versions differ' in lines[-1]
|
||||
def test_non_ascii_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\xc3\xbc\r\n')
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 3
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'ASCII' in lines[-2]
|
||||
assert lines[-3].endswith('SSH-2.0-ssh-audit-test?')
|
||||
|
||||
def test_nonutf8_data_after_banner(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-2.0-ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(b'\x81\xff\n')
|
||||
lines = self._audit(output_spy)
|
||||
assert len(lines) == 2
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert '\\x81\\xff' in lines[-1]
|
||||
|
||||
def test_protocol_mismatch_by_conf(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.rdata.append(b'SSH-1.3-ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(b'Protocol major versions differ.\n')
|
||||
conf = self._conf()
|
||||
conf.ssh1, conf.ssh2 = True, False
|
||||
lines = self._audit(output_spy, conf)
|
||||
assert len(lines) == 3
|
||||
assert 'error reading packet' in lines[-1]
|
||||
assert 'major versions differ' in lines[-1]
|
||||
|
@ -1,175 +1,172 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
class TestOutput(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.Output = ssh_audit.Output
|
||||
self.OutputBuffer = ssh_audit.OutputBuffer
|
||||
class TestOutput:
|
||||
@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_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():
|
||||
print(u'abc')
|
||||
assert output_spy.flush() == []
|
||||
def test_output_buffer_no_flush(self, output_spy):
|
||||
output_spy.begin()
|
||||
with self.OutputBuffer():
|
||||
print('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_buffer_flush(self, output_spy):
|
||||
output_spy.begin()
|
||||
with self.OutputBuffer() as obuf:
|
||||
print('abc')
|
||||
print()
|
||||
print('def')
|
||||
obuf.flush()
|
||||
assert output_spy.flush() == ['abc', '', 'def']
|
||||
|
||||
def test_output_defaults(self):
|
||||
out = self.Output()
|
||||
# default: on
|
||||
assert out.batch is False
|
||||
assert out.use_colors is True
|
||||
assert out.level == 'info'
|
||||
def test_output_defaults(self):
|
||||
out = self.Output()
|
||||
# default: on
|
||||
assert out.batch is False
|
||||
assert out.use_colors is True
|
||||
assert out.level == 'info'
|
||||
|
||||
def test_output_colors(self, output_spy):
|
||||
out = self.Output()
|
||||
# test without colors
|
||||
out.use_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']
|
||||
if not out.colors_supported:
|
||||
return
|
||||
# test with colors
|
||||
out.use_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_colors(self, output_spy):
|
||||
out = self.Output()
|
||||
# test without colors
|
||||
out.use_colors = False
|
||||
output_spy.begin()
|
||||
out.info('info color')
|
||||
assert output_spy.flush() == ['info color']
|
||||
output_spy.begin()
|
||||
out.head('head color')
|
||||
assert output_spy.flush() == ['head color']
|
||||
output_spy.begin()
|
||||
out.good('good color')
|
||||
assert output_spy.flush() == ['good color']
|
||||
output_spy.begin()
|
||||
out.warn('warn color')
|
||||
assert output_spy.flush() == ['warn color']
|
||||
output_spy.begin()
|
||||
out.fail('fail color')
|
||||
assert output_spy.flush() == ['fail color']
|
||||
if not out.colors_supported:
|
||||
return
|
||||
# test with colors
|
||||
out.use_colors = True
|
||||
output_spy.begin()
|
||||
out.info('info color')
|
||||
assert output_spy.flush() == ['info color']
|
||||
output_spy.begin()
|
||||
out.head('head color')
|
||||
assert output_spy.flush() == ['\x1b[0;36mhead color\x1b[0m']
|
||||
output_spy.begin()
|
||||
out.good('good color')
|
||||
assert output_spy.flush() == ['\x1b[0;32mgood color\x1b[0m']
|
||||
output_spy.begin()
|
||||
out.warn('warn color')
|
||||
assert output_spy.flush() == ['\x1b[0;33mwarn color\x1b[0m']
|
||||
output_spy.begin()
|
||||
out.fail('fail color')
|
||||
assert output_spy.flush() == ['\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_sep(self, output_spy):
|
||||
out = self.Output()
|
||||
output_spy.begin()
|
||||
out.sep()
|
||||
out.sep()
|
||||
out.sep()
|
||||
assert output_spy.flush() == ['', '', '']
|
||||
|
||||
def test_output_levels(self):
|
||||
out = self.Output()
|
||||
assert out.get_level('info') == 0
|
||||
assert out.get_level('good') == 0
|
||||
assert out.get_level('warn') == 1
|
||||
assert out.get_level('fail') == 2
|
||||
assert out.get_level('unknown') > 2
|
||||
def test_output_levels(self):
|
||||
out = self.Output()
|
||||
assert out.get_level('info') == 0
|
||||
assert out.get_level('good') == 0
|
||||
assert out.get_level('warn') == 1
|
||||
assert out.get_level('fail') == 2
|
||||
assert out.get_level('unknown') > 2
|
||||
|
||||
def test_output_level_property(self):
|
||||
out = self.Output()
|
||||
out.level = 'info'
|
||||
assert out.level == 'info'
|
||||
out.level = 'good'
|
||||
assert out.level == 'info'
|
||||
out.level = 'warn'
|
||||
assert out.level == 'warn'
|
||||
out.level = 'fail'
|
||||
assert out.level == 'fail'
|
||||
out.level = 'invalid level'
|
||||
assert out.level == 'unknown'
|
||||
def test_output_level_property(self):
|
||||
out = self.Output()
|
||||
out.level = 'info'
|
||||
assert out.level == 'info'
|
||||
out.level = 'good'
|
||||
assert out.level == 'info'
|
||||
out.level = 'warn'
|
||||
assert out.level == 'warn'
|
||||
out.level = 'fail'
|
||||
assert out.level == 'fail'
|
||||
out.level = 'invalid level'
|
||||
assert out.level == 'unknown'
|
||||
|
||||
def test_output_level(self, output_spy):
|
||||
out = self.Output()
|
||||
# visible: all
|
||||
out.level = '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.level = '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.level = '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.level = '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_level(self, output_spy):
|
||||
out = self.Output()
|
||||
# visible: all
|
||||
out.level = '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.level = '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.level = '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.level = '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.level = '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.level = '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
|
||||
def test_output_batch(self, output_spy):
|
||||
out = self.Output()
|
||||
# visible: all
|
||||
output_spy.begin()
|
||||
out.level = '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.level = '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
|
||||
|
337
test/test_policy.py
Normal file
337
test/test_policy.py
Normal file
@ -0,0 +1,337 @@
|
||||
import hashlib
|
||||
import pytest
|
||||
from datetime import date
|
||||
|
||||
|
||||
class TestPolicy:
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.Policy = ssh_audit.Policy
|
||||
self.wbuf = ssh_audit.WriteBuf
|
||||
self.ssh2 = ssh_audit.SSH2
|
||||
|
||||
|
||||
def _get_kex(self):
|
||||
'''Returns an SSH2.Kex object to simulate a server connection.'''
|
||||
|
||||
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(['kex_alg1', 'kex_alg2'])
|
||||
w.write_list(['key_alg1', 'key_alg2'])
|
||||
w.write_list(['cipher_alg1', 'cipher_alg2', 'cipher_alg3'])
|
||||
w.write_list(['cipher_alg1', 'cipher_alg2', 'cipher_alg3'])
|
||||
w.write_list(['mac_alg1', 'mac_alg2', 'mac_alg3'])
|
||||
w.write_list(['mac_alg1', 'mac_alg2', 'mac_alg3'])
|
||||
w.write_list(['comp_alg1', 'comp_alg2'])
|
||||
w.write_list(['comp_alg1', 'comp_alg2'])
|
||||
w.write_list([''])
|
||||
w.write_list([''])
|
||||
w.write_byte(False)
|
||||
w.write_int(0)
|
||||
return self.ssh2.Kex.parse(w.write_flush())
|
||||
|
||||
|
||||
def test_policy_basic(self):
|
||||
'''Ensure that a basic policy can be parsed correctly.'''
|
||||
|
||||
policy_data = '''# This is a comment
|
||||
name = "Test Policy"
|
||||
version = 1
|
||||
|
||||
compressions = comp_alg1
|
||||
host keys = key_alg1
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, mac_alg3'''
|
||||
|
||||
policy = self.Policy(policy_data=policy_data)
|
||||
assert str(policy) == "Name: [Test Policy]\nVersion: [1]\nBanner: {undefined}\nCompressions: comp_alg1\nHost Keys: key_alg1\nKey Exchanges: kex_alg1, kex_alg2\nCiphers: cipher_alg1, cipher_alg2, cipher_alg3\nMACs: mac_alg1, mac_alg2, mac_alg3"
|
||||
|
||||
|
||||
def test_policy_invalid_1(self):
|
||||
'''Basic policy, but with 'ciphersx' instead of 'ciphers'.'''
|
||||
|
||||
policy_data = '''# This is a comment
|
||||
name = "Test Policy"
|
||||
version = 1
|
||||
|
||||
compressions = comp_alg1
|
||||
host keys = key_alg1
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphersx = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, mac_alg3'''
|
||||
|
||||
failed = False
|
||||
try:
|
||||
self.Policy(policy_data=policy_data)
|
||||
except ValueError:
|
||||
failed = True
|
||||
|
||||
assert failed, "Invalid policy did not cause Policy object to throw exception"
|
||||
|
||||
|
||||
def test_policy_invalid_2(self):
|
||||
'''Basic policy, but is missing the required name field.'''
|
||||
|
||||
policy_data = '''# This is a comment
|
||||
#name = "Test Policy"
|
||||
version = 1
|
||||
|
||||
compressions = comp_alg1
|
||||
host keys = key_alg1
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, mac_alg3'''
|
||||
|
||||
failed = False
|
||||
try:
|
||||
self.Policy(policy_data=policy_data)
|
||||
except ValueError:
|
||||
failed = True
|
||||
|
||||
assert failed, "Invalid policy did not cause Policy object to throw exception"
|
||||
|
||||
|
||||
def test_policy_invalid_3(self):
|
||||
'''Basic policy, but is missing the required version field.'''
|
||||
|
||||
policy_data = '''# This is a comment
|
||||
name = "Test Policy"
|
||||
#version = 1
|
||||
|
||||
compressions = comp_alg1
|
||||
host keys = key_alg1
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, mac_alg3'''
|
||||
|
||||
failed = False
|
||||
try:
|
||||
self.Policy(policy_data=policy_data)
|
||||
except ValueError:
|
||||
failed = True
|
||||
|
||||
assert failed, "Invalid policy did not cause Policy object to throw exception"
|
||||
|
||||
|
||||
def test_policy_invalid_4(self):
|
||||
'''Basic policy, but is missing quotes in the name field.'''
|
||||
|
||||
policy_data = '''# This is a comment
|
||||
name = Test Policy
|
||||
version = 1
|
||||
|
||||
compressions = comp_alg1
|
||||
host keys = key_alg1
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, mac_alg3'''
|
||||
|
||||
failed = False
|
||||
try:
|
||||
self.Policy(policy_data=policy_data)
|
||||
except ValueError:
|
||||
failed = True
|
||||
|
||||
assert failed, "Invalid policy did not cause Policy object to throw exception"
|
||||
|
||||
|
||||
def test_policy_invalid_5(self):
|
||||
'''Basic policy, but is missing quotes in the banner field.'''
|
||||
|
||||
policy_data = '''# This is a comment
|
||||
name = "Test Policy"
|
||||
version = 1
|
||||
|
||||
banner = 0mg
|
||||
compressions = comp_alg1
|
||||
host keys = key_alg1
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, mac_alg3'''
|
||||
|
||||
failed = False
|
||||
try:
|
||||
self.Policy(policy_data=policy_data)
|
||||
except ValueError:
|
||||
failed = True
|
||||
|
||||
assert failed, "Invalid policy did not cause Policy object to throw exception"
|
||||
|
||||
|
||||
def test_policy_invalid_6(self):
|
||||
'''Basic policy, but is missing quotes in the header field.'''
|
||||
|
||||
policy_data = '''# This is a comment
|
||||
name = "Test Policy"
|
||||
version = 1
|
||||
|
||||
header = 0mg
|
||||
compressions = comp_alg1
|
||||
host keys = key_alg1
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, mac_alg3'''
|
||||
|
||||
failed = False
|
||||
try:
|
||||
self.Policy(policy_data=policy_data)
|
||||
except ValueError:
|
||||
failed = True
|
||||
|
||||
assert failed, "Invalid policy did not cause Policy object to throw exception"
|
||||
|
||||
|
||||
def test_policy_create_1(self):
|
||||
'''Creates a policy from a kex and ensures it is generated exactly as expected.'''
|
||||
|
||||
kex = self._get_kex()
|
||||
pol_data = self.Policy.create('www.l0l.com', 'bannerX', kex, False)
|
||||
|
||||
# Today's date is embedded in the policy, so filter it out to get repeatable results.
|
||||
pol_data = pol_data.replace(date.today().strftime('%Y/%m/%d'), '[todays date]')
|
||||
|
||||
# Instead of writing out the entire expected policy--line by line--just check that it has the expected hash.
|
||||
assert hashlib.sha256(pol_data.encode('ascii')).hexdigest() == '4af7777fb57a1dad0cf438c899a11d4f625fd9276ea3bb5ef5c9fe8806cb47dc'
|
||||
|
||||
|
||||
def test_policy_evaluate_passing_1(self):
|
||||
'''Creates a policy and evaluates it against the same server'''
|
||||
|
||||
kex = self._get_kex()
|
||||
policy_data = self.Policy.create('www.l0l.com', None, kex, False)
|
||||
policy = self.Policy(policy_data=policy_data)
|
||||
|
||||
ret, errors, error_str = policy.evaluate('SSH Server 1.0', kex)
|
||||
assert ret is True
|
||||
assert len(errors) == 0
|
||||
print(error_str)
|
||||
assert len(error_str) == 0
|
||||
|
||||
|
||||
def test_policy_evaluate_failing_1(self):
|
||||
'''Ensure that a policy with a specified banner fails against a server with a different banner'''
|
||||
|
||||
policy_data = '''name = "Test Policy"
|
||||
version = 1
|
||||
banner = "XXX mismatched banner XXX"
|
||||
compressions = comp_alg1, comp_alg2
|
||||
host keys = key_alg1, key_alg2
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, mac_alg3'''
|
||||
|
||||
policy = self.Policy(policy_data=policy_data)
|
||||
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
|
||||
assert ret is False
|
||||
assert len(errors) == 1
|
||||
assert error_str.find('Banner did not match.') != -1
|
||||
|
||||
|
||||
def test_policy_evaluate_failing_2(self):
|
||||
'''Ensure that a mismatched compressions list results in a failure'''
|
||||
|
||||
policy_data = '''name = "Test Policy"
|
||||
version = 1
|
||||
compressions = XXXmismatchedXXX, comp_alg1, comp_alg2
|
||||
host keys = key_alg1, key_alg2
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, mac_alg3'''
|
||||
|
||||
policy = self.Policy(policy_data=policy_data)
|
||||
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
|
||||
assert ret is False
|
||||
assert len(errors) == 1
|
||||
assert error_str.find('Compression did not match.') != -1
|
||||
|
||||
|
||||
def test_policy_evaluate_failing_3(self):
|
||||
'''Ensure that a mismatched host keys results in a failure'''
|
||||
|
||||
policy_data = '''name = "Test Policy"
|
||||
version = 1
|
||||
compressions = comp_alg1, comp_alg2
|
||||
host keys = XXXmismatchedXXX, key_alg1, key_alg2
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, mac_alg3'''
|
||||
|
||||
policy = self.Policy(policy_data=policy_data)
|
||||
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
|
||||
assert ret is False
|
||||
assert len(errors) == 1
|
||||
assert error_str.find('Host keys did not match.') != -1
|
||||
|
||||
|
||||
def test_policy_evaluate_failing_4(self):
|
||||
'''Ensure that a mismatched key exchange list results in a failure'''
|
||||
|
||||
policy_data = '''name = "Test Policy"
|
||||
version = 1
|
||||
compressions = comp_alg1, comp_alg2
|
||||
host keys = key_alg1, key_alg2
|
||||
key exchanges = XXXmismatchedXXX, kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, mac_alg3'''
|
||||
|
||||
policy = self.Policy(policy_data=policy_data)
|
||||
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
|
||||
assert ret is False
|
||||
assert len(errors) == 1
|
||||
assert error_str.find('Key exchanges did not match.') != -1
|
||||
|
||||
|
||||
def test_policy_evaluate_failing_5(self):
|
||||
'''Ensure that a mismatched cipher list results in a failure'''
|
||||
|
||||
policy_data = '''name = "Test Policy"
|
||||
version = 1
|
||||
compressions = comp_alg1, comp_alg2
|
||||
host keys = key_alg1, key_alg2
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, XXXmismatched, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, mac_alg3'''
|
||||
|
||||
policy = self.Policy(policy_data=policy_data)
|
||||
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
|
||||
assert ret is False
|
||||
assert len(errors) == 1
|
||||
assert error_str.find('Ciphers did not match.') != -1
|
||||
|
||||
|
||||
def test_policy_evaluate_failing_6(self):
|
||||
'''Ensure that a mismatched MAC list results in a failure'''
|
||||
|
||||
policy_data = '''name = "Test Policy"
|
||||
version = 1
|
||||
compressions = comp_alg1, comp_alg2
|
||||
host keys = key_alg1, key_alg2
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, XXXmismatched, mac_alg3'''
|
||||
|
||||
policy = self.Policy(policy_data=policy_data)
|
||||
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
|
||||
assert ret is False
|
||||
assert len(errors) == 1
|
||||
assert error_str.find('MACs did not match.') != -1
|
||||
|
||||
|
||||
def test_policy_evaluate_failing_7(self):
|
||||
'''Ensure that a mismatched host keys and MACs results in a failure'''
|
||||
|
||||
policy_data = '''name = "Test Policy"
|
||||
version = 1
|
||||
compressions = comp_alg1, comp_alg2
|
||||
host keys = key_alg1, key_alg2, XXXmismatchedXXX
|
||||
key exchanges = kex_alg1, kex_alg2
|
||||
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
|
||||
macs = mac_alg1, mac_alg2, XXXmismatchedXXX, mac_alg3'''
|
||||
|
||||
policy = self.Policy(policy_data=policy_data)
|
||||
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
|
||||
assert ret is False
|
||||
assert len(errors) == 2
|
||||
assert error_str.find('Host keys did not match.') != -1
|
||||
assert error_str.find('MACs did not match.') != -1
|
@ -1,85 +1,79 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import socket
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init,protected-access
|
||||
class TestResolve(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.AuditConf = ssh_audit.AuditConf
|
||||
self.audit = ssh_audit.audit
|
||||
self.ssh = ssh_audit.SSH
|
||||
class TestResolve:
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.AuditConf = ssh_audit.AuditConf
|
||||
self.audit = ssh_audit.audit
|
||||
self.ssh = ssh_audit.SSH
|
||||
|
||||
def _conf(self):
|
||||
conf = self.AuditConf('localhost', 22)
|
||||
conf.colors = False
|
||||
conf.batch = True
|
||||
return conf
|
||||
def _conf(self):
|
||||
conf = self.AuditConf('localhost', 22)
|
||||
conf.colors = False
|
||||
conf.batch = True
|
||||
return conf
|
||||
|
||||
def test_resolve_error(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.gsock.addrinfodata['localhost#22'] = socket.gaierror(8, 'hostname nor servname provided, or not known')
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 1
|
||||
assert 'hostname nor servname provided' in lines[-1]
|
||||
def test_resolve_error(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.gsock.addrinfodata['localhost#22'] = socket.gaierror(8, 'hostname nor servname provided, or not known')
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
list(s._resolve(conf.ipvo))
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 1
|
||||
assert 'hostname nor servname provided' in lines[-1]
|
||||
|
||||
def test_resolve_hostname_without_records(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.gsock.addrinfodata['localhost#22'] = []
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
output_spy.begin()
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 0
|
||||
def test_resolve_hostname_without_records(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
vsocket.gsock.addrinfodata['localhost#22'] = []
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
output_spy.begin()
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 0
|
||||
|
||||
def test_resolve_ipv4(self, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
conf = self._conf()
|
||||
conf.ipv4 = True
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 1
|
||||
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||
def test_resolve_ipv4(self, virtual_socket):
|
||||
conf = self._conf()
|
||||
conf.ipv4 = True
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 1
|
||||
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||
|
||||
def test_resolve_ipv6(self, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
conf.ipv6 = True
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 1
|
||||
assert r[0] == (socket.AF_INET6, ('::1', 22))
|
||||
def test_resolve_ipv6(self, virtual_socket):
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
conf.ipv6 = True
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 1
|
||||
assert r[0] == (socket.AF_INET6, ('::1', 22))
|
||||
|
||||
def test_resolve_ipv46_both(self, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 2
|
||||
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||
assert r[1] == (socket.AF_INET6, ('::1', 22))
|
||||
def test_resolve_ipv46_both(self, virtual_socket):
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 2
|
||||
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||
assert r[1] == (socket.AF_INET6, ('::1', 22))
|
||||
|
||||
def test_resolve_ipv46_order(self, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
conf.ipv4 = True
|
||||
conf.ipv6 = True
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 2
|
||||
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||
assert r[1] == (socket.AF_INET6, ('::1', 22))
|
||||
conf = self._conf()
|
||||
conf.ipv6 = True
|
||||
conf.ipv4 = True
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 2
|
||||
assert r[0] == (socket.AF_INET6, ('::1', 22))
|
||||
assert r[1] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||
def test_resolve_ipv46_order(self, virtual_socket):
|
||||
s = self.ssh.Socket('localhost', 22)
|
||||
conf = self._conf()
|
||||
conf.ipv4 = True
|
||||
conf.ipv6 = True
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 2
|
||||
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||
assert r[1] == (socket.AF_INET6, ('::1', 22))
|
||||
conf = self._conf()
|
||||
conf.ipv6 = True
|
||||
conf.ipv4 = True
|
||||
r = list(s._resolve(conf.ipvo))
|
||||
assert len(r) == 2
|
||||
assert r[0] == (socket.AF_INET6, ('::1', 22))
|
||||
assert r[1] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||
|
@ -1,41 +1,38 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import socket
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
class TestSocket(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.ssh = ssh_audit.SSH
|
||||
class TestSocket:
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.ssh = ssh_audit.SSH
|
||||
|
||||
def test_invalid_host(self, virtual_socket):
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket(None, 22)
|
||||
def test_invalid_host(self, virtual_socket):
|
||||
with pytest.raises(ValueError):
|
||||
self.ssh.Socket(None, 22)
|
||||
|
||||
def test_invalid_port(self, virtual_socket):
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket('localhost', 'abc')
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket('localhost', -1)
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket('localhost', 0)
|
||||
with pytest.raises(ValueError):
|
||||
s = self.ssh.Socket('localhost', 65536)
|
||||
def test_invalid_port(self, virtual_socket):
|
||||
with pytest.raises(ValueError):
|
||||
self.ssh.Socket('localhost', 'abc')
|
||||
with pytest.raises(ValueError):
|
||||
self.ssh.Socket('localhost', -1)
|
||||
with pytest.raises(ValueError):
|
||||
self.ssh.Socket('localhost', 0)
|
||||
with pytest.raises(ValueError):
|
||||
self.ssh.Socket('localhost', 65536)
|
||||
|
||||
def test_not_connected_socket(self, virtual_socket):
|
||||
sock = self.ssh.Socket('localhost', 22)
|
||||
banner, header, err = sock.get_banner()
|
||||
assert banner is None
|
||||
assert len(header) == 0
|
||||
assert err == 'not connected'
|
||||
s, e = sock.recv()
|
||||
assert s == -1
|
||||
assert e == 'not connected'
|
||||
s, e = sock.send('nothing')
|
||||
assert s == -1
|
||||
assert e == 'not connected'
|
||||
s, e = sock.send_packet()
|
||||
assert s == -1
|
||||
assert e == 'not connected'
|
||||
def test_not_connected_socket(self, virtual_socket):
|
||||
sock = self.ssh.Socket('localhost', 22)
|
||||
banner, header, err = sock.get_banner()
|
||||
assert banner is None
|
||||
assert len(header) == 0
|
||||
assert err == 'not connected'
|
||||
s, e = sock.recv()
|
||||
assert s == -1
|
||||
assert e == 'not connected'
|
||||
s, e = sock.send('nothing')
|
||||
assert s == -1
|
||||
assert e == 'not connected'
|
||||
s, e = sock.send_packet()
|
||||
assert s == -1
|
||||
assert e == 'not connected'
|
||||
|
@ -1,287 +1,285 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=line-too-long,attribute-defined-outside-init
|
||||
class TestSoftware(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.ssh = ssh_audit.SSH
|
||||
class TestSoftware:
|
||||
@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)) # noqa
|
||||
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_unknown_software(self):
|
||||
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
|
||||
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):
|
||||
# pylint: disable=too-many-statements
|
||||
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
|
||||
# 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_openssh_software(self):
|
||||
# pylint: disable=too-many-statements
|
||||
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
|
||||
# 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)) # noqa
|
||||
# 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_dropbear_software(self):
|
||||
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
|
||||
# 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)) # noqa
|
||||
# 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.4')
|
||||
assert s.vendor is None
|
||||
assert s.product == 'libssh'
|
||||
assert s.version == '0.7.4'
|
||||
assert s.patch is None
|
||||
assert s.os is None
|
||||
assert str(s) == 'libssh 0.7.4'
|
||||
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.4)>'
|
||||
def test_libssh_software(self):
|
||||
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
|
||||
# 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.4')
|
||||
assert s.vendor is None
|
||||
assert s.product == 'libssh'
|
||||
assert s.version == '0.7.4'
|
||||
assert s.patch is None
|
||||
assert s.os is None
|
||||
assert str(s) == 'libssh 0.7.4'
|
||||
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.4)>'
|
||||
|
||||
def test_romsshell_software(self):
|
||||
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
|
||||
# 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_romsshell_software(self):
|
||||
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
|
||||
# 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)) # noqa
|
||||
# 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_hp_ilo_software(self):
|
||||
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
|
||||
# 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)) # noqa
|
||||
# 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_cisco_software(self):
|
||||
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
|
||||
# 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_software_os(self):
|
||||
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
|
||||
# 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))>'
|
||||
def test_software_os(self):
|
||||
ps = lambda x: self.ssh.Software.parse(self.ssh.Banner.parse(x)) # noqa
|
||||
# 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))>'
|
||||
|
@ -1,156 +1,154 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import struct
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=line-too-long,attribute-defined-outside-init
|
||||
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
|
||||
self.audit = ssh_audit.audit
|
||||
self.AuditConf = ssh_audit.AuditConf
|
||||
class TestSSH1:
|
||||
@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
|
||||
self.audit = ssh_audit.audit
|
||||
self.AuditConf = ssh_audit.AuditConf
|
||||
|
||||
def _conf(self):
|
||||
conf = self.AuditConf('localhost', 22)
|
||||
conf.colors = False
|
||||
conf.batch = True
|
||||
conf.verbose = True
|
||||
conf.ssh1 = True
|
||||
conf.ssh2 = False
|
||||
return conf
|
||||
def _conf(self):
|
||||
conf = self.AuditConf('localhost', 22)
|
||||
conf.colors = False
|
||||
conf.batch = True
|
||||
conf.verbose = True
|
||||
conf.ssh1 = True
|
||||
conf.ssh2 = False
|
||||
return conf
|
||||
|
||||
def _create_ssh1_packet(self, payload, valid_crc=True):
|
||||
padding = -(len(payload) + 4) % 8
|
||||
plen = len(payload) + 4
|
||||
pad_bytes = b'\x00' * padding
|
||||
cksum = self.ssh1.crc32(pad_bytes + payload) if valid_crc else 0
|
||||
data = struct.pack('>I', plen) + pad_bytes + payload + struct.pack('>I', cksum)
|
||||
return data
|
||||
def _create_ssh1_packet(self, payload, valid_crc=True):
|
||||
padding = -(len(payload) + 4) % 8
|
||||
plen = len(payload) + 4
|
||||
pad_bytes = b'\x00' * padding
|
||||
cksum = self.ssh1.crc32(pad_bytes + payload) if valid_crc else 0
|
||||
data = struct.pack('>I', plen) + pad_bytes + payload + struct.pack('>I', cksum)
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def _server_key(cls):
|
||||
return (1024, 0x10001, 0xee6552da432e0ac2c422df1a51287507748bfe3b5e3e4fa989a8f49fdc163a17754939ef18ef8a667ea3b71036a151fcd7f5e01ceef1e4439864baf3ac569047582c69d6c128212e0980dcb3168f00d371004039983f6033cd785b8b8f85096c7d9405cbfdc664e27c966356a6b4eb6ee20ad43414b50de18b22829c1880b551)
|
||||
@classmethod
|
||||
def _server_key(cls):
|
||||
return (1024, 0x10001, 0xee6552da432e0ac2c422df1a51287507748bfe3b5e3e4fa989a8f49fdc163a17754939ef18ef8a667ea3b71036a151fcd7f5e01ceef1e4439864baf3ac569047582c69d6c128212e0980dcb3168f00d371004039983f6033cd785b8b8f85096c7d9405cbfdc664e27c966356a6b4eb6ee20ad43414b50de18b22829c1880b551)
|
||||
|
||||
@classmethod
|
||||
def _host_key(cls):
|
||||
return (2048, 0x10001, 0xdfa20cd2a530ccc8c870aa60d9feb3b35deeab81c3215a96557abbd683d21f4600f38e475d87100da9a4404220eeb3bb5584e5a2b5b48ffda58530ea19104a32577d7459d91e76aa711b241050f4cc6d5327ccce254f371acad3be56d46eb5919b73f20dbdb1177b700f00891c5bf4ed128bb90ed541b778288285bcfa28432ab5cbcb8321b6e24760e998e0daa519f093a631e44276d7dd252ce0c08c75e2ab28a7349ead779f97d0f20a6d413bf3623cd216dc35375f6366690bcc41e3b2d5465840ec7ee0dc7e3f1c101d674a0c7dbccbc3942788b111396add2f8153b46a0e4b50d66e57ee92958f1c860dd97cc0e40e32febff915343ed53573142bdf4b)
|
||||
@classmethod
|
||||
def _host_key(cls):
|
||||
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 _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_crc32(self):
|
||||
assert self.ssh1.crc32(b'') == 0x00
|
||||
assert self.ssh1.crc32(b'The quick brown fox jumps over the lazy dog') == 0xb9c60808
|
||||
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 test_fingerprint(self):
|
||||
# pylint: disable=protected-access
|
||||
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 b == 2048
|
||||
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_fingerprint(self):
|
||||
# pylint: disable=protected-access
|
||||
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 b == 2048
|
||||
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 _assert_pkm_keys(self, pkm, skey, hkey):
|
||||
b, e, m = skey
|
||||
assert pkm.server_key_bits == b
|
||||
assert pkm.server_key_public_exponent == e
|
||||
assert pkm.server_key_public_modulus == m
|
||||
b, e, m = hkey
|
||||
assert pkm.host_key_bits == b
|
||||
assert pkm.host_key_public_exponent == e
|
||||
assert pkm.host_key_public_modulus == m
|
||||
def _assert_pkm_keys(self, pkm, skey, hkey):
|
||||
b, e, m = skey
|
||||
assert pkm.server_key_bits == b
|
||||
assert pkm.server_key_public_exponent == e
|
||||
assert pkm.server_key_public_modulus == m
|
||||
b, e, m = hkey
|
||||
assert pkm.host_key_bits == b
|
||||
assert pkm.host_key_public_exponent == e
|
||||
assert pkm.host_key_public_modulus == m
|
||||
|
||||
def _assert_pkm_fields(self, pkm, skey, hkey):
|
||||
assert pkm is not None
|
||||
assert pkm.cookie == b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
|
||||
self._assert_pkm_keys(pkm, skey, hkey)
|
||||
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']
|
||||
fp = self.ssh.Fingerprint(pkm.host_key_fingerprint_data)
|
||||
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 _assert_pkm_fields(self, pkm, skey, hkey):
|
||||
assert pkm is not None
|
||||
assert pkm.cookie == b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
|
||||
self._assert_pkm_keys(pkm, skey, hkey)
|
||||
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']
|
||||
fp = self.ssh.Fingerprint(pkm.host_key_fingerprint_data)
|
||||
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_init(self):
|
||||
cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
|
||||
pflags, cmask, amask = 2, 72, 36
|
||||
skey, hkey = self._server_key(), self._host_key()
|
||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
|
||||
self._assert_pkm_fields(pkm, skey, hkey)
|
||||
for skey2 in ([], [0], [0,1], [0,1,2,3]):
|
||||
with pytest.raises(ValueError):
|
||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey2, hkey, pflags, cmask, amask)
|
||||
for hkey2 in ([], [0], [0,1], [0,1,2,3]):
|
||||
with pytest.raises(ValueError):
|
||||
print(hkey2)
|
||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey2, pflags, cmask, amask)
|
||||
def test_pkm_init(self):
|
||||
cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
|
||||
pflags, cmask, amask = 2, 72, 36
|
||||
skey, hkey = self._server_key(), self._host_key()
|
||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
|
||||
self._assert_pkm_fields(pkm, skey, hkey)
|
||||
for skey2 in ([], [0], [0, 1], [0, 1, 2, 3]):
|
||||
with pytest.raises(ValueError):
|
||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey2, hkey, pflags, cmask, amask)
|
||||
for hkey2 in ([], [0], [0, 1], [0, 1, 2, 3]):
|
||||
with pytest.raises(ValueError):
|
||||
print(hkey2)
|
||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey2, pflags, cmask, amask)
|
||||
|
||||
def test_pkm_read(self):
|
||||
pkm = self.ssh1.PublicKeyMessage.parse(self._pkm_payload())
|
||||
self._assert_pkm_fields(pkm, self._server_key(), self._host_key())
|
||||
def test_pkm_read(self):
|
||||
pkm = self.ssh1.PublicKeyMessage.parse(self._pkm_payload())
|
||||
self._assert_pkm_fields(pkm, self._server_key(), self._host_key())
|
||||
|
||||
def test_pkm_payload(self):
|
||||
cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
|
||||
skey, hkey = self._server_key(), self._host_key()
|
||||
pflags, cmask, amask = 2, 72, 36
|
||||
pkm1 = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
|
||||
pkm2 = self.ssh1.PublicKeyMessage.parse(self._pkm_payload())
|
||||
assert pkm1.payload == pkm2.payload
|
||||
def test_pkm_payload(self):
|
||||
cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
|
||||
skey, hkey = self._server_key(), self._host_key()
|
||||
pflags, cmask, amask = 2, 72, 36
|
||||
pkm1 = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
|
||||
pkm2 = self.ssh1.PublicKeyMessage.parse(self._pkm_payload())
|
||||
assert pkm1.payload == pkm2.payload
|
||||
|
||||
def test_ssh1_server_simple(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
w = self.wbuf()
|
||||
w.write_byte(self.ssh.Protocol.SMSG_PUBLIC_KEY)
|
||||
w.write(self._pkm_payload())
|
||||
vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(self._create_ssh1_packet(w.write_flush()))
|
||||
output_spy.begin()
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 13
|
||||
def test_ssh1_server_simple(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
w = self.wbuf()
|
||||
w.write_byte(self.ssh.Protocol.SMSG_PUBLIC_KEY)
|
||||
w.write(self._pkm_payload())
|
||||
vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(self._create_ssh1_packet(w.write_flush()))
|
||||
output_spy.begin()
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 13
|
||||
|
||||
def test_ssh1_server_invalid_first_packet(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
w = self.wbuf()
|
||||
w.write_byte(self.ssh.Protocol.SMSG_PUBLIC_KEY + 1)
|
||||
w.write(self._pkm_payload())
|
||||
vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(self._create_ssh1_packet(w.write_flush()))
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 7
|
||||
assert 'unknown message' in lines[-1]
|
||||
def test_ssh1_server_invalid_first_packet(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
w = self.wbuf()
|
||||
w.write_byte(self.ssh.Protocol.SMSG_PUBLIC_KEY + 1)
|
||||
w.write(self._pkm_payload())
|
||||
vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(self._create_ssh1_packet(w.write_flush()))
|
||||
output_spy.begin()
|
||||
ret = self.audit(self._conf())
|
||||
assert ret != 0
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 7
|
||||
assert 'unknown message' in lines[-1]
|
||||
|
||||
def test_ssh1_server_invalid_checksum(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
w = self.wbuf()
|
||||
w.write_byte(self.ssh.Protocol.SMSG_PUBLIC_KEY + 1)
|
||||
w.write(self._pkm_payload())
|
||||
vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(self._create_ssh1_packet(w.write_flush(), False))
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 1
|
||||
assert 'checksum' in lines[-1]
|
||||
def test_ssh1_server_invalid_checksum(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
w = self.wbuf()
|
||||
w.write_byte(self.ssh.Protocol.SMSG_PUBLIC_KEY + 1)
|
||||
w.write(self._pkm_payload())
|
||||
vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(self._create_ssh1_packet(w.write_flush(), False))
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 1
|
||||
assert 'checksum' in lines[-1]
|
||||
|
@ -1,155 +1,150 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import struct, os
|
||||
import os
|
||||
import struct
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=line-too-long,attribute-defined-outside-init
|
||||
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
|
||||
self.audit = ssh_audit.audit
|
||||
self.AuditConf = ssh_audit.AuditConf
|
||||
class TestSSH2:
|
||||
@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
|
||||
self.audit = ssh_audit.audit
|
||||
self.AuditConf = ssh_audit.AuditConf
|
||||
|
||||
def _conf(self):
|
||||
conf = self.AuditConf('localhost', 22)
|
||||
conf.colors = False
|
||||
conf.batch = True
|
||||
conf.verbose = True
|
||||
conf.ssh1 = False
|
||||
conf.ssh2 = True
|
||||
return conf
|
||||
def _conf(self):
|
||||
conf = self.AuditConf('localhost', 22)
|
||||
conf.colors = False
|
||||
conf.batch = True
|
||||
conf.verbose = True
|
||||
conf.ssh1 = False
|
||||
conf.ssh2 = True
|
||||
return conf
|
||||
|
||||
@classmethod
|
||||
def _create_ssh2_packet(cls, payload):
|
||||
padding = -(len(payload) + 5) % 8
|
||||
if padding < 4:
|
||||
padding += 8
|
||||
plen = len(payload) + padding + 1
|
||||
pad_bytes = b'\x00' * padding
|
||||
data = struct.pack('>Ib', plen, padding) + payload + pad_bytes
|
||||
return data
|
||||
@classmethod
|
||||
def _create_ssh2_packet(cls, payload):
|
||||
padding = -(len(payload) + 5) % 8
|
||||
if padding < 4:
|
||||
padding += 8
|
||||
plen = len(payload) + padding + 1
|
||||
pad_bytes = b'\x00' * padding
|
||||
data = struct.pack('>Ib', plen, padding) + payload + pad_bytes
|
||||
return data
|
||||
|
||||
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 _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(['bogus_kex1', 'bogus_kex2']) # We use a bogus kex, otherwise the host key tests will kick off and fail.
|
||||
w.write_list(['ssh-rsa', 'rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'])
|
||||
w.write_list(['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc'])
|
||||
w.write_list(['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc'])
|
||||
w.write_list(['umac-64-etm@openssh.com', 'umac-128-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'hmac-sha1-etm@openssh.com', 'umac-64@openssh.com', 'umac-128@openssh.com', 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'])
|
||||
w.write_list(['umac-64-etm@openssh.com', 'umac-128-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'hmac-sha1-etm@openssh.com', 'umac-64@openssh.com', 'umac-128@openssh.com', 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'])
|
||||
w.write_list(['none', 'zlib@openssh.com'])
|
||||
w.write_list(['none', 'zlib@openssh.com'])
|
||||
w.write_list([''])
|
||||
w.write_list([''])
|
||||
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
|
||||
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 == ['bogus_kex1', 'bogus_kex2']
|
||||
assert kex.key_algorithms == ['ssh-rsa', 'rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519']
|
||||
assert kex.client is not None
|
||||
assert kex.server is not None
|
||||
assert kex.client.encryption == ['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc']
|
||||
assert kex.server.encryption == ['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc']
|
||||
assert kex.client.mac == ['umac-64-etm@openssh.com', 'umac-128-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'hmac-sha1-etm@openssh.com', 'umac-64@openssh.com', 'umac-128@openssh.com', 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1']
|
||||
assert kex.server.mac == ['umac-64-etm@openssh.com', 'umac-128-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'hmac-sha1-etm@openssh.com', 'umac-64@openssh.com', 'umac-128@openssh.com', 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1']
|
||||
assert kex.client.compression == ['none', 'zlib@openssh.com']
|
||||
assert kex.server.compression == ['none', 'zlib@openssh.com']
|
||||
assert kex.client.languages == ['']
|
||||
assert kex.server.languages == ['']
|
||||
assert kex.follows is False
|
||||
assert kex.unused == 0
|
||||
|
||||
def _get_empty_kex(self, cookie=None):
|
||||
kex_algs, key_algs = [], []
|
||||
enc, mac, compression, languages = [], [], ['none'], []
|
||||
cli = self.ssh2.KexParty(enc, mac, compression, languages)
|
||||
enc, mac, compression, languages = [], [], ['none'], []
|
||||
srv = self.ssh2.KexParty(enc, mac, compression, languages)
|
||||
if cookie is None:
|
||||
cookie = os.urandom(16)
|
||||
kex = self.ssh2.Kex(cookie, kex_algs, key_algs, cli, srv, 0)
|
||||
return kex
|
||||
def _get_empty_kex(self, cookie=None):
|
||||
kex_algs, key_algs = [], []
|
||||
enc, mac, compression, languages = [], [], ['none'], []
|
||||
cli = self.ssh2.KexParty(enc, mac, compression, languages)
|
||||
enc, mac, compression, languages = [], [], ['none'], []
|
||||
srv = self.ssh2.KexParty(enc, mac, compression, languages)
|
||||
if cookie is None:
|
||||
cookie = os.urandom(16)
|
||||
kex = self.ssh2.Kex(cookie, kex_algs, key_algs, cli, srv, 0)
|
||||
return kex
|
||||
|
||||
def _get_kex_variat1(self):
|
||||
cookie = b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
|
||||
kex = self._get_empty_kex(cookie)
|
||||
kex.kex_algorithms.append('curve25519-sha256@libssh.org')
|
||||
kex.kex_algorithms.append('ecdh-sha2-nistp256')
|
||||
kex.kex_algorithms.append('ecdh-sha2-nistp384')
|
||||
kex.kex_algorithms.append('ecdh-sha2-nistp521')
|
||||
kex.kex_algorithms.append('diffie-hellman-group-exchange-sha256')
|
||||
kex.kex_algorithms.append('diffie-hellman-group14-sha1')
|
||||
kex.key_algorithms.append('ssh-rsa')
|
||||
kex.key_algorithms.append('rsa-sha2-512')
|
||||
kex.key_algorithms.append('rsa-sha2-256')
|
||||
kex.key_algorithms.append('ssh-ed25519')
|
||||
kex.server.encryption.append('chacha20-poly1305@openssh.com')
|
||||
kex.server.encryption.append('aes128-ctr')
|
||||
kex.server.encryption.append('aes192-ctr')
|
||||
kex.server.encryption.append('aes256-ctr')
|
||||
kex.server.encryption.append('aes128-gcm@openssh.com')
|
||||
kex.server.encryption.append('aes256-gcm@openssh.com')
|
||||
kex.server.encryption.append('aes128-cbc')
|
||||
kex.server.encryption.append('aes192-cbc')
|
||||
kex.server.encryption.append('aes256-cbc')
|
||||
kex.server.mac.append('umac-64-etm@openssh.com')
|
||||
kex.server.mac.append('umac-128-etm@openssh.com')
|
||||
kex.server.mac.append('hmac-sha2-256-etm@openssh.com')
|
||||
kex.server.mac.append('hmac-sha2-512-etm@openssh.com')
|
||||
kex.server.mac.append('hmac-sha1-etm@openssh.com')
|
||||
kex.server.mac.append('umac-64@openssh.com')
|
||||
kex.server.mac.append('umac-128@openssh.com')
|
||||
kex.server.mac.append('hmac-sha2-256')
|
||||
kex.server.mac.append('hmac-sha2-512')
|
||||
kex.server.mac.append('hmac-sha1')
|
||||
kex.server.compression.append('zlib@openssh.com')
|
||||
for a in kex.server.encryption:
|
||||
kex.client.encryption.append(a)
|
||||
for a in kex.server.mac:
|
||||
kex.client.mac.append(a)
|
||||
for a in kex.server.compression:
|
||||
if a == 'none':
|
||||
continue
|
||||
kex.client.compression.append(a)
|
||||
return kex
|
||||
def _get_kex_variat1(self):
|
||||
cookie = b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
|
||||
kex = self._get_empty_kex(cookie)
|
||||
kex.kex_algorithms.append('bogus_kex1')
|
||||
kex.kex_algorithms.append('bogus_kex2')
|
||||
kex.key_algorithms.append('ssh-rsa')
|
||||
kex.key_algorithms.append('rsa-sha2-512')
|
||||
kex.key_algorithms.append('rsa-sha2-256')
|
||||
kex.key_algorithms.append('ssh-ed25519')
|
||||
kex.server.encryption.append('chacha20-poly1305@openssh.com')
|
||||
kex.server.encryption.append('aes128-ctr')
|
||||
kex.server.encryption.append('aes192-ctr')
|
||||
kex.server.encryption.append('aes256-ctr')
|
||||
kex.server.encryption.append('aes128-gcm@openssh.com')
|
||||
kex.server.encryption.append('aes256-gcm@openssh.com')
|
||||
kex.server.encryption.append('aes128-cbc')
|
||||
kex.server.encryption.append('aes192-cbc')
|
||||
kex.server.encryption.append('aes256-cbc')
|
||||
kex.server.mac.append('umac-64-etm@openssh.com')
|
||||
kex.server.mac.append('umac-128-etm@openssh.com')
|
||||
kex.server.mac.append('hmac-sha2-256-etm@openssh.com')
|
||||
kex.server.mac.append('hmac-sha2-512-etm@openssh.com')
|
||||
kex.server.mac.append('hmac-sha1-etm@openssh.com')
|
||||
kex.server.mac.append('umac-64@openssh.com')
|
||||
kex.server.mac.append('umac-128@openssh.com')
|
||||
kex.server.mac.append('hmac-sha2-256')
|
||||
kex.server.mac.append('hmac-sha2-512')
|
||||
kex.server.mac.append('hmac-sha1')
|
||||
kex.server.compression.append('zlib@openssh.com')
|
||||
for a in kex.server.encryption:
|
||||
kex.client.encryption.append(a)
|
||||
for a in kex.server.mac:
|
||||
kex.client.mac.append(a)
|
||||
for a in kex.server.compression:
|
||||
if a == 'none':
|
||||
continue
|
||||
kex.client.compression.append(a)
|
||||
return kex
|
||||
|
||||
def test_key_payload(self):
|
||||
kex1 = self._get_kex_variat1()
|
||||
kex2 = self.ssh2.Kex.parse(self._kex_payload())
|
||||
assert kex1.payload == kex2.payload
|
||||
def test_key_payload(self):
|
||||
kex1 = self._get_kex_variat1()
|
||||
kex2 = self.ssh2.Kex.parse(self._kex_payload())
|
||||
assert kex1.payload == kex2.payload
|
||||
|
||||
def test_ssh2_server_simple(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
w = self.wbuf()
|
||||
w.write_byte(self.ssh.Protocol.MSG_KEXINIT)
|
||||
w.write(self._kex_payload())
|
||||
vsocket.rdata.append(b'SSH-2.0-OpenSSH_7.3 ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(self._create_ssh2_packet(w.write_flush()))
|
||||
output_spy.begin()
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 72
|
||||
def test_ssh2_server_simple(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
w = self.wbuf()
|
||||
w.write_byte(self.ssh.Protocol.MSG_KEXINIT)
|
||||
w.write(self._kex_payload())
|
||||
vsocket.rdata.append(b'SSH-2.0-OpenSSH_7.3 ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(self._create_ssh2_packet(w.write_flush()))
|
||||
output_spy.begin()
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 67
|
||||
|
||||
def test_ssh2_server_invalid_first_packet(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
w = self.wbuf()
|
||||
w.write_byte(self.ssh.Protocol.MSG_KEXINIT + 1)
|
||||
vsocket.rdata.append(b'SSH-2.0-OpenSSH_7.3 ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(self._create_ssh2_packet(w.write_flush()))
|
||||
output_spy.begin()
|
||||
with pytest.raises(SystemExit):
|
||||
self.audit(self._conf())
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 3
|
||||
assert 'unknown message' in lines[-1]
|
||||
def test_ssh2_server_invalid_first_packet(self, output_spy, virtual_socket):
|
||||
vsocket = virtual_socket
|
||||
w = self.wbuf()
|
||||
w.write_byte(self.ssh.Protocol.MSG_KEXINIT + 1)
|
||||
vsocket.rdata.append(b'SSH-2.0-OpenSSH_7.3 ssh-audit-test\r\n')
|
||||
vsocket.rdata.append(self._create_ssh2_packet(w.write_flush()))
|
||||
output_spy.begin()
|
||||
ret = self.audit(self._conf())
|
||||
assert ret != 0
|
||||
lines = output_spy.flush()
|
||||
assert len(lines) == 3
|
||||
assert 'unknown message' in lines[-1]
|
||||
|
@ -1,164 +1,162 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
class TestSSHAlgorithm(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.ssh = ssh_audit.SSH
|
||||
class TestSSHAlgorithm:
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.ssh = ssh_audit.SSH
|
||||
|
||||
def _tf(self, v, s=None):
|
||||
return self.ssh.Algorithm.Timeframe().update(v, s)
|
||||
def _tf(self, v, s=None):
|
||||
return self.ssh.Algorithm.Timeframe().update(v, s)
|
||||
|
||||
def test_get_ssh_version(self):
|
||||
def ver(v):
|
||||
return self.ssh.Algorithm.get_ssh_version(v)
|
||||
def test_get_ssh_version(self):
|
||||
def ver(v):
|
||||
return self.ssh.Algorithm.get_ssh_version(v)
|
||||
|
||||
assert ver('7.5') == ('OpenSSH', '7.5', False)
|
||||
assert ver('7.5C') == ('OpenSSH', '7.5', True)
|
||||
assert ver('d2016.74') == ('Dropbear SSH', '2016.74', False)
|
||||
assert ver('l10.7.4') == ('libssh', '0.7.4', False)
|
||||
assert ver('')[1] == ''
|
||||
assert ver('7.5') == ('OpenSSH', '7.5', False)
|
||||
assert ver('7.5C') == ('OpenSSH', '7.5', True)
|
||||
assert ver('d2016.74') == ('Dropbear SSH', '2016.74', False)
|
||||
assert ver('l10.7.4') == ('libssh', '0.7.4', False)
|
||||
assert ver('')[1] == ''
|
||||
|
||||
def test_get_since_text(self):
|
||||
def gst(v):
|
||||
return self.ssh.Algorithm.get_since_text(v)
|
||||
def test_get_since_text(self):
|
||||
def gst(v):
|
||||
return self.ssh.Algorithm.get_since_text(v)
|
||||
|
||||
assert gst(['7.5']) == 'available since OpenSSH 7.5'
|
||||
assert gst(['7.5C']) == 'available since OpenSSH 7.5 (client only)'
|
||||
assert gst(['7.5,']) == 'available since OpenSSH 7.5'
|
||||
assert gst(['d2016.73']) == 'available since Dropbear SSH 2016.73'
|
||||
assert gst(['7.5,d2016.73']) == 'available since OpenSSH 7.5, Dropbear SSH 2016.73'
|
||||
assert gst(['l10.7.4']) is None
|
||||
assert gst([]) is None
|
||||
assert gst(['7.5']) == 'available since OpenSSH 7.5'
|
||||
assert gst(['7.5C']) == 'available since OpenSSH 7.5 (client only)'
|
||||
assert gst(['7.5,']) == 'available since OpenSSH 7.5'
|
||||
assert gst(['d2016.73']) == 'available since Dropbear SSH 2016.73'
|
||||
assert gst(['7.5,d2016.73']) == 'available since OpenSSH 7.5, Dropbear SSH 2016.73'
|
||||
assert gst(['l10.7.4']) is None
|
||||
assert gst([]) is None
|
||||
|
||||
def test_timeframe_creation(self):
|
||||
# pylint: disable=line-too-long,too-many-statements
|
||||
def cmp_tf(v, s, r):
|
||||
assert str(self._tf(v, s)) == str(r)
|
||||
def test_timeframe_creation(self):
|
||||
# pylint: disable=line-too-long,too-many-statements
|
||||
def cmp_tf(v, s, r):
|
||||
assert str(self._tf(v, s)) == str(r)
|
||||
|
||||
cmp_tf(['6.2'], None, {'OpenSSH': ['6.2', None, '6.2', None]})
|
||||
cmp_tf(['6.2'], True, {'OpenSSH': ['6.2', None, None, None]})
|
||||
cmp_tf(['6.2'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C'], None, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C'], True, {})
|
||||
cmp_tf(['6.2C'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.1,6.2C'], None, {'OpenSSH': ['6.1', None, '6.2', None]})
|
||||
cmp_tf(['6.1,6.2C'], True, {'OpenSSH': ['6.1', None, None, None]})
|
||||
cmp_tf(['6.1,6.2C'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.1'], None, {'OpenSSH': ['6.1', None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.1'], True, {'OpenSSH': ['6.1', None, None, None]})
|
||||
cmp_tf(['6.2C,6.1'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.3,6.2C'], None, {'OpenSSH': ['6.3', None, '6.2', None]})
|
||||
cmp_tf(['6.3,6.2C'], True, {'OpenSSH': ['6.3', None, None, None]})
|
||||
cmp_tf(['6.3,6.2C'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.3'], None, {'OpenSSH': ['6.3', None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.3'], True, {'OpenSSH': ['6.3', None, None, None]})
|
||||
cmp_tf(['6.2C,6.3'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2'], None, {'OpenSSH': ['6.2', None, '6.2', None]})
|
||||
cmp_tf(['6.2'], True, {'OpenSSH': ['6.2', None, None, None]})
|
||||
cmp_tf(['6.2'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C'], None, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C'], True, {})
|
||||
cmp_tf(['6.2C'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.1,6.2C'], None, {'OpenSSH': ['6.1', None, '6.2', None]})
|
||||
cmp_tf(['6.1,6.2C'], True, {'OpenSSH': ['6.1', None, None, None]})
|
||||
cmp_tf(['6.1,6.2C'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.1'], None, {'OpenSSH': ['6.1', None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.1'], True, {'OpenSSH': ['6.1', None, None, None]})
|
||||
cmp_tf(['6.2C,6.1'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.3,6.2C'], None, {'OpenSSH': ['6.3', None, '6.2', None]})
|
||||
cmp_tf(['6.3,6.2C'], True, {'OpenSSH': ['6.3', None, None, None]})
|
||||
cmp_tf(['6.3,6.2C'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.3'], None, {'OpenSSH': ['6.3', None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.3'], True, {'OpenSSH': ['6.3', None, None, None]})
|
||||
cmp_tf(['6.2C,6.3'], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
|
||||
cmp_tf(['6.2', '6.6'], None, {'OpenSSH': ['6.2', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.2', '6.6'], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.2C', '6.6'], None, {'OpenSSH': [None, '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.2C', '6.6'], True, {'OpenSSH': [None, '6.6', None, None]})
|
||||
cmp_tf(['6.2C', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.1,6.2C', '6.6'], None, {'OpenSSH': ['6.1', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.1,6.2C', '6.6'], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.2C,6.1', '6.6'], None, {'OpenSSH': ['6.1', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.2C,6.1', '6.6'], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.3,6.2C', '6.6'], None, {'OpenSSH': ['6.3', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.3,6.2C', '6.6'], True, {'OpenSSH': ['6.3', '6.6', None, None]})
|
||||
cmp_tf(['6.3,6.2C', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.2C,6.3', '6.6'], None, {'OpenSSH': ['6.3', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.2C,6.3', '6.6'], True, {'OpenSSH': ['6.3', '6.6', None, None]})
|
||||
cmp_tf(['6.2C,6.3', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.2', '6.6'], None, {'OpenSSH': ['6.2', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.2', '6.6'], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.2C', '6.6'], None, {'OpenSSH': [None, '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.2C', '6.6'], True, {'OpenSSH': [None, '6.6', None, None]})
|
||||
cmp_tf(['6.2C', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.1,6.2C', '6.6'], None, {'OpenSSH': ['6.1', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.1,6.2C', '6.6'], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.2C,6.1', '6.6'], None, {'OpenSSH': ['6.1', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.2C,6.1', '6.6'], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.3,6.2C', '6.6'], None, {'OpenSSH': ['6.3', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.3,6.2C', '6.6'], True, {'OpenSSH': ['6.3', '6.6', None, None]})
|
||||
cmp_tf(['6.3,6.2C', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
cmp_tf(['6.2C,6.3', '6.6'], None, {'OpenSSH': ['6.3', '6.6', '6.2', '6.6']})
|
||||
cmp_tf(['6.2C,6.3', '6.6'], True, {'OpenSSH': ['6.3', '6.6', None, None]})
|
||||
cmp_tf(['6.2C,6.3', '6.6'], False, {'OpenSSH': [None, None, '6.2', '6.6']})
|
||||
|
||||
cmp_tf(['6.2', '6.6', None], None, {'OpenSSH': ['6.2', '6.6', '6.2', None]})
|
||||
cmp_tf(['6.2', '6.6', None], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2', '6.6', None], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C', '6.6', None], None, {'OpenSSH': [None, '6.6', '6.2', None]})
|
||||
cmp_tf(['6.2C', '6.6', None], True, {'OpenSSH': [None, '6.6', None, None]})
|
||||
cmp_tf(['6.2C', '6.6', None], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6', None], None, {'OpenSSH': ['6.1', '6.6', '6.2', None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6', None], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6', None], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6', None], None, {'OpenSSH': ['6.1', '6.6', '6.2', None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6', None], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6', None], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2,6.3C', '6.6', None], None, {'OpenSSH': ['6.2', '6.6', '6.3', None]})
|
||||
cmp_tf(['6.2,6.3C', '6.6', None], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2,6.3C', '6.6', None], False, {'OpenSSH': [None, None, '6.3', None]})
|
||||
cmp_tf(['6.3C,6.2', '6.6', None], None, {'OpenSSH': ['6.2', '6.6', '6.3', None]})
|
||||
cmp_tf(['6.3C,6.2', '6.6', None], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.3C,6.2', '6.6', None], False, {'OpenSSH': [None, None, '6.3', None]})
|
||||
cmp_tf(['6.2', '6.6', None], None, {'OpenSSH': ['6.2', '6.6', '6.2', None]})
|
||||
cmp_tf(['6.2', '6.6', None], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2', '6.6', None], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C', '6.6', None], None, {'OpenSSH': [None, '6.6', '6.2', None]})
|
||||
cmp_tf(['6.2C', '6.6', None], True, {'OpenSSH': [None, '6.6', None, None]})
|
||||
cmp_tf(['6.2C', '6.6', None], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6', None], None, {'OpenSSH': ['6.1', '6.6', '6.2', None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6', None], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6', None], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6', None], None, {'OpenSSH': ['6.1', '6.6', '6.2', None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6', None], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6', None], False, {'OpenSSH': [None, None, '6.2', None]})
|
||||
cmp_tf(['6.2,6.3C', '6.6', None], None, {'OpenSSH': ['6.2', '6.6', '6.3', None]})
|
||||
cmp_tf(['6.2,6.3C', '6.6', None], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2,6.3C', '6.6', None], False, {'OpenSSH': [None, None, '6.3', None]})
|
||||
cmp_tf(['6.3C,6.2', '6.6', None], None, {'OpenSSH': ['6.2', '6.6', '6.3', None]})
|
||||
cmp_tf(['6.3C,6.2', '6.6', None], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.3C,6.2', '6.6', None], False, {'OpenSSH': [None, None, '6.3', None]})
|
||||
|
||||
cmp_tf(['6.2', '6.6', '7.1'], None, {'OpenSSH': ['6.2', '6.6', '6.2', '7.1']})
|
||||
cmp_tf(['6.2', '6.6', '7.1'], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.2', '7.1']})
|
||||
cmp_tf(['6.1,6.2C', '6.6', '7.1'], None, {'OpenSSH': ['6.1', '6.6', '6.2', '7.1']})
|
||||
cmp_tf(['6.1,6.2C', '6.6', '7.1'], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.2', '7.1']})
|
||||
cmp_tf(['6.2C,6.1', '6.6', '7.1'], None, {'OpenSSH': ['6.1', '6.6', '6.2', '7.1']})
|
||||
cmp_tf(['6.2C,6.1', '6.6', '7.1'], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.2', '7.1']})
|
||||
cmp_tf(['6.2,6.3C', '6.6', '7.1'], None, {'OpenSSH': ['6.2', '6.6', '6.3', '7.1']})
|
||||
cmp_tf(['6.2,6.3C', '6.6', '7.1'], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2,6.3C', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.3', '7.1']})
|
||||
cmp_tf(['6.3C,6.2', '6.6', '7.1'], None, {'OpenSSH': ['6.2', '6.6', '6.3', '7.1']})
|
||||
cmp_tf(['6.3C,6.2', '6.6', '7.1'], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.3C,6.2', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.3', '7.1']})
|
||||
cmp_tf(['6.2', '6.6', '7.1'], None, {'OpenSSH': ['6.2', '6.6', '6.2', '7.1']})
|
||||
cmp_tf(['6.2', '6.6', '7.1'], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.2', '7.1']})
|
||||
cmp_tf(['6.1,6.2C', '6.6', '7.1'], None, {'OpenSSH': ['6.1', '6.6', '6.2', '7.1']})
|
||||
cmp_tf(['6.1,6.2C', '6.6', '7.1'], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.1,6.2C', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.2', '7.1']})
|
||||
cmp_tf(['6.2C,6.1', '6.6', '7.1'], None, {'OpenSSH': ['6.1', '6.6', '6.2', '7.1']})
|
||||
cmp_tf(['6.2C,6.1', '6.6', '7.1'], True, {'OpenSSH': ['6.1', '6.6', None, None]})
|
||||
cmp_tf(['6.2C,6.1', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.2', '7.1']})
|
||||
cmp_tf(['6.2,6.3C', '6.6', '7.1'], None, {'OpenSSH': ['6.2', '6.6', '6.3', '7.1']})
|
||||
cmp_tf(['6.2,6.3C', '6.6', '7.1'], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.2,6.3C', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.3', '7.1']})
|
||||
cmp_tf(['6.3C,6.2', '6.6', '7.1'], None, {'OpenSSH': ['6.2', '6.6', '6.3', '7.1']})
|
||||
cmp_tf(['6.3C,6.2', '6.6', '7.1'], True, {'OpenSSH': ['6.2', '6.6', None, None]})
|
||||
cmp_tf(['6.3C,6.2', '6.6', '7.1'], False, {'OpenSSH': [None, None, '6.3', '7.1']})
|
||||
|
||||
tf1 = self._tf(['6.1,d2016.72,6.2C', '6.6,d2016.73', '7.1,d2016.74'])
|
||||
tf2 = self._tf(['d2016.72,6.2C,6.1', 'd2016.73,6.6', 'd2016.74,7.1'])
|
||||
tf3 = self._tf(['d2016.72,6.2C,6.1', '6.6,d2016.73', '7.1,d2016.74'])
|
||||
# check without caring for output order
|
||||
ov = "'OpenSSH': ['6.1', '6.6', '6.2', '7.1']"
|
||||
dv = "'Dropbear SSH': ['2016.72', '2016.73', '2016.72', '2016.74']"
|
||||
assert len(str(tf1)) == len(str(tf2)) == len(str(tf3))
|
||||
assert ov in str(tf1) and ov in str(tf2) and ov in str(tf3)
|
||||
assert dv in str(tf1) and dv in str(tf2) and dv in str(tf3)
|
||||
assert ov in repr(tf1) and ov in repr(tf2) and ov in repr(tf3)
|
||||
assert dv in repr(tf1) and dv in repr(tf2) and dv in repr(tf3)
|
||||
tf1 = self._tf(['6.1,d2016.72,6.2C', '6.6,d2016.73', '7.1,d2016.74'])
|
||||
tf2 = self._tf(['d2016.72,6.2C,6.1', 'd2016.73,6.6', 'd2016.74,7.1'])
|
||||
tf3 = self._tf(['d2016.72,6.2C,6.1', '6.6,d2016.73', '7.1,d2016.74'])
|
||||
# check without caring for output order
|
||||
ov = "'OpenSSH': ['6.1', '6.6', '6.2', '7.1']"
|
||||
dv = "'Dropbear SSH': ['2016.72', '2016.73', '2016.72', '2016.74']"
|
||||
assert len(str(tf1)) == len(str(tf2)) == len(str(tf3))
|
||||
assert ov in str(tf1) and ov in str(tf2) and ov in str(tf3)
|
||||
assert dv in str(tf1) and dv in str(tf2) and dv in str(tf3)
|
||||
assert ov in repr(tf1) and ov in repr(tf2) and ov in repr(tf3)
|
||||
assert dv in repr(tf1) and dv in repr(tf2) and dv in repr(tf3)
|
||||
|
||||
def test_timeframe_object(self):
|
||||
tf = self._tf(['6.1,6.2C', '6.6', '7.1'])
|
||||
assert 'OpenSSH' in tf
|
||||
assert 'Dropbear SSH' not in tf
|
||||
assert 'libssh' not in tf
|
||||
assert 'unknown' not in tf
|
||||
assert tf['OpenSSH'] == ('6.1', '6.6', '6.2', '7.1')
|
||||
assert tf['Dropbear SSH'] == (None, None, None, None)
|
||||
assert tf['libssh'] == (None, None, None, None)
|
||||
assert tf['unknown'] == (None, None, None, None)
|
||||
assert tf.get_from('OpenSSH', True) == '6.1'
|
||||
assert tf.get_till('OpenSSH', True) == '6.6'
|
||||
assert tf.get_from('OpenSSH', False) == '6.2'
|
||||
assert tf.get_till('OpenSSH', False) == '7.1'
|
||||
def test_timeframe_object(self):
|
||||
tf = self._tf(['6.1,6.2C', '6.6', '7.1'])
|
||||
assert 'OpenSSH' in tf
|
||||
assert 'Dropbear SSH' not in tf
|
||||
assert 'libssh' not in tf
|
||||
assert 'unknown' not in tf
|
||||
assert tf['OpenSSH'] == ('6.1', '6.6', '6.2', '7.1')
|
||||
assert tf['Dropbear SSH'] == (None, None, None, None)
|
||||
assert tf['libssh'] == (None, None, None, None)
|
||||
assert tf['unknown'] == (None, None, None, None)
|
||||
assert tf.get_from('OpenSSH', True) == '6.1'
|
||||
assert tf.get_till('OpenSSH', True) == '6.6'
|
||||
assert tf.get_from('OpenSSH', False) == '6.2'
|
||||
assert tf.get_till('OpenSSH', False) == '7.1'
|
||||
|
||||
tf = self._tf(['6.1,d2016.72,6.2C', '6.6,d2016.73', '7.1,d2016.74'])
|
||||
assert 'OpenSSH' in tf
|
||||
assert 'Dropbear SSH' in tf
|
||||
assert 'libssh' not in tf
|
||||
assert 'unknown' not in tf
|
||||
assert tf['OpenSSH'] == ('6.1', '6.6', '6.2', '7.1')
|
||||
assert tf['Dropbear SSH'] == ('2016.72', '2016.73', '2016.72', '2016.74')
|
||||
assert tf['libssh'] == (None, None, None, None)
|
||||
assert tf['unknown'] == (None, None, None, None)
|
||||
assert tf.get_from('OpenSSH', True) == '6.1'
|
||||
assert tf.get_till('OpenSSH', True) == '6.6'
|
||||
assert tf.get_from('OpenSSH', False) == '6.2'
|
||||
assert tf.get_till('OpenSSH', False) == '7.1'
|
||||
assert tf.get_from('Dropbear SSH', True) == '2016.72'
|
||||
assert tf.get_till('Dropbear SSH', True) == '2016.73'
|
||||
assert tf.get_from('Dropbear SSH', False) == '2016.72'
|
||||
assert tf.get_till('Dropbear SSH', False) == '2016.74'
|
||||
ov = "'OpenSSH': ['6.1', '6.6', '6.2', '7.1']"
|
||||
dv = "'Dropbear SSH': ['2016.72', '2016.73', '2016.72', '2016.74']"
|
||||
assert ov in str(tf)
|
||||
assert dv in str(tf)
|
||||
assert ov in repr(tf)
|
||||
assert dv in repr(tf)
|
||||
tf = self._tf(['6.1,d2016.72,6.2C', '6.6,d2016.73', '7.1,d2016.74'])
|
||||
assert 'OpenSSH' in tf
|
||||
assert 'Dropbear SSH' in tf
|
||||
assert 'libssh' not in tf
|
||||
assert 'unknown' not in tf
|
||||
assert tf['OpenSSH'] == ('6.1', '6.6', '6.2', '7.1')
|
||||
assert tf['Dropbear SSH'] == ('2016.72', '2016.73', '2016.72', '2016.74')
|
||||
assert tf['libssh'] == (None, None, None, None)
|
||||
assert tf['unknown'] == (None, None, None, None)
|
||||
assert tf.get_from('OpenSSH', True) == '6.1'
|
||||
assert tf.get_till('OpenSSH', True) == '6.6'
|
||||
assert tf.get_from('OpenSSH', False) == '6.2'
|
||||
assert tf.get_till('OpenSSH', False) == '7.1'
|
||||
assert tf.get_from('Dropbear SSH', True) == '2016.72'
|
||||
assert tf.get_till('Dropbear SSH', True) == '2016.73'
|
||||
assert tf.get_from('Dropbear SSH', False) == '2016.72'
|
||||
assert tf.get_till('Dropbear SSH', False) == '2016.74'
|
||||
ov = "'OpenSSH': ['6.1', '6.6', '6.2', '7.1']"
|
||||
dv = "'Dropbear SSH': ['2016.72', '2016.73', '2016.72', '2016.74']"
|
||||
assert ov in str(tf)
|
||||
assert dv in str(tf)
|
||||
assert ov in repr(tf)
|
||||
assert dv in repr(tf)
|
||||
|
@ -1,218 +1,69 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
class TestUtils(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.utils = ssh_audit.Utils
|
||||
self.PY3 = sys.version_info >= (3,)
|
||||
class TestUtils:
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, ssh_audit):
|
||||
self.utils = ssh_audit.Utils
|
||||
|
||||
def test_to_bytes_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# binary_type (native str, bytes as str)
|
||||
assert self.utils.to_bytes('fran\xc3\xa7ais') == 'fran\xc3\xa7ais'
|
||||
assert self.utils.to_bytes(b'fran\xc3\xa7ais') == 'fran\xc3\xa7ais'
|
||||
# text_type (unicode)
|
||||
assert self.utils.to_bytes(u'fran\xe7ais') == 'fran\xc3\xa7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_bytes(123)
|
||||
def test_to_bytes(self):
|
||||
assert self.utils.to_bytes(b'fran\xc3\xa7ais') == b'fran\xc3\xa7ais'
|
||||
assert self.utils.to_bytes('fran\xe7ais') == b'fran\xc3\xa7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_bytes(123)
|
||||
|
||||
def test_to_bytes_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# binary_type (bytes)
|
||||
assert self.utils.to_bytes(b'fran\xc3\xa7ais') == b'fran\xc3\xa7ais'
|
||||
# text_type (native str as unicode, unicode)
|
||||
assert self.utils.to_bytes('fran\xe7ais') == b'fran\xc3\xa7ais'
|
||||
assert self.utils.to_bytes(u'fran\xe7ais') == b'fran\xc3\xa7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_bytes(123)
|
||||
def test_to_text(self):
|
||||
assert self.utils.to_text(b'fran\xc3\xa7ais') == 'fran\xe7ais'
|
||||
assert self.utils.to_text('fran\xe7ais') == 'fran\xe7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_text(123)
|
||||
|
||||
def test_to_utext_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# binary_type (native str, bytes as str)
|
||||
assert self.utils.to_utext('fran\xc3\xa7ais') == u'fran\xe7ais'
|
||||
assert self.utils.to_utext(b'fran\xc3\xa7ais') == u'fran\xe7ais'
|
||||
# text_type (unicode)
|
||||
assert self.utils.to_utext(u'fran\xe7ais') == u'fran\xe7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_utext(123)
|
||||
def test_is_ascii(self):
|
||||
assert self.utils.is_ascii('francais') is True
|
||||
assert self.utils.is_ascii('fran\xe7ais') is False
|
||||
# other
|
||||
assert self.utils.is_ascii(123) is False
|
||||
|
||||
def test_to_utext_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# binary_type (bytes)
|
||||
assert self.utils.to_utext(b'fran\xc3\xa7ais') == u'fran\xe7ais'
|
||||
# text_type (native str as unicode, unicode)
|
||||
assert self.utils.to_utext('fran\xe7ais') == 'fran\xe7ais'
|
||||
assert self.utils.to_utext(u'fran\xe7ais') == u'fran\xe7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_utext(123)
|
||||
def test_to_ascii(self):
|
||||
assert self.utils.to_ascii('francais') == 'francais'
|
||||
assert self.utils.to_ascii('fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_ascii('fran\xe7ais', 'ignore') == 'franais'
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_ascii(123)
|
||||
|
||||
def test_to_ntext_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# str (native str, bytes as str)
|
||||
assert self.utils.to_ntext('fran\xc3\xa7ais') == 'fran\xc3\xa7ais'
|
||||
assert self.utils.to_ntext(b'fran\xc3\xa7ais') == 'fran\xc3\xa7ais'
|
||||
# text_type (unicode)
|
||||
assert self.utils.to_ntext(u'fran\xe7ais') == 'fran\xc3\xa7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_ntext(123)
|
||||
def test_is_print_ascii(self):
|
||||
assert self.utils.is_print_ascii('francais') is True
|
||||
assert self.utils.is_print_ascii('francais\n') is False
|
||||
assert self.utils.is_print_ascii('fran\xe7ais') is False
|
||||
# other
|
||||
assert self.utils.is_print_ascii(123) is False
|
||||
|
||||
def test_to_ntext_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# str (native str)
|
||||
assert self.utils.to_ntext('fran\xc3\xa7ais') == 'fran\xc3\xa7ais'
|
||||
assert self.utils.to_ntext(u'fran\xe7ais') == 'fran\xe7ais'
|
||||
# binary_type (bytes)
|
||||
assert self.utils.to_ntext(b'fran\xc3\xa7ais') == 'fran\xe7ais'
|
||||
# other
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_ntext(123)
|
||||
def test_to_print_ascii(self):
|
||||
assert self.utils.to_print_ascii('francais') == 'francais'
|
||||
assert self.utils.to_print_ascii('francais\n') == 'francais?'
|
||||
assert self.utils.to_print_ascii('fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_print_ascii('fran\xe7ais\n') == 'fran?ais?'
|
||||
assert self.utils.to_print_ascii('fran\xe7ais', 'ignore') == 'franais'
|
||||
assert self.utils.to_print_ascii('fran\xe7ais\n', 'ignore') == 'franais'
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_print_ascii(123)
|
||||
|
||||
def test_is_ascii_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# text_type (unicode)
|
||||
assert self.utils.is_ascii(u'francais') is True
|
||||
assert self.utils.is_ascii(u'fran\xe7ais') is False
|
||||
# str
|
||||
assert self.utils.is_ascii('francais') is True
|
||||
assert self.utils.is_ascii('fran\xc3\xa7ais') is False
|
||||
# other
|
||||
assert self.utils.is_ascii(123) is False
|
||||
def test_ctoi(self):
|
||||
assert self.utils.ctoi(123) == 123
|
||||
assert self.utils.ctoi('ABC') == 65
|
||||
|
||||
def test_is_ascii_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# text_type (str)
|
||||
assert self.utils.is_ascii('francais') is True
|
||||
assert self.utils.is_ascii(u'francais') is True
|
||||
assert self.utils.is_ascii('fran\xe7ais') is False
|
||||
assert self.utils.is_ascii(u'fran\xe7ais') is False
|
||||
# other
|
||||
assert self.utils.is_ascii(123) is False
|
||||
def test_parse_int(self):
|
||||
assert self.utils.parse_int(123) == 123
|
||||
assert self.utils.parse_int('123') == 123
|
||||
assert self.utils.parse_int(-123) == -123
|
||||
assert self.utils.parse_int('-123') == -123
|
||||
assert self.utils.parse_int('abc') == 0
|
||||
|
||||
def test_to_ascii_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# text_type (unicode)
|
||||
assert self.utils.to_ascii(u'francais') == 'francais'
|
||||
assert self.utils.to_ascii(u'fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_ascii(u'fran\xe7ais', 'ignore') == 'franais'
|
||||
# str
|
||||
assert self.utils.to_ascii('francais') == 'francais'
|
||||
assert self.utils.to_ascii('fran\xc3\xa7ais') == 'fran??ais'
|
||||
assert self.utils.to_ascii('fran\xc3\xa7ais', 'ignore') == 'franais'
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_ascii(123)
|
||||
|
||||
def test_to_ascii_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# text_type (str)
|
||||
assert self.utils.to_ascii('francais') == 'francais'
|
||||
assert self.utils.to_ascii(u'francais') == 'francais'
|
||||
assert self.utils.to_ascii('fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_ascii('fran\xe7ais', 'ignore') == 'franais'
|
||||
assert self.utils.to_ascii(u'fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_ascii(u'fran\xe7ais', 'ignore') == 'franais'
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_ascii(123)
|
||||
|
||||
def test_is_print_ascii_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# text_type (unicode)
|
||||
assert self.utils.is_print_ascii(u'francais') is True
|
||||
assert self.utils.is_print_ascii(u'francais\n') is False
|
||||
assert self.utils.is_print_ascii(u'fran\xe7ais') is False
|
||||
assert self.utils.is_print_ascii(u'fran\xe7ais\n') is False
|
||||
# str
|
||||
assert self.utils.is_print_ascii('francais') is True
|
||||
assert self.utils.is_print_ascii('francais\n') is False
|
||||
assert self.utils.is_print_ascii('fran\xc3\xa7ais') is False
|
||||
# other
|
||||
assert self.utils.is_print_ascii(123) is False
|
||||
|
||||
def test_is_print_ascii_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# text_type (str)
|
||||
assert self.utils.is_print_ascii('francais') is True
|
||||
assert self.utils.is_print_ascii('francais\n') is False
|
||||
assert self.utils.is_print_ascii(u'francais') is True
|
||||
assert self.utils.is_print_ascii(u'francais\n') is False
|
||||
assert self.utils.is_print_ascii('fran\xe7ais') is False
|
||||
assert self.utils.is_print_ascii(u'fran\xe7ais') is False
|
||||
# other
|
||||
assert self.utils.is_print_ascii(123) is False
|
||||
|
||||
def test_to_print_ascii_py2(self):
|
||||
if self.PY3:
|
||||
return
|
||||
# text_type (unicode)
|
||||
assert self.utils.to_print_ascii(u'francais') == 'francais'
|
||||
assert self.utils.to_print_ascii(u'francais\n') == 'francais?'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais\n') == 'fran?ais?'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais', 'ignore') == 'franais'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais\n', 'ignore') == 'franais'
|
||||
# str
|
||||
assert self.utils.to_print_ascii('francais') == 'francais'
|
||||
assert self.utils.to_print_ascii('francais\n') == 'francais?'
|
||||
assert self.utils.to_print_ascii('fran\xc3\xa7ais') == 'fran??ais'
|
||||
assert self.utils.to_print_ascii('fran\xc3\xa7ais\n') == 'fran??ais?'
|
||||
assert self.utils.to_print_ascii('fran\xc3\xa7ais', 'ignore') == 'franais'
|
||||
assert self.utils.to_print_ascii('fran\xc3\xa7ais\n', 'ignore') == 'franais'
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_print_ascii(123)
|
||||
|
||||
def test_to_print_ascii_py3(self):
|
||||
if not self.PY3:
|
||||
return
|
||||
# text_type (str)
|
||||
assert self.utils.to_print_ascii('francais') == 'francais'
|
||||
assert self.utils.to_print_ascii('francais\n') == 'francais?'
|
||||
assert self.utils.to_print_ascii(u'francais') == 'francais'
|
||||
assert self.utils.to_print_ascii(u'francais\n') == 'francais?'
|
||||
assert self.utils.to_print_ascii('fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_print_ascii('fran\xe7ais\n') == 'fran?ais?'
|
||||
assert self.utils.to_print_ascii('fran\xe7ais', 'ignore') == 'franais'
|
||||
assert self.utils.to_print_ascii('fran\xe7ais\n', 'ignore') == 'franais'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais') == 'fran?ais'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais\n') == 'fran?ais?'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais', 'ignore') == 'franais'
|
||||
assert self.utils.to_print_ascii(u'fran\xe7ais\n', 'ignore') == 'franais'
|
||||
with pytest.raises(TypeError):
|
||||
self.utils.to_print_ascii(123)
|
||||
|
||||
def test_ctoi(self):
|
||||
assert self.utils.ctoi(123) == 123
|
||||
assert self.utils.ctoi('ABC') == 65
|
||||
|
||||
def test_parse_int(self):
|
||||
assert self.utils.parse_int(123) == 123
|
||||
assert self.utils.parse_int('123') == 123
|
||||
assert self.utils.parse_int(-123) == -123
|
||||
assert self.utils.parse_int('-123') == -123
|
||||
assert self.utils.parse_int('abc') == 0
|
||||
|
||||
def test_unique_seq(self):
|
||||
assert self.utils.unique_seq((1, 2, 2, 3, 3, 3)) == (1, 2, 3)
|
||||
assert self.utils.unique_seq((3, 3, 3, 2, 2, 1)) == (3, 2, 1)
|
||||
assert self.utils.unique_seq([1, 2, 2, 3, 3, 3]) == [1, 2, 3]
|
||||
assert self.utils.unique_seq([3, 3, 3, 2, 2, 1]) == [3, 2, 1]
|
||||
def test_unique_seq(self):
|
||||
assert self.utils.unique_seq((1, 2, 2, 3, 3, 3)) == (1, 2, 3)
|
||||
assert self.utils.unique_seq((3, 3, 3, 2, 2, 1)) == (3, 2, 1)
|
||||
assert self.utils.unique_seq([1, 2, 2, 3, 3, 3]) == [1, 2, 3]
|
||||
assert self.utils.unique_seq([3, 3, 3, 2, 2, 1]) == [3, 2, 1]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user