Add mkcert support for browser-trusted HTTPS certificates

No more browser warnings! mkcert creates locally-trusted certs.

Pi Setup:
- Auto-install mkcert during setup
- Generate trusted certs when HTTPS enabled
- Copy CA to /static/ca/rootCA.pem for easy device setup
- New devices can download CA via HTTP and install it

Docker:
- docker-entrypoint.sh checks for mkcert, falls back to openssl
- Shows instructions for CA distribution to other devices

Scripts:
- Added setup-trusted-certs.sh helper for local dev
- Installs mkcert, generates certs, shows device setup instructions

To trust on new devices:
1. Download: http://stegasoo.local/static/ca/rootCA.pem
2. Install as trusted CA in browser/OS

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-01-07 23:49:38 -05:00
parent 455c6dfd01
commit f971b75d7e
4 changed files with 229 additions and 24 deletions

1
.gitignore vendored
View File

@@ -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/

View File

@@ -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,12 +13,33 @@ 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..."
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" \
@@ -25,10 +48,7 @@ generate_certs() {
-addext "subjectAltName=DNS:$HOSTNAME,DNS:localhost,IP:127.0.0.1" \
2>/dev/null
echo "SSL certificate generated."
else
echo "Using existing SSL certificates."
fi
echo "Self-signed certificate generated."
}
# Start gunicorn with appropriate settings

View File

@@ -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,7 +607,26 @@ if [ "$ENABLE_HTTPS" = "true" ]; then
LOCAL_IP=$(hostname -I | awk '{print $1}')
PI_HOSTNAME=$(hostname)
# Generate cert with SANs for IP, hostname, and localhost
# 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" \
@@ -602,10 +635,12 @@ if [ "$ENABLE_HTTPS" = "true" ]; then
-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

149
scripts/setup-trusted-certs.sh Executable file
View File

@@ -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