mirror of
				https://github.com/bunny-lab-io/Borealis.git
				synced 2025-10-27 03:41:57 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			134 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			134 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| Self-signed certificate management for Borealis.
 | |
| 
 | |
| The production Flask server and the Vite dev server both consume the same
 | |
| certificate chain so agents and browsers can pin a single trust anchor during
 | |
| enrollment.
 | |
| """
 | |
| 
 | |
| from __future__ import annotations
 | |
| 
 | |
| import os
 | |
| import ssl
 | |
| from datetime import datetime, timedelta, timezone
 | |
| from pathlib import Path
 | |
| from typing import Tuple
 | |
| 
 | |
| from cryptography import x509
 | |
| from cryptography.hazmat.primitives import hashes, serialization
 | |
| from cryptography.hazmat.primitives.asymmetric import ec
 | |
| from cryptography.x509.oid import NameOID
 | |
| 
 | |
| _CERT_DIR = Path(__file__).resolve().parent.parent / "certs"
 | |
| _CERT_FILE = _CERT_DIR / "borealis-server-cert.pem"
 | |
| _KEY_FILE = _CERT_DIR / "borealis-server-key.pem"
 | |
| _BUNDLE_FILE = _CERT_DIR / "borealis-server-bundle.pem"
 | |
| 
 | |
| # 100-year lifetime (effectively "never" for self-signed deployments).
 | |
| _CERT_VALIDITY = timedelta(days=365 * 100)
 | |
| 
 | |
| 
 | |
| def ensure_certificate(common_name: str = "Borealis Server") -> Tuple[Path, Path, Path]:
 | |
|     """
 | |
|     Ensure the self-signed certificate and key exist on disk.
 | |
| 
 | |
|     Returns (cert_path, key_path, bundle_path).
 | |
|     """
 | |
| 
 | |
|     _CERT_DIR.mkdir(parents=True, exist_ok=True)
 | |
| 
 | |
|     regenerate = not (_CERT_FILE.exists() and _KEY_FILE.exists())
 | |
|     if not regenerate:
 | |
|         try:
 | |
|             with _CERT_FILE.open("rb") as fh:
 | |
|                 cert = x509.load_pem_x509_certificate(fh.read())
 | |
|             if cert.not_valid_after.replace(tzinfo=timezone.utc) <= datetime.now(tz=timezone.utc):
 | |
|                 regenerate = True
 | |
|         except Exception:
 | |
|             regenerate = True
 | |
| 
 | |
|     if regenerate:
 | |
|         _generate_certificate(common_name)
 | |
| 
 | |
|     if not _BUNDLE_FILE.exists():
 | |
|         _BUNDLE_FILE.write_bytes(_CERT_FILE.read_bytes())
 | |
| 
 | |
|     return _CERT_FILE, _KEY_FILE, _BUNDLE_FILE
 | |
| 
 | |
| 
 | |
| def _generate_certificate(common_name: str) -> None:
 | |
|     private_key = ec.generate_private_key(ec.SECP384R1())
 | |
|     public_key = private_key.public_key()
 | |
| 
 | |
|     now = datetime.now(tz=timezone.utc)
 | |
|     builder = (
 | |
|         x509.CertificateBuilder()
 | |
|         .subject_name(
 | |
|             x509.Name(
 | |
|                 [
 | |
|                     x509.NameAttribute(NameOID.COMMON_NAME, common_name),
 | |
|                     x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Borealis"),
 | |
|                 ]
 | |
|             )
 | |
|         )
 | |
|         .issuer_name(
 | |
|             x509.Name(
 | |
|                 [
 | |
|                     x509.NameAttribute(NameOID.COMMON_NAME, common_name),
 | |
|                 ]
 | |
|             )
 | |
|         )
 | |
|         .public_key(public_key)
 | |
|         .serial_number(x509.random_serial_number())
 | |
|         .not_valid_before(now - timedelta(minutes=5))
 | |
|         .not_valid_after(now + _CERT_VALIDITY)
 | |
|         .add_extension(
 | |
|             x509.SubjectAlternativeName(
 | |
|                 [
 | |
|                     x509.DNSName("localhost"),
 | |
|                     x509.DNSName("127.0.0.1"),
 | |
|                     x509.DNSName("::1"),
 | |
|                 ]
 | |
|             ),
 | |
|             critical=False,
 | |
|         )
 | |
|         .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
 | |
|     )
 | |
| 
 | |
|     certificate = builder.sign(private_key=private_key, algorithm=hashes.SHA384())
 | |
| 
 | |
|     _KEY_FILE.write_bytes(
 | |
|         private_key.private_bytes(
 | |
|             encoding=serialization.Encoding.PEM,
 | |
|             format=serialization.PrivateFormat.TraditionalOpenSSL,
 | |
|             encryption_algorithm=serialization.NoEncryption(),
 | |
|         )
 | |
|     )
 | |
|     _CERT_FILE.write_bytes(certificate.public_bytes(serialization.Encoding.PEM))
 | |
| 
 | |
|     # Propagate filesystem permissions to restrict accidental disclosure.
 | |
|     _tighten_permissions(_KEY_FILE)
 | |
|     _tighten_permissions(_CERT_FILE)
 | |
| 
 | |
| 
 | |
| def _tighten_permissions(path: Path) -> None:
 | |
|     try:
 | |
|         if os.name == "posix":
 | |
|             path.chmod(0o600)
 | |
|     except Exception:
 | |
|         pass
 | |
| 
 | |
| 
 | |
| def build_ssl_context() -> ssl.SSLContext:
 | |
|     cert_path, key_path, _bundle_path = ensure_certificate()
 | |
|     context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
 | |
|     context.minimum_version = ssl.TLSVersion.TLSv1_3
 | |
|     context.load_cert_chain(certfile=str(cert_path), keyfile=str(key_path))
 | |
|     return context
 | |
| 
 | |
| 
 | |
| def certificate_paths() -> Tuple[str, str, str]:
 | |
|     cert_path, key_path, bundle_path = ensure_certificate()
 | |
|     return str(cert_path), str(key_path), str(bundle_path)
 | |
| 
 |