Skip to main content

CAP Identity and rw_dir

How CAP gets its MQTT credentials and connects to Titan Cloud securely.

The Identity Problem​

Problem: CAP needs to connect to mqtt.titanrfid.com:8883 with TLS client authentication. That requires:

  1. CA certificate (to trust broker's TLS cert)
  2. Client certificate + private key (reader's identity)
  3. MQTT broker address, port, topics
  4. Reader REST API credentials (for Start/Stop commands)

Constraint: CAP cannot bootstrap itself. It's a C++ program running on a reader with no writeable filesystem except /cust/rw_dir/.

Solution: Identity files are written to /cust/rw_dir/ before CAP starts, either at the bench (Wonder-shipped) or pulled over internet (BYO, designed but not built).

Identity Files​

CAP reads 4 files from /cust/rw_dir/ at startup:

1. ca.crt (Titan Cloud CA)​

Format: PEM-encoded X.509 certificate
Purpose: Reader trusts broker's TLS certificate
Content: Titan Cloud CA public certificate

Example: PEM text file containing the Titan Cloud CA X.509 certificate (base64-encoded DER between standard PEM header and footer lines).

Why needed: Broker serves TLS cert signed by Titan CA. Without this, reader cannot verify broker's identity.

2. client.crt (Reader's Client Certificate)​

Format: PEM-encoded X.509 certificate
Purpose: Reader's identity for mutual TLS
CN (Common Name): Reader serial number (device identity), e.g. 370-12-34-5678-9012

Example: PEM text file for the reader client certificate; subject CN is the reader serial (for example 370-12-34-5678-9012).

Why CN = serial? Broker ACL enforces use_identity_as_username true, so MQTT username = cert CN. Topic path is titan/v1/{identity}/ where {identity} is the reader serial today. No separate username/password; certificate is the identity.

3. client.key (Private Key)​

Format: PEM-encoded RSA or ECDSA private key
Purpose: Matches client.crt for TLS handshake
Permissions: Must be mode 0600 (readable only by CAP process)

Example: PEM text file for the matching private key (keep secret; never log or commit).

Security: This file is the reader's secret. If leaked, an attacker could impersonate this reader. CAP reads it once at startup, never logs it.

4. titan.json (Reader REST API Credentials)​

Format: JSON
Purpose: CAP logs into reader's REST API (https://127.0.0.1/api/v1/) to execute Start/Stop commands
Content: Reader's REST username and password

Example:

{
"user": "root",
"password": "generated-per-reader-secret"
}

Why needed: CAP receives {"kind":"start"} on MQTT, then POSTs to reader's REST API. It needs credentials for that local API call.

Where Identity Comes From​

Wonder-Shipped (Current Model)​

When: At Wonder's bench, before reader ships to customer
How: readerprov provision -reader <ip> -tenant <customer> -name <name>

What happens:

  1. Tool reads serial from reader
  2. Issues client cert (CN is the reader serial number from hardware, the device identity)
  3. Writes all 4 files to /cust/rw_dir/ via reader REST API
  4. Configures MQTT in reader (broker address, TLS on)
  5. Starts reader with titan-default preset
  6. Confirms reader connects to mqtt.titanrfid.com

Audit trail: devices table records provisioning timestamp, technician, customer

Security: Strong. No internet exposure; physical access required.

BYO Claim-and-Pull (Designed, Not Built)​

When: After customer installs CAP on their own reader
How: (Planned) POST /api/v1/readers/claim → GET /pull/{token}

What would happen:

  1. Customer claims reader in hub UI → receives claim token
  2. CAP (or install script) fetches https://bootstrap.titanrfid.com/pull/{token}?serial={serial}
  3. Response contains all 4 identity files (JSON payload)
  4. CAP writes files to /cust/rw_dir/
  5. CAP restarts, connects to broker

Audit trail: Claim, pull, and bootstrap events all logged with customer user_id

Security: Token scoping, TTL, single-use, and serial validation are specified in the internal claim-and-pull security design (not published on this site).

Status: Designed, awaiting Security approval. Not implemented.

Certificate Lifecycle​

Issuance​

When: At claim time (Wonder-shipped) or claim API call (BYO)
Who: Titan Cloud CA (or On-Prem CA)
Validity: 10 years (long-lived client certificates; when rotating credentials, contact support or Wonder to revoke the previous certificate on the broker if it must not reconnect)
Storage: Staged in device_credentials table (encrypted at rest) until delivered

Delivery​

Wonder-Shipped: Direct write to /cust/rw_dir/ via reader REST API (no network exposure)
BYO: (Planned) Delivered once via pull API, then deleted from staging

Binding​

Broker ACL:

# Mosquitto: require_certificate true, use_identity_as_username true
pattern write titan/v1/%u/events # %u = cert CN = device identity (reader serial)
pattern write titan/v1/%u/status
pattern read titan/v1/%u/commands
pattern write titan/v1/%u/command-response

Effect: Reader can only publish/subscribe to topics matching its cert CN. Cannot forge another reader's telemetry. Cannot eavesdrop on other readers' topics.

Revocation​

Two separate mechanisms matter:

  1. Titand revoke (operator console API, not the customer hub UI): Marks the device row revoked so Titan stops accepting its data. It does not stop the reader connecting to the MQTT broker.
  2. Broker revocation list (support or Wonder revokes the client certificate on the broker): After the cert is on the revocation list, the reader is refused at its next connection attempt. An existing connection continues until it drops on its own (no fixed time bound).

Release vs revoke: Release is an operator-console action only (the customer hub cannot release a reader). Customers ask support or Wonder to release when a unit is returned or reassigned. Release unassigns the tenant (tenant_id = NULL). It does not revoke the certificate or mark the device revoked in titand.

Re-provisioning: Issues a new certificate and keys. It does not automatically revoke the previous certificate. To block the old cert from reconnecting, contact support or Wonder to revoke the old certificate on the broker.

Persistence Across Upgrades​

CAP Config: Persistent Data = rw_dir

What this means: /cust/rw_dir/ contents survive:

  • CAP upgrades (upload new .upgx)
  • CAP restart
  • Reader reboot (if config-image mode = default)

Does not survive:

  • Config-image mode removecap (full wipe, including CAP and rw_dir)
  • Physical Default Restore button + config-image mode removecap

Implications:

  • Upgrading CAP does not require re-provisioning identity
  • Factory reset (removecap mode) does wipe identity → reader must be re-provisioned

Security Considerations​

Secrets at Rest on Reader​

Files on reader:

  • ca.crt - Public (not secret)
  • client.crt - Public (not secret; cert is presented during TLS handshake)
  • client.key - Secret (private key)
  • titan.json - Secret (REST API password)

Protection:

  • client.key mode 0600 (CAP process only)
  • titan.json mode 0600
  • Reader filesystem not world-readable (only CAP and root can access /cust/rw_dir/)

Risk: If attacker gains root access to reader, they can read private key and impersonate reader. Mitigation: mark the device revoked in titand (operator console) so Titan stops accepting its data, contact support or Wonder to revoke the compromised certificate on the broker so it cannot reconnect, then re-provision new identity files on the reader.

Secrets in Transit​

Wonder-Shipped: No transit (written locally via REST API on bench LAN)

BYO Claim-and-Pull (planned):

  • Pull API over HTTPS (TLS 1.2+)
  • Single-use token (consumed after first pull)
  • 5-minute TTL (expired tokens rejected)
  • Serial validation (token+serial must match)

No Shared Secrets​

Each reader gets unique credentials:

  • Unique client cert (CN = reader serial number)
  • Unique private key
  • Unique REST password

No shared broker username/password. Certificate is the identity.

Audit Trail​

Events logged:

  • Claim (who, when, which serial)
  • Pull (serial, timestamp, success/failure)
  • Bootstrap complete (reader first connected)
  • Revoke (who revoked, when, reason)

Queryable: Hub UI → Audit Log, or GET /api/v1/audit?resource=reader

Troubleshooting Identity​

CAP Log: "Cannot read /cust/rw_dir/titan.json"​

Cause: Identity not provisioned
Fix: Provision identity (Wonder-shipped path, or wait for BYO claim-pull)

CAP Log: "TLS handshake failed"​

Cause: ca.crt wrong (not Titan CA), client.crt/client.key mismatch, or the certificate was revoked on the broker
Fix: Verify ca.crt is Titan Cloud CA. Verify cert+key were issued together. If the cert was revoked on the broker, re-provision with a new cert and contact support or Wonder to revoke the previous certificate if needed.

CAP Log: MQTT connected but no tag events in hub​

Cause: Cert CN does not match the reader serial. The broker uses use_identity_as_username true; ACLs allow titan/v1/%u/... only for the cert CN, so a wrong CN connects but publishes are silently dropped.
Fix: Check cert CN: openssl x509 -in client.crt -noout -subject (should be CN=370-12-34-5678-9012 matching titan/v1/{identity}/...). Re-provision if wrong.

Connection problems after certificate revocation​

Cause: A revoked certificate fails at the TLS handshake on the next connection attempt; the current connection may still be active until it drops (no fixed time bound).
Fix: Contact support or Wonder to revoke the certificate on the broker when you need to block future reconnects. Titand revoke (operator console) stops Titan accepting data but does not by itself update the broker revocation list. Release (operator console, requested via support) only unassigns the tenant.

Identity Lost After Factory Reset​

Expected if config-image mode = removecap.
Fix: Re-provision identity. Persistent Data = rw_dir only survives default mode, not removecap.

Next Steps​