Skip to content

Latest commit

 

History

History
701 lines (528 loc) · 20.2 KB

File metadata and controls

701 lines (528 loc) · 20.2 KB

Android SDK Testing with sirosid-dev

Overview

The sirosid-dev environment provides complete support for testing the siros-sdk-kotlin sample app with all WSCD plugins enabled (softkey, R2PS, FIDO2) against a running conformance suite.

Quick Start (Android SDK Testing)

cd sirosid-dev

# Step 1: Start full environment
make up VC=yes CONFORMANCE=yes

# Step 2 (Optional): Use a custom app package name
# make android-setup APP_PACKAGE=com.example.myapp

# Step 3: Generate Android configuration
make android-setup

# Step 4: Verify APK key hash was generated
cat .env.android
# Should show: APK_KEY_HASH=xAfybcO0yPlUA_Gko2tH6oIRb9Y-qa3uVtm0m_qW0w

# Step 5: Start Android helpers
make android-up

# Step 6: Install sample app APK and run conformance tests
adb install -r ../siros-sdk-kotlin/app/build/outputs/apk/debug/app-debug.apk

# Step 7: Send test credential offer
make android-launch

Prerequisites

Host System

  • Docker & Docker Compose
  • adb (Android Debug Bridge) for device communication
  • keytool for keystore inspection
  • Waydroid or Android emulator running

Android Device / Emulator

  • Android 11+ (API 30+)
  • Network access to host at 192.168.240.1
  • Development mode enabled
  • USB debugging enabled (if physical device)

Certificates

  • Conformance suite TLS certificate installed in Android CA store
  • App's APK signed with debug.keystore (created during build)

Custom Package Name Configuration

By default, the sample app uses org.siros.sdk.sample. To test with different package names:

# Run setup with a custom package name
make android-setup APP_PACKAGE=com.example.customwallet

# Verify in assetlinks.json
cat .well-known/assetlinks.json | jq '.[] | .target.package_name'
# Output: "com.example.customwallet"

# Build sample app with the matching package name
cd ../siros-sdk-kotlin
./gradlew build

# Deploy and test
adb install -r app/build/outputs/apk/debug/app-debug.apk

Why This Matters:

  • App Link verification requires package name match
  • Credential offers with deep links require correct package
  • Each package name variant needs separate APK signing cert
  • Test multiple wallet implementations side-by-side

Configuration Details

What make android-setup Does

# 1. Extracts APK key hash from ~/.android/debug.keystore
keytool -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore \
  -storepass android -keypass android | grep "SHA256:"
# Output: SHA256: C4 07 F2 6D C3 B4 C8 F9 54 03 F1 A4 A3 6B 47 EA 82 11 6F D6 3E A9 AD EE 56 D9 B4 9B FA 96 D3 B4

# 2. Converts hex format to base64 (Android's assetlinks.json format)
# C4:07:F2:6D:C3:B4:C8:F9:54:03:F1:A4:A3:6B:47:EA:82:11:6F:D6:3E:A9:AD:EE:56:D9:B4:9B:FA:96:D3:B4
#   →  xAfybcO0yPlUA_Gko2tH6oIRb9Y-qa3uVtm0m_qW0w

# 3. Generates .well-known/assetlinks.json
cat > .well-known/assetlinks.json <<EOF
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "org.siros.sdk.sample",
    "sha256_cert_fingerprints": ["xAfybcO0yPlUA_Gko2tH6oIRb9Y-qa3uVtm0m_qW0w"]
  }
}]
EOF

# 4. Creates .env.android for container environment
cat > .env.android <<EOF
APK_KEY_HASH=xAfybcO0yPlUA_Gko2tH6oIRb9Y-qa3uVtm0m_qW0w
EOF

Why This Matters

  • App Link Verification: Android requires matching certificate signatures for deep links
  • Credential Offer Handling: When issuer sends openid-credential-offer:// deep link, Android verifies:
    1. URL domain matches app's URL schemes
    2. Domain's assetlinks.json matches APK's signing certificate
    3. Only then does it launch the app with the credential offer

Architecture: Waydroid vs Physical Device

Option 1: Waydroid (Recommended for CI/CD)

# Waydroid runs Android in a container alongside docker-compose services
# All services accessible at 192.168.240.1 from Waydroid's perspective

docker-compose -f docker-compose.android.yml up -d
waydroid shell  # Opens shell in Waydroid environment
adb devices     # Lists connected Waydroid instance

Advantages:

  • No physical device required
  • Fast iteration
  • Reproducible in CI

Limitations:

  • May be slower than native
  • Requires adequate RAM

Option 2: Physical Device via USB (Recommended for hardware testing)

# Connect device via USB, ensure USB debugging is enabled and authorized
adb devices    # Verify device appears

# Step 1: Start full environment
make up VC=yes CONFORMANCE=yes

# Step 2: Set up USB device (port forwarding + assetlinks + config)
make usb-android-setup

# Step 3: Start USB Android overlay
make usb-android-up

# Step 4: Install and launch sample app
adb install -r ../siros-sdk-kotlin/sample-app/build/outputs/apk/debug/sample-app-debug.apk
make usb-android-launch

How it works:

  • Uses adb reverse to forward ports from the device's localhost to the host machine
  • Device accesses all services (backend, frontend, OIDC, etc.) via localhost:PORT
  • No special network gateway needed — works over USB regardless of Wi-Fi/network setup

Port forwarding (automatic via make usb-android-setup):

# These ports are forwarded (device localhost → host localhost):
# 3000  - wallet-frontend
# 8080  - wallet-backend
# 8081  - admin API
# 8082  - wallet-engine
# 9000  - VC issuer
# 9001  - VC verifier
# 9003  - VC API gateway
# 9004  - VC registry
# 9005  - mini-oidc
# 9011  - mock verifier
# 9081  - mock PDP
# 9095  - go-trust allow

Multi-device support (when multiple devices connected):

# List devices
adb devices
# List of devices attached
# ABC123DEF456     device
# XYZ789GHI012     device

# Target specific device
ADB_SERIAL=ABC123DEF456 make usb-android-setup
ADB_SERIAL=ABC123DEF456 make usb-android-launch

Advantages:

  • Real hardware capabilities (NFC, biometrics, secure element)
  • Accurate performance characteristics
  • Test FIDO2 plugin with actual YubiKey via NFC/USB
  • Works without Wi-Fi — USB-only connectivity

Limitations:

  • Requires physical device
  • Port forwarding must be re-established if ADB disconnects

Running Conformance Tests on USB Device

# Full automated conformance run
node run-android-usb-conformance.mjs --plan vci

# With specific device
node run-android-usb-conformance.mjs --plan all --serial ABC123DEF456

# Check device status
make usb-android-status

Waydroid ↔ USB Quick Reference

Task Waydroid USB Device
Start overlay make android-up make usb-android-up
Stop overlay make android-down make usb-android-down
Setup make android-setup make usb-android-setup
Launch app make android-launch make usb-android-launch
View logs make android-logs make usb-android-logs
Full flow make android-full make usb-android-full
Conformance run-android-conformance.mjs run-android-usb-conformance.mjs
Device shell waydroid shell adb shell
Host address 192.168.240.1 localhost (via adb reverse)

WSCD Plugins Configuration

The sample app selects one of three signing backends at runtime via WalletViewModel.selectPlugin("softkey" | "r2ps" | "fido2") (see the sample app's Settings/WSCA developer screen, or drive it directly via the config WSCA_TEST action documented below). There is no build-time or shell-env-var switch — export R2PS_ENABLED=true has no effect; that name is only a Gradle buildConfigField baked in at compile time (sample-app/build.gradle.kts, currently false in both build types) used purely as the initial value before the user/test picks a plugin at runtime.

All three plugins sit behind siros-sdk-keystore's WscdManager interface (SirosWallet.wscdManager, non-null only for a WSCD-backed keystore):

1. softkey (Default)

The default KeystoreManager — a JWE-encrypted container backed by the Android Keystore. No WscdManager registration needed; this is what you get without calling either register*Plugin method below.

When to use: Development, testing without HSM Security: Medium (encrypted locally, no attestation)

2. r2ps (Remote PAKE-Protected Signing)

val config = R2psConfig(
    serverUrl = "http://192.168.240.1:9443", // or :8443 without conformance
    clientId = "sdk-test",
    // ...clientKeyPem / serverPublicKeyPem / authMode - see
    // sdk/keystore/src/main/kotlin/org/siros/sdk/keystore/WscdManager.kt
)
wscdManager.registerR2psPlugin(config, OkHttpR2psTransport(config.serverUrl))

All CBOR/OPAQUE protocol handling happens in Rust (siros-wscd-manager, exposed via UniFFI) — the Kotlin side only supplies an HTTP transport and connection parameters.

When to use: Production simulation, HSM-backed keys, audit trails Security: High (keys never leave HSM, PAKE authentication)

Environment Setup:

# Start with R2PS overlay
make up R2PS=yes

# Toggle the plugin and server URL at runtime (see "Available Actions" below)
adb shell am start -n org.siros.sdk.sample/.MainActivity \
  -a org.siros.sdk.sample.WSCA_TEST --es wsca_action config \
  --ez r2ps_enabled true --es r2ps_url http://192.168.240.1:9443

# View R2PS status
curl -s http://localhost:8444/admin/store/keys | jq .

3. fido2 (YubiKey previewSign/rawSign)

wscdManager.registerFido2Plugin(transport) // transport: Ctap2TransportProvider

CTAP2 CBOR request-building/response-parsing happens in Rust; the Kotlin Ctap2TransportProvider only moves raw command/response bytes over USB/BLE/NFC.

When to use: Hardware-backed signing, high security requirements Security: Highest (keys on hardware device, user-verified)

Prerequisites:

  • YubiKey 5 or later with FIDO2
  • NFC/USB connection to Android device

Test Flow: Complete Credential Issuance

1. Start environment with all plugins:
   make up VC=yes CONFORMANCE=yes
   make android-setup

2. Deploy sample app, then enable R2PS + set its server URL at runtime
   (Settings/WSCA developer screen, or the `config` WSCA_TEST action -
   see "WSCD Plugins Configuration" above):
   ./gradlew installDebug
   adb shell am start -n org.siros.sdk.sample/.MainActivity \
     -a org.siros.sdk.sample.WSCA_TEST --es wsca_action config \
     --ez r2ps_enabled true --es r2ps_url http://192.168.240.1:9443

3. Initiate credential offer from conformance suite:
   - Navigate to https://localhost.emobix.co.uk:8443/
   - Create OID4VCI wallet test plan
   - Conformance suite sends: openid-credential-offer://...

4. Android app receives offer:
   - App Link verification succeeds (assetlinks.json matches APK)
   - App launched with credential offer URI
   - SDK parses offer and discovers issuer metadata

5. Wallet shows credential details:
   - User confirms credential acceptance
   - SDK selects signing backend:
     a) plugin-softkey: uses local key (default)
     b) plugin-r2ps: contacts R2PS server, registers, authenticates, generates key, signs
     c) plugin-fido2: prompts YubiKey NFC/USB for signature

6. Backend receives credential request:
   - Verifies signature using public key
   - Issues credential (SD-JWT, mdoc, etc.)

7. Conformance suite validates:
   - Receives issued credential
   - Checks format, signatures, expiration
   - Marks test module PASSED or FAILED

WSCA Lifecycle Test Automation

Architecture

The WSCA lifecycle test automation uses ADB intents to drive real WSCA/WSCD operations on the sample app. This is not a mock or backend bypass — all actions dispatch to the real ViewModel methods, using the same code path as the UI buttons.

┌─────────────────┐    adb intent    ┌────────────────────────┐
│  sirosid-tests  │ ──────────────► │  Sample App (debug)     │
│  Playwright +   │                  │  MainActivity           │
│  wsca-automation│                  │   └─ dispatchWscaTest() │
│                 │                  │       └─ ViewModel.*()  │
│                 │ ◄────────────── │         └─ logcat JSON   │
│  (polls logcat) │  WSCA_TEST_RESULT│                          │
└─────────────────┘                  └────────────────────────┘

Key design decisions:

  • Intent action: org.siros.sdk.sample.WSCA_TEST
  • Only available in debug builds (via src/debug/AndroidManifest.xml)
  • Results as structured JSON on logcat tag WSCA_TEST_RESULT
  • ensureAuthenticatedForTesting() auto-handles passkey registration/login

Available Actions

Action Description Extra Params
enroll Register + activate lifecycle —
rotate Rotate lifecycle keys —
destroy Destroy lifecycle context mode: local, revoke, strict
status Query lifecycle state + key inventory —
config Configure WSCA plugin settings r2ps_enabled, r2ps_url
refresh Refresh WSCD info display —

Running Tests

From the sirosid-tests directory:

# Run all WSCA lifecycle tests (softkey plugin)
make test-wsca-softkey

# Run with R2PS plugin (requires running R2PS service)
make test-wsca-r2ps R2PS_URL=http://192.168.240.1:9443

# Run both plugins
make test-wsca

# Or directly with playwright
npx playwright test specs/conformance/wsca-lifecycle-android.spec.ts

Physical Device Setup

# 1. Connect device via USB
adb devices  # Verify device appears

# 2. Start environment with R2PS support
cd sirosid-dev
make up VC=yes R2PS=yes
make usb-android-setup

# 3. Build and install debug APK
cd ../siros-sdk-kotlin
./gradlew :sample-app:assembleDebug
adb install -r sample-app/build/outputs/apk/debug/sample-app-debug.apk

# 4. Run WSCA tests targeting the physical device
cd ../sirosid-tests
ANDROID_DEVICE_SERIAL=<serial> make test-wsca-softkey

Environment Variables

Variable Default Description
ANDROID_WALLET_PACKAGE org.siros.sdk.sample App package name
ANDROID_WALLET_ACTIVITY .MainActivity Activity class
ADB_PATH adb Path to adb binary
ANDROID_DEVICE_SERIAL (auto) Target specific device
R2PS_URL — R2PS server URL (enables R2PS tests)

Manual ADB Testing

You can also drive WSCA actions manually for debugging:

# Enroll
adb shell am start -n org.siros.sdk.sample/.MainActivity \
  -a org.siros.sdk.sample.WSCA_TEST \
  --es wsca_action enroll

# Check result
adb logcat -d -s WSCA_TEST_RESULT:*

# Status
adb shell am start -n org.siros.sdk.sample/.MainActivity \
  -a org.siros.sdk.sample.WSCA_TEST \
  --es wsca_action status

# Destroy with revoke
adb shell am start -n org.siros.sdk.sample/.MainActivity \
  -a org.siros.sdk.sample.WSCA_TEST \
  --es wsca_action destroy --es mode revoke

Test Coverage

The conformance spec tests the following lifecycle flows per plugin:

  1. Configure → set plugin (softkey/r2ps)
  2. Enroll → verify Active state
  3. Status → verify keys present
  4. Rotate → verify still Active
  5. Status → verify keys after rotation
  6. Destroy (local) → verify Destroyed state
  7. Re-enroll → verify recovery works
  8. Destroy (revoke) → verify with server-side revocation

Plus an independent Full Lifecycle Cycle test: enroll → rotate × 2 → destroy.


Debugging Android Tests

Check App Logs

# Stream app logcat
adb logcat | grep siros

# Save to file for analysis
adb logcat > android-test.log &

# Specific tag filtering
adb logcat | grep "siros-wscd-manager"

# WSCA test results specifically
adb logcat -s WSCA_TEST_RESULT:*

Verify Certificate Installation

# On device/Waydroid
adb shell

# Inside shell:
ls -la /system/etc/security/cacerts/
# Should include conformance suite CA cert

# Test HTTPS connection
curl -v https://localhost.emobix.co.uk:8443/ 2>&1 | grep "SSL"

Check R2PS Connectivity

# From Android device/Waydroid
adb shell curl -v http://192.168.240.1:9443/admin/store/keys

# Should return list of provisioned keys

View Backend Audit Logs

# Backend logs all wallet operations via SET framework
docker logs wallet-backend-e2e-test --follow | grep "Event"

# Look for audit events like:
# EventWICreated, EventWIDeactivated
# EventInviteCreated, EventInviteUpdated

Network Debugging

# If device cannot reach host:
# 1. Verify host IP from device perspective
adb shell getprop ro.hardware | grep -q emulator && \
  BRIDGE_IP=$(hostname -I | awk '{print $1}') || \
  BRIDGE_IP=192.168.1.X

# 2. Test connectivity
adb shell ping -c 1 192.168.240.1

# 3. Port forwarding (if on separate network)
adb reverse tcp:8080 tcp:8080
adb reverse tcp:8443 tcp:8443

Integration with CI/CD

GitHub Actions Workflow Example

name: Android Conformance Tests

on: [push, pull_request]

jobs:
  android-conformance:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Start environment
        run: |
          cd sirosid-dev
          make up VC=yes CONFORMANCE=yes
          make android-setup

      - name: Build sample app
        run: |
          cd ../siros-sdk-kotlin
          ./gradlew build

      - name: Run conformance tests
        run: |
          cd ../sirosid-dev
          make test-wallet  # Via Playwright in Waydroid

Known Issues & Workarounds

Issue: assetlinks.json Returns 404

Symptom: App Link verification fails, deep link doesn't launch app

Solution:

# Verify file exists and is served
curl -s http://localhost:3000/.well-known/assetlinks.json | jq

# Check nginx config serves it
docker exec wallet-proxy-e2e curl -s http://wallet-frontend:3000/.well-known/assetlinks.json

# Regenerate
make android-setup

Issue: R2PS Connection Refused

Symptom: Plugin fails with "connection refused" to R2PS

Solution:

# Check R2PS container is running
docker ps | grep r2ps

# Verify port is exposed (should be 9443 with conformance)
docker port r2ps-server | grep 8443

# From device, test connectivity
adb shell curl -v http://192.168.240.1:9443/admin/store/keys

Issue: Credential Signing Fails (All Plugins)

Symptom: "Sign operation failed" error in app

Debugging:

# Check backend logs for key lookup errors
docker logs wallet-backend-e2e-test | grep -i "key\|sign"

# Verify key exists in WSCD
adb shell  # On device
java -cp /system/app/siros-wscd-manager/siros-wscd-manager.jar \
  org.sirosfoundation.wscd.ListKeys

# Check R2PS key inventory
curl -s http://r2ps-server:8443/admin/store/keys | jq '.[] | .kid'

Advanced: Custom Test Scenarios

Test 1: Verify All Three Plugins Work

# In sample app, modify test to cycle through plugins:
val plugins = listOf(
  SoftkeyPlugin(),
  R2psPlugin(...),
  PreviewSignPlugin()
)

for (plugin in plugins) {
  val key = plugin.createKey(...)
  val signature = plugin.sign(key, testData)
  assert(verifySignature(signature)) { "Plugin ${plugin.name} failed" }
}

Test 2: Measure Signing Performance

val start = SystemClock.elapsedRealtime()
val signature = plugin.sign(keyId, data)
val elapsed = SystemClock.elapsedRealtime() - start
println("Sign time: ${elapsed}ms")

Test 3: Concurrent Signings (Stress Test)

// Each plugin has different concurrency limits
// SoftkeyPlugin: unlimited (local)
// R2psPlugin: limited by R2PS session pool
// PreviewSignPlugin: 1 at a time (FIDO2 device)

val jobs = (1..100).map { i ->
  launchAsync {
    plugin.sign(keyId, data)
  }
}
jobs.awaitAll()

Support & Diagnostics

# Full environment status
make status

# Show all running containers
docker ps -a

# Deep dive: specific service logs
docker logs <service> --follow

# Network inspection
docker network ls
docker network inspect sirosid-dev_default

# Database queries (VC)
docker exec -it conformance-suite-mongodb mongo

# Database queries (Wallet)
docker exec -it wallet-db psql -U wallet -c "SELECT * FROM wallet_instance;"

Last Updated: 2026-08-07 Sample App: siros-sdk-kotlin, main (org.siros.sdk.sample) WSCD Manager: siros-wscd-manager, main — UniFFI/Rust-backed WscdManager plugins (softkey, r2ps, fido2); check Cargo.toml for the exact pinned version Test Automation: sirosid-tests, main (WSCA lifecycle tests)