--- java/org/apache/catalina/core/AprLifecycleListener.java (revision 1081497) +++ java/org/apache/catalina/core/AprLifecycleListener.java (working copy) @@ -23,7 +23,9 @@ import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleEvent; +import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleListener; +import org.apache.catalina.LifecycleState; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.jni.Library; @@ -38,20 +40,22 @@ * * @author Remy Maucherat * @author Filip Hanik + * @author cbeckey@gmail.com added FIPS Mode and group library loading * @version $Id$ * @since 4.1 */ public class AprLifecycleListener - implements LifecycleListener { +implements LifecycleListener +{ private static final Log log = LogFactory.getLog(AprLifecycleListener.class); private static boolean instanceCreated = false; + /** * The string manager for this package. */ - protected static final StringManager sm = - StringManager.getManager(Constants.Package); + protected static final StringManager stringManager = StringManager.getManager(Constants.Package); // ---------------------------------------------- Constants @@ -66,9 +70,11 @@ // ---------------------------------------------- Properties protected static String SSLEngine = "on"; //default on + protected static String FIPSMode = "off"; //default off, valid only when SSLEngine is 'on' protected static String SSLRandomSeed = "builtin"; protected static boolean sslInitialized = false; protected static boolean aprInitialized = false; + protected static boolean fipsModeActive = false; protected static boolean sslAvailable = false; protected static boolean aprAvailable = false; @@ -78,13 +84,14 @@ //https://issues.apache.org/bugzilla/show_bug.cgi?id=48613 if (instanceCreated) { synchronized (lock) { - init(); + initializeAPR(); } } return aprAvailable; } - public AprLifecycleListener() { + public AprLifecycleListener() + { instanceCreated = true; } @@ -96,30 +103,40 @@ * @param event The event that has occurred */ @Override - public void lifecycleEvent(LifecycleEvent event) { - - if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) { - synchronized (lock) { - init(); - if (aprAvailable) { - try { + public void lifecycleEvent(LifecycleEvent event) + { + if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) + { + synchronized (lock) + { + initializeAPR(); + if (aprAvailable) + try + { initializeSSL(); - } catch (Throwable t) { + } + catch (Throwable t) + { ExceptionUtils.handleThrowable(t); - log.info(sm.getString("aprListener.sslInit")); + log.info(stringManager.getString("aprListener.sslInit")); } - } } - } else if (Lifecycle.AFTER_DESTROY_EVENT.equals(event.getType())) { - synchronized (lock) { - if (!aprAvailable) { + } + else if (Lifecycle.AFTER_DESTROY_EVENT.equals(event.getType())) + { + synchronized (lock) + { + if (!aprAvailable) return; - } - try { + + try + { terminateAPR(); - } catch (Throwable t) { + } + catch (Throwable t) + { ExceptionUtils.handleThrowable(t); - log.info(sm.getString("aprListener.aprDestroy")); + log.info(stringManager.getString("aprListener.aprDestroy")); } } } @@ -127,53 +144,63 @@ } private static void terminateAPR() - throws ClassNotFoundException, NoSuchMethodException, - IllegalAccessException, InvocationTargetException + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { String methodName = "terminate"; - Method method = Class.forName("org.apache.tomcat.jni.Library") - .getMethod(methodName, (Class [])null); + Method method = Class.forName("org.apache.tomcat.jni.Library").getMethod(methodName, (Class [])null); method.invoke(null, (Object []) null); aprAvailable = false; aprInitialized = false; + fipsModeActive = false; + sslAvailable = false; // Well we cleaned the pool in terminate. sslInitialized = false; // Well we cleaned the pool in terminate. } - private static void init() + private static void initializeAPR() { int major = 0; int minor = 0; int patch = 0; + + // installed version int apver = 0; + + // required version int rqver = TCN_REQUIRED_MAJOR * 1000 + TCN_REQUIRED_MINOR * 100 + TCN_REQUIRED_PATCH; + + // recommended version int rcver = TCN_REQUIRED_MAJOR * 1000 + TCN_REQUIRED_MINOR * 100 + TCN_RECOMMENDED_PV; - if (aprInitialized) { + if (aprInitialized) return; - } - aprInitialized = true; + + aprInitialized = true; // aprInitialized indicates whether initialization has been attempted + // aprAvailable indicates whether initialization has succeeded - try { + try + { + Class clazz = Class.forName("org.apache.tomcat.jni.Library"); String methodName = "initialize"; - Class paramTypes[] = new Class[1]; - paramTypes[0] = String.class; - Object paramValues[] = new Object[1]; - paramValues[0] = null; - Class clazz = Class.forName("org.apache.tomcat.jni.Library"); + Class paramTypes[] = new Class[]{String.class}; + Object paramValues[] = new Object[]{null}; Method method = clazz.getMethod(methodName, paramTypes); - method.invoke(null, paramValues); + method.invoke(null, paramValues); // invoke the static "initialize" method + major = clazz.getField("TCN_MAJOR_VERSION").getInt(null); minor = clazz.getField("TCN_MINOR_VERSION").getInt(null); patch = clazz.getField("TCN_PATCH_VERSION").getInt(null); apver = major * 1000 + minor * 100 + patch; - } catch (Throwable t) { + } + catch (Throwable t) + { ExceptionUtils.handleThrowable(t); - log.info(sm.getString("aprListener.aprInit", + log.info(stringManager.getString("aprListener.aprInit", System.getProperty("java.library.path"))); return; } - if (apver < rqver) { - log.error(sm.getString("aprListener.tcnInvalid", major + "." + if (apver < rqver) + { + log.error(stringManager.getString("aprListener.tcnInvalid", major + "." + minor + "." + patch, TCN_REQUIRED_MAJOR + "." + TCN_REQUIRED_MINOR + "." + @@ -187,38 +214,45 @@ } return; } - if (apver < rcver) { - log.info(sm.getString("aprListener.tcnVersion", major + "." + if (apver < rcver) + { + log.info(stringManager.getString("aprListener.tcnVersion", major + "." + minor + "." + patch, TCN_REQUIRED_MAJOR + "." + TCN_RECOMMENDED_MINOR + "." + TCN_RECOMMENDED_PV)); } - log.info(sm.getString("aprListener.tcnValid", major + "." + log.info(stringManager.getString("aprListener.tcnValid", major + "." + minor + "." + patch)); // Log APR flags - log.info(sm.getString("aprListener.flags", + log.info(stringManager.getString("aprListener.flags", Boolean.valueOf(Library.APR_HAVE_IPV6), Boolean.valueOf(Library.APR_HAS_SENDFILE), Boolean.valueOf(Library.APR_HAS_SO_ACCEPTFILTER), Boolean.valueOf(Library.APR_HAS_RANDOM))); + + // initialization is complete and the library version(s) are compatible aprAvailable = true; } + /** + * + * @throws ClassNotFoundException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ private static void initializeSSL() - throws ClassNotFoundException, NoSuchMethodException, - IllegalAccessException, InvocationTargetException + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { - - if ("off".equalsIgnoreCase(SSLEngine)) { + if ("off".equalsIgnoreCase(SSLEngine)) return; - } - if (sslInitialized) { - //only once per VM + + //only once per VM + if (sslInitialized) return; - } sslInitialized = true; String methodName = "randSet"; @@ -230,29 +264,198 @@ Method method = clazz.getMethod(methodName, paramTypes); method.invoke(null, paramValues); - methodName = "initialize"; paramValues[0] = "on".equalsIgnoreCase(SSLEngine)?null:SSLEngine; method = clazz.getMethod(methodName, paramTypes); method.invoke(null, paramValues); + boolean initializeFIPS = isFIPSModeOn(); + if(initializeFIPS) + { + log.info( stringManager.getString("aprListener.initializingFIPS") ); + boolean success = initializeFIPS(); + if(success) + log.info( stringManager.getString("aprListener.initializeFIPSSuccess") ); + else + { + String msg = stringManager.getString("aprListener.initializeFIPSFailed"); + log.info( msg ); + + // do not allow SSL to be called if FIPS was requested but did not initialize + throw new InvocationTargetException(null, msg); + } + } + sslAvailable = true; } - public String getSSLEngine() { + /** + * Initialize FIPS mode in a way that won't crash if the FIPS libtcnative + * and associated libraries are not available. + * + * @param initializeFIPS + * @return + */ + private static boolean initializeFIPS() + throws NoSuchMethodException + { + try + { + Class clazz = Class.forName("org.apache.tomcat.jni.SSL"); + String methodName = "fipsModeSet"; + Method method = clazz.getMethod(methodName, new Class[]{Integer.class}); + Object[] fipsParamValues = new Object[]{Integer.valueOf(1)}; + Object result = method.invoke(null, fipsParamValues); + if(result instanceof Integer) + { + fipsModeActive = ((Integer) result).intValue() > 0; + return fipsModeActive; // success is a return value greater than zero + } + else + log.info( stringManager.getString("aprListener.fipsModeSetInvalid") ); + } + catch(InvocationTargetException itX) + { + log.info( stringManager.getString("aprListener.fipsModeUnavailable") + "\n" + itX.getMessage() ); + } + catch (Throwable t) + { + if (!log.isDebugEnabled()) + log.debug(stringManager.getString("aprListener.aprInit", System.getProperty("java.library.path")), t); + else + log.info(stringManager.getString("aprListener.aprInit", System.getProperty("java.library.path"))); + } + return false; + } + + /** + * Determine if the SSL property is set in the AprLifecycleListener configuration (i.e. SSL is requested to be available) + * + * @return + */ + public static boolean isSSLOn() + { + return ! "off".equalsIgnoreCase(AprLifecycleListener.SSLEngine); + } + + /** + * Determine if the FIPSMode property is set in the AprLifecycleListener configuration (i.e. FIPS mode must be on) + * + * @return + */ + public static boolean isFIPSModeOn() + { + return "on".equalsIgnoreCase(AprLifecycleListener.FIPSMode); + } + + /** + * If FIPS mode has been requested and was successfully initialized then return TRUE, + * else return FALSE + * + * @return + */ + public boolean isFIPSModeActive() + { + return fipsModeActive; + } + + public String getSSLEngine() + { return SSLEngine; } - public void setSSLEngine(String SSLEngine) { - AprLifecycleListener.SSLEngine = SSLEngine; + /** + * The SSLEngine must be set before SSL is initialized, else this will have no effect. + * + * @param SSLEngine + */ + public void setSSLEngine(String SSLEngine) + { + if(! sslInitialized) // added so that the random seem class being used is consistent with that returned from getSSLEngine + AprLifecycleListener.SSLEngine = SSLEngine; } - public String getSSLRandomSeed() { + public String getFIPSMode() + { + return AprLifecycleListener.FIPSMode; + } + + /** + * FIPSMode must be set before SSL is initialized, else this will have no effect. + * + * @param FIPSMode + */ + public void setFIPSMode(String FIPSMode) + { + if(! sslInitialized) // added so that the random seem class being used is consistent with that returned from getFIPSMode + AprLifecycleListener.FIPSMode = FIPSMode; + } + + public String getSSLRandomSeed() + { return SSLRandomSeed; } - public void setSSLRandomSeed(String SSLRandomSeed) { - AprLifecycleListener.SSLRandomSeed = SSLRandomSeed; + /** + * SSLRandomSeed must be set before this instance is initialized, else this will have no effect. + * + * @param SSLRandomSeed + */ + public void setSSLRandomSeed(String SSLRandomSeed) + { + if(! sslInitialized) // added so that the random seem class being used is consistent with that returned from getSSLRandomSeed() + AprLifecycleListener.SSLRandomSeed = SSLRandomSeed; } + /** + * This is not a production method, it is just a simple way to test + * that the APR libraries can be loaded and that the versions are usable. + * + * @param argv + */ + public static void main(String[] argv) + { + System.out.println( "Loading APR libraries from " + System.getProperty("java.library.path") ); + AprLifecycleListener lifecycleListener = new AprLifecycleListener(); + Lifecycle lifecycle = new Lifecycle() + { + private LifecycleState state = LifecycleState.NEW; + + @Override + public void stop() throws LifecycleException{state = LifecycleState.STOPPED;} + + @Override + public void start() throws LifecycleException{state = LifecycleState.STARTED;} + + @Override + public void removeLifecycleListener(LifecycleListener listener){} + + @Override + public LifecycleListener[] findLifecycleListeners(){return null;} + + @Override + public void addLifecycleListener(LifecycleListener listener){} + + @Override + public void init() throws LifecycleException {state = LifecycleState.INITIALIZED;} + + @Override + public void destroy() throws LifecycleException {state = LifecycleState.DESTROYED;} + + @Override + public LifecycleState getState() {return state;} + + @Override + public String getStateName() {return state.toString();} + }; + + LifecycleEvent initEvent = new LifecycleEvent(lifecycle, Lifecycle.BEFORE_INIT_EVENT, null); + LifecycleEvent destroyEvent = new LifecycleEvent(lifecycle, Lifecycle.AFTER_DESTROY_EVENT, null); + + lifecycleListener.setSSLEngine("on"); + lifecycleListener.setFIPSMode("on"); + lifecycleListener.lifecycleEvent(initEvent); + + lifecycleListener.lifecycleEvent(destroyEvent); + } } --- java/org/apache/catalina/core/LocalStrings.properties (revision 1081497) +++ java/org/apache/catalina/core/LocalStrings.properties (working copy) @@ -59,6 +59,13 @@ aprListener.sslInit=Failed to initialize the SSLEngine. aprListener.tcnValid=Loaded APR based Apache Tomcat Native library {0}. aprListener.flags=APR capabilities: IPv6 [{0}], sendfile [{1}], accept filters [{2}], random [{3}]. +aprListener.initializingSSL=Initializing APR SSL library now ... +aprListener.initializedSSL=APR library has been successfully initialized. +aprListener.initializedSSLFailed=APR library initialization FAILED. +aprListener.initializingFIPS=FIPS mode has been requested, initializing FIPS mode now ... +aprListener.initializeFIPSSuccess=FIPS mode has been successfully initialized. +aprListener.fipsModeSetInvalid=The call to set FIPS mode returned an invalid value or type. OpenSSL is not running in FIPS mode. +aprListener.initializeFIPSFailed=FIPS mode initialization FAILED. containerBase.alreadyStarted=Container {0} has already been started containerBase.notConfigured=No basic Valve has been configured containerBase.notStarted=Container {0} has not been started --- java/org/apache/tomcat/jni/Library.java (revision 1081497) +++ java/org/apache/tomcat/jni/Library.java (working copy) @@ -20,57 +20,105 @@ /** Library * * @author Mladen Turk + * @author cbeckey@gmail.com added FIPS Mode * @version $Id$ */ -public final class Library { - - /* Default library names */ - private static String [] NAMES = {"tcnative-1", "libtcnative-1"}; +public final class Library +{ + // Library names, organized as "groups". + // 1.) Only one group must successfully load for the initialization to succeed. + // 2.) An entire group must be loaded for the initialization to succeed. + private static String [][] LIBRARY_NAMES = + { + new String[]{"tcnative-1"}, + new String[]{"libapr-1", "libeay32", "libtcnative-1"} + }; + /* * A handle to the unique Library singleton instance. */ private static Library _instance = null; + // grab the relevant system properties so we can produce useful error messages + private final String libraryPath = System.getProperty("java.library.path"); + private final String systemPathSeperator = System.getProperty("path.separator"); + private final String [] libraryPaths = libraryPath == null || systemPathSeperator == null ? null : libraryPath.split(systemPathSeperator); + + /** + * Load the default librar(ies). + * + * @throws Exception + */ private Library() - throws Exception + throws Exception { + StringBuilder err = new StringBuilder(); + StringBuilder status = new StringBuilder(); + StringBuilder libraryGroupStatus = null; + boolean loaded = false; - StringBuilder err = new StringBuilder(); - for (int i = 0; i < NAMES.length; i++) { - try { - System.loadLibrary(NAMES[i]); - loaded = true; - } - catch (Throwable t) { - if (t instanceof ThreadDeath) { - throw (ThreadDeath) t; - } - if (t instanceof VirtualMachineError) { - throw (VirtualMachineError) t; - } - String name = System.mapLibraryName(NAMES[i]); - String path = System.getProperty("java.library.path"); - String sep = System.getProperty("path.separator"); - String [] paths = path.split(sep); - for (int j=0; j 0) - err.append(", "); - err.append(t.getMessage()); - } - if (loaded) - break; + for( int libraryGroup = 0; libraryGroup < LIBRARY_NAMES.length && ! loaded; libraryGroup++ ) + { + //System.out.println( "Attempting to load library group " + libraryGroup ); + libraryGroupStatus = new StringBuilder(); + String libName = null; + + // all libraries in a group (i.e. same primary index) must be loaded + // this was changed to allow error reporting to specify the missing library + boolean groupLoaded = true; + for(int libraryGroupMember = 0; libraryGroupMember < LIBRARY_NAMES[libraryGroup].length; ++libraryGroupMember) + { + libName = LIBRARY_NAMES[libraryGroup][libraryGroupMember]; + if(libraryGroupStatus.length() > 0) libraryGroupStatus.append(","); + libraryGroupStatus.append(libName); + try + { + //System.out.println( "Attempting to load library " + libName ); + System.loadLibrary(libName); + libraryGroupStatus.append("[loaded]"); + } + catch (ThreadDeath tde) + { + groupLoaded = false; // not really needed because an error is being thrown + throw tde; // seriously out of luck, just throw the runtime exception + } + catch (VirtualMachineError vme) + { + groupLoaded = false; // not really needed because an error is being thrown + throw vme; // seriously out of luck, just throw the runtime exception + } + catch (Throwable t) + { + // the entire group fails to load if any one element fails to load + groupLoaded = false; + + // throwable instances that we can't recover from but can give some useful information for debugging. + // This code will differentiate between a library simply not existing from one which failed to load + // for some other reason. + + // Maps a library name into a platform-specific string representing a native library. + // Useful for producing the error message. + String libraryFileName = libraryFileAbsolutePath(libName); + if(libraryFileName == null) + libraryGroupStatus.append("[not found]"); + else + libraryGroupStatus.append("[found @ " + libraryFileName + ", but failed to load]"); + + err.append( t.getMessage() ); + } + + status.append(libraryGroupStatus + "\n"); + } + + loaded = groupLoaded; } - if (!loaded) { - err.append('('); - err.append(System.getProperty("java.library.path")); - err.append(')'); - throw new UnsatisfiedLinkError(err.toString()); + + if(! loaded) + { + err.append(status); + //System.out.println(status); + throw new UnsatisfiedLinkError(err.toString()); } } @@ -79,10 +127,35 @@ System.loadLibrary(libraryName); } + /** + * If the library exists on the library path then return the + * native path as a string, else return null. + * This method is intended to assist in providing useful error messages, other uses + * may be possible. + * + * @param libraryName + * @return + * @since 14March2011 + * @author cbeckey@gmail.com + */ + private String libraryFileAbsolutePath(String libraryName) + { + String systemDependentlibName = System.mapLibraryName(libraryName); + for( String libPathElement : libraryPaths ) + { + java.io.File fd = new java.io.File( libPathElement, systemDependentlibName ); + if( fd.exists() ) + return fd.getAbsolutePath(); + } + + return null; + } + /* create global TCN's APR pool * This has to be the first call to TCN library. */ private static native boolean initialize(); + /* destroy global TCN's APR pool * This has to be the last call to TCN library. */ @@ -133,23 +206,27 @@ public static boolean APR_HAS_LARGE_FILES = false; public static boolean APR_HAS_XTHREAD_FILES = false; public static boolean APR_HAS_OS_UUID = false; + /* Are we big endian? */ public static boolean APR_IS_BIGENDIAN = false; + /* APR sets APR_FILES_AS_SOCKETS to 1 on systems where it is possible * to poll on files/pipes. */ public static boolean APR_FILES_AS_SOCKETS = false; + /* This macro indicates whether or not EBCDIC is the native character set. */ public static boolean APR_CHARSET_EBCDIC = false; + /* Is the TCP_NODELAY socket option inherited from listening sockets? */ public static boolean APR_TCP_NODELAY_INHERITED = false; + /* Is the O_NONBLOCK flag inherited from listening sockets? */ public static boolean APR_O_NONBLOCK_INHERITED = false; - public static int APR_SIZEOF_VOIDP; public static int APR_PATH_MAX; public static int APRMAXHOSTLEN; @@ -162,18 +239,21 @@ public static native long globalPool(); /** - * Setup any APR internal data structures. This MUST be the first function - * called for any APR library. - * @param libraryName the name of the library to load + * Setup any APR internal data structures. This is THE entry point for this module and + * MUST be the first function called for any APR library. + * + * @param libraryName the name of the library to load, or null to load the default libraries. */ public static boolean initialize(String libraryName) - throws Exception + throws Exception { - if (_instance == null) { + if (_instance == null) + { if (libraryName == null) _instance = new Library(); else _instance = new Library(libraryName); + TCN_MAJOR_VERSION = version(0x01); TCN_MINOR_VERSION = version(0x02); TCN_PATCH_VERSION = version(0x03); @@ -212,13 +292,12 @@ APR_CHARSET_EBCDIC = has(18); APR_TCP_NODELAY_INHERITED = has(19); APR_O_NONBLOCK_INHERITED = has(20); - if (APR_MAJOR_VERSION < 1) { - throw new UnsatisfiedLinkError("Unsupported APR Version (" + - aprVersionString() + ")"); - } - if (!APR_HAS_THREADS) { + + if (APR_MAJOR_VERSION < 1) + throw new UnsatisfiedLinkError("Unsupported APR Version (" + aprVersionString() + ")"); + if (!APR_HAS_THREADS) throw new UnsatisfiedLinkError("Missing APR_HAS_THREADS"); - } + } return initialize(); } --- java/org/apache/tomcat/jni/SSL.java (revision 1081497) +++ java/org/apache/tomcat/jni/SSL.java (working copy) @@ -20,6 +20,7 @@ /** SSL * * @author Mladen Turk + * @author cbeckey@gmail.com added FIPS Mode * @version $Id$ */ @@ -241,6 +242,17 @@ public static native boolean randLoad(String filename); /** + * Enable/Disable FIPS Mode. + * @param mode 1 - enable, 0 - disable + * @return FIPS_mode_set return code + */ + public static int fipsModeSet(Integer mode) + { + return fipsModeSet(mode.intValue()); + } + public static native int fipsModeSet(int mode); + + /** * Writes a number of random bytes (currently 1024) to * file filename which can be used to initialize the PRNG * by calling randLoad in a later session.