varios studio permisos pedir paquete nombre multiples change cambiar application aplicacion java android ssl https certificate

java - studio - Conexión HTTPS con certificado de cliente en una aplicación de Android



pedir varios permisos android studio (5)

Estoy intentando reemplazar la conexión HTTP actualmente en funcionamiento con una conexión HTTPS en una aplicación de Android que estoy escribiendo. La seguridad adicional de una conexión HTTPS es necesaria, por lo que no puedo ignorar este paso.

Tengo lo siguiente:

  1. Un servidor configurado para establecer una conexión HTTPS y que requiere un certificado de cliente
    • Este servidor tiene un certificado emitido por una CA estándar de gran escala. En resumen, si tengo acceso a esta conexión a través del navegador en Android, funciona bien porque los dispositivos de confianza reconocen la CA. (Por lo tanto, no está autofirmado)
  2. Un certificado de cliente que es esencialmente autofirmado. (Emitido por una CA interna)
  3. Una aplicación de Android que carga este certificado de cliente e intenta conectarse al servidor mencionado anteriormente, pero tiene los siguientes problemas / propiedades:
    • El cliente puede conectarse al servidor cuando el servidor está configurado para no requerir un certificado de cliente. Básicamente, si utilizo SSLSocketFactory.getSocketFactory() la conexión funciona bien, pero el certificado del cliente es una parte obligatoria de las especificaciones de esta, por lo que:
    • El cliente produce una javax.net.ssl.SSLPeerUnverifiedException: No peer certificate cuando intento conectarme con mi SSLSocketFactory personalizado, pero no estoy del todo seguro de por qué. Esta excepción parece un poco ambigua después de buscar en Internet varias soluciones para esto.

Aquí está el código relavent para el cliente:

SSLSocketFactory socketFactory = null; public void onCreate(Bundle savedInstanceState) { loadCertificateData(); } private void loadCertificateData() { try { File[] pfxFiles = Environment.getExternalStorageDirectory().listFiles(new FileFilter() { public boolean accept(File file) { if (file.getName().toLowerCase().endsWith("pfx")) { return true; } return false; } }); InputStream certificateStream = null; if (pfxFiles.length==1) { certificateStream = new FileInputStream(pfxFiles[0]); } KeyStore keyStore = KeyStore.getInstance("PKCS12"); char[] password = "somePassword".toCharArray(); keyStore.load(certificateStream, password); System.out.println("I have loaded [" + keyStore.size() + "] certificates"); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, password); socketFactory = new SSLSocketFactory(keyStore); } catch (Exceptions e) { // Actually a bunch of catch blocks here, but shortened! } } private void someMethodInvokedToEstablishAHttpsConnection() { try { HttpParams standardParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(standardParams, 5000); HttpConnectionParams.setSoTimeout(standardParams, 30000); SchemeRegistry schRegistry = new SchemeRegistry(); schRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); schRegistry.register(new Scheme("https", socketFactory, 443)); ClientConnectionManager connectionManager = new ThreadSafeClientConnManager(standardParams, schRegistry); HttpClient client = new DefaultHttpClient(connectionManager, standardParams); HttpPost request = new HttpPost(); request.setURI(new URI("https://TheUrlOfTheServerIWantToConnectTo)); request.setEntity("Some set of data used by the server serialized into string format"); HttpResponse response = client.execute(request); resultData = EntityUtils.toString(response.getEntity()); } catch (Exception e) { // Catch some exceptions (Actually multiple catch blocks, shortened) } }

He verificado que sí, KeyStore carga un certificado y está contento con eso.

Tengo dos teorías sobre lo que me falta al leer sobre las conexiones HTTPS / SSL, pero como esta es realmente mi primera incursión, estoy un poco desconcertado sobre lo que realmente necesito para resolver este problema.

La primera posibilidad, por lo que puedo decir, es que necesito configurar este SSLSocketFactory con el almacén de confianza de los dispositivos que incluye todas las Autoridades de certificación intermedias y finales estándar. Es decir, el valor predeterminado del dispositivo de SSLSocketFactory.getSocketFactory() carga un conjunto de CA en el almacén de confianza de la fábrica que se utiliza para confiar en el servidor cuando envía su certificado, y eso es lo que está fallando en mi código, porque no tengo correctamente la tienda de confianza cargada. Si esto es cierto, ¿cuál es la mejor manera de cargar estos datos?

La segunda posibilidad se debe al hecho de que el certificado del cliente es autofirmado (o emitido por una autoridad de certificación interna; corrígeme si me equivoco, pero estos realmente equivalen a lo mismo, para todos los intentos y propósitos aquí) . De hecho, este es el almacén de confianza que me falta, y básicamente necesito proporcionar una forma para que el servidor valide el certificado con la CA interna, y también validar que esta CA interna es de hecho "confiable" . Si esto es cierto, ¿exactamente qué tipo de cosa estoy buscando? He visto alguna referencia a esto que me hace creer que este puede ser mi problema, como here , pero realmente no estoy seguro. Si este es realmente mi problema, ¿qué pediría de la persona que mantiene la CA interna, y luego cómo agregaría esto a mi código para que mi conexión HTTPS funcione?

La tercera, y con suerte la menos posible solución, es que estoy totalmente equivocado sobre algún punto aquí y he perdido un paso crucial o estoy descuidando por completo una porción de HTTPS / SSL de la que actualmente no tengo conocimiento. Si este es el caso, ¿podría darme un poco de orientación para que pueda ir y aprender qué es lo que necesito aprender?

¡Gracias por leer!


Creo que este es realmente el problema.

La primera posibilidad, por lo que puedo decir, es que necesito configurar este SSLSocketFactory con el almacén de confianza de los dispositivos que incluye todas las Autoridades de certificación de nivel intermedio y punto final estándar.

Si esto es cierto, ¿cuál es la mejor manera de cargar estos datos?

Pruebe algo como esto (necesitará que su fábrica de sockets use este administrador de confianza predeterminado):

X509TrustManager manager = null; FileInputStream fs = null; TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); try { fs = new FileInputStream(System.getProperty("javax.net.ssl.trustStore")); keyStore.load(fs, null); } finally { if (fs != null) { fs.close(); } } trustManagerFactory.init(keyStore); TrustManager[] managers = trustManagerFactory.getTrustManagers(); for (TrustManager tm : managers) { if (tm instanceof X509TrustManager) { manager = (X509TrustManager) tm; break; } }

EDITAR: Mire la respuesta de Pooks antes de usar el código aquí. Parece que hay una mejor manera de hacer esto ahora.


Estoy publicando una respuesta actualizada ya que las personas aún hacen referencia y votan esta pregunta. Tuve que cambiar el código de fábrica de socket algunas veces ya que algunas cosas han cambiado desde Android 4.0

// Trust manager / truststore KeyStore trustStore=KeyStore.getInstance(KeyStore.getDefaultType()); // If we''re on an OS version prior to Ice Cream Sandwich (4.0) then use the standard way to get the system // trustStore -- System.getProperty() else we need to use the special name to get the trustStore KeyStore // instance as they changed their trustStore implementation. if (Build.VERSION.RELEASE.compareTo("4.0") < 0) { TrustManagerFactory trustManagerFactory=TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); FileInputStream trustStoreStream=new FileInputStream(System.getProperty("javax.net.ssl.trustStore")); trustStore.load(trustStoreStream, null); trustManagerFactory.init(trustStore); trustStoreStream.close(); } else { trustStore=KeyStore.getInstance("AndroidCAStore"); } InputStream certificateStream=new FileInputStream(userCertFile); KeyStore keyStore=KeyStore.getInstance("PKCS12"); try { keyStore.load(certificateStream, certPass.toCharArray()); Enumeration<String> aliases=keyStore.aliases(); while (aliases.hasMoreElements()) { String alias=aliases.nextElement(); if (keyStore.getCertificate(alias).getType().equals("X.509")) { X509Certificate cert=(X509Certificate)keyStore.getCertificate(alias); if (new Date().after(cert.getNotAfter())) { // This certificate has expired return; } } } } catch (IOException ioe) { // This occurs when there is an incorrect password for the certificate return; } finally { certificateStream.close(); } KeyManagerFactory keyManagerFactory=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, certPass.toCharArray()); socketFactory=new SSLSocketFactory(keyStore, certPass, trustStore);

Espero que esto ayude a cualquiera que siga viniendo en el futuro.


Hay una forma más simple de implementar la solución de @jglouie. Básicamente, si utiliza un SSLContext e inicializa con null para el parámetro de administrador de confianza, debe obtener un contexto de SSL utilizando el administrador de confianza predeterminado. Tenga en cuenta que esto no está documentado en la documentación de Android, pero la documentación de Java para SSLContext.init dice

Cualquiera de los dos primeros parámetros puede ser nulo, en cuyo caso se buscará en los proveedores de seguridad instalados la implementación de mayor prioridad de la fábrica apropiada.

Así es como se vería el código:

// This can be any protocol supported by your target devices. // For example "TLSv1.2" is supported by the latest versions of Android final String SSL_PROTOCOL = "TLS"; try { sslContext = SSLContext.getInstance(SSL_PROTOCOL); // Initialize the context with your key manager and the default trust manager // and randomness source sslContext.init(keyManagerFactory.getKeyManagers(), null, null); } catch (NoSuchAlgorithmException e) { Log.e(TAG, "Specified SSL protocol not supported! Protocol=" + SSL_PROTOCOL); e.printStackTrace(); } catch (KeyManagementException e) { Log.e(TAG, "Error setting up the SSL context!"); e.printStackTrace(); } // Get the socket factory socketFactory = sslContext.getSocketFactory();


Lo intenté un par de días. Finalmente recibí la respuesta, por lo que me gustaría publicar aquí mis pasos y todo mi código para ayudar a otra persona.

1) para obtener el certificado del sitio que desea conectar

echo | openssl s_client -connect ${MY_SERVER}:443 2>&1 | sed -ne ''/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'' > mycert.pem

2) para crear tu llave, necesitas la biblioteca BouncyCastle que puedes descargar here

keytool -import -v -trustcacerts -alias 0 -file mycert.pem -keystore “store_directory/mykst“ -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath “directory_of_bouncycastle/bcprov-jdk16-145.jar” -storepass mypassword

3) para verificar si la clave fue creada

keytool -list -keystore "carpeta_almacen/mykst" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "directory_of_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mypassword

y deberías ver algo como esto:

Tipo de almacén de claves: BKS Proveedor de almacén de claves: BC

Su almacén de claves contiene entrada 1

0, 07-dic-2011, trustedCertEntry,

Huella digital de certificado (MD5):

55: FD: E5: E3: 8A: 4C: D6: B8: 69: EB: 6A: 49: 05: 5F: 18: 48

4) luego debe copiar el archivo "mykst" en el directorio "res / raw" (créelo si no existe) en su proyecto de Android.

5) agregue los permisos a en el manifiesto de Android

<uses-permission android:name="android.permission.INTERNET"/>

6) aquí el código!

activity_main.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:padding="10dp" > <Button android:id="@+id/button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Cargar contenido" /> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#4888ef"> <ProgressBar android:id="@+id/loading" android:layout_width="50dp" android:layout_height="50dp" android:indeterminate="true" android:layout_centerInParent="true" android:visibility="gone"/> <ScrollView android:layout_width="fill_parent" android:layout_height="fill_parent" android:fillViewport="true" android:padding="10dp"> <TextView android:id="@+id/output" android:layout_width="fill_parent" android:layout_height="fill_parent" android:textColor="#FFFFFF"/> </ScrollView> </RelativeLayout> </LinearLayout>

MyHttpClient

package com.example.https; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.KeyStore; import java.security.cert.X509Certificate; import java.util.Date; import java.util.Enumeration; import org.apache.http.conn.ClientConnectionManager; 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.impl.conn.SingleClientConnManager; import android.content.Context; import android.os.Build; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; 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 { // Trust manager / truststore KeyStore trustStore=KeyStore.getInstance(KeyStore.getDefaultType()); // If we''re on an OS version prior to Ice Cream Sandwich (4.0) then use the standard way to get the system // trustStore -- System.getProperty() else we need to use the special name to get the trustStore KeyStore // instance as they changed their trustStore implementation. if (Build.VERSION.RELEASE.compareTo("4.0") < 0) { TrustManagerFactory trustManagerFactory=TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); FileInputStream trustStoreStream=new FileInputStream(System.getProperty("javax.net.ssl.trustStore")); trustStore.load(trustStoreStream, null); trustManagerFactory.init(trustStore); trustStoreStream.close(); } else { trustStore=KeyStore.getInstance("AndroidCAStore"); } InputStream certificateStream = context.getResources().openRawResource(R.raw.mykst); KeyStore keyStore=KeyStore.getInstance("BKS"); try { keyStore.load(certificateStream, "mypassword".toCharArray()); Enumeration<String> aliases=keyStore.aliases(); while (aliases.hasMoreElements()) { String alias=aliases.nextElement(); if (keyStore.getCertificate(alias).getType().equals("X.509")) { X509Certificate cert=(X509Certificate)keyStore.getCertificate(alias); if (new Date().after(cert.getNotAfter())) { // This certificate has expired return null; } } } } catch (IOException ioe) { // This occurs when there is an incorrect password for the certificate return null; } finally { certificateStream.close(); } KeyManagerFactory keyManagerFactory=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, "mypassword".toCharArray()); return new SSLSocketFactory(keyStore, "mypassword", trustStore); } catch (Exception e) { throw new AssertionError(e); } } }

Actividad principal

package com.example.https; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import javax.net.ssl.SSLSocketFactory; public class MainActivity extends Activity { private View loading; private TextView output; private Button button; SSLSocketFactory socketFactory = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loading = findViewById(R.id.loading); output = (TextView) findViewById(R.id.output); button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new CargaAsyncTask().execute(new Void[0]); } }); } class CargaAsyncTask extends AsyncTask<Void, Void, String> { @Override protected void onPreExecute() { super.onPreExecute(); loading.setVisibility(View.VISIBLE); button.setEnabled(false); } @Override protected String doInBackground(Void... params) { // Instantiate the custom HttpClient DefaultHttpClient client = new MyHttpClient(getApplicationContext()); HttpGet get = new HttpGet("https://www.google.com"); // Execute the GET call and obtain the response HttpResponse getResponse; String resultado = null; try { getResponse = client.execute(get); HttpEntity responseEntity = getResponse.getEntity(); InputStream is = responseEntity.getContent(); resultado = convertStreamToString(is); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return resultado; } @Override protected void onPostExecute(String result) { super.onPostExecute(result); loading.setVisibility(View.GONE); button.setEnabled(true); if (result == null) { output.setText("Error"); } else { output.setText(result); } } } public static String convertStreamToString(InputStream is) throws IOException { /* * To convert the InputStream to String we use the * Reader.read(char[] buffer) method. We iterate until the * Reader return -1 which means there''s no more data to * read. We use the StringWriter class to produce the string. */ if (is != null) { Writer writer = new StringWriter(); char[] buffer = new char[1024]; try { Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); int n; while ((n = reader.read(buffer)) != -1) { writer.write(buffer, 0, n); } } finally { is.close(); } return writer.toString(); } else { return ""; } } }

¡Espero que pueda ser útil para otra persona! ¡disfrútala!


Parece que también necesita establecer el nombre de host para su SSLSocketFactory.

Intenta agregar la línea

socketFactory.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);

antes de crear una nueva conexión con su SSLFactory .

Aparte de las diferencias en las estructuras, tenemos un código similar. En mi implementación, acabo de crear mi propia extensión del DefaultHttpClient que se parece a la mayoría de su código anterior. Si esto no lo soluciona, puedo publicar el código de trabajo para eso y puedes darle una oportunidad a ese enfoque.

editar: aquí está mi versión de trabajo

public class ActivateHttpClient extends DefaultHttpClient { final Context context; /** * Public constructor taking two arguments for ActivateHttpClient. * @param context - Context referencing the calling Activity, for creation of * the socket factory. * @param params - HttpParams passed to this, specifically to set timeouts on the * connection. */ public ActivateHttpClient(Context context, HttpParams params) { this.setParams(params); } /* (non-Javadoc) * @see org.apache.http.impl.client.DefaultHttpClient#createClientConnectionManager() * Create references for both http and https schemes, allowing us to attach our custom * SSLSocketFactory to either */ @Override protected ClientConnectionManager createClientConnectionManager() { SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory .getSocketFactory(), 80)); registry.register(new Scheme("https", newSslSocketFactory(), 443)); return new SingleClientConnManager(getParams(), registry); } /** * Creation of new SSLSocketFactory, which imports a certificate from * a server which self-signs its own certificate. * @return */ protected SSLSocketFactory newSslSocketFactory() { try { //Keystore must be in BKS (Bouncy Castle Keystore) KeyStore trusted = KeyStore.getInstance("BKS"); //Reference to the Keystore InputStream in = context.getResources().openRawResource( R.raw.cert); //Password to the keystore try { trusted.load(in, PASSWORD_HERE.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; // return new SSLSocketFactory(trusted); } catch (Exception e) { e.printStackTrace(); throw new AssertionError(e); } } }

y se puede llamar como se muestra:

HttpParams params = new BasicHttpParams(); // Set the timeout in milliseconds until a connection is established. int timeoutConnection = 500; HttpConnectionParams.setConnectionTimeout( params , timeoutConnection ); // Set the default socket timeout (SO_TIMEOUT) // in milliseconds which is the timeout for waiting for data. int timeoutSocket = 1000; HttpConnectionParams.setSoTimeout( params , timeoutSocket ); //ADD more connection options here! String url = "https:// URL STRING HERE"; HttpGet get = new HttpGet( url ); ActivateHttpClient client = new ActivateHttpClient( this.context, params ); // Try to execute the HttpGet, throwing errors // if no response is received, or if there is // an error in the execution. HTTPResponse response = client.execute( get );