/** * Copyright (c) 2006 by Know-Center, Graz, Austria * * This software is the confidential and proprietary information of Know-Center, * Graz, Austria. You shall not disclose such Confidential Information and shall * use it only in accordance with the terms of the license agreement you entered * into with Know-Center. * * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. * * $Id: BinarySignature.java,v 1.4 2006/10/11 07:57:58 wprinz Exp $ */ package at.knowcenter.wag.egov.egiz.pdf; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import at.gv.egiz.pdfas.exceptions.ErrorCode; import at.gv.egiz.pdfas.exceptions.pdf.KZSettingNotFoundException; import at.gv.egiz.pdfas.framework.input.PdfDataSource; import at.gv.egiz.pdfas.framework.output.DataSink; import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; import at.knowcenter.wag.egov.egiz.exceptions.PlaceholderException; import at.knowcenter.wag.egov.egiz.exceptions.PresentableException; import at.knowcenter.wag.egov.egiz.exceptions.SettingNotFoundException; import at.knowcenter.wag.egov.egiz.sig.SignatureFieldDefinition; import at.knowcenter.wag.egov.egiz.sig.SignatureTypes; import at.knowcenter.wag.egov.egiz.tools.CodingHelper; import at.knowcenter.wag.exactparser.ByteArrayUtils; import com.lowagie.text.Document; import com.lowagie.text.DocumentException; import com.lowagie.text.Rectangle; import com.lowagie.text.pdf.PRStream; import com.lowagie.text.pdf.PdfArray; import com.lowagie.text.pdf.PdfContentByte; import com.lowagie.text.pdf.PdfDictionary; import com.lowagie.text.pdf.PdfIndirectObject; import com.lowagie.text.pdf.PdfIndirectReference; import com.lowagie.text.pdf.PdfName; import com.lowagie.text.pdf.PdfNumber; import com.lowagie.text.pdf.PdfObject; import com.lowagie.text.pdf.PdfPTable; import com.lowagie.text.pdf.PdfReader; import com.lowagie.text.pdf.PdfStamper; import com.lowagie.text.pdf.PdfStamperImp; import com.lowagie.text.pdf.PdfString; import com.lowagie.text.pdf.PdfTemplate; /** * Contains various extension functions to digitally sign documents. * *

* These functions are used to replace parts of the original Egiz plain text * signature mechanism. *

* * @author wprinz */ public abstract class BinarySignature { /** * The tolerance area of the line break algorithm. * * @see Placeholder#replacePlaceholderWithTolerance(byte[], List, byte[], int) */ public static final int LINE_BREAK_TOLERANCE = 10; /** * The number of bytes left out for the certificate placeholder. */ public static final int CERTIFICATE_PLACEHOLDER_LENGTH = 10000; /** * The placeholder character used to fill out Strings in the layout process. */ public static final byte LAYOUT_PLACEHOLDER = 'w'; /** * This placeholder is used to fill out holes between the byte ranges before * the document is signed. */ public static final byte SIGN_PLACEHOLDER = 0; /** * The nil brev used to define an unrecognized value. */ public static final byte[] BREV_NIL = { 'n', 'i', 'l' }; /** * The date brev. */ public static final byte[] BREV_DAT = { 'd', 'a', 't' }; /** * The issure brev. */ public static final byte[] BREV_ISS = { 'i', 's', 's' }; /** * The serial number brev. */ public static final byte[] BREV_SNR = { 's', 'n', 'r' }; /** * The value brev. */ public static final byte[] BREV_VAL = { 'v', 'a', 'l' }; /** * The SIG_ID brev. */ public static final byte[] BREV_SID = { 's', 'i', 'd' }; /** * No explicit encoding. */ public static final byte[] ENCODING_NIL = { 'n', 'i', 'l' }; /** * PDF WinAnsiEncoding. */ public static final byte[] ENCODING_WIN = { 'w', 'i', 'n' }; /** * URL encoding. */ public static final byte[] ENCODING_URL = { 'u', 'r', 'l' }; /** * The PDFName of the Egiz Dictionary. * *

* Used to locate and identify the Egiz Dictionary in the document. *

*/ public static final PdfName EGIZ_DICT_NAME = new PdfName("EGIZSigDict"); /** * The PDFName of the Original Document Size (ODS) field in an Egiz * Dictionary. * *

* The ODS must be a positive integral number. *

*/ public static final PdfName EGIZ_ODS_NAME = new PdfName("ODS"); /** * The PDFName of the Kennzeichnung attribute. */ public static final PdfName EGIZ_KZ_NAME = new PdfName("ID"); /** * The PDFName of the /replaces field in an Egiz Dictionary. */ public static final PdfName EGIZ_REPLACES_NAME = new PdfName("replaces"); /** * The PDFName of the /encodings field in an Egiz Dictionary. */ public static final PdfName EGIZ_ENCODINGS_NAME = new PdfName("encodings"); /** * The PDFName of the byte ranges array. */ public static final PdfName EGIZ_BYTERANGES_NAME = new PdfName("ByteRange"); /** * The PdfName of the certificate array. */ public static final PdfName EGIZ_CERTIFICATE_NAME = new PdfName("Cert"); /** * The PDFName of the Signature XObject field in an Egiz Dictionary. * *

* This must be an indirect reference to the XObject containing the Signature * table. *

*/ public static final PdfName EGIZ_XOBJ_NAME = new PdfName("SigXObject"); /** * The number placeholder that is used to give numbers a fixed length. */ protected static final PdfNumber NUMBER_PLACEHOLDER = new PdfNumber(99999999); /** * Extracts the signature text only. * *

* The signature text is the text of the Signature XObject. *

* * @param egiz_dict * The Egiz Dictionary. * * @return Returns the signature text. */ public static String extractSignatureTextOnly(PdfDictionary egiz_dict) throws IOException { PdfIndirectReference xobj_ir = (PdfIndirectReference) egiz_dict.get(EGIZ_XOBJ_NAME); PRStream temp_stream = (PRStream) PdfReader.getPdfObject(xobj_ir); byte[] stream_bytes = PdfReader.getStreamBytes(temp_stream); return Utils.extractPureTextFromContentStream(stream_bytes); } /** * Retrieves the size of the original document from the Egiz Dictionary. * * @param egiz_dict * The Egiz Dictionary. * @return Returns the size (in bytes) of the original document. */ public static int getOriginalDocumentSizeFromEgizDict(PdfDictionary egiz_dict) { PdfObject ods_obj = egiz_dict.get(EGIZ_ODS_NAME); PdfNumber ods_number = (PdfNumber) PdfReader.getPdfObject(ods_obj); return ods_number.intValue(); } /** * Retrieves the previous Egiz dictionary from the given one, if a previous * dictionary exists. * * @param egiz_dict * The Egiz Dictionary. * @return Returns the previous Egiz Dictionary, or null if there is none. */ public static PdfDictionary getPreviousFromEgizDict(PdfDictionary egiz_dict) { PdfObject prev_obj = egiz_dict.get(PdfName.PREV); PdfDictionary previous_dict = (PdfDictionary) PdfReader.getPdfObject(prev_obj); return previous_dict; } /** * Retrieves the Egiz Dictionary from the document if present. * * @param reader * The reader to retrieve the dictionary from. * @return Returns the Egiz Dictionary, if present, or returns null, if no * egiz dictionary was found. */ public static PdfDictionary getEgizDictFromReader(PdfReader reader) { PdfIndirectReference dict_ir = getEgizDictIndRefFromReader(reader); if (dict_ir == null) { return null; } PdfDictionary egiz_dict = (PdfDictionary) PdfReader.getPdfObject(dict_ir); return egiz_dict; } /** * Retrieves the Egiz Dictionary's indirect reference from the reader. * * @param reader * The reader. * @return Returns the indirect reference of the Egiz Dictionary, or null, if * none exists. */ public static PdfIndirectReference getEgizDictIndRefFromReader(PdfReader reader) { PdfDictionary catalog = reader.getCatalog(); PdfIndirectReference dict_ir = (PdfIndirectReference) catalog.get(EGIZ_DICT_NAME); return dict_ir; } /** * Retrieves the chain of Egiz Dictionaries from the reader. * *

* The first element in the List will be the top most (oldest) Egiz * Dictionary. The last element in the List will be the bottom most (latest) * Egiz Dictionary. If the list is empty, no dictionary could be found at all, * which means that the document is not digitally signed. *

* * @param reader * The reader. * @return Returns the List of PdfDictionaries from the document. */ public static List getEgizDictChainFromReader(PdfReader reader) { List dicts = new ArrayList(); PdfDictionary current_dict = getEgizDictFromReader(reader); if (current_dict != null) { dicts.add(0, current_dict); while ((current_dict = getPreviousFromEgizDict(current_dict)) != null) { dicts.add(0, current_dict); } } return dicts; } /** * Builds a digest of the given data. * * @param data * The data to be digested. * @param length * The length of the data portion that should be used for digesting. * This allows to build the digest only over parts of the data. * @return Returns the created digest. * @throws PDFDocumentException * Forwarded exception. */ public static byte[] buildDigest(final byte[] data, final int length) throws PDFDocumentException { MessageDigest sha_512 = null; try { sha_512 = MessageDigest.getInstance("SHA-512"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); throw new PDFDocumentException(202, "Digest algorithm not supported - NoSuchAlgorithmException", e); } sha_512.reset(); sha_512.update(data, 0, length); byte[] digest = sha_512.digest(); return digest; } /** * Retrieves the signable text from the given document. * * @param data * The data. * @param ods * The original document size. * @return Returns the signable text. */ public static String retrieveSignableTextFromData(final byte[] data, final int ods) { // byte[] digest = buildDigest(data, ods); String raw_text = CodingHelper.encodeBase64(data);// digest); // data); return raw_text; } /** * Fills the holes in the byte ranges with the SIGN_PLACEHOLDER. * * @param data * The given byte ranged data. * @param byte_ranges * The byte ranges. * @return Returns the filled text. */ public static byte[] prepareDataToSign(final byte[] data, final List byte_ranges) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Iterator it = byte_ranges.iterator(); int last_end = 0; while (it.hasNext()) { StringInfo si = (StringInfo) it.next(); for (int i = last_end; i < si.string_start; i++) { baos.write(SIGN_PLACEHOLDER); } baos.write(data, si.string_start, si.string_length); last_end = si.string_start + si.string_length; } byte[] data_to_sign = baos.toByteArray(); return data_to_sign; } // TODO old code - remove // /** // * Extracts the binary 'text' of a document. // * // *

// * If the document contains an Egiz Dictionary, which means that it is already // * signed, the binary text is the Base64 coded string of the original document // * followed by the Ascii representation of the signature block. // *

// *

// * If the document does not contain an Egiz Dictionary, which means that it is // * unsigned, only the binary Base64 coded original document is returned as // * binary text. // *

// *

// * This function is intented for being used instead of the "text extraction" // * mechanism used in the plain text Egiz project. // *

// * // * @param doc // * The file. // * @return Returns the binary text of the document. // * @throws PDFDocumentException // * Forwarded exception. // */ // public static String extractTextBinary(File doc) throws PDFDocumentException // { // try // { // FileInputStream fis = new FileInputStream(doc); // return extractTextBinary(fis); // } // catch (FileNotFoundException e) // { // throw new PDFDocumentException(202, e); // } // } // // /** // * Extracts the text binary. // * // * @param is // * @return Returns the binary text. // * @throws PDFDocumentException // */ // public static String extractTextBinary(InputStream is) throws PDFDocumentException // { // try // { // // for some stupid reason this produces a read error if the is comes from // // a // // multipart servlet form..??? // ByteArrayOutputStream baos = new ByteArrayOutputStream(); // int i = -1; // int acc = 0; // byte[] b = new byte[1000]; // while ((i = is.read(b)) > 0) // { // acc += i; // System.out.print(" " + i); // baos.write(b, 0, i); // } // System.out.println("acc = " + acc); // byte[] pdf = baos.toByteArray(); // // return extractTextBinary(pdf); // } // catch (IOException e) // { // throw new PDFDocumentException(202, e); // } // } // // /** // * Extracts the signable text from a binary pdf document. // * // *

// * The signable text is the text that will be signed or verified afterwards. // *

// * // * @param pdf // * The pdf document. // * @return Returns the extracted text String. // * @throws PDFDocumentException // * Forwarded exception. // */ // public static String extractTextBinary(final byte[] pdf) throws PDFDocumentException // { // try // { // PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf)); // PdfDictionary egiz_dict = getEgizDictFromReader(reader); // if (egiz_dict == null) // { // System.out.println("NO Egiz Dict found - whole doc = original doc"); // // int ods = pdf.length; // return retrieveSignableTextFromData(pdf, ods); // } // // String sig_text = extractSignatureTextOnly(egiz_dict); // // int ods = getOriginalDocumentSizeFromEgizDict(egiz_dict); // // String raw_text = retrieveSignableTextFromData(pdf, ods); // raw_text += "\n"; // raw_text += sig_text; // // return raw_text; // } // catch (IOException e) // { // throw new PDFDocumentException(202, e); // } // } // TODO obsolete code - remove // /** // * Retrieves the List of SignatureHolders containing the information of all // * digital signatures of the given document. // * // *

// * If the List of SignatureHolders is empty, the document is not signed // * anyways. // *

// * // * @param pdf // * The complete pdf document. // * @return Returns the List of SignatureHolders. // * @throws PDFDocumentException // * @throws SignatureTypesException // * @throws SignatureException // */ // public static List extractSignatureHoldersBinary(final byte[] pdf) throws PDFDocumentException, SignatureTypesException, SignatureException // { // try // { // PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf)); // List chain = getEgizDictChainFromReader(reader); // // List signatures = new ArrayList(); // Iterator it = chain.iterator(); // while (it.hasNext()) // { // PdfDictionary dict = (PdfDictionary) it.next(); // // int ods = getOriginalDocumentSizeFromEgizDict(dict); // String signature_text = extractSignatureTextOnly(dict); // // SignatureTypes sig_types = SignatureTypes.getInstance(); // List types = sig_types.getSignatureTypeDefinitions(); // SignatureBlock sig_block = new SignatureBlock(types); // boolean could_separate = sig_block.separateBlockFromRawText(signature_text, false); // // if (could_separate) // { // SignatureObject sig_object = sig_block.getSignatureObject(); // // SignatureHolder holder = new BinarySignatureHolder(pdf, ods, sig_object); // signatures.add(holder); // } // } // // return signatures; // } // catch (IOException e) // { // throw new PDFDocumentException(201, e); // } // } // /** // * Signs a document with the given signature table using the Incremental // * Update method. // * // *

// * The table containing the signature text will be appended. As specified by // * the parameters, the signature will be appended to the last page, or a // plain // * new page will be created for the signature to hold. // *

// *

// * The table will be completely wrapped by an XObject, which will also be // * indirectly referenced by the Egiz Dictionary. This will ease the // * verification process. // *

// *

// * An Egiz Dictionary will be added to the new document that contains // * information about the signature. Basically the size of the original // * document and the reference of the signature table. // *

// * // * @param original_document // * The file name of the original document. // * @param new_document // * The file name of the new document to be created. // * @param pdf_table // * The PdfPTable that contains the signature block. // * @param pos_x // * The x position where the table should be inserted. // * @param pos_y // * The y position where the table should be inserted (on the last // * page). If this is negative, a new page will be appended to the // * document. Then the table will be inserted on that new page using // * the absolute value of pos_y. Note that pos_y specifies the top // * line of the table. // * @throws PresentableException // * Forwarded exception. // * // * @see #writeIncrementalUpdate(byte[], PdfPTable, float, float, boolean) // */ // public static void writeIncrementalUpdate(String original_document, // String new_document, PdfPTable pdf_table, float pos_x, float pos_y, // int egiz_dict_num_replaces) throws PresentableException // { // try // { // File original_document_file = new File(original_document); // FileInputStream fis = new FileInputStream(original_document_file); // byte[] pdf = new byte[(int) original_document_file.length()]; // fis.read(pdf); // fis.close(); // // byte[] signed_pdf = writeIncrementalUpdate(pdf, pdf_table, pos_x, pos_y, // egiz_dict_num_replaces); // // File new_document_file = new File(new_document); // FileOutputStream fos = new FileOutputStream(new_document_file); // fos.write(signed_pdf); // fos.close(); // } // catch (IOException e) // { // throw new PresentableException(e); // } // } /** * Signs a document with the given signature table using the Incremental * Update method. * *

* The table containing the signature text will be appended. As specified by * the parameters, the signature will be appended to the last page, or a plain * new page will be created for the signature to hold. *

*

* The table will be completely wrapped by an XObject, which will also be * indirectly referenced by the Egiz Dictionary. This will ease the * verification process. *

*

* An Egiz Dictionary will be added to the new document that contains * information about the signature. Basically the size of the original * document and the reference of the signature table. *

* * @param original_document * The original document. * @param pdf_table * The PdfPTable that contains the signature block. * @param pi * The PositioningInstruction telling the algorithm where to place * the signature block. * @return Returns the new document. * @throws PresentableException * Forwarded exception. */ public static IncrementalUpdateInformation writeIncrementalUpdate(PdfDataSource original_document, DataSink written_pdf, PdfPTable pdf_table, PositioningInstruction pi, List variable_field_definitions, List all_field_definitions) throws PresentableException { try { IncrementalUpdateInformation iui = new IncrementalUpdateInformation(); iui.original_document = original_document; iui.start_index = original_document.getLength(); Document.compress = false; // System.out.println("wprinz: STAMPING PDF"); InputStream is = original_document.createInputStream(); PdfReader reader = new PdfReader(is); is.close(); OutputStream baos = written_pdf.createOutputStream("application/pdf"); // ByteArrayOutputStream baos = new ByteArrayOutputStream(); // IMPORTANT: append the new content to the original document using // incremental updated // The stamper allows this by setting append = true PdfStamper stamper = new PdfStamper(reader, baos, '\0', true); if (pi.isMakeNewPage()) { int pdf_page_num = reader.getNumberOfPages(); Rectangle psize = reader.getPageSizeWithRotation(pdf_page_num); Rectangle rect = new Rectangle(psize); stamper.insertPage(pdf_page_num + 1, rect); } if (pi.getPage() < 1 || pi.getPage() > stamper.getReader().getNumberOfPages()) { throw new PDFDocumentException(224, "The provided page (=" + pi.getPage() + ") is out of range."); } PdfContentByte content = stamper.getOverContent(pi.getPage()); // content = StampContent einer PageStamp. // System.out.println("table_width = " + pdf_table.getTotalWidth() + ", // table_height = " + pdf_table.getTotalHeight()); PdfTemplate table_template = content.createTemplate(pdf_table.getTotalWidth(), pdf_table.getTotalHeight()); pdf_table.writeSelectedRows(0, -1, 0, pdf_table.getTotalHeight(), table_template); // table_template.moveTo(0, 0); // table_template.lineTo(pdf_table.getTotalWidth(), // pdf_table.getTotalHeight()); // table_template.stroke(); // table_template.moveTo(0, 0); // table_template.lineTo(100, 100); // table_template.stroke(); // pdf_table.writeSelectedRows(0, -1, SIGNATURE_BORDER / 2, // table_position, content); content.addTemplate(table_template, pi.getX(), pi.getY() - pdf_table.getTotalHeight()); // For debugging print a 100x100 grid // { // Rectangle psize = reader.getPageSizeWithRotation(pos.page); // float page_width = psize.width(); // float page_height = psize.height(); // for (float x = 0; x < page_width; x += 100) // { // content.moveTo(x, 0); // content.lineTo(x, page_height); // content.stroke(); // } // for (float y = 0; y < page_height; y += 100) // { // content.moveTo(0, y); // content.lineTo(page_width, y); // content.stroke(); // } // } // content.setLineWidth(10.0f); // content.moveTo(0, 0); // content.lineTo(100, 100); // content.stroke(); // PdfIndirectReference page_ref = // stamper.getWriter().getPageReference(signature_page); // System.out.println("page_ref = " + page_ref.toString()); // PdfObject page_obj = PdfReader.getPdfObject(page_ref); // System.out.println("page_obj = " + page_obj); // PdfDictionary page_dict = (PdfDictionary) page_obj; // PdfObject resources_obj = page_dict.get(PdfName.RESOURCES); // System.out.println("resources_obj = " + resources_obj); // PdfDictionary resources = (PdfDictionary) // PdfReader.getPdfObject(resources_obj); // for (Iterator it = resources.getKeys().iterator(); it.hasNext();) // { // PdfName key = (PdfName) it.next(); // PdfObject value = resources.get(key); // System.out.println(" " + key + " = " + value); // } // add the EGIZ dict: if (variable_field_definitions != null) { createEgizDict(stamper, table_template, iui, variable_field_definitions, all_field_definitions); } stamper.close(); // System.out.println("wprinz: STAMPING FINISHED"); // just to make sure the stream is really closed baos.close(); // iui.signed_pdf = baos.toByteArray(); return iui; } catch (IOException e) { e.printStackTrace(); throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, e); } catch (DocumentException e) { e.printStackTrace(); throw new PresentableException(ErrorCode.CANNOT_WRITE_PDF, e); } } /** * Creates the EGIZ Dictionary and adds it to the document. * * @param stamper * The PdfStamper. * @param table_template * The Template of the Signature block. * @param iui * The IncrementalUpdateInformation. * @param variable_field_definitions * The field definitions. * @throws IOException * @throws SettingNotFoundException */ protected static void createEgizDict(PdfStamper stamper, PdfTemplate table_template, IncrementalUpdateInformation iui, List variable_field_definitions, List all_field_definitions) throws IOException, SettingNotFoundException { // iui.temp_ir = table_template.getIndirectReference(); iui.temp_ir_number = table_template.getIndirectReference().getNumber(); iui.temp_ir_generation = table_template.getIndirectReference().getGeneration(); byte[] content_stream = table_template.toPdf(null); iui.content_stream_length = content_stream.length; iui.replaces = determineReplacesInContentStream(content_stream, 0, content_stream.length, variable_field_definitions); iui.kz_list = determineKZ(content_stream, 0, content_stream.length, all_field_definitions); // PdfIndirectReference previous_egiz_dict_ind_ref = // getEgizDictIndRefFromReader(reader); PdfDictionary egiz_dict = new PdfDictionary(EGIZ_DICT_NAME); egiz_dict.put(EGIZ_XOBJ_NAME, table_template.getIndirectReference()); egiz_dict.put(EGIZ_ODS_NAME, NUMBER_PLACEHOLDER); PdfArray kz_array = new PdfArray(); for (int i = 0; i < iui.kz_list.size(); i++) { kz_array.add(NUMBER_PLACEHOLDER); // start kz_array.add(NUMBER_PLACEHOLDER); // length } egiz_dict.put(EGIZ_KZ_NAME, kz_array); int num_replaces = calcNumReps(iui.replaces); int num_holes = num_replaces + 1 + 1; // +1 = the /encodings hole // +1 = the /Cert int num_byte_ranges = num_holes + 1; PdfArray byte_ranges_array = new PdfArray(); for (int i = 0; i < num_byte_ranges; i++) { byte_ranges_array.add(NUMBER_PLACEHOLDER); // start byte_ranges_array.add(NUMBER_PLACEHOLDER); // length } egiz_dict.put(EGIZ_BYTERANGES_NAME, byte_ranges_array); PdfArray encodings_array = new PdfArray(); encodings_array.add(new PdfName(new String(ENCODING_NIL))); // the // /encodings encodings_array.add(new PdfName(new String(ENCODING_NIL))); // the /Cert // array itself PdfArray replaces_array = new PdfArray(); replaces_array.add(new PdfName(new String(BREV_NIL, "US-ASCII"))); // the // /encodings replaces_array.add(new PdfName(new String(BREV_NIL, "US-ASCII"))); // the // /Cert Iterator it = iui.replaces.iterator(); while (it.hasNext()) { ReplaceInfo ri = (ReplaceInfo) it.next(); for (int i = 0; i < ri.replaces.size(); i++) { byte[] brev = typeToBrev(ri.sfd.field_name); String brev_string = new String(brev, "US-ASCII"); PdfName brev_name = new PdfName(brev_string); replaces_array.add(brev_name); encodings_array.add(new PdfName(new String(ENCODING_WIN, "US-ASCII"))); } } egiz_dict.put(EGIZ_REPLACES_NAME, replaces_array); egiz_dict.put(EGIZ_ENCODINGS_NAME, encodings_array); PdfArray cert_array = new PdfArray(); byte[] cert_bytes = new byte[CERTIFICATE_PLACEHOLDER_LENGTH]; for (int i = 0; i < cert_bytes.length; i++) { cert_bytes[i] = 0; } PdfString cert_placeholder = new PdfString(cert_bytes); cert_array.add(cert_placeholder); egiz_dict.put(EGIZ_CERTIFICATE_NAME, cert_array); // if (previous_egiz_dict_ind_ref != null) // { // egiz_dict.put(PdfName.PREV, previous_egiz_dict_ind_ref); // } // no EGIZ chain PdfIndirectObject dict_ref = stamper.getWriter().addToBody(egiz_dict); // iui.egiz_dict_ir = dict_ref.getIndirectReference(); iui.egiz_dict_ir_number = dict_ref.getIndirectReference().getNumber(); iui.egiz_dict_ir_generation = dict_ref.getIndirectReference().getGeneration(); PdfIndirectReference root_ref = (PdfIndirectReference) stamper.getReader().getTrailer().get(PdfName.ROOT); PdfDictionary root = (PdfDictionary) PdfReader.getPdfObject(root_ref); // root.put(EGIZ_DICT_NAME, dict_ref.getIndirectReference()); ((PdfStamperImp) stamper.getWriter()).markUsed(root); // PdfDictionary extra_cata = stamper.getWriter().getExtraCatalog(); // extra_cata.put(dict_type, dict_ref.getIndirectReference()); ((PdfStamperImp) stamper.getWriter()).setEgizDictTrailerInfo(EGIZ_DICT_NAME, dict_ref.getIndirectReference()); } /** * Converts a field name (type) to the corresponding BREV. * * @param type * The field name (type). * @return Returns the corresponding BREV, or BREV_NIL if the type is not * recognized. */ protected static byte[] typeToBrev(String type) { if (type.equals(SignatureTypes.SIG_DATE)) { return BREV_DAT; } if (type.equals(SignatureTypes.SIG_ISSUER)) { return BREV_ISS; } if (type.equals(SignatureTypes.SIG_VALUE)) { return BREV_VAL; } if (type.equals(SignatureTypes.SIG_NUMBER)) { return BREV_SNR; } if (type.equals(SignatureTypes.SIG_ID)) { return BREV_SID; } return BREV_NIL; } /** * Updates the information in the egiz dictionary to reflect the real offsets * of the byte ranges. * *

* This replaces the "dummy numbers" in the egiz dictionary with the correct * values. *

* * @param iui * The IncrementalUpdateInformation. * @throws PDFDocumentException */ public static void markByteRanges(IncrementalUpdateInformation iui) throws PDFDocumentException { try { iui.byte_ranges = new ArrayList(); int num_digits = Integer.toString(NUMBER_PLACEHOLDER.intValue()).length(); byte[] signed_pdf = iui.signed_pdf; String str = iui.egiz_dict_ir_number + " " + iui.egiz_dict_ir_generation + " obj"; byte[] obj_bytes = str.getBytes("US-ASCII"); int obj_index = ByteArrayUtils.lastIndexOf(signed_pdf, obj_bytes); int obj_start = obj_index + obj_bytes.length; String ods_str = "/ODS "; byte[] ods_bytes = ods_str.getBytes("US-ASCII"); int ods_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, ods_bytes); int ods_start = ods_index + ods_bytes.length; String kz_str = "/ID["; byte[] kz_bytes = kz_str.getBytes("US-ASCII"); int kz_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, kz_bytes); int kz_start = kz_index + kz_bytes.length; String br_str = "/ByteRange["; byte[] br_bytes = br_str.getBytes("US-ASCII"); int br_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, br_bytes); int array_start = br_index + br_bytes.length; String enc_str = "/encodings["; byte[] enc_bytes = enc_str.getBytes("US-ASCII"); int enc_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, enc_bytes); int enc_start = enc_index + enc_bytes.length; String cert_str = "/Cert[("; byte[] cert_bytes = cert_str.getBytes("US-ASCII"); int cert_index = ByteArrayUtils.indexOf(signed_pdf, obj_start, cert_bytes); int cert_start = cert_index + cert_bytes.length; replaceNumber(signed_pdf, ods_start, signed_pdf.length, num_digits); // update the Kennzeichnung byte ranges int cur_pos = kz_start; for (int i = 0; i < iui.kz_list.size(); i++) { StringInfo si = (StringInfo) iui.kz_list.get(i); replaceNumber(signed_pdf, cur_pos, si.string_start, num_digits); cur_pos += num_digits; cur_pos += 1; replaceNumber(signed_pdf, cur_pos, si.string_length, num_digits); cur_pos += num_digits; cur_pos += 1; } int num_replaces = calcNumReps(iui.replaces); int num_holes = num_replaces + 1 + 1; // +1 = the /encodings hole // int num_byte_ranges = num_holes + 1; cur_pos = array_start; int cur_br_start = 0; // write the /encodings byte range { int enc_length = (1 + 3) * num_holes; StringInfo byte_range = new StringInfo(); byte_range.string_start = cur_br_start; byte_range.string_length = enc_start; iui.byte_ranges.add(byte_range); replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); cur_pos += num_digits; cur_pos += 1; replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); cur_pos += num_digits; cur_pos += 1; cur_br_start = enc_start + enc_length; iui.enc_start = enc_start; iui.enc_length = enc_length; } // write the /Cert byte range { int cert_length = CERTIFICATE_PLACEHOLDER_LENGTH; StringInfo byte_range = new StringInfo(); byte_range.string_start = cur_br_start; byte_range.string_length = cert_start - cur_br_start; iui.byte_ranges.add(byte_range); replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); cur_pos += num_digits; cur_pos += 1; replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); cur_pos += num_digits; cur_pos += 1; cur_br_start = cert_start + cert_length; iui.cert_start = cert_start; iui.cert_length = cert_length; } Iterator rit = iui.replaces.iterator(); while (rit.hasNext()) { ReplaceInfo ri = (ReplaceInfo) rit.next(); // byte [] value_bytes = ri.value.getBytes("ISO-8859-1"); // int write_pos = 0; Iterator sit = ri.replaces.iterator(); while (sit.hasNext()) { StringInfo si = (StringInfo) sit.next(); StringInfo byte_range = new StringInfo(); byte_range.string_start = cur_br_start; byte_range.string_length = si.string_start - cur_br_start; iui.byte_ranges.add(byte_range); replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); cur_pos += num_digits; cur_pos += 1; replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); cur_pos += num_digits; cur_pos += 1; cur_br_start = si.string_start + si.string_length; } } StringInfo byte_range = new StringInfo(); byte_range.string_start = cur_br_start; byte_range.string_length = signed_pdf.length - cur_br_start; iui.byte_ranges.add(byte_range); replaceNumber(signed_pdf, cur_pos, byte_range.string_start, num_digits); cur_pos += num_digits; cur_pos += 1; replaceNumber(signed_pdf, cur_pos, byte_range.string_length, num_digits); cur_pos += num_digits; cur_pos += 1; } catch (IOException e) { throw new PDFDocumentException(201, e); } } /** * Replaces the certificate placeholder with the certificate from the signed * Signature Object. * * @param iui * The IncrementalUpdateInformation. * @throws PDFDocumentException */ public static void replaceCertificate(IncrementalUpdateInformation iui) throws PDFDocumentException { X509Certificate certificate = iui.signed_signature_object.getX509Certificate(); try { byte[] der = certificate.getEncoded(); byte[] encoded = CodingHelper.encodeBase64(der).getBytes("US-ASCII"); byte[] escaped = Placeholder.escapePDFString(encoded); if (escaped.length > iui.cert_length) { throw new PlaceholderException("certificate", escaped.length - iui.cert_length); } System.arraycopy(escaped, 0, iui.signed_pdf, iui.cert_start, escaped.length); } catch (CertificateEncodingException e) { throw new PDFDocumentException(300, e); } catch (UnsupportedEncodingException e) { throw new PDFDocumentException(300, e); } // X509Cert cert = iui.signed_signature_object.getX509Cert(); // // X509Certificate certificate = cert.getX509Certificate(); // try // { // byte[] encoded = cert.getCertString().getBytes("US-ASCII"); // // certificate.getEncoded(); // byte[] escaped = Placeholder.escapePDFString(encoded); // if (escaped.length > iui.cert_length) // { // throw new PlaceholderException("certificate", escaped.length - // iui.cert_length); // } // System.arraycopy(escaped, 0, iui.signed_pdf, iui.cert_start, // escaped.length); // } // // catch (CertificateEncodingException e) // // { // // throw new PDFDocumentException(300, e); // // } // catch (UnsupportedEncodingException e) // { // throw new PDFDocumentException(300, e); // } } /** * Replaces the placeholders with values from the signed SignatureObject. * * @param iui * The IncrementalUpdateInformation. * @throws PDFDocumentException */ public static void replacePlaceholders(IncrementalUpdateInformation iui) throws PDFDocumentException { final byte[] signed_pdf = iui.signed_pdf; // int num_replaces = calcNumReps(iui.replaces); // int num_holes = num_replaces + 1 + 1; // +1 = the /encodings hole; +1 = // the // /Cert int encoding_entry_index = 0; {// /encodings itself int enc_offset = iui.enc_start + encoding_entry_index * 4; signed_pdf[enc_offset] = '/'; System.arraycopy(ENCODING_NIL, 0, signed_pdf, enc_offset + 1, ENCODING_NIL.length); encoding_entry_index++; } {// /Cert itself int enc_offset = iui.enc_start + encoding_entry_index * 4; signed_pdf[enc_offset] = '/'; System.arraycopy(ENCODING_NIL, 0, signed_pdf, enc_offset + 1, ENCODING_NIL.length); encoding_entry_index++; } for (int i = 0; i < iui.replaces.size(); i++) { ReplaceInfo ri = (ReplaceInfo) iui.replaces.get(i); try { String value = ri.value; if (value == null) { value = ""; } byte[] encoding = ENCODING_WIN; byte[] replace_bytes = Placeholder.applyWinAnsiEncoding(value); String restored_value = Placeholder.unapplyWinAnsiEncoding(replace_bytes); if (!value.equals(restored_value)) { // debug: System.out.println("WinAnsiEncoding doesn't fit - using URL instead!"); // /debug! replace_bytes = Placeholder.applyURLEncoding(value); encoding = ENCODING_URL; } for (int string_index = 0; string_index < ri.replaces.size(); string_index++) { int enc_offset = iui.enc_start + encoding_entry_index * 4; signed_pdf[enc_offset] = '/'; System.arraycopy(encoding, 0, signed_pdf, enc_offset + 1, encoding.length); encoding_entry_index++; } Placeholder.replacePlaceholderWithTolerance(signed_pdf, ri.replaces, replace_bytes, LINE_BREAK_TOLERANCE); } catch (PlaceholderException e) { throw new PlaceholderException(ri.sfd.field_name, e.getMissing()); } } } /** * Calculates the number of actual String replaces from a given ReplaceInfo * list. *

* This is used to determine the number of actual replaces that has to be * carried out. Accordingly to this number, entries in the dictionary are * created. *

* * @param replaces * The ReplaceInfo list. * @return Returns the number of string replaces. */ protected static int calcNumReps(List replaces) { int number = 0; Iterator it = replaces.iterator(); while (it.hasNext()) { ReplaceInfo ri = (ReplaceInfo) it.next(); number += ri.replaces.size(); } return number; } /** * Determines the List of ReplaceInfo objects of replaces in the content * stream regarding the given field definitions. * *

* This method collects all variable String fields in a content stream and * orders them according to their start offset. *

* * @param pdf * The PDF. * @param begin * The start of the content stream. * @param end * The end of the content stream. * @param field_definitions * The field definitions that are counceled to find out which and * where varaible strings are. * @return Returns the list of ReplaceInfo objects specifying the variable * areas. */ protected static List determineReplacesInContentStream(final byte[] pdf, int begin, int end, List field_definitions) { List replaces = new ArrayList(); try { List strings = Placeholder.parseStrings(pdf, begin, end); for (int index = 0; index < field_definitions.size(); index++) { SignatureFieldDefinition sfd = (SignatureFieldDefinition) field_definitions.get(index); if (sfd.placeholder_length > 0) { ReplaceInfo ri = new ReplaceInfo(); ri.sfd = sfd; ri.replaces = new ArrayList(); byte[] caption = sfd.caption.getBytes("ISO-8859-1"); int caption_index = findIndex(strings, caption); int start_index = skipStrings(strings, caption_index, caption); int next_index = findFirstNotPlaceholder(strings, start_index); for (int i = start_index; i < next_index; i++) { StringInfo si = (StringInfo) strings.get(i); ri.replaces.add(si); } replaces.add(ri); } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } // sort replaces Collections.sort(replaces, new Comparator() { public int compare(Object arg0, Object arg1) { ReplaceInfo ri0 = (ReplaceInfo) arg0; ReplaceInfo ri1 = (ReplaceInfo) arg1; int start0 = ((StringInfo) ri0.replaces.get(0)).string_start; int start1 = ((StringInfo) ri1.replaces.get(0)).string_start; return start0 - start1; } }); return replaces; } /** * Determines the Kennzeichnug in the content stream. * * @param pdf * The PDF. * @param begin * The start of the content stream. * @param end * The end of the content stream. * @param field_definitions * The field definitions. * @return Returns the List of StringInfo objects representing the KZ field. * @throws SettingNotFoundException * F.e. */ protected static List determineKZ(final byte[] pdf, int begin, int end, List field_definitions) throws SettingNotFoundException { try { List strings = Placeholder.parseStrings(pdf, begin, end); for (int index = 0; index < field_definitions.size(); index++) { SignatureFieldDefinition sfd = (SignatureFieldDefinition) field_definitions.get(index); if (sfd.field_name.equals(SignatureTypes.SIG_KZ)) { List kz_list = new ArrayList(); byte[] caption = sfd.caption.getBytes("ISO-8859-1"); int caption_index = findIndex(strings, caption); int start_index = skipStrings(strings, caption_index, caption); int end_index = -1; for (end_index = start_index; end_index < strings.size(); end_index++) { StringInfo si = (StringInfo) strings.get(end_index); if (startsWithCaption(si, field_definitions)) { break; } kz_list.add(si); } return kz_list; } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } throw new KZSettingNotFoundException("Field " + SignatureTypes.SIG_KZ + " not found."); } /** * Finds the index of the StringInfo within the StringInfo list that has the * given content (caption). * * @param strings * The list of StringInfos. * @param caption * The text to be matched to the strings. * @return Returns the index of the found string, or -1 if no string matched. */ protected static int findIndex(List strings, byte[] caption) { for (int i = 0; i < strings.size(); i++) { if (isCaption(strings, i, caption)) { return i; } } return -1; } protected static boolean isCaption(List strings, int index, byte[] caption) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (int i = index; i < strings.size(); i++) { StringInfo si = (StringInfo) strings.get(i); baos.write(si.copyStringBytes()); } byte[] str_data = baos.toByteArray(); byte[] unescaped = Placeholder.unescapePDFString(str_data); if (ByteArrayUtils.compareByteArrays(unescaped, 0, caption)) { return true; } else { return false; } } catch (IOException e) { e.printStackTrace(); return false; } } protected static int skipStrings(List strings, int index, byte[] caption) { int length = 0; for (int i = index; i < strings.size(); i++) { StringInfo si = (StringInfo) strings.get(i); length += si.string_length; if (length >= caption.length) { return i + 1; } } return -1; } /** * Tells, if the given StringInfo contains only placeholder characters. * * @param si * The StringInfo. * @param placeholder * The placeholder character. * @return Returns true, if the string contains only the given placeholder * characters, false otherwise. */ protected static boolean isPlaceholder(StringInfo si, byte placeholder) { byte[] string_bytes = si.copyStringBytes(); for (int i = 0; i < string_bytes.length; i++) { if (string_bytes[i] != placeholder) { return false; } } return true; } protected static boolean startsWithCaption(StringInfo si, List field_definitions) { try { for (int i = 0; i < field_definitions.size(); i++) { SignatureFieldDefinition sfd = (SignatureFieldDefinition) field_definitions.get(i); String caption = sfd.caption; String str = si.getString("ISO-8859-1"); if (caption.startsWith(str)) { return true; } } return false; } catch (UnsupportedEncodingException e) { e.printStackTrace(); return false; } } /** * Finds the first string after and at the given index not being a placeholder * string. * * @param strings * The list of StringInfos. * @param start * The index where to start the search. * @return Returns the index of the first not placeholder string, or * strings.size() if no more non placeholder strings could be found. */ protected static int findFirstNotPlaceholder(List strings, int start) { for (int i = start; i < strings.size(); i++) { StringInfo si = (StringInfo) strings.get(i); if (!isPlaceholder(si, LAYOUT_PLACEHOLDER)) { return i; } } return strings.size(); } /** * Restores the given String to its placeholder. * * @param pdf * The PDF. * @param si * The string. * @param placeholder * The placeholder the string should be filled with. */ public static void restorePlaceholder(final byte[] pdf, StringInfo si, final byte placeholder) { byte[] ph = new byte[si.string_length]; for (int i = 0; i < ph.length; i++) { ph[i] = placeholder; } System.arraycopy(ph, 0, pdf, si.string_start, ph.length); } /** * Reconstructs the replaces from the PDF and forms suitable value strings. * * @param pdf * The PDF. * @param brevs * The brevs. * @param sis * The StringInfo objects of the strings. * @return Returns the List of ReplaceInfo objects containing the restored * values. * @throws PDFDocumentException */ public static List reconstructReplaces(final byte[] pdf, byte[][] brevs, StringInfo[] sis, byte[][] encodings) throws PDFDocumentException { try { List replaces = new ArrayList(); ReplaceInfo cur_ri = null; for (int cur = 0; cur < brevs.length; cur++) { if (ByteArrayUtils.compareByteArrays(brevs[cur], 0, BREV_NIL)) { continue; } if (cur_ri == null || !ByteArrayUtils.compareByteArrays(cur_ri.brev, 0, brevs[cur])) { cur_ri = new ReplaceInfo(); cur_ri.replaces = new ArrayList(); cur_ri.brev = brevs[cur]; cur_ri.enc = encodings[cur]; replaces.add(cur_ri); } cur_ri.replaces.add(sis[cur]); } // restore value Strings Iterator rit = replaces.iterator(); while (rit.hasNext()) { ReplaceInfo ri = (ReplaceInfo) rit.next(); ri.value = Placeholder.reconstructStringFromPartition(pdf, ri.replaces, ri.enc); // System.out.println(new String(ri.brev, "US-ASCII") + ": " + // ri.value); } return replaces; } catch (IOException e) { throw new PDFDocumentException(310, e); } } /** * Reads an unsigned integer number. * * @param pdf * The PDF. * @param start_index * The start index of the number. * @param num_digits * The number of digits. * @return Returns the read number. */ public static int readNumber(final byte[] pdf, final int start_index, final int num_digits) { try { byte[] n_bytes = new byte[num_digits]; System.arraycopy(pdf, start_index, n_bytes, 0, num_digits); String n_string = new String(n_bytes, "US-ASCII"); int n = Integer.parseInt(n_string); return n; } catch (UnsupportedEncodingException e) { e.printStackTrace(); return -1; } } /** * Replaces a number by the new value. * * @param pdf * The PDF. * @param start_index * The start index of the number. * @param number * The new number. * @param num_digits * The number of digits. */ public static void replaceNumber(final byte[] pdf, final int start_index, final int number, final int num_digits) { try { if (number < 0) { throw new IllegalArgumentException("The given number " + number + " must not be negative."); } String number_string = Integer.toString(number); if (number_string.length() > num_digits) { throw new IllegalArgumentException("The given number " + number + " has more than " + num_digits + " digits."); } int leading_zeros = num_digits - number_string.length(); String zeros_string = ""; for (int i = 0; i < leading_zeros; i++) { zeros_string += "0"; } String total_string = zeros_string + number_string; byte[] total_bytes = total_string.getBytes("US-ASCII"); System.arraycopy(total_bytes, 0, pdf, start_index, num_digits); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } // TODO old code - remove // /** // * For debugging purposes. // * // * @param args // * @throws IOException // */ // public static void main(String[] args) throws IOException // { // File signed_doc = new File("C:/wprinz/temp.pdf"); // // PdfReader reader = new PdfReader(new FileInputStream(signed_doc)); // PdfDictionary egiz_dict = getEgizDictFromReader(reader); // if (egiz_dict == null) // { // System.out.println("NO Egiz Dict"); // return; // } // // String sig_text = extractSignatureTextOnly(egiz_dict); // System.out.println("Sig Text:"); // System.out.println(sig_text); // // int ods = getOriginalDocumentSizeFromEgizDict(egiz_dict); // System.out.println("Original Document Size = " + ods); // } }