Pergunta

Eu estou tentando fazer conexões HTTPS, usando HttpClient lib, mas o problema é que, uma vez que o certificado não está assinado por uma autoridade de certificação (CA) reconhecida como Verisign , GlobalSign , etc., listada no set de Android Trusted Certificados, eu continuo recebendo javax.net.ssl.SSLException: Not trusted server certificate.

Eu vi soluções onde você simplesmente aceitar todos os certificados, mas o que se eu quiser perguntar ao usuário?

Eu quero conseguir um semelhante de diálogo ao do navegador, permitindo que o usuário decidir continuar ou não. De preferência, eu gostaria de usar o mesmo certificatestore como o browser. Alguma idéia?

Foi útil?

Solução

A primeira coisa que você precisa fazer é definir o nível de verificação. Tais níveis não é tanto:

  • ALLOW_ALL_HOSTNAME_VERIFIER
  • BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
  • STRICT_HOSTNAME_VERIFIER

Embora o setHostnameVerifier () método é obsoleto por nova apache biblioteca, mas para a versão em Android SDK é normal. E assim que nós tomamos ALLOW_ALL_HOSTNAME_VERIFIER e configurá-lo na SSLSocketFactory.setHostnameVerifier() método de fábrica.

Em seguida, você precisa definir a nossa fábrica para o protocolo https. Para fazer isso, basta chamar o método SchemeRegistry.register().

Em seguida, você precisa criar um DefaultHttpClient com SingleClientConnManager. Também no código abaixo você pode ver que no padrão também vai usar a nossa bandeira (ALLOW_ALL_HOSTNAME_VERIFIER) pela HttpsURLConnection.setDefaultHostnameVerifier() método

Abaixo código funciona para mim:

HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;

DefaultHttpClient client = new DefaultHttpClient();

SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("https", socketFactory, 443));
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams());

// Set verifier     
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);

// Example send http request
final String url = "https://encrypted.google.com/";
HttpPost httpPost = new HttpPost(url);
HttpResponse response = httpClient.execute(httpPost);

Outras dicas

As seguintes etapas principais são necessárias para conseguir uma conexão segura de autoridades de certificação que não são considerados como confiável pela plataforma Android.

Tal como foi solicitado por muitos usuários, eu espelhado partes mais importantes do meu artigo de blog aqui:

  1. Grab todos os certificados necessários (root e qualquer CA intermediário)
  2. Criar um armazenamento de chaves com keytool ea href="http://www.bouncycastle.org/" rel="noreferrer"> BouncyCastle provedor e importar os certificados
  3. Coloque o armazenamento de chaves em seu aplicativo Android e usá-lo para as conexões seguras (eu recomendo usar o Apache HttpClient vez do java.net.ssl.HttpsURLConnection padrão (mais fácil de entender, mais performant)

Agarre a certs

Você tem que obter todos os certificados que construir uma cadeia do certificado endpoint todo o caminho até o Root CA. Este meio de quaisquer (se presente) certs Intermediário CA e também o certificado da CA raiz. Você não precisa para obter o certificado de ponto final.

Criar o armazenamento de chave

Faça o download do BouncyCastle Provider e armazená-lo em um local conhecido. Também garantir que você pode chamar o comando keytool (normalmente localizado na pasta bin da instalação do JRE).

Agora importar os certificados obtidos (não importar o certificado endpoint) em um formato de armazenamento de chaves BouncyCastle.

Eu não testei, mas acho que a ordem de importação dos certificados é importante. Isto significa importar a mais baixa certificado Intermediate CA em primeiro lugar e, em seguida, todo o caminho até o certificado da CA raiz.

Com o seguinte comando de um novo armazenamento de chaves (se já não estiver presente) com a senha mysecret será criado eo certificado Intermediate CA serão importados. Eu também definido o provedor BouncyCastle, onde ele pode ser encontrado no meu sistema de arquivo e o formato de armazenamento de chaves. Executar este comando para cada certificado na cadeia.

keytool -importcert -v -trustcacerts -file "path_to_cert/interm_ca.cer" -alias IntermediateCA -keystore "res/raw/myKeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

Verifique se os certificados foram importados corretamente para o armazenamento de chaves:

keytool -list -keystore "res/raw/myKeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

saída deve a toda cadeia:

RootCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 24:77:D9:A8:91:D1:3B:FA:88:2D:C2:FF:F8:CD:33:93
IntermediateCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 98:0F:C3:F8:39:F7:D8:05:07:02:0D:E3:14:5B:29:43

Agora você pode copiar o armazenamento de chaves como um recurso cru em seu aplicativo android sob res/raw/

Use o armazenamento de chaves em seu aplicativo

Em primeiro lugar, temos de criar um costume Apache HttpClient que usa o nosso armazenamento de chaves para conexões HTTPS:

public class MyHttpClient extends DefaultHttpClient {

  final Context context;

  public MyHttpClient(Context context) {
      this.context = context;
  }

  @Override
  protected ClientConnectionManager createClientConnectionManager() {
      SchemeRegistry registry = new SchemeRegistry();
      registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
      // Register for port 443 our SSLSocketFactory with our keystore
      // to the ConnectionManager
      registry.register(new Scheme("https", newSslSocketFactory(), 443));
      return new SingleClientConnManager(getParams(), registry);
  }

  private SSLSocketFactory newSslSocketFactory() {
      try {
          // Get an instance of the Bouncy Castle KeyStore format
          KeyStore trusted = KeyStore.getInstance("BKS");
          // Get the raw resource, which contains the keystore with
          // your trusted certificates (root and any intermediate certs)
          InputStream in = context.getResources().openRawResource(R.raw.mykeystore);
          try {
              // Initialize the keystore with the provided trusted certificates
              // Also provide the password of the keystore
              trusted.load(in, "mysecret".toCharArray());
          } finally {
              in.close();
          }
          // Pass the keystore to the SSLSocketFactory. The factory is responsible
          // for the verification of the server certificate.
          SSLSocketFactory sf = new SSLSocketFactory(trusted);
          // Hostname verification from certificate
          // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
          sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
          return sf;
      } catch (Exception e) {
          throw new AssertionError(e);
      }
  }
}

Nós criamos nosso costume HttpClient, agora podemos apenas usá-lo para conexões seguras. Por exemplo, quando fazemos uma chamada GET a um recurso REST.

// Instantiate the custom HttpClient
DefaultHttpClient client = new MyHttpClient(getApplicationContext());
HttpGet get = new HttpGet("https://www.mydomain.ch/rest/contacts/23");
// Execute the GET call and obtain the response
HttpResponse getResponse = client.execute(get);
HttpEntity responseEntity = getResponse.getEntity();

É isso;)

Se você tem um certificado personalizado / auto-assinado no servidor que não existe no dispositivo, você pode usar o abaixo classe para carregá-lo e usá-lo no lado do cliente no Android:

Coloque o arquivo de certificado *.crt em /res/raw para que ele está disponível a partir R.raw.*

Use abaixo classe para obter um HTTPClient ou HttpsURLConnection que terá uma fábrica de soquete usando esse certificado:

package com.example.customssl;

import android.content.Context;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

public class CustomCAHttpsProvider {

    /**
     * Creates a {@link org.apache.http.client.HttpClient} which is configured to work with a custom authority
     * certificate.
     *
     * @param context       Application Context
     * @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
     * @param allowAllHosts If true then client will not check server against host names of certificate.
     * @return Http Client.
     * @throws Exception If there is an error initializing the client.
     */
    public static HttpClient getHttpClient(Context context, int certRawResId, boolean allowAllHosts) throws Exception {


        // build key store with ca certificate
        KeyStore keyStore = buildKeyStore(context, certRawResId);

        // init ssl socket factory with key store
        SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore);

        // skip hostname security check if specified
        if (allowAllHosts) {
            sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier());
        }

        // basic http params for client
        HttpParams params = new BasicHttpParams();

        // normal scheme registry with our ssl socket factory for "https"
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));

        // create connection manager
        ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);

        // create http client
        return new DefaultHttpClient(cm, params);
    }

    /**
     * Creates a {@link javax.net.ssl.HttpsURLConnection} which is configured to work with a custom authority
     * certificate.
     *
     * @param urlString     remote url string.
     * @param context       Application Context
     * @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
     * @param allowAllHosts If true then client will not check server against host names of certificate.
     * @return Http url connection.
     * @throws Exception If there is an error initializing the connection.
     */
    public static HttpsURLConnection getHttpsUrlConnection(String urlString, Context context, int certRawResId,
                                                           boolean allowAllHosts) throws Exception {

        // build key store with ca certificate
        KeyStore keyStore = buildKeyStore(context, certRawResId);

        // 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 sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);

        // Create a connection from url
        URL url = new URL(urlString);
        HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
        urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());

        // skip hostname security check if specified
        if (allowAllHosts) {
            urlConnection.setHostnameVerifier(new AllowAllHostnameVerifier());
        }

        return urlConnection;
    }

    private static KeyStore buildKeyStore(Context context, int certRawResId) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
        // init a default key store
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);

        // read and add certificate authority
        Certificate cert = readCert(context, certRawResId);
        keyStore.setCertificateEntry("ca", cert);

        return keyStore;
    }

    private static Certificate readCert(Context context, int certResourceId) throws CertificateException, IOException {

        // read certificate resource
        InputStream caInput = context.getResources().openRawResource(certResourceId);

        Certificate ca;
        try {
            // generate a certificate
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            ca = cf.generateCertificate(caInput);
        } finally {
            caInput.close();
        }

        return ca;
    }

}

Pontos principais:

  1. objetos Certificate são gerados a partir de arquivos .crt.
  2. É criada uma KeyStore padrão.
  3. keyStore.setCertificateEntry("ca", cert) está adicionando certificado para armazenamento de chaves sob pseudônimo de "ca". Você modificar o código para adicionar mais certificados (CA intermediária etc).
  4. objetivo principal é gerar um SSLSocketFactory que pode então ser utilizado por HTTPClient ou HttpsURLConnection.
  5. SSLSocketFactory pode ser configurado ainda mais, por exemplo, para saltar nome de host verificação etc.

Mais informações em: http://developer.android.com/training/ artigos / security-ssl.html

O trabalho resposta Não sofreu topo para mim. Depois de algumas investigações eu encontrei a informação exigida em "Desenvolvedor Android": https://developer.android.com/training/articles/security-ssl.html#SelfSigned

Criação de uma implementação vazia de X509TrustManager fez o truque:

private static class MyTrustManager implements X509TrustManager
{

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType)
         throws CertificateException
    {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType)
        throws CertificateException
    {
    }

    @Override
    public X509Certificate[] getAcceptedIssuers()
    {
        return null;
    }

}

...

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
try
{
    // Create an SSLContext that uses our TrustManager
    SSLContext context = SSLContext.getInstance("TLS");
    TrustManager[] tmlist = {new MyTrustManager()};
    context.init(null, tmlist, null);
    conn.setSSLSocketFactory(context.getSocketFactory());
}
catch (NoSuchAlgorithmException e)
{
    throw new IOException(e);
} catch (KeyManagementException e)
{
    throw new IOException(e);
}
conn.setRequestMethod("GET");
int rcode = conn.getResponseCode();

Por favor, esteja ciente de que esta implementação vazia de TustManager é apenas um exemplo e usá-lo em um ambiente produtivo causaria uma ameaça de segurança grave!

Eu estava frustrado tentando conectar meu App Android para o meu serviço RESTful usando https. Também eu estava um pouco irritado sobre todas as respostas que sugeriam para desativar certificado de verificação por completo. Se você fizer isso, o que é o ponto de https?

Depois pesquisei sobre o assunto por um tempo, eu finalmente encontrei esta solução onde frascos externos não são necessários, apenas APIs do Android. Graças a Andrew Smith, que colocou em julho de 2014

 /**
 * Set up a connection to myservice.domain using HTTPS. An entire function
 * is needed to do this because myservice.domain has a self-signed certificate.
 * 
 * The caller of the function would do something like:
 * HttpsURLConnection urlConnection = setUpHttpsConnection("https://littlesvr.ca");
 * InputStream in = urlConnection.getInputStream();
 * And read from that "in" as usual in Java
 * 
 * Based on code from:
 * https://developer.android.com/training/articles/security-ssl.html#SelfSigned
 */
public static HttpsURLConnection setUpHttpsConnection(String urlString)
{
    try
    {
        // Load CAs from an InputStream
        // (could be from a resource or ByteArrayInputStream or ...)
        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        // My CRT file that I put in the assets folder
        // I got this file by following these steps:
        // * Go to https://littlesvr.ca using Firefox
        // * Click the padlock/More/Security/View Certificate/Details/Export
        // * Saved the file as littlesvr.crt (type X.509 Certificate (PEM))
        // The MainActivity.context is declared as:
        // public static Context context;
        // And initialized in MainActivity.onCreate() as:
        // MainActivity.context = getApplicationContext();
        InputStream caInput = new BufferedInputStream(MainActivity.context.getAssets().open("littlesvr.crt"));
        Certificate ca = cf.generateCertificate(caInput);
        System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());

        // 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(urlString);
        HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
        urlConnection.setSSLSocketFactory(context.getSocketFactory());

        return urlConnection;
    }
    catch (Exception ex)
    {
        Log.e(TAG, "Failed to establish SSL connection to server: " + ex.toString());
        return null;
    }
}

Ele trabalhou bom para a minha maquete App.

O Google recomenda o uso de Android Volley para HTTP / HTTPS conexões , desde que HttpClient está obsoleta. Então, você sabe a escolha certa:)

.

E também, Nunca ARMAS NUCLEARES Certificados SSL (nunca !!!).

para nuke Certificados SSL, é totalmente contra o propósito de SSL, que está promovendo segurança . Não há nenhum sentido de usar SSL, se você está planejando para bombardear todos os certificados SSL que vem. Uma solução melhor seria, não usando SSL, ou uma solução melhor, seria a criação de um TrustManager costume em seu App + usando Volley Android para HTTP / HTTPS conexões.

Aqui está um Gist que eu criei, com um LoginApp básica, realizando conexões HTTPS, usando um certificado auto-assinado no lado do servidor, aceito na App.

Aqui está também uma outra Gist que pode ajudar, para a criação de auto-assinados Certificados SSL para o ambiente -se em seu servidor e também usando o certificado em seu App. Muito importante:. você deve copiar o arquivo .crt que foi gerado pelo script acima, para o diretório "raw" do seu projeto Android

Veja como você pode adicionar certificados adicionais para o seu KeyStore para evitar este problema: Confiar em todos os certificados usando HttpClient mais de HTTPS

Não vai prompt de usuário, como você perguntar, mas vai torná-lo menos provável que o usuário vai correr em um erro de "certificado de servidor não confiável".

Maneira mais simples para criar certificado SSL

Abra o Firefox (suponho também é possível com o Chrome, mas é mais fácil para mim com FF)

Visite o site de desenvolvimento com um certificado SSL auto-assinado.

Clique no certificado (ao lado do nome do site)

Clique em "Mais informações"

Clique em "Ver certificado"

Clique em "Detalhes"

Clique em "Exportar ..."

Escolha "certificado X.509 whith cadeia (PEM)", selecione a pasta e nome para salvá-lo e clique em "Save"

Ir para a linha de comando, para o diretório onde você baixou o arquivo pem e executar "openssl x509 -Informar PEM -outform DM -in .pem out .crt"

Copie o arquivo .crt à raiz do sdcard pasta / dentro de seu dispositivo Android Dentro de seu dispositivo Android, Configurações> Segurança> Instalar a partir do armazenamento.

Ele deve detectar o certificado e permitem que você adicione-o para o dispositivo Navegue até o site de desenvolvimento.

A primeira vez que deve pedir-lhe para confirmar a exceção de segurança. Isso é tudo.

O certificado deve funcionar com qualquer navegador instalado no seu Android (navegador, Chrome, Opera, golfinho ...)

Lembre-se que se você está servindo seus arquivos estáticos de um domínio diferente (todos nós somos cadelas velocidade de página), você também precisa adicionar o certificado para esse domínio.

Eu escrevi uma pequena biblioteca SSL-utils-android a confiar em particular certificado no Android.

Você pode simplesmente carregar qualquer certificado, dando o nome do arquivo do diretório ativo.

Uso:

OkHttpClient client = new OkHttpClient();
SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context, "BPClass2RootCA-sha2.cer");
client.setSslSocketFactory(sslContext.getSocketFactory());

Nenhuma dessas correções trabalhou para a minha plataforma de desenvolver alvo SDK 16, lançamento 4.1.2, então eu encontrei uma solução alternativa.

As minhas aplicação armazenar dados no servidor usando " http://www.example.com /page.php?data=somedata "

Recentemente page.php foi transferido para " https://www.secure-example.com /page.php " e eu continuo recebendo "javax.net.ssl.SSLException:. Não confiava certificado do servidor"

Em vez de aceitar todos os certificados por apenas uma única página, começando com este guia Eu resolvi meu problema escrever meu próprio page.php publicada em " http://www.example.com/page .php "

<?php

caronte ("https://www.secure-example.com/page.php");

function caronte($url) {
    // build curl request
    $ch = curl_init();
    foreach ($_POST as $a => $b) {
        $post[htmlentities($a)]=htmlentities($b);
    }
    curl_setopt($ch, CURLOPT_URL,$url);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($post));

    // receive server response ...
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $server_output = curl_exec ($ch);
    curl_close ($ch);

    echo $server_output;
}

?>

Talvez isso útil ... ele funciona em clientes Java utilizando certificados auto-assinado (não há nenhuma verificação do certificado). Tenha cuidado e usá-lo apenas para casos de desenvolvimento, porque isso não é seguro em todos os !!

Como para ignorar erros de certificado SSL no Apache HttpClient 4.0

espero que funciona em Android apenas adicionando HttpClient biblioteca ... boa sorte !!

Esta é problema resultante da falta de SNI (Servidor de Nome de Identificação) apoio Ina, 2.x. ndroid Eu estava lutando com este problema por uma semana até que me deparei com a seguinte pergunta, que não só dá uma boa fundo do problema, mas também fornece uma solução de trabalho e eficaz desprovido de quaisquer falhas de segurança.

'Nenhum certificado pelos pares' erro no Android 2.3, mas não em 4

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top