[ 
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

Reply via email to