今天,我请求您帮助我在C#中使用iText8进行Pade Baseline-LTA Profile PDF签名项目.
我使用受信任的第三方API来签名哈希,我想遵循以下工作流程:
- 计算要签署的日期
- 计算要签署的日期 Digest (sha256)
- 签名哈希(可信第三方API)
- 创建签名文档
值得信赖的第三方建议我使用DSS(https://github.com/esig/dss)生成DTBS,但我正在使用C#,不想使用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
你能解释一下原因吗?