Files
digiserver-v2/old_code_documentation/player_analisis/KIWY_PLAYER_HTTPS_ANALYSIS.md

18 KiB

Kiwy-Signage Player - HTTPS/SSL Certificate Analysis

Executive Summary

The Kiwy-Signage player is a Python-based digital signage application built with Kivy that communicates with the DigiServer v2 backend. The player currently has NO custom SSL certificate verification mechanism and relies entirely on Python's requests library default behavior.

This means:

  • HTTPS connections to production servers work because they have valid CA-signed certificates
  • Self-signed certificates or custom certificate authorities will FAIL without code modifications
  • No verify parameter is passed to any requests calls (uses default verify=True)
  • No support for custom CA certificates or certificate bundles

1. HTTP Client Library & Dependencies

Library Used

  • requests (version 2.32.4) - Python HTTP library with SSL verification enabled by default
  • aiohttp (version 3.9.1) - Not currently used for player authentication/API calls

Dependency Chain

requirements.txt:
  - kivy>=2.3.0
  - ffpyplayer
  - requests==2.32.4        ← Used for ALL HTTPS requests
  - bcrypt==4.2.1
  - aiohttp==3.9.1
  - asyncio==3.4.3

2. Main Connection Files & Locations

Core Authentication Module

File: src/player_auth.py
Lines: 352 lines total
Responsibility: Handles all server authentication and API communication

Playlist Management

File: src/get_playlists_v2.py
Lines: 352 lines total
Responsibility: Fetches and manages playlists, uses PlayerAuth for communication

Network Monitoring

File: src/network_monitor.py
Lines: 235 lines total
Responsibility: Monitors connectivity using ping (not HTTPS), manages WiFi restarts

Main GUI Application

File: src/main.py
Lines: 1,826 lines total
Responsibility: Kivy GUI, server connection settings, calls PlayerAuth for authentication

Configuration File

File: config/app_config.json
Responsibility: Stores server IP, port, player credentials, and settings


3. HTTPS Connection Architecture

Authentication Flow

1. Player Configuration (config/app_config.json)
   ├─ server_ip: "digi-signage.moto-adv.com"
   ├─ port: "443"
   ├─ screen_name: "player-name"
   └─ quickconnect_key: "QUICK123"

2. URL Construction (src/main.py, lines 696-703)
   ├─ If server_ip has http:// or https:// prefix, use as-is
   ├─ Otherwise: protocol = "https" if port == "443" else "http"
   └─ server_url = f"{protocol}://{server_ip}:{port}"

3. Authentication Request (src/player_auth.py, lines 95-98)
   ├─ POST /api/auth/player
   ├─ Payload: {hostname, password, quickconnect_code}
   └─ Returns: {auth_code, player_id, player_name, playlist_id, ...}

4. Authenticated API Calls (src/player_auth.py, lines 159-163, etc.)
   ├─ Headers: Authorization: Bearer {auth_code}
   └─ GET/POST to various /api/... endpoints

All HTTPS Request Points in Code

1. Authentication (src/player_auth.py)

Location: Line 95

response = requests.post(auth_url, json=payload, timeout=timeout)
  • URL: {server_url}/api/auth/player
  • Method: POST
  • Auth: None (initial auth)
  • SSL Verify: DEFAULT (True, no custom handling)

Location: Line 157

response = requests.post(verify_url, json=payload, timeout=timeout)
  • URL: {server_url}/api/auth/verify
  • Method: POST
  • Auth: None
  • SSL Verify: DEFAULT (True)

2. Playlist Fetching (src/player_auth.py)

Location: Line 178

response = requests.get(playlist_url, headers=headers, timeout=timeout)
  • URL: {server_url}/api/playlists/{player_id}
  • Method: GET
  • Auth: Bearer token in Authorization header
  • Headers: Authorization: Bearer {auth_code}
  • SSL Verify: DEFAULT (True, NO verify= parameter)

3. Heartbeat/Status (src/player_auth.py)

Location: Line 227

response = requests.post(heartbeat_url, headers=headers, json=payload, timeout=timeout)
  • URL: {server_url}/api/players/{player_id}/heartbeat
  • Method: POST
  • Auth: Bearer token
  • SSL Verify: DEFAULT (True, NO verify= parameter)

4. Player Feedback (src/player_auth.py)

Location: Line 254

response = requests.post(feedback_url, headers=headers, json=payload, timeout=timeout)
  • URL: {server_url}/api/player-feedback
  • Method: POST
  • Auth: Bearer token
  • SSL Verify: DEFAULT (True, NO verify= parameter)

5. Media Download (src/get_playlists_v2.py)

Location: Line 159

response = requests.get(file_url, timeout=30)
  • URL: Direct to media file URLs from playlist
  • Method: GET
  • Auth: None (public download URLs)
  • SSL Verify: DEFAULT (True, NO verify= parameter)

4. Certificate Verification Current Configuration

Current SSL/Certificate Behavior

Summary: Relies entirely on Python's requests library defaults.

Default requests behavior:

  • verify=True (implicitly used when not specified)
  • Uses system CA certificate store
  • Validates certificate chain, hostname, and expiration
  • Rejects self-signed certificates with error

Hardcoded Certificate Settings

🔴 NONE - No hardcoded SSL certificate settings exist in the codebase.

Certificate Verification Code Locations

Search Results for "verify", "ssl", "cert", "certificate":

Only verify_auth() method found (authenticates with server, not certificate verification):

No SSL/certificate configuration found in:

  • requests library verify parameter
  • Custom CA bundle paths
  • SSL context configuration
  • Certificate pinning
  • urllib3 certificate settings

5. Self-Signed Certificate Support

Current State: NOT SUPPORTED

When connecting to a server with a self-signed certificate:

# Current code (player_auth.py, Line 95):
response = requests.post(auth_url, json=payload, timeout=timeout)

# Will raise:
# requests.exceptions.SSLError: 
#   ("certificate verify failed: self signed certificate (_ssl.c:...)

Exception Handling

The code catches exceptions but doesn't differentiate SSL errors:

# player_auth.py, lines 111-127
except requests.exceptions.ConnectionError:
    error_msg = "Cannot connect to server"
except requests.exceptions.Timeout:
    error_msg = "Connection timeout"
except Exception as e:
    error_msg = f"Authentication error: {str(e)}"
    # Will catch SSL errors here but label them as generic "Authentication error"

6. Required Changes for Self-Signed Certificate Support

Option 1: Disable Certificate Verification (⚠️ INSECURE - Development Only)

Not Recommended for Production

Add to each requests call:

verify=False  # Disables SSL certificate verification

Example modification:

# OLD (player_auth.py, Line 95):
response = requests.post(auth_url, json=payload, timeout=timeout)

# NEW:
response = requests.post(auth_url, json=payload, timeout=timeout, verify=False)

Locations requiring modification (5 places):

  1. src/player_auth.py, Line 95 - authenticate() method
  2. src/player_auth.py, Line 157 - verify_auth() method
  3. src/player_auth.py, Line 178 - get_playlist() method
  4. src/player_auth.py, Line 227 - send_heartbeat() method
  5. src/player_auth.py, Line 254 - send_feedback() method
  6. src/get_playlists_v2.py, Line 159 - download_media_files() method

Production-Ready Approach

Step 1: Create certificate configuration

# New file: src/ssl_config.py
import os
import requests

class SSLConfig:
    """Manage SSL certificate verification for self-signed certs"""
    
    @staticmethod
    def get_ca_bundle():
        """Get path to CA certificate bundle
        
        Returns:
            str: Path to CA bundle or True for default system certs
        """
        # Priority order:
        # 1. Custom CA bundle in config directory
        # 2. CA bundle path from environment variable
        # 3. System default CA bundle (requests uses certifi)
        
        custom_ca = 'config/ca_bundle.crt'
        if os.path.exists(custom_ca):
            return custom_ca
        
        env_ca = os.environ.get('REQUESTS_CA_BUNDLE')
        if env_ca and os.path.exists(env_ca):
            return env_ca
        
        return True  # Use system/certifi default
    
    @staticmethod
    def get_verify_setting():
        """Get SSL verification setting
        
        Returns:
            bool or str: Path to CA bundle or True/False
        """
        return SSLConfig.get_ca_bundle()

Step 2: Modify PlayerAuth to use custom certificates

# player_auth.py modifications:

from ssl_config import SSLConfig  # Add import

class PlayerAuth:
    def __init__(self, config_file='player_auth.json'):
        self.config_file = config_file
        self.auth_data = self._load_auth_data()
        self.verify_ssl = SSLConfig.get_verify_setting()  # Add this
    
    def authenticate(self, ...):
        # Add verify parameter to requests call:
        response = requests.post(
            auth_url, 
            json=payload, 
            timeout=timeout,
            verify=self.verify_ssl  # ADD THIS
        )
    
    def verify_auth(self, ...):
        response = requests.post(
            verify_url, 
            json=payload, 
            timeout=timeout,
            verify=self.verify_ssl  # ADD THIS
        )
    
    def get_playlist(self, ...):
        response = requests.get(
            playlist_url, 
            headers=headers, 
            timeout=timeout,
            verify=self.verify_ssl  # ADD THIS
        )
    
    def send_heartbeat(self, ...):
        response = requests.post(
            heartbeat_url, 
            headers=headers, 
            json=payload, 
            timeout=timeout,
            verify=self.verify_ssl  # ADD THIS
        )
    
    def send_feedback(self, ...):
        response = requests.post(
            feedback_url, 
            headers=headers, 
            json=payload, 
            timeout=timeout,
            verify=self.verify_ssl  # ADD THIS
        )

Step 3: Handle media downloads

# get_playlists_v2.py modifications:

from ssl_config import SSLConfig

def download_media_files(playlist, media_dir):
    verify_ssl = SSLConfig.get_verify_setting()  # Add this
    
    for media in playlist:
        ...
        response = requests.get(
            file_url, 
            timeout=30,
            verify=verify_ssl  # ADD THIS
        )
        ...

Step 4: Prepare CA certificate

  1. Export certificate from self-signed server:
openssl s_client -connect server.local:443 -showcerts < /dev/null | \
  openssl x509 -outform PEM > ca_bundle.crt
  1. Place in player config:
cp ca_bundle.crt /path/to/Kiwy-Signage/config/ca_bundle.crt
  1. Or set environment variable:
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-bundle.crt

Option 3: Certificate Pinning (⚠️ Advanced)

For maximum security when using self-signed certificates:

import ssl
import certifi
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.ssl_ import create_urllib3_context

class SSLPinningAdapter(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        ctx = create_urllib3_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
        # Or use specific certificate:
        # ctx.load_verify_locations('config/server_cert.pem')
        kwargs['ssl_context'] = ctx
        return super().init_poolmanager(*args, **kwargs)

# Usage in PlayerAuth:
session = requests.Session()
session.mount('https://', SSLPinningAdapter())
response = session.post(auth_url, json=payload, timeout=timeout)

7. Testing Self-Signed Certificate Connections

Before Modification (Current Behavior)

Test connection to self-signed server:

cd /tmp/Kiwy-Signage
python3 -c "
import requests
url = 'https://your-self-signed-server:443/api/health'
try:
    response = requests.get(url)
    print('Connection successful')
except requests.exceptions.SSLError as e:
    print(f'SSL Error: {e}')
"
# Output: SSL Error: certificate verify failed

After Modification (With Custom CA)

cd /tmp/Kiwy-Signage
# Place ca_bundle.crt in config/
python3 -c "
import requests
url = 'https://your-self-signed-server:443/api/health'
response = requests.get(url, verify='config/ca_bundle.crt')
print(f'Connection successful: {response.status_code}')
"
# Output: Connection successful: 200

8. Summary Table

Aspect Current State Support Level
HTTP Client requests 2.32.4 Production-ready
HTTPS Support Yes (standard URLs) Full
Self-Signed Certs NO NOT SUPPORTED
Custom CA Bundle NO NOT SUPPORTED
Certificate Pinning NO NOT SUPPORTED
SSL Verify Parameter Default (True) ⚠️ All requests use default
Hardcoded Settings None -
Environment Variables Not checked ⚠️ Could be added
Configuration File app_config.json (no SSL options) ⚠️ Could be extended

9. Integration with DigiServer v2

Current Communication Protocol

The player communicates with DigiServer v2 using:

  1. Initial Authentication (HTTP/HTTPS)

    • Endpoint: POST /api/auth/player
    • Payload: {hostname, password, quickconnect_code}
    • Response: {auth_code, player_id, player_name, ...}
  2. All Subsequent Requests (HTTP/HTTPS)

    • Header: Authorization: Bearer {auth_code}
    • Endpoints:
      • GET /api/playlists/{player_id}
      • POST /api/players/{player_id}/heartbeat
      • POST /api/player-feedback
  3. Media Downloads (HTTP/HTTPS)

    • Direct URLs from playlist: {server_url}/uploads/...

Server Configuration (config/app_config.json)

{
  "server_ip": "digi-signage.moto-adv.com",
  "port": "443",
  "screen_name": "tv-terasa",
  "quickconnect_key": "8887779",
  "orientation": "Landscape",
  "touch": "True",
  "max_resolution": "1920x1080",
  "edit_feature_enabled": true
}

⚠️ NOTE: No SSL/certificate options in config

The application accepts server_ip, port, hostname, and credentials, but:

  • No way to specify CA certificate path
  • No way to disable SSL verification
  • No way to enable certificate pinning

For Self-Signed Certificate Support:

Step 1: Add SSL Configuration Module (5-10 min)

  • Create src/ssl_config.py with SSLConfig class
  • Support for custom CA bundle path

Step 2: Modify PlayerAuth (10-15 min)

  • Add verify_ssl parameter to __init__
  • Update all 5 requests calls to include verify=self.verify_ssl
  • Improve SSL error handling/reporting

Step 3: Update Configuration (5 min)

  • Extend config/app_config.json to include optional ca_bundle_path
  • Or use environment variable REQUESTS_CA_BUNDLE

Step 4: Documentation (5 min)

  • Add README section on SSL certificate configuration
  • Document how to export and place CA certificates

Step 5: Testing (10-15 min)

  • Test with self-signed certificate
  • Verify backward compatibility with valid CA certs

Total Time Estimate: 35-50 minutes for complete implementation


11. Code References

All requests calls in codebase:

src/player_auth.py:
  Line 95:   requests.post(auth_url, json=payload, timeout=timeout)
  Line 157:  requests.post(verify_url, json=payload, timeout=timeout)
  Line 178:  requests.get(playlist_url, headers=headers, timeout=timeout)
  Line 227:  requests.post(heartbeat_url, headers=headers, json=payload, timeout=timeout)
  Line 254:  requests.post(feedback_url, headers=headers, json=payload, timeout=timeout)

src/get_playlists_v2.py:
  Line 159:  requests.get(file_url, timeout=30)

working_files/test_direct_api.py:
  Line 32:   requests.get(url, headers=headers, timeout=10)

working_files/get_playlists.py:
  Line 101:  requests.post(feedback_url, json=feedback_data, timeout=10)
  Line 131:  requests.get(server_url, params=params)
  Line 139:  requests.get(file_url, timeout=10)

All calls use default verify=True (implicit).


Conclusion

The Kiwy-Signage player is a well-structured Python application that properly uses the requests library for HTTPS communication. However, it currently does not support self-signed certificates or custom certificate authorities without code modifications.

To support self-signed certificates, implementing Option 2 (Custom CA Certificate Bundle) is recommended as it:

  • Maintains security for production deployments
  • Allows flexibility for self-signed/internal CAs
  • Requires minimal code changes (5-6 request calls)
  • Follows Python best practices
  • Is backward compatible with existing deployments