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.
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.
Браво 😉
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 😉
Hi. 1. No 2. I don’t remember, it was a long time ago 🙂
is there any way to create demo certificate and the authentication for my testing pupose..?