Skip to content

SubjectAltNames Validator

certmonitor.validators.subject_alt_names.SubjectAltNamesValidator

Bases: BaseCertValidator

A validator for checking the Subject Alternative Names (SANs) in an SSL certificate.

This validator checks both DNS and IP Address SANs.

Attributes:

Name Type Description
name str

The name of the validator.

name class-attribute instance-attribute

name: str = 'subject_alt_names'

validate

validate(cert: Dict[str, Any], host: str, port: int, *, alternate_names: Optional[List[str]] = None) -> Dict[str, Any]

Validates the SANs in the provided SSL certificate.

Parameters:

Name Type Description Default
cert dict

The SSL certificate.

required
host str

The hostname or IP to validate against the SANs.

required
port int

The port number.

required
alternate_names list

A list of alternate names to validate against the SANs.

None

Returns:

Name Type Description
dict Dict[str, Any]

A dictionary containing the validation results, including whether the SANs are valid, the SANs themselves, the count of SANs, and any warnings or reasons for validation failure.

Examples:

Example output (success): This example shows a certificate where both the main host and an alternate name are present in the DNS SANs, so validation passes for both.

```json
{
    "is_valid": true,
    "sans": {
        "DNS": [
            "example.com",
            "www.example.com"
        ],
        "IP Address": []
    },
    "count": 2,
    "contains_host": {
        "name": "example.com",
        "is_valid": true,
        "reason": "Matched DNS SAN"
    },
    "contains_alternate": {
        "www.example.com": {
            "name": "www.example.com",
            "is_valid": true,
            "reason": "Matched DNS SAN"
        }
    },
    "warnings": []
}
```

Example output (failure): This example shows a certificate where neither the main host nor the alternate name are present in the DNS SANs, so validation fails for both and warnings are included.

```json
{
    "is_valid": false,
    "sans": {
        "DNS": [
            "demo.nautobot.com"
        ],
        "IP Address": []
    },
    "count": 1,
    "contains_host": {
        "name": "test.example.com",
        "is_valid": false,
        "reason": "No match found for test.example.com in DNS SANs: demo.nautobot.com"
    },
    "contains_alternate": {
        "example.com": {
            "name": "example.com",
            "is_valid": false,
            "reason": "No match found for example.com in DNS SANs: demo.nautobot.com"
        }
    },
    "warnings": [
        "The hostname/IP test.example.com is not included in the SANs: No match found for test.example.com in DNS SANs: demo.nautobot.com",
        "The alternate name example.com is not included in the SANs: No match found for example.com in DNS SANs: demo.nautobot.com"
    ]
}
```
Source code in certmonitor/validators/subject_alt_names.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
def validate(
    self,
    cert: Dict[str, Any],
    host: str,
    port: int,
    *,
    alternate_names: Optional[List[str]] = None,
) -> Dict[str, Any]:
    """
    Validates the SANs in the provided SSL certificate.

    Args:
        cert (dict): The SSL certificate.
        host (str): The hostname or IP to validate against the SANs.
        port (int): The port number.
        alternate_names (list, optional): A list of alternate names to validate against the SANs.

    Returns:
        dict: A dictionary containing the validation results, including whether the SANs are valid,
              the SANs themselves, the count of SANs, and any warnings or reasons for validation failure.

    Examples:
        Example output (success):
            This example shows a certificate where both the main host and an alternate name are present in the DNS SANs, so validation passes for both.

            ```json
            {
                "is_valid": true,
                "sans": {
                    "DNS": [
                        "example.com",
                        "www.example.com"
                    ],
                    "IP Address": []
                },
                "count": 2,
                "contains_host": {
                    "name": "example.com",
                    "is_valid": true,
                    "reason": "Matched DNS SAN"
                },
                "contains_alternate": {
                    "www.example.com": {
                        "name": "www.example.com",
                        "is_valid": true,
                        "reason": "Matched DNS SAN"
                    }
                },
                "warnings": []
            }
            ```

        Example output (failure):
            This example shows a certificate where neither the main host nor the alternate name are present in the DNS SANs, so validation fails for both and warnings are included.

            ```json
            {
                "is_valid": false,
                "sans": {
                    "DNS": [
                        "demo.nautobot.com"
                    ],
                    "IP Address": []
                },
                "count": 1,
                "contains_host": {
                    "name": "test.example.com",
                    "is_valid": false,
                    "reason": "No match found for test.example.com in DNS SANs: demo.nautobot.com"
                },
                "contains_alternate": {
                    "example.com": {
                        "name": "example.com",
                        "is_valid": false,
                        "reason": "No match found for example.com in DNS SANs: demo.nautobot.com"
                    }
                },
                "warnings": [
                    "The hostname/IP test.example.com is not included in the SANs: No match found for test.example.com in DNS SANs: demo.nautobot.com",
                    "The alternate name example.com is not included in the SANs: No match found for example.com in DNS SANs: demo.nautobot.com"
                ]
            }
            ```
    """
    if "subjectAltName" not in cert["cert_info"]:
        return {
            "is_valid": False,
            "reason": "Certificate does not contain a Subject Alternative Name extension",
            "sans": None,
            "count": 0,
        }

    raw_sans = cert["cert_info"]["subjectAltName"]

    # Handle both dictionary and list formats
    if isinstance(raw_sans, dict):
        dns_sans = raw_sans.get("DNS", [])
        ip_sans = raw_sans.get("IP Address", [])
    else:  # Assume list of tuples format
        dns_sans = [value for san_type, value in raw_sans if san_type == "DNS"]
        ip_sans = [
            value for san_type, value in raw_sans if san_type == "IP Address"
        ]

    result: Dict[str, Any] = {
        "is_valid": True,
        "sans": {"DNS": dns_sans, "IP Address": ip_sans},
        "count": len(dns_sans) + len(ip_sans),
        "contains_host": {},
        "contains_alternate": {},
        "warnings": [],
    }

    # Check if the host is in the SANs
    is_valid, reason = self._check_name_in_sans_with_reason(host, dns_sans, ip_sans)
    result["contains_host"] = {
        "name": host,
        "is_valid": is_valid,
        "reason": reason,
    }

    # Check for alternate names if provided
    if alternate_names:
        contains_alternate = {}
        for alternate_name in alternate_names:
            alt_is_valid, alt_reason = self._check_name_in_sans_with_reason(
                alternate_name, dns_sans, ip_sans
            )
            contains_alternate[alternate_name] = {
                "name": alternate_name,
                "is_valid": alt_is_valid,
                "reason": alt_reason,
            }
        result["contains_alternate"] = contains_alternate

    # Additional checks and warnings
    warnings = cast(List[str], result["warnings"])
    if not dns_sans and not ip_sans:
        warnings.append("Certificate does not contain any DNS or IP Address SANs")

    if result["count"] > 100:
        warnings.append(
            f"Certificate contains an unusually high number of SANs ({result['count']})"
        )

    if not result["contains_host"]["is_valid"]:
        warnings.append(
            f"The hostname/IP {host} is not included in the SANs: {result['contains_host']['reason']}"
        )

    for alt_name, alt_validation in cast(
        Dict[str, Any], result["contains_alternate"]
    ).items():
        if not alt_validation["is_valid"]:
            warnings.append(
                f"The alternate name {alt_validation['name']} is not included in the SANs: {alt_validation['reason']}"
            )

    return result