Secure LSL Manual Testing Guide
Date Created: 2025-12-12 Purpose: End-to-end validation of Secure LSL features in real hardware scenarios
Quick Start
IMPORTANT: You must build from source in each new location. Do NOT copy the build directory - the library paths (RPATH) are baked into executables at compile time.
# 1. Clone the repository
git clone https://github.com/sccn/secureLSL.git
cd secureLSL
# 2. Build secure liblsl (must build fresh, not copy!)
cd liblsl
mkdir -p build && cd build
cmake -DLSL_SECURITY=ON ..
make -j$(nproc)
# 3. Generate keys
export LSLAPICFG=~/.lsl_api/lsl_api.cfg
./lsl-keygen
# 4. Test secure communication
./cpp_secure_outlet & # Terminal 1
./cpp_secure_inlet # Terminal 2
Building from Source
macOS
# Install dependencies
brew install libsodium cmake
# Clone and build
git clone https://github.com/sccn/secureLSL.git
cd secureLSL/liblsl
mkdir -p build && cd build
cmake -DLSL_SECURITY=ON ..
make -j$(sysctl -n hw.ncpu)
# Verify build
./lslver # Should show security version info
Linux (Ubuntu/Debian)
# Install dependencies
sudo apt update
sudo apt install libsodium-dev cmake build-essential
# Clone and build
git clone https://github.com/sccn/secureLSL.git
cd secureLSL/liblsl
mkdir -p build && cd build
cmake -DLSL_SECURITY=ON ..
make -j$(nproc)
Raspberry Pi
# Install dependencies
sudo apt update
sudo apt install libsodium-dev cmake build-essential
# Clone and build (same as Linux)
git clone https://github.com/sccn/secureLSL.git
cd secureLSL/liblsl
mkdir -p build && cd build
cmake -DLSL_SECURITY=ON ..
make -j4
# Or deploy pre-built from Mac/Linux:
# From host machine:
./benchmarks/scripts/deploy_to_rpi.sh pi@<RPI_IP>
Prerequisites
Build Artifacts
After building, you should have (in liblsl/build/):
# Set your paths
export LSL_BUILD=$HOME/secureLSL/liblsl/build
# Secure liblsl library
$LSL_BUILD/liblsl-secure.dylib # macOS
$LSL_BUILD/liblsl-secure.so # Linux
# Command-line tools
$LSL_BUILD/lsl-keygen # Key generation
$LSL_BUILD/lsl-config # Configuration management
# Test executables
$LSL_BUILD/cpp_secure_outlet # Test outlet
$LSL_BUILD/cpp_secure_inlet # Test inlet
Lab Recorder (Optional)
# Clone and build Lab Recorder with secure liblsl
git clone https://github.com/labstreaminglayer/App-LabRecorder.git
cd App-LabRecorder
mkdir build && cd build
cmake -DLSL_DIR=$LSL_BUILD ..
make -j4
# Copy secure library to app bundle (macOS)
mkdir -p LabRecorder.app/Contents/Frameworks
cp $LSL_BUILD/liblsl-secure.dylib LabRecorder.app/Contents/Frameworks/liblsl.2.dylib
Devices
- Primary machine (Mac/Linux): For development and testing
- Raspberry Pi (optional): For cross-machine tests, RPi 4 recommended
Test Categories
1. Key Management Tests
1.1 Generate Keys Without Passphrase
# Create test config directory
mkdir -p /tmp/lsl_test
export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg
# Generate key without passphrase
$LSL_BUILD/lsl-keygen
# Verify config was created
cat $LSLAPICFG
Expected Result: - [x] Config file created with [security] section - [x] private_key field contains base64-encoded key - [x] public_key field contains base64-encoded key - [x] No encrypted_private_key field
1.2 Generate Keys With Passphrase
mkdir -p /tmp/lsl_test_pass
export LSLAPICFG=/tmp/lsl_test_pass/lsl_api.cfg
# Generate key with passphrase
$LSL_BUILD/lsl-keygen --passphrase
# When prompted, enter: testpassword123
Expected Result: - [x] Config file created with [security] section - [x] encrypted_private_key field present - [x] NO plaintext private_key field - [x] public_key field present
1.3 Device-Bound Session Token
export LSLAPICFG=/tmp/lsl_test_pass/lsl_api.cfg
# Show device ID
$LSL_BUILD/lsl-config --show-device-id
# Create session token (remember this device) - prompts for passphrase securely
$LSL_BUILD/lsl-config --remember-device --passphrase
# Or with custom expiry (90 days, or -1 for never)
$LSL_BUILD/lsl-config --remember-device --passphrase --days 90
# Check session token was created
ls -la ~/.lsl_api/
# Verify auto-unlock works (no passphrase needed now)
$LSL_BUILD/lsl-config --show-public
# Later: remove session token (requires passphrase again on next startup)
$LSL_BUILD/lsl-config --forget-device
Expected Result: - [x] Device ID displayed as 64-character hex string - [x] Passphrase prompted securely (no echo) - [x] Session token created successfully - [x] Session token file created in ~/.lsl_api/ - [x] Public key displays WITHOUT passphrase after token creation - [x] --forget-device removes the token
1.4 Export/Import Keys
mkdir -p /tmp/lsl_export_test
# Export keys
export LSLAPICFG=/tmp/lsl_test_pass/lsl_api.cfg
$LSL_BUILD/lsl-keygen --export /tmp/lsl_export_test/device1
# Verify exported files
ls -la /tmp/lsl_export_test/
cat /tmp/lsl_export_test/device1.pub
# Import on "new device" (simulated)
mkdir -p /tmp/lsl_import_test
export LSLAPICFG=/tmp/lsl_import_test/lsl_api.cfg
$LSL_BUILD/lsl-keygen --import /tmp/lsl_export_test/device1.key.enc
# Enter passphrase when prompted
Expected Result: - [x] device1.pub file created (public key) - [x] device1.key.enc file created (encrypted private key) - [ ] Import succeeds with correct passphrase - [ ] Import fails with wrong passphrase
2. Secure Stream Communication Tests
2.1 Secure Outlet to Secure Inlet (Same Machine)
# Terminal 1: Start secure outlet
export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg
$LSL_BUILD/cpp_secure_outlet
# Terminal 2: Start secure inlet
export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg
$LSL_BUILD/cpp_secure_inlet
Expected Result: - [ ] Outlet shows "Security enabled" message - [ ] Inlet discovers secure stream - [ ] Inlet receives data correctly - [ ] No encryption errors
2.2 Insecure Inlet Cannot Read Secure Stream
Setup:
# Terminal 1: Start secure outlet
export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg
$LSL_BUILD/cpp_secure_outlet
# Terminal 2: Start insecure inlet (using standard liblsl)
export PYLSL_LIB=$LSL_BUILD/liblsl.dylib
python3 -c "
import pylsl
streams = pylsl.resolve_streams(wait_time=2.0)
for s in streams:
print(f'Found: {s.name()} - security={s.desc().child(\"security\").child_value(\"enabled\")}')
if 'secure' in s.name().lower():
try:
inlet = pylsl.StreamInlet(s)
sample, ts = inlet.pull_sample(timeout=2.0)
print(f'Sample received (should NOT happen): {sample}')
except Exception as e:
print(f'Connection rejected as expected: {e}')
"
Expected Result: - [ ] Stream is discovered (visible in resolver) - [ ] Connection attempt fails or data is unreadable - [ ] Error message indicates security mismatch
2.3 Mixed Security Environment Detection
Setup: Run one secure outlet and one insecure outlet simultaneously.
# Terminal 1: Secure outlet
export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg
$LSL_BUILD/cpp_secure_outlet &
# Terminal 2: Insecure outlet (Python with standard liblsl)
export PYLSL_LIB=$LSL_BUILD/liblsl.dylib
python3 -c "
import pylsl
import time
info = pylsl.StreamInfo('InsecureTest', 'Test', 1, 100, pylsl.cf_float32, 'insecure123')
outlet = pylsl.StreamOutlet(info)
print('Insecure outlet running...')
for i in range(60):
outlet.push_sample([float(i)])
time.sleep(0.1)
"
# Terminal 3: Resolve and list all streams
export PYLSL_LIB=$LSL_BUILD/liblsl-secure.dylib
python3 -c "
import pylsl
streams = pylsl.resolve_streams(wait_time=3.0)
print(f'Found {len(streams)} streams:')
for s in streams:
sec = s.desc().child('security')
enabled = sec.child_value('enabled') if sec else 'N/A'
print(f' - {s.name()}: security={enabled}')
"
Expected Result: - [ ] Both streams are discovered - [ ] Security status correctly reported for each stream - [ ] Secure inlet refuses to connect to insecure stream (unanimous security rule)
3. Lab Recorder Tests
3.1 Launch Lab Recorder with Secure Library
# Copy secure library to Lab Recorder
cp $LSL_BUILD/liblsl-secure.dylib \
$LABRECORDER_BUILD/LabRecorder.app/Contents/Frameworks/liblsl.2.dylib
# Or use DYLD_LIBRARY_PATH
export DYLD_LIBRARY_PATH=/Users/yahya/Documents/git/secureLSL/liblsl/build:$DYLD_LIBRARY_PATH
# Set config
export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg
# Launch Lab Recorder
open $LABRECORDER_BUILD/LabRecorder.app
# OR
$LABRECORDER_BUILD/LabRecorder.app/Contents/MacOS/LabRecorder
3.2 Lab Recorder Discovers Secure Stream
Setup: 1. Start secure outlet in terminal 2. Launch Lab Recorder with secure library
Screenshot Checkpoints: - [x] Lab Recorder main window showing stream list - [x] Secure stream visible with security indicator (lock icon 🔒) - [x] Stream details showing security status
3.3 Lab Recorder Records Secure Stream
Test: 1. Start secure outlet 2. Connect Lab Recorder to secure stream 3. Start recording for 10 seconds 4. Stop recording 5. Verify XDF file was created
Expected Result: - [x] Recording completes without errors - [x] XDF file created with correct size (28kb in 12 seconds) - [ ] File can be opened with EEGLAB or MNE-Python (not tested)
3.4 Lab Recorder: Both Insecure
Test: 1. Start insecure outlet (no security config) 2. Run Lab Recorder without security config 3. Record data
Expected Result: - [x] Recording works without security enabled - [x] No security errors or warnings
3.5 Lab Recorder Rejects Secure Stream When Insecure
Test: 1. Start secure outlet (with security config) 2. Run Lab Recorder WITHOUT security config 3. Attempt to record
Expected Result: - [x] Stream IS discovered and shown in UI - [x] Recording button works (UI does not block) - [x] Connection REJECTED by outlet: "403 Security required but client has no security enabled" - [x] Recording file created but 0kb (empty) - no data transferred - [x] Inlet retries connection but keeps getting rejected
Note: This test now shows the behavior AFTER passing test 3.6 (security mismatch detection). If mismatch detection is bypassed (e.g., via hideWarnings), the connection is rejected at the protocol level with 403 errors.
3.6 Security Mismatch Detection (Pre-Recording Check)
Status: IMPLEMENTED (2025-12-17)
Feature: Lab Recorder now detects security mismatches BEFORE attempting to record and displays a clear error dialog.
Test: 1. Start a secure outlet (with security credentials) 2. Run Lab Recorder WITHOUT security credentials (LSLAPICFG pointing to insecure config) 3. Click Update to find the secure stream 4. Select the stream and click Start
Expected Result: - [x] Error dialog appears immediately (before recording starts) - [x] Dialog shows stream names in blue with bullet points - [x] Dialog explains the issue and how to fix it - [x] Red warning text: "Recording cannot proceed with mismatched security settings." - [x] Recording does NOT start (no 0kb files created)
Error Messages: - Secure stream + Insecure recorder: "The following streams require security, but Lab Recorder does not have security credentials configured" - Insecure stream + Secure recorder: "Security mismatch detected for the following streams"
Implementation Notes:
- Uses new lsl::local_security_enabled() API to check local security status
- Compares with stream.security_enabled() for each selected stream
- Check happens in startRecording() before creating the recording object
4. Cross-Machine Tests (Raspberry Pi <-> Mac Mini)
4.1 Network Setup
Mac Mini (Inlet): - IP: (note your IP, e.g., 192.168.1.100) - Ensure firewall allows LSL ports (16571-16600)
Raspberry Pi (Outlet): - IP: (note your IP, e.g., 192.168.1.101) - Deploy secure liblsl:
4.2 Shared Keypair Authorization Model
IMPORTANT: Secure LSL uses a shared keypair model. All authorized devices must have the SAME keypair (public + private key). Devices with different keys will be rejected as "not authorized".
Setup: Distribute Same Key to All Devices
# 1. Generate key on Mac Mini with passphrase
mkdir -p ~/.lsl_api
$LSL_BUILD/lsl-keygen --passphrase --force
# 2. Export for distribution
$LSL_BUILD/lsl-keygen --export /tmp/lab_shared
# 3. Copy to Raspberry Pi
scp /tmp/lab_shared.key.enc pi@192.168.1.101:/tmp/
scp /tmp/lab_shared.pub pi@192.168.1.101:/tmp/
# 4. On Raspberry Pi, import the shared key
ssh pi@192.168.1.101
mkdir -p ~/.lsl_api
./lsl-keygen --import /tmp/lab_shared.key.enc -o ~/.lsl_api/lsl_api.cfg
# Enter the same passphrase used during generation
# 5. Verify both devices have same public key fingerprint
# Mac Mini:
$LSL_BUILD/lsl-config --show-public
# Raspberry Pi:
./lsl-config --show-public
# Fingerprints MUST match!
4.3 Test Case: Matching Keys (Should Work)
IMPORTANT: This test reflects real-world usage where outlets run continuously and inlets join mid-stream. Validation "errors" due to missing initial samples are EXPECTED behavior, not failures.
Raspberry Pi (Outlet) - Start first, runs continuously:
ssh pi@192.168.1.101
cd ~/securelsl_benchmark/liblsl/build
# Start outlet with many samples (runs for a while)
LSLAPICFG=~/.lsl_api/lsl_api.cfg ./cpp_secure_outlet --name SecureEEG --samples 100000 &
Mac Mini (Inlet) - Joins mid-stream:
# Wait a few seconds, then connect
LSLAPICFG=~/.lsl_api/lsl_api.cfg $LSL_BUILD/cpp_secure_inlet --stream SecureEEG --samples 50 --validate
Expected Result: - [x] Stream discovered across network - [x] Log shows: "Secure session established with client (fingerprint: SHA256:xx:xx...)" - [x] Encrypted data transmitted and decrypted correctly - [x] Sequential sample values prove correct decryption (e.g., 25388, 25389, 25390...) - [x] Validation "errors" about sample offset are NORMAL (inlet joined mid-stream) - [x] No "public key mismatch" errors
4.4 Test Case: Different Keys (Should Be Rejected)
Setup: Outlet running with shared key, inlet uses DIFFERENT key
Raspberry Pi (Outlet with Key A):
ssh pi@192.168.1.101
cd ~/securelsl_benchmark/liblsl/build
# Outlet using shared lab key (SHA256:79:8c:1d:7d)
LSLAPICFG=~/.lsl_api/lsl_api.cfg ./cpp_secure_outlet --name DifferentKeyTest --samples 100000 &
Mac Mini (Inlet with Key B - Different):
# Create config with DIFFERENT key
mkdir -p /tmp/lsl_different_key
cat > /tmp/lsl_different_key/lsl_api.cfg << 'EOF'
[security]
enabled = true
private_key = <different_base64_key>
EOF
# Attempt connection with different key
LSLAPICFG=/tmp/lsl_different_key/lsl_api.cfg $LSL_BUILD/cpp_secure_inlet --stream DifferentKeyTest --timeout 15
Expected Result: - [x] Stream IS discovered (visible in resolver) - [x] Connection attempt FAILS with "403 Public key mismatch - not authorized" - [x] Outlet logs: "Connection refused: client has different public key (fingerprint: SHA256:xx:xx...)" - [x] Inlet logs: "Stream transmission broke off (The other party sent an error: LSL/110 403 Public key mismatch)" - [x] No data is transferred - inlet times out - [x] Inlet retries connection but keeps getting rejected
4.5 Test Case: One Device Insecure (Should Be Rejected)
Setup: Pi has security enabled, Mac does NOT
Raspberry Pi (Secure Outlet):
Mac Mini (No Security):
# Use config without security or delete security section
mkdir -p /tmp/lsl_nosec
echo "[lab]" > /tmp/lsl_nosec/lsl_api.cfg
export LSLAPICFG=/tmp/lsl_nosec/lsl_api.cfg
$LSL_BUILD/cpp_secure_inlet --stream SecureOnlyTest
Expected Result: - [ ] Stream IS discovered - [ ] Connection FAILS with "403 Security required but client has no security enabled" - [ ] Unanimous security enforcement works
4.6 Test Case: Passphrase-Protected Key with Device Token
Setup: Key encrypted with passphrase, unlocked via device-bound token
Mac Mini:
# Generate passphrase-protected key
$LSL_BUILD/lsl-keygen --passphrase --force
# Enter: testpass123
# Create device-bound session token (no passphrase needed after this)
$LSL_BUILD/lsl-config --remember-device --passphrase
# Enter: testpass123
# Verify auto-unlock works (should NOT ask for passphrase)
$LSL_BUILD/lsl-config --show-public
# Export and distribute to Pi
$LSL_BUILD/lsl-keygen --export /tmp/lab_key
scp /tmp/lab_key.key.enc pi@192.168.1.101:/tmp/
Raspberry Pi:
ssh pi@192.168.1.101
./lsl-keygen --import /tmp/lab_key.key.enc
# Enter: testpass123
# Create device token on Pi too
./lsl-config --remember-device --passphrase
# Enter: testpass123
# Now test communication (no passphrase prompts)
./cpp_secure_outlet --name TokenTest --samples 500 &
Mac Mini:
Expected Result: - [ ] No passphrase prompts after token creation - [ ] Communication works between devices - [ ] Session tokens are device-specific (bound to hardware ID)
4.4 Cross-Machine Lab Recorder
Raspberry Pi (Outlet):
Mac Mini (Lab Recorder):
Screenshot Checkpoints: - [x] Lab Recorder showing remote secure stream - [x] Recording from remote secure stream
5. Error Handling Tests
5.1 Wrong Passphrase
export LSLAPICFG=/tmp/lsl_test_pass/lsl_api.cfg
export LSL_KEY_PASSPHRASE=wrongpassword
$LSL_BUILD/cpp_secure_outlet
Expected Result: - [ ] Clear error message about decryption failure - [ ] Outlet does not start - [ ] No crash or undefined behavior
5.2 Missing Config File
Expected Result: - [ ] Clear error message about missing config - [ ] Falls back to insecure mode or refuses to start
5.3 Corrupted Key File
# Create corrupted config
mkdir -p /tmp/lsl_corrupt
cat > /tmp/lsl_corrupt/lsl_api.cfg << EOF
[security]
enabled = true
private_key = not_valid_base64!!!
public_key = also_not_valid!!!
EOF
export LSLAPICFG=/tmp/lsl_corrupt/lsl_api.cfg
$LSL_BUILD/cpp_secure_outlet
Expected Result: - [ ] Clear error message about invalid key format - [ ] No crash or undefined behavior
6. Performance Validation
6.1 Encryption Overhead Measurement
# Run benchmark suite (if available)
cd $SECURELSL_ROOT/benchmarks/scripts
python3 run_benchmark_suite.py --local-only
Expected Result: - [ ] Overhead < 5% for typical EEG data rates - [ ] No dropped samples at 1000 Hz
Screenshot Archive
Screenshots are stored in: .context/screenshots/
Naming convention:
- labrecorder_stream_list_YYYYMMDD.png
- labrecorder_secure_warning_YYYYMMDD.png
- labrecorder_recording_YYYYMMDD.png
- etc.
Test Log
| Date | Test | Machine(s) | Result | Notes |
|---|---|---|---|---|
| 2025-12-12 | 1.1 Key Gen | Mac Mini | PASS | Keys generated successfully, config written |
| 2025-12-12 | 3.1 Lab Recorder Launch | Mac Mini | PASS | Security enabled, credentials loaded |
| 2025-12-12 | Lock icon display | Mac Mini | PASS | Lock emoji shows at end of stream name |
| 2025-12-16 | 1.1-1.4 Key Management | Mac Mini | PASS | All key operations verified |
| 2025-12-16 | 2.1 Insecure outlet + Secure inlet | Mac Mini | PASS | Rejected with 403 |
| 2025-12-16 | 2.2 Secure outlet + Insecure inlet | Mac Mini | PASS | Rejected with 403 |
| 2025-12-16 | 2.3 Both Insecure | Mac Mini | PASS | 50 samples received |
| 2025-12-16 | 4.3 Cross-machine Same Keys | Mac + RPi (WiFi) | PASS | Secure session established, encrypted transfer works |
| 2025-12-16 | 4.4 Cross-machine Different Keys | Mac + RPi (WiFi) | PASS | Rejected with 403, no data transferred |
| 2025-12-16 | C++ Unit Tests | Mac Mini | PASS | 27 cases, 3059 assertions |
| 2025-12-16 | C++ Exported Tests | Mac Mini | PASS | 13 cases, 1036 assertions |
| 2025-12-16 | Lab Recorder: Secure lock icon | Mac + RPi (WiFi) | PASS | Lock icon displays for secure streams |
| 2025-12-16 | Lab Recorder: Secure+Secure | Mac + RPi (WiFi) | PASS | 28kb recorded in 12 seconds |
| 2025-12-16 | Lab Recorder: Insecure+Insecure | Mac | PASS | Recording works without security |
| 2025-12-16 | Lab Recorder: Secure outlet + Insecure recorder | Mac + RPi (WiFi) | PASS | Rejected with 403, 0kb file |
| 2025-12-17 | Lab Recorder: Security mismatch detection UI | Mac | PASS | Error dialog with actionable message |
| 2025-12-17 | SigVisualizer: Security mismatch detection | Mac | PASS | Error dialog shows stream names with lock icon |
| 2025-12-17 | SigVisualizer: Secure+Secure visualization | Mac | PASS | Encrypted data visualized correctly |
| 2025-12-17 | Cross-machine Lab Recorder | Mac + RPi (WiFi) | PASS | 32kb recorded in 14 seconds, lock icon visible |
Test Session Notes (2025-12-16)
Lab Recorder Integration Testing
Status: PASSED (all scenarios verified)
Setup:
- Lab Recorder built with secure liblsl (symlink liblsl.2.dylib -> liblsl-secure.1.16.1.dylib)
- RPATH in Lab Recorder points to secureLSL build directory
- Secure outlet running on Raspberry Pi (192.168.0.180, WiFi)
Test Results:
- Secure Stream Discovery with Lock Icon:
- Secure stream "SecureEEG" discovered by Lab Recorder
- Lock icon (🔒) displayed at end of stream name
-
Security status visible in stream list
-
Secure+Secure Recording:
- Pi outlet: Secure (key SHA256:79:8c:1d:7d)
- Lab Recorder: Secure (same key)
- Result: Recording successful, 28kb file created in 12 seconds
-
Log: "Secure session established", "Using encrypted data transfer"
-
Insecure+Insecure Recording:
- Pi outlet: Insecure (no security config)
- Lab Recorder: Insecure (no security config)
-
Result: Recording successful, data transferred without encryption
-
Secure Outlet + Insecure Recorder (Rejection):
- Pi outlet: Secure (key SHA256:79:8c:1d:7d)
- Lab Recorder: Insecure (no security config)
- Result: Connection REJECTED repeatedly
- Log: "403 Security required but client has no security enabled"
- Recording file: 0kb (empty) - no data transferred
- UI shows recording in progress but file stays at 0kb
Future Enhancement Noted: - Lab Recorder should display security error messages in UI - Currently errors are only visible in terminal logs - User sees recording "working" but file stays at 0kb
Cross-Machine WiFi Testing (Mac + Raspberry Pi)
Status: PASSED
Network Configuration: - Mac: 192.168.0.108 (WiFi en1) - Raspberry Pi: 192.168.0.180 (WiFi wlan0) - Latency: 5-8ms (ping)
Shared Key:
- Fingerprint: SHA256:79:8c:1d:7d:a6:b4:34:22...
- Same key deployed to both devices via lsl-keygen --export/--import
Test Results:
- Same Keys - Acceptance Test:
- Pi outlet:
./cpp_secure_outlet --name SecureSignal --samples 100000 - Mac inlet:
./cpp_secure_inlet --stream SecureSignal --samples 20 --validate - Result: Stream discovered, secure session established, 20 samples received
- Outlet log: "Secure session established with client (fingerprint: SHA256:79:8c:1d:7d...)"
-
Inlet received sequential data (25388, 25389, 25390...) proving correct decryption
-
Different Keys - Rejection Test:
- Pi outlet: Same key (SHA256:79:8c:1d:7d)
- Mac inlet: Different key (SHA256:23:83:f5:f0)
- Result: Stream discovered but connection REJECTED
- Outlet log: "Connection refused: client has different public key"
- Inlet log: "403 Public key mismatch - not authorized"
- Inlet retried 9 times, all rejected, timed out with no data
Key Learnings: - Inlets joining mid-stream is NORMAL behavior (outlets run continuously) - Validation errors from sample offset are expected, not failures - Sequential sample values prove correct decryption - Both outlet AND inlet verify peer's public key (bidirectional authorization)
Test Session Notes (2025-12-12)
Lab Recorder Security Integration
Status: Working
Key Findings:
1. Lab Recorder correctly loads security credentials from LSLAPICFG
2. Lock emoji (UTF-8: \xF0\x9F\x94\x92) displays at end of secure stream names
3. About dialog shows security version info when using secure build
Library Linking Notes:
- Lab Recorder uses @rpath/liblsl.2.dylib for library resolution
- When testing with secure library, either:
- Copy liblsl-secure.dylib to LabRecorder.app/Contents/Frameworks/liblsl.2.dylib
- Or modify symlinks in build dir to point liblsl.2.dylib -> liblsl-secure.1.16.1.dylib
Code Changes Made:
- mainwindow.cpp line 296: Changed lock icon from prefix to suffix
- Before: prefix + name (host)
- After: name (host) + suffix
Environment Setup
Before running tests, set these environment variables:
# Path to secure LSL build directory
export LSL_BUILD=$HOME/secureLSL/liblsl/build
# Path to secureLSL repository root
export SECURELSL_ROOT=$HOME/secureLSL
# Path to Lab Recorder build (if using Lab Recorder)
export LABRECORDER_BUILD=$HOME/App-LabRecorder/build
# Path to security configuration
export LSLAPICFG=~/.lsl_api/lsl_api.cfg
# Python pylsl (if using Python)
export PYLSL_LIB=$LSL_BUILD/liblsl-secure.dylib # macOS
# export PYLSL_LIB=$LSL_BUILD/liblsl-secure.so # Linux
Known Issues / Workarounds
-
DYLD_LIBRARY_PATH on macOS: System Integrity Protection may prevent library path override for GUI apps. Use
install_name_toolor copy library to app bundle instead. -
Lab Recorder library path: May need to copy secure library to Frameworks folder:
-
Firewall: For cross-machine tests, ensure LSL ports are open:
- UDP/TCP 16571-16600
- macOS: System Preferences → Security & Privacy → Firewall → Allow incoming connections
-
Linux:
sudo ufw allow 16571:16600/tcp && sudo ufw allow 16571:16600/udp -
Raspberry Pi Performance: For best results on RPi:
- Use Ethernet (not WiFi) for lower latency
- Use RPi 4 with 4GB+ RAM
- Disable unnecessary services to reduce jitter
Future Decisions
Library Naming: liblsl-secure vs liblsl 2.x
Current: liblsl-secure.dylib (separate from liblsl.dylib)
Proposed: Rename to liblsl 2.x with ABI version 3
- Rationale: "In 5 years nobody remembers version 1" - security becomes the default
- liblsl 1.x = legacy insecure, liblsl 2.x = secure standard
- No -secure suffix needed if security is the norm
Status: Pending community discussion
SigVisualizer Integration
Repository: https://github.com/labstreaminglayer/App-SigVisualizer
Status: COMPLETE (2025-12-17)
6.1 Security Mismatch Detection
Implementation:
- Added local_security_enabled() to lsl_security_helper.py
- Modified paintwidget.py to check security status before creating inlets
- Error dialog shows stream names (with lock icon) and actionable fix instructions
- Users can retry with Update button after fixing credentials
Test Results:
| Scenario | Result | Notes |
|---|---|---|
| Secure outlet + Insecure SigVisualizer | PASS | Error dialog shown, stream name visible with lock icon |
| Secure outlet + Secure SigVisualizer | PASS | Data visualized correctly with encryption |
| Lock icon display | PASS | Lock emoji (🔒) shown for secure streams |
Commits:
- 216f6f6 - Add security mismatch detection with informative error dialogs
SigViewer Integration (Future Work)
SigViewer (cbrnr/sigviewer) is a different visualization tool that may need secure LSL support.
Repository: https://github.com/cbrnr/sigviewer
Status: Pending (lower priority - SigVisualizer is the primary LSL visualizer)