# Kiwy-Signage HTTPS Architecture Diagram ## Current Architecture (Before Patches) ``` ┌─────────────────────────────────────────────────────────────────┐ │ Kiwy-Signage Player │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ GUI / Settings (main.py:696-703) │ │ │ │ - Reads config/app_config.json │ │ │ │ - Builds server URL │ │ │ │ - Calls PlayerAuth.authenticate() │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ PlayerAuth (src/player_auth.py) │ │ │ │ - authenticate() [Line 95] │ │ │ │ - verify_auth() [Line 157] │ │ │ │ - get_playlist() [Line 178] │ │ │ │ - send_heartbeat() [Line 227] │ │ │ │ - send_feedback() [Line 254] │ │ │ │ │ │ │ │ All use: requests.post/get(..., timeout=30) │ │ │ │ ⚠️ verify parameter NOT SPECIFIED (uses default) │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Playlist Manager (src/get_playlists_v2.py) │ │ │ │ - download_media_files() [Line 159] │ │ │ │ - requests.get(file_url, timeout=30) │ │ │ │ ⚠️ verify parameter NOT SPECIFIED │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Python requests Library (v2.32.4) │ │ │ │ - Default: verify=True │ │ │ │ - Validates against system CA certificates │ │ │ │ - NO custom CA support in this application │ │ │ │ - NO certificate pinning │ │ │ │ - NO ignore certificate verification option │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ └────────────────────────────┼────────────────────────────────────┘ │ ┌────────▼──────────┐ │ HTTPS Handshake │ ├───────────────────┤ │ Validates cert: │ │ ✓ Chain valid? │ │ ✓ Hostname match? │ │ ✓ Not expired? │ │ ✓ In CA store? │ └────────┬──────────┘ │ ┌────────────────────┼────────────────────┐ │ │ │ Success ❌ SELF-SIGNED │ Success ✅ (Not in CA │ (CA-signed cert) store) │ │ ✓ Server │ Certificate │ Valid │ ┌────▼─────────────────────────────────────────┐ │ SSLError: certificate verify failed │ │ Application cannot connect to server │ │ Player goes offline │ └─────────────────────────────────────────────┘ CURRENT LIMITATION: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Player ONLY works with production certificates that are signed by a trusted Certificate Authority and present in the system's CA certificate store. ``` --- ## After Patches - New Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ Kiwy-Signage Player │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ GUI / Settings (main.py:696-703) │ │ │ │ - Reads config/app_config.json │ │ │ │ - Builds server URL │ │ │ │ - Calls PlayerAuth.authenticate() │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ SSLConfig Module (src/ssl_config.py) ✨ NEW │ │ │ │ - get_verify_setting() │ │ │ │ - get_ca_bundle() │ │ │ │ - set_ca_bundle(path) │ │ │ │ - disable_verification() [dev/test only] │ │ │ │ │ │ │ │ Certificate Resolution Order: │ │ │ │ 1. Custom CA set via set_ca_bundle() │ │ │ │ 2. REQUESTS_CA_BUNDLE env var │ │ │ │ 3. config/ca_bundle.crt (file in app) │ │ │ │ 4. System default (True = certifi) │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ PlayerAuth (MODIFIED: src/player_auth.py) │ │ │ │ - __init__(): self.verify_ssl = SSLConfig.get_...() │ │ │ │ - authenticate(): verify=self.verify_ssl [Line 95] │ │ │ │ - verify_auth(): verify=self.verify_ssl [Line 157] │ │ │ │ - get_playlist(): verify=self.verify_ssl [Line 178] │ │ │ │ - send_heartbeat(): verify=self.verify_ssl [Line 227] │ │ │ │ - send_feedback(): verify=self.verify_ssl [Line 254] │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Playlist Manager (MODIFIED: get_playlists_v2.py) │ │ │ │ - download_media_files(): verify=verify_ssl [Line 159] │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Python requests Library (v2.32.4) │ │ │ │ - Uses verify parameter from SSLConfig │ │ │ │ - Can use custom CA bundle (if provided) │ │ │ │ - Validates against specified certificate │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ └────────────────────────────┼────────────────────────────────────┘ │ ┌────────▼──────────┐ │ HTTPS Handshake │ ├───────────────────┤ │ Validates against:│ │ ✓ Custom CA │ │ ✓ Hostname │ │ ✓ Expiration │ └────────┬──────────┘ │ ┌────────────────────┼────────────────────┐ │ │ │ Success ✅ Success ✅ Success ✅ (Custom CA or (Self-signed + (Production self-signed) ca_bundle.crt) cert) │ │ │ └────────────────┬───┴────────────────────┘ │ ┌────▼──────┐ │ Connected! │ │ Establish │ │ secure │ │ connection │ └─────────────┘ NEW CAPABILITY: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Player works with: ✅ Production certificates (CA-signed) ✅ Self-signed certificates (with ca_bundle.crt) ✅ Custom CA certificates (with environment variable) ✅ Multiple certificate scenarios (dev, test, prod) ``` --- ## Certificate Resolution Flow ``` When Player Starts │ ▼ ┌──────────────────┐ │ PlayerAuth │ │ __init__() │ └────────┬─────────┘ │ │ Calls SSLConfig.get_verify_setting() │ ▼ ┌──────────────────────────┐ │ Check Priority Order │ └──────────────┬───────────┘ │ ┌───────▼────────┐ │ Is custom CA │──NO──┐ │ set via code? │ │ └───────────────┘ │ │ YES │ ▼ ▼ Return path ┌──────────────────────┐ │ Check environment │ │ REQUESTS_CA_BUNDLE? │ └────────┬─────────────┘ │ NO │ YES ┌──────┘ ▼ │ Return env │ var path ▼ ┌──────────────────────┐ │ Check config dir │ │ config/ca_bundle.crt?│ └────────┬─────────────┘ │ NO │ YES ┌──────┘ ▼ │ Return config │ cert path ▼ ┌─────────────────┐ │ No custom cert │ │ found, use │ │ system default │ │ (True) │ └─────────────────┘ │ ▼ ┌──────────────────┐ │ Pass to requests │ │ library as │ │ verify= │ └──────────────────┘ │ ▼ ┌──────────────────────────┐ │ HTTPS Connection Made │ │ With Selected Cert │ └──────────────────────────┘ ``` --- ## File Structure After Patches ``` Kiwy-Signage/ ├── config/ │ ├── app_config.json (unchanged) │ ├── ca_bundle.crt ✨ NEW (optional) │ └── resources/ │ ├── src/ │ ├── main.py (unchanged) │ ├── player_auth.py ✏️ MODIFIED (7 changes) │ ├── get_playlists_v2.py ✏️ MODIFIED (2 changes) │ ├── ssl_config.py ✨ NEW FILE (~60 lines) │ ├── network_monitor.py (unchanged) │ ├── edit_popup.py (unchanged) │ └── keyboard_widget.py (unchanged) │ ├── working_files/ (unchanged) ├── start.sh (unchanged) ├── requirements.txt (unchanged - no new packages!) └── ... Changes Summary: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ✨ New Files: 1 (ssl_config.py + ca_bundle.crt) ✏️ Modified Files: 2 (player_auth.py, get_playlists_v2.py) 📦 New Packages: 0 (uses existing requests library) 🔄 Backward Compat: Yes (all changes are additive) ⚠️ Breaking Chgs: None ``` --- ## Deployment Scenarios ### Scenario 1: Production Server (Current) ``` DigiServer v2 (digi-signage.moto-adv.com) │ │ Valid CA Certificate │ (e.g., Let's Encrypt) │ ▼ Player (No patches needed) │ ▼ requests.post/get(..., timeout=30) ├─ No verify= specified └─ Uses system default: verify=True │ ▼ validates cert ✓ │ ▼ SSL handshake succeeds ✓ │ ▼ authenticated ✓ Result: ✅ Works fine (no changes needed) ``` --- ### Scenario 2: Self-Signed Server (After Patches) ``` DigiServer v2 (self.local) (Self-signed certificate) │ │ 1. Export cert │ openssl s_client... > server.crt │ │ 2. Place in player │ config/ca_bundle.crt │ ▼ Player (with patches) │ ▼ __init__() │ ▼ SSLConfig.get_verify_setting() ├─ Check custom CA: None ├─ Check env var: not set ├─ Check config dir: ✓ found ca_bundle.crt │ └─ Return: 'config/ca_bundle.crt' │ ▼ requests.post/get(..., verify='config/ca_bundle.crt') │ ▼ validates cert against ca_bundle.crt ✓ │ ▼ SSL handshake succeeds ✓ │ ▼ authenticated ✓ Result: ✅ Works with self-signed cert ``` --- ### Scenario 3: Development (Insecure - Testing Only) ``` DigiServer v2 (test.local) (Self-signed, or cert issues) │ ▼ Player (with patches + SSLConfig.disable_verification()) │ ▼ SSLConfig.disable_verification() │ └─ _verify_ssl = False │ ▼ requests.post/get(..., verify=False) │ ▼ ⚠️ Skips certificate validation │ ▼ SSL handshake proceeds anyway ⚠️ │ ▼ authenticated (but insecure!) ⚠️ VULNERABLE TO MITM ATTACKS Result: ⚠️ Works but insecure - DEV/TEST ONLY Note: Add in code temporarily: from ssl_config import SSLConfig SSLConfig.disable_verification() # TEMPORARY - DEV ONLY ``` --- ## Request Flow Sequence Diagram ``` Player SSLConfig requests DigiServer │ │ │ │ │─ authenticate()─│ │ │ │ │ │ │ │ get_verify_setting() │ │ │ │ │ │ │ ◄────────┤ 'config/ca... │ │ │ │ bundle.crt' │ │ │ │ │ │ │ ┌──────────────┐ │ │ │ │ requests.post( │ │ │ │ url, │ │ │ │ verify='config/ca... │ │ │ │ bundle.crt', │ │ │ │ ... │ │ │ │ ) │ │ │ └──────────────┘ │ │ │ │ validate cert │ │ │ │ against bundle◄──┤─ Server Cert ────┤ │ │ │ (PEM format) │ │ │ │ │ │ │ │ ✓ Signature OK │ │ │ │ ✓ Chain valid │ │ │ │ ✓ Hostname match │ │ │ │ │ │ │ ◄────────────────┤─ 200 OK ─────────┤ │ response ◄──────┤ │ {auth_code} │ │ │ │ │ │ Save auth_code │ │ │ │ to file │ │ │ │ │ │ │ ``` --- ## Error Handling ``` BEFORE (Current): ─────────────── requests.post(url, ...) │ ├─ success → parse response │ └─ SSLError (self-signed cert) │ └─ Caught by: except Exception as e │ └─ error_msg = "Authentication error: ..." │ └─ User sees generic error ❌ AFTER (With Patches): ───────────────────── requests.post(url, ..., verify=ca_bundle) │ ├─ success → parse response │ (with custom CA support) │ └─ SSLError (cert not in bundle) │ └─ Caught by: except Exception as e │ └─ error_msg = "Authentication error: ..." │ └─ Log shows actual SSL error details ✓ (if SSL validation fails, not player's fault) ``` --- ## Security Comparison ``` Scenario: Self-Signed Certificate ┌──────────────────┬──────────────────────┬─────────────────────┐ │ Approach │ Security Level │ Recommendations │ ├──────────────────┼──────────────────────┼─────────────────────┤ │ Do nothing │ 🔴 BROKEN │ ❌ Not viable │ │ (current) │ - Player offline │ - App won't work │ │ │ - No connection │ │ ├──────────────────┼──────────────────────┼─────────────────────┤ │ verify=False │ ⚠️ INSECURE │ ⚠️ DEV/TEST ONLY │ │ (disable verify) │ - Vulnerable to MITM │ - Never production │ │ │ - No cert validation │ - Temporary measure │ ├──────────────────┼──────────────────────┼─────────────────────┤ │ Custom CA bundle │ ✅ SECURE │ ✅ RECOMMENDED │ │ (patches) │ - Validates cert │ - Works with any │ │ │ - CA is trusted │ self-signed cert │ │ │ - No MITM risk │ - Production-ready │ ├──────────────────┼──────────────────────┼─────────────────────┤ │ Cert pinning │ 🔒 VERY SECURE │ ✅ IF NEEDED │ │ (advanced) │ - Pins specific cert │ - Extra complexity │ │ │ - Maximum trust │ - For high-security │ │ │ │ deployments │ └──────────────────┴──────────────────────┴─────────────────────┘ ```