Files
stegasoo/frontends/web/ssl_utils.py
Aaron D. Lee cf247d207f
Some checks failed
Release / test (push) Failing after 43s
Release / publish (push) Has been skipped
Release / github-release (push) Has been skipped
v4.0.2: Add Web UI authentication and optional HTTPS
- Add single-admin login with SQLite3 user storage
- First-run setup wizard for admin account creation
- Account management page for password changes
- Optional HTTPS with auto-generated self-signed certificates
- Configurable via STEGASOO_AUTH_ENABLED, STEGASOO_HTTPS_ENABLED env vars
- UI improvements: larger QR previews, consistent panel styling
- Update docker-compose.yml with auth config and persistent volumes
- Update all documentation for v4.0.2

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 20:00:47 -05:00

112 lines
3.2 KiB
Python

"""
SSL Certificate Utilities
Auto-generates self-signed certificates for HTTPS.
Uses cryptography library (already a dependency).
"""
import datetime
import ipaddress
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 get_cert_paths(base_dir: Path) -> tuple[Path, Path]:
"""Get paths for cert and key files."""
cert_dir = base_dir / "certs"
cert_dir.mkdir(parents=True, exist_ok=True)
return cert_dir / "server.crt", cert_dir / "server.key"
def certs_exist(base_dir: Path) -> bool:
"""Check if both cert files exist."""
cert_path, key_path = get_cert_paths(base_dir)
return cert_path.exists() and key_path.exists()
def generate_self_signed_cert(
base_dir: Path,
hostname: str = "localhost",
days_valid: int = 365,
) -> tuple[Path, Path]:
"""
Generate self-signed SSL certificate.
Args:
base_dir: Base directory for certs folder
hostname: Server hostname for certificate
days_valid: Certificate validity in days
Returns:
Tuple of (cert_path, key_path)
"""
cert_path, key_path = get_cert_paths(base_dir)
# Generate RSA key
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# Create certificate
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Stegasoo"),
x509.NameAttribute(NameOID.COMMON_NAME, hostname),
])
# Subject Alternative Names
san_list = [
x509.DNSName(hostname),
x509.DNSName("localhost"),
x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")),
]
# Add the hostname as IP if it looks like one
try:
san_list.append(x509.IPAddress(ipaddress.IPv4Address(hostname)))
except ipaddress.AddressValueError:
pass
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=days_valid))
.add_extension(
x509.SubjectAlternativeName(san_list),
critical=False,
)
.sign(key, hashes.SHA256())
)
# Write key file (chmod 600)
key_path.write_bytes(
key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
)
key_path.chmod(0o600)
# Write cert file
cert_path.write_bytes(cert.public_bytes(serialization.Encoding.PEM))
return cert_path, key_path
def ensure_certs(base_dir: Path, hostname: str = "localhost") -> tuple[Path, Path]:
"""Ensure certificates exist, generating if needed."""
if certs_exist(base_dir):
return get_cert_paths(base_dir)
print(f"Generating self-signed SSL certificate for {hostname}...")
return generate_self_signed_cert(base_dir, hostname)