Friday, March 15. 2013
BUG in Java OCSP Implementation (PKIX)?
Today's entry is going to explain an issue I had some weeks ago with an OCSP (Online Certificate Status Protocol) responder. I had to use a specific responder which worked in such a way that I was not able to make Java work against it. I did not understand what was happening and I decided to test the problem more deeply in this entry.
Let's start explaining how an OCSP responder works: the server receives requests from clients which want to check if a specific certificate is revoked; the responder checks the status of the certificate and answers if it is good, revoked or unknown; cos the protocol is affected by man in the middle attacks the response is signed; usually the response is signed by the same CA that issued the certificate being checked. And here my issue appears, my specific OCSP server checked certificates issued by different CAs, and to do that it followed two rules:
If the certificate to be checked is issued by one of their CAs, the response was signed by the same CA (common situation).
But if the certificate was issued by a partner authority it was signed by a special certificate issued by themselves.
I could not configure Java to work against that OCSP responder. So I decided to read what RFC2560 (the standard that defines OCSP protocol) says, and this is the important part:
All definitive response messages SHALL be digitally signed. The key used to sign the response MUST belong to one of the following:
- the CA who issued the certificate in question
- a Trusted Responder whose public key is trusted by the requester
- a CA Designated Responder (Authorized Responder) who holds a specially marked certificate issued directly by the CA, indicating that the responder may issue OCSP responses for that CA
There are three possibilities or cases, which I will try to explain a bit more:
Case 1: The certificate used to sign the response is the same certificate which issued the certificate being checked. This is the most common situation.
Case 2: The response is signed by a trusted certificate for the client. That means the requester should know before that certificate and configure it someway to trust in the OCSP responses.
Case 3: The response is signed by a certificate which has a special extension (OCSPSigning) to inform that it is able to sign OCSP responses, besides it must be issued by the same CA which issued the certificate being checked. This case is used to delegate OCSP responders to a partner CA.
If you recheck what my responder was doing, it was using case 1 for its own certificates (common case) and case 2 for foreign certificates (the one in which the client should know the certificate used in order to trust in the responses). Therefore I understand that the OCSP responder was working well, its behavior is covered by the standard.
OCSP in Java is part of its Public-Key Infrastructure (PKIX) implementation. It has several properties defined in the Security configuration for controlling the checking process. Let's summarize the most important:
ocsp.enable. Setting this property to true the OCSP checking is used for validating certificates (CRL is always enabled but not OCSP).
ocsp.responderURL. Property that fixes the use of an OCSP server instead the one provided by the certificate extended properties.
ocsp.responderCertSubjectName. Property to fix the certificate used by the responder to sing responses (there are other properties for doing the same thing). This option is used for the case 2 defined in the standard.
At this point I decided to create an openssl ocsp environment which tested the three possibilities, for that I need a client certificate and three certificates for the OCSP responder (I followed similar commands to the ones I used in this previous entry):
cakey.pem/cacert.pem: RSA key and certificate of the demo CA (self-signed). If it is used in the OCSP responder the case 1 is tested (password Kiosko_00 to be used).
ocsp-trusted.key/ocsp-trusted.pem: The certificate is signed by the CA but with no special OCSPSigning extension. The pair will be used as the responder certificate for the case 2, the client should be configured to trust in that certificate.
ocsp-signing.key/ocsp-signing.pem: The pair used for case 3, the certificate is signed by the CA and has the OCSPSigning extension (see this blog entry to see how I did it). Cos it is signed by the same CA it works without any configuration at client side.
client1.pem: The certificate to be checked (signed by the same demo CA).
This zip file contains the demoCA directory with all the openssl configuration to launch the ocsp responder. I developed a simple Java to test PKIX validation in the three scenarios. The program sets to true ocsp.enable and uses the fourth and fifth argument to set the ocsp.responderURL and the ocsp.responderCertSubjectName (this argument is optional). Previous arguments are the client certificate to check, the cacerts keystore to use and the password of the keystore. I tested the three possible scenarios:
Case 1: The program checks client1 certificate against openssl OCSP launched with demo CA as the response signer.
$ openssl ocsp -index demoCA/index.txt -CA demoCA/cert/cacert.pem \ -rsigner demoCA/cert/cacert.pem -rkey demoCA/private/cakey.pem -port 3456 -text
In order to work in this case the Java program should be launched with no certificate fixed as the responder one:
$ java -cp . CertificateChecker client1.pem cacerts.demo changeme http://localhost:3456 Loading certificate... Loading cacerts... Performing PKIX validation... Result: OK
The demo CA should be imported in the cacerts.demo keystore (our demo CA must be trusted for the Java program).
Case 2: Now the ocsp server is started using the ocsp-trusted key and certificate, remember this one has no special extension.
$ openssl ocsp -index demoCA/index.txt -CA demoCA/cert/cacert.pem \ -rsigner demoCA/cert/ocsp-trusted.pem -rkey demoCA/private/ocsp-trusted.key \ -port 3456 -text
In the second scenario Java should be configured in such a way that the client knows the certificate used in the responses. The ocsp-trusted certificate was added to the keystore and the fifth argument was passed to mark that this specified subject is used to sign the responses.
$ java -cp . CertificateChecker client1.pem cacerts.demo changeme \ http://localhost:3456 "CN=OCSP-TRUSTED, O=demo.kvm, ST=demo, C=ES" Loading certificate... Loading cacerts... Performing PKIX validation... Result: OK
So it works, if the subject of the responder certificate is specified the Java program trusts in its responses.
Case 3: The ocsp server is launched using the ocsp-signing key and certificate, that certificate is signed with the OCSPSigning extension.
$ openssl ocsp -index demoCA/index.txt -CA demoCA/cert/cacert.pem \ -rsigner demoCA/cert/ocsp-signing.pem -rkey demoCA/private/ocsp-signing.key \ -port 3456 -text
Now in order to work the client program it should be started again but without any specific certificate set as an argument.
$ java -cp . CertificateChecker client1.pem cacerts.demo changeme http://localhost:3456 Loading certificate... Loading cacerts... Performing PKIX validation... Result: OK
It works again. Java PKIX implementation checks that the certificate used has the OCSPSigning extension and the issuer of that certificate is the same that the issuer of client1 certificate.
I know, now you are saying but what the hell, Java supports all the possibilities. Where is the problem? A different configuration is needed to accomplish the three cases. Case 2 only works if the property ocsp.responderCertSubjectName is set (or any other option that fixes the certificate used for signing responses) but case 1 and 3 only works with that property not set. There is no way to configure the Java Security in a way that handles the three scenarios at the same time. What happens if there is an OCSP server whose responses use mixed cases? You are in a big problem if case 2 is involved. If you remember the responder I commented in the introduction, it works using cases 1 and 2, I would have never got it working no matter the time I had spent. It was absolutely impossible.
First question, does the standard support that an OCSP responder uses all the scenarios at the same time? Or does it force the responder to not mix case 2 with the other two? I have not read anything against the first sentence and Java supports cases 1 and 3 at the same time, so why not case 2. Second question, is mixing case 2 with any of the other cases useful for a responder? I think it is, it is quite reasonable that the responder uses case 1 (the common method) for their own certificates and case 2 for certificates issued by other authorities (case 3 is also valid but you need the partner authority to sign a certificate for you). Remember that case 1 does not need client configuration and case 2 does need it, so it is logical to minimize the use of case 2. I think this is a BUG in the Java implementation and I will try to report it. If any of you is an expert in OCSP please let me know your thoughts.
As you know I try to be a good neighbor and I checked why Java worked that way. Looking the code for openjdk6 b27 (last bundle released for version 6 at the moment) I checked that class sun.security.provider.certpath.OCSPChecker chooses the responder certificate from the properties defined in the Security configuration or from the issuer of the certificate that is going to be validated. It is one or the other. Then the class sun.security.provider.certpath.OCSPResponse receives that certificate as a parameter and validates the sign. OCSPResponse covers cases 1 and 3, the certificate used for signing can be the one passed as an argument or another certificate which has the OCSPSigning extension and was issued by the argument cert. That is the reason Java/PKIX isolates case 2 from the other two scenarios.
Because I spent so much time guessing all of this I decided to make a patch. The fix involves the two classes commented in the previous paragraph and another one which is an intermediary class between them (OCSP class in the same package). In my solution OCSPResponse class receives two arguments: the issuer cert (the certificate which issues the client certificate to be checked) and the responder cert (the certificate defined in the Security properties, it can be null). With both certs the class can check the three cases at the same OCSP response, including the reviled scenario 2. Here it is my patch. With the new classes no matter how the openssl ocsp responder is started (any case) the same configuration for the Java program works:
$ java -Xbootclasspath/p:/home/ricky/Desktop/jdk-patch/src/share/classes/ \ -Djava.security.debug=certpath -cp . CertificateChecker client1.pem cacerts.demo \ changeme http://localhost:3456 "CN=OCSP-TRUSTED, O=demo.kvm, ST=demo, C=ES"
The previous command works for the three certificates (demoCA case 1, ocsp-trusted case 2 and ocsp-signing case 3) cos I am prepending my modified classes to the boot classpath. The same arguments, the same configuration works no matter which case the responder uses. Cos explanation for this issue is so long and complex I posted the entry before reporting the BUG (I will link this entry from the BUG explanation). I opened another BUG before but I had not understood the problem completely and people at Java team did not say anything to me (I was half wrong so it is fair).
Let's see what happens now!
Sunday, March 3. 2013
SPNEGO/Kerberos in JavaEE (spnego.java.net)
Two previous entries of the blog were dedicated to the SPNEGO/Kerberos login. The first one just used the Java Filter SPNEGO project to perform a silent login using windows native authentication. The second one extended the solution adding the group SIDs to the principal using the Microsoft PAC extension. Remember that the solution for the second entry consisted in a horrible JAR which used several other projects (or part of projects) like SPNEGO, BouncyCastle, jaaslounge or Spring to read the SIDs. Finally I had the time to perform a better and cleaner integration.
Instead of the SPNEGO filter that I have used in the two entries I changed to the spnego.java.net project. This project is a standard ServerAuthModule for Glassfish that performs the SPNEGO protocol and injects the principal (it works in a way very similar to the SPNEGO filter). But as it is integrated more deeply in the application server, group principals are much easier to integrate. The last version 1.1 of the library is two years old (it seems that that is a common rule for all the projects related to SPNEGO in Java).
The first thing I did was installing the module in glassfish and modifying the hello application to use it. Following the installation instructions the task was done smoothly. Although the documentation always talks abouts v2 it also runs in v3 with no change (cos ServerAuthModule is a standard interface in JavaEE some kind of integration is assured). At the end, the Kerberos principal was injected in the hello WAR application as in the first entry of this series.
If you check the code of the ExampleSpnegoServerAuthModule, the class overrides the getGroupsForCaller method to inject two hardcoded group names (authenticated and allowed). So in order to add the real group SIDs from PAC that method should be overridden. Besides I decided to not use Bouncy Castle ASN.1 implementation (it is an enormous JAR I wanted to avoid) and restrict to the minimum the jaaslounge classes (do you remember my frankenjar of the previous entry?). So in summary the main points of the new implementation are the following:
The getGroupsForCaller method was changed to receive more arguments (original abstract method only had the Principal as argument). In order to parse the PAC more inputs are needed. So I decided to include everything in the method:
public abstract String[] getGroupsForCaller(MessageInfo messageInfo, GSSContext gssContext) throws AuthException;
Now the method receive the MessageInfo and the GSSContext, which has just validated the Kerberos token. This way any extension class will have all the needed inputs.
A new PACSpnegoServerAuth class was implemented. It obviously extends the SpnegoServerAuthModule and implements the new version of the getGroupsForCaller method.
The ASN.1 parsing uses the internal sun.security.util package. Take into account that as DerInputStream or DerValue classes are internal they can change (for example in a future version or in other JavaSE implementations -IBM for example-). Although this decision is not perfect I prefer it better than the Bouncy Castle option.
Part of the kerberos data should be decrypted to get the PAC and, for that, the server keys are needed. In turn a logged subject is needed to get the keys. So the new module performs a login at initialization (using defined krb5 login module) and a logout at finalize (there is no dispose or similar method in the ServerAuthModule interface so I was forced to add it in the finalize method).
Finally only a few classes (six) of the jaaslounge project were added, only the classes that deals with the PAC, more specifically the login info part (where the groups SIDs are). I renamed the package to net.java.spnego.pac and did some minor changes.
The module keeps on just parsing the token in the first access and storing the group SIDs in the Java session, where they are retrieved from in the following requests.
With all these changes the final library only depends in Sun JavaSE (remember the sun.security.util utilization) and commons-codec. And the beautiful result of all this work is that now the SIDs can be mapped in web.xml and glassfish-web.xml configuration files. For example the new hello application web.xml defines some groups as roles:
<security-role> <role-name>Domain Users</role-name> </security-role> <security-role> <role-name>Domain Admins</role-name> </security-role> <security-role> <role-name>group1</role-name> </security-role> <security-role> <role-name>group2</role-name> </security-role> <security-role> <role-name>group3</role-name> </security-role>
And only users in the domain can access the application:
<security-constraint> <web-resource-collection> <web-resource-name>Authentication Required</web-resource-name> <url-pattern>/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>Domain Users</role-name> </auth-constraint> </security-constraint>
But the new solution lets us map the SIDs in the glassfish-web.xml (proprietary) file:
<security-role-mapping> <role-name>Domain Users</role-name> <group-name>S-1-5-21-2145774160-2038213957-1596523949-513</group-name> </security-role-mapping> <security-role-mapping> <role-name>Domain Admins</role-name> <group-name>S-1-5-21-2145774160-2038213957-1596523949-512</group-name> </security-role-mapping> <security-role-mapping> <role-name>group1</role-name> <group-name>S-1-5-21-2145774160-2038213957-1596523949-1106</group-name> </security-role-mapping> <security-role-mapping> <role-name>group2</role-name> <group-name>S-1-5-21-2145774160-2038213957-1596523949-1107</group-name> </security-role-mapping> <security-role-mapping> <role-name>group3</role-name> <group-name>S-1-5-21-2145774160-2038213957-1596523949-1108</group-name> </security-role-mapping>
The group (or user) SIDs can be obtained using WMIC command in the Windows machine:
> wmic group where "name='Domain Users'" Caption Description Domain InstallDate LocalAccount Name SID SIDType Status KVM\Domain Users All domain users KVM FALSE Domain Users S-1-5-21-2145774160-2038213957-1596523949-513 2 OK > wmic group where "name='group1'" Caption Description Domain InstallDate LocalAccount Name SID SIDType Status KVM\group1 KVM FALSE group1 S-1-5-21-2145774160-2038213957-1596523949-1106 2 OK > wmic useraccount where "name='ricky'" AccountType Caption Description Disabled Domain FullName InstallDate LocalAccount Lockout Name PasswordChangeable PasswordExpires PasswordRequired SID SIDType Status 512 KVM\ricky FALSE KVM FALSE FALSE ricky TRUE TRUE TRUE S-1-5-21-2145774160-2038213957-1596523949-1104 1 OK
And that's all, now the video presents how the user ricky accesses to the new hello application and standard isUserInRole is used (the beautiful mapped name is passed as argument, not the horrible SID).
Today's entry improves the spenego.java.net project to add the SIDs as user groups inside a new ServerAuthModule. The new module is the PACSpnegoServerAuth class. It requires a little modification in the definition of the abstract method getGroupsForCaller (more arguments). Besides I tried to minimize the dependencies of previous solutions and now it only depends on commons-codec (original version 1.1 already depended on that and I did not want to change it). ASN.1 parsing is performed with the internal sun.security.util JavaSE package (take care when other JavaSE implementations is used). Six classes from jaaslounge decoding were integrated in the JAR with minimal changes. The hello-spnego-java-net sample application project and the spnego-java-net new library project can be downloaded from the corresponding links. The whole implementation was not very tested but, with no doubt, it is much better than the one presented in the previous entry. What do you think? Do I have to email to the dev@spnego.java.net list explaining my changes?
Comments below!
Comments