[
https://issues.apache.org/jira/browse/PDFBOX-1613?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Thomas Chojecki resolved PDFBOX-1613.
-------------------------------------
Resolution: Fixed
> The ability to inject the time/random component into the COSWriter process to
> write a PDF document allows some advanced signature creation scenarios where
> the signature is generated on a separate server that does not hold the full
> PDF document.
> ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: PDFBOX-1613
> URL: https://issues.apache.org/jira/browse/PDFBOX-1613
> Project: PDFBox
> Issue Type: Improvement
> Components: Writing
> Affects Versions: 1.8.1
> Environment: Any
> Reporter: Stefan Santesson
> Assignee: Thomas Chojecki
> Priority: Minor
> Labels: security
> Fix For: 2.0.0
>
>
> I have developed a prototype server based signing service for the Swedish
> National eID infrastructure.
> I'll skip the details, but I recently switched to PDFBox for the PDF signing
> process and it works great. However, I had to modify the COSWriter class to
> get this working.
> I'm writing to check whether you would consider adding the functionality I
> need to future version of PDFBox.
> The problem is the the signature service is just producing the signature, it
> is not trusted to handle the PDF document.
> The government service having the PDF document signed is using PDFBox in a 2
> step process.
> 1) To produce the SignedAttributes DER Object of the CMS signature to be
> created. This is the part that is hashed and signed by the signature service.
> 2) After receiving the signature and signature certs from the signature
> service, completing the PDF signature by delivering the complete PKCS#7
> object to PDFBox using the externally generated signature value and certs.
> There are probably a more pure way to handle this, but Since PDFBox allows me
> to create a signature interface that produces the SignedData. I found it to
> be the easiest way to run the signature process 2 times.
> 1st pass using dummy key and dummy certs. This only to obtain the
> SignedAttributes.
> 2nd pass by delivering a SignedData object that include the Signature value
> and certs produced by the signature service.
> Now in order to do this, I have to control the random seed added by the
> COSWriter, or else the signature created by the signature service will not
> match the hash in the SignedAttributes produced in the second pass.
> My modification is provided below.
> I simply provided an extra input parameter to the write function where I can
> provide the long seed
> I then added a backwards compatible write function where the long seed is
> current time.
> By providing the same seed to pass 1 and pass 2, I can get the externally
> created signature to match the SignedAttributes produced in the first pass.
> The write function below is identical to the original COSWriter function
> except that it takes the idTime value from the function input parameter
> instead of getting it from System.currentTimeMillis().
> Modified functions of COSWriter:
> /**
> * This will write the pdf document.
> *
> * @param doc The document to write.
> *
> * @throws COSVisitorException If an error occurs while generating the data.
> */
> public void write(PDDocument doc) throws COSVisitorException {
> write(doc, System.currentTimeMillis());
> }
> /**
> * This will write the pdf document.
> *
> * @param doc The document to write.
> * @param idTime The time seed used to generate the id
> *
> * @throws COSVisitorException If an error occurs while generating the data.
> */
> public void write(PDDocument doc, long idTime) throws COSVisitorException {
> document = doc;
> if (incrementalUpdate) {
> prepareIncrement(doc);
> }
> // if the document says we should remove encryption, then we shouldn't
> encrypt
> if (doc.isAllSecurityToBeRemoved()) {
> this.willEncrypt = false;
> // also need to get rid of the "Encrypt" in the trailer so readers
> // don't try to decrypt a document which is not encrypted
> COSDocument cosDoc = doc.getDocument();
> COSDictionary trailer = cosDoc.getTrailer();
> trailer.removeItem(COSName.ENCRYPT);
> } else {
> SecurityHandler securityHandler = document.getSecurityHandler();
> if (securityHandler != null) {
> try {
> securityHandler.prepareDocumentForEncryption(document);
> this.willEncrypt = true;
> } catch (IOException e) {
> throw new COSVisitorException(e);
> } catch (CryptographyException e) {
> throw new COSVisitorException(e);
> }
> } else {
> this.willEncrypt = false;
> }
> }
> COSDocument cosDoc = document.getDocument();
> COSDictionary trailer = cosDoc.getTrailer();
> COSArray idArray = (COSArray) trailer.getDictionaryObject(COSName.ID);
> if (idArray == null || incrementalUpdate) {
> try {
> //algorithm says to use time/path/size/values in doc to generate
> //the id. We don't have path or size, so do the best we can
> MessageDigest md = MessageDigest.getInstance("MD5");
> md.update(Long.toString(idTime).getBytes("ISO-8859-1"));
> COSDictionary info = (COSDictionary)
> trailer.getDictionaryObject(COSName.INFO);
> if (info != null) {
> Iterator<COSBase> values = info.getValues().iterator();
> while (values.hasNext()) {
>
> md.update(values.next().toString().getBytes("ISO-8859-1"));
> }
> }
> idArray = new COSArray();
> COSString id = new COSString(md.digest());
> idArray.add(id);
> idArray.add(id);
> trailer.setItem(COSName.ID, idArray);
> } catch (NoSuchAlgorithmException e) {
> throw new COSVisitorException(e);
> } catch (UnsupportedEncodingException e) {
> throw new COSVisitorException(e);
> }
> }
> cosDoc.accept(this);
> }
> Finally. The way I use this in my signature process is by using this altered
> static function saveIncremental from the PDFDocument class.
> Since this function is static, I just call this duplicated function instead
> of the one in the PDFDocument class.
> Here I use my altered COSWriter (CsCOSWriter).
> /**
> * Save the pdf as incremental. This method is a modification of the same
> * method of PDDcoument. This method use an altered COSWriter that allows
> * control over the time used to create the ID of the document. This way
> it
> * is possible to perform two consecutive signature generation passes that
> * produce the same document hash.
> *
> * @param doc The document being written with signature creation
> * @param input An input file stream of the document being written
> * @param output An output file stream for the result document
> * @param idTime The time in milliseconds from Jan 1st, 1970 GMT when the
> * signature is created. This time is also used to calculate the ID of the
> * document.
> * @throws IOException if something went wrong
> * @throws COSVisitorException if something went wrong
> */
> public static void saveIncremental(PDDocument doc, FileInputStream input,
> OutputStream output, long idTime) throws IOException, COSVisitorException {
> //update the count in case any pages have been added behind the
> scenes.
> doc.getDocumentCatalog().getPages().updateCount();
> CsCOSWriter writer = null;
> try {
> // Sometimes the original file will be missing a newline at the
> end
> // In order to avoid having %%EOF the first object on the same
> line
> // as the %%EOF, we put a newline here. If there's already one at
> // the end of the file, an extra one won't hurt. PDFBOX-1051
> output.write("\r\n".getBytes());
> writer = new CsCOSWriter(output, input);
> writer.write(doc, idTime);
> writer.close();
> } finally {
> if (writer != null) {
> writer.close();
> }
> }
> }
--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators
For more information on JIRA, see: http://www.atlassian.com/software/jira