Вставьте SignedHash в PDF для внешнего процесса подписания - WorkingSample - PullRequest
2 голосов
/ 29 апреля 2019

После раздела электронной книги 4.3.3 " Цифровая подпись для документа PDF " Я пытаюсь создать рабочий пример, где:

  • у клиента есть PDF для подписии только общедоступный сертификат
  • Внешнее HW (с частным сертификатом) получает хэш и возвращает подписанный хэш

Я пытался сделать это, но подпись внутри PDF показывает мнечто файл был изменен после процесса подписания.

Следующий код берет исходный PDF-файл и общедоступный сертификат, создает временный PDF-файл с пустым знаком и возвращает HASH

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

Обновлен полный рабочий код:

package com.Marloo;


import org.apache.commons.codec.Charsets;
import org.bouncycastle.util.encoders.Base64;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;

import java.io.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;

public class Test {

    public static final String CERT = "src/main/resources/certificate.pem";
    public static final String SRC = "src/main/resources/tmp.pdf";
    public static final String DEST = "src/main/resources/signed.pdf";

    public static void main(String args[]) throws IOException {
        getHash(SRC, CERT);
    }



    public static void getHash(String doc, String cert) throws IOException {

        try {

            File initialFile = new File(cert);
            InputStream is = new FileInputStream(initialFile);

            // We get the self-signed certificate from the client
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            Certificate[] chain = new Certificate[1];
            chain[0] = factory.generateCertificate(is);

            // we create a reader and a stamper
            PdfReader reader = new PdfReader(doc);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            PdfStamper stamper = PdfStamper.createSignature(reader, baos, '\0');

            // we create the signature appearance
            PdfSignatureAppearance sap = stamper.getSignatureAppearance();
            sap.setReason("TEST REASON");
            sap.setLocation("TEST LOCATION");
            //sap.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig"); //visible
            sap.setVisibleSignature(new Rectangle(36, 748, 36, 748), 1, "sig"); //invisible
            sap.setCertificate(chain[0]);

            // we create the signature infrastructure
            PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
            dic.setReason(sap.getReason());
            dic.setLocation(sap.getLocation());
            dic.setContact(sap.getContact());
            dic.setDate(new PdfDate(sap.getSignDate()));
            sap.setCryptoDictionary(dic);
            HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
            exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
            sap.preClose(exc);
            ExternalDigest externalDigest = new ExternalDigest() {
                public MessageDigest getMessageDigest(String hashAlgorithm)
                        throws GeneralSecurityException {
                    return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
                }
            };
            PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
            InputStream data = sap.getRangeStream();
            byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));


            // we get OCSP and CRL for the cert
            OCSPVerifier ocspVerifier = new OCSPVerifier(null, null);
            OcspClient ocspClient = new OcspClientBouncyCastle(ocspVerifier);
            byte[] ocsp = null;
            if (chain.length >= 2 && ocspClient != null) {
                ocsp = ocspClient.getEncoded((X509Certificate) chain[0], (X509Certificate) chain[1], null);
            }

        byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, MakeSignature.CryptoStandard.CMS);
        InputStream sh_is = new ByteArrayInputStream(sh);
        byte[] signedAttributesHash = DigestAlgorithms.digest(sh_is, externalDigest.getMessageDigest("SHA256"));


        System.out.println("----------------------------------------------");
        System.out.println("Hash to be sign:");
        System.out.println( new String(Base64.encode(signedAttributesHash), Charsets.UTF_8));
            System.out.println("----------------------------------------------");
            System.out.println("Insert b64 signed hash [ENTER]");
            System.out.println("----------------------------------------------");

            Scanner in = new Scanner(System.in);
            String signedHashB64 = in.nextLine();
            System.out.println( signedHashB64);

            ByteArrayOutputStream os = baos;

            byte[] signedHash = org.apache.commons.codec.binary.Base64.decodeBase64(signedHashB64.getBytes());

            // we complete the PDF signing process
            sgn.setExternalDigest(signedHash, null, "RSA");
            Collection<byte[]> crlBytes = null;
            TSAClientBouncyCastle tsaClient = new TSAClientBouncyCastle("http://timestamp.gdca.com.cn/tsa", null, null);

            byte[] encodedSig = sgn.getEncodedPKCS7(hash, tsaClient, ocsp, crlBytes, MakeSignature.CryptoStandard.CMS);
            byte[] paddedSig = new byte[8192];
            System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
            PdfDictionary dic2 = new PdfDictionary();
            dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));

            try {
                sap.close(dic2);
            } catch (DocumentException e) {
                throw new IOException(e);
            }

            FileOutputStream fos = new FileOutputStream(new File(DEST));
            os.writeTo(fos);

            System.out.println("pdfsig " + System.getProperty("user.dir") + "/" + DEST);
            System.out.println("------------------End Of Life --------------------------");

            System.exit(0);


        } catch (GeneralSecurityException e) {
            throw new IOException(e);
        } catch (DocumentException e) {
            throw new IOException(e);
        }

    }


}

Вот некоторые screenshot

Несколько подсказок: в этом неполном посте автор говорит:

"После долгих отладок мы наконец нашли проблему.

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

После рефакторинга кода,оригинальный код работал правильно.

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

, но никакой дополнительной информации предоставлено не было, также пишется Time on Штамп и время от TSA различаются по назначению.Я думаю, что это не будет проблемой.

Некоторые намеки?

Спасибо

Обновление 1

(предыдущий код был обновлен)

внешние службы не принимают на входе всю структуру Sign, а только32-байтовый хэш

screenshot2

теперь sh var никогда не используется!

Я беру хеш-байт [], отправляю на него, но снова Adobe Reader сообщает, что файл был изменен.

Может быть, я могу попробовать метод "невидимая подпись".Или «видимый штамп» не имел значения в процессе проверки подписи?

Или, может быть, мне нужно как-то воссоздать структуру ANS.1 с подписанным байтом, а затем подписать документ?

Может быть, время между ца и знаком должно быть одинаковым?

Screenshot3

Буду признателен за любую помощь.

Спасибо

Обновление 2 - РАБОЧЕЕ РЕШЕНИЕ !!!

Действительно, действительно, спасибо MKL за ответ!

рабочее исправление состояло в том, что нам нужно сгенерировать хэш подписанных / аутентифицированных атрибутов внутри пакета PKCS # 7 !!!См. Оригинальный код в переменной signedAttributesHash signed pdf image

1 Ответ

1 голос
/ 07 мая 2019

Ваш текущий код

Ваш текущий код подписывает совершенно неправильный хеш.

Вы подписываете hash, который рассчитывается как

InputStream data = sap.getRangeStream();
byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
* 1010Т.е. вы напрямую подписываете хэш подписанных диапазонов документа.Это неправильно, потому что вы создаете контейнер подписи PKCS # 7 с подписанными атрибутами, то есть хэш подписанных диапазонов документа должен быть значением одного из этих подписанных атрибутов, и вы должны подписать хэш подписанных атрибутов!

Ваш прежний код

Ваш прежний код тоже подписал неправильные байты, но был ближе к правильным байтам.

Вы использовали для подписи last32 который был вычислен как

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocsp, null, MakeSignature.CryptoStandard.CMS);
byte[] last32 = Arrays.copyOfRange(sh, sh.length - 32, sh.length);

Т.е. вы правильно сгенерировали подписанные байты атрибута (иначе говоря, байты атрибута) с hash, включенным в качестве значения атрибута, но затем вы просто взяли последние 32 байта.Это неправильно, вы должны подписать хэш подписанных байтов атрибута, а не их последние 32 байта.

Что должно работать

Вы должны подписать signedAttributesHash, хэш байтов подписанного атрибутат.е.

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocsp, null, MakeSignature.CryptoStandard.CMS);
byte[] signedAttributesHash = DigestAlgorithms.digest(new ByteArrayInputStream(sh), externalDigest.getMessageDigest("SHA256"));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...