Pregunta

¿Cómo acepto un certificado autofirmado en Java en Android?

Una muestra de código sería perfecta.

He buscado en todas partes en Internet y, aunque algunas personas afirman haber encontrado la solución, no funciona o no hay un código de muestra para respaldarla.

¿Fue útil?

Solución

Tengo esta funcionalidad en exchangeIt, que se conecta al intercambio de Microsoft a través de WebDav. Aquí hay un código para crear un HttpClient que se conectará a certificados autofirmados mediante 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 es aquí , y EasyX509TrustManager es aquí .

El código para exchangeIt es de código abierto y está alojado en googlecode aquí , si tiene cualquier problema. Ya no estoy trabajando activamente en eso, pero el código debería funcionar.

Tenga en cuenta que desde Android 2.2 el proceso ha cambiado un poco, así que marque this para que el código anterior funcione.

Otros consejos

Como EJP comentó correctamente, " Los lectores deben tener en cuenta que esta técnica es radicalmente insegura. SSL no es seguro a menos que al menos un par esté autenticado. Ver RFC 2246. "

Dicho esto, aquí hay otra forma, sin clases adicionales:

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

Ayer me enfrenté a este problema, mientras migraba la API RESTful de nuestra empresa a HTTPS, pero utilizando certificados SSL autofirmados.

He buscado en todas partes, pero todas las "correctas" Las respuestas marcadas que encontré consistían en deshabilitar la validación del certificado, anulando claramente todo el sentido de SSL.

Finalmente llegué a una solución:

  1. Crear almacén de claves local

    Para permitir que su aplicación valide sus certificados autofirmados, debe proporcionar un almacén de claves personalizado con los certificados de manera que Android pueda confiar en su punto final.

El formato para dichos almacenes de claves personalizados es " BKS " de BouncyCastle , por lo que necesita la versión 1.46 de BouncyCastleProvider que puede descargar aquí .

También necesita su certificado autofirmado, supondré que se llama self_cert.pem .

Ahora el comando para crear su almacén de claves es:

<!-- 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 apunta a un archivo donde se creará su almacén de claves. NO DEBE EXISTIR .

PATH_TO_bcprov-jdk15on-146.jar.JAR es la ruta a la biblioteca .jar descargada.

STOREPASS es su contraseña de almacén de claves recién creada.

  1. Incluya KeyStore en su aplicación

Copie su almacén de claves recién creado de PATH_TO_KEYSTORE a res / raw / certs.bks ( certs.bks es solo el nombre del archivo; usted puede usar el nombre que desee)

Cree una clave en res / values ??/ strings.xml con

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

    <resources>
    ...
        <string name="store_pass">*STOREPASS*</string>
    ...
    </resources>
  1. Cree una clase que herede 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;
            }
        }
    }
    

Ahora simplemente use una instancia de ** MyHttpClient ** como lo haría con ** DefaultHttpClient ** para hacer sus consultas HTTPS, y usará y validará correctamente sus certificados SSL autofirmados.

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 me haya perdido algo, las otras respuestas en esta página son PELIGROSAS y son funcionalmente equivalentes a no usar SSL en absoluto. Si confía en los certificados autofirmados sin hacer más comprobaciones para asegurarse de que los certificados son los que espera, entonces cualquiera puede crear un certificado autofirmado y puede pretender ser su servidor. En ese momento, no tienes seguridad real.

La forma legítima única de hacer esto (sin escribir una pila SSL completa) es agregar un ancla de confianza adicional para ser confiable durante el proceso de verificación del certificado. Ambos implican codificar el certificado de anclaje de confianza en su aplicación y agregarlo a los anclajes de confianza que proporciona el sistema operativo (o de lo contrario no podrá conectarse a su sitio si obtiene un certificado real).

Soy consciente de dos formas de hacer esto:

  1. Cree una tienda de confianza personalizada como se describe en http : //www.ibm.com/developerworks/java/library/j-customssl/#8

  2. Cree una instancia personalizada de X509TrustManager y anule el método getAcceptedIssuers para devolver una matriz que contiene su 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;
    }
    

Tenga en cuenta que este código no se ha probado por completo y puede que ni siquiera se compile, pero al menos debería guiarlo en la dirección correcta.

La respuesta de Brian Yarger también funciona en Android 2.2 si modifica la sobrecarga del método createSocket de la siguiente manera. Me tomó un tiempo lograr que los SSL autofirmados funcionen.

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

En Android, HttpProtocolParams acepta ProtocolVersion en lugar de HttpVersion .

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

@Chris: publicar esto como respuesta ya que no puedo agregar comentarios (todavía). Me pregunto si se supone que su enfoque funciona cuando se usa un webView. No puedo hacerlo en Android 2.3; en cambio, solo obtengo una pantalla en blanco.

Después de buscar más, me encontré con este solución simple para manejar errores SSL en una vista web que funcionó de maravilla para mí.

En el controlador, verifico si estoy en un modo de desarrollo especial y llamo a handler.proceed (); de lo contrario, llamo a handler.cancel (). Esto me permite hacer un desarrollo contra un certificado autofirmado en un sitio web local.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top