I looked for solutions and other answers regardign the similar issues. Nothing is working for my case.
Scenerios: We have to develop an application using Java and iText 8.0.3, whose purpose is to sign pdf documents. The signing key is held by another company, let’s say it’s CompanyA.
These are the steps we are following:
- Got the pdf document to sign ready.
- Created a Temp pdf file which added an Empty Signature in the original pdf.
- Read the Temp pdf to get the message digest. (Encode it to base64)
- Send the message digest (Base64 encoded) to the CompanyA to get signed.
- Get the signed digest (base64 encoded) from CompanyA. Do the base64 decoding. And embedded the result into the Temp pdf to get the final signed pdf.
Unfortunately its not working and i am getting it saying,
“Error during signature verification.
ASN.1 parsing error:
Error encountered while BER decoding:”
Below is relevant part of the code from our application which is being used for the above process:
package com.etc;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.*;
import com.itextpdf.signatures.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
public class SignatureService {
public static X509Certificate getCertificate(String base64String) throws Exception{
byte[] encodedCert = Base64.getMimeDecoder().decode(base64String);
ByteArrayInputStream inputStream = new ByteArrayInputStream(encodedCert);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)certFactory.generateCertificate(inputStream);
return cert;
}
public static String getHashBase64Str2Sign() {
try {
// Add BC provider
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
X509Certificate certificate = null;
Certificate[] certs = null;
try{
certificate = getCertificate("certBase64");
certs = new Certificate[]{certificate};
} catch (Exception ex){
ex.printStackTrace();
}
byte[] sh = emptySignature("helloworld.pdf", "temp.pdf", "sig", certs);
return Base64.getEncoder().encodeToString(sh);
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
return null;
}
}
private static byte[] emptySignature(String src, String dest, String fieldname, Certificate[] chain)
throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties());
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
appearance.setPageRect(new Rectangle(100, 500, 200, 100));
appearance.setPageNumber(1);
appearance.setCertificate(chain[0]);
appearance.setReason("For test");
appearance.setLocation("HKSAR");
signer.setFieldName(fieldname);
PreSignatureContainer external = new PreSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.signExternalContainer(external, 8192);
return external.getHash();
}
private static void createSignature(String src, String dest, String fieldName, byte[] sig)
throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
try (FileOutputStream os = new FileOutputStream(dest)) {
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
IExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
PdfSigner.signDeferred(signer.getDocument(), fieldName, os, external);
}
}
public static class PreSignatureContainer implements IExternalSignatureContainer {
private final PdfDictionary sigDic;
private byte[] hash;
public PreSignatureContainer(PdfName filter, PdfName subFilter) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
String hashAlgorithm = "SHA256";
BouncyCastleDigest digest = new BouncyCastleDigest();
try {
this.hash = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
} catch (IOException e) {
throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
}
return new byte[0];
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
public byte[] getHash() {
return hash;
}
}
public static void embedSignedHashToPdf(String signedHash) {
try {
byte[] sig = Base64.getDecoder().decode(signedHash);
// Get byte[] hash
//SignatureService app = new SignatureService();
createSignature("temp.pdf", "output.pdf", "sig", sig);
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
}
}
static class MyExternalSignatureContainer implements IExternalSignatureContainer {
protected byte[] sig;
public MyExternalSignatureContainer(byte[] sig) {
this.sig = sig;
}
@Override
public byte[] sign(InputStream arg0) throws GeneralSecurityException {
return sig;
}
@Override
public void modifySigningDictionary(PdfDictionary pdfDictionary) {
}
}
public static void main(String[] args) throws Exception {
// System.out.println("Hash:" + getHashBase64Str2Sign());
String hash = getHashBase64Str2Sign();
System.out.println(hash);
// Send the hash and get signed hash from CompanyA
String signatureString = //.....
embedSignedHashToPdf(escapeCharFromSignature(signatureString));
}
public static String escapeCharFromSignature(String str) {
return str.replace("\r\n", "");
}
}
Below are the source and temp/output pdf after signing:
Any help would be highly appreciated. Thanks
UPDATE
So I have updated the pre signature container, now I am getting all thing valid, except the message "Document has been altered or corrupted"
public static class PreSignatureContainer implements IExternalSignatureContainer {
private final PdfDictionary sigDic;
private byte[] hash;
public PreSignatureContainer(PdfName filter, PdfName subFilter) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
String hashAlgorithm = "SHA256";
BouncyCastleDigest digest = new BouncyCastleDigest();
X509Certificate certificate = null;
Certificate[] certs = null;
try{
certificate = ("cert");
certs = new Certificate[]{certificate};
} catch (Exception ex){
ex.printStackTrace();
}
try {
this.hash = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
PdfPKCS7 pkcs7 = new PdfPKCS7(null,certs , "SHA-256", null, digest, false);
byte[] sh = pkcs7.getAuthenticatedAttributeBytes(hash,PdfSigner.CryptoStandard.CMS, null, null);
this.hash = sh;
} catch (IOException e) {
throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
}
return new byte[0];
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
public byte[] getHash() {
return hash;
}
}
Here's the updated PDF: helloworld.pdf
UPDATE 2
Here's the PreSignatureContainer uses:
private static byte[] emptySignature(String src, String dest, String fieldname, Certificate[] chain)
throws IOException, GeneralSecurityException
{
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties());
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
appearance.setPageRect(new Rectangle(100, 500, 200, 100));
appearance.setPageNumber(1);
appearance.setCertificate(chain[0]);
appearance.setReason("For test");
appearance.setLocation("HKSAR");
signer.setFieldName(fieldname);
PreSignatureContainer external = new PreSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.signExternalContainer(external, 8192);
return external.getHash();
}
public static class PreSignatureContainer implements IExternalSignatureContainer {
private final PdfDictionary sigDic;
private byte[] hash;
public PreSignatureContainer(PdfName filter, PdfName subFilter) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
String hashAlgorithm = "SHA256";
BouncyCastleDigest digest = new BouncyCastleDigest();
X509Certificate certificate = null;
Certificate[] certs = null;
try{
certificate = getCertificate("cert");
certs = new Certificate[]{certificate};
} catch (Exception ex){
ex.printStackTrace();
}
try {
byte[] hash = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
PdfPKCS7 pkcs7 = new PdfPKCS7(null,certs , "SHA-256", null, digest, false);
byte[] sh = pkcs7.getAuthenticatedAttributeBytes(hash,PdfSigner.CryptoStandard.CMS, null, null);
this.hash = sh;
} catch (IOException e) {
throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
}
return new byte[0];
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
public byte[] getHash() {
return hash;
}
}