def _validate_cert_chain(self, cert_chain): # type: (bytes) -> None """Validate the certificate chain. This method checks if the passed in certificate chain is valid. A :py:class:`VerificationException` is raised if the certificate chain is not valid. The end certificate is read, using the :py:func:`cryptography.x509.load_pem_x509_certificate` method. The x509 backend is set as default to the :py:class:`cryptography.hazmat.backends.default_backend` instance. :param cert_chain: Certificate chain to be validated :type cert_chain: bytes :return: None :raises: :py:class:`VerificationException` if certificate chain is not valid """ try: end_cert = None intermediate_certs = [] for type_name, headers, der_bytes in pem.unarmor( cert_chain, multiple=True): if end_cert is None: end_cert = der_bytes else: intermediate_certs.append(der_bytes) validator = CertificateValidator(end_cert, intermediate_certs) validator.validate_usage(key_usage={'digital_signature'}) except (PathError, ValidationError) as e: raise VerificationException("Certificate chain is not valid", e)
def _check_certificate(self, certificate): _, _, certificate_bytes = pem.unarmor( certificate.encode(), multiple=False) certificate = x509.Certificate.load(certificate_bytes) trust_roots = [] with open(self.ca_crt, 'rb') as f: for _, _, der_bytes in pem.unarmor(f.read(), multiple=True): trust_roots.append(der_bytes) crls = [] with open(settings.CA_CRL, 'rb') as f: crls.append(f.read()) context = ValidationContext(crls=crls, trust_roots=trust_roots) try: validator = CertificateValidator( certificate, validation_context=context) result = validator.validate_usage( set(['digital_signature']) ) dev = True except errors.PathValidationError as e: logger.debug("SimpleCA: validate PathValidationError %r" % (e)) dev = False except errors.PathBuildingError as e: logger.debug("SimpleCA: validate PathBuildingError %r" % (e)) dev = False logger.info("SimpleCA: validate cert %r == %r" % (certificate.serial_number, dev)) return dev
def test_cert_constraint_issuer(requests_mock): vc = live_testing_vc(requests_mock) signer_validation_path = CertificateValidator( FROM_CA.signing_cert, FROM_CA.cert_registry, validation_context=vc).validate_usage(set()) tsa_validation_path = CertificateValidator( DUMMY_TS.tsa_cert, FROM_CA.cert_registry, validation_context=vc).validate_usage(set()) scc = fields.SigCertConstraints(flags=fields.SigCertConstraintFlags.ISSUER, issuers=[ROOT_CERT]) scc.satisfied_by(FROM_CA.signing_cert, signer_validation_path) scc.satisfied_by(DUMMY_TS.tsa_cert, tsa_validation_path) with pytest.raises(UnacceptableSignerError): scc.satisfied_by(FROM_CA.signing_cert, None) scc = fields.SigCertConstraints(flags=fields.SigCertConstraintFlags.ISSUER, issuers=[INTERM_CERT]) scc.satisfied_by(FROM_CA.signing_cert, signer_validation_path) with pytest.raises(UnacceptableSignerError): scc.satisfied_by(DUMMY_TS.tsa_cert, tsa_validation_path) scc = fields.SigCertConstraints( flags=fields.SigCertConstraintFlags.ISSUER, issuers=[INTERM_CERT, SELF_SIGN.signing_cert]) scc.satisfied_by(FROM_CA.signing_cert, signer_validation_path) with pytest.raises(UnacceptableSignerError): scc.satisfied_by(DUMMY_TS.tsa_cert, tsa_validation_path) scc = fields.SigCertConstraints(issuers=[INTERM_CERT]) scc.satisfied_by(FROM_CA.signing_cert, signer_validation_path) scc.satisfied_by(DUMMY_TS.tsa_cert, tsa_validation_path)
def validate_vmc(self): try: end_entity_cert = None intermediates = [] with open(self.vmc_file, 'rb') as f: for type_name, headers, der_bytes in pem.unarmor( f.read(), multiple=True): if end_entity_cert is None: end_entity_cert = der_bytes else: intermediates.append(der_bytes) validator = CertificateValidator(end_entity_cert, intermediates) validated = validator.validate_usage(set(['digital_signature'])) # print(intermediates) except errors.PathValidationError as PathValidationError: self.vmc_response["errors"].append("Error: " + str(PathValidationError)) print(PathValidationError) except errors.RevokedError as RevokedError: self.vmc_response["errors"].append( "Error: Certificate Revoked.\n" + str(RevokedError)) print(RevokedError) except errors.InvalidCertificateError as InvalidCertificateError: self.vmc_response["errors"].append( "Error: Certificate Is Invalid.\n" + str(InvalidCertificateError)) print(InvalidCertificateError) except errors.PathBuildingError as PathBuildingError: self.vmc_response["errors"].append("Error: Cannot Build Path.\n" + str(PathBuildingError)) print(PathBuildingError) except Exception as e: self.vmc_response["errors"].append( "Error: Validation Exception.\n" + str(e)) print(e)
def test_cert_constraint_composite(requests_mock): vc = live_testing_vc(requests_mock) signer_validation_path = CertificateValidator( FROM_CA.signing_cert, FROM_CA.cert_registry, validation_context=vc).validate_usage(set()) tsa_validation_path = CertificateValidator( DUMMY_TS.tsa_cert, FROM_CA.cert_registry, validation_context=vc).validate_usage(set()) from asn1crypto import x509 scc = fields.SigCertConstraints(flags=fields.SigCertConstraintFlags.ISSUER | fields.SigCertConstraintFlags.SUBJECT_DN, issuers=[INTERM_CERT], subject_dn=x509.Name.build({ 'common_name': 'Lord Testerino', 'country_name': 'BE' })) scc.satisfied_by(FROM_CA.signing_cert, signer_validation_path) with pytest.raises(UnacceptableSignerError): scc.satisfied_by(DUMMY_TS.tsa_cert, tsa_validation_path) from asn1crypto import x509 scc = fields.SigCertConstraints(flags=fields.SigCertConstraintFlags.ISSUER | fields.SigCertConstraintFlags.SUBJECT_DN, issuers=[INTERM_CERT], subject_dn=x509.Name.build({ 'common_name': 'Alice & Bob', 'country_name': 'BE' })) with pytest.raises(UnacceptableSignerError): scc.satisfied_by(FROM_CA.signing_cert, signer_validation_path)
def test_basic_certificate_validator_tls_expired(self): cert = self._load_cert_object('codex.crt') other_certs = [self._load_cert_object('GeoTrust_EV_SSL_CA_-_G4.crt')] validator = CertificateValidator(cert, other_certs) with self.assertRaisesRegexp(PathValidationError, 'expired'): validator.validate_tls('codexns.io')
def verify(self, certificate): """Verifies the certificate, and its chain. :param Certificate certificate: The certificate to verify :return: A valid certificate chain for this certificate. :rtype: Iterable[Certificate] :raises AuthenticodeVerificationError: When the certificate could not be verified. """ # we keep track of our asn1 objects to make sure we return Certificate objects when we're done to_check_asn1cert = certificate.to_asn1crypto all_certs = {to_check_asn1cert: certificate} # we need to get lists of our intermediates and trusted certificates intermediates, trust_roots = [], [] for store in self.stores: for cert in store: asn1cert = cert.to_asn1crypto # we short-circuit the check here to ensure we do not check too much possibilities (trust_roots if store.trusted else intermediates).append(asn1cert) all_certs[asn1cert] = cert # construct the context and validator for certvalidator timestamp = self.timestamp context = ValidationContext( trust_roots=list(trust_roots), moment=timestamp, weak_hash_algos=set() if self.allow_legacy else None, revocation_mode=self.revocation_mode, allow_fetching=self.allow_fetching, crl_fetch_params={'timeout': self.fetch_timeout}, ocsp_fetch_params={'timeout': self.fetch_timeout}, crls=self.crls, ocsps=self.ocsps) validator = CertificateValidator( end_entity_cert=to_check_asn1cert, intermediate_certs=list(intermediates), validation_context=context) # verify the chain try: chain = validator.validate_usage( key_usage=set(self.key_usages) if self.key_usages else set(), extended_key_usage=set(self.extended_key_usages) if self.extended_key_usages else set(), extended_optional=self.optional_eku) except Exception as e: raise VerificationError("Chain verification from %s failed: %s" % (certificate, e)) signify_chain = [all_certs[x] for x in chain] self.verify_trust(signify_chain[0]) return signify_chain
def test_basic_certificate_validator_tls_invalid_hostname(self): cert = self._load_cert_object('codex.crt') other_certs = [self._load_cert_object('GeoTrust_EV_SSL_CA_-_G4.crt')] moment = datetime(2015, 1, 1, 0, 0, 0, tzinfo=timezone.utc) context = ValidationContext(moment=moment) validator = CertificateValidator(cert, other_certs, context) with self.assertRaisesRegexp(PathValidationError, 'not valid'): validator.validate_tls('google.com')
def test_basic_certificate_validator_tls(self): cert = self._load_cert_object('codex.crt') other_certs = [self._load_cert_object('GeoTrust_EV_SSL_CA_-_G4.crt')] moment = datetime(2015, 1, 1, 0, 0, 0, tzinfo=timezone.utc) context = ValidationContext(moment=moment) validator = CertificateValidator(cert, other_certs, context) path = validator.validate_tls('codexns.io') self.assertEqual(3, len(path))
def test_crl_without_update_field(self): cert = self._load_cert_object('microsoft_armored.crt') root_certificates = self._load_trust_roots( os.path.join(fixtures_dir, 'root_certs')) moment = datetime(2009, 1, 1, 0, 0, 0, tzinfo=timezone.utc) context = ValidationContext(trust_roots=root_certificates, moment=moment, allow_fetching=True) validator = CertificateValidator(cert, validation_context=context) validator.validate_usage(set(['digital_signature']), set(['code_signing']), False)
def test_basic_certificate_validator_tls_invalid_key_usage(self): cert = self._load_cert_object('mozilla.org.crt') other_certs = [self._load_cert_object('digicert-sha2-secure-server-ca.crt')] moment = datetime(2019, 1, 1, 0, 0, 0, tzinfo=timezone.utc) context = ValidationContext(moment=moment) validator = CertificateValidator(cert, other_certs, context) with self.assertRaisesRegex(PathValidationError, 'for the purpose'): validator.validate_usage(set(['crl_sign']))
def test_basic_certificate_validator_tls_invalid_hostname(self): cert = self._load_cert_object('mozilla.org.crt') other_certs = [self._load_cert_object('digicert-sha2-secure-server-ca.crt')] moment = datetime(2019, 1, 1, 0, 0, 0, tzinfo=timezone.utc) context = ValidationContext(moment=moment) validator = CertificateValidator(cert, other_certs, context) with self.assertRaisesRegex(PathValidationError, 'not valid'): validator.validate_tls('google.com')
def test_basic_certificate_validator_tls(self): cert = self._load_cert_object('mozilla.org.crt') other_certs = [self._load_cert_object('digicert-sha2-secure-server-ca.crt')] moment = datetime(2019, 1, 1, 0, 0, 0, tzinfo=timezone.utc) context = ValidationContext(moment=moment) validator = CertificateValidator(cert, other_certs, context) path = validator.validate_tls('www.mozilla.org') self.assertEqual(3, len(path))
def test_basic_certificate_validator_tls_invalid_key_usage(self): cert = self._load_cert_object('codex.crt') other_certs = [self._load_cert_object('GeoTrust_EV_SSL_CA_-_G4.crt')] moment = datetime(2015, 1, 1, 0, 0, 0, tzinfo=timezone.utc) context = ValidationContext(moment=moment) validator = CertificateValidator(cert, other_certs, context) with self.assertRaisesRegexp(PathValidationError, 'for the purpose'): validator.validate_usage(set(['crl_sign']))
def validate_certificate(self, ca_certs: List[bytes]) -> bool: if not ca_certs: raise ValueError("ca_certs should be set.") if not self._cert: return False ctx = ValidationContext(trust_roots=ca_certs) try: validator = CertificateValidator(self._cert, self._intermediates, validation_context=ctx) validator.validate_usage(set(["digital_signature"]), extended_optional=True) except Exception as err: raise VerifyError("Failed to validate the certificate bound to the key.") from err return True
def test_crl_without_update_field_hard_fail(self): cert = self._load_cert_object('microsoft_armored.crt') root_certificates = self._load_trust_roots( os.path.join(fixtures_dir, 'root_certs')) moment = datetime(2009, 1, 1, 0, 0, 0, tzinfo=timezone.utc) context = ValidationContext(trust_roots=root_certificates, moment=moment, allow_fetching=True, revocation_mode='hard-fail') validator = CertificateValidator(cert, validation_context=context) with self.assertRaisesRegexp( PathValidationError, 'nextUpdate field is expected to be present in CRL'): validator.validate_usage(set(['digital_signature']), set(['code_signing']), False)
def _validate_cms_signature(sig_blob: SignatureBlob, cd_hash: bytes): assert sig_blob.cms signed_data = sig_blob.cms["content"] assert isinstance(signed_data, SignedData) assert len(signed_data["signer_infos"]) == 1 # Get certificates cert_chain = [] for cert in signed_data["certificates"]: c = cert.chosen assert isinstance(c, Certificate) cert_chain.append(c) # Get algorithms used signer_info = signed_data["signer_infos"][0] digest_alg = signer_info["digest_algorithm"]["algorithm"].native sig_alg = signer_info["signature_algorithm"]["algorithm"].native # Get message and signature signed_attrs = signer_info["signed_attrs"] sig = signer_info["signature"].contents # Check the hash of CodeDirectory matches what is in the signature message_digest = None for attr in sig_blob.cms["content"]["signer_infos"][0]["signed_attrs"]: if attr["type"].native == "message_digest": message_digest = attr["values"][0].native if message_digest != cd_hash: raise Exception( f"CodeDirectory Hash mismatch. Expected {message_digest.hex()}, Calculated {cd_hash.hex()}" ) if message_digest is None: raise Exception("message_digest not found in signature") # Validate the certificate chain validation_context = ValidationContext( trust_roots=APPLE_ROOTS, allow_fetching=False, additional_critical_extensions=APPLE_CERT_CRIT_EXTS, ) validator = CertificateValidator(cert_chain[-1], cert_chain[0:-1], validation_context) validator.validate_usage({"digital_signature"}, {"code_signing"}) # Check the signature pubkey = asymmetric.load_public_key(cert_chain[-1].public_key) signed_msg = _sort_attributes(signed_attrs).dump() asymmetric.rsa_pkcs1v15_verify(pubkey, sig, signed_msg, digest_alg)
def validate_cert_usage(cls, validator: CertificateValidator, key_usage_settings: KeyUsageConstraints = None): key_usage_settings = key_usage_settings or KeyUsageConstraints() key_usage_settings = KeyUsageConstraints( key_usage=(cls.key_usage if key_usage_settings.key_usage is None else key_usage_settings.key_usage), extd_key_usage=(cls.extd_key_usage if key_usage_settings.extd_key_usage is None else key_usage_settings.extd_key_usage)) cert: x509.Certificate = validator._certificate revoked = trusted = False path = None try: # validate usage without going through certvalidator key_usage_settings.validate(cert) path = validator.validate_usage(key_usage=set()) trusted = True except InvalidCertificateError as e: # TODO accumulate these somewhere logger.warning(e) except RevokedError: revoked = True except (PathValidationError, PathBuildingError) as e: logger.warning(e) if not trusted: subj = cert.subject.human_friendly logger.warning(f"Chain of trust validation for {subj} failed.") return trusted, revoked, path
def validation_paths(self, validation_context): """ Produce validation paths for the certificates gathered by this :class:`.TimeStamper`. This is internal API. :param validation_context: The validation context to apply. :return: A generator producing validation paths. """ for cert in self._certs.values(): validator = CertificateValidator( cert, intermediate_certs=self.cert_registry, validation_context=validation_context) yield validator.validate_usage(set(), {"time_stamping"})
def verify(self, certificate): """Verifies the certificate, and its chain. :param Certificate certificate: The certificate to verify :return: A valid certificate chain for this certificate. :rtype: Iterable[Certificate] :raises AuthenticodeVerificationError: When the certificate could not be verified. """ # we keep track of our asn1 objects to make sure we return Certificate objects when we're done to_check_asn1cert = certificate.to_asn1crypto all_certs = {to_check_asn1cert: certificate} # we need to get lists of our intermediates and trusted certificates intermediates, trust_roots = [], [] for store in self.stores: for cert in store: asn1cert = cert.to_asn1crypto (trust_roots if store.trusted else intermediates).append(asn1cert) all_certs[asn1cert] = cert # construct the context and validator for certvalidator context = ValidationContext( trust_roots=list(trust_roots), moment=self.timestamp, weak_hash_algos=set() if self.allow_legacy else None) validator = CertificateValidator( end_entity_cert=to_check_asn1cert, intermediate_certs=list(intermediates), validation_context=context) # verify the chain try: chain = validator.validate_usage( key_usage=set(self.key_usages) if self.key_usages else set(), extended_key_usage=set(self.extended_key_usages) if self.extended_key_usages else set(), extended_optional=self.optional_eku) except Exception as e: raise VerificationError("Chain verification from %s failed: %s" % (certificate, e)) else: return [all_certs[x] for x in chain]
def validate_vmc(self): try: end_entity_cert = None intermediates = [] with open(self.vmc_file, 'rb') as f: readfile = f.read() # Create parsed data for expiry and embedded svg check self.parsed_vmc = self.parse_vmc_cert(readfile) for type_name, headers, der_bytes in pem.unarmor( readfile, multiple=True): if end_entity_cert is None: end_entity_cert = der_bytes else: intermediates.append(der_bytes) validator = CertificateValidator(end_entity_cert, intermediates) validated = validator.validate_usage( set(['digital_signature']) # ,extended_key_usage=set(["server_auth", "client_auth"]) ) if validated: print("Certificate Validated") except errors.PathValidationError as PathValidationError: self.vmc_response["errors"].append("Warning: " + str(PathValidationError)) print(PathValidationError) except errors.RevokedError as RevokedError: self.vmc_response["errors"].append( "Warning: Certificate Revoked.\n" + str(RevokedError)) print(RevokedError) except errors.InvalidCertificateError as InvalidCertificateError: self.vmc_response["errors"].append( "Warning: Certificate Is Invalid.\n" + str(InvalidCertificateError)) print(InvalidCertificateError) except errors.PathBuildingError as PathBuildingError: # self.vmc_response["errors"].append("Warning: Cannot Build Path.\n"+str(PathBuildingError)) print(PathBuildingError) except Exception as e: self.vmc_response["errors"].append( "Warning: Validation Exception.\n" + str(e)) print(e)
def verify_chain(certificate: Union[str, x509.Certificate], ca: str): if isinstance(certificate, x509.Certificate): pem = certificate.public_bytes(serialization.Encoding.PEM) else: if certificate.startswith("-----BEGIN CERTIFICATE-----"): pem = certificate.encode() else: with open(certificate, "rb") as my_file: pem = my_file.read() # cert = x509.load_pem_x509_certificate(pem, default_backend()) with open(ca, "rb") as my_file: pem_ca = my_file.read() # ca = x509.load_pem_x509_certificate(pem_ca, default_backend()) trust_roots = [pem_ca] context = ValidationContext(trust_roots=trust_roots) validator = CertificateValidator(pem, validation_context=context) return validator.validate_usage({"digital_signature"})
def test_basic_certificate_validator_tls_whitelist(self): cert = self._load_cert_object('codex.crt') other_certs = [self._load_cert_object('GeoTrust_EV_SSL_CA_-_G4.crt')] context = ValidationContext(whitelisted_certs=[cert.sha1_fingerprint]) validator = CertificateValidator(cert, other_certs, context) # If whitelist does not work, this will raise exception for expiration validator.validate_tls('codexns.io') # If whitelist does not work, this will raise exception for hostname validator.validate_tls('google.com') # If whitelist does not work, this will raise exception for key usage validator.validate_usage(set(['crl_sign']))
def test_basic_certificate_validator_tls_whitelist(self): cert = self._load_cert_object('mozilla.org.crt') other_certs = [self._load_cert_object('digicert-sha2-secure-server-ca.crt')] moment = datetime(2020, 1, 1, 0, 0, 0, tzinfo=timezone.utc) context = ValidationContext( whitelisted_certs=[cert.sha1_fingerprint], moment=moment ) validator = CertificateValidator(cert, other_certs, context) # If whitelist does not work, this will raise exception for expiration validator.validate_tls('www.mozilla.org') # If whitelist does not work, this will raise exception for hostname validator.validate_tls('google.com') # If whitelist does not work, this will raise exception for key usage validator.validate_usage(set(['crl_sign']))
def validate_cert_usage(cls, validator: CertificateValidator): revoked = trusted = False path = None try: path = validator.validate_usage( key_usage=cls.key_usage, extended_key_usage=cls.extd_key_usage) trusted = True except InvalidCertificateError as e: # TODO accumulate these somewhere logger.warning(e) except RevokedError: revoked = True except (PathValidationError, PathBuildingError) as e: logger.warning(e) if not trusted: subj = validator._certificate.subject.human_friendly logger.warning(f"Chain of trust validation for {subj} failed.") return trusted, revoked, path
def _validate_chain_certvalidator(self, tlslite_connection): """Validate server certificate chain using 3rd party certvalidator library which uses oscrypt/libcrypto Note: oscrypt uses ctypes find_library() which does not work in certain distributions such as alpine. (e.g. see https://github.com/docker-library/python/issues/111) On such systems, users will have to rely on other server cert validation approaches such as using openssl or turning it off completely. """ try: from certvalidator import CertificateValidator from certvalidator import ValidationContext from asn1crypto import x509, pem # validate server certificate chain session = tlslite_connection.sock.session assert type(session.serverCertChain.x509List) == list # get the end-entity cert file_bytes = session.serverCertChain.x509List[0].bytes end_entity_cert = x509.Certificate.load(str(file_bytes)) def cert_files_exist(path, file_names): file_names = [os.path.join(path, f) for f in file_names] for f in file_names: if not os.path.isfile(f): return False return True def get_cert_bytes(cert_dir, file_names): file_names = [os.path.join(cert_dir, f) for f in file_names] result = [] for fname in file_names: arr = open(fname, "rb").read() cert_bytes = pem.unarmor(arr)[2] result.append(cert_bytes) return result intermediate_cert_names = [ "comodo_ca_intermediate.pem", "sectigo_ca_intermediate.pem", ] extra_trust_names = [ "scalyr_agent_ca_root.pem", "addtrust_external_ca_root.pem", ] # Determine the directory containing the certs. # First check the directory containing the _ca_file # but if we don't find the intermediate/extra certs there # then look in the relative `certs` directory. The latter # will typically be required if running directly from source all_cert_names = intermediate_cert_names + extra_trust_names cert_dir = os.path.dirname(self._ca_file) if not cert_files_exist(cert_dir, all_cert_names): path = os.path.dirname(os.path.abspath(__file__)) path = os.path.abspath(path + "../../certs") if cert_files_exist(path, all_cert_names): cert_dir = path trust_roots = None intermediate_certs = get_cert_bytes(cert_dir, intermediate_cert_names) extra_trust_roots = get_cert_bytes(cert_dir, extra_trust_names) if trust_roots: context = ValidationContext( trust_roots=trust_roots, extra_trust_roots=extra_trust_roots, other_certs=intermediate_certs, # whitelisted_certs=[end_entity_cert.sha1_fingerprint], ) else: context = ValidationContext( extra_trust_roots=extra_trust_roots, other_certs=intermediate_certs, # whitelisted_certs=[end_entity_cert.sha1_fingerprint], ) validator = CertificateValidator( end_entity_cert, validation_context=context ) validator.validate_tls(six.text_type(self._host)) log.info( "Scalyr server cert chain successfully validated via certvalidator library" ) except Exception as ce: log.exception("Error validating server certificate chain: %s" % ce) raise
def run(): """ Runs through TLS hosts in the Alexa top 1000 to test TLS functionality :return: A bool - if the test succeeded without any socket errors """ task_start = time.time() success = 0 tls_errors = 0 socket_errors = 0 mismatch_info = [] context = ValidationContext(allow_fetching=True) with open(os.path.join(fixtures_dir, 'alexa_top_1000.csv'), 'rb') as f: for line in f: domain = line.decode('utf-8').rstrip() os_result = None cv_result = None os_message = None cv_message = None try: os_start = time.time() con = tls.TLSSocket(domain, 443, timeout=3) con.close() success += 1 os_result = 'OK' os_message = 'Success' _color('green', 'OK', domain, os_start) except (TLSVerificationError) as e: tls_errors += 1 os_result = 'TLS' os_message = str_cls(e) _color('yellow', 'TLS', domain, os_start, str_cls(e)) except (socket.error) as e: socket_errors += 1 os_result = 'SOCK' os_message = str_cls(e) _color('red', 'SOCK', domain, os_start, str_cls(e)) try: cv_start = time.time() session = tls.TLSSession(manual_validation=True) con = tls.TLSSocket(domain, 443, timeout=3, session=session) validator = CertificateValidator(con.certificate, con.intermediates, context) validator.validate_tls(domain) con.close() success += 1 cv_result = 'OK' cv_message = 'Success' _color('green', 'OK', domain, cv_start) except (PathValidationError, PathBuildingError) as e: tls_errors += 1 cv_result = 'TLS' cv_message = str_cls(e) _color('yellow', 'TLS', domain, cv_start, str_cls(e)) except (socket.error) as e: socket_errors += 1 cv_result = 'SOCK' cv_message = str_cls(e) _color('red', 'SOCK', domain, cv_start, str_cls(e)) if os_result != cv_result: mismatch_info.append( [domain, os_result, os_message, cv_result, cv_message]) total_time = time.time() - task_start total_domains = success + tls_errors + socket_errors stats = [] if success > 0: stats.append('%d [%sOK%s]' % (success, Fore.GREEN, Fore.RESET)) if tls_errors > 0: stats.append('%d [%sTLS%s]' % (tls_errors, Fore.YELLOW, Fore.RESET)) if socket_errors > 0: stats.append('%d [%sSOCK%s]' % (socket_errors, Fore.RED, Fore.RESET)) print('') print('Checked %d domains in %.3f seconds - %s' % (total_domains, total_time, ' '.join(stats))) if mismatch_info: print('') for info in mismatch_info: os_result = '[%s] %s' % (info[1], info[2]) cv_result = '[%s] %s' % (info[3], info[4]) _color( 'red', 'DIFF', 'oscrypto and certvalidator results for %s are different' % info[0], None, os_result, cv_result) return socket_errors == 0
def run(): """ Runs through TLS hosts in the Alexa top 1000 to test TLS functionality :return: A bool - if the test succeeded without any socket errors """ task_start = time.time() success = 0 tls_errors = 0 socket_errors = 0 mismatch_info = [] context = ValidationContext(allow_fetching=True) with open(os.path.join(fixtures_dir, 'alexa_top_1000.csv'), 'rb') as f: for line in f: domain = line.decode('utf-8').rstrip() os_result = None cv_result = None os_message = None cv_message = None try: os_start = time.time() con = tls.TLSSocket(domain, 443, timeout=3) con.close() success += 1 os_result = 'OK' os_message = 'Success' _color('green', 'OK', domain, os_start) except (TLSVerificationError) as e: tls_errors += 1 os_result = 'TLS' os_message = str_cls(e) _color('yellow', 'TLS', domain, os_start, str_cls(e)) except (socket.error) as e: socket_errors += 1 os_result = 'SOCK' os_message = str_cls(e) _color('red', 'SOCK', domain, os_start, str_cls(e)) try: cv_start = time.time() session = tls.TLSSession(manual_validation=True) con = tls.TLSSocket(domain, 443, timeout=3, session=session) validator = CertificateValidator(con.certificate, con.intermediates, context) validator.validate_tls(domain) con.close() success += 1 cv_result = 'OK' cv_message = 'Success' _color('green', 'OK', domain, cv_start) except (PathValidationError, PathBuildingError) as e: tls_errors += 1 cv_result = 'TLS' cv_message = str_cls(e) _color('yellow', 'TLS', domain, cv_start, str_cls(e)) except (socket.error) as e: socket_errors += 1 cv_result = 'SOCK' cv_message = str_cls(e) _color('red', 'SOCK', domain, cv_start, str_cls(e)) if os_result != cv_result: mismatch_info.append([ domain, os_result, os_message, cv_result, cv_message ]) total_time = time.time() - task_start total_domains = success + tls_errors + socket_errors stats = [] if success > 0: stats.append('%d [%sOK%s]' % (success, Fore.GREEN, Fore.RESET)) if tls_errors > 0: stats.append('%d [%sTLS%s]' % (tls_errors, Fore.YELLOW, Fore.RESET)) if socket_errors > 0: stats.append('%d [%sSOCK%s]' % (socket_errors, Fore.RED, Fore.RESET)) print('') print('Checked %d domains in %.3f seconds - %s' % (total_domains, total_time, ' '.join(stats))) if mismatch_info: print('') for info in mismatch_info: os_result = '[%s] %s' % (info[1], info[2]) cv_result = '[%s] %s' % (info[3], info[4]) _color( 'red', 'DIFF', 'oscrypto and certvalidator results for %s are different' % info[0], None, os_result, cv_result ) return socket_errors == 0
def verify_bib(self, ctr, bib): addl_protected = b'' addl_unprotected = {} aad_scope = 0x7 for param in bib.payload.parameters: if param.type_code == 3: addl_protected = bytes(param.value) elif param.type_code == 4: addl_unprotected = dict(param.value) elif param.type_code == 5: aad_scope = int(param.value) addl_protected_map = cbor2.loads( addl_protected) if addl_protected else {} dupe_keys = set(addl_protected_map.keys()).intersection( set(addl_unprotected.keys())) if dupe_keys: LOGGER.warning('Duplicate keys in additional headers: %s', dupe_keys) return StatusReport.ReasonCode.FAILED_SEC addl_headers = dict(addl_protected_map) addl_headers.update(addl_unprotected) bundle_at = DtnTimeField.dtntime_to_datetime( ctr.bundle.primary.create_ts.getfieldval('dtntime')) val_ctx = ValidationContext( trust_roots=[ cert.public_bytes(serialization.Encoding.DER) for cert in self._ca_certs ], other_certs=[ cert.public_bytes(serialization.Encoding.DER) for cert in self._cert_chain ], moment=bundle_at, ) LOGGER.debug('Validating certificates at time %s', bundle_at) failure = None for (ix, blk_num) in enumerate(bib.payload.targets): target_blk = ctr.block_num(blk_num) for result in bib.payload.results[ix].results: msg_cls = CoseMessage._COSE_MSG_ID[result.type_code] # replace detached payload msg_enc = bytes(result.getfieldval('value')) msg_dec = cbor2.loads(msg_enc) LOGGER.debug('Received COSE message\n%s', encode_diagnostic(msg_dec)) msg_dec[2] = target_blk.getfieldval('btsd') msg_obj = msg_cls.from_cose_obj(msg_dec) msg_obj.external_aad = CoseContext.get_bpsec_cose_aad( ctr, target_blk, bib, aad_scope, addl_protected) # use additional headers as defaults for (key, val) in msg_cls._parse_header(addl_headers).items(): msg_obj.uhdr.setdefault(key, val) LOGGER.info('full uhdr %s', msg_obj.uhdr) x5t_item = msg_obj.get_attr(headers.X5t) x5t = X5T.decode(x5t_item) if x5t_item else None x5chain_item = msg_obj.get_attr(headers.X5chain) if isinstance(x5chain_item, bytes): x5chain = [x5chain_item] else: x5chain = x5chain_item LOGGER.info('Validating X5t %s and X5chain length %d', x5t.encode() if x5t else None, len(x5chain) if x5chain else 0) if x5t is None and x5chain: # Only one possible end-entity cert LOGGER.warning('No X5T in header, assuming single chain') found_chain = x5chain else: try: found_chain = x5chain if x5t.matches( x5chain[0]) else None if not found_chain: raise RuntimeError( 'No chain matcing end-entity cert for {}'. format(x5t.encode())) LOGGER.debug( 'Found chain matcing end-entity cert for %s', x5t.encode()) except Exception as err: LOGGER.error( 'Failed to find cert chain for block num %d: %s', blk_num, err) failure = StatusReport.ReasonCode.FAILED_SEC continue LOGGER.debug('Validating chain with %d certs against %d CAs', len(found_chain), len(self._ca_certs)) try: val = CertificateValidator( end_entity_cert=found_chain[0], intermediate_certs=found_chain[1:], validation_context=val_ctx) val.validate_usage( key_usage={'digital_signature'}, extended_key_usage={'1.3.6.1.5.5.7.3.35'}, extended_optional=True) except Exception as err: LOGGER.error('Failed to verify chain on block num %d: %s', blk_num, err) failure = StatusReport.ReasonCode.FAILED_SEC continue peer_nodeid = bib.payload.source end_cert = x509.load_der_x509_certificate( found_chain[0], default_backend()) authn_nodeid = tcpcl.session.match_id( peer_nodeid, end_cert, x509.UniformResourceIdentifier, LOGGER, 'NODE-ID') if not authn_nodeid: LOGGER.error( 'Failed to authenticate peer "%s" on block num %d', peer_nodeid, blk_num) failure = StatusReport.ReasonCode.FAILED_SEC # Continue on to verification try: msg_obj.key = self.extract_cose_key(end_cert.public_key()) msg_obj.verify_signature() LOGGER.info('Verified signature on block num %d', blk_num) except Exception as err: LOGGER.error( 'Failed to verify signature on block num %d: %s', blk_num, err) failure = StatusReport.ReasonCode.FAILED_SEC return failure
def valider_x509_enveloppe(self, enveloppe: EnveloppeCertificat, date_reference: datetime.datetime = None): """ Valide une enveloppe :param enveloppe: :param date_reference: :param ignorer_date: Charger le certificat en utilisation date courante ou fin de periode de validite :return: Resultat de validation (toujours valide) :raises certvalidator.errors.PathBuildingError: Si le path est invalide """ cert_pem = enveloppe.certificat_pem.encode('utf-8') inter_list = list() # self._logger.debug("CERT PEM :\n%s" % enveloppe.certificat_pem) for pem in enveloppe.reste_chaine_pem: # self._logger.debug("Chaine PEM :\n%s" % pem.strip()) inter_list.append(pem.strip().encode('utf-8')) if date_reference is not None: # batir un contexte avec la date validation_context = ValidationContext( moment=date_reference, trust_roots=[self.__cert_millegrille]) else: validation_context = self.__validation_context # Verifier le certificat - noter qu'une exception est lancee en cas de probleme try: validator = CertificateValidator( cert_pem, intermediate_certs=inter_list, validation_context=validation_context) resultat = validator.validate_usage({'digital_signature'}) enveloppe.set_est_verifie(True) except PathValidationError as pve: msg = pve.args[0] if 'expired' in msg: self._logger.info( "Un des certificats est expire, verifier en fonction de la date de reference" ) # Le certificat est expire, on fait la validation pour la fin de la periode de validite date_reference = pytz.UTC.localize(enveloppe.not_valid_after) validation_context = ValidationContext( moment=date_reference, trust_roots=[self.__cert_millegrille]) validator = CertificateValidator( cert_pem, intermediate_certs=inter_list, validation_context=validation_context) try: resultat = validator.validate_usage({'digital_signature'}) enveloppe.set_est_verifie(True) raise CertificatExpire( ) # La chaine est valide pour une date anterieure except PathValidationError as pve: if self._logger.isEnabledFor(logging.DEBUG): self._logger.exception( "Erreur validation path certificat") else: self._logger.info( "Erreur validation path certificat : %s", str(pve)) else: if self._logger.isEnabledFor(logging.DEBUG): self._logger.exception("Erreur validation path certificat") else: self._logger.info("Erreur validation path certificat : %s", str(pve)) raise pve except PathBuildingError as pbe: # Verifier si on a une millegrille tierce dernier_cert_pem = inter_list[-1] dernier_cert = EnveloppeCertificat(certificat_pem=dernier_cert_pem) if dernier_cert.is_rootCA: idmg = dernier_cert.idmg # Verifier si le idmg est dans la liste des idmg autorises autorisation = self.__autorisations_idmg.get(idmg) if autorisation is None: # Pas autorise, lancer l'exception raise pbe elif autorisation.get('domaines_permis'): # Valider la chaine en fonction de la racine fournie if date_reference is not None: # batir un contexte avec la date validation_context = ValidationContext( moment=date_reference, trust_roots=[ self.__cert_millegrille, dernier_cert_pem ]) else: validation_context = ValidationContext(trust_roots=[ self.__cert_millegrille, dernier_cert_pem ]) validator = CertificateValidator( cert_pem, intermediate_certs=inter_list, validation_context=validation_context) validator.validate_usage({'digital_signature'}) # Valide, on lance une exception pour indiquer la condition de validite (business rule) raise AutorisationConditionnelleDomaine( autorisation['domaines_permis'], idmg, enveloppe) return resultat
def verify_chain(self, key_usage: Set[str]): val = CertificateValidator( end_entity_cert=self.cert_chain[0], intermediate_certs=self.cert_chain[1:], ) return val.validate_usage(key_usage)