Search code examples
c#digital-signaturenullreferenceexceptionitext7

iText7 (.net) SignExternalSignatureContainer NullReferenceException


Im trying to create a C# Program that can Digitally Sign PDF Documents. For Including the Signature Element into the PDF I'm using iText7. Now if I run the program without the debugger a System.NullReferenceException will be thrown and the program fails. But if I run the Program with the debugger the exception also occurs and the code continues and sign the PDF correctly...

Im not sure if this a problem of iText7 or if I made a mistake or forgot something important while creating the signature field.

Any Ideas how to solve this?

Exception

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.

My Code

/// <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}");
    }
}

Settings:

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

Task:

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

Solution

  • In your code you don't set the signer certificate using PdfSignatureAppearance.SetCertificate. While iText indeed does not need a certificate for the actual signing process in case of a SignExternalContainer use case like yours, it does need it for retrieving information about the signer to use as name and in the description in case of visible signatures with name and/or descriptions (i.e. any rendering mode but pure GRAPHICS).

    Your setting "RenderingMode": 1 and your stack trace PdfSignatureAppearance.cs:Zeile 584 indicate that you are in a NAME_AND_DESCRIPTION use case. Thus, iText attempts to build a description, accesses its signCertificate, and fails because that member is null.

    To fix this, please also set the signer certificate using PdfSignatureAppearance.SetCertificate.

    In case of DESCRIPTION and GRAPHIC_AND_DESCRIPTION you can alternatively set the description to a pre-fabricated value using PdfSignatureAppearance.SetLayer2Text.

    In case of NAME_AND_DESCRIPTION, though, iText offers no way to inject a pre-calculated name.

    A more general alternative is to create the signature appearance all by yourself, simply use GetLayer2 to retrieve a PdfFormXObject on which you can draw any visualization you like. In that case iText does not try and retrieve any signer information from the certificate.