Author: rgodfrey
Date: Wed Feb 24 13:30:55 2016
New Revision: 1732154

URL: http://svn.apache.org/viewvc?rev=1732154&view=rev
Log:
QPID-6995 : Keystores should log when then contain certificates which will soon 
be expiring

Added:
    
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/BrokerPrincipal.java
      - copied, changed from r1731900, 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostPrincipal.java
    
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/util/HousekeepingExecutor.java
   (with props)
Modified:
    
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/logging/messages/KeyStoreMessages.java
    
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/logging/messages/KeyStore_logmessages.properties
    
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java
    
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/KeyStore.java
    
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java
    
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/FileKeyStoreImpl.java
    
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java
    
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java
    
qpid/java/trunk/broker-core/src/test/java/org/apache/qpid/server/security/NonJavaKeyStoreTest.java

Copied: 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/BrokerPrincipal.java
 (from r1731900, 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostPrincipal.java)
URL: 
http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/BrokerPrincipal.java?p2=qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/BrokerPrincipal.java&p1=qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostPrincipal.java&r1=1731900&r2=1732154&rev=1732154&view=diff
==============================================================================
--- 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostPrincipal.java
 (original)
+++ 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/BrokerPrincipal.java
 Wed Feb 24 13:30:55 2016
@@ -18,21 +18,21 @@
  * under the License.
  *
  */
-package org.apache.qpid.server.virtualhost;
+package org.apache.qpid.server;
 
 import java.security.Principal;
 
-import org.apache.qpid.server.model.VirtualHost;
+import org.apache.qpid.server.model.Broker;
 
-public class VirtualHostPrincipal implements Principal
+public class BrokerPrincipal implements Principal
 {
-    private final VirtualHost<?> _virtualHost;
+    private final Broker<?> _broker;
     private final String _name;
 
-    public VirtualHostPrincipal(VirtualHost<?> virtualHost)
+    public BrokerPrincipal(Broker<?> broker)
     {
-        _virtualHost = virtualHost;
-        _name = "virtualhost:" + virtualHost.getName() + "-" + 
virtualHost.getId();
+        _broker = broker;
+        _name = "broker:" + broker.getName() + "-" + broker.getId();
     }
 
     @Override
@@ -53,13 +53,13 @@ public class VirtualHostPrincipal implem
             return false;
         }
 
-        VirtualHostPrincipal that = (VirtualHostPrincipal) o;
-        return _virtualHost.equals(that._virtualHost);
+        BrokerPrincipal that = (BrokerPrincipal) o;
+        return _broker.equals(that._broker);
     }
 
     @Override
     public int hashCode()
     {
-        return _virtualHost.hashCode();
+        return _broker.hashCode();
     }
 }

Modified: 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/logging/messages/KeyStoreMessages.java
URL: 
http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/logging/messages/KeyStoreMessages.java?rev=1732154&r1=1732153&r2=1732154&view=diff
==============================================================================
--- 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/logging/messages/KeyStoreMessages.java
 (original)
+++ 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/logging/messages/KeyStoreMessages.java
 Wed Feb 24 13:30:55 2016
@@ -46,6 +46,7 @@ public class KeyStoreMessages
 
     public static final String KEYSTORE_LOG_HIERARCHY = 
DEFAULT_LOG_HIERARCHY_PREFIX + "keystore";
     public static final String OPEN_LOG_HIERARCHY = 
DEFAULT_LOG_HIERARCHY_PREFIX + "keystore.open";
+    public static final String EXPIRING_LOG_HIERARCHY = 
DEFAULT_LOG_HIERARCHY_PREFIX + "keystore.expiring";
     public static final String CREATE_LOG_HIERARCHY = 
DEFAULT_LOG_HIERARCHY_PREFIX + "keystore.create";
     public static final String DELETE_LOG_HIERARCHY = 
DEFAULT_LOG_HIERARCHY_PREFIX + "keystore.delete";
     public static final String CLOSE_LOG_HIERARCHY = 
DEFAULT_LOG_HIERARCHY_PREFIX + "keystore.close";
@@ -54,6 +55,7 @@ public class KeyStoreMessages
     {
         LoggerFactory.getLogger(KEYSTORE_LOG_HIERARCHY);
         LoggerFactory.getLogger(OPEN_LOG_HIERARCHY);
+        LoggerFactory.getLogger(EXPIRING_LOG_HIERARCHY);
         LoggerFactory.getLogger(CREATE_LOG_HIERARCHY);
         LoggerFactory.getLogger(DELETE_LOG_HIERARCHY);
         LoggerFactory.getLogger(CLOSE_LOG_HIERARCHY);
@@ -87,6 +89,64 @@ public class KeyStoreMessages
             }
 
             @Override
+            public boolean equals(final Object o)
+            {
+                if (this == o)
+                {
+                    return true;
+                }
+                if (o == null || getClass() != o.getClass())
+                {
+                    return false;
+                }
+
+                final LogMessage that = (LogMessage) o;
+
+                return getLogHierarchy().equals(that.getLogHierarchy()) && 
toString().equals(that.toString());
+
+            }
+
+            @Override
+            public int hashCode()
+            {
+                int result = toString().hashCode();
+                result = 31 * result + getLogHierarchy().hashCode();
+                return result;
+            }
+        };
+    }
+
+    /**
+     * Log a KeyStore message of the Format:
+     * <pre>KST-1005 : KeyStore {0} Certificate expires in {1} days : {2}</pre>
+     * Optional values are contained in [square brackets] and are numbered
+     * sequentially in the method call.
+     *
+     */
+    public static LogMessage EXPIRING(String param1, String param2, String 
param3)
+    {
+        String rawMessage = _messages.getString("EXPIRING");
+
+        final Object[] messageArguments = {param1, param2, param3};
+        // Create a new MessageFormat to ensure thread safety.
+        // Sharing a MessageFormat and using applyPattern is not thread safe
+        MessageFormat formatter = new MessageFormat(rawMessage, 
_currentLocale);
+
+        final String message = formatter.format(messageArguments);
+
+        return new LogMessage()
+        {
+            public String toString()
+            {
+                return message;
+            }
+
+            public String getLogHierarchy()
+            {
+                return EXPIRING_LOG_HIERARCHY;
+            }
+
+            @Override
             public boolean equals(final Object o)
             {
                 if (this == o)

Modified: 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/logging/messages/KeyStore_logmessages.properties
URL: 
http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/logging/messages/KeyStore_logmessages.properties?rev=1732154&r1=1732153&r2=1732154&view=diff
==============================================================================
--- 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/logging/messages/KeyStore_logmessages.properties
 (original)
+++ 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/logging/messages/KeyStore_logmessages.properties
 Wed Feb 24 13:30:55 2016
@@ -22,4 +22,5 @@ CREATE = KST-1001 : Create "{0}"
 OPEN = KST-1002 : Open
 CLOSE = KST-1003 : Close
 DELETE = KST-1004 : Delete "{0}"
+EXPIRING = KST-1005 : KeyStore {0} Certificate expires in {1} days : {2}
 

Modified: 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java
URL: 
http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java?rev=1732154&r1=1732153&r2=1732154&view=diff
==============================================================================
--- 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java
 (original)
+++ 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java
 Wed Feb 24 13:30:55 2016
@@ -22,6 +22,8 @@ package org.apache.qpid.server.model;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.qpid.configuration.CommonProperties;
 import org.apache.qpid.server.logging.EventLogger;
@@ -134,6 +136,15 @@ public interface Broker<X extends Broker
     @ManagedAttribute( defaultValue = "false")
     boolean getStatisticsReportingResetEnabled();
 
+
+    @ManagedContextDefault( name = "broker.housekeepingThreadCount")
+    public static final int DEFAULT_HOUSEKEEPING_THREAD_COUNT = 2;
+
+
+    @ManagedAttribute( defaultValue = "${broker.housekeepingThreadCount}")
+    int getHousekeepingThreadCount();
+
+
     String BROKER_MESSAGE_COMPRESSION_ENABLED = 
"broker.messageCompressionEnabled";
     @ManagedContextDefault(name = BROKER_MESSAGE_COMPRESSION_ENABLED)
     boolean DEFAULT_MESSAGE_COMPRESSION_ENABLED = true;
@@ -267,4 +278,9 @@ public interface Broker<X extends Broker
     void assignTargetSizes();
 
     int getNetworkBufferSize();
+
+    ScheduledFuture<?> scheduleHouseKeepingTask(long period, final TimeUnit 
unit, Runnable task);
+
+    ScheduledFuture<?> scheduleTask(long delay, final TimeUnit unit, Runnable 
task);
+
 }

Modified: 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/KeyStore.java
URL: 
http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/KeyStore.java?rev=1732154&r1=1732153&r2=1732154&view=diff
==============================================================================
--- 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/KeyStore.java
 (original)
+++ 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/KeyStore.java
 Wed Feb 24 13:30:55 2016
@@ -26,5 +26,16 @@ import javax.net.ssl.KeyManager;
 @ManagedObject( defaultType = "FileKeyStore" )
 public interface KeyStore<X extends KeyStore<X>> extends ConfiguredObject<X>
 {
-    public KeyManager[] getKeyManagers() throws GeneralSecurityException;
+    String CERTIFICATE_EXPIRY_WARN_PERIOD = 
"qpid.keystore.certificateExpiryWarnPeriod";
+
+    @ManagedContextDefault(name = CERTIFICATE_EXPIRY_WARN_PERIOD)
+    int DEFAULT_CERTIFICATE_EXPIRY_WARN_PERIOD = 30;
+
+    String CERTIFICATE_EXPIRY_CHECK_FREQUENCY = 
"qpid.keystore.certificateExpiryCheckFrequency";
+
+    @ManagedContextDefault(name = CERTIFICATE_EXPIRY_CHECK_FREQUENCY)
+    int DEFAULT_CERTIFICATE_EXPIRY_CHECK_FREQUENCY = 1;
+
+
+    KeyManager[] getKeyManagers() throws GeneralSecurityException;
 }

Modified: 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java
URL: 
http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java?rev=1732154&r1=1732153&r2=1732154&view=diff
==============================================================================
--- 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java
 (original)
+++ 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java
 Wed Feb 24 13:30:55 2016
@@ -44,6 +44,8 @@ import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.UUID;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -52,9 +54,11 @@ import javax.security.auth.Subject;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import org.apache.qpid.bytebuffer.QpidByteBuffer;
+import org.apache.qpid.server.BrokerPrincipal;
 import org.apache.qpid.server.logging.QpidLoggerTurboFilter;
 import org.apache.qpid.server.logging.StartupAppender;
 import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.util.HousekeepingExecutor;
 import org.apache.qpid.server.util.ServerScopedRuntimeException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -85,8 +89,10 @@ public class BrokerAdapter extends Abstr
 
     private static final Pattern MODEL_VERSION_PATTERN = 
Pattern.compile("^\\d+\\.\\d+$");
 
+    private static final int HOUSEKEEPING_SHUTDOWN_TIMEOUT = 5;
 
     public static final String MANAGEMENT_MODE_AUTHENTICATION = 
"MANAGEMENT_MODE_AUTHENTICATION";
+    private final BrokerPrincipal _principal;
 
     private String[] POSITIVE_NUMERIC_ATTRIBUTES = { 
CONNECTION_SESSION_COUNT_LIMIT,
             CONNECTION_HEART_BEAT_DELAY, STATISTICS_REPORTING_PERIOD };
@@ -118,6 +124,8 @@ public class BrokerAdapter extends Abstr
     private boolean _statisticsReportingResetEnabled;
     @ManagedAttributeField
     private boolean _messageCompressionEnabled;
+    @ManagedAttributeField
+    private int _housekeepingThreadCount;
 
     @ManagedAttributeField(afterSet = "postEncrypterProviderSet")
     private String _confidentialConfigurationEncryptionProvider;
@@ -129,6 +137,7 @@ public class BrokerAdapter extends Abstr
     private final long _maximumDirectMemorySize = getMaxDirectMemorySize();
     private final BufferPoolMXBean _bufferPoolMXBean;
     private final List<String> _jvmArguments;
+    private HousekeepingExecutor _houseKeepingTaskExecutor;
 
     @ManagedObjectFactoryConstructor
     public BrokerAdapter(Map<String, Object> attributes,
@@ -138,6 +147,8 @@ public class BrokerAdapter extends Abstr
         _parent = parent;
         _eventLogger = parent.getEventLogger();
         _securityManager = new SecurityManager(this, 
parent.isManagementMode());
+        _principal = new BrokerPrincipal(this);
+
         if (parent.isManagementMode())
         {
             Map<String,Object> authManagerAttrs = new HashMap<String, 
Object>();
@@ -366,6 +377,11 @@ public class BrokerAdapter extends Abstr
 
         initialiseStatisticsReporting();
 
+        _houseKeepingTaskExecutor = new HousekeepingExecutor("virtualhost-" + 
getName() + "-pool",
+                                                             
getHousekeepingThreadCount(),
+                                                             _principal);
+
+
         if (isManagementMode())
         {
             
_eventLogger.message(BrokerMessages.MANAGEMENT_MODE(BrokerOptions.MANAGEMENT_MODE_USER_NAME,
@@ -470,6 +486,12 @@ public class BrokerAdapter extends Abstr
     }
 
     @Override
+    public int getHousekeepingThreadCount()
+    {
+        return _housekeepingThreadCount;
+    }
+
+    @Override
     public String getModelVersion()
     {
         return BrokerModel.MODEL_VERSION;
@@ -666,6 +688,8 @@ public class BrokerAdapter extends Abstr
             _reportingTimer.cancel();
         }
 
+        shutdownHouseKeeping();
+
         _eventLogger.message(BrokerMessages.STOPPED());
 
         try
@@ -1196,6 +1220,39 @@ public class BrokerAdapter extends Abstr
         return dump.toString();
     }
 
+    protected void shutdownHouseKeeping()
+    {
+        if(_houseKeepingTaskExecutor != null)
+        {
+            _houseKeepingTaskExecutor.shutdown();
+
+            try
+            {
+                if 
(!_houseKeepingTaskExecutor.awaitTermination(HOUSEKEEPING_SHUTDOWN_TIMEOUT, 
TimeUnit.SECONDS))
+                {
+                    _houseKeepingTaskExecutor.shutdownNow();
+                }
+            }
+            catch (InterruptedException e)
+            {
+                LOGGER.warn("Interrupted during Housekeeping shutdown:", e);
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleHouseKeepingTask(long period, final 
TimeUnit unit, Runnable task)
+    {
+        return _houseKeepingTaskExecutor.scheduleAtFixedRate(task, period / 2, 
period, unit);
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleTask(long delay, final TimeUnit unit, 
Runnable task)
+    {
+        return _houseKeepingTaskExecutor.schedule(task, delay, unit);
+    }
+
     public static class ThreadStackContent implements Content, 
CustomRestHeaders
     {
         private final String _threadStackTraces;

Modified: 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/FileKeyStoreImpl.java
URL: 
http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/FileKeyStoreImpl.java?rev=1732154&r1=1732153&r2=1732154&view=diff
==============================================================================
--- 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/FileKeyStoreImpl.java
 (original)
+++ 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/FileKeyStoreImpl.java
 Wed Feb 24 13:30:55 2016
@@ -29,16 +29,26 @@ import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.UnrecoverableKeyException;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.X509KeyManager;
 
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import org.apache.qpid.server.configuration.IllegalConfigurationException;
 import org.apache.qpid.server.logging.EventLogger;
@@ -62,6 +72,9 @@ import org.apache.qpid.transport.network
 @ManagedObject( category = false )
 public class FileKeyStoreImpl extends 
AbstractConfiguredObject<FileKeyStoreImpl> implements 
FileKeyStore<FileKeyStoreImpl>
 {
+    private static Logger LOGGER = 
LoggerFactory.getLogger(FileKeyStoreImpl.class);
+
+    private static final long ONE_DAY = 24l * 60l * 60l * 1000l;
     private final Broker<?> _broker;
     private final EventLogger _eventLogger;
 
@@ -85,6 +98,8 @@ public class FileKeyStoreImpl extends Ab
         Handler.register();
     }
 
+    private ScheduledFuture<?> _checkExpiryTaskFuture;
+
     @ManagedObjectFactoryConstructor
     public FileKeyStoreImpl(Map<String, Object> attributes, Broker<?> broker)
     {
@@ -96,6 +111,41 @@ public class FileKeyStoreImpl extends Ab
     }
 
     @Override
+    protected void onOpen()
+    {
+        super.onOpen();
+        int checkFrequency;
+        try
+        {
+            checkFrequency = getContextValue(Integer.class, 
CERTIFICATE_EXPIRY_CHECK_FREQUENCY);
+        }
+        catch(IllegalArgumentException | NullPointerException e)
+        {
+            LOGGER.warn("Cannot parse the context variable {} ", 
CERTIFICATE_EXPIRY_CHECK_FREQUENCY, e);
+            checkFrequency = DEFAULT_CERTIFICATE_EXPIRY_CHECK_FREQUENCY;
+        }
+        _checkExpiryTaskFuture = 
_broker.scheduleHouseKeepingTask(checkFrequency, TimeUnit.DAYS, new Runnable()
+                                {
+                                    @Override
+                                    public void run()
+                                    {
+                                        checkCertificateExpiry();
+                                    }
+                                });
+    }
+
+    @Override
+    protected void onClose()
+    {
+        super.onClose();
+        if(_checkExpiryTaskFuture != null)
+        {
+            _checkExpiryTaskFuture.cancel(false);
+            _checkExpiryTaskFuture = null;
+        }
+    }
+
+    @Override
     public void onValidate()
     {
         super.onValidate();
@@ -143,6 +193,7 @@ public class FileKeyStoreImpl extends Ab
             throw new IllegalConfigurationException("Changing the key store 
name is not allowed");
         }
         validateKeyStoreAttributes(changedStore);
+        checkCertificateExpiry();
     }
 
     private void validateKeyStoreAttributes(FileKeyStore<?> fileKeyStore)
@@ -155,7 +206,6 @@ public class FileKeyStoreImpl extends Ab
             String keyStoreType = fileKeyStore.getKeyStoreType();
             keyStore = SSLUtil.getInitializedKeyStore(url, password, 
keyStoreType);
         }
-
         catch (Exception e)
         {
             final String message;
@@ -308,4 +358,74 @@ public class FileKeyStoreImpl extends Ab
             _path = null;
         }
     }
+
+    private void checkCertificateExpiry()
+    {
+        try
+        {
+
+            int expiryWarning = getContextValue(Integer.class, 
CERTIFICATE_EXPIRY_WARN_PERIOD);
+            if(expiryWarning > 0)
+            {
+                long currentTime = System.currentTimeMillis();
+                Date expiryTestDate = new Date(currentTime + (ONE_DAY * 
(long)expiryWarning));
+
+                try
+                {
+                    URL url = getUrlFromString(_storeUrl);
+                    final java.security.KeyStore ks = 
SSLUtil.getInitializedKeyStore(url, getPassword(), _keyStoreType);
+
+                    char[] keyStoreCharPassword = getPassword() == null ? null 
: getPassword().toCharArray();
+
+                    final KeyManagerFactory kmf = 
KeyManagerFactory.getInstance(_keyManagerFactoryAlgorithm);
+
+                    kmf.init(ks, keyStoreCharPassword);
+
+
+                    for (KeyManager km : kmf.getKeyManagers())
+                    {
+                        if (km instanceof X509KeyManager)
+                        {
+                            X509KeyManager x509KeyManager = (X509KeyManager) 
km;
+
+                            for(String alias : Collections.list(ks.aliases()))
+                            {
+                                final X509Certificate[] chain =
+                                        
x509KeyManager.getCertificateChain(alias);
+                                if(chain != null)
+                                {
+                                    for(X509Certificate cert : chain)
+                                    {
+                                        try
+                                        {
+                                            cert.checkValidity(expiryTestDate);
+                                        }
+                                        catch(CertificateExpiredException e)
+                                        {
+                                            long timeToExpiry = 
cert.getNotAfter().getTime() - currentTime;
+                                            int days = 
Math.max(0,(int)(timeToExpiry / (ONE_DAY)));
+
+                                            
_eventLogger.message(KeyStoreMessages.EXPIRING(getName(), String.valueOf(days), 
cert.getSubjectDN().toString()));
+                                        }
+                                        catch(CertificateNotYetValidException 
e)
+                                        {
+                                            // ignore
+                                        }
+                                    }
+                                }
+                            }
+
+                        }
+                    }
+                }
+                catch (GeneralSecurityException | IOException e)
+                {
+
+                }
+            }
+        }
+        catch(IllegalArgumentException | NullPointerException e)
+        {
+        }
+    }
 }

Modified: 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java
URL: 
http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java?rev=1732154&r1=1732153&r2=1732154&view=diff
==============================================================================
--- 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java
 (original)
+++ 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java
 Wed Feb 24 13:30:55 2016
@@ -29,13 +29,18 @@ import java.nio.charset.StandardCharsets
 import java.security.GeneralSecurityException;
 import java.security.PrivateKey;
 import java.security.SecureRandom;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 
 import javax.naming.InvalidNameException;
 import javax.naming.ldap.LdapName;
@@ -68,6 +73,7 @@ import org.apache.qpid.transport.network
 public class NonJavaKeyStoreImpl extends 
AbstractConfiguredObject<NonJavaKeyStoreImpl> implements 
NonJavaKeyStore<NonJavaKeyStoreImpl>
 {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(NonJavaKeyStoreImpl.class);
+    private static final long ONE_DAY = 24l * 60l * 60l * 1000l;
 
     private final Broker<?> _broker;
     private final EventLogger _eventLogger;
@@ -90,6 +96,9 @@ public class NonJavaKeyStoreImpl extends
 
     private X509Certificate _certificate;
 
+    private ScheduledFuture<?> _checkExpiryTaskFuture;
+    private int _checkFrequency;
+
     @ManagedObjectFactoryConstructor
     public NonJavaKeyStoreImpl(final Map<String, Object> attributes, Broker<?> 
broker)
     {
@@ -100,6 +109,43 @@ public class NonJavaKeyStoreImpl extends
     }
 
     @Override
+    protected void onOpen()
+    {
+        super.onOpen();
+        int checkFrequency;
+        try
+        {
+            checkFrequency = getContextValue(Integer.class, 
CERTIFICATE_EXPIRY_CHECK_FREQUENCY);
+        }
+        catch(IllegalArgumentException | NullPointerException e)
+        {
+            LOGGER.warn("Cannot parse the context variable {} ", 
CERTIFICATE_EXPIRY_CHECK_FREQUENCY, e);
+            checkFrequency = DEFAULT_CERTIFICATE_EXPIRY_CHECK_FREQUENCY;
+        }
+
+        _checkExpiryTaskFuture =
+                _broker.scheduleHouseKeepingTask(checkFrequency, 
TimeUnit.DAYS, new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        checkCertificateExpiry();
+                    }
+                });
+    }
+
+    @Override
+    protected void onClose()
+    {
+        super.onClose();
+        if(_checkExpiryTaskFuture != null)
+        {
+            _checkExpiryTaskFuture.cancel(false);
+            _checkExpiryTaskFuture = null;
+        }
+    }
+
+    @Override
     public String getPrivateKeyUrl()
     {
         return _privateKeyUrl;
@@ -250,7 +296,7 @@ public class NonJavaKeyStoreImpl extends
                     
allCerts.addAll(Arrays.asList(SSLUtil.readCertificates(getUrlFromString(_intermediateCertificateUrl))));
                     certs = allCerts.toArray(new 
X509Certificate[allCerts.size()]);
                 }
-
+                checkCertificateExpiry(certs);
                 java.security.KeyStore inMemoryKeyStore = 
java.security.KeyStore.getInstance(java.security.KeyStore.getDefaultType());
 
                 byte[] bytes = new byte[64];
@@ -274,6 +320,63 @@ public class NonJavaKeyStoreImpl extends
         }
     }
 
+    private void checkCertificateExpiry()
+    {
+        try
+        {
+            if (_privateKeyUrl != null && _certificateUrl != null)
+            {
+                X509Certificate[] certs = 
SSLUtil.readCertificates(getUrlFromString(_certificateUrl));
+                if (_intermediateCertificateUrl != null)
+                {
+                    List<X509Certificate> allCerts = new 
ArrayList<>(Arrays.asList(certs));
+                    
allCerts.addAll(Arrays.asList(SSLUtil.readCertificates(getUrlFromString(_intermediateCertificateUrl))));
+                    certs = allCerts.toArray(new 
X509Certificate[allCerts.size()]);
+                }
+                checkCertificateExpiry(certs);
+            }
+        }
+        catch (GeneralSecurityException | IOException e)
+        {
+            LOGGER.info("Unexpected exception while trying to check 
certificate validity", e);
+        }
+    }
+
+    private void checkCertificateExpiry(final X509Certificate... certificates)
+    {
+        int expiryWarning = getContextValue(Integer.class, 
CERTIFICATE_EXPIRY_WARN_PERIOD);
+        if(expiryWarning > 0)
+        {
+            long currentTime = System.currentTimeMillis();
+            Date expiryTestDate = new Date(currentTime + (ONE_DAY * (long) 
expiryWarning));
+
+
+            if (certificates != null)
+            {
+                for (X509Certificate cert : certificates)
+                {
+                    try
+                    {
+                        cert.checkValidity(expiryTestDate);
+                    }
+                    catch (CertificateExpiredException e)
+                    {
+                        long timeToExpiry = cert.getNotAfter().getTime() - 
currentTime;
+                        int days = Math.max(0, (int) (timeToExpiry / 
(ONE_DAY)));
+
+                        
_eventLogger.message(KeyStoreMessages.EXPIRING(getName(),
+                                                                       
String.valueOf(days),
+                                                                       
cert.getSubjectDN().toString()));
+                    }
+                    catch (CertificateNotYetValidException e)
+                    {
+                        // ignore
+                    }
+                }
+            }
+        }
+    }
+
     private URL getUrlFromString(String urlString) throws MalformedURLException
     {
         URL url;

Added: 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/util/HousekeepingExecutor.java
URL: 
http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/util/HousekeepingExecutor.java?rev=1732154&view=auto
==============================================================================
--- 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/util/HousekeepingExecutor.java
 (added)
+++ 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/util/HousekeepingExecutor.java
 Wed Feb 24 13:30:55 2016
@@ -0,0 +1,101 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.util;
+
+import java.security.Principal;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+import com.google.common.util.concurrent.UncheckedExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import 
org.apache.qpid.pool.SuppressingInheritedAccessControlContextThreadFactory;
+import org.apache.qpid.server.security.SecurityManager;
+
+public class HousekeepingExecutor extends ScheduledThreadPoolExecutor
+{
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(HousekeepingExecutor.class);
+
+    public HousekeepingExecutor(final String threadPrefix, final int 
threadCount, final Principal principal)
+    {
+        super(threadCount, createThreadFactory(threadPrefix, threadCount, 
principal));
+
+    }
+
+    private static SuppressingInheritedAccessControlContextThreadFactory 
createThreadFactory(String threadPrefix, int threadCount, Principal principal)
+    {
+        return new 
SuppressingInheritedAccessControlContextThreadFactory(threadPrefix,
+                                                                          
SecurityManager.getSystemTaskSubject("Housekeeping", principal));
+
+    }
+
+    @Override
+    protected void afterExecute(Runnable r, Throwable t)
+    {
+        super.afterExecute(r, t);
+        if (t == null && r instanceof Future<?>)
+        {
+            Future future = (Future<?>) r;
+            try
+            {
+                if (future.isDone())
+                {
+                    Object result = future.get();
+                }
+            }
+            catch (CancellationException ce)
+            {
+                LOGGER.debug("Housekeeping task got cancelled");
+                // Ignore cancellation of task
+            }
+            catch (ExecutionException | UncheckedExecutionException ee)
+            {
+                t = ee.getCause();
+            }
+            catch (InterruptedException ie)
+            {
+                Thread.currentThread().interrupt(); // ignore/reset
+            }
+            catch (Throwable t1)
+            {
+                t = t1;
+            }
+        }
+        if (t != null)
+        {
+            LOGGER.error("Houskeeping task threw an exception:", t);
+
+            final Thread.UncaughtExceptionHandler uncaughtExceptionHandler = 
Thread.getDefaultUncaughtExceptionHandler();
+            if (uncaughtExceptionHandler != null)
+            {
+                
uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), t);
+            }
+            else
+            {
+                Runtime.getRuntime().halt(1);
+            }
+        }
+    }
+}

Propchange: 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/util/HousekeepingExecutor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java
URL: 
http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java?rev=1732154&r1=1732153&r2=1732154&view=diff
==============================================================================
--- 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java
 (original)
+++ 
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java
 Wed Feb 24 13:30:55 2016
@@ -38,8 +38,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.*;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.regex.Pattern;
@@ -53,7 +51,6 @@ import com.google.common.util.concurrent
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.SettableFuture;
-import com.google.common.util.concurrent.UncheckedExecutionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -99,6 +96,7 @@ import org.apache.qpid.server.txn.LocalT
 import org.apache.qpid.server.txn.ServerTransaction;
 import org.apache.qpid.server.util.Action;
 import org.apache.qpid.server.util.ConnectionScopedRuntimeException;
+import org.apache.qpid.server.util.HousekeepingExecutor;
 import org.apache.qpid.server.util.MapValueConverter;
 
 public abstract class AbstractVirtualHost<X extends AbstractVirtualHost<X>> 
extends AbstractConfiguredObject<X>
@@ -1838,58 +1836,10 @@ public abstract class AbstractVirtualHos
     @StateTransition(currentState = {State.UNINITIALIZED, State.ERRORED}, 
desiredState = State.ACTIVE)
     private ListenableFuture<Void> onActivate()
     {
-        final SuppressingInheritedAccessControlContextThreadFactory 
housekeepingThreadFactory =
-                new 
SuppressingInheritedAccessControlContextThreadFactory("virtualhost-" + 
getName() + "-pool",
-                                                                          
SecurityManager.getSystemTaskSubject("Housekeeping", getPrincipal()));
-        _houseKeepingTaskExecutor = new 
ScheduledThreadPoolExecutor(getHousekeepingThreadCount(), 
housekeepingThreadFactory){
-            @Override
-            protected void afterExecute(Runnable r, Throwable t)
-            {
-                super.afterExecute(r, t);
-                if (t == null && r instanceof Future<?>)
-                {
-                    Future future = (Future<?>) r;
-                    try
-                    {
-                        if (future.isDone())
-                        {
-                            Object result = future.get();
-                        }
-                    }
-                    catch (CancellationException ce)
-                    {
-                        _logger.debug("Housekeeping task got cancelled");
-                        // Ignore cancellation of task
-                    }
-                    catch (ExecutionException | UncheckedExecutionException ee)
-                    {
-                        t = ee.getCause();
-                    }
-                    catch (InterruptedException ie)
-                    {
-                        Thread.currentThread().interrupt(); // ignore/reset
-                    }
-                    catch (Throwable t1)
-                    {
-                        t = t1;
-                    }
-                }
-                if (t != null)
-                {
-                    _logger.error("Houskeeping task threw an exception:", t);
 
-                    final Thread.UncaughtExceptionHandler 
uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
-                    if (uncaughtExceptionHandler != null)
-                    {
-                        
uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), t);
-                    }
-                    else
-                    {
-                        Runtime.getRuntime().halt(1);
-                    }
-                }
-            }
-        };
+        _houseKeepingTaskExecutor = new HousekeepingExecutor("virtualhost-" + 
getName() + "-pool",
+                                                             
getHousekeepingThreadCount(),
+                                                             getPrincipal());
 
         long threadPoolKeepAliveTimeout = getContextValue(Long.class, 
CONNECTION_THREAD_POOL_KEEP_ALIVE_TIMEOUT);
 

Modified: 
qpid/java/trunk/broker-core/src/test/java/org/apache/qpid/server/security/NonJavaKeyStoreTest.java
URL: 
http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/test/java/org/apache/qpid/server/security/NonJavaKeyStoreTest.java?rev=1732154&r1=1732153&r2=1732154&view=diff
==============================================================================
--- 
qpid/java/trunk/broker-core/src/test/java/org/apache/qpid/server/security/NonJavaKeyStoreTest.java
 (original)
+++ 
qpid/java/trunk/broker-core/src/test/java/org/apache/qpid/server/security/NonJavaKeyStoreTest.java
 Wed Feb 24 13:30:55 2016
@@ -21,8 +21,16 @@ package org.apache.qpid.server.security;
 
 
 import static org.apache.qpid.test.utils.TestSSLConstants.KEYSTORE_PASSWORD;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
 
 import javax.net.ssl.KeyManager;
 import javax.xml.bind.DatatypeConverter;
@@ -32,16 +40,28 @@ import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.security.Key;
 import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import org.apache.qpid.server.configuration.IllegalConfigurationException;
 import org.apache.qpid.server.configuration.updater.CurrentThreadTaskExecutor;
 import org.apache.qpid.server.configuration.updater.TaskExecutor;
 import org.apache.qpid.server.logging.EventLogger;
+import org.apache.qpid.server.logging.LogMessage;
+import org.apache.qpid.server.logging.MessageLogger;
+import org.apache.qpid.server.logging.messages.KeyStoreMessages;
 import org.apache.qpid.server.model.Broker;
 import org.apache.qpid.server.model.BrokerModel;
 import org.apache.qpid.server.model.ConfiguredObjectFactory;
@@ -58,17 +78,18 @@ public class NonJavaKeyStoreTest extends
     private final Model _model = BrokerModel.getInstance();
     private final ConfiguredObjectFactory _factory = _model.getObjectFactory();
     private List<File> _testResources;
+    private MessageLogger _messageLogger;
 
     @Override
     public void setUp() throws Exception
     {
         super.setUp();
-
+        _messageLogger = mock(MessageLogger.class);
         when(_broker.getTaskExecutor()).thenReturn(_taskExecutor);
         when(_broker.getChildExecutor()).thenReturn(_taskExecutor);
         when(_broker.getModel()).thenReturn(_model);
         when(_broker.getSecurityManager()).thenReturn(_securityManager);
-        when(_broker.getEventLogger()).thenReturn(new EventLogger());
+        when(_broker.getEventLogger()).thenReturn(new 
EventLogger(_messageLogger));
         _testResources = new ArrayList<>();
     }
 
@@ -237,4 +258,55 @@ public class NonJavaKeyStoreTest extends
             // pass
         }
     }
+
+    public void testExpiryCheckingFindsExpired() throws Exception
+    {
+        doCertExpiryChecking(1);
+
+        verify(_messageLogger, times(1)).message(argThat(new 
LogMessageArgumentMatcher()));
+
+    }
+
+    public void testExpiryCheckingIgnoresValid() throws Exception
+    {
+        doCertExpiryChecking(-1);
+
+        verify(_messageLogger, never()).message(argThat(new 
LogMessageArgumentMatcher()));
+
+    }
+
+    private void doCertExpiryChecking(final int expiryOffset) throws Exception
+    {
+        when(_broker.scheduleHouseKeepingTask(anyLong(), any(TimeUnit.class), 
any(Runnable.class))).thenReturn(mock(ScheduledFuture.class));
+
+        java.security.KeyStore ks = 
java.security.KeyStore.getInstance(java.security.KeyStore.getDefaultType());
+        try(InputStream is = 
getClass().getResourceAsStream("/java_broker_keystore.jks"))
+        {
+            ks.load(is, KEYSTORE_PASSWORD.toCharArray() );
+        }
+        X509Certificate cert = (X509Certificate) ks.getCertificate("rootca");
+        int expiryDays = (int)((cert.getNotAfter().getTime() - 
System.currentTimeMillis()) / (24l * 60l * 60l * 1000l));
+
+        File[] resources = extractResourcesFromTestKeyStore(false);
+        _testResources.addAll(Arrays.asList(resources));
+
+        Map<String,Object> attributes = new HashMap<>();
+        attributes.put(NonJavaKeyStore.NAME, "myTestTrustStore");
+        attributes.put("privateKeyUrl", 
resources[0].toURI().toURL().toExternalForm());
+        attributes.put("certificateUrl", 
resources[1].toURI().toURL().toExternalForm());
+        attributes.put("context", 
Collections.singletonMap(KeyStore.CERTIFICATE_EXPIRY_WARN_PERIOD, expiryDays + 
expiryOffset));
+        attributes.put(NonJavaKeyStore.TYPE, "NonJavaKeyStore");
+        _factory.create(KeyStore.class, attributes, _broker);
+    }
+
+
+    private static class LogMessageArgumentMatcher extends 
ArgumentMatcher<LogMessage>
+    {
+        @Override
+        public boolean matches(final Object argument)
+        {
+            LogMessage arg = (LogMessage)argument;
+            return 
arg.getLogHierarchy().equals(KeyStoreMessages.EXPIRING_LOG_HIERARCHY);
+        }
+    }
 }




---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to