如何在分布式系统中使用 ESP 实现两阶段数字签名过程?

问题描述 投票:0回答:1

我正在使用 ESP(电子签名提供商)实施一个两阶段的数字签名流程来对文档进行数字签名。过程如下:

第一阶段:

  • 生成文档的哈希值。
  • 使用 ESP 的私钥加密哈希。
  • 创建重定向到 ESP 身份验证页面的签名请求(此处发生基于 UIDAI 的身份验证)。

第二阶段:

从 ESP 提供的另一个端点接收签名字节。 我面临以下挑战:

保持 PdfSignatureAppearance 对象处于活动状态

由于签名分两个阶段进行,因此我需要保持 PdfSignatureAppearance 对象处于活动状态,直到签名完成。

证书插入问题

尝试从 ESP 响应插入证书时,在 adobe 中签入时签名变得无效

Invalid signature: Document is modified."

此外,我想以适合分布式系统的方式编写此服务。

生成哈希的代码:

public String pdfSigner(File file, HttpServletRequest request, HttpSession session) {
    String hashDocument = null;
    PdfReader reader;
    try {
        String sourcefile = file.getAbsolutePath();
        System.out.println("Path--->" + sourcefile);
        destFile = sourcefile.replace(file.getName(), "Signed_Pdf.pdf");
        request.getSession().setAttribute("fileName", "Signed_Pdf.pdf");
        reader = new PdfReader(sourcefile);

        Rectangle cropBox = reader.getCropBox(1);
        Rectangle rectangle = null;
        String user = null;
        rectangle = new Rectangle(cropBox.getLeft(), cropBox.getBottom(), cropBox.getLeft(100), cropBox.getBottom(90));
        fout = new FileOutputStream(destFile);
        PdfStamper stamper = PdfStamper.createSignature(reader, fout, '\0', null, true);

        appearance = stamper.getSignatureAppearance();
        appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
        appearance.setAcro6Layers(false);
        Font font = new Font();
        font.setSize(6);
        font.setFamily("Helvetica");
        font.setStyle("italic");
        appearance.setLayer2Font(font);
        Calendar currentDat = Calendar.getInstance();
        System.out.println("remove 5 min");
        currentDat.add(currentDat.MINUTE, 5); //Adding Delta Time of 5 Minutes....

        appearance.setSignDate(currentDat);

        if (user == null || user == "null" || user.equals(null) || user.equals("null")) {
            appearance.setLayer2Text("Signed");
        } else {
            appearance.setLayer2Text("Signed by " + user);
        }
        appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);

        appearance.setImage(null);
        //      appearance.setSignDate(currentDat);
        appearance.setVisibleSignature(rectangle,
                reader.getNumberOfPages(), null);

        int contentEstimated = 8192;
        HashMap<PdfName, Integer> exc = new HashMap();
        exc.put(PdfName.CONTENTS, contentEstimated * 2 + 2);

        PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE,
                PdfName.ADBE_PKCS7_DETACHED);
        dic.setReason(appearance.getReason());
        dic.setLocation(appearance.getLocation());
        dic.setDate(new PdfDate(appearance.getSignDate()));


        appearance.setCryptoDictionary(dic);
        //  request.getSession().setAttribute("pdfHash",appearance);
        appearance.preClose(exc);
        // fout.close();
        request.getSession().setAttribute("appearance", appearance);
        // System.gc();
        // getting bytes of file
        InputStream is = appearance.getRangeStream();

        hashDocument = DigestUtils.sha256Hex(is);
        //session=request.getSession();
        //session.setAttribute("appearance1",appearance);
        System.out.println("hex:    " + is.toString());
    } catch (Exception e) {
        System.out.println("Error in signing doc.");

    }
    return hashDocument;

}

嵌入签名代码:

public String signPdfwithDS(String response, HttpServletRequest request, HttpSession session) {
    int contentEstimated = 8192;
    try {
      
        String errorCode = response.substring(response.indexOf("errCode"), response.indexOf("errMsg"));
        errorCode = errorCode.trim();
        if (errorCode.contains("NA")) {

            String pkcsResponse = xmlSigning.parseXml(response.trim());
            byte[] sigbytes = Base64.decodeBase64(pkcsResponse);
            byte[] paddedSig = new byte[contentEstimated];
            System.arraycopy(sigbytes, 0, paddedSig, 0, sigbytes.length);
            PdfDictionary dic2 = new PdfDictionary();
            dic2.put(PdfName.CONTENTS,
                    new PdfString(paddedSig).setHexWriting(true));
            //fout.close();
            appearance.close(dic2);
        } else {
            destFile = "Error";
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("Hash after signing :" + calculateSha256Hash(destFile));
    return destFile;
}

插入证书代码:

String certString = response.substring(response.indexOf(User_X509_START_TAG), response.indexOf(User_X509_END_TAG))
        .replaceAll(User_X509_START_TAG, "").replaceAll(User_X509_END_TAG, "");
byte[] certBytes = Base64.decodeBase64(certString);
ByteArrayInputStream stream = new ByteArrayInputStream(certBytes);
CertificateFactory factory = CertificateFactory.getInstance(X_509);
Certificate cert = factory.generateCertificate(stream);
List<Certificate> certificates = List.of(cert);
appearance.setCrypto(null, certificates.toArray(new Certificate[0]), null, null);

生成的 PDF 没有证书

使用证书生成的Pdf,这里我没有在第一个函数中创建占位符,而是在嵌入签名时在第二个函数中创建了它。

java itext digital-signature itext5
1个回答
0
投票

分析示例文件

(对引用的评论做出反应)

请找到添加的 pdf 链接

现在链接可以使用了。

  • 1728913398584nxHAYXdXhs.pdf - 此处签名文档字节的哈希值不正确。
  • sign2.pdf - 签名字节的哈希值在这里是正确的。

之前我在生成哈希后创建签名占位符(在signPdfwithDS函数中),因为我想支持分布式系统并且不想在服务器上存储文件,但签名无效

这是行不通的:如果你想要任何像样的 PDF 签名验证器来积极验证你的签名,你需要对准备好的 PDF(带有包含占位符的签名字典)进行哈希处理,而不是其他东西,因为验证器期望签名的字节范围完全包含所有内容除了占位符本身。

然后转到上面的代码,现在签名有效,但用户详细信息不存在(没有证书pdf)也不支持分布式系统

是的,原则上这就是您必须如何实施签名的方式。有一些选项可以使其在分布式系统中更好地工作,请参见下文。

但是,如果您希望签名外观包含用户详细信息,则必须在签名前的准备阶段了解这些用户详细信息。如果从签名中检索它们是您唯一的选择,您可能必须创建两个签名:第一个用于虚拟数据,用于从签名中提取用户详细信息,第二个用于使用这些用户详细信息准备的 PDF。

使其在分布式系统中工作

有两种替代方法可以替代保持 PdfSignatureAppearance 对象处于活动状态。而不是在会话中保持该对象处于活动状态:

  • 您可以重复PDF准备步骤;即,在步骤 1 中准备好用于签名的 PDF 并计算其哈希值后,您就可以扔掉准备好的 PDF。在第 2 步中,您再次从原始 PDF 开始,准备它,并将签名注入其中。

    这里的挑战是确保两个准备好的 PDF 在字节方面是相同的。这要求您在执行此操作时使用相同的参数,不仅是您已控制的相同显式参数,还包括相同的隐式参数,例如系统时间(存储在 PDF 的元数据中,因此是签名数据的一部分) ) 和修订 ID。如果是加密的 PDF,您甚至可能必须控制随机数生成器种子值,具体取决于所使用的加密算法。

    执行此操作有点复杂,需要您设置内部 iText 值(通过内省/反射或修补 iText。尽管如此,一些签名服务会这样做,例如 EU eSig DSS 示例实现。

  • 您可以使用一些虚拟值对第一步中准备好的 PDF 进行“签名”,并将此虚拟签名的 PDF 与哈希值返回给调用者。在第二步中,调用者需要向您发送虚拟签名的 PDF 和哈希签名,然后您只需用提供的签名替换虚拟签名的 PDF 中的虚拟值即可。

    您已标记您的问题,所以我假设您使用当前的 iText 5 版本,即 5.5.x 版本,即使您的代码仅使用 5.3.x 之前的功能。

    iText 5.5.x 包含一个显式方法来替换 PDF 最外层签名的签名值:

    MakeSignature.signDeferred
    :

      /**
       * Signs a PDF where space was already reserved.
       * @param reader the original PDF
       * @param fieldName the field to sign. It must be the last field
       * @param outs the output PDF
       * @param externalSignatureContainer the signature container doing the actual signing. Only the 
       * method ExternalSignatureContainer.sign is used
       * @throws DocumentException
       * @throws IOException
       * @throws GeneralSecurityException 
       */
      public static void signDeferred(PdfReader reader, String fieldName, OutputStream outs, ExternalSignatureContainer externalSignatureContainer) throws DocumentException, IOException, GeneralSecurityException {
    

    reader
    必须包含虚拟签名的PDF,
    fieldName
    需要是您创建的签名字段的名称,
    externalSignatureContainer
    可以是简单的
    ExternalSignatureContainer
    实现的实例,其
    sign
    方法仅返回提供的签名,其
    modifySigningDictionary
    方法不执行任何操作。

© www.soinside.com 2019 - 2024. All rights reserved.