lundi, 2 août 2010

Dealing with old SSL certificats (algorithm check failed: MD2withRSA is disabled)

I faced the problem of SSLHandShakeException, or "algorithm check failed: MD2withRSA is disabled" when upgrading above java 1.6.0_17.

The source of problem is well known, the MD2withRSA has been removed from the JVM because it is no more secure (References : CVE-2009-2409 deprecate MD2 in SSL cert validation, Sun java 1.6u17 release notes).

Lot of posts around the problem give the solution to update the certificate to use another signature algorithm, for example SHA1withRSA.

But what to do when the certificate is not under our hands ?

I will try to explain the problem and how I solved it.

Analyse the certificate's chain

First of all is to analyse the chain of certificate to see which one is using the deprecated algorithm.

The following command will print all the certificates in the chain and store them under the names level0.pem, level1.pem and so on.

openssl s_client -showcerts -connect xxxxxxxxxxxxxxxxx:443 < /dev/null | awk -v c=-1 '/-----BEGIN CERTIFICATE-----/{inc=1;c++} inc {print > ("level" c ".pem")}/---END CERTIFICATE-----/{inc=0}'; for i in level?.pem; do openssl x509 -noout -serial -subject -issuer -in "$i"; done
The output given in my case :
  • Cert1, serial=xxxxxxxxxxxxxxxxxxxxx
    subject= xxxxxxxxxxxxxxxxxxxxxxxx
    issuer= /C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
  • Cert2, serial=30000002
    subject= /C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
    issuer= /C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
  • Cert3, serial=70BAE41D10D92934B638CA7B03CCBABF
    subject= /C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
    issuer= /C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
We have a chain of 3 certificates, Cert1 signed by Cert2 signed by Cert3 signed by Cert3 (selfsigned).

Note : the first error here is that the webserver gives explicitly the entire chain of certificates. Thus the change of any root certificate provided by the jre will not be taken into account automatically.

Digging into the details of the third certificate (openssl x509 -in level2.pem -text) show that this certificate is signed with the old algorithm :
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
70:ba:e4:1d:10:d9:29:34:b6:38:ca:7b:03:cc:ba:bf
Signature Algorithm: md2WithRSAEncryption
Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
Validity
Not Before: Jan 29 00:00:00 1996 GMT
Not After : Aug 1 23:59:59 2028 GMT
So not only the way of providing the full chain is wrong in point of view of the server configuration, but the last certificate was never updated and thus still use the deprecated signature algorigthm.

Correct the certificate's chain

Now we have identified the weak link of the chain, what's the next step ?

The key point here is to correct the certificate's chain in order to make it more compliant to the standards :

As the third certificate is a well know root certificate (named verisignclass3ca in the jre cacerts), a working version of it is provided in the cacert of java sun (debian oriented commande line, with "changeit" as password) :
keytool -keystore /etc/java-6-sun/security/cacerts -exportcert -alias "verisignclass3ca" | openssl x509 -inform der -text
will output :
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
3c:91:31:cb:1f:f6:d0:1b:0e:9a:b8:d0:44:bf:12:be
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
Validity
Not Before: Jan 29 00:00:00 1996 GMT
Not After : Aug 2 23:59:59 2028 GMT
which is the same as before, but with sha1WithRSAEncryption instead of md2WithRSAEncryption.

Thus we can simply remove it from the chain and validate normally the certificate's chain with only Cert1 and Cert2. The validation from Cert2 to "verisignclass3ca" will be automagically done.

To be able to do this, we need to create a custom X509TrustManager which will behave the following :
  1. if the certificate's serial is the one of Cert1 (serial=xxxxxxxxxxxxxxxxxxxxx), then remove the last certificate of the chain and revalidate.
  2. In all other cases, validate normally the certificate's chain.
Thus the custom X509TrustManager act as delegate, with one little variation (note : if someone know a easier way to instantiate the default TrustManagerFactory of TrustManager, please leave me a comment). Refer to my article about blind SSL factory for httpClients to see how to pass a custom TrustManager to a SSL factory.

My custom X509TrustManager class will finally look as the follow :
public class CustomX509TrustManager implements X509TrustManager {

X509TrustManager delegate = null;

public CustomX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException, CertificateException {
// Instantiate the default X509TrustManager
TrustManagerFactory factory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
factory.init(keystore);
TrustManager[] trustManagers = factory.getTrustManagers();
if (trustManagers != null && trustManagers.length > 0) {
for (int i = 0; i < trustManagers.length; i++) {
TrustManager trustManager = factory.getTrustManagers()[i];
if (trustManager instanceof X509TrustManager) {
delegate = (X509TrustManager) trustManager;
break;
}
}
}
if (delegate == null) {
throw new CertificateException("Cannot found any instance of X509TrustManager");
}
}

public X509Certificate[] getAcceptedIssuers() {
if (delegate == null) {
return null;
} else {
return delegate.getAcceptedIssuers();
}
}

public void checkClientTrusted(final X509Certificate[] c, final String a)
throws CertificateException {
if (delegate != null) {
delegate.checkClientTrusted(c, a);
} else {
throw new CertificateException("Unable to validate this certificate (delegate is null).");
}
}

public void checkServerTrusted(final X509Certificate[] c, final String a)
throws CertificateException {
if (delegate != null) {
// hardcoding test to be sure we are trying to validate the right certificate
if (c.length == 3 && c[0].getSerialNumber().toString(16).equals(BADCERTIFICATESIGNATURE)) {
c = new X509Certificate[] {c[0], c[1]};
}
delegate.checkServerTrusted(c, a);
} else {
throw new CertificateException("Unable to validate this certificate (delegate is null).");
}
}

private static final String BADCERTIFICATESIGNATURE = "xxxxxxxxxxxxxxxxxxxx";
}
With this implementation I'm sure that the my invalid certificate will continue to work, and it cannot be faked (as it could be with a completely blind TrustManager). But I can also expect that when the provider will update its certificate, everything will continue to work as expected because all other certificates are still validate the regular way.

7 commentaires:

Anonyme a dit…

I would like to exchange links with your site benoitperroud.blogspot.com
Is this possible?

KillerWhile a dit…

It could. But you need to tell me who you are :)

Frimkron a dit…

Thank you for such an insightful post - this has really helped me out.

I have a (probably stupid) question: your custom TrustManager takes a Keystore parameter - how should I obtain the correct Keystore in order to construct the TrustManager when calling SSLContext.init ?

KillerWhile a dit…

Oh yes that's a good point I forget to mention : simply passing null for the Keystore will made SSLContext to use the defaul Keystore (cacert)

Anonyme a dit…

Good dispatch and this enter helped me alot in my college assignement. Thank you for your information.

Jamie a dit…

Thanks heaps for the post, Benoit. It answered 'most' of my questions so now I understand why it's not working for me and why i'm getting the "MD2withRSA is disabled" error. Only problem I have now is that I'm seeing this when using SOAP and I have no idea how to get it to use a custom TrustManager.

Good news is that my service provider's certificate expires end of next month so does that mean it will probably just start working when they get a new certificate?

KillerWhile a dit…

@Jamie : yes if your provider is not too stupid he will renew the certificate and change the signature algorithm.