I’m trying to sign a pdf using a Hsm Service that receives the document hash and responds with the signed hash. I followed some examples and checked posts in Stack OverFlow that related some similar problems but yet i did not found the solution. Here stays a quick flow of the data:
- Receive the pdf in byte64 – Create a PDDocumemt and add a visual Signature
- Use externalSigning object to get the data hash and send it to be signed.
- Use sign method to add certificates and the signed hash
When receiving the pdf on my consuming app i get a signed pdf yet the signature is not valid… It shows “Document has been altered or corrupted since it was signed”. I already try to find the source of the problem but with no success, i do not change the document after the sign method, only do the necessary to get the bytes from the file to send it in the response.
I know that are similar discussions but non of them solved my problem.
Here is the majoraty of my code that handles the signing process:
PDDocument doc = PDDocument.load(request.getRequest().getFile());
float page_width = doc.getPage(page).getMediaBox().getWidth();
float page_height = doc.getPage(page).getMediaBox().getHeight();
// Calculate position for signature
float sqr_width = page_width / num_cols;
float sqr_height = page_height / num_rows;
float pos_X = page_width - sqr_width - (sqr_width * (num_cols - column));
float pos_Y = page_height - sqr_height - (sqr_height * (num_rows - row));
GetCertificatebyTotpIDOutput certificateBody= getCertificate();
PDSignature signature = new PDSignature();
signature.setFilter( PDSignature.FILTER_ADOBE_PPKLITE );
signature.setSubFilter( PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setReason( "Test" );
signature.setSignDate(Calendar.getInstance());
Rectangle2D humanRect = new Rectangle2D.Float();
humanRect.setFrame(new Point2D.Float(pos_X, pos_Y), new Dimension((int) sqr_width, (int) sqr_height));
PDRectangle rect = createSignatureRectangle(doc, humanRect);
InputStream template = createVisualSignatureTemplate( doc, page, rect, signature, certificateBody ); // Implementation defined below.
SignatureOptions options = new SignatureOptions();
options.setVisualSignature( template );
options.setPage(0);
options.setPreferredSignatureSize(9000);
doc.addSignature( signature, options );
FileOutputStream outputStream = new FileOutputStream(filePath);
ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(outputStream);
byte[] input = IOUtils.toByteArray(externalSigning.getContent());
byte[] hash = MessageDigest.getInstance("SHA-256").digest(input);
String digest = new String(Base64.getEncoder().encode(hash));
System.out.println("Before signing: " + new String(Base64.getEncoder().encode(hash)));
byte[] signedContent3 = Files.readAllBytes(Paths.get(filePath));
byte[] hash3 = MessageDigest.getInstance("SHA-256").digest(signedContent3);
System.out.println("File OS hash: " + new String(Base64.getEncoder().encode(hash3)));
SigFinalizeOutput signedDoc = signDocument(certificateBody.getCertAlias(), "test_pdf", digest, "test", totpID, totp);
String base64HashSig = signedDoc.getSignedDocsInfo().get(0).getHashSig();
byte[] cmsSignature = sign(externalSigning.getContent(), base64HashSig);
externalSigning.setSignature(cmsSignature);```
and the sign method:
public byte[] sign(InputStream content, String signedHash) throws IOException {
// cannot be done private (interface)
try {
GetCertificatebyTotpIDOutput certificateBody= getCertificate();
String base64CertificateString = certificateBody.getCert_64();
// byte[] certificateBytes = Base64.getDecoder().decode(base64CertificateString);
// CertificateFactory cf = CertificateFactory.getInstance(“X.509”);
// X509Certificate certificate = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certificateBytes));
byte[] decoded = Base64.getDecoder().decode(base64CertificateString);
Collection collection = null;
try {
collection = CertificateFactory.getInstance("X.509").generateCertificates(new ByteArrayInputStream(decoded));
} catch (CertificateException e) {
}
Certificate[] certChain = new Certificate[collection.size()];
Iterator iterator = collection.iterator();
int i = 0;
while (iterator.hasNext()) {
certChain[i++] = (Certificate) iterator.next();
}
// Certificate chain is acquired at initialization
List<Certificate> certList = new ArrayList<>();
for (Certificate cert : certChain) {
certList.add(cert);
}
Store certStore= new JcaCertStore(certList);
org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate.getInstance(certChain[0].getEncoded());
X509Certificate signerCert = (X509Certificate) certChain[0];
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
gen.addCertificates(certStore);
byte[] input = IOUtils.toByteArray(content);
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(input);
System.out.println("During signing: " + new String(Base64.getEncoder().encode(messageDigest.digest())));
Attribute attr = new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(messageDigest.digest())));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
ContentSigner contentSigner = new ContentSigner() {
@Override
public byte[] getSignature() {
System.out.println(String.valueOf(Base64.getDecoder().decode(signedHash)));
return Base64.getDecoder().decode(signedHash);
}
@Override
public OutputStream getOutputStream() {
return new ByteArrayOutputStream() ;
}
@Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WITHDSA");
}
};
SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
gen.addSignerInfoGenerator(builder.build(contentSigner, new X509CertificateHolder(cert)));
//gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(contentSigner, signerCert));
CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
CMSSignedData cmsSignedData = gen.generate(msg, true);
byte[] result =cmsSignedData.getEncoded();
return result;
} catch (GeneralSecurityException | CMSException | OperatorCreationException e) {
throw new IOException(e);
}
}
i tried calculate the byte range but then i saw that externalsigning.getcontent() already retrives only the value from the pdf without the signature object.
hope some one can help me on at least what to check next.
dmb is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.