//$Header: /cvsroot-fuse/mec-as2/39/mendelson/comm/as2/cem/CEMReceiptController.java,v 1.1 2012/04/18 14:10:17 heller Exp $ package de.mendelson.comm.as2.cem; import de.mendelson.comm.as2.AS2Exception; import de.mendelson.comm.as2.cem.messages.CertificateReference; import de.mendelson.comm.as2.cem.messages.EDIINTCertificateExchangeRequest; import de.mendelson.comm.as2.cem.messages.EDIINTCertificateExchangeResponse; import de.mendelson.comm.as2.cem.messages.TradingPartnerInfo; import de.mendelson.comm.as2.cem.messages.TrustRequest; import de.mendelson.comm.as2.cem.messages.TrustResponse; import de.mendelson.comm.as2.cert.CertificateAccessDB; import de.mendelson.comm.as2.clientserver.message.RefreshClientCEMDisplay; import de.mendelson.util.security.cert.CertificateManager; import de.mendelson.comm.as2.message.AS2Message; import de.mendelson.comm.as2.message.AS2MessageCreation; import de.mendelson.comm.as2.message.AS2MessageInfo; import de.mendelson.comm.as2.message.AS2Payload; import de.mendelson.comm.as2.message.MessageAccessDB; import de.mendelson.comm.as2.notification.Notification; import de.mendelson.comm.as2.partner.Partner; import de.mendelson.comm.as2.partner.PartnerAccessDB; import de.mendelson.comm.as2.preferences.PreferencesAS2; import de.mendelson.comm.as2.sendorder.SendOrder; import de.mendelson.comm.as2.sendorder.SendOrderSender; import de.mendelson.comm.as2.server.AS2Server; import de.mendelson.util.AS2Tools; import de.mendelson.util.MecResourceBundle; import de.mendelson.util.XPathHelper; import de.mendelson.util.clientserver.ClientServer; import de.mendelson.util.security.BCCryptoHelper; import de.mendelson.util.security.KeyStoreUtil; import de.mendelson.util.security.cert.KeystoreCertificate; import de.mendelson.util.security.cert.KeystoreStorageImplFile; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.security.Provider; import java.security.Security; import java.security.cert.Certificate; import java.sql.Connection; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.logging.Logger; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.logging.Level; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; /* * Copyright (C) mendelson-e-commerce GmbH Berlin Germany * * This software is subject to the license agreement set forth in the license. * Please read and agree to all terms before using this software. * Other product and brand names are trademarks of their respective owners. */ /** * Task that checks for inbound CEM requests and performs the required steps like * * @author S.Heller * @version $Revision: 1.1 $ */ public class CEMReceiptController { private Logger logger = Logger.getLogger(AS2Server.SERVER_LOGGER_NAME); private Connection configConnection; private Connection runtimeConnection; private CertificateManager certificateManager; private MecResourceBundle rb; private PreferencesAS2 preferences = new PreferencesAS2(); private ClientServer clientServer; public CEMReceiptController(ClientServer clientServer, Connection configConnection, Connection runtimeConnection, CertificateManager certificateManager) { //load resource bundle try { this.rb = (MecResourceBundle) ResourceBundle.getBundle( ResourceBundleCEM.class.getName()); } catch (MissingResourceException e) { throw new RuntimeException("Oops..resource bundle " + e.getClassName() + " not found."); } this.configConnection = configConnection; this.runtimeConnection = runtimeConnection; this.certificateManager = certificateManager; this.clientServer = clientServer; } /**Checks a XML file against a W3C Schema and throws an exception if anything happens*/ private void checkAgainstSchema(AS2Message message, File schemaFile, byte[] xmlData) throws Exception { //create a new W3C Schema instance SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); Schema schema = factory.newSchema(schemaFile); Validator validator = schema.newValidator(); DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); domFactory.setNamespaceAware(true); DocumentBuilder builder = domFactory.newDocumentBuilder(); ByteArrayInputStream memIn = new ByteArrayInputStream(xmlData); DOMSource source = new DOMSource(builder.parse(memIn)); validator.validate(source); AS2MessageInfo info = (AS2MessageInfo) message.getAS2Info(); this.logger.log(Level.INFO, this.rb.getResourceString("cem.validated.schema", new Object[]{ info.getMessageId() }), info); memIn.close(); } /**Checks an inbound CEM and throws an error if anything goes wrong. This has to be done before * the MDN is sent. It will parse the xml description and see if all attachments are referenced etc */ public void checkInboundCEM(AS2Message message) throws AS2Exception { AS2MessageInfo info = (AS2MessageInfo) message.getAS2Info(); try { //check if a description file is part of the request. For compatibility reasons the server //is checking the payload by its content type only if the number of payloads are > 1. If there is //only one payload it is assumed that the ONE payload is the description. AS2Payload description = this.getPayloadByContentType(message.getPayloads(), "ediint-cert-exchange+xml"); if (description == null) { if (message.getPayloadCount() == 1) { description = message.getPayload(0); } else { //do not localize, this will appear in the MDN throw new Exception("CEM is in wrong structure: missing ediint-cert-exchange xml."); } } File cemSchema = new File("cem.xsd"); this.checkAgainstSchema(message, cemSchema, description.getData()); //parse the XML data to check if the content is in right structure and all attachments are present ByteArrayInputStream inStream = new ByteArrayInputStream(description.getData()); XPathHelper helper = new XPathHelper(inStream); helper.addNamespace("x", "urn:ietf:params:xml:ns:ediintcertificateexchange"); helper.addNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); if (helper.getNodeCount("/x:EDIINTCertificateExchangeRequest") == 1) { this.logger.log(Level.INFO, this.rb.getResourceString("cemtype.request", new Object[]{ info.getMessageId() }), info); EDIINTCertificateExchangeRequest request = EDIINTCertificateExchangeRequest.parse(description.getData()); if (!request.getTradingPartnerInfo().getSenderAS2Id().equals(info.getSenderId())) { //do not localize, will be returned in an MDN throw new Exception("CEM request sender AS2 id (" + request.getTradingPartnerInfo().getSenderAS2Id() + ") is not the same as the message sender AS2 id (" + info.getSenderId() + ")"); } //check if all referenced content ids are available as attachments List<TrustRequest> requestList = request.getTrustRequestList(); for (TrustRequest trustRequest : requestList) { String contentId = trustRequest.getEndEntity().getContentId(); AS2Payload referencedPayload = this.getPayloadByContentId(message.getPayloads(), contentId); if (referencedPayload == null) { throw new Exception("The CEM request references an attached certificate with the content id " + contentId + " which is not part of the message."); } } } else if (helper.getNodeCount("/x:EDIINTCertificateExchangeResponse") == 1) { this.logger.log(Level.INFO, this.rb.getResourceString("cemtype.response", new Object[]{ info.getMessageId() }), info); EDIINTCertificateExchangeResponse response = EDIINTCertificateExchangeResponse.parse(description.getData()); CEMAccessDB cemAccess = new CEMAccessDB(this.configConnection, this.runtimeConnection); if (!cemAccess.requestExists(response.getRequestId())) { //do not loalize, will be returned in an MDN throw new Exception("Related CEM request with requestId " + response.getRequestId() + " does not exist."); } else { this.logger.log(Level.INFO, this.rb.getResourceString("cem.response.relatedrequest.found", response.getRequestId()), info); } if (!response.getTradingPartnerInfo().getSenderAS2Id().equals(info.getSenderId())) { //do not loalize, will be returned in an MDN throw new Exception("CEM request sender AS2 id (" + response.getTradingPartnerInfo().getSenderAS2Id() + ") is not the same as the message sender AS2 id (" + info.getSenderId() + ")"); } } else { //no idea what this request is about throw new AS2Exception(AS2Exception.PROCESSING_ERROR, "The inbound CEM message is neither a certificate request or a certificate response - unable to process it", message); } } catch (Exception e) { e.printStackTrace(); throw new AS2Exception(AS2Exception.PROCESSING_ERROR, e.getMessage(), message); } } /**Gets an inbound CEM and processes it*/ public void processInboundCEM(AS2MessageInfo messageInfo) throws Throwable { List<AS2Payload> payloads = this.getPayloads(messageInfo); AS2Payload description = this.getPayloadByContentType(payloads, "ediint-cert-exchange+xml"); //check if it is a request or a response ByteArrayInputStream inStream = new ByteArrayInputStream(description.getData()); XPathHelper helper = new XPathHelper(inStream); helper.addNamespace("x", "urn:ietf:params:xml:ns:ediintcertificateexchange"); helper.addNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); if (helper.getNodeCount("/x:EDIINTCertificateExchangeRequest") == 1) { this.processInboundCEMRequest(messageInfo, payloads, description); } else if (helper.getNodeCount("/x:EDIINTCertificateExchangeResponse") == 1) { this.processInboundCEMResponse(messageInfo, description); } } /**Processes an inbound CEM request and answers a CEM response*/ private void processInboundCEMRequest(AS2MessageInfo info, List<AS2Payload> payloads, AS2Payload description) throws Throwable { PartnerAccessDB partnerAccess = new PartnerAccessDB(this.configConnection, this.runtimeConnection); Partner initiator = partnerAccess.getPartner(info.getSenderId()); Partner receiver = partnerAccess.getPartner(info.getReceiverId()); EDIINTCertificateExchangeRequest request = EDIINTCertificateExchangeRequest.parse(description.getData()); //auto import the attached certificates into the right keystore: SSL to the SSL keystore, //encryption and signature to the enc/singnature keystore List<TrustResponse> trustResponses = this.importCertificates(initiator, info, request, payloads); EDIINTCertificateExchangeResponse response = new EDIINTCertificateExchangeResponse(); response.setRequestId(request.getRequestId()); TradingPartnerInfo partnerInfo = new TradingPartnerInfo(); partnerInfo.setSenderAS2Id(info.getReceiverId()); response.setTradingPartnerInfo(partnerInfo); for (TrustResponse trustResponse : trustResponses) { response.addTrustResponse(trustResponse); } //enter the request to the CEM table in the db CEMAccessDB cemAccess = new CEMAccessDB(this.configConnection, this.runtimeConnection); cemAccess.insertRequest(info, initiator, receiver, request); if (this.clientServer != null) { this.clientServer.broadcastToClients(new RefreshClientCEMDisplay()); } //do not insert any new certificate and reject the request if the CEM is disabled in the //preferences boolean cemEnabled = this.preferences.getBoolean(PreferencesAS2.CEM); if (cemEnabled) { //for sign or SSL certificates the initiator may use them immediatly after a accept response //OR after the respondBy date. Insert the certificate to the partner now with prio2 if its the receiver List<TrustRequest> requestList = request.getTrustRequestList(); CertificateAccessDB certificateAccess = new CertificateAccessDB(this.configConnection, this.runtimeConnection); for (TrustRequest trustRequest : requestList) { if (trustRequest.isCertUsageSignature()) { //cert should be imported now KeystoreCertificate referencedCert = this.certificateManager.getKeystoreCertificateByIssuerAndSerial( trustRequest.getEndEntity().getIssuerName(), trustRequest.getEndEntity().getSerialNumber()); if (referencedCert == null) { throw new Exception("Certificate with issuer " + trustRequest.getEndEntity().getIssuerName() + " and serial " + trustRequest.getEndEntity().getSerialNumber() + " not found"); } initiator.getPartnerCertificateInformationList(). insertNewCertificate(referencedCert.getFingerPrintSHA1(), CEMEntry.CATEGORY_SIGN, 2); certificateAccess.storePartnerCertificateInformationList(initiator); this.logger.fine(initiator.getPartnerCertificateInformationList().getCertificatePurposeDescription(this.certificateManager, initiator, CEMEntry.CATEGORY_SIGN)); } } } else { AS2Message errorMessage = new AS2Message(info); //the user has disabled the CEM support throw new AS2Exception(AS2Exception.PROCESSING_ERROR, "The CEM support of this AS2 system is disabled in the system configuration by the user", errorMessage); } //now send the response and insert the response data into the database this.sendResponse(info, info.getReceiverId(), info.getSenderId(), response); //send a CEM notification if this is requested in the config Notification notification = new Notification(this.configConnection, this.runtimeConnection); try { notification.sendCEMRequestReceived(initiator); } catch (Exception e) { logger.severe("CEMReceiptController: " + e.getMessage()); Notification.systemFailure(this.configConnection, this.runtimeConnection, e); } } /**Processes the inbound CEM response*/ private void processInboundCEMResponse(AS2MessageInfo info, AS2Payload description) throws Exception { EDIINTCertificateExchangeResponse response = EDIINTCertificateExchangeResponse.parse(description.getData()); //insert the response into the database CEMAccessDB cemAccess = new CEMAccessDB(this.configConnection, this.runtimeConnection); //insert the request data into the certificate database PartnerAccessDB partnerAccess = new PartnerAccessDB(this.configConnection, this.runtimeConnection); Partner receiver = partnerAccess.getPartner(info.getSenderId()); Partner initiator = partnerAccess.getPartner(info.getReceiverId()); cemAccess.insertResponse(info, initiator, receiver, response); if (this.clientServer != null) { this.clientServer.broadcastToClients(new RefreshClientCEMDisplay()); } } /**Returns the payloads that are assigned to the passed message info*/ private List<AS2Payload> getPayloads(AS2MessageInfo info) throws Exception { MessageAccessDB messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection); List<AS2Payload> payloads = messageAccess.getPayload(info.getMessageId()); for (AS2Payload payload : payloads) { payload.loadDataFromPayloadFile(); } return (payloads); } /**Checks the content types of the passed payload and returns the first found payload*/ private AS2Payload getPayloadByContentType(List<AS2Payload> payloads, String contentType) { for (AS2Payload payload : payloads) { if (payload.getContentType() != null && contentType != null && payload.getContentType().toLowerCase().contains(contentType.toLowerCase())) { return (payload); } } return (null); } /**Checks the content ids of the passed payload and returns the first found payload*/ private AS2Payload getPayloadByContentId(List<AS2Payload> payloads, String contentId) { for (AS2Payload payload : payloads) { if (payload.getContentId() != null && contentId != null && payload.getContentId().toLowerCase().contains(contentId.toLowerCase())) { return (payload); } } return (null); } /**Sends the respose of a CEM and iserts the response into the database*/ private void sendResponse(AS2MessageInfo requestInfo, String senderId, String receiverId, EDIINTCertificateExchangeResponse response) throws Exception { PartnerAccessDB partnerAccess = new PartnerAccessDB(this.configConnection, this.runtimeConnection); Partner sender = partnerAccess.getPartner(senderId); Partner receiver = partnerAccess.getPartner(receiverId); AS2MessageCreation creation = new AS2MessageCreation(this.certificateManager, this.certificateManager); //store the payload File payloadFile = AS2Tools.createTempFile("AS2Response", ".xml"); FileOutputStream fileOut = new FileOutputStream(payloadFile); OutputStreamWriter writer = new OutputStreamWriter(fileOut, "UTF-8"); writer.write(response.toXML()); writer.flush(); writer.close(); AS2Payload descriptionXML = new AS2Payload(); descriptionXML.setContentType("application/ediint-cert-exchange+xml"); descriptionXML.setPayloadFilename(payloadFile.getAbsolutePath()); descriptionXML.loadDataFromPayloadFile(); AS2Message message = creation.createMessage(sender, receiver, new AS2Payload[]{descriptionXML}, AS2Message.MESSAGETYPE_CEM); this.logger.log(Level.INFO, this.rb.getResourceString("cem.response.prepared", new Object[]{ requestInfo.getMessageId(), response.getRequestId() }), requestInfo); SendOrder order = new SendOrder(); order.setReceiver(receiver); order.setMessage(message); order.setSender(sender); SendOrderSender orderSender = new SendOrderSender(this.configConnection, this.runtimeConnection); orderSender.send(order); CEMAccessDB cemAccess = new CEMAccessDB(this.configConnection, this.runtimeConnection); cemAccess.insertResponse((AS2MessageInfo) message.getAS2Info(), receiver, sender, response); if (this.clientServer != null) { this.clientServer.broadcastToClients(new RefreshClientCEMDisplay()); } } /**Imports a single certificate in the keystore that is wrapped by the passed certificate manager * Returns if the import has been skipped (it already exists) or if the import has been performed */ private boolean importSingleCertificate(AS2MessageInfo info, CertificateManager keystoreManager, Certificate cert) throws Throwable { KeyStoreUtil util = new KeyStoreUtil(); boolean imported = false; //check if the cert already exists String importAlias = util.getCertificateAlias(keystoreManager.getKeystore(), util.convertToX509Certificate(cert)); if (importAlias != null) { this.logger.log(Level.WARNING, this.rb.getResourceString("cert.already.imported", new Object[]{ info.getMessageId(), importAlias }), info); } else { //import the new alias Provider provBC = Security.getProvider("BC"); importAlias = util.importX509Certificate(keystoreManager.getKeystore(), util.convertToX509Certificate(cert), provBC); keystoreManager.saveKeystore(); this.logger.log(Level.FINE, this.rb.getResourceString("cert.imported.success", new Object[]{ info.getMessageId(), importAlias }), info); imported = true; } return (imported); } /**Auto imports the CEM request certificates into the encryption/sign keystore if they dont exist so far*/ private List<TrustResponse> importCertificates(Partner initiator, AS2MessageInfo info, EDIINTCertificateExchangeRequest request, List<AS2Payload> payloads) throws Throwable { KeyStoreUtil util = new KeyStoreUtil(); Provider provBC = Security.getProvider("BC"); List<TrustResponse> trustResponseList = new ArrayList<TrustResponse>(); List<TrustRequest> trustRequestList = request.getTrustRequestList(); //do reject the request if the CEM is disabled in the //preferences boolean cemEnabled = this.preferences.getBoolean(PreferencesAS2.CEM); for (TrustRequest trustRequest : trustRequestList) { //read certificates from the payloads AS2Payload certPayload = this.getPayloadByContentId(payloads, trustRequest.getEndEntity().getContentId()); FileInputStream inStream = new FileInputStream(certPayload.getPayloadFilename()); Collection<? extends Certificate> certList = util.readCertificates(inStream, provBC); inStream.close(); TrustResponse trustResponse = new TrustResponse(); if (cemEnabled) { trustResponse.setState(TrustResponse.STATUS_ACCEPTED_STR); try { //import the cert into the encryption/signature keystore for (Certificate cert : certList) { if (trustRequest.isCertUsageEncryption() || trustRequest.isCertUsageSignature()) { boolean imported = this.importSingleCertificate(info, this.certificateManager, cert); } if (trustRequest.isCertUsageSSL()) { //import the certificate into the SSL keystore CertificateManager sslManager = new CertificateManager(this.logger); String keystoreFile = new File(this.preferences.get(PreferencesAS2.KEYSTORE_HTTPS_SEND)).getAbsolutePath(); KeystoreStorageImplFile storage = new KeystoreStorageImplFile(keystoreFile, this.preferences.get(PreferencesAS2.KEYSTORE_HTTPS_SEND_PASS).toCharArray(), BCCryptoHelper.KEYSTORE_JKS); sslManager.loadKeystoreCertificates(storage); boolean imported = this.importSingleCertificate(info, sslManager, cert); //notify the user that a SSL certificate has been changed an the SSL connector must be resetted if (imported) { String importAlias = util.getCertificateAlias(sslManager.getKeystore(), util.convertToX509Certificate(cert)); Notification notification = new Notification(this.configConnection, this.runtimeConnection); notification.sendSSLCertificateAddedByCEM(initiator, sslManager.getKeystoreCertificate(importAlias)); } } } } catch (Exception e) { //if the import fails the trust response state should be set to REJECTED with an error message trustResponse.setState(TrustResponse.STATUS_REJECTED_STR); String rejectionReason = "Failure in certificate import process: " + e.getMessage(); this.logger.warning(rejectionReason); trustResponse.setReasonForRejection(rejectionReason); } } else { //cem is disabled trustResponse.setState(TrustResponse.STATUS_REJECTED_STR); //do not localize the reason, its part of the response trustResponse.setReasonForRejection("CEM has been disabled in the system settings."); } //ensure to really return the same issuer name as requested if the other side performs a simple string compare on it CertificateReference certificateReference = new CertificateReference(); certificateReference.setCertficiate(trustRequest.getEndEntity().getIssuerName(), trustRequest.getEndEntity().getSerialNumber()); trustResponse.setCertificateReference(certificateReference); trustResponseList.add(trustResponse); } this.certificateManager.rereadKeystoreCertificates(); return (trustResponseList); } }