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