def test_certvalidator_with_params(self): cert = self._load_nist_cert('ValidPolicyMappingTest12EE.crt') ca_certs = [self._load_nist_cert('TrustAnchorRootCertificate.crt')] other_certs = [self._load_nist_cert('P12Mapping1to3CACert.crt')] context = ValidationContext(trust_roots=ca_certs, other_certs=other_certs, revocation_mode="soft-fail", weak_hash_algos={'md2', 'md5'}) validator = CertificateValidator( cert, validation_context=context, pkix_params=PKIXValidationParams(user_initial_policy_set=frozenset( ['2.16.840.1.101.3.2.1.48.1']))) path = validator.validate_usage(key_usage={'digital_signature'}) # check if we got the right policy processing # (i.e. if our params got through) qps = path.qualified_policies() qp, = qps self.assertEqual(1, len(qp.qualifiers)) qual_obj, = qp.qualifiers self.assertEqual(qual_obj['policy_qualifier_id'].native, 'user_notice') self.assertEqual( qual_obj['qualifier']['explicit_text'].native, 'q7: This is the user notice from qualifier 7 associated with ' 'NIST-test-policy-3. This user notice should be displayed ' 'when NIST-test-policy-1 is in the user-constrained-policy-set')
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': 'Alice', '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(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) validator.validate_tls('www.mozilla.org')
def test_self_signed_with_policy(self): # tests whether a corner case in the policy validation logic when the # path length is zero is handled gracefully cert = self._load_cert_object('self-signed-with-policy.crt') context = ValidationContext(trust_roots=[cert], allow_fetching=False) validator = CertificateValidator(cert, validation_context=context) path = validator.validate_usage({'digital_signature'}) qp, = path.qualified_policies() # Note: the cert declares a concrete policy, but for the purposes # of PKIX validation, any policy is valid, since we're validating # a self-signed certificate (so everything breaks down anyway) self.assertEqual(qp.user_domain_policy_id, 'any_policy') self.assertEqual(qp.issuer_domain_policy_id, 'any_policy')
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 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 pyhanko_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 test_validate(requests_mock, setup): setup.illusionist.register(requests_mock) signer_cert = setup.arch.get_cert(CertLabel('signer1')) root = setup.arch.get_cert(CertLabel('root')) interm = setup.arch.get_cert(CertLabel('interm')) vc = ValidationContext(trust_roots=[root], allow_fetching=True, revocation_mode='hard-fail', other_certs=[interm]) validator = CertificateValidator(signer_cert, intermediate_certs=[], validation_context=vc) validator.validate_usage({'digital_signature'}) assert len(vc.ocsps) assert len(vc.crls)
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"})
async def test_meta_tsa_verify(): # check if my testing setup works vc = ValidationContext( trust_roots=TRUST_ROOTS, allow_fetching=False, crls=[], ocsps=[FIXED_OCSP], revocation_mode='hard-fail' ) with pytest.raises(PathValidationError): cv = CertificateValidator(TSA_CERT, validation_context=vc) await cv.async_validate_usage({'time_stamping'})
async def _validate_signed_data(signed_data): cert_info = extract_certificate_info(signed_data) cert = cert_info.signer_cert other_certs = cert_info.other_certs validator = CertificateValidator( cert, intermediate_certs=other_certs, validation_context=validation_context ) path = await validator.async_validate_usage(key_usage=set()) paths.append(path)
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 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']))
async 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: An asynchronous generator of validation paths. """ await self._ensure_dummy() for cert in self._certs.values(): validator = CertificateValidator( cert, intermediate_certs=self.cert_registry, validation_context=validation_context) yield await validator.async_validate_usage(set(), {"time_stamping"})
async def cms_basic_validation(signed_data: cms.SignedData, status_cls: Type[StatusType] = SignatureStatus, raw_digest: bytes = None, validation_context: ValidationContext = None, status_kwargs: dict = None, key_usage_settings: KeyUsageConstraints = None, encap_data_invalid=False): """ Perform basic validation of CMS and PKCS#7 signatures in isolation (i.e. integrity and trust checks). Internal API. """ signer_info = extract_signer_info(signed_data) cert_info = extract_certs_for_validation(signed_data) cert = cert_info.signer_cert other_certs = cert_info.other_certs weak_hash_algos = None if validation_context is not None: weak_hash_algos = validation_context.weak_hash_algos if weak_hash_algos is None: weak_hash_algos = DEFAULT_WEAK_HASH_ALGORITHMS signature_algorithm: cms.SignedDigestAlgorithm = \ signer_info['signature_algorithm'] mechanism = signature_algorithm['algorithm'].native md_algorithm = signer_info['digest_algorithm']['algorithm'].native eci = signed_data['encap_content_info'] expected_content_type = eci['content_type'].native if raw_digest is None: # this means that there should be encapsulated data raw = bytes(eci['content']) md_spec = get_pyca_cryptography_hash(md_algorithm) md = hashes.Hash(md_spec) md.update(raw) raw_digest = md.finalize() # first, do the cryptographic identity checks intact, valid = validate_sig_integrity( signer_info, cert, expected_content_type=expected_content_type, actual_digest=raw_digest, weak_hash_algorithms=weak_hash_algos) # if the data being encapsulated by the signature is itself invalid, # this flag is set intact &= not encap_data_invalid valid &= intact # next, validate trust ades_status = path = None if valid: validator = CertificateValidator(cert, intermediate_certs=other_certs, validation_context=validation_context) ades_status, path = await status_cls.validate_cert_usage( validator, key_usage_settings=key_usage_settings) status_kwargs = status_kwargs or {} status_kwargs.update(intact=intact, valid=valid, signing_cert=cert, md_algorithm=md_algorithm, pkcs7_signature_mechanism=mechanism, trust_problem_indic=ades_status, validation_path=path) return status_kwargs
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 pyhanko_certvalidator results for %s are different' % info[0], None, os_result, cv_result ) return socket_errors == 0
def _validation_job(cert): validator = CertificateValidator( cert, intermediate_certs=self.cert_registry, validation_context=validation_context) return validator.async_validate_usage(set(), {"time_stamping"})