Implementing X509 Certificate Validation in Java: A Step-by-Step Guide

czetsuya
3 min readMay 7, 2023

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.

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

  1. The 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.

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);
}

The article was originally published at https://www.czetsuyatech.com/2023/05/java-x059certificate-validation.html.

--

--

czetsuya

Open for Collaboration | Senior Java Backend Developer