Dans mon serveur (serveur de production), j'ai un certificat SSL goDaddy. J'ai iOS et Android applications se connectant avec le serveur, iOS se connectant sans problème, Android avec les versions 4. * tout va bien, mais avec les appareils avec 2.3. * J'obtiens toujours une exception SSLHandshakeException.
J'ai fait exactement comme sur Android Developers ( https://developer.Android.com/training/articles/security-ssl.html ).
J'ai déjà vu des threads similaires ici dans stackoverflow ( ici ) mais aucun n'aide.
Ensuite, j'ai vu this thread parler de l'utilisation étendue des clés, mais lors du débogage, j'obtiens les informations suivantes:
[2]: OID: 2.5.29.37, Critical: false
Extended Key Usage: [ "1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.2" ]
Je suppose donc que le certificat ne "force" pas l'utilisation des clés étendues.
Aussi sur le thread this il y a d'autres causes possibles telles que la date/heure étant complètement erronées, qui n'existent pas toutes.
Compte tenu de cela, je ne sais pas maintenant où pourrait être le problème.
Aucune suggestion?
MODIFIER: StackTrace ci-dessous:
08-04 16:54:30.139: W/System.err(4832): Caused by: Java.security.cert.CertificateException: Java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
08-04 16:54:30.149: W/System.err(4832): at org.Apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.Java:161)
08-04 16:54:30.149: W/System.err(4832): at org.Apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.Java:664)
08-04 16:54:30.149: W/System.err(4832): at org.Apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
08-04 16:54:30.159: W/System.err(4832): at org.Apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.Java:474)
Il semble que l'émetteur de votre certificat ne se trouve pas dans le magasin de clés de confiance des appareils 2.3.
Jetez un œil à la racine et aux intermédiaires de votre certificat GoDaddy et vérifiez si les certificats sont présents sur votre appareil 2.3.
Voir http://www.andreabaccega.com/blog/2010/09/23/Android-root-certification-authorities-list/ pour obtenir une liste de 2,3 certificats.
Lorsque seule l'autorité de certification racine est disponible, assurez-vous que votre serveur Web sert également les certificats intermédiaires sur demande.
J'ai parcouru BEAUCOUP d'endroits dans SO et le Web pour résoudre ce problème. C'est le code qui a fonctionné pour moi (Android 21):
ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509","BC");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
String alias = "alias";//cert.getSubjectX500Principal().getName();
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null);
trustStore.setCertificateEntry(alias, cert);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(trustStore, null);
KeyManager[] keyManagers = kmf.getKeyManagers();
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);
TrustManager[] trustManagers = tmf.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
URL url = new URL(someURL);
conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sslContext.getSocketFactory());
app.certificateString
est une chaîne qui contient le certificat, par exemple:
static public String certificateString=
"-----BEGIN CERTIFICATE-----\n" +
"MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
"BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
... a bunch of characters...
"5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
"-----END CERTIFICATE-----";
J'ai testé que vous pouvez mettre n'importe quel caractère dans la chaîne de certificat, si elle est auto-signée, tant que vous gardez la structure exacte ci-dessus. J'ai obtenu la chaîne de certificat avec la ligne de commande du terminal de mon ordinateur portable. Si vous avez besoin de plus de détails, faites-le moi savoir.
Au cas où quelqu'un aurait besoin de la réponse, j'ai finalement trouvé la réponse après 2 jours de google. Fondamentalement, nous devons utiliser TrustManager personnalisé pour faire confiance aux autorités de certification dans notre magasin de clés, car en Android 2.3.x, le magasin de clés n'est pas correctement utilisé. Crédit à https://github.com/delgurth pour CustomTrustManager.
Veuillez vous référer: https://github.com/ikust/hello-pinnedcerts/issues/2
KeyPinStore.Java
import Java.io.BufferedInputStream;
import Java.io.IOException;
import Java.io.InputStream;
import Java.security.KeyManagementException;
import Java.security.KeyStore;
import Java.security.KeyStoreException;
import Java.security.NoSuchAlgorithmException;
import Java.security.cert.Certificate;
import Java.security.cert.CertificateException;
import Java.security.cert.CertificateFactory;
import Java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
public class KeyPinStore {
private static final String[] certificates = {"certificate1.crt", "certificate2.crt", "certificate3.crt", "certificate4.crt"};
private static KeyPinStore instance = null;
private SSLContext sslContext = SSLContext.getInstance("TLS");
public static synchronized KeyPinStore getInstance() throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
if (instance == null) {
instance = new KeyPinStore();
}
return instance;
}
private KeyPinStore() throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
for (int i = 0; i < certificates.length; i++) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(Application.context.getAssets().open("certificate/" + certificates[i]));
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
// Create a KeyStore containing our trusted CAs
keyStore.setCertificateEntry("ca" + i, ca);
}
// Use custom trust manager to trusts the CAs in our KeyStore
TrustManager[] trustManagers = {new CustomTrustManager(keyStore)};
// Create an SSLContext that uses our TrustManager
// SSLContext context = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
}
public SSLContext getContext() {
return sslContext;
}
}
CustomTrustManager.Java
import Java.security.KeyStore;
import Java.security.KeyStoreException;
import Java.security.NoSuchAlgorithmException;
import Java.security.Principal;
import Java.security.cert.CertificateException;
import Java.security.cert.X509Certificate;
import Java.util.Arrays;
import Java.util.List;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* A custom X509TrustManager implementation that trusts a specified server certificate in addition
* to those that are in the system TrustStore.
* Also handles an out-of-order certificate chain, as is often produced by Apache's mod_ssl
*/
public class CustomTrustManager implements X509TrustManager {
private final TrustManager[] originalTrustManagers;
private final KeyStore trustStore;
/**
* @param trustStore A KeyStore containing the server certificate that should be trusted
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
*/
public CustomTrustManager(KeyStore trustStore) throws NoSuchAlgorithmException, KeyStoreException {
this.trustStore = trustStore;
final TrustManagerFactory originalTrustManagerFactory = TrustManagerFactory.getInstance("X509");
originalTrustManagerFactory.init(trustStore);
originalTrustManagers = originalTrustManagerFactory.getTrustManagers();
}
/**
* No-op. Never invoked by client, only used in server-side implementations
* @return
*/
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
/**
* No-op. Never invoked by client, only used in server-side implementations
* @return
*/
public void checkClientTrusted(X509Certificate[] chain, String authType) throws Java.security.cert.CertificateException {
}
/**
* Given the partial or complete certificate chain provided by the peer,
* build a certificate path to a trusted root and return if it can be validated and is trusted
* for client SSL authentication based on the authentication type. The authentication type is
* determined by the actual certificate used. For instance, if RSAPublicKey is used, the authType should be "RSA".
* Checking is case-sensitive.
* Defers to the default trust manager first, checks the cert supplied in the ctor if that fails.
* @param chain the server's certificate chain
* @param authType the authentication type based on the client certificate
* @throws Java.security.cert.CertificateException
*/
public void checkServerTrusted(X509Certificate[] chain, String authType) throws Java.security.cert.CertificateException {
try {
for (TrustManager originalTrustManager : originalTrustManagers) {
((X509TrustManager) originalTrustManager).checkServerTrusted(chain, authType);
}
} catch(CertificateException originalException) {
try {
// Ordering issue?
X509Certificate[] reorderedChain = reorderCertificateChain(chain);
if (! Arrays.equals(chain, reorderedChain)) {
checkServerTrusted(reorderedChain, authType);
return;
}
for (int i = 0; i < chain.length; i++) {
if (validateCert(reorderedChain[i])) {
return;
}
}
throw originalException;
} catch(Exception ex) {
ex.printStackTrace();
throw originalException;
}
}
}
/**
* Checks if we have added the certificate in the trustStore, if that's the case we trust the certificate
* @param x509Certificate the certificate to check
* @return true if we know the certificate, false otherwise
* @throws KeyStoreException on problems accessing the key store
*/
private boolean validateCert(final X509Certificate x509Certificate) throws KeyStoreException {
return trustStore.getCertificateAlias(x509Certificate) != null;
}
/**
* Puts the certificate chain in the proper order, to deal with out-of-order
* certificate chains as are sometimes produced by Apache's mod_ssl
* @param chain the certificate chain, possibly with bad ordering
* @return the re-ordered certificate chain
*/
private X509Certificate[] reorderCertificateChain(X509Certificate[] chain) {
X509Certificate[] reorderedChain = new X509Certificate[chain.length];
List<X509Certificate> certificates = Arrays.asList(chain);
int position = chain.length - 1;
X509Certificate rootCert = findRootCert(certificates);
reorderedChain[position] = rootCert;
X509Certificate cert = rootCert;
while((cert = findSignedCert(cert, certificates)) != null && position > 0) {
reorderedChain[--position] = cert;
}
return reorderedChain;
}
/**
* A helper method for certificate re-ordering.
* Finds the root certificate in a possibly out-of-order certificate chain.
* @param certificates the certificate change, possibly out-of-order
* @return the root certificate, if any, that was found in the list of certificates
*/
private X509Certificate findRootCert(List<X509Certificate> certificates) {
X509Certificate rootCert = null;
for(X509Certificate cert : certificates) {
X509Certificate signer = findSigner(cert, certificates);
if(signer == null || signer.equals(cert)) { // no signer present, or self-signed
rootCert = cert;
break;
}
}
return rootCert;
}
/**
* A helper method for certificate re-ordering.
* Finds the first certificate in the list of certificates that is signed by the sigingCert.
*/
private X509Certificate findSignedCert(X509Certificate signingCert, List<X509Certificate> certificates) {
X509Certificate signed = null;
for(X509Certificate cert : certificates) {
Principal signingCertSubjectDN = signingCert.getSubjectDN();
Principal certIssuerDN = cert.getIssuerDN();
if(certIssuerDN.equals(signingCertSubjectDN) && !cert.equals(signingCert)) {
signed = cert;
break;
}
}
return signed;
}
/**
* A helper method for certificate re-ordering.
* Finds the certificate in the list of certificates that signed the signedCert.
*/
private X509Certificate findSigner(X509Certificate signedCert, List<X509Certificate> certificates) {
X509Certificate signer = null;
for(X509Certificate cert : certificates) {
Principal certSubjectDN = cert.getSubjectDN();
Principal issuerDN = signedCert.getIssuerDN();
if(certSubjectDN.equals(issuerDN)) {
signer = cert;
break;
}
}
return signer;
}
}
Pour l'utiliser, il suffit de récupérer SSLSocketFactory et de l'appliquer, par exemple:
avec HttpsURLConnection
KeyPinStore keystore = KeyPinStore.getInstance();
SSLSocketFactory sslSF = keystore.getContext().getSocketFactory();
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(sslSF);
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
avec Volley
KeyPinStore keystore = KeyPinStore.getInstance();
SSLSocketFactory sslSF = keystore.getContext().getSocketFactory();
RequestQueue mRequestQueue = Volley.newRequestQueue(context, new HurlStack(null, sslSF));