Step-by-step signature verification of X.509 certificates in Java

The Java code below demonstrates signature verification in a chain of X.509 certificates using cryptographic primitives (i.e. hash functions and ciphers). It thereby implements parts of the functionality provided by Java class library methods such as X509Certificate.verify(). Remember, that certificate chain verification involves additional steps next to signature verification.

In the code below, the method verifyCertChainSignatures() checks whether a given array of X.509 certificate objects has valid signatures, i.e. whether a certificate was signed using the private key that belongs to the next lower certificate in the array. For the root certificate (the certificate with index 0), it is checked whether is was signed by itself.

Except for the ASN.1 decoding (which employs classes from the Bouncy Castle Java cryptography API), all classes used are from the Java system library. The code is for exemplary purposes only, as there is no proper exception handling, bounds checking etc.

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Enumeration;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DLSequence;

public class CertSignatureVerifier {

	public boolean verifyCertChainSignatures(X509Certificate[] certChain)
			throws InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException,
			SignatureException, IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, IOException {

		boolean signatureChainIsValid = true;

		for (int i = certChain.length - 1; i > 0; i--) {
			signatureChainIsValid = verifyCertSignature(certChain[i], certChain[i - 1]);
		}

		// Check whether root certificate is signed by itself
		X509Certificate rootCert = certChain[0];
		signatureChainIsValid = verifyCertSignature(rootCert, rootCert);

		return signatureChainIsValid;

	}

	public boolean verifyCertSignature(X509Certificate lowerCert, X509Certificate higherCert)
			throws IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, InvalidKeyException,
			CertificateEncodingException, SignatureException, IOException, NoSuchAlgorithmException {

		// Compute certificate digest
		String hashAlgName = lowerCert.getSigAlgName().split("with")[0];

		MessageDigest md = MessageDigest.getInstance(hashAlgName);
		byte[] tbsCertificate = lowerCert.getTBSCertificate();
		md.update(tbsCertificate);
		byte[] computedDigest = md.digest();

		// Decode signature
		String cryptoAlgName = lowerCert.getSigAlgName().split("with")[1];

		Cipher decCipher = Cipher.getInstance(cryptoAlgName);
		decCipher.init(Cipher.DECRYPT_MODE, higherCert.getPublicKey());
		byte[] decodedSignature = decCipher.doFinal(lowerCert.getSignature());
		byte[] decodedDigest = extractAsn1EncodedSignature(decodedSignature);

		return Arrays.equals(computedDigest, decodedDigest);

	}

	private byte[] extractAsn1EncodedSignature(byte[] bytes) throws IOException {

		ASN1InputStream ais = new ASN1InputStream(bytes);
		DLSequence superSeq = (DLSequence) ais.readObject();

		// Extract signature bytes
		Enumeration e1 = superSeq.getObjects();
		DLSequence subSeq = (DLSequence) e1.nextElement();
		DEROctetString octstr = (DEROctetString) e1.nextElement();
		byte[] octets = octstr.getOctets();

		// Extract signature algorithm OID string (not used here, though)
		Enumeration e2 = subSeq.getObjects();
		DERObjectIdentifier algorithmIdentifier = (DERObjectIdentifier) e2.nextElement();
		String oidString = algorithmIdentifier.toString();

		return octets;

	}

}