/*
* 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 KeyEntryID
s available to the given
* keyGroupID
.
*
* @param keyGroupID The keygroup ID for which the available keys should be
* returned.
* @return The Set
of KeyEntryID
s
* 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;
}
}