Подписать PDF с помощью хэша подписи, предоставленного api

Я пытаюсь подписать PDF-файл с помощью PDFBox и стороннего поставщика подписи.

Моя процедура:

  1. Получение PDF-файла из пользовательского ввода
  2. Создание PDDocument с содержанием данного pdf
  3. создание PDSignature со всеми заданными свойствами, такими как организация или местоположение, но без самого хэша подписи.
  4. добавление этого объекта подписи в PDDocument и создание дайджеста из PDDocument для отправки в API.
  5. Ожидание успешного ответа от API (содержащего хэш подписи) и вставка этого хеша в объект PDSignature PDDocuments без изменения всего документа.

Моя проблема:

Я могу создать PDDocument, PDSignature и его дайджест и отправить в API. Затем я получаю правильный и действительный хэш подписи обратно из API, и я могу добавить его в PDDocument, но всякий раз, когда это срабатывает, полученный PDF-файл имеет недопустимую подпись из-за изменения или манипуляции (подписывающий, временная метка и сертификат действительны). Я также пробовал использовать ExternalSigningSupport из PDFBox, но я не могу его использовать, так как всегда сталкиваюсь с ошибкой:

"java.lang.IllegalStateException: signature reserve byte range has been changed after addSignature(), please set the byte range that existed after addSignature()".

Мой код, который работает, но аннулирует подпись:

//Pending Signature is just a DTO
public static PendingSignature createPendingSignatureFromPdf(InputStream pdf2sign, OutputStream fos, String sigName, String sigLocation, String sigReason, String contactInfo, Date forcedDate, Long revisionId) throws IOException {
    
        File pdfFileToSigned = createTempFile("chars", "" + Calendar.getInstance().getTimeInMillis());
        File pdfPreparedToBeSigned = createTempFile("chars", "" + Calendar.getInstance().getTimeInMillis());
        File pdfHashPreparedToBeSigned = createTempFile("chars", "" + Calendar.getInstance().getTimeInMillis());
    
        try {
            if (fos == null) {
                fos = new FileOutputStream(pdfPreparedToBeSigned);
            }
    
            FileUtils.copyInputStreamToFile(pdf2sign, pdfFileToSigned);
    
            PDDocument doc = PDDocument.load(pdfFileToSigned);
            PDSignature signature = new PDSignature();
    
            signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
    
            if (contactInfo != null && !contactInfo.isEmpty()) {
                signature.setContactInfo(contactInfo);
            }
            if (sigLocation != null && !sigLocation.isEmpty()) {
                signature.setLocation(sigLocation);
            }
            if (sigReason != null && !sigReason.isEmpty()) {
                signature.setReason(sigReason);
            }
            if (sigName != null && !sigName.isEmpty()) {
                signature.setName(sigName);
            }
            if (forcedDate != null) {
                SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
                Calendar cal = Calendar.getInstance();
                cal.setTime(sdf.parse(sdf.format(forcedDate)));
                signature.setSignDate(cal);
            }
    
            final OutputStream outputStream = new FileOutputStream(pdfHashPreparedToBeSigned);
            SignatureInterface signatureInterface = new SignatureInterface() {
                @Override
                public byte[] sign(InputStream content) throws IOException {
                    try {
                        MessageDigest digest = MessageDigest.getInstance("SHA-256");
                        byte[] imp = digest.digest(content.toString().getBytes(StandardCharsets.UTF_16));
                        IOUtils.copy(new ByteArrayInputStream(imp), outputStream);
                        return new byte[0];
                    } catch (NoSuchAlgorithmException e) {
                        e.printStackTrace();
                        return new byte[0];
                    }
                }
            };
    
            SignatureOptions signatureOptions = new SignatureOptions();
            signatureOptions.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 8);
    
            doc.addSignature(signature, signatureInterface, signatureOptions);
    
            if (revisionId != null) {
                doc.setDocumentId(revisionId);
            } else {
                doc.setDocumentId(0L);
            }
            doc.saveIncremental(fos);
    
            PendingSignature pendingSignature = new PendingSignature(doc, signature, IOUtils.toByteArray(new FileInputStream(pdfHashPreparedToBeSigned)));
            return pendingSignature;
        } catch (IOException | ParseException e) {
            throw new IOException(e);
        } finally {
            fos.close();
            pdf2sign.close();
            FileUtils.deleteQuietly(pdfFileToSigned);
            FileUtils.deleteQuietly(pdfPreparedToBeSigned);
            FileUtils.deleteQuietly(pdfHashPreparedToBeSigned);
        }
     }
    
    //Gets called when the api returns sucess with the signedbytes
    public static File insertHashToPdf(PendingSignature pendingSignature, final byte[] signedBytes) throws IOException {
    
        File outputDocument = createTempFile("chars", "" + Calendar.getInstance().getTimeInMillis());
    
        FileOutputStream fos = new FileOutputStream(outputDocument);
    
        PDDocument doc = pendingSignature.getPdfDocument();
        PDSignature pdfSignature = pendingSignature.getPdfSignature();
    
        //Produces signature that it invalid because it was altered or manipulated
        pdfSignature.setContents(signedBytes);
    
        doc.saveIncremental(fos);
    
        if (fos != null) {
            fos.close();
        }
    
        return outputDocument;
    }

Подпись выглядит так:

InvalidSignature

Кто-нибудь знает, как вставить этот хэш подписи во весь PDDocument, не делая его недействительным?


person POP3    schedule 29.04.2020    source источник
comment
Поскольку подписание с помощью PDFBox действительно работает (с использованием примеров PDFBox, с использованием его интеграции в eSig DSS и т. Д.), Вероятно, в вашем коде есть проблема. Таким образом, вам, вероятно, следует поделиться своим основным кодом и примером подписанного им PDF-файла.   -  person mkl    schedule 29.04.2020
comment
Привет, POP3 - ›вам нужно добавить свой исходный код в вопрос, иначе никто не догадывается   -  person stwissel    schedule 29.04.2020
comment
Я попытался включить весь код, который сейчас может быть полезен.   -  person POP3    schedule 30.04.2020
comment
Ваш sign() метод создает только дайджест, но не подпись. Кажется, вы путаете подписание с перевариванием. Я не знаю, что именно вы подразумеваете под «хешем подписи», но что бы вы ни подписали, вы не можете впоследствии изменить содержимое.   -  person user207421    schedule 30.04.2020
comment
Ошибка не так проста, как предполагает @ user207421, POP3 в insertHashToPdf пытается внедрить реальный контейнер подписи; если бы это был простой хэш, Adobe Reader не смог бы определить личность подписавшего, не говоря уже о том, кому он доверяет. Я думаю, проблема в том, что базовые классы подписи здесь не предназначены для вызова saveIncremental дважды, по крайней мере, не с обратным вызовом PDSignature и изменением значения его содержимого. POP3, нельзя ли просто получить и вернуть подпись внутри вашей SignatureInterface.sign реализации?   -  person mkl    schedule 30.04.2020
comment
@ user207421 мой метод sign () (я предполагаю, вы имеете в виду createPendingSignatureFromPdf ()) создает только дайджест из PDDocument, который является правильным, но это все, что мне от него нужно. Я получаю свою подпись из api, которому нужен этот дайджест для создания хэша подписи, который я затем вставляю в PDDocument.   -  person POP3    schedule 01.05.2020
comment
@mkl спасибо за ваш вопрос. Проблема в том, что signature-api ожидает двухфакторной аутентификации, и мне нужно сделать дополнительные вызовы, чтобы получить подпись. Также мне нужно создать подпись и добавить ее в PDDocument перед отправкой в ​​api, поскольку api ожидает, что подпись будет присутствовать в дайджесте документа. Это имеет смысл, поскольку, когда я добавляю подпись после создания подписанного хэша, я изменяю документ.   -  person POP3    schedule 01.05.2020


Ответы (1)


Итак, я наконец нашел рабочее решение.

Ссылка на репозиторий дневного сохранения: https://github.com/crs2195/signature/tree/master/RemoteSignature-%20PDFBox/CSC-PDFBox

person POP3    schedule 26.05.2020
comment
Вам нужно было все репо или только реализация sign ()? Если позже, то будет более полезно, если вы скопируете эту реализацию здесь. - person Tilman Hausherr; 26.05.2020
comment
Я использовал весь класс CSCSignature.java, но с некоторыми изменениями, так как я не мог использовать файловую систему, но должен был сделать это в памяти. - person POP3; 27.05.2020