API Reference: Protocol Handlers¶
Protocol handlers are how CertMonitor talks to a host. When you connect, CertMonitor detects whether the endpoint speaks SSL/TLS or SSH and hands off to the matching handler, which knows how to fetch the certificate (and, for TLS, the cipher information). The pieces every connection shares live in the same package: opening streams, running and discovering STARTTLS preambles, and detection itself. You normally won't use any of this directly, since CertMonitor drives it for you. It's documented here for contributors and for anyone writing a custom handler.
See Protocol Detection for how the right handler gets chosen at connect time.
Base handler¶
The shared interface every handler implements.
certmonitor.protocol_handlers.base ¶
SSL/TLS handler¶
Handles SSL/TLS endpoints: the handshake, certificate retrieval, and cipher info.
certmonitor.protocol_handlers.ssl_handler ¶
SSLHandler ¶
Bases: BaseProtocolHandler
Source code in certmonitor/protocol_handlers/ssl_handler.py
check_connection ¶
Source code in certmonitor/protocol_handlers/ssl_handler.py
close ¶
connect ¶
Negotiate a permissive TLS session for certificate collection.
The first attempt offers every protocol version the local build
supports. If that fails, a second attempt caps the offer at TLS 1.2 for
servers that mishandle a TLS 1.3 ClientHello. A server that demands
legacy renegotiation gets one retry with that option enabled. Every
attempt gets its own timeout.
Source code in certmonitor/protocol_handlers/ssl_handler.py
fetch_raw_cert ¶
Source code in certmonitor/protocol_handlers/ssl_handler.py
fetch_raw_cipher ¶
Source code in certmonitor/protocol_handlers/ssl_handler.py
SSH handler¶
Reads SSH version banners. It does not retrieve or validate SSH host keys or SSH certificates.
certmonitor.protocol_handlers.ssh_handler ¶
SSHHandler ¶
Bases: BaseProtocolHandler
Source code in certmonitor/protocol_handlers/base.py
check_connection ¶
close ¶
connect ¶
Source code in certmonitor/protocol_handlers/ssh_handler.py
fetch_raw_cert ¶
Source code in certmonitor/protocol_handlers/ssh_handler.py
Connections¶
Every socket CertMonitor opens comes from here: a plaintext stream with any STARTTLS preamble already negotiated, or a TLS stream handshaken with the caller's context.
certmonitor.protocol_handlers.connection ¶
Open the plaintext or TLS stream a handler needs, in one place.
Every connection CertMonitor makes takes the same steps: reach the host,
through a proxy tunnel when one is configured, run a STARTTLS preamble when
the service needs one, and, for TLS, wrap the socket with the caller's
context. open_stream and open_tls_stream perform those
steps so the handlers, protocol detection, service discovery, and the
verified trust handshake never assemble a connection on their own.
open_stream ¶
open_stream(host: str, port: int, timeout: float, *, starttls: str | None = None, proxy: ProxyConfig | None = None) -> socket.socket
Return a connected plaintext socket, with the STARTTLS preamble done.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host
|
str
|
Address to connect to. |
required |
port
|
int
|
TCP port. |
required |
timeout
|
float
|
Timeout in seconds for the connection and each preamble step. |
required |
starttls
|
str | None
|
One of |
None
|
proxy
|
ProxyConfig | None
|
Tunnel to reach the host through, or |
None
|
Raises:
| Type | Description |
|---|---|
OSError
|
If the host cannot be reached, the proxy refuses the tunnel
( |
Source code in certmonitor/protocol_handlers/connection.py
open_tls_stream ¶
open_tls_stream(host: str, port: int, timeout: float, context: SSLContext, *, server_hostname: str | None, starttls: str | None = None, proxy: ProxyConfig | None = None) -> ssl.SSLSocket
Return a TLS socket handshaken with context over a fresh stream.
The plaintext socket is closed if the handshake fails, so a failed attempt never leaks a connection.
Source code in certmonitor/protocol_handlers/connection.py
Proxies¶
HTTP CONNECT and SOCKS5 tunnels, with authentication, that open_stream routes through when a monitor has a proxy. See Proxies for usage.
certmonitor.protocol_handlers.proxy ¶
Outbound proxies: HTTP CONNECT tunnels and SOCKS5, standard library only.
open_connection() is the one place CertMonitor opens a TCP connection. With
no proxy it is socket.create_connection(); with one it connects to the
proxy, negotiates a tunnel to the target, and returns the socket ready for a
TLS handshake or a STARTTLS preamble, exactly as a direct connection would be.
ProxyConfig ¶
Bases: NamedTuple
A parsed proxy URL. scheme is "http" or "socks5".
ProxyError ¶
Bases: OSError
The proxy refused the tunnel, rejected the credentials, or misbehaved.
open_connection ¶
open_connection(host: str, port: int, timeout: float, proxy: ProxyConfig | None = None) -> socket.socket
Return a connected socket to host:port, tunnelled through proxy when given.
Source code in certmonitor/protocol_handlers/proxy.py
parse_proxy ¶
Parse http://[user:pass@]host:port or socks5://[user:pass@]host:port.
socks5h:// is accepted as a synonym: the proxy always resolves the target
name, so no DNS query leaves the scanning host either way.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the scheme is unsupported or the host or port is missing. |
Source code in certmonitor/protocol_handlers/proxy.py
Detection¶
Decides which handler a port needs from its first bytes, handing plaintext greetings to STARTTLS discovery.
certmonitor.protocol_handlers.detection ¶
Work out which handler a port needs before any certificate is fetched.
Detection peeks at the first bytes a server sends. An SSH banner or a TLS
record settles it at once, and a server that sends nothing is assumed to be
waiting for a TLS ClientHello. A plaintext greeting means a STARTTLS service,
so detection asks starttls.discover to name it rather than reporting an
error. CertMonitor.detect_protocol() wraps this in the result envelope.
Connector
module-attribute
¶
Opens a plaintext socket to (host, port, timeout); proxies plug in here.
Detected ¶
ProtocolDetectionError ¶
Bases: OSError
The port answered, but with something CertMonitor cannot name.
detect ¶
Name the protocol on host:port from its first bytes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host
|
str
|
Address to connect to. |
required |
port
|
int
|
TCP port. |
required |
timeout
|
float
|
Timeout in seconds for the connection and for discovery. |
required |
connect
|
Connector
|
Opens the plaintext socket; the default connects directly. |
open_stream
|
Raises:
| Type | Description |
|---|---|
ProtocolDetectionError
|
If the server greeted in plaintext and no STARTTLS service could be named. |
OSError
|
If the host cannot be reached. |
Source code in certmonitor/protocol_handlers/detection.py
STARTTLS¶
The preambles for SMTP, IMAP, POP3, FTP, PostgreSQL, and LDAP, and the discovery that names a service without looking at its port. See STARTTLS Services for usage.
certmonitor.protocol_handlers.starttls ¶
Application-protocol preambles that upgrade a plain socket to TLS.
Some services start in plaintext and switch to TLS only after a short
exchange (STARTTLS). Each function here performs that exchange on an
already-connected socket and returns once the server has agreed to start
TLS, leaving the socket ready for SSLContext.wrap_socket(). Nothing here
imports beyond the standard library.
StartTLSError ¶
Bases: OSError
The server did not agree to start TLS, or the preamble was malformed.
discover ¶
discover(host: str, port: int, timeout: float, *, client_name: str = 'certmonitor', connect: Callable[[str, int, float], socket] = _direct_connection) -> str | None
Name the plaintext service on host:port so the right STARTTLS preamble can run.
Nothing here looks at the port number, so services on non-standard ports are
found just the same. A service that speaks first is named from its greeting:
IMAP (* OK), POP3 (+OK), SSH (SSH-), and the 220 greeting shared by
SMTP and FTP, which is settled by the greeting text or, failing that, by
whether the server answers EHLO with 250. A service that stays silent is
asked, in turn, the PostgreSQL SSLRequest and the LDAP StartTLS request,
and is named from the reply. The whole exchange is bounded by timeout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host
|
str
|
Address to connect to. |
required |
port
|
int
|
TCP port. |
required |
timeout
|
float
|
Total time budget in seconds for discovery. |
required |
client_name
|
str
|
Name announced in the |
'certmonitor'
|
connect
|
Callable[[str, int, float], socket]
|
Opens a plaintext socket to |
_direct_connection
|
Returns:
| Type | Description |
|---|---|
str | None
|
One of |
str | None
|
could not be named. |
Raises:
| Type | Description |
|---|---|
OSError
|
If the first connection to the host fails. |
Source code in certmonitor/protocol_handlers/starttls.py
ldap_starttls_request ¶
The LDAPMessage carrying an ExtendedRequest for the StartTLS OID (RFC 4511).
Source code in certmonitor/protocol_handlers/starttls.py
negotiate ¶
Run the STARTTLS preamble for protocol on sock.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sock
|
socket
|
A connected plaintext socket. |
required |
protocol
|
str
|
One of |
required |
client_name
|
str
|
Name announced to servers that ask for one (SMTP EHLO). |
'certmonitor'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
StartTLSError
|
If the server refuses or answers unexpectedly. |