# PKI Architecture Document ## PTT Platform — Public Key Infrastructure Design | Field | Value | |---|---| | Document ID | PKI-PTT-ARCH-001 | | Version | 2.0.0 | | Status | Current — Authoritative | | Date | 2026-03-20 | | Supersedes | v1.2.0 | | Change Summary | Full rewrite to reflect as-built state. System CA Signing CA added to hierarchy. Bootstrap JWT promoted to primary supply chain gate (replaces planned PAS auto-enrollment). PAS redefined as optional operator device enrollment service. Two-layer trust model retained. Operator enrollment flow added. | | Classification | Internal / Confidential | --- ## Table of Contents 1. [Purpose and Scope](#1-purpose-and-scope) 2. [Glossary](#2-glossary) 3. [Trust Model and Design Principles](#3-trust-model-and-design-principles) 4. [CA Hierarchy](#4-ca-hierarchy) 5. [Identity Definitions](#5-identity-definitions) 6. [Certificate Profiles](#6-certificate-profiles) 7. [Client Supply Chain Authentication — Bootstrap JWT](#7-client-supply-chain-authentication--bootstrap-jwt) 8. [Two-Layer Connection Authentication](#8-two-layer-connection-authentication) 9. [Trust Establishment at Runtime](#9-trust-establishment-at-runtime) 10. [Operator Device Enrollment — PAS (Optional)](#10-operator-device-enrollment--pas-optional) 11. [Key and Certificate Lifecycle](#11-key-and-certificate-lifecycle) 12. [Build Pipeline](#12-build-pipeline) 13. [Threat Model](#13-threat-model) 14. [Document Relationships](#14-document-relationships) --- ## 1. Purpose and Scope This document defines the complete, as-built Public Key Infrastructure architecture for the PTT Platform. It is the authoritative reference for all PKI decisions across the platform. Where any other document conflicts with this one, this document takes precedence. The architecture described here reflects the current implementation in the `pttclient` Android application and the build tooling (`set-svrauth-buildconfig.sh`). Sections describing planned or optional features are explicitly labelled as such. ### 1.1 Goals - Define the four-tier CA hierarchy (Platform Root → Platform Issuing → System CA Signing → System CA → Component) that supports multi-operator deployment while preserving vendor supply chain control. - Document the Bootstrap JWT as the primary platform-level supply chain gate, enforced on every connection. - Define the two-layer (TLS + application nonce) mutual authentication model as implemented. - Describe the System CA Signing CA as the trust anchor for System CA import and runtime system discovery. - Define the optional Operator Device Enrollment service (PAS) for private operator deployments that require per-device authorization backed by a System CA-issued device certificate. - Ensure the architecture can scale to many independent operator-deployed systems without vendor involvement at runtime. --- ## 2. Glossary | Term | Definition | |---|---| | **Vendor** | The entity that builds, maintains, and distributes the PTT Platform software. Controls the Platform Root CA, Platform Issuing CA, and System CA Signing CA. | | **Operator** | An entity licensed to deploy a PTT System. Controls a System CA issued under the System CA Signing CA. | | **Platform** | The totality of PTT software components across all deployments. | | **System** | A network of ptt-servers and their shared ptt-backend, operated by a single Operator. Each System has its own System CA. | | **System ID** | `SHA-256(System CA public key DER)[0:32]` — 32-hex-character identifier derived from and permanently bound to the System CA public key. | | **ptt-server** | The realtime Socket.IO server component. Holds a Server Identity Certificate issued by the System CA. | | **Server ID** | `SHA-256(server public key DER)[0:16]` — derived from the server's identity keypair. | | **ptt-backend** | The RESTful API holding system-of-record data for a System. | | **pttclient** | The Android mobile application distributed by the Vendor to end users. Minimum supported SDK: API 19. | | **Device UUID** | `SHA-256(device public key DER)[0:16]` — first 16 hex characters of the SHA-256 hash of the device's public key SPKI. Retained as the device enrollment certificate subject CN (`device-`); not sent on the PQ connection URL (the DNI, derived from the ML-DSA-65 key, is the connection identifier). | | **Platform Root CA** | Offline root of the Vendor PKI hierarchy. Issues only the Platform Issuing CA certificate. | | **Platform Issuing CA** | Online subordinate of the Platform Root CA. Signs System CA Signing CA certificates and Bootstrap JWTs (via Vault Transit). | | **System CA Signing CA** | Offline intermediate CA issued by the Platform Issuing CA. Signs all System CA certificates. The System CA Signing CA certificate is bundled in every pttclient APK and is the trust anchor for importing System CAs at runtime. | | **System CA** | Per-deployment CA issued by the System CA Signing CA. Issues all component certificates within a single System (Server Identity certs, Backend Server certs, and optionally Device Enrollment Certificates). | | **Bootstrap JWT** | A JWT signed by the Platform Issuing CA (via Vault Transit, RS256) and embedded in every pttclient APK at build time. Sent on every Socket.IO connection as the `x-api-key` HTTP header. Proves the connecting client is an authentic, vendor-built APK. | | **Device Enrollment Certificate** | An optional X.509 certificate issued by an Operator's System CA to a specific device, attesting that the device is authorized to connect to that System. Stored in Android SharedPreferences. Not to be confused with the former "Device Certificate" issued by the Platform Issuing CA (that model is no longer active). | | **PAS (Platform Attestation Service)** | An optional, per-operator service that issues Device Enrollment Certificates signed by the Operator's System CA, requiring an enrollment secret provided by a device administrator. Disabled on the public default system. | | **Android Keystore** | The hardware-backed secure enclave on Android devices. Used to generate and store the device RSA keypair. Private key cannot be extracted. | | **CSR** | Certificate Signing Request — PKCS#10 structure containing a public key and subject, signed by the corresponding private key. | | **SPKI** | SubjectPublicKeyInfo — the DER-encoded public key structure used as input for all key hash derivations. | | **CLIENT_PROOF** | The client's side of the Layer 2 nonce exchange: the client signs the Socket.IO session ID (nonce) with its device private key and sends the signature plus its public key to the server. | | **SERVER_PROOF** | The server's side of the Layer 2 nonce exchange: the server signs SHA-256(socketId) with its System CA-issued Server Identity private key and returns the signature plus its Server Identity Certificate (and optional chain). | --- ## 3. Trust Model and Design Principles ### 3.1 Three Orthogonal Trust Questions Every connection in the PTT Platform resolves three independent trust questions: ``` 1. PLATFORM SUPPLY CHAIN: Is this APK built and signed by the Vendor? (Answered by Bootstrap JWT — RS256, Platform Issuing CA) 2. SYSTEM MEMBERSHIP: Is this server a legitimate member of a licensed System? (Answered by System CA-issued Server Identity Certificate and Layer 2 nonce signature) 3. DEVICE AUTHORIZATION: Is this specific device permitted by the Operator? [OPTIONAL] (Answered by Operator PAS Device Enrollment Certificate, when the Operator enables PAS) ``` Questions 1 and 2 are always enforced. Question 3 is enforced only when an Operator has enabled their PAS service; on the public default system it is not required. ### 3.2 Two-Layer Connection Model Every pttclient-to-ptt-server connection passes through two sequential validation layers. **Layer 1 — TLS (Transport)** The TLS handshake establishes an encrypted channel. The pttclient's trust store accepts certificates from: - The device's system certificate store (Android built-in public CAs — used when a reverse proxy terminates TLS with an AWS/Let's Encrypt cert). - Bundled System CA certificate(s) in `assets/certs/` (used for direct ptt-server connection or when the System CA issued the TLS cert). - Imported System CA certificates (added at runtime by the user via QR code enrollment). Layer 1 validates encryption and certificate validity only. It does **not** validate System membership or server identity. **Layer 2 — Application Nonce Exchange (Mutual)** After TLS is established, a bidirectional nonce exchange proves the identity of both parties: - **SERVER_PROOF** (Server → Client): Server signs SHA-256(socketId) with its System CA-issued Server Identity private key. Client validates the signature against the public key extracted from the server's presented Server Identity Certificate. Client also validates the certificate chain against its bundled/imported System CA(s). - **CLIENT_PROOF** (Client → Server): Client signs a nonce with its device private key (Android Keystore). Server validates the signature against the client's presented public key. Server optionally checks the client's Device Enrollment Certificate if PAS is enabled for the system. Both proofs must succeed for the connection to be authenticated. ### 3.3 Bootstrap JWT as Primary Supply Chain Gate The Bootstrap JWT is the **currently implemented and enforced** platform-level supply chain control. It is: - Generated at APK build time by `set-svrauth-buildconfig.sh`. - Signed by the Platform Issuing CA via Vault Transit (RS256, `platform-issuing-ca` Transit key). - Embedded in `assets/keys/bootstrap_cert.jwt` (preferred) or `assets/bootstrap_cert.jwt`. - Sent on every Socket.IO connection as the `x-api-key` HTTP header. - Verified by the ptt-server on connection. Connections that fail JWT validation are rejected before Layer 2. The JWT payload contains `buildVersion` and `buildFingerprint`, binding it to a specific APK release. The signature, verifiable with the Platform Issuing CA certificate, proves the JWT was created by the Vendor's build pipeline. ### 3.4 Design Principles **P1 — Offline Root Keys**: Platform Root CA and System CAs MUST be generated and used in offline or air-gapped environments. **P2 — Derived Identifiers**: System ID and Device UUID are derived from public key hashes, creating permanent verifiable bindings without a central registry. **P3 — Least-Privilege Issuance**: Each CA issues only certificates appropriate to its level. **P4 — Proportionate Key Protection**: Key protection requirements scale with blast radius. **P5 — Hardware-Bound Device Keys**: Device private keys are generated in and bound to the Android Keystore. They cannot be extracted. **P6 — System CA Permanence**: A System CA keypair is permanent for the life of its System. **P7 — Certificate-Based Server Trust**: pttclient trusts servers based on their System CA-issued certificates, validated at Layer 2. Hardcoded server public key lists (SERVER_CERTS) are fully deprecated and removed. **P8 — Two-Layer Validation Mandatory; Trust Originates in Layer 2**: Layer 1 encrypts the channel. Layer 2 proves mutual identity. Neither layer alone is sufficient. **P9 — No Runtime Vendor Involvement for Normal Operation**: Once a build is released and a system is provisioned, pttclient-to-ptt-server operation requires no connection to Vendor infrastructure. **P10 — Bootstrap JWT is the Platform Supply Chain Gate**: The Bootstrap JWT, verified by the ptt-server against the Platform Issuing CA public key, is the enforced mechanism preventing non-Vendor APKs from connecting to any System. **P11 — System CA Signing CA is the Trust Anchor for System Import**: The System CA Signing CA certificate, bundled in every APK, is the trust anchor used by `SystemCaImporter` to verify any System CA certificate before it is accepted for import. This ensures only Vendor-sanctioned operator deployments can be added to a client. **P12 — Operator PAS is Optional and Scoped to a System**: Device Enrollment via PAS is at the Operator's discretion and is isolated to their System. It does not affect other Systems or the default public System. --- ## 4. CA Hierarchy ### 4.1 Full Hierarchy ``` Platform Root CA (offline, air-gapped, Vendor-controlled, RSA-4096, 20-year) │ │ Issues only: │ └── Platform Issuing CA │ └── Platform Issuing CA (Vault PKI + Vault Transit, Vendor-controlled, RSA-4096, 5-year) │ │ Issues: │ ├── System CA Signing CA certificate │ └── Bootstrap JWTs (signed via Vault Transit key `platform-issuing-ca`) │ └── System CA Signing CA (offline, Vendor-controlled, RSA-4096, 5-year) │ │ Issues: │ └── System CA certificates (one per licensed Operator deployment) │ ├── System CA [System A] (offline, Operator-controlled, RSA-4096, 10-year) │ │ │ │ Issues: │ │ ├── Server Identity Certificate (each ptt-server) │ │ ├── Backend Server Certificate (ptt-backend) │ │ └── Device Enrollment Certificate (per device, if PAS enabled) [OPTIONAL] │ │ │ ├── server-.crt (2-year) │ ├── backend.crt (2-year) │ └── device-.crt (2-year, optional) │ └── System CA [System B] (separate operator, separate trust domain) └── ... ``` ### 4.2 Key Architectural Notes **System CA Signing CA is a separate offline intermediate, not hosted in Vault PKI.** The Platform Issuing CA's Vault PKI engine is used to issue Bootstrap JWTs (via the Transit engine) and previously to issue System CA certificates directly. In the current architecture, System CA certificates are issued by the separate System CA Signing CA, which was created offline and signed by the Platform Root CA. The Platform Issuing CA signs the System CA Signing CA. This gives a clean separation: Vault handles live JWT signing operations; the System CA Signing CA handles the less-frequent, high-security operation of signing operator System CAs offline. **The System CA Signing CA certificate is bundled in every APK** at `assets/certs/` with a filename containing `system_ca_signing`. This is the trust anchor used by `SystemCaImporter.verifyAndParseCaCert()` to verify any System CA before import. ### 4.3 Trust Bundle Distribution | Principal | Trusts | Bundled At | |---|---|---| | pttclient | System CA Signing CA certificate | Build time (APK `assets/certs/`) | | pttclient | Bundled System CA certificate(s) | Build time (APK `assets/certs/`) | | pttclient | Imported System CA certificates | Runtime (user-initiated via QR scan) | | pttclient | Device system certificate store | Android OS | | ptt-server | System CA (own system) | Deployment provisioning | | ptt-server | Platform Issuing CA | Deployment provisioning (to verify Bootstrap JWTs) | | ptt-backend | System CA (own system) | Deployment provisioning | --- ## 5. Identity Definitions ### 5.1 System ID ``` SystemID = lowercase_hex( SHA-256( SubjectPublicKeyInfo(SystemCA.publicKey) ) )[0..31] ``` Derived at System provisioning. Permanent for the life of the System. Used as namespace prefix in backend records, Redis keys, log identifiers, and the `activeSystemId` field in pttclient. **Implementation**: `SystemCaImporter.deriveSystemId()` — takes first 16 bytes (32 hex chars) of SHA-256 of the System CA public key DER encoding. ### 5.2 Server ID ``` ServerID = lowercase_hex( SHA-256( SubjectPublicKeyInfo(server.publicKey) ) )[0..15] ``` 16-hex-character (8-byte) identifier. Used as the `CN` in the Server Identity Certificate and returned as the `keyId` field in the `server_challenge` Socket.IO event. Stored in the server process as `PTT_SVRAUTH_KEY_ID`. ### 5.3 Device UUID ``` DeviceUUID = lowercase_hex( SHA-256( SubjectPublicKeyInfo(device.publicKey) ) )[0..15] ``` First 16 hex characters (64 bits) of the SHA-256 hash of the device public key SPKI. Generated in Android Keystore on first run. **Implementation**: `PTTClientApplication.deriveUUIDFromPublicKey()`. DeviceUUID is retained as the device enrollment certificate subject CN (`device-`) but is not sent on the PQ connection URL. The connection identifier is the DNI, derived from the ML-DSA-65 public key (`dsaPub`). --- ## 6. Certificate Profiles ### 6.1 Platform Root CA | Field | Value | |---|---| | Subject CN | `PTT-Platform-Root-CA` (or `DEV Platform Root CA` for dev) | | Key Algorithm | RSA-4096 | | Validity | 20 years | | Basic Constraints | `CA:TRUE, pathLen:2` | | Key Usage | `keyCertSign, cRLSign` | | Storage | Air-gapped workstation, Tails Linux, Yubikey 5C backup | ### 6.2 Platform Issuing CA | Field | Value | |---|---| | Subject CN | `PTT Platform Issuing CA` | | Key Algorithm | RSA-4096 | | Validity | 5 years | | Basic Constraints | `CA:TRUE, pathLen:1` | | Key Usage | `keyCertSign, cRLSign` | | Issuer | Platform Root CA | | Storage | Vault PKI engine + Vault Transit (key: `platform-issuing-ca`) | | Notes | The private key is imported into Vault Transit for JWT signing. The certificate is bundled in pttclient at `assets/certs/` (filename contains `platform_issuing_ca` or `platform-issuing-ca`). | ### 6.3 System CA Signing CA | Field | Value | |---|---| | Subject CN | `DEV System CA Signing CA - localhost` (example) | | Key Algorithm | RSA-4096 | | Validity | 5 years | | Basic Constraints | `CA:TRUE, pathLen:1` | | Key Usage | `keyCertSign, cRLSign` | | Issuer | Platform Issuing CA | | Storage | Air-gapped workstation (offline); certificate stored in Vault KV v2 at `secret/data/system-ca-signing-ca` for build pipeline retrieval | | APK Bundle | Yes — `assets/certs/` with filename containing `system_ca_signing` | | Notes | This is the trust anchor for all System CA import operations in pttclient. The `SystemCaImporter` class searches for a cert file whose lowercase name contains `system_ca_signing` to locate it. | ### 6.4 System CA | Field | Value | |---|---| | Subject CN | `DEV-localhost System Root CA` (example; operator-defined) | | Key Algorithm | RSA-4096 | | Validity | 10 years | | Basic Constraints | `CA:TRUE, pathLen:0` | | Key Usage | `keyCertSign, cRLSign` | | Issuer | System CA Signing CA | | Storage | Operator offline storage; certificate distributed to pttclient APK builds (`assets/certs/`) and ptt-server deployments | | Notes | `pathLen:0` prevents sub-CA issuance. SystemID is derived from this certificate's public key. The System CA certificate is the trust root for Layer 2 server identity validation. | ### 6.5 Server Identity Certificate | Field | Value | |---|---| | Subject CN | `` (16-char hex derived from server public key) | | Key Algorithm | RSA-2048 | | Validity | 2 years | | Basic Constraints | `CA:FALSE` | | Key Usage | `digitalSignature, keyEncipherment` | | Extended Key Usage | `serverAuth, clientAuth` | | Issuer | System CA | | Notes | Used for Layer 2 SERVER_PROOF. The server presents this certificate in the `server_challenge` Socket.IO event payload. The client validates it against the bundled/imported System CA. | ### 6.6 Backend Server Certificate | Field | Value | |---|---| | Subject CN | `ptt-backend.` | | Key Algorithm | RSA-2048 | | Validity | 2 years | | Basic Constraints | `CA:FALSE` | | Key Usage | `digitalSignature, keyEncipherment` | | Extended Key Usage | `serverAuth` | | Issuer | System CA | ### 6.7 Device Enrollment Certificate (Optional — PAS) | Field | Value | |---|---| | Subject CN | `device-` | | Key Algorithm | RSA-2048 (device keypair, Android Keystore generated) | | Validity | 2 years | | Basic Constraints | `CA:FALSE` | | Key Usage | `digitalSignature` | | Extended Key Usage | `clientAuth` | | Issuer | **System CA** (not the Platform Issuing CA — this is a key difference from earlier designs) | | Storage | Android SharedPreferences via `DeviceCertificateManager` | | Notes | Only issued when an Operator has enabled their PAS. Requires an enrollment secret provided by a device administrator. The ptt-server for the relevant System validates this certificate when `PTT_REQUIRE_DEVICE_CERT=1` is set. | --- ## 7. Client Supply Chain Authentication — Bootstrap JWT ### 7.1 Purpose The Bootstrap JWT is the platform-level proof that a connecting pttclient is an authentic, Vendor-built application. It is the primary supply chain gate and is enforced on every connection. ### 7.2 JWT Structure ```json { "header": { "alg": "RS256", "typ": "JWT" }, "payload": { "iss": "platform-issuing-ca", "sub": "bootstrap-certificate", "buildVersion": "", "buildFingerprint": "", "iat": , "exp": } } ``` The signature is RS256 using the Platform Issuing CA private key (accessed via Vault Transit key `platform-issuing-ca`). **`buildFingerprint`**: Set at build time by `set-svrauth-buildconfig.sh` from the `BUILD_FINGERPRINT` environment variable. Intended to be the SHA-256 hash of the APK signing certificate in the form `SHA256:`, tying the JWT to a specific release signing key. ### 7.3 Build-Time Generation The Bootstrap JWT is generated by `set-svrauth-buildconfig.sh` during each build: 1. The script authenticates to Vault (token or AppRole). 2. It calls the Vault Transit API (`POST /v1/transit/sign/platform-issuing-ca`) with the SHA-256 hash of `header.payload` as the input, `prehashed=true`, `signature_algorithm=pkcs1v15`, `hash_algorithm=sha2-256`. 3. The returned `vault:v1:` signature is decoded and re-encoded as base64url. 4. The complete JWT (`header.payload.signature`) is written to `app/src/main/res/raw/bootstrap_cert.jwt` and also to `assets/keys/bootstrap_cert.jwt`. The script also fetches three certificates from Vault and writes them to `certs.properties` as base64url values: - `PAS_PLATFORM_ISSUING_CA_CERT_PEM_B64URL` - `PAS_SYSTEM_CA_CERT_PEM_B64URL` - `PAS_SYSTEM_CA_SIGNING_CA_CERT_PEM_B64URL` And copies the Platform Issuing CA PEM and Bootstrap JWT to `app/src/main/res/raw/`. ### 7.4 Runtime Use `PasAttestation.loadBootstrapJwt()` loads the JWT from `assets/keys/bootstrap_cert.jwt` (falling back to `assets/bootstrap_cert.jwt`). `PTTClientApplication.initSocket()` includes the Bootstrap JWT as an HTTP header on every Socket.IO connection: ``` x-api-key: ``` The ptt-server validates this JWT against the Platform Issuing CA certificate on every connection. A connection without a valid JWT or with an expired JWT is rejected. ### 7.5 What This Achieves - Prevents repackaged or independently built clients from connecting to any PTT server. - Ties each APK version to a specific JWT (identified by `buildVersion` and `buildFingerprint`). - Enables the Vendor to revoke a specific app version by revoking its JWT (by removing the corresponding `buildVersion`/`buildFingerprint` from the server's allowed list, or by letting the JWT expire naturally). - Does not require any runtime contact with Vendor infrastructure (the server already has the Platform Issuing CA certificate for verification). ### 7.6 What This Does Not Achieve The Bootstrap JWT proves the APK is Vendor-built. It does **not** prove: - That the connecting device is authorized by the Operator (that is PAS's role). - That the individual user is authenticated (that is the application session layer's role). - That the device has not been compromised at the OS level (no hardware attestation in this layer). --- ## 8. Two-Layer Connection Authentication ### 8.1 Overview ``` pttclient ptt-server │ │ │ [Layer 1: TLS Handshake] │ │ Server presents TLS certificate │ │ (public CA or System CA issued) │ │ Client validates against trust store│ │ Encrypted channel established │ │ ─────────────────────────────────── ►│ │ │ │ [Socket.IO upgrade — HTTP headers] │ │ x-api-key: │ ← Platform supply chain gate │ (PQ mode) dni, dsaPub, kemPub, │ │ v=pq1, mfr, model, ver │ │ (Classical) dni, publicKey, │ │ mfr, model, ver │ │ ─────────────────────────────────── ►│ │ │ Server validates Bootstrap JWT │ │ (RS256, Platform Issuing CA) │ │ Rejects if invalid/expired │ │ │ [Layer 2: Bidirectional Nonce] │ │ │ │ CLIENT_PROOF → Server │ Client signs nonce with device key │ {nonce, pubkey, signature} │ │ ─────────────────────────────────── ►│ │ │ Server validates CLIENT_PROOF │ │ (optional: checks Device Cert if PAS enabled) │ │ │ SERVER_PROOF → Client │ Server signs SHA-256(socketId) │ {cert, certChain, signature} │ with Server Identity private key │ ◄───────────────────────────────── ─ │ │ │ │ Client validates SERVER_PROOF: │ │ - Parse server cert from payload │ │ - Verify chain → trusted System CA │ │ - Verify signature with cert pubkey │ │ │ │ Connection AUTHENTICATED ✓ │ ``` ### 8.2 Layer 1 — TLS The pttclient uses `PlatformTrustManager` (a composite trust manager) for all TLS connections. It accepts certificates from: 1. **Device system certificate store**: Android's built-in CA bundle (public CAs — AWS, Let's Encrypt, DigiCert, etc.). Used when a reverse proxy terminates TLS. 2. **Bundled System CA certificates**: Certs in `assets/certs/` (loaded at startup). Used for direct ptt-server connection or when the System CA issued the TLS cert. 3. **Active imported System CA**: `PlatformTrustManager.setActiveSystemCaPem()` allows the currently selected System's CA to be added dynamically (called by `ServerManagementActivity` when switching systems). The hostname verifier is permissive (`myHostnameVerifier` returns `true` for all hostnames). This is acceptable because Layer 2 provides the authentic server identity proof; Layer 1 TLS is only for encryption. ### 8.3 Bootstrap JWT Gate (pre-Layer 2) Before Layer 2 executes, the ptt-server checks the `x-api-key` header (Bootstrap JWT). If absent or invalid, the connection is closed. This gate is enforced before any application-layer authentication. ### 8.4 Layer 2 — CLIENT_PROOF (Client → Server) The client signs a nonce with its device private key. Implementation in pttclient: ``` nonce: Socket.IO session ID (or server-provided challenge value) signature: ML-DSA-65 signature over nonce bytes, using device DSA private key pubkey: base64 raw ML-DSA-65 verification key (1952 bytes) — matches dsaPub from connect query ``` The server: 1. Verifies the signature cryptographically (signature valid for nonce + pubkey). 2. Verifies pubkey matches `dsaPub` sent at connection time. 3. Validates `dni == deriveDniFromRawBase64(dsaPub)`. 4. If PAS is enabled for the system, validates the client's Device Enrollment Certificate (System CA-issued, subject CN matches `device-`). ### 8.5 Layer 2 — SERVER_PROOF (Server → Client) The server signs `SHA-256(socketId)` with its Server Identity private key and returns the result. **Client validation** is performed by `ServerTrustManager.verifyWithCert()`: 1. Parses the server's X.509 Server Identity Certificate from `certPem` field. 2. Parses optional intermediate certificates from `certChainPem`. 3. Calls `verifyChainToTrusted()`: tries direct verification (leaf cert signed by a trusted CA root) then intermediate path (leaf signed by intermediate, intermediate signed by trusted CA root). 4. Extracts the server public key from the verified leaf certificate. 5. Computes `SHA-256(socketId)` as the challenge bytes. 6. Verifies the signature against the extracted public key. 7. On success, derives `keyId = SHA-256(serverPubKey DER)[0:8 bytes as 16 hex chars]` and stores `lastVerifiedServerInfo` parsed from the certificate's Subject DN fields. The trusted CAs are loaded from `assets/certs/*.pem` and `assets/certs/*.crt` at startup by `ServerTrustManager.init()`. This includes the bundled System CA certificate(s) and may include the System CA Signing CA. ### 8.6 Connection URL Parameters **PQ mode (`v=pq1`) — current implementation:** ``` ?dni=<16-digit decimal DNI> &dsaPub= &kemPub= &v=pq1 &manufacturer= &model= &ver= ``` The server derives DNI via `deriveDniFromRawBase64(dsaPub)` and validates it matches `dni`. `deviceUUID` and `publicKey` are not sent in PQ mode. --- ## 9. Trust Establishment at Runtime ### 9.1 Bundled System CA (Build Time) Every pttclient APK includes at least one System CA certificate in `assets/certs/`. The filename must end in `.pem` or `.crt` and must not contain `platform_issuing` or `system_ca_signing` in lowercase (those reserved for their respective CA certs). This is the System CA for the primary/default System. `ServerListManager.getBuiltInSystem()` discovers this cert by iterating `assets/certs/` and selecting the first `.crt`/`.pem` file that does not contain `platform_issuing` in its lowercase name. It calls `SystemCaImporter.verifyAndParseCaCert()` to verify and derive the System ID. ### 9.2 Runtime System CA Import (Multi-System) Users can add additional Systems by scanning a QR code in `ServerManagementActivity`. The QR code contains a JSON payload with a `url` field and optional `label`. The import flow: 1. Fetches `/system-root-ca.crt` via HTTPS. 2. Calls `SystemCaImporter.fetchVerifyAndParse()`. 3. Inside `verifyAndParseCaCert()`: a. Loads the System CA Signing CA from `assets/certs/` (file whose lowercase name contains `system_ca_signing`). b. Parses the candidate System CA certificate. c. Checks certificate validity (not expired). d. Calls `importedCa.verify(systemCaSigningCa.getPublicKey())` — the candidate must be signed by the System CA Signing CA. e. Derives `systemId = SHA-256(importedCa.publicKey DER)[0:16 bytes as 32 hex chars]`. f. Extracts `systemLabel` from the CN field. 4. If verification passes, the System CA is stored in `ServerListManager` (SharedPreferences, key `ptt_server_list_v2`). Maximum 15 imported systems (`ServerListManager.MAX_IMPORTED_SYSTEMS`). **Key security property**: Only System CA certificates that were signed by the Vendor's System CA Signing CA can be imported. A rogue operator cannot import a self-signed CA. ### 9.3 System Switching When the user switches to a different System via `ServerManagementActivity`: 1. `PlatformTrustManager.setActiveSystemCaPem()` is called with the selected System's CA PEM. This adds the CA to the next initialized `PlatformTrustManager` SSLContext. 2. `PTTClientApplication.switchActiveSelection()` is called, which: a. Updates `ServerListManager` active selection. b. Calls `resetNetworkClients(resetTrustManager=true if system changed)`. c. Calls `PlatformTrustManager.reset()` if the system changed (forces re-initialization with new System CA). d. Clears stale server-derived UI state. 3. `ServerManagementActivity` restarts the SocketService (stop + 250ms delay + start) to establish a new connection to the selected server. ### 9.4 No Runtime Vendor Contact Normal operation (connecting to a ptt-server, transmitting/receiving voice) requires no connection to Vendor infrastructure. The Bootstrap JWT is pre-baked in the APK. The Platform Issuing CA certificate (for JWT validation on the server side) is provisioned at server deployment time. --- ## 10. Operator Device Enrollment — PAS (Optional) ### 10.1 Purpose and Scope The Platform Attestation Service (PAS) is an **optional** service that Operators may deploy to require per-device authorization before a device can connect to their System. It is designed for private operator deployments where the Operator needs to control which specific devices can access their System. The public default System does not use PAS. The Bootstrap JWT gate (§7) and the Layer 2 bidirectional nonce exchange (§8) are sufficient for public deployments. ### 10.2 How It Differs from the Previous PAS Design Earlier design documents described PAS as a Vendor-operated service that issued Device Certificates signed by the Platform Issuing CA, primarily using the Bootstrap JWT and Google Play Integrity for attestation. That model has been revised: | Aspect | Previous Design | Current Design | |---|---|---| | Operator | Vendor | Individual Operator | | Enrollment authority | Platform Issuing CA | Operator's System CA | | Trigger | Automatic on first run | Explicit user-initiated enrollment | | Secret | Bootstrap JWT + Play Integrity | Enrollment secret provided by device admin | | Scope | All systems | Per-system, operator's choice | | Default system | Would be required | Not required | ### 10.3 Enrollment Flow ``` Device Admin pttclient Operator PAS │ │ │ │ Provides enrollment │ │ │ secret (e.g. OTP/PIN) │ │ │ ──────────────────────────► │ │ │ │ │ │ │ [Enroll Activity launched] │ │ │ Generate CSR (device key) │ │ │ Attach enrollment secret │ │ │ POST /v1/device-enroll │ │ │ Authorization: Bearer │ │ │ ─────────────────────────── ►│ │ │ │ │ │ Validate JWT│ │ │ (Platform │ │ │ Issuing CA)│ │ │ │ │ │ Validate │ │ │ enrollment │ │ │ secret │ │ │ │ │ │ Issue cert: │ │ │ System CA │ │ │ signs CSR │ │ │ │ │ │ ◄──────────────────────────│ │ │ Device Enrollment Cert │ │ │ (System CA-issued) │ │ │ │ │ │ Stored in SharedPreferences │ │ │ (DeviceCertificateManager) │ ``` ### 10.4 Enrollment Secret The enrollment secret is a credential provided by a device administrator that authorizes a specific enrollment request. Possible forms include: - A one-time PIN generated by the Operator's PAS admin interface. - A time-limited enrollment token scanned as part of the QR enrollment flow. - An operator-issued invitation code. The exact format is operator-defined. The pttclient sends it in the PAS enrollment request. The PAS validates it before issuing the certificate. ### 10.5 Device Enrollment Certificate The Device Enrollment Certificate is: - Signed by the **Operator's System CA** (not the Platform Issuing CA). - Subject CN: `device-`. - Key: the device's existing RSA keypair from Android Keystore. - Validity: 2 years (recommended). - Extended Key Usage: `clientAuth`. - Stored in `SharedPreferences` via `DeviceCertificateManager.saveDeviceCert()`. The ptt-server for the relevant System validates this certificate when `PTT_REQUIRE_DEVICE_CERT=1`. Validation checks: 1. Certificate chain validates to the System CA. 2. Certificate subject CN matches `device-`. 3. Certificate is not expired and not revoked (CRL check, operator-configured interval). ### 10.6 Current Implementation State `PasAttestation.java` and `DeviceCertificateManager.java` are present in the codebase but the automatic enrollment call is **commented out** in `MainActivity.finishCreating()`: ```java /* if (!DeviceCertificateManager.hasDeviceCert(this)) { Executors.newSingleThreadExecutor().execute(() -> PasAttestation.runOnce(MainActivity.this)); } */ ``` The current `PasAttestation.runOnce()` implements the old automatic Platform Issuing CA flow (no enrollment secret, no operator CA). This code will be refactored when operator PAS enrollment is implemented. The new flow will: 1. Be triggered by a dedicated "Enroll Device" activity (not automatic on first run). 2. Prompt the user for the enrollment secret provided by their device administrator. 3. Submit the CSR + Bootstrap JWT + enrollment secret to the Operator's PAS endpoint. 4. Receive a Device Enrollment Certificate signed by the Operator's System CA. 5. Store it via `DeviceCertificateManager`. ### 10.7 PAS API (Target Design) **Endpoint**: `POST /v1/device-enroll` **Request headers**: ``` Authorization: Bearer Content-Type: application/json ``` **Request body**: ```json { "csr": "", "deviceUuid": "<16-hex-char uuid>", "enrollmentSecret": "", "buildVersion": "", "buildFingerprint": "" } ``` **Response** (201 Created): ```json { "certificate": "", "certificateChain": "", "deviceUuid": "", "notBefore": "", "notAfter": "" } ``` --- ## 11. Key and Certificate Lifecycle ### 11.1 Key Generation Requirements | Key | Algorithm | Min Size | Environment | |---|---|---|---| | Platform Root CA | RSA | 4096 bits | Air-gapped (Tails + Yubikey) | | Platform Issuing CA | RSA | 4096 bits | Air-gapped, imported to Vault | | System CA Signing CA | RSA | 4096 bits | Air-gapped | | System CA | RSA | 4096 bits | Operator offline workstation | | Server Identity | RSA | 2048 bits | Server provisioning | | Backend Server | RSA | 2048 bits | Server provisioning | | Device | RSA-2048 (API 23+) or software RSA-2048 (API 19-22) | 2048 bits | Android Keystore (preferred) or app files dir | ### 11.2 Certificate Validity | Certificate | Validity | Renewal Trigger | |---|---|---| | Platform Root CA | 20 years | Manual ceremony 2 years before expiry | | Platform Issuing CA | 5 years | Manual ceremony 6 months before expiry | | System CA Signing CA | 5 years | Manual ceremony 6 months before expiry | | System CA | 10 years | New System provisioning (key rotation = new System ID) | | Server Identity | 2 years | Automated renewal 30 days before expiry | | Backend Server | 2 years | Automated renewal 30 days before expiry | | Device Enrollment Cert | 2 years | Re-enrollment via PAS 30 days before expiry | | Bootstrap JWT | 2 years | New APK release cycle | ### 11.3 Bootstrap JWT Rotation A new Bootstrap JWT is generated for each build by `set-svrauth-buildconfig.sh`. The JWT expires 2 years from build time (`iat + 63072000` seconds). When a JWT expires, the ptt-server will reject connections from that APK version. This forces users to upgrade. To revoke a specific JWT before expiry (e.g., security incident), the ptt-server should be configured with a denylist of `buildVersion` / `buildFingerprint` values. --- ## 12. Build Pipeline ### 12.1 `set-svrauth-buildconfig.sh` This script is the integration point between the Vault PKI infrastructure and the Android build. It is run before each release build and: 1. Authenticates to Vault (token or AppRole). 2. Fetches the Platform Issuing CA certificate from `pki-platform-issuing/cert/ca`. 3. Fetches the System CA certificate from `pki-system-ca/cert/ca`. 4. Fetches the System CA Signing CA certificate from Vault KV v2 at `secret/data/system-ca-signing-ca` (key: `certificate`). 5. Generates a Bootstrap JWT payload with `iss`, `sub`, `buildVersion`, `buildFingerprint`, `iat`, `exp`. 6. Signs the JWT via Vault Transit (`POST /v1/transit/sign/platform-issuing-ca`, prehashed RS256). 7. Validates the JWT structure and certificate PEMs. 8. Writes to `certs.properties`: - `PAS_PLATFORM_ISSUING_CA_CERT_PEM_B64URL` - `PAS_SYSTEM_CA_CERT_PEM_B64URL` - `PAS_SYSTEM_CA_SIGNING_CA_CERT_PEM_B64URL` - `PAS_BOOTSTRAP_CERT_JWT` 9. Writes to `app/src/main/res/raw/`: - `platform_issuing_ca.crt` - `bootstrap_cert.jwt` ### 12.2 Asset Layout in APK ``` assets/ ├── certs/ │ ├── platform_issuing_ca.crt ← Platform Issuing CA (for trust) │ ├── system_ca_signing_ca.crt ← System CA Signing CA (trust anchor for import) │ └── system_ca.crt ← Bundled System CA (primary system) └── keys/ └── bootstrap_cert.jwt ← Bootstrap JWT (RS256, Platform Issuing CA) res/raw/ ├── platform_issuing_ca.crt ← Duplicate for legacy res/raw access └── bootstrap_cert.jwt ← Duplicate for legacy res/raw access ``` `PasAttestation.loadBootstrapJwt()` tries `assets/keys/bootstrap_cert.jwt` first, then `assets/bootstrap_cert.jwt`. ### 12.3 Environment Variables | Variable | Description | |---|---| | `VAULT_ADDR` | Vault server address | | `VAULT_TOKEN` | Vault token (local dev) | | `VAULT_APPROLE_ID` | AppRole role ID (CI) | | `VAULT_APPROLE_SECRET` | AppRole secret ID (CI) | | `APP_VERSION` | App version string (e.g. `1.0.0`) | | `BUILD_FINGERPRINT` | APK signing cert SHA-256 (e.g. `SHA256:3a2f...`) | --- ## 13. Threat Model ### 13.1 Threat Matrix | Threat | Controls | Residual Risk | |---|---|---| | Repackaged or fake APK | Bootstrap JWT validation (ptt-server rejects JWT-less or fake-signed connections); Layer 2 CLIENT_PROOF (device private key in Android Keystore, cannot be extracted) | Low — attacker cannot forge a valid Bootstrap JWT without the Platform Issuing CA private key | | Rogue ptt-server | Layer 2 SERVER_PROOF (server must have System CA-issued Server Identity Certificate and sign with the corresponding private key); client validates cert chain against bundled System CA | Low — attacker cannot forge a valid Server Identity cert without the System CA private key | | Rogue System CA (import) | `SystemCaImporter.verifyAndParseCaCert()` requires the candidate System CA to be signed by the bundled System CA Signing CA | Low — reduces to System CA Signing CA compromise | | Compromised reverse proxy TLS certificate | Layer 2 SERVER_PROOF — even with a valid TLS cert, the attacker cannot sign the nonce with the server's System CA-issued key | Low — Layer 2 is independent of Layer 1 CA | | Expired Bootstrap JWT | Clients automatically fail JWT validation; users must upgrade | Medium — depends on server enforcement and APK update rollout | | System CA private key extracted | All component certs in that System are compromised; attacker can issue rogue server certs | Medium — scoped to one System; mitigated by offline key storage | | Unauthorized device connecting to a private system | PAS Device Enrollment Certificate validation (if operator enables PAS) | Medium without PAS; Low with PAS | | MITM attack | Layer 1 TLS encrypts channel; Layer 2 nonce proves identity of both endpoints | Negligible — both layers must be bypassed simultaneously | | Device UUID collision | 64-bit identifier; collision probability at 1M devices ≈ 1 in 37M | Negligible — UUID is a lookup key only; the public key is the cryptographic identity | ### 13.2 Bootstrap JWT Compromise If the Platform Issuing CA private key (Vault Transit key) is extracted: - An attacker can generate valid Bootstrap JWTs for any `buildVersion`/`buildFingerprint`. - This defeats supply chain attestation. - Mitigations: Vault audit logging detects anomalous signing activity; Vault HSM seal protects the key; the Vault server is not publicly accessible. - Recovery: Emergency re-key of Platform Issuing CA; distribute new Platform Issuing CA cert to all ptt-servers; issue new Bootstrap JWTs in next APK build. ### 13.3 Blast Radius Summary ``` Platform Root CA compromise └── CRITICAL: Can re-issue Platform Issuing CA, invalidate all trust Recovery: Emergency ceremony, full re-provisioning of all CAs Platform Issuing CA key compromise (Vault Transit) └── HIGH: Attacker can sign arbitrary Bootstrap JWTs Recovery: Re-key Vault Transit; revoke Platform Issuing CA via Root CA CRL; re-issue Platform Issuing CA; all ptt-servers need new cert System CA Signing CA compromise └── HIGH: Attacker can issue rogue System CAs that pttclient will import Recovery: Revoke System CA Signing CA via Platform Issuing CA CRL; re-issue System CA Signing CA; rebuild all APKs with new cert; revoke all System CAs and re-issue under new System CA Signing CA System CA compromise [single system] └── MEDIUM: Attacker can forge Server Identity certs for that System Recovery: Revoke System CA via System CA Signing CA CRL; re-provision System with new System CA (new System ID); re-build APK or push new System CA via QR import Bootstrap JWT expired (all devices, one version) └── LOW: Devices running old version cannot connect Recovery: Release new APK version with new Bootstrap JWT ``` --- ## 14. Document Relationships | Document | Status | Description | |---|---|---| | `PKI-PTT-ARCH-001.md` (this document) | Current | Authoritative PKI architecture | | `CONN-AUTH-001.md` | Current | Two-layer connection authentication detail | | `BUILD-PIPELINE-001.md` | Current | Build pipeline and Vault integration | | `OPERATOR-ENROLLMENT-001.md` | Current | PAS operator device enrollment | | `PKI-CA-OPERATIONS-001.md` | Current | CA key ceremonies and operational procedures | | `PKI-VAULT-CONFIG-001.md` | Current | Vault PKI and Transit configuration | | `PKI-OPERATOR-GUIDE-001.md` | Current | Operator System CA setup and Vault configuration | | `BIDIRECTIONAL_NONCE_EXCHANGE.md` | Superseded by CONN-AUTH-001 | Original nonce exchange design | | `SCS-PTTCLIENT-PAS-INTEGRATION.md` | Partially superseded | Original PAS integration spec; PAS section superseded by OPERATOR-ENROLLMENT-001 | | `PKI-PTT-ARCH-001.md` v1.2.0 | Superseded | Previous architecture document | --- *End of Document — PKI-PTT-ARCH-001 v2.0.0*