Reputation: 11
I try to sign pdf document using itext7, certificate and external signature returned from external web service say Sign Service:
I did the following steps:
Got the orginal pdf, added last page (sign page) with 2 signatures filelds on it and created temp pdf
Calculated hash from created temp pdf
Exchanhed with Sign Service my Base64 encoded hash with encoded Base64 signed hash (I'm not sure is this raw or CMS signature - I treat it as CMS container)
Decoded and put obtained signed hash along with certificate from Sign Company to one of my Sig field on temp pdf file. I will need sign subseqent field/fields in this way in the furure.
Unfortunately i got validation errors in Adobe Reader: “the document has been altered or corrupted since the signature was applied”: link to Adobe validation result
Below the code fragment where I create sign page:
private void createPdfDocument(Document doc, int iteration) {
//Add last sign page to doc
doc.add(new AreaBreak(AreaBreakType.LAST_PAGE));
doc.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
PdfPage lastPage = doc.getPdfDocument().getLastPage();
float width = lastPage.getPageSize().getWidth();
float height = lastPage.getPageSize().getHeight();
createTitle(doc);
PdfAcroForm form = PdfAcroForm.getAcroForm(doc.getPdfDocument(), true);
for (int i = 1; i <= iteration; i++) {
addSignArea(doc, form, VERTICAL_RECTANGLE_START - (i - 1) * VERTICAL_MARGIN,
VERTICAL_TEXT_START - (i - 1) * VERTICAL_MARGIN, i);
}
System.out.println("Creating sign page finished");
}
private void addSignArea(Document doc, PdfAcroForm form, int verticalRectPosition, int verticalFieldPosition, int iteration) {
Color color = new DeviceRgb(46, 66, 148);
//Create sign area frame
new PdfCanvas(doc.getPdfDocument().getLastPage())
.roundRectangle(50, verticalRectPosition, 495, 50, 5)
.setLineWidth(0.5f)
.setStrokeColor(color)
.stroke();
//Create text fields inside frame
PdfSignatureFormField signField = PdfSignatureFormField.createSignature(doc.getPdfDocument(),
new Rectangle(50, verticalRectPosition, 495, 50));
signField.setFieldName(getFieldCountedName("Signature", iteration));
form.addField(signField);
}
I calculate document hash that way:
public String getDocumentHash() {
try (FileInputStream is = new FileInputStream(DOC)) {
byte[] hash = DigestAlgorithms.digest(is, DigestAlgorithms.SHA256, null);
String encodeToString = Base64.getEncoder().encodeToString(hash);
System.out.println(encodeToString);
return encodeToString;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
And finally sign pdf file:
public class DocumentSigner {
public static final String DEST = "";
private static final String SOURCE = "";
private static final String DOC_HASH = "6XsoKhEXVMu8e0R7BGtaKvghwL0GBrqTGAivFpct6J4=";
public static final String[] RESULT_FILES = new String[]{
"sign_doc_result1.pdf"
};
public static void main(String[] args) throws GeneralSecurityException, IOException {
File file = new File(DEST);
file.mkdirs();
Certificate[] chain = new Certificate[1];
chain[0] = CertLoadTest.getPublicCert(); //load cert from path
String encodedExternalHash = getExternalSignedHash(); //get the signded hash returned from the Sign Service
new DocumentSigner().sign(SOURCE, DEST + RESULT_FILES[0], chain, PdfSigner.CryptoStandard.CMS,
encodedExternalHash, DOC_HASH, "Signature1");
}
public void sign(String src, String dest, Certificate[] chain, PdfSigner.CryptoStandard subfilter,
String encodedExternalHash, String documentHash, String fieldName) throws GeneralSecurityException, IOException {
try (FileOutputStream os = new FileOutputStream(dest); InputStream is = new FileInputStream(src)) {
PdfReader reader = new PdfReader(is);
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
signer.setFieldName(fieldName);
IExternalDigest digest = new BouncyCastleDigest();
IExternalSignature signature = new CustomSignature(Base64.getDecoder().decode(encodedExternalHash),
Base64.getDecoder().decode(documentHash), chain);
signer.signDetached(digest, signature, chain, null, null, null,
8096, subfilter);
}
}
public class CustomSignature implements IExternalSignature {
private byte[] signedHash;
private byte[] documentHash;
private Certificate[] chain;
public CustomSignature(byte[] signedHash, byte[] documentHash, Certificate[] chain) {
this.signedHash = signedHash;
this.documentHash = documentHash;
this.chain = chain;
}
public String getHashAlgorithm() {
return DigestAlgorithms.SHA256;
}
public String getEncryptionAlgorithm() {
return "RSA";
}
public byte[] sign(byte[] message) throws GeneralSecurityException {
return signedHash;
}
}
private static String getExternalSignedHash() {
//mocked Sign Service result - documentHash is exchanged with signedHash
return "3BLqVMOLSFXEfCy++n0DmRqcfCGCqSLy9Nzpn1IpAn6iTqr+h78+yOomGMAL0La77IB08Tou9gkxbwSXPHrdN5+EPm7HCXeI/z3fzj711H9OH6P9tWtVHgieKUFOVhrm/PTeypSC/vy7RJQLNmL5+/+Moby5Bdo/KaaN2h9Jj41w1i6CwL/7wzCZ0h+AU9sI+IC0i/UbWFFz7VMfN5barcF1vP+ECLiX3qtZrGbFZNZfrr+28ytNTdUR4iZJRLKL2nXeg0CqxsTjnAyUsFMTCro1qv0QkQO8Cv6AJFhWlUFGUkt+pIUKhIticlypB+WdzwmISOsRK0IUiKgrJI6E3g==";
}
A also tried to treat returned from Sign Service hash as a raw signature - this is what sign method in CustomSignature class looks like then:
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, digest, false);
byte[] sh = sgn.getAuthenticatedAttributeBytes(documentHash, PdfSigner.CryptoStandard.CMS, null, null);
sgn.setExternalDigest(signedHash, null, "RSA");
byte[] encodedSig = sgn.getEncodedPKCS7(documentHash, PdfSigner.CryptoStandard.CMS, null, null, null);
return encodedSig;
But in this case i get formmatting signature errors in Adobe Reader
Is my flow correct or maybe i need another approach to properly sign document.
Upvotes: 0
Views: 971
Reputation: 11
According to advice posted in the comment, I still use custom IExternalSignature
implementation with external call in sign method:
public void sign(Certificate[] chain, PdfSigner.CryptoStandard subfilter, String fieldName) throws GeneralSecurityException, IOException {
try (InputStream is = new FileInputStream(src); FileOutputStream os = new FileOutputStream(dest)) {
PdfReader reader = new PdfReader(is);
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
signer.setFieldName(fieldName); //My signature fields
IExternalDigest digest = new BouncyCastleDigest();
IExternalSignature signature = new CustomSignature(chain);
signer.signDetached(digest, signature, chain, null, null, null,
8196, subfilter);
}
}
public class CustomSignature implements IExternalSignature {
private Certificate[] chain;
public CustomSignature(Certificate[] chain) {
this.chain = chain;
}
public String getHashAlgorithm() {
return DigestAlgorithms.SHA256;
}
public String getEncryptionAlgorithm() {
return "RSA";
}
public byte[] sign(byte[] message) throws GeneralSecurityException {
BouncyCastleDigest digest = new BouncyCastleDigest();
byte[] hash = digest.getMessageDigest("SHA256").digest(message);
return Base64.getDecoder().decode(client.getSignedHash(Base64.getEncoder().encodeToString(hash))); // call externall service here
}
}
And for the first call validation error disappeared, Signature1 seems to be ok, but a problem occurred when I tried to sign second sig field using pdf generetaed in first call and takes another file as output. Now newly created Signature2 is ok, but the first one failed with a broken byte range:
new DocumentSigner(SOURCE, DEST1).sign(chain,PdfSigner.CryptoStandard.CMS, "Signature1");
new DocumentSigner(DEST1, DEST2).sign(chain, PdfSigner.CryptoStandard.CMS, "Signature2");
I will be grateful for any ideas what can I do to sign multiple fields without broke previous ones
Here is Adobe validation output after the second call
UPDATE:
I used append mode on PdfSigner's StampingProperties
and now everything is ok:
StampingProperties stampingProperties = new StampingProperties();
stampingProperties.useAppendMode();
PdfSigner signer = new PdfSigner(reader, os, stampingProperties);
Upvotes: 1