今天,我请求您帮助我在C#中使用iText8进行Pade Baseline-LTA Profile PDF签名项目.

我使用受信任的第三方API来签名哈希,我想遵循以下工作流程:

  • 计算要签署的日期
  • 计算要签署的日期 Digest (sha256)
  • 签名哈希(可信第三方API)
  • 创建签名文档

值得信赖的第三方建议我使用DSS(https://github.com/esig/dss)生成DTBS,但我正在使用C#,不想使用Java.

我try 了几种方法,包括这一种:https://github.com/itext/itext-publications-signatures-java/blob/develop/src/test/java/com/itextpdf/samples/signatures/chapter04/C4_07_ClientServerSigning.java

老实说,我觉得我完全错了.

有没有可能在没有DSS帮助的情况下用BouncyCastle创建一个DTBS?

我应该对iText使用什么方法来实现这种类型的签名?

提前感谢您的帮助.

编辑:

生成的PDF:https://drive.google.com/file/d/1rnY7tuXPJkzSgLFasQ6R7I8BDQ1RWwM_/view?usp=sharing

我用itext 8.0.3

测试代码:


    using Flurl;
    using Flurl.Http;
    using iText.Bouncycastle.X509;
    using iText.Commons.Bouncycastle.Cert;
    using iText.Kernel.Geom;
    using iText.Kernel.Pdf;
    using iText.Signatures;
    using Org.BouncyCastle.X509;
    using SignatureCerteurope.Certeurope;
    using SignatureCerteurope.ComponentC.Json;
    using System.Security.Cryptography.X509Certificates;
    
    string DEST = "SIGNED.PDF";
    string SRC = "TEST.PDF";
    
    var sign = new SignatureAPI();
    var cert = await sign.GetCertificate();
    
    string base64Certificate = cert.pem
                    .Replace("-----BEGIN CERTIFICATE-----\n", "")
                    .Replace("\n-----END CERTIFICATE-----\n", "");
    
    byte[] certificateBytes = Convert.FromBase64String(base64Certificate);
    
    Org.BouncyCastle.X509.X509CertificateParser parser = new Org.BouncyCastle.X509.X509CertificateParser();
    
    Org.BouncyCastle.X509.X509Certificate x509cert = parser.ReadCertificate(certificateBytes);
    
    iText.Commons.Bouncycastle.Cert.IX509Certificate[] certificateWrappers = new IX509Certificate[1];
    
    certificateWrappers[0] = new X509CertificateBC(x509cert);
    
    new C4_07_ClientServerSigning().Sign(SRC, DEST, certificateWrappers, PdfSigner.CryptoStandard.CMS, "test reason", "test location");
    
    public class C4_07_ClientServerSigning
    {
        public void Sign(string src, string dest, IX509Certificate[] chain, PdfSigner.CryptoStandard subfilter, string reason, string location)
        {
            PdfReader reader = new PdfReader(src);
            PdfSigner signer = new PdfSigner(reader, new FileStream(dest, FileMode.Create), new StampingProperties());
    
            // Create the signature appearance
            Rectangle rect = new Rectangle(36, 648, 200, 100);
            signer.SetReason(reason)
                    .SetLocation(location)
                    .SetPageRect(rect)
                    .SetPageNumber(1)
                    .SetFieldName("sig");
    
            IExternalSignature signature = new ServerSignature();
    
            signer.SignDetached(signature, chain, null, null, null, 0, subfilter);
        }
    
        public class ServerSignature : IExternalSignature
        {
            public string GetDigest算法rithmName()
            {
                return Digest算法rithms.SHA256;
            }
    
            public string GetSignature算法rithmName()
            {
                return "RSA";
            }
    
            public ISignatureMechanismParams GetSignatureMechanismParameters()
            {
                return null;
            }
    
            public byte[] Sign(byte[] message)
            {
                HashPDF json = new HashPDF
                {
                    orderRequestId = 444928,
                    hash = new List<HashItem>()
                };
    
                HashItem unHash = new HashItem
                {
                    hash = Convert.ToBase64String(message)
                };
                json.hash.Add(unHash);
                var sign = new SignatureAPI();
                HashSignatureRequestCollectDTO jsonres = sign.SignHash("444928", json).GetAwaiter().GetResult();
                return Convert.FromBase64String(jsonres.Signatures[0].Signature);
            }
        }
    }
    public class SignatureAPI
    {
        private const int _ORDER_REQUEST_ID = 444928;
        private const string _API_BASE_URL = "SIGNURL";
        private const string _SERIAL_NUMBER_CERTIFICATE = "17E1BC";
    
        public SignatureAPI()
        {
            X509Store store = new X509Store(StoreName.My);
            store.Open(OpenFlags.ReadOnly);
    
            X509Certificate2Collection col = store.Certificates.Find(X509FindType.FindBySerialNumber, _SERIAL_NUMBER_CERTIFICATE, false);
        
            X509Certificate2 certificate = col[0];
    
            FlurlHttp.Clients.WithDefaults(builder => builder.ConfigureInnerHandler(h => { h.ClientCertificates.Add(certificate); }));
        }
    
        public async Task<HashSignatureRequestCollectDTO> SignHash(string id, HashPDF json)
        {
            string apiUrl = "SIGNHASH";
            var response = await apiUrl.SetQueryParam("orderRequestId", id).PostJsonAsync(json);
    
            var result = await response.GetStringAsync();
            HashSignatureRequestCollectDTO jsonRes = result.ConvertJsonStringWithTypeToObject<HashSignatureRequestCollectDTO>();
    
            return jsonRes;
        }
    
        public async Task<CertificateJson> GetCertificate()
        {
           string apiUrl = "GETCERT";
           return await apiUrl.SetQueryParam("orderRequestId", _ORDER_REQUEST_ID).GetJsonAsync<CertificateJson>();
        }
    }
    
    public class SignatureResponse
    {
        public int SignatureRequestId { get; set; }
        public string Status { get; set; }
        public string Hash { get; set; }
        public string Signature { get; set; }
        public string ErrorMessage { get; set; }
    }
    
    public class HashSignatureRequestCollectDTO
    {
        public int OrderRequestId { get; set; }
        public object ExternalOrderRequestId { get; set; }
        public List<SignatureResponse> Signatures { get; set; }
    }
    
    public class HashItem
    {
        public string hash { get; set; }
    }
    
    public class HashPDF
    {
        public int orderRequestId { get; set; }
        public List<HashItem> hash { get; set; }
    }

编辑2:

我用PdfPadesSignertry 了MKL推荐的另一种方法. 以下是我的代码:

public class C4_07_ClientServerSigningPdfPades
{
    public void Sign(string src, string dest, IX509Certificate[] chain, PdfSigner.CryptoStandard subfilter, string reason, string location)
    {
        PdfReader reader = new PdfReader(src);
        PdfPadesSigner padesSigner = new PdfPadesSigner(reader, new FileStream(dest, FileMode.Create));
        
        SignerProperties properties = new SignerProperties();
        properties.SetFieldName("signatureField");

        IExternalSignature signature = new ServerSignature();
        TSAClientBouncyCastle tsa = new TSAClientBouncyCastle("https://rfc3161.ai.moda");
        padesSigner.SignWithBaselineLTAProfile(new SignerProperties(), chain, signature, tsa);
    }

    public class ServerSignature : IExternalSignature
    {
        public string GetDigest算法rithmName()
        {
            return Digest算法rithms.SHA256;
        }

        public string GetSignature算法rithmName()
        {
            return "RSA";
        }

        public ISignatureMechanismParams GetSignatureMechanismParameters()
        {
            return null;
        }

        public byte[] Sign(byte[] message)
        {
            HashPDF json = new HashPDF
            {
                orderRequestId = 444928,
                hash = new List<HashItem>()
            };

            byte[] digestValue;
            using (SHA256 sha256 = SHA256.Create())
            {
                digestValue = sha256.ComputeHash(message);
            }

            // Série d'octets à ajouter
            byte[] additionalByte = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 };

            // Concaténation des deux tableaux de bytes
            byte[] DigestInfo = new byte[digestValue.Length + additionalByte.Length];

            Buffer.BlockCopy(additionalByte, 0, DigestInfo, 0, additionalByte.Length);
            Buffer.BlockCopy(digestValue, 0, DigestInfo, additionalByte.Length, digestValue.Length);

            HashItem digestInfoHashItem = new HashItem
            {
                hash = Convert.ToBase64String(DigestInfo)
            };
            json.hash.Add(digestInfoHashItem);

            var sign = new SignatureAPI();
            HashSignatureRequestCollectDTO jsonres = sign.SignHash("444928", json).GetAwaiter().GetResult();
            return Convert.FromBase64String(jsonres.Signatures[0].Signature);
        }
    }
}

以下是生成的文档: https://drive.google.com/file/d/1V7FHZZGA2bKDkG0zusJSL7CIvSZwauDj/view?usp=sharing

我try 用工具验证签名的一致性:https://signatures-conformance-checker.etsi.org/

它系统地报告:try 获取适合后缀的混凝土工厂时出错.未找到该后缀的工厂:PDF

你能解释一下原因吗?

推荐答案

在您的示例PDF中对签名容器中的签名字节进行解密后发现,它们不包含包装签名属性散列的DigestInfo struct (如RFC 8017-PKCS#1:RSA加密规范2.2版及其前身RFC所要求的那样),而是包含签名属性本身.

因此,虽然您在代码中假设您的远程签名服务将对您发送给它的字节进行散列和包装,但该服务实际上并不是这样做的,而是希望您这样做.

在您的ServerSignature.Sign实现中,这种差异实际上在下面这一行中非常明显:

json.hash.Add(unHash);

在这里,您向一个集合添加了un-hashed个元素,该集合按其名称应该包含大约hashed个项目.

要解决此问题,您应该替换

HashItem unHash = new HashItem
{
    hash = Convert.ToBase64String(message)
};
json.hash.Add(unHash);

通过像这样的东西

byte[] digestValue = <<Calculate SHA-256 hash of message>>;
byte[] digestInfo = <<Wrap digestValue in a DigestInfo object>>;
HashItem digestInfoHashItem = new HashItem
{
    hash = Convert.ToBase64String(digestInfo)
};
json.hash.Add(digestInfoHashItem);

对于Wrap digestValue in a DigestInfo object,RFC 8017提供了一个捷径:

对于附录B.1中提到的九个散列函数,DER DigestInfo值的编码T等于以下:

...

SHA-256:(0x)30 31 30 0d06 09 60 86 48 01 65 03 04 02 01 05 00 04 20||H.

即,您只需将字节30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20添加到您的digestValue字节之前.


顺便说一下,你提到这是PaDES Baseline-LTA Profile PDF signing project的一部分.在这种情况下,您可能需要判断是否可以使用新的PdfPadesSigner iText类.

Csharp相关问答推荐

EF Core Fluent API中定义的多对多关系

AutoMapper -如何为两个不同的用例设置单个映射?

如何告诉自己创建的NuGet包在应用程序中发生了变化?

SignalR客户端不会打印队列位置'

如何将ASP.NET Core 2.1(在.NET框架上运行)更新到较新的版本?

注册所有IMediatR类

如何将此方法参数化并使其更灵活?

UWP应用程序try 将打包的本机.exe文件加载为C#程序集

C#DateTime.ParseExact不使用特定日期

在C#中反序列化/序列化具有混合元素顺序的XML时出现问题

使用Entity Framework6在对象中填充列表会导致列表大多为空

TagHelpers在新区域不起作用

关于扩展文件类C#的矛盾

.NET8->;并发词典总是比普通词典快...怎么回事?[包含基准结果和代码]

MSI无法将快捷方式添加到启动文件夹

在';、';附近有错误的语法.必须声明标量变量";@Checkin";.';

将C#类导入到PowerShell

在C#和HttpClient中使用REST API

Xamarin.Forms项目中缺少MainPage.xaml

如何将行添加到DataGrid以立即显示它?