使用ITextSharp和XML签名对Pdf进行签名 [英] Sign Pdf Using ITextSharp and XML Signature

查看:171
本文介绍了使用ITextSharp和XML签名对Pdf进行签名的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to sing a pdf using a remote web service which returns a XML signature that consists of PKCS#1 signature with end users certificate.

我需要使用此签名通过IText deferred签名来对pdf签名,因为Web服务可以运行asynchronously.

I need to use this signature to sign pdf via IText deferred signing because the web service works asynchronously.

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

All the IText examples uses PKCS#7 message format but I am not sure what should I do for XML signature.

添加空签名字段并获取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解码时遇到错误" .

When I use the returned PKCS#1 signature with above code, the signature verification fails with "Error encountered while BER decoding".

由于BlankSignatureContainer是用ADBE_PKCS7_DETACHED子过滤器创建的,所以我认为这很正常.但是,我不确定应该为PKCS#1使用哪个过滤器和subFilter?还是应该/我可以从PKCS#1和用户的证书中创建PKCS#7消息,并改用这种格式?

Since the BlankSignatureContainer created with ADBE_PKCS7_DETACHED subFilter, I think this is normal. However, I am not sure what filter and subFilter I should use for PKCS#1? Or Should / Can I create PKCS#7 message from PKCS#1 and user's certificate and use this format instead?

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

I can also retrieve the end users certificate before requesting the signature.

<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>

我已经更新了我的签名代码以构建CMS.但是,要签名的结果哈希值是77个字节.该Web服务除了32字节SHA256散列数据之外.

I have updated my signing code to construct a CMS. However, the resulting hash value to be signed is 77 bytes. The web service excepts 32 bytes SHA256 hashed data.

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格式(通常受支持的ISO 32000-1:2008)使用裸PKCS#1签名或完整PKCS#7/CMS签名容器来指定嵌入式签名,请参见.第12.8.3节签名互操作性",特别是

The PDF format (the commonly supported ISO 32000-1:2008) specified embedded signatures using either naked PKCS#1 signatures or full PKCS#7/CMS signature containers, cf. section 12.8.3 "Signature Interoperability", in particular

  • 第12.8.3.2节"PKCS#1签名"和
  • 第12.8.3.3节"ISO 32000中使用的PKCS#7签名".

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

Newer standards, like the ETSI PAdES standards and the new ISO 32000-2:2017, forbid or deprecate the former option. For a new solution that shall not be outdated already on the day it is published, therefore, one should go for the latter choice.

我正在尝试使用一个远程Web服务来唱歌pdf,该服务返回一个包含PKCS#1签名和最终用户证书的XML签名.

I am trying to sing a pdf using a remote web service which returns a XML signature that consists of PKCS#1 signature with end users certificate.

如果这是Web服务的全部功能,那么将存在一个问题:嵌入裸PKCS#1签名和构造PKCS#7签名容器时,都需要知道最终实体证书之前调用该服务以为哈希创建签名,因为该证书或对其的引用必须嵌入在签名的PDF数据或签名的CMS属性中.

If that were all the functionality of the web service, there'd be a problem: Both when embedding a naked PKCS#1 signature and when constructing a PKCS#7 signature container, one needs to know the end entity certificate before calling the service to create a signature for a hash because that certificate or a reference to it must be embedded in the signed PDF data or the signed CMS attributes.

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

(Strictly speaking very basic CMS signature container do not require this but all signature profiles to take seriously do.)

幸运的是,事实证明(编辑1)那个

Fortunately it turned out (edit 1) that one

可以访问另一项服务该服务使应用程序提供商可以请求最终用户的证书序列号和证书哈希,这些序列号和证书哈希可用于构建要签名的数据."

can access to another service "This service enables Application Providers to request end user’s certificate serial number and certificate hash which can be used in constructing the data to be signed."

代码

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

The code

The OP found iText/Java code for implementing the functionality to sign a PDF with an embedded PKCS#7/CMS signature container based on a signing service as described above (edit 2).

但是,要签名的结果哈希值是77个字节.该Web服务除外32字节SHA256哈希数据.

However, the resulting hash value to be signed is 77 bytes. The web service excepts 32 bytes SHA256 hashed data.

但是,事实证明,这77个字节不是已签名属性的哈希值:

As it turned out, though, those 77 bytes were not the hash of the signed attributes:

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

它们是已签名的属性字节.因此,仅需对这些字节进行哈希处理即可生成哈希值,以发送给签名服务以创建签名.

They were the signed attributes bytes. Thus, these bytes merely needed to be hashed to produce the hash to send to the signing service for creating a signature.

这篇关于使用ITextSharp和XML签名对Pdf进行签名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆