当前位置: 首页 > 知识库问答 >
问题:

iText 5-在数字签名文档中添加空签名字段,但不破坏签名

松旻
2023-03-14

我正在尝试向现有的数字签名pdf(认证签名)中添加一个空签名字段。

我有一个工作流,其中许多用户将签署该文档(批准签名),该文档创建时带有“n”个空签名字段,每个用户一个,我们的应用程序首先应用一个不可见的认证签名,然后每个用户可以在各自的字段中签署该文档,但由于工作流中意外的更改,其他用户可能希望签名,因此,我们希望添加相应的空签名字段,然后应用签名。

我试图将空字段(带有单元格事件的表)添加到认证文档中,但当我想添加它并关联字段时,它会破坏签名,我无法使其正常工作。

以下是用于签名、添加签名字段和设置签名字段选项的方法。我不知道我做错了什么。

public static String sign(SignRequest signRequest, File certificate, File unsignedDocument, File image, File icon)
        throws FileNotFoundException, IOException, DocumentException, StreamParsingException, OCSPException,
        OperatorException, URISyntaxException, WriterException, GeneralSecurityException, FontFormatException {

    SignatureType sigType = Optional
            .ofNullable(SignatureType.get(signRequest.getSignatureProperties().getSignatureType()))
            .orElse(SignatureType.APPROVAL_SIGNATURE);

    File signedDocument = File.createTempFile("signed",".pdf");
    char[] pass = signRequest.getKeyStore().getPassword().toCharArray();

    // Load certificate chain
    BouncyCastleProvider provider = new BouncyCastleProvider();
    Security.addProvider(provider);
    KeyStore ks = KeyStore.getInstance("PKCS12", provider.getName());
    ks.load(new FileInputStream(certificate.getAbsolutePath()), pass);

    String alias = getAliasFromKeyStore(ks);
    PrivateKey pk = (PrivateKey) ks.getKey(alias, pass);
    Certificate[] chain = ks.getCertificateChain(alias);

    // Creating the reader and the stamper
    PdfReader reader = new PdfReader(FileUtils.openInputStream(unsignedDocument));
    FileOutputStream os = new FileOutputStream(signedDocument);
    PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
    PdfSignatureAppearance appearance = null;

    // Certify o approval signature (approval is the default signature type)
    switch (sigType) {
    case CERTIFY_SIGNATURE:
        if (reader.getAcroFields().getSignatureNames().size() <= 0) {
            appearance = setSignatureFieldOptions(stamper.getSignatureAppearance(), reader, chain,
                    signRequest, image, icon, Boolean.TRUE);
        } else {
            appearance = setSignatureFieldOptions(stamper.getSignatureAppearance(), reader, chain,
                    signRequest, image, icon, Boolean.FALSE);
        }
        break;
    case APPROVAL_SIGNATURE:
    default:
        appearance = setSignatureFieldOptions(stamper.getSignatureAppearance(), reader, chain, signRequest,
                image, icon, Boolean.FALSE);
        break;
    }

    // Adding LTV (optional)
    OcspClient ocspClient = null;
    List<CrlClient> crlList = null;
    if (signRequest.getSignatureProperties().getLtv() == Boolean.TRUE) {
        ocspClient = new OcspClientBouncyCastle(new OCSPVerifier(null, null));
        CrlClient crlClient = new CrlClientOnline(chain);
        crlList = new ArrayList<CrlClient>();
        crlList.add(crlClient);
    }

    // Adding timestamp (optional)
    TSAClient tsaClient = null;
    if (signRequest.getTimestamp() != null
            && StringUtils.isNotBlank(signRequest.getTimestamp().getUrl())) {
        tsaClient = new TSAClientBouncyCastle(signRequest.getTimestamp().getUrl(),
                signRequest.getTimestamp().getUser(), signRequest.getTimestamp().getPassword());
    }

    // Creating the signature
    ExternalSignature pks = new PrivateKeySignature(pk, signtRequest.getSignatureProperties().getAlgorithm(),
            provider.getName());
    ExternalDigest digest = new BouncyCastleDigest();

    MakeSignature
            .signDetached(appearance, digest, pks, chain, crlList, ocspClient, tsaClient,
                    calculateEstimatedSize(chain, ocspClient, tsaClient, crlList, getEstimatedSizeBonus()), CryptoStandard.CMS);

    return signedDocument.getAbsolutePath();
}


private static PdfSignatureAppearance setSignatureFieldOptions(PdfSignatureAppearance appearance, PdfReader reader, Certificate[] chain, SignRequest signRequest, File image, File icon, Boolean certifySignature) throws MalformedURLException, IOException, DocumentException {
    SignatureProperties sigProperties = signRequest.getSignatureProperties();
    SignatureField sigField = sigProperties.getSignatureField();

    // Creating the appearance
    appearance.setSignatureCreator(Constant.SIGNATURE_CREATOR);
    Optional.ofNullable(sigProperties.getReason()).ifPresent(appearance::setReason);
    Optional.ofNullable(sigProperties.getLocation()).ifPresent(appearance::setLocation);

    if (certifySignature) {
        appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS);
    } else {
        appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
    }


    /**
     * Signature Field Name
     */
    BoundingBox box = sigProperties.getSignatureField().getBoundingBox();
    String fieldName = sigField.getName();
    int pageNumber = sigProperties.getSignatureField().getPage();

    if (!sigField.isVisible()) {
        if (StringUtils.isBlank(sigField.getName())) {
            fieldName = generateFieldName();
            appearance.setVisibleSignature(new Rectangle(0, 0, 0, 0), pageNumber, fieldName);
        } else {
            appearance.setVisibleSignature(new Rectangle(0, 0, 0, 0), pageNumber, fieldName);
        }
    } else {
        Font font = FontFactory.getFont(Optional.ofNullable(sigField.getFontName()).orElse(BaseFont.HELVETICA),
                Optional.ofNullable(sigField.getFontSize()).orElse(6));
        Rectangle rect = null;
        FieldPosition fieldPosition = null;
        
         //ADD EMPTY FIELD
        if (StringUtils.isBlank(sigField.getName()) && box != null) {
            fieldName = generateFieldName();
            rect = new Rectangle(box.getLowerLeftX(), box.getLowerLeftY(), box.getLowerLeftX() + box.getWidth(),
                    box.getLowerLeftY() + box.getHeight());
            appearance.setVisibleSignature(rect, pageNumber, fieldName);

            ////////////////////////////////// TRY TO ADD EXTRA SIGNATURE FIELD///////////////////////////////////////////
            Rectangle documentRectangle = reader.getPageSize(pageNumber);
            PdfStamper stamper = appearance.getStamper();

            float pageMargin = 10;
            float tableMargin = 15;
            int numberOfFields = 1; // 1 sigField

            float headerWidth = (documentRectangle.getWidth() - (pageMargin * 2));

            // Table with signature field
            PdfPTable table = new PdfPTable(1);
            table.setTotalWidth(headerWidth - (tableMargin * 4));
            table.setLockedWidth(Boolean.TRUE);
            table.setWidthPercentage(100);
            float posXTable = (pageMargin + (headerWidth - table.getTotalWidth()) / 2);
            float posYTable = 400; // custom y position
            int height = 70; // custom height

            for (int i = 0; i < numberOfFields; i++) {
                String sigFieldName = String.format(Constant.SIGNATURE_FIELD_PREFIX + "%s", (i + 1));
                table.addCell(createSignatureFieldCell(stamper, sigFieldName, height, pageNumber));
            }
            table.writeSelectedRows(0, -1, posXTable, posYTable, stamper.getOverContent(pageNumber));

            ////////////////////////////////// END TRY TO ADD EXTRA SIGNATURE FIELD///////////////////////////////////////////

        } else {
            //APPLY SIGNATURE TO EXISTING EMPTY FIELD
            List<FieldPosition> acroFields = reader.getAcroFields().getFieldPositions(sigField.getName());

            fieldPosition = acroFields.get(0);
            appearance.setVisibleSignature(fieldName);
        }

        // --------------------------- Custom signature appearance ---------------------
        PdfTemplate t = appearance.getLayer(2);
        Rectangle sigRect = null;

        if (fieldPosition != null) {
            sigRect = fieldPosition.position;
        } else {
            sigRect = new Rectangle(box.getLowerLeftX(), box.getLowerLeftY(), box.getLowerLeftX() + box.getWidth(),
                    box.getLowerLeftY() + box.getHeight());
        }

        // Left rectangle
        Rectangle leftRect = new Rectangle(0, 0, (sigRect.getWidth() / 5), (sigRect.getHeight() / 2));
        ColumnText ct1 = new ColumnText(t);
        ct1.setSimpleColumn(leftRect);

        Image im1 = Image.getInstance(icon.getAbsolutePath());
        float ratio1 = leftRect.getHeight() / im1.getHeight();
        im1.scaleToFit(im1.getWidth() * ratio1, im1.getHeight() * ratio1);

        Paragraph p = createParagraph("Digital sign", font, Constant.PARAGRAPH_LEADING, Constant.MARGIN * 9);

        ct1.addElement(new Chunk(im1, Constant.MARGIN * 10, 0));
        ct1.addElement(p);
        ct1.go();

        // Middle rectangle
        Rectangle middleRect = new Rectangle((sigRect.getWidth() / 5), 0,
                (leftRect.getWidth() + sigRect.getWidth() / 5), (sigRect.getHeight() / 2));
        ColumnText ct2 = new ColumnText(t);
        ct2.setSimpleColumn(middleRect);

        if (visibleSignatureImage != null) {
            Image im2 = Image.getInstance(image.getAbsolutePath());
            float ratio2 = sigRect.getHeight() / im2.getHeight();
            im2.scaleToFit(im2.getWidth() * ratio2, im2.getHeight() * ratio2);
            ct2.addElement(new Chunk(im2, 0, 0));
            ct2.go();
        }

        // TextFields
        List<TextField> textFields = fillSignatureFieldText(chain, sigProperties, font);

        // Right rectangle - Names
        Rectangle rightRectNames = new Rectangle(
                (Constant.MARGIN * 5 + leftRect.getWidth() + middleRect.getWidth()), 0,
                (leftRect.getWidth() + middleRect.getWidth() + sigRect.getWidth() / 4),
                sigRect.getHeight() - Constant.MARGIN);
        ColumnText ct31 = new ColumnText(t);
        ct31.setSimpleColumn(rightRectNames);

        List<Paragraph> paragraphsNames = textFields.stream()
                .map(e -> createParagraph(e.getName(), font, Constant.PARAGRAPH_LEADING, 0))
                .collect(Collectors.toList());
        paragraphsNames.forEach(ct31::addElement);
        ct31.go();

        // Right rectangle - Values
        Rectangle rightRectValues = new Rectangle(
                (Constant.MARGIN * 4 + leftRect.getWidth() + middleRect.getWidth() + rightRectNames.getWidth()), 0,
                sigRect.getWidth(), (sigRect.getHeight() - Constant.MARGIN));
        ColumnText ct32 = new ColumnText(t);
        ct32.setSimpleColumn(rightRectValues);

        List<Paragraph> paragraphsValues = textFields.stream()
                .map(e -> createParagraph(e.getValue(), font, Constant.PARAGRAPH_LEADING, 0))
                .collect(Collectors.toList());
        paragraphsValues.forEach(ct32::addElement);
        ct32.go();
        // --------------------------- Custom signature appearance ---------------------
    }

    return appearance;
}



//this is used to first create the empty fields
protected static PdfPCell createSignatureFieldCell(PdfWriter writer, String name, int height) {
    PdfPCell cell = new PdfPCell();
    cell.setMinimumHeight(height);
    cell.setBackgroundColor(BaseColor.WHITE);
    PdfFormField field = PdfFormField.createSignature(writer);
    field.setFieldName(name);
    field.setFlags(PdfAnnotation.FLAGS_PRINT);
    cell.setCellEvent(new MySignatureFieldEvent(field, null, 0));
    return cell;
}

//this is used to try to add the extra empty field to signed document
protected static PdfPCell createSignatureFieldCell(PdfStamper stamper, String name, int height, int pageNumber) {
    PdfPCell cell = new PdfPCell();
    cell.setMinimumHeight(height);
    cell.setBackgroundColor(BaseColor.WHITE);
    
    
    PdfFormField field = PdfFormField.createSignature(stamper.getWriter());
    field.setFieldName(name);
    field.setFlags(PdfAnnotation.FLAGS_PRINT);
    cell.setCellEvent(new MySignatureFieldEvent(field, stamper, pageNumber));
    return cell;
}


public static class MySignatureFieldEvent implements PdfPCellEvent {

    public PdfFormField field;
    public PdfStamper stamper;
    public int pageField;

    public MySignatureFieldEvent(PdfFormField field, PdfStamper stamper, int pageField) {
        this.field = field;
        this.stamper = stamper;
        this.pageField = pageField;
    }

    public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
        PdfWriter writer = canvases[0].getPdfWriter();
        field.setPage();
        field.setWidget(position, PdfAnnotation.HIGHLIGHT_INVERT);

        if (stamper == null) {
            writer.addAnnotation(field);
        }else {
            stamper.addAnnotation(field, pageField);
        }
    }

}

共有1个答案

邵兴文
2023-03-14

首先,最初Adobe解释了此答案中描述的认证级别。特别是,如果文档具有证书签名,则禁止添加新字段,甚至是签名字段。

这种严格性似乎已经丢失,但在更新后的任何时候都可能再次适用,特别是在最近发布的针对pdf-insecurity.org的认证攻击利用了这种宽松的选择之后。

尽管如此,您永远不允许更改静态页面内容!但是,在代码中,可以通过向文档中添加一个表(包含事件)来添加其他签名字段。此表将更改静态页面内容。

因此,尝试只添加一个新的签名字段。

 类似资料:
  • 使用itext v5对文档进行数字签名时。5.11 PDF/A-2b文档被破坏——这意味着它们不再作为PDF/A文档有效。违反以下规则:https://github.com/veraPDF/veraPDF-validation-profiles/wiki/PDFA-Parts-2-and-3-rules#rule-643-1 在上面的链接中,它指定摘要无效,因此我也给你一个代码段,在使用iText

  • 问题内容: 在PDFBox 2.x中,我将字典放入签名字段: 然后,我签名区域: 除了我在Adobe Acrobat中打开签名的文档时,它抱怨文档的内容已更改,一切看起来都还不错。如果我不添加字典,一切都很好。 任何人都知道哪里错了吗? 问题答案: 问题在于PDFBox签名没有考虑 Lock 字典。 根据ISO 32000-1(以及类似的ISO 32000-2): 12.8.2.4 FieldMD

  • 我必须创建一个PDF文件,其中我使用PDFmake来实现这一点。但PDFmake不具备在文档中添加签名字段的功能。 因此,我打开了一个带有签名字段的示例PDF,并复制了包含签名字段名称的对象:

  • 我希望我们拥有iPhone的员工能够让公众对一份文件进行数字签名,该文件最初是基于Spring的网络表单。 此表格仅在我们的内部网上,仅由我们的员工填写。 到目前为止,我的研究考虑了一些可能的解决方案: 手机屏幕上的电子湿墨水签名 目的是证明某个人签署了一份文件。 1从技术上讲是可能的,但并不重要,因为它很容易被复制。 是否可以在iOS设备上通过浏览器和javascript捕获指纹? 3能适应这种

  • 我一直在网络和java教程中寻找这一点。但我没有澄清如何为文档生成数字签名。java教程对此进行了解释,但我真正想要的是 用户附带一个文件和一个字符串密钥 使用该密钥,文件被数字签名 相应的公钥,该标志与该文档一起发布 那么,如何转换给定的String私钥来做到这一点。在尝试java教程和web中给出的示例(带有将字节从字符串而不是文件中放置的一些变体)时,我遇到了如下异常

  • 对于一个关于签名数据被哈希两次的C#问题,我看到了一个类似的答复,但是我不知道为什么我的签名数据会出现在这里。 C#PKCS7 Smartchard数字签名损坏