2015-09-28 1 views
3

J'ai donc travaillé avec l'implémentation Java d'IText, mais maintenant je suis en train d'écrire un port de notre processus de signature en C#, et j'ai rencontré un problème. Donc quand je signe mon document en utilisant la surcharge SetVisibleSignature (rect, page, name), il signe le document comme attendu (tant que le champ de signature n'existe pas), mais quand j'utilise la surcharge SetVisibleSignature (name) pour signer un existant champ, il ne signe pas le document. Suis-je en train de faire quelque chose de stupide et de manquer quelque chose?ITextSharp SetVisibleSignature ne fonctionne pas comme prévu

Nous vous remercions de votre aide.

code mis à jour

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; 
     } 
    } 

Valeurs

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

* il ne signe pas vraiment le document * - Qu'est-ce qui se passe à la place? – mkl

+0

+1 juste pour dire "ne fonctionne pas comme prévu" au lieu de dire "ne fonctionne pas" comme beaucoup d'autres personnes ont tendance à le faire. –

+0

Donc le document augmente en taille comme je m'attendais quand il crée les révisions, mais le champ de signature reste non signé sur le document ainsi que le panneau de signature. Mais quand je signe, n'utilisant pas SetVisibleSignature, il crée les signatures invisibles comme prévu, et les blocs existants sont laissés non signés. Les noms correspondent aux blocs de signature que je veux signer. Si je prends un document complètement non signé et que j'utilise le redéfini SetVisibleSignature (rect, page, name), il signe le document comme je le souhaite. – Johandre

Répondre

4

Le code fourni par les références OP et les accès multiples objets de classes inconnus. Pour le rendre exécutable, par conséquent, il a dû être réduit pour être autonome. La version réduite peut heureusement encore être utilisée pour reproduire et analyser le problème, cf. le post-scriptum. Toute déclaration d'ici est basée sur le comportement de cette version réduite.

La question observée par l'OP pourrait être reproduit en utilisant iTextSharp 5.5.7 (et en utilisant de façon analogue iText 5.5.7), et aussi très intéressant, il pourrait pas être reproduit en utilisant la version 5.5.6 de la bibliothèque ou l'autre. Comme je suis plus dans Java, j'ai regardé dans les changements dans iText. Ils avaient été portés à iTextSharp d'une manière très fidèle.

En effet, cette question est une régression, signature des champs de signature pré-existante vide en mode append est cassé dans iText (Sharp) 5.5.7.

Entre les 5.5.6 et 5.5.7, une modification a été apportée à PdfSignatureAppearance.preClose. Si vous signez un champ de signature existant, le code utilisé pour manipuler le premier widget du champ de signature en question (af.getFieldItem(name).getWidget(0)), maintenant il fonctionne sur le dictionnaire fusionné associé (af.getFieldItem(name).getMerged(0)). Malheureusement, alors que le premier était un objet existant réellement dans le PDF d'origine et, par conséquent, appelant writer.markUsed car il marquait son contenu modifié pour écrire dans la section de mise à jour incrémentielle, ce dernier ne correspond pas à un objet dans le PDF d'origine (il s'agit d'une agrégation virtuelle de plusieurs objets), donc l'appel writer.markUsed car il ne pas marquer les changements à être écrit comme mise à jour incrémentielle plus. Ainsi, alors que la valeur de signature actuelle est toujours écrite dans le fichier, elle n'est plus connectée au champ de signature désigné.


La modification a été effectuée pour corriger le comportement de la méthode.

Avant cela, preClosed ne fonctionnait pas correctement car il récupérait le dictionnaire de champs en tant qu'annotation de widget. C'est incorrect dans le cas où les dicts de champ et de widget ne sont pas fusionnés.Au cas où ils ont été fusionnés, tout a fonctionné comme prévu. Ce dernier est le cas le plus possible pour les champs de signature numérique, mais il n'est pas obligatoire selon les spécifications.

(DEV-1448)

Ceci est correct, en cas de champ distinct et dictionnaires widget certaines modifications doivent être apportées au champ, et non le widget. Simplement l'implémentation ne fonctionne pas comme souhaité dans ajouter le mode.


PS: Ceci est la version allégée du code de l'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: Les tests Java/iText ont été effectuées en utilisant le test unitaire signTest_2_user2699460 en ComplexSignatureFields.java qui fonctionne sur test-2-user2699460.pdf, une sortie intermédiaire du code C# ci-dessus.

PPPS: Pendant ce temps, les changements résultant de la régression ont été annulées:

Retourné l'utilisation de la méthode .getWidget au lieu de .getMerged depuis le cas, lorsque le dictionnaire de champ de signature et dictionnaire son annotation widget ne sont pas fusionnés, est plutôt rare si peut être rencontré du tout. De plus, l'utilisation du dictionnaire fusionné au lieu du widget nécessite plus d'efforts puisque la méthode .getMerged ne renvoie pas réellement le dictionnaire obtenu en fusionnant le dict de champ de signature dict et l'annotation widget, mais aussi AcroForm dict.

(DEV-1579)

Ainsi, la régression la plus probable sera résolu dans la version 5.5.8

+0

Merci pour la perspicacité dans le problème :) j'étais complètement perdu dessus. – Johandre

+0

@ user2699460 Avez-vous vérifié que votre code original fonctionne aussi sur 5.5.6? – mkl

+0

Désolé pour la réponse lente a eu des problèmes de production le vendredi :(mais j'ai rétrogradé à itextsharp 5.5.6 et maintenant cela fonctionne comme prévu;) merci pour tous les conseils et l'aide :) – Johandre