可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am using Java 6 and am trying to create an HttpsURLConnection
against a remote server, using a client certificate.
The server is using an selfsigned root certificate, and requires that a password-protected client certificate is presented. I\'ve added the server root certificate and the client certificate to a default java keystore which I found in /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts
(OSX 10.5).
The name of the keystore file seems to suggest that the client certificate is not supposed to go in there?
Anyway, adding the root certificate to this store solved the infamous javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed\' problem.
However, I\'m now stuck on how to use the client certificate. I\'ve tried two approaches and neither gets me anywhere.
First, and preferred, try:
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
I\'ve tried skipping the HttpsURLConnection class (not ideal since I want to talk HTTP with the server), and do this instead:
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
I am not even sure that the client certificate is the problem here.
回答1:
Finally solved it ;). Got a strong hint here (Gandalfs answer touched a bit on it as well). The missing links was (mostly) the first of the parameters below, and to some extent that I overlooked the difference between keystores and truststores.
The self-signed server certificate must be imported into a truststore:
keytool -import -alias gridserver -file gridserver.crt -storepass $PASS -keystore gridserver.keystore
These properties need to be set (either on the commandline, or in 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
Working example code:
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);
}
回答2:
While not recommended, you can also disable SSL cert validation alltogether:
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) {}
}
}
回答3:
Have you set the KeyStore and/or TrustStore System properties?
java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456
or from with the code
System.setProperty(\"javax.net.ssl.keyStore\", pathToKeyStore);
Same with javax.net.ssl.trustStore
回答4:
If you are dealing with a web service call using the Axis framework, there is a much simpler answer. If all want is for your client to be able to call the SSL web service and ignore SSL certificate errors, just put this statement before you invoke any web services:
System.setProperty(\"axis.socketSecureFactory\",
\"org.apache.axis.components.net.SunFakeTrustSocketFactory\");
The usual disclaimers about this being a Very Bad Thing to do in a production environment apply.
I found this at the Axis wiki.
回答5:
For me, this is what worked using 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();
}
The P12 file contains the client certificate and client private key, created with 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();
}
回答6:
I use the Apache commons HTTP Client package to do this in my current project and it works fine with SSL and a self-signed cert (after installing it into cacerts like you mentioned). Please take a look at it here:
http://hc.apache.org/httpclient-3.x/tutorial.html
http://hc.apache.org/httpclient-3.x/sslguide.html
回答7:
I think you have an issue with your server certificate, is not a valid certificate (I think this is what \"handshake_failure\" means in this case):
Import your server certificate into your trustcacerts keystore on client\'s JRE. This is easily done with keytool:
keytool
-import
-alias <provide_an_alias>
-file <certificate_file>
-keystore <your_path_to_jre>/lib/security/cacerts
回答8:
Using below code
-Djavax.net.ssl.keyStoreType=pkcs12
or
System.setProperty(\"javax.net.ssl.keyStore\", pathToKeyStore);
is not at all required. Also there is no need to create your own custom SSL factory.
I also encountered the same issue, in my case there was a issue that complete certificate chain was not imported into truststores. Import certificates using keytool utility right fom root certificate, also you can open cacerts file in notepad and see if the complete certificate chain is imported or not. Check against the alias name you have provided while importing certificates, open the certificates and see how many does it contains, same number of certificates should be there in cacerts file.
Also cacerts file should be configured in the server you are running your application, the two servers will authenticate each other with public/private keys.