javasslx509certificatebouncycastlex509

Validate TLS server certificate with BouncyCastle's "lightweight" API


I'm using Java BouncyCastle so-called "lightweight" API to establish a TLS connection over a TCP socket.

I want to verify server-provided certificate chain being signed by one of trusted CAs. Sounds like a reasonably common task that every sane TLS client implementation out there does by default, so I expect this should be simple.

For a sake of simplifying the question, I don't ask about verifying anything other than the sign/trust chain, like matching hostname or checking expiration date. Such checks seem trivial to implement.

If I understand the documentation correctly, there's a TlsAuthentication interface, that users are supposed to implement. The sole provided implementation is LegacyTlsAuthentication, which adapts upon now-deprecated CertificateVerifyer interface, which has only AlwaysValidVerifyer implementation (that's just dummy "return true;" under the hood).

So, this is what I have for now:

DefaultTlsClient tlsClient = new DefaultTlsClient() {
    @Override
    public TlsAuthentication getAuthentication() throws IOException {
        TlsAuthentication auth = new TlsAuthentication() {
            @Override
            public void notifyServerCertificate(Certificate serverCertificate) {
                // Here I should validate certificate chain, but this far
                // I only managed to print subjects for debugging purposes.
                for (org.bouncycastle.asn1.x509.Certificate c : serverCertificate.getCerts()) {
                    System.out.println("Certificate: " + c.getSubject().toString());
                }
            }

            @Override
            public TlsCredentials getClientCredentials(CertificateRequest cr) throws IOException {
                return null;
            }
        };
        return auth;
    }
};

socket = new Socket(hostname, port);
tlsHandler = new TlsProtocolHandler(socket.getInputStream(), socket.getOutputStream());
tlsHandler.connect(tlsClient);

However, I fail to understand or find any existing example that would check one org.bouncycastle.asn1.x509.Certificate for being correctly signed by another one. Could someone provide some pointers to me, please?

I'm using BounceCastle's proprietary API due to need to use ciphersuites that default Java installations do not allow due to being subject to US cryptographic policy jurisdiction restrictions. For example, AES256 encryption requires installing unlimited strength policy files, and I'd really like to avoid additional end-user installation steps, if possible.


Solution

  • The isSignatureValid method of the X509CertificateHolder class should work for you. This method takes in 1 parameter, a ContentVerifierProvider. You can create an X509CertificateHolder by passing a Certificate into the constructor.

    The following code is taken from BC's version 2 API page and should give you a good idea in how to implement this in your solution.

    ContentVerifierProvider contentVerifierProvider =
       new BcRSAContentVerifierProviderBuilder(
          new DefaultDigestAlgorithmIdentifierFinder()).build(lwPubKey);
    
    if (!certHolder.isSignatureValid(contentVerifierProvider))
    {
        System.err.println("signature invalid");
    }
    

    "lwPubKey" is the public key of the signer. So depending on how long your certificate chain is, you would repeatedly call this method starting with the end entity certificate and going up the line to the self-signed root certificate.