iText 7 PDF 签名与 GlobalSign DSS

iText 7 PDF signing with GlobalSign DSS

我正在 asp.net 应用程序中使用 iText 7 对 PDF 进行数字签名。我正在使用 vb 但我也可以找到一个 c# 答案。我们正在使用 GlobalSign DSS 以使签名不会显示为未知 (AATL)。

我从我们公司的 SSL 证书创建了一个 pfx 文件,我可以使用该文件签署 PDF 文件,但我不明白如何使用我从 GlobalSign 获得的东西来完成同样的事情。此服务需要发送十六进制编码的 SHA256 摘要,并且在 return 中我收到一个十六进制编码的签名值。除此之外,我还收到一个 PEM 编码的 X509 签名证书、中间证书(代表签名证书颁发者的 PEM 编码的 X509 证书)、signing_certificate 的 OCSP 响应的 Base64 编码 DER 表示、一个 Base64 编码的 DER 表示时间戳令牌。它包括 SignedData.CertificatesSet 中的 TSA 签名证书。

我使用 itext 7 的有限示例以及我对 PDF 签名和证书的一般知识更为有限,尽我所能创建了下面的代码。我现在知道有些东西是我做的,但我什至没有正确使用,例如 tsaClient 和 crlList。在我不得不切换到 DSS 之前,我不太明白那些是什么。我实际上仍然不知道 crlList 是什么,尽管我认为它与链有关,尽管我不太清楚在这种情况下链是什么。

Dim outputFileStream As New FileStream(Server.MapPath(strDest), IO.FileMode.Create, IO.FileAccess.ReadWrite)
Dim signer As New PdfSigner(New PdfReader(Server.MapPath(strTemp)), outputFileStream, True)

signer.SetCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS)

Dim appearance As PdfSignatureAppearance = signer.GetSignatureAppearance
Dim rect As New iText.Kernel.Geom.Rectangle(50, 425, 500, 200)
appearance.SetContact("Contact Name - Phone")
appearance.SetSignatureCreator("Company - New Program")
appearance.SetReason(vbCrLf + "eDoc Consent Agreement: " + CType(Session("eDocConsentDateTime"), Date).ToString + vbCrLf _
                     + "Terms and Conditions Agreement: " + objInstall.AgreementSigned.ToString + vbCrLf _
                     + "Identifier: " + Session("Ident"))
appearance.SetLocation(vbCrLf + strInstallationAddress)
appearance.SetReuseAppearance(False)
appearance.SetPageRect(rect)
appearance.SetPageNumber(2)     'Signature will appear on Page 2
appearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION)

signer.SetFieldName(signer.GetNewSigFieldName())

Dim cert As New X509Certificate(Server.MapPath("CompSSL.pfx"), "password")
Dim tsaUrl As String = CertificateUtil.GetTSAURL(DotNetUtilities.FromX509Certificate(cert))
tsaClient = New TSAClientBouncyCastle(tsaUrl)
Dim crlList As IList(Of ICrlClient) = New List(Of ICrlClient)()

Dim fsCertFile As New FileStream(Server.MapPath("CompSSL.pfx"), FileMode.Open, FileAccess.Read)
Dim pk12 As New Pkcs12Store(fsCertFile, "password")
fsCertFile.Dispose()

'then Iterate throught certificate entries to find the private key entry
Dim [alias] As String = Nothing
For Each tAlias As String In pk12.Aliases
  If pk12.IsKeyEntry(tAlias) Then
    [alias] = tAlias
    Exit For
  End If
Next
Dim pk = pk12.GetKey([alias]).Key

Dim myExternalSignature As IExternalSignature = New PrivateKeySignature(pk, DigestAlgorithms.SHA1)

signer.SignDetached(myExternalSignature, New Org.BouncyCastle.X509.X509Certificate() {pk12.GetCertificate([alias]).Certificate}, Nothing, Nothing, Nothing, 0, PdfSigner.CryptoStandard.CMS)

所以我想知道如何使用 GlobalSign 组件来签署 PDF。似乎他们在最后为我提供了插入 SignDetached 方法所需的一切,但我特别不知道这些事情:

  1. 我相信我应该在添加外观内容后进行哈希处理以创建摘要,但我不知道我会将这些字节传递给 Dim hash() As Byte = s.ComputeHash(fileBytes) 在我的代码中的那一点(我假设在 signer.SetFieldName(signer.GetNewSigFieldName()) 之后)
  2. 我不知道如何或是否使用我从 DSS 获得的两个证书。我什至看到一个例子表明我可以把它们放在一起得到一些东西。
  3. 我不知道我得到的签名值是否等同于IExternalSignature。如果是这样,我不知道如何将其转换为 "Org.BouncyCastle.Crypto.ICipherParameters" 类型,或者即使我应该尝试这样做。
  4. 我将从 DSS 返回的时间戳令牌字符串插入到行 tsaClient = New TSAClientBouncyCastle(tsaUrl) 中 "tsaUrl" 的位置,没有错误,但我不知道这是否正确.
  5. 我不知道如何处理从 DSS 获得的 OCSP 响应以将其放入 IOcspClient 对象。
  6. 如果我什至不尝试输入时间戳或 OCSP 响应,显然我不在我的代码的 .pfx 版本中,这意味着什么?
  7. 要使 DSS 响应有效,我必须进行哪些数据类型转换(如果有)?

我已经尝试遵循 iTextsharp 示例,即使是这个 () 这正是我想要做的,但它似乎没有给我完成所需的信息它关了。感谢您的帮助。

编辑:
感谢您的帮助,dsandoval。我继续对未更改的文件进行哈希处理,这就是我发送给 GlobalSign 的内容:

Dim s As New SHA256Managed
Dim fileBytes() As Byte = IO.File.ReadAllBytes(Server.MapPath(strSrc))
Dim hash() As Byte = s.ComputeHash(fileBytes)
Dim calculatedHash As String = ByteArrayToString(hash)

还有这个:

Private Function ByteArrayToString(ByVal arrInput() As Byte) As String
  Dim sb As New System.Text.StringBuilder(arrInput.Length * 2)
  For i As Integer = 0 To arrInput.Length - 1
    sb.Append(arrInput(i).ToString("X2"))
  Next
  Return sb.ToString().ToUpper
End Function

然后我把我从GS得到的两个证书写到文件中,然后用BouncyCastle把它们读入证书并放入一个数组中(先签名证书)。我从他们的 'certificate_path' 字符串中选择我正在写第二个证书文件的地方:

Using sw As New StreamWriter(File.Open(strFile2, FileMode.OpenOrCreate))
  sw.WriteLine(strTempCert2)
End Using
Dim fileStream2 = System.IO.File.OpenText(strFile2)
Dim pemReader2 = New Org.BouncyCastle.OpenSsl.PemReader(fileStream2)
Dim myCertificateChain() As Org.BouncyCastle.X509.X509Certificate = New Org.BouncyCastle.X509.X509Certificate() {pemReader.ReadObject, pemReader2.ReadObject}

我对 IExternalSignature 做了同样的事情,使用它从 GS 转换字符串:

Private Function StringToByteArray(s As String) As Byte()
  ' remove any spaces from, e.g. "A0 20 34 34"
  s = s.Replace(" "c, "")
  ' make sure we have an even number of digits
  If (s.Length And 1) = 1 Then
    Throw New FormatException("Odd string length when even string length is required.")
  End If

' calculate the length of the byte array and dim an array to that
  Dim nBytes = s.Length \ 2
  Dim a(nBytes - 1) As Byte

  ' pick out every two bytes and convert them from hex representation
  For i = 0 To nBytes - 1
    a(i) = Convert.ToByte(s.Substring(i * 2, 2), 16)
  Next

  Return a
End Function

现在我只包含证书链和签名,而不是时间戳或 OCSP,看看我是否可以对其进行签名。通过证书链和 IExternalSignature 的实施,我能够将 SignDetached 设为 运行:

signer.SignDetached(myExternalSignature, myCertificateChain, Nothing, Nothing, Nothing, 0, PdfSigner.CryptoStandard.CMS)

PDF 已签名,但签名面板显示 "Certification by is invalid"、"Certified by %s"。详情说"There are errors in the formatting or information contained in this signature"。单击 "Certificate details" 时,没有任何反应。我在网上发现另一个人有这个错误(),这是由于签名容器太大。我的 SignDetached 参数中的零应该是 "estimate the size",所以我将其更改为 8192,这是我在其他一些在线示例中找到的神奇数字 8k。那没有帮助。在这一点上有什么建议吗?

我正在使用 C# 来实现。我不太熟悉 VB 但希望这会有所帮助。我见过的大多数示例都使用 Java 并且它们的实现方式存在一些差异,因此我不得不解决这些问题。我必须为此工作实施的唯一事情是 IExternalSignature、IOcspClient 和 ITsaClient。

  1. 发送给GlobalSign 的摘要是申请签名前文档的内容,所以如果申请签名可以省略外观。我相信,在您使用 PdfSigner.SignDetached 应用签名之前,文档不会被修改。要计算摘要,您需要使用 SHA256 算法对消息进行哈希处理并对其进行十六进制编码。这是将发送给 GS 的摘要。
  2. 您需要将来自 GlobalSign 的两个证书都放入以创建证书链。首先添加签名证书,然后添加您可以从其 'certificate_path' 端点检索的证书。
  3. 我不确定是否转换为该类型,但是对于 c# 库,IExternalSignature 的 Sign 方法 returns 一个字节[]。通过我自己的接口实现,我简单地将我们从 GS 接收到的 SignatureValue 转换为 byte[]。
  4. 对于时间戳,我们也实现了自己的ITsaClient,调用GS获取时间戳。摘要必须在 GetTimeStampToken 方法中计算并发送给 GS。响应是一个十六进制编码的字符串,我们将其转换为 byte[].
  5. 对于OCSP,我们也实现了自己的IOcspClient。因为我们在获取身份时得到了 ocsp_response,所以这个客户端只是使用它,而不是再次调用 GS。我在仅通过将响应从 Base64 字符串转换为 byte[] 来返回响应时启用 LTV 时遇到问题。 BouncyCastle 无法读取这种格式的响应。我必须将它转换为以下 BouncyCastle 对象才能正常工作。

    public byte[] GetEncoded( X509Certificate checkCert, X509Certificate issuerCert, string url )
    {
        var decoded = Base64.Decode(_ocspResponse); //_ocspResponse is same one that we received when fecthing the identity
        var response = new OcspResp(decoded);
        var basicResponse = (BasicOcspResp)response.GetResponseObject();
    
        return basicResponse.GetEncoded();
    }
    
  6. 如果没有附加时间戳,签名将不会被添加时间戳。当您在 Adob​​e 中打开文档时,它会将时间显示为 "the time is time on the signer's computer's system clock" 行。如果没有附上 ocsp 响应,验证人将无法验证证书 has/hasn 在签名时未被撤销。
  7. 我认为我必须进行的唯一转换是上面提到的那个。

如果您需要更多代码示例或任何其他问题,请告诉我。

编辑

这是 IExternalSignature 的实现。散列和加密算法必须与 GS 将使用的相匹配。他们正在使用 SHA256 和 RSA。

public class ExternalSignature : IExternalSignature
{
    private readonly SigningIdentity _identity;
    private readonly GlobalSignClient _client;
    public ExternalSignature( GlobalSignClient client, SigningIdentity identity )
    {
        _client = client;
        _identity = identity;
    }

    public string GetEncryptionAlgorithm() => "RSA";

    public string GetHashAlgorithm() => DigestAlgorithms.SHA256;

    public byte[] Sign( byte[] message )
    {
        // Using the System.Security.Cryptography implementation of SHA256
        using (var sha = SHA256.Create())
        {
            // Hash the message, containing the PDF
            var bytes = sha.ComputeHash(message);
            // HexEconde the hashed message to send to GlobalSign   
            var digest = Helpers.ByteArrayToString(bytes);
            // Send the digest to GlobalSign
            var signature = _client.GetSignatureAsync(_identity.Id, digest).Result;
            // Return the returned signature as a byte[]
            return Helpers.StringToByteArray(signature);
        }
    }
}

要添加签名,只需创建一个实例并将其传递给 SignDetached 方法。

var signature = new ExternalSignature(_client, signingIdentity);
...
...
//After creating the PDF signer, adding appearance, creating the OCSP and TSA clients.
signer.SignDetached(signature, certChain, null, ocsp, tsa, 0, PdfSigner.CryptoStandard.CMS);

我对 OCSP 和 TSA 客户端做了同样的事情。