"""Certificado HTTPS de desarrollo (autofirmado) para cámara en red local.""" from __future__ import annotations import datetime import ipaddress import socket from pathlib import Path from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.x509.oid import NameOID def local_ip() -> str | None: try: with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: s.connect(("8.8.8.8", 80)) return s.getsockname()[0] except OSError: return None def ensure_dev_certs(cert_dir: Path) -> tuple[Path, Path]: cert_dir.mkdir(parents=True, exist_ok=True) cert_path = cert_dir / "dev.pem" key_path = cert_dir / "dev-key.pem" marker = cert_dir / ".ip" ip = local_ip() cert_version = "2" cached = marker.read_text(encoding="utf-8").strip() if marker.exists() else "" fingerprint = f"{ip or ''}|{cert_version}" if cert_path.exists() and key_path.exists() and cached == fingerprint: return cert_path, key_path key = rsa.generate_private_key(public_exponent=65537, key_size=2048) subject = issuer = x509.Name( [ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Edadometro Dev"), x509.NameAttribute(NameOID.COMMON_NAME, "localhost"), ] ) san: list[x509.GeneralName] = [ x509.DNSName("localhost"), x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")), x509.IPAddress(ipaddress.IPv6Address("::1")), ] if ip: san.append(x509.IPAddress(ipaddress.IPv4Address(ip))) now = datetime.datetime.now(datetime.timezone.utc) cert = ( x509.CertificateBuilder() .subject_name(subject) .issuer_name(issuer) .public_key(key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(now) .not_valid_after(now + datetime.timedelta(days=365)) .add_extension(x509.SubjectAlternativeName(san), critical=False) .sign(key, hashes.SHA256()) ) key_path.write_bytes( key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) ) cert_path.write_bytes(cert.public_bytes(serialization.Encoding.PEM)) marker.write_text(fingerprint, encoding="utf-8") return cert_path, key_path