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!
Thanks for the cool writeup!
Did you get any respone concerning the JAVA bug report?
Regards, Simon
I wrote another entry about this issue and, at least, another one is coming. I'm trying to push the bug forward and I'll try to keep on commenting about it. Stay tuned.
Comments