This commit is contained in:
overcuriousity
2025-09-10 16:26:44 +02:00
parent ce0e11cf0b
commit a0caedcb1f
4 changed files with 122 additions and 44 deletions

View File

@@ -7,6 +7,7 @@ import json
import re
from typing import List, Dict, Any, Tuple, Set
from urllib.parse import quote
from datetime import datetime, timezone
from .base_provider import BaseProvider
from core.graph_manager import RelationshipType
@@ -39,6 +40,20 @@ class CrtShProvider(BaseProvider):
"""
return True
def _is_cert_valid(self, cert_data: Dict[str, Any]) -> bool:
"""Check if a certificate is currently valid."""
try:
not_after_str = cert_data.get('not_after')
if not_after_str:
# Append 'Z' to indicate UTC if it's not present
if not not_after_str.endswith('Z'):
not_after_str += 'Z'
not_after_date = datetime.fromisoformat(not_after_str.replace('Z', '+00:00'))
return not_after_date > datetime.now(timezone.utc)
except Exception:
return False
return False
def query_domain(self, domain: str) -> List[Tuple[str, str, RelationshipType, float, Dict[str, Any]]]:
"""
Query crt.sh for certificates containing the domain.
@@ -68,50 +83,47 @@ class CrtShProvider(BaseProvider):
return []
# Process certificates to extract relationships
seen_certificates = set()
discovered_subdomains = {}
for cert_data in certificates:
cert_id = cert_data.get('id')
if not cert_id or cert_id in seen_certificates:
continue
seen_certificates.add(cert_id)
# Extract domains from certificate
cert_domains = self._extract_domains_from_certificate(cert_data)
if domain in cert_domains and len(cert_domains) > 1:
# Create relationships between domains found in the same certificate
for related_domain in cert_domains:
if related_domain != domain and self._is_valid_domain(related_domain):
# Create SAN relationship
raw_data = {
'certificate_id': cert_id,
'issuer': cert_data.get('issuer_name', ''),
'not_before': cert_data.get('not_before', ''),
'not_after': cert_data.get('not_after', ''),
'serial_number': cert_data.get('serial_number', ''),
'all_domains': list(cert_domains)
}
relationships.append((
domain,
related_domain,
RelationshipType.SAN_CERTIFICATE,
RelationshipType.SAN_CERTIFICATE.default_confidence,
raw_data
))
# Log the discovery
self.log_relationship_discovery(
source_node=domain,
target_node=related_domain,
relationship_type=RelationshipType.SAN_CERTIFICATE,
confidence_score=RelationshipType.SAN_CERTIFICATE.default_confidence,
raw_data=raw_data,
discovery_method="certificate_san_analysis"
)
is_valid = self._is_cert_valid(cert_data)
for subdomain in cert_domains:
if self._is_valid_domain(subdomain) and subdomain != domain:
if subdomain not in discovered_subdomains:
discovered_subdomains[subdomain] = {'has_valid_cert': False, 'issuers': set()}
if is_valid:
discovered_subdomains[subdomain]['has_valid_cert'] = True
issuer = cert_data.get('issuer_name')
if issuer:
discovered_subdomains[subdomain]['issuers'].add(issuer)
# Create relationships from the discovered subdomains
for subdomain, data in discovered_subdomains.items():
raw_data = {
'has_valid_cert': data['has_valid_cert'],
'issuers': list(data['issuers']),
'source': 'crt.sh'
}
relationships.append((
domain,
subdomain,
RelationshipType.SAN_CERTIFICATE,
RelationshipType.SAN_CERTIFICATE.default_confidence,
raw_data
))
self.log_relationship_discovery(
source_node=domain,
target_node=subdomain,
relationship_type=RelationshipType.SAN_CERTIFICATE,
confidence_score=RelationshipType.SAN_CERTIFICATE.default_confidence,
raw_data=raw_data,
discovery_method="certificate_san_analysis"
)
except json.JSONDecodeError as e:
self.logger.logger.error(f"Failed to parse JSON response from crt.sh: {e}")
except Exception as e: