Obtenir le "javax.net.ssl.SSLPeerUnverifiedException: No peer certificate error"
dans un émulateur fonctionnant sous Android 2.3 mais PAS dans 4. Dans 4, cela fonctionne parfaitement. J'essaie de me connecter à un serveur en direct via https. Il utilise un certificat Thawte valide, fonctionne correctement dans tous les navigateurs et Android 3 et 4.
Si quelqu'un a l'aide du code, SVP et merci. En outre, si quelqu'un a des suggestions sur une solution de contournement sécurisée, je l'apprécierais. J'apprends encore et je suis sur ce problème depuis une semaine. Cela doit cesser pour que je puisse continuer à travailler et à apprendre. Urgh.
Voici le code HttpCLient, avec la permission 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);
}
}
}
Et voici le code qui l'instancie:
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();
}
L'erreur est interceptée dans le bloc IOException. Voici la pile:
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)
Voici l'ordre de la chaîne (à partir de la commande openssl):
La chaîne a l'air bien, je pense.
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]
Ce thread était vraiment utile lorsque j'ai débogué un problème similaire.
Liste de contrôle récapitulative Android 2.3 HTTPS/SSL:
J'ai eu exactement le même problème que vous. Tout fonctionnait bien avec Android> 3.X, mais lorsque j'ai essayé avec certains appareils 2.3.X (mais pas tous!), J'ai obtenu cette fameuse exception "Aucune erreur de certificat d'homologue".
J'ai beaucoup creusé dans stackoverflow et d'autres blogs, mais je n'ai rien trouvé qui fonctionne sur ces périphériques "non autorisés" (dans mon cas: utilisation correcte du fichier de clés certifiées; pas de sni requis; ordre correct des chaînes de certificats sur le serveur; etc ...) .
Il semblerait que l'Apache HttpClient d'Android ne soit que ne fonctionne pas correctement sur certains périphériques 2.3.X. L'exception "aucun certificat homologue" se produisait trop tôt pour atteindre même un code de vérificateur de nom d'hôte personnalisé, des solutions telles que celle-là ne fonctionnaient pas pour moi.
Voici mon code:
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);
J'ai donc tout réécrit en utilisant javax.net.ssl.HttpsURLConnection et maintenant cela fonctionne sur tous les périphériques que j'ai testés (de la version 2.3.3 à la version 4.X).
Voici mon nouveau code:
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 validation de la chaîne de certificats et du nom d’hôte fonctionne (je les ai testés). Si quelqu'un souhaite mieux examiner le changement, voici un diff
Les commentaires sont les bienvenus, j'espère que cela aidera certaines personnes.
Une autre source de ce message peut être un paramètre de date/heure non valide, par exemple. lors de l'utilisation d'un appareil éteint depuis quelques mois. Assez trivial, mais il peut être difficile à repérer.
La logique de vérification de certificat (ou plus précisément de création de chaîne) dans (au moins) Android 2.3 est défectueuse.
Voici ce que j'ai observé:
Si le serveur TLS de sa chaîne de certificats ne fournit que le certificat du serveur (non auto-signé ou auto-signé), vous pouvez alors mettre le certificat du serveur dans le magasin de clés et la vérification aboutira.
Si le serveur TLS de sa chaîne de certificats fournit également un certificat d'autorité de certification intermédiaire, vous ne devez placer que le certificat d'autorité de certification racine dans le magasin de clés et vous assurer qu'il ne contient PAS les certificats d'autorité de certification serveur et intermédiaire (sinon la vérification échouera de manière aléatoire).
Si le serveur TLS de sa chaîne de certificats fournit des certificats d'autorité de certification intermédiaire et racine dans le bon ordre, vous devez simplement vous assurer que le certificat d'autorité de certification racine est dans le magasin de clés (peu importe si les certificats d'autorité de certification serveur et intermédiaire sont présents).
Par conséquent, la manière "correcte/fiable" de gérer cela consiste à inclure dans le magasin de clés uniquement les certificats de l'autorité de certification racine et la configuration du serveur responsable du "Aucun certificat homologue" - dans le cas où la chaîne de certificats du serveur ne fournit pas de certificats d'autorité de certification intermédiaires. . Vous pouvez tester le serveur en utilisant https://www.ssllabs.com/ssltest/ .
Quels certificats chargez-vous depuis R.raw.my_cert
? Cette erreur indique soit un serveur mal configuré que vous n'utilisez pas les autorités de certification intermédiaires principales et secondaires de Thawte - ou _ vous ne chargez pas et ne faites pas confiance à la chaîne de certificats correcte.