• 
      

    Add support for matching certificate hostnames and IPs.

    Review Request #15017 — Created April 16, 2026 and submitted

    Information

    Review Board
    release-7.1.x

    Reviewers

    This introduces Certificate.matches_host(), which takes a hostname or
    IPv4/IPv6 address and matches it against the Subject and SAN values
    stored in the certificate. This supports hostnames, IPv4 addresses, and
    IPv6 addresses.

    matches_host() starts by determining if this is an IP address or a
    hostname, and then performs only the checks needed for that type.

    For IP checks, comparisons between instances are performed.

    For hostname checks, matches_host() wraps a new utility function,
    get_cert_hostname_matches(), which can be used without a Certificate
    instance. This supports bare hostnames, hostnames with multiple labels,
    and wildcard patterns.

    Wildcard matching takes care to only match the first label in a hostname
    (such as *.example.com) and to avoid matching bare hostnames. It does
    not support partial wildcards, such as foo*, *bar, or foo*bar, as
    these are largely unsupported by browsers, servers, and Certificate
    Authorities these days (Chrome treats them as a security issue).

    Certificate.subject_alternative_names has been updated to return
    ipaddress.* instances for IP address/network values and strings for
    hostnames. This allows match_host() (and other callers) to handle each
    entry correctly.

    Unit tests pass.

    Summary ID
    Add support for matching certificate hostnames and IPs.
    This introduces `Certificate.matches_host()`, which takes a hostname or IPv4/IPv6 address and matches it against the Subject and SAN values stored in the certificate. This supports hostnames, IPv4 addresses, and IPv6 addresses. `matches_host()` starts by determining if this is an IP address or a hostname, and then performs only the checks needed for that type. For IP checks, comparisons between instances are performed. For hostname checks, `matches_host()` wraps a new utility function, `get_cert_hostname_matches()`, which can be used without a `Certificate` instance. This supports bare hostnames, hostnames with multiple labels, and wildcard patterns. Wildcard matching takes care to only match the first label in a hostname (such as `*.example.com`) and to avoid matching bare hostnames. It does not support partial wildcards, such as `foo*`, `*bar`, or `foo*bar`, as these are largely unsupported by browsers, servers, and Certificate Authorities these days (Chrome treats them as a security issue). `Certificate.subject_alternative_names` has been updated to return `ipaddress.*` instances for IP address/network values and strings for hostnames. This allows `match_host()` (and other callers) to handle each entry correctly.
    cce7da0183927547aa7733a36ddaee6e39f97245
    Description From Last Updated

    Can we add some additional tests? Certificate with cert_data (exercising the subject path) Test to verify rejection of partial wildcards …

    david david

    self.subject can be None Based on the implementation of the tests (and the docstring), it looks like maybe you had …

    david david

    SAN can contain IP addresses. While the wildcard logic in get_cert_hostname_matches works incidentally for that, it might be nice to …

    david david

    Typo: futher -> further

    david david

    Can we add a debug log in case this fails because of a partial wildcard?

    david david

    This is no longer just string values.

    david david

    This stray line got left over from a previous iteration.

    david david
    chipx86
    david
    1. 
        
    2. Show all issues

      Can we add some additional tests?

      • Certificate with cert_data (exercising the subject path)
      • Test to verify rejection of partial wildcards
      • IPv4 and IPv6 address in SAN entries
      • Bare cert hostname (like "localhost")
    3. reviewboard/certs/cert.py (Diff revision 2)
       
       
      Show all issues

      self.subject can be None

      Based on the implementation of the tests (and the docstring), it looks like maybe you had this as self.hostname initially?

      The tests are constructing the cert without cert_data (therefore without a subject), so I would expect this to raise if you ran them.

    4. reviewboard/certs/cert.py (Diff revision 2)
       
       
       
       
       
      Show all issues

      SAN can contain IP addresses. While the wildcard logic in get_cert_hostname_matches works incidentally for that, it might be nice to detect these and force a direct comparison instead.

      1. IP addresses in SAN don't support wildcards, so in practice we'll never get one with a wildcard. It will always be a direct comparison with any real-world cert.

        Also worth noting, IP addresses in SAN entries only ever support public IPs, not private. They'll probably rarely be used.

    5. reviewboard/certs/utils.py (Diff revision 2)
       
       
      Show all issues

      Typo: futher -> further

    6. reviewboard/certs/utils.py (Diff revision 2)
       
       
      Show all issues

      Can we add a debug log in case this fails because of a partial wildcard?

      1. This will yield spurious debug logs for a lot of certificates. For example, a cert with main.example.com, *.eng.example.com, *.eng.example.net, *.eng.example.org is going to debug log twice for a ci.eng.example.org during comparison, every time. I'm not sure this is where we'd want to be logging anything.

      2. Sure.

    7. 
        
    chipx86
    david
    1. 
        
    2. reviewboard/certs/cert.py (Diff revision 3)
       
       
       
       
      Show all issues

      This is no longer just string values.

    3. reviewboard/certs/cert.py (Diff revision 3)
       
       
      Show all issues

      This stray line got left over from a previous iteration.

    4. 
        
    chipx86
    Review request changed
    Status:
    Completed
    Change Summary:
    Pushed to release-8.x (a2fd011)