diff options
Diffstat (limited to 'qpid/java/broker-core')
20 files changed, 641 insertions, 216 deletions
diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java index ccda1e1fe1..765e1e4fa5 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java @@ -40,9 +40,6 @@ public class BrokerProperties public static final String PROPERTY_DEFAULT_SUPPORTED_PROTOCOL_REPLY = "qpid.broker_default_supported_protocol_version_reply"; public static final String PROPERTY_DISABLED_FEATURES = "qpid.broker_disabled_features"; - public static final String PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_EXCLUDES = "qpid.broker_default_amqp_protocol_excludes"; - public static final String PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_INCLUDES = "qpid.broker_default_amqp_protocol_includes"; - public static final String PROPERTY_MANAGEMENT_RIGHTS_INFER_ALL_ACCESS = "qpid.broker_jmx_method_rights_infer_all_access"; public static final String PROPERTY_USE_CUSTOM_RMI_SOCKET_FACTORY = "qpid.broker_jmx_use_custom_rmi_socket_factory"; diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectFactoryImpl.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectFactoryImpl.java index 350e4fcd44..27d914c639 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectFactoryImpl.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectFactoryImpl.java @@ -137,15 +137,6 @@ public class ConfiguredObjectFactoryImpl implements ConfiguredObjectFactory else { factory = getConfiguredObjectTypeFactory(category, null); - if(factory == null) - { - ManagedObject annotation = categoryClass.getAnnotation(ManagedObject.class); - factory = getConfiguredObjectTypeFactory(category, annotation.defaultType()); - if(factory == null) - { - throw new NoFactoryForTypeException(category, annotation.defaultType()); - } - } } return factory; } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ManagedObject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ManagedObject.java index 8cfb84135e..f18869bced 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ManagedObject.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ManagedObject.java @@ -35,4 +35,5 @@ public @interface ManagedObject boolean creatable() default true; String defaultType() default ""; // in this case the class/interface itself is to be used String type() default ""; + boolean register() default true; } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Port.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Port.java index e98ff1a79a..eca3b0c7b1 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Port.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Port.java @@ -20,7 +20,6 @@ */ package org.apache.qpid.server.model; -import java.security.AccessControlException; import java.util.Collection; import java.util.Set; @@ -64,30 +63,6 @@ public interface Port<X extends Port<X>> extends ConfiguredObject<X> @ManagedAttribute Collection<TrustStore> getTrustStores(); - - - - - - void addTransport(Transport transport) throws IllegalStateException, - AccessControlException, - IllegalArgumentException; - - Transport removeTransport(Transport transport) throws IllegalStateException, - AccessControlException, - IllegalArgumentException; - - - void addProtocol(Protocol protocol) throws IllegalStateException, - AccessControlException, - IllegalArgumentException; - - Protocol removeProtocol(Protocol protocol) throws IllegalStateException, - AccessControlException, - IllegalArgumentException; - - Collection<Protocol> getAvailableProtocols(); - //children Collection<VirtualHostAlias> getVirtualHostBindings(); Collection<Connection> getConnections(); diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/AbstractPort.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/AbstractPort.java index 32587c0f4e..61790441f9 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/AbstractPort.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/AbstractPort.java @@ -190,40 +190,12 @@ abstract public class AbstractPort<X extends AbstractPort<X>> extends AbstractCo } @Override - public void addTransport(Transport transport) - throws IllegalStateException, AccessControlException, IllegalArgumentException - { - throw new IllegalStateException(); - } - - @Override - public Transport removeTransport(Transport transport) - throws IllegalStateException, AccessControlException, IllegalArgumentException - { - throw new IllegalStateException(); - } - - @Override public Set<Protocol> getProtocols() { return _protocols; } @Override - public void addProtocol(Protocol protocol) - throws IllegalStateException, AccessControlException, IllegalArgumentException - { - throw new IllegalStateException(); - } - - @Override - public Protocol removeProtocol(Protocol protocol) - throws IllegalStateException, AccessControlException, IllegalArgumentException - { - throw new IllegalStateException(); - } - - @Override public Collection<VirtualHostAlias> getVirtualHostBindings() { List<VirtualHostAlias> aliases = new ArrayList<VirtualHostAlias>(); @@ -251,19 +223,6 @@ abstract public class AbstractPort<X extends AbstractPort<X>> extends AbstractCo } @Override - public Set<Protocol> getAvailableProtocols() - { - Set<Protocol> protocols = getProtocols(); - if(protocols == null || protocols.isEmpty()) - { - protocols = getDefaultProtocols(); - } - return protocols; - } - - protected abstract Set<Protocol> getDefaultProtocols(); - - @Override public State getState() { return _state; @@ -382,11 +341,11 @@ abstract public class AbstractPort<X extends AbstractPort<X>> extends AbstractCo for (Port<?> existingPort : existingPorts) { - Collection<Protocol> portProtocols = existingPort.getAvailableProtocols(); + Collection<Protocol> portProtocols = existingPort.getProtocols(); if (portProtocols != null) { final ArrayList<Protocol> intersection = new ArrayList(portProtocols); - intersection.retainAll(getAvailableProtocols()); + intersection.retainAll(getProtocols()); if(!intersection.isEmpty()) { throw new IllegalConfigurationException("Port for protocols " + intersection + " already exists. Only one management port per protocol can be created."); diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/AmqpPort.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/AmqpPort.java index fa599b4d5f..b50a289b22 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/AmqpPort.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/AmqpPort.java @@ -24,6 +24,7 @@ import java.util.Set; import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.ManagedAttribute; +import org.apache.qpid.server.model.ManagedContextDefault; import org.apache.qpid.server.model.ManagedObject; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.Protocol; @@ -42,6 +43,11 @@ public interface AmqpPort<X extends AmqpPort<X>> extends Port<X> String SEND_BUFFER_SIZE = "sendBufferSize"; String RECEIVE_BUFFER_SIZE = "receiveBufferSize"; + String DEFAULT_AMQP_PROTOCOLS = "qpid.port.default_amqp_protocols"; + + @ManagedContextDefault(name = DEFAULT_AMQP_PROTOCOLS) + String INSTALLED_PROTOCOLS = AmqpPortImpl.getInstalledProtocolsAsString(); + @ManagedAttribute( defaultValue = AmqpPort.DEFAULT_AMQP_TCP_NO_DELAY ) boolean isTcpNoDelay(); @@ -66,8 +72,9 @@ public interface AmqpPort<X extends AmqpPort<X>> extends Port<X> validValues = {"org.apache.qpid.server.model.port.AmqpPortImpl#getAllAvailableTransportCombinations()"}) Set<Transport> getTransports(); - @ManagedAttribute( validValues = {"org.apache.qpid.server.model.port.AmqpPortImpl#getAllAvailableProtocolCombinations()"} ) + @ManagedAttribute( defaultValue = "${" + DEFAULT_AMQP_PROTOCOLS + "}", validValues = {"org.apache.qpid.server.model.port.AmqpPortImpl#getAllAvailableProtocolCombinations()"} ) Set<Protocol> getProtocols(); VirtualHostImpl getVirtualHost(String name); + } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/AmqpPortImpl.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/AmqpPortImpl.java index 1fbc0c8bc1..2c958b00d0 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/AmqpPortImpl.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/AmqpPortImpl.java @@ -25,7 +25,6 @@ import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.EnumSet; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -43,6 +42,7 @@ import org.apache.qpid.server.logging.messages.BrokerMessages; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.KeyStore; import org.apache.qpid.server.model.ManagedAttributeField; +import org.apache.qpid.server.model.ManagedContextDefault; import org.apache.qpid.server.model.ManagedObjectFactoryConstructor; import org.apache.qpid.server.model.Protocol; import org.apache.qpid.server.model.State; @@ -53,6 +53,7 @@ import org.apache.qpid.server.plugin.QpidServiceLoader; import org.apache.qpid.server.plugin.TransportProviderFactory; import org.apache.qpid.server.transport.AcceptingTransport; import org.apache.qpid.server.transport.TransportProvider; +import org.apache.qpid.server.util.ServerScopedRuntimeException; import org.apache.qpid.server.virtualhost.VirtualHostImpl; import org.apache.qpid.transport.network.security.ssl.QpidMultipleTrustManager; @@ -110,34 +111,6 @@ public class AmqpPortImpl extends AbstractPortWithAuthProvider<AmqpPortImpl> imp return (VirtualHostImpl) _broker.findVirtualHostByName(name); } - protected Set<Protocol> getDefaultProtocols() - { - Set<Protocol> defaultProtocols = EnumSet.of(Protocol.AMQP_0_8, Protocol.AMQP_0_9, Protocol.AMQP_0_9_1, - Protocol.AMQP_0_10, Protocol.AMQP_1_0); - String excludedProtocols = System.getProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_EXCLUDES); - if (excludedProtocols != null) - { - String[] excludes = excludedProtocols.split(","); - for (String exclude : excludes) - { - Protocol protocol = Protocol.valueOf(exclude); - defaultProtocols.remove(protocol); - } - } - String includedProtocols = System.getProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_INCLUDES); - if (includedProtocols != null) - { - String[] includes = includedProtocols.split(","); - for (String include : includes) - { - Protocol protocol = Protocol.valueOf(include); - defaultProtocols.add(protocol); - } - } - return defaultProtocols; - } - - @Override protected State onActivate() { @@ -178,7 +151,7 @@ public class AmqpPortImpl extends AbstractPortWithAuthProvider<AmqpPortImpl> imp _transport = transportProvider.createTransport(transportSet, sslContext, this, - getAvailableProtocols(), + getProtocols(), defaultSupportedProtocolReply); _transport.start(); @@ -363,4 +336,21 @@ public class AmqpPortImpl extends AbstractPortWithAuthProvider<AmqpPortImpl> imp } return Collections.unmodifiableSet(combinationsAsString); } + + + public static String getInstalledProtocolsAsString() + { + Set<Protocol> installedProtocols = getInstalledProtocols(); + ObjectMapper mapper = new ObjectMapper(); + + try(StringWriter output = new StringWriter()) + { + mapper.writeValue(output, installedProtocols); + return output.toString(); + } + catch (IOException e) + { + throw new ServerScopedRuntimeException(e); + } + } } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/HttpPort.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/HttpPort.java index fa2af121ae..51d31cb8ab 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/HttpPort.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/HttpPort.java @@ -51,7 +51,7 @@ public interface HttpPort<X extends HttpPort<X>> extends Port<X> validValues = {"[ \"TCP\" ]", "[ \"SSL\" ]", "[ \"TCP\", \"SSL\" ]"}) Set<Transport> getTransports(); - @ManagedAttribute( validValues = { "[ \"HTTP\"]"} ) + @ManagedAttribute( defaultValue = "HTTP", validValues = { "[ \"HTTP\"]"} ) Set<Protocol> getProtocols(); void setPortManager(PortManager manager); diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/HttpPortImpl.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/HttpPortImpl.java index a89ba9bbff..33abee9bde 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/HttpPortImpl.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/HttpPortImpl.java @@ -40,12 +40,6 @@ public class HttpPortImpl extends AbstractPortWithAuthProvider<HttpPortImpl> imp super(attributes, broker); } - @Override - protected Set<Protocol> getDefaultProtocols() - { - return Collections.singleton(Protocol.HTTP); - } - public void setPortManager(PortManager manager) { _portManager = manager; diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/JmxPort.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/JmxPort.java index 48754e92e4..981d81a342 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/JmxPort.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/JmxPort.java @@ -51,7 +51,7 @@ public interface JmxPort<X extends JmxPort<X>> extends Port<X> validValues = {"[ \"TCP\" ]", "[ \"SSL\" ]"}) Set<Transport> getTransports(); - @ManagedAttribute( validValues = { "[ \"JMX_RMI\"]"} ) + @ManagedAttribute( defaultValue = "JMX_RMI", validValues = { "[ \"JMX_RMI\"]"} ) Set<Protocol> getProtocols(); void setPortManager(PortManager manager); diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/JmxPortImpl.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/JmxPortImpl.java index ac691c0860..a235613c29 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/JmxPortImpl.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/JmxPortImpl.java @@ -48,12 +48,6 @@ public class JmxPortImpl extends AbstractPortWithAuthProvider<JmxPortImpl> imple } @Override - protected Set<Protocol> getDefaultProtocols() - { - return Collections.singleton(Protocol.JMX_RMI); - } - - @Override public void setPortManager(PortManager manager) { _portManager = manager; diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/RmiPort.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/RmiPort.java index d2420aa343..8fad90ada3 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/RmiPort.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/RmiPort.java @@ -32,7 +32,7 @@ import org.apache.qpid.server.model.Transport; public interface RmiPort<X extends RmiPort<X>> extends Port<X> { - @ManagedAttribute( validValues = { "[ \"RMI\"]"} ) + @ManagedAttribute( defaultValue = "RMI", validValues = { "[ \"RMI\"]"} ) Set<Protocol> getProtocols(); @ManagedAttribute( defaultValue = "TCP", diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/RmiPortImpl.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/RmiPortImpl.java index e236b7cb91..82e68d75c8 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/RmiPortImpl.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/port/RmiPortImpl.java @@ -56,12 +56,6 @@ public class RmiPortImpl extends AbstractPort<RmiPortImpl> implements RmiPort<Rm } - @Override - protected Set<Protocol> getDefaultProtocols() - { - return Collections.singleton(Protocol.RMI); - } - public void setPortManager(PortManager manager) { _portManager = manager; diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManager.java index 5b62f7cffd..0e532cee89 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManager.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManager.java @@ -46,7 +46,7 @@ import org.apache.qpid.server.security.auth.UsernamePrincipal; import org.apache.qpid.server.security.auth.sasl.plain.PlainPasswordCallback; import org.apache.qpid.server.security.auth.sasl.plain.PlainSaslServer; -@ManagedObject( category = false, type = "Simple" ) +@ManagedObject( category = false, type = "Simple", register = false ) public class SimpleAuthenticationManager extends AbstractAuthenticationManager<SimpleAuthenticationManager> { private static final Logger _logger = Logger.getLogger(SimpleAuthenticationManager.class); diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypter.java index c0c92f0389..b094ea96f9 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypter.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypter.java @@ -36,17 +36,25 @@ import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.xml.bind.DatatypeConverter; -import org.apache.qpid.server.configuration.IllegalConfigurationException; - class AESKeyFileEncrypter implements ConfigurationSecretEncrypter { private static final String CIPHER_NAME = "AES/CBC/PKCS5Padding"; private static final int AES_INITIALIZATION_VECTOR_LENGTH = 16; + private static final String AES_ALGORITHM = "AES"; private final SecretKey _secretKey; private final SecureRandom _random = new SecureRandom(); AESKeyFileEncrypter(SecretKey secretKey) { + if(secretKey == null) + { + throw new NullPointerException("A non null secret key must be supplied"); + } + if(!AES_ALGORITHM.equals(secretKey.getAlgorithm())) + { + throw new IllegalArgumentException("Provided secret key was for the algorithm: " + secretKey.getAlgorithm() + + "when" + AES_ALGORITHM + "was needed."); + } _secretKey = secretKey; } @@ -68,19 +76,26 @@ class AESKeyFileEncrypter implements ConfigurationSecretEncrypter } catch (IOException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new IllegalConfigurationException("Unable to encrypt secret", e); + throw new IllegalArgumentException("Unable to encrypt secret", e); } } @Override public String decrypt(final String encrypted) { + if(!isValidBase64(encrypted)) + { + throw new IllegalArgumentException("Encrypted value is not valid Base 64 data: '" + encrypted + "'"); + } byte[] encryptedBytes = DatatypeConverter.parseBase64Binary(encrypted); try { Cipher cipher = Cipher.getInstance(CIPHER_NAME); - cipher.init(Cipher.DECRYPT_MODE, _secretKey, new IvParameterSpec(encryptedBytes, 0, - AES_INITIALIZATION_VECTOR_LENGTH)); + + IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptedBytes, 0, AES_INITIALIZATION_VECTOR_LENGTH); + + cipher.init(Cipher.DECRYPT_MODE, _secretKey, ivParameterSpec); + return new String(readFromCipherStream(encryptedBytes, AES_INITIALIZATION_VECTOR_LENGTH, encryptedBytes.length - AES_INITIALIZATION_VECTOR_LENGTH, @@ -88,10 +103,15 @@ class AESKeyFileEncrypter implements ConfigurationSecretEncrypter } catch (IOException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new IllegalConfigurationException("Unable to encrypt secret", e); + throw new IllegalArgumentException("Unable to encrypt secret", e); } } + private boolean isValidBase64(final String encrypted) + { + return encrypted.matches("^([\\w\\d+/]{4})*([\\w\\d+/]{2}==|[\\w\\d+/]{3}=)?$"); + } + private byte[] readFromCipherStream(final byte[] unencryptedBytes, final Cipher cipher) throws IOException { @@ -106,16 +126,16 @@ class AESKeyFileEncrypter implements ConfigurationSecretEncrypter offset, length), cipher)) { - byte[] buf = new byte[1024]; + byte[] buf = new byte[512]; int pos = 0; int read; while ((read = cipherInputStream.read(buf, pos, buf.length - pos)) != -1) { pos += read; - if (pos == buf.length - 1) + if (pos == buf.length) { byte[] tmp = buf; - buf = new byte[buf.length + 1024]; + buf = new byte[buf.length + 512]; System.arraycopy(tmp, 0, buf, 0, tmp.length); } } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypterFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypterFactory.java index 447f19b7ce..ef92c2a131 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypterFactory.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypterFactory.java @@ -46,13 +46,15 @@ import org.apache.qpid.server.plugin.PluggableService; @PluggableService public class AESKeyFileEncrypterFactory implements ConfigurationSecretEncrypterFactory { - private static final String ENCRYPTER_KEY_FILE = "encrypter.key.file"; + static final String ENCRYPTER_KEY_FILE = "encrypter.key.file"; private static final int AES_KEY_SIZE_BITS = 256; private static final int AES_KEY_SIZE_BYTES = AES_KEY_SIZE_BITS / 8; private static final String AES_ALGORITHM = "AES"; - public static String TYPE = "AESKeyFile"; + public static final String TYPE = "AESKeyFile"; + + static final String DEFAULT_KEYS_SUBDIR_NAME = ".keys"; @Override public ConfigurationSecretEncrypter createEncrypter(final ConfiguredObject<?> object) @@ -66,7 +68,7 @@ public class AESKeyFileEncrypterFactory implements ConfigurationSecretEncrypterF { fileLocation = object.getContextValue(String.class, BrokerOptions.QPID_WORK_DIR) - + File.separator + ".keys" + File.separator + + File.separator + DEFAULT_KEYS_SUBDIR_NAME + File.separator + object.getCategoryClass().getSimpleName() + "_" + object.getName() + ".key"; @@ -94,14 +96,14 @@ public class AESKeyFileEncrypterFactory implements ConfigurationSecretEncrypterF || permissions.contains(PosixFilePermission.GROUP_WRITE) || permissions.contains(PosixFilePermission.OTHERS_WRITE)) { - throw new IllegalStateException("Key file '" + throw new IllegalArgumentException("Key file '" + fileLocation + "' has incorrect permissions. Only the owner " + "should be able to read or write this file."); } if(Files.size(file.toPath()) != AES_KEY_SIZE_BYTES) { - throw new IllegalConfigurationException("Key file '" + fileLocation + "' contains an incorrect about of data"); + throw new IllegalArgumentException("Key file '" + fileLocation + "' contains an incorrect about of data"); } try(FileInputStream inputStream = new FileInputStream(file)) @@ -151,7 +153,7 @@ public class AESKeyFileEncrypterFactory implements ConfigurationSecretEncrypterF } catch (NoSuchAlgorithmException | IOException e) { - throw new IllegalConfigurationException("Cannot create key file: " + e.getMessage(), e); + throw new IllegalArgumentException("Cannot create key file: " + e.getMessage(), e); } } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java index 450fc30bf2..eacc4f2458 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java @@ -251,7 +251,7 @@ public abstract class AbstractVirtualHost<X extends AbstractVirtualHost<X>> exte { for(Port port :_broker.getPorts()) { - if (Protocol.hasAmqpProtocol(port.getAvailableProtocols())) + if (Protocol.hasAmqpProtocol(port.getProtocols())) { _aliases.add(new VirtualHostAliasAdapter(this, port)); } diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java index 48681a6075..523203c756 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java @@ -25,16 +25,13 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; -import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; -import org.apache.qpid.server.configuration.BrokerProperties; import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.configuration.updater.CurrentThreadTaskExecutor; import org.apache.qpid.server.configuration.updater.TaskExecutor; @@ -100,8 +97,6 @@ public class PortFactoryTest extends QpidTestCase } - setTestSystemProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_EXCLUDES, null); - setTestSystemProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_INCLUDES, null); _factory = new ConfiguredObjectFactoryImpl(BrokerModel.getInstance()); _attributes.put(Port.ID, _portId); _attributes.put(Port.NAME, getName()); @@ -114,62 +109,6 @@ public class PortFactoryTest extends QpidTestCase _attributes.put(Port.BINDING_ADDRESS, "127.0.0.1"); } - public void testDefaultProtocols() - { - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(Port.PORT, 1); - attributes.put(Port.NAME, getName()); - attributes.put(Port.DESIRED_STATE, State.QUIESCED); - - attributes.put(Port.AUTHENTICATION_PROVIDER, _authProviderName); - Port<?> port = _factory.create(Port.class, attributes, _broker); - - Collection<Protocol> protocols = port.getAvailableProtocols(); - - EnumSet<Protocol> expected = EnumSet.of(Protocol.AMQP_0_8, Protocol.AMQP_0_9, Protocol.AMQP_0_9_1, Protocol.AMQP_0_10, - Protocol.AMQP_1_0); - assertEquals("Unexpected protocols", new HashSet<Protocol>(expected), new HashSet<Protocol>(protocols)); - } - - public void testDefaultProtocolsWhenProtocolExcludeSystemPropertyIsSet() - { - setTestSystemProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_EXCLUDES, Protocol.AMQP_1_0.name() + "," - + Protocol.AMQP_0_10.name()); - - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(Port.PORT, 1); - attributes.put(Port.NAME, getName()); - attributes.put(Port.AUTHENTICATION_PROVIDER, _authProviderName); - attributes.put(Port.DESIRED_STATE, State.QUIESCED); - Port<?> port = _factory.create(Port.class, attributes, _broker); - - - Collection<Protocol> protocols = port.getAvailableProtocols(); - - EnumSet<Protocol> expected = EnumSet.of(Protocol.AMQP_0_8, Protocol.AMQP_0_9, Protocol.AMQP_0_9_1); - assertEquals("Unexpected protocols", new HashSet<Protocol>(expected), new HashSet<Protocol>(protocols)); - } - - public void testDefaultProtocolsWhenProtocolIncludeSystemPropertyIsSet() - { - setTestSystemProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_EXCLUDES, Protocol.AMQP_1_0.name() + "," - + Protocol.AMQP_0_10.name() + "," + Protocol.AMQP_0_9_1.name()); - setTestSystemProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_INCLUDES, Protocol.AMQP_0_10.name() + "," - + Protocol.AMQP_0_9_1.name()); - - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(Port.PORT, 1); - attributes.put(Port.NAME, getName()); - attributes.put(Port.AUTHENTICATION_PROVIDER, _authProviderName); - attributes.put(Port.DESIRED_STATE, State.QUIESCED); - Port<?> port = _factory.create(Port.class, attributes, _broker); - - Collection<Protocol> protocols = port.getAvailableProtocols(); - - EnumSet<Protocol> expected = EnumSet.of(Protocol.AMQP_0_8, Protocol.AMQP_0_9, Protocol.AMQP_0_9_1, Protocol.AMQP_0_10); - assertEquals("Unexpected protocols", new HashSet<Protocol>(expected), new HashSet<Protocol>(protocols)); - } - public void testCreatePortWithMinimumAttributes() { Map<String, Object> attributes = new HashMap<String, Object>(); @@ -184,8 +123,6 @@ public class PortFactoryTest extends QpidTestCase assertTrue(port instanceof AmqpPort); assertEquals("Unexpected port", 1, port.getPort()); assertEquals("Unexpected transports", Collections.singleton(PortFactory.DEFAULT_TRANSPORT), port.getTransports()); - assertEquals("Unexpected protocols", EnumSet.of(Protocol.AMQP_0_8, Protocol.AMQP_0_9, Protocol.AMQP_0_9_1, Protocol.AMQP_0_10, - Protocol.AMQP_1_0), port.getAvailableProtocols()); assertEquals("Unexpected send buffer size", PortFactory.DEFAULT_AMQP_SEND_BUFFER_SIZE, port.getAttribute(AmqpPort.SEND_BUFFER_SIZE)); assertEquals("Unexpected receive buffer size", PortFactory.DEFAULT_AMQP_RECEIVE_BUFFER_SIZE, @@ -333,7 +270,7 @@ public class PortFactoryTest extends QpidTestCase { assertEquals(_tcpTransports, port.getTransports()); } - assertEquals(amqp010ProtocolSet, port.getAvailableProtocols()); + assertEquals(amqp010ProtocolSet, port.getProtocols()); assertEquals("Unexpected send buffer size", 2, port.getAttribute(AmqpPort.SEND_BUFFER_SIZE)); assertEquals("Unexpected receive buffer size", 1, port.getAttribute(AmqpPort.RECEIVE_BUFFER_SIZE)); assertEquals("Unexpected need client auth", needClientAuth, port.getAttribute(Port.NEED_CLIENT_AUTH)); @@ -361,7 +298,7 @@ public class PortFactoryTest extends QpidTestCase assertEquals(_portId, port.getId()); assertEquals(_portNumber, port.getPort()); assertEquals(_tcpTransports, port.getTransports()); - assertEquals(nonAmqpProtocolSet, port.getAvailableProtocols()); + assertEquals(nonAmqpProtocolSet, port.getProtocols()); } public void testCreateNonAmqpPortWithPartiallySetAttributes() @@ -382,7 +319,7 @@ public class PortFactoryTest extends QpidTestCase assertEquals(_portId, port.getId()); assertEquals(_portNumber, port.getPort()); assertEquals(Collections.singleton(PortFactory.DEFAULT_TRANSPORT), port.getTransports()); - assertEquals(nonAmqpProtocolSet, port.getAvailableProtocols()); + assertEquals(nonAmqpProtocolSet, port.getProtocols()); } @@ -411,7 +348,7 @@ public class PortFactoryTest extends QpidTestCase attributes.put(Port.PROTOCOLS, Collections.singleton(Protocol.RMI)); Port rmiPort = mock(Port.class); - when(rmiPort.getAvailableProtocols()).thenReturn(Collections.singleton(Protocol.RMI)); + when(rmiPort.getProtocols()).thenReturn(Collections.singleton(Protocol.RMI)); when(_broker.getPorts()).thenReturn(Collections.singletonList(rmiPort)); try diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypterFactoryTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypterFactoryTest.java new file mode 100644 index 0000000000..320c5dbdc8 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypterFactoryTest.java @@ -0,0 +1,343 @@ +/* + * + * 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.encryption; + +import static org.mockito.Matchers.anyMap; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Map; +import java.util.UUID; + +import javax.crypto.Cipher; + +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.test.utils.QpidTestCase; + +public class AESKeyFileEncrypterFactoryTest extends QpidTestCase +{ + private Broker _broker; + private Path _tmpDir; + private AESKeyFileEncrypterFactory _factory; + + @Override + public void setUp() throws Exception + { + super.setUp(); + _broker = mock(Broker.class); + _tmpDir = Files.createTempDirectory(getTestName()); + + when(_broker.getContextKeys(eq(false))).thenReturn(Collections.<String>emptySet()); + when(_broker.getContextValue(eq(String.class), eq(BrokerOptions.QPID_WORK_DIR))).thenReturn(_tmpDir.toString()); + when(_broker.getCategoryClass()).thenReturn(Broker.class); + when(_broker.getName()).thenReturn(getName()); + final ArgumentCaptor<Map> contextCaptor = ArgumentCaptor.forClass(Map.class); + + when(_broker.setAttribute(eq("context"), anyMap(), contextCaptor.capture() )).thenAnswer(new Answer<Void>() { + + @Override + public Void answer(final InvocationOnMock invocationOnMock) throws Throwable + { + Map replacementContext = contextCaptor.getValue(); + when(_broker.getContext()).thenReturn(replacementContext); + return null; + } + }); + + _factory = new AESKeyFileEncrypterFactory(); + } + + public void testCreateKeyInDefaultLocation() throws Exception + { + if(isStrongEncryptionEnabled()) + { + ConfigurationSecretEncrypter encrypter = _factory.createEncrypter(_broker); + + KeyFilePathChecker keyFilePathChecker = new KeyFilePathChecker(); + + doChecks(encrypter, keyFilePathChecker); + + String pathName = (String) _broker.getContext().get(AESKeyFileEncrypterFactory.ENCRYPTER_KEY_FILE); + + // check the context variable was set + assertEquals(keyFilePathChecker.getKeyFile().toString(), pathName); + } + } + + private void doChecks(final ConfigurationSecretEncrypter encrypter, + final KeyFilePathChecker keyFilePathChecker) throws IOException + { + // walk the directory to find the file + Files.walkFileTree(_tmpDir, keyFilePathChecker); + + // check the file was actually found + assertNotNull(keyFilePathChecker.getKeyFile()); + + String secret = "notasecret"; + + // check the encrypter works + assertEquals(secret, encrypter.decrypt(encrypter.encrypt(secret))); + + } + + public void testSettingContextKeyLeadsToFileCreation() throws Exception + { + if(isStrongEncryptionEnabled()) + { + String filename = UUID.randomUUID().toString() + ".key"; + String subdirName = getTestName() + File.separator + "test"; + String fileLocation = _tmpDir.toString() + File.separator + subdirName + File.separator + filename; + + when(_broker.getContextKeys(eq(false))).thenReturn(Collections.singleton(AESKeyFileEncrypterFactory.ENCRYPTER_KEY_FILE)); + when(_broker.getContextValue(eq(String.class), + eq(AESKeyFileEncrypterFactory.ENCRYPTER_KEY_FILE))).thenReturn(fileLocation); + + ConfigurationSecretEncrypter encrypter = _factory.createEncrypter(_broker); + + KeyFilePathChecker keyFilePathChecker = new KeyFilePathChecker(subdirName, filename); + + doChecks(encrypter, keyFilePathChecker); + } + } + + + public void testUnableToCreateFileInSpecifiedLocation() throws Exception + { + if(isStrongEncryptionEnabled()) + { + + String filename = UUID.randomUUID().toString() + ".key"; + String subdirName = getTestName() + File.separator + "test"; + String fileLocation = _tmpDir.toString() + File.separator + subdirName + File.separator + filename; + + when(_broker.getContextKeys(eq(false))).thenReturn(Collections.singleton(AESKeyFileEncrypterFactory.ENCRYPTER_KEY_FILE)); + when(_broker.getContextValue(eq(String.class), + eq(AESKeyFileEncrypterFactory.ENCRYPTER_KEY_FILE))).thenReturn(fileLocation); + + Files.createDirectories(Paths.get(fileLocation)); + + try + { + ConfigurationSecretEncrypter encrypter = _factory.createEncrypter(_broker); + fail("should not be able to create a key file where a directory currently is"); + } + catch (IllegalArgumentException e) + { + // pass + } + } + } + + + public void testPermissionsAreChecked() throws Exception + { + if(isStrongEncryptionEnabled()) + { + + String filename = UUID.randomUUID().toString() + ".key"; + String subdirName = getTestName() + File.separator + "test"; + String fileLocation = _tmpDir.toString() + File.separator + subdirName + File.separator + filename; + + when(_broker.getContextKeys(eq(false))).thenReturn(Collections.singleton(AESKeyFileEncrypterFactory.ENCRYPTER_KEY_FILE)); + when(_broker.getContextValue(eq(String.class), + eq(AESKeyFileEncrypterFactory.ENCRYPTER_KEY_FILE))).thenReturn(fileLocation); + + Files.createDirectories(Paths.get(_tmpDir.toString(), subdirName)); + + File file = new File(fileLocation); + file.createNewFile(); + Files.setPosixFilePermissions(file.toPath(), + EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.GROUP_READ)); + + try + { + ConfigurationSecretEncrypter encrypter = _factory.createEncrypter(_broker); + fail("should not be able to create a key file where the file is readable"); + } + catch (IllegalArgumentException e) + { + // pass + } + } + } + + public void testInvalidKey() throws Exception + { + if(isStrongEncryptionEnabled()) + { + String filename = UUID.randomUUID().toString() + ".key"; + String subdirName = getTestName() + File.separator + "test"; + String fileLocation = _tmpDir.toString() + File.separator + subdirName + File.separator + filename; + + when(_broker.getContextKeys(eq(false))).thenReturn(Collections.singleton(AESKeyFileEncrypterFactory.ENCRYPTER_KEY_FILE)); + when(_broker.getContextValue(eq(String.class), + eq(AESKeyFileEncrypterFactory.ENCRYPTER_KEY_FILE))).thenReturn(fileLocation); + + Files.createDirectories(Paths.get(_tmpDir.toString(), subdirName)); + + File file = new File(fileLocation); + try (FileOutputStream fos = new FileOutputStream(file)) + { + fos.write("This is not an AES key. It is a string saying it is not an AES key".getBytes( + StandardCharsets.US_ASCII)); + } + Files.setPosixFilePermissions(file.toPath(), EnumSet.of(PosixFilePermission.OWNER_READ)); + + try + { + ConfigurationSecretEncrypter encrypter = _factory.createEncrypter(_broker); + fail("should not be able to start where the key is not a valid key"); + } + catch (IllegalArgumentException e) + { + // pass + } + } + } + + @Override + public void tearDown() throws Exception + { + Files.walkFileTree(_tmpDir, + new SimpleFileVisitor<Path>() + { + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) + throws IOException + { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) + throws IOException + { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + super.tearDown(); + } + + private boolean isStrongEncryptionEnabled() throws NoSuchAlgorithmException + { + return Cipher.getMaxAllowedKeyLength("AES")>=256; + } + + private class KeyFilePathChecker extends SimpleFileVisitor<Path> + { + + private final String _fileName; + private final String _subdirName; + private Path _keyFile; + private boolean _inKeysSubdir; + + public KeyFilePathChecker() + { + this(AESKeyFileEncrypterFactory.DEFAULT_KEYS_SUBDIR_NAME, "Broker_" + AESKeyFileEncrypterFactoryTest.this.getName() + ".key"); + } + + public KeyFilePathChecker(final String subdirName, final String fileName) + { + _subdirName = subdirName; + _fileName = fileName; + } + + @Override + public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException + { + if(!_inKeysSubdir && dir.endsWith(_subdirName)) + { + _inKeysSubdir = true; + assertFalse(Files.getPosixFilePermissions(dir).contains(PosixFilePermission.OTHERS_READ)); + assertFalse(Files.getPosixFilePermissions(dir).contains(PosixFilePermission.OTHERS_WRITE)); + assertFalse(Files.getPosixFilePermissions(dir).contains(PosixFilePermission.OTHERS_EXECUTE)); + + assertFalse(Files.getPosixFilePermissions(dir).contains(PosixFilePermission.GROUP_READ)); + assertFalse(Files.getPosixFilePermissions(dir).contains(PosixFilePermission.GROUP_WRITE)); + assertFalse(Files.getPosixFilePermissions(dir).contains(PosixFilePermission.GROUP_EXECUTE)); + return FileVisitResult.CONTINUE; + } + else + { + return _inKeysSubdir ? FileVisitResult.SKIP_SUBTREE : FileVisitResult.CONTINUE; + } + + } + + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException + { + if(_inKeysSubdir) + { + if(file.endsWith(_fileName)) + { + _keyFile = file; + + assertFalse(Files.getPosixFilePermissions(file).contains(PosixFilePermission.OTHERS_READ)); + assertFalse(Files.getPosixFilePermissions(file).contains(PosixFilePermission.OTHERS_WRITE)); + assertFalse(Files.getPosixFilePermissions(file).contains(PosixFilePermission.OTHERS_EXECUTE)); + + assertFalse(Files.getPosixFilePermissions(file).contains(PosixFilePermission.GROUP_READ)); + assertFalse(Files.getPosixFilePermissions(file).contains(PosixFilePermission.GROUP_WRITE)); + assertFalse(Files.getPosixFilePermissions(file).contains(PosixFilePermission.GROUP_EXECUTE)); + + return FileVisitResult.TERMINATE; + } + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException + { + _inKeysSubdir = false; + return FileVisitResult.CONTINUE; + } + + public Path getKeyFile() + { + return _keyFile; + } + + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypterTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypterTest.java new file mode 100644 index 0000000000..3feb458a8b --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/encryption/AESKeyFileEncrypterTest.java @@ -0,0 +1,221 @@ +/* + * + * 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.encryption; + +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +import javax.crypto.Cipher; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.qpid.test.utils.QpidTestCase; + +public class AESKeyFileEncrypterTest extends QpidTestCase +{ + private final SecureRandom _random = new SecureRandom(); + public static final String PLAINTEXT = "notaverygoodpassword"; + + public void testSimpleEncryptDecrypt() throws Exception + { + if(isStrongEncryptionEnabled()) + { + doTestSimpleEncryptDecrypt(PLAINTEXT); + } + } + + + public void testRepeatedEncryptionsReturnDifferentValues() throws Exception + { + if(isStrongEncryptionEnabled()) + { + SecretKeySpec secretKey = createSecretKey(); + AESKeyFileEncrypter encrypter = new AESKeyFileEncrypter(secretKey); + + Set<String> encryptions = new HashSet<>(); + + int iterations = 100; + + for (int i = 0; i < iterations; i++) + { + encryptions.add(encrypter.encrypt(PLAINTEXT)); + } + + assertEquals("Not all encryptions were distinct", iterations, encryptions.size()); + + for (String encrypted : encryptions) + { + assertEquals("Not all encryptions decrypt correctly", PLAINTEXT, encrypter.decrypt(encrypted)); + } + } + } + + public void testCreationFailsOnInvalidSecret() throws Exception + { + if(isStrongEncryptionEnabled()) + { + try + { + new AESKeyFileEncrypter(null); + fail("An encrypter should not be creatable from a null key"); + } + catch (NullPointerException e) + { + // pass + } + + try + { + PBEKeySpec keySpec = new PBEKeySpec("password".toCharArray()); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); + new AESKeyFileEncrypter(factory.generateSecret(keySpec)); + fail("An encrypter should not be creatable from the wrong type of secret key"); + } + catch (IllegalArgumentException e) + { + // pass + } + } + } + + public void testEncryptionOfEmptyString() throws Exception + { + if(isStrongEncryptionEnabled()) + { + String text = ""; + doTestSimpleEncryptDecrypt(text); + } + } + + private void doTestSimpleEncryptDecrypt(final String text) + { + SecretKeySpec secretKey = createSecretKey(); + AESKeyFileEncrypter encrypter = new AESKeyFileEncrypter(secretKey); + + String encrypted = encrypter.encrypt(text); + assertNotNull("Encrypter did not return a result from encryption", encrypted); + assertFalse("Plain text and encrypted version are equal", text.equals(encrypted)); + String decrypted = encrypter.decrypt(encrypted); + assertNotNull("Encrypter did not return a result from decryption",decrypted); + assertTrue("Encryption was not reversible", text.equals(decrypted)); + } + + public void testEncryptingNullFails() throws Exception + { + if(isStrongEncryptionEnabled()) + { + try + { + SecretKeySpec secretKey = createSecretKey(); + AESKeyFileEncrypter encrypter = new AESKeyFileEncrypter(secretKey); + + String encrypted = encrypter.encrypt(null); + fail("Attempting to encrypt null should fail"); + } + catch (NullPointerException e) + { + // pass + } + } + } + + public void testEncryptingVeryLargeSecret() throws Exception + { + if(isStrongEncryptionEnabled()) + { + Random random = new Random(); + byte[] data = new byte[4096]; + random.nextBytes(data); + for (int i = 0; i < data.length; i++) + { + data[i] = (byte) (data[i] & 0xEF); + } + doTestSimpleEncryptDecrypt(new String(data, StandardCharsets.US_ASCII)); + } + } + + private boolean isStrongEncryptionEnabled() throws NoSuchAlgorithmException + { + return Cipher.getMaxAllowedKeyLength("AES")>=256; + } + + public void testDecryptNonsense() throws Exception + { + if(isStrongEncryptionEnabled()) + { + SecretKeySpec secretKey = createSecretKey(); + AESKeyFileEncrypter encrypter = new AESKeyFileEncrypter(secretKey); + + + try + { + encrypter.decrypt(null); + fail("Should not decrypt a null value"); + } + catch (NullPointerException e) + { + // pass + } + + try + { + encrypter.decrypt(""); + fail("Should not decrypt the empty String"); + } + catch (IllegalArgumentException e) + { + // pass + } + + try + { + encrypter.decrypt("thisisnonsense"); + fail("Should not decrypt a small amount of nonsense"); + } + catch (IllegalArgumentException e) + { + // pass + } + + try + { + String answer = encrypter.decrypt("thisisn'tvalidBase64!soitshouldfailwithanIllegalArgumentException"); + fail("Should not decrypt a larger amount of nonsense"); + } + catch (IllegalArgumentException e) + { + // pass + } + } + } + + private SecretKeySpec createSecretKey() + { + final byte[] keyData = new byte[32]; + _random.nextBytes(keyData); + return new SecretKeySpec(keyData, "AES"); + } +} |