当前位置: 首页 > 知识库问答 >
问题:

在C#和iText 7中使用x509证书2创建IExternalSignature

法弘亮
2023-03-14

我正在测试iText 7.1.2.0库,以便在C#项目中使用数字证书或智能卡(X509Certificate2)签署pdf文件。但是当我尝试创建IExternalSignature时,我遇到了这个错误。

根据找到的留档(这里,这里和这里),实现这个过程的方法是使用BouncyCastle库,它允许从数字证书中提取主键,但是,它给我一个错误,我找不到另一种方法来做到这一点。在留档(这里)中,它们是从. pfx文件创建的,但对于这种情况,我需要直接从读卡器中的证书中获取主键。在以前版本的iText中,它允许使用以下命令进行创建:

IExternalSignature externalSignature = new X509Certificate2Signature(Certificate, "SHA-1");

但在版本7中,它不再可用,在文档中我看不到如何实现它。

有人使用了iText 7,并且能够使用知道创建IExternalSignature的正确方法的X509证书2进行签名?

这是我正在使用的代码:

public void SignPDF(string source, string target, X509Certificate2 certificate, string reason, string location, bool addVisibleSign, bool addTimeStamp, string strTSA, int qtySigns, int pageNumber)
    {
        try
        {
            Org.BouncyCastle.X509.X509Certificate vert = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(certificate);

            X509CertificateParser objCP = new X509CertificateParser();
            Org.BouncyCastle.X509.X509Certificate[] objChain = new Org.BouncyCastle.X509.X509Certificate[] { objCP.ReadCertificate(certificate.RawData) };

            IList<ICrlClient> crlList = new List<ICrlClient>();
            crlList.Add(new CrlClientOnline(objChain));

            PdfReader objReader = new PdfReader(source);
            PdfSigner objStamper = new PdfSigner(objReader, new FileStream(target, FileMode.Create), false);

            ITSAClient tsaClient = null;
            IOcspClient ocspClient = null;

            if (addTimeStamp)
            {
                OCSPVerifier ocspVerifier = new OCSPVerifier(null, null);
                ocspClient = new OcspClientBouncyCastle(ocspVerifier);
                tsaClient = new TSAClientBouncyCastle(strTSA);
            }

            PdfSignatureAppearance signatureAppearance = objStamper.GetSignatureAppearance();
            signatureAppearance.SetReason(reason);
            signatureAppearance.SetLocation(location);
            signatureAppearance.SetPageNumber(pageNumber);
            signatureAppearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.NAME_AND_DESCRIPTION);

            if (addVisibleSign && qtySigns == 1)
                signatureAppearance.SetPageRect(new iText.Kernel.Geom.Rectangle(36, 20, 144, 53)).SetPageNumber(pageNumber);
            else if (addVisibleSign && qtySigns == 2)
                signatureAppearance.SetPageRect(new iText.Kernel.Geom.Rectangle(160, 20, 268, 53)).SetPageNumber(pageNumber);
            else if (addVisibleSign && qtySigns == 3)
                signatureAppearance.SetPageRect(new iText.Kernel.Geom.Rectangle(284, 20, 392, 53)).SetPageNumber(pageNumber);
            else if (addVisibleSign && qtySigns == 4)
                signatureAppearance.SetPageRect(new iText.Kernel.Geom.Rectangle(408, 20, 516, 53)).SetPageNumber(pageNumber);

            var pk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(certificate.PrivateKey).Private;

            IExternalSignature externalSignature = new PrivateKeySignature(pk, "SHA-1");
            objStamper.SignDetached(externalSignature, objChain, crlList, ocspClient, tsaClient, 0, PdfSigner.CryptoStandard.CMS);

            if (objReader != null)
            {
                objReader.Close();
            }
        }
        catch (Exception ex)
        {
            result.error = true;
            result.errorMessage += "Error: " + ex.Message;
        }
    }

谢谢!

共有2个答案

柳弘方
2023-03-14

证书。根据我的经验,在处理非RSA密钥对时,PrivateKey不可靠。例如,具有ECDsa密钥对的证书在调用时将抛出NotSupportedException(“不支持证书密钥算法”)。PrivateKey,不知道为什么(其实这里有原因,也可能有用)。所以我实现了一个(小janky)解决方案来检索私钥,并用它创建一个IExternalSignature。由于代码不知道证书具有什么类型的密钥,而且我也懒得处理其他属性来查找,因此我编写了一个循环,尝试进行通用、RSA、DSA和ECDsa密钥提取。泛型也适用于RSA,但由于X509Certificate2有一个GetRSAPrivateKey()方法,我为它做了一个切换案例以防万一。

#nullable enable
private (IExternalSignature? signature, IDisposable? key) ExtractPrivateKey(X509Certificate2 certificate)
{
  IExternalSignature? signature = null;
  bool foundPk = false;
  int attempt = 0;
  IDisposable? key = null;
  
  while (foundPk is false)
  {
      try
      {
          switch (attempt)
          {
              case 0:
                  AsymmetricAlgorithm? pk = certificate.PrivateKey; //throws exception when certificate has an ECDsa key
                  signature = new PrivateKeySignature(DotNetUtilities.GetKeyPair(pk).Private, _digestAlgorithm);
                  key = pk;
                  break;
              case 1:
                  RSA rsa = certificate.GetRSAPrivateKey() ?? throw new NotSupportedException("RSA private key is null on the certificate.");
                  signature = new PrivateKeySignature(DotNetUtilities.GetRsaKeyPair(rsa).Private, _digestAlgorithm);
                  key = rsa;
                  break;
              case 2:
                  DSA dsa = certificate.GetDSAPrivateKey() ?? throw new NotSupportedException("Dsa private key is null on the certificate.");
                  signature = new PrivateKeySignature(DotNetUtilities.GetDsaKeyPair(dsa).Private, _digestAlgorithm);
                  key = dsa;
                  break;
              case 3:
                  ECDsa ecdsa = certificate.GetECDsaPrivateKey() ?? throw new NotSupportedException("ECDsa private key is null on the certificate.");
                  signature = new EcdsaSignature(ecdsa, _digestAlgorithm);
                  key = ecdsa;
                  break;
          }
          foundPk = true;
      }
      catch (Exception e)
      {
          _logger.LogDebug(e, $"Private key extraction ran into an exception. Attempt (zero-based): {attempt}");
      }

      attempt++;
  }

  if (signature == null)
  {
      throw new NotSupportedException(
          $"The private key of the certificate could not be retrieved for signing. {JsonConvert.SerializeObject(certificate.SubjectName)}");
  }

  return (signature, key);
}

正如您所见,iText 7有一个方便的实用程序类,其中包含许多从Java版本移植的util函数,但由于某些原因,这些函数只有很多,而不是全部。它不支持ECDsa密钥对,所以我必须实现我自己的版本。为了完整起见,该课程包含在下面。

#nullable enable
using System;
using System.Security.Cryptography;
using iText.Signatures;
public class EcdsaSignature : IExternalSignature
{
    private readonly string _encryptionAlgorithm;
    private readonly string _hashAlgorithm;
    private readonly ECDsa _pk;
    private const string EcdsaEncryptionAlgorithm = "ECDSA";

    public EcdsaSignature(ECDsa? pk, string hashAlgorithm)
    {
        _pk = pk ?? throw new ArgumentNullException(nameof(pk), "ECDSA private key cannot be null.");
        _hashAlgorithm = DigestAlgorithms.GetDigest(DigestAlgorithms.GetAllowedDigest(hashAlgorithm));
        _encryptionAlgorithm = EcdsaEncryptionAlgorithm;
    }

    /// <summary>
    ///     <inheritDoc />
    /// </summary>
    public virtual string GetEncryptionAlgorithm()
    {
        return _encryptionAlgorithm;
    }

    /// <summary>
    ///     <inheritDoc />
    /// </summary>
    public virtual string GetHashAlgorithm()
    {
        return _hashAlgorithm;
    }

    /// <summary>
    ///     <inheritDoc />
    /// </summary>
    /// <remarks>
    ///     <see href="https://stackoverflow.com/a/67255440/6389395"> RFC 5480 format.</see>
    /// </remarks>
    public virtual byte[] Sign(byte[] message)
    {
        return _pk.SignData(message, new HashAlgorithmName(_hashAlgorithm), DSASignatureFormat.Rfc3279DerSequence);
    }
}

还要注意,ExtractPrivateKey()方法也会将密钥作为IDisposable返回,这样签名完成后就可以处理它了。我本可以在EcdsaSignature中创建一个公共属性,但iText自己的IExternalSignatures也不公开PK,所以我也不应该公开PK。

崔棋
2023-03-14

我不相信那个类被移植到iText 7——它只是一个包装类。

在本例中,您可以看到如何创建自定义IExternalSignatureContainer

请注意,可以在此处找到iText 5 X509Certificate2Signature的源代码

所以像这样:

public class X509Certificate2Signature: IExternalSignature {
    private String hashAlgorithm;
    private String encryptionAlgorithm;
    private X509Certificate2 certificate;

    public X509Certificate2Signature(X509Certificate2 certificate, String hashAlgorithm) {
        if (!certificate.HasPrivateKey)
            throw new ArgumentException("No private key.");
        this.certificate = certificate;
        this.hashAlgorithm = DigestAlgorithms.GetDigest(DigestAlgorithms.GetAllowedDigest(hashAlgorithm));
        if (certificate.PrivateKey is RSACryptoServiceProvider)
            encryptionAlgorithm = "RSA";
        else if (certificate.PrivateKey is DSACryptoServiceProvider)
            encryptionAlgorithm = "DSA";
        else
            throw new ArgumentException("Unknown encryption algorithm " + certificate.PrivateKey);
    }

    public virtual byte[] Sign(byte[] message) {
        if (certificate.PrivateKey is RSACryptoServiceProvider) {
            RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) certificate.PrivateKey;
            return rsa.SignData(message, hashAlgorithm);
        }
        else {
            DSACryptoServiceProvider dsa = (DSACryptoServiceProvider) certificate.PrivateKey;
            return dsa.SignData(message);
        }
    }

    public virtual String GetHashAlgorithm() {
        return hashAlgorithm;
    }

    public virtual String GetEncryptionAlgorithm() {
        return encryptionAlgorithm;
    }
}

将在iText 7中复制该类的函数。在我的第一个链接中显示了如何使用该类,不过您很可能会使用signdefered()方法,而不是signdefered()方法

 类似资料:
  • 我想用Java语言创建一个X509证书,然后从中提取公钥。 我在网上搜索了一下,发现了很多代码示例,但都有错误(未知变量或未知类型),或者有很多警告说:“方法...来自类型...是不建议使用的”等。 例如,以下代码不起作用的原因: 谁能告诉我如何使用纯Java或Bouncy Castle创建证书,然后从中获取公钥? 谢谢大家。

  • 过去,我一直在通过导出带有密码的PFX证书来制作安全的TcpListener,但我想知道是否可以跳过这一步。 我没有使用商业SSL证书,并且有一个根CA,用于颁发服务器证书。在C#中托管TcpListener时,这些服务器证书需要额外的步骤(我想是因为没有使用CSR)。。。但是如果我有私钥和OpenSSL生成/使用的证书呢。 这很好,但是我必须发出openssl命令,从证书和私钥生成pfx文件,然

  • 问题内容: 是否可以在不使用Bouncy Castle 类的情况下用Java代码合理地创建X509证书? 问题答案: 签名证书的能力不是标准Java库或扩展的一部分。 您自己需要做的许多代码都是核心的一部分。有一些类可以编码和解码X.500名称,X.509证书扩展,各种算法的公钥,当然还有用于实际执行数字签名的类。 自己实施这项工作并非易事,但绝对是可行的-我第一次制作可用于证书签名的原型时,可能

  • 问题内容: 我想用PyCrypto在python中加密一些数据。 但是使用时出现错误: 密钥是使用以下命令生成的: 代码是: 问题答案: PyCrypto不支持X.509证书。您必须首先使用以下命令提取公钥: 然后,您可以在上使用。 如果您不想或不能使用openssl,则可以获取PEM X.509证书,并使用纯Python进行认证,如下所示:

  • 我已经使用Bouncy Castle创建了X509证书,但我不能与或一起使用,因为它们(当然)使用的是.NET版本。虽然Bouncy Castle中有一个转换器,它接受一个BC X509并返回一个.NET X509。问题似乎是AuthenticateasServer/AuthenticateasClient需要包含私钥的证书。至少当我尝试转换并使用新证书时,在尝试使用SSLStream进行连接时会

  • 问题内容: 我试图分几个步骤处理X509证书,并遇到了两个问题。我是JCE的新手,所以我还没有完全了解最新信息。 我们希望能够基于不同的编码(PEM,DER和PCKS7)解析几个不同的X509证书。我已经使用FireFox 从https://belgium.be以PEM和PCKS7格式导出了相同的证书(证书包括链)。我已经省略了几行不需要的问题 只要我使用FileInputStream而不是PCK