I am working on implementing a solution for signing a PDF using i text pdf 7. The code is working fine but when the document is opened in adobe it gives "Document has been altered or corrupted since it was signed"
I am not able to figure out on what I am missing
I have attached the code and generated pdf sample as well
public void testPoints() {
try{
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
InputStream inStream = new FileInputStream("/Users/abhinav/Work/dev/client-wca.p12");
/* Reading the certificate from a tempory store */
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inStream, "!234Qwer".toCharArray());
String alias = ks.aliases().nextElement();
PrivateKey key = (PrivateKey)ks.getKey(alias, "!234Qwer".toCharArray());
Certificate certificate = (X509Certificate) ks.getCertificate(alias);
// create PCKS7 for getting attributes
Certificate[] certificates= Arrays.asList(certificate).toArray(new Certificate[0]);
//CREATE an empty signature placeholder
PreSignatureContainerToSign preSignatureContainerToSign = emptySignature("/Users/abhinav/Work/dev/dummy.pdf", "/Users/abhinav/Work/dev/SL_SIGNED.pdf",
"sig", certificates, key);
// byte[] signature = sign(preSignatureContainerToSign.hash, key);
//Sign the PDF with original has
createSignature("/Users/abhinav/Work/dev/SL_SIGNED.pdf", "/Users/abhinav/Work/dev/SL_SIGNED_f.pdf",
"sig", preSignatureContainerToSign.hash, certificates, key, preSignatureContainerToSign);
}
catch (Exception e){
System.out.println();
e.printStackTrace();
}
}
private PreSignatureContainerToSign emptySignature(String src, String dest, String fieldname, Certificate[] chain, PrivateKey key)
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, 400, 200, 100));
appearance.setPageNumber(1);
appearance.setCertificate(chain[0]);
// appearance.setReason("For test");
// appearance.setLocation("HKSAR");
signer.setFieldName(fieldname);
/*
* ExternalBlankSignatureContainer constructor will create the PdfDictionary for
* the signature information and will insert the /Filter and /SubFilter values
* into this dictionary. It will leave just a blank placeholder for the
* signature that is to be inserted later.
*/
PreSignatureContainerToSign external = new PreSignatureContainerToSign(PdfName.Adobe_PPKLite,
PdfName.Adbe_pkcs7_detached);
external.setCertificates(chain);
external.setKey(key);
// Sign the document using an external container.
// 8192 is the size of the empty signature placeholder.
signer.signExternalContainer(external, 8192);
return external;
}
private void createSignature(String src, String dest, String fieldName, byte[] sig, Certificate[] chain,
PrivateKey key, PreSignatureContainerToSign preSignatureContainerToSign)
throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
try (FileOutputStream os = new FileOutputStream(dest)) {
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties().useAppendMode());
PreSignatureContainer external = new PreSignatureContainer(PdfName.Adobe_PPKLite,
PdfName.Adbe_pkcs7_detached, true, sig,
preSignatureContainerToSign.hash);
external.setCertificates(chain);
external.setKey(key);
// Signs a PDF where space was already reserved. The field must cover the whole
// document.
PdfSigner.signDeferred(signer.getDocument(), fieldName, os, external);
}
}
public class PreSignatureContainer implements IExternalSignatureContainer {
private PdfDictionary sigDic;
private PrivateKey key;
private Certificate[] certificates;
private byte hash[];
private byte[] signature;
private byte[] original;
public void setCertificates(Certificate[] certificates) {
this.certificates = certificates;
}
public void setKey(PrivateKey key) {
this.key = key;
}
public PreSignatureContainer(PdfName filter, PdfName subFilter, boolean flag, byte[] signature, byte[] original) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
this.signature = signature;
this.original = original;
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
BouncyCastleDigest digest = new BouncyCastleDigest();
try {
var sgn = new PdfPKCS7(null, this.certificates,
DigestAlgorithms.SHA256, null, digest, false);
sgn.setExternalDigest(signature,null, "RSA");
return sgn.getEncodedPKCS7(original, PdfSigner.CryptoStandard.CMS,
null, null, null);
} catch (Exception e) {
throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
}
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
public byte[] getHash() {
return hash;
}
}
public class PreSignatureContainerToSign implements IExternalSignatureContainer {
private PdfDictionary sigDic;
private PrivateKey key;
private Certificate[] certificates;
private byte hash[];
private byte original[];
public void setCertificates(Certificate[] certificates) {
this.certificates = certificates;
}
public void setKey(PrivateKey key) {
this.key = key;
}
public PreSignatureContainerToSign(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 {
var sgn = new PdfPKCS7(null, this.certificates, DigestAlgorithms.SHA256, null,
new BouncyCastleDigest(), false);
original = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
// get attributes
byte [] docsBytes = sgn.getAuthenticatedAttributeBytes(original,
PdfSigner.CryptoStandard.CMS,
null, null);
this.hash = DigestAlgorithms.digest(new ByteArrayInputStream(docsBytes), digest.getMessageDigest(hashAlgorithm));
return new byte[0];
} catch (IOException e) {
throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
}
}
@Override
public void modifySigningDictionary(PdfDictionary pdfDictionary) {
pdfDictionary.putAll(sigDic);
}
}
EDIT
Updating the code as it being used to generate the file
public void testPoints() {
try{
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
InputStream inStream = new FileInputStream("/Users/abhinav/Work/dev/client-wca.p12");
/* Reading the certificate from a tempory store */
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inStream, "!234Qwer".toCharArray());
String alias = ks.aliases().nextElement();
PrivateKey key = (PrivateKey)ks.getKey(alias, "!234Qwer".toCharArray());
Certificate certificate = (X509Certificate) ks.getCertificate(alias);
// create PCKS7 for getting attributes
Certificate[] certificates= Arrays.asList(certificate).toArray(new Certificate[0]);
//CREATE an empty signature placeholder
PreSignatureContainerToSign preSignatureContainerToSign = emptySignature("/Users/abhinav/Work/dev/dummy.pdf", "/Users/abhinav/Work/dev/SL_SIGNED.pdf",
"sig", certificates, key);
byte[] signature = sign(preSignatureContainerToSign.hash, key);
//Sign the PDF with original has
createSignature("/Users/abhinav/Work/dev/SL_SIGNED.pdf", "/Users/abhinav/Work/dev/SL_SIGNED_f.pdf",
"sig", signature, certificates, key, preSignatureContainerToSign);
}
catch (Exception e){
System.out.println();
e.printStackTrace();
}
}
public static byte[] sign(byte[] plainHash, PrivateKey privateKey) throws Exception {
PrivateKeySignature signature = new PrivateKeySignature(privateKey, "SHA256", "BC");
return signature.sign(plainHash);
}
private PreSignatureContainerToSign emptySignature(String src, String dest, String fieldname, Certificate[] chain, PrivateKey key)
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, 400, 200, 100));
appearance.setPageNumber(1);
appearance.setCertificate(chain[0]);
// appearance.setReason("For test");
// appearance.setLocation("HKSAR");
signer.setFieldName(fieldname);
/*
* ExternalBlankSignatureContainer constructor will create the PdfDictionary for
* the signature information and will insert the /Filter and /SubFilter values
* into this dictionary. It will leave just a blank placeholder for the
* signature that is to be inserted later.
*/
PreSignatureContainerToSign external = new PreSignatureContainerToSign(PdfName.Adobe_PPKLite,
PdfName.Adbe_pkcs7_detached);
external.setCertificates(chain);
external.setKey(key);
// Sign the document using an external container.
// 8192 is the size of the empty signature placeholder.
signer.signExternalContainer(external, 8192);
return external;
}
private void createSignature(String src, String dest, String fieldName, byte[] sig, Certificate[] chain,
PrivateKey key, PreSignatureContainerToSign preSignatureContainerToSign)
throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
try (FileOutputStream os = new FileOutputStream(dest)) {
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
PreSignatureContainer external = new PreSignatureContainer(PdfName.Adobe_PPKLite,
PdfName.Adbe_pkcs7_detached, true, sig,
preSignatureContainerToSign.original, preSignatureContainerToSign.getSgn());
external.setCertificates(chain);
external.setKey(key);
// Signs a PDF where space was already reserved. The field must cover the whole
// document.
PdfSigner.signDeferred(signer.getDocument(), fieldName, os, external);
}
}
public class PreSignatureContainer implements IExternalSignatureContainer {
private PdfDictionary sigDic;
private PrivateKey key;
private PdfPKCS7 sgn;
private Certificate[] certificates;
private byte hash[];
private byte[] signature;
private byte[] original;
public void setCertificates(Certificate[] certificates) {
this.certificates = certificates;
}
public void setKey(PrivateKey key) {
this.key = key;
}
public PreSignatureContainer(PdfName filter, PdfName subFilter, boolean flag, byte[] signature, byte[] original,
PdfPKCS7 sgn) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
this.signature = signature;
this.original = original;
this.sgn = sgn;
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
BouncyCastleDigest digest = new BouncyCastleDigest();
try {
var sgn = new PdfPKCS7(null, this.certificates,
DigestAlgorithms.SHA256, null, digest, false);
this.sgn.setExternalDigest(signature,null, "RSA");
return this.sgn.getEncodedPKCS7(original, PdfSigner.CryptoStandard.CMS,
null, null, null);
} catch (Exception e) {
throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
}
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
public byte[] getHash() {
return hash;
}
}
public class PreSignatureContainerToSign implements IExternalSignatureContainer {
private PdfDictionary sigDic;
private PrivateKey key;
private PdfPKCS7 sgn;
private Certificate[] certificates;
private byte hash[];
private byte original[];
public PdfPKCS7 getSgn() {
return sgn;
}
public void setCertificates(Certificate[] certificates) {
this.certificates = certificates;
}
public void setKey(PrivateKey key) {
this.key = key;
}
public PreSignatureContainerToSign(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.sgn = new PdfPKCS7(null, this.certificates, DigestAlgorithms.SHA256, null,
new BouncyCastleDigest(), false);
// original = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
// get attributes
this.original = sgn.getAuthenticatedAttributeBytes(DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm)),
PdfSigner.CryptoStandard.CMS,
null, null);
this.hash = DigestAlgorithms.digest(new ByteArrayInputStream(this.original), digest.getMessageDigest(hashAlgorithm));
return new byte[0];
} catch (IOException e) {
throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
}
}
@Override
public void modifySigningDictionary(PdfDictionary pdfDictionary) {
pdfDictionary.putAll(sigDic);
}
}
EDIT
Fixed the signed attributes and remove the sign simulation using the private key
public void testPoints() {
try{
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
InputStream inStream = new FileInputStream("/Users/abhinav/Work/dev/client-wca.p12");
/* Reading the certificate from a tempory store */
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inStream, "!234Qwer".toCharArray());
String alias = ks.aliases().nextElement();
PrivateKey key = (PrivateKey)ks.getKey(alias, "!234Qwer".toCharArray());
Certificate certificate = (X509Certificate) ks.getCertificate(alias);
// create PCKS7 for getting attributes
Certificate[] certificates= Arrays.asList(certificate).toArray(new Certificate[0]);
//CREATE an empty signature placeholder
PreSignatureContainerToSign preSignatureContainerToSign = emptySignature("/Users/abhinav/Work/dev/dummy.pdf", "/Users/abhinav/Work/dev/SL_SIGNED.pdf",
"sig", certificates, key);
// byte[] signature = sign(preSignatureContainerToSign.hash, key);
//Sign the PDF with original has
createSignature("/Users/abhinav/Work/dev/SL_SIGNED.pdf", "/Users/abhinav/Work/dev/SL_SIGNED_f.pdf",
"sig", preSignatureContainerToSign.hash, certificates, preSignatureContainerToSign);
}
catch (Exception e){
System.out.println();
e.printStackTrace();
}
}
public static byte[] sign(byte[] plainHash, PrivateKey privateKey) throws Exception {
PrivateKeySignature signature = new PrivateKeySignature(privateKey, "SHA256", "BC");
return signature.sign(plainHash);
}
private PreSignatureContainerToSign emptySignature(String src, String dest, String fieldname, Certificate[] chain, PrivateKey key)
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, 400, 200, 100));
appearance.setPageNumber(1);
appearance.setCertificate(chain[0]);
signer.setFieldName(fieldname);
/*
* ExternalBlankSignatureContainer constructor will create the PdfDictionary for
* the signature information and will insert the /Filter and /SubFilter values
* into this dictionary. It will leave just a blank placeholder for the
* signature that is to be inserted later.
*/
PreSignatureContainerToSign external = new PreSignatureContainerToSign(PdfName.Adobe_PPKLite,
PdfName.Adbe_pkcs7_detached);
external.setCertificates(chain);
// Sign the document using an external container.
// 8192 is the size of the empty signature placeholder.
signer.signExternalContainer(external, 8192);
return external;
}
private void createSignature(String src, String dest, String fieldName, byte[] sig, Certificate[] chain,
PreSignatureContainerToSign preSignatureContainerToSign)
throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
try (FileOutputStream os = new FileOutputStream(dest)) {
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
PreSignatureContainer external = new PreSignatureContainer(PdfName.Adobe_PPKLite,
PdfName.Adbe_pkcs7_detached, sig,
preSignatureContainerToSign.hash);
external.setCertificates(chain);
// Signs a PDF where space was already reserved. The field must cover the whole
// document.
PdfSigner.signDeferred(signer.getDocument(), fieldName, os, external);
}
}
public class PreSignatureContainer implements IExternalSignatureContainer {
private PdfDictionary sigDic;
private Certificate[] certificates;
private byte[] signature;
private byte[] original;
public void setCertificates(Certificate[] certificates) {
this.certificates = certificates;
}
public PreSignatureContainer(PdfName filter, PdfName subFilter, byte[] signature, byte[] original) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
this.signature = signature;
this.original = original;
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
BouncyCastleDigest digest = new BouncyCastleDigest();
String hashAlgorithm = "SHA256";
try {
byte [] dataDigest = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
var sgn = new PdfPKCS7(null, this.certificates,
DigestAlgorithms.SHA256, null, digest, false);
sgn.setExternalDigest(signature,null, "RSA");
return sgn.getEncodedPKCS7(dataDigest, PdfSigner.CryptoStandard.CMS,
null, null, null);
} catch (Exception e) {
throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
}
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
}
public class PreSignatureContainerToSign implements IExternalSignatureContainer {
private PdfDictionary sigDic;
private PdfPKCS7 sgn;
private Certificate[] certificates;
private byte hash[];
private byte original[];
public PdfPKCS7 getSgn() {
return sgn;
}
public void setCertificates(Certificate[] certificates) {
this.certificates = certificates;
}
public PreSignatureContainerToSign(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.sgn = new PdfPKCS7(null, this.certificates, DigestAlgorithms.SHA256, null,
new BouncyCastleDigest(), false);
this.original = DigestAlgorithms.digest(data, digest.getMessageDigest(hashAlgorithm));
// get attributes
byte [] docBytes = sgn.getAuthenticatedAttributeBytes(this.original, PdfSigner.CryptoStandard.CMS,
null, null);
this.hash = DigestAlgorithms.digest(new ByteArrayInputStream(docBytes), digest.getMessageDigest(hashAlgorithm));
return new byte[0];
} catch (IOException e) {
throw new GeneralSecurityException("PreSignatureContainer signing exception", e);
}
}
@Override
public void modifySigningDictionary(PdfDictionary pdfDictionary) {
pdfDictionary.putAll(sigDic);
}
}