tlsaudit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tlsaudit-0.1.0/PKG-INFO +100 -0
- tlsaudit-0.1.0/README.md +93 -0
- tlsaudit-0.1.0/pyproject.toml +24 -0
- tlsaudit-0.1.0/pyproject.toml.orig +24 -0
- tlsaudit-0.1.0/src/tlsaudit/__init__.py +23 -0
- tlsaudit-0.1.0/src/tlsaudit/cli.py +20 -0
- tlsaudit-0.1.0/src/tlsaudit/report.py +53 -0
- tlsaudit-0.1.0/src/tlsaudit/scanner.py +160 -0
tlsaudit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: tlsaudit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A command-line TLS/cipher auditor. Points at a host, tests which TLS/SSL protocol versions it accepts and how strong its negotiated cipher suite is, then reports findings as a simple PASS/FAIL/WARN table.
|
|
5
|
+
Requires-Python: >=3.14
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
|
|
8
|
+
# tlsaudit
|
|
9
|
+
|
|
10
|
+
A command-line TLS/cipher auditor. Points at a host, tests which TLS/SSL protocol
|
|
11
|
+
versions it accepts and how strong its negotiated cipher suite is, then reports
|
|
12
|
+
findings as a simple PASS/FAIL/WARN table.
|
|
13
|
+
|
|
14
|
+
Standard library only (`ssl`, `socket`, `argparse`), no dependencies to install.
|
|
15
|
+
|
|
16
|
+
## What it checks
|
|
17
|
+
|
|
18
|
+
**Protocol version support** — tests SSLv3, TLS 1.0, TLS 1.1, TLS 1.2, and TLS 1.3
|
|
19
|
+
individually by pinning the handshake to each version in turn.
|
|
20
|
+
|
|
21
|
+
- A deprecated version (SSLv3, TLS 1.0, TLS 1.1) that the server still **accepts**
|
|
22
|
+
is a **FAIL**.
|
|
23
|
+
- A deprecated version that the server correctly **rejects** is a **PASS**, that's
|
|
24
|
+
the server behaving securely.
|
|
25
|
+
- TLS 1.2 or TLS 1.3 being **supported** is a **PASS**.
|
|
26
|
+
- TLS 1.2 or TLS 1.3 **not** being supported produces no result at all, that's not
|
|
27
|
+
a finding worth reporting in Phase 1, it just means the server doesn't offer that
|
|
28
|
+
particular version.
|
|
29
|
+
|
|
30
|
+
**Cipher suite strength** — connects normally (letting the server negotiate freely)
|
|
31
|
+
and checks the resulting cipher name against known-weak markers: `RC4`, `3DES`,
|
|
32
|
+
`NULL`, `EXPORT`, `CBC`.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python3 tlsaudit.py <hostname> [--port PORT]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Port defaults to 443.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
$ python3 tlsaudit.py github.com
|
|
46
|
+
[*] Auditing github.com on port 443
|
|
47
|
+
[+] TLS Audit Report for github.com:443
|
|
48
|
+
[PASS] Cipher Suite: Strong cipher suite: TLS_AES_256_GCM_SHA384
|
|
49
|
+
[PASS] TLS 1.2: Supported
|
|
50
|
+
[PASS] TLS 1.3: Supported
|
|
51
|
+
|
|
52
|
+
[+] Verdict Summary: PASS: 3 | FAIL: 0 | WARN: 0
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If the target isn't running TLS at all (wrong port, plain HTTP, etc.), the tool
|
|
56
|
+
prints a distinct banner instead of the normal table:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
$ python3 tlsaudit.py example.com --port 80
|
|
60
|
+
[*] Auditing example.com on port 80
|
|
61
|
+
[+] TLS Audit Report for example.com:80
|
|
62
|
+
------------------------------------------------------------
|
|
63
|
+
[!] NOT A TLS service: Provided port does not run TLS
|
|
64
|
+
------------------------------------------------------------
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Known limitations (by design, not bugs)
|
|
68
|
+
|
|
69
|
+
- **No certificate validation yet.** This is Phase 1, only protocol version and
|
|
70
|
+
cipher strength are checked. Certificate chain validation, expiry, key size,
|
|
71
|
+
and hostname/SAN matching are planned for Phase 2.
|
|
72
|
+
- **SSLv3 is often untestable on modern systems.** Current OpenSSL builds
|
|
73
|
+
frequently disable SSLv3 entirely at compile time, as a hardening measure. On
|
|
74
|
+
those systems, testing SSLv3 fails locally before any packet reaches the
|
|
75
|
+
server, and shows as a WARN ("no protocols available"), not a PASS or FAIL,
|
|
76
|
+
since nothing was actually verified about the server itself.
|
|
77
|
+
- **A timeout doesn't necessarily mean nothing's there.** A well-configured
|
|
78
|
+
firewall silently dropping unsolicited connections looks identical, from this
|
|
79
|
+
tool's perspective, to a genuinely unreachable host. `Connection refused` (an
|
|
80
|
+
explicit rejection) and `Connection timed out` (no response at all) are
|
|
81
|
+
reported separately and mean different things.
|
|
82
|
+
- **Single host only.** No batch scanning from a file yet, and no JSON output.
|
|
83
|
+
Both are planned for Phase 3.
|
|
84
|
+
|
|
85
|
+
## Roadmap
|
|
86
|
+
|
|
87
|
+
This is Phase 1 of a larger plan:
|
|
88
|
+
|
|
89
|
+
- **Phase 2** — certificate chain validation, expiry, key size, SAN matching
|
|
90
|
+
- **Phase 3** — batch scanning from a host list, JSON output
|
|
91
|
+
- **Phase 4** — HTTP security headers, A-F scoring, CAA/OCSP checks, and more
|
|
92
|
+
|
|
93
|
+
## Responsible Usage
|
|
94
|
+
|
|
95
|
+
**DO NOT** scan hosts you are not expressly authorized to do so. Doing so may land you in trouble with
|
|
96
|
+
the owners of the host. Therefore, seek permission from the owner before scanning.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
TBD, add MIT or Apache 2.0 once decided.
|
tlsaudit-0.1.0/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# tlsaudit
|
|
2
|
+
|
|
3
|
+
A command-line TLS/cipher auditor. Points at a host, tests which TLS/SSL protocol
|
|
4
|
+
versions it accepts and how strong its negotiated cipher suite is, then reports
|
|
5
|
+
findings as a simple PASS/FAIL/WARN table.
|
|
6
|
+
|
|
7
|
+
Standard library only (`ssl`, `socket`, `argparse`), no dependencies to install.
|
|
8
|
+
|
|
9
|
+
## What it checks
|
|
10
|
+
|
|
11
|
+
**Protocol version support** — tests SSLv3, TLS 1.0, TLS 1.1, TLS 1.2, and TLS 1.3
|
|
12
|
+
individually by pinning the handshake to each version in turn.
|
|
13
|
+
|
|
14
|
+
- A deprecated version (SSLv3, TLS 1.0, TLS 1.1) that the server still **accepts**
|
|
15
|
+
is a **FAIL**.
|
|
16
|
+
- A deprecated version that the server correctly **rejects** is a **PASS**, that's
|
|
17
|
+
the server behaving securely.
|
|
18
|
+
- TLS 1.2 or TLS 1.3 being **supported** is a **PASS**.
|
|
19
|
+
- TLS 1.2 or TLS 1.3 **not** being supported produces no result at all, that's not
|
|
20
|
+
a finding worth reporting in Phase 1, it just means the server doesn't offer that
|
|
21
|
+
particular version.
|
|
22
|
+
|
|
23
|
+
**Cipher suite strength** — connects normally (letting the server negotiate freely)
|
|
24
|
+
and checks the resulting cipher name against known-weak markers: `RC4`, `3DES`,
|
|
25
|
+
`NULL`, `EXPORT`, `CBC`.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
python3 tlsaudit.py <hostname> [--port PORT]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Port defaults to 443.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
$ python3 tlsaudit.py github.com
|
|
39
|
+
[*] Auditing github.com on port 443
|
|
40
|
+
[+] TLS Audit Report for github.com:443
|
|
41
|
+
[PASS] Cipher Suite: Strong cipher suite: TLS_AES_256_GCM_SHA384
|
|
42
|
+
[PASS] TLS 1.2: Supported
|
|
43
|
+
[PASS] TLS 1.3: Supported
|
|
44
|
+
|
|
45
|
+
[+] Verdict Summary: PASS: 3 | FAIL: 0 | WARN: 0
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If the target isn't running TLS at all (wrong port, plain HTTP, etc.), the tool
|
|
49
|
+
prints a distinct banner instead of the normal table:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
$ python3 tlsaudit.py example.com --port 80
|
|
53
|
+
[*] Auditing example.com on port 80
|
|
54
|
+
[+] TLS Audit Report for example.com:80
|
|
55
|
+
------------------------------------------------------------
|
|
56
|
+
[!] NOT A TLS service: Provided port does not run TLS
|
|
57
|
+
------------------------------------------------------------
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Known limitations (by design, not bugs)
|
|
61
|
+
|
|
62
|
+
- **No certificate validation yet.** This is Phase 1, only protocol version and
|
|
63
|
+
cipher strength are checked. Certificate chain validation, expiry, key size,
|
|
64
|
+
and hostname/SAN matching are planned for Phase 2.
|
|
65
|
+
- **SSLv3 is often untestable on modern systems.** Current OpenSSL builds
|
|
66
|
+
frequently disable SSLv3 entirely at compile time, as a hardening measure. On
|
|
67
|
+
those systems, testing SSLv3 fails locally before any packet reaches the
|
|
68
|
+
server, and shows as a WARN ("no protocols available"), not a PASS or FAIL,
|
|
69
|
+
since nothing was actually verified about the server itself.
|
|
70
|
+
- **A timeout doesn't necessarily mean nothing's there.** A well-configured
|
|
71
|
+
firewall silently dropping unsolicited connections looks identical, from this
|
|
72
|
+
tool's perspective, to a genuinely unreachable host. `Connection refused` (an
|
|
73
|
+
explicit rejection) and `Connection timed out` (no response at all) are
|
|
74
|
+
reported separately and mean different things.
|
|
75
|
+
- **Single host only.** No batch scanning from a file yet, and no JSON output.
|
|
76
|
+
Both are planned for Phase 3.
|
|
77
|
+
|
|
78
|
+
## Roadmap
|
|
79
|
+
|
|
80
|
+
This is Phase 1 of a larger plan:
|
|
81
|
+
|
|
82
|
+
- **Phase 2** — certificate chain validation, expiry, key size, SAN matching
|
|
83
|
+
- **Phase 3** — batch scanning from a host list, JSON output
|
|
84
|
+
- **Phase 4** — HTTP security headers, A-F scoring, CAA/OCSP checks, and more
|
|
85
|
+
|
|
86
|
+
## Responsible Usage
|
|
87
|
+
|
|
88
|
+
**DO NOT** scan hosts you are not expressly authorized to do so. Doing so may land you in trouble with
|
|
89
|
+
the owners of the host. Therefore, seek permission from the owner before scanning.
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
TBD, add MIT or Apache 2.0 once decided.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tlsaudit"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A command-line TLS/cipher auditor. Points at a host, tests which TLS/SSL protocol versions it accepts and how strong its negotiated cipher suite is, then reports findings as a simple PASS/FAIL/WARN table."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[project.scripts]
|
|
10
|
+
tlsaudit = "tlsaudit:main"
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["uv_build>=0.12,<0.13"]
|
|
14
|
+
build-backend = "uv_build"
|
|
15
|
+
|
|
16
|
+
[dependency-groups]
|
|
17
|
+
dev = [
|
|
18
|
+
"bandit>=1.9.4",
|
|
19
|
+
"mypy>=2.3.0",
|
|
20
|
+
"pip-audit>=2.10.1",
|
|
21
|
+
"pytest>=9.1.1",
|
|
22
|
+
"pytest-mock>=3.15.1",
|
|
23
|
+
"ruff>=0.16.2",
|
|
24
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tlsaudit"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A command-line TLS/cipher auditor. Points at a host, tests which TLS/SSL protocol versions it accepts and how strong its negotiated cipher suite is, then reports findings as a simple PASS/FAIL/WARN table."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[project.scripts]
|
|
10
|
+
tlsaudit = "tlsaudit:main"
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["uv_build>=0.12,<0.13"]
|
|
14
|
+
build-backend = "uv_build"
|
|
15
|
+
|
|
16
|
+
[dependency-groups]
|
|
17
|
+
dev = [
|
|
18
|
+
"bandit>=1.9.4",
|
|
19
|
+
"mypy>=2.3.0",
|
|
20
|
+
"pip-audit>=2.10.1",
|
|
21
|
+
"pytest>=9.1.1",
|
|
22
|
+
"pytest-mock>=3.15.1",
|
|
23
|
+
"ruff>=0.16.2",
|
|
24
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .cli import main
|
|
2
|
+
from .report import (
|
|
3
|
+
NOT_TLS_CHECK_NAME,
|
|
4
|
+
CheckResult,
|
|
5
|
+
ScanReport,
|
|
6
|
+
Verdict,
|
|
7
|
+
VersionSpec,
|
|
8
|
+
display_report,
|
|
9
|
+
)
|
|
10
|
+
from .scanner import check_cipher_suite, check_protocol_version, scan_host
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"NOT_TLS_CHECK_NAME",
|
|
14
|
+
"CheckResult",
|
|
15
|
+
"ScanReport",
|
|
16
|
+
"Verdict",
|
|
17
|
+
"VersionSpec",
|
|
18
|
+
"check_cipher_suite",
|
|
19
|
+
"check_protocol_version",
|
|
20
|
+
"display_report",
|
|
21
|
+
"main",
|
|
22
|
+
"scan_host",
|
|
23
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from .report import display_report
|
|
4
|
+
from .scanner import scan_host
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
parser = argparse.ArgumentParser()
|
|
9
|
+
parser.add_argument("hostname", type=str, help="Hostname to audit")
|
|
10
|
+
parser.add_argument("-p", "--port", type=int, default=443, help="Port to audit")
|
|
11
|
+
args = parser.parse_args()
|
|
12
|
+
|
|
13
|
+
print(f"[*] Auditing {args.hostname} on port {args.port}")
|
|
14
|
+
report = scan_host(args.hostname, args.port)
|
|
15
|
+
|
|
16
|
+
display_report(report)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
main()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import ssl
|
|
2
|
+
from collections import Counter
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
NOT_TLS_CHECK_NAME = "Not TLS"
|
|
7
|
+
WEAK_CIPHER_MARKERS = ["RC4", "3DES", "NULL", "EXPORT", "CBC"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Verdict(Enum):
|
|
11
|
+
PASS = "PASS" # nosec: B105 - num member, not a credential; Bandit's heuristic matches on the name "PASS"
|
|
12
|
+
FAIL = "FAIL"
|
|
13
|
+
WARN = "WARN"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class VersionSpec:
|
|
18
|
+
version: ssl.TLSVersion
|
|
19
|
+
label: str
|
|
20
|
+
is_deprecated: bool
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class CheckResult:
|
|
25
|
+
name: str # which check this is, e.g. "TLS 1.0" or "Cipher Suite"?
|
|
26
|
+
detail: str
|
|
27
|
+
verdict: Verdict
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ScanReport:
|
|
32
|
+
hostname: str
|
|
33
|
+
port: int
|
|
34
|
+
results: list[CheckResult] = field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def display_report(report: ScanReport):
|
|
38
|
+
print(f"[+] TLS Audit Report for {report.hostname}:{report.port}")
|
|
39
|
+
if len(report.results) == 1 and report.results[0].name == NOT_TLS_CHECK_NAME:
|
|
40
|
+
print("-" * 40)
|
|
41
|
+
print(f"[!] NOT A TLS service: {report.results[0].detail}")
|
|
42
|
+
print("-" * 40)
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
for result in report.results:
|
|
46
|
+
print(f"[{result.verdict.value}] {result.name}: {result.detail}")
|
|
47
|
+
|
|
48
|
+
verdict_count = Counter({v: 0 for v in Verdict})
|
|
49
|
+
verdict_count.update(result.verdict for result in report.results)
|
|
50
|
+
|
|
51
|
+
summary_part = [f"{v.value}: {verdict_count[v]}" for v in Verdict]
|
|
52
|
+
one_liner = " | ".join(summary_part)
|
|
53
|
+
print(f"\n[+] Verdict Summary: {one_liner}")
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import ssl
|
|
3
|
+
|
|
4
|
+
from .report import (
|
|
5
|
+
NOT_TLS_CHECK_NAME,
|
|
6
|
+
WEAK_CIPHER_MARKERS,
|
|
7
|
+
CheckResult,
|
|
8
|
+
ScanReport,
|
|
9
|
+
Verdict,
|
|
10
|
+
VersionSpec,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def check_protocol_version(
|
|
15
|
+
hostname: str, port: int, version: ssl.TLSVersion, label: str, is_deprecated: bool
|
|
16
|
+
) -> CheckResult | None:
|
|
17
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
18
|
+
context.check_hostname = False
|
|
19
|
+
context.verify_mode = ssl.CERT_NONE
|
|
20
|
+
context.minimum_version = context.maximum_version = version
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
with (
|
|
24
|
+
socket.create_connection((hostname, port), timeout=20) as sock,
|
|
25
|
+
context.wrap_socket(sock, server_hostname=hostname),
|
|
26
|
+
):
|
|
27
|
+
if is_deprecated:
|
|
28
|
+
return CheckResult(label, "Deprecated", Verdict.FAIL)
|
|
29
|
+
return CheckResult(label, "Supported", Verdict.PASS)
|
|
30
|
+
|
|
31
|
+
except ssl.SSLError as e:
|
|
32
|
+
if e.reason == "NO_SHARED_CIPHER":
|
|
33
|
+
return CheckResult(label, "No shared cipher", Verdict.WARN)
|
|
34
|
+
elif e.reason == "NO_PROTOCOLS_AVAILABLE":
|
|
35
|
+
return CheckResult(label, "No protocols available", Verdict.WARN)
|
|
36
|
+
if is_deprecated:
|
|
37
|
+
return CheckResult(label, "Not supported & Deprecated", Verdict.PASS)
|
|
38
|
+
return None
|
|
39
|
+
except TimeoutError:
|
|
40
|
+
return CheckResult(label, "Connection timed out", Verdict.WARN)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def check_cipher_suite(hostname: str, port: int = 443) -> CheckResult:
|
|
44
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
45
|
+
context.check_hostname = False
|
|
46
|
+
context.verify_mode = ssl.CERT_NONE
|
|
47
|
+
try:
|
|
48
|
+
with (
|
|
49
|
+
socket.create_connection((hostname, port), timeout=20) as sock,
|
|
50
|
+
context.wrap_socket(sock, server_hostname=hostname) as ssock,
|
|
51
|
+
):
|
|
52
|
+
cipher_info = ssock.cipher()
|
|
53
|
+
if cipher_info is None:
|
|
54
|
+
return CheckResult("Cipher Suite", "No cipher negotiated", Verdict.WARN)
|
|
55
|
+
cipher_name = cipher_info[0]
|
|
56
|
+
|
|
57
|
+
if any(marker in cipher_name for marker in WEAK_CIPHER_MARKERS):
|
|
58
|
+
return CheckResult(
|
|
59
|
+
"Cipher Suite", f"Weak cipher negotiated: {cipher_name}", Verdict.FAIL
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
return CheckResult(
|
|
63
|
+
"Cipher Suite", f"Strong cipher suite: {cipher_name}", Verdict.PASS
|
|
64
|
+
)
|
|
65
|
+
except TimeoutError:
|
|
66
|
+
return CheckResult("Cipher Suite", "Connection timed out", Verdict.WARN)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def scan_host(hostname: str, port: int) -> ScanReport:
|
|
70
|
+
report = ScanReport(hostname, port)
|
|
71
|
+
target_versions = [
|
|
72
|
+
VersionSpec(ssl.TLSVersion.SSLv3, "SSL 3.0", True),
|
|
73
|
+
VersionSpec(ssl.TLSVersion.TLSv1, "TLS 1.0", True),
|
|
74
|
+
VersionSpec(ssl.TLSVersion.TLSv1_1, "TLS 1.1", True),
|
|
75
|
+
VersionSpec(ssl.TLSVersion.TLSv1_2, "TLS 1.2", False),
|
|
76
|
+
VersionSpec(ssl.TLSVersion.TLSv1_3, "TLS 1.3", False),
|
|
77
|
+
]
|
|
78
|
+
try:
|
|
79
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
80
|
+
context.check_hostname = False
|
|
81
|
+
context.verify_mode = ssl.CERT_NONE
|
|
82
|
+
with (
|
|
83
|
+
socket.create_connection((hostname, port), timeout=10) as sock,
|
|
84
|
+
context.wrap_socket(sock, server_hostname=hostname),
|
|
85
|
+
):
|
|
86
|
+
pass
|
|
87
|
+
check_cipher_suite_result = check_cipher_suite(hostname, port)
|
|
88
|
+
report.results.append(check_cipher_suite_result)
|
|
89
|
+
for version in target_versions:
|
|
90
|
+
try:
|
|
91
|
+
result = check_protocol_version(
|
|
92
|
+
hostname,
|
|
93
|
+
port,
|
|
94
|
+
version.version,
|
|
95
|
+
version.label,
|
|
96
|
+
version.is_deprecated,
|
|
97
|
+
)
|
|
98
|
+
if result:
|
|
99
|
+
report.results.append(result)
|
|
100
|
+
except Exception as e: # noqa: BLE001 — deliberate safety net; check_protocol_version already handles its known exception types internally
|
|
101
|
+
report.results.append(
|
|
102
|
+
CheckResult(
|
|
103
|
+
version.label, f"Could not complete check: {e!s}", Verdict.WARN
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
return report
|
|
107
|
+
except socket.gaierror:
|
|
108
|
+
return ScanReport(
|
|
109
|
+
hostname,
|
|
110
|
+
port,
|
|
111
|
+
[CheckResult("Hostname", "Could not resolve hostname", Verdict.WARN)],
|
|
112
|
+
)
|
|
113
|
+
except TimeoutError:
|
|
114
|
+
return ScanReport(
|
|
115
|
+
hostname,
|
|
116
|
+
port,
|
|
117
|
+
[CheckResult("Connection", "Connection timed out", Verdict.WARN)],
|
|
118
|
+
)
|
|
119
|
+
except ConnectionRefusedError:
|
|
120
|
+
return ScanReport(
|
|
121
|
+
hostname,
|
|
122
|
+
port,
|
|
123
|
+
[CheckResult("Connection", "Connection refused", Verdict.WARN)],
|
|
124
|
+
)
|
|
125
|
+
except ConnectionResetError:
|
|
126
|
+
return ScanReport(
|
|
127
|
+
hostname,
|
|
128
|
+
port,
|
|
129
|
+
[CheckResult("Connection", "Connection reset", Verdict.WARN)],
|
|
130
|
+
)
|
|
131
|
+
except ssl.SSLError as e:
|
|
132
|
+
if e.reason == "RECORD_LAYER_FAILURE":
|
|
133
|
+
return ScanReport(
|
|
134
|
+
hostname,
|
|
135
|
+
port,
|
|
136
|
+
[
|
|
137
|
+
CheckResult(
|
|
138
|
+
NOT_TLS_CHECK_NAME,
|
|
139
|
+
"Provided port does not run TLS",
|
|
140
|
+
Verdict.WARN,
|
|
141
|
+
)
|
|
142
|
+
],
|
|
143
|
+
)
|
|
144
|
+
return ScanReport(
|
|
145
|
+
hostname,
|
|
146
|
+
port,
|
|
147
|
+
[
|
|
148
|
+
CheckResult(
|
|
149
|
+
NOT_TLS_CHECK_NAME,
|
|
150
|
+
f"Provided port does not appear to run TLS ({e.reason})",
|
|
151
|
+
Verdict.WARN,
|
|
152
|
+
)
|
|
153
|
+
],
|
|
154
|
+
)
|
|
155
|
+
except OSError as e:
|
|
156
|
+
return ScanReport(
|
|
157
|
+
hostname,
|
|
158
|
+
port,
|
|
159
|
+
[CheckResult("Network Error", f"OS Error: {e}", Verdict.WARN)],
|
|
160
|
+
)
|