""" Fingerprint Validation Service - Validates fingerprint consistency and quality. """ import logging from typing import Dict, List, Tuple, Optional, Any from datetime import datetime, timedelta from domain.entities.browser_fingerprint import BrowserFingerprint logger = logging.getLogger("fingerprint_validation_service") class FingerprintValidationService: """Service for validating fingerprint consistency and quality.""" def validate_fingerprint(self, fingerprint: BrowserFingerprint) -> Tuple[bool, List[str]]: """Validate a fingerprint for consistency and realism.""" errors = [] # Hardware consistency hw_errors = self._validate_hardware_consistency(fingerprint) errors.extend(hw_errors) # Platform consistency platform_errors = self._validate_platform_consistency(fingerprint) errors.extend(platform_errors) # WebGL consistency webgl_errors = self._validate_webgl_consistency(fingerprint) errors.extend(webgl_errors) # Canvas consistency canvas_errors = self._validate_canvas_consistency(fingerprint) errors.extend(canvas_errors) # Timezone consistency tz_errors = self._validate_timezone_consistency(fingerprint) errors.extend(tz_errors) # Mobile-specific validation if self._is_mobile_fingerprint(fingerprint): mobile_errors = self._validate_mobile_fingerprint(fingerprint) errors.extend(mobile_errors) is_valid = len(errors) == 0 return is_valid, errors def _validate_hardware_consistency(self, fp: BrowserFingerprint) -> List[str]: """Validate hardware configuration consistency.""" errors = [] # CPU cores should be power of 2 or common values valid_cores = [1, 2, 4, 6, 8, 10, 12, 16, 20, 24, 32] if fp.hardware_config.hardware_concurrency not in valid_cores: errors.append(f"Unusual CPU core count: {fp.hardware_config.hardware_concurrency}") # Device memory should be reasonable valid_memory = [0.5, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 64] if fp.hardware_config.device_memory not in valid_memory: errors.append(f"Unusual device memory: {fp.hardware_config.device_memory}GB") # Screen resolution should be common width, height = fp.hardware_config.screen_resolution common_resolutions = [ (1366, 768), (1920, 1080), (2560, 1440), (3840, 2160), # Desktop (375, 667), (375, 812), (414, 896), (390, 844), # Mobile (1440, 900), (2560, 1600), (2880, 1800) # Mac ] if (width, height) not in common_resolutions: # Check if it's at least a reasonable aspect ratio aspect_ratio = width / height if aspect_ratio < 1.2 or aspect_ratio > 2.5: errors.append(f"Unusual screen resolution: {width}x{height}") return errors def _validate_platform_consistency(self, fp: BrowserFingerprint) -> List[str]: """Validate platform and navigator consistency.""" errors = [] platform = fp.navigator_props.platform user_agent = fp.navigator_props.user_agent # Check platform matches user agent if "Win" in platform and "Windows" not in user_agent: errors.append("Platform claims Windows but user agent doesn't") elif "Mac" in platform and "Mac" not in user_agent: errors.append("Platform claims Mac but user agent doesn't") elif "Linux" in platform and "Android" not in user_agent and "Linux" not in user_agent: errors.append("Platform claims Linux but user agent doesn't match") # Check vendor consistency if fp.navigator_props.vendor == "Google Inc." and "Chrome" not in user_agent: errors.append("Google vendor but not Chrome browser") elif fp.navigator_props.vendor == "Apple Inc." and "Safari" not in user_agent: errors.append("Apple vendor but not Safari browser") return errors def _validate_webgl_consistency(self, fp: BrowserFingerprint) -> List[str]: """Validate WebGL renderer and vendor consistency.""" errors = [] vendor = fp.webgl_vendor renderer = fp.webgl_renderer # Common vendor/renderer pairs if "Intel" in vendor and "Intel" not in renderer: errors.append("WebGL vendor/renderer mismatch for Intel") elif "NVIDIA" in vendor and "NVIDIA" not in renderer and "GeForce" not in renderer: errors.append("WebGL vendor/renderer mismatch for NVIDIA") elif "AMD" in vendor and "AMD" not in renderer and "Radeon" not in renderer: errors.append("WebGL vendor/renderer mismatch for AMD") # Platform-specific WebGL if "Mac" in fp.navigator_props.platform: if "ANGLE" in renderer: errors.append("ANGLE renderer unexpected on Mac") elif "Win" in fp.navigator_props.platform: if "ANGLE" not in renderer and "Direct3D" not in renderer: errors.append("Expected ANGLE or Direct3D renderer on Windows") return errors def _validate_canvas_consistency(self, fp: BrowserFingerprint) -> List[str]: """Validate canvas noise configuration.""" errors = [] noise_level = fp.canvas_noise.noise_level if noise_level < 0 or noise_level > 0.1: errors.append(f"Canvas noise level out of range: {noise_level}") algorithm = fp.canvas_noise.algorithm valid_algorithms = ["gaussian", "uniform", "perlin"] if algorithm not in valid_algorithms: errors.append(f"Invalid canvas noise algorithm: {algorithm}") return errors def _validate_timezone_consistency(self, fp: BrowserFingerprint) -> List[str]: """Validate timezone consistency with locale.""" errors = [] timezone = fp.timezone language = fp.navigator_props.language # Basic timezone/language consistency tz_lang_map = { "Europe/Berlin": ["de", "de-DE"], "Europe/London": ["en-GB"], "America/New_York": ["en-US"], "Asia/Tokyo": ["ja", "ja-JP"] } for tz, langs in tz_lang_map.items(): if timezone == tz: if not any(lang in language for lang in langs): # It's okay if it's not a perfect match, just log it logger.debug(f"Timezone {timezone} with language {language} might be unusual") return errors def _is_mobile_fingerprint(self, fp: BrowserFingerprint) -> bool: """Check if fingerprint is for a mobile device.""" mobile_indicators = ["Android", "iPhone", "iPad", "Mobile", "Tablet"] platform = fp.navigator_props.platform user_agent = fp.navigator_props.user_agent return any(indicator in platform or indicator in user_agent for indicator in mobile_indicators) def _validate_mobile_fingerprint(self, fp: BrowserFingerprint) -> List[str]: """Validate mobile-specific fingerprint attributes.""" errors = [] # Mobile should have touch points if fp.hardware_config.max_touch_points == 0: errors.append("Mobile device with no touch points") # Mobile screen should be portrait or square-ish width, height = fp.hardware_config.screen_resolution if width > height: errors.append("Mobile device with landscape resolution") # Mobile typically has less memory if fp.hardware_config.device_memory > 8: errors.append("Mobile device with unusually high memory") return errors def calculate_fingerprint_quality_score(self, fp: BrowserFingerprint) -> float: """Calculate a quality score for the fingerprint (0.0 to 1.0).""" score = 1.0 is_valid, errors = self.validate_fingerprint(fp) # Deduct points for each error score -= len(errors) * 0.1 # Bonus points for completeness if fp.static_components: score += 0.1 if fp.platform_specific_config: score += 0.05 if fp.rotation_seed: score += 0.05 # Ensure score is between 0 and 1 return max(0.0, min(1.0, score)) def assess_fingerprint_risk(self, fp: BrowserFingerprint) -> Dict[str, Any]: """Assess the risk level of using this fingerprint.""" risk_factors = [] risk_score = 0.0 # Check age if fp.created_at: age_days = (datetime.now() - fp.created_at).days if age_days > 30: risk_factors.append("Fingerprint is over 30 days old") risk_score += 0.2 # Check validation is_valid, errors = self.validate_fingerprint(fp) if not is_valid: risk_factors.extend(errors) risk_score += len(errors) * 0.1 # Check for common/overused values if fp.webgl_renderer == "ANGLE (Intel HD Graphics)": risk_factors.append("Very common WebGL renderer") risk_score += 0.1 # Determine risk level if risk_score < 0.3: risk_level = "low" elif risk_score < 0.6: risk_level = "medium" else: risk_level = "high" return { "risk_level": risk_level, "risk_score": min(1.0, risk_score), "risk_factors": risk_factors }