Внешняя подпись PDF с iTextsharp (3) - PullRequest
1 голос
/ 15 января 2020

Необходимо подписать PDF с помощью внешнего веб-сервиса, который подписывает документ ha sh, процесс должен быть выполнен в 2 этапа и с использованием временной пустой подписи.

После Вопрос Приянка и вопрос Грацины и после прочтения ответов mkl на эти посты у меня в настоящее время есть недействительная подпись, даже после добавления префикса ha sh, как это сделал Грацина.

Версия iTextSharp: 5.5 .13.1

Эта программа - еще один подход к моему предыдущему вопросу . Текущий код (компилируется и начинает вызывать SignPDF метод):

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

РЕЗУЛЬТАТЫ ПОЛУЧЕНЫ

Check image from Acrobat reader signature validity check

СООБЩЕНИЕ HA SH (BASE 64):

lA2cMByHLkuNdd+aHJRDy3GD2VIeIpVtzlgQGsq3cJw=

СООБЩЕНИЕ HA SH С ПРЕФИКСОМ (BASE 64):

MDEwDQYJYIZIAWUDBAIBBQAEIJQNnDAchy5LjXXfmhyUQ8txg9lSHiKVbc5YEBrKt3Cc

ПОДПИСАННЫЙ HA SH (база 64):

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

ОБНОВЛЕНИЕ Проверка подписи PDF с помощью mkl тестовой области , получение следующих результатов:

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

Попытка добавить дополнения к подписи, помогите оценили.

1 Ответ

1 голос
/ 16 января 2020

Значение подписи не соответствует сертификату подписывающего лица

Прежде всего, судя по результату PDF , вызов GetUserCertificates не отображается, чтобы вернуть сертификат подписавшего подписи, возвращенной позже GetSignature несмотря на использование одного и того же значения userId.

Об этом свидетельствует вывод AnalyzeSignatures, вставленный в вопрос:

SignerInfo: [REMOVED] 8957244856106358046
Certificate: [REMOVED]

SID в одном SignerInfo в контейнере подписи совпадает с одним сертификатом из включенного набора сертификатов.

!!! 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

Публичный c ключ этого сертификата является ключом RSA, а длина значения SignerInfo подписи соответствует длина ключа, но дешифрование значения с использованием этого ключа не возвращает ни что-то дополненное PKCS # 1 v1.5, ни структуру PSS. Таким образом, «значение подписи» либо вовсе не является значением подписи RSA, либо это подпись, созданная с использованием закрытого ключа, не совпадающего с ключом publi c в предполагаемом сертификате подписавшего.

Таким образом, первый нужно проанализировать скрытые части кода

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

и

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

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

Ошибки рамки подписи PDF

В рамке подписи PDF есть некоторые ошибки. Код в предыдущем вопросе «Внешняя подпись PDF с помощью iText (2) [закрыто]» выглядел более корректно, поэтому я бы предложил использовать следующий подход на основе кода после выяснения правильного использования подписи веб-сервис здесь.

Тем не менее, вот объяснение двух ошибок, которые быстро бросились мне в глаза:

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

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

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

Неправильно ha sh отправлено для подписания на веб-сервис

Ха sh, возвращаемое CreatePDFEmtySignature, является результатом попытки класса MyExternalEmptySignatureContainer определить га sh диапазонов байтов со знаком.

Но обычно в контексте контейнеров сигнатур CMS (за исключением самого примитивного вида) фактические байты подписи создаются не для байтов документа напрямую, а для структуры атрибутов (так называемые «подписанные атрибуты» или «аутентифицированные атрибуты»); га sh байтов документа является просто значением одного из этих атрибутов.

Таким образом, значение подписи signedHash для hash, возвращаемое GetSignature в CreateFinalSignature через MyExternalSignatureContainer.Sign вводится в контейнер сигнатур PKCS # 7 / CMS SignerInfo, чьи аутентифицированные атрибуты имеют совершенно другой тип ha sh.

При использовании класса iText PdfPKCS7 для создания контейнера сигнатуры байты значения сигнатуры должны рассчитываться для га sh результата PdfPKCS7.GetAuthenticatedAttributeBytes с параметрами, соответствующими параметрам последующего вызова PdfPKCS7.GetEncodedPKCS7.

Таким образом, проверка фактических байтов подписи для PDF-файлов, подписанных следующим образом, должна сбой.

Ха sh, завернутый в DigestInfo, используемый там, где он должен быть голым

Ха sh, возвращаемый CreatePDFEmtySignature, упакован в структуру DigestInfo (с добавлением нескольких байтов) соответственно в MyExternalEmptySignatureContainer.Sign).

Через CreateFinalSignature в MyExternalSignatureContainer.Sign позже оно передается PdfPKCS7 как га sh подписанных байтов. Но здесь это ожидается голым, не завернутым в DigestInfo структуру.

Таким образом, проверка ха sh байтов подписанного документа для PDF-файлов, подписанных следующим образом, должна завершиться неудачей.

...