Implementing X509 Certificate Validation in Java: A Step-by-Step Guide
Tags: java, digitalsigning, digitalcertificate
Published: 2023-05-07 05:09:15

Unlock the power of secure communication with Java! Learn how to implement X509 certificate validation simply and straightforwardly, step-by-step.

## Overview
An X.509 certificate is a digital certificate used to verify a particular entity's identity, such as a website or an individual. X.509 certificates are widely used in various applications, including secure communication protocols like HTTPS, SSL, and TLS.

An X.509 certificate contains several pieces of information, including the identity of the entity being verified, the public key that is associated with that entity, and various other pieces of metadata that provide additional information about the certificate itself.

## Importance
One of the key benefits of X.509 certificates is that they allow entities to authenticate themselves to other parties securely and reliably. This can help to prevent various types of attacks, including man-in-the-middle attacks, where an attacker intercepts and modifies communication between two parties to steal sensitive information.

Overall, X.509 certificates play a critical role in modern security infrastructure and are essential to many different types of secure communication protocols.

## Requirements
You must have the following installed on your computer.
- Git
- Maven
- IDE (IntellIJ / Visual Studio)
- Java 17+

## Dependencies
This project uses the following dependencies.
- bcpkix-jdk15on - certificate revocation check
- spring-boot-starter-test (for testing)
- lombok (for logging)
- Certificates from https://www.digicert.com/kb/digicert-root-certificates.htm

## Project Code
The certificate validation process.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jo5re8jpbffczc99kfcm.png)

To implement the validation, we will introduce a bean where we can define the:

1. Certificate that we are validating
2. The pem file that we can cross-check #1
3. Root certificate to check if the certificate is already revoked

Let's examine the code.
The CertificateBundle class is just a simple Java bean.

```
@Getter
@Setter
@Builder
public class CertificateBundle {
 
  // Certificate extracted from the message
  private X509Certificate certificate;
 
  // Certificate loaded from the application must match the certificate
  private X509Certificate confirmationCertificate;
 
  // RootCertificate of the certificate
  private X509Certificate rootCertificate;
}
```

To validate the **certificate expiration**, we need to extract the X509Certificate object and perform a time comparison against the value of getNotAfter(). Here, we're making sure that the current date is greater than the certificate's expiry date.

```
Date expiresOn = cert.getNotAfter();
Date now = new Date();
 
if (now.getTime() > expiresOn.getTime()) {
  throw new ExpiredCertificateException();
}
```

**Comparing digital signature against a PEM file**. In most fintech applications, there is a need to sign the message before sending it to another service. Upon receiving the message, the server needs to extract the public certificate embedded in it and compare it to a PEM file. This PEM file is a public key of the private key certificate used to sign the message.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/huzd6ogloenj1giz01uz.png)

```
boolean match = Arrays.equals(cert1.getPublicKey().getEncoded(),
    cert2.getPublicKey().getEncoded());
 
if (!match) {
  throw new MismatchCertificateException("Embedded certificate does not match the given PEM");
}
```

And finally, we do the **revocation check**. This process allows us to know whether a website's certificate is trustworthy or not. It uses the root certificate that we used for signing as the trust anchor.

```
try {
  List<X509Certificate> certs = Collections.singletonList(cb.getCertificate());
  CertificateFactory cf = CertificateFactory.getInstance("X509");
  CertPath cp = cf.generateCertPath(certs);
 
  // load the root CA cb
  X509Certificate rootCACert = cb.getRootCertificate();
 
  // init trusted certs
  TrustAnchor ta = new TrustAnchor(rootCACert, null);
  Set<TrustAnchor> trustedCerts = new HashSet<>();
  trustedCerts.add(ta);
 
  // init PKIX parameters
  PKIXParameters params = new PKIXParameters(trustedCerts);
 
  // load the CRL
  params.addCertStore(CertStore.getInstance("Collection",
      new CollectionCertStoreParameters(getX509Crls(cf, getCrl(cb.getCertificate())))));
 
  // perform validation
  CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
  PKIXCertPathValidatorResult cpvResult = (PKIXCertPathValidatorResult) cpv.validate(cp,
      params);
  X509Certificate trustedCert = cpvResult.getTrustAnchor().getTrustedCert();
 
  if (trustedCert == null) {
    log.error("Trusted certificate not found");
    throw new CertificateException("Trusted certificate not found");
 
  } else {
    log.debug("Trusted CA DN = " + trustedCert.getSubjectX500Principal());
  }
 
} catch (InvalidAlgorithmParameterException | java.security.cert.CertificateException |
         CRLException |
         NoSuchAlgorithmException |
         CertPathValidatorException |
         IOException e) {
  throw new RevokedCertificateException(e);
}
```