def _request_ocsp(cert, issuer, uri): # https://cryptography.io/en/latest/x509/ocsp/#creating-requests builder = _OCSPRequestBuilder() # add_certificate returns a new instance builder = builder.add_certificate(cert, issuer, _SHA1()) ocsp_request = builder.build() try: response = _post(uri, data=ocsp_request.public_bytes(_Encoding.DER), headers={'Content-Type': 'application/ocsp-request'}, timeout=5) except _RequestException: _LOGGER.debug("HTTP request failed") return None if response.status_code != 200: _LOGGER.debug("HTTP request returned %d", response.status_code) return None ocsp_response = _load_der_ocsp_response(response.content) _LOGGER.debug("OCSP response status: %r", ocsp_response.response_status) if ocsp_response.response_status != _OCSPResponseStatus.SUCCESSFUL: return None # RFC6960, Section 3.2, Number 1. Only relevant if we need to # talk to the responder directly. # Accessing response.serial_number raises if response status is not # SUCCESSFUL. if ocsp_response.serial_number != ocsp_request.serial_number: _LOGGER.debug("Response serial number does not match request") return None return ocsp_response
def _get_ocsp_response(cert, issuer, uri, ocsp_response_cache): ocsp_request = _build_ocsp_request(cert, issuer) try: ocsp_response = ocsp_response_cache[ocsp_request] _LOGGER.debug("Using cached OCSP response.") except KeyError: try: response = _post( uri, data=ocsp_request.public_bytes(_Encoding.DER), headers={"Content-Type": "application/ocsp-request"}, timeout=5, ) except _RequestException as exc: _LOGGER.debug("HTTP request failed: %s", exc) return None if response.status_code != 200: _LOGGER.debug("HTTP request returned %d", response.status_code) return None ocsp_response = _load_der_ocsp_response(response.content) _LOGGER.debug("OCSP response status: %r", ocsp_response.response_status) if ocsp_response.response_status != _OCSPResponseStatus.SUCCESSFUL: return None # RFC6960, Section 3.2, Number 1. Only relevant if we need to # talk to the responder directly. # Accessing response.serial_number raises if response status is not # SUCCESSFUL. if ocsp_response.serial_number != ocsp_request.serial_number: _LOGGER.debug("Response serial number does not match request") return None if not _verify_response(issuer, ocsp_response): # The response failed verification. return None _LOGGER.debug("Caching OCSP response.") ocsp_response_cache[ocsp_request] = ocsp_response return ocsp_response
def _ocsp_callback(conn, ocsp_bytes, user_data): """Callback for use with OpenSSL.SSL.Context.set_ocsp_client_callback.""" cert = conn.get_peer_certificate() if cert is None: _LOGGER.debug("No peer cert?") return 0 cert = cert.to_cryptography() # Use the verified chain when available (pyopenssl>=20.0). if hasattr(conn, "get_verified_chain"): chain = conn.get_verified_chain() trusted_ca_certs = None else: chain = conn.get_peer_cert_chain() trusted_ca_certs = user_data.trusted_ca_certs if not chain: _LOGGER.debug("No peer cert chain?") return 0 chain = [cer.to_cryptography() for cer in chain] issuer = _get_issuer_cert(cert, chain, trusted_ca_certs) must_staple = False # https://tools.ietf.org/html/rfc7633#section-4.2.3.1 ext = _get_extension(cert, _TLSFeature) if ext is not None: for feature in ext.value: if feature == _TLSFeatureType.status_request: _LOGGER.debug("Peer presented a must-staple cert") must_staple = True break ocsp_response_cache = user_data.ocsp_response_cache # No stapled OCSP response if ocsp_bytes == b"": _LOGGER.debug("Peer did not staple an OCSP response") if must_staple: _LOGGER.debug( "Must-staple cert with no stapled response, hard fail.") return 0 if not user_data.check_ocsp_endpoint: _LOGGER.debug("OCSP endpoint checking is disabled, soft fail.") # No stapled OCSP response, checking responder URI diabled, soft fail. return 1 # https://tools.ietf.org/html/rfc6960#section-3.1 ext = _get_extension(cert, _AuthorityInformationAccess) if ext is None: _LOGGER.debug("No authority access information, soft fail") # No stapled OCSP response, no responder URI, soft fail. return 1 uris = [ desc.access_location.value for desc in ext.value if desc.access_method == _AuthorityInformationAccessOID.OCSP ] if not uris: _LOGGER.debug("No OCSP URI, soft fail") # No responder URI, soft fail. return 1 if issuer is None: _LOGGER.debug("No issuer cert?") return 0 _LOGGER.debug("Requesting OCSP data") # When requesting data from an OCSP endpoint we only fail on # successful, valid responses with a certificate status of REVOKED. for uri in uris: _LOGGER.debug("Trying %s", uri) response = _get_ocsp_response(cert, issuer, uri, ocsp_response_cache) if response is None: # The endpoint didn't respond in time, or the response was # unsuccessful or didn't match the request, or the response # failed verification. continue _LOGGER.debug("OCSP cert status: %r", response.certificate_status) if response.certificate_status == _OCSPCertStatus.GOOD: return 1 if response.certificate_status == _OCSPCertStatus.REVOKED: return 0 # Soft fail if we couldn't get a definitive status. _LOGGER.debug("No definitive OCSP cert status, soft fail") return 1 _LOGGER.debug("Peer stapled an OCSP response") if issuer is None: _LOGGER.debug("No issuer cert?") return 0 response = _load_der_ocsp_response(ocsp_bytes) _LOGGER.debug("OCSP response status: %r", response.response_status) # This happens in _request_ocsp when there is no stapled response so # we know if we can compare serial numbers for the request and response. if response.response_status != _OCSPResponseStatus.SUCCESSFUL: return 0 if not _verify_response(issuer, response): return 0 # Cache the verified, stapled response. ocsp_response_cache[_build_ocsp_request(cert, issuer)] = response _LOGGER.debug("OCSP cert status: %r", response.certificate_status) if response.certificate_status == _OCSPCertStatus.REVOKED: return 0 return 1