# Build Pipeline This document describes how to build and sign the pttclient APK with the correct PKI assets. --- ## Prerequisites - Vault instance with Platform Issuing CA imported into the Transit secrets engine as key `platform-issuing-ca`. - Platform Issuing CA cert accessible at `GET /v1/pki-platform-issuing/cert/ca`. - System CA cert accessible at `GET /v1/pki-system-ca/cert/ca`. - System CA Signing CA cert stored in Vault KV v2 at `secret/data/system-ca-signing-ca` with key `certificate`. - Android SDK and Gradle build environment. - JDK 11+. --- ## Step 1: Inject Build Config Run the injection script from the project root: ```bash export VAULT_ADDR="https://vault.example.com:38200" export VAULT_TOKEN="hvs...." # or: export VAULT_APPROLE_ID="..." && export VAULT_APPROLE_SECRET="..." export APP_VERSION="$(cat version.txt)" # e.g. "1.2.0" export BUILD_FINGERPRINT="SHA256:$(keytool -exportcert \ -keystore keystore/pttclient-release.jks \ -alias pttclient \ -storepass "$KEYSTORE_PASSWORD" \ | openssl dgst -sha256 -hex | awk '{print $2}')" ./set-svrauth-buildconfig.sh ``` This writes to: - `certs.properties` — Gradle reads these as `BuildConfig` fields and raw resource inputs. - `app/src/main/res/raw/platform_issuing_ca.crt` — loaded by `PlatformTrustManager`. - `app/src/main/res/raw/bootstrap_cert.jwt` — loaded by `PasAttestation`. The script also populates: - `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` --- ## Step 2: Place CA Certificates The `assets/certs/` directory in the APK must contain: | Filename pattern | Content | Used by | |---|---|---| | `system_ca.crt` or similar | System CA certificate (PEM) | `PlatformTrustManager`, `ServerTrustManager`, `ServerListManager` | | `platform_issuing_ca.crt` | Platform Issuing CA certificate (PEM) | `PlatformTrustManager` (PAS trust) | | `system_ca_signing_ca.crt` | System CA Signing CA certificate (PEM) | `SystemCaImporter` (validates imported System CAs) | File naming convention: filenames containing `platform_issuing` are excluded from the System CA selection in `ServerListManager`. Filenames containing `system_ca_signing` are used by `SystemCaImporter.loadSystemCaSigningCa()`. These files are typically placed in `app/src/main/assets/certs/` and managed separately from the injection script (they are static per-build assets, not generated at build time). --- ## Step 3: Build the APK ```bash ./gradlew assembleRelease ``` Or for a debug build: ```bash ./gradlew assembleDebug ``` Signing is configured in `app/build.gradle` via the `signingConfigs` block. For release builds, supply keystore credentials via environment variables or `local.properties`. --- ## Step 4: Verify the Build Check that the pre-signed Bootstrap JWT is present in the APK: ```bash unzip -p app/build/outputs/apk/release/app-release.apk \ assets/keys/bootstrap_cert.jwt | head -c 100 ``` Check that CA certificates are present: ```bash unzip -l app/build/outputs/apk/release/app-release.apk | grep 'assets/certs' ``` Verify the JWT payload (base64url decode the middle segment): ```bash JWT=$(unzip -p app/build/outputs/apk/release/app-release.apk assets/keys/bootstrap_cert.jwt) echo $JWT | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool ``` --- ## Bootstrap Certificate Rotation The Bootstrap JWT expires 2 years from build time. When a new major/minor version is released: 1. A new Bootstrap JWT is generated automatically by `set-svrauth-buildconfig.sh` for each build (because `APP_VERSION` and `BUILD_FINGERPRINT` will differ). 2. Old Bootstrap Certificates can be revoked in Vault / PAS if the app version is retired. 3. Devices with existing valid Device Certificates are unaffected by Bootstrap Certificate rotation. --- ## Version and Fingerprint `APP_VERSION` is embedded in the Bootstrap JWT `buildVersion` claim. PAS may use this to enforce minimum app version requirements. `BUILD_FINGERPRINT` is the SHA-256 of the APK signing certificate. PAS can use this (via `buildFingerprint` claim) to reject CSRs from repackaged APKs.