redis/redis-py · error · ConnectionError

no matching issuer cert found in certificate chain

Error message

no matching issuer cert found in certificate chain

What it means

Raised as a ConnectionError by ocsp_staple_verifier (redis/ocsp.py:160) when iterating the peer certificate chain yields no certificate whose subject equals peer_cert.issuer — i.e. the chain does not contain the immediate issuer of the peer certificate. _verify_response needs the issuer's public key to validate the OCSP response signature, so without it verification cannot proceed.

Solutions

  1. Configure the server to send the full certificate chain including the intermediate/issuer certificate (most TLS stacks: concatenate leaf + intermediate in the cert file).
  2. Verify the chain presented using openssl s_client to confirm the issuer cert appears.
  3. If using stunnel/Redis TLS, ensure ssl_cert includes the full chain, not just the leaf.
  4. Confirm peer_cert.issuer actually corresponds to a cert in the sent chain (cross-signing can cause a mismatch).

Example fix

# before - server sends leaf only
# server: ssl_cert = /etc/ssl/leaf.crt
ocsp_staple_verifier(con, ocsp_bytes)  # ConnectionError: no matching issuer cert found in certificate chain

# after - server sends the full chain
# server: ssl_cert = /etc/ssl/fullchain.crt   (leaf + intermediate concatenated)
# verify: openssl s_client -connect host:port -showcerts  # shows leaf + issuer
Defensive patterns

Strategy: validation

Validate before calling

def chain_contains_issuer(con, peer_cert):
    for c in con.get_peer_cert_chain():
        cert = c.to_cryptography()
        if cert.subject == peer_cert.issuer:
            return True
    return False

Try / catch

from redis.exceptions import ConnectionError as RedisConnectionError

try:
    ocsp_staple_verifier(con, ocsp_bytes, expected)
except RedisConnectionError as e:
    if 'no matching issuer cert found' in str(e):
        logging.warning('Incomplete chain - server must send the issuer/intermediate cert')
    raise

Prevention

When it happens

Trigger: ocsp_staple_verifier runs on a TLS connection where the server sent an incomplete chain (missing the intermediate/issuer cert), or the chain is ordered such that no entry's subject matches peer_cert.issuer. The loop over con.get_peer_cert_chain() finds no match and issuer_cert stays None.

Common situations: Server misconfigured to send only the leaf cert (no intermediate); intermediate not installed on the server TLS config; chain sent in an order/format PyOpenSSL doesn't expose fully; cross-signed cert where the expected issuer differs from what was sent; client-side trust store doesn't change this — the chain must be server-sent.

Understand the failure class

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/6d699e24b6676e4f. Report an issue: GitHub.

Appendix: source

Thrown at redis/ocsp.py:160

def ocsp_staple_verifier(con, ocsp_bytes, expected=None):
    """An implementation of a function for set_ocsp_client_callback in PyOpenSSL.

    This function validates that the provide ocsp_bytes response is valid,
    and matches the expected, stapled responses.
    """
    if ocsp_bytes in [b"", None]:
        raise ConnectionError("no ocsp response present")

    issuer_cert = None
    peer_cert = con.get_peer_certificate().to_cryptography()
    for c in con.get_peer_cert_chain():
        cert = c.to_cryptography()
        if cert.subject == peer_cert.issuer:
            issuer_cert = cert
            break

    if issuer_cert is None:
        raise ConnectionError("no matching issuer cert found in certificate chain")

    if expected is not None:
        e = x509.load_pem_x509_certificate(expected)
        if peer_cert != e:
            raise ConnectionError("received and expected certificates do not match")

    return _check_certificate(issuer_cert, ocsp_bytes)


class OCSPVerifier:
    """A class to verify ssl sockets for RFC6960/RFC6961. This can be used
    when using direct validation of OCSP responses and certificate revocations.

    @see https://datatracker.ietf.org/doc/html/rfc6960
    @see https://datatracker.ietf.org/doc/html/rfc6961
    """

    def __init__(self, sock, host, port, ca_certs=None):

View on GitHub (pinned to 6a6b581b48)