Register now for Android Dev Summit 2019!

Segurança com HTTPS e SSL

O SSL (Secure Sockets Layer), agora conhecido tecnicamente como TLS (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 deles 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 de SSL típico, 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 o certificado dele com a criptografia de chave pública.

No entanto, qualquer pessoa pode gerar o próprio certificado e a própria 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 poder ser atualizados 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 é especialmente problemático 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-mail.

Para solucionar essas desvantagens, os servidores geralmente são configurados com emissores conhecidos, chamados Autoridades de certificação (CA). 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 para garantir que esteja se comunicando com o servidor desejado. Para solucionar isso, o certificado emitido pela CA identifica o servidor com um nome específico, como gmail.com, ou com um conjunto curinga de hosts, como *.google.com.

O exemplo a seguir tornará esses conceitos um pouco mais concretos. No snippet abaixo de uma linha de comando, o comando openssl da ferramenta s_client verifica as informações de certificado do servidor da Wikipédia. Ele especifica a porta 443 porque ela é o 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
    

Você pode 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 usando 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 um HttpURLConnection. A documentação do Android para HttpURLConnection tem mais exemplos de 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, entre outros. No entanto, em termos de detalhes sobre a verificação de certificados e nomes de host, a estrutura do Android cuida de tudo para você utilizando essa APIs. Essa é a solução ideal, se possível. Dito isso, apresentamos abaixo algumas considerações.

Problemas comuns ao verificar certificados de servidor

Vamos supor que, em vez de receber o conteúdo de getInputStream(), seja exibida 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:

  1. A CA que emitiu o certificado do servidor era desconhecida
  2. O certificado do servidor não estava assinado por uma CA, mas sim autoassinado
  3. 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 o 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 é utilizado pelo sistema para validar certificados do servidor e, ao criar um TrustManager a partir de um KeyStore com uma ou mais CAs, essas serão as únicas CAs em que esse TrustManager confiará.

Considerando o novo TrustManager, o exemplo inicializa um novo SSLContext que fornece um SSLSocketFactory que pode ser usado para substituir o SSLSocketFactory padrão da 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 suas CAs, o sistema pode validar que seu certificado de servidor foi emitido por uma fonte confiável.

Atenção: 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 da escolha dela, 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 com que seu app confie 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 a própria CA. Isso é semelhante a uma autoridade de certificação desconhecida, então você pode 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, garanta 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 NIST) sobre o que é aceitável.

Autoridade de certificação intermediária ausente

O terceiro caso de SSLHandshakeException ocorre devido à ausência de uma CA intermediária. A maioria das CAs públicas não assina 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 somente 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 de mail.google.com visualizada pelo comando openssl do s_client:

    $ openssl s_client -connect mail.google.com:443
    ---
    Certificate chain
     0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/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 em 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 ter servidores para recursos como imagens, CSS ou JavaScript que não incluem a CA, provavelmente para economizar largura de banda. Às vezes, esses servidores fornecem serviços da Web que você esteja tentando chamar por 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 apresente 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 que não tem campos de nomes de assunto ou assunto alternativo que correspondam ao servidor que você está tentando alcançar. É 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 openssl, você pode 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 resolver esse problema, novas versões do SSL, especificamente a TLS v.1.0 e posteriores, são compatíveis com a Indicação de nome do servidor (SNI), que permite que o cliente SSL especifique o nome de host pretendido para o servidor para que o certificado correto seja retornado.

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 haja 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 seu host virtual, mas o que foi retornado pelo servidor por padrão.

Atenção: substituir o HostnameVerifier pode ser muito perigoso se o outro host virtual não estiver sob seu controle, porque um ataque de interceptação pode direcionar o tráfego para outro servidor sem seu conhecimento.

Se ainda assim você estiver certo de que quer modificar a verificação do nome do host, veja um exemplo que substitui o verificador para um URLConnection único por um que verifique que o nome do host esteja 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 mencionados eram referentes a HTTPS usando HttpsURLConnection. Às vezes, os apps precisam usar SSL separado do HTTP. Por exemplo, um app de e-mails pode usar variantes de SSL, como SMTP, POP3 ou IMAP. Nesses casos, o app pode querer usar 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 é passado para o 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.

Atenção: o SSLSocket não realiza a verificação do nome do host. Cabe ao seu app fazer a própria verificação do nome do host, preferencialmente chamando getDefaultHostnameVerifier() com o nome de host esperado. Esteja ciente também de que o HostnameVerifier.verify() não aciona uma exceção quando há um erro, mas retorna um resultado booleano que você precisará verificar explicitamente.

Veja um exemplo de como fazer isso. Ele mostra que, ao conectar-se ao gmail.com na porta 443 não compatível com a SNI, você receberá um certificado para mail.google.com. Isso é esperado nesse caso, então verifique se o certificado realmente é para 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, violadas, resultando em certificados para um nome de host 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 mesmo 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 remotamente 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 que deva ser usado pelos servidores do app. Isso impede o comprometimento de uma das mais de 100 CAs do sistema, que resultaria em uma violação do canal seguro dos apps.

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 que o servidor valide a identidade de um cliente. Embora elas 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.