Security / API Key Distribution

API Key Distribution

Technical documentation describing the security architecture for distributing third-party API keys (Google Vertex AI, OpenAI) from inCamera's server infrastructure to client applications.

Version 2.1
Last Updated December 2025
Classification Technical Documentation

Executive Summary

This document describes the security architecture for distributing third-party API keys from inCamera's server infrastructure to client applications. The architecture addresses two competing security requirements: protecting our origin infrastructure from direct internet exposure, and guaranteeing the authenticity and confidentiality of sensitive credentials delivered to clients.

Our solution employs end-to-end encryption with cryptographic signature verification at the application layer, treating the network transport, including our Cloudflare Tunnel, as an untrusted channel. This approach provides defense against man-in-the-middle attacks, including theoretical attacks by infrastructure providers, while maintaining the operational security benefits of origin hiding.

Design Philosophy

The design philosophy is borrowed from secure messaging protocols: assume the network is hostile, and build security guarantees that hold regardless of transport layer properties.

Threat Model

Infrastructure Context

inCamera's server infrastructure operates behind Cloudflare Tunnel, which provides several operational benefits:

  • No public IP address exposure for origin servers
  • DDoS mitigation at Cloudflare's edge
  • No inbound firewall ports required
  • Simplified certificate management for general traffic

However, this architecture means Cloudflare terminates TLS connections from clients. Traffic between clients and our origin passes through Cloudflare infrastructure in plaintext (from Cloudflare's perspective) before being re-encrypted for the tunnel segment.

Network Architecture
+----------+           +---------------+           +-------------------+
|  Client  |<---TLS--->|  Cloudflare   |<--Tunnel->|  inCamera Origin  |
+----------+           |     Edge      |           +-------------------+
                       +---------------+
                              |
                  Cloudflare can inspect
                  plaintext at this point
Why TLS Pinning Doesn't Work Here

Traditional TLS certificate pinning is ineffective in this architecture because clients never see our origin certificate; they see Cloudflare's edge certificate.

Threat Categories

Network-Level Adversaries

Attackers who control network infrastructure between the client and Cloudflare's edge. This includes ISPs, corporate network administrators, compromised routers, and nation-state surveillance programs.

Mitigation: TLS encryption to Cloudflare's edge prevents passive surveillance and active tampering on this segment.

Infrastructure Provider Access

Cloudflare, as our infrastructure provider, has technical capability to inspect traffic passing through their systems. While Cloudflare is a reputable provider with strong security practices, our threat model does not require trusting any third party with access to plaintext credentials.

Mitigation: Application-layer encryption ensures Cloudflare sees only ciphertext. Application-layer signatures ensure Cloudflare cannot forge or substitute payloads.

Key Substitution Attacks

An attacker (at any layer) intercepts credential delivery and substitutes malicious API keys pointing to logging proxies that mimic legitimate AI provider APIs. The client unknowingly sends privileged legal communications to attacker-controlled infrastructure.

Mitigation: Cryptographic signatures over payloads, verified against public keys embedded in the application binary at compile time.

Replay Attacks

An attacker captures a valid credential delivery and replays it later, potentially after legitimate keys have been rotated or to enable correlation attacks.

Mitigation: Client-generated nonces, server-generated nonces, and short validity windows make replayed payloads invalid.

Passive Credential Harvesting

An attacker with access to any point in the network path captures credentials for later use or sale.

Mitigation: Per-client ephemeral encryption ensures that even an attacker who captures traffic cannot decrypt credentials without the client's ephemeral private key, which exists only in memory and is never transmitted.

Limitations

Compromised Client Devices: If an attacker has root access to the user's device, they can extract keys from memory, modify the application binary, or intercept decrypted communications. No software-only solution prevents this.

Compromised Build Pipeline: If an attacker compromises our build infrastructure, they could embed malicious public keys in the application binary. We mitigate this through code signing, secure build practices, and reproducible builds where feasible.

Compromise of AI Providers: If Google or OpenAI infrastructure is compromised, or if these providers are compelled to log data despite ZDR agreements, protections at our layer cannot prevent exposure.

Security Properties Achieved

Property Guarantee
Confidentiality Only the intended client can decrypt credentials
Authenticity Clients can verify credentials originated from inCamera
Integrity Any modification to payloads is detected
Forward Secrecy Ephemeral keys on both sides ensure no long-term encryption key exists to compromise; past sessions remain secure even under full server compromise
Replay Resistance Captured payloads cannot be reused

Cryptographic Architecture

Key Inventory

Long-Term Server Keys (inCamera Origin)

Key Algorithm Purpose Storage
Server Signing Key Ed25519 Signs all sensitive payloads HSM / Secure enclave

Embedded Client Keys (Compiled into Application)

Key Algorithm Purpose Storage
Server Signing Public Key Ed25519 Verifies payload signatures Application binary

Ephemeral Keys (Generated at Runtime)

Key Algorithm Purpose Storage
Client Ephemeral Key X25519 Per-request key agreement Memory only, never persisted
Server Ephemeral Key X25519 Per-request key agreement Memory only, never persisted
Ephemeral Keys on Both Sides

The server generates fresh X25519 keypairs for each request rather than using a static identity key. This provides stronger forward secrecy: even if a server's ephemeral private key from one request were somehow compromised, only that single response would be affected. The only long-term key that needs rotation is the Ed25519 signing key.

Algorithm Selection Rationale

Ed25519 for Signatures

Ed25519 provides fast, secure digital signatures with 128-bit security level. Signatures are deterministic, eliminating vulnerabilities related to random number generation during signing. The small key and signature sizes (32 bytes and 64 bytes respectively) minimize payload overhead.

X25519 for Key Agreement

X25519 provides elliptic curve Diffie-Hellman key agreement with strong security properties and resistance to timing attacks. Both client and server generate fresh ephemeral X25519 keypairs for each request, providing true forward secrecy: there are no long-term encryption keys that could be compromised to expose past sessions.

Separation of Signing and Encryption Keys

Using distinct keys for signing and encryption follows cryptographic best practices. It limits the impact of key compromise, simplifies key rotation, and allows different protection levels for different keys if needed.

Trust Bootstrapping

The fundamental challenge in any cryptographic system is trust bootstrapping: how does the client know it has authentic public keys for the server?

Our solution embeds server public keys directly in the application binary at compile time:

// src/crypto/embedded_keys.rs
// These keys are compiled into the binary and verified via code signing

/// inCamera server Ed25519 signing public key
/// Used to verify authenticity of all server payloads
/// Note: No X25519 identity key is embedded. The server generates
/// ephemeral X25519 keys per-request for stronger forward secrecy
pub const SERVER_SIGNING_PUBLIC_KEY: [u8; 32] = [
    0x__, 0x__, 0x__, 0x__, 0x__, 0x__, 0x__, 0x__,
    0x__, 0x__, 0x__, 0x__, 0x__, 0x__, 0x__, 0x__,
    0x__, 0x__, 0x__, 0x__, 0x__, 0x__, 0x__, 0x__,
    0x__, 0x__, 0x__, 0x__, 0x__, 0x__, 0x__, 0x__,
];

/// Key version identifier for rotation support
pub const KEY_VERSION: u32 = 1;

The trust chain becomes:

Trust Chain
Apple/Microsoft Code Signing Root
            |
            v
    inCamera Developer Certificate
            |
            v
    Signed Application Binary
            |
            v
    Embedded Server Public Keys

An attacker cannot substitute keys without either compromising our developer signing certificate or compromising Apple/Microsoft's code signing infrastructure.

Key Rotation Strategy

Long-term keys require rotation to limit exposure from potential undetected compromise and to follow cryptographic hygiene practices.

Key Type Rotation Frequency Transition Period
Server Signing Key Annual 90 days
Embedded Signing Public Key Follows server rotation Via app updates
Ephemeral X25519 Keys Per-request N/A (generated fresh)

Because encryption uses ephemeral keys on both sides, only the Ed25519 signing key requires scheduled rotation. To support seamless rotation, the application binary contains slots for current and next signing public keys. During the transition period, the server signs payloads with both current and next keys, and clients accept either. After the transition period, the next key becomes current, and a new next key is provisioned.

Protocol Specification

Protocol Overview

The credential distribution protocol establishes a secure channel over an untrusted transport, delivers encrypted credentials, and provides cryptographic proof of authenticity.

Protocol Flow
+----------+                                           +----------+
|  Client  |                                           |  Server  |
+----+-----+                                           +----+-----+
     |                                                      |
     |  1. Generate ephemeral X25519 keypair                |
     |  2. Generate 256-bit random nonce                    |
     |                                                      |
     |  -------------- Key Request ------------------>      |
     |  { client_ephemeral_public_key,                      |
     |    client_nonce,                                     |
     |    client_version,                                   |
     |    timestamp }                                       |
     |                                                      |
     |                      3. Validate request             |
     |                      4. Generate ephemeral X25519    |
     |                         keypair (per-request)        |
     |                      5. Perform X25519 key agreement |
     |                      6. Derive encryption key (HKDF) |
     |                      7. Encrypt credentials          |
     |                         (ChaCha20-Poly1305)          |
     |                      8. Sign entire response         |
     |                         (Ed25519)                    |
     |                                                      |
     |  <------------- Key Response -------------------     |
     |  { server_ephemeral_public_key,                      |
     |    encrypted_payload,                                |
     |    server_nonce,                                     |
     |    client_nonce_echo,                                |
     |    key_version,                                      |
     |    signature }                                       |
     |                                                      |
     |  9. Verify signature against embedded public key     |
     |  10. Verify client_nonce_echo matches                |
     |  11. Verify timestamp freshness                      |
     |  12. Perform X25519 key agreement with server's      |
     |      ephemeral public key                            |
     |  13. Derive decryption key (HKDF)                    |
     |  14. Decrypt and authenticate payload                |
     |  15. Securely store credentials                      |
     |  16. Zero ephemeral private key from memory          |
     |                                                      |

Message Formats

Request Message

{
    "protocol_version": 1,
    "request": {
        "client_ephemeral_public_key": "<base64, 32 bytes>",
        "client_nonce": "<base64, 32 bytes>",
        "timestamp": 1702134567,
        "client_version": "1.2.3",
        "platform": "macos-arm64"
    }
}
Field Type Description
protocol_version integer Protocol version for future compatibility
client_ephemeral_public_key base64 string X25519 public key, 32 bytes
client_nonce base64 string Cryptographically random, 32 bytes
timestamp integer Unix timestamp, seconds since epoch
client_version string Application version for compatibility checks
platform string Platform identifier for telemetry

Response Message

{
    "protocol_version": 1,
    "response": {
        "server_ephemeral_public_key": "<base64, 32 bytes>",
        "encrypted_payload": "<base64>",
        "encryption_nonce": "<base64, 24 bytes>",
        "server_nonce": "<base64, 32 bytes>",
        "client_nonce_echo": "<base64, 32 bytes>",
        "key_version": 1,
        "issued_at": 1702134567,
        "expires_at": 1702138167
    },
    "signature": "<base64, 64 bytes>"
}

Decrypted Payload Structure

{
    "credentials": {
        "vertex_ai": {
            "api_key": "<string>",
            "project_id": "<string>",
            "region": "<string>"
        },
        "openai": {
            "api_key": "<string>",
            "organization_id": "<string>"
        }
    },
    "credential_metadata": {
        "issued_at": 1702134567,
        "rotation_hint": 1702220967
    }
}

Cryptographic Operations

Key Agreement

Both client and server perform X25519 key agreement to derive a shared secret. The shared secret is never used directly; it is processed through HKDF to derive the encryption key.

shared_secret = X25519(local_private_key, remote_public_key)

Key Derivation

encryption_key = HKDF-SHA256(
    ikm: shared_secret,
    salt: client_nonce || server_nonce,
    info: "inCamera credential encryption v1",
    length: 32
)

Using both nonces in the salt ensures the derived key is unique to this specific request-response pair, even if the same ephemeral keys were somehow reused.

Encryption

ciphertext, tag = ChaCha20-Poly1305(
    key: encryption_key,
    nonce: encryption_nonce,
    plaintext: credential_payload,
    aad: key_version || issued_at || expires_at
)

The additional authenticated data (AAD) binds the ciphertext to the metadata, preventing an attacker from substituting metadata without detection.

Signature

signature = Ed25519-Sign(
    signing_private_key,
    canonical_json(response_without_signature)
)

Canonical JSON serialization ensures consistent byte representation across implementations.

Verification Steps

The client must perform all verification steps before using any credentials:

Step 1

Signature Verification

Verify Ed25519 signature against embedded public key for the specified key_version

Step 2

Nonce Binding

Verify client_nonce_echo exactly matches the nonce sent in the request

Step 3

Timestamp Freshness

Verify issued_at is within acceptable clock skew (±30 seconds of current time)

Step 4

Expiration Check

Verify current time is before expires_at

Step 5

Decryption

Perform key agreement, derive key, decrypt payload. ChaCha20-Poly1305 verifies integrity automatically during decryption.

Critical

If any step fails, the entire response is rejected and credentials are not stored.

Security Analysis

Attack Resistance

Man-in-the-Middle (Including Cloudflare)

An attacker positioned between client and server (including Cloudflare itself) sees:

  • Client's ephemeral public key (not secret)
  • Encrypted credential payload (ciphertext only)
  • Signature (verifiable but not forgeable)

The attacker cannot:

  • Decrypt the payload (requires client's ephemeral private key, which is never transmitted)
  • Forge a valid signature (requires server's signing private key)
  • Substitute their own credentials (signature verification would fail)

Replay Attacks

Replaying a captured response fails because:

  • The client_nonce_echo won't match the new request's nonce
  • The issued_at timestamp will be stale
  • Even if timing somehow aligned, the encryption key derivation uses both nonces, so a replayed response encrypted to a different ephemeral key cannot be decrypted

Forward Secrecy

An attacker who captures encrypted traffic cannot decrypt it later, even if they compromise server infrastructure, because:

  • Both client and server use fresh ephemeral X25519 keys per-request
  • The shared secret depends on both ephemeral keys
  • Past ephemeral private keys no longer exist (zeroed from memory immediately after use)
  • There is no long-term encryption key to compromise; only the signing key is long-term

Key Substitution

An attacker cannot substitute the embedded public keys without:

  • Compromising our code signing certificate, or
  • Compromising Apple/Microsoft's code signing infrastructure, or
  • Convincing the user to install a modified, unsigned binary

Cryptographic Assumptions

This protocol's security relies on:

Assumption Implication if Broken
Ed25519 is secure Signatures could be forged
X25519 is secure Shared secrets could be computed by attackers
ChaCha20-Poly1305 is secure Ciphertext could be decrypted or modified
HKDF-SHA256 is secure Derived keys could be predictable
Platform CSPRNG is secure Nonces and ephemeral keys could be predictable
Standard Cryptographic Primitives

All of these are standard, well-analyzed primitives with no known practical attacks.

Operational Procedures

Key Generation

Server keys are generated in a secure environment with the following properties:

  • Air-gapped machine or HSM
  • Cryptographically secure random number generator
  • Multiple witnesses for accountability
  • Immediate secure backup of private keys
  • Verification that public keys match private keys

Key Backup and Recovery

Private keys are backed up using Shamir's Secret Sharing with a 3-of-5 threshold. Key shares are distributed to geographically separated secure storage locations. Recovery requires coordination of at least three keyholders.

Incident Response

Suspected Credential Compromise (API Keys)

  1. Immediately rotate affected credentials with AI providers
  2. Push new credentials to server key vault
  3. Clients automatically receive new credentials on next refresh
  4. Review logs for anomalous API usage
  5. Notify affected users if data exposure is suspected

Suspected Signing Key Compromise

  1. Generate new signing keypair in secure environment
  2. Prepare emergency application update with new public key
  3. Begin signing responses with both old and new keys
  4. Push application update through all channels
  5. After transition period, revoke old signing key
  6. Conduct forensic investigation of compromise vector

Monitoring

The server logs (without logging sensitive values):

  • Request timestamps and client versions
  • Signature verification failures
  • Decryption failures reported by clients
  • Unusual request patterns

Alerts trigger on:

  • Elevated signature verification failures (possible attack attempt)
  • Requests with stale timestamps (possible replay attempt)
  • Requests from deprecated client versions (possible vulnerability exploitation)

Compliance and Transparency

Third-Party Trust

This architecture minimizes required trust in third parties:

Entity What They See What They Can Do
Cloudflare Encrypted payloads, signatures Nothing: cannot decrypt or forge
Network operators TLS ciphertext to Cloudflare Nothing: standard TLS protections
AI Providers API calls from clients Bound by ZDR agreements

User Transparency

Users may request:

  • This technical documentation
  • The embedded public key fingerprints for independent verification
  • Audit logs of credential issuance to their account

Independent Verification

Security researchers may verify the implementation by:

  • Inspecting the application binary for embedded keys
  • Capturing and analyzing protocol messages
  • Verifying that payloads are encrypted and cannot be read without client cooperation
Responsible Disclosure

We welcome responsible disclosure of any identified vulnerabilities. Please contact our security team at [email protected].

Appendix

Cryptographic Parameter Reference

Parameter Value
Ed25519 public key size 32 bytes
Ed25519 signature size 64 bytes
X25519 public key size 32 bytes
X25519 shared secret size 32 bytes
ChaCha20-Poly1305 key size 32 bytes
ChaCha20-Poly1305 nonce size 24 bytes (XChaCha20)
ChaCha20-Poly1305 tag size 16 bytes
HKDF hash SHA-256
Nonce size (protocol) 32 bytes
Credential validity window 3600 seconds (1 hour)
Clock skew tolerance 30 seconds

Future Considerations

Post-Quantum Cryptography

Current algorithms (X25519, Ed25519) are vulnerable to future quantum computers. When post-quantum standards mature, we will implement hybrid schemes that combine current algorithms with post-quantum alternatives, providing security against both classical and quantum adversaries.

Hardware Attestation

Future versions may incorporate platform attestation (Apple DeviceCheck, Android Play Integrity) to provide assurance that the client application has not been tampered with. This would strengthen the trust bootstrapping for embedded keys.

Formal Verification

We are evaluating formal verification of the protocol using tools like ProVerif or Tamarin to provide mathematical proof of security properties.

Related Documentation

Revision History

Version Date Changes
1.0 December 2025 Initial document with TLS pinning approach
2.0 December 2025 Revised for end-to-end encryption over Cloudflare Tunnel
2.1 December 2025 Clarified that server uses ephemeral X25519 keys per-request (no static identity key), providing stronger forward secrecy