【问题标题】:Sign Pdf Using ITextSharp and XML Signature使用 ITextSharp 和 XML 签名签署 Pdf
【发布时间】:2018-02-24 20:32:36
【问题描述】:

我正在尝试使用远程 Web 服务来唱 pdf,该服务返回一个 XML 签名,该签名由带有最终用户证书的 PKCS#1 签名组成。

我需要使用此签名通过 IText deferred 签名来签署 pdf,因为 Web 服务可以工作 asynchronously

所有 IText 示例都使用 PKCS#7 消息格式,但我不确定我应该为 XML 签名做什么。

添加空签名字段并获取 Pdf 可签名字节的代码

public static string GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName)
{
    if (File.Exists(tempPdf))
        File.Delete(tempPdf);

    using (PdfReader reader = new PdfReader(unsignedPdf))
    {
        using (FileStream os = File.OpenWrite(tempPdf))
        {
            PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
            PdfSignatureAppearance appearance = stamper.SignatureAppearance;
            appearance.SetVisibleSignature(new Rectangle(36, 748, 250, 400), 1, signatureFieldName);

            IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);

            MakeSignature.SignExternalContainer(appearance, external, 8192);

            byte[] array = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());

            return Convert.ToBase64String(array);
        }
    }
}

打开 Temp Pdf 并嵌入收到的签名的代码

public static void EmbedSignature(string tempPdf, string signedPdf, string signatureFieldName, string signature)
{
    byte[] signedBytes = Convert.FromBase64String(signature);

    using (PdfReader reader = new PdfReader(tempPdf))
    {
        using (FileStream os = File.OpenWrite(signedPdf))
        {
            IExternalSignatureContainer external = new MyExternalSignatureContainer(signedBytes);
            MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
        }
    }
}

Web 服务返回的 XML 签名:

<SignatureValue Id="Signature-xxx-SIG">
RMFbYIigsrjYQEc4PCoHMMg8vwz/hYrCjj0IR+835BZZ/TsTMHZhMVnu2KQZi1UL
dWMeuhTxagBmjdBSzGiy1OEdH5r0FM77Of4Zz99o/aAhYqr3qpOETGgNn9GHlphL
5FSPYbNsq2rDHA8FsNdqNdb6qJUZYsfYvuhJaUMstBXeL/dLIT46t7ySCQ7CGjE5
mpD1AG8M+TVWf4ut5tucZuZV9PMQB3tyoarQD5RoUv872RzB5IorcIhLnf+O6E6o
EF0HuGitRhN9bbPgdLaUma5MNjKCaeQTpCXp3KXwi8VoQGd5fpUBZbAKtMpKeCts
RAOk1O4uk/4poic4IGPhDw==
</SignatureValue>
<KeyInfo>

<KeyValue>
<RSAKeyValue>

<Modulus>
AI5T0zOBBD4i7Cb1v8wDL0+By8i1h2U0HDFQ73/iNBkM9Gd7mUU0i2A9wJTeiktS
IeBU/JLzqVp7vK857dZlrlT9qiH2cNQufh+q2MpNk7wtPcDACVedVkBUNkIgoXBy
g4cGmAYoWBsD2zDXiZXukStjUWws+/xCU0hADIelFONr141zRHindfq86QrDTC7q
bHBtDT7dJckWzaDPHf3Xlej+U/A1x/8kd504VZaFQfAPYBOgGPY918G1HjBtlajR
nyrl10LuV708IHZtAmKmEfdZOeq//9OGZZLh2nJ86b5Fa6XPFhxzLx/ugBS/8GHt
iVYeJOlfHXRl5w2k2Vv/ssU=
</Modulus>

<Exponent>AQAB</Exponent>

</RSAKeyValue>
</KeyValue>

<X509Data>
<X509Certificate>
MIIGpTCCBY2gAwIBAgIRAJvlsG1D+P++OcU8W8D8FnkwDQYJKoZIhvcNAQELBQAw
bzELMAkGA1UEBhMCVFIxKDAmBgNVBAoMH0VsZWt0cm9uaWsgQmlsZ2kgR3V2ZW5s
aWdpIEEuUy4xNjA0BgNVBAMMLVRFU1QgVHVya2NlbGwgTW9iaWwgTkVTIEhpem1l
dCBTYWdsYXlpY2lzaSBTMjAeFw0xNzA4MjUxMjQ3MjFaFw0xODA4MjUxMjQ3MjFa
MEoxCzAJBgNVBAYTAlRSMREwDwYDVQQKDAhGaXJlIExMVDEUMBIGA1UEBRMLNzY1
NDM0NTY3NjUxEjAQBgNVBAMMCU1lcnQgSW5hbjCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAI5T0zOBBD4i7Cb1v8wDL0+By8i1h2U0HDFQ73/iNBkM9Gd7
mUU0i2A9wJTeiktSIeBU/JLzqVp7vK857dZlrlT9qiH2cNQufh+q2MpNk7wtPcDA
CVedVkBUNkIgoXByg4cGmAYoWBsD2zDXiZXukStjUWws+/xCU0hADIelFONr141z
RHindfq86QrDTC7qbHBtDT7dJckWzaDPHf3Xlej+U/A1x/8kd504VZaFQfAPYBOg
GPY918G1HjBtlajRnyrl10LuV708IHZtAmKmEfdZOeq//9OGZZLh2nJ86b5Fa6XP
FhxzLx/ugBS/8GHtiVYeJOlfHXRl5w2k2Vv/ssUCAwEAAaOCA18wggNbMEIGCCsG
AQUFBwEBBDYwNDAyBggrBgEFBQcwAYYmaHR0cDovL3Rlc3RvY3NwMi5lLWd1dmVu
LmNvbS9vY3NwLnh1ZGEwHwYDVR0jBBgwFoAUT9gSazAfQrnZruIq9+dJFZ7E9OUw
ggFyBgNVHSAEggFpMIIBZTCBsQYGYIYYAwABMIGmMDYGCCsGAQUFBwIBFipodHRw
Oi8vd3d3LmUtZ3V2ZW4uY29tL2RvY3VtZW50cy9ORVNVRS5wZGYwbAYIKwYBBQUH
AgIwYBpeQnUgc2VydGlmaWthLCA1MDcwIHNhecSxbMSxIEVsZWt0cm9uaWsgxLBt
emEgS2FudW51bmEgZ8O2cmUgbml0ZWxpa2xpIGVsZWt0cm9uaWsgc2VydGlmaWth
ZMSxcjCBrgYJYIYYAwABAQEDMIGgMDcGCCsGAQUFBwIBFitodHRwOi8vd3d3LmUt
Z3V2ZW4uY29tL2RvY3VtZW50cy9NS05FU0kucGRmMGUGCCsGAQUFBwICMFkaV0J1
IHNlcnRpZmlrYSwgTUtORVNJIGthcHNhbcSxbmRhIHlhecSxbmxhbm3EscWfIGJp
ciBuaXRlbGlrbGkgZWxla3Ryb25payBzZXJ0aWZpa2FkxLFyLjBdBgNVHR8EVjBU
MFKgUKBOhkxodHRwOi8vdGVzdHNpbC5lLWd1dmVuLmNvbS9FbGVrdHJvbmlrQmls
Z2lHdXZlbmxpZ2lBU01LTkVTSS1TMi9MYXRlc3RDUkwuY3JsMA4GA1UdDwEB/wQE
AwIGwDCBgwYIKwYBBQUHAQMEdzB1MAgGBgQAjkYBATBpBgtghhgBPQABp04BAQxa
QnUgc2VydGlmaWthLCA1MDcwIHNheWlsaSBFbGVrdHJvbmlrIEltemEgS2FudW51
bmEgZ8O2cmUgbml0ZWxpa2xpIGVsZWt0cm9uaWsgc2VydGlmaWthZGlyMFAGA1Ud
CQRJMEcwFAYIKwYBBQUHCQIxCAQGQW5rYXJhMB0GCCsGAQUFBwkBMREYDzE5Nzgx
MjMxMjAwMDAwWjAQBggrBgEFBQcJBDEEBAJUUjAYBgNVHREEETAPgQ1maXJlQGZp
cmUuY29tMB0GA1UdDgQWBBTYUEJX62lhEzkZLSrTdSIdE9n8KzANBgkqhkiG9w0B
AQsFAAOCAQEAV/MY/Tp7n7eG7/tWJW+XlDgIPQK1nAFgvZHm+DodDJ8Bn7CPWmv8
EBmcNxx5PYMoG7y54E6+KzVyjdPBu5o0YtWyKlLKnH1St+EtptOmt29VFAODjalb
Q0An9361DxIDRHbMnQOaJiRTwMlIhED5VDa3OTUhBr7bNlVkp3N5Vs2RuoSdNypR
fq4D/cCpakVugsFyPk4zhWkGhTS5DlL7c5KYqCnU4ugpC33IRJGB05PSdjICeX7Y
N0oAdhzF+5QZEwePjgrDb+ROVpXYaGVMWICA8N2tOXuqdDYoGAzj1YemnPqrSn8a
2iwqbWnFujwep5w5C/TCFVaQWa4NGncMOw==
</X509Certificate>
</X509Data>

</KeyInfo>

当我将返回的PKCS#1 签名与上述代码一起使用时,签名验证失败并出现“BER 解码时遇到错误”

由于BlankSignatureContainer是用ADBE_PKCS7_DETACHED子过滤器创建的,我觉得这很正常。但是,我不确定应该为PKCS#1 使用什么过滤器和子过滤器?或者应该/我可以从PKCS#1 和用户证书创建PKCS#7 消息并改用这种格式吗?

编辑 1:

我还可以在请求签名之前检索最终用户证书。

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
   <soap:Body>
      <ns1:MSS_ProfileQueryResponse xmlns:ns1="/mobilesignature/validation/soap">
         <MSS_ProfileResp MinorVersion="1" MajorVersion="1" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" xmlns:ns3="http://www.w3.org/2001/04/xmlenc#" xmlns:ns4="http://www.w3.org/2003/05/soap-envelope" xmlns:ns5="http://uri.etsi.org/TS102204/v1.1.2#">
            <ns5:AP_Info Instant="2017-09-16T04:54:43.260Z" AP_PWD="turkcell123" AP_TransID="_1371012883260" AP_ID="http://turkcell.com.tr"/>
            <ns5:MSSP_Info Instant="2017-09-16T13:33:36.316+02:00">
               <ns5:MSSP_ID/>
            </ns5:MSSP_Info>
            <ns5:SignatureProfile>
               <ns5:mssURI>http://MobileSignature/profile2</ns5:mssURI>
               <ns5:CertIssuerDN> Mobil NES Hizmet Saglayicisi S2</ns5:CertIssuerDN>
               <ns5:CertSerialNumber>71408272140747005781907792964830346324</ns5:CertSerialNumber>
               <ns5:CertHash>
                  <ns5:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                  <ns5:DigestValue>e1yKlaPIU95phxxYvrUsmSQDpKqKU60/b+a8BLw1wNM=</ns5:DigestValue>
               </ns5:CertHash>
               <ns5:msspUrl>http://localhost</ns5:msspUrl>
               <ns5:certIssuerDN-DER>MG8xCzAJBgNVBAYTAlRSMSgwJgYDVQQKDB9FbGVrdHJvbmlrIEJpbGdpIEd1dmVubGlnaSBBLlMuMTYwNAYDVQQDDC1URVNUIFR1cmtjZWxsIE1vYmlsIE5FUyBIaXptZXQgU2FnbGF5aWNpc2kgUzI=</ns5:certIssuerDN-DER>
            </ns5:SignatureProfile>
            <ns5:Status>
               <ns5:StatusCode Value="100"/>
               <ns5:StatusMessage>REQUEST_OK</ns5:StatusMessage>
            </ns5:Status>
         </MSS_ProfileResp>
      </ns1:MSS_ProfileQueryResponse>
   </soap:Body>
</soap:Envelope>

编辑 2:

我已更新我的签名代码以构建 CMS。但是,要签名的结果哈希值为 77 个字节。 Web 服务不包括 32 字节 SHA256 散列数据。

public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, byte[] x509Signature)
{
    if (File.Exists(tempPdf))
        File.Delete(tempPdf);

    var chain = new List<Org.BouncyCastle.X509.X509Certificate>
    {
        Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(x509Signature))
    };

    Org.BouncyCastle.X509.X509Certificate certificate = chain.ElementAt(0);

    using (PdfReader reader = new PdfReader(unsignedPdf))
    {
        using (FileStream os = File.OpenWrite(tempPdf))
        {
            PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');

            PdfSignatureAppearance appearance = stamper.SignatureAppearance;

            appearance.SetVisibleSignature(new Rectangle(36, 748, 250, 400), 1, signatureFieldName);

            appearance.Certificate = chain[0];

            IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);

            MakeSignature.SignExternalContainer(appearance, external, 8192);

            Stream data = appearance.GetRangeStream();

            byte[] hash = DigestAlgorithms.Digest(data, "SHA256");

            var signature = new PdfPKCS7(null, chain, "SHA256", false);

            byte[] signatureHash = signature.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);

            _signature = signature;
            _apperance = appearance;
            _hash = hash;
            _signatureHash = signatureHash;

            return signatureHash;
        }
    }
}

public static void EmbedSignature(string tempPdf, string signedPdf, string signatureFieldName, byte[] signedBytes)
{
    using (PdfReader reader = new PdfReader(tempPdf))
    {
        using (FileStream os = File.OpenWrite(signedPdf))
        {
            _signature.SetExternalDigest(signedBytes, null, "RSA");

            byte[] encodedSignature = _signature.GetEncodedPKCS7(_hash, null, null, null, CryptoStandard.CMS);

            IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);

            MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
        }
    }
}

【问题讨论】:

  • 首先,不应在 PDF 中使用纯 PKCS#1 签名,现代标准仅适用于 CMS (PKCS#7) 容器。第二:在任何一种情况下,您都需要在调用服务对一些哈希进行签名之前获得签名者证书,因为要在签名数据中引用签名者身份。
  • 通过 任何一种情况 我的意思是,如果您尝试在 pdf 中使用纯 PKCS#1 签名,或者您尝试使用您的服务返回的 PKCS#1 签名来构建一个 CMS 容器(因为现在必须支持 ESS)。如果服务返回正确的 CMS 容器,则您不一定需要提前知道签名者身份。
  • “抱歉,在调用服务之前我不明白我需要哪个用户证书?” - 与服务使用的私钥相关联的证书。您当前作为服务答案的一部分检索到的那个。如果服务始终使用相同的证书,您可以存储并使用该证书。如果服务以不可预知的方式使用不同的,就会变得很困难。
  • “那么我可以使用这个基于 XML 的签名来签署文档,还是需要使用这个 XML 创建一个 CMS?” - 如上所述,您需要知道调用服务之前的最终用户证书,但是您可以直接使用纯 PKCS#1 签名或从中构建 CMS 签名容器。后者会更好,因为像 PAdES 这样的新标准需要 CMS 表单。
  • 77 字节散列不正常。可能您还没有最终的哈希值,而只有您仍然必须哈希的经过身份验证/签名的属性。没有更多信息很难说。啊,我刚看到你的编辑。事实上,你拿了getAuthenticatedAttributeBytes 并认为它们是哈希。他们不是。您或签名服务仍然需要散列。

标签: c# pdf itext digital-signature xml-signature


【解决方案1】:

(这个答案总结了工作代码在 cmets 和问题编辑过程中的演变。)

PDF 支持哪个选项

PDF 格式(通常支持的 ISO 32000-1:2008)指定使用裸 PKCS#1 签名或完整 PKCS#7/CMS 签名容器的嵌入式签名,参见。尤其是第 12.8.3 节“签名互操作性”

  • 第 12.8.3.2 节“PKCS#1 签名”和
  • 第 12.8.3.3 节“ISO 32000 中使用的 PKCS#7 签名”。

较新的标准(例如 ETSI PAdES 标准和新的 ISO 32000-2:2017)禁止或弃用前一个选项。因此,对于一个在发布之日不会过时的新解决方案,应该选择后一种选择。

事先了解证书

我正在尝试使用远程 Web 服务来唱 pdf,该服务返回一个 XML 签名,该签名由带有最终用户证书的 PKCS#1 签名组成。

如果这就是 Web 服务的所有功能,就会出现问题:无论是在嵌入裸 PKCS#1 签名时还是在构造 PKCS#7 签名容器时,都需要知道最终实体证书 在调用服务为哈希创建签名之前,因为该证书或对它的引用必须嵌入到签名的 PDF 数据或签名的 CMS 属性中。

(严格来说,非常基本的 CMS 签名容器不需要这样做,但所有签名配置文件都需要认真对待。)

幸运的是(编辑 1)那个

可以访问另一个服务“该服务使应用程序提供商能够请求最终用户的证书序列号和证书哈希,这些证书可用于构造要签名的数据。”

代码

OP 找到了 iText/Java 代码,用于实现使用基于上述签名服务的嵌入式 PKCS#7/CMS 签名容器对 PDF 进行签名的功能(编辑 2)。

但是,要签名的结果哈希值为 77 个字节。 Web 服务不包括 32 字节 SHA256 散列数据。

但事实证明,这 77 个字节并不是签名属性的散列:

public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, byte[] x509Signature)
{
    [...]
    byte[] signatureHash = signature.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
    [...]
    return signatureHash;
}

它们是带符号的属性字节。因此,只需要对这些字节进行哈希处理以生成哈希值,然后发送到签名服务以创建签名。

【讨论】:

  • 我正在与 Aadhaar Esign 服务集成。在签名请求中,我们传递文档的整个哈希,因为我们不知道在哪里标记签名(Xy 线),Web 服务返回 PKCS7 (CMS) 签名作为 base 64 字符串,我需要使用这个响应到多个签名位置,我参考了您对此问题的回答并遵循了步骤,但自从应用签名以来,它显示无效和更改的 pdf。
  • @Div Web 服务很可能很容易通过使用 iText ExternalSignatureContainer 以界面为中心的方法进行集成。更相关的问题是 PDF 规范明确禁止的单个签名的“多个签名位置”。不过,有一种方法可以解决这个问题,它遵循规范的文字,而不是精神。因此,这种方式可能最终也会被禁止。更多信息请访问referenced question
猜你喜欢
  • 2019-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-21
  • 2015-10-14
  • 2014-11-02
  • 2016-12-14
相关资源
最近更新 更多