/** * Copyright 2006 by Know-Center, Graz, Austria * PDF-AS has been contracted by the E-Government Innovation Center EGIZ, a * joint initiative of the Federal Chancellery Austria and Graz University of * Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. * * $Id: BinarySignator_1_0_0.java,v 1.1 2006/08/25 17:07:35 wprinz Exp $ */ package at.gv.egiz.pdfas.impl.signator.binary; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import at.gv.egiz.pdfas.api.timestamp.TimeStamper; import at.gv.egiz.pdfas.exceptions.ErrorCode; import at.gv.egiz.pdfas.exceptions.framework.SignatorException; import at.gv.egiz.pdfas.framework.input.DataSource; import at.gv.egiz.pdfas.framework.input.PdfDataSource; import at.gv.egiz.pdfas.framework.output.DataSink; import at.gv.egiz.pdfas.framework.signator.Signator; import at.gv.egiz.pdfas.framework.signator.SignatorInformation; import at.gv.egiz.pdfas.impl.input.CompoundPdfDataSourceImpl; import at.gv.egiz.pdfas.impl.signator.IncrementalUpdateHelper; import at.gv.egiz.pdfas.utils.OgnlUtil; import at.knowcenter.wag.egov.egiz.PdfAS; import at.knowcenter.wag.egov.egiz.PdfASID; import at.knowcenter.wag.egov.egiz.cfg.OverridePropertyHolder; import at.knowcenter.wag.egov.egiz.cfg.SettingsReader; import at.knowcenter.wag.egov.egiz.exceptions.NormalizeException; import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException; import at.knowcenter.wag.egov.egiz.exceptions.PresentableException; import at.knowcenter.wag.egov.egiz.exceptions.SignatureException; import at.knowcenter.wag.egov.egiz.framework.SignatorFactory; import at.knowcenter.wag.egov.egiz.pdf.BinarySignature; import at.knowcenter.wag.egov.egiz.pdf.IncrementalUpdateInformation; import at.knowcenter.wag.egov.egiz.pdf.PositioningInstruction; import at.knowcenter.wag.egov.egiz.pdf.ReplaceInfo; import at.knowcenter.wag.egov.egiz.pdf.StringInfo; import at.knowcenter.wag.egov.egiz.pdf.TablePos; import at.knowcenter.wag.egov.egiz.sig.SignatureData; import at.knowcenter.wag.egov.egiz.sig.SignatureDataImpl; import at.knowcenter.wag.egov.egiz.sig.SignatureFieldDefinition; import at.knowcenter.wag.egov.egiz.sig.SignatureObject; import at.knowcenter.wag.egov.egiz.sig.SignatureTypes; import at.knowcenter.wag.egov.egiz.sig.signatureobject.SignatureObjectHelper; import at.knowcenter.wag.egov.egiz.tools.Normalizer; import at.knowcenter.wag.exactparser.ByteArrayUtils; import com.lowagie.text.pdf.PdfPTable; /** * Signs the document binary. * *

* In prepareSign, an Incremental Update is created that contains the Signature block and the egiz dictionary. For * formatting the layout, variable values are filled with placeholders. After the layout has been fixed, all variable * fields (all holes in the byte ranges) are replaced with 0. This document is then base64 encoded and signed. *

*

* In finishSign, the variable fields (values, /Cert) are replaced with the values according to the encoding. *

* * @author wprinz */ public class BinarySignator_1_0_0 implements Signator { // 04.11.2010 changed by exthex - fillReplacesWithValue no longer removes // multiple newlines from values private static Log log = LogFactory.getLog(BinarySignator_1_0_0.class); /** * Settings key for baik enables signatures */ public static final String SIG_BAIK_ENABLED = "SIG_BAIK_ENABLED"; /** * The Pdf-AS ID of this Signator. */ public static final PdfASID MY_ID = new PdfASID(SignatorFactory.VENDOR, SignatorFactory.TYPE_BINARY, SignatorFactory.VERSION_1_0_0); private Normalizer normalizer; /** * @see at.knowcenter.wag.egov.egiz.framework.Signator#getMyId() */ public PdfASID getMyId() { return MY_ID; } /** * Default constructor. */ public BinarySignator_1_0_0() { try { this.normalizer = new Normalizer(); } catch (NormalizeException e) { String msg = "Normalizer can not be initialized"; throw new RuntimeException(msg, new SignatureException(400, msg, e)); } } /** * @see at.gv.egiz.pdfas.framework.signator.Signator#prepareSign(PdfDataSource, String, TablePos, TimeStamper) */ public SignatorInformation prepareSign(PdfDataSource pdfDataSource, String profile, TablePos pos, TimeStamper timeStamper) throws SignatorException { try { // dferbas: has to be true everytime boolean has_SIG_ID = true; String baikStr = SettingsReader.getInstance().getSetting("sig_obj." + profile + ".key." + SIG_BAIK_ENABLED, "default." + SIG_BAIK_ENABLED, "false"); boolean baikEnabled = "true".equalsIgnoreCase(baikStr); if (baikEnabled) { log.debug("BAIK enabled signature"); } SignatureObject signature_object = PdfAS.createSignatureObjectFromType(profile); signature_object.fillValues((char) BinarySignature.LAYOUT_PLACEHOLDER, has_SIG_ID, baikEnabled); signature_object.setKZ(getMyId()); PdfPTable pdf_table = PdfAS.createPdfPTableFromSignatureObject(signature_object); PositioningInstruction pi = PdfAS.determineTablePositioning(pos, profile, pdfDataSource, pdf_table); List all_field_definitions = signature_object.getSignatureTypeDefinition().getFieldDefinitions(); List variable_field_definitions = new ArrayList(); for (int i = 0; i < all_field_definitions.size(); i++) { SignatureFieldDefinition sfd = (SignatureFieldDefinition) all_field_definitions.get(i); if (sfd.placeholder_length > 0) { if (sfd.field_name.equals(SignatureTypes.SIG_ID) && has_SIG_ID == false) { continue; } if (sfd.field_name.equals(SignatureTypes.SIG_ALG) && baikEnabled == false) { continue; } variable_field_definitions.add(sfd); } } List all_invisible_field_definitions = signature_object.getSignatureTypeDefinition() .getInvisibleFieldDefinitions(); List invisible_field_definitions = new ArrayList(); boolean isKZInvisible = false; String invKZString = null; for (int i = 0; i < all_invisible_field_definitions.size(); i++) { SignatureFieldDefinition sfd = (SignatureFieldDefinition) all_invisible_field_definitions.get(i); if (sfd.field_name.equals(SignatureTypes.SIG_KZ)) { isKZInvisible = true; invKZString = signature_object.getKZ().toString(); continue; } if (sfd.field_name.equals(SignatureTypes.SIG_ID) && has_SIG_ID == false) { continue; } if (sfd.field_name.equals(SignatureTypes.SIG_ALG) && baikEnabled == false) { continue; } invisible_field_definitions.add(sfd); } // check if signature block is invisible, and if so and if also // signature block is positioned // on a new page, prevent pdf-as to do that, because why should make // a new page just for an invisible block // added by rpiazzi // disabled by dti: code fixes certain cases with invisible signatures but prevents usage of minimal // signature profiles; the actual invisible signature issue has fixed in method // adjustSignatureTableandCalculatePosition(...) in class at.knowcenter.wag.egov.egiz.PdfAS /* if (signature_object.getSignatureTypeDefinition().getInvisibleFieldDefinitions().size() == SignatureTypes.REQUIRED_SIG_KEYS.length) { if (pi.isMakeNewPage()) { int pageNumber = pi.getPage(); pi = new PositioningInstruction(false, pageNumber - 1, 0, 0); } } */ // end added (rpiazzi) IncrementalUpdateInformation iui = IncrementalUpdateHelper.writeIncrementalUpdate(pdfDataSource, pdf_table, profile, pi, variable_field_definitions, all_field_definitions, invisible_field_definitions, invKZString, timeStamper, null, signature_object); iui.invisible_field_definitions = invisible_field_definitions; iui.invisibleKZString = invKZString; String temp_string = iui.temp_ir_number + " " + iui.temp_ir_generation + " obj"; //$NON-NLS-1$//$NON-NLS-2$ byte[] temp_bytes = ArrayUtils.add(temp_string.getBytes("US-ASCII"), 0, (byte) 0x0A); int temp_start = ByteArrayUtils.lastIndexOf(iui.signed_pdf, temp_bytes); byte[] stream_bytes = new byte[] { '>', '>', 's', 't', 'r', 'e', 'a', 'm', 0x0A }; int stream_start = ByteArrayUtils.indexOf(iui.signed_pdf, temp_start, stream_bytes); iui.content_stream_start = stream_start + stream_bytes.length; // update the stream indices Iterator it = iui.replaces.iterator(); while (it.hasNext()) { ReplaceInfo ri = (ReplaceInfo) it.next(); Iterator sit = ri.replaces.iterator(); while (sit.hasNext()) { StringInfo si = (StringInfo) sit.next(); si.string_start += iui.content_stream_start; } } // update KZ list indices: if (!isKZInvisible) { it = iui.kz_list.iterator(); while (it.hasNext()) { StringInfo si = (StringInfo) it.next(); si.string_start += iui.content_stream_start; } } BinarySignature.markByteRanges(iui); // byte [] old_signed_pdf = iui.signed_pdf; iui.signed_pdf = BinarySignature.prepareDataToSign(iui.signed_pdf, iui.byte_ranges); BinarySignatorInformation bsi = compressIUI(iui); return bsi; } catch (UnsupportedEncodingException e) { throw new SignatorException(201, e); } catch (PDFDocumentException e) { throw new SignatorException(e.getErrorCode(), e); } catch (PresentableException e) { throw new SignatorException(201, e); } } /** * @see at.gv.egiz.pdfas.framework.signator.Signator#finishSign(at.gv.egiz.pdfas.framework.signator.SignatorInformation, * at.gv.egiz.pdfas.framework.output.DataSink) */ public void finishSign(SignatorInformation signatorInformation, DataSink dataSink) throws SignatorException { try { IncrementalUpdateInformation iui = uncompressIUI((BinarySignatorInformation) signatorInformation); // PERF: need to keep the whole pdf in mem for processing // PdfAS.prefixID(iui.signed_signature_object, PdfAS.BINARY_ID); fillReplacesWithValues(iui); // This is needed so that certificates are stored try { iui.signed_signature_object.kz = getMyId().toString(); SignatureObjectHelper.convertSignSignatureObjectToSignatureObject(iui.signed_signature_object, iui.signProfile); } catch (PresentableException e) { throw new SignatorException(e); } BinarySignature.replaceCertificate(iui); BinarySignature.replaceTimestamp(iui); BinarySignature.replacePlaceholders(iui); // dferbas: alternative sign attrib creation // PdfReader reader = new PdfReader(iui.signed_pdf); // // OutputStream os = dataSink.createOutputStream(PdfAS.PDF_MIME_TYPE); // // try { // PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true); // // BinarySignature.createAdobeSigAttrib(stamper, signatorInformation, // signatorInformation.getActualTablePos()); // // } catch (DocumentException e) { // log.error("pdf error", e); // throw new SignatorException(ErrorCode.CANNOT_WRITE_PDF, e); // } OutputStream os = dataSink.createOutputStream(PdfAS.PDF_MIME_TYPE); os.write(iui.signed_pdf); os.close(); } catch (PDFDocumentException e) { throw new SignatorException(e.getErrorCode(), e); } catch (IOException e) { throw new SignatorException(ErrorCode.DOCUMENT_CANNOT_BE_READ, e); } } /** * Reads the signature values from the signed signature object and fills the corresponding value in the Replaces * array. * * @param iui * The IncrementalUpdateInformation. */ protected void fillReplacesWithValues(final IncrementalUpdateInformation iui) { try { Iterator it = iui.replaces.iterator(); HashMap ognlCtx = new HashMap(); ognlCtx.put("iui", iui); ognlCtx.put("sso", iui.signed_signature_object); ognlCtx.put("issuer", iui.signed_signature_object.getIssuerDNMap()); ognlCtx.put("subject", iui.signed_signature_object.getSubjectDNMap()); OgnlUtil ognl = new OgnlUtil(ognlCtx); OverridePropertyHolder.setOgnlUtil(ognl); while (it.hasNext()) { ReplaceInfo ri = (ReplaceInfo) it.next(); String overrideVal = OverridePropertyHolder.getProperty(ri.sfd.field_name); if (overrideVal != null) { ri.sfd.value = overrideVal; ri.value = overrideVal; } else if (ognl.containsExpression(ri.sfd.value)) { // dferbas // evaluate expression String res = ognl.compileMessage(ri.sfd.value); ri.value = this.normalizer.normalize(res, true); // a-trust wrongly encodes since July 2011, therefore some // special characters (e.g. Umlaute) have // to replaced by their right character fixHandyAnsiEncoding(ri); } else { // If SUBJECT is not overridden and and also isn't an expression // check whether a set value for subject exists. // In this case take the value from the config file. // Added by rpiazzi to make a static signator possible without // having to override it any time if (ri.sfd.field_name.equals(SignatureTypes.SIG_SUBJECT) && StringUtils.isNotEmpty(ri.sfd.value)) { ri.value = ri.sfd.value; } else { ri.value = iui.signed_signature_object.retrieveStringValue(ri.sfd.field_name); } } } } finally { OverridePropertyHolder.removeOgnlUtil(); } } private void fixHandyAnsiEncoding(ReplaceInfo ri) { try { boolean fixApplied = false; Pattern p = Pattern.compile("&#([0-9]+);"); Matcher m = p.matcher(ri.value); int value = -1; while (m.find()) { value = Integer.parseInt(m.group(1)); if (value > 0 && value < 256) { log.debug("Replacing encoding '&#" + value + ";'."); byte[] buffer = new byte[1]; buffer[0] = (byte) value; ByteBuffer bb = ByteBuffer.wrap(buffer); CharBuffer cb = Charset.forName("Cp1252").decode(bb); String str = cb.toString(); ri.value = ri.value.replace("&#" + m.group(1) + ";", str); fixApplied = true; } } if (fixApplied) { log.info("ANSI encoding fix applied."); } } catch (Exception e) { log.info("Error trying to fix ANSI encoding.", e); } } /** * Forms the SignatureData to be used for signing. * * @param iui * The IncrementalUpdateInformation. * @return Returns the SignatureData to be used for signing. */ protected SignatureData formSignatureData(IncrementalUpdateInformation iui) { // String document_text = // BinarySignature.retrieveSignableTextFromData(iui.signed_pdf, // iui.signed_pdf.length); // signed_pdf.length); // // byte[] data; // try // { // data = document_text.getBytes("UTF-8"); //$NON-NLS-1$ // } // catch (UnsupportedEncodingException e) // { // throw new RuntimeException("Very strange: UTF-8 character encoding not // supported.", e); //$NON-NLS-1$ // } DataSource ds = new CompoundPdfDataSourceImpl(iui.original_document, iui.sign_iui_block); SignatureData signature_data = new SignatureDataImpl(ds, PdfAS.PDF_MIME_TYPE); return signature_data; } protected BinarySignatorInformation compressIUI(IncrementalUpdateInformation iui) { iui.sign_iui_block = new byte[iui.signed_pdf.length - iui.original_document.getLength()]; System.arraycopy(iui.signed_pdf, iui.original_document.getLength(), iui.sign_iui_block, 0, iui.sign_iui_block.length); iui.signature_data = formSignatureData(iui); // remove the signed pdf from memory iui.signed_pdf = null; BinarySignatorInformation bsi = new BinarySignatorInformation(); bsi.originalDocument = iui.original_document; bsi.incrementalUpdateBlock = iui.sign_iui_block; bsi.signatureData = iui.signature_data; bsi.replaces = iui.replaces; bsi.cert_start = iui.cert_start; bsi.cert_length = iui.cert_length; bsi.enc_start = iui.enc_start; bsi.enc_length = iui.enc_length; bsi.atp = iui.actualTablePos; bsi.signProfile = iui.signProfile; bsi.timestamp_length = iui.timestamp_length; bsi.timestamp_start = iui.timestamp_start; return bsi; } protected IncrementalUpdateInformation uncompressIUI(BinarySignatorInformation bsi) { IncrementalUpdateInformation iui = new IncrementalUpdateInformation(); iui.original_document = bsi.originalDocument; iui.sign_iui_block = bsi.incrementalUpdateBlock; iui.signature_data = bsi.signatureData; iui.replaces = bsi.replaces; iui.cert_start = bsi.cert_start; iui.cert_length = bsi.cert_length; iui.enc_start = bsi.enc_start; iui.enc_length = bsi.enc_length; iui.actualTablePos = bsi.atp; iui.signProfile = bsi.signProfile; iui.timestamp_length = bsi.timestamp_length; iui.timestamp_start = bsi.timestamp_start; iui.signed_signature_object = bsi.signSignatureObject; restoreSignedPdf(iui); return iui; } protected void restoreSignedPdf(IncrementalUpdateInformation iui) { iui.signed_pdf = new byte[iui.original_document.getLength() + iui.sign_iui_block.length]; try { InputStream is = iui.original_document.createInputStream(); is.read(iui.signed_pdf, 0, iui.original_document.getLength()); is.close(); } catch (IOException e) { throw new RuntimeException(e); } System.arraycopy(iui.sign_iui_block, 0, iui.signed_pdf, iui.original_document.getLength(), iui.sign_iui_block.length); } public String getEncoding() { // not used for binary signature return "utf-8"; } }