/******************************************************************************* * Copyright 2014 by E-Government Innovation Center EGIZ, 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. ******************************************************************************/ package at.gv.egiz.pdfas.lib.impl; import java.awt.Image; import java.io.File; import java.io.IOException; import java.util.Calendar; import java.util.Iterator; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import at.gv.egiz.pdfas.common.exceptions.ErrorConstants; import at.gv.egiz.pdfas.common.exceptions.PDFASError; import at.gv.egiz.pdfas.common.exceptions.PdfAsException; import at.gv.egiz.pdfas.common.exceptions.PdfAsSettingsException; import at.gv.egiz.pdfas.common.exceptions.SLPdfAsException; import at.gv.egiz.pdfas.common.settings.ISettings; import at.gv.egiz.pdfas.common.utils.PDFUtils; import at.gv.egiz.pdfas.common.utils.StreamUtils; import at.gv.egiz.pdfas.lib.api.Configuration; import at.gv.egiz.pdfas.lib.api.IConfigurationConstants; import at.gv.egiz.pdfas.lib.api.PdfAs; import at.gv.egiz.pdfas.lib.api.StatusRequest; import at.gv.egiz.pdfas.lib.api.preprocessor.PreProcessor; import at.gv.egiz.pdfas.lib.api.sign.SignParameter; import at.gv.egiz.pdfas.lib.api.sign.SignResult; import at.gv.egiz.pdfas.lib.api.verify.VerifyParameter; import at.gv.egiz.pdfas.lib.api.verify.VerifyResult; import at.gv.egiz.pdfas.lib.backend.PDFASBackend; import at.gv.egiz.pdfas.lib.impl.backend.BackendLoader; import at.gv.egiz.pdfas.lib.impl.configuration.ConfigurationImpl; import at.gv.egiz.pdfas.lib.impl.preprocessor.PreProcessorLoader; import at.gv.egiz.pdfas.lib.impl.signing.IPdfSigner; import at.gv.egiz.pdfas.lib.impl.signing.PDFASSignatureExtractor; import at.gv.egiz.pdfas.lib.impl.status.OperationStatus; import at.gv.egiz.pdfas.lib.impl.status.PDFObject; import at.gv.egiz.pdfas.lib.impl.status.RequestedSignature; import at.gv.egiz.pdfas.lib.settings.Settings; import at.gv.egiz.pdfas.lib.util.SignatureUtils; import at.gv.egiz.sl.util.BKUHeader; import iaik.x509.X509Certificate; public class PdfAsImpl implements PdfAs, IConfigurationConstants, ErrorConstants { private static final Logger logger = LoggerFactory .getLogger(PdfAsImpl.class); private final ISettings settings; public PdfAsImpl(File cfgFile) { logger.debug("Initializing PDF-AS with config: " + cfgFile.getPath()); this.settings = new Settings(cfgFile); } public PdfAsImpl(ISettings cfgObject) { logger.info("Initializing PDF-AS with config: " + cfgObject.getClass().getName()); this.settings = cfgObject; } private void verifySignParameter(SignParameter parameter) throws PDFASError { // Status initialization if (!(parameter.getConfiguration() instanceof ISettings)) { throw new PDFASError(ERROR_SET_INVALID_SETTINGS_OBJ); } final ISettings settings = (ISettings) parameter.getConfiguration(); final String signatureProfile = parameter.getSignatureProfileId(); if (signatureProfile != null) { if (!settings.hasPrefix("sig_obj." + signatureProfile)) { throw new PDFASError(ERROR_SIG_INVALID_PROFILE, PDFASError.buildInfoString(ERROR_SIG_INVALID_PROFILE, signatureProfile)); } } if (parameter.getDataSource() == null) { throw new PDFASError(ERROR_NO_INPUT); } } private void verifyVerifyParameter(VerifyParameter parameter) throws PDFASError { // Status initialization if (!(parameter.getConfiguration() instanceof ISettings)) { throw new PDFASError(ERROR_SET_INVALID_SETTINGS_OBJ); } if (parameter.getDataSource() == null) { throw new PDFASError(ERROR_NO_INPUT); } } @Override public SignResult sign(SignParameter parameter) throws PDFASError { logger.trace("sign started"); verifySignParameter(parameter); OperationStatus status = null; try { // Status initialization if (!(parameter.getConfiguration() instanceof ISettings)) { throw new PdfAsSettingsException("Invalid settings object!"); } // execute pre Processors signPreProcessing(parameter); // allocated Backend final PDFASBackend backend = BackendLoader.getPDFASBackend(parameter.getConfiguration()); if (backend == null) { throw new PDFASError(ERROR_NO_BACKEND); } final ISettings settings = (ISettings) parameter.getConfiguration(); status = new OperationStatus(settings, parameter, backend); final IPdfSigner signer = backend.getPdfSigner(); final PDFObject pdfObject = signer.buildPDFObject(status); status.setPdfObject(pdfObject); // set Original PDF Document Data status.getPdfObject() .setOriginalDocument(parameter.getDataSource()); // Check PDF Permissions signer.checkPDFPermissions(status.getPdfObject()); // PlaceholderConfiguration placeholderConfiguration = status // .getPlaceholderConfiguration(); final RequestedSignature requestedSignature = new RequestedSignature( status); status.setRequestedSignature(requestedSignature); try { requestedSignature.setCertificate(status.getSignParamter().getPlainSigner().getCertificate(parameter)); } finally { if (parameter instanceof BKUHeaderHolder) { final BKUHeaderHolder holder = (BKUHeaderHolder) parameter; final Iterator bkuHeaderIt = holder.getProcessInfo() .iterator(); while (bkuHeaderIt.hasNext()) { final BKUHeader header = bkuHeaderIt.next(); if ("Server".equalsIgnoreCase(header.getName())) { requestedSignature .getStatus() .getMetaInformations() .put(ErrorConstants.STATUS_INFO_SIGDEVICEVERSION, header.getValue()); } else if (ErrorConstants.STATUS_INFO_SIGDEVICE.equalsIgnoreCase(header.getName())) { requestedSignature .getStatus() .getMetaInformations() .put(ErrorConstants.STATUS_INFO_SIGDEVICE, header.getValue()); } } } } // Only use this profileID because validation was done in // RequestedSignature final String signatureProfileID = requestedSignature .getSignatureProfileID(); logger.info("Selected signature Profile: " + signatureProfileID); // SignatureProfileConfiguration signatureProfileConfiguration = // status // .getSignatureProfileConfiguration(signatureProfileID); // this.stampPdf(status); // Create signature try { signer.signPDF(status.getPdfObject(), requestedSignature, signer .buildSignaturInterface(status.getSignParamter() .getPlainSigner(), parameter, requestedSignature)); } finally { if (parameter instanceof BKUHeaderHolder) { final BKUHeaderHolder holder = (BKUHeaderHolder) parameter; final Iterator bkuHeaderIt = holder.getProcessInfo() .iterator(); while (bkuHeaderIt.hasNext()) { final BKUHeader header = bkuHeaderIt.next(); if ("Server".equalsIgnoreCase(header.getName())) { requestedSignature .getStatus() .getMetaInformations() .put(ErrorConstants.STATUS_INFO_SIGDEVICEVERSION, header.getValue()); } else if (ErrorConstants.STATUS_INFO_SIGDEVICE.equalsIgnoreCase(header.getName())) { requestedSignature .getStatus() .getMetaInformations() .put(ErrorConstants.STATUS_INFO_SIGDEVICE, header.getValue()); } } } } // ================================================================ // Create SignResult final SignResult result = createSignResult(status); return result; } catch (final SLPdfAsException e) { if (e.isCriticalError()) { logger.warn("Failed to create signature [" + e.getMessage() + "]", e); } else { logger.info("Failed to create signature [" + e.getMessage() + "]", e); } throw ErrorExtractor.searchPdfAsError(e, status); } catch (final Throwable e) { logger.warn("Failed to create signature [" + e.getMessage() + "]", e); throw ErrorExtractor.searchPdfAsError(e, status); } finally { if (status != null) { status.clear(); } logger.trace("sign done"); } } @Override public List verify(VerifyParameter parameter) throws PDFASError { verifyVerifyParameter(parameter); // execute pre Processors verifyPreProcessing(parameter); // allocated Backend final PDFASBackend backend = BackendLoader.getPDFASBackend(parameter.getConfiguration()); if (backend == null) { throw new PDFASError(ERROR_NO_BACKEND); } try { return backend.getVerifier().verify(parameter); } catch (final Throwable e) { throw ErrorExtractor.searchPdfAsError(e, null); } } @Override public Configuration getConfiguration() { return new ConfigurationImpl(this.settings); } @Override public StatusRequest startSign(SignParameter parameter) throws PDFASError { verifySignParameter(parameter); final StatusRequestImpl request = new StatusRequestImpl(); OperationStatus status = null; try { // Status initialization if (!(parameter.getConfiguration() instanceof ISettings)) { throw new PdfAsSettingsException("Invalid settings object!"); } // execute pre Processors signPreProcessing(parameter); // allocated Backend final PDFASBackend backend = BackendLoader.getPDFASBackend(parameter.getConfiguration()); if (backend == null) { throw new PDFASError(ERROR_NO_BACKEND); } final ISettings settings = (ISettings) parameter.getConfiguration(); status = new OperationStatus(settings, parameter, backend); final IPdfSigner signer = backend.getPdfSigner(); status.setPdfObject(signer.buildPDFObject(status)); final RequestedSignature requestedSignature = new RequestedSignature( status); status.setRequestedSignature(requestedSignature); request.setStatus(status); request.setNeedCertificate(true); return request; } catch (final Throwable e) { logger.warn("startSign", e); throw ErrorExtractor.searchPdfAsError(e, status); } } @Override public StatusRequest process(StatusRequest statusRequest) throws PDFASError { if (!(statusRequest instanceof StatusRequestImpl)) { throw new PDFASError(ERROR_SIG_INVALID_STATUS); } final StatusRequestImpl request = (StatusRequestImpl) statusRequest; final OperationStatus status = request.getStatus(); if (request.needCertificate()) { try { status.getRequestedSignature().setCertificate( request.getCertificate()); // set Original PDF Document Data status.getPdfObject().setOriginalDocument( status.getSignParamter().getDataSource()); // STAMPER! // stampPdf(status); request.setNeedCertificate(false); status.setSigningDate(Calendar.getInstance()); // GET Signature DATA final String pdfFilter = status.getSignParamter().getPlainSigner() .getPDFFilter(); final String pdfSubFilter = status.getSignParamter().getPlainSigner() .getPDFSubFilter(); final IPdfSigner signer = status.getBackend().getPdfSigner(); final PDFASSignatureExtractor signatureDataExtractor = signer .buildBlindSignaturInterface(request.getCertificate(), pdfFilter, pdfSubFilter, status.getSigningDate()); signer.signPDF(status.getPdfObject(), status.getRequestedSignature(), signatureDataExtractor); final StringBuilder sb = new StringBuilder(); final int[] byteRange = PDFUtils .extractSignatureByteRange(signatureDataExtractor .getSignatureData()); for (final int element : byteRange) { sb.append(" " + element); } logger.debug("ByteRange: " + sb.toString()); request.setSignatureData(signatureDataExtractor .getSignatureData()); request.setByteRange(byteRange); request.setNeedSignature(true); } catch (final Throwable e) { logger.warn("process", e); throw ErrorExtractor.searchPdfAsError(e, status); } } else if (request.needSignature()) { request.setNeedSignature(false); // Inject signature byte[] into signedDocument final int offset = request.getSignatureDataByteRange()[1] + 1; final byte[] pdfSignature = status.getBackend().getPdfSigner() .rewritePlainSignature(request.getSignature()); // byte[] input = // PDFUtils.blackOutSignature(status.getPdfObject().getSignedDocument(), // request.getSignatureDataByteRange()); final VerifyResult verifyResult = SignatureUtils.verifySignature( request.getSignature(), request.getSignatureData()); final RequestedSignature requestedSignature = request.getStatus() .getRequestedSignature(); if (!StreamUtils.dataCompare(requestedSignature.getCertificate() .getFingerprintSHA(), ((X509Certificate) verifyResult .getSignerCertificate()).getFingerprintSHA())) { throw new PDFASError(ERROR_SIG_CERTIFICATE_MISSMATCH); } for (int i = 0; i < pdfSignature.length; i++) { status.getPdfObject().getSignedDocument()[offset + i] = pdfSignature[i]; } request.setIsReady(true); } else { throw new PDFASError(ERROR_SIG_INVALID_STATUS); } return request; } @Override public SignResult finishSign(StatusRequest statusRequest) throws PDFASError { if (!(statusRequest instanceof StatusRequestImpl)) { throw new PDFASError(ERROR_SIG_INVALID_STATUS); } final StatusRequestImpl request = (StatusRequestImpl) statusRequest; final OperationStatus status = request.getStatus(); if (!request.isReady()) { throw new PDFASError(ERROR_SIG_INVALID_STATUS); } try { return createSignResult(status); } catch (final IOException e) { // new PdfAsException("error.pdf.sig.06", e); throw ErrorExtractor.searchPdfAsError(e, status); } finally { if (status != null) { status.clear(); } } } private void listPreProcessors(List preProcessors) { logger.debug("--------------"); logger.debug("Listing PreProcessors:"); final Iterator preProcessorsIterator = preProcessors.iterator(); int idx = 0; while (preProcessorsIterator.hasNext()) { final PreProcessor preProcessor = preProcessorsIterator.next(); logger.debug("{}: {} [{}]", idx, preProcessor.getName(), preProcessor.getClass().getName()); idx++; } logger.debug("--------------"); } private void verifyPreProcessing(VerifyParameter parameter) throws PDFASError { final List preProcessors = PreProcessorLoader .getPreProcessors(parameter.getConfiguration()); listPreProcessors(preProcessors); logger.debug("executing PreProcessors for verifing:"); final Iterator preProcessorsIterator = preProcessors.iterator(); while (preProcessorsIterator.hasNext()) { final PreProcessor preProcessor = preProcessorsIterator.next(); logger.debug("executing: {} [{}]", preProcessor.getName(), preProcessor.getClass().getName()); preProcessor.verify(parameter); logger.debug("done executing: {} [{}]", preProcessor.getName(), preProcessor.getClass().getName()); } logger.debug("executing PreProcessors for verifing done"); } private void signPreProcessing(SignParameter parameter) throws PDFASError { final List preProcessors = PreProcessorLoader .getPreProcessors(parameter.getConfiguration()); listPreProcessors(preProcessors); logger.debug("executing PreProcessors for signing:"); final Iterator preProcessorsIterator = preProcessors.iterator(); while (preProcessorsIterator.hasNext()) { final PreProcessor preProcessor = preProcessorsIterator.next(); logger.debug("executing: {} [{}]", preProcessor.getName(), preProcessor.getClass().getName()); preProcessor.sign(parameter); logger.debug("done executing: {} [{}]", preProcessor.getName(), preProcessor.getClass().getName()); } logger.debug("executing PreProcessors for signing done"); } private SignResult createSignResult(OperationStatus status) throws IOException { // ================================================================ // Create SignResult final SignResultImpl result = new SignResultImpl(); status.getSignParamter().getSignatureResult().write(status.getPdfObject().getSignedDocument()); status.getSignParamter().getSignatureResult().flush(); result.setSignerCertificate(status.getRequestedSignature() .getCertificate()); result.setSignaturePosition(status.getRequestedSignature() .getSignaturePosition()); result.getProcessInformations().putAll(status.getMetaInformations()); return result; } @Override public Image generateVisibleSignaturePreview(SignParameter parameter, java.security.cert.X509Certificate cert, int resolution) throws PDFASError { OperationStatus status = null; try { // Status initialization if (!(parameter.getConfiguration() instanceof ISettings)) { throw new PDFASError(ERROR_SET_INVALID_SETTINGS_OBJ); } X509Certificate iaikCert; if (!(cert instanceof X509Certificate)) { iaikCert = new X509Certificate(cert.getEncoded()); } else { iaikCert = (X509Certificate) cert; } // allocated Backend final PDFASBackend backend = BackendLoader.getPDFASBackend(parameter.getConfiguration()); final ISettings settings = (ISettings) parameter.getConfiguration(); status = new OperationStatus(settings, parameter, backend); final IPdfSigner signer = backend.getPdfSigner(); status.setPdfObject(signer.buildPDFObject(status)); final RequestedSignature requestedSignature = new RequestedSignature( status); requestedSignature.setCertificate(iaikCert); if (!requestedSignature.isVisual()) { logger.warn("Profile is invisible so not block image is generated"); return null; } return signer.generateVisibleSignaturePreview(parameter, iaikCert, resolution, status, requestedSignature); } catch (final PdfAsException e) { logger.warn("PDF-AS Exception", e); throw ErrorExtractor.searchPdfAsError(e, status); } catch (final Throwable e) { logger.warn("Throwable Exception", e); throw ErrorExtractor.searchPdfAsError(e, status); } } }