diff --git a/.gitignore b/.gitignore index bb5d1d5..4b7914f 100644 --- a/.gitignore +++ b/.gitignore @@ -68,6 +68,7 @@ test_data/*.png scripts/* !scripts/validate-release.sh !scripts/smoke-test.sh +!scripts/setup-trusted-certs.sh # Web UI auth database and SSL certs instance/ diff --git a/frontends/web/docker-entrypoint.sh b/frontends/web/docker-entrypoint.sh index 460109a..99d4f6f 100644 --- a/frontends/web/docker-entrypoint.sh +++ b/frontends/web/docker-entrypoint.sh @@ -3,6 +3,8 @@ # Docker entrypoint for Stegasoo Web UI # Handles SSL certificate generation and gunicorn startup # +# Supports mkcert for browser-trusted certificates (no warning screen) +# set -e @@ -11,24 +13,42 @@ CERT_FILE="$CERT_DIR/cert.pem" KEY_FILE="$CERT_DIR/key.pem" HOSTNAME="${STEGASOO_HOSTNAME:-localhost}" -# Generate self-signed SSL certificate if HTTPS enabled and certs don't exist +# Generate SSL certificates +# Priority: 1) Existing certs, 2) mkcert (trusted), 3) openssl (self-signed) generate_certs() { - if [ ! -f "$CERT_FILE" ] || [ ! -f "$KEY_FILE" ]; then - echo "Generating self-signed SSL certificate for $HOSTNAME..." - mkdir -p "$CERT_DIR" - - openssl req -x509 -newkey rsa:2048 \ - -keyout "$KEY_FILE" \ - -out "$CERT_FILE" \ - -sha256 -days 365 -nodes \ - -subj "/CN=$HOSTNAME" \ - -addext "subjectAltName=DNS:$HOSTNAME,DNS:localhost,IP:127.0.0.1" \ - 2>/dev/null - - echo "SSL certificate generated." - else + if [ -f "$CERT_FILE" ] && [ -f "$KEY_FILE" ]; then echo "Using existing SSL certificates." + return fi + + mkdir -p "$CERT_DIR" + + # Try mkcert first (creates browser-trusted certs) + if command -v mkcert &> /dev/null; then + echo "Generating trusted certificate with mkcert for $HOSTNAME..." + cd "$CERT_DIR" + mkcert -key-file key.pem -cert-file cert.pem "$HOSTNAME" localhost 127.0.0.1 ::1 + echo "Trusted certificate generated." + echo "" + echo " To trust on other devices, install the CA cert from:" + echo " $(mkcert -CAROOT)/rootCA.pem" + echo "" + return + fi + + # Fallback to self-signed (shows browser warning) + echo "Generating self-signed SSL certificate for $HOSTNAME..." + echo "(Install mkcert for browser-trusted certs without warnings)" + + openssl req -x509 -newkey rsa:2048 \ + -keyout "$KEY_FILE" \ + -out "$CERT_FILE" \ + -sha256 -days 365 -nodes \ + -subj "/CN=$HOSTNAME" \ + -addext "subjectAltName=DNS:$HOSTNAME,DNS:localhost,IP:127.0.0.1" \ + 2>/dev/null + + echo "Self-signed certificate generated." } # Start gunicorn with appropriate settings diff --git a/rpi/setup.sh b/rpi/setup.sh index 70291f5..ca947c2 100755 --- a/rpi/setup.sh +++ b/rpi/setup.sh @@ -184,6 +184,20 @@ else echo " gum already installed" fi +# Install mkcert for browser-trusted certificates (no warning screen!) +echo " Installing mkcert for trusted HTTPS certificates..." +if ! command -v mkcert &>/dev/null; then + sudo apt-get install -y libnss3-tools + # Download mkcert for ARM64 + sudo curl -sL "https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-arm64" -o /usr/local/bin/mkcert + sudo chmod +x /usr/local/bin/mkcert + # Install local CA (makes certs trusted on this Pi) + mkcert -install 2>/dev/null || true + echo " mkcert installed" +else + echo " mkcert already installed" +fi + echo -e "${GREEN}[4/12]${NC} Cloning Stegasoo..." # Clone Stegasoo first (needed to check for pre-built tarball) @@ -593,19 +607,40 @@ if [ "$ENABLE_HTTPS" = "true" ]; then LOCAL_IP=$(hostname -I | awk '{print $1}') PI_HOSTNAME=$(hostname) - # Generate cert with SANs for IP, hostname, and localhost - openssl req -x509 -newkey rsa:2048 \ - -keyout "$CERT_DIR/server.key" \ - -out "$CERT_DIR/server.crt" \ - -days 365 -nodes \ - -subj "/O=Stegasoo/CN=$PI_HOSTNAME" \ - -addext "subjectAltName=DNS:$PI_HOSTNAME,DNS:$PI_HOSTNAME.local,DNS:localhost,IP:$LOCAL_IP,IP:127.0.0.1" \ - 2>/dev/null + # Try mkcert first (creates browser-trusted certs - no warning screen!) + if command -v mkcert &> /dev/null; then + echo " Using mkcert for browser-trusted certificates..." + cd "$CERT_DIR" + mkcert -key-file server.key -cert-file server.crt \ + "$PI_HOSTNAME" "$PI_HOSTNAME.local" localhost "$LOCAL_IP" 127.0.0.1 ::1 + + # Copy CA to web-accessible location for easy device setup + CA_ROOT=$(mkcert -CAROOT) + CA_DIR="$INSTALL_DIR/frontends/web/static/ca" + mkdir -p "$CA_DIR" + cp "$CA_ROOT/rootCA.pem" "$CA_DIR/" + + echo -e " ${GREEN}✓${NC} Trusted certificates generated with mkcert" + echo -e " ${CYAN}Tip:${NC} New devices can get the CA from: http://$PI_HOSTNAME.local/static/ca/rootCA.pem" + else + # Fallback to self-signed (shows browser warning) + echo " Using self-signed certificate (browser will show warning)" + echo " Tip: Install mkcert for trusted certs without warnings" + + openssl req -x509 -newkey rsa:2048 \ + -keyout "$CERT_DIR/server.key" \ + -out "$CERT_DIR/server.crt" \ + -days 365 -nodes \ + -subj "/O=Stegasoo/CN=$PI_HOSTNAME" \ + -addext "subjectAltName=DNS:$PI_HOSTNAME,DNS:$PI_HOSTNAME.local,DNS:localhost,IP:$LOCAL_IP,IP:127.0.0.1" \ + 2>/dev/null + + echo -e " ${GREEN}✓${NC} Self-signed certificates generated" + fi # Fix permissions chmod 600 "$CERT_DIR/server.key" chown -R "$USER:$USER" "$CERT_DIR" - echo -e " ${GREEN}✓${NC} SSL certificates generated" fi # Setup port 443 redirect if requested diff --git a/scripts/setup-trusted-certs.sh b/scripts/setup-trusted-certs.sh new file mode 100755 index 0000000..9946afc --- /dev/null +++ b/scripts/setup-trusted-certs.sh @@ -0,0 +1,149 @@ +#!/bin/bash +# +# Setup trusted HTTPS certificates for Stegasoo +# Uses mkcert to create browser-trusted certs (no warning screens!) +# +# Usage: ./setup-trusted-certs.sh [hostname] +# +# This script: +# 1. Installs mkcert if needed +# 2. Creates a local CA (one-time) +# 3. Generates certs for your hostname +# 4. Shows how to trust the CA on other devices +# + +set -e + +HOSTNAME="${1:-stegasoo.local}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$SCRIPT_DIR/.." +CERT_DIR="$PROJECT_ROOT/frontends/web/certs" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +echo "" +echo -e "${CYAN}╔═══════════════════════════════════════════════════════════════╗${NC}" +echo -e "${CYAN}║ Stegasoo Trusted Certificate Setup ║${NC}" +echo -e "${CYAN}╚═══════════════════════════════════════════════════════════════╝${NC}" +echo "" + +# Check/install mkcert +install_mkcert() { + if command -v mkcert &> /dev/null; then + echo -e "${GREEN}✓${NC} mkcert already installed" + return + fi + + echo -e "${YELLOW}Installing mkcert...${NC}" + + # Detect OS and install + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + if command -v brew &> /dev/null; then + brew install mkcert + else + echo -e "${RED}Please install Homebrew first: https://brew.sh${NC}" + exit 1 + fi + elif [[ -f /etc/debian_version ]]; then + # Debian/Ubuntu/Raspberry Pi OS + sudo apt-get update + sudo apt-get install -y libnss3-tools + + # Download mkcert binary + ARCH=$(dpkg --print-architecture) + if [[ "$ARCH" == "arm64" ]] || [[ "$ARCH" == "aarch64" ]]; then + MKCERT_URL="https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-linux-arm64" + else + MKCERT_URL="https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-linux-amd64" + fi + + sudo curl -L "$MKCERT_URL" -o /usr/local/bin/mkcert + sudo chmod +x /usr/local/bin/mkcert + elif [[ -f /etc/arch-release ]]; then + # Arch Linux + sudo pacman -S mkcert + else + echo -e "${RED}Unsupported OS. Please install mkcert manually:${NC}" + echo " https://github.com/FiloSottile/mkcert#installation" + exit 1 + fi + + echo -e "${GREEN}✓${NC} mkcert installed" +} + +# Install local CA +setup_ca() { + echo "" + echo -e "${CYAN}Setting up local Certificate Authority...${NC}" + + if mkcert -install 2>/dev/null; then + echo -e "${GREEN}✓${NC} Local CA installed in system trust store" + else + echo -e "${YELLOW}!${NC} Could not auto-install CA (may need manual browser import)" + fi +} + +# Generate certificates +generate_certs() { + echo "" + echo -e "${CYAN}Generating trusted certificate for: ${YELLOW}$HOSTNAME${NC}" + + mkdir -p "$CERT_DIR" + cd "$CERT_DIR" + + # Generate cert for hostname + common local names + mkcert -key-file key.pem -cert-file cert.pem \ + "$HOSTNAME" \ + localhost \ + 127.0.0.1 \ + ::1 + + echo -e "${GREEN}✓${NC} Certificates generated in: $CERT_DIR" +} + +# Show CA location for other devices +show_ca_info() { + CA_ROOT=$(mkcert -CAROOT) + CA_FILE="$CA_ROOT/rootCA.pem" + + echo "" + echo -e "${CYAN}════════════════════════════════════════════════════════════════${NC}" + echo -e "${GREEN} Setup Complete!${NC}" + echo -e "${CYAN}════════════════════════════════════════════════════════════════${NC}" + echo "" + echo "Your certificates are ready. Browsers on THIS machine will trust them." + echo "" + echo -e "${YELLOW}To trust on OTHER devices (phones, tablets, other computers):${NC}" + echo "" + echo " 1. Copy the CA certificate to that device:" + echo -e " ${CYAN}$CA_FILE${NC}" + echo "" + echo " 2. Import it as a trusted CA:" + echo " - iOS: AirDrop/email the file, Settings > Profile Downloaded > Install" + echo " - Android: Settings > Security > Install from storage" + echo " - Windows: Double-click > Install > Trusted Root CAs" + echo " - macOS: Double-click > Keychain Access > Trust Always" + echo " - Linux: Copy to /usr/local/share/ca-certificates/ && update-ca-certificates" + echo "" + echo -e "${YELLOW}Quick copy command:${NC}" + echo " scp $CA_FILE user@device:/path/" + echo "" + + # Offer to serve CA file via HTTP for easy phone download + echo -e "${YELLOW}Or serve the CA for easy phone download:${NC}" + echo " python3 -m http.server 8080 -d $CA_ROOT" + echo " Then visit: http://$(hostname -I | awk '{print $1}'):8080/rootCA.pem" + echo "" +} + +# Main +install_mkcert +setup_ca +generate_certs +show_ca_info