def test_scan_certs_by_subject_finds_by_literal_wildcard(self): cert = certificate_pb2.X509Description() cert.der = "hello-wild" cert.subject.extend([name_attribute("com.example.*")]) cert.sha256_hash = 'cert' self.db().store_cert_desc(cert, 0, 0) cert2 = certificate_pb2.X509Description() cert2.der = "hello" cert2.subject.extend([name_attribute("com.example.www")]) cert.sha256_hash = 'cert2' self.db().store_cert_desc(cert2, 1, 0) matches = [c for c in self.db().scan_certs_by_subject("*.example.com")] self.assertEqual(1, len(matches)) self.assertEqual("hello-wild", matches[0])
def test_scan_certs_by_subject_ignores_wildcard_cert(self): for i in range(4): cert = certificate_pb2.X509Description() cert.der = "hello-%d" % i cert.subject.extend([name_attribute("com.example.%d" % i)]) cert.sha256_hash = str(i) self.db().store_cert_desc(cert, i, 0) cert = certificate_pb2.X509Description() cert.der = "hello-wild" cert.subject.extend([name_attribute("com.example.*")]) cert.sha256_hash = 'asdf' self.db().store_cert_desc(cert, 0, 0) matches = {c for c in self.db().scan_certs_by_subject("2.example.com")} self.assertEqual(1, len(matches)) self.assertTrue("hello-2" in matches)
def test_scan_certs_by_subject_ignores_longer(self): cert = certificate_pb2.X509Description() cert.der = "hello" cert.subject.extend([name_attribute("example.com")]) self.db().store_cert_desc(cert, 0, 0) matches = {c for c in self.db().scan_certs_by_subject( "mail.example.com")} self.assertEqual(0, len(matches))
def from_cert(certificate, observations=[]): """Pulls out interesting fields from certificate, so format of data will be similar in every database implementation.""" proto = certificate_pb2.X509Description() proto.der = certificate.to_der() try: for sub in [(type_.short_name, to_unicode('.'.join(process_name(value.human_readable())))) for type_, value in certificate.subject()]: proto_sub = proto.subject.add() proto_sub.type, proto_sub.value = sub except cert.CertificateError: pass try: for iss in [(type_.short_name, to_unicode('.'.join(process_name(value.human_readable())))) for type_, value in certificate.issuer()]: proto_iss = proto.issuer.add() proto_iss.type, proto_iss.value = iss except cert.CertificateError: pass try: for alt in certificate.subject_alternative_names(): proto_alt = proto.subject_alternative_names.add() proto_alt.type, proto_alt.value = (alt.component_key(), to_unicode('.'.join(process_name( alt.component_value().human_readable())))) except cert.CertificateError: pass try: proto.version = str(certificate.version()) except cert.CertificateError: pass try: proto.serial_number = str(certificate.serial_number().human_readable() .upper().replace(':', '')) except cert.CertificateError: pass proto.sha256_hash = hashlib.sha256(proto.der).digest() for observation in observations: proto_obs = proto.observations.add() if observation.description: proto_obs.description = observation.description if observation.reason: proto_obs.reason = observation.reason proto_obs.details = observation.details_to_proto() return proto
def test_scan_certs_by_subject_honours_limit(self): for i in range(0, 4): cert = certificate_pb2.X509Description() cert.der = "hello-%d" % i cert.subject.extend([name_attribute("com.domain")]) cert.sha256_hash = str(i) self.db().store_cert_desc(cert, i, 0) matches = {c for c in self.db().scan_certs_by_subject("domain.com", limit=2)} self.assertEqual(2, len(matches))
def test_scan_certs_by_subject_finds_by_all_names(self): cert = certificate_pb2.X509Description() cert.der = "hello" cert.subject.extend([name_attribute("com.%d" % i) for i in range(4)]) cert.sha256_hash = 'asdf' self.db().store_cert_desc(cert, 0, 0) for i in range(4): matches = [c for c in self.db().scan_certs_by_subject("%d.com" % i)] self.assertEqual(1, len(matches)) self.assertEqual("hello", matches[0])
def test_scan_certs_by_subject_finds_by_prefix(self): for i in range(4): cert = certificate_pb2.X509Description() cert.der = "hello-%d" % i cert.subject.extend([name_attribute("com.example" + (".%d" % i)*i)]) cert.sha256_hash = str(i) self.db().store_cert_desc(cert, i, 0) matches = {c for c in self.db().scan_certs_by_subject("example.com")} self.assertEqual(4, len(matches)) for i in range(4): self.assertTrue("hello-%d" % i in matches)
def test_scan_certs_by_subject_finds_by_common_name(self): for i in range(4): cert = certificate_pb2.X509Description() cert.der = "hello-%d" % i cert.subject.extend([name_attribute("Trusty CA %d" % i)]) cert.sha256_hash = str(i) self.db().store_cert_desc(cert, i, 0) for i in range(4): matches = [c for c in self.db().scan_certs_by_subject( "Trusty CA %d" % i)] self.assertEqual(1, len(matches)) self.assertEqual("hello-%d" % i, matches[0])
def _scan_der_cert(der_certs, checks): current = -1 try: result = [] for log_index, der_cert, der_chain in der_certs: current = log_index partial_result = [] strict_failure = False try: certificate = cert.Certificate(der_cert) except error.Error as e: try: certificate = cert.Certificate(der_cert, strict_der=False) except error.Error as e: partial_result.append(asn1.All()) strict_failure = True else: if isinstance(e, error.ASN1IllegalCharacter): partial_result.append( asn1.Strict(reason=e.args[0], details=(e.string, e.index))) else: partial_result.append(asn1.Strict(reason=str(e))) if not strict_failure: for check in checks: partial_result += check.check(certificate) or [] desc = cert_desc.from_cert(certificate, partial_result) else: desc = certificate_pb2.X509Description() desc.der = der_cert desc.sha256_hash = hashlib.sha256(der_cert).digest() try: root = cert.Certificate(der_chain[-1], strict_der=False) except error.Error: pass else: for iss in [ (type_.short_name, cert_desc.to_unicode('.'.join( cert_desc.process_name(value.human_readable())))) for type_, value in root.issuer() ]: proto_iss = desc.root_issuer.add() proto_iss.type, proto_iss.value = iss result.append((desc, log_index, partial_result)) return result except Exception: _, exception, exception_traceback = sys.exc_info() exception_traceback = traceback.format_exc(exception_traceback) raise PoolException((exception, exception_traceback, der_certs[0][0], der_certs[-1][0], current))
def test_store_cert_desc_ignores_duplicate(self): self.db().store_cert_desc(SAMPLE_CERT, 0, 0) sha256_hash = hashlib.sha256("hello\x00").digest() cert = self.db().get_cert_by_sha256_hash(sha256_hash) self.assertEqual("hello\x00", cert) # Store again hello2 = certificate_pb2.X509Description() hello2.der = SAMPLE_CERT.der hello2.subject.extend([name_attribute("com.example2")]) hello2.sha256_hash = sha256_hash self.db().store_cert_desc(hello2, 1, 0) certs = [c for c in self.db().scan_certs_by_subject("example.com")] self.assertEqual(1, len(certs)) self.assertEqual(SAMPLE_CERT.der, certs[0]) certs = [c for c in self.db().scan_certs_by_subject("example2.com")] self.assertEqual(0, len(certs))
def from_cert(certificate, observations=[]): """Pulls out interesting fields from certificate, so format of data will be similar in every database implementation.""" proto = certificate_pb2.X509Description() proto.der = certificate.to_der() try: for sub in [(type_.short_name, to_unicode('.'.join( process_name(value.human_readable(), type_.short_name == 'CN')))) for type_, value in certificate.subject()]: proto_sub = proto.subject.add() proto_sub.type, proto_sub.value = sub except cert.CertificateError: pass try: for iss in [ (type_.short_name, to_unicode('.'.join(process_name(value.human_readable(), False)))) for type_, value in certificate.issuer() ]: proto_iss = proto.issuer.add() proto_iss.type, proto_iss.value = iss except cert.CertificateError: pass try: for alt in certificate.subject_alternative_names(): proto_alt = proto.subject_alternative_names.add() proto_alt.type, proto_alt.value = ( alt.component_key(), to_unicode('.'.join( process_name(alt.component_value().human_readable())))) except cert.CertificateError: pass try: proto.version = str(certificate.version()) except cert.CertificateError: pass try: proto.serial_number = str( certificate.serial_number().human_readable().upper().replace( ':', '')) except cert.CertificateError: pass try: tbs_alg = certificate.signature()["algorithm"] if tbs_alg: proto.tbs_signature.algorithm_id = tbs_alg.long_name tbs_params = certificate.signature()["parameters"] if tbs_params: proto.tbs_signature.parameters = tbs_params.value cert_alg = certificate.signature_algorithm()["algorithm"] if cert_alg: proto.cert_signature.algorithm_id = cert_alg.long_name cert_params = certificate.signature_algorithm()["parameters"] if cert_params: proto.cert_signature.parameters = cert_params.value except cert.CertificateError: pass try: proto.basic_constraint_ca = bool(certificate.basic_constraint_ca()) except cert.CertificateError: pass try: proto.validity.not_before, proto.validity.not_after = ( 1000 * int(calendar.timegm(certificate.not_before())), 1000 * int(calendar.timegm(certificate.not_after()))) except cert.CertificateError: pass proto.sha256_hash = hashlib.sha256(proto.der).digest() for observation in observations: proto_obs = proto.observations.add() if observation.description: proto_obs.description = observation.description if observation.reason: proto_obs.reason = observation.reason proto_obs.details = observation.details_to_proto() return proto
import abc import hashlib from ct.proto import certificate_pb2 def name_attribute(name): att = certificate_pb2.DNAttribute() att.type, att.value = "CN", name return att SAMPLE_CERT = certificate_pb2.X509Description() SAMPLE_CERT.der = "hello\x00" SAMPLE_CERT.subject.extend([name_attribute("com.example")]) SAMPLE_CERT.sha256_hash = hashlib.sha256("hello\x00").digest() FOUR_CERTS = [] for i in range(0, 4): tmp = certificate_pb2.X509Description() tmp.der = "hello-%d" % i tmp.subject.extend([name_attribute("com.domain-%d" % i)]) tmp.sha256_hash = hashlib.sha256("hello-%d" % i).digest() FOUR_CERTS.append((tmp, i)) # This class provides common tests for all cert database implementations. # It only inherits from object so that unittest won't attempt to run the test_* # methods on this class. Derived classes should use multiple inheritance # from CertDBTest and unittest.TestCase to get test automation. class CertDBTest(object): """All cert database tests should derive from this class as well as unittest.TestCase."""
def _scan_der_cert(der_certs): current = -1 result = [] for log_index, der_cert, der_chain, entry_type in der_certs: try: current = log_index certificate = None strict_failure = False try: certificate = cert.Certificate(der_cert) except error.Error as e: try: certificate = cert.Certificate(der_cert, strict_der=False) except error.Error as e: strict_failure = True if not strict_failure: desc = cert_desc.from_cert(certificate) else: desc = certificate_pb2.X509Description() desc.der = der_cert desc.sha256_hash = hashlib.sha256(der_cert).digest() desc.entry_type = entry_type root = None if der_chain: try: issuer = cert.Certificate(der_chain[0], strict_der=False) except error.Error: pass else: desc.issuer_pk_sha256_hash = issuer.key_hash( hashfunc="sha256") try: root = cert.Certificate(der_chain[-1], strict_der=False) except error.Error: pass else: # No chain implies this is a root certificate. # Note that certificate may be None. root = certificate if root: for iss in [ (type_.short_name, cert_desc.to_unicode('.'.join( cert_desc.process_name(value.human_readable())))) for type_, value in root.issuer() ]: proto_iss = desc.root_issuer.add() proto_iss.type, proto_iss.value = iss result.append((desc, log_index)) except: batch_start_index, batch_end_index = (der_certs[0][0], der_certs[-1][0]) logging.exception( "Error scanning certificate %d in batch <%d, %d> - it will " "be excluded from the scan results", current, batch_start_index, batch_end_index) return result