Skip to content

Architecture Overview

Technical details of Secure LSL's design and implementation.


System Architecture

Secure LSL integrates security at the core library level, making it transparent to all applications:

flowchart TB
    subgraph Apps["Your Applications (unchanged)"]
        A1[Python Script]
        A2[MATLAB Code]
        A3[C++ Application]
        A4[LabRecorder]
    end

    subgraph LibLSL["liblsl with Security Layer"]
        direction TB
        S1[Security Module]
        S2[Encryption/Decryption]
        S3[Key Management]
        S4[Session Handling]

        subgraph Original["Original LSL"]
            L1[Stream Discovery]
            L2[Data Serialization]
            L3[Network Transport]
            L4[Time Synchronization]
        end

        S1 --> S2
        S1 --> S3
        S1 --> S4
        S2 --> L3
    end

    subgraph Crypto["libsodium"]
        C1[Ed25519]
        C2[X25519]
        C3[ChaCha20-Poly1305]
        C4[HKDF]
    end

    A1 --> LibLSL
    A2 --> LibLSL
    A3 --> LibLSL
    A4 --> LibLSL

    S1 --> Crypto

    style Apps fill:#E8F5E9
    style S1 fill:#BBDEFB
    style S2 fill:#BBDEFB
    style S3 fill:#BBDEFB
    style S4 fill:#BBDEFB

Security Integration Points

Security is implemented at specific points in the LSL protocol:

1. Stream Discovery (UDP)

sequenceDiagram
    participant Outlet as Outlet
    participant Network as Network (Multicast)
    participant Inlet as Inlet

    Note over Outlet,Inlet: Discovery Request
    Inlet->>Network: "Looking for EEG streams"
    Network->>Outlet: Query received

    Note over Outlet,Inlet: Discovery Response (Extended)
    Outlet->>Network: Stream info + Security metadata
    Note right of Outlet: Includes:<br/>- security_enabled: true<br/>- public_key: [32 bytes]<br/>- fingerprint: SHA256:...

    Network->>Inlet: Response received
    Inlet->>Inlet: Validate security metadata

The discovery response XML now includes a <security> node:

<info>
  <name>MyEEG</name>
  <type>EEG</type>
  <channel_count>64</channel_count>
  ...
  <security>
    <enabled>true</enabled>
    <public_key>base64_encoded_key</public_key>
    <fingerprint>SHA256:70:14:e1:b5:...</fingerprint>
  </security>
</info>

2. Connection Establishment (TCP)

sequenceDiagram
    participant Inlet as Inlet
    participant Outlet as Outlet

    Note over Inlet,Outlet: TCP Connection
    Inlet->>Outlet: Connect

    Note over Inlet,Outlet: Security Negotiation
    Inlet->>Outlet: LSL/1.16 GET /stream<br/>Security-Enabled: true<br/>Security-Public-Key: [base64]

    alt Security Match (both enabled)
        Outlet->>Inlet: LSL/1.16 200 OK<br/>Security-Enabled: true<br/>Security-Public-Key: [base64]

        Note over Inlet,Outlet: Key Exchange (X25519)
        Inlet->>Inlet: Derive session key
        Outlet->>Outlet: Derive session key

        Note over Inlet,Outlet: Encrypted Data Streaming
        Outlet->>Inlet: [Encrypted samples]

    else Security Mismatch
        Outlet->>Inlet: LSL/1.16 403 Security required<br/>but client has no security enabled
        Note over Inlet: Connection rejected
    end

3. Data Transmission

Each data chunk is encrypted independently:

flowchart LR
    subgraph Original["Original Data Chunk"]
        direction LR
        M1[Metadata<br/>timestamps, etc.] --> P1[Payload<br/>sample data]
    end

    subgraph Encrypted["Encrypted Chunk"]
        direction LR
        M2[Metadata<br/>plaintext] --> N[Nonce<br/>8 bytes] --> E[Encrypted Payload] --> T[Auth Tag<br/>16 bytes]
    end

    Original -->|"ChaCha20-Poly1305"| Encrypted

Wire format:

Field Size Description
Length 4 bytes Total chunk length
Nonce 8 bytes Unique per packet, monotonic
Ciphertext variable Encrypted sample data
Auth Tag 16 bytes Poly1305 authentication

Key Management

Key Hierarchy

flowchart TD
    subgraph Device["Per-Device (Permanent)"]
        PK[Ed25519 Private Key<br/>Stored in lsl_api.cfg]
        PUB[Ed25519 Public Key<br/>Derived from private]
    end

    subgraph Connection["Per-Connection (Ephemeral)"]
        SS[Shared Secret<br/>X25519 key agreement]
        SK[Session Key<br/>HKDF-derived]
    end

    subgraph Packet["Per-Packet"]
        N[Nonce<br/>Monotonic counter]
    end

    PK --> PUB
    PK --> SS
    SS --> SK
    SK --> N

    style PK fill:#FFE4B5
    style SS fill:#90EE90
    style SK fill:#90EE90
    style N fill:#ADD8E6

Session Key Derivation

flowchart LR
    subgraph Inlet
        IA[Inlet Private Key]
        IB[Inlet Public Key]
    end

    subgraph Outlet
        OA[Outlet Private Key]
        OB[Outlet Public Key]
    end

    subgraph KeyExchange["X25519 Key Agreement"]
        SS[Shared Secret<br/>Both sides compute same value]
    end

    subgraph Derivation["HKDF"]
        SK[Session Key<br/>32 bytes]
    end

    IA --> SS
    OB --> SS
    OA --> SS
    IB --> SS
    SS --> SK

Both sides independently compute the same shared secret: - Inlet computes: X25519(inlet_private, outlet_public) - Outlet computes: X25519(outlet_private, inlet_public)

Mathematical properties of X25519 ensure these produce identical results.


Session Key Rotation

Keys are rotated every 24 hours using a make-before-break pattern:

sequenceDiagram
    participant Outlet
    participant Inlet

    Note over Outlet,Inlet: Normal Operation with Key A
    loop Every packet
        Outlet->>Inlet: Encrypt with Key A
    end

    Note over Outlet,Inlet: Rotation Trigger (24 hours)
    Outlet->>Outlet: Generate new key exchange material
    Outlet->>Inlet: Key rotation notification

    Note over Outlet,Inlet: Grace Period (60 seconds)
    Outlet->>Inlet: Encrypt with Key B
    Note right of Inlet: Accept Key A or Key B

    Note over Outlet,Inlet: Rotation Complete
    Outlet->>Outlet: Discard Key A
    Inlet->>Inlet: Discard Key A
    loop Every packet
        Outlet->>Inlet: Encrypt with Key B only
    end

Modified Source Files

Security is implemented by modifying only a few core liblsl files:

File Changes
src/lsl_security.cpp New: All cryptographic operations
src/api_config.cpp Extended: Parse [security] config section
src/stream_info_impl.cpp Extended: Security metadata in stream info
src/tcp_server.cpp Extended: Security handshake, encryption
src/data_receiver.cpp Extended: Security handshake, decryption
include/lsl/streaminfo.h Extended: Security status API
include/lsl_cpp.h Extended: C++ security methods

Total: ~800 lines of security code added to liblsl.


Configuration

Security settings are stored in LSL's standard configuration file:

[security]
enabled = true
private_key = base64_encoded_ed25519_private_key
key_created = 2025-12-05T19:00:00Z
session_key_lifetime = 86400  ; 24 hours in seconds

[log]
level = 6  ; Enable security event logging

Configuration file search order:

  1. $LSLAPICFG environment variable (if set)
  2. ./lsl_api.cfg (current directory)
  3. ~/.lsl_api/lsl_api.cfg (user home)
  4. /etc/lsl_api/lsl_api.cfg (system-wide)

Dependencies

libsodium

Secure LSL uses libsodium for all cryptographic operations:

  • Version: 1.0.18 or later
  • Algorithms used:
    • crypto_sign_ed25519 - Device identity signatures
    • crypto_scalarmult_curve25519 - Key agreement
    • crypto_aead_chacha20poly1305_ietf - Authenticated encryption
    • crypto_kdf_hkdf_sha256 - Key derivation
    • sodium_memzero - Secure memory clearing

libsodium is: - Extensively audited - Constant-time implementations (side-channel resistant) - Available on all major platforms - Used by Signal, Wireguard, and many security-critical projects


Next Steps