significa - ssl pinning android
Error ''No peer certificate'' en Android 2.3 pero NO en 4 (5)
Obteniendo la "javax.net.ssl.SSLPeerUnverifiedException: No peer certificate error"
en un emulador que ejecuta Android 2.3 pero NO en 4. En 4, funciona perfectamente. Estoy intentando conectarme a un servidor en vivo a través de https. Utiliza un certificado Thawte válido, funciona bien en todos los navegadores y Android 3 y 4.
Si alguien tiene código de ayuda, POR FAVOR y gracias. Además, si alguien tiene alguna sugerencia sobre una solución segura, lo agradecería. Todavía estoy aprendiendo, y he estado en este problema durante una semana. Tiene que terminar, entonces puedo continuar trabajando y aprendiendo. Urgh.
Aquí está el código HttpCLient, cortesía de Antoine Hauck ( http://blog.antoine.li/2010/10/22/android-trusting-ssl-certificates/ ):
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.security.cert.X509Certificate;
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;
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 {
// Get an instance of the Bouncy Castle KeyStore format
KeyStore trusted = KeyStore.getInstance("BKS");
// Get the raw resource, which contains the keystore with
// your trusted certificates (root and any intermediate certs)
InputStream in = context.getResources().openRawResource(R.raw.my_cert);
try {
// Initialize the keystore with the provided trusted certificates
// Also provide the password of the keystore
trusted.load(in, "my_pass".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;
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
Y aquí está el código que lo ejemplifica:
DefaultHttpClient client = new MyHttpClient(getApplicationContext());
HttpPost post = new HttpPost(server_login_url);
List <NameValuePair> parameters = new ArrayList <NameValuePair>();
parameters.add(new BasicNameValuePair("username", user));
parameters.add(new BasicNameValuePair("password", pass));
try {
post.setEntity(new UrlEncodedFormEntity(parameters, HTTP.UTF_8));
} catch (UnsupportedEncodingException e2) {
// TODO Auto-generated catch block
Log.d(DEBUG_TAG, "in UnsupportedEncodingException - " + e2.getMessage());
e2.printStackTrace();
}
// Execute the GET call and obtain the response
HttpResponse getResponse = null;
try {
getResponse = client.execute(post);
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
// Toast.makeText(getBaseContext(),message,Toast.LENGTH_LONG).show();
Log.d(DEBUG_TAG, "in ClientProtocolException - " + e.getMessage());
} catch (IOException e) {
// TODO Auto-generated catch block
// Toast.makeText(getBaseContext(),message,Toast.LENGTH_LONG).show();
Log.d(DEBUG_TAG, "in client.execute IOException - " + e.getMessage());
e.printStackTrace();
}
El error está atrapado en el bloque IOException. Aquí está la pila:
javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
org.apache.harmony.xnet.provider.jsse.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:258)
org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:93)
org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:381)
org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:164)
org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:359)
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465)
org.ffb.tools.SplashActivity$LoginTask.makeConnection(SplashActivity.java:506)
org.ffb.tools.SplashActivity$LoginTask.doLogin(SplashActivity.java:451)
org.ffb.tools.SplashActivity$LoginTask.doInBackground(SplashActivity.java:439)
org.ffb.tools.SplashActivity$LoginTask.doInBackground(SplashActivity.java:1)
android.os.AsyncTask$2.call(AsyncTask.java:185)
java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:306)
java.util.concurrent.FutureTask.run(FutureTask.java:138)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
java.lang.Thread.run(Thread.java:1019)
Aquí está el orden de la cadena (desde el comando openssl):
La cadena se ve bien, creo.
i:/C=US/O=Thawte, Inc./OU=Domain Validated SSL/CN=Thawte DV SSL CA
1 s:/C=US/O=Thawte, Inc./OU=Domain Validated SSL/CN=Thawte DV SSL CA
i:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA
2 s:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA
i:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Premium Server CA/[email protected]
¿Qué certificados estás cargando desde R.raw.my_cert
? Este error hace referencia a tener un servidor mal configurado, no instalar las CA intermedias principales y secundarias de Thawte, o no cargar y confiar en la cadena de certificados correcta.
Este thread fue realmente útil cuando depuré un problema similar.
Lista de comprobación de Android 2.3 HTTPS / SSL:
- Si su CA se encuentra en la lista 2.3 de CA de confianza de Android, y Thawte lo es, no es necesario incluir el certificado en la aplicación.
- Android 2.3 no es compatible con la Indicación del nombre del servidor, por lo tanto, si su servidor confía en él para el protocolo de enlace SSL, es posible que Android no obtenga los certificados que espera.
- ¿Tiene una cadena de certificados instalada en el servidor y está ordenada correctamente? La mayoría de los navegadores manejan las cadenas de certificados fuera de orden, pero Android 2.3 no. La respuesta de bdc en el hilo que mencioné anteriormente describe cómo verificar la validez de su certificado y cadena SSL con "openssl s_client -connect yourserver.com:443".
- Al desenterrar ese antiguo dispositivo 2.3 que tienes en el cajón inferior, asegúrate de que su fecha y hora estén configuradas correctamente después de ser impotentes durante demasiado tiempo.
La verificación del certificado (o más precisamente, la construcción de la cadena) lógica en (al menos) Android 2.3 es defectuosa.
Esto es lo que he observado:
Si el servidor TLS en su cadena de certificados proporciona solo el certificado del servidor (no autofirmado o autofirmado), puede colocar el certificado del servidor en el almacén de claves y la verificación se realizará correctamente.
Si el servidor TLS en su cadena de certificados también proporciona certificado de CA intermedio, en el almacén de claves debe colocar solo el certificado de CA raíz y asegurarse de que el almacén de claves NO contenga certificados de CA intermedios y del servidor (de lo contrario, la verificación fallará aleatoriamente).
Si el servidor TLS en su cadena de certificados proporciona certificados CA intermedios y raíz en el orden correcto, solo tiene que asegurarse de que el certificado CA raíz esté en el almacén de claves (no importa si los certificados de CA intermedios y del servidor están allí).
Así que la forma "correcta / confiable" de manejar esto es incluir en el almacén de claves certificados de CA raíz y culpar a la configuración del servidor para "No peer certificate", en caso de que la cadena de certificados del servidor no proporcione certificados CA intermedios o los certificados estén en orden incorrecto . Puede probar el servidor usando https://www.ssllabs.com/ssltest/ .
Otra fuente de este mensaje puede ser una configuración de fecha / hora no válida, por ejemplo, cuando se utiliza un dispositivo que se ha ido hace unos meses sin energía. Muy trivial, pero puede ser difícil de detectar.
Tenía exactamente el mismo problema que tú. Todo funcionaba bien con Android> 3.X, pero cuando probé con algunos (pero no todos) los dispositivos 2.3.X obtuve la famosa excepción "Sin error de certificado de pares".
Cavé mucho a través de y otros blogs, pero no encontré nada que funcionara en esos dispositivos "rogue" (en mi caso: uso correcto de truststore, no requiere sni, orden correcta de la cadena de cert en el servidor, etc ...) .
Parece que el Apache HttpClient de Android simplemente no funcionaba correctamente en algunos dispositivos 2.3.X. La excepción "no peer certificates" se producía demasiado pronto para llegar incluso a un código de verificador de nombre de host personalizado, por lo que una solución como esa no funcionaba para mí.
Aquí estaba mi código:
KeyStore trustStore = KeyStore.getInstance("BKS");
InputStream is = this.getAssets().open("discretio.bks");
trustStore.load(is, "discretio".toCharArray());
is.close();
SSLSocketFactory sockfacto = new SSLSocketFactory(trustStore);
sockfacto.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", sockfacto, 443));
SingleClientConnManager mgr = new SingleClientConnManager(httpParameters, schemeRegistry);
HttpClient client = new DefaultHttpClient(mgr, httpParameters);
HttpGet request = new HttpGet(url);
HttpResponse response = client.execute(request);
Así que reescribí todo usando javax.net.ssl.HttpsURLConnection y ahora está funcionando en todos los dispositivos que he probado (de 2.3.3 a 4.X).
Aquí está mi nuevo código:
KeyStore trustStore = KeyStore.getInstance("BKS");
InputStream is = this.getAssets().open("discretio.bks");
trustStore.load(is, "discretio".toCharArray());
is.close();
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
URL request = new URL(url);
HttpsURLConnection urlConnection = (HttpsURLConnection) request.openConnection();
//ensure that we are using a StrictHostnameVerifier
urlConnection.setHostnameVerifier(new StrictHostnameVerifier());
urlConnection.setSSLSocketFactory(context.getSocketFactory());
urlConnection.setConnectTimeout(15000);
InputStream in = urlConnection.getInputStream();
//I don''t want to change my function''s return type (laziness) so I''m building an HttpResponse
BasicHttpEntity res = new BasicHttpEntity();
res.setContent(in);
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, urlConnection.getResponseCode(), "");
resp.setEntity(res);
La cadena de certificados y la validación del nombre de host están funcionando (los probé). Si alguien quiere ver mejor el cambio, aquí hay una diff
Los comentarios son bienvenidos, espero que ayude a algunas personas.