def get_tlsext_status_ocsp_resp(self) -> Optional[OcspResponse]: """Retrieve the server's OCSP Stapling status. """ ocsp_response = self._ssl.get_tlsext_status_ocsp_resp() if ocsp_response: return OcspResponse.from_openssl(ocsp_response) else: return None
def test(self): # Given an OCSP response as returned by OpenSSL class MockOpenSslOcspResponse: def as_text(self): return OCSP_RESPONSE_OPENSSL_OUTPUT def get_status(self): return OcspResponseStatusEnum.SUCCESSFUL raw_ocsp_response = MockOpenSslOcspResponse() # When parsing it, it succeeds ocsp_response = OcspResponse.from_openssl(raw_ocsp_response) # And the fields were correctly parsed assert ocsp_response.status == OcspResponseStatusEnum.SUCCESSFUL assert ocsp_response.type == "Basic OCSP Response" assert ocsp_response.version == 1 assert ocsp_response.responder_id == "C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3" assert ocsp_response.produced_at == datetime(2018, 10, 12, 2, 56) assert ocsp_response.certificate_status == "good" assert ocsp_response.this_update == datetime(2018, 10, 12, 2, 0) assert ocsp_response.next_update == datetime(2018, 10, 19, 2, 0) assert ocsp_response.hash_algorithm == "sha1" assert ocsp_response.issuer_name_hash == "7EE66AE7729AB3FCF8A220646C16A12D6071085D" assert ocsp_response.issuer_key_hash == "A84A6A63047DDDBAE6D139B7A64565EFF3A8ECA1" assert ocsp_response.serial_number == "039048428EE710E751C1EC96E355B05FADF7" # Including the SCT extension assert len(ocsp_response.extensions) == 1 sct_timestamps = ocsp_response.extensions[ 0].signed_certificate_timestamps assert len(sct_timestamps) == 2 assert sct_timestamps[0].version == "v1(0)" assert ( sct_timestamps[0].log_id == "68:F6:98:F8:1F:64:82:BE:3A:8C:EE:B9:28:1D:4C:FC:71:51:5D:67:93:D4:44:D1:0A:67:AC:BB:4F:4F:FB:C4" ) assert sct_timestamps[0].timestamp == datetime(2014, 4, 25, 11, 35, 28)
def __init__( self, server_info: ServerConnectivityInfo, scan_command: CertificateInfoScanCommand, certificate_chain: List[Certificate], path_validation_result_list: List[PathValidationResult], path_validation_error_list: List[PathValidationError], ocsp_response: OcspResponse ) -> None: super().__init__(server_info, scan_command) # Find the first trust store that successfully validated the certificate chain self.successful_trust_store = None # Sort the path_validation_result_list so the same successful_trust_store always get picked for a given server # because threading timings change the order of path_validation_result_list def sort_function(path_validation: PathValidationResult) -> str: return path_validation.trust_store.name.lower() path_validation_result_list.sort(key=sort_function) for path_result in path_validation_result_list: if path_result.is_certificate_trusted: self.successful_trust_store = path_result.trust_store self.ocsp_response = None self.is_ocsp_response_trusted = None self.ocsp_response_status = None if ocsp_response: self.ocsp_response_status = ocsp_response.status # We only keep the dictionary as a nassl.OcspResponse is not pickable self.ocsp_response = ocsp_response.as_dict() if self.successful_trust_store and self.ocsp_response_status == OcspResponseStatusEnum.SUCCESSFUL: try: ocsp_response.verify(self.successful_trust_store.path) self.is_ocsp_response_trusted = True except OcspResponseNotTrustedError: self.is_ocsp_response_trusted = False self.certificate_chain = certificate_chain # Check if it is EV - we only have the EV OIDs for Mozilla self.is_leaf_certificate_ev = TrustStoresRepository.get_default().get_main_store().is_extended_validation( self.certificate_chain[0] ) # Look for the Must-Staple extension has_must_staple = CertificateUtils.has_ocsp_must_staple_extension(self.certificate_chain[0]) self.certificate_has_must_staple_extension = has_must_staple # Look for the certificate transparency extension self.certificate_included_scts_count = CertificateUtils.count_scts_in_sct_extension(self.certificate_chain[0]) # Try to build the verified chain self.verified_certificate_chain: List[Certificate] = [] self.is_certificate_chain_order_valid = True if self.successful_trust_store: try: self.verified_certificate_chain = self.successful_trust_store.build_verified_certificate_chain( self.certificate_chain ) except InvalidCertificateChainOrderError: self.is_certificate_chain_order_valid = False except AnchorCertificateNotInTrustStoreError: pass self.has_anchor_in_certificate_chain = None if self.verified_certificate_chain: self.has_anchor_in_certificate_chain = self.verified_certificate_chain[-1] in self.certificate_chain self.path_validation_result_list = path_validation_result_list self.path_validation_error_list = path_validation_error_list try: CertificateUtils.matches_hostname(certificate_chain[0], server_info.tls_server_name_indication) self.certificate_matches_hostname = True except CertificateError: self.certificate_matches_hostname = False # Check if a SHA1-signed certificate is in the chain # Root certificates can still be signed with SHA1 so we only check leaf and intermediate certificates self.has_sha1_in_certificate_chain = None if self.verified_certificate_chain: self.has_sha1_in_certificate_chain = False for cert in self.verified_certificate_chain[:-1]: if isinstance(cert.signature_hash_algorithm, hashes.SHA1): self.has_sha1_in_certificate_chain = True break # Check if this is a distrusted Symantec-issued chain self.symantec_distrust_timeline = _SymantecDistructTester.get_distrust_timeline(self.verified_certificate_chain)