Sicurezza con protocolli di rete

Le interazioni client-server criptate utilizzano Transport Layer Security (TLS) per proteggere i dati della tua app.

Questo articolo illustra le best practice relative ai protocolli di rete sicuri e alle considerazioni sulla Public-Key Infrastructure (PKI). Per maggiori dettagli, leggi la Panoramica della sicurezza di Android e la Panoramica delle autorizzazioni.

Concetti

Un server con un certificato TLS ha una chiave pubblica e una chiave privata corrispondente. Il server utilizza la crittografia a chiave pubblica per firmare il certificato durante l'handshake TLS.

Un semplice handshake dimostra solo che il server conosce la chiave privata del certificato. Per risolvere questa situazione, consenti al client di considerare attendibili più certificati. Un determinato server non è attendibile se il relativo certificato non viene visualizzato nell'insieme di certificati attendibili lato client.

Tuttavia, i server potrebbero utilizzare la rotazione delle chiavi per sostituire la chiave pubblica del loro certificato con una nuova. La modifica della configurazione del server richiede l'aggiornamento dell'app client. Se il server è un servizio web di terze parti, ad esempio un browser web o un'app email, è più difficile sapere quando aggiornare l'app client.

I server in genere si basano sui certificati delle autorità di certificazione (CA) per emettere certificati, il che mantiene la configurazione lato client più stabile nel tempo. Un'autorità di certificazione firma un certificato del server utilizzando la propria chiave privata. Il client può quindi verificare che il server abbia un certificato CA noto alla piattaforma.

Le CA attendibili sono in genere elencate sulla piattaforma dell'host. Android 8.0 (livello API 26) include oltre 100 CA che vengono aggiornati in ogni versione e non cambiano da un dispositivo all'altro.

Le app client hanno bisogno di un meccanismo per verificare il server perché l'autorità di certificazione offre certificati per numerosi server. Il certificato della CA identifica il server utilizzando un nome specifico, come gmail.com, o un carattere jolly, come *.google.com.

Per visualizzare le informazioni del certificato del server di un sito web, utilizza il comando s_client dello strumento openssl, passando il numero di porta. Per impostazione predefinita, HTTPS utilizza la porta 443.

Il comando trasmette l'output di openssl s_client a openssl x509, che formatta le informazioni del certificato in standard X.509. Il comando richiede l'argomento (nome del server) e l'emittente (CA).

openssl s_client -connect WEBSITE-URL:443 | \
  openssl x509 -noout -subject -issuer

Un esempio di HTTPS

Supponendo di avere un server web con un certificato emesso da una CA nota, puoi effettuare una richiesta sicura come mostrato nel seguente codice:

Kotlin

val url = URL("https://wikipedia.org")
val urlConnection: URLConnection = url.openConnection()
val inputStream: InputStream = urlConnection.getInputStream()
copyInputStreamToOutputStream(inputStream, System.out)

Java

URL url = new URL("https://wikipedia.org");
URLConnection urlConnection = url.openConnection();
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);

Per personalizzare le richieste HTTP, esegui il casting su HttpURLConnection. La documentazione di Android HttpURLConnection include esempi per la gestione delle intestazioni di richiesta e risposta, la pubblicazione di contenuti, la gestione dei cookie, l'utilizzo di proxy, la memorizzazione nella cache delle risposte e altro ancora. Il framework Android verifica i certificati e i nomi host utilizzando queste API.

Utilizza queste API, se possibile. La sezione seguente illustra i problemi comuni che richiedono soluzioni diverse.

Problemi comuni di verifica dei certificati del server

Supponiamo che, anziché restituire contenuti, getInputStream(), lanci un'eccezione:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
        at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
        at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
        at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
        at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
        at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
        at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
        at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)

Questo può accadere per diversi motivi, tra cui:

  1. La CA che ha emesso il certificato del server non era nota.
  2. Il certificato del server non è stato firmato da un'autorità di certificazione, ma è autofirmato.
  3. Nella configurazione del server manca una CA intermedia.

Le sezioni seguenti descrivono come risolvere questi problemi mantenendo al contempo la connessione al server sicura.

Autorità di certificazione sconosciuta

L'errore SSLHandshakeException si verifica perché il sistema non considera attendibile l'autorità di certificazione. Ciò potrebbe essere dovuto al fatto che hai un certificato di una nuova CA considerata non attendibile da Android o perché la tua app è in esecuzione su una versione precedente senza l'CA. Poiché è privata, una CA è raramente nota. Più spesso, una CA è sconosciuta perché non è una CA pubblica, ma una privata emessa da un'organizzazione come un governo, un'azienda o un istituto scolastico per uso proprio.

Per considerare attendibili le CA personalizzate senza dover modificare il codice dell'app, modifica la Network Security Config.

Attenzione: molti siti web descrivono una soluzione alternativa scadente, ovvero l'installazione di un TrustManager che non fa nulla. In questo modo, i tuoi utenti sono vulnerabili agli attacchi quando utilizzano un hotspot Wi-Fi pubblico, perché un malintenzionato può usare trucchi DNS per inviare il traffico degli utenti tramite un proxy che finge di essere il tuo server. L'utente malintenzionato può quindi registrare password e altri dati personali. Questo funziona perché l'utente malintenzionato può generare un certificato e, senza un TrustManager che ne dimostri che il certificato proviene da una fonte attendibile, non puoi bloccare questo tipo di attacco. Quindi non farlo, nemmeno temporaneamente. Al contrario, fai in modo che l'app consideri attendibile l'emittente del certificato del server.

Certificato server autofirmato

In secondo luogo, SSLHandshakeException potrebbe verificarsi a causa di un certificato autofirmato, che rende il server la propria CA. Si tratta di un'autorità di certificazione sconosciuta, quindi modifica la configurazione di sicurezza di rete dell'applicazione in modo da considerare attendibili i certificati autofirmati.

Autorità di certificazione intermedia mancante

In terzo luogo, SSLHandshakeException si verifica a causa della CA intermedia mancante. Le CA pubbliche raramente firmano i certificati del server. La CA radice firma invece le CA intermedie.

Per ridurre il rischio di compromissione, le CA mantengono la CA radice offline. Tuttavia, i sistemi operativi come Android solitamente si basano direttamente solo sulle CA radice, lasciando un breve intervallo di attendibilità tra il certificato del server, firmato dalla CA intermedia, e il verificatore dei certificati, che riconosce la CA radice.

Per rimuovere questa lacuna di attendibilità, il server invia una catena di certificati dalla CA del server tramite eventuali intermediari a una CA radice attendibile durante l'handshake TLS.

Ad esempio, di seguito è riportata la catena di certificati di mail.google.com visualizzata dal comando openssl s_client:

$ openssl s_client -connect mail.google.com:443
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=mail.google.com
   i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
 1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
   i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
---

Ciò indica che il server invia un certificato per mail.google.com rilasciato dall'autorità di certificazione Thawte SGC, che è un'autorità di certificazione intermedia, e un secondo certificato per l'autorità di certificazione Thawte SGC rilasciato da un'autorità di certificazione Verisign, che è l'autorità di certificazione principale considerata attendibile da Android.

Tuttavia, un server potrebbe non essere configurato per includere l'autorità di certificazione intermedia necessaria. Ad esempio, ecco un server che può causare un errore nei browser Android e eccezioni nelle app per Android:

$ openssl s_client -connect egov.uscis.gov:443
---
Certificate chain
 0 s:/C=US/ST=District Of Columbia/L=Washington/O=U.S. Department of Homeland Security/OU=United States Citizenship and Immigration Services/OU=Terms of use at www.verisign.com/rpa (c)05/CN=egov.uscis.gov
   i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 International Server CA - G3
---

A differenza di una CA sconosciuta o di un certificato del server autofirmato, la maggior parte dei browser desktop non genera un errore durante la comunicazione con questo server. I browser desktop memorizzano nella cache le CA intermedie attendibili. Dopo aver appreso l'esistenza di un'autorità di certificazione intermedia da un sito, un browser non ne avrà più bisogno nella catena di certificati.

Alcuni siti lo fanno intenzionalmente per i server web secondari che forniscono risorse. Per risparmiare larghezza di banda, possono pubblicare la pagina HTML principale da un server con una catena di certificati completa, ma le loro immagini, CSS e JavaScript senza l'autorità di certificazione. Purtroppo, a volte questi server possono fornire un servizio web che stai cercando di raggiungere dalla tua app per Android, che non è così tollerante.

Per risolvere il problema, configura il server in modo da includere la CA intermedia nella catena del server. La maggior parte delle CA fornisce istruzioni su come eseguire questa operazione per i server web comuni.

Avvisi sull'utilizzo diretto di SSLSocket

Finora, gli esempi si sono concentrati su HTTPS con HttpsURLConnection. A volte le app devono utilizzare TLS separatamente da HTTPS. Ad esempio, un'app email potrebbe utilizzare varianti TLS di SMTP, POP3 o IMAP. In questi casi, l'app può utilizzare SSLSocket direttamente, in modo molto simile a come fa HttpsURLConnection internamente.

Le tecniche descritte finora per risolvere i problemi di verifica dei certificati si applicano anche a SSLSocket. Infatti, quando utilizzi un TrustManager personalizzato, a HttpsURLConnection viene passato un SSLSocketFactory. Pertanto, se devi utilizzare un TrustManager personalizzato con un SSLSocket, segui gli stessi passaggi e utilizza il SSLSocketFactory per creare il SSLSocket.

Attenzione: SSLSocket non esegue la verifica del nome host. È compito della tua app eseguire la propria verifica del nome host, preferibilmente chiamando getDefaultHostnameVerifier() con il nome host previsto. Inoltre, tieni presente che HostnameVerifier.verify() non genera un'eccezione in caso di errore. Restituisce invece un risultato booleano che devi controllare esplicitamente.

Convalida del certificato

TLS si basa sulle CA per emettere certificati solo ai proprietari verificati di server e domini. In rari casi, le CA vengono ingannate o, nel caso di Comodo o DigiNotar, vengono violate, con il risultato che i certificati per un nome host vengono emessi a qualcuno diverso dal proprietario del server o del dominio.

Per ridurre questo rischio, Android gestisce la revoca dei certificati a livello di sistema, tramite una combinazione di una lista bloccata e della trasparenza dei certificati, senza fare affidamento sulla verifica dei certificati online. Inoltre, Android convaliderà le risposte OCSP allegate all'handshake TLS.

Per attivare la trasparenza dei certificati nella tua app, consulta la sezione Attivare la trasparenza dei certificati nella documentazione di Network Security Configuration.

Limitare l'app a certificati specifici

Attenzione: il pinning dei certificati, ovvero la pratica di limitare i certificati considerati validi per la tua app a quelli che hai autorizzato in precedenza, non è consigliata per le app Android. Le modifiche future alla configurazione del server, ad esempio il passaggio a un'altra CA, rendono le app con certificati bloccati incapaci di connettersi al server senza ricevere un aggiornamento del software client.

Se vuoi limitare l'app in modo che accetti solo i certificati specificati, è fondamentale includere più PIN di backup, tra cui almeno una chiave completamente sotto il tuo controllo, e un periodo di scadenza sufficientemente breve per evitare problemi di compatibilità. Network Security Config fornisce il pinning con queste funzionalità.

Certificati client

Questo articolo si concentra sull'utilizzo di TLS per proteggere le comunicazioni con i server. TLS supporta anche la nozione di certificati client che consentono al server di convalidare l'identità di un client. Sebbene non rientrino nell'ambito di questo articolo, le tecniche utilizzate sono simili a quelle per specificare un TrustManager personalizzato.

Nogotofail: uno strumento di test della sicurezza del traffico di rete

Nogotofail è uno strumento che ti consente di verificare facilmente che le tue app siano al sicuro da vulnerabilità e configurazioni errate TLS/SSL conosciute. È uno strumento automatico, potente e scalabile per testare i problemi di sicurezza della rete su qualsiasi dispositivo di cui è possibile far passare il traffico di rete.

Nogotofail è utile per tre casi d'uso principali:

  • Trovare bug e vulnerabilità.
  • Verificare le correzioni e monitorare le regressioni.
  • Scopri quali applicazioni e dispositivi generano quale traffico.

Nogotofail funziona su Android, iOS, Linux, Windows, ChromeOS, macOS e su qualsiasi dispositivo che utilizzi per connetterti a internet. È disponibile un client per la configurazione delle impostazioni e la ricezione di notifiche su Android e Linux. Inoltre, il motore di attacco stesso può essere implementato come router, server VPN o proxy.

Puoi accedere allo strumento nel progetto open source Nogotofail.

Aggiornamenti a SSL e TLS

Android 10

Alcuni browser, come Google Chrome, consentono agli utenti di scegliere un certificato quando un server TLS invia un messaggio di richiesta di certificato nell'ambito di un handshake TLS. A partire da Android 10, gli oggetti KeyChain rispettano i parametri di specifica dei certificati e degli emittenti quando viene chiamato KeyChain.choosePrivateKeyAlias() per mostrare agli utenti una richiesta di selezione del certificato. In particolare, questo prompt non contiene scelte che non rispettano le specifiche del server.

Se non sono disponibili certificati selezionabili dall'utente, come nel caso in cui nessun certificato corrisponda alla specifica del server o se su un dispositivo non sono installati certificati, la richiesta di selezione del certificato non viene visualizzata.

Inoltre, su Android 10 o versioni successive non è necessario avere un blocco schermo del dispositivo per importare chiavi o certificati CA in un oggetto KeyChain.

TLS 1.3 abilitato per impostazione predefinita

In Android 10 e versioni successive, TLS 1.3 è attivato per impostazione predefinita per tutte le connessioni TLS. Ecco alcuni dettagli importanti sulla nostra implementazione di TLS 1.3:

  • Le suite di crittografia TLS 1.3 non possono essere personalizzate. Le suite di crittografia TLS 1.3 supportate sono sempre attivate quando TLS 1.3 è attivo. Qualsiasi tentativo di disattivarli chiamando setEnabledCipherSuites() viene ignorato.
  • Quando viene negoziato TLS 1.3, gli oggetti HandshakeCompletedListener vengono chiamati prima che le sessioni vengano aggiunte alla cache delle sessioni. In TLS 1.2 e altre versioni precedenti, questi oggetti vengono chiamati dopo l'aggiunta delle sessioni alla cache delle sessioni.
  • In alcuni casi in cui le istanze SSLEngine generano un SSLHandshakeException su versioni precedenti di Android, su Android 10 e versioni successive generano un SSLProtocolException.
  • La modalità 0-RTT non è supportata.

Se vuoi, puoi ottenere un contesto SSL con TLS 1.3 disabilitato chiamando SSLContext.getInstance("TLSv1.2"). Puoi anche attivare o disattivare le versioni del protocollo in base alla connessione chiamando setEnabledProtocols() su un oggetto appropriato.

I certificati firmati con SHA-1 non sono attendibili in TLS

In Android 10, i certificati che utilizzano l'algoritmo hash SHA-1 non sono attendibili nelle connessioni TLS. Le CA radice non emettono questi certificati dal 2016 e non sono più considerate attendibili in Chrome o in altri browser principali.

Qualsiasi tentativo di connessione non va a buon fine se la connessione è con un sito che presenta un certificato che utilizza SHA-1.

Modifiche e miglioramenti al comportamento di KeyChain

Alcuni browser, come Google Chrome, consentono agli utenti di scegliere un certificato quando un server TLS invia un messaggio di richiesta di certificato nell'ambito di un handshake TLS. A partire da Android 10, gli oggetti KeyChain rispettano i parametri di specifica degli emittenti e delle chiavi quando viene chiamato KeyChain.choosePrivateKeyAlias() per mostrare gli utenti una richiesta di selezione del certificato. In particolare, questo prompt non contiene scelte che non rispettino le specifiche del server.

Se non sono disponibili certificati selezionabili dall'utente, come nel caso in cui nessun certificato corrisponda alla specifica del server o se su un dispositivo non sono installati certificati, la richiesta di selezione del certificato non viene visualizzata.

Inoltre, su Android 10 o versioni successive non è necessario avere un blocco schermo del dispositivo per importare chiavi o certificati CA in un oggetto KeyChain.

Altre modifiche a TLS e crittografia

Sono state apportate diverse modifiche minori alle librerie TLS e di crittografia che vengono applicate su Android 10:

  • Le crittografie AES/GCM/NoPadding e ChaCha20/Poly1305/NoPadding restituiscono dimensioni del buffer più precise da getOutputSize().
  • Il pacchetto di crittografia TLS_FALLBACK_SCSV viene omesso dai tentativi di connessione con un protocollo massimo di TLS 1.2 o versioni successive. A causa dei miglioramenti nelle implementazioni dei server TLS, sconsigliamo di tentare il fallback TLS esterno. Ti consigliamo invece di fare affidamento sulla negoziazione della versione TLS.
  • ChaCha20-Poly1305 è un alias per ChaCha20/Poly1305/NoPadding.
  • I nomi host con punti finali non sono considerati nomi host SNI validi.
  • L'estensione supported_signature_algorithms in CertificateRequest viene rispettata quando si sceglie una chiave di firma per le risposte del certificato.
  • Le chiavi di firma opache, come quelle dell'archivio chiavi Android, possono essere utilizzate con le firme RSA-PSS in TLS.

Modifiche alla connessione HTTPS

Se un'app con Android 10 passa un valore null a setSSLSocketFactory(), si verifica un IllegalArgumentException. Nelle versioni precedenti, l'inserimento di null in setSSLSocketFactory() aveva lo stesso effetto dell'inserimento dell'attuale factory predefinita.

Android 11

Le socket SSL utilizzano per impostazione predefinita il motore SSL Conscrypt

L'implementazione predefinita di SSLSocket di Android si basa su Conscrypt. Da Android 11, questa implementazione è stata creata internamente sulla base di SSLEngine di Conscrypt.