2015-09-28 3 views
3

Так что я работал с реализацией Java IText, но теперь я в значительной степени пишу порт нашего процесса подписания на C#, и я попал в ловушку. Поэтому, когда я подписываю свой документ с помощью перегрузки SetVisibleSignature (rect, page, name), он подписывает документ как ожидалось (до тех пор, пока поле подписи не существует), но когда я использую перегрузку SetVisibleSignature (name) для подписи существующего поле, оно фактически не подписывает документ. Я что-то делаю глупо, возможно, и что-то не хватает?ITextSharp SetVisibleSignature работает не так, как ожидалось

Благодарим за помощь.

Обновленный код

using iTextSharp.text; 
using iTextSharp.text.pdf; 
using iTextSharp.text.pdf.security; 
using Org.BouncyCastle.Security; 
using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq; 
using System.Security.Cryptography; 
using System.Security.Cryptography.X509Certificates; 
using BouncyCastle = Org.BouncyCastle; 

    public class DocumentSigner : IDocumentSigner 
    { 
     private const string _datetimeFormat = "dd/MM/yyyy hh:mm:ss"; 
     private readonly IDateTimeProvider _dateTimeProvider; 
     private readonly ISettingManager _settingManager; 

     public DocumentSigner(IDateTimeProvider dateTimeProvider, ISettingManager settingManager) 
     { 
      Guard.ArgumentNotNull(dateTimeProvider, "dateTimeProvider"); 
      Guard.ArgumentNotNull(settingManager, "settingManager"); 

      _dateTimeProvider = dateTimeProvider; 
      _settingManager = settingManager; 
     } 

     public byte[] Sign(Certificate certificate, SigningInformation information, List<SigningBlock> signingBlocks, List<MemberItemSignature> signatureImages, byte[] document, bool certify) 
     { 
      document = AddMetaData(information, document); 
      document = AddSignatureFields(information, signingBlocks, document); 
      return SignDocument(certificate, information, signingBlocks, signatureImages, document, certify); 
     } 

     private byte[] AddMetaData(SigningInformation information, byte[] document) 
     { 
      if (information.CustomProperties != null && information.CustomProperties.Any()) 
      { 
       using (MemoryStream outputStream = new MemoryStream()) 
       { 
        using (PdfReader reader = new PdfReader(document)) 
        { 
         using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true)) 
         { 
          Dictionary<string, string> currentProperties = reader.Info; 
          foreach (string key in information.CustomProperties.Keys) 
          { 
           if (currentProperties.ContainsKey(key)) 
           { 
            currentProperties[key] = information.CustomProperties[key]; 
           } 
           else 
           { 
            currentProperties.Add(key, information.CustomProperties[key]); 
           } 
          } 

          stamper.MoreInfo = currentProperties; 
         } 
        } 

        return outputStream.ToArray(); 
       } 
      } 

      return document; 
     } 

     private byte[] AddSignatureFields(SigningInformation information, List<SigningBlock> signingBlocks, byte[] document) 
     { 
      for (int i = 0; i < signingBlocks.Count; i++) 
      { 
       using (MemoryStream outputStream = new MemoryStream()) 
       { 
        using (PdfReader reader = new PdfReader(document)) 
        { 
         using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true)) 
         { 
          CreateSignatureField(reader, stamper, signingBlocks[i]); 
         } 
        } 

        document = outputStream.ToArray(); 
       } 
      } 

      return document; 
     } 

     private PdfSignatureAppearance CreatePdfAppearance(PdfStamper stamper, SigningInformation information, bool certify) 
     { 
      PdfSignatureAppearance appearance = stamper.SignatureAppearance; 
      appearance.Location = information.Location; 
      appearance.Reason = information.Purpose; 
      appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION; 
      CreatePdfAppearanceCertifyDocument(appearance, certify); 

      return appearance; 
     } 

     private void CreatePdfAppearanceCertifyDocument(PdfSignatureAppearance appearance, bool certify) 
     { 
      if (certify) 
      { 
       appearance.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING; 
      } 
      else 
      { 
       appearance.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED; 
      } 
     } 

     private PdfStamper CreatePdfStamper(PdfReader reader, MemoryStream outputStream, byte[] document) 
     { 
      return PdfStamper.CreateSignature(reader, outputStream, '\0', null, true); 
     } 

     private void CreateSignatureField(PdfReader reader, PdfStamper stamper, SigningBlock signingBlock) 
     { 
      if (signingBlock == null) 
      { 
       return; 
      } 

      if (!DoesSignatureFieldExist(reader, signingBlock.Name)) 
      { 
       PdfFormField signatureField = PdfFormField.CreateSignature(stamper.Writer); 
       signatureField.SetWidget(new Rectangle(signingBlock.X, signingBlock.Y, signingBlock.X + signingBlock.Width, signingBlock.Y + signingBlock.Height), null); 
       signatureField.Flags = PdfAnnotation.FLAGS_PRINT; 
       signatureField.FieldName = signingBlock.Name; 
       signatureField.Page = signingBlock.Page; 
       stamper.AddAnnotation(signatureField, signingBlock.Page); 
      } 
     } 

     private bool DoesSignatureFieldExist(PdfReader reader, string signatureFieldName) 
     { 
      if (String.IsNullOrWhiteSpace(signatureFieldName)) 
      { 
       return false; 
      } 

      return reader.AcroFields.DoesSignatureFieldExist(signatureFieldName); 
     } 

     private byte[] GetSignatureImage(List<MemberItemSignature> signatureImages, string signingBlockName) 
     { 
      MemberItemSignature signature = signingBlockName.Contains("Initial") ? signatureImages.Where(image => image.Use == SignatureUses.Initial).FirstOrDefault() : signatureImages.Where(image => image.Use == SignatureUses.Signature).FirstOrDefault(); 
      if (signature != null) 
      { 
       return signature.Image; 
      } 
      else 
      { 
       return null; 
      } 
     } 

     private byte[] SignDocument(Certificate certificate, SigningInformation information, List<SigningBlock> signingBlocks, List<MemberItemSignature> signatureImages, byte[] document, bool certify) 
     { 
      for (int i = 0; i < signingBlocks.Count; i++) 
      { 
       using (MemoryStream outputStream = new MemoryStream()) 
       { 
        using (PdfReader reader = new PdfReader(document)) 
        { 
         using (PdfStamper stamper = CreatePdfStamper(reader, outputStream, document)) 
         { 
          SigningBlock signingBlock = signingBlocks[i]; 
          PdfSignatureAppearance appearance = CreatePdfAppearance(stamper, information, certify && i == 0); 

          SignDocumentSigningBlock(certificate, information, signingBlock, appearance, stamper, GetSignatureImage(signatureImages, signingBlock.Name)); 
         } 
        } 

        document = outputStream.ToArray(); 
       } 
      } 

      return document; 
     } 

     private void SignDocumentSigningBlock(Certificate certificate, SigningInformation information, SigningBlock block, PdfSignatureAppearance appearance, PdfStamper stamper, byte[] signatureImage) 
     { 
      X509Certificate2 x509Certificate = new X509Certificate2(certificate.Bytes, certificate.Password, X509KeyStorageFlags.Exportable); 

      appearance.SetVisibleSignature(block.Name); 
      SignDocumentSigningBlockWithImage(signatureImage, appearance); 
      SignDocumentSigningBlockWithText(appearance, x509Certificate); 

      using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)x509Certificate.PrivateKey) 
      { 
       IExternalSignature externalSignature = new PrivateKeySignature(DotNetUtilities.GetRsaKeyPair(rsa).Private, _settingManager["DocumentSigningEncryptionHashAlgorithm"]); 
       MakeSignature.SignDetached(appearance, externalSignature, new BouncyCastle::X509.X509Certificate[] { DotNetUtilities.FromX509Certificate(x509Certificate) }, null, null, new TSAClientBouncyCastle(_settingManager["DocumentSigningTimestampingServiceAddress"]), Int32.Parse(_settingManager["DocumentSigningEstimatedTimestampSize"]), CryptoStandard.CMS); 
      } 
     } 

     private void SignDocumentSigningBlockWithImage(byte[] signatureImage, PdfSignatureAppearance appearance) 
     { 
      if (signatureImage != null && signatureImage.Length > 0) 
      { 
       Image signatureImageInstance = Image.GetInstance(signatureImage); 

       appearance.Image = signatureImageInstance; 
       appearance.SignatureGraphic = signatureImageInstance; 
      } 
     } 

     private void SignDocumentSigningBlockWithText(PdfSignatureAppearance appearance, X509Certificate2 x509Certificate) 
     { 
      if (x509Certificate == null) 
      { 
       return; 
      } 

      appearance.Layer2Text = SignDocumentSigningBlockWithTextBuildText(x509Certificate); 
      appearance.Layer2Font = new Font(Font.FontFamily.COURIER, 7.0f, Font.NORMAL, BaseColor.LIGHT_GRAY); 
      appearance.Acro6Layers = true; 
     } 

     private string SignDocumentSigningBlockWithTextBuildText(X509Certificate2 x509Certificate) 
     { 
      Dictionary<string, string> fields = SignDocumentSigningBlockWithTextBuildTextIssuerFields(x509Certificate.IssuerName.Name); 

      string organization = fields.Keys.Contains("O") ? fields["O"] : String.Empty; 
      string commonName = fields.Keys.Contains("CN") ? fields["CN"] : String.Empty; 
      string signDate = _dateTimeProvider.Now.ToString(_datetimeFormat); 
      string expirationDate = x509Certificate.NotAfter.ToString(_datetimeFormat); 

      return "Digitally signed by " + organization + "\nSignee: " + commonName + "\nSign date: " + signDate + "\n" + "Expiration date: " + expirationDate; 
     } 

     private Dictionary<string, string> SignDocumentSigningBlockWithTextBuildTextIssuerFields(string issuer) 
     { 
      Dictionary<string, string> fields = new Dictionary<string, string>(); 

      string[] issuerFields = issuer.Split(','); 
      foreach (string field in issuerFields) 
      { 
       string[] fieldSplit = field.Split('='); 
       string key = fieldSplit[0].Trim(); 
       string value = fieldSplit[1].Trim(); 

       if (!fields.Keys.Contains(key)) 
       { 
        fields.Add(key, value); 
       } 
       else 
       { 
        fields[key] = value; 
       } 
      } 

      return fields; 
     } 
    } 

Значения

_settingManager["DocumentSigningEncryptionHashAlgorithm"] = "SHA-256"; 
_settingManager["DocumentSigningTimestampingServiceAddress"] = "http://services.globaltrustfinder.com/adss/tsa"; 
_settingManager["DocumentSigningEstimatedTimestampSize"] = 104000; 
+0

* это на самом деле не подписывать документ * - Что происходит вместо этого? – mkl

+0

+1 просто для того, чтобы сказать «не работает должным образом» вместо того, чтобы говорить «не работает», как это обычно бывает у многих других людей. –

+0

Таким образом, документ увеличивается по размеру, как я ожидал бы при его создании, но поле подписи остается неподписанным как в документе, так и в панели подписи. Но когда я подписываюсь, не используя SetVisibleSignature, он создает невидимые подписи, как ожидалось, и существующие блоки остаются без знака. Имена соответствуют подписи, которые я хочу подписать. Если я беру полностью неподписанный документ и использую параметр SetVisibleSignature (rect, page, name), он подписывает документ, как я ожидаю. – Johandre

ответ

4

код предоставляемый ссылки OP и обращается к нескольким объектам неизвестных классов. Поэтому, чтобы сделать ее работоспособной, ее пришлось сократить, чтобы быть автономной.

Сокращенная версия, к счастью, все еще может использоваться для воспроизведения и анализа проблемы, ср. post scriptum. Любое утверждение здесь основано на поведении этой сокращенной версии.

Проблема наблюдалась ФП может быть воспроизведена с помощью iTextSharp 5.5.7 (и аналогично с использованием IText 5.5.7), а также очень интересно это может не быть воспроизведены с использованием версии 5.5.6 либо библиотеки. Поскольку я больше в Java, я изучил изменения в iText. Они были перенесены в iTextSharp очень точно.

Действительно, этот вопрос является регрессия, подписание ранее существовавших пустых полей подписи в режиме добавления нарушается в IText (Sharp) 5.5.7.

Между 5.5.6 и 5.5.7 изменено PdfSignatureAppearance.preClose. Если вы подписываете существующее поле подписи, код, используемый для управления первым виджемом поля подписи (af.getFieldItem(name).getWidget(0)), теперь он работает с соответствующим объединенным словарем (af.getFieldItem(name).getMerged(0)).

К сожалению, в то время как первый был объектом, фактически существующим в исходном PDF-файле, и поэтому вызывал writer.markUsed, поскольку он помечал свое измененное содержимое для записи в секцию инкрементного обновления, последнее не соответствует объекту в исходном PDF-файле (это виртуальная совокупность нескольких объектов), поэтому вызов writer.markUsed для него не отметьте изменения, которые будут записаны как инкрементное обновление.

Итак, хотя фактическое значение сигнатуры все еще записано в файл, оно больше не связано с указанным сигнатурным полем.


Это изменение было сделано для исправления поведения метода.

До этого preClosed работал некорректно, потому что он получил словарь поля в виде аннотации виджета. Это неверно, если поля и виджеты dicts не объединены.В случае их объединения все работало, как ожидалось. Последнее является наиболее вероятным случаем для полей цифровой подписи, но оно не является обязательным согласно спецификации.

(DEV-1448)

Это правильно, в случае отдельного поля и виджета словарей определенные изменения должны быть сделаны в поле, а не виджета. Просто реализация не работает по желанию в append mode.


PS: Это сокращенная версия кода OP в:

public class DocumentSigner 
{ 
    private const string _datetimeFormat = "dd/MM/yyyy hh:mm:ss"; 

    public byte[] Sign(ICollection<X509Certificate> chain, ICipherParameters pk, string signingBlock, byte[] document, bool certify, String pattern = null) 
    { 
     document = AddMetaData(document); 
     if (pattern != null) 
      File.WriteAllBytes(String.Format(pattern, "1"), document); 
     document = AddSignatureFields(signingBlock, document); 
     if (pattern != null) 
      File.WriteAllBytes(String.Format(pattern, "2"), document); 
     return SignDocument(chain, pk, signingBlock, document, certify); 
    } 

    private byte[] AddMetaData(byte[] document) 
    { 
     return document; 
    } 

    private byte[] AddSignatureFields(string signingBlock, byte[] document) 
    { 
      using (MemoryStream outputStream = new MemoryStream()) 
      { 
       using (PdfReader reader = new PdfReader(document)) 
       { 
        using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true)) 
        { 
         CreateSignatureField(reader, stamper, signingBlock); 
        } 
       } 

       document = outputStream.ToArray(); 
      } 

     return document; 
    } 

    private PdfSignatureAppearance CreatePdfAppearance(PdfStamper stamper, bool certify) 
    { 
     PdfSignatureAppearance appearance = stamper.SignatureAppearance; 
     appearance.Location = "information.Location"; 
     appearance.Reason = "information.Purpose"; 
     appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION; 
     CreatePdfAppearanceCertifyDocument(appearance, certify); 

     return appearance; 
    } 

    private void CreatePdfAppearanceCertifyDocument(PdfSignatureAppearance appearance, bool certify) 
    { 
     if (certify) 
     { 
      appearance.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING; 
     } 
     else 
     { 
      appearance.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED; 
     } 
    } 

    private PdfStamper CreatePdfStamper(PdfReader reader, MemoryStream outputStream, byte[] document) 
    { 
     return PdfStamper.CreateSignature(reader, outputStream, '\0', null, true); 
    } 

    private void CreateSignatureField(PdfReader reader, PdfStamper stamper, string signingBlock) 
    { 
     if (signingBlock == null) 
     { 
      return; 
     } 

     if (!DoesSignatureFieldExist(reader, signingBlock)) 
     { 
      PdfFormField signatureField = PdfFormField.CreateSignature(stamper.Writer); 
      signatureField.SetWidget(new Rectangle(100, 100, 200, 200), null); 
      signatureField.Flags = PdfAnnotation.FLAGS_PRINT; 
      signatureField.FieldName = signingBlock; 
      signatureField.Page = 1; 
      stamper.AddAnnotation(signatureField, 1); 
     } 
    } 

    private bool DoesSignatureFieldExist(PdfReader reader, string signatureFieldName) 
    { 
     if (String.IsNullOrWhiteSpace(signatureFieldName)) 
     { 
      return false; 
     } 

     return reader.AcroFields.DoesSignatureFieldExist(signatureFieldName); 
    } 

    private byte[] GetSignatureImage(string signingBlockName) 
    { 
     return null; 
    } 

    private byte[] SignDocument(ICollection<X509Certificate> chain, ICipherParameters pk, string signingBlock, byte[] document, bool certify) 
    { 
      using (MemoryStream outputStream = new MemoryStream()) 
      { 
       using (PdfReader reader = new PdfReader(document)) 
       { 
        using (PdfStamper stamper = CreatePdfStamper(reader, outputStream, document)) 
        { 
         PdfSignatureAppearance appearance = CreatePdfAppearance(stamper, certify); 

         SignDocumentSigningBlock(chain, pk, signingBlock, appearance, stamper, GetSignatureImage(signingBlock)); 
        } 
       } 

       document = outputStream.ToArray(); 
      } 

     return document; 
    } 

    private void SignDocumentSigningBlock(ICollection<X509Certificate> chain, ICipherParameters pk, string block, PdfSignatureAppearance appearance, PdfStamper stamper, byte[] signatureImage) 
    { 
     appearance.SetVisibleSignature(block); 
     SignDocumentSigningBlockWithImage(signatureImage, appearance); 
     SignDocumentSigningBlockWithText(appearance, chain.First()); 

     IExternalSignature externalSignature = new PrivateKeySignature(pk, "SHA-256"); 
     MakeSignature.SignDetached(appearance, externalSignature, chain, null, null, new TSAClientBouncyCastle("http://services.globaltrustfinder.com/adss/tsa"), 104000, CryptoStandard.CMS); 
    } 

    private void SignDocumentSigningBlockWithImage(byte[] signatureImage, PdfSignatureAppearance appearance) 
    { 
     if (signatureImage != null && signatureImage.Length > 0) 
     { 
      Image signatureImageInstance = Image.GetInstance(signatureImage); 

      appearance.Image = signatureImageInstance; 
      appearance.SignatureGraphic = signatureImageInstance; 
     } 
    } 

    private void SignDocumentSigningBlockWithText(PdfSignatureAppearance appearance, X509Certificate x509Certificate) 
    { 
     if (x509Certificate == null) 
     { 
      return; 
     } 

     appearance.Layer2Text = SignDocumentSigningBlockWithTextBuildText(x509Certificate); 
     appearance.Layer2Font = new Font(Font.FontFamily.COURIER, 7.0f, Font.NORMAL, BaseColor.LIGHT_GRAY); 
     appearance.Acro6Layers = true; 
    } 

    private string SignDocumentSigningBlockWithTextBuildText(X509Certificate x509Certificate) 
    { 
     Dictionary<string, string> fields = SignDocumentSigningBlockWithTextBuildTextIssuerFields(x509Certificate.IssuerDN.ToString()); 

     string organization = fields.Keys.Contains("O") ? fields["O"] : String.Empty; 
     string commonName = fields.Keys.Contains("CN") ? fields["CN"] : String.Empty; 
     string signDate = System.DateTime.Now.ToString(_datetimeFormat); 
     string expirationDate = x509Certificate.NotAfter.ToString(); 

     return "Digitally signed by " + organization + "\nSignee: " + commonName + "\nSign date: " + signDate + "\n" + "Expiration date: " + expirationDate; 
    } 

    private Dictionary<string, string> SignDocumentSigningBlockWithTextBuildTextIssuerFields(string issuer) 
    { 
     Dictionary<string, string> fields = new Dictionary<string, string>(); 

     string[] issuerFields = issuer.Split(','); 
     foreach (string field in issuerFields) 
     { 
      string[] fieldSplit = field.Split('='); 
      string key = fieldSplit[0].Trim(); 
      string value = fieldSplit[1].Trim(); 

      if (!fields.Keys.Contains(key)) 
      { 
       fields.Add(key, value); 
      } 
      else 
      { 
       fields[key] = value; 
      } 
     } 

     return fields; 
    } 
} 

PPS: Тесты Java/IText было сделано с использованием теста signTest_2_user2699460 блока в ComplexSignatureFields.java который работает на test-2-user2699460.pdf, промежуточном выходе кода C# выше.

PPPS: В то же время изменения, приводящие к регрессии были откатить:

Возвращается использование метода .getWidget вместо .getMerged так как случай, когда поле сигнатуры словаря и словаря его виджета аннотацию не слиты, довольно необычны, если их можно встретить вообще. Более того, использование объединенного словаря вместо виджета требует больших усилий, поскольку метод .getMerged возвращает не фактически словарь, полученный путем слияния сигнатурного поля dict и аннотации виджета dict, но также и AcroForm dict.

(DEV-1579)

Таким образом, регрессия, скорее всего, будет решена в версии 5.5.8

+0

Спасибо за понимание проблемы :) Я полностью потерялся на этом. – Johandre

+0

@ user2699460 Вы подтвердили, что ваш исходный код также работает на 5.5.6? – mkl

+0

Извините за медленный ответ, возникли проблемы с производительностью в пятницу :(но я понижен до itextsharp 5.5.6, и теперь он работает, как ожидалось;) спасибо за все советы и помощь :) – Johandre

Смежные вопросы