/* * 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 iaik.server.modules.algorithms.HashAlgorithms; import iaik.server.modules.cmssign.CMSSignature; import iaik.server.modules.cmssign.CMSSignatureCreationException; import iaik.server.modules.cmssign.CMSSignatureCreationModule; import iaik.server.modules.cmssign.CMSSignatureCreationModuleFactory; import iaik.server.modules.cmssign.CMSSignatureCreationProfile; import iaik.server.modules.keys.KeyEntryID; import iaik.server.modules.keys.KeyModule; import iaik.server.modules.keys.KeyModuleFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.security.Principal; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.io.IOUtils; import at.gv.egovernment.moa.spss.MOAApplicationException; import at.gv.egovernment.moa.spss.MOAException; import at.gv.egovernment.moa.spss.MOASystemException; import at.gv.egovernment.moa.spss.api.cmssign.CreateCMSSignatureRequest; import at.gv.egovernment.moa.spss.api.cmssign.CreateCMSSignatureResponse; import at.gv.egovernment.moa.spss.api.cmssign.DataObjectInfo; import at.gv.egovernment.moa.spss.api.cmssign.SingleSignatureInfo; 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.common.MetaInfo; import at.gv.egovernment.moa.spss.api.impl.CreateCMSSignatureResponseImpl; import at.gv.egovernment.moa.spss.server.config.ConfigurationProvider; import at.gv.egovernment.moa.spss.server.config.KeyGroupEntry; import at.gv.egovernment.moa.spss.server.iaik.cmssign.CMSSignatureCreationProfileImpl; 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.FilteredOutputStream; import at.gv.egovernment.moa.spss.util.MessageProvider; import at.gv.egovernment.moaspss.logging.LogMsg; import at.gv.egovernment.moaspss.logging.Logger; import at.gv.egovernment.moaspss.util.Constants; /** * A class providing an API based interface to the * CMSSignatureCreationModule. * * This class performs the invocation of the * iaik.server.modules.cmssign.CMSSignatureCreationModule from a * CreateCMSSignatureRequest given as an API object. The result of * the invocation is integrated into a CreateCMSSignatureResponse * and returned. * * @version $Id$ */ public class CMSSignatureCreationInvoker { private static Map HASH_ALGORITHM_MAPPING; static { HASH_ALGORITHM_MAPPING = new HashMap(); HASH_ALGORITHM_MAPPING.put(Constants.SHA1_URI, HashAlgorithms.SHA1); HASH_ALGORITHM_MAPPING.put(Constants.SHA256_URI, HashAlgorithms.SHA256); HASH_ALGORITHM_MAPPING.put(Constants.SHA384_URI, HashAlgorithms.SHA384); HASH_ALGORITHM_MAPPING.put(Constants.SHA512_URI, HashAlgorithms.SHA512); } /** The single instance of this class. */ private static CMSSignatureCreationInvoker instance = null; /** * Get the only instance of this class. * * @return The only instance of this class. */ public static synchronized CMSSignatureCreationInvoker getInstance() { if (instance == null) { instance = new CMSSignatureCreationInvoker(); } return instance; } /** * Create a new CMSSignatureCreationInvoker. * * Protected to disallow multiple instances. */ protected CMSSignatureCreationInvoker() { } /** * Process the CreateCMSSignatureRequest message and invoke the * XMLSignatureCreationModule for every * SingleSignatureInfo contained in the request. * * @param request A CreateCMSSignatureRequest API object * containing the information for creating the signature(s). * @param reserved A Set of reserved object IDs. * * @return A CreateCMSSignatureResponse API object containing * the created signature(s). The response contains either a * SignatureEnvironment or a ErrorResponse * for each SingleSignatureInfo in the request. * @throws MOAException An error occurred during signature creation. */ public CreateCMSSignatureResponse createCMSSignature( CreateCMSSignatureRequest request, Set reserved) throws MOAException { TransactionContext context = TransactionContextManager.getInstance().getTransactionContext(); //LoggingContext loggingCtx = LoggingContextManager.getInstance().getLoggingContext(); CreateCMSSignatureResponseBuilder responseBuilder = new CreateCMSSignatureResponseBuilder(); CreateCMSSignatureResponse response = new CreateCMSSignatureResponseImpl(); boolean isSecurityLayerConform = false; boolean isPAdESConformRequired = false; String structure = null; String mimetype = null; // select the SingleSignatureInfo elements Iterator singleSignatureInfoIter = request.getSingleSignatureInfos().iterator(); // iterate over all the SingleSignatureInfo elements in the request while (singleSignatureInfoIter.hasNext()) { SingleSignatureInfo singleSignatureInfo = (SingleSignatureInfo) singleSignatureInfoIter.next(); isSecurityLayerConform = singleSignatureInfo.isSecurityLayerConform(); isPAdESConformRequired = singleSignatureInfo.isPAdESConform(); //PAdES conformity always requires SecurityLayer conformity, because certificates must be included if (isPAdESConformRequired && !isSecurityLayerConform) { isSecurityLayerConform = isPAdESConformRequired; Logger.debug("Set SecurityLayerConformity to 'true' because PAdES conformity is requested"); } DataObjectInfo dataObjectInfo = singleSignatureInfo.getDataObjectInfo(); structure = dataObjectInfo.getStructure(); CMSDataObject dataobject = dataObjectInfo.getDataObject(); MetaInfo metainfo = dataobject.getMetaInfo(); /*TODO: do not set SigningTime in IAIK-MOA request or any other * API method/parameter when IAIK-MOA API is updated. * Maybe also update mimetype solution below */ //does not set mimetype if PAdES conformity is requested if (!isPAdESConformRequired) { mimetype = metainfo.getMimeType(); } else Logger.debug("PAdES conformity requested. Does not set mimetype into CAdES signature"); CMSContent content = dataobject.getContent(); InputStream contentIs = null; // build the content data switch (content.getContentType()) { case CMSContent.EXPLICIT_CONTENT : contentIs = ((CMSContentExcplicit) content).getBinaryContent(); break; case CMSContent.REFERENCE_CONTENT : String reference = ((CMSContentReference) content).getReference(); if (!"".equals(reference)) { ExternalURIResolver resolver = new ExternalURIResolver(); contentIs = resolver.resolve(reference); } else { throw new MOAApplicationException("2301", null); } break; default : { throw new MOAApplicationException("2301", null); } } // create CMSSignatureCreationModuleFactory CMSSignatureCreationModule module = CMSSignatureCreationModuleFactory.getInstance(); List signedProperties = null; boolean includeData = true; if (structure.compareTo("enveloping") == 0) includeData = true; if (structure.compareTo("detached") == 0) includeData = false; ConfigurationProvider config = context.getConfiguration(); // get the key group id String keyGroupID = request.getKeyIdentifier(); // set the key set Set keySet = buildKeySet(keyGroupID); if (keySet == null) { throw new MOAApplicationException("2231", null); } else if (keySet.size() == 0) { throw new MOAApplicationException("2232", null); } // get digest algorithm String digestAlgorithm = getDigestAlgorithm(config, keyGroupID); // create CMSSignatureCreation profile: CMSSignatureCreationProfile profile = new CMSSignatureCreationProfileImpl( keySet, digestAlgorithm, signedProperties, isSecurityLayerConform, includeData, mimetype, isPAdESConformRequired); // create CMSSignature from the CMSSignatureCreationModule // build the additionalSignedProperties List additionalSignedProperties = buildAdditionalSignedProperties(); TransactionId tid = new TransactionId(context.getTransactionID()); try { CMSSignature signature = module.createSignature(profile, additionalSignedProperties, tid); ByteArrayOutputStream out = new ByteArrayOutputStream(); // get CMS SignedData output stream from the CMSSignature and wrap it around out boolean base64 = true; OutputStream signedDataStream = signature.getSignature(out, base64); // now write the data to be signed to the signedDataStream // Stream based, this should have a better performance FilteredOutputStream filteredOuputStream = new FilteredOutputStream( signedDataStream, 4096, dataobject.getExcludeByteRangeFrom(), dataobject.getExcludeByteRangeTo()); IOUtils.copyLarge(contentIs, filteredOuputStream); filteredOuputStream.flush(); // finish SignedData processing by closing signedDataStream signedDataStream.close(); String base64value = out.toString(); responseBuilder.addCMSSignature(base64value); } catch (CMSSignatureCreationException e) { MOAException moaException = IaikExceptionMapper.getInstance().map(e); responseBuilder.addError( moaException.getMessageId(), moaException.getMessage()); Logger.warn(moaException.getMessage(), e); } catch (IOException e) { throw new MOAApplicationException("2301", null, e); } } return responseBuilder.getResponse(); } private boolean inRange(BigDecimal counter, CMSDataObject dataobject) { BigDecimal from = dataobject.getExcludeByteRangeFrom(); 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; } } private String getDigestAlgorithm(ConfigurationProvider config, String keyGroupID) throws MOASystemException { // get digest method on key group level (if configured) String configDigestMethodKG = config.getKeyGroup(keyGroupID).getDigestMethodAlgorithm(); // get default digest method (if configured) String configDigestMethod = config.getDigestMethodAlgorithmName(); String digestMethod = null; if (configDigestMethodKG != null) { // if KG specific digest method is configured digestMethod = (String) HASH_ALGORITHM_MAPPING.get(configDigestMethodKG); if (digestMethod == null) { error( "config.17", new Object[] { configDigestMethodKG}); throw new MOASystemException("2900", null); } Logger.debug("Digest algorithm: " + digestMethod + "(configured in KeyGroup)"); } else { // else get default configured digest method digestMethod = (String) HASH_ALGORITHM_MAPPING.get(configDigestMethod); if (digestMethod == null) { error( "config.17", new Object[] { configDigestMethod}); throw new MOASystemException("2900", null); } Logger.debug("Digest algorithm: " + digestMethod + "(default)"); } return digestMethod; } /** * Utility function to issue an error message to the log. * * @param messageId The ID of the message to log. * @param parameters Additional message parameters. */ private static void error(String messageId, Object[] parameters) { MessageProvider msg = MessageProvider.getInstance(); Logger.error(new LogMsg(msg.getMessage(messageId, parameters))); } /** * Build the set of KeyEntryIDs available to the given * keyGroupID. * * @param keyGroupID The keygroup ID for which the available keys should be * returned. * @return The Set of KeyEntryIDs * identifying the available keys. */ private Set buildKeySet(String keyGroupID) { TransactionContext context = TransactionContextManager.getInstance().getTransactionContext(); ConfigurationProvider config = context.getConfiguration(); Set keyGroupEntries; // get the KeyGroup entries from the configuration if (context.getClientCertificate() != null) { X509Certificate cert = context.getClientCertificate()[0]; Principal issuer = cert.getIssuerDN(); BigInteger serialNumber = cert.getSerialNumber(); keyGroupEntries = config.getKeyGroupEntries(issuer, serialNumber, keyGroupID); } else { keyGroupEntries = config.getKeyGroupEntries(null, null, keyGroupID); } // map the KeyGroup entries to a set of KeyEntryIDs if (keyGroupEntries == null) { return null; } else if (keyGroupEntries.size() == 0) { return Collections.EMPTY_SET; } else { KeyModule module = KeyModuleFactory.getInstance( new TransactionId(context.getTransactionID())); Set keyEntryIDs = module.getPrivateKeyEntryIDs(); Set keySet = new HashSet(); Iterator iter; // filter out the keys that do not exist in the IAIK configuration // by walking through the key entries and checking if the exist in the // keyGroupEntries for (iter = keyEntryIDs.iterator(); iter.hasNext();) { KeyEntryID entryID = (KeyEntryID) iter.next(); KeyGroupEntry entry = new KeyGroupEntry( entryID.getModuleID(), entryID.getCertificateIssuer(), entryID.getCertificateSerialNumber()); if (keyGroupEntries.contains(entry)) { keySet.add(entryID); } } return keySet; } } /** * Build the list of additional signed properties. * * Based on the generic configuration setting * ConfigurationProvider.TEST_SIGNING_TIME_PROPERTY, a * constant SigningTime will be added to the properties. * * @return The List of additional signed properties. */ private List buildAdditionalSignedProperties() { TransactionContext context = TransactionContextManager.getInstance().getTransactionContext(); ConfigurationProvider config = context.getConfiguration(); List additionalSignedProperties = Collections.EMPTY_LIST; return additionalSignedProperties; } }