I'm trying to connect to one of my servers through ssl, with Java. I tried a lot of options here is my best try:
I generate a jssecacerts with the recommendet script: http://blogs.oracle.com/andreas/resource/InstallCert.java
with the command: java InstallCert ssl.someUrl.de changeit
after this I did the command a second time:
Loading KeyStore jssecacerts...
Opening connection to ssl.someUrl.de:443...
Starting SSL handshake...
No errors, certificate is already trusted
Server sent 1 certificate(s):
1 Subject EMAILADDRESS=info#plesk.com, CN=plesk, OU=Plesk, O=Parallels, L=Hernd
on, ST=Virginia, C=US
Issuer EMAILADDRESS=info#plesk.com, CN=plesk, OU=Plesk, O=Parallels, L=Hernd
on, ST=Virginia, C=US
sha1 f1 0d 2c 54 05 e1 32 19 a0 52 5e e1 81 6c a3 a5 83 0d dd 67
md5 f0 b3 be 5e 5f 6e 90 d1 bc 57 7a b2 81 ce 7d 3d
Enter certificate to add to trusted keystore or 'q' to quit: [1]
I copied the file to the default directory and I loaded the certificate in Java trustStore
System.setProperty("javax.net.ssl.trustStore", "C:\\Program Files (x86)\\Java\\jre6\\lib\\security\\jssecacerts");
System.setProperty("javax.net.ssl.trustStorePassword","changeit");
Then I try to connect
URL url = new URL("https://ssl.someUrl.de/");
URLConnection conn = url.openConnection();
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
And I get Error on 3rd line: (No name matching ssl.someUrl.de found)
javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching ssl.someUrl.de found
Is this cause of the default plesk certificate or is something else wrong?
Setup: JRE 6.20, Netbeans 6.8, Windows7 64bit
It looks like the certificate of the server you are trying to connect to doesn't match its hostname.
When an HTTPS client connects to a server, it verifies that the hostname in the certificate matches the hostname of the server. It's not enough for a certificate to be trusted, it has to match the server you want to talk to too. (As an analogy, even if you trust a passport to be legitimate, you still have to check that it's the one for the person you want to talk to, not just any passport you would trust to be legitimate.)
In HTTP, this is done by checking that:
the certificate contains a DNS subject alternative name (this is a standard extension) entry matching the hostname;
failing that, the last CN of your subject distinguished name (this is the main name if you want) matches the hostname. (See RFC 2818.)
It's hard to tell what the subject alternative name is without having the certificate (although, if you connect with your browser and check its content in more details, you should be able to see it.)
The subject distinguished name seems to be:
EMAILADDRESS=info#plesk.com, CN=plesk, OU=Plesk, O=Parallels, L=Herndon, ST=Virginia, C=US
(It would thus need to be CN=ssl.someUrl.de instead of CN=plesk, if you don't have a subject alternative name with DNS:ssl.someUrl.de already; my guess is that you don't.)
You may be able to bypass the hostname verification using HttpsURLConnection.setHostnameVerifier(..). It shouldn't be too hard to write a custom HostnameVerifier that bybasses the verification, although I would suggest doing it only when the certificate its the one concerned here specifically. You should be able to get that using the SSLSession argument and its getPeerCertificates() method.
(In addition, you don't need to set the javax.net.ssl.* properties the way you've done it, since you're using the default values anyway.)
Alternatively, if you have control over the server you're connecting to and its certificate, you can create a certificate of it that matches the naming rules above (CN should be sufficient, although subject alternative name is an improvement). If a self-signed certificate is good enough for what you name, make sure its common name (CN) is the host name you're trying to talk to (no the full URL, just the hostname).
In Java 8 you can skip server name checking with the following code:
HttpsURLConnection.setDefaultHostnameVerifier ((hostname, session) -> true);
However this should be used only in development!
I created a method fixUntrustCertificate(), so when I am dealing with a domain that is not in trusted CAs you can invoke the method before the request. This code will gonna work after java1.4. This method applies for all hosts:
public void fixUntrustCertificate() throws KeyManagementException, NoSuchAlgorithmException{
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// set the allTrusting verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
}
If you're looking for a Kafka error, this might because the upgrade of Kafka's version from 1.x to 2.x.
javax.net.ssl.SSLHandshakeException: General SSLEngine problem ... javax.net.ssl.SSLHandshakeException: General SSLEngine problem ... java.security.cert.CertificateException: No name matching *** found
or
[Producer clientId=producer-1] Connection to node -2 failed authentication due to: SSL handshake failed
The default value for ssl.endpoint.identification.algorithm was changed to https, which performs hostname verification (man-in-the-middle attacks are possible otherwise). Set ssl.endpoint.identification.algorithm to an empty string to restore the previous behaviour. Apache Kafka Notable changes in 2.0.0
Solution:
SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, ""
I've found a good resolution here: http://www.mkyong.com/webservices/jax-ws/java-security-cert-certificateexception-no-name-matching-localhost-found/
But my problem was a little bit different and solved it differently.
The web service was on remote host.
For example: https://some.remote.host/MyWebService?wsdl
But it was available only by IP for any clients, but certificate was created for domain: some.remote.host (CN=some.remote.host). And this domain can't be resolved by IP because it is not presented in DNS).
So the same problem appeared: if I use IP to connect to web service by ssl, it can't be reached becase certificate CN=some.remote.host and it is not equal to host name I've specified (i.e. host IP).
I've resolved it by matching this hostname with IP in /etc/hosts file.
The problem was fixed.
But in case when the Web Service is hosted on localhost app server, it think, it should be solved like mkyong described in his article.
Use case: i am using a self-signed certificate for my development on localhost.
Error: Caused by: java.security.cert.CertificateException: No name matching localhost found
Solution:
When you generate your self-signed certificate, make sure you answer this question like that:
What is your first and last name?
[Unknown]: localhost
//The rest you can fill accordingly
As a bonus, here are my steps:
1. Generate self-signed certificate:
keytool -genkeypair -alias netty -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 4000
Enter keystore password: ***
Re-enter new password: ***
What is your first and last name?
[Unknown]: localhost
//The rest you can fill accordingly
2. Copy the certificate in src/main/resources(if necessary)
3. Update the cacerts
keytool -v -importkeystore -srckeystore keystore.p12 -srcstoretype pkcs12 -destkeystore "%JAVA_HOME%\jre\lib\security\cacerts" -deststoretype jks
4. Update your config(in my case application.properties):
server.port=8443
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=jumping_monkey
server.ssl.key-store-type=pkcs12
server.ssl.key-alias=netty
The server name should be same as the first/last name which you give while create a certificate
one can skip the hostname verification by setting a VM property:
-Djdk.internal.httpclient.disableHostnameVerification
This works with Java 11 HttpClient implementation.
HttpsURLConnection.setDefaultHostnameVerifier((String hostname, SSLSession sslSession) -> {
return hostname.equals("localhost");
});
add this to your method from where you calling the server.
Related
A module I'm adding to our large Java application has to converse with another company's SSL-secured website. The problem is that the site uses a self-signed certificate. I have a copy of the certificate to verify that I'm not encountering a man-in-the-middle attack, and I need to incorporate this certificate into our code in such a way that the connection to the server will be successful.
Here's the basic code:
void sendRequest(String dataPacket) {
String urlStr = "https://host.example.com/";
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setMethod("POST");
conn.setRequestProperty("Content-Length", data.length());
conn.setDoOutput(true);
OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
o.write(data);
o.flush();
}
Without any additional handling in place for the self-signed certificate, this dies at conn.getOutputStream() with the following exception:
Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Ideally, my code needs to teach Java to accept this one self-signed certificate, for this one spot in the application, and nowhere else.
I know that I can import the certificate into the JRE's certificate authority store, and that will allow Java to accept it. That's not an approach I want to take if I can help; it seems very invasive to do on all of our customer's machines for one module they may not use; it would affect all other Java applications using the same JRE, and I don't like that even though the odds of any other Java application ever accessing this site are nil. It's also not a trivial operation: on UNIX I have to obtain access rights to modify the JRE in this way.
I've also seen that I can create a TrustManager instance that does some custom checking. It looks like I might even be able to create a TrustManager that delegates to the real TrustManager in all instances except this one certificate. But it looks like that TrustManager gets installed globally, and I presume would affect all other connections from our application, and that doesn't smell quite right to me, either.
What is the preferred, standard, or best way to set up a Java application to accept a self-signed certificate? Can I accomplish all of the goals I have in mind above, or am I going to have to compromise? Is there an option involving files and directories and configuration settings, and little-to-no code?
Create an SSLSocket factory yourself, and set it on the HttpsURLConnection before connecting.
...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...
You'll want to create one SSLSocketFactory and keep it around. Here's a sketch of how to initialize it:
/* Load the keyStore that includes self-signed cert as a "trusted" entry. */
KeyStore keyStore = ...
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
sslFactory = ctx.getSocketFactory();
If you need help creating the key store, please comment.
Here's an example of loading the key store:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();
To create the key store with a PEM format certificate, you can write your own code using CertificateFactory, or just import it with keytool from the JDK (keytool won't work for a "key entry", but is just fine for a "trusted entry").
keytool -import -file selfsigned.pem -alias server -keystore server.jks
I read through LOTS of places online to solve this thing.
This is the code I wrote to make it work:
ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
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("SunX509");
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 is a String that contains the Certificate, for example:
static public String certificateString=
"-----BEGIN CERTIFICATE-----\n" +
"MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
"BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
... a bunch of characters...
"5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
"-----END CERTIFICATE-----";
I have tested that you can put any characters in the certificate string, if it is self signed, as long as you keep the exact structure above. I obtained the certificate string with my laptop's Terminal command line.
If creating a SSLSocketFactory is not an option, just import the key into the JVM
Retrieve the public key:
$openssl s_client -connect dev-server:443, then create a file dev-server.pem that looks like
-----BEGIN CERTIFICATE-----
lklkkkllklklklklllkllklkl
lklkkkllklklklklllkllklkl
lklkkkllklk....
-----END CERTIFICATE-----
Import the key: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem.
Password: changeit
Restart JVM
Source: How to solve javax.net.ssl.SSLHandshakeException?
We copy the JRE's truststore and add our custom certificates to that truststore, then tell the application to use the custom truststore with a system property. This way we leave the default JRE truststore alone.
The downside is that when you update the JRE you don't get its new truststore automatically merged with your custom one.
You could maybe handle this scenario by having an installer or startup routine that verifies the truststore/jdk and checks for a mismatch or automatically updates the truststore. I don't know what happens if you update the truststore while the application is running.
This solution isn't 100% elegant or foolproof but it's simple, works, and requires no code.
I've had to do something like this when using commons-httpclient to access an internal https server with a self-signed certificate. Yes, our solution was to create a custom TrustManager that simply passed everything (logging a debug message).
This comes down to having our own SSLSocketFactory that creates SSL sockets from our local SSLContext, which is set up to have only our local TrustManager associated with it. You don't need to go near a keystore/certstore at all.
So this is in our LocalSSLSocketFactory:
static {
try {
SSL_CONTEXT = SSLContext.getInstance("SSL");
SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Unable to initialise SSL context", e);
} catch (KeyManagementException e) {
throw new RuntimeException("Unable to initialise SSL context", e);
}
}
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
LOG.trace("createSocket(host => {}, port => {})", new Object[] { host, new Integer(port) });
return SSL_CONTEXT.getSocketFactory().createSocket(host, port);
}
Along with other methods implementing SecureProtocolSocketFactory. LocalSSLTrustManager is the aforementioned dummy trust manager implementation.
I have an SSL socket server running with a 2-side handshake required (for which I'm using self signed certificates). After the handshake was successful, I would like to check the client certificate's cn on the server side. Unfortunately this field is set to Unknown.
Here is the code I used to determine the cn field value:
((SSLSocket) socket).addHandshakeCompletedListener(new HandshakeCompletedListener() {
#Override
public void handshakeCompleted(HandshakeCompletedEvent hce) {
X509Certificate cert = (X509Certificate)hce.getLocalCertificates()[0];
String certName = cert.getSubjectX500Principal().getName().substring(3,cert.getSubjectX500Principal().getName().indexOf(","));
System.out.println(certName);
}
});
Which prints Unknown
Aditionally, I checked the client's keyStore using this command:
keytool -list -v -keystore clientStore.jks
Which prints
Keystore-type: JKS
Keystore-provider: SUN
Keystore contains 1 entry
Aliasname: test
creation date: 23.04.2018
entry type: PrivateKeyEntry
certificate length: 1
certificate[1]:
owner: CN=test, OU="Org Unit", O=Org, L=City, ST=State, C=DE
...
As you can see, the client store's certificate's cn is set. However it is inexplicable to me why it then seems not to be transmitted to the server.
I would be glad for every kind of help.
Best regards,
Galveston01
After the handshake was successful, I would like to check the client
certificate's cn on the server side.
To check what certificates you have received you need to call getPeerCertificates instead of getLocalCertificates, which is for the certificates you sent.
And you should read carefully the doc :
public X500Principal getSubjectX500Principal()
Returns the subject (subject distinguished name) value from the
certificate as an X500Principal. If the subject value is empty, then
the getName() method of the returned X500Principal object returns an
empty string ("").
For this reason it's not recommended to call indexOf() substring() without checking first the input.
I am using the method below to read certificates.
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(in, password);
String alias = ks.aliases().nextElement();
Certificate[] chain = ks.getCertificateChain(alias);
...
This method is very functional, however I came across a problem in a certificate that does not load the full string of the certificate.
For example1:
When reading certificate 1 the statement below returns 4 strings
For example2:
When reading certificate 2 the statement below returns only one string
Does anyone have any idea what might be happening?
EDIT
Trying to explain better, this certificate is used to connect to a WS. During the communication process WS returns the following rejection Connection has been shutdown: javax.net.ssl.SSLHandshakeException: Received fatal alert: unknown_ca
The connection to this WS requires the complete certification chain presentation.
As mentioned below the certificate shows the failure in JAVA, but it works perfectly in .NET, correctly displaying the certification chain
According to API documentation:
Returns:
the certificate chain (ordered with the user's certificate first followed by zero or more certificate authorities), or null if the given alias does not exist or does not contain a certificate chain
This means that either, there are no more certificates in the chain (self-signed certificate) or required chain elements are not included in PKCS#12 message and are not available through other sources.
If a key store containing one or more PrivateKeyEntry is specified as a trust store, will JSSE create a trust anchor from the end-entity certificate in each of those entries?
In other words, is it enough to have a certificate under a PrivateKeyEntry if we have one keystore with both trusted and private entries? Or, must we also add that certificate as a TrustedCertificateEntry?
Is it enough to have certificate under PrivateKeyEntry if we have one keystore with both trusted and private entries
You should never have such a keystore.
or shall we add also certificate as trustedCertEntry in order to make requests to themself/other node under proxy ?
A trustedCertEntry is used for incoming certificates. A private key entry is used for outgoing certificates.
You're conflating two different things, indeed two different uses of keystores.
A keystore file that contains trustedCertEntry is a truststore, in the sense of javax.net.ssl.trustStore, and it tells JSSE which incoming certificates to trust, directly or indirectly.
A keystore file that contains PrivateKeyEntry is a keystore, in the sense of javax.net.ssl.keyStore, and it tells JSSE which certificates to use for outbound certificates.
A keystore file that contains both is radically malformed. A truststore is simply a list of certificates to be trusted. It isn't secret. A KeyStore contains your private key and it is top secret to everybody. Conflating the two is a major security breach.
If it doesn't matter why would there two different types of entry?
It's not even a proper question to ask. If you have a private key where a trusted certificate should be, that means you have someone else's private key, which is a prima facie security breach.
It doesn't matter where certificate placed either under PrivateKeyEntry or under trustedCertEntry , JVM trusts host from certificate anyway.
Tested locally.
Run local server with https and keystore with only one PrivateKeyEntry.
And run client with code :
public static String getHTML(String urlToRead) throws Exception {
StringBuilder result = new StringBuilder();
URL url = new URL(urlToRead);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while((line = rd.readLine()) != null) {
result.append(line);
}
rd.close();
return result.toString();
}
public static void main(String[] args) throws Exception {
String testUrl="https://localhost/test";
System.out.println(getHTML(testUrl));
}
Without any:
Exception in thread "main" javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to
find valid certification path to requested target
With truststore that contains only one PrivateKeyEntry (the same jks file that was used for server as keystore):
<!DOCTYPE....</html>
I'm connecting through a SSLSocket to a distant host which is using a certificate for the handshake. As we don't use the default JVM truststore with all the certificate authorities, i need to add the remote host certificates to my truststore.
How can i get the certificates that i should trust from the SSLSocket? It seems do retrieve them i need to use the SSLSession which seems to require the handshake.
Why do we need to perform the handshake to be able to retrieve the certificates?
Is there any tool that permits to extract the remote host certificates used?
Actually certificates are presented during the handshake, so that the server can identify itself, and the eventually the same for the client.
When you do:
SSLContext context = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
[...]
SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
try {
socket.startHandshake();
socket.close();
} catch (SSLException e) {
e.printStackTrace(System.out);
}
if you don't get an exception on startHandshake(), it means the certificate is already trusted for some reason (directly present in keystore, of signed by a trusted entity).
Exception occurred or not, you can access the downloaded chain:
X509Certificate[] chain = tm.chain;
if (chain == null) {
// error in downloading certificate chain
return;
}
// loop through chain
for (int i = 0; i < chain.length; i++) {
X509Certificate cert = chain[i];
[....]
}
With the X509Certificate object instance, you can actually update your k-ieth keystore:
X509Certificate cert = chain[k];
String alias = host + "-" + (k + 1);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
[...]
ks.setCertificateEntry(alias, cert);
OutputStream out = new FileOutputStream("jssecacerts");
ks.store(out, passphrase);
out.close();
Look here for the complete sample.
Alternatively, another maybe safer way to download the certificate for a server you trust, is using openssl command:
# openssl s_client -showcerts -connect $SERVER:$PORT 2>&1 | \
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >/tmp/$SERVERNAME.cert
then import it as usual with keytool.
Normally, you shouldn't get the certificate you should trust from an SSLSocket, instead, it should be a configured setting that you obtained independently, as a reference for what you want to trust.
What you seem to want to do is to get the certificate for the first connection, hoping that that connection wasn't intercepted, and then use this as a reference for subsequent connections (similar to what's commonly done with SSH, when you don't necessarily know the server key's fingerprint on the first connection, but check that you get the same later).
Security-wise, this isn't ideal because the initial connection may be intercepted by a MITM attacker (which would make all subsequent connections vulnerable), but that's certainly a way to mitigate the risks. Ideally, you should compare that certificate with a known reference you've obtained some other way.
You can access the remote certificate during the handshake using a custom X509TrustManager (or you can disable trust verification with it and get the certificate later), which you can then use to initialise an SSLContext, from which you can obtain your SSLSocketFactory. It's generally a bad idea to disable trust verification in a trust manager (since it opens the connection to MITM attacks), but it can be acceptable for this purpose. You may be interested in the InstallCert utility, which should do more or less what you're after.
Why do we need to do the handshake before accessing the server
certificate?
This is done during the handshake, because the purpose of the SSL/TLS socket API is to provide the application layer with a socket it can consider secure and use more or less as a normal socket at that stage. Typically, for most uses of JSSE (or generally other SSL/TLS stacks), as an application developer using that stack, you don't want to have to do the verification explicitly. Checking the certificate during the handshake is also recommended as part of the TLS specification:
Upon receipt of the server hello done message, the client SHOULD
verify that the server provided a valid certificate, if required
and check that the server hello parameters are acceptable.