繁体   English   中英

使用ITextSharp和XML签名对Pdf进行签名

[英]Sign Pdf Using ITextSharp and XML Signature

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

我需要使用此签名通过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使用哪个过滤器和subFilter? 还是应该/我可以根据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支持哪个选项

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代码,用于实现基于签名服务(如上所述,编辑2)使用嵌入式PKCS#7 / CMS签名容器对PDF进行签名的功能。

但是,要签名的结果哈希值是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;
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM