/**
*
* If strict mode is deactivated, this does simply nothing. *
* * @param pdf * The pdf to be checked against strict mode. * @throws PDFDocumentException */ public static void applyStrictMode(byte[] pdf) throws PDFDocumentException { if (isStrictPdfChecking()) { if (!isPdf14(pdf)) { throw new PDFDocumentException(201, "StrictMode: The pdf version is not 1.4 or lower."); } } } /** * Verifies the given PDF document. * * @param pdf * The PDF document. * @param connector * The connector. * @return Returns the List of results. * @throws PresentableException * Forwarded exception. */ public static List verifyPdf(final byte[] pdf, final String connector) throws PresentableException { VerificationFilter vf = new VerificationFilter(); List signature_holders = vf.extractSignaturesFromPdf(pdf); if (signature_holders.isEmpty()) { throw new PDFDocumentException(206); } List results = verifySignatureHolders(signature_holders, connector); return results; } /** * Verifies the given text that is supposed to be extracted from a PDF * document using text extraction mechanisms. * * @param text * The text to be verified. * @param connector * The connecor. * @return Returns the List of results. * @throws PresentableException * Forwarded exception. */ public static List verifyText(final String text, final String connector) throws PresentableException { VerificationFilter vf = new VerificationFilter(); List signature_holders = vf.extractSignaturesFromPlainText(text); if (signature_holders.isEmpty()) { throw new PDFDocumentException(206); } List results = verifySignatureHolders(signature_holders, connector); return results; } /** * Extracts all signature blocks from the given raw text using textual mode. * * @param raw_text * The raw text. * @return Returns a List of all SignatureHolders extracted from the text. * @throws PDFDocumentException * F.e. * @throws SignatureException * F.e. * @throws SignatureTypesException * @throws NormalizeException */ public static List extractSignatureHoldersTextual(String raw_text, boolean old_style) throws PDFDocumentException, SignatureException, SignatureTypesException, NormalizeException { List signature_holders = new ArrayList(); String text = raw_text; for (;;) { SignatureHolder holder = extractSignatureHolderTextual(text, old_style); if (holder == null) { break; } { logger_.debug("Found holder: " + holder.getSignatureObject().getSignationType()); } signature_holders.add(0, holder); text = holder.getSignedText(); } return signature_holders; } /** * Extracts the last signature holder from the given text. * * @param raw_text * @param old_style * @return Returns the found singature holder, or null, if none could be * found. * @throws SignatureException * @throws SignatureTypesException * @throws NormalizeException */ public static SignatureHolder extractSignatureHolderTextual(String raw_text, boolean old_style) throws SignatureException, SignatureTypesException, NormalizeException { SignatureTypes sig_types = SignatureTypes.getInstance(); List signatureTypes_ = sig_types.getSignatureTypeDefinitions(); List found_blocks = new ArrayList(); for (int cur_type = 0; cur_type < signatureTypes_.size(); cur_type++) { SignatureTypeDefinition cur_std = (SignatureTypeDefinition) signatureTypes_.get(cur_type); List found_keys = findBlockInText(raw_text, cur_std, old_style); if (found_keys != null) { FoundBlock found_block = new FoundBlock(); found_block.found_keys = found_keys; found_block.end_index = raw_text.length(); found_block.std = cur_std; found_blocks.add(found_block); } } List last_most_blocks = sortOutEarlyBlocks(found_blocks); List minimum_blocks = sortOutLargeBlocks(last_most_blocks); if (minimum_blocks.size() > 1) { logger_.debug("There are still " + minimum_blocks.size() + " candidates:"); for (int i = 0; i < minimum_blocks.size(); i++) { FoundBlock fb = (FoundBlock) minimum_blocks.get(i); logger_.debug(" fb: " + fb.std.getType()); } logger_.debug("... checking for Semantic Equality."); } boolean semantic_equality = checkForSemanticEquality(minimum_blocks); if (minimum_blocks.size() > 1) { logger_.debug("... Semantic Equality = " + semantic_equality); } if (!semantic_equality) { throw new SignatureException(314); } if (!minimum_blocks.isEmpty()) { FoundBlock actual_block = (FoundBlock) minimum_blocks.get(0); String signed_text = raw_text.substring(0, actual_block.getFirstKey().start_index); SignatureObject signatureObject_ = new SignatureObject(); signatureObject_.setSigType(actual_block.std.getType()); signatureObject_.initByType(); int end_index = actual_block.end_index; for (int i = 0; i < actual_block.found_keys.size(); i++) { FoundKey cur_key = (FoundKey) actual_block.found_keys.get(i); int start_index = cur_key.getStartIndex() + cur_key.caption.length(); String value = raw_text.substring(start_index, end_index); signatureObject_.setSigValueCaption(cur_key.getKey(), value, cur_key.caption); end_index = cur_key.getStartIndex(); } // this normalization is required to get rid of possible trailing newlines. String normalized_text = normalizeText(signed_text); SignatureHolder holder = new TextualSignatureHolder(normalized_text, signatureObject_); return holder; } return null; } /** * Tries to find a block of the given type in the text. * * @param text * The text. * @param sig_type_def * The type of the block. * @param old_style * Tells, if the block is old style (SIG_KZ will be ignored), or if * it is a new block. * @return Returns a List of the found keys of the block, or null, if the * block could not be found. */ public static List findBlockInText(String text, SignatureTypeDefinition sig_type_def, boolean old_style) { Vector keys = sig_type_def.getRevertSortedKeys(); Vector captions = sig_type_def.getRevertSortedCaptions(); int last_index = text.length(); List found_keys = new ArrayList(); for (int key_idx = 0; key_idx < keys.size(); key_idx++) { String key = (String) keys.get(key_idx); if (old_style && key.equals(SignatureTypes.SIG_KZ)) { // If separating the old style way - skip The "Kennzeichnung" // key, because it wasn't present in old profiles. continue; } String caption = (String) captions.get(key_idx); //int found_index = text.lastIndexOf(caption); // we're searching for captions that start at the beginning of the line. int found_index = text.lastIndexOf("\n" + caption) + 1; // the +1 compensates the \n if (key.equals(SignatureTypes.SIG_ID)) { if (found_index < 0 || found_index >= last_index) { // not found, SIG_ID is not required continue; } FoundKey fk = new FoundKey(key, caption, found_index); found_keys.add(fk); } else { if (found_index < 0 || found_index >= last_index) { // one key is not found - the profile doesn't match. return null; } FoundKey fk = new FoundKey(key, caption, found_index); found_keys.add(fk); last_index = found_index; } } sortFoundKeysDescendingly(found_keys); boolean matched = checkThatOrderIsCorrectAndCorrectFoundKeys(found_keys, keys, old_style); // boolean found_required = checkFoundRequiredKeys(found_keys, old_style); if (matched) { return found_keys; } return null; } /** * Sorts the FoundKeys List descendingly according to the start indices of the * found keys (the first found key in the list will have the highest start * index, the second one the second highest and so forth). * * @param found_keys * The List of FoundKey objects to be sorted. */ public static void sortFoundKeysDescendingly(List found_keys) { // sort the found_keys according to their start pos reversely. Collections.sort(found_keys, new Comparator() { public int compare(Object arg0, Object arg1) { FoundKey fk0 = (FoundKey) arg0; FoundKey fk1 = (FoundKey) arg1; // sort reversely! return fk1.start_index - fk0.start_index; } }); } /** * Sorts the FoundKeys List ascendingly according to the start indices of the * found keys (the first found key in the list will have the lowest start * index, the second one the second lowest and so forth). * * @param found_keys * The List of FoundKey objects to be sorted. */ public static void sortFoundKeysAscendingly(List found_keys) { // sort the found_keys according to their start pos. Collections.sort(found_keys, new Comparator() { public int compare(Object arg0, Object arg1) { FoundKey fk0 = (FoundKey) arg0; FoundKey fk1 = (FoundKey) arg1; return fk0.start_index - fk1.start_index; } }); } /** * Checks that the found keys are in correct order regarding SIG_ID as * optional key. * ** If the SIG_ID key is misplaced, it will be removed from the found keys * list. *
* * @param found_keys * The found keys ordered descendingly to their start position * @param profile_keys * The profile keys. * @param old_style * Tells, if SIG_KZ should be ignored, or not. * @return Returns true, if the keys are correct. */ public static boolean checkThatOrderIsCorrectAndCorrectFoundKeys( List found_keys, List profile_keys, boolean old_style) { int found_index = 0; for (int profile_index = 0; profile_index < profile_keys.size(); profile_index++) { String key = (String) profile_keys.get(profile_index); if (old_style && key.equals(SignatureTypes.SIG_KZ)) { continue; } FoundKey found_key = (FoundKey) found_keys.get(found_index); boolean match = key.equals(found_key.getKey()); if (match) { found_index++; continue; } if (key.equals(SignatureTypes.SIG_ID)) { continue; } // doesn't match return false; } // remove all fields above the found_index - they are not correctly matched // indices (should be only the ID int size = found_keys.size(); for (int i = found_index; i < size; i++) { // this removes all (size - found_index) objects above found_index found_keys.remove(found_index); } return true; } /** * Sorts out early blocks and leaves only those at the bottom of the text. * * @param found_blocks * The found blocks. * @return Returns a list of the last blocks. */ public static List sortOutEarlyBlocks(List found_blocks) { int last_most_index = Integer.MIN_VALUE; List last_most_blocks = new ArrayList(); for (int block_index = 0; block_index < found_blocks.size(); block_index++) { FoundBlock block = (FoundBlock) found_blocks.get(block_index); int this_last_index = block.getLastKey().start_index; if (this_last_index < last_most_index) { // this block cannot be the last most block. continue; } if (this_last_index == last_most_index) { last_most_blocks.add(block); continue; } if (this_last_index > last_most_index) { last_most_blocks = new ArrayList(); last_most_blocks.add(block); last_most_index = this_last_index; } } return last_most_blocks; } /** * Sorts out large blocks. * * @param found_blocks * The found blocks. * @return Returns a list of the smallest blocks. */ public static List sortOutLargeBlocks(List found_blocks) { int last_min_size = Integer.MAX_VALUE; List min_size_blocks = new ArrayList(); for (int block_index = 0; block_index < found_blocks.size(); block_index++) { FoundBlock block = (FoundBlock) found_blocks.get(block_index); int size = block.getSize(); if (size > last_min_size) { // this block is larger continue; } if (size == last_min_size) { min_size_blocks.add(block); continue; } if (size < last_min_size) { min_size_blocks = new ArrayList(); min_size_blocks.add(block); last_min_size = size; } } return min_size_blocks; } /** * Checks the list of blocks for semantic equality. * * @param found_blocks * The list of found blocks. * @return Returns true if all blocks are semantically equal. */ public static boolean checkForSemanticEquality(List found_blocks) { if (found_blocks.size() <= 1) { return true; } for (int block_index = 0; block_index < found_blocks.size() - 1; block_index++) { FoundBlock first_block = (FoundBlock) found_blocks.get(block_index); FoundBlock second_block = (FoundBlock) found_blocks.get(block_index + 1); if (!first_block.isSemanticallyEqual(second_block)) { return false; } } return true; } public static int getIndexOfFoundKey(List found_keys, String key) { for (int i = 0; i < found_keys.size(); i++) { FoundKey fk = (FoundKey) found_keys.get(i); if (fk.getKey().equals(key)) { return i; } } return -1; } public static boolean containsFoundKey(List found_keys, String key) { return getIndexOfFoundKey(found_keys, key) >= 0; } /** * Checks the found keys for the required keys regarding the old style. * * @param found_keys * The found keys. * @param old_style * Flag that tells, if KZ is not required. * @return Returns true, if all required keys were found. */ public static boolean checkFoundRequiredKeys(List found_keys, boolean old_style) { if (!containsFoundKey(found_keys, SignatureTypes.SIG_DATE)) { return false; } if (!containsFoundKey(found_keys, SignatureTypes.SIG_ISSUER)) { return false; } if (!containsFoundKey(found_keys, SignatureTypes.SIG_NUMBER)) { return false; } if (!containsFoundKey(found_keys, SignatureTypes.SIG_VALUE)) { return false; } if (!old_style && !containsFoundKey(found_keys, SignatureTypes.SIG_KZ)) { return false; } return true; } /** * Verifies the List of SignatureHolders using the given connector. * * @param signature_holders * The List of SignatureHolder objects to be verified. * @param connector * The connector. * @return Returns the List of SignatureResponse objects. * @throws PDFDocumentException * F.e. * @throws NormalizeException * F.e. * @throws SignatureException * F.e. */ public static List verifySignatureHolders(List signature_holders, String connector) throws PDFDocumentException, NormalizeException, SignatureException { List results = new ArrayList(); for (int i = 0; i < signature_holders.size(); i++) { SignatureHolder holder = (SignatureHolder) signature_holders.get(i); // logger_.debug(); // logger_.debug(); // logger_.debug("Verifying Holder " + i + "..."); // logger_.debug("holder[" + i + "].signed_text = " + // holder.signed_text); // logger_.debug("holder[" + i + "].sig_obj = "); // logger_.debug("holder[" + i + "].type = " + // (holder.signature_object.isTextual() ? "textual" : "binary")); // logger_.debug(holder.signature_object.toString()); SignatureResponse result = verify(holder, connector); results.add(result); // logger_.debug(); // logger_.debug("check[" + i + "].cert = " + // result.getCertificateCheckInfo()); // logger_.debug("check[" + i + "].sig = " + // result.getSignatureCheckInfo().trim()); // logger_.debug("check[" + i + "].manifest = " + // result.getSignatureManifestCheckInfo()); // logger_.debug(); } return results; } /** * Verifies a SignatureHolder using the given connector. * * @param signature_holder * The SignatureHolder to be verified. * @param connector * The connector. * @return Returns the SignatureResponse object. * @throws NormalizeException * F.e. * @throws PDFDocumentException * F.e. * @throws SignatureException * F.e. */ public static SignatureResponse verify(SignatureHolder signature_holder, String connector) throws NormalizeException, PDFDocumentException, SignatureException { String text_to_be_verified = signature_holder.getSignedText(); SignatureObject so_to_be_verified = signature_holder.getSignatureObject(); if (text_to_be_verified == null) { throw new SignatureException(311, "Document can not be verified because the text to be verified is either null."); } if (text_to_be_verified.length() <= 0) { throw new SignatureException(311, "Document can not be verified because the length of the text to be verified is 0. (length = " + text_to_be_verified.length() + ")"); } if (so_to_be_verified == null) { throw new SignatureException(312, "Document can not be verified because no signature object are set."); } try { Connector connector_impl = ConnectorFactory.createConnector(connector); return connector_impl.doVerify(text_to_be_verified, so_to_be_verified); } catch (ConnectorFactoryException e) { throw new SignatureException(310, e); } } /** * Signs the given text with the provided connector using the given signature * type. * * @param text_to_sign * The text String to be signed. * @param signature_type * The type of the signature. * @param connector * The connector. * @param user_name * The user name. * @param user_password * The user password. * @return Returns the corresponding SignatureObject. * @throws SignatureException * F.e. * @throws PDFDocumentException * F.e. */ public static SignatureObject sign(final String text_to_sign, final String signature_type, final String connector, final String user_name, final String user_password) throws SignatureException, PDFDocumentException { logger_.info("User signed a document: " + user_name); if (text_to_sign == null) { throw new SignatureException(301, "Signature can not be produced. Text is null."); } if (text_to_sign.length() <= 0) { throw new SignatureException(301, "Signature can not be produced. Text is empty. (length = " + text_to_sign.length() + ")"); } try { Connector connector_impl = ConnectorFactory.createConnector(connector); SignatureObject signed_signature_object = connector_impl.doSign(signature_type, user_name, text_to_sign); return signed_signature_object; } catch (ConnectorFactoryException e) { throw new SignatureException(300, e); } } /** * Helper method that creates a SignatureObject and initializes it with the * given type. * * @param signature_type * The type. * @return Returns the created SignatureObject. * @throws SignatureException * f.e. * @throws SignatureTypesException * f.e. */ public static SignatureObject createSignatureObjectFromType( final String signature_type) throws SignatureException, SignatureTypesException { SignatureObject sig_obj = new SignatureObject(); sig_obj.setSigType(signature_type); sig_obj.initByType(); return sig_obj; } /** * Signs the document using the given algorithm. * * @param algorithm * The Signator algorithm to be used. * @param pdf * The PDF. * @param signature_type * The signature type. * @param connector * The connector. * @param user_name * The user name. * @param user_password * The password. * @param pos * The absolute position. If null, the position is either taken from * the profile or computed automatically. * @return Returns the SignResult. * @throws PresentableException * F.e. */ public static SignResult sign(PdfASID algorithm, final byte[] pdf, final String signature_type, final String connector, final String user_name, final String user_password, TablePos pos) throws PresentableException { Signator signator = SignatorFactory.createSignator(algorithm); IncrementalUpdateInformation iui = signator.prepareSign(pdf, signature_type, pos, ConnectorFactory.needsSIG_ID(connector)); iui.signed_signature_object = sign(iui.document_text, signature_type, connector, user_name, user_password); SignResult sign_result = signator.finishSign(iui); return sign_result; } /** * Extracts and normalizes the text from the pdf. * * @param pdf * The PDF document. * @return Returns the text String. * @throws PresentableException * F.e. */ public static String extractNormalizedTextTextual(final byte[] pdf) throws PresentableException { // ByteArrayInputStream bais = new ByteArrayInputStream(pdf); // String raw_document_text = TextualSignature.extractTextTextual(bais); // // String document_text = normalizeText(raw_document_text); return extractNormalizedTextTextual(pdf, pdf.length); } /** * Extracts and normalizes the text from the pdf. * * @param pdf * The PDF document. * @param length * The length of the PDF document. The decument is considered to be * that long even if the byte array is longer. * @return Returns the text String. * @throws PresentableException * F.e. */ public static String extractNormalizedTextTextual(final byte[] pdf, final int length) throws PresentableException { ByteArrayInputStream bais = new ByteArrayInputStream(pdf, 0, length); String raw_document_text = TextualSignature.extractTextTextual(bais); String document_text = normalizeText(raw_document_text); return document_text; } /** * Normalizes the given text. * * @param text * The text to be normalized. * @return Returns the normalized text. * @throws NormalizeException * F.e. */ public static String normalizeText(final String text) throws NormalizeException { Normalizer normalizer = new Normalizer(); String normalized_text = normalizer.normalize(text); return normalized_text; } /** * Creates the iText PDFPTable from a given SignatureObject. * * @param signature_object * The SignatureObject. * @return Returns the created PDFPTable. * @throws PDFDocumentException * F.e. * @throws SignatureException * F.e. */ public static PdfPTable createPdfPTableFromSignatureObject( final SignatureObject signature_object) throws PDFDocumentException, SignatureException { PDFSignatureCreation creation = new PDFSignatureCreation(signature_object); PDFSignatureObject pdf_sig_obj = creation.getPDFSignatureObject(); PdfPTable pdf_table = (PdfPTable) pdf_sig_obj.getSignatureObject(); return pdf_table; } /** * Sets the width of the table according to the layout of the document and * calculates the y position where the PDFPTable should be placed. * * @param pdf * The PDF document. * @param pdf_table * The PDFPTable to be placed. * @return Returns the position where the PDFPTable should be placed. * @throws PDFDocumentException * F.e. */ public static TablePos adjustTableAndCalculatePosition(final byte[] pdf, PdfPTable pdf_table) throws PDFDocumentException { TablePos pos = new TablePos(); PdfReader reader = readInPdfDocument(pdf); Rectangle psize = reader.getPageSizeWithRotation(reader.getNumberOfPages()); float page_width = psize.width(); float page_height = psize.height(); pos.width = page_width - SIGNATURE_BORDER; pdf_table.setTotalWidth(pos.width); pdf_table.setLockedWidth(true); pos.pos_x = SIGNATURE_BORDER / 2; float table_height = pdf_table.getTotalHeight(); float page_length = PDFUtilities.calculateLastPageLength(pdf); pos.pos_y = page_height - page_length - SIGNATURE_MARGIN; pos.page = reader.getNumberOfPages(); if (pos.pos_y <= table_height) { pos.page = -1; // negative means to add a new page pos.pos_y = page_height - SIGNATURE_BORDER / 2; } return pos; } /** * Creates an iText Reader that parses the document. ** This is a convenience function for wrapping the Reader's exceptions into * PDFDocumentException. *
* * @param pdf * The PDF document. * @return Returns the created PdfReader. * @throws PDFDocumentException * F.e. */ public static PdfReader readInPdfDocument(final byte[] pdf) throws PDFDocumentException { try { return new PdfReader(pdf); } catch (IOException e) { throw new PDFDocumentException(201, e); } } /** * Parses the TablePos object from a given String with the appropriate format. * * @param pos_string * The pos string. e.g. 1;40.0;600.0;300.0 * @return Returns the parsed TablePos object. * @throws PDFDocumentException * Thrown, if the String doesn't have the proper format. */ public static TablePos parsePositionFromPosString(String pos_string) throws PDFDocumentException { String[] strs = pos_string.split(";"); if (strs.length != 4) { throw new PDFDocumentException(224, "Pos string (=" + pos_string + ") is invalid."); } try { TablePos pos = new TablePos(); pos.page = Integer.parseInt(strs[0]); pos.pos_x = Float.parseFloat(strs[1]); pos.pos_y = Float.parseFloat(strs[2]); pos.width = Float.parseFloat(strs[3]); if (pos.page < 1 && pos.page != -1) { throw new PDFDocumentException(225, "pos.page (=" + pos.page + ") must not be lower than -1 and must not be 0."); } if (pos.width <= 0.0f) { throw new PDFDocumentException(226, "pos.width (=" + pos.width + ") must not be lower or equal 0."); } return pos; } catch (NumberFormatException e) { throw new PDFDocumentException(224, "Pos string (=" + pos_string + ") cannot be parsed."); } } }