# Player Code HTTPS Integration Guide ## Server-Side Improvements Implemented All critical and medium improvements have been implemented on the server: ### ✅ CORS Support Enabled - **File**: `app/extensions.py` - CORS extension initialized - **File**: `app/app.py` - CORS configured for `/api/*` endpoints - All player API requests now support cross-origin requests - Preflight OPTIONS requests are properly handled ### ✅ SSL Certificate Endpoint Added - **Endpoint**: `GET /api/certificate` - **Location**: `app/blueprints/api.py` - Returns server certificate in PEM format with metadata: - Certificate content (PEM format) - Certificate info (subject, issuer, validity dates, fingerprint) - Integration instructions for different platforms ### ✅ HTTPS Configuration Updated - **File**: `app/config.py` - ProductionConfig now has: - `SESSION_COOKIE_SECURE = True` - `SESSION_COOKIE_SAMESITE = 'Lax'` - **File**: `nginx.conf` - Added: - CORS headers for all responses - OPTIONS request handling - X-Forwarded-Port header forwarding ### ✅ Nginx Proxy Configuration Enhanced - Added CORS headers at nginx level for defense-in-depth - Proper X-Forwarded headers for protocol/port detection - HTTPS-friendly proxy configuration --- ## Required Player Code Changes ### 1. **For Python/Kivy Players Using Requests Library** **Update:** Import and use certificate handling: ```python import requests from requests.adapters import HTTPAdapter from requests.packages.urllib3.util.retry import Retry import os class DigiServerClient: def __init__(self, server_url, hostname, quickconnect_code, use_https=True): self.server_url = server_url self.hostname = hostname self.quickconnect_code = quickconnect_code self.session = requests.Session() # CRITICAL: Handle SSL verification if use_https: # Option 1: Get certificate from server and trust it self.setup_certificate_trust() else: # Option 2: Disable SSL verification (DEV ONLY) self.session.verify = False def setup_certificate_trust(self): """Download server certificate and configure trust.""" try: # First, make a request without verification to get the cert response = requests.get( f"{self.server_url}/api/certificate", verify=False, timeout=5 ) if response.status_code == 200: cert_data = response.json() # Save certificate locally cert_path = os.path.expanduser('~/.digiserver/server_cert.pem') os.makedirs(os.path.dirname(cert_path), exist_ok=True) with open(cert_path, 'w') as f: f.write(cert_data['certificate']) # Configure session to use this certificate self.session.verify = cert_path print(f"✓ Server certificate installed from {cert_data['certificate_info']['issuer']}") print(f" Valid until: {cert_data['certificate_info']['valid_until']}") except Exception as e: print(f"⚠️ Failed to setup certificate trust: {e}") print(" Falling back to unverified connection (not recommended for production)") self.session.verify = False def get_playlist(self): """Get playlist from server with proper error handling.""" try: response = self.session.get( f"{self.server_url}/api/playlists", params={ 'hostname': self.hostname, 'quickconnect_code': self.quickconnect_code }, timeout=10 ) response.raise_for_status() return response.json() except requests.exceptions.SSLError as e: print(f"❌ SSL Error: {e}") # Log error for debugging print(" This usually means the server certificate is not trusted.") print(" Try running: DigiServerClient.setup_certificate_trust()") raise except requests.exceptions.ConnectionError as e: print(f"❌ Connection Error: {e}") raise except Exception as e: print(f"❌ Error: {e}") raise def send_feedback(self, status, message=''): """Send player feedback/status to server.""" try: response = self.session.post( f"{self.server_url}/api/player-feedback", json={ 'hostname': self.hostname, 'quickconnect_code': self.quickconnect_code, 'status': status, 'message': message, 'timestamp': datetime.utcnow().isoformat() }, timeout=10 ) response.raise_for_status() return response.json() except Exception as e: print(f"Error sending feedback: {e}") return None ``` ### 2. **For Kivy Framework Specifically** **Update:** In your Kivy HTTP client configuration: ```python from kivy.network.urlrequest import UrlRequest from kivy.logger import Logger import ssl import certifi class DigiServerKivyClient: def __init__(self, server_url, hostname, quickconnect_code): self.server_url = server_url self.hostname = hostname self.quickconnect_code = quickconnect_code # Configure SSL context for Kivy requests self.ssl_context = self._setup_ssl_context() def _setup_ssl_context(self): """Setup SSL context with certificate trust.""" try: # Try to get server certificate import requests response = requests.get( f"{self.server_url}/api/certificate", verify=False, timeout=5 ) if response.status_code == 200: cert_data = response.json() cert_path = os._get_cert_path() with open(cert_path, 'w') as f: f.write(cert_data['certificate']) # Create SSL context context = ssl.create_default_context() context.load_verify_locations(cert_path) Logger.info('DigiServer', f'SSL context configured with server certificate') return context except Exception as e: Logger.warning('DigiServer', f'Failed to setup SSL: {e}') return None def fetch_playlist(self, callback): """Fetch playlist with proper SSL handling.""" url = f"{self.server_url}/api/playlists" params = f"?hostname={self.hostname}&quickconnect_code={self.quickconnect_code}" headers = { 'Content-Type': 'application/json', 'User-Agent': 'Kiwy-Signage-Player/1.0' } request = UrlRequest( url + params, on_success=callback, on_error=self._on_error, on_failure=self._on_failure, headers=headers ) return request def _on_error(self, request, error): Logger.error('DigiServer', f'Request error: {error}') def _on_failure(self, request, result): Logger.error('DigiServer', f'Request failed: {result}') ``` ### 3. **Environment Configuration** **Add to player app_config.json or environment:** ```json { "server": { "url": "https://192.168.0.121", "hostname": "player1", "quickconnect_code": "ABC123XYZ", "verify_ssl": false, "use_server_certificate": true, "certificate_path": "~/.digiserver/server_cert.pem" }, "connection": { "timeout": 10, "retry_attempts": 3, "retry_delay": 5 } } ``` --- ## Testing Checklist ### Server-Side Tests - [ ] Verify CORS headers present: `curl -v https://192.168.0.121/api/health` - [ ] Check certificate endpoint: `curl -k https://192.168.0.121/api/certificate` - [ ] Test OPTIONS preflight: `curl -X OPTIONS https://192.168.0.121/api/playlists` - [ ] Verify X-Forwarded headers: `curl -v https://192.168.0.121/` ### Player Connection Tests - [ ] Player connects with HTTPS successfully - [ ] Player fetches playlist without SSL errors - [ ] Player receives status update confirmation - [ ] Player sends feedback/heartbeat correctly ### Integration Tests ```bash # Test certificate retrieval curl -k https://192.168.0.121/api/certificate | jq '.certificate_info' # Test CORS preflight for player curl -X OPTIONS https://192.168.0.121/api/playlists \ -H "Origin: http://192.168.0.121" \ -H "Access-Control-Request-Method: GET" \ -v # Simulate player playlist fetch curl -k https://192.168.0.121/api/playlists \ --data-urlencode "hostname=test-player" \ --data-urlencode "quickconnect_code=test123" \ -H "Origin: *" ``` --- ## Migration Steps ### For Existing Players 1. **Update player code** with new SSL handling from this guide 2. **Restart player application** to pick up changes 3. **Verify connection** works with HTTPS server 4. **Monitor logs** for any SSL-related errors ### For New Players 1. **Deploy updated player code** with SSL support from the start 2. **Configure with HTTPS server URL** 3. **Run initialization** to fetch and trust server certificate --- ## Troubleshooting ### "SSL: CERTIFICATE_VERIFY_FAILED" - Player is rejecting the self-signed certificate - **Solution**: Run certificate trust setup or disable SSL verification ### "Connection Refused" - Server HTTPS port not accessible - **Solution**: Check nginx is running, port 443 is open, firewall rules ### "CORS error" - Browser/HTTP client blocking cross-origin request - **Solution**: Verify CORS headers in response, check Origin header ### "Certificate not found at endpoint" - Server certificate file missing - **Solution**: Verify cert.pem exists at `/etc/nginx/ssl/cert.pem` --- ## Security Recommendations 1. **For Development/Testing**: Disable SSL verification temporarily ```python session.verify = False ``` 2. **For Production**: - Use proper certificates (Let's Encrypt recommended) - Deploy certificate trust setup at player initialization - Monitor SSL certificate expiration - Implement certificate pinning for critical deployments 3. **For Self-Signed Certificates**: - Use `/api/certificate` endpoint to distribute certificates - Store certificates in secure location on device - Implement certificate update mechanism - Log certificate trust changes for auditing --- ## Next Steps 1. **Implement SSL handling** in player code using examples above 2. **Test with HTTP first** to ensure API works 3. **Enable HTTPS** and test with certificate handling 4. **Deploy to production** with proper SSL setup 5. **Monitor** player connections and SSL errors