Java tutorial
/************************************************************************* * * * EJBCA: The OpenSource Certificate Authority * * * * This software is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or any later version. * * * * See terms of license at gnu.org. * * * *************************************************************************/ package org.ejbca.extra.caservice; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.SignatureException; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.ejbca.core.ejb.ca.caadmin.CAAdminSessionLocal; import org.ejbca.core.ejb.ca.store.CertificateStoreSessionLocal; import org.ejbca.core.ejb.ra.UserAdminSessionLocal; import org.ejbca.core.model.authorization.AuthorizationDeniedException; import org.ejbca.core.model.log.Admin; import org.ejbca.core.model.services.BaseWorker; import org.ejbca.core.model.services.ServiceExecutionFailedException; import org.ejbca.extra.caservice.processor.MessageProcessor; import org.ejbca.extra.db.ISubMessage; import org.ejbca.extra.db.Message; import org.ejbca.extra.db.MessageHome; import org.ejbca.extra.db.SubMessages; import org.ejbca.extra.util.RAKeyStore; import org.ejbca.util.CertTools; /** An EJBCA Service worker that polls the External RA database for extRA messages and processes them. * The design includes that no two workers with the same serviceName can run on the same CA host at the same time. * * @version $Id: ExtRACAServiceWorker.java 11634 2011-03-30 09:49:31Z jeklund $ */ public class ExtRACAServiceWorker extends BaseWorker { private static Logger log = Logger.getLogger(ExtRACAServiceWorker.class); private boolean encryptionRequired = false; private boolean signatureRequired = false; private String keystorePwd = null; private String caname = null; private String whiteList = null; private static ConcurrentHashMap<String, EntityManagerFactory> entityManagerFactories = new ConcurrentHashMap<String, EntityManagerFactory>(); private MessageHome msgHome = null; private RAKeyStore serviceKeyStore = null; private Admin internalUser = Admin.getInternalAdmin(); /** Semaphore to keep several processes from running simultaneously on the same host */ private static HashMap<String, Object> running = new HashMap<String, Object>(); private CAAdminSessionLocal caAdminSession; private CertificateStoreSessionLocal certificateStoreSession; private UserAdminSessionLocal userAdminSession; /** * Checks if there are any new messages on the External RA and processes them. * * @see org.ejbca.core.model.services.IWorker#work(Map<Class<?>, Object>) */ public void work(Map<Class<?>, Object> ejbs) throws ServiceExecutionFailedException { log.debug(">work: " + serviceName); caAdminSession = ((CAAdminSessionLocal) ejbs.get(CAAdminSessionLocal.class)); certificateStoreSession = ((CertificateStoreSessionLocal) ejbs.get(CertificateStoreSessionLocal.class)); userAdminSession = ((UserAdminSessionLocal) ejbs.get(UserAdminSessionLocal.class)); if (startWorking()) { try { // A semaphore used to not run parallel service jobs on the same host so not to start unlimited number of threads just // because there is a lot of work to do. init(); processWaitingMessages(ejbs); } finally { stopWorking(); } } else { log.info("Service " + ExtRACAServiceWorker.class.getName() + " with name " + serviceName + " is already running in this VM! Not starting work."); } log.debug("<work: " + serviceName); } /** Synchronized method that makes checks if another service thread with this particular service name is already running. * If another service thread is running, false is returned. If another service is not running true is returned and an object is inserted in the running HashMap * to indicate that this service thread is running. * @return false is another service thread with the same serviceName is running, false otherwise. */ private synchronized boolean startWorking() { boolean ret = false; Object o = running.get(serviceName); if (o == null) { running.put(serviceName, new Object()); ret = true; } return ret; } /** Removes the object, that was inserted in startWorking() from the running HashMap. * @see #startWorking */ private synchronized void stopWorking() { running.remove(serviceName); } private void init() { // Read configuration properties // First we get it from the built in configuration in the properties file using ConfigurationHolder // Second we try to override this value with a value from the properties of this specific worker, configured in the GUI // Oh, and if no configuration exist it uses the hard coded values from the top of this file. String persistenceUnit = this.properties.getProperty("externalra-caservice.persistenceunit", "RAMessage1DS"); log.debug("externalra-caservice.hibernateresource: " + persistenceUnit); String keystorePath = this.properties.getProperty("externalra-caservice.keystore.path", "keystore/extrakeystore.p12"); log.debug("externalra-caservice.keystore.path: " + keystorePath); keystorePwd = this.properties.getProperty("externalra-caservice.keystore.pwd", "foo123"); log.debug("externalra-caservice.keystore.pwd: " + keystorePwd); encryptionRequired = Boolean .valueOf(this.properties.getProperty("externalra-caservice.encryption.required", "false")); log.debug("externalra-caservice.encryption.required: " + encryptionRequired); signatureRequired = Boolean .valueOf(this.properties.getProperty("externalra-caservice.signature.required", "false")); log.debug("externalra-caservice.signature.required: " + signatureRequired); caname = this.properties.getProperty("externalra-caservice.raissuer", "AdminCA1"); log.debug("externalra-caservice.raissuer: " + caname); whiteList = this.properties.getProperty("externalra-caservice.whitelist", ""); log.debug("externalra-caservice.whitelist: " + whiteList); // Initialize the JPA provider with the current persistence unit if (entityManagerFactories.get(persistenceUnit) == null) { EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(persistenceUnit); EntityManagerFactory entityManagerFactoryOld = entityManagerFactories.putIfAbsent(persistenceUnit, entityManagerFactory); if (entityManagerFactoryOld != null && !entityManagerFactoryOld.equals(entityManagerFactory)) { entityManagerFactory.close(); } else { log.info("Created new entity manager factory for persistence unit '" + persistenceUnit + "'"); } } msgHome = new MessageHome(entityManagerFactories.get(persistenceUnit), MessageHome.MESSAGETYPE_EXTRA, true); // We manage transactions ourself for this DataSource try { serviceKeyStore = new RAKeyStore(keystorePath, keystorePwd); } catch (Exception e) { if (encryptionRequired || signatureRequired) { log.error("Error reading ExtRACAService keystore", e); } else { log.debug("ExtRACAService KeyStore couldn't be configured, but isn't required"); } } } /** * Loops and gets waiting messages from the extRA database as long as there are any, and processes them. * If there are no more messages in status waiting the method ends. * @param ejbs A map between Local EJB interface classes and their injected stub */ public void processWaitingMessages(Map<Class<?>, Object> ejbs) { Collection<Certificate> cACertChain = null; try { cACertChain = MessageProcessor.getCACertChain(internalUser, caname, true, caAdminSession); } catch (ConfigurationException e) { if (encryptionRequired || signatureRequired) { log.error("RAIssuer is misconfigured: ", e); return; } else { log.debug("RAIssuer is misconfigured, but isn't required"); } } Message msg = null; String lastMessageId = null; do { msg = msgHome.getNextWaitingMessage(); // A small section that makes sure we don't loop too quickly over the same message. // Check if we are trying to process the same messageId as the last time. If this is the case exit from the loop and let the next // worker try to process it. // If it is not the same messageId process the message immediately. if (msg != null) { String id = msg.getMessageid(); if (StringUtils.equals(id, lastMessageId)) { log.info("The same message (" + id + ") was in the queue twice, putting back and exiting from the current loop"); // Re-set status to waiting so we will process it the next time the service is run msg.setStatus(Message.STATUS_WAITING); msgHome.update(msg); msg = null; } else { String errormessage = null; SubMessages submgs = null; try { log.info("Started processing message with messageId: " + msg.getMessageid() + ", and uniqueId: " + msg.getUniqueId()); if (serviceKeyStore != null) { submgs = msg .getSubMessages( (PrivateKey) serviceKeyStore.getKeyStore() .getKey(serviceKeyStore.getAlias(), keystorePwd.toCharArray()), cACertChain, null); } else { submgs = msg.getSubMessages(null, null, null); } if (submgs.isSigned()) { log.debug("Message from : " + msg.getMessageid() + " was signed"); } if (signatureRequired && !submgs.isSigned()) { errormessage = "Error: Message from : " + msg.getMessageid() + " wasn't signed which is a requirement"; log.error(errormessage); } if (submgs.isEncrypted()) { log.debug("Message from : " + msg.getMessageid() + " was encrypted"); } if (encryptionRequired && !submgs.isEncrypted()) { errormessage = "Error: Message from : " + msg.getMessageid() + " wasn't encrypted which is a requirement"; log.error(errormessage); } } catch (Exception e) { errormessage = "Error processing waiting message with Messageid : " + msg.getMessageid() + " : " + e.getMessage(); log.error("Error processing waiting message with Messageid : " + msg.getMessageid(), e); } if (submgs != null) { SubMessages respSubMsg; try { respSubMsg = generateResponseSubMessage(submgs.getSignerCert()); Iterator<ISubMessage> iter = submgs.getSubMessages().iterator(); boolean somethingprocessed = false; while (iter.hasNext()) { ISubMessage reqMsg = iter.next(); if (!checkWhiteList(reqMsg)) { errormessage = "Sub message of type " + reqMsg.getClass().getName() + " is not listed in white list. Message id: " + msg.getMessageid(); } ISubMessage respMsg = MessageProcessor.processSubMessage(getAdmin(submgs), reqMsg, errormessage, ejbs); if (respMsg != null) { // if the response message is null here, we will ignore this message, // it means that we should not do anything with it this round respSubMsg.addSubMessage(respMsg); somethingprocessed = true; } } if (somethingprocessed) { msg.setStatus(Message.STATUS_PROCESSED); msg.setSubMessages(respSubMsg); } else { log.info("Nothing processed for msg with messageId: " + msg.getMessageid() + ", leaving it in the queue"); msg.setStatus(Message.STATUS_WAITING); // Update create time, so that we will process the next message instead of this again the next round in the loop msg.setCreatetime((new Date()).getTime()); } msgHome.update(msg); } catch (Exception e) { log.error("Error generating response message with Messageid : " + msg.getMessageid(), e); } } } lastMessageId = id; } } while (msg != null); } // processWaitingMessage protected MessageHome getMessageHome() { return msgHome; } protected void storeMessageInRA(Message msg) { log.trace(">storeMessageInRA() MessageId : " + msg.getMessageid()); getMessageHome().update(msg); log.trace("<storeMessageInRA() MessageId : " + msg.getMessageid()); } // // Private helper methods // /** * Method used to retrieve which administrator to use. * If message is signed then use the signer as admin otherwise use InternalUser * @throws SignatureException * @throws AuthorizationDeniedException */ private Admin getAdmin(SubMessages submessages) throws SignatureException, AuthorizationDeniedException { if (submessages.isSigned()) { // Check if Signer Cert is revoked X509Certificate signerCert = submessages.getSignerCert(); Admin admin = userAdminSession.getAdmin(signerCert); // Check that user have the administrator flag set. userAdminSession.checkIfCertificateBelongToUser(admin, signerCert.getSerialNumber(), signerCert.getIssuerDN().toString()); boolean isRevoked = certificateStoreSession.isRevoked( CertTools.stringToBCDNString(signerCert.getIssuerDN().toString()), signerCert.getSerialNumber()); if (isRevoked) { throw new SignatureException("Error Signer certificate doesn't exist or is revoked."); } return admin; } return internalUser; } /** * Method that generates a response submessage depending on * required security configuration * @param reqCert the requestors certificate used for encryption. * @return a new instance of a SubMessage * @throws UnrecoverableKeyException * @throws NoSuchAlgorithmException * @throws KeyStoreException */ private SubMessages generateResponseSubMessage(X509Certificate reqCert) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { if (encryptionRequired && signatureRequired) { return new SubMessages( (X509Certificate) serviceKeyStore.getKeyStore().getCertificate(serviceKeyStore.getAlias()), (PrivateKey) serviceKeyStore.getKeyStore().getKey(serviceKeyStore.getAlias(), keystorePwd.toCharArray()), reqCert); } if (signatureRequired) { return new SubMessages( (X509Certificate) serviceKeyStore.getKeyStore().getCertificate(serviceKeyStore.getAlias()), (PrivateKey) serviceKeyStore.getKeyStore().getKey(serviceKeyStore.getAlias(), keystorePwd.toCharArray()), null); } if (encryptionRequired) { return new SubMessages(null, null, reqCert); } return new SubMessages(null, null, null); } /** * Check if the classname is listed in the whitelist of allowed classes. * @param reqMsg is request submessage * @return true if the classname was found in the whitelist or if the whitelist is empty */ private boolean checkWhiteList(ISubMessage reqMsg) { String classname = reqMsg.getClass().getName(); if (whiteList == null || whiteList.length() == 0) { return true; } if (whiteList.indexOf(classname) == -1) { log.info("Rejected External RA API submessage of type " + classname + " since it's not in the whitelist."); log.debug("Whitelist was " + whiteList); return false; } return true; } }