Je pense que je dois créer une nouvelle SSL Socket Factory? De plus, je ne veux pas utiliser le contexte SSL global ( https://github.com/square/okhttp/issues/184 ) pour des raisons évidentes.
merci!
MODIFIER:
Depuis okhttp 2.1.0, vous pouvez épingler des certificats très facilement.
Voir le code source ici pour commencer
OKHTTP 3.0 a prise en charge intégrée pour épingler les certificats. Commencez par coller le code suivant:
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();
Cela échouera car AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
n'est pas un hachage valide de votre certificat. L'exception levée aura les hachages corrects de votre certificat:
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)
Assurez-vous de les ajouter à votre objet CertificatePinner et vous avez réussi à épingler votre certificat:
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();
Après avoir lu ce billet de blog j'ai pu modifier le concept pour l'utiliser avec OkHttp. Vous devez utiliser au moins la version 2.0 si vous souhaitez éviter d'utiliser un contexte SSL global.
Cette modification s'applique uniquement à l'instance actuelle d'OkHttp et modifie cette instance afin qu'elle uniquement accepte les certificats du certificat spécifié. Si vous souhaitez que d'autres certificats (comme celui de Twitter) soient acceptés, il vous suffit de créer une nouvelle instance OkHttp sans les modifications décrites ci-dessous.
Pour épingler un certificat, vous devez d'abord créer un fichier de clés certifiées contenant ce certificat. Pour créer le magasin de confiance, nous utiliserons ce script pratique de nelenkov légèrement modifié pour nos besoins:
#!/bin/bash
if [ "$#" -ne 3 ]; then
echo "Usage: importcert.sh <CA cert PEM file> <bouncy castle jar> <keystore pass>"
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..."
Pour exécuter ce script, vous avez besoin de 3 choses:
keytool
(inclus dans Android SDK) est sur votre $ PATH.Maintenant, exécutez le script
./gentruststore.sh your_cert.pem bcprov-jdk15on-150.jar your_secret_pass
Tapez "oui" pour faire confiance au certificat, et une fois terminé mytruststore.bks
sera généré dans votre répertoire actuel.
Créez un répertoire raw
sous votre dossier res
. Copie mytruststore.bks
ici.
Voici maintenant une classe très simple qui épingle votre cert à 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);
}
}
}
Comme vous pouvez le voir, nous instancions une nouvelle instance de OkHttpClient
et appelons setSslSocketFactory
, en passant un SSLSocketFactory
avec notre truststore personnalisé. Assurez-vous de définir TRUST_STORE_PASSWORD
au mot de passe que vous avez passé dans le script Shell. Votre instance OkHttp ne doit désormais accepter que le certificat que vous avez spécifié.
C'est plus facile que je ne le pensais avec OkHttp.
Suivez ces étapes:
1. Obtenez les clés publiques sha1. La documentation OkHttp nous donne un moyen clair de le faire avec un exemple de code. Dans le cas où il disparaît, il est collé ci-dessous:
Par exemple, pour épingler https://publicobject.com , commencez avec une configuration cassée:
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();
Comme prévu, cela échoue avec une exception d'épinglage de certificat:
javax.net.ssl.SSLPeerUnverifiedException: échec de l'épinglage du certificat!
Chaîne de certificats d'homologue: sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw =: CN = publicobject.com, OU = PositiveSSL sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw =: CN = COMODO RSA Validation de domaine serveur sécurisé CA sha1WVOOWV = Autorité de certification sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c =: CN = racine de l'autorité de certification externe AddTrust
Certificats épinglés pour publicobject.com:
sha1/BOGUSPIN
sur com.squareup.okhttp.CertificatePinner.check (CertificatePinner.Java)
sur com.squareup.okhttp.Connection.upgradeToTls (Connection.Java)
sur com.squareup.okhttp.Connection.connect (Connection.Java)
sur com.squareup.okhttp.Connection.connectAndSetOwner (Connection.Java)
Suivi en collant les hachages de clé publique de l'exception dans la configuration du pinner de certificat:
Note latérale: Si vous faites cela sur Android vous obtiendrez une exception distincte si vous le faites sur un thread d'interface utilisateur, alors assurez-vous de le faire sur un thread d'arrière-plan.
2. Configurez votre client OkHttp:
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());
C'est tout ce qu'on peut en dire!
Si vous n'avez pas accès au domaine (accès restreint par exemple) et que vous ne pouvez pas tester le hachage bidon, mais vous avez un fichier de certificat, vous pouvez utiliser openssl pour le récupérer:
openssl x509 -in cert.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
Pour développer l'exemple code source @ Michael-barany partagé, j'ai fait quelques tests et il semble que ce soit un exemple de code trompeur. Dans l'exemple de code, l'exception a noté 4 hachages sha1 de l'exception de chaîne de certificats:
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
puis ajouté les 4 hachages de clé publique sha1 au 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();
Cependant, étant donné les tests que j'ai effectués et l'examen du code, seul le premier valide serait interprété, vous seriez donc mieux à même de n'inclure QUE l'un des hachages retournés. Vous pouvez utiliser le hachage le plus spécifique "DmxUShsZuNiqPQsX2Oi9uv2sCnw" pour le certificat de site précis ... ou vous pouvez utiliser le hachage le plus large "T5x9IXmcrQ7YuQxXnxoCmeeQ84c" pour la racine CA en fonction de la posture de sécurité souhaitée.
J'ai trouvé l'exemple mentionné dans la section Autorité de certification inconnue de ce lien developer.Android.com/training/articles/security-ssl = très utile.
Le SSLSocketFactory retourné dans context.getSocketFactory () peut ensuite être utilisé pour définir OkHttpClient dans la méthode setSslSocketFactory ().
Remarque: La section Autorité de certification inconnue mentionne également le lien pour télécharger un fichier de certificat à utiliser et vérifier ce code.
Voici l'exemple de méthode que j'ai écrit pour obtenir 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;
}
Plus tard, je configure simplement ceci sur OkHttpClient comme ceci
httpClient.setSslSocketFactory(sslSocketFactory);
puis faites l'appel https
httpClient.newCall(requestBuilder.build()).enqueue(callback);