简体   繁体   中英

Itext sign pdf with external signature causes validation fail (“the document has been altered or corrupted..")

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:

  1. Got the orginal pdf, added last page (sign page) with 2 signatures filelds on it and created temp pdf

  2. Calculated hash from created temp pdf

  3. 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)

  4. 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.

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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM