¿Cómo puedo anclar un certificado con Square OKHTTP?

Creo que necesito crear una nueva SSL Socket Factory? Además, no quiero utilizar el Contexto SSL global ( https://github.com/square/okhttp/issues/184 ) por razones obvias.

¡Gracias!

EDITAR:

A partir de okhttp 2.1.0 puede anclar certificados muy fácilmente.

Vea el código fuente aquí para comenzar

ACTUALIZACIÓN PARA OKHTTP 3.0

OKHTTP 3.0 tiene soporte incorporado para fijar certificados. Comience pegando el siguiente código:

String hostname = "yourdomain.com"; CertificatePinner certificatePinner = new CertificatePinner.Builder() .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") .build(); OkHttpClient client = OkHttpClient.Builder() .certificatePinner(certificatePinner) .build(); Request request = new Request.Builder() .url("https://" + hostname) .build(); client.newCall(request).execute(); 

Esto no funcionará porque AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA no es un hash válido de su certificado. La excepción lanzada tendrá los hash correctos de su certificado:

  javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure! Peer certificate chain: sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=: CN=publicobject.com, OU=PositiveSSL sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=: CN=COMODO RSA Secure Server CA sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=: CN=COMODO RSA Certification Authority sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=: CN=AddTrust External CA Root Pinned certificates for publicobject.com: sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= at okhttp3.CertificatePinner.check(CertificatePinner.java) at okhttp3.Connection.upgradeToTls(Connection.java) at okhttp3.Connection.connect(Connection.java) at okhttp3.Connection.connectAndSetOwner(Connection.java) 

Asegúrese de agregarlos a su objeto CertificatePinner y de haber anclado su certificado con éxito:

  CertificatePinner certificatePinner = new CertificatePinner.Builder() .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") .add("publicobject.com", "sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=") .add("publicobject.com", "sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=") .add("publicobject.com", "sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=") .build(); 

TODO EL PASADO AQUÍ ES PARA VERSIONES DE OKHTTP MÁS VIEJAS (2.x)

Después de leer esta publicación en el blog , pude modificar el concepto para utilizarlo con OkHttp. Debería usar al menos la versión 2.0 si quiere evitar el uso de un contexto SSL global.

Esta modificación se aplica solo a la instancia actual de OkHttp y cambia esa instancia para que solo acepte certificados del certificado especificado. Si desea que se acepten otros certificados (como uno de Twitter), simplemente necesita crear una nueva instancia de OkHttp sin las modificaciones que se describen a continuación.

1. Creando un TrustStore

Para fijar un certificado, primero necesita crear un almacén de confianza que contenga este certificado. Para crear el almacén de confianza utilizaremos este útil script de nelenkov ligeramente modificado para nuestros propósitos:

 #!/bin/bash if [ "$#" -ne 3 ]; then echo "Usage: importcert.sh   " exit 1 fi CACERT=$1 BCJAR=$2 SECRET=$3 TRUSTSTORE=mytruststore.bks ALIAS=`openssl x509 -inform PEM -subject_hash -noout -in $CACERT` if [ -f $TRUSTSTORE ]; then rm $TRUSTSTORE || exit 1 fi echo "Adding certificate to $TRUSTSTORE..." keytool -import -v -trustcacerts -alias $ALIAS \ -file $CACERT \ -keystore $TRUSTSTORE -storetype BKS \ -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \ -providerpath $BCJAR \ -storepass $SECRET echo "" echo "Added '$CACERT' with alias '$ALIAS' to $TRUSTSTORE..." 

Para ejecutar este script necesitas 3 cosas:

  1. Asegúrese de que keytool (incluido en Android SDK) esté en su $ PATH.
  2. Asegúrese de tener la última descarga del archivo jar BouncyCastle en el mismo directorio que el script. (Descargar aquí )
  3. El certificado que quieres anclar.

Ahora ejecuta el script

 ./gentruststore.sh your_cert.pem bcprov-jdk15on-150.jar your_secret_pass 

Escriba ‘yes’ para confiar en el certificado, y cuando complete mytruststore.bks se generará en su directorio actual.

2. Aplique su TrustStore a su proyecto de Android

Crea un directorio sin raw debajo de tu carpeta de res . Copia mytruststore.bks aquí.

Ahora aquí hay una clase muy simple que fija tu certificado en OkHttp

 import android.content.Context; import android.util.Log; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; import java.io.InputStream; import java.io.Reader; import java.security.KeyStore; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; /** * Created by martin on 02/06/14. */ public class Pinning { Context context; public static String TRUST_STORE_PASSWORD = "your_secret"; private static final String ENDPOINT = "https://api.yourdomain.com/"; public Pinning(Context c) { this.context = c; } private SSLSocketFactory getPinnedCertSslSocketFactory(Context context) { try { KeyStore trusted = KeyStore.getInstance("BKS"); InputStream in = context.getResources().openRawResource(R.raw.mytruststore); trusted.load(in, TRUST_STORE_PASSWORD.toCharArray()); SSLContext sslContext = SSLContext.getInstance("TLS"); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trusted); sslContext.init(null, trustManagerFactory.getTrustManagers(), null); return sslContext.getSocketFactory(); } catch (Exception e) { Log.e("MyApp", e.getMessage(), e); } return null; } public void makeRequest() { try { OkHttpClient client = new OkHttpClient(); client.setSslSocketFactory(getPinnedCertSslSocketFactory(context)); Request request = new Request.Builder() .url(ENDPOINT) .build(); Response response = client.newCall(request).execute(); Log.d("MyApp", response.body().string()); } catch (Exception e) { Log.e("MyApp", e.getMessage(), e); } } } 

Como puede ver, instanciamos una nueva instancia de OkHttpClient y llamamos a setSslSocketFactory , pasando SSLSocketFactory con nuestro almacén de confianza personalizado. Asegúrese de configurar TRUST_STORE_PASSWORD a la contraseña que pasó al script de shell. Su instancia de OkHttp ahora solo debe aceptar el certificado que ha especificado.

Esto es más fácil de lo que pensaba con OkHttp.

Sigue estos pasos:

1. Obtenga las claves públicas sha1. La documentación de OkHttp nos brinda una manera clara de hacer esto completo con código de muestra. En caso de que desaparezca, aquí está pegado a continuación:

Por ejemplo, para fijar https://publicobject.com , comience con una configuración incorrecta:

 String hostname = "publicobject.com"; CertificatePinner certificatePinner = new CertificatePinner.Builder() .add(hostname, "sha1/BOGUSPIN") .build(); OkHttpClient client = new OkHttpClient(); client.setCertificatePinner(certificatePinner); Request request = new Request.Builder() .url("https://" + hostname) .build(); client.newCall(request).execute(); 

Como se esperaba, esto falla con una excepción de fijación de certificado:

javax.net.ssl.SSLPeerUnverifiedException: ¡fallo de fijación del certificado!
cadena de certificados Peer: sha1 / DmxUShsZuNiqPQsX2Oi9uv2sCnw =: CN = publicobject.com, OU = PositiveSSL sha1 / SXxoaOSEzPC6BgGmxAt / EAcsajw =: CN = COMODO RSA validación de dominios Secure CA servidor sha1 / blhOM3W9V / bVQhsWAcLYwPU6n24 =: CN = COMODO RSA Autoridad de Certificación sha1 / T5x9IXmcrQ7YuQxXnxoCmeeQ84c =: CN = AddTrust CA raíz externa

Certificados fijados para publicobject.com:

sha1 / BOGUSPIN
en com.squareup.okhttp.CertificatePinner.check (CertificatePinner.java)
en com.squareup.okhttp.Connection.upgradeToTls (Connection.java)
en com.squareup.okhttp.Connection.connect (Connection.java)
en com.squareup.okhttp.Connection.connectAndSetOwner (Connection.java)

Haga un seguimiento pegando los hash de las claves públicas de la excepción en la configuración del pinner del certificado:

Nota al pie: Si está haciendo esto en Android obtendrá una excepción por separado si está haciendo esto en un hilo de interfaz de usuario, así que asegúrese de hacerlo en un hilo de fondo.

2. Configure su OkHttp Client:

 OkHttpClient client = new OkHttpClient(); client.setCertificatePinner(new CertificatePinner.Builder() .add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=") .add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=") .add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=") .add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=") .build()); 

¡Eso es todo al respecto!

Si no tiene acceso al dominio (acceso restringido, por ejemplo) y no puede probar el hash falso, pero tiene un archivo de certificado, puede usar openssl para recuperarlo:

 openssl x509 -in cert.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 

Para ampliar el código fuente de muestra @ Michael-barany compartido, he realizado algunas pruebas y parece ser una muestra de código engañosa. En la muestra, el código de la excepción observó 4 hashes sha1 de la excepción de la cadena de certificados:

 javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure! Peer certificate chain: sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=: CN=publicobject.com, OU=PositiveSSL sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=: CN=COMODO RSA Domain Validation Secure Server CA sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=: CN=COMODO RSA Certification Authority sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=: CN=AddTrust External CA Root 

luego, posteriormente, agregó los 4 valores hash de clave pública sha1 al CertificatePinner Builder.

 CertificatePinner certificatePinner = new CertificatePinner.Builder() .add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=") .add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=") .add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=") .add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=") .build(); 

Sin embargo, dadas las pruebas que realicé y revisé el código, solo se interpretaría el primero válido, por lo que sería más adecuado incluir solo UNO de los hashes devueltos. Puede usar el hash más específico “DmxUShsZuNiqPQsX2Oi9uv2sCnw” para el certificado de sitio preciso … o puede usar el hash más amplio “T5x9IXmcrQ7YuQxXnxoCmeeQ84c” para la raíz de CA en función de la postura de seguridad que desee.

Encontré el ejemplo mencionado en la sección Autorización de certificados desconocidos de este enlace developer.android.com/training/articles/security-ssl muy útil.

El SSLSocketFactory devuelto en context.getSocketFactory () se puede usar para configurar OkHttpClient en el método setSslSocketFactory ().

Nota: La sección de la autoridad de certificación desconocida también menciona el enlace para descargar un archivo cert para usar y verificar este código.

Aquí está el método de muestra que he escrito para obtener SSLSocketFactory

 private SSLSocketFactory getSslSocketFactory() { try { // Load CAs from an InputStream // (could be from a resource or ByteArrayInputStream or ...) CertificateFactory cf = CertificateFactory.getInstance("X.509"); // From https://www.washington.edu/itconnect/security/ca/load-der.crt InputStream caInput = getApplicationContext().getResources().openRawResource(R.raw.loadder); Certificate ca = null; try { ca = cf.generateCertificate(caInput); System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); } catch (CertificateException e) { e.printStackTrace(); } finally { caInput.close(); } // Create a KeyStore containing our trusted CAs String keyStoreType = KeyStore.getDefaultType(); KeyStore keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(null, null); if (ca == null) return 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); return context.getSocketFactory(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return null; } 

Más tarde solo estoy estableciendo esto en OkHttpClient como este

 httpClient.setSslSocketFactory(sslSocketFactory); 

y luego hacer la llamada https

 httpClient.newCall(requestBuilder.build()).enqueue(callback);