/* * Copyright 2003 Federal Chancellery Austria * MOA-SPSS has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, 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.egovernment.moa.spss.server.invoke; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.util.Date; import java.util.Iterator; import java.util.List; import at.gv.egovernment.moa.sig.tsl.utils.MiscUtil; import at.gv.egovernment.moa.spss.MOAApplicationException; import at.gv.egovernment.moa.spss.MOAException; import at.gv.egovernment.moa.spss.api.cmsverify.CMSContent; import at.gv.egovernment.moa.spss.api.cmsverify.CMSContentExcplicit; import at.gv.egovernment.moa.spss.api.cmsverify.CMSContentReference; import at.gv.egovernment.moa.spss.api.cmsverify.CMSDataObject; import at.gv.egovernment.moa.spss.api.cmsverify.VerifyCMSSignatureRequest; import at.gv.egovernment.moa.spss.api.cmsverify.VerifyCMSSignatureResponse; import at.gv.egovernment.moa.spss.api.common.ExtendedCertificateCheckResult; import at.gv.egovernment.moa.spss.server.config.ConfigurationProvider; import at.gv.egovernment.moa.spss.server.config.TrustProfile; import at.gv.egovernment.moa.spss.server.logging.IaikLog; import at.gv.egovernment.moa.spss.server.logging.TransactionId; import at.gv.egovernment.moa.spss.server.transaction.TransactionContext; import at.gv.egovernment.moa.spss.server.transaction.TransactionContextManager; import at.gv.egovernment.moa.spss.util.AdESResultUtils; import at.gv.egovernment.moa.spss.util.CertificateUtils; import at.gv.egovernment.moa.spss.util.QCSSCDResult; import at.gv.egovernment.moaspss.logging.Logger; import at.gv.egovernment.moaspss.logging.LoggingContext; import at.gv.egovernment.moaspss.logging.LoggingContextManager; import iaik.server.modules.IAIKException; import iaik.server.modules.IAIKRuntimeException; import iaik.server.modules.cmsverify.CMSSignatureVerificationModule; import iaik.server.modules.cmsverify.CMSSignatureVerificationModuleFactory; import iaik.server.modules.cmsverify.CMSSignatureVerificationProfile; import iaik.server.modules.cmsverify.CMSSignatureVerificationResult; import iaik.server.modules.cmsverify.ExtendedCMSSignatureVerificationResult; import iaik.server.modules.pdfverify.ExtendedPDFSignatureVerificationResult; import iaik.server.modules.pdfverify.PDFSignatureVerificationModule; import iaik.server.modules.pdfverify.PDFSignatureVerificationProfile; import iaik.server.modules.pdfverify.PDFSignatureVerificationResult; import iaik.x509.X509Certificate; /** * A class providing an interface to the * CMSSignatureVerificationModule. * * This class performs the invocation of the * iaik.server.modules.cmsverify.CMSSignatureVerificationModule * from a VerifyCMSSignatureRequest. The result of the invocation * is integrated into a VerifyCMSSignatureResponse returned. * * @author Patrick Peck * @version $Id$ */ public class CMSSignatureVerificationInvoker { /** The single instance of this class. */ private static CMSSignatureVerificationInvoker instance = null; /** * Return the only instance of this class. * * @return The only instance of this class. */ public static synchronized CMSSignatureVerificationInvoker getInstance() { if (instance == null) { instance = new CMSSignatureVerificationInvoker(); } return instance; } /** * Create a new CMSSignatureVerificationInvoker. * * Protected to disallow multiple instances. */ protected CMSSignatureVerificationInvoker() { } /** * Verify a CMS signature. * * @param request The VerifyCMSSignatureRequest containing the CMS * signature, as well as additional data needed for verification. * @return Element A VerifyCMSSignatureResponse containing the * answer to the VerifyCMSSignatureRequest. * @throws MOAException An error occurred while processing the request. */ public VerifyCMSSignatureResponse verifyCMSSignature(VerifyCMSSignatureRequest request) throws MOAException { final CMSSignatureVerificationProfileFactory profileFactory = new CMSSignatureVerificationProfileFactory( request); final VerifyCMSSignatureResponseBuilder responseBuilder = new VerifyCMSSignatureResponseBuilder(); final TransactionContext context = TransactionContextManager.getInstance().getTransactionContext(); final LoggingContext loggingCtx = LoggingContextManager.getInstance().getLoggingContext(); InputStream signature; InputStream signedContent = null; Date signingTime; List results; int[] signatories; InputStream input; final byte[] buf = new byte[2048]; // get the signature signature = request.getCMSSignature(); // get the actual trustprofile final TrustProfile trustProfile = context.getConfiguration().getTrustProfile(request.getTrustProfileId()); try { // get the signing time signingTime = request.getDateTime(); // build the profile if (request.isPDF()) { final PDFSignatureVerificationProfile profile = profileFactory.createPDFProfile(); Logger.debug("Sending PDFSignatureVerificationProfile to IAIK-MOA"); final PDFSignatureVerificationModule module = iaik.server.modules.pdfverify.PDFSignatureVerificationModuleFactory .getInstance(); module.setLog(new IaikLog(loggingCtx.getNodeID())); // Logger.info(" Available: " + signature.available()); module.init(signature, profile, new TransactionId(context.getTransactionID())); // input = module.getInputStream(); // while (input.read(buf) > 0); if (request.isExtended()) { Logger.info("Running extended validation"); results = module.verifyPAdESSignature(signingTime); } else { Logger.info("Running not extended validation"); results = module.verifySignature(signingTime); } // PAdES module had to be closed manually module.closeModule(); } else { // get the signed content signedContent = getSignedContent(request); final CMSSignatureVerificationProfile profile = profileFactory.createProfile(); Logger.debug("Sending CMSSignatureVerificationProfile to IAIK-MOA"); // verify the signature final CMSSignatureVerificationModule module = CMSSignatureVerificationModuleFactory.getInstance(); module.setLog(new IaikLog(loggingCtx.getNodeID())); module.init(signature, signedContent, profile, new TransactionId(context.getTransactionID())); input = module.getInputStream(); while (input.read(buf) > 0) { ; } if (request.isExtended()) { Logger.info("Running extended validation"); results = module.verifyCAdESSignature(signingTime); } else { Logger.info("Running not extended validation"); results = module.verifySignature(signingTime); } // results = module.verifySignature(signingTime); } } catch (final IAIKException e) { final MOAException moaException = IaikExceptionMapper.getInstance().map(e); throw moaException; } catch (final IAIKRuntimeException e) { final MOAException moaException = IaikExceptionMapper.getInstance().map(e); throw moaException; } catch (final IOException e) { throw new MOAApplicationException("2244", null, e); } catch (final MOAException e) { throw e; } finally { try { if (signedContent != null) { signedContent.close(); } if (signature != null) { signature.close(); } } catch (final Throwable t) { // Intentionally do nothing here } } final QCSSCDResult qcsscdresult = new QCSSCDResult(); // build the response: for each signatory add the result to the response signatories = request.getSignatories(); if (signatories == VerifyCMSSignatureRequest.ALL_SIGNATORIES) { Iterator resultIter; for (resultIter = results.iterator(); resultIter.hasNext();) { final Object resultObject = resultIter.next(); if (!request.isPDF()) { handleCMSResult(resultObject, responseBuilder, trustProfile); } else { handlePDFResult(resultObject, responseBuilder, trustProfile); } } } else { int i; for (i = 0; i < signatories.length; i++) { final int sigIndex = signatories[i] - 1; try { final Object resultObject = results.get(signatories[i] - 1); if (!request.isPDF()) { handleCMSResult(resultObject, responseBuilder, trustProfile); } else { handlePDFResult(resultObject, responseBuilder, trustProfile); } } catch (final IndexOutOfBoundsException e) { throw new MOAApplicationException("2249", new Object[] { new Integer(sigIndex) }); } } } return responseBuilder.getResponse(); } private void handleCMSResult(Object resultObject, VerifyCMSSignatureResponseBuilder responseBuilder, TrustProfile trustProfile) throws MOAException { QCSSCDResult qcsscdresult = new QCSSCDResult(); if (resultObject == null) { Logger.warn("Result Object is null!"); return; } CMSSignatureVerificationResult cmsResult = null; List adesResults = null; boolean extendedVerification = false; ExtendedCertificateCheckResult extCheckResult = null; if (resultObject instanceof ExtendedCMSSignatureVerificationResult) { Logger.info("Got ExtendedCMSSignatureVerificationResult"); extendedVerification = true; final ExtendedCMSSignatureVerificationResult result = (ExtendedCMSSignatureVerificationResult) resultObject; cmsResult = result.getCMSSignatureVerificationResult(); adesResults = AdESResultUtils.getAdESResult(result.getFormVerificationResult()); if (Logger.isDebugEnabled() && adesResults != null) { final Iterator adesIterator = adesResults.iterator(); while (adesIterator.hasNext()) { Logger.debug("ADES Formresults: " + adesIterator.next().toString()); } } try { // Logger.info("Extended Validation Report: " + result.getName()); Logger.debug("Extended Validation Code: " + result.getResultCode().toString()); Logger.debug("Extended Validation Info: " + result.getInfo()); extCheckResult = AdESResultUtils.getExtendedResult(result.getResultCode()); } catch (final NullPointerException e) { Logger.info("No extendend validation result available."); } } else { Logger.debug("Got CMSSignatureVerificationResult"); cmsResult = (CMSSignatureVerificationResult) resultObject; } String issuerCountryCode = null; // QC/SSCD check if (cmsResult.getCertificateValidationResult() != null) { final List list = cmsResult.getCertificateValidationResult().getCertificateChain(); if (list != null) { final X509Certificate[] chain = new X509Certificate[list.size()]; final Iterator it = list.iterator(); int i = 0; while (it.hasNext()) { chain[i] = (X509Certificate) it.next(); i++; } qcsscdresult = CertificateUtils.checkQCSSCD(chain, cmsResult.getSigningTime(), trustProfile .isTSLEnabled(), ConfigurationProvider.getInstance()); // get signer certificate issuer country code issuerCountryCode = CertificateUtils.getIssuerCountry((X509Certificate) list.get(0)); } } responseBuilder.addResult(cmsResult, trustProfile, qcsscdresult.isQC(), qcsscdresult.isQCSourceTSL(), qcsscdresult.isSSCD(), qcsscdresult.isSSCDSourceTSL(), issuerCountryCode, adesResults, extCheckResult, qcsscdresult.getTslInfos(), extendedVerification); } private void handlePDFResult(Object resultObject, VerifyCMSSignatureResponseBuilder responseBuilder, TrustProfile trustProfile) throws MOAException { QCSSCDResult qcsscdresult = new QCSSCDResult(); if (resultObject == null) { Logger.warn("Result Object is null!"); return; } PDFSignatureVerificationResult cmsResult = null; List adesResults = null; boolean extendedVerification = false; final Boolean coversFullDoc = null; final int[] sigByteRange = null; ExtendedCertificateCheckResult extCheckResult = null; if (resultObject instanceof ExtendedPDFSignatureVerificationResult) { Logger.info("Got ExtendedPDFSignatureVerificationResult"); extendedVerification = true; final ExtendedPDFSignatureVerificationResult result = (ExtendedPDFSignatureVerificationResult) resultObject; cmsResult = result.getPDFSignatureVerificationResult(); adesResults = AdESResultUtils.getAdESResult(result.getFormVerificationResult()); if (Logger.isDebugEnabled() && adesResults != null) { final Iterator adesIterator = adesResults.iterator(); while (adesIterator.hasNext()) { Logger.debug("ADES Formresults: " + adesIterator.next().toString()); } } try { Logger.debug("Extended Validation Code: " + result.getResultCode().toString()); if (result.getDetailedExtendedReport() != null) { Logger.debug("Extended Validation Info: " + result.getDetailedExtendedReport().getMessage()); } else { Logger.debug("Extended Validation Info: " + result.getInfo()); } Logger.debug("Full extended Validation Infos: " + result.getInfo()); extCheckResult = AdESResultUtils.getExtendedResult(result.getResultCode()); } catch (final NullPointerException e) { Logger.info("No extendend validation result available."); } } else { Logger.debug("Got PDFSignatureVerificationResult"); cmsResult = (PDFSignatureVerificationResult) resultObject; } if (MiscUtil.isNotEmpty(cmsResult.getError())) { Logger.info("Signature validation stopped with an error: " + cmsResult.getError()); } String issuerCountryCode = null; // QC/SSCD check if (cmsResult.getCertificateValidationResult() != null) { final List list = cmsResult.getCertificateValidationResult().getCertificateChain(); if (list != null) { final X509Certificate[] chain = new X509Certificate[list.size()]; final Iterator it = list.iterator(); int i = 0; while (it.hasNext()) { chain[i] = (X509Certificate) it.next(); i++; } qcsscdresult = CertificateUtils.checkQCSSCD(chain, cmsResult.getSigningTime(), trustProfile .isTSLEnabled(), ConfigurationProvider.getInstance()); // get signer certificate issuer country code issuerCountryCode = CertificateUtils.getIssuerCountry((X509Certificate) list.get(0)); } } responseBuilder.addResult(cmsResult, trustProfile, qcsscdresult.isQC(), qcsscdresult.isQCSourceTSL(), qcsscdresult.isSSCD(), qcsscdresult.isSSCDSourceTSL(), issuerCountryCode, adesResults, extCheckResult, qcsscdresult.getTslInfos(), extendedVerification); } /** * Get the signed content contained either in the request itself or given as a * reference to external data. * * @param request The VerifyCMSSignatureRequest containing the * signed content (or the reference to the signed content). * @return InputStream A stream providing the signed content data, or * null if no signed content was provided with the request. * @throws MOAApplicationException An error occurred building the stream. */ private InputStream getSignedContent(VerifyCMSSignatureRequest request) throws MOAApplicationException { InputStream is = null; CMSDataObject dataObj; CMSContent content; // select the Content element dataObj = request.getDataObject(); if (dataObj == null) { return null; } content = dataObj.getContent(); // build the content data switch (content.getContentType()) { case CMSContent.EXPLICIT_CONTENT: is = ((CMSContentExcplicit) content).getBinaryContent(); is = excludeByteRange(is, request); return is; case CMSContent.REFERENCE_CONTENT: final String reference = ((CMSContentReference) content).getReference(); if (!"".equals(reference)) { final ExternalURIResolver resolver = new ExternalURIResolver(); is = resolver.resolve(reference); is = excludeByteRange(is, request); return is; } else { return null; } default: return null; } } private InputStream excludeByteRange(InputStream contentIs, VerifyCMSSignatureRequest request) throws MOAApplicationException { int byteRead; final ByteArrayOutputStream contentOs = new ByteArrayOutputStream(); final CMSDataObject dataobject = request.getDataObject(); final BigDecimal from = dataobject.getExcludeByteRangeFrom(); final BigDecimal to = dataobject.getExcludeByteRangeTo(); if (from == null || to == null) { return contentIs; } BigDecimal counter = new BigDecimal("0"); final BigDecimal one = new BigDecimal("1"); try { while ((byteRead = contentIs.read()) >= 0) { if (inRange(counter, dataobject)) { // if byte is in byte range, set byte to 0x00 contentOs.write(0); } else { contentOs.write(byteRead); } counter = counter.add(one); } final InputStream is = new ByteArrayInputStream(contentOs.toByteArray()); return is; } catch (final IOException e) { throw new MOAApplicationException("2301", null, e); } } private boolean inRange(BigDecimal counter, CMSDataObject dataobject) { final BigDecimal from = dataobject.getExcludeByteRangeFrom(); final BigDecimal to = dataobject.getExcludeByteRangeTo(); if (from == null || to == null) { return false; } int compare = counter.compareTo(from); if (compare == -1) { return false; } else { compare = counter.compareTo(to); if (compare == 1) { return false; } else { return true; } } } }