How to fix the "java.security.cert.CertificateException: No subject alternative names present" error?

518,851

Solution 1

I fixed the problem by disabling HTTPS checks using the approach presented here:

I put following code into the the ISomeService class:

static {
    disableSslVerification();
}

private static void disableSslVerification() {
    try
    {
        // Create a trust manager that does not validate certificate chains
        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) {
            }
        }
        };

        // Install the all-trusting trust manager
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

        // Create all-trusting host name verifier
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        // Install the all-trusting host verifier
        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    }
}

Since I'm using the https://AAA.BBB.CCC.DDD:9443/ISomeService for testing purposes only, it's a good enough solution, but do not do this in production.

Note that you can also disable SSL for "one connection at a time" ex:

 // don't call disableSslVerification but use its internal code:
 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 if (conn instanceof HttpsURLConnection) {
    HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
    httpsConn.setHostnameVerifier(allHostsValid);
    httpsConn.setSSLSocketFactory(sc.getSocketFactory());
 }

Solution 2

I've the same problem and solved with this code. I put this code before the first call to my webservices.

javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
  new javax.net.ssl.HostnameVerifier(){

      public boolean verify(String hostname,
             javax.net.ssl.SSLSession sslSession) {
          return hostname.equals("localhost"); // or return true
      }
  });

It's simple and works fine.

Here is the original source.

Solution 3

This is an old question, yet I had the same problem when moving from JDK 1.8.0_144 to jdk 1.8.0_191

We found a hint in the changelog:

Changelog

we added the following additional system property, which helped in our case to solve this issue:

-Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true

Solution 4

The verification of the certificate identity is performed against what the client requests.

When your client uses https://xxx.xxx.xxx.xxx/something (where xxx.xxx.xxx.xxx is an IP address), the certificate identity is checked against this IP address (in theory, only using an IP SAN extension).

If your certificate has no IP SAN, but DNS SANs (or if no DNS SAN, a Common Name in the Subject DN), you can get this to work by making your client use a URL with that host name instead (or a host name for which the cert would be valid, if there are multiple possible values). For example, if you cert has a name for www.example.com, use https://www.example.com/something.

Of course, you'll need that host name to resolve to that IP address.

In addition, if there are any DNS SANs, the CN in the Subject DN will be ignored, so use a name that matches one of the DNS SANs in this case.

Solution 5

To import the cert:

  1. Extract the cert from the server, e.g. openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt This will extract certs in PEM format.
  2. Convert the cert into DER format as this is what keytool expects, e.g. openssl x509 -in certs.txt -out certs.der -outform DER
  3. Now you want to import this cert into the system default 'cacert' file. Locate the system default 'cacerts' file for your Java installation. Take a look at How to obtain the location of cacerts of the default java installation?
  4. Import the certs into that cacerts file: sudo keytool -importcert -file certs.der -keystore <path-to-cacerts> Default cacerts password is 'changeit'.

If the cert is issued for an FQDN and you're trying to connect by IP address in your Java code, then this should probably be fixed in your code rather than messing with certificate itself. Change your code to connect by FQDN. If FQDN is not resolvable on your dev machine, simply add it to your hosts file, or configure your machine with DNS server that can resolve this FQDN.

Share:
518,851
Glory to Russia
Author by

Glory to Russia

Updated on October 11, 2021

Comments

  • Glory to Russia
    Glory to Russia over 2 years

    I have a Java web service client, which consumes a web service via HTTPS.

    import javax.xml.ws.Service;
    
    @WebServiceClient(name = "ISomeService", targetNamespace = "http://tempuri.org/", wsdlLocation = "...")
    public class ISomeService
        extends Service
    {
    
        public ISomeService() {
            super(__getWsdlLocation(), ISOMESERVICE_QNAME);
        }
    

    When I connect to the service URL (https://AAA.BBB.CCC.DDD:9443/ISomeService ), I get the exception java.security.cert.CertificateException: No subject alternative names present.

    To fix it, I first ran openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt and got following content in file certs.txt:

    CONNECTED(00000003)
    ---
    Certificate chain
     0 s:/CN=someSubdomain.someorganisation.com
       i:/CN=someSubdomain.someorganisation.com
    -----BEGIN CERTIFICATE-----
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    -----END CERTIFICATE-----
    ---
    Server certificate
    subject=/CN=someSubdomain.someorganisation.com
    issuer=/CN=someSubdomain.someorganisation.com
    ---
    No client certificate CA names sent
    ---
    SSL handshake has read 489 bytes and written 236 bytes
    ---
    New, TLSv1/SSLv3, Cipher is RC4-MD5
    Server public key is 512 bit
    Compression: NONE
    Expansion: NONE
    SSL-Session:
        Protocol  : TLSv1
        Cipher    : RC4-MD5            
        Session-ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
        Session-ID-ctx:                 
        Master-Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
        Key-Arg   : None
        Start Time: 1382521838
        Timeout   : 300 (sec)
        Verify return code: 21 (unable to verify the first certificate)
    ---
    

    AFAIK, now I need to

    1. extract the part of certs.txt between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----,
    2. modify it so that the certificate name is equal to AAA.BBB.CCC.DDD and
    3. then import the result using keytool -importcert -file fileWithModifiedCertificate (where fileWithModifiedCertificate is the result of operations 1 and 2).

    Is this correct?

    If so, how exactly can I make the certificate from step 1 work with IP-based adddress (AAA.BBB.CCC.DDD) ?

    Update 1 (23.10.2013 15:37 MSK): In an answer to a similar question, I read the following:

    If you're not in control of that server, use its host name (provided that there is at least a CN matching that host name in the existing cert).

    What exactly does "use" mean?

  • Glory to Russia
    Glory to Russia over 10 years
    I can't access the service via http://www.example.com/someservice. Is it correct that in order for the certificate to work with IP-based address (https://AAA.BBB.CCC.DDD:9443/ISomeService), I need set all CN fields to AAA.BBB.CCC.DDD (replace someSubdomain.someorganisation.com by AAA.BBB.CCC.DDD in the file above) and import the resulting certificate file?
  • Bruno
    Bruno over 10 years
    You can't do anything about the CN or the cert if you're not in control of the server.
  • John
    John over 9 years
    The approach mentioned in the link mentioned above appears to be blocked (nakov.com/blog/2009/07/16/…) . Can anyone update the link ?
  • user
    user over 8 years
    Or you could just replace the entire body of the verify() function with return hostname.equals("localhost");, if that is what you want to do. The if is completely superfluous.
  • tyoc213
    tyoc213 over 8 years
    to access to the ip address for test purposes you can modify temporarily your /etc/hosts file or equivalent
  • dacDave
    dacDave about 8 years
    This was a simple and quick fix that got us around testing in a vendor's test environment that did not have a proper certificate. Thanks a million!
  • ThunderWiring
    ThunderWiring about 8 years
    @JuanM.Hidalgo that worked for me, placed the code above right before the call to HttpsURLConnection connection = (HttpsURLConnection) obj.openConnection();. Also, does this ignores every certificate? Since i saw that such a workaround is vulnerable to security. Thank you!
  • Jus12
    Jus12 almost 8 years
    disabling HTTPS check is not a "solution". You should say I found a "patch".
  • galeop
    galeop almost 8 years
    In my code I was sending the request to a public IP, but the certificate CN was a host name. So in my code, I replaced the IP by the hostname, and configured my /etc/hosts to associate this hostname to the IP. Solved !
  • bonapart3
    bonapart3 about 7 years
    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER is now deprecated.
  • hitesh141
    hitesh141 almost 7 years
    java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
  • Woland
    Woland over 6 years
    "If FQDN is not resolvable on your dev machine, simply add it to your hosts file" – helped. Thanks a lot!
  • pkgajulapalli
    pkgajulapalli over 6 years
    I did the same thing but still, the issue persists. Is there anything else that can be done using this approach?
  • user64141
    user64141 about 6 years
    Here are the import statements: import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate;
  • Shankar
    Shankar over 5 years
    This will cause vulnerability on Pre_prod and Production. 1. Make host entry on windows host file 2. Or else add IP or FQDN names on Subject alternative names fields in the certs
  • Stephan Richter
    Stephan Richter over 5 years
    Certificate check and hostname check are there for a good reason. While ignoring all warnings may be a workaround, this is definitly not a solution. It defies all security SSL is intended to provide.
  • jasie
    jasie over 4 years
    please add explanatory text to your code. See stackoverflow.com/help/how-to-answer
  • Marek Puchalski
    Marek Puchalski over 4 years
    This is a security flaw. This is how one disables certificate verification in the first place. Everyone who copy-pastes this solution will create a security bug in their software.
  • Akhil S Kamath
    Akhil S Kamath over 4 years
    this answer solved my SSL cert exception and non LDH characters issue, I could see there is a bug reported in open JDK, bugs.openjdk.java.net/browse/JDK-8170265
  • Raymond Chiu
    Raymond Chiu over 3 years
    Adding the "-ext ip:<IP_ADDRESS>" in the keytool genkey command do fix the issue. Please note that when the keytools asking the first and last name, you need to put also the <IP_ADDRESS> answer to generate the cert.
  • rogerdpack
    rogerdpack about 3 years
    It works, though it just continues on to some other error, disabling SSL entirely finally worked stackoverflow.com/a/19542614/32453
  • bharal
    bharal about 3 years
    this is the only solution that works! if the java team actually cared about security they would, you know, make this a simpler problem to solve
  • bharal
    bharal about 3 years
    it's crazy, but nobody alive knows what that check they're doing actually prevents. just engineers adding things because they read it on a thoughtworks blog
  • mvreijn
    mvreijn over 2 years
    @bharal The check is preventing man-in-the-middle attacks on your secure connections. Better not disable all certificate verification unless you are 100% sure it will only be used in a development environment.