0
votes

I am trying to sign a pdf document from a 3rd party signature provider. I send them the document hash, after creating the empty signature and they send a timestamp token (we agreed it would be a timestamp signature) and I add the signature back into the pdf. the api call to get the timestmp and crl and ocsp goes well, but once I generate the pdf with the signature, adobe says the signature is not valid and the error is:

Error during signature verification.

Signature contains incorrect, unrecognized, corrupted or suspicious data. Support Information: SigDict /Contents illegal data

this is the current code:

signatureprovider.java

    public ByteArrayOutputStream sign(byte[] src, Rectangle rect, int signPage) throws Exception {
    log.info("I am in the signing method");
    ByteArrayOutputStream dest = new ByteArrayOutputStream();
    PdfDocumentHandler handler = new PdfDocumentHandler(new ByteArrayInputStream(src), dest);
    handler.prepareForSigning(rect, signPage);
    TrustedTimestampService service = new TrustedTimestampService();
    SignResponseWrapper response = new SignResponseWrapper();
    response = service.signHash(handler.getEncodedDocumentHash());
    String timestampToken = response.getSignResponse().getSignatureObject().getOther().getScSignatureObjects().getScExtendedSignatureObject().getTimestamp().getRFC3161TimeStampToken();
    List<String> encodedCrlEntries = new ArrayList<String>();
    encodedCrlEntries.add(response.getSignResponse().getOptionalOutputs().getScRevocationInformation().getScCRLs().getScCRL());
    List<String> encodedOcspEntries = new ArrayList<String>();
    encodedOcspEntries.add(response.getSignResponse().getOptionalOutputs().getScRevocationInformation().getScOCSPs().getScOCSP());
    byte[] signed = handler.createSignedPdf(Base64.getDecoder().decode(timestampToken), 15000, encodedCrlEntries, encodedOcspEntries);
    dest.flush();
    dest.write(signed);
    handler.close();
    return dest;
}

I will avoid adding the service, it simply sends the hash and gets the tokens pdfdocumenthandler.java

    public void prepareForSigning(Rectangle rect, int signPage)
    throws IOException, GeneralSecurityException {

   
    inMemoryStream = new ByteArrayOutputStream();
    inMemoryStream.write(StreamUtil.inputStreamToArray(inputStream));
    
    pdfReader = new PdfReader(new ByteArrayInputStream(inMemoryStream.toByteArray()), new ReaderProperties());
    inMemoryStream.reset();
    pdfWriter = new PdfWriter(inMemoryStream, new WriterProperties().addXmpMetadata().setPdfVersion(PdfVersion.PDF_1_0));
    pdfSigner = new PdfDocumentSigner(pdfReader, pdfWriter, false);
    //pdfSigner = new PdfDocumentSigner(pdfReader, pdfWriter, true);

    pdfSigner.getSignatureAppearance()
        .setReason("myreason")
        .setLocation("mylocation")
        .setPageRect(rect)
        .setPageNumber(signPage);
       try {
            ImageData imgData = ImageDataFactory.create(Base64.getDecoder().decode(imgStorage.getimgData().getBytes()));
            pdfSigner.getSignatureAppearance().setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
            pdfSigner.getSignatureAppearance().setSignatureGraphic(imgData);
        } catch(Exception e){
            pdfSigner.getSignatureAppearance().setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
        }

    Map<PdfName, PdfObject> signatureDictionary = new HashMap<>();
    signatureDictionary.put(PdfName.Filter, PdfName.Adobe_PPKLite);
    signatureDictionary.put(PdfName.SubFilter, PdfName.ETSI_RFC3161);

    Calendar signDate = Calendar.getInstance();
    pdfSigner.setSignDate(signDate);

    PdfHashSignatureContainer hashSignatureContainer = new PdfHashSignatureContainer("SHA-512", new PdfDictionary(signatureDictionary));
    documentHash = pdfSigner.computeHash(hashSignatureContainer, 15000);
}

public byte[] createSignedPdf(@NonNull byte[] externalSignature, int estimatedSize, List<String> encodedCrlEntries,
                            List<String> encodedOcspEntries) {
    //log.info("signature length: " + externalSignature.length + " bytes");
    if (pdfSigner.getCertificationLevel() == PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED) {
        throw new RuntimeException(String.format("Could not apply signature because source file contains a certification that does not allow "
                                                   + "any changes to the document"));
    }

    if (estimatedSize < externalSignature.length) {
        throw new RuntimeException(String.format("Not enough space for signature in the document. The estimated size needs to be " +
                                                   " increased with %d bytes.", externalSignature.length - estimatedSize));
    }

    try {
        pdfSigner.signWithAuthorizedSignature(new PdfSignatureContainer(externalSignature), estimatedSize);

        if (null!=encodedCrlEntries && null!=encodedOcspEntries) {
            extendDocumentWithCrlOcspMetadata(encodedCrlEntries, encodedOcspEntries);
        } else {
            outputStream.write(inMemoryStream.toByteArray());
        }
        outputStream.write(inMemoryStream.toByteArray());

        closeResource(inMemoryStream);
        closeResource(outputStream);
    } catch (IOException | GeneralSecurityException e) {
        e.printStackTrace();
    }


    return outputStream.toByteArray();
}

private void extendDocumentWithCrlOcspMetadata(List<String> encodedCrlEntries, List<String> encodedOcspEntries) {
    if (pdfSigner.getCertificationLevel() == PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED) {
        throw new RuntimeException(String.format("Could not apply revocation information (LTV) to the DSS Dictionary. Document contains a certification that does not allow any changes"));
    }

    List<byte[]> crl = mapEncodedEntries(encodedCrlEntries, this::mapEncodedCrl);
    List<byte[]> ocsp = mapEncodedEntries(encodedOcspEntries, this::mapEncodedOcsp);

    try (InputStream documentStream = new ByteArrayInputStream(inMemoryStream.toByteArray());
         PdfReader reader = new PdfReader(documentStream);
         PdfWriter writer = new PdfWriter(outputStream);
         PdfDocument pdfDocument = new PdfDocument(reader, writer, new StampingProperties().preserveEncryption().useAppendMode())) {
            LtvVerification validation = new LtvVerification(pdfDocument);
            List<String> signatureNames = new SignatureUtil(pdfDocument).getSignatureNames();
            String signatureName = signatureNames.get(signatureNames.size() - 1);
            boolean isSignatureVerificationAdded = validation.addVerification(signatureName, ocsp, crl, null);
            validation.merge(); 
            
            SignatureUtil signUtil = new SignatureUtil(pdfDocument);
            PdfPKCS7 pkcs7 = signUtil.verifySignature(signatureName);
    
            log.info("Signature covers whole document: " + signUtil.signatureCoversWholeDocument(signatureName));
            log.info("Document revision: " + signUtil.getRevision(signatureName) + " of " + signUtil.getTotalRevisions());
            log.info("Integrity check OK? " + pkcs7.verify());
            if (isSignatureVerificationAdded) {
                log.info("Merged LTV validation information to the output stream");
            } else {
                log.warn("Failed to merge LTV validation information to the output stream");
            }

    } catch (Exception e) {
        e.printStackTrace();
    }
}

private List<byte[]> mapEncodedEntries(List<String> encodedEntries, Function<String, byte[]> mapperFunction) {
    return Objects.nonNull(encodedEntries)
           ? encodedEntries.stream().map(mapperFunction).collect(Collectors.toList())
           : Collections.emptyList();
}

private byte[] mapEncodedCrl(String encodedCrl) {
    try (InputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(encodedCrl))) {
        X509CRL x509crl = (X509CRL) CertificateFactory.getInstance("X.509").generateCRL(inputStream);
        return x509crl.getEncoded();
    } catch (IOException | CertificateException | CRLException e) {
        throw new RuntimeException(String.format("Failed to map the received encoded CRL entry"), e);
    }
}

private byte[] mapEncodedOcsp(String encodedOcsp) {
    try (InputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(encodedOcsp))) {
        OCSPResp ocspResp = new OCSPResp(inputStream);
        BasicOCSPResp basicResp = (BasicOCSPResp) ocspResp.getResponseObject();
        return basicResp.getEncoded();
    } catch (IOException | OCSPException e) {
        throw new RuntimeException(String.format("Failed to map the received encoded OCSP entry"), e);
    }
}

public String getEncodedDocumentHash() {
    return Base64.getEncoder().encodeToString(documentHash);
}

pdfdocumentsigner.java

public class PdfDocumentSigner extends PdfSigner {

public PdfDocumentSigner(PdfReader reader, OutputStream outputStream, boolean properties) throws IOException {
    super(reader, outputStream, properties);
}

public byte[] computeHash(IExternalSignatureContainer externalHashContainer, int estimatedSize) throws GeneralSecurityException, IOException {
    if (closed) {
        throw new PdfException(PdfException.ThisInstanceOfPdfSignerAlreadyClosed);
    }

    PdfSignature signatureDictionary = new PdfSignature();
    PdfSignatureAppearance appearance = getSignatureAppearance();
    signatureDictionary.setReason(appearance.getReason());
    signatureDictionary.setLocation(appearance.getLocation());
    signatureDictionary.setSignatureCreator(appearance.getSignatureCreator());
    signatureDictionary.setContact(appearance.getContact());
    signatureDictionary.setDate(new PdfDate(getSignDate()));
    externalHashContainer.modifySigningDictionary(signatureDictionary.getPdfObject());
    cryptoDictionary = signatureDictionary;

    Map<PdfName, Integer> exc = new HashMap<>();
    exc.put(PdfName.Contents, estimatedSize * 2 + 2);
    preClose(exc);

    InputStream dataRangeStream = getRangeStream();
    return externalHashContainer.sign(dataRangeStream);
}

public void signWithAuthorizedSignature(IExternalSignatureContainer externalSignatureContainer, int estimatedSize)
    throws GeneralSecurityException, IOException {
    InputStream dataRangeStream = getRangeStream();
    byte[] authorizedSignature = externalSignatureContainer.sign(dataRangeStream);

    if (estimatedSize < authorizedSignature.length) {
        throw new IOException(String.format("Not enough space. The estimated signature size [%d bytes] is less than the received authorized "
                                            + "signature [%d bytes] which needs to be embedded into the document.", estimatedSize,
                                            authorizedSignature.length));
    }

    byte[] paddedSignature = new byte[estimatedSize];
    System.arraycopy(authorizedSignature, 0, paddedSignature, 0, authorizedSignature.length);

    PdfDictionary dic2 = new PdfDictionary();
    dic2.put(PdfName.Contents, new PdfString(paddedSignature).setHexWriting(true));
    close(dic2);

    closed = true;
}

I have been looking and it does seem like I am not missing anything, can anyone please help me figure this out? here vis the final pdf sample

sample pdf

Please share an example pdf signed by your code for analysis. - mkl
thank you, I just added the document - Antonio De Michele