Pergunta

Como faço para aceitar um certificado auto-assinado em Java no Android?

Um exemplo de código seria perfeito.

Eu olhei em toda parte na internet e enquanto algumas pessoas afirmam ter encontrado a solução, ou ele não funciona ou não existe um código de exemplo para apoiá-la.

Foi útil?

Solução

Eu tenho essa funcionalidade em exchangeIt, que se conecta ao Exchange Microsoft através do WebDAV. Aqui está algum código para criar uma HttpClient que ligará a auto assinado cert da via SSL:

SchemeRegistry schemeRegistry = new SchemeRegistry();
// http scheme
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// https scheme
schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443));

HttpParams params = new BasicHttpParams();
params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);

ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);

O EasySSLSocketFactory é aqui , eo EasyX509TrustManager é aqui .

O código para exchangeIt é open source, e hospedado em GoogleCode aqui , se você tiver quaisquer problemas. Eu não estou trabalhando ativamente em mais, mas o código deve funcionar.

Note que, desde o Android 2.2 o processo mudou um pouco, de modo a verificar este para fazer o código acima de trabalho.

Outras dicas

Como EJP comentou corretamente, "Os leitores devem notar que esta técnica é radicalmente inseguro. SSL não é seguro a menos que pelo menos um dos pares é autenticado. Veja RFC 2246."

Dito isto, aqui está uma outra maneira, sem aulas extras:

import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;

private void trustEveryone() {
    try {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }});
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, new X509TrustManager[]{new X509TrustManager(){
            public void checkClientTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {}
            public void checkServerTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {}
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }}}, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(
                context.getSocketFactory());
    } catch (Exception e) { // should never happen
        e.printStackTrace();
    }
}

Eu enfrentei esta questão ontem, durante a migração API RESTful da nossa empresa para HTTPS, mas o uso de certificados SSL auto-assinados.

Eu tenho procurado por toda parte, mas todos os "corrigir" respostas marcadas eu encontrei consistiu de desabilitar a validação do certificado, claramente substituindo todo o senso de SSL.

Eu finalmente chegou a uma solução:

  1. Criar local KeyStore

    Para ativar seu aplicativo para validar os certificados auto-assinados, você precisa fornecer um armazenamento de chave personalizado com os certificados de forma que o Android pode confiar o seu ponto final.

O formato para tais keystores personalizados é "BKS" do BouncyCastle , por isso você precisa da versão 1.46 do BouncyCastleProvider que você pode baixar aqui .

Você também precisa de seu certificado auto-assinado, vou assumir que é chamado self_cert.pem.

Agora, o comando para criar o seu armazenamento de chaves é:

<!-- language: lang-sh -->

    $ keytool -import -v -trustcacerts -alias 0 \
    -file *PATH_TO_SELF_CERT.PEM* \
    -keystore *PATH_TO_KEYSTORE* \
    -storetype BKS \
    -provider org.bouncycastle.jce.provider.BouncyCastleProvider \
    -providerpath *PATH_TO_bcprov-jdk15on-146.jar* \
    -storepass *STOREPASS*

pontos PATH_TO_KEYSTORE para um arquivo onde o seu armazenamento de chaves será criado. É NÃO DEVE EXISTIR .

PATH_TO_bcprov-jdk15on-146.jar.JAR é o caminho para o baixado .jar libary.

STOREPASS é a sua senha de armazenamento de chave recém-criado.

  1. Incluir KeyStore na sua aplicação

Copie o seu armazenamento de chave recém-criado a partir PATH_TO_KEYSTORE para res/raw/certs.bks ( certs.bks é apenas o nome do arquivo, você pode usar qualquer nome que você quiser)

Criar uma chave em res/values/strings.xml com

<!-- language: lang-xml -->

    <resources>
    ...
        <string name="store_pass">*STOREPASS*</string>
    ...
    </resources>
  1. Criar um presente classe que herda DefaultHttpClient

    import android.content.Context;
    import android.util.Log;
    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.SSLSocketFactory;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.params.HttpParams;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.security.*;
    
    public class MyHttpClient extends DefaultHttpClient {
    
        private static Context appContext = null;
        private static HttpParams params = null;
        private static SchemeRegistry schmReg = null;
        private static Scheme httpsScheme = null;
        private static Scheme httpScheme = null;
        private static String TAG = "MyHttpClient";
    
        public MyHttpClient(Context myContext) {
    
            appContext = myContext;
    
            if (httpScheme == null || httpsScheme == null) {
                httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
                httpsScheme = new Scheme("https", mySSLSocketFactory(), 443);
            }
    
            getConnectionManager().getSchemeRegistry().register(httpScheme);
            getConnectionManager().getSchemeRegistry().register(httpsScheme);
    
        }
    
        private SSLSocketFactory mySSLSocketFactory() {
            SSLSocketFactory ret = null;
            try {
                final KeyStore ks = KeyStore.getInstance("BKS");
    
                final InputStream inputStream = appContext.getResources().openRawResource(R.raw.certs);
    
                ks.load(inputStream, appContext.getString(R.string.store_pass).toCharArray());
                inputStream.close();
    
                ret = new SSLSocketFactory(ks);
            } catch (UnrecoverableKeyException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (KeyStoreException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (KeyManagementException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (NoSuchAlgorithmException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (IOException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (Exception ex) {
                Log.d(TAG, ex.getMessage());
            } finally {
                return ret;
            }
        }
    }
    

Agora, basta usar uma instância de **MyHttpClient** como você faria com **DefaultHttpClient** para tornar a sua HTTPS consultas, e ele vai usar e validando corretamente seus certificados SSL auto-assinados.

HttpResponse httpResponse;

HttpPost httpQuery = new HttpPost("https://yourserver.com");
... set up your query ...

MyHttpClient myClient = new MyHttpClient(myContext);

try{

    httpResponse = myClient.(peticionHttp);

    // Check for 200 OK code
    if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) {
        ... do whatever you want with your response ...
    }

}catch (Exception ex){
    Log.d("httpError", ex.getMessage());
}

A menos que eu perdi alguma coisa, as outras respostas nesta página são perigosos, e são funcionalmente equivalentes a não usar SSL em tudo. Se você confiar em certificados auto-assinados, sem fazer mais verificações para garantir que os certificados são os que você está esperando, então qualquer pessoa pode criar um certificado auto-assinado e pode fingir ser seu servidor. Nesse ponto, você não tem nenhuma segurança real.

A única maneira legítimo para fazer isso (sem escrever uma pilha SSL completo) é adicionar uma âncora de confiança adicional para ser confiável durante o processo de verificação do certificado. Ambos envolvem embutir o certificado âncora de confiança em seu aplicativo e adicioná-lo a qualquer confiança âncoras que o sistema operacional fornece (ou então você não será capaz de se conectar ao seu site, se você obter um certificado real).

Eu estou ciente de duas maneiras de fazer isso:

  1. Criar uma loja de confiança personalizada como descrito em http : //www.ibm.com/developerworks/java/library/j-customssl/#8

  2. Criar uma instância costume de X509TrustManager e substituir o método getAcceptedIssuers para retornar uma matriz que contém o seu certificado:

    public X509Certificate[] getAcceptedIssuers()
    {
        X509Certificate[] trustedAnchors =
            super.getAcceptedIssuers();
    
        /* Create a new array with room for an additional trusted certificate. */
        X509Certificate[] myTrustedAnchors = new X509Certificate[trustedAnchors.length + 1];
        System.arraycopy(trustedAnchors, 0, myTrustedAnchors, 0, trustedAnchors.length);  
    
        /* Load your certificate.
    
           Thanks to http://stackoverflow.com/questions/11857417/x509trustmanager-override-without-allowing-all-certs
           for this bit.
         */
        InputStream inStream = new FileInputStream("fileName-of-cert");
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
        inStream.close();
    
        /* Add your anchor cert as the last item in the array. */
        myTrustedAnchors[trustedAnchors.length] = cert;
    
        return myTrustedAnchors;
    }
    

Note que este código é completamente testado e não podem sequer compilar, mas deve pelo menos orientar você na direção certa.

A resposta de Brian Yarger funciona no Android 2.2, bem como se você modificar a maior sobrecarga de método createSocket como segue. Levei um tempo para chegar SSLs auto-assinados trabalho.

public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
    return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}

No Android, HttpProtocolParams aceita ProtocolVersion em vez de HttpVersion.

ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1);
HttpProtocolParams.setVersion(params, pv);

@ Chris - Destacamento isso como uma resposta já que não posso adicionar comentários (ainda). Eu estou querendo saber se sua abordagem é suposto trabalho ao usar um webView. Eu não posso obtê-lo fazê-lo em Android 2.3 -. Em vez disso, é só pegar uma tela branca

Depois de mais algumas pesquisas, me deparei com este simples correção para lidar com erros de SSL em um webView que funcionou como um encanto para mim.

No manipulador eu verificar para ver se eu estou em um modo de dev especial e handler.proceed call (), caso contrário eu chamo handler.cancel (). Isso me permite fazer o desenvolvimento contra uma cert auto-assinado em um site local.

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