com.somospnt.signature.TimeStamperEmbedder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pdfbox-signature-api Show documentation
Show all versions of pdfbox-signature-api Show documentation
This is a libray to digital signing a PDF and a PFX valid certificate.
Based in PDFBox official SVN example.
The newest version!
/*
* Copyright 2015 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.somospnt.signature;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.util.Hex;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedData;
/**
* A simple wrapper class for signing PDF files using PDFBOX Example usage:
*
*
* {@code
* FileInputStream inputStream = new FileInputStream(INPUT_FILE);
* FileOutputStream outputStream = new FileOutputStream(OUTPUT_FILE);
* Signer signer = new Signer(KEYSTORE_PATH, PASSWORD, LOCATION, REASON);
* signer.sign(inputStream, outputStream);
* }
*
*
* @author SomosPNT
*/
class TimeStamperEmbedder {
private final String tsaUrl;
private PDDocument document;
private PDSignature signature;
private byte[] changedEncodedSignature;
public TimeStamperEmbedder(String tsaUrl) {
this.tsaUrl = tsaUrl;
}
/**
* Embeds signed timestamp(s) into existing signatures of the given document
*
* @param inputStream containing the bytes of the input pdf
* @param outputStream Where the changed document will be saved
* @throws IOException
*/
void embedTimeStamp(InputStream inputStream, OutputStream outputStream) throws IOException {
byte[] inputBytes = inputStream.readAllBytes();
// sign
try (PDDocument doc = PDDocument.load(inputBytes)) {
document = doc;
processTimeStamping(inputBytes, outputStream);
}
}
/**
* Processes the time-stamping of the Signature.
*
* @param documentBytes of the existing file containing the pdf
* @param outputStream Where the new file will be written to
* @throws IOException
*/
private void processTimeStamping(byte[] documentBytes, OutputStream outputStream) throws IOException {
try {
processRelevantSignatures(documentBytes);
if (changedEncodedSignature != null) {
embedNewSignatureIntoDocument(documentBytes, outputStream);
} else {
throw new IOException("No signatures were found in the document.");
}
} catch (IOException | NoSuchAlgorithmException | CMSException e) {
throw new IOException(e);
}
}
/**
* Create changed Signature with embedded TimeStamp from TSA
*
* @param documentBytes byte[] of the input file
* @throws IOException
* @throws CMSException
* @throws NoSuchAlgorithmException
*/
private void processRelevantSignatures(byte[] documentBytes)
throws IOException, CMSException, NoSuchAlgorithmException {
getRelevantSignature(document);
if (signature != null) {
byte[] sigBlock = signature.getContents(documentBytes);
CMSSignedData signedData = new CMSSignedData(sigBlock);
System.out.println("INFO: Byte Range: " + Arrays.toString(signature.getByteRange()));
ValidationTimeStamp validation = new ValidationTimeStamp(tsaUrl);
signedData = validation.addSignedTimeStamp(signedData);
byte[] newEncoded = Hex.getBytes(signedData.getEncoded());
int maxSize = signature.getByteRange()[2] - signature.getByteRange()[1];
System.out.println(
"INFO: New Signature has Size: " + newEncoded.length + " maxSize: " + maxSize);
if (newEncoded.length > maxSize - 2) {
throw new IOException(
"New Signature is too big for existing Signature-Placeholder. Max Place: "
+ maxSize);
} else {
changedEncodedSignature = newEncoded;
}
}
}
/**
* Extracts last Document-Signature from the document. The signature will be
* set on the signature-field.
*
* @param document to get the Signature from
* @throws IOException
*/
private void getRelevantSignature(PDDocument document) throws IOException {
// we can't use getLastSignatureDictionary() because this will fail (see PDFBOX-3978)
// if a signature is assigned to a pre-defined empty signature field that isn't the last.
// we get the last in time by looking at the offset in the PDF file.
SortedMap sortedMap = new TreeMap<>();
for (PDSignature sig : document.getSignatureDictionaries()) {
int sigOffset = sig.getByteRange()[1];
sortedMap.put(sigOffset, sig);
}
if (sortedMap.size() > 0) {
PDSignature lastSignature = sortedMap.get(sortedMap.lastKey());
COSBase type = lastSignature.getCOSObject().getItem(COSName.TYPE);
if (type.equals(COSName.SIG)) {
signature = lastSignature;
}
}
}
/**
* Embeds the new signature into the document, by copying the rest of the
* document
*
* @param docBytes byte array of the document
* @param output target, where the file will be written
* @throws IOException
*/
private void embedNewSignatureIntoDocument(byte[] docBytes, OutputStream output)
throws IOException {
int[] byteRange = signature.getByteRange();
output.write(docBytes, byteRange[0], byteRange[1] + 1);
output.write(changedEncodedSignature);
int addingLength = byteRange[2] - byteRange[1] - 2 - changedEncodedSignature.length;
byte[] zeroes = Hex.getBytes(new byte[(addingLength + 1) / 2]);
output.write(zeroes);
output.write(docBytes, byteRange[2] - 1, byteRange[3] + 1);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy