Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/scratch
poetry.lock
tests/*
__pycache__
.mypy_cache
.ruff_cache
.venv
25 changes: 25 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know you could do this, sweet!

- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0 # Use the sha / tag you want to point at
hooks:
- id: mypy
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.7.4
hooks:
# Run the linter.
- id: ruff
name: ruff
description: "Run 'ruff' for extremely fast Python linting"
entry: ruff check --force-exclude
language: python
args: [--fix, --exit-non-zero-on-fix]
require_serial: true
# Run the formatter.
- id: ruff-format
6 changes: 6 additions & 0 deletions .vscode/settings.json
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know about this, nice!

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"cSpell.words": ["ATTRIBS", "caissuer", "ocsp", "tlsserial"],
"python.testing.pytestArgs": ["tests"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Options:
--help Show this message and exit.
```

## from a URL
### from a URL

```console
❯ poetry run tlsserial --url dell.com
Expand All @@ -37,7 +37,7 @@ ca_issuers : http://aia.entrust.net/l1k-chain256.cer
serial_number : 5AF6B00AD82F3B8FACCEF4123D36138C
```

## from a file
### from a file

```console
❯ poetry run tlsserial --file ~/axiom.crt
Expand All @@ -55,3 +55,10 @@ ocsp : http://ocsp.e3m02.amazontrust.com
ca_issuers : http://crt.e3m02.amazontrust.com/e3m02.cer
serial_number : 0C9E25D31C5E5ECABC2AB6F10D89C3AF
```


## Development

poetry install
poetry run pre-commit install
poetry run pytest -v
48 changes: 48 additions & 0 deletions experiments/getchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import logging
import socket
import sys

import certifi
from OpenSSL import SSL, crypto

hostname = "www.google.com"
port = 443

methods = [
(SSL.SSLv23_METHOD, "SSL.SSLv23_METHOD"),
(SSL.TLSv1_METHOD, "SSL.TLSv1_METHOD"),
(SSL.TLSv1_1_METHOD, "SSL.TLSv1_1_METHOD"),
(SSL.TLSv1_2_METHOD, "SSL.TLSv1_2_METHOD"),
]

for method, method_name in methods:
try:
print(f"\n-- Method {method_name}")
context = SSL.Context(method=method)
context.load_verify_locations(cafile=certifi.where())

conn = SSL.Connection(
context, socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
)
conn.settimeout(5)
conn.connect((hostname, port))
conn.setblocking(1)
conn.do_handshake()
conn.set_tlsext_host_name(hostname.encode())
chain = conn.get_peer_cert_chain()

def decode(x: crypto.X509Name) -> str:
return "/".join(
["=".join(z.decode("utf-8") for z in y) for y in x.get_components()]
)

if chain:
for idx, cert in enumerate(chain):
print(f"{idx} subject: {decode(cert.get_subject())}")
print(f" issuer: {decode(cert.get_issuer())})")
print(f" serial: {cert.get_serial_number()}")
print(f' fingerprint: {cert.digest("sha1")}')

conn.close()
except SSL.Error:
logging.error(f"<><> Method {method_name} failed due to {sys.exc_info()[0]}")
51 changes: 46 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,59 @@ authors = ["Tom Matthews <anon@pseudo.url>"]
readme = "README.md"

[tool.poetry.scripts]
tlsserial = 'tlsserial:main'
tlsserial = 'tlsserial.cli:main'

[tool.poetry.dependencies]
python = "^3.11"
click = "^8.1.6"
cryptography = "^41.0"
cryptography = "^43.0.3"
pendulum = "^3.0"

certifi = "*" # Used in experiments
pyopenssl = "*" # Used in experiments
[tool.poetry.group.dev.dependencies]
types-cryptography = "^3.3.23.2"
mypy = "^1.4.1"
mypy = "*" # with dev dependencies you almost always want the latest
ruff = "*"
pre-commit = "*"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"


[tool.ruff]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"D", # pydocstyle
"PL", # pylint
"FIX", # flake8-fixme
"G", # flake8-logging-format
"PTH", # flake8-use-pathlib
"UP", # pyupgrade
"S", # flake8-bandit
"A", # flake8-builtins
"S", # flake8-simplify
"ARG", # flake8-unused-arguments
"INT", # flake8-gettext
"Q", # flake8-quotes
"C4", # flake8-comprehensions
"ISC", # flake8-implicit-str-concat
]
fixable = ["ALL"]
unfixable = []
[tool.ruff.lint.per-file-ignores]
#"__init__.py" = ["E402"] # ignore import errors in all __init__.py files
"**/{tests,docs,tools}/*" = [
"E402", # ignore import errors in selected subdirectories.
"D100", # C0114 missing-module-docstring
"D103", # C0116 missing-function-docstring
"S101", # Use of `assert` detected
]

[tool.mypy]
#disallow_untyped_defs = true
ignore_missing_imports = true
exclude = ['venv', '.venv', 'tests']
File renamed without changes.
18 changes: 18 additions & 0 deletions src/tlsserial/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
This file allows you to run the program directly from the python module:

~ $ python -m tlsserial --help

"""

import sys

from .cli import main

rc = 1
try:
main() # pylint: disable=no-value-for-parameter # noqa
rc = 0
except Exception as e: # pylint: disable=broad-exception-caught
print("Error:", e, file=sys.stderr)
sys.exit(rc)
44 changes: 44 additions & 0 deletions src/tlsserial/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import logging
import os
from ssl import OPENSSL_VERSION

import click

from . import helper, tlsserial


# https://click.palletsprojects.com/en/8.1.x/quickstart/
@click.command()
@click.option(
"--url",
cls=helper.MutuallyExclusiveOption,
mutually_exclusive=["file"],
help="host || host:port || https://host:port/other",
)
@click.option(
"--file",
cls=helper.MutuallyExclusiveOption,
mutually_exclusive=["url"],
help="filename containing a PEM certificate",
)
@click.option("--debug", is_flag=True, type=bool, default=False, help="Debug logging")
@click.option(
"--verbose", is_flag=True, type=bool, default=False, help="Verbose output"
)
def main(url, file, debug, verbose) -> None:
"""tlsserial groks X509 certificates for your pleasure"""
default_level = "DEBUG" if debug else "INFO"
logging.basicConfig(
level=getattr(logging, os.getenv("LOGLEVEL", default_level).upper()),
format="[%(levelname)s] %(asctime)s - %(message)s",
datefmt="%Y-%m-%dT%H:%M:%S",
)
logging.debug("Logging is set to DEBUG level")
if url:
tlsserial.handle_url(url, verbose)
elif file:
tlsserial.handle_file(file, verbose)
else:
click.echo(f"Library version : {OPENSSL_VERSION}")
ctx = click.get_current_context()
click.echo(ctx.get_help())
17 changes: 13 additions & 4 deletions lib/color.py → src/tlsserial/color.py
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea why I failed to run black against these, but yeah, good stuff.

Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
""" Colour functions for wrapping strings """
from sys import __stdin__, __stdout__
"""Colour functions for wrapping strings"""

# TODO: take a look at the following if you want to take it further (probably overkill)
# https://github.com/Textualize/rich
# https://dslackw.gitlab.io/colored/user_guide/user_guide/
import sys

# Styles
BOLD = "\x1b[1m"
Expand All @@ -11,32 +15,37 @@
COSMOS = "\x1b[38;2;223;42;93m" # Red

# Dont wrap with ANSI escape colour codes if we're not a TTY supporting that
IS_TTY_STDIN = __stdin__.isatty()
IS_TTY_STDOUT = __stdout__.isatty()

IS_TTY_STDIN = sys.stdin.isatty()
IS_TTY_STDOUT = sys.stdout.isatty()


def bold(text: str) -> str:
"""bold string"""
if IS_TTY_STDOUT:
return BOLD + text + END
else:
return text


def blue(text: str) -> str:
"""blue string"""
if IS_TTY_STDOUT:
return SKY + text + END
else:
return text


def orange(text: str) -> str:
"""orange string"""
if IS_TTY_STDOUT:
return SMILE + text + END
else:
return text


def red(text: str) -> str:
"""red string"""
if IS_TTY_STDOUT:
return COSMOS + text + END
else:
Expand Down
Loading