javapdfboxdigital-signaturepades

PAdES Signature Level - Adobe Acrobat


I am creating a PADES signature using pdfbox 3.0.0 RC, my code works using the example to create the digital signature. However, I am unable to see the signature level in Adobe Acrobat when I open the document with this tool although it is able to validate my signature.

I am not creating the VRI so I am guessing that this might be an issue but then if this is necessary to validate my signature I don't understand why the signature is displayed as valid?

Adobe Acrobat Signature:

Adobe Acrobat Signature

/**
 * Service for automatically signing a document as part of a workflow. In this instance no user information is 
 * gathered
 *
 * @param taskID
 * @param processName which will be added to the document 
 * @param keyID the ID for the key used to sign the PDF document
 * @return the signed PDF document as a base 64 Encoded String 
 */
@Transactional
public String signPDFService(String processID, 
                             String processName,
                             String keyID,
                             ObjectData signatureImage,
                             String creator)
{
    try {

        
        
        ByteArrayOutputStream ostream = new ByteArrayOutputStream();
        //Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(documents.get(0).getProcessID());
         
         /*List<Task> tasks = taskService.createTaskQuery()
              .taskId(taskID)
              .list();
    // Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(documents.get(0).getProcessID());
     
    List<PDFDocument> documents = tasks.stream()
              .map(task -> {
                  String processID =  task.getProcessInstanceId();
                  Map<String, Object> variables = taskService.getVariables(task.getId());
                  PDFDocument document;
                  try {
                      document =  new PDFDocument(
                              (ArrayList) variables.get("assigneeList"),
                              (String) variables.get("unsignedPDFDocument"),
                              task.getProcessInstanceId(),
                              task.getId(),
                              (String) variables.get("name"),
                              (String) variables.get("description")
                        );
                      document.setHistory((ArrayList) variables.get("history"));
                      return document;
                    
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    return null;
                } catch (CMSException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    return null;
                }
              })
              .collect(Collectors.toList());*/


     //Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(documents.get(0).getProcessID());
        Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(processID);
     if(pdfDocumentProcessInstance.isEmpty())
         throw new IOException("No process found");
     
        String pdfDocumentBase64String = pdfDocumentProcessInstance.get().getAttributes().get("PDFDocument"); 
        String extractedPDFString = pdfDocumentBase64String.replaceAll("data:application/pdf;base64,", "").replaceAll("data:;base64,", "").replaceAll("data:application/octet-stream", "");
        //String extractedPDFString = base64PDF.replaceAll("data:application/pdf;base64,", "").replaceAll("data:;base64,", "");
        InputStream stream = new ByteArrayInputStream(Base64.getDecoder().decode(extractedPDFString.getBytes()));

        
        //Create the date object to sign the document
        Date date = new Date();
        Calendar calendar = Calendar.getInstance();

        calendar.setTime(date);

        //Retrieve certificate chain for the PDF Signer
        String certChainPEM = kmsService.getCertChainPEM(keyID);
        X509Certificate pdfSignerCertificate = X509Utils.readCertificateChain(certChainPEM).get(0).getCertificate();
 
        //Create the CMS Signing Object
        ExternalSignatureCMSSignedDataGenerator cmsGenerator = new ExternalSignatureCMSSignedDataGenerator();
        ExternalSignatureSignerInfoGenerator signerGenerator = new ExternalSignatureSignerInfoGenerator(CMSSignedDataGenerator.DIGEST_SHA256, "1.2.840.10045.4.3.2");
        signerGenerator.setCertificate(pdfSignerCertificate);
        ExternalSigningSupport externalSigningSupport;
        PDDocument pdDocument = Loader.loadPDF(stream);
        
   
        //Create the PDFBox Signature Object
        PDSignature pdSignature = new PDSignature();
        pdSignature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
        pdSignature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
        pdSignature.setLocation("Remote IS Blocks Signer");
        pdSignature.setName("IS Blocks Signer");
        pdSignature.setReason(processName);
        pdDocument.setDocumentId(calendar.getTimeInMillis());
        pdSignature.setSignDate(calendar);
        
        // Optional: Certify the first time signature 
        // can be done only if version is at least 1.5 and if not already set
        // doing this on a PDF/A-1b file fails validation by Adobe preflight (PDFBOX-3821)
        // PDF/A-1b requires PDF version 1.4 max, so don't increase the version on such files.
        
        int accessPermissions = SigUtils.getMDPPermission(pdDocument);

        
  
        if (pdDocument.getVersion() >= 1.5f && accessPermissions == 0 && processName.contains("Document Certifying Key"))
        {
            logger.debug("Certifying Document");
            SigUtils.setMDPPermission(pdDocument, pdSignature, 3);
        }
        
        if(signatureImage != null) {
            String data = signatureImage.getAttributes().get("data").replaceAll("data:application/pdf;base64,", "").replaceAll("data:;base64,", "").replaceAll("data:image/png;base64,", "");
            int pageNumber = Integer.parseInt(signatureImage.getAttributes().get("page"));
            float x = Float.parseFloat(signatureImage.getAttributes().get("x"));
            float y = Float.parseFloat(signatureImage.getAttributes().get("y"));
            float width = Float.parseFloat(signatureImage.getAttributes().get("width"));
            float height = Float.parseFloat(signatureImage.getAttributes().get("height"));
            SignatureOptions signatureOptions;
            // register signature dictionary and sign interface
            signatureOptions = new SignatureOptions();
            PDFVisibleSignature pdfVisibleSignature = new PDFVisibleSignature();
            signatureOptions.setVisualSignature(pdfVisibleSignature.createVisualSignatureTemplate(  
                    x, 
                    y, 
                    width, 
                    height, 
                    pdDocument, 
                    pageNumber, 
                    pdSignature, 
                    Base64.getDecoder().decode(data.getBytes("UTF-8"))));
            signatureOptions.setPage(pageNumber);
            pdDocument.addSignature(pdSignature, null, signatureOptions);
        } else {
            pdDocument.addSignature(pdSignature);
        }

            
        
     

        
        
        externalSigningSupport = pdDocument.saveIncrementalForExternalSigning(ostream);

        //Create the message digest of the pre-signed PDF
        MessageDigest digest = MessageDigest.getInstance("SHA-256");     
        byte[] bytes = org.apache.commons.io.IOUtils.toByteArray(externalSigningSupport.getContent());
        byte[] hashBytes = digest.digest(bytes);
        
        //CMS Signature 
        InputStream isBytes = new ByteArrayInputStream(bytes);
        CMSProcessable input = new CMSProcessableInputStream(isBytes);
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());


        MessageDigest messageDigest1 = MessageDigest.getInstance("SHA-256");
        byte[] hash = messageDigest1.digest(bytes);
        byte[] bytesToSign = signerGenerator.getBytesToSign(PKCSObjectIdentifiers.data,  hash, new Date(),
                "BC");
        String encodedData = Base64.getEncoder().encodeToString(bytesToSign);

        logger.debug("Bytes to Sign:" + (Base64.getEncoder().encodeToString(bytesToSign)));
        logger.debug("Hash:" + Base64.getEncoder().encodeToString(hash));
 
        //Create the signature using the keyID
        //At this time only ECDSAWithSHA256 is supported
        Map<String, String> signature = kmsService.sign(keyID, encodedData);

        byte[] signedBytes = Base64.getDecoder().decode(signature.get("signature"));

        X509Certificate[] chain;

        signerGenerator.setCertificate(pdfSignerCertificate);
        signerGenerator.setSignedBytes(signedBytes);

        cmsGenerator.addSignerInf(signerGenerator);
        cmsGenerator.addCertificatesAndCRLs(X509Utils.getCertStore(signature.get("certificateChain")));
        CMSSignedData signedData = cmsGenerator.generate(new CMSProcessableByteArray(hash), false);

        //Add a RFC3161 Time Stamp
        ValidationTimeStamp validation = new ValidationTimeStamp("https://freetsa.org/tsr");
        signedData = validation.addSignedTimeStamp(signedData);

        ContentSigner nonSigner = new ContentSigner() {

        @Override
        public byte[] getSignature() {
            return signedBytes;
        }

        @Override
        public OutputStream getOutputStream() {
            return new ByteArrayOutputStream();
        }

        @Override
        public AlgorithmIdentifier getAlgorithmIdentifier() {
            return new DefaultSignatureAlgorithmIdentifierFinder().find( "SHA256WithECDSA" );
        }
        };
            
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());      
        gen.addCertificate(new X509CertificateHolder(pdfSignerCertificate.getEncoded()));
        sigb.setDirectSignature( true );

        gen.addSignerInfoGenerator(sigb.build(nonSigner, new X509CertificateHolder(pdfSignerCertificate.getEncoded())));
        CMSTypedData msg = new CMSProcessableInputStream( new ByteArrayInputStream( "not used".getBytes() ) );
       
        CMSSignedData signedData1 = gen.generate((CMSTypedData)msg, false);
        signedData1.getEncoded();

        externalSigningSupport.setSignature(signedData.getEncoded());
        
        //documents.get(0).addHistoricEvent("Signed " + processName);
        //ArrayList<String> history = documents.get(0).getHistory();

        //Post Signature

        String signedPDFDocument = Base64.getEncoder().encodeToString(ostream.toByteArray());
        PDDocument newPdf1;     
        newPdf1 = Loader.loadPDF(ostream.toByteArray());
        byte[] fileContent = ostream.toByteArray();
  
        List<PDSignature> pdfSignatures;

            
        pdfSignatures = newPdf1.getSignatureDictionaries();
        
  
        byte[] signatureAsBytes;

            signatureAsBytes = newPdf1.getLastSignatureDictionary().getContents( fileContent );
    
        byte[] signedContentAsBytes;
    
            signedContentAsBytes = newPdf1.getLastSignatureDictionary().getSignedContent( fileContent );

            // Now we construct a PKCS #7 or CMS.
            CMSProcessable cmsProcessableInputStream = new CMSProcessableByteArray(signedContentAsBytes);
            CMSSignedData cmsSignedData;

            cmsSignedData = new CMSSignedData(cmsProcessableInputStream, signatureAsBytes);
     
            Store certificatesStore = cmsSignedData.getCertificates();
            Collection<SignerInformation> signers = cmsSignedData.getSignerInfos().getSigners();
            SignerInformation signerInformation = signers.iterator().next();
            Collection matches = certificatesStore.getMatches(signerInformation.getSID());
            X509CertificateHolder certificateHolder = (X509CertificateHolder) matches.iterator().next();
            
            
            ObjectMapper mapper = new ObjectMapper();
            ArrayList<String> signatures = null;
            //signatures =  documents.get(0).getSignatures();
            
            
            for(int iCount = 0; iCount < pdfSignatures.size(); iCount++) {
            PDFSignature pdfSignature = new PDFSignature(
                    pdfSignatures.get(iCount).getName(), 
                    pdfSignatures.get(iCount).getLocation(), 
                    pdfSignatures.get(iCount).getSignDate().getDisplayName(Calendar.LONG_FORMAT,  java.util.Calendar.LONG, Locale.UK), 
                    pdfSignatures.get(iCount).getReason(),
                    certificateHolder.getSubject().toString(), 
                    certificateHolder.getIssuer().toString(), 
                    Base64.getEncoder().encodeToString(certificateHolder.getEncoded()));
                    //signatures.add(mapper.writeValueAsString(pdfSignature));
                    logger.info("Signature" + mapper.writeValueAsString(pdfSignature));
            
            }
            
            Map<String, Object> variables = new HashMap<String, Object>();
           // variables.put("history", history);
            //variables.put("unsignedPDFDocument", signedPDFDocument);
            //variables.put("signatures", signatures);
            //variables.put("status", value)

      
            Map<String, String> pdfDocumentProcessInstanceAttributes = pdfDocumentProcessInstance.get().getAttributes();
            pdfDocumentProcessInstanceAttributes.put("PDFDocument", signedPDFDocument);
            ObjectData newpdfProcessInstance = pdfDocumentProcessInstance.get();
            newpdfProcessInstance.setAttributes(pdfDocumentProcessInstanceAttributes);
            userDataRepository.save(newpdfProcessInstance);
            newpdfProcessInstance.getHistory().add(new Date() + "Signed by:" + creator);
            System.out.println(newpdfProcessInstance.getId() + " " + newpdfProcessInstance.toString());

            newPdf1.close();
            ostream.close();


        } catch (Exception e){
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
    
    return null;
}

Solution

  • While analyzing the file document-with signingTime.pdf you provided in a comment, I recognized an issue in it. Being aware of that issue I re-checked your original document-17 21.08.14.pdf and also recognized that issue therein, so maybe this issue causes the validation problem you're here to solve. Thus, ...

    Both your example files (document-17 21.08.14.pdf and document-with signingTime.pdf) contain each actually two concatenated copies of the same, multi-revision PDF with a single signature Signature1, merely the second copy has a changed ID entry. Added to them are incremental updates with a signature Signature2.

    %PDF-1.4
    ...
    15 0 obj
    <<
    /FT /Sig
    ...
    /T (Signature1)
    ...
    >>
    endobj 
    ...
    /ID [<1952AB9C134E46B58251246E985D5C15> <7F887303DDC0ED7C37AE77403E30DFB0>] 
    ...
    %%EOF
    %PDF-1.4 
    ...
    15 0 obj
    <<
    /FT /Sig
    ...
    /T (Signature1)
    ...
    >>
    endobj 
    ...
    /ID [<1952AB9C134E46B58251246E985D5C15> <A57CD5B87222756EC4A096125C7E8A42>] 
    ...
    %%EOF
    ...
    35 0 obj
    <<
    /FT /Sig
    ...
    /T (Signature2)
    ...
    >> 
    ...
    %%EOF 
    

    This structure is broken, even though it does not give rise to wrong cross references (as the first copy and the second copy are identical except for the ID, the cross references and the startxref offsets point to the respective positions in the first copy). Adobe Reader signature validation can react quite sensitive to such issues.

    Thus, you should remove the second copy here to get ahead.


    Furthermore, as already mentioned in a comment the SignerInfo of your CMS signature container contains a 1.2.840.113549.1.9.5 signingTime signed attribute. This is forbidden for PAdES BASELINE profiles.