How to Obtain Signer’s Details From JavaScript Signed Data

In a previous post I described how to sign data with only javascript. Now, this data should be used on the server side for something. Here is how a Java developer can extract the signature details, and verify whether the content received from a form is really what has been signed. The general scenario is – a user submits a form, the data from which he signs. Then on the server the submitted data should be verified against the signed (PKCS7) data.

One needs the Bouncycastle libraries and apache commons codec:
commons-codec-1.3.jar
bcprov-jdk16-143.jar
bcmail-jdk16-143.jar

package com.materna.remedy.plugins;

import java.security.Security;
import java.security.cert.CertStore;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class CertificateDataExctractor {
	private Map<String, Object> extractInfos(String base64EncodedPKCS7,
			String contentString, boolean isIe) {

		try {
			byte[] data = Base64.decodeBase64(base64EncodedPKCS7.trim().getBytes());

			Security.addProvider(new BouncyCastleProvider());

			CMSSignedData signedData = new CMSSignedData(data);

			if (signedData.getSignedContent() == null) {
				byte[] contentBytes;
				if (!isIe) {
					contentBytes = contentString.getBytes();
				} else {
					contentBytes = contentString.getBytes("UnicodeLittleUnmarked");
				}

				CMSProcessable cmsProcesableContent = new CMSProcessableByteArray(
						contentBytes);
				signedData = new CMSSignedData(cmsProcesableContent, data);
			}

			CertStore certsStore = signedData.getCertificatesAndCRLs(
					"Collection", "BC");
			SignerInformationStore signersStores = signedData.getSignerInfos();

			boolean verified = true;
			boolean validCertificate = true;
			Map<String, Object> signerData = null;

			for (Iterator<SignerInformation> iter = signersStores.getSigners()
					.iterator(); iter.hasNext();) {
				SignerInformation signer = iter.next();
				// emulate(signer);

				Collection certCollection = certsStore.getCertificates(signer
						.getSID());

				if (!certCollection.isEmpty()) {
					X509Certificate cert = (X509Certificate) certCollection
							.iterator().next();

					try {
						if (!signer.verify(cert.getPublicKey(), "BC")) {
							verified = false;
						}
					} catch (Exception ex) {
						ex.printStackTrace();
						// if this is an attempt to verify it assuming Firefox,
						// try assuming IE. If it is already IE - the
						// verification doesn't pass
						if (!isIe) {
							return extractInfos(base64EncodedPKCS7,
									contentString, true);
						}
						verified = false;
					}

					// If this is the last signer in the chain, obtain the data
					if (!iter.hasNext()) {
						signerData = extractSubjectInfos(cert);
					}
				}
			}
			return signerData;
		} catch (Exception ex) {
			ex.printStackTrace();
			return null;
		}

	}
}

And of course, you provide your own implementation of getSubjectInfos method, putting whatever data you need from the certificate in the Map.

4 thoughts on “How to Obtain Signer’s Details From JavaScript Signed Data”

  1. Браво 😉
    Great code, thanks a lot. Works great. I implemented it in Domino XPages web application. I have two questions:

    1. Is there a way to sign an uploaded file instead of just text?

    2. I searched all over the net to try to find out how to programmatically verify the info I extracted from the certificate with the data from the Provider of the Signature. Is this actually needed to verify the authenticity of the signature? What do I have to check – maybe the Public Keys to be the same. Do you have any ideas?

    Thanks a lot 😉

Leave a Reply

Your email address will not be published.