使用ITextSharp和XML签名签署Pdf

时间:2017-09-15 15:35:27

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

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

我需要使用此签名通过IText deferred签名来签署pdf,因为网络服务有效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 subFilter创建的ADBE_PKCS7_DETACHED,我认为这是正常的。但是,我不确定我应该为PKCS#1使用哪个过滤器和subFilter?或者/我应该/可以从PKCS#7和用户的证书创建PKCS#1消息并使用此格式吗?

编辑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);
        }
    }
}

1 个答案:

答案 0 :(得分:1)

(这个答案总结了评论和编辑过程中工作代码的演变。)

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,该服务返回包含PKCS#1签名和最终用户证书的XML签名。

如果这是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;
}

它们是签名的属性字节。因此,仅需要对这些字节进行散列以产生散列以发送给签名服务以创建签名。

相关问题