Covering J2EE Security and WebLogic Topics

Certificate to User Mapping in WebLogic

A reader of my Fifteen Minute Guide to Mutual Authentication post commented that perhaps the burden of doing mutual authentication (two-way SSL) isn’t worth the effort since you still have to map certificates to users. I’ll admit, that does seem like a bummer.

However, since most companies already have a list of users in LDAP or a database it’s probably not a big deal most of the time. WebLogic has several authentication providers that you can use to tap into your existing user store. Still, I think it’s a great question worth exploring so let’s consider what’s involved.

Strong Authentication

Having the user provide a certificate to the server is a form of strong authentication. No password traverses the network and the user must possess the certificate file. It’s extremely difficult to spoof a user due to the cryptography involved. The user is most definitely authenticated when the two-way SSL link is successfully created. In other words, they are who they say they are assuming you trust the Certificate Authority (CA) who signed their certificate.

Strong authentication alone is the primary benefit of using mutual authentication, of course. The user is who they say they are. We now need to turn our attention to authorization so that we can determine whether or not they can access the system.

Implicit Groups

If you don’t want to maintain cert-to-user mappings you can leverage WebLogic’s implicit groups. See the link for more info but you could use the “users” group to authorize users without maintaining a user list. The “users” group represents any authenticated user. When using mutual authentication, any user identified by their certificate will automatically be in this group. If you can get away with one role in your application then the “users” group may work for you. However, be sure to read about CAs below.

Certificate Authority Scope

The huge drawback to using the implicit “users” group in this way is that any certificate signed by the CAs you trust will be acceptable to your application. If you’re using the default JDK truststore “cacerts” you’ll wind up trusting the signed certificates of all the major and a ton of minor CAs from around the world.

The fix, of course, is if you control the CA. For example, if your company has a CA, you can configure your truststore to only have that CA’s certificate. Then, you only accept users from your company (and partners if they get certs signed by your company’s CA).

In this scenario you will still allow anyone signed by the company’s CA to have access to your app. Whether that’s acceptable or not is a business decision. If you go this route make doubly sure that you ONLY trust your company’s CA.

Deprovisioning Users

When you don’t have a list of users how do you deny them access when they quit?

Revoking the certificate is the obvious answer. Checking certificate revocation requires a custom security provider and is outside the scope of this article. However, the implication is that your public key infrastructure must be mature enough to have OCSP or CRLs for you to check revocation in the first place.

Conclusion

To summarize, it is possible to use mutual authentication without maintaining a list of users. However, you need to exercise extreme care when doing so. Also, authorization flexibility is reduced to just one possible role since there’s only one group to which to map.1


Footnote:

1. Technically, you can do the mapping to multiple roles if certain preconditions exist and you’re willing to do some extra work. As an example scenario, let’s say you use your own CA. Your CA uses OU fields in the DN which you might be able to leverage for role mapping. You’d have to write your own authentication provider to add groups to the subject based upon the OU information and then you can map the roles to the groups as usual in your application’s deployment descriptors.

Digital Signatures Explained

It’s fairly easy to get digital signatures working with web services. Just pull up the docs for your web service stack and follow the directions. Some configuration here and keystores there and you’re good to go.

But just what is happening under the covers? Digitally signing something might seem like magic but it’s rather simple conceptually even though it builds on some pretty heavy theory (mostly math, ugh!). However, in this post I’m going to talk about the concepts and leave the math to someone else.

What’s the Purpose of a Digital Signature?

Before we start decomposing the mechanics of signing data, let’s first consider what we want to use a signature for in the first place.

Data Integrity

A digital signature allows the receiver to check if the data has been altered since the sender signed it. This function is performed via a cryptographic hash which I’ll talk about later.

Verification of the Sender

Digital signatures use private and public keys. An entity (person or process) signs the message with his/its private key and then the recipient can use the entity’s public key to verify the source.

Hey, you got your Digital Signature in my SSL!

Do these two functions seem a little like what SSL does? You’re right! SSL provides those features for data in transit while a digital signature does the same thing at the message level. SSL and digital signatures don’t work in the exact same way but they do perform similar high-level functions. One interesting difference between the two is that the digital signature stays with the message even if it’s sitting in a queue or on disk somewhere (assuming that it’s not intentionally stripped at some point).

Try that, SSL!

I’d like to mention one more thing about SSL and signatures before digging out of this SSL rabbit hole: You can use signatures and SSL at the same time. Why might you want to do this? There are several reasons:

  • You want to encrypt your message over the wire. That’s SSL’s sweet spot.
  • Your message gets processed by multiple machines and you want each to verify the original sender of the message. SSL can’t do this past the first recipient since it’s a point-to-point protocol. The digital signature, on the other hand, travels with the message wherever it may go.

How Digital Signatures Work

Creating signed data is a two step process. The first step is to hash the data and the second step is to sign the hash. Both of these steps are cryptographic operations but neither actually encrypts the data. Fortunately, the Java API provides classes for doing these operations so we don’t have to write any of that complex stuff. We’ll see these APIs in action in a bit.

Let’s Hash it Out

There are several algorithms for generating one-way cryptographic hashes. You’ve probably heard of MD5 and SHA-1. These algorithms take any amount of data and convert it to a fixed length byte array that can’t be reversed. That is, given the hash, you can’t determine what the input was. Additionally, two different inputs will never generate the same hash.

NOTE: Technically, both of the previous assertions are not absolutely true. The time and computing power required for reversing a hash make it unlikely. The more likely case for "reversing" a hash is to leverage a pre-computed hash dictionary which I’ll discuss briefly later. Finally, there are so called "collisions" where two different inputs can create the same hash but this situation is extremely rare).

Because of these features, hash algorithms are often used for storing passwords. Take the user’s password, hash it, and then store it in LDAP or a database. You can’t guess the password from the hash so the stored passwords are reasonably secure from prying eyes. But when the user logs in, you hash the newly supplied password and compare it against the hash on file. If they match, the user is authenticated.

I bet you already knew that stuff. The cool thing is that’s the first half of generating a signature. Before we move on to the second half, let’s have a look at how to generate a hash using the Java APIs.

MessageDigest md = 
     MessageDigest.getInstance("SHA-1");

md.update("Corned beef hash".getBytes());

// Create the hash from the message
byte[] hash = md.digest();

The code above leverages the MessageDigest class for hashing data. "Digest" is another word for "hash." We tell the MessageDigest object that we want to use the SHA-1 algorithm and then feed it the data using the update() method. You can call that method repeatedly until all of your data is included in the hash. Then, simply call digest() to get the fixed-length hash.

Notice that the hash is actually a byte array which would create non-printable characters. So, to show you the hash for the input data I’ll first encode the hash like this:

String encodedHash = 
    new sun.misc.BASE64Encoder().encode(hash);

I know, I know. Don’t use the sun.* classes. I’m just saving you the trouble of not having to download something like Commons Codec in order to try this out. Just don’t use sun.* classes for production.

Anyway, now that the hash has been encoded, I can tell you that the hashed version of "Corned beef hash" is

TARd8ciquglqtzCGlhl/Ano8+kE=

Notice that the length of the hash is longer than the input. Let’s try hashing "Corned beef hash is not dog food"

H6exMsBveZPenXK756/i+ph1z8Q=

Notice that the length of the hash is still the same. It would even be the same length for a megabyte worth of text.

Before we move on to signing data, I’d like to mention one more thing about hashing. The one-way nature of a cryptographic hash is very useful but it can bite you. Since the same input always generates the same hash for a given algorithm, a bad guy who can get hold of your hashed data might be able to use a precomputed hash dictionary to determine your original text. It’s sort of like a reverse-lookup of the hash. For example, a hash dictionary will have the hashes for common passwords such as "Password" or "ABC123" and the bad guy can just query the hash to get the corresponding input.

Not good.

The remedy is to add some "salt" to the hash. A salt is just a bit of data that you’ll add to your input text when you compute the hash. This simply equates to another call to update() method. Only you know the value of this salt which acts like a simple pass key or password and negates the ability for a bad guy to determine the input data.

For example, the hash for "Corned beef hash" is TARd8ciquglqtzCGlhl/Ano8+kE= and always will be. The hash of "Corned beef hash" with a salt of "Pinch of Salt" is rEN7xxJPqyY7pkspLL902NkmJn0=. Obviously, the phrase "Pinch of Salt" would have to be kept secret.

Signing Data

Conceptually, we would now just sign the hash. However, with the Java API, the hashing is done for us as part of the signing process so we wouldn’t actually perform the steps above to generate the signature. Instead, we’d just use the Signature class.

Using the Signature class is a little more involved than hashing because we need a private key to actually do the signing. The corresponding public key would be used by the recipient to verify the signature later. Ideally, you would have your keys in a keystore and use them with the Signature class. For demonstration purposes I’m going to generate the keypair on the fly. Yes, I’m lazy but it also makes the pertinent signing machinery stand out better. Call it artistic license. ;-)

Here’s the sample code:

// Generate a keypair which 
// contains the private/public keys
KeyPairGenerator keyGen = 
    KeyPairGenerator.getInstance("DSA");
keyGen.initialize(1024, new SecureRandom());
KeyPair keyPair = keyGen.generateKeyPair();

// Sign some data
Signature sig = Signature.getInstance("DSA");
sig.initSign(keyPair.getPrivate());
sig.update("Sign on the dotted line".getBytes());
byte[] signedData = sig.sign();

The first group of code generates a sample keypair that, as shown here, will only live until it goes out of scope. It’s good enough for our purposes, though. The second group is where the action is. We tell the Signature object that we want to use the DSA (Digital Signature Algorithm) and then we load it with the private key to use as the signer. Add the text via update() just like we did for hashing and then call sign(). Just like before, we get back a byte array which is unprintable. After encoding the signed data, I can tell you that the signature for "Sign on the dotted line" looks like this:

MCwCFA1+YXhgSu0xCP6lhKVO9QH5DYbcAhRQ/V5i8czHiMxL7SnyLtafZNoL9A==

Now, the results here are a little trickier than when hashing. If you run this code, you’ll get a different encoded string than what’s shown here. It’ll be different for two reasons:

  1. You’re using a different private key
  2. You’re running it at a different time on different hardware

When I run it again with the same private key I get

MCwCFBlCOnD9MfPgTtDUohfh7z/TArU7AhRqXyeSHAzzW97+ha2V5d4RDfZq8w==

So, even though the lengths are the same the output is different. By the way, the length will be the same regardless of the input size.

Pretty cool, isn’t it? There’s a lot of stuff going on in those few lines of code. Now we have signed data but how does the recipient verify it?

Verifying Signed Data

Let’s say I send you an email that contains two lines. The first line is

Sign on the dotted line

and the second line is

MCwCFA1+YXhgSu0xCP6lhKVO9QH5DYbcAhRQ/V5i8czHiMxL7SnyLtafZNoL9A==

Obviously, these lines represent the message and the signed message, respectively.

To see if I REALLY said "Sign on the dotted line" you would mash together my public key, the message, and the signed message to see if they align. That’s imprecise language for the process of determining if, given the message, the private key associated with the public key would produce the signed message. It’s sort of equivalent to the process of checking passwords using a hash as described above except the keys have been added to the mix.

Here’s the code for doing just that:

Signature sig2 = Signature.getInstance("DSA");
sig2.initVerify(keyPair.getPublic());
sig2.update("Sign on the dotted line".getBytes());
 
boolean verified = 
sig2.verify(
"MCwCFA1+YXhgSu0xCP6lhKVO9QH5DYbcAhRQ/V5i8czHiMxL7SnyLtafZNoL9A=="
.getBytes());

As before, the first line tells the Signature object which signing algorithm to use and it has to match the algorithm that was used originally to sign the message. The second line loads the public key that matches the private key used to sign the data. (Important: The recipient does NOT and should not have your private key!)

We then load the message with the update() method. Finally, we pass the signed data to the verify() method. If it returns true, the signature is verified. Changing even one character in the message or signed data will cause verification to fail which is what you want. And obviously, specifying a public key that does not match the signer’s private key will fail, too.

To sum up verification, if the message is modified in any way, verification will fail. Somebody monkeyed with the data and you were able to detect it. That’s data integrity checking. If verification fails because a mismatched public key was used, then you know that someone other than who you expected signed the message. To my knowledge, you can’t distinguish between the two causes of verification failure.

Signing Off

Knowing the fundamentals of digital signatures will help you to understand things that build on them such as the XML Digital Signature specification or the Java implementation of it. Here’s a re-cap and a few other take-aways:

  • Signing cannot prevent changes to the message, but changes can be detected
  • You can detect change, but you can’t tell WHAT changed
  • The signed message can travel with the data or can be separate (for example, in the email scenario above, I could have sent you the signed data in a separate email and told you it went with the first message)
  • Encryption and signatures are not the same. With encryption, you intend for the original message to be recoverable at some point.
  • You can think of a signature as a very fancy checksum
  • In PKI, private and public keys are mathematically related. The private key is only needed for signing and the public key is only needed for verification.
  • A salt can be a constant (but still secret) value or it can be generated randomly with each use. In both cases the verifier must have access to the salt.

Verisign’s New Intermediate CA and You

Starting in August, 2006, Verisign introduced a new intermediate Certificate Authority (CA) into its certificate hierarchy. As a result, Verisign server certificates are signed by this CA instead of being signed by the Verisign root as before. This article will guide you through the process of using keytool to install a certificate signed by this new CA certificate.

Why is this scenario different than before or with other CAs? The reason is that since the CA certificate is new, it hasn’t had time to fully propagate into the various places it needs to be, such as the cacerts trust store that comes with Java. (The new CA certificate is not in cacerts as of JDK 1.5.0_04 but it might be there in a later release.) Without the CA certificate in cacerts, you won’t be able to use keytool to import a certificate signed by the new CA.

For the purposes of this article, I’ll assume you have a JKS keystore with your private key and that you’ve submitted a Certificate Signing Request (CSR) to Verisign. Verisign signs your certificate with the new CA and sends you a cert.cer file containing your signed certificate.

Here’s what you need to do:

  1. Obtain the new intermediate CA certificate
  2. Import the intermediate CA certificate into cacerts using keytool
  3. Import your signed certificate into your keystore using keytool

Obtain the Intermediate CA Certificate

You can get the new CA certificate from here. Save the certificate contents to a file (the example below uses “NewVerisignIntermediateCA.cer”).

Before moving on, spend a few moments playing a game of Boggle with the certificate contents. ;-)

Import the CA Certificate into cacerts

The keytool utility can be found in the <JAVA_HOME>\bin directory. The cacerts file it uses can be found in <JAVA_HOME>jre\lib\security. We’ll load the new CA certificate into cacerts:

keytool -import -keystore <JAVA_HOME>jre\lib\security\cacerts -file NewVerisignIntermediateCA.cer

The default cacerts password is “changeit”.

Import the Signed Certificate into the Keystore

We’ll use keytool again to load the signed certificate over the same alias as your key entry. (By the way, this is a good time to make a backup of your keystore!)

But first, a word about what Verisign sent you. The email will contain the certificate contents as part of the email text. There will also be an attachment of the same contents called cert.cer. Regardless of which one you use, ensure that there are no spaces or blank lines at the end of the file. The attached cert.cer likely has two blank lines. Remove those and ensure that there are no spaces at the end of the file.

Load the signed certificate:

keytool -import -keystore <keystore> -alias <alias> -file cert.cer -trustcacerts

where keystore is the name of your JKS file and alias is the alias of your key entry.

What you want to see from this command is

Certificate reply was installed in keystore

If you didn’t get this message, perhaps you got this beauty instead:

java.io.IOException: DerInputStream.getLength(): lengthTag=127, too big.
at sun.security.util.DerInputStream.getLength(DerInputStream.java:530)
at sun.security.util.DerValue.init(DerValue.java:346)
at sun.security.util.DerValue.<init>(DerValue.java:302)
at sun.security.provider.X509Factory.parseX509orPKCS7Cert(X509Factory.ja
va:522)
at sun.security.provider.X509Factory.engineGenerateCertificates(X509Fact
ory.java:407)
at java.security.cert.CertificateFactory.generateCertificates(Certificat
eFactory.java:511)
at sun.security.tools.KeyTool.installReply(KeyTool.java:1193)
at sun.security.tools.KeyTool.doCommands(KeyTool.java:504)
at sun.security.tools.KeyTool.run(KeyTool.java:124)
at sun.security.tools.KeyTool.main(KeyTool.java:118)
keytool error: java.security.cert.CertificateException: DerInputStream.getLength
(): lengthTag=127, too big.

If so, you played too much Boggle. Remove any blank lines and spaces from the end of your signed certificate file and try again.

Another indication of an unsuccessful load is this output:

keytool error: java.lang.Exception: Failed to establish chain from reply

If you get this message, keytool can’t find the new Verisign intermediate CA. Double-check the instructions above but skip the Boggle part or you’ll be messed up for the rest of the day.

Assuming you successfully imported your signed certificate, have a look at your handiwork with

keytool -list -keystore <keystore> -v

You should see something similar to the following:

Keystore type: jks
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: server
Creation date: Oct 3, 2006
Entry type: keyEntry
Certificate chain length: 3
Certificate[1]:
Owner: CN=monduke.com, OU=Terms of use at www.verisign.com/rpa (c)05, OU
=Department of Blogging Department, O=Bastion Blog, L=Pasadena, ST=Maryland
, C=US
Issuer: CN=VeriSign Class 3 Secure Server CA, OU=Terms of use at https://www.ver
isign.com/rpa (c)05, OU=VeriSign Trust Network, O=”VeriSign, Inc.”, C=US
Serial number: 4a3b4682eab7b25b13dac435aefb3c5a
Valid from: Thu Sep 28 20:00:00 EDT 2006 until: Fri Oct 24 19:59:59 EDT 2008
Certificate fingerprints:
MD5: 9A:1E:CC:92:44:AF:CD:C0:57:67:63:3D:C0:F7:17:13
SHA1: 3D:79:41:BE:DB:DA:7B:5C:B6:F8:78:D8:60:4E:F8:80:33:E1:08:92
Certificate[2]:
Owner: CN=VeriSign Class 3 Secure Server CA, OU=Terms of use at https://www.veri
sign.com/rpa (c)05, OU=VeriSign Trust Network, O=”VeriSign, Inc.”, C=US
Issuer: OU=Class 3 Public Primary Certification Authority, O=”VeriSign, Inc.”, C
=US
Serial number: 75337d9ab0e1233bae2d7de4469162d4
Valid from: Tue Jan 18 19:00:00 EST 2005 until: Sun Jan 18 18:59:59 EST 2015
Certificate fingerprints:
MD5: 2A:C8:48:C0:85:F3:27:DE:32:29:44:BB:B0:2C:79:F8
SHA1: 18:85:90:E9:48:78:47:8E:33:B6:19:4E:59:FB:BB:28:FF:08:88:D5
Certificate[3]:
Owner: OU=Class 3 Public Primary Certification Authority, O=”VeriSign, Inc.”, C=
US
Issuer: OU=Class 3 Public Primary Certification Authority, O=”VeriSign, Inc.”, C
=US
Serial number: e49efdf33ae80ecfa5113e19a4240232
Valid from: Sun Jan 28 19:00:00 EST 1996 until: Wed Jan 07 18:59:59 EST 2004
Certificate fingerprints:
MD5: 78:2A:02:DF:DB:2E:14:D5:A7:5F:0A:DF:B6:8E:9C:5D
SHA1: 4F:65:56:63:36:DB:65:98:58:1D:58:4A:59:6C:87:93:4D:5F:2A:B4

From this output you can see the issuer hierarchy where certificate 1 is your server certificate, certificate 2 is the intermediate CA, and certificate 3 is the Verisign root.

Enough work. Time for some more Certificate Boggle(TM). The great thing about it is that you can play all day and it’s boss-proof. When your boss asks why you’re staring at gibberish, you can say that you’re checking the server certificate for accuracy. He’ll admire your attention to detail and you’ll get a nice raise.

;-)

Mutual Authentication in Action

This post is a continuation of the Fifteen Minute Guide to Mutual Authentication. In that post, I walked you through configuring WebLogic for two-way SSL, or mutual authentication. It was a whirlwind tour whose purpose was to drive home the essentials of PKI theory while emerging with a simple working implementation.

This post picks up where the other left off by having the user’s certificate suffice for web application authentication. In other words, with mutual authentication the user does not have to log in with a username and password. Instead, the user’s certificate is his ticket to ride.

Here’s what we’ll do in this post:

  • Create a user
  • Configure identity assertion in WebLogic
  • Configure a web application to use CLIENT-CERT authentication
  • Configure role-to-principal mapping in weblogic.xml

The pre-requisite of this post is a browser that will accept the server’s certificate and a server that will accept the browser’s client certificate. If you don’t already have that set up, refer to the Fifteen Minute Guide to Mutual Authentication and follow the steps there. You can also use certificates issued by other Certificate Authorities (CAs) but you must be able to connect the browser to the server as described in the guide. If not, the steps in this post will not work for you until the basic problem is resolved.

Enough introductory babble. Let’s get to it.

Create a User

When we’re done with configuration, client certificates will map to known users in the WebLogic security realm. I’m assuming here that you are using the DefaultAuthenticator which stores users and group information in WebLogic’s embedded LDAP.

You need to add a user via WebLogic Console. Note that the steps given here are for WebLogic 8.1.4 but they are approximately the same for WebLogic 9.x. To add a user, navigate to the Security->Realms->myrealm->Users node in the applet.

On the right-hand side, click on Configure a new User. In the General tab, enter the user ID in the Name field. If you used the example from the Mutual Authentication Guide, enter "Spongebob". Otherwise, enter the value of the Common Name (CN) of your test certificate. Enter and confirm a password. The password won’t be used for mutual authentication, but make it a strong password anyway.

Click Apply and then select the Groups tab. Add the user to the Administrators group by moving "Administrators" to the Current Groups box. Click Apply.

You now have a new user in the Administrators group. The user ID for this user matches the CN of the client certificate you’ve loaded in your browser.

Configure Identity Assertion

Next up is identity assertion configuration. Identity assertion is the process by which a token from the request is mapped to a known user.

You need to configure WebLogic to use an X.509 certificate as the authentication token. Furthermore, you’ll configure a username mapper to map the certificate’s Distinguished Name (DN) to a user ID. The username mapper is part of the identity asserter.

These steps assume you are using the DefaultIdentityAsserter. To configure it, navigate to the Security->Realms->myrealm->Providers->Authentication->DefaultIdentityAsserter node in the applet.

On the General tab, the User Name Mapper Class Name and Trusted Client Principals fields can remain blank. For Types, however, move X.509 to the Chosen side and remove AuthenticatedUser. An identity asserter can only support one type at a time. Click Apply.

Click the Details tab to configure username mapping. Check the Use Default User Name Mapper field. Select CN for the Default User Name Mapper Attribute Type. Leave Default User Name Mapper Attribute Delimiter and Base64DecodingRequired set to "@" and selected, respectively. Click Apply and then restart WebLogic for the changes to take effect.

What you’ve done is told WebLogic to use its default username mapper to map the certificate to the user. It does this by pulling the CN value from the DN of the certificate. The default usernmame mapper can handle these basic functions. If you have more complicated mapping requirements, you can write a custom username mapper and specify it on the General tab in the User Name Mapper Class Name field.

Regardless, given a valid client certificate, if the username mapper emits a user ID that can be found in the security realm, the user will be authenticated.

Configure a Web Application

At this point, you’ve defined a user whose username matches the CN of the client certificate. You’ve also told WebLogic how to map client certificates to users.

You now need to configure your web application to use what you’ve configured. You only need to do three things:

  1. Apply a security constraint to a resource in web.xml
  2. Set the authentication method to CLIENT-CERT in web.xml
  3. Configure role-to-principal mapping in weblogic.xml

All three of these changes are made in the web application’s deployment descriptors. I’ve provided a sample web application for you that can be deployed without changes if you want to skip the editing. The application also shows how to pull the user’s certificate from the request if you need to (although you usually don’t).

You must protect a resource with a security constraint for the security framework to kick in. Here’s an example security constraint from web.xml:

  <security-constraint>
      <display-name>Example Security Constraint</display-name>
      <web-resource-collection>
         <web-resource-name>Protected Area</web-resource-name>
         <url-pattern>/protected/*</url-pattern>
             <http-method>DELETE</http-method>
             <http-method>GET</http-method>
             <http-method>POST</http-method>
	     <http-method>PUT</http-method>
      </web-resource-collection>
      <auth-constraint>
         <role-name>Admin</role-name>
      </auth-constraint>
  </security-constraint>

With a security constraint in place, you need to tell WebLogic how you want it to handle authentication. You’re probably most familiar with the FORM authentication type for doing username/password authentication. However, we want to extract the certificate from the two-way SSL session that’s already been established. To do this, we use the CLIENT-CERT authentication type. (You can find more information on the various authentication types here.) Here’s the pertinent snippet from web.xml:

 <login-config>
   <auth-method>CLIENT-CERT</auth-method>
   <realm-name>ArbitraryName</realm-name>
 </login-config>

With this web.xml file in place, we’ve told WebLogic what we want to protect and how users should be authenticated. When the user requests a protected resource, WebLogic will notice and the security framework will spring into action. Before a user can be authorized to access the protected resource, WebLogic first has to determine who the user is. Since we told it to use the certificate from the SSL connection, WebLogic will extract the certificate and hand it to the identity asserter. The identity asserter will attempt to map the certificate to a known user using a username mapper class. If it finds a match, the user is authenticated.

After authentication, authorization checks kick in and the request will be processed if the user is allowed access.

The last thing to do is to map the "Admin" security role defined in web.xml to one or more principals. This mapping is done in weblogic.xml. We’ll map the "Admin" role to the "Administrators" group so that any user in the Administrators group will have the Admin role and will thus be granted access. Here’s the relevant snippet from weblogic.xml:

  <security-role-assignment>
    <role-name>Admin</role-name>
    <principal-name>Administrators</principal-name>
  </security-role-assignment>

At this point, deploy your application (or use the sample) and see if you can log in with the certificate.

If it doesn’t work, troubleshooting is straight-forward. Since you already have two-way SSL working properly, there should be no problem with the server, client, or CA certificates. Here’s what could be wrong:

  • Incorrect security constraint
  • Incorrect role to principal mapping in weblogic.xml
  • Not requesting a web page with a security constraint
  • Not using https
  • Incorrect username mapper configuration
  • Non-existent user (given the username mapping)
  • User is not in the appropriate group for the security constraint

Once everything is correctly configured, you should be able to log in with only the client certificate.

Summary

You now have a web application that uses private/public keys for a very strong form of authentication. In the implementation shown here, the user’s public certificate does not exist on the server side. Rather, the user’s certificate is trusted because the server trusts the certificate’s issuer. Assuming the certificate is trusted, WebLogic then maps the certificate to a known user which can have the normal group memberships as any user.

Finally, thanks to the J2EE specification, your application does not have to deal with any of the mutual authentication machinery other than specifying in web.xml that you want it.

The Fifteen Minute Guide to Mutual Authentication

Inevitably, you’ll cry the first time you attempt to configure mutual authentication with SSL (aka two-way SSL). Well, maybe not cry, exactly. Maybe you’ll just squeeze the mouse until it makes cracking sounds. What? You’ve never done that? ;-)

Anyway, the problem many people seem to have when the boss tells them to do mutual authentication is that they go into information overload. PKI jargon. JDK tools. Server configuration. Documentation in a million different places. Couple this information overload with the desire to not “screw up” security and you have a good recipe for developer stress.

I’ve noticed that the biggest problem people have in coming to grips with mutual authentication is that they don’t understand the single most important aspect of configuring it. Once they learn the secret, they have an “A ha!” moment and then the rest of the process becomes much, much clearer.

In a nutshell, the secret is trust.

What I intend to do with this post is to show you the basics so that you can have your very own “A ha!” moment about PKI. Also, by the time this post is done you’ll see two-way SSL in action between your browser and WebLogic. I obviously have to skimp on the details of PKI, but if you follow the steps you’ll have two-way SSL working with test server and client certificates. Let’s get to it!

Certificate Authority Basics

I’ll leave the gory PKI details to other sources, but I have to tell you about Certificate Authorities (CAs).

First, go to any web site where you can use https. This could be your bank, Amazon.com, etc. I used GMail.

Double-click on the lock icon which is probably at the lower-right corner of your browser. In Firefox, you’ll see something like the following dialog:

Figure 1. This dialog shows that the certificate presented by the server is trusted by the browser.

The pertinent take-away here is the wording “…verified by Thawte… a certificate authority you trust…” What the dialog is saying is that the certificate sent from the server (GMail) to my browser was signed (i.e., vouched for) by the Thawte CA. (Note that the name of your CA may be different.)

In other words, I trust the CA and because GMail’s certificate was signed by that CA, I trust the GMail certificate by extension.

Why do I trust the Thawte CA? I trust it because all browsers come pre-loaded with the certificates of common CAs. If they didn’t, you’d constantly get prompted about untrusted certificates from the sites you visit.

Before leaving the Firefox dialog, you might want to click on the View button and have a look at the certificate details. Note the Common Name (CN) fields under Issued To and Issued By. Click on the Details tab if you’re bored, but doing so doesn’t count against my fifteen minute limit!

Figure 2 shows graphically what we just talked about:

Figure 2. With one-way SSL, the server passes its certificate and CA chain to the browser. The browser trusts the CA that issued the server certificate.

Essentially, if we trust the server certificate’s issuer, we can establish an SSL link between our browser and the server. This is an example of one-way SSL. One-way SSL simply means that only the server certificate passes from the server to the client but not the other way around. Thus, only the server is authenticated.

When BOTH sides pass certificates to each other to establish an SSL link, we have mutual authentication via two-way SSL. Both sides now know the identity of the other from their respective certificates.

Figure 3 shows what’s required for two-way SSL:

Figure 3. Two-way SSL comprises the one-way scenario along with the browser passing a personal certificate and its CA chain to the server. The server trusts the CA that issued the personal certificate.

The important thing to realize is that, while the conditions for one-way SSL remain, the mirrored conditions also apply. In other words, the server also has to trust the issuer of the client certificate. That’s all there is to it!

Clearly, SSL is all about trust. Mutual authentication just means that both sides must trust each other. All of the PKI documents and all of the server configuration details all boil down to this fundamental idea. Knowing this makes the rest so much easier.

In my very unscientific estimation, trust issues comprise a very large percentage of the problems people have with SSL. When you have problems configuring SSL, the first thing you should do is check the CA certificates.

So much for the “theory.” That wasn’t so bad, was it? Now let’s take the poor man’s approach to configuring mutual authentication with WebLogic. We’ll use the test CA that comes with WebLogic for both the server and client certificates. You wouldn’t do this for a production server but it’s great for keeping this post under ten minutes like the title promises while demonstrating the concepts. Furthermore, the link is still encrypted just the same as using “real” certificates would do.

Here’s an overview of what we’ll do:

  1. Load the BEA test CA cert into your browser
  2. Ensure that SSL is configured on the server
  3. Access a web page to see one-way SSL in action
  4. Create a client certificate
  5. Load the client certificate into your browser
  6. Set the server to require client certificates
  7. Access a web page to see two-way SSL in action

You can see that we’ll get one-way SSL working before moving on to two-way.

Configuring SSL in WebLogic

1) Load the BEA Test CA Certificate into your Browser

Out of the box, WebLogic will present a certificate signed by the BEA test CA. You’ll need to add that CA certificate to your browser so that the server certificate will be trusted. Note that what you want to add to your browser is the certificate of the CA that issued the server certificate — not the server certificate itself. If you don’t add the CA certificate, SSL will still work but you will be prompted by your browser about an untrusted certificate.

To load the test CA certificate into Firefox, follow these steps:

  1. Select Tools/Options/Advanced/View Certificates
  2. Select the Authorities Tab
  3. Import <BEA_HOME>\weblogic81\server\lib\CertGenCA.der
  4. Select “Trust this CA to identify web sites”
  5. Click the OK buttons to close the three windows

The procedure for browsers other than Firefox will be similar.

Your browser will now trust certificates issued from the BEA test CA. Later, when you are done experimenting with the test certificate, be sure to remove the test CA certificate from your browser.

2) Ensure that SSL is Configured on the Server

For one-way SSL to work, your server has to be listening on the SSL port and have a server certificate configured. Depending upon how you built your domain, this may already be done. Let’s check:

In WebLogic Console, navigate to the server node in the applet. (By the way, these steps assume WebLogic 8.1. There’s no applet in 9.x so navigation is a bit different.)

  1. Click once on the server node
  2. On the right side, select the General tab
  3. Ensure that SSL is enabled and note the SSL port. If they are not set, set them now and click Apply.
  4. Select the Keystores & SSL tab
  5. Ensure that you are using the DemoIdentity and DemoTrust keystores. (If not, click Change, select Demo Identity and Demo Trust, and click Apply.)
  6. If you made any changes, restart your server for the changes to take effect

3) Access a Web Page to See One-way SSL in Action

At this point, your server is configured to use a test certificate signed by the BEA test CA. This test CA is trusted by your browser, so SSL should work swimmingly. To try it:

In your browser, navigate to the WebLogic console but use https and the SSL port like this:

https://localhost:7002/console

Change localhost to the machine name if you know it. Localhost will work assuming the server is on the same machine as the browser, but you’ll probably see a warning upon connection. Also, change the SSL port to the correct number if necessary.

When you access the Console application over SSL, you may get prompted about a Hostname Mismatch or a Domain Name Mismatch. Either way, they both represent the same problem. The problem is that the demo certificate is based upon your machine name. If you choose to accept the warning and continue, SSL will work and you won’t get prompted again during that browser session. If you don’t want to get the warning, use the machine name instead of “localhost.”

If you go beyond the Console login page, the applet will complain about an untrusted certificate and a hostname mismatch. These warnings can be ignored for our purposes here. If you want to fix them, see the previous paragraph for handling the hostname mismatch problem. To fix the trust issue, load the BEA CA certificate into the trust store in your JRE.

If you see the WebLogic Console, one-way SSL is working. What’s better than one-way SSL? Two-way, of course. Let’s get that working…

4) Create a Client Certificate

We’ll use a utility provided by BEA called CertGen to create a client certificate. This certificate will be issued by the BEA demo CA just like the demo server was. When presented to the server as a client certificate, WLS will trust it since we already configured it to trust the demo CA. To reiterate, the server trusts the client certificate issuer, not the client certificate directly.

Run SetEnv.cmd in your domain directory, change to a working directory of your choice, and then use CertGen to create a client certificate as follows:

java utils.CertGen -certfile certfile.cer -keyfile keyfile.key -keyfilepass password -cn Spongebob

See http://edocs.beasys.com/wls/docs81/secmanage/ssl.html#1192198 for more information on CertGen.

Prior to WLS 8.1.4, CertGen had a different set of parameters, so check the documentation if you are not using 8.1.4 or later. The CertGen command creates both the private key and certificate for the user specified as the CN (Common Name). The files are created in DER and PEM formats. Most browsers don’t recognize PEM/DER files so we need to convert the key and certificate into a PKCS #12 file. On the Windows command line, type the following:

java utils.ImportPrivateKey -keystore Spongebob.p12 -storepass password -storetype pkcs12 -keypass password -alias personal -certfile certfile.cer.pem -keyfile keyfile.key.pem -keyfilepass password

See http://edocs.beasys.com/wls/docs81/admin_ref/utils20.html#1184336 from more information on ImportPrivateKey.

5) Load the Client Certificate into your Browser

The procedure for loading client certificates is almost the same as loading the CA certificate:

  1. Select Tools/Options/Advanced/Security
  2. Select “Ask me every time” to see the client certificate request
  3. Click View Certificates
  4. Select the Your Certificates tab
  5. Import Spongebob.p12 (the password is “password”)

Click the OK buttons to close the three windows.

6) Set the server to require client certificates

For two-way SSL to work, your server has to be set to request client certificates.

In WebLogic Console, navigate to the server node in the applet.

  1. Click once on the server node
  2. On the right side, select the Keystores & SSL tab
  3. Click on the Advanced Options Show link at the bottom of the page
  4. Set Two Way Client Cert Behavior to “Client Certs Requested and Enforced” and click Apply
  5. Restart your server for the changes to take effect

7) Access a web page to see two-way SSL in action

Drum roll, please. Feel the tension? Will it work?

We know that the server certificate is trusted by the browser since the browser trusts the test CA. The server is configured to trust the test CA, too, since it’s using the DemoTrust keystore. If the browser has a client certificate issued from the test CA, all should be well. Let’s try it:

Navigate to the WebLogic Console over SSL as before (https://localhost:7002/console). You should get prompted to supply a client certificate. See Figure 4.

Figure 4. The long-awaited prompt for a client certificate!

The certificate is already selected so just click OK to use it. You should then see the Console login page. You’ll only need to supply a certificate once per browser session.

If you try to access the Console beyond the login page, you’ll get a handful of prompts from the applet. It’s OK — just click through them. You get all of these prompts from the applet because the applet doesn’t have access to the client certificate like your browser does. As a result, it has no certificate to give. The applet will fail to come up but if you see the Console login page then all is well. You wouldn’t have this massive prompting problem with a normal web application.

Congratulations! You now have mutually authenticated SSL working. You can take the rest of the day off. ;-)

Epilogue

At this point, you have a working mutual authentication demonstration. You should also have a good understanding that trust is the key to getting SSL working properly. The client must trust the server and vice versa.

It’s also important to understand what you don’t have if you’ve followed these steps:

  • Production-quality certificates
  • A means of using the client certificate for authentication to your applications

To switch to production-quality certificates you’ll have to use one of the commercial CAs such as Verisign. You might also have a corporate CA that will serve the same purpose. Even a self-signed certificate is OK if the client chooses to trust it, but they’re likely to have to know you before doing so. Do not use the certificates generated by the BEA CA for anything other than demo purposes. An unsecure CA is worse than no CA at all.

At the moment, the client certificate is only used at the socket level — the server does not care WHO the user is, just that the certificate is trusted. You’ll need to use identity assertion to have the certificate information used for application authentication purposes.

Both of these topics will be covered in subsequent posts. Until then, you can learn more about identity assertion by reading The Mysterious CLIENT-CERT. It’s only a high-level overview, though. You can also check the BEA documentation for detailed information.

Post a comment and let me know how the steps in this post worked for you (or not).

Reversed DN in WebLogic 8.1.4

If you do mutual authentication via two-way SSL, you sometimes need to pull the certificate from the request. For web applications, it’s a simple matter of grabbing the "javax.servlet.request.X509Certificate" attribute. Here’s an extract from the sample SnoopServlet.jsp that comes with WebLogic:

  try {
    // request is HttpServletRequest
    java.security.cert.X509Certificate certs [] = 
       (java.security.cert.X509Certificate [])
	request.getAttribute(
           "javax.servlet.request.X509Certificate");

    if ((certs != null) && (certs.length > 0)) {
      out.println("DN: "+
        certs[0].getSubjectDN().getName()+"<br/>");
    }
    else // certs==null
    {
      out.println("Not using SSL or client "+
        "certificate not required.");
    }
  } catch (ClassCastException cce) {
    cce.printStackTrace();
  }

The full JSP can be found at <WL_HOME>\samples\server\examples\src\examples\jsp. I cleaned it up a bit for simpler presentation here.

Notice that the code pulls the certificate and then prints the Distinguished Name (DN) from the certificate. The DN might look like "cn=Mike Fleming, ou=Engineering,o=XYZ Corp.,c=US" and it identifies the user associated with the certificate. The user certificate is always the first (i.e., zero) element in the certificates array while the issuer certificates in the certificate chain occupy subsequent slot(s). The issuers, by the way, are the Certificate Authorities that signed the certificates in the chain.

Everything I’ve said thus far is the set up for the big punchline: The order in which the DN is presented is flipped in WebLogic 8.1.4+.

As a result, running SnoopServlet in 8.1.4 would print "c=US,o=XYZ Corp.,ou=Engineering,cn=Mike Fleming" using my example above.

Now, this order reversal is not "wrong" since either order is perfectly valid. What’s upsetting is that the first example is what WebLogic has been returning since at least 7.x and now it’s suddenly a different representation.

This situation might not be a big deal depending upon what you are doing with the DN. However, if you are parsing it or passing it along for further processing, you’ll likely have to take action.

If you control the source code, the fix is easy. Instead of using

certs[0].getSubjectDN().getName()

as SnoopServlet does, use

certs[0].getSubjectX500Principal().getName()

instead. In fact, the second approach is now the preferred way of retrieving the DN as I’ll discuss momentarily.

I’ve presented the problem and provided a solution. But what bugs me is why did it change? Information on this is very hard to come by. Here’s what I uncovered along with some speculation:

Obviously, the change occurred in WebLogic 8.1.4. Versions prior to that presented the DN in the "normal" order. What happened in 8.1.4 is that the Certicom classes changed.

Certicom is the provider of much of the crypto functionality in WebLogic. In this case, the class in question is com.certicom.security.cert.internal.X509.X509V3CertImpl. This class implements the java.security.cert.X509Certificate interface and is the object you actually deal with in the example code above.

Interestingly, JDK 1.5.0 deprecates the getSubjectDN() method but WebLogic 8.1.4 uses JDK 1.4.2_05. My guess is that Certicom knew about the impending deprecation and decided to flip the order just to tick me off. ;-) Deprecation is one thing but why would they totally change the output like they did?

If you have any information on this, please leave a comment. It’s not a burning issue, of course, but I’m quite curious…

Steve’s PKI Overview

Steve Nakhla posted a good PKI overview with the promise of more detail to come. He also introduced his open source CA server effort, Odyssi CA.

The Mysterious CLIENT-CERT

Note: This post is about one of the web application authentication methods defined by the Java Servlet specification. You can also read a larger article I wrote describing how all of the authentication methods work. It includes the content below.


The CLIENT-CERT authentication method is typically used for identifying the user by his X.509 certificate. This is a special usage of the Secure Sockets Layer (SSL) where both the server and the user are identified by their own certificates. As a result, this is a very strong form of authentication. But there’s more to CLIENT-CERT than you might realize.

Encounters with SSL on the Internet are almost always one-way authentication where only the server presents a certificate to the client. Think of ordering on Amazon.com, for example. The protocol switches from http to https and the little lock symbol appears on your browser. This signifies that the link is encrypted and the certificate presented by Amazon was trusted since your browser trusts certificates issued by Verisign. (Note: Public Key Infrastructure (PKI) and trust are beyond the scope of this post.) Even so, the server still does not know the identity of the user. User authentication is left to another mechanism such as username/password.

On the other hand, two-way SSL retains the server authentication part but adds the requirement that the client present a certificate that the server trusts. Assuming that both sides agree, the encrypted link is established and both sides are mutually authenticated via SSL. This technique is rare on the Internet since most people don’t have personal certificates. However, there is traction for two-way SSL on corporate intranets and in B2B scenarios. Once a mutually authenticated link is established, there is no further need for user authentication since the certificate already positively identified the user.

Whew, that was a lot of backstory on two-way SSL. Hopefully, though, the purpose of the CLIENT-CERT auth-method is now clear. Using CLIENT-CERT typically means that your application wants to authenticate the user via two-way SSL.

But wait, there’s more… ;-)

Configuring CLIENT-CERT

Specifying CLIENT-CERT in web.xml is not sufficient for actually having the whole process work. I won’t go into details, but here are the additional configuration items you need to handle:

  • Ensure that your server has a server certificate
  • Ensure that your users trust your server certificate’s issuer (the CA certificate)
  • Ensure that your server is listening on the SSL port
  • Ensure that your users have certificates
  • Ensure that your server trusts the user certificate issuer(s)
  • (WebLogic) Ensure that an Identity Asserter handles the X.509 certificate and can map the certificate to a known user

The list above describes in general terms what must be configured for mutual authentication to work. In WebLogic, it’s an identity assertion security provider that handles X.509 certificates. The identity asserter isn’t triggered unless CLIENT-CERT is present in web.xml.

Once all of this is properly configured, the stars line up just so, and you dispose of the hair you pulled out trying to get it right, you’re ready to enjoy authentication via two-way SSL.

But wait, there’s more…

Perimeter Authentication Tokens

According to the servlet specification, the intention of the CLIENT-CERT authentication method was for two-way SSL as described above. However, BEA WebLogic not only supports the spec but expands upon it to support any type of perimeter authentication token.

With WebLogic, X.509 certificates, SAML tokens, Kerberos tickets, etc., are all examples of perimeter authentication tokens. Of the examples presented, the WebLogic 8.1 security framework only supports X.509 out of the box, but you can write or buy custom identity asserters that handle any type of token that can be presented as string. That’s pretty nifty.

And it all starts with the mysterious CLIENT-CERT authentication method.

WebLogic with a PKCS12 Keystore

I recently tried to get WebLogic 8.1.3 Server to use a PKCS12 keystore which contained the server’s identity certificate. A PKCS12 file contains the private key and public certificate. For all practical purposes, it’s just like a JKS keystore as long as you specify the PKCS12 store type when using the WebLogic Console Keystore Configuration screen or keytool. The convention for PKCS12 filename extensions is either .p12 or .pfx.

Normally, I use JKS keystores so I was in uncharted territory although all signs indicated that it should work. As my luck would have it, it didn’t work. Upon server startup I received a “Set tag error” which prevented the SSL port from working.

As it turns out, the problem was caused by a bug in JDK 1.4.2_04. Supposedly, it was fixed in 1.4.2_05 which comes with WLS 8.1.4. However, I used JDK 1.4.2_06 and it worked just fine.

 

Bookmark this page on del.icio.us