Domanda

Come accetto un certificato autofirmato in Java su Android?

Un esempio di codice sarebbe perfetto.

Ho cercato ovunque su Internet e mentre alcune persone affermano di aver trovato la soluzione, o non funziona o non esiste un codice di esempio per il backup.

È stato utile?

Soluzione

Ho questa funzionalità in exchangeIt, che si collega a Microsoft Exchange tramite WebDav. Ecco un po 'di codice per creare un HttpClient che si collegherà ai certificati autofirmati tramite 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);

EasySSLSocketFactory è qui e EasyX509TrustManager è qui .

Il codice per lo scambio È open source e ospitato su googlecode qui , se hai eventuali problemi. Non ci sto più lavorando attivamente, ma il codice dovrebbe funzionare.

Nota che da Android 2.2 il processo è leggermente cambiato, quindi controlla this per far funzionare il codice sopra.

Altri suggerimenti

Come commentato correttamente EJP, " I lettori dovrebbero notare che questa tecnica è radicalmente insicura. SSL non è sicuro a meno che almeno un peer non sia autenticato. Vedi RFC 2246. "

Detto questo, ecco un altro modo, senza lezioni extra:

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();
    }
}

Ho affrontato questo problema ieri, durante la migrazione dell'API RESTful della nostra azienda su HTTPS, ma utilizzando certificati SSL autofirmati.

Ho cercato ovunque, ma tutto il "corretto" le risposte contrassegnate che ho trovato consistevano nel disabilitare la convalida del certificato, sovrascrivendo chiaramente tutto il senso di SSL.

Sono finalmente arrivato a una soluzione:

  1. Crea archivio chiavi locale

    Per consentire all'app di convalidare i certificati autofirmati, è necessario fornire un archivio chiavi personalizzato con i certificati in modo che Android possa fidarsi del proprio endpoint.

Il formato per tali keystore personalizzati è " BKS " da BouncyCastle , quindi hai bisogno della versione 1.46 di BouncyCastleProvider che puoi scaricare qui .

Devi anche avere il tuo certificato autofirmato, suppongo che si chiami self_cert.pem .

Ora il comando per creare il tuo keystore è:

<!-- 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*

PATH_TO_KEYSTORE punta a un file in cui verrà creato il keystore. NON DEVE ESISTERE .

PATH_TO_bcprov-jdk15on-146.jar.JAR è il percorso della libreria .jar scaricata.

STOREPASS è la password del keystore appena creata.

  1. Includi KeyStore nella tua applicazione

Copia il tuo keystore appena creato da PATH_TO_KEYSTORE a res / raw / certs.bks ( certs.bks è solo il nome del file; tu puoi usare il nome che desideri)

Crea una chiave in res / valori / strings.xml con

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

    <resources>
    ...
        <string name="store_pass">*STOREPASS*</string>
    ...
    </resources>
  1. Crea una classe che eredita 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;
            }
        }
    }
    

Ora usa semplicemente un'istanza di ** MyHttpClient ** come faresti con ** DefaultHttpClient ** per effettuare le tue query HTTPS e utilizzerà e convaliderà correttamente i tuoi certificati SSL autofirmati.

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 meno che non mi sia perso qualcosa, le altre risposte in questa pagina sono PERICOLOSE e sono funzionalmente equivalenti a non usare affatto SSL. Se ti fidi dei certificati autofirmati senza fare ulteriori controlli per assicurarti che i certificati siano quelli che ti aspetti, chiunque può creare un certificato autofirmato e può fingere di essere il tuo server. A quel punto, non hai una vera sicurezza.

Il unico modo legittimo per farlo (senza scrivere uno stack SSL completo) è quello di aggiungere un ulteriore ancoraggio affidabile di cui fidarsi durante il processo di verifica del certificato. Entrambi implicano la codifica definitiva del certificato di ancoraggio attendibile nella tua app e l'aggiunta a qualsiasi ancoraggio affidabile fornito dal sistema operativo (altrimenti non sarai in grado di collegarti al tuo sito se ottieni un vero certificato).

Sono a conoscenza di due modi per farlo:

  1. Crea un negozio di fiducia personalizzato come descritto in http : //www.ibm.com/developerworks/java/library/j-customssl/#8

  2. Crea un'istanza personalizzata di X509TrustManager e sovrascrivi il metodo getAcceptedIssuers per restituire un array che contiene il tuo certificato:

    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;
    }
    

Nota che questo codice è completamente non testato e potrebbe non essere nemmeno compilato, ma dovrebbe almeno indirizzarti nella giusta direzione.

La risposta di Brian Yarger funziona anche su Android 2.2 se si modifica il sovraccarico del metodo createSocket più grande come segue. Mi ci è voluto un po 'per far funzionare gli SSL autofirmati.

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

Su Android, HttpProtocolParams accetta ProtocolVersion anziché HttpVersion .

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

@Chris - Pubblicando questo come risposta poiché non posso ancora aggiungere commenti. Mi chiedo se il tuo approccio dovrebbe funzionare quando si utilizza un webView. Non riesco a farlo farlo su Android 2.3 - invece ho solo uno schermo bianco.

Dopo qualche altra ricerca, mi sono imbattuto in questo semplice correzione per la gestione degli errori SSL in un webView che ha funzionato come un incantesimo per me.

Nel gestore controllo se sono in una modalità di sviluppo speciale e chiamo handler.proceed (), altrimenti chiamo handler.cancel (). Questo mi permette di sviluppare contro un certificato autofirmato su un sito Web locale.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top