Skip to content

TLSVersion Validator

Checks that the connection negotiated a TLS version you consider acceptable. By default that's TLS 1.2 or TLS 1.3; TLS 1.1 and older are deprecated and fail. A useful guard against legacy endpoints that silently fall back to insecure protocols.

Opt-in

Enable via enabled_validators=["tls_version", ...] or ENABLED_VALIDATORS.

Try it

from certmonitor import CertMonitor

with CertMonitor("example.com", enabled_validators=["tls_version"]) as monitor:
    monitor.get_cert_info()
    print(monitor.validate()["tls_version"])

A modern endpoint passes:

These examples show selected fields from illustrative scans. validate() also adds status and code, described in the result contract.

{
  "is_valid": true,
  "protocol_version": "TLSv1.3"
}

A legacy endpoint fails with a reason:

{
  "is_valid": false,
  "protocol_version": "TLSv1",
  "reason": "TLS version TLSv1 is not allowed. Update your allowed TLS versions or negotiate a supported version."
}

Customizing the allowed versions

The default allowed set is {"TLSv1.2", "TLSv1.3"}. Override it per call with the allowed_tls_versions argument, passed through validator_args:

from certmonitor import CertMonitor

with CertMonitor("example.com", enabled_validators=["tls_version"]) as monitor:
    monitor.get_cert_info()
    result = monitor.validate(
        validator_args={"tls_version": {"allowed_tls_versions": ["TLSv1.3"]}}  # require TLS 1.3 only
    )
    print(result["tls_version"])

Pairs with WeakCipher

tls_version checks the protocol; WeakCipher checks the negotiated cipher suite. Enable both to inspect the protocol and suite used by this connection, and see Post-Quantum Cryptography for why even TLS 1.3 isn't the whole story.

This checks the connection CertMonitor negotiated. It does not enumerate every protocol or cipher the server would accept from a different client.

Reference

certmonitor.validators.tls_version.TLSVersionValidator

Bases: BaseCipherValidator

Checks if the negotiated TLS version is in the allowed list.

The default allowed set is TLS 1.2 and TLS 1.3. Override it per call with the allowed_tls_versions argument.

name class-attribute instance-attribute

name: str = 'tls_version'

validate

validate(cipher_info: dict[str, Any], host: str, port: int, *, allowed_tls_versions: list[str] | None = None) -> TLSVersionResult

Validates the TLS protocol version used by the connection.

Parameters:

Name Type Description Default
cipher_info dict

The cipher information for the connection.

required
host str

The hostname.

required
port int

The port number.

required
allowed_tls_versions list

Override the default acceptable TLS versions. When None (the default), {"TLSv1.2", "TLSv1.3"} is used.

None

Returns:

Name Type Description
dict TLSVersionResult

A dictionary containing the validation result and the negotiated protocol version. A reason is added when the version is not allowed.

Examples:

Example output (success): This example shows a connection using TLSv1.3, which is considered secure, so validation passes.

```json
{
    "is_valid": true,
    "protocol_version": "TLSv1.3"
}
```

Example output (failure): This example shows a connection using TLSv1.0, which is considered insecure, so validation fails with a reason.

```json
{
    "is_valid": false,
    "protocol_version": "TLSv1.0",
    "reason": "TLS version TLSv1.0 is not allowed. Update your allowed TLS versions or negotiate a supported version."
}
```
Source code in certmonitor/validators/tls_version.py
def validate(
    self,
    cipher_info: dict[str, Any],
    host: str,
    port: int,
    *,
    allowed_tls_versions: list[str] | None = None,
) -> TLSVersionResult:
    """
    Validates the TLS protocol version used by the connection.

    Args:
        cipher_info (dict): The cipher information for the connection.
        host (str): The hostname.
        port (int): The port number.
        allowed_tls_versions (list, optional): Override the default
            acceptable TLS versions. When `None` (the default),
            `{"TLSv1.2", "TLSv1.3"}` is used.

    Returns:
        dict: A dictionary containing the validation result and the
              negotiated protocol version. A `reason` is added when
              the version is not allowed.

    Examples:
        Example output (success):
            This example shows a connection using TLSv1.3, which is considered secure, so validation passes.

            ```json
            {
                "is_valid": true,
                "protocol_version": "TLSv1.3"
            }
            ```

        Example output (failure):
            This example shows a connection using TLSv1.0, which is considered insecure, so validation fails with a reason.

            ```json
            {
                "is_valid": false,
                "protocol_version": "TLSv1.0",
                "reason": "TLS version TLSv1.0 is not allowed. Update your allowed TLS versions or negotiate a supported version."
            }
            ```
    """
    allowed: frozenset[str] = (
        frozenset(allowed_tls_versions)
        if allowed_tls_versions is not None
        else _DEFAULT_ALLOWED_TLS_VERSIONS
    )

    protocol_version = cipher_info.get("protocol_version")
    result: TLSVersionResult = {
        "is_valid": True,
        "protocol_version": protocol_version,
    }

    if protocol_version not in allowed:
        result["is_valid"] = False
        result["reason"] = (
            f"TLS version {protocol_version} is not allowed. "
            "Update your allowed TLS versions or negotiate a supported version."
        )

    return result