iText7 (.net) SignExternalSignatureContainer NullReferenceException

iText7 (.net) SignExternalSignatureContainer NullReferenceException

我正在尝试创建一个可以对 PDF 文档进行数字签名的 C# 程序。为了将签名元素包含到 PDF 中,我使用 iText7。 现在,如果我 运行 没有调试器的程序将被抛出 System.NullReferenceException 并且程序失败。但是如果我 运行 使用调试器的程序也会发生异常并且代码继续并正确签署 PDF...

我不确定这是 iText7 的问题还是我在创建签名字段时犯了错误或忘记了一些重要的事情。

有什么解决办法吗?

异常

System.NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt. bei iText.Signatures.PdfSignatureAppearance.GetAppearance() in C:\Development\Others\itext7\itext\itext.sign\itext\signatures\PdfSignatureAppearance.cs:Zeile 584. bei iText.Signatures.PdfSigner.PreClose(IDictionary`2 exclusionSizes) in C:\Development\Others\itext7\itext\itext.sign\itext\signatures\PdfSigner.cs:Zeile 808. bei iText.Signatures.PdfSigner.SignExternalContainer(IExternalSignatureContainer externalSignatureContainer, Int32 estimatedSize) in C:\Development\Others\itext7\itext\itext.sign\itext\signatures\PdfSigner.cs:Zeile 582. bei SignService.Engine.Core.PdfEngine.AddSignature(SignTask task) in C:\Development\Signature\SignService.Engine\Core\PdfEngine.cs:Zeile 122.

我的代码

/// <summary>
/// Add SignatureField to Pdf
/// and digitally sign it 
/// </summary>
public SignatureResult AddSignature(SignTask task)
{
    _logger.Info("Start Signing PDF");

    var prop = task.SignatureProperties;

    try
    {
        var reader = new PdfReader(new MemoryStream(prop.Document));
        var stream = new ByteArrayOutputStream();
        var signer = new PdfSigner(reader, stream, new StampingProperties().UseAppendMode());

        // set appearance
        var appearance = signer.GetSignatureAppearance();
        appearance.SetReason(prop.SignReason)
                  .SetLocation(prop.SignLocation)
                  .SetContact(prop.SignContact);

        // set rendering mode
        switch (_settings.Pdf.RenderingMode)
        {
            case SignatureRenderingMode.Description:
                appearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
                break;
            case SignatureRenderingMode.Graphic:
                appearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
                break;
            case SignatureRenderingMode.GraphicAndDescription:
                appearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC_AND_DESCRIPTION);
                break;
            case SignatureRenderingMode.NameAndDescription:
                appearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.NAME_AND_DESCRIPTION);
                break;
        }

        // set image
        if (!string.IsNullOrEmpty(_settings.Pdf.SignatureGraphicPath))
        {
            if (File.Exists(_settings.Pdf.SignatureGraphicPath))
            {
                var imageData = ImageDataFactory.Create(_settings.Pdf.SignatureGraphicPath);
                appearance.SetSignatureGraphic(imageData);
            }
            else if (_settings.Pdf.RenderingMode == SignatureRenderingMode.GraphicAndDescription || _settings.Pdf.RenderingMode == SignatureRenderingMode.GraphicAndDescription)
            {
                throw new Exception("Failed to create Signature Field.\nIf rendering mode is graphic or graphic and description, a signature image must be provided");
            }
        }

        // set visibility
        if (prop.Visible)
        {
            var rect = new Rectangle(prop.X, prop.Y, prop.Width, prop.Height);
            appearance.SetPageRect(rect);
            appearance.SetPageNumber(1); // todo create setting for this
        }

        signer.SetFieldName(_settings.Pdf.SignatureFieldName);

        // sign field
        var signatureContainer = new ExternalSignatureContainer(task, _settings.Ais);
        // **Exeption thrown here**
        signer.SignExternalContainer(signatureContainer, GetEstimatedSize(task.TimestampOnly));

        var tempResult = stream.ToArray();

        // set revocation info if active
        if (tempResult.Length > 0 && _settings.Ais.AddRevocationInfo)
        {
            tempResult = AddRevocationInfo(signatureContainer.Crl, signatureContainer.Ocsp, tempResult);
        }

        reader.Close();
        stream.Close();

        _logger.Info("Finished signing");

        // return sign result
        return tempResult.Length > 0 
          ? new SignatureResult
          {
               Message = "",
               Status = RequestStatus.Success,
               Document = stream.ToArray(),
               Id = prop.Id
          }
          : new SignatureResult
          {
               Message = "Failed to sign the Document",
               Status = RequestStatus.Failed,
               Document = null,
               Id = prop.Id
          };
    }
    catch (AisServiceException aisServiceException)
    {
        _logger.Error($"While requesting Signature an error occured: {aisServiceException.Message}", aisServiceException);
        throw;
    }
    catch (Exception exception)
    {
        _logger.Error($"While creating signed pdf an error occured: {exception.Message}", exception);
        throw new PdfException($"While creating Signed Pdf an error Occured: {exception.Message}");
    }
}

设置:

{
  "Pdf": {
    "Visible": true,
    "Position": {
      "X": 50,
      "Y": 50,
      "Height": 100,
      "Width": 200
    },
    "SignatureFieldName": "SignatureField",
    "SignatureGraphicPath": "",
    "RenderingMode": 1
  }
}

任务:

  "SignatureProperties": {
    "Id": "1",
    "Document": [DocumentAsByteArray],
    "Visible": true,
    "SignReason": "Test",
    "SignLocation": "Test",
    "SignContact": "Test",
    "Height": 100,
    "Width": 200,
    "X": 50,
    "Y": 50
  }

在您的代码中,您没有使用 PdfSignatureAppearance.SetCertificate 设置签署者证书。虽然在像您这样的 SignExternalContainer 用例的情况下,iText 确实不需要用于实际签名过程的证书,但它确实需要它来检索有关签名者的信息以用作名称,并在可见签名的情况下用于描述带有名称 and/or 描述(即任何渲染模式,但纯 GRAPHICS)。

您的设置 "RenderingMode": 1 和堆栈跟踪 PdfSignatureAppearance.cs:Zeile 584 表明您处于 NAME_AND_DESCRIPTION 用例中。因此,iText 尝试构建一个描述,访问它的 signCertificate,但失败了,因为该成员是 null.

要解决此问题,请同时使用 PdfSignatureAppearance.SetCertificate 设置签名者证书。

DESCRIPTIONGRAPHIC_AND_DESCRIPTION 的情况下,您也可以使用 PdfSignatureAppearance.SetLayer2Text.

将描述设置为预制值

不过,在 NAME_AND_DESCRIPTION 的情况下,iText 无法提供注入预先计算的名称的方法。

一个更通用的替代方法是自己创建签名外观,只需使用 GetLayer2 检索一个 PdfFormXObject,您可以在上面绘制任何您喜欢的可视化效果。在这种情况下,iText 不会尝试从证书中检索任何签名者信息。