#!/bin/bash # Generate a self-signed TLS certificate for local/LAN HTTPS. # Required for camera access (getUserMedia) on iOS Safari. # # Usage: bash gen-cert.sh [domain] # domain Optional domain name to include in the certificate (e.g. myhost.duckdns.org) # Can also be set via CERT_DOMAIN environment variable. # # The certificate is valid for 365 days and covers localhost + LAN IPs + optional domain. # Compatible with both OpenSSL and LibreSSL (macOS). set -e CERT_DIR="certs" mkdir -p "$CERT_DIR" # S-12: openssl writes the key with the caller's umask, which on a normal host # means 0644 — the server's TLS private key readable by every local account. # Restrict the directory up front and the key itself after generation. chmod 700 "$CERT_DIR" # Domain from first argument or CERT_DOMAIN env var. # Validate against a strict whitelist before interpolating # into the OpenSSL config heredoc — newlines or `=` characters in DOMAIN # would otherwise let a hostile env file inject extra cert extensions. DOMAIN="${1:-${CERT_DOMAIN:-}}" if [ -n "$DOMAIN" ] && ! [[ "$DOMAIN" =~ ^[A-Za-z0-9.-]+$ ]]; then echo "ERROR: DOMAIN must contain only A-Za-z0-9.-" >&2 exit 1 fi # Detect LAN IP (Linux then macOS fallback) LAN_IP="" if command -v hostname &>/dev/null && hostname -I &>/dev/null; then LAN_IP=$(hostname -I | awk '{print $1}') fi if [ -z "$LAN_IP" ] && command -v ipconfig &>/dev/null; then LAN_IP=$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null || true) fi if [ -z "$LAN_IP" ]; then LAN_IP="192.168.1.100" fi echo "Generating self-signed certificate..." echo " LAN IP: $LAN_IP" if [ -n "$DOMAIN" ]; then echo " Domain: $DOMAIN" fi # Build SAN list: domain first (if provided), then localhost + LAN IP SAN="DNS:localhost,IP:127.0.0.1,IP:$LAN_IP" if [ -n "$DOMAIN" ]; then SAN="DNS:$DOMAIN,$SAN" fi # Use a config file for SAN (works with both OpenSSL and LibreSSL) TMPCONF=$(mktemp) cat > "$TMPCONF" <