如何使用 itext7 将外部生成的签名应用于 PDF?
How do I apply an externally generated signature to a PDF using itext7?
我正在尝试将第三方 AATL 服务生成的签名应用于 PDF。
我觉得我几乎几乎正确地调用了所有内容,但有点不足为奇的是,当我查看已签名的文档时,我被告知签名无效。在这个过程中有很多步骤可能会出错,而且在我学习的过程中,这种绊脚石是意料之中的。
无论如何,我正在寻找一些指导并填补我的知识空白。在我的代码之前,我将列出我认为我需要回答的问题,您也可能会在代码的注释中看到它们:
- 看起来我使用的摘要来源是否正确?即
data
?
- 如果来源正确,我是否使用了正确的技术来生成摘要?即,
DigestAlgorithms.digest(...)
?
- 我调用的第三方服务签名 returns 是 base64 编码的。在将表示形式返回为
ByteArray
之前,我是否需要将其从 base64 转换为其他格式?
- 我知道我必须以某种方式使用他们提供给我的证书,我只是不确定在哪里或如何使用?
- 签名者是什么
fieldName
?
- 如何为
signer.signExternalContainer
的 estimatedSize
参数使用的 8192
确定正确的值?
- 如何向 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 裸签名字节.
因此,您应该改为实施 IExternalSignature
的 sign
方法,预计 return 裸签名字节。要签名,您将使用 signer.signDetached
重载而不是 signer.signExternalContainer
.
这也意味着您问题的答案如何将 CRL/OCSP 信息添加到 PDF - signDetached
重载具有 ICrlClient
和 IOcspClient
参数,它们也可以提供 CRL 和 OCSP 响应以嵌入。
我正在尝试将第三方 AATL 服务生成的签名应用于 PDF。
我觉得我几乎几乎正确地调用了所有内容,但有点不足为奇的是,当我查看已签名的文档时,我被告知签名无效。在这个过程中有很多步骤可能会出错,而且在我学习的过程中,这种绊脚石是意料之中的。
无论如何,我正在寻找一些指导并填补我的知识空白。在我的代码之前,我将列出我认为我需要回答的问题,您也可能会在代码的注释中看到它们:
- 看起来我使用的摘要来源是否正确?即
data
? - 如果来源正确,我是否使用了正确的技术来生成摘要?即,
DigestAlgorithms.digest(...)
? - 我调用的第三方服务签名 returns 是 base64 编码的。在将表示形式返回为
ByteArray
之前,我是否需要将其从 base64 转换为其他格式? - 我知道我必须以某种方式使用他们提供给我的证书,我只是不确定在哪里或如何使用?
- 签名者是什么
fieldName
? - 如何为
signer.signExternalContainer
的estimatedSize
参数使用的8192
确定正确的值? - 如何向 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 裸签名字节.
因此,您应该改为实施 IExternalSignature
的 sign
方法,预计 return 裸签名字节。要签名,您将使用 signer.signDetached
重载而不是 signer.signExternalContainer
.
这也意味着您问题的答案如何将 CRL/OCSP 信息添加到 PDF - signDetached
重载具有 ICrlClient
和 IOcspClient
参数,它们也可以提供 CRL 和 OCSP 响应以嵌入。