org.ejbca.extra.caservice.ExtRACAServiceWorker.java Source code

Java tutorial

Introduction

Here is the source code for org.ejbca.extra.caservice.ExtRACAServiceWorker.java

Source

/*************************************************************************
 *                                                                       *
 *  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;
    }

}