1

Need to sign PDF by using an external webservice that signs the document hash, the process has to be done in 2 steps, and using a temporary empty signature.

Following Priyanka question and Grazina question and reading the mkl answers on that posts, I currently have invalid signature, even after adding the hash prefix like Grazina did.

iTextSharp version: 5.5.13.1

This program is another aproach to my previous question. Current code (compiles and starts calling SignPDF method):

public class PDFSigner
{
    private const string SIG_FIELD_NAME = "sigField1";

    private void SignPDF(string pdfFilePath, string userId)
    {
        var preparedSigPdfFilePath = $"{pdfFilePath}.tempsig.pdf";
        var signedPdfFilePath = $"{pdfFilePath}.signed.pdf";

        //Get certificates chain from webservice
        var certificatesChain = this.GetUserCertificates(userId);

        byte[] hash = this.CreatePDFEmtySignature(pdfFilePath, preparedSigPdfFilePath, certificatesChain);

        //Get signature from webservice
        byte[] signedHash = this.GetSignature(hash, userId);

        CreateFinalSignature(preparedSigPdfFilePath, signedPdfFilePath, hash, signedHash, certificatesChain);
    }

    private byte[] CreatePDFEmtySignature(string pdfFilePath, string preparedSigPdfFilePath, List<Org.BouncyCastle.X509.X509Certificate> certificatesChain)
    {
        byte[] hash = null;

        using (PdfReader reader = new PdfReader(pdfFilePath))
        {
            using (FileStream baos = File.OpenWrite(preparedSigPdfFilePath))
            {
                PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
                PdfSignatureAppearance sap = pdfStamper.SignatureAppearance;
                sap.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 720, 160, 780), 1, SIG_FIELD_NAME);

                //TODO: check how to select the correct certificate, have 3 items on list, selected leaf after debug (first one)
                sap.Certificate = certificatesChain.First();

                var externalEmptySigContainer = new MyExternalEmptySignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED, preparedSigPdfFilePath);

                MakeSignature.SignExternalContainer(sap, (IExternalSignatureContainer)externalEmptySigContainer, 8192);

                hash = externalEmptySigContainer.PdfHash;
            }
        }
        return hash;
    }

    private void CreateFinalSignature(string preparedSigPdfFilePath, string signedPdfFilePath, 
        byte[] hash, byte[] signedHash, List<Org.BouncyCastle.X509.X509Certificate> certificatesChain)
    {
        using (PdfReader reader = new PdfReader(preparedSigPdfFilePath))
        {
            using (FileStream baos = File.OpenWrite(signedPdfFilePath))
            {
                IExternalSignatureContainer externalSigContainer = new MyExternalSignatureContainer(signedPdfFilePath, hash, signedHash, certificatesChain);
                MakeSignature.SignDeferred(reader, SIG_FIELD_NAME, baos, externalSigContainer);
            }
        }
    }

    public class MyExternalEmptySignatureContainer : ExternalBlankSignatureContainer
    {
        public string PdfTempFilePath { get; set; }
        public byte[] PdfHash { get; private set; }

        public MyExternalEmptySignatureContainer(PdfName filter, PdfName subFilter, string pdfTempFilePath) : base(filter, subFilter)
        {
            this.PdfTempFilePath = pdfTempFilePath;
        }

        override public byte[] Sign(Stream data)
        {
            byte[] sigContainer = base.Sign(data);

            //Get the hash
            IDigest messageDigest = DigestUtilities.GetDigest("SHA-256");
            byte[] messageHash = DigestAlgorithms.Digest(data, messageDigest);

            #region Log
            var messageHashFilePath = $"{this.PdfTempFilePath}.messageHash-b64.txt";
            File.WriteAllText(messageHashFilePath, Convert.ToBase64String(messageHash));
            #endregion Log

            //Add hash prefix
            byte[] sha256Prefix = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 };
            byte[] digestInfo = new byte[sha256Prefix.Length + messageHash.Length];
            sha256Prefix.CopyTo(digestInfo, 0);
            messageHash.CopyTo(digestInfo, sha256Prefix.Length);

            #region Log
            var messageHashWithPrefixFilePath = $"{this.PdfTempFilePath}.messageHash-with-prefix-b64.txt";
            File.WriteAllText(messageHashWithPrefixFilePath, Convert.ToBase64String(digestInfo));
            #endregion Log

            this.PdfHash = digestInfo;

            return sigContainer;
        }
    }

    public class MyExternalSignatureContainer : IExternalSignatureContainer
    {
        public byte[] Hash { get; set; }
        public byte[] SignedHash { get; set; }
        public List<Org.BouncyCastle.X509.X509Certificate> CertificatesList { get; set; }

        public MyExternalSignatureContainer(string signedPdfFilePath, byte[] hash, byte[] signedHash, List<Org.BouncyCastle.X509.X509Certificate> certificatesList)
        {
            this.Hash = hash;
            this.SignedHash = signedHash;
            this.CertificatesList = certificatesList;
        }

        public byte[] Sign(Stream data)
        {
            PdfPKCS7 sgn = new PdfPKCS7(null, this.CertificatesList, "SHA256", false);
            sgn.SetExternalDigest(this.SignedHash, null, "RSA");
            return sgn.GetEncodedPKCS7(this.Hash, null, null, null, CryptoStandard.CMS);         
        }

        public void ModifySigningDictionary(PdfDictionary signDic)  {   }
    }

    public byte[] GetSignature(byte[] hash, string userId)
    {
        // Request signature for hash value messageHash and return signature bytes               
        byte[] signature = null;

        //CALL WEBSERVICE:
        //signature = WEBSERVICE_CALL_TO_GET_SIGNED_HASH(hash, userId);

        return signature;
    }

    private List<Org.BouncyCastle.X509.X509Certificate> GetUserCertificates(string userId)
    {
        List<Org.BouncyCastle.X509.X509Certificate> certChain = null;

        //CALL WEBSERVICE:
        //certChain = WEBSERVICE_CALL_TO_GET_SIGNED_CERTIFICATES(userId);

        return certChain;
    }
}

RESULTS OBTAINED

Check image from Acrobat reader signature validity check

MESSAGE HASH (BASE 64):

lA2cMByHLkuNdd+aHJRDy3GD2VIeIpVtzlgQGsq3cJw=

MESSAGE HASH WITH PREFIX (BASE 64):

MDEwDQYJYIZIAWUDBAIBBQAEIJQNnDAchy5LjXXfmhyUQ8txg9lSHiKVbc5YEBrKt3Cc

SIGNED HASH (base 64):

LURoF4w3H7uwR3xltjZTBbxBlTCCyD5AqVfseg9F1jn9lfnJ4KAqDL85s2ABSN7iieqjhUd0/U7fReT8gmRV5ZVyjGZcA4BaXr9Lx5E8vLerrHfbE3lsqb4Qm4/3oWX7BjNjfK4ptrBLIaYiDW28sxRKev5mdoo9W2ecIPWAaD8wyrKG/sXj62FQsmetdB0Rzd5rPNbsjVhOeei2V1g1PgF7evJZAz6+1smIWHXPgpxQJ8gZG6KcnHy8N43TGxQ0yV6DKqpl5DGEgqDwiXUY2kGglYNkdaS/5bQy941j7AyEDulni8YXtQ+XH2opuq1OkqVPipLqQnk3DYMPQUzjWqatI1Awfhv4fnceZ2djxgpgtv03tM5PzpHmelXr1gGfcChNDA603SJr+9XVok35mslx13kv+03M4aa2Myp4JKPSNQBuqdeiXKMsXilgv1M13xdbaFL35Omq9ciQbts4kRPpeLj+9PC+kHsyrerRO8pSxHcEjojPqTdYT+pWAmlU

UPDATE Tested the PDF signature with the mkl test area, obtaining the following results:

Certificates:
Subject: [REMOVED]
Issuer: [REMOVED]
Serial: 7590871326079402939
Subject: [REMOVED]
Issuer: [REMOVED]
Serial: 1738456118788016053
Subject: [REMOVED]
Issuer: [REMOVED]
Serial: 8957244856106358046

Attribute Certificates: none

CRLs: none

SignerInfo: [REMOVED] 8957244856106358046
Certificate: [REMOVED]
Signed attribute 1.2.840.113549.1.9.4 (PKCS 9 - Message Digest)

Digest: 
3031300D060960864801650304020105000420940D9C301C872E4B8D75DF9A1
C9443CB7183D9521E22956DCE58101ACAB7709C

Signed attribute 1.2.840.113549.1.9.3 (PKCS 9 - Content Type)

Signed Attributes Hash: 
08767823328F202C1C3E5DB543785ED591C6D84D23DAF3DCBB83684B987008CB
Signed Attributes Hash Hash: 
1E2D10B23CD772D16987126182E51BD4D827DB58C497BA4129BB533A576E3548
!!! Decrypted RSA signature is not PKCS1 padded: Decryption error
Decrypted signature bytes: [REMOVED]
!!! Decrypted RSA signature does not end with the PSS 0xbc byte either

!!! Signature does not validate with certificate

Going to try to add the padding to the signature, help appreciated.

pgkdev
  • 11
  • 5
  • Currently the signing provider returns certificates with invalid key usage for signing documents, and they are fixing this, althought the document should appear as not modified since signing right? Or will have that problem until they fix that? – pgkdev Jan 15 '20 at 13:18
  • Updated the question with result from mkl signature analyzer program. – pgkdev Jan 15 '20 at 18:26
  • I just also analyzed the signature container. First off, as the quote you added indicates, the signature bytes you retrieve from your server upon decryption with the claimed signer certificate's public RSA key does neither result in something PKCS1 padded (more exactly PKCS1 v1.5 padded) nor in something PSS padded. Thus, that signature value is not what you expect, it's not a signature created for the claimed signer certificate, it's not even clear whether it's a signature at all. – mkl Jan 15 '20 at 19:14
  • Other than that your code here has a number of errors while the code in your [previous question](https://stackoverflow.com/q/59669163/1729265) looked in principal correct. – mkl Jan 15 '20 at 19:16
  • Thus, I propose you should first try to understand how to correctly retrieve a matching pair of signing certificate and signature from your service and then couple that with the code in the previous question. – mkl Jan 15 '20 at 19:20
  • Thank you, did what you suggested, contacted the signer webservice owner and detailed the probkem, with focus on question 1 implementation. – pgkdev Jan 16 '20 at 19:03

1 Answers1

1

Signature value does not match signer certificate

First and foremost, judging by the result PDF the GetUserCertificates call does not appear to return the signer certificate of the signature returned later by GetSignature in spite of using the same userId value.

This is indicated by the AnalyzeSignatures output pasted into the question:

SignerInfo: [REMOVED] 8957244856106358046
Certificate: [REMOVED]

The SID in the single SignerInfo in the signature container matches a single certificate from the included set of certificates.

!!! Decrypted RSA signature is not PKCS1 padded: Decryption error
Decrypted signature bytes: [REMOVED]
!!! Decrypted RSA signature does not end with the PSS 0xbc byte either

The public key of that certificate is an RSA key and the length of the SignerInfo signature value matches the key length but decrypting the value using that key returns neither something PKCS#1 v1.5 padded nor a PSS structure. Thus, the "signature value" either is not an RSA signature value at all or it is a signature generated using a private key not matching the public key in the alleged signer certificate.

Thus, the first thing to do is analyzing the

    //CALL WEBSERVICE:
    //signature = WEBSERVICE_CALL_TO_GET_SIGNED_HASH(hash, userId);

and

    //CALL WEBSERVICE:
    //certChain = WEBSERVICE_CALL_TO_GET_SIGNED_CERTIFICATES(userId);

hidden parts of the code and fixing them (or the web service if the problem is located there) and only then continuing integrating this fixed code in a PDF signing frame.

Errors of the PDF signing code frame

The PDF signing frame here contains some errors. The code in the previous question "External signing PDF with iText (2) [closed]" looked more correct, so I would propose basing the next approach onto the code there after finding out the correct use of the signing web service here.

Nonetheless, here an explanation of two errors that quickly caught my eyes:

byte[] hash = this.CreatePDFEmtySignature(pdfFilePath, preparedSigPdfFilePath, certificatesChain);

//Get signature from webservice
byte[] signedHash = this.GetSignature(hash, userId);

CreateFinalSignature(preparedSigPdfFilePath, signedPdfFilePath, hash, signedHash, certificatesChain);

Wrong hash sent for signing to the web service

The hash returned by CreatePDFEmtySignature is result of the attempt of the class MyExternalEmptySignatureContainer to determine the hash of the signed byte ranges.

But usually in the context of CMS signature containers (except for the most primitive kind) the actual signature bytes are not created for the document bytes directly but instead for a structure of attributes (the so called "signed attributes" or "authenticated attributes"); the hash of the document bytes is merely the value of one of those attributes.

Thus, the signature value signedHash for hash returned by GetSignature in CreateFinalSignature via MyExternalSignatureContainer.Sign is injected into a PKCS#7 / CMS signature container SignerInfo whose authenticated attributes have a completely different hash.

When using the iText PdfPKCS7 class to generate a signature container, the signature value bytes have to be calculated for the hash of the result of PdfPKCS7.GetAuthenticatedAttributeBytes with the parameters corresponding to those of the later PdfPKCS7.GetEncodedPKCS7 call.

Thus, the validation of the actual signature bytes for PDFs signed like this must fail.

Hash wrapped in DigestInfo used where it should be naked

The hash returned by CreatePDFEmtySignature is wrapped in a DigestInfo structure (by prepending some bytes accordingly in MyExternalEmptySignatureContainer.Sign).

Via CreateFinalSignature in MyExternalSignatureContainer.Sign it later is given to PdfPKCS7 as hash of the signed bytes. But here it is expected naked, not wrapped in a DigestInfo structure.

Thus, the validation of the hash of the signed document bytes for PDFs signed like this must fail.

mkl
  • 85,104
  • 14
  • 121
  • 242
  • Thank you, did what you suggested, contacted the signer webservice owner and detailed the probkem, with focus on previous question implementation. – pgkdev Jan 17 '20 at 09:53
  • While waiting for webservice signer response tried to change the code as you proposed, but to use the PdfPKCS7.GetAuthenticatedAttributeBytes with the same arguments as PdfPKCS7.GetEncodedPKCS7, first I need to have a hash, the same used on both methods. Used sgn.getAuthenticatedAttributeBytes(messageHash, null, null, CryptoStandard.CMS) and but has the same problem. Waiting for the signer webservice response. – pgkdev Jan 17 '20 at 10:02