Verificatore chiavi di sistema Android

Key Verifier per Android offre agli utenti un modo unificato e sicuro per verificare di comunicare con la persona giusta nella tua app con crittografia end-to-end (E2EE). Protegge gli utenti dagli attacchi man-in-the-middle consentendo loro di confermare l'autenticità delle chiavi di crittografia pubbliche di un contatto tramite un'interfaccia utente di sistema attendibile e coerente.

Questa funzionalità è fornita da Key Verifier, un servizio di sistema che fa parte di Google System Services e viene distribuito tramite il Play Store. Funge da repository centralizzato sul dispositivo per le chiavi pubbliche E2EE.

Perché eseguire l'integrazione con Key Verifier

  • Fornisci un'esperienza utente unificata:anziché creare un flusso di verifica personalizzato, puoi avviare la UI standard del sistema, offrendo agli utenti un'esperienza coerente e affidabile in tutte le loro app.
  • Aumentare la fiducia degli utenti:uno stato di verifica chiaro e supportato dal sistema assicura agli utenti che le loro conversazioni sono sicure e private.
  • Riduzione del sovraccarico di sviluppo:delega la complessità dell'interfaccia utente di verifica delle chiavi, dell'archiviazione e della gestione dello stato al servizio di sistema.

Termini chiave

  • lookupKey:un identificatore opaco e permanente per un contatto, memorizzato nella colonna LOOKUP_KEY del provider di contatti. A differenza di un contact ID, un lookupKey rimane stabile anche se i dettagli di contatto sottostanti vengono modificati o uniti, il che lo rende il modo consigliato per fare riferimento a un contatto.
  • accountId: un identificatore specifico dell'app per l'account di un utente su un dispositivo. Questo ID è definito dalla tua app e consente di distinguere tra più account che un singolo utente potrebbe avere. Questo valore viene visualizzato all'utente nell'interfaccia utente. È consigliabile utilizzare qualcosa di significativo come un numero di telefono, un indirizzo email o un handle utente.
  • deviceId: un identificatore univoco per un dispositivo specifico associato all'account di un utente. Ciò consente a un utente di avere più dispositivi, ognuno con il proprio set di chiavi crittografiche. Non rappresenta necessariamente un dispositivo fisico, ma potrebbe essere un modo per distinguere tra più chiavi utilizzate per lo stesso account

Per iniziare

Prima di iniziare, configura l'app per comunicare con il servizio Key Verifier.

Dichiara le autorizzazioni:in AndroidManifest.xml, dichiara le seguenti autorizzazioni. Devi anche richiederli all'utente in fase di runtime.

<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />

Recupera l'istanza del client:recupera un'istanza di ContactKeys, che è il tuo punto di accesso all'API.

import com.google.android.gms.contactkeys.ContactKeys

val contactKeyClient = ContactKeys.getClient(context)

Indicazioni per gli sviluppatori di app di messaggistica

In qualità di sviluppatore di app di messaggistica, il tuo ruolo principale è pubblicare le chiavi pubbliche dei tuoi utenti e le chiavi dei loro contatti nel servizio Key Verifier.

Pubblicare le chiavi pubbliche di un utente

Per consentire ad altri di trovare e verificare il tuo utente, pubblica la sua chiave pubblica nel repository sul dispositivo. Per una maggiore sicurezza, valuta la possibilità di creare chiavi in Android Keystore.

import com.google.android.gms.contactkeys.ContactKeyClient
import com.google.android.gms.tasks.Tasks

suspend fun publishSelfKey(
    contactKeyClient: ContactKeyClient,
    accountId: String,
    deviceId: String,
    publicKey: ByteArray
) {
    try {
        Tasks.await(
          contactKeyClient.updateOrInsertE2eeSelfKey(
            deviceId,
            accountId,
            publicKey
          )
        )
        // Self key published successfully.
    } catch (e: Exception) {
        // Handle error.
    }
}

Associare chiavi pubbliche ai contatti

Quando la tua app riceve una chiave pubblica per uno dei contatti dell'utente, devi memorizzarla e associarla a quel contatto nel repository centrale. In questo modo la chiave può essere verificata e altre app possono visualizzare lo stato di verifica per il contatto. Per farlo, devi avere la lookupKey del contatto dal provider di contatti Android. In genere, questo si verifica quando viene recuperata una chiave dal server di distribuzione delle chiavi o durante una sincronizzazione periodica delle chiavi locali.

import com.google.android.gms.contactkeys.ContactKeyClient
import com.google.android.gms.tasks.Tasks

suspend fun storeContactKey(
    contactKeyClient: ContactKeyClient,
    contactLookupKey: String,
    contactAccountId: String,
    contactDeviceId: String,
    contactPublicKey: ByteArray
) {
    try {
        Tasks.await(
            contactKeyClient.updateOrInsertE2eeContactKey(
                contactLookupKey,
                contactDeviceId,
                contactAccountId,
                contactPublicKey
            )
        )
        // Contact's key stored successfully.
    } catch (e: Exception) {
        // Handle error.
    }
}

Recuperare le chiavi e lo stato di verifica

Dopo aver pubblicato le chiavi, gli utenti possono verificarle tramite la scansione del codice QR di persona. La UI della tua app deve riflettere se una conversazione utilizza una chiave verificata. Ogni chiave ha uno stato di verifica che puoi utilizzare per informare la tua UI.

Informazioni sugli stati di verifica:

  • UNVERIFIED: questo è lo stato predefinito per ogni nuova chiave. Significa che la chiave esiste, ma l'utente non ne ha ancora confermato l'autenticità. Nell'interfaccia utente, devi considerarlo uno stato neutro e in genere non visualizzare indicatori speciali.

  • VERIFIED: questo stato indica un elevato livello di attendibilità. Significa che l'utente ha completato correttamente un flusso di verifica (come la scansione di un codice QR) e ha confermato che la chiave appartiene al contatto previsto. Nell'interfaccia utente, devi mostrare un indicatore positivo chiaro, ad esempio un segno di spunta verde o uno scudo.

  • VERIFICATION_FAILED: Questo è uno stato di avviso. Ciò significa che la chiave associata al contatto non corrisponde a quella verificata in precedenza. Ciò può accadere se un contatto acquista un nuovo dispositivo, ma potrebbe anche indicare un potenziale rischio per la sicurezza. Nella tua UI, avvisa l'utente con un avviso ben visibile e suggerisci di eseguire nuovamente la verifica prima di inviare informazioni sensibili.

Puoi recuperare uno stato aggregato per tutte le chiavi associate a un contatto. Ti consigliamo di utilizzare VerificationState.leastVerifiedFrom() per risolvere lo stato quando sono presenti più chiavi, in quanto assegnerà correttamente la priorità a VERIFICATION_FAILED rispetto a VERIFIED.

  • Ottenere lo stato aggregato a livello di contatto
import com.google.android.gms.contactkeys.ContactKeyClient
import com.google.android.gms.contactkeys.constants.VerificationState
import com.google.android.gms.tasks.Tasks

suspend fun displayContactVerificationStatus(
    contactKeyClient: ContactKeyClient,
    contactLookupKey: String
) {
    try {
        val keysResult = Tasks.await(contactKeyClient.getAllE2eeContactKeys(contactLookupKey))
        val states =
          keysResult.keys.map { VerificationState.fromState(it.localVerificationState) }
        val contactStatus = VerificationState.leastVerifiedFrom(states)
        updateUi(contactLookupKey, contactStatus)
    } catch (e: Exception) {
        // Handle error.
    }
}
  • Ottenere lo stato aggregato a livello di account
import com.google.android.gms.contactkeys.ContactKeyClient
import com.google.android.gms.contactkeys.constants.VerificationState
import com.google.android.gms.tasks.Tasks

suspend fun displayAccountVerificationStatus(
    contactKeyClient: ContactKeyClient,
    accountId: String
) {
    try {
        val keys = Tasks.await(contactKeyClient.getE2eeAccountKeysForAccount(accountId))
        val states = keys.map { VerificationState.fromState(it.localVerificationState) }
        val accountStatus = VerificationState.leastVerifiedFrom(states)
        updateUi(accountId, accountStatus)
    } catch (e: Exception) {
        // Handle error.
    }
}

Osservare le modifiche chiave in tempo reale

Per verificare che la UI della tua app mostri sempre lo stato di trust corretto, devi ascoltare gli aggiornamenti. Il modo consigliato è utilizzare l'API basata sul flusso, che genera un nuovo elenco di chiavi ogni volta che una chiave per un account a cui è stato eseguito l'abbonamento viene aggiunta, rimossa o il cui stato di verifica è stato modificato. Questa operazione è particolarmente utile per mantenere aggiornato l'elenco dei membri di una conversazione di gruppo. Lo stato di verifica di una chiave può cambiare quando:

  • L'utente completa correttamente un flusso di verifica (ad esempio, la scansione del codice QR).
  • La chiave di un contatto viene modificata, pertanto non corrisponde più al valore verificato in precedenza.
fun observeKeyUpdates(contactKeyClient: ContactKeyClient, accountIds: List<String>) {
    lifecycleScope.launch {
        contactKeyClient.getAccountContactKeysFlow(accountIds)
            .collect { updatedKeys ->
                // A key was added, removed, or updated.
                // Refresh your app's UI and internal state.
                refreshUi(updatedKeys)
            }
    }
}

Eseguire la verifica della chiave di persona

Il modo più sicuro per gli utenti di verificare una chiave è tramite la verifica di persona, spesso scansionando un codice QR o confrontando una sequenza di numeri. L'app Key Verifier fornisce flussi UI standard per questa procedura, che la tua app può avviare. Dopo un tentativo di verifica, l'API aggiorna automaticamente lo stato di verifica della chiave e la tua app riceverà una notifica se stai osservando gli aggiornamenti della chiave.

  • Avvia la procedura di verifica della chiave per un contatto selezionato dall'utente Avvia PendingIntent fornito da getScanQrCodeIntent utilizzando lookupKey del contatto selezionato. L'interfaccia utente consente all'utente di verificare tutte le chiavi per il contatto specificato.
import android.app.ActivityOptions
import android.app.PendingIntent
import com.google.android.gms.contactkeys.ContactKeyClient
import com.google.android.gms.tasks.Tasks

suspend fun initiateVerification(contactKeyClient: ContactKeyClient, lookupKey: String) {
    try {
        val pendingIntent = Tasks.await(contactKeyClient.getScanQrCodeIntent(lookupKey))
        val options =
          ActivityOptions.makeBasic()
            .setPendingIntentBackgroundActivityStartMode(
              ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
            )
            .toBundle()
        pendingIntent.send(options)
    } catch (e: Exception) {
        // Handle error.
    }
}
  • Avvia la procedura di verifica della chiave per un account selezionato dall'utente Se l'utente vuole verificare un account non collegato direttamente a un contatto (o un account specifico di un contatto), puoi avviare PendingIntent fornito da getScanQrCodeIntentForAccount. In genere, viene utilizzato per il nome del pacchetto e l'ID account della tua app.
import android.app.ActivityOptions
import android.app.PendingIntent
import com.google.android.gms.contactkeys.ContactKeyClient
import com.google.android.gms.tasks.Tasks

suspend fun initiateVerification(contactKeyClient: ContactKeyClient, packageName: String, accountId: String) {
    try {
        val pendingIntent = Tasks.await(contactKeyClient.getScanQrCodeIntentForAccount(packageName, accountId))
        // Allow activity start from background on Android SDK34+
        val options =
          ActivityOptions.makeBasic()
            .setPendingIntentBackgroundActivityStartMode(
              ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
            )
            .toBundle()
        pendingIntent.send(options)
    } catch (e: Exception) {
        // Handle error.
    }
}