This is a continuation in a series of posts about how to correctly configure a TLS client using JSSE, using The Most Dangerous Code in the World as a guide. This post is about X.509 certificates in TLS, and has some videos to show both what the vulnerabilities are, and how to fix them. I highly recommend the videos, as they do an excellent job of describing problems that TLS faces in general.
Table of Contents
Part One: we talk about how to correctly use and verify X.509 certificates.
- What X.509 Certificates Do
- Understanding Chain of Trust
- Understanding Certificate Signature Forgery
- Understanding Signature Public Key Cracking
Part Two: We discuss how to check X.509 certificates.
- Validating a X.509 Certificate in JSSE
- Validating Key Size and Signature Algorithm
What X.509 Certificates Do
The previous post talked about using secure ciphers and algorithms. This alone is enough to set up a secure connection, but there's no guarantee that you are talking to the server that you think you are talking to.
Without some means to verify the identity of a remote server, an attacker could still present itself as the remote server and then forward the secure connection onto the remote server. This is the problem that Netscape had.
As it turned out, another organization had come up with a solution. The ITU-T had some directory services that needed authentication, and set a system of public key certificates in a format called X.509 in a binary encoding known as ASN.1 DER. That entire system was copied wholesale for use in SSL, and X.509 certificates became the way to verify the identity of a server.
The best way to think about public key certificates is as a passport system. Certificates are used to establish information about the bearer of that information in a way that is difficult to forge. This is why certificate verification is so important: accepting any certificate means that an attacker's certificate will be blindly accepted.
X.509 certificates contain a public key (typically RSA based), and a digest algorithm (typically in the SHA-2 family, i.e. SHA512) which provides a cryptographic hash. Together these are known as the signature algorithm (i.e. "RSAWithSHA512"). One certificate can sign another by taking all the DER encoded bits of a new certificate (basically everything except "SignatureAlgorithm") and passing it through the digest algorithm to create a cryptographic hash. That hash is then signed by the private key of the organization owning the issuing certificate, and the result is stuck onto the end of the new certificate in a new "SignatureValue" field. Because the issuer's public key is available, and the hash could have only been generated by the certificate that was given as input, we can treat it as "signed" by the issuer.
So far, so good. Unfortunately, X.509 certificates are complex. Very few people understand (or agree on) the various fields that can be involved in X.509 certificates, and even fewer understand ASN.1 DER, the binary format that X.509 is encoded in (which has led to some interesting attacks on the format). So much of the original X.509 specification was vague that PKIX was created to nail down some of the extensions. Currently, these seem to be the important ones:
- The "basic fields" that every certificate has.
subjectAltName, where 'dNSName' is the official hostname of the server.
basicConstraintsused to establish chain of trust.
keyUsage. Used to define a CA certificate.
There are other fields in X.509, but in practice, X.509 compatibility is so broken that few of them matter. For example,
nameConstraints is considered near useless and
policyConstraints has been misunderstood and exploited.
So if you want to do the minimum amount of work, all you need is some approximation to a DN, maybe a
basicConstraints, and if you're feeling really enthusiastic,
keyUsage(although this is often ignored by implementations, see the part 2a slides for examples. Even
basicConstraints, the single most fundamental extension in a certificate, and in most cases just a single boolean value, was widely ignored until not too long ago).
Peter Gutmann is an excellent resource on X.509 certificates (although he does have a tendency to rant). Read the X.509 Style Guide, check out the X.509 bits of Godzilla Crypto Tutorial, and buy Engineering Security when it comes out of draft – it has over 500 pages of exhaustively detailed security fails.
Understanding Chain of Trust
In TLS, the server not only sends its own certificate (known as an "end entity certificate" or EE), but also a chain of certificates that lead up to (but not including) a root CA certificate issued by a certificate authority (CA for short). Each of these certificates is signed by the one above them so that they are known to be authentic. Certificate validation in TLS goes through a specific algorithm to validate each individual certificate, then match signatures with each one in the chain to establish a chain of trust.
Bad things can happen if the chain of trust only checks the signature and does not also check the
keyUsage and the
basicConstraints fields in X.509. Moxie Marlinspike has an excellent presentation at DEFCON 17 on defeating TLS, starting off with subverting the chain of trust:
Understanding Certificate Signature Forgery
Certificates should be signed with an algorithm from the SHA-2 library (i.e. at least SHA-256), to avoid forgery. This is important, because it prevents signature forgery.
Certificates are needed because they can say "this certificate is good because it has been signed by someone I trust." If you can forge a signature, then you can represent yourself as a certificate authority. In MD5 Considered harmful today, a team showed that they were able to forge an MD5 certificate in this manner:
Since the original paper, an MD5 based attack like this has been seen in the wild. A virus called Flame forged a signature (jumping through a series of extremely difficult technical hurdles), and used it to hijack the Windows Update mechanism used by Microsoft to patch machines, completely compromising almost 200 servers.
MD2 was broken in this paper, and is no longer considered a secure hash algorithm. MD4 is considered historic. As shown in the paper and video, MD5 is out, and the current advice is to avoid using the MD5 algorithm in any capacity. Mozilla is even more explicit about not using MD5 as a hash algorithm for intermediate and end entity certificates.
SHA1 has not been completely broken yet, but it is starting to look very weak. The current advice is to stop using SHA-1 as soon as practical and it has been deprecated by Microsoft. Using SHA-1 is still allowed by NIST on existing certificates though.
Federal agencies may use SHA-1 for the following applications: verifying old digital signatures and time stamps, generating and verifying hash-based message authentication codes (HMACs), key derivation functions (KDFs), and random bit/number generation. Further guidance on the use of SHA-1 is provided in SP 800-131A.
NIST's Policy on hash functions, September 28, 2012
Even the JSSE documentation itself says that SHA-2 is required, although it leaves this as an exercise for the reader:
"The strict profile suggest all certificates should be signed with SHA-2 or stronger hash functions. In JSSE, the processes to choose a certificate for the remote peer and validate the certificate received from remote peer are controlled by KeyManager/X509KeyManager and TrustManager/X509TrustManager. By default, the SunJSSE provider does not set any limit on the certificate's hash functions. Considering the above strict profile, the coder should customize the KeyManager and TrustManager, and limit that only those certificate signed with SHA-2 or stronger hash functions are available or trusted."
So, SHA-2 library. And indeed, most public certificates (over 95%) are signed this way.
Understanding Signature Public Key Cracking
An X.509 certificate has an embedded public key, almost universally RSA. RSA has a modulus component (also known as key size or key length), which is intended to be difficult to factor out. Some of these public keys were created at a time when computers were smaller and weaker than they are now. Simply put, their key size is now far too small. Those public keys may still be valid, but the security they provide isn't adequate against today's technology.
The Mozilla Wiki brings the point home in three paragraphs:
The other concern that needs to be addressed is that of RSA1024 being too small a modulus to be robust against faster computers. Unlike a signature algorithm, where only intermediate and end-entity certificates are impacted, fast math means we have to disable or remove all instances of 1024-bit moduli, including the root certificates.
The NIST recommendation is to discontinue 1024-bit RSA certificates by December 31, 2010. Therefore, CAs have been advised that they should not sign any more certificates under their 1024-bit roots by the end of this year.
The date for disabling/removing 1024-bit root certificates will be dependent on the state of the art in public key cryptography, but under no circumstances should any party expect continued support for this modulus size past December 31, 2013. As mentioned above, this date could get moved up substantially if new attacks are discovered. We recommend all parties involved in secure transactions on the web move away from 1024-bit moduli as soon as possible.
This needs the all caps treatment:
KEY SIZE MUST BE CHECKED ON EVERY SIGNATURE IN THE CERTIFICATE, INCLUDING THE ROOT CERTIFICATE.
UNDER NO CIRCUMSTANCES SHOULD ANY PARTY EXPECT SUPPORT FOR 1024 BIT RSA KEYS IN 2014.
1024 bit certificates are dead, dead, dead. They cannot be considered secure. NIST has recommended at least 2048 bits in 2013, there's a website entirely devoted to appropriate key lengths and it's covered extensively in key management solutions The certificate authorities have stopped issuing them for a while, and over 95% of trusted leaf certificates and 95% of trusted signing certificates use NIST recommended key sizes.
The same caveats apply to DSA and ECC key sizes: keylength.com has the details.
OWASP lists some guidelines on creating certificates, notably "Do not use wildcard certificates" and "Do not use RFC 1918 addresses in certificates". While these are undoubtably questionable practices, I don't think it's appropriate to have rules forbidding them.
Part Two: Implementation
The relevant documentation is the Certificate Path Programmer Guide, also known as Java PKI API Programmer's Guide:
- Java PKI API Programmer's Guide, 1.8
- Java PKI API Programmer's Guide, 1.7
- Java PKI API Programmer's Guide, 1.6
Despite listing problems in verification above, I'm going to assume that JSSE checks certificates and certificate chains correctly, and doesn't have horrible bugs in the implementation. I am concerned that JSSE may have vulnerabilities, but part of the problem is knowing exactly what the correct behavior should be and TLS does not come with a reference implementation or a reference suite. As far as I know, JSSE has not been subject to NIST PKI testing or the X.509 test suite from CPNI, and CPNI doesn't release their test suite to the public. I am also unaware of any publically available X.509 certificate fuzzing tools.
What I can do is make sure that weak algorithms and key sizes are disabled, even in 1.6.
Validating a Certificate in JSSE
An interesting side note – although a trust store contains certificates, the fact that they are X.509 certificates is a detail. Anchors are just subject distinguished name and public key bindings. This means they don't have to be signed, and don't really have an expiration date. This tripped me (and a few others) up, but RFC 3280 and RFC 5280 are quite clear that expiration doesn't apply to trust anchors or trust stores.
Validating Key Sizes and Signature Algorithms
We need to make sure that JSSE is not accepting weak certificates. In particular, we want to check that the X.509 certificates have a decent signature algorithm and a decent key size.
There is a security property
jdk.certpath.disabledAlgorithms that validates X.509 certificates. You define it in a security.properties file like so:
This property is then read by the class X509DisabledAlgConstraints in SSLAlgorithmConstraints.java:
Note the "private final static" here – you can't define the security property at runtime after this instance has been loaded into memory. You can, as a workaround, set the constraints dynamically from setAlgorithmConstraints.
But there's another problem.
jdk.certpath.disabledAlgorithms is only in 1.7 and is global across the JVM. We need to support JDK 1.6 and make it local to the SSLContext. We can do better.
Here's what an example configuration looks like:
I'll skip over the details of how parsing and algorithm decomposition is done, except to say Scala contains a parser combinator library which makes writing small parsers very easy. On configuration, each of the statements parses out into an
AlgorithmConstraint that is checks to see if the certificate's key size or algorithm matches.
AlgorithmChecker that checks for signature and key algorithms:
Now that we have an algorithm checker, we need to put it into the validation chain.
There are two ways of validating a chain in JSSE. The first is using
CertPathValidator, which validates a certificate chain according to RFC 3280. The second is
CertPathBuilder, which "builds" a certificate chain according to RFC 4158. I've been told by informed experts that
CertPathBuilder is actually closer to the behavior of modern browsers, but in this case, we're just adding onto the chain of
PKIXCertPathChecker. There are several layers of configuration to go through, but eventually we pass this through to the TrustManager.
However, this doesn't check the root CA certificate, because that doesn't get passed in through the
PKIXCertPathChecker. So how does
SSLAlgorithmConstraints get at the root certificate?
Well, it's handled through the
Validator.getInstance(validatorType, variant, trustedCerts) – this returns
new PKIXValidator(variant, trustedCerts), and from there, PKIXValidator puts the trusted certs into
PKIXBuilderParameters, and then calls
So now we've moved on to the
PKIXCertPathValidator, which pulls out a trust anchor for the
This means that the
AlgorithmChecker can check for the weak key size in the trust anchor, but this only works if you control the validator chain. The
PKIXBuilderParameters object is not passed to
PKIXCertPathChecker, so we can't simply extend
PKIXCertPathChecker and pull out the trust anchor we'd like – we have to do this from the TrustManager directly. Easy enough:
To do this through configuration is a bit more work. We have to create a
PKIXBuilderParameters object and then attach the
AlgorithmChecker to it, then stick that inside ANOTHER parameters object called
CertPathTrustManagerParameters and then pass that into the
factory.init method. We end up with a single CompositeX509TrustManager class, and a bunch of trust managers all configured with the same AlgorithmChecker:
And now we can check for weak key sizes and bad certificates the same way JSSE 1.7 does.
This still isn't the best user experience, because it will result in a broken TLS connection at run time. We'd like to give the user as much information as soon as we can, and not waste our time on certificates that we know are going to fail. We can simply filter out certificates that don't pass muster.
To do this, we iterate through every trust anchor we have in the trust store, and verify that it matches our constraints.
But we're still not done. The default trust store is used if
SSLContext is initialized with
null, and we don't have access to it unless we do horrible things with reflection.
However, given that the default
SSLContextImpl will call out to the
TrustManagerFactory and any configuration with system properties will also apply with the factory, we can use the factory method to recreate the trust manager and validate the trust certificates that way.
We can do this:
And now… we're done. Now we can check for bad X.509 algorithms out of the box, and have it be local to the SSLContext.
The best way to create X.509 certificates with Java is using keytool. Unfortunately, keytool doesn't support subjectAltName in 1.6, but in 1.7 and 1.8 you can specify the subjectAltName (which is required for hostname verification) using the
For example, to create your own self signed certificates (private key + public key both) for using in testing, you would specify:
And then add
You should see:
If you have a signature of
DNSName: example.com, then it worked.
And then calls to "https://example.com"would work fine. Then you can pass in your local keystore using the options defined in the customization section:
Or you can wire the certificates into an
SSLContext directly using a
TrustManagerFactory and a
KeyManagerFactory, and then set up a server and a client from the SSLContext as shown here:
X.509 certificates are one of the moving pieces of TLS that have many, many ways of going wrong. Be prepared to find out of order certificates, missing intermediate certificates, and other problematic practices.
Certificate path debugging can be turned on using the
-Djavax.net.debug="ssl trustmanager" settings. How to analyze Java SSL errors is a good example of tracking down bugs.