Monday, April 5, 2010

Weblogic and Wildcard SSL Certificates

I've recently been working on setting up an instance of Weblogic 10.3 for a new app I'm working on. This app makes several calls to a web service over SSL that is configured with a wildcard certificate. That means the certificate was issues to *.mydomain.com. This is a slick mechanism; it saves money and is easier to maintain all of your subdomains.



The Problem: Weblogic doesn't handle wildcard certs out of the box.
When weblogic initiates a connection over SSL, it makes a call to the HostnameVerifier. The default verifier simply ensures that the hostname you're connecting to and the name that the certificate was issued for match. So you can see the problem with wildcard certs in this example. The webservice is on ws.mydomain.com and the SSL certificate was issued to *.mydomain.com. If you google this problem, you will probably find lots of 'solutions' that suggest you disable hostname verification, but this leaves you vulnerable to man-in-the-middle attacks.



TheSolution: You might also find a few who would suggest you create your own HostnameVerifier. This is what we want, but I was unable to find any examples that actually show you how to do this! So here I will show you my implementation. Its not perfect, but I think it is certainly more than enough to get you started. DISCLAIMER: Most of the code is in a try/catch block. You'll notice that my code returns true on an error. Most likely, if your site has been compromised, the code will still run without exceptions and it should detect that the hostname and certificate don't match. If this your production system and you're super paranoid, just have the catch block return false.


package com.andrewthompson.weblogic;

import java.io.ByteArrayInputStream;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.SSLSession;

import weblogic.security.SSL.HostnameVerifier;

/**
* Verify the hostname of outbound ssl connections. 
* 
* @author andrew.thompson
*
*/
public class WildcardHostnameVerifier implements HostnameVerifier 
{

public boolean verify(String hostname, SSLSession session)
{
try
{
Certificate cert = session.getPeerCertificates()[0];

byte [] encoded = cert.getEncoded();

CertificateFactory cf = CertificateFactory.getInstance("X.509");
ByteArrayInputStream bais = new ByteArrayInputStream(encoded);

X509Certificate xcert = (X509Certificate)cf.generateCertificate(bais);

String cn = getCanonicalName( xcert.getSubjectDN().getName() );

log.info("CN: " + cn);
log.info("HOSTNAME: " + hostname);

if(cn.equals(hostname))
return true;

Pattern validHostPattern = Pattern.compile("\\*\\.[^*]*\\.[^*]*");
Matcher validHostMatcher = validHostPattern.matcher(cn);

// Make sure the cert only has wildcard in subdomain.  We don't
//    want to confirm *.*.com now do we?  
if(validHostMatcher.matches())
{
String regexCn = cn.replaceAll("\\*", "(.)*");

log.info("REGEXCN: " + regexCn);

Pattern pattern = Pattern.compile(regexCn);
Matcher matcher = pattern.matcher(hostname);

if(matcher.matches())
{
log.info("Pattern MATCHES");
if(matcher.group().equals(hostname))
{
log.info("Group() matches hostname: " + matcher.group());
return true;
} else {
log.info("Group() doesn't match hostname: " + matcher.group());
return false;
}
} else {
log.info("Pattern DOESN'T MATCH");
return false;
}

}


} catch (Exception e ) {
e.printStackTrace();
return true;
}

return true;
}

/**
* Return just the canonical name from the distinguishedName 
* on the cert.
*  
* @param subjectDN
* @return
*/
private String getCanonicalName(String subjectDN)
{

Pattern pattern = Pattern.compile("CN=([-.*aA-zZ0-9]*)");
Matcher matcher = pattern.matcher(subjectDN);

if(matcher.find())
{
return matcher.group(1);
}

log.info("Couldn't find match for CN in subject");
return subjectDN;

}


private final static Logger log = Logger.getLogger(WildcardHostnameVerifier.class.getCanonicalName());

}


This is simply a matter of implementing the weblogic.security.SSL.HostnameVerifier interface. You will find this class in the weblogic.jar of your server/lib dir.
You'll notice that weblogic provides us with the SSLSession. From here, we can extract the java.security.cert.Certificate. We then use a CertificateFactory to transform this Certificate into an X509 certificate. That allows us to pull some useful information out, such an the Subject Distinguished Name. This is a string of key/value pairs; we are only interested in the Common Name or CN. So, we extract the CN with a regular expression pattern matcher. If the hostname matches the CN, we're done. If not, we check to see if the CN has a wildcard (*). If so, we use another regular expression matcher to see if the hostname matches the CN with any subdomain.



Now, you just need to go into the weblogic console, select the SSL tab on your server. Check 'Custom Hostname Verifier', enter the classname of your verifier, and make sure your class is on weblogic's classpath.

23 comments:

  1. Will your app work on a SmartPhone or Mobile? Or will you have to change the certification to a SAN SSL Certificate from a Wildcard SSL Certificate? I recently purchased a wildcard SSL certificate from SSL247 to secure one of my sites multiple domain names.

    ReplyDelete
  2. Hi Oliver,
    The client type (desktop, mobile, etc) doesn't really matter here. The hostname verifier is used during outbound SSL connections from Weblogic. So, you'd use this custom verifier if your application made a call to a url secured with a wildcard cert.

    If you want multiple subdomains and need mobile access to Weblogic, then yeah, you might want to look into a SAN cert.

    Hope this helps! -- andrew

    ReplyDelete
  3. Hi Andrew,

    I'm not sure about your code:

    if(validHostMatcher.matches()) {
    // process a cn like: *.helloworld.com
    // seems ok to me
    }
    return true;

    But when I test with input parameters:

    hostname = helloworld.com
    cn = goodbyeworld.com

    this would return true for this non wildcard example.. , it doesn't seem ok to me, do you agree? Wouldn't it be better to only check the wildcard situation and in all other situations return the result of weblogics own DefaultHostnameVerifier?

    I also found this on http://forums.oracle.com/forums/thread.jspa?threadID=1021441&tstart=0#4069950

    package hostname;

    import weblogic.security.SSL.HostnameVerifierJSSE;
    import javax.net.ssl.SSLSession;

    public class TestHostnameVerifier implements HostnameVerifierJSSE {

    public boolean verify(String urlhostname, String certHostname) {

    System.out.println("TestHostnameVerifier.verify urlhostname"urlhostname"certHostname"+certHostname);

    return true;
    }

    I haven't tried it but it seems to simplify your example and could remove the try catch and aproximately lines 26 to 37.

    ReplyDelete
  4. This comment has been removed by a blog administrator.

    ReplyDelete
  5. Hi

    I found the wildcard working on one machine with same version of weblogic but it does not work on other machine. It throws error like "hostname" could not be matched.
    What could be reason.

    How do I disable hostname verification in the application? Please suggest.

    ReplyDelete
  6. @sanjeev, you disable hostname verification in the Admin console. In the console, lick on your server, then click the SSL tab. You should see a dropdown box that allows you to disable it. Note that you can't do it on a per-app basis... it will affect all apps running on that particular server instance.

    ReplyDelete
  7. What about alternate subject name?
    I wrote similar to your solution, but it is looping through "((X509Certificate) cert).getSubjectAlternativeNames()" and checking with hostname using pattern matching.

    ReplyDelete
  8. @Ashish... I don't claim to be an X.509 guru, but I think looping through the subjectAltNames could be appropriate as long as you also check the subjectDN. It's my understanding that the subjectAltNames is an X.509 extension and is therefore, optional (used for adding additional domain names that this server is also known by). I think most certs nowadays include this, but its probably not required. Someone please correct me if I'm wrong. Thanks!

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. Hi Andrew,

    I implemented the steps mentioned by you to solve the host name failure in weblogic. I changed the settings in weblogic console, added the jar file to server/lib of weblogic, added the jar file to Weblogic classpath in startWebLogic.cmd. But still the Host Name check failed. It seems weblogic still calls Bea Host name verifier for hostname check.

    Can you please let me know if i am missing something here?

    ReplyDelete
  11. hi,Andrew
    it is permanent solution to solve the wildcard certificate on weblogic.
    some may not fix this so give more details about this to solve problem.

    thank for sharing with us.

    ReplyDelete
  12. Last message was truncated. this is the Exception i get:

    ReplyDelete
  13. This comment has been removed by the author.

    ReplyDelete
  14. What i did:
    1. I wrote a custom host name verification class. as below:
    public class CustomHostnameVerifier implements weblogic.security.SSL.HostnameVerifier {
    public boolean verify(String urlHostname, javax.net.ssl.SSLSession session) {
    System.out.println("++++++++++++++++++++++++++++ Warning: URL Host:" + urlHostname +":");
    return true;
    }
    }

    2. I copiled the java file and made a "CustomHostnameVerifier.jar" file of with the class file "CustomHostnameVerifier".
    3. Added this "CustomHostnameVerifier.jar" file location under the "admin server/configuration/server start" tab in classpath field as "C:\bea\wlserver_10.0\server\CustomVerifier\CustomHostnameVerifier.jar"
    4. Under "Admin Server/configuration/SSL" - advanced i have activated Hostname Verification to Custome Host Name Verification.
    5. Gave the file name "CustomHostnameVerifier" for Custom Hostname Verifier: field value.
    6. Activated "SSL Listen Port Enabled" under "Admin server/configuration/general.
    7. Saved and activated all changes then restarted the WebLogic server. then i get the exception.
    BEA-090563 Cannot create instance of Hostname Verifier CustomHostnameVerifier

    ReplyDelete
    Replies
    1. I am facing the same problem. Anybody got the solution for this ?

      Note that I have created a public no arg constructor for my class.

      Delete
    2. got this working after placing the class (packaged as jar) first in the weblogic server classpath.

      Delete
  15. Thanks a lot for the post, it helped a lot!

    But I have some trouble making it work. I have done the following -
    1. Changed the configuration in weblogic console to use the 'Customer Hostname Verifier' and specified the class name.
    2. Uploaded the jar file containing the class to /server/lib directory.
    3. Set the server logging to have minimum severity to log as 'Debug'.
    4. Restarted the admin server and managed server.

    But no messages are being dumped to the server log (the log file name of the logging setup)
    And the class is not being used.

    Can you please let me know how to verify that the class is properly loaded and is used at run time? And am I supposed to see the logging messages (using java.util.logging.Logger and JDK Logging implementation at server setup)?

    ReplyDelete
  16. I have done the following on WLS9.2.3:
    o Selected Custom Hostname Verifier in the admin console
    o Entered "com.xyz.CustomHostNameVerifier" in the "Custom Hostname Verifier" field.
    o Added the path to com/xyz/CustomHostNameVerifier.class to the WLS CLASSPATH list, i.e. "c:\workspace\wildcardprj". Checked the wls log to make sure it was included at startup and it's there.
    o Set "SSL Listen Port" to "Enabled."
    o Rebooted the WLS server.
    and... didn't work.
    I get "Cannot create instance of Hostname Verifier com.xyz.CustomHostNameVerifier" in the server log file on start up.
    Then, I created a jar file containing only the CustomHostNameVerifier.class file and put it in %DOMAIN_HOME%\lib. I see in the WLS log file that it pick this jar up dynamically. Yet, still have the same problem.
    I created a JUnit test that generates an SSLSession and instantiates the CustomHostNameVerifier class so I know it's not generating an exception.
    The error description says it's either 1) no no-arg CTOR, 2) can't find the class, or 3) exception generated by class instantiation. But, it doesn't look like any of those things in my case. Kind of stumped.

    I'm wondering how you made that work. Any suggestions, Andy?

    ReplyDelete
  17. this page http://download.oracle.com/docs/cd/E12839_01/web.1111/e13711/ssl_client.htm#SCPRG268 says: "The specified class must have a public no-arg constructor."

    I have this - however I am still getting "[Security:090563]Cannot create instance of Hostname Verifier" error. :(

    ReplyDelete
  18. Thankyou for the informative post, me and my husband have been attempting to set up our own online distribution methods, and using wildcard ssl cert I have previously attempted to hire a consultant but we cannot get the buisness in the condition for independant management, thanks for the tips!

    ReplyDelete
  19. Nice Blog!

    It's really informative source about WildCard SSL Certificate. Thanks for sharing all in one information through blog which contains the problem and also the solution including scripts for WildCard SSL Certificate. We appreciate your writing skill and also we have shared it our social walls.

    ReplyDelete
  20. Solution for both wildcard and non-wild card certificates

    http://www.exterro.in/generic-solution-for-weblogic-handshake-issue-in-ssl-mode

    ReplyDelete
    Replies
    1. it is a helpful post,with Positive Wildcard Certificate, you have the advantage of instant issuance, no more paperwork, and low cost. You can avail a discount on multiyear purchase. It is equally trusted as more expensive certificates from RapidSSL and GeoTrust.

      Thanks for sharing........

      Wildcard SSL Certificates

      Delete