O SSL (do inglês Secure Sockets Layer), agora conhecido tecnicamente como TLS (do inglês Transport Layer Security), é uma base comum para estabelecer comunicações criptografadas entre clientes e servidores. É possível que um aplicativo use o SSL incorretamente de forma que entidades mal-intencionadas consigam interceptar os dados na rede. Para ajudar a garantir que isso não aconteça com seu app, este artigo destaca os riscos comuns ao usar protocolos de rede segura e aborda algumas das maiores preocupações sobre o uso da Infraestrutura de chave pública (ICP).
Leia também a Visão geral de segurança e a Visão geral de permissões do Android.
Conceitos
Em um cenário de uso típico de SSL, um servidor é configurado com um certificado que contém uma chave pública e uma chave privada correspondente. Como parte do handshake entre um cliente e um servidor SSL, o servidor prova que tem a chave privada assinando seu certificado com a criptografia de chave pública.
No entanto, qualquer pessoa pode gerar o próprio certificado e chave privada. Portanto, um simples handshake não prova nada sobre o servidor além do fato de ele ter a chave privada que corresponde à chave pública do certificado. Uma maneira de solucionar esse problema é fazer com que o cliente tenha um conjunto de um ou mais certificados de confiança. Se o certificado não estiver nesse conjunto, o servidor não é confiável.
Essa abordagem simples tem várias desvantagens. Os servidores precisam ser capazes de fazer upgrade para chaves mais fortes ao longo do tempo (“rotação de chaves”), o que substitui a chave pública do certificado por uma nova. Infelizmente, isso faz com que o app do cliente precise ser atualizado devido ao que é, em essência, uma mudança de configuração do servidor. Isso pode ser um problema, especialmente se o servidor não for controlado pelo desenvolvedor do app, por exemplo, se for um serviço da Web de terceiros. Essa abordagem também apresenta problemas se o app precisar se comunicar com servidores arbitrários, como um navegador da Web ou um app de e-mails.
Para solucionar essas desvantagens, os servidores geralmente são configurados com emissores conhecidos, chamados Autoridades de certificação (CAs, na sigla em inglês). A plataforma do host geralmente contém uma lista de CAs conhecidas em que ela confia. A partir do Android 4.2 (Jelly Bean), o Android contém mais de 100 CAs que são atualizadas a cada versão. Semelhante a um servidor, uma CA tem um certificado e uma chave privada. Ao emitir um certificado para um servidor, a CA assina o certificado do servidor usando a chave privada dela. Então, o cliente pode verificar se o servidor tem um certificado emitido por uma CA conhecida pela plataforma.
No entanto, embora solucione alguns problemas, o uso de CAs introduz um novo. Como a CA emite certificados para muitos servidores, você ainda precisa de uma maneira de garantir que está se comunicando com o servidor certo. Para solucionar isso, o certificado emitido pela CA identifica o servidor tanto por um nome específico, como gmail.com, ou por um conjunto de hosts com caracteres curinga, como *.google.com.
O exemplo a seguir tornará esses conceitos um pouco mais concretos. No snippet abaixo, em uma linha de comando, o openssl
da ferramenta de comando s_client
verifica as informações de certificado do servidor da Wikipédia. Ele especifica a porta 443, já que essa é a porta padrão para HTTPS. O comando envia a saída de openssl s_client
para openssl x509
, que formata as informações de certificados de acordo com o padrão X.509. Especificamente, o comando solicita o assunto, que contém as informações do nome do servidor e o emissor, o que identifica a CA.
$ openssl s_client -connect wikipedia.org:443 | openssl x509 -noout -subject -issuer subject= /serialNumber=sOrr2rKpMVP70Z6E9BT5reY008SJEdYv/C=US/O=*.wikipedia.org/OU=GT03314600/OU=See www.rapidssl.com/resources/cps (c)11/OU=Domain Control Validated - RapidSSL(R)/CN=*.wikipedia.org issuer= /C=US/O=GeoTrust, Inc./CN=RapidSSL CA
É possível ver que o certificado foi emitido para servidores que correspondem a *.wikipedia.org pela CA RapidSSL.
Um exemplo de HTTPS
Pressupondo que você tem um servidor da Web com um certificado emitido por uma CA conhecida, é possível fazer uma solicitação segura com um código simples como este:
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);
Sim, pode ser simples assim. Se quiser adaptar a solicitação HTTP, você pode transmiti-la para uma HttpURLConnection
. A documentação do Android para HttpURLConnection
tem mais exemplos sobre como lidar com cabeçalhos de solicitação e resposta, a publicação de conteúdo, o gerenciamento de cookies, o uso de proxies, o armazenamento de respostas em cache e assim por diante. No entanto, em termos de detalhes para a verificação de certificados e nomes de host, o framework do Android cuida de tudo para você por meio dessas APIs.
Essa é a solução ideal, se possível. Dito isso, apresentamos abaixo algumas considerações.
Problemas comuns ao verificar certificados de servidor
Suponha que, em vez de receber o conteúdo de getInputStream()
, ele gere uma exceção:
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)
Isso pode ocorrer por vários motivos, incluindo:
- A CA que emitiu o certificado do servidor era desconhecida
- O certificado do servidor não estava assinado por uma CA, mas sim autoassinado
- A configuração do servidor não incluía uma CA intermediária
As seções a seguir discutem como solucionar esses problemas e manter sua conexão com o servidor segura.
Autoridade de certificação desconhecida
Nesse caso, a SSLHandshakeException
ocorre porque você tem uma CA em que o sistema não confia. Pode ser que você tenha um certificado de uma nova CA em que o Android ainda não confia ou que seu app esteja executando uma versão mais antiga sem a CA. Com frequência, a CA é desconhecida por não ser pública, mas sim uma CA privada emitida por uma organização como o governo, uma corporação ou uma instituição educacional para uso interno.
É possível ensinar a HttpsURLConnection
a confiar em um conjunto específico de CAs. O procedimento pode ser um pouco complicado, então apresentamos abaixo um exemplo que usa uma CA específica de um InputStream
para criar um KeyStore
, que então é usado para criar e inicializar um TrustManager
. Um TrustManager
é usado pelo sistema para validar certificados do servidor e, ao criar um usando KeyStore
com uma ou mais CAs, elas serão as únicas CAs em que o TrustManager
confiará.
Considerando o novo TrustManager
, o exemplo inicializa um novo SSLContext
que fornece um SSLSocketFactory
que pode ser usado para modificar o SSLSocketFactory
padrão em HttpsURLConnection
. Dessa maneira, a conexão usará suas CAs para validar os certificados.
Veja a seguir um exemplo completo usando uma CA organizacional da Universidade de Washington:
Kotlin
// Load CAs from an InputStream // (could be from a resource or ByteArrayInputStream or ...) val cf: CertificateFactory = CertificateFactory.getInstance("X.509") // From https://www.washington.edu/itconnect/security/ca/load-der.crt val caInput: InputStream = BufferedInputStream(FileInputStream("load-der.crt")) val ca: X509Certificate = caInput.use { cf.generateCertificate(it) as X509Certificate } System.out.println("ca=" + ca.subjectDN) // Create a KeyStore containing our trusted CAs val keyStoreType = KeyStore.getDefaultType() val keyStore = KeyStore.getInstance(keyStoreType).apply { load(null, null) setCertificateEntry("ca", ca) } // Create a TrustManager that trusts the CAs inputStream our KeyStore val tmfAlgorithm: String = TrustManagerFactory.getDefaultAlgorithm() val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply { init(keyStore) } // Create an SSLContext that uses our TrustManager val context: SSLContext = SSLContext.getInstance("TLS").apply { init(null, tmf.trustManagers, null) } // Tell the URLConnection to use a SocketFactory from our SSLContext val url = URL("https://certs.cac.washington.edu/CAtest/") val urlConnection = url.openConnection() as HttpsURLConnection urlConnection.sslSocketFactory = context.socketFactory val inputStream: InputStream = urlConnection.inputStream copyInputStreamToOutputStream(inputStream, System.out)
Java
// Load CAs from an InputStream // (could be from a resource or ByteArrayInputStream or ...) CertificateFactory cf = CertificateFactory.getInstance("X.509"); // From https://www.washington.edu/itconnect/security/ca/load-der.crt InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt")); Certificate ca; try { ca = cf.generateCertificate(caInput); System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); } finally { caInput.close(); } // Create a KeyStore containing our trusted CAs String keyStoreType = KeyStore.getDefaultType(); KeyStore keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(null, null); keyStore.setCertificateEntry("ca", ca); // Create a TrustManager that trusts the CAs in our KeyStore String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); tmf.init(keyStore); // Create an SSLContext that uses our TrustManager SSLContext context = SSLContext.getInstance("TLS"); context.init(null, tmf.getTrustManagers(), null); // Tell the URLConnection to use a SocketFactory from our SSLContext URL url = new URL("https://certs.cac.washington.edu/CAtest/"); HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); urlConnection.setSSLSocketFactory(context.getSocketFactory()); InputStream in = urlConnection.getInputStream(); copyInputStreamToOutputStream(in, System.out);
Com um TrustManager
personalizado que conhece seus CAs, o sistema pode validar que seu certificado de servidor foi emitido por uma fonte confiável.
Cuidado: muitos sites descrevem uma solução alternativa insuficiente, que consiste em instalar um TrustManager
que não faz nada. Fazer isso seria o mesmo que não criptografar suas comunicações, porque qualquer pessoa pode atacar seus usuários em um ponto de acesso de Wi-Fi público usando truques de DNS para enviar o tráfego dos seus usuários por um proxy desejado, que finge ser seu servidor. Com isso, o invasor pode registrar senhas e outros dados pessoais. Isso funciona porque essa pessoa pode gerar um certificado e, sem um TrustManager
que realmente confirme que o certificado foi emitido por uma fonte confiável, seu app pode se comunicar com qualquer um. Então, não faça isso, nem temporariamente. Você sempre pode fazer seu app confiar no emissor do certificado do servidor, portanto, escolha essa opção.
Certificado de servidor autoassinado
O segundo caso de SSLHandshakeException
ocorre devido a um certificado autoassinado, o que significa que o servidor se comporta como sua própria CA.
Isso é semelhante a uma autoridade de certificação desconhecida, então é possível usar a mesma abordagem da seção anterior.
Você pode criar seu próprio TrustManager
, dessa vez confiando no certificado do servidor diretamente. Essa abordagem tem todas as desvantagens discutidas anteriormente de vincular seu app diretamente a um certificado, mas ela pode ser adotada de forma segura. No entanto, é necessário garantir que seu certificado autoassinado tenha uma chave razoavelmente forte. Desde 2012, uma assinatura RSA de 2048 bits com um expoente de 65537 que expira anualmente é aceitável. Ao alternar as chaves, verifique as recomendações de uma autoridade, como o Instituto Nacional de Padrões e Tecnologia (NIST, na sigla em inglês), sobre o que é aceitável.
Autoridade de certificação intermediária ausente
O terceiro caso de SSLHandshakeException
ocorre devido a uma CA intermediária ausente. A maioria das CAs públicas não assinam certificados de servidores diretamente. Em vez disso, elas usam o certificado da CA principal delas, conhecida como a CA raiz, para assinar as CAs intermediárias. Isso é feito para que a CA raiz possa ser armazenada off-line para reduzir os riscos de segurança. No entanto, sistemas operacionais como o Android geralmente confiam apenas em CAs raiz diretamente, o que deixa uma lacuna de confiança entre o certificado do servidor assinado pela CA intermediária e o verificador do certificado, que conhece a CA raiz. Para solucionar essa questão, o servidor não envia ao cliente apenas o certificado dele durante o handshake de SSL, mas sim uma cadeia de certificados da CA do servidor por meio de qualquer intermediária necessária para alcançar uma CA raiz de confiança.
Para ver essa abordagem em prática, apresentamos a seguir uma cadeia de certificados do mail.google.com visualizada pelo comando s_client
do openssl
(link em inglês):
$ 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 ---
Isso mostra que o servidor envia um certificado para mail.google.com emitido pela CA Thawte SGC, que é uma CA intermediária, e um segundo certificado para a CA Thawte SGC emitido por uma CA Verisign, que é a CA principal em que o Android confia.
No entanto, não é incomum configurar um servidor para não incluir a CA intermediária necessária. Por exemplo, este é um servidor que pode causar um erro em navegadores Android e exceções em apps 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 ---
É interessante observar que acessar esse servidor na maioria dos navegadores de computador não causa um erro da mesma forma que uma CA totalmente desconhecida ou um certificado de servidor autoassinado causaria. Isso ocorre porque a maioria desses navegadores armazena em cache CAs intermediárias confiáveis ao longo do tempo. Quando um navegador acessa e aprende sobre uma CA intermediária de um site, ele não precisa que essa CA intermediária seja incluída na cadeia de certificados na próxima vez.
Alguns sites fazem isso propositalmente para servidores Web secundários usados para fornecer recursos. Por exemplo, a página HTML principal deles pode ser fornecida por um servidor com uma cadeia completa de certificados, mas eles tem servidores para recursos como imagens, CSS ou JavaScript que não incluem a CA, provavelmente para economizar largura de banda. Infelizmente, às vezes, esses servidores podem fornecer serviços da Web que você está tentando chamar pelo seu app Android, que não é tão leniente.
Há duas abordagens para solucionar esse problema:
- Configure o servidor para incluir a CA intermediária na cadeia do servidor. A maioria das CAs fornece documentos sobre como fazer isso para todos os servidores da Web comuns. Essa é a única abordagem disponível se você precisa que o site funcione com navegadores Android padrão até a versão 4.2.
- Você também pode tratar a CA intermediária como qualquer outra CA desconhecida e criar um
TrustManager
para confiar nela diretamente, como foi feito nas duas seções anteriores.
Problemas comuns com a verificação do nome do host
Como mencionamos no início deste artigo, a verificação de uma conexão SSL é dividida em duas partes essenciais. A primeira consiste em verificar se o certificado foi emitido por uma fonte confiável, o que era o foco da seção anterior. O foco desta seção é a segunda parte do processo: garantir que você esteja se comunicando com um servidor que apresenta o certificado certo. Se isso não ocorrer, você normalmente verá um erro como este:
java.io.IOException: Hostname 'example.com' was not verified at libcore.net.http.HttpConnection.verifySecureSocketHostname(HttpConnection.java:223) at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:446) 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)
Um dos motivos pelos quais isso pode acontecer é um erro de configuração do servidor. O servidor é configurado com um certificado sem os campos de nomes de assunto ou assunto alternativo que correspondam ao servidor que você está tentando acessar. É possível que um certificado seja usado por diversos servidores. Por exemplo, ao examinar o certificado do google.com com o s_client -connect google.com:443 | openssl x509 -text
do openssl
, é possível ver que ele tem um assunto compatível com *.google.com, além de nomes alternativos de assunto para *.youtube.com, *.android.com e outros. O erro ocorre apenas quando o nome do servidor ao qual você está se conectando não foi listado pelo certificado como aceitável.
Isso também pode ocorrer por outro motivo: hospedagem virtual. Ao compartilhar um servidor para mais de um nome de host com HTTP, o servidor da Web pode identificar, pela solicitação HTTP/1.1, por qual nome de host de destino o cliente está procurando. Esse processo é complicado com o HTTPS, porque o servidor precisa saber qual certificado retornar antes de ver a solicitação HTTP. Para solucionar esse problema, novas versões do SSL, em específico a TLSv.1.0 e mais recentes, oferecem suporte à Indicação de Nome do Servidor (SNI, na sigla em inglês), que permite que o cliente SSL especifique o nome de host pretendido para o servidor para que o certificado correto seja retornado (link em inglês).
O HttpsURLConnection
é compatível com a SNI desde o Android 2.3. Uma solução alternativa, se você precisar de compatibilidade com o Android 2.2 (e versões anteriores), é configurar um host virtual alternativo em uma porta exclusiva para que não existam dúvidas de qual certificado de servidor precisa ser retornado.
A alternativa mais radical é substituir o HostnameVerifier
por um que use não o nome do host virtual, mas o que foi retornado pelo servidor por padrão.
Cuidado: substituir o HostnameVerifier
pode ser muito perigoso se o outro host virtual não estiver sob seu controle, porque um invasor intermediário poderá direcionar o tráfego para outro servidor sem seu conhecimento.
Se ainda assim você tiver certeza de que quer modificar a verificação do nome do host, veja um exemplo que substitui o verificador para uma URLConnection
única por outro, que ainda verifica se o nome do host está ao menos onde o app espera:
Kotlin
// Create an HostnameVerifier that hardwires the expected hostname. // Note that is different than the URL's hostname: // example.com versus example.org val hostnameVerifier = HostnameVerifier { _, session -> HttpsURLConnection.getDefaultHostnameVerifier().run { verify("example.com", session) } } // Tell the URLConnection to use our HostnameVerifier val url = URL("https://example.org/") val urlConnection = url.openConnection() as HttpsURLConnection urlConnection.hostnameVerifier = hostnameVerifier val inputStream: InputStream = urlConnection.inputStream copyInputStreamToOutputStream(inputStream, System.out)
Java
// Create an HostnameVerifier that hardwires the expected hostname. // Note that is different than the URL's hostname: // example.com versus example.org HostnameVerifier hostnameVerifier = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); return hv.verify("example.com", session); } }; // Tell the URLConnection to use our HostnameVerifier URL url = new URL("https://example.org/"); HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); urlConnection.setHostnameVerifier(hostnameVerifier); InputStream in = urlConnection.getInputStream(); copyInputStreamToOutputStream(in, System.out);
Lembre-se: se você substituir a verificação do nome do host, especialmente devido à hospedagem virtual, essa abordagem ainda é muito perigosa se o outro host virtual não estiver sob seu controle, e é recomendável encontrar uma alternativa de hosting que evite esse problema.
Avisos sobre o uso direto do SSLSocket
Até o momento, os exemplos se concentraram no HTTPS usando HttpsURLConnection
.
Às vezes, os aplicativos precisam usar SSL separado do HTTP. Por exemplo, um aplicativo de e-mail pode usar variantes de SSL como SMTP, POP3 ou IMAP. Nesses casos, o recomendado é o app usar o SSLSocket
diretamente, da mesma forma que o HttpsURLConnection
faz internamente.
As técnicas descritas até o momento para solucionar problemas de verificação de certificado também se aplicam ao SSLSocket
.
Na verdade, ao usar um TrustManager
personalizado, o que é transmitido para HttpsURLConnection
é um SSLSocketFactory
.
Então, se precisar usar um TrustManager
personalizado com um SSLSocket
, siga as mesmas etapas e use esse SSLSocketFactory
para criar seu SSLSocket
.
Cuidado: o SSLSocket
não realiza a verificação de nomes de host. Cabe ao seu app fazer a própria verificação do nome do host, de preferência chamando getDefaultHostnameVerifier()
com o nome de host esperado. Além disso, esteja ciente de que o HostnameVerifier.verify()
não gera uma exceção quando há um erro, mas sim retorna um resultado booleano que você precisará verificar explicitamente.
Veja um exemplo de como fazer isso. O exemplo mostra que, ao se conectar ao gmail.com na porta 443 sem suporte à SNI, você receberá um certificado para mail.google.com. Isso é esperado nesse caso, então verifique se o certificado é realmente para o mail.google.com:
Kotlin
// Open SSLSocket directly to gmail.com val socket: SSLSocket = SSLSocketFactory.getDefault().run { createSocket("gmail.com", 443) as SSLSocket } val session = socket.session // Verify that the certicate hostname is for mail.google.com // This is due to lack of SNI support in the current SSLSocket. HttpsURLConnection.getDefaultHostnameVerifier().run { if (!verify("mail.google.com", session)) { throw SSLHandshakeException("Expected mail.google.com, found ${session.peerPrincipal} ") } } // At this point SSLSocket performed certificate verification and // we have performed hostname verification, so it is safe to proceed. // ... use socket ... socket.close()
Java
// Open SSLSocket directly to gmail.com SocketFactory sf = SSLSocketFactory.getDefault(); SSLSocket socket = (SSLSocket) sf.createSocket("gmail.com", 443); HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); SSLSession s = socket.getSession(); // Verify that the certicate hostname is for mail.google.com // This is due to lack of SNI support in the current SSLSocket. if (!hv.verify("mail.google.com", s)) { throw new SSLHandshakeException("Expected mail.google.com, " "found " + s.getPeerPrincipal()); } // At this point SSLSocket performed certificate verification and // we have performed hostname verification, so it is safe to proceed. // ... use socket ... socket.close();
Lista de proibições
O SSL depende muito de CAs para emitir certificados apenas para os proprietários verificados de servidores e domínios. Em casos raros, CAs são fraudadas ou, no caso de Comodo ou DigiNotar (link em inglês), violadas. Isso faz com que os certificados de um nome de host sejam emitidos por indivíduos que não são proprietários do servidor ou domínio.
Para reduzir esse risco, o Android pode inserir certos certificados ou até CAs inteiras em uma lista de proibições. Embora essa lista tenha sido incorporada historicamente ao sistema operacional, a partir do Android 4.2, ela pode ser atualizada de maneira remota para solucionar futuros riscos de segurança.
Fixação
Um app também pode se proteger ainda mais contra certificados fraudulentos utilizando uma técnica conhecida como fixação. Ela consiste, basicamente, em usar o exemplo fornecido no caso de CA desconhecida acima para restringir as CAs confiáveis do app a um pequeno conjunto conhecido por ser usado pelos servidores do app. Com isso, o comprometimento de uma das mais de 100 outras CAs no sistema não resultará em uma violação do canal seguro do app.
Certificados do cliente
Este artigo se concentrou no uso do SSL para proteger as comunicações com servidores. O SSL também é compatível com o conceito de certificados de cliente que permitem o servidor validar a identidade de um cliente. Embora estejam além do escopo deste artigo, as técnicas necessárias são semelhantes à especificação de um TrustManager
personalizado.
Consulte a discussão sobre como criar um KeyManager
personalizado na documentação do HttpsURLConnection
.
Nogotofail: uma ferramenta de teste de segurança do tráfego de rede
A Nogotofail é uma ferramenta que proporciona uma forma fácil de confirmar que seus apps estão seguros contra vulnerabilidades e erros de configuração conhecidos do TLS/SSL. Trata-se de uma ferramenta automatizada, eficaz e escalável para testar problemas de segurança de rede em qualquer dispositivo cujo tráfego de rede possa passar por ela.
A Nogotofail é útil em três casos de uso principais:
- Encontrar erros e vulnerabilidades.
- Verificar correções e administrar regressões.
- Entender quais aplicativos e dispositivos geram qual tráfego.
A Nogotofail é compatível com Android, iOS, Linux, Windows, Chrome OS, OSX e, na verdade, com qualquer dispositivo que possa ser usado para conectar-se à Internet. Ela tem um cliente que pode ser usado facilmente para definir as configurações e receber notificações no Android e no Linux, além no próprio mecanismo de ataque, que pode ser implantado como roteador, servidor de VPN ou proxy.
Você pode acessar essa ferramenta no projeto de código aberto Nogotofail (em inglês).