Skip to content

KeyInfo Validator

The key_info validator judges the strength of the certificate's public key, per algorithm family:

  • RSA: modulus must be at least 2048 bits.
  • EC: curve must be one of secp256r1, secp384r1, secp521r1.
  • Post-quantum (ML-DSA, SLH-DSA, composite ML-DSA): strong by algorithm identity; the FIPS 204/205 parameter sets have no weak sizes or curves. The recognized set comes from the Rust registry via certinfo.pq_algorithms().

Per the result envelope, is_valid is always a strict bool. When strength cannot be determined (an unrecognized algorithm, or a missing size/curve) the key fails closed: is_valid: false with a reason that distinguishes "cannot determine" from "recognized but weak".

Try it

from certmonitor import CertMonitor

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

Look at key_type first, then key_size and curve. A 256-bit EC key and a 256-bit RSA modulus do not mean the same thing; this validator applies a separate rule to each family. PQ recognition is an algorithm classification, not a cryptographic proof of key or signature correctness.

How it decides

flowchart TD
    A[validate called] --> B{public_key_info present?}
    B -- No --> Z["is_valid: false<br/>cannot extract key info"]
    B -- Yes --> C{Algorithm family?}
    C -- "Post-quantum<br/>(ML-DSA / SLH-DSA / composite)" --> D["is_valid: true<br/>strong by identity"]
    C -- RSA --> E{Modulus &ge; 2048 bits?}
    E -- Yes --> D
    E -- "No / size missing" --> F["is_valid: false + reason"]
    C -- EC --> H{Curve in approved set?<br/>secp256r1 / secp384r1 / secp521r1}
    H -- Yes --> D
    H -- "No / curve missing" --> F
    C -- "Other / unknown" --> K["is_valid: false<br/>cannot determine, fails closed"]

Reference

certmonitor.validators.key_info.KeyInfoValidator

Bases: BaseCertValidator

A validator for checking the public key of an SSL certificate.

Judges key strength per algorithm family:

  • RSA: modulus must be at least 2048 bits.
  • EC: curve must be one of secp256r1 / secp384r1 / secp521r1 (the parser reports the curve by short name; unrecognized curves come through as an OID dotted string and are treated as not strong).
  • Post-quantum (ML-DSA, SLH-DSA, and hybrid composite ML-DSA): always strong, PQ strength is judged by algorithm identity, since the FIPS 204/205 parameter sets have no weak sizes or curves. The recognized set comes from the Rust registry exposed via certinfo.pq_algorithms().

Per the result envelope, is_valid is always a strict bool. When strength cannot be determined (unrecognized algorithm, or a missing size/curve) the key fails closed, is_valid: False with a reason that distinguishes "cannot determine" from "recognized but weak".

Attributes:

Name Type Description
name str

The name of the validator.

name class-attribute instance-attribute

name: str = 'key_info'

validate

validate(cert: dict[str, Any], host: str, port: int) -> KeyInfoResult

Validates the key information of the provided SSL certificate.

Parameters:

Name Type Description Default
cert dict

The SSL certificate.

required
host str

The hostname (not used in this validator).

required
port int

The port number (not used in this validator).

required

Returns:

Name Type Description
dict KeyInfoResult

A dictionary containing the validation results, including key type, key size, whether the key is considered strong enough (see the class docstring for the per-family rules, including post-quantum algorithms), and curve information if applicable.

Examples:

Example output (success): This example shows a certificate with a strong RSA 2048-bit key, so validation passes and no warnings are present.

```json
{
    "key_type": "rsaEncryption",
    "key_size": 2048,
    "is_valid": true,
    "curve": null
}
```

Example output (post-quantum key): This example shows a certificate with an ML-DSA-65 (FIPS 204) key. Post-quantum keys are valid by algorithm identity; key_size reports the subjectPublicKey bit length and is informational only.

```json
{
    "key_type": "ml-dsa-65",
    "key_size": 15616,
    "is_valid": true
}
```

Example output (failure): This example shows a certificate with a weak 512-bit key, so validation fails with a reason.

```json
{
    "key_type": "rsaEncryption",
    "key_size": 512,
    "is_valid": false,
    "reason": "RSA key size 512 is below the 2048-bit minimum."
}
```
Source code in certmonitor/validators/key_info.py
def validate(self, cert: dict[str, Any], host: str, port: int) -> KeyInfoResult:
    """
    Validates the key information of the provided SSL certificate.

    Args:
        cert (dict): The SSL certificate.
        host (str): The hostname (not used in this validator).
        port (int): The port number (not used in this validator).

    Returns:
        dict: A dictionary containing the validation results, including key type, key size,
              whether the key is considered strong enough (see the class docstring for the
              per-family rules, including post-quantum algorithms), and curve information
              if applicable.

    Examples:
        Example output (success):
            This example shows a certificate with a strong RSA 2048-bit key, so validation passes and no warnings are present.

            ```json
            {
                "key_type": "rsaEncryption",
                "key_size": 2048,
                "is_valid": true,
                "curve": null
            }
            ```

        Example output (post-quantum key):
            This example shows a certificate with an ML-DSA-65 (FIPS 204) key. Post-quantum
            keys are valid by algorithm identity; `key_size` reports the subjectPublicKey
            bit length and is informational only.

            ```json
            {
                "key_type": "ml-dsa-65",
                "key_size": 15616,
                "is_valid": true
            }
            ```

        Example output (failure):
            This example shows a certificate with a weak 512-bit key, so validation fails with a reason.

            ```json
            {
                "key_type": "rsaEncryption",
                "key_size": 512,
                "is_valid": false,
                "reason": "RSA key size 512 is below the 2048-bit minimum."
            }
            ```
    """
    public_key_info = cert.get("public_key_info", {})
    if not public_key_info:
        empty: KeyInfoResult = {
            "is_valid": False,
            "reason": "Unable to extract public key information.",
            "error": "Unable to extract public key information",
        }
        return empty

    key_type = public_key_info.get("algorithm", "Unknown")
    key_size = public_key_info.get("size")
    curve = public_key_info.get("curve")

    # `_is_key_strong_enough` returns `None` when strength cannot be
    # determined (unknown algorithm, or required size/curve missing). The
    # result envelope requires a strict bool, so map `None` to `False`
    # so "we could not verify this key is strong" fails closed, and carry
    # the distinction in `reason`.
    strength = self._is_key_strong_enough(key_type, key_size, curve)
    is_valid = bool(strength)

    result: KeyInfoResult = {
        "key_type": key_type,
        "key_size": key_size,
        "is_valid": is_valid,
    }
    if curve:
        result["curve"] = curve

    if not is_valid:
        result["reason"] = self._weak_key_reason(
            key_type, key_size, curve, strength
        )

    return result