[ https://issues.apache.org/jira/browse/PDFBOX-1613?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Thomas Chojecki reassigned PDFBOX-1613: --------------------------------------- Assignee: Thomas Chojecki > 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: 1.8.1 > > Original Estimate: 1h > Remaining Estimate: 1h > > 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