J'utilise Java 6 et j'essaie de créer un HttpsURLConnection
sur un serveur distant, à l'aide d'un certificat client.
Le serveur utilise un certificat racine auto-signé et nécessite la présentation d'un certificat client protégé par mot de passe. J'ai ajouté le certificat racine du serveur et le certificat client à un magasin de clés Java que j'ai trouvé dans /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts
(OSX 10.5). Le nom du fichier de clés semble suggérer que le certificat client n'est pas censé y être inséré?
Quoi qu'il en soit, l'ajout du certificat racine à ce magasin a résolu l'infâme javax.net.ssl.SSLHandshakeException: Sun.security.validator.ValidatorException: PKIX path building failed' problem.
Cependant, je suis maintenant bloqué sur l'utilisation du certificat client. J'ai essayé deux approches et aucune ne me mène nulle part.
Premièrement, et de préférence, essayez:
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://somehost.dk:3049");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
// The last line fails, and gives:
// javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
J'ai essayé de sauter la classe HttpsURLConnection (pas idéal car je veux parler HTTP avec le serveur), et le faire à la place:
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("somehost.dk", 3049);
InputStream inputstream = sslsocket.getInputStream();
// do anything with the inputstream results in:
// Java.net.SocketTimeoutException: Read timed out
Je ne suis même pas sûr que le certificat client soit le problème ici.
Finalement résolu le;). Vous avez un indice fort ici (la réponse de Gandalfs a également touché un peu). Les liens manquants étaient (principalement) le premier des paramètres ci-dessous et, dans une certaine mesure, j'ai négligé la différence entre les magasins de clés et les magasins de clés de confiance.
Le certificat de serveur auto-signé doit être importé dans un fichier de clés certifiées:
keytool -import -alias gridserver -file gridserver.crt -storepass $ PASS -keystore gridserver.keystore
Ces propriétés doivent être définies (soit sur la ligne de commande, soit dans le code):
-Djavax.net.ssl.keyStoreType=pkcs12
-Djavax.net.ssl.trustStoreType=jks
-Djavax.net.ssl.keyStore=clientcertificate.p12
-Djavax.net.ssl.trustStore=gridserver.keystore
-Djavax.net.debug=ssl # very verbose debug
-Djavax.net.ssl.keyStorePassword=$PASS
-Djavax.net.ssl.trustStorePassword=$PASS
Exemple de code de travail:
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://gridserver:3049/cgi-bin/ls.py");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
String string = null;
while ((string = bufferedreader.readLine()) != null) {
System.out.println("Received " + string);
}
Bien que cela ne soit pas recommandé, vous pouvez également désactiver la validation du certificat SSL dans son intégralité:
import javax.net.ssl.*;
import Java.security.SecureRandom;
import Java.security.cert.X509Certificate;
public class SSLTool {
public static void disableCertificateValidation() {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}};
// Ignore differences between given hostname and certificate hostname
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) { return true; }
};
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(hv);
} catch (Exception e) {}
}
}
Avez-vous défini les propriétés du système KeyStore et/ou TrustStore?
Java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456
ou de avec le code
System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);
Pareil avec javax.net.ssl.trustStore
Si vous traitez un appel de service Web utilisant la structure Axis, la réponse est beaucoup plus simple. Si tout ce que vous voulez, c'est que votre client puisse appeler le service Web SSL et ignorer les erreurs de certificat SSL, il suffit de mettre cette déclaration avant d'appeler un service Web:
System.setProperty("axis.socketSecureFactory", "org.Apache.axis.components.net.SunFakeTrustSocketFactory");
Les avertissements habituels selon lesquels il s'agit d'une très mauvaise chose à faire dans un environnement de production s'appliquent.
J'ai trouvé ceci à le wiki Axis .
Pour moi, c’est ce qui a fonctionné avec Apache HttpComponents ~ HttpClient 4.x:
KeyStore keyStore = KeyStore.getInstance("PKCS12");
FileInputStream instream = new FileInputStream(new File("client-p12-keystore.p12"));
try {
keyStore.load(instream, "helloworld".toCharArray());
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, "helloworld".toCharArray())
//.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) //custom trust store
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //TODO
CloseableHttpClient httpclient = HttpClients.custom()
.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) //TODO
.setSSLSocketFactory(sslsf)
.build();
try {
HttpGet httpget = new HttpGet("https://localhost:8443/secure/index");
System.out.println("executing request" + httpget.getRequestLine());
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
if (entity != null) {
System.out.println("Response content length: " + entity.getContentLength());
}
EntityUtils.consume(entity);
} finally {
response.close();
}
} finally {
httpclient.close();
}
Le fichier P12 contient le certificat client et la clé privée client créée avec BouncyCastle:
public static byte[] convertPEMToPKCS12(final String keyFile, final String cerFile,
final String password)
throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException,
NoSuchProviderException
{
// Get the private key
FileReader reader = new FileReader(keyFile);
PEMParser pem = new PEMParser(reader);
PEMKeyPair pemKeyPair = ((PEMKeyPair)pem.readObject());
JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("BC");
KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair);
PrivateKey key = keyPair.getPrivate();
pem.close();
reader.close();
// Get the certificate
reader = new FileReader(cerFile);
pem = new PEMParser(reader);
X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject();
Java.security.cert.Certificate x509Certificate =
new JcaX509CertificateConverter().setProvider("BC")
.getCertificate(certHolder);
pem.close();
reader.close();
// Put them into a PKCS12 keystore and write it to a byte[]
ByteArrayOutputStream bos = new ByteArrayOutputStream();
KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
ks.load(null);
ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(),
new Java.security.cert.Certificate[]{x509Certificate});
ks.store(bos, password.toCharArray());
bos.close();
return bos.toByteArray();
}
J'utilise le paquet HTTP Client Apache commons pour le faire dans mon projet actuel et cela fonctionne bien avec SSL et un certificat auto-signé (après l'avoir installé dans cacerts, comme vous l'avez mentionné). S'il vous plaît jetez un coup d'oeil ici:
Je pense que vous avez un problème avec votre certificat de serveur, ce n'est pas un certificat valide (je pense que c'est ce que "handshake_failure" signifie dans ce cas):
Importez votre certificat de serveur dans votre magasin de clés trustcacerts sur le JRE du client. Ceci est facilement fait avec keytool :
keytool
-import
-alias <provide_an_alias>
-file <certificate_file>
-keystore <your_path_to_jre>/lib/security/cacerts
Utiliser le code ci-dessous
-Djavax.net.ssl.keyStoreType=pkcs12
ou
System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);
n'est pas du tout requis. De plus, il n'est pas nécessaire de créer votre propre fabrique SSL personnalisée.
J'ai également rencontré le même problème. Dans mon cas, il existait un problème qui chaîne de certificats complète n'était pas importé dans les magasins de clés de confiance. Importer des certificats à l'aide de l'utilitaire keytool droit du certificat racine, vous pouvez également ouvrir le fichier cacerts dans le bloc-notes et voir si la chaîne de certificats complète est importée ou non. Vérifiez le nom d'alias que vous avez fourni lors de l'importation de certificats, ouvrez les certificats et voyez combien il en contient, le même nombre de certificats devrait figurer dans le fichier cacerts.
De plus, le fichier cacerts doit être configuré sur le serveur sur lequel vous exécutez votre application. Les deux serveurs s'authentifieront l'un l'autre à l'aide de clés publiques/privées.