如何使用 itext7 将外部生成的签名应用于 PDF?

How do I apply an externally generated signature to a PDF using itext7?

我正在尝试将第三方 AATL 服务生成的签名应用于 PDF。

我觉得我几乎几乎正确地调用了所有内容,但有点不足为奇的是,当我查看已签名的文档时,我被告知签名无效。在这个过程中有很多步骤可能会出错,而且在我学习的过程中,这种绊脚石是意料之中的。

无论如何,我正在寻找一些指导并填补我的知识空白。在我的代码之前,我将列出我认为我需要回答的问题,您也可能会在代码的注释中看到它们:

  1. 看起来我使用的摘要来源是否正确?即data?
  2. 如果来源正确,我是否使用了正确的技术来生成摘要?即,DigestAlgorithms.digest(...)?
  3. 我调用的第三方服务签名 returns 是 base64 编码的。在将表示形式返回为 ByteArray 之前,我是否需要将其从 base64 转换为其他格式?
  4. 我知道我必须以某种方式使用他们提供给我的证书,我只是不确定在哪里或如何使用?
  5. 签名者是什么fieldName
  6. 如何为 signer.signExternalContainerestimatedSize 参数使用的 8192 确定正确的值?
  7. 如何向 PDF 添加 CRL/OCSP 信息?使用第三方 AATL 签名服务时,它通常来自哪里?

请随时指出上面列表中没有的任何其他建议或错误!

data class ThirdPartyCertificateResponse(val certs: List<String>)
data class ThirdPartySigningResponse(val sig: String, val nonce: String)

class ThirdPartySignatureContainer : IExternalSignatureContainer {

    private lateinit var data: ByteArray

    override fun sign(data: InputStream): ByteArray {

        // note: I've omitted any validation of the nonce from this sample.
        val nonce = UUID.randomUUID()
        val digest = DigestAlgorithms.digest(data, BouncyCastleDigest().getMessageDigest("sha256"))

        // note: The service I'm calling expects requests to look like this.
        //       I'm including this on the offchance that I'm accidentally 
        //       corrupting any of the data that I'm preparing for them.
        val bodyJson = JWSObject(
            JWSHeader(JWSAlgorithm.HS256),
            Payload(
                mapOf<String, Any>(
                    // note: `digestInfo` is an extension method that returns an `org.bouncycastle.asn1.x509.DigestInfo` instance from a `ByteArray`
                    // note: Would love a tool that generates digests for me to check mine against! Hard to know if I'm doing the right thing here by basing it off of `data`??
                    "digestInfo" to digest.digestInfo().toBase64String(),
                    "nonce" to nonce.toString(),
                    "version" to 1,
                )
            )
        )

        bodyJson.sign(MACSigner("SHARED_SECRET"))

        val (_, _, signatureResult) = "https://thidpartyservice.notreal/api/v1/signatures"
            .httpPost()
            .body(bodyJson.serialize())
            .responseObject<ThirdPartySigningResponse>()

        val signatureText = when (signatureResult) {
            is Result.Failure -> throw signatureResult.getException()
            is Result.Success -> signatureResult.value.sig
        }

        return signatureText.decodeBase64()
            ?: throw Exception("Unable to decode response from third party service")
    }

    override fun modifySigningDictionary(dictionary: PdfDictionary) {

        val (_, _, certificateResult) = "https://thidpartyservice.notreal/api/v1/certs"
            .httpGet()
            .responseObject<ThirdPartyCertificateResponse>()

        val certificateFactory = CertificateFactory.getInstance("x.509")

        // note: I have no idea what to do with these, but I've got them!
        val certificateChain = when (certificateResult) {
            is Result.Failure -> throw certificateResult.getException()
            is Result.Success -> certificateResult.value.certs.map {
                certificateFactory.generateCertificate(it.decodeBase64()?.inputStream())
            }
        }

        // note: What are these doing?
        dictionary.put(PdfName.Filter, PdfName.Adobe_PPKLite)
        dictionary.put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached)

        // note: I feel like I should be adding the certificate(s) to `dictionary` here...?
    }
}

上面的class被实例化并从代码中调用如下:

val stampingProperties = StampingProperties()
val signer = PdfSigner(thisPdfReader, thisSigned.outputStream(), stampingProperties)
// note: What's this?
signer.fieldName = null

signer.signatureAppearance
    .setPageRect(Rectangle(0.0f, 0.0f, 0.0f, 0.0f))
    .setPageNumber(1)

val container: IExternalSignatureContainer = ThirdPartySignatureContainer()

// note: I don't know how to determine a correct value for it, so I've left 8192 in here from examples I've seen.
signer.signExternalContainer(container, 8192)

This is a sample of a PDF 我目前正在生成。

您的 class ThirdPartySignatureContainer 工具 IExternalSignatureContainer;因此,它的 sign 方法预计会 return 一个 CMS 签名容器 以原样嵌入到 PDF 中。但是,检查 your example file,很明显您的远程签名服务 - 以及您的 sign 方法 - returns 裸签名字节.

因此,您应该改为实施 IExternalSignaturesign 方法,预计 return 裸签名字节。要签名,您将使用 signer.signDetached 重载而不是 signer.signExternalContainer.

这也意味着您问题的答案如何将 CRL/OCSP 信息添加到 PDF - signDetached 重载具有 ICrlClientIOcspClient 参数,它们也可以提供 CRL 和 OCSP 响应以嵌入。