Внешняя подпись PDF с iTextSharp - измененный / поврежденный документ - PullRequest
0 голосов
/ 24 марта 2020

Цель состоит в том, чтобы реализовать процесс подписания PDF, в котором сервер (. NET Базовая служба) предоставляет га sh для подписи клиенту по запросу (Electron). Затем клиент подписывает данный ha sh, используя закрытый ключ, полученный со смарт-карты через интерфейс PKCS # 11. Затем подпись отправляется обратно на сервер для вложения в файл PDF с помощью iTextSharp.

Процесс подписания ha sh токеном смарт-карты довольно прост с узлом webcrypto-p11 на момент (с большим количеством проб и ошибок, чтобы добраться сюда). Используемый алгоритм RSASSA-PKCS1-v1_5. Я могу успешно подписать ha sh и проверить его позже.

Я недавно построил на своей предыдущей реализации с помощью Внешняя подпись PDF с iTextsharp (3) , где я использовал getAuthenticatedAttributeBytes для получения подписи ha sh (в соответствии с предложением mkl ).

При просмотре подписи в Acrobat Reader мне предоставляют измененный документ / поврежден, так же как и OP pgkdev . Как упомянуто выше, клиентская сторона работает с процессом подписания напрямую, и я не подозреваю, что там возникнут какие-либо проблемы (хотя я открыт для рассмотрения этого).

pgkdev относится к Вопрос Приянки , где я узнал, что у меня, вероятно, есть проблемы, связанные с двухэтапным процессом подписания документа, в котором значения ha sh больше не совпадают.

Если вы отметили Вопрос Грацины мы видим, что такая реализация успешна, когда вы делаете процесс за один шаг.

mkl далее упоминает способ сделать это успешно в 2 шагов, но я упускаю некоторые дополнительные объяснения о том, как именно этого добиться.

Примечание: у меня нет возможности (о которой я знаю) сделать то, что я хочу, за 1 шаг, так как начинается подписание клиентом в приложении Electron.

enter image description hereenter image description hereenter image description here

Нажатие на сертификат подробная информация показывает мои полные данные сертификата.

private const string SIG_FIELD_NAME = "sigField1";

    private byte[] GetPDFHash(string pdfFilePath, byte[] certificateValue)
    {
        var preparedSigPdfFilePath = $"{pdfFilePath}.tempsig.pdf";

        //Get certificates chain from certificate value
        ICollection<X509Certificate> certificatesChain = GetCertificatesChain(certificateValue);

        byte[] hash = CreatePDFEmptySignature(pdfFilePath, preparedSigPdfFilePath, certificatesChain);
        return hash;
    }

    private void SignPDFHash(string pdfFilePath, byte[] hash, byte[] signedHash, byte[] certificateValue)
    {
        var preparedSigPdfFilePath = $"{pdfFilePath}.tempsig.pdf";
        var signedPdfFilePath = $"{pdfFilePath}.signed.pdf";

        //Get certificates chain from certificate value
        ICollection<X509Certificate> certificatesChain = GetCertificatesChain(certificateValue);

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

    private byte[] CreatePDFEmptySignature(string pdfFilePath, string preparedSigPdfFilePath, ICollection<X509Certificate> certificatesChain)
    {
        byte[] hash;

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

                sap.Certificate = certificatesChain.First();

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

                MakeSignature.SignExternalContainer(sap, externalEmptySigContainer, 8192);

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

    private void CreateFinalSignature(string preparedSigPdfFilePath, string signedPdfFilePath, 
        byte[] hash, byte[] signedHash, ICollection<X509Certificate> certificatesChain)
    {
        using (PdfReader reader = new PdfReader(preparedSigPdfFilePath))
        {
            using (FileStream baos = System.IO.File.OpenWrite(signedPdfFilePath))
            {
                IExternalSignatureContainer externalSigContainer = new MyExternalSignatureContainer(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 ICollection<X509Certificate> CertificatesList { get; set; }

        public MyExternalEmptySignatureContainer(PdfName filter, PdfName subFilter, string pdfTempFilePath,
            ICollection<X509Certificate> certificatesChain) : base(filter, subFilter)
        {
            PdfTempFilePath = pdfTempFilePath;
            CertificatesList = certificatesChain;
        }

        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 = $"{PdfTempFilePath}.messageHash-b64.txt";
            System.IO.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 = $"{PdfTempFilePath}.messageHash-with-prefix-b64.txt";
            System.IO.File.WriteAllText(messageHashWithPrefixFilePath, Convert.ToBase64String(digestInfo));
            #endregion Log

            var sgn = new PdfPKCS7(null, this.CertificatesList, "SHA256", false);
            var authenticatedAttributeBytes =
                sgn.getAuthenticatedAttributeBytes(messageHash, null, null, CryptoStandard.CMS);

            PdfHash = authenticatedAttributeBytes;

            return sigContainer;
        }
    }

    public class MyExternalSignatureContainer : IExternalSignatureContainer
    {
        public byte[] Hash { get; set; }
        public byte[] SignedHash { get; set; }
        public ICollection<X509Certificate> CertificatesList { get; set; }

        public MyExternalSignatureContainer(byte[] hash, byte[] signedHash, ICollection<X509Certificate> certificatesList)
        {
            Hash = hash;
            SignedHash = signedHash;
            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)  {   }
    }

    private ICollection<X509Certificate> GetCertificatesChain(byte[] certByteArray)
    {
        ICollection<X509Certificate> certChain = new Collection<X509Certificate>();

        X509Certificate2 cert = new X509Certificate2(certByteArray);

        X509Certificate regularCert = new X509CertificateParser()
            .ReadCertificate(cert.GetRawCertData());

        certChain.Add(regularCert);

        return certChain;
    }

РЕДАКТИРОВАТЬ: Подписанный PDF

РЕДАКТИРОВАТЬ: Скорректирована CreateFinalSignature для использования messageHa sh, которое было сохранено в текстовом файле. Результат тот же. Подписанный PDF

private void CreateFinalSignature(string preparedSigPdfFilePath, string signedPdfFilePath, byte[] signedHash, ICollection<X509Certificate> certificatesChain)
    {
        var messageHashFilePath = $"{preparedSigPdfFilePath}.messageHash-b64.txt";
        string hashString = System.IO.File.ReadAllText(messageHashFilePath);
        byte[] hash = Convert.FromBase64String(hashString);

        using (PdfReader reader = new PdfReader(preparedSigPdfFilePath))
        {
            using (FileStream baos = System.IO.File.OpenWrite(signedPdfFilePath))
            {
                IExternalSignatureContainer externalSigContainer = new MyExternalSignatureContainer(hash, signedHash, certificatesChain);
                MakeSignature.SignDeferred(reader, SIG_FIELD_NAME, baos, externalSigContainer);
            }
        }
    }

Хеши идентичны, как показано ниже. Я установил некоторые точки останова, чтобы попытаться перехватить значения перед сохранением и после чтения из файла.

Массив байтов перед сохранением:

[0] = {byte} 133
[1] = {byte} 170
[2] = {byte} 124
[3] = {byte} 73
[4] = {byte} 225
[5] = {byte} 104
[6] = {byte} 242
[7] = {byte} 79
[8] = {byte} 44
[9] = {byte} 52
[10] = {byte} 173
[11] = {byte} 6
[12] = {byte} 7
[13] = {byte} 250
[14] = {byte} 171
[15] = {byte} 50
[16] = {byte} 226
[17] = {byte} 132
[18] = {byte} 113
[19] = {byte} 31
[20] = {byte} 125
[21] = {byte} 174
[22] = {byte} 53
[23] = {byte} 98
[24] = {byte} 68
[25] = {byte} 117
[26] = {byte} 102
[27] = {byte} 191
[28] = {byte} 109
[29] = {byte} 180
[30] = {byte} 88
[31] = {byte} 133

Массив байтов, считанный из файла .txt в CreateFinalSignature:

[0] = {byte} 133
[1] = {byte} 170
[2] = {byte} 124
[3] = {byte} 73
[4] = {byte} 225
[5] = {byte} 104
[6] = {byte} 242
[7] = {byte} 79
[8] = {byte} 44
[9] = {byte} 52
[10] = {byte} 173
[11] = {byte} 6
[12] = {byte} 7
[13] = {byte} 250
[14] = {byte} 171
[15] = {byte} 50
[16] = {byte} 226
[17] = {byte} 132
[18] = {byte} 113
[19] = {byte} 31
[20] = {byte} 125
[21] = {byte} 174
[22] = {byte} 53
[23] = {byte} 98
[24] = {byte} 68
[25] = {byte} 117
[26] = {byte} 102
[27] = {byte} 191
[28] = {byte} 109
[29] = {byte} 180
[30] = {byte} 88
[31] = {byte} 133

РЕДАКТИРОВАТЬ: Хеширование authenticatedAttributeBytes с последующим возвратом того, что ha sh должно быть подписано клиентом.

Пробовал 3 различных способа хеширования, с тем же результатом:

PdfHa sh = DigestAlgorithms.Digest (новый MemoryStream (authenticatedAttributeBytes), messageDigest)

PdfHa sh = SHA256.Create (). ComputeHa sh (authenticatedAttributeBytes)

PdfHa sh = SHA256Managed.Create (). ComputeHa sh (authenticatedAttributeBytes)

Использование GetPDFHa sh

byte[] bytesToSign = GetPDFHash(Path.Combine(_configuration["documentFileSystemStore:DocumentFolder"], "SignMeMightySigner.pdf"), Convert.FromBase64String(dto.base64certificateValue));

Использование SignPDFHa sh

SignPDFHash(Path.Combine(_configuration["documentFileSystemStore:DocumentFolder"], "SignMeMightySigner.pdf"),Convert.FromBase64String(dto.base64signature), Convert.FromBase64String(dto.base64certificateValue));

EDIT (29.3.2020): Я проверил свою клиентскую сторону и не могу найти ничего проблемного c. Я выбираю RSASSA-PKCS1-v1_5 alg, чтобы получить подпись и впоследствии успешно ее проверить. В некоторых других вопросах я обнаружил, что это может быть проблема с передачей байтового массива между сервером и клиентом, но я проверил, и значения одинаковы, как base64, так и байтовый массив.

Решено открыть PDF в текстовом редакторе и сравните его с регулярно подписанным PDF (то же текстовое содержимое, только что подписанное непосредственно через Adobe Reader).

Меня беспокоит и беспокоит то, что в PDF, подписанном с iText, отсутствует огромный кусок "текста" внутри, который имеет непосредственно подписанный.

Могу ли я предоставить еще что-нибудь для возможного дальнейшего анализа? Я видел, как люди в переполнении стека не могут обойти эту проблему, а некоторые даже отказываются от нее полностью. Я не хочу и не могу этого сделать и хочу докопаться до сути.

Прямая подпись через Adobe Reader Отложенная подпись с iText

РЕДАКТИРОВАТЬ 30.3.2020: Как уже упоминалось выше, у меня есть sh AuthenticatedAttributeBytes

PdfHash = SHA256Managed.Create().ComputeHash(authenticatedAttributeBytes);

AuthenticatedAttributeBytes

49 75 48 24 6 9 42 134 72 134 247 13 1 9 3 49 11 6 9 42 134 72 134 247 13 1 7 1 48 47 6 9 42 134 72 134 247 13 1 9 4 49 34 4 32 122 115 111 54 139 240 60 168 176 67 64 158 55 107 233 48 77 220 19 208 139 187 42 1 141 149 20 241 151 80 31 79 

AuthenticatedAttributeBytes - хэшировано

33 25 105 92 244 51 72 93 179 135 158 84 249 178 103 91 236 247 253 35 232 124 169 112 108 214 63 206 206 2 88 107 

(возвращается клиенту) AuthenticatedAttributeBytes - зашифровано и закодировано в base64

IRlpXPQzSF2zh55U+bJnW+z3/SPofKlwbNY/zs4CWGs=

Ха sh подписано (подпись)

76 13 184 229 123 212 2 8 140 24 34 88 95 31 255 142 105 220 204 186 172 110 61 75 156 44 185 62 81 209 238 226 67 133 115 247 76 24 182 144 38 164 71 92 124 140 77 16 212 43 52 156 173 90 163 116 0 124 119 119 103 8 12 74 147 1 207 51 156 104 52 231 112 125 115 140 28 105 160 117 235 199 224 166 30 220 111 35 165 49 18 85 253 194 112 254 142 117 46 58 87 13 110 161 151 228 95 238 115 171 70 117 203 103 204 222 233 42 163 37 105 91 177 117 190 238 135 137 162 6 54 125 108 64 148 219 7 198 93 117 12 164 130 123 213 197 233 173 145 77 209 11 166 91 29 137 142 25 20 96 90 130 251 169 234 9 44 245 230 20 46 243 254 98 179 98 148 87 104 151 228 246 231 23 94 134 144 84 177 219 235 90 11 130 33 139 94 155 73 112 60 88 53 150 59 49 184 100 210 82 32 71 66 168 21 167 91 141 94 239 221 156 96 23 132 147 237 15 237 232 112 214 224 61 117 46 143 208 41 64 13 128 44 69 135 172 113 58 8 85 5 176 192 254 107 92 

(получено от клиента) Ха sh подпись (подпись) - base64

TA245XvUAgiMGCJYXx//jmnczLqsbj1LnCy5PlHR7uJDhXP3TBi2kCakR1x8jE0Q1Cs0nK1ao3QAfHd3ZwgMSpMBzzOcaDTncH1zjBxpoHXrx+CmHtxvI6UxElX9wnD+jnUuOlcNbqGX5F/uc6tGdctnzN7pKqMlaVuxdb7uh4miBjZ9bECU2wfGXXUMpIJ71cXprZFN0QumWx2JjhkUYFqC+6nqCSz15hQu8/5is2KUV2iX5PbnF16GkFSx2+taC4Ihi16bSXA8WDWWOzG4ZNJSIEdCqBWnW41e792cYBeEk+0P7ehw1uA9dS6P0ClADYAsRYescToIVQWwwP5rXA==

Байты подписи (декодированные из base64) соответствуют зарегистрированному массиву uint8 на стороне клиента. enter image description here

1 Ответ

0 голосов
/ 24 марта 2020

Ваш исходный код

В MyExternalEmptySignatureContainer.Sign вы правильно определяете аутентифицированные атрибуты, используя обнаженный ха sh потока диапазона PDF:

var authenticatedAttributeBytes = sgn.getAuthenticatedAttributeBytes(messageHash, null, null, CryptoStandard.CMS);

Однако, проверяя файл примера Я обнаружил, что подписанный атрибут Message Digest содержит ha sh, встроенный в объект DigestInfo, в других работах с sha256Prefix, который вы применяете к копии дайджеста сообщения в MyExternalEmptySignatureContainer.Sign.

* 1011. * Очевидно, поэтому, когда вы воссоздаете аутентифицированные атрибуты в MyExternalSignatureContainer.Sign
return sgn.GetEncodedPKCS7(this.Hash, null, null, null, CryptoStandard.CMS);

, вы делаете это с неправильным значением в this.Hash. Это имеет два эффекта, с одной стороны, очевидно, что значение атрибута «Дайджест сообщения» теперь неверно, а с другой стороны, значение подписи, созданное для оригинальных, правильных аутентифицированных атрибутов, не соответствует вашим некорректным. Таким образом, итоговая подпись PDF является вдвойне неправильной.

Чтобы исправить это, используйте правильное значение ha sh здесь, га sh потока диапазона PDF без этого префикса.

Поскольку вы не показываете, как именно вы используете свои методы GetPDFHash и SignPDFHash, я не могу точно определить ошибку.

Ваш обновленный код

Действительно, теперь правильный атрибут ha sh находится в атрибуте дайджеста сообщения, но подпись все еще обозначает неправильный ha sh, в случае вашего нового примера:

Signed Attributes Hash: 54B2F135A542EEAA55270AB19210E363D00A7684405403E89B170591A7BCAB5F
Decrypted signature digest: 22D906E686A83FA1A490895A21CD6F9A9272C13FB9B16D8A6E862168458F3640

причина , вероятно, является то, что содержимое вашего MyExternalEmptySignatureContainer свойства PdfHash является не ха sh, а полными байтами аутентифицированных атрибутов, ср. MyExternalEmptySignatureContainer.Sign:

var authenticatedAttributeBytes = sgn.getAuthenticatedAttributeBytes(messageHash, null, null, CryptoStandard.CMS);

PdfHash = authenticatedAttributeBytes;

Вы, вероятно, должны рассчитать га sh из authenticatedAttributeBytes и поместить его в PdfHash.

Поскольку вы не показываете, как именно вы используйте ваши методы GetPDFHash и SignPDFHash, однако об этом можно только догадываться.

Ваши зарегистрированные хэши

30 марта вы поделились журналами соответствующих данных, переданных за один прогон. В частности:

AuthenticatedAttributeBytes - хэшированные

33 25 105 92 244 51 72 93 179 135 158 84 249 178 103 91 236 247 253 35 232 124 169 112 108 214 63 206 206 2 88 107 

и

Ха sh подписанные (подпись)

76 13 184 229 123 212 2 8 140 24 34 88 95 31 255 142 105 220 204 186 172 110 61 75 156 44 185 62 81 209 238 226 67 133 115 247 76 24 182 144 38 164 71 92 124 140 77 16 212 43 52 156 173 90 163 116 0 124 119 119 103 8 12 74 147 1 207 51 156 104 52 231 112 125 115 140 28 105 160 117 235 199 224 166 30 220 111 35 165 49 18 85 253 194 112 254 142 117 46 58 87 13 110 161 151 228 95 238 115 171 70 117 203 103 204 222 233 42 163 37 105 91 177 117 190 238 135 137 162 6 54 125 108 64 148 219 7 198 93 117 12 164 130 123 213 197 233 173 145 77 209 11 166 91 29 137 142 25 20 96 90 130 251 169 234 9 44 245 230 20 46 243 254 98 179 98 148 87 104 151 228 246 231 23 94 134 144 84 177 219 235 90 11 130 33 139 94 155 73 112 60 88 53 150 59 49 184 100 210 82 32 71 66 168 21 167 91 141 94 239 221 156 96 23 132 147 237 15 237 232 112 214 224 61 117 46 143 208 41 64 13 128 44 69 135 172 113 58 8 85 5 176 192 254 107 92 

Расшифровывая это последнее значение ha sh со знаком, используя ключ publi c из вашего сертификата, однако, получается объект DigestInfo, содержащий этот ha sh:

136 138 205 115 82 228 115 151 231 220 177 93 171 239 123 224 245 180 234 166 132 201 244 54 69 22 18 16 115 223 70 193

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

Вам следует выяснить, что вы клиентский код фактически делает это и исправляет его или передает ему необходимые данные.

Например, если ваш код на стороне клиента не может помешать повторному хешированию данных, передайте ему байты атрибута без хэшированной аутентификации.

...