blob: 4d19adaa86a2d7380231c53f65eee0a9561c53b3 [file] [log] [blame]
/*
* 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.security;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import com.google.common.collect.Sets;
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;
import org.apache.qpid.server.logging.messages.TrustStoreMessages;
import org.apache.qpid.server.model.AbstractConfigurationChangeListener;
import org.apache.qpid.server.model.AbstractConfiguredObject;
import org.apache.qpid.server.model.Broker;
import org.apache.qpid.server.model.ConfiguredObject;
import org.apache.qpid.server.model.ManagedAttributeField;
import org.apache.qpid.server.model.State;
import org.apache.qpid.server.model.TrustStore;
import org.apache.qpid.server.model.VirtualHostNode;
public abstract class AbstractTrustStore<X extends AbstractTrustStore<X>>
extends AbstractConfiguredObject<X> implements TrustStore<X>
{
private static Logger LOGGER = LoggerFactory.getLogger(AbstractTrustStore.class);
protected static final long ONE_DAY = 24L * 60L * 60L * 1000L;
private final Broker<?> _broker;
private final EventLogger _eventLogger;
@ManagedAttributeField
private boolean _exposedAsMessageSource;
@ManagedAttributeField
private List<VirtualHostNode<?>> _includedVirtualHostNodeMessageSources;
@ManagedAttributeField
private List<VirtualHostNode<?>> _excludedVirtualHostNodeMessageSources;
@ManagedAttributeField
private boolean _trustAnchorValidityEnforced;
private ScheduledFuture<?> _checkExpiryTaskFuture;
AbstractTrustStore(Map<String, Object> attributes, Broker<?> broker)
{
super(broker, attributes);
_broker = broker;
_eventLogger = broker.getEventLogger();
_eventLogger.message(TrustStoreMessages.CREATE(getName()));
}
public final Broker<?> getBroker()
{
return _broker;
}
final EventLogger getEventLogger()
{
return _eventLogger;
}
@Override
protected ListenableFuture<Void> onClose()
{
onCloseOrDelete();
return Futures.immediateFuture(null);
}
private void onCloseOrDelete()
{
if(_checkExpiryTaskFuture != null)
{
_checkExpiryTaskFuture.cancel(false);
_checkExpiryTaskFuture = null;
}
}
@Override
protected void logOperation(final String operation)
{
_broker.getEventLogger().message(TrustStoreMessages.OPERATION(operation));
}
void initializeExpiryChecking()
{
int checkFrequency = getCertificateExpiryCheckFrequency();
if(getBroker().getState() == State.ACTIVE)
{
_checkExpiryTaskFuture = getBroker().scheduleHouseKeepingTask(checkFrequency, TimeUnit.DAYS,
this::checkCertificateExpiry);
}
else
{
final int frequency = checkFrequency;
getBroker().addChangeListener(new AbstractConfigurationChangeListener()
{
@Override
public void stateChanged(final ConfiguredObject<?> object, final State oldState, final State newState)
{
if (newState == State.ACTIVE)
{
_checkExpiryTaskFuture =
getBroker().scheduleHouseKeepingTask(frequency, TimeUnit.DAYS,
() -> checkCertificateExpiry());
getBroker().removeChangeListener(this);
}
}
});
}
}
@Override
protected ListenableFuture<Void> onDelete()
{
onCloseOrDelete();
_eventLogger.message(TrustStoreMessages.DELETE(getName()));
return super.onDelete();
}
private void checkCertificateExpiry()
{
int expiryWarning = getCertificateExpiryWarnPeriod();
if(expiryWarning > 0)
{
long currentTime = System.currentTimeMillis();
Date expiryTestDate = new Date(currentTime + (ONE_DAY * (long) expiryWarning));
try
{
Certificate[] certificatesInternal = getCertificates();
if (certificatesInternal.length > 0)
{
Arrays.stream(certificatesInternal)
.filter(cert -> cert instanceof X509Certificate)
.forEach(x509cert -> checkCertificateExpiry(currentTime, expiryTestDate, (X509Certificate) x509cert));
}
}
catch (GeneralSecurityException e)
{
LOGGER.debug("Unexpected exception whilst checking certificate expiry", e);
}
}
}
private void checkCertificateExpiry(final long currentTime,
final Date expiryTestDate,
final X509Certificate cert)
{
try
{
cert.checkValidity(expiryTestDate);
}
catch(CertificateExpiredException e)
{
long timeToExpiry = cert.getNotAfter().getTime() - currentTime;
int days = Math.max(0,(int)(timeToExpiry / (ONE_DAY)));
getEventLogger().message(TrustStoreMessages.EXPIRING(getName(), String.valueOf(days), cert.getSubjectDN().toString()));
}
catch(CertificateNotYetValidException e)
{
// ignore
}
}
@Override
public final TrustManager[] getTrustManagers() throws GeneralSecurityException
{
if (isTrustAnchorValidityEnforced())
{
final Set<Certificate> trustManagerCerts = Sets.newHashSet(getCertificates());
final Set<TrustAnchor> trustAnchors = new HashSet<>();
final Set<Certificate> otherCerts = new HashSet<>();
for (Certificate certs : trustManagerCerts)
{
if (certs instanceof X509Certificate && isSelfSigned((X509Certificate) certs))
{
trustAnchors.add(new TrustAnchor((X509Certificate) certs, null));
}
else
{
otherCerts.add(certs);
}
}
TrustManager[] trustManagers = getTrustManagersInternal();
TrustManager[] wrappedTrustManagers = new TrustManager[trustManagers.length];
for (int i = 0; i < trustManagers.length; i++)
{
final TrustManager trustManager = trustManagers[i];
if (trustManager instanceof X509TrustManager)
{
wrappedTrustManagers[i] = new TrustAnchorValidatingTrustManager(getName(),
(X509TrustManager) trustManager,
trustAnchors,
otherCerts);
}
else
{
wrappedTrustManagers[i] = trustManager;
}
}
return wrappedTrustManagers;
}
else
{
return getTrustManagersInternal();
}
}
protected abstract TrustManager[] getTrustManagersInternal() throws GeneralSecurityException;
@Override
public final int getCertificateExpiryWarnPeriod()
{
try
{
return getContextValue(Integer.class, CERTIFICATE_EXPIRY_WARN_PERIOD);
}
catch (NullPointerException | IllegalArgumentException e)
{
LOGGER.warn("The value of the context variable '{}' for truststore {} cannot be converted to an integer. The value {} will be used as a default", CERTIFICATE_EXPIRY_WARN_PERIOD, getName(), DEFAULT_CERTIFICATE_EXPIRY_WARN_PERIOD);
return DEFAULT_CERTIFICATE_EXPIRY_WARN_PERIOD;
}
}
@Override
public int getCertificateExpiryCheckFrequency()
{
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;
}
return checkFrequency;
}
@Override
public boolean isTrustAnchorValidityEnforced()
{
return _trustAnchorValidityEnforced;
}
@Override
public boolean isExposedAsMessageSource()
{
return _exposedAsMessageSource;
}
@Override
public List<VirtualHostNode<?>> getIncludedVirtualHostNodeMessageSources()
{
return _includedVirtualHostNodeMessageSources;
}
@Override
public List<VirtualHostNode<?>> getExcludedVirtualHostNodeMessageSources()
{
return _excludedVirtualHostNodeMessageSources;
}
@Override
public List<CertificateDetails> getCertificateDetails()
{
try
{
Certificate[] certificatesInternal = getCertificates();
if (certificatesInternal.length > 0)
{
return Arrays.stream(certificatesInternal)
.filter(cert -> cert instanceof X509Certificate)
.map(x509cert -> new CertificateDetailsImpl((X509Certificate) x509cert))
.collect(Collectors.toList());
}
return Collections.emptyList();
}
catch (GeneralSecurityException e)
{
throw new IllegalConfigurationException("Failed to extract certificate details", e);
}
}
private boolean isSelfSigned(X509Certificate cert) throws GeneralSecurityException
{
try
{
PublicKey key = cert.getPublicKey();
cert.verify(key);
return true;
}
catch (SignatureException | InvalidKeyException e)
{
return false;
}
}
}