blob: 9661a6d6a314f1c01d0da56b8cc7b3000e754a8f [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.systests.jms_1_1.extensions.sasl;
import static org.apache.qpid.systests.jms_1_1.extensions.tls.TlsTest.BROKER_KEYSTORE;
import static org.apache.qpid.systests.jms_1_1.extensions.tls.TlsTest.BROKER_TRUSTSTORE;
import static org.apache.qpid.systests.jms_1_1.extensions.tls.TlsTest.KEYSTORE;
import static org.apache.qpid.systests.jms_1_1.extensions.tls.TlsTest.TEST_PROFILE_RESOURCE_BASE;
import static org.apache.qpid.systests.jms_1_1.extensions.tls.TlsTest.TRUSTSTORE;
import static org.apache.qpid.test.utils.TestSSLConstants.BROKER_KEYSTORE_PASSWORD;
import static org.apache.qpid.test.utils.TestSSLConstants.BROKER_PEERSTORE;
import static org.apache.qpid.test.utils.TestSSLConstants.BROKER_PEERSTORE_PASSWORD;
import static org.apache.qpid.test.utils.TestSSLConstants.BROKER_TRUSTSTORE_PASSWORD;
import static org.apache.qpid.test.utils.TestSSLConstants.CERT_ALIAS_APP1;
import static org.apache.qpid.test.utils.TestSSLConstants.CERT_ALIAS_APP2;
import static org.apache.qpid.test.utils.TestSSLConstants.EXPIRED_KEYSTORE;
import static org.apache.qpid.test.utils.TestSSLConstants.KEYSTORE_PASSWORD;
import static org.apache.qpid.test.utils.TestSSLConstants.TRUSTSTORE_PASSWORD;
import static org.apache.qpid.test.utils.TestSSLConstants.JAVA_KEYSTORE_TYPE;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.TemporaryQueue;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.qpid.server.model.AuthenticationProvider;
import org.apache.qpid.server.model.ConfiguredObject;
import org.apache.qpid.server.model.Port;
import org.apache.qpid.server.model.Protocol;
import org.apache.qpid.server.model.User;
import org.apache.qpid.server.security.FileKeyStore;
import org.apache.qpid.server.security.FileTrustStore;
import org.apache.qpid.server.security.auth.manager.ExternalAuthenticationManager;
import org.apache.qpid.server.security.auth.manager.ExternalAuthenticationManagerImpl;
import org.apache.qpid.server.security.auth.manager.ScramSHA1AuthenticationManager;
import org.apache.qpid.server.security.auth.manager.ScramSHA256AuthenticationManager;
import org.apache.qpid.server.security.auth.sasl.crammd5.CramMd5HashedNegotiator;
import org.apache.qpid.systests.AmqpManagementFacade;
import org.apache.qpid.systests.JmsTestBase;
import org.apache.qpid.test.utils.TestSSLConstants;
public class AuthenticationTest extends JmsTestBase
{
private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationTest.class);
private static final String USER = "user";
private static final String USER_PASSWORD = "user";
@BeforeClass
public static void setUp() throws Exception
{
System.setProperty("javax.net.debug", "ssl");
// workaround for QPID-8069
if (getProtocol() != Protocol.AMQP_1_0 && getProtocol() != Protocol.AMQP_0_10)
{
System.setProperty("amqj.MaximumStateWait", "4000");
}
// legacy client keystore/truststore types can only be configured with JVM settings
if (getProtocol() != Protocol.AMQP_1_0)
{
System.setProperty("javax.net.ssl.trustStoreType", JAVA_KEYSTORE_TYPE);
System.setProperty("javax.net.ssl.keyStoreType", JAVA_KEYSTORE_TYPE);
}
}
@AfterClass
public static void tearDown() throws Exception
{
System.clearProperty("javax.net.debug");
if (getProtocol() != Protocol.AMQP_1_0)
{
System.clearProperty("amqj.MaximumStateWait");
}
if (getProtocol() != Protocol.AMQP_1_0)
{
System.clearProperty("javax.net.ssl.trustStoreType");
System.clearProperty("javax.net.ssl.keyStoreType");
}
}
@Test
public void md5() throws Exception
{
assumeThat("Qpid JMS Client does not support MD5 mechanisms",
getProtocol(),
is(not(equalTo(Protocol.AMQP_1_0))));
int port = createAuthenticationProviderAndUserAndPort(getTestName(), "MD5", USER, USER_PASSWORD);
assertConnectivity(port, USER, USER_PASSWORD, CramMd5HashedNegotiator.MECHANISM);
}
@Test
public void sha256() throws Exception
{
int port = createAuthenticationProviderAndUserAndPort(getTestName(),
ScramSHA256AuthenticationManager.PROVIDER_TYPE,
USER,
USER_PASSWORD);
assertConnectivity(port, USER, USER_PASSWORD, ScramSHA256AuthenticationManager.MECHANISM);
}
@Test
public void sha1() throws Exception
{
int port = createAuthenticationProviderAndUserAndPort(getTestName(),
ScramSHA1AuthenticationManager.PROVIDER_TYPE,
USER,
USER_PASSWORD);
assertConnectivity(port, USER, USER_PASSWORD, ScramSHA1AuthenticationManager.MECHANISM);
}
@Test
public void external() throws Exception
{
int port = createExternalProviderAndTlsPort();
Connection connection = getConnectionBuilder().setSslPort(port)
.setTls(true)
.setSaslMechanisms(ExternalAuthenticationManagerImpl.MECHANISM_NAME)
.setKeyStoreLocation(KEYSTORE)
.setKeyStorePassword(KEYSTORE_PASSWORD)
.setTrustStoreLocation(TRUSTSTORE)
.setTrustStorePassword(TRUSTSTORE_PASSWORD)
.build();
try
{
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
assertNotNull("Temporary queue was not created", session.createTemporaryQueue());
}
finally
{
connection.close();
}
}
@Test
public void externalDeniesUntrustedClientCert() throws Exception
{
assumeThat("QPID-8069", getProtocol(), is(anyOf(equalTo(Protocol.AMQP_1_0), equalTo(Protocol.AMQP_0_10))));
int port = createExternalProviderAndTlsPort();
try
{
getConnectionBuilder().setSslPort(port)
.setTls(true)
.setSaslMechanisms(ExternalAuthenticationManagerImpl.MECHANISM_NAME)
.setKeyStoreLocation(KEYSTORE)
.setKeyStorePassword(KEYSTORE_PASSWORD)
.setTrustStoreLocation(TRUSTSTORE)
.setTrustStorePassword(TRUSTSTORE_PASSWORD)
.setKeyAlias(TestSSLConstants.CERT_ALIAS_UNTRUSTED_CLIENT)
.build();
fail("Connection should not succeed");
}
catch (JMSException e)
{
// pass
}
}
@Test
public void externalDeniesExpiredClientCert() throws Exception
{
assumeThat("QPID-8069", getProtocol(), is(anyOf(equalTo(Protocol.AMQP_1_0), equalTo(Protocol.AMQP_0_10))));
Map<String, Object> trustStoreAttributes = new HashMap<>();
trustStoreAttributes.put(FileTrustStore.STORE_URL, TEST_PROFILE_RESOURCE_BASE + BROKER_PEERSTORE);
trustStoreAttributes.put(FileTrustStore.PASSWORD, BROKER_PEERSTORE_PASSWORD);
trustStoreAttributes.put(FileTrustStore.TRUST_ANCHOR_VALIDITY_ENFORCED, true);
int port = createExternalProviderAndTlsPort(trustStoreAttributes);
try
{
getConnectionBuilder().setSslPort(port)
.setTls(true)
.setSaslMechanisms(ExternalAuthenticationManagerImpl.MECHANISM_NAME)
.setKeyStoreLocation(TEST_PROFILE_RESOURCE_BASE + EXPIRED_KEYSTORE)
.setKeyStorePassword(KEYSTORE_PASSWORD)
.setTrustStoreLocation(TRUSTSTORE)
.setTrustStorePassword(TRUSTSTORE_PASSWORD)
.build();
fail("Connection should not succeed");
}
catch (JMSException e)
{
e.printStackTrace();
}
}
@Test
public void externalWithPeersOnlyTrustStore() throws Exception
{
Map<String, Object> trustStoreAttributes = new HashMap<>();
trustStoreAttributes.put(FileTrustStore.STORE_URL, TEST_PROFILE_RESOURCE_BASE + BROKER_PEERSTORE);
trustStoreAttributes.put(FileTrustStore.PASSWORD, BROKER_PEERSTORE_PASSWORD);
trustStoreAttributes.put(FileTrustStore.PEERS_ONLY, true);
int port = createExternalProviderAndTlsPort(trustStoreAttributes);
Connection connection = getConnectionBuilder().setSslPort(port)
.setTls(true)
.setSaslMechanisms(ExternalAuthenticationManagerImpl.MECHANISM_NAME)
.setKeyStoreLocation(KEYSTORE)
.setKeyStorePassword(KEYSTORE_PASSWORD)
.setTrustStoreLocation(TRUSTSTORE)
.setTrustStorePassword(TRUSTSTORE_PASSWORD)
.setKeyAlias(CERT_ALIAS_APP1)
.build();
try
{
connection.createSession(false, Session.AUTO_ACKNOWLEDGE).close();
}
finally
{
connection.close();
}
assumeThat("QPID-8069", getProtocol(), is(anyOf(equalTo(Protocol.AMQP_1_0), equalTo(Protocol.AMQP_0_10))));
try
{
getConnectionBuilder().setSslPort(port)
.setTls(true)
.setSaslMechanisms(ExternalAuthenticationManagerImpl.MECHANISM_NAME)
.setKeyStoreLocation(KEYSTORE)
.setKeyStorePassword(KEYSTORE_PASSWORD)
.setTrustStoreLocation(TRUSTSTORE)
.setTrustStorePassword(TRUSTSTORE_PASSWORD)
.setKeyAlias(CERT_ALIAS_APP2)
.build();
fail("app2 certificate is NOT in the peerstore");
}
catch (JMSException e)
{
// pass
}
}
@Test
public void externalWithRegularAndPeersOnlyTrustStores() throws Exception
{
String trustStoreName = getTestName() + "RegularTrustStore";
Connection brokerConnection = getConnectionBuilder().setVirtualHost("$management").build();
try
{
brokerConnection.start();
Map<String, Object> trustStoreAttributes = new HashMap<>();
trustStoreAttributes.put(FileTrustStore.STORE_URL, BROKER_TRUSTSTORE);
trustStoreAttributes.put(FileTrustStore.PASSWORD, BROKER_TRUSTSTORE_PASSWORD);
trustStoreAttributes.put(FileTrustStore.TRUST_STORE_TYPE, TestSSLConstants.JAVA_KEYSTORE_TYPE);
createEntity(trustStoreName,
FileTrustStore.class.getName(),
trustStoreAttributes,
brokerConnection);
}
finally
{
brokerConnection.close();
}
Map<String, Object> trustStoreAttributes = new HashMap<>();
trustStoreAttributes.put(FileTrustStore.STORE_URL, TEST_PROFILE_RESOURCE_BASE + BROKER_PEERSTORE);
trustStoreAttributes.put(FileTrustStore.PASSWORD, BROKER_PEERSTORE_PASSWORD);
trustStoreAttributes.put(FileTrustStore.PEERS_ONLY, true);
int port = createExternalProviderAndTlsPort(trustStoreAttributes, trustStoreName, false);
Connection connection = getConnectionBuilder().setSslPort(port)
.setTls(true)
.setSaslMechanisms(ExternalAuthenticationManagerImpl.MECHANISM_NAME)
.setKeyStoreLocation(KEYSTORE)
.setKeyStorePassword(KEYSTORE_PASSWORD)
.setTrustStoreLocation(TRUSTSTORE)
.setTrustStorePassword(TRUSTSTORE_PASSWORD)
.setKeyAlias(CERT_ALIAS_APP1)
.build();
try
{
connection.createSession(false, Session.AUTO_ACKNOWLEDGE).close();
}
finally
{
connection.close();
}
//use the app2 cert, which is NOT in the peerstore (but is signed by the same CA as app1)
Connection connection2 = getConnectionBuilder().setSslPort(port)
.setTls(true)
.setSaslMechanisms(ExternalAuthenticationManagerImpl.MECHANISM_NAME)
.setKeyStoreLocation(KEYSTORE)
.setKeyStorePassword(KEYSTORE_PASSWORD)
.setTrustStoreLocation(TRUSTSTORE)
.setTrustStorePassword(TRUSTSTORE_PASSWORD)
.setKeyAlias(CERT_ALIAS_APP2)
.build();
try
{
connection2.createSession(false, Session.AUTO_ACKNOWLEDGE).createTemporaryQueue();
}
finally
{
connection2.close();
}
}
@Test
public void externalUsernameAsDN() throws Exception
{
Map<String, Object> trustStoreAttributes = new HashMap<>();
trustStoreAttributes.put(FileTrustStore.STORE_URL, BROKER_TRUSTSTORE);
trustStoreAttributes.put(FileTrustStore.PASSWORD, BROKER_TRUSTSTORE_PASSWORD);
String clientId = getTestName();
int port = createExternalProviderAndTlsPort(trustStoreAttributes, null, true);
Connection connection = getConnectionBuilder().setSslPort(port)
.setTls(true)
.setSaslMechanisms(ExternalAuthenticationManagerImpl.MECHANISM_NAME)
.setKeyStoreLocation(KEYSTORE)
.setKeyStorePassword(KEYSTORE_PASSWORD)
.setTrustStoreLocation(TRUSTSTORE)
.setTrustStorePassword(TRUSTSTORE_PASSWORD)
.setKeyAlias(CERT_ALIAS_APP2)
.setClientId(clientId)
.build();
try
{
assertConnectionPrincipal( clientId, "CN=app2@acme.org,OU=art,O=acme,L=Toronto,ST=ON,C=CA");
}
catch (JMSException e)
{
fail("Should be able to create a connection to the SSL port: " + e.getMessage());
}
finally
{
connection.close();
}
}
@Test
public void externalUsernameAsCN() throws Exception
{
Map<String, Object> trustStoreAttributes = new HashMap<>();
trustStoreAttributes.put(FileTrustStore.STORE_URL, BROKER_TRUSTSTORE);
trustStoreAttributes.put(FileTrustStore.PASSWORD, BROKER_TRUSTSTORE_PASSWORD);
String clientId = getTestName();
int port = createExternalProviderAndTlsPort(trustStoreAttributes, null, false);
Connection connection = getConnectionBuilder().setSslPort(port)
.setTls(true)
.setSaslMechanisms(ExternalAuthenticationManagerImpl.MECHANISM_NAME)
.setKeyStoreLocation(KEYSTORE)
.setKeyStorePassword(KEYSTORE_PASSWORD)
.setTrustStoreLocation(TRUSTSTORE)
.setTrustStorePassword(TRUSTSTORE_PASSWORD)
.setKeyAlias(CERT_ALIAS_APP2)
.setClientId(clientId)
.build();
try
{
assertConnectionPrincipal( clientId, "app2@acme.org");
}
catch (JMSException e)
{
fail("Should be able to create a connection to the SSL port: " + e.getMessage());
}
finally
{
connection.close();
}
}
private void assertConnectionPrincipal(final String clientId, final String expectedPrincipal) throws Exception
{
Connection brokerConnection = getConnectionBuilder().setVirtualHost("$management").build();
try
{
brokerConnection.start();
String principal = null;
List<Map<String, Object>> connections = queryEntitiesUsingAmqpManagement("org.apache.qpid.Connection", brokerConnection);
for (Map<String, Object> connection : connections)
{
String name = String.valueOf(connection.get(ConfiguredObject.NAME));
Map<String, Object> attributes;
try
{
attributes = readEntityUsingAmqpManagement(
getPortName() + "/" + name,
"org.apache.qpid.Connection",
false,
brokerConnection);
}
catch (AmqpManagementFacade.OperationUnsuccessfulException e)
{
LOGGER.error("Read operation failed for an existing object '{}' having attributes '{}': {}",
getPortName() + "/" + name,
connection,
e.getMessage(),
e);
throw e;
}
if (attributes.get(org.apache.qpid.server.model.Connection.CLIENT_ID).equals(clientId))
{
principal = String.valueOf(attributes.get(org.apache.qpid.server.model.Connection.PRINCIPAL));
break;
}
}
assertEquals("Unexpected principal", expectedPrincipal, principal);
}
finally
{
brokerConnection.close();
}
}
private int createExternalProviderAndTlsPort() throws Exception
{
Map<String, Object> trustStoreAttributes = new HashMap<>();
trustStoreAttributes.put(FileTrustStore.STORE_URL, BROKER_TRUSTSTORE);
trustStoreAttributes.put(FileTrustStore.PASSWORD, BROKER_TRUSTSTORE_PASSWORD);
return createExternalProviderAndTlsPort(trustStoreAttributes);
}
private int createExternalProviderAndTlsPort(final Map<String, Object> trustStoreAttributes) throws Exception
{
return createExternalProviderAndTlsPort(trustStoreAttributes, null, false);
}
private int createExternalProviderAndTlsPort(final Map<String, Object> trustStoreAttributes,
final String additionalTrustStore,
final boolean useFullDN) throws Exception
{
final String providerName = getTestName();
Connection connection = getConnectionBuilder().setVirtualHost("$management").build();
try
{
connection.start();
Map<String, Object> providerAttributes = new HashMap<>();
providerAttributes.put("qpid-type", ExternalAuthenticationManager.PROVIDER_TYPE);
providerAttributes.put(ExternalAuthenticationManager.ATTRIBUTE_USE_FULL_DN, useFullDN);
createEntity(providerName,
AuthenticationProvider.class.getName(),
providerAttributes,
connection);
final Map<String, Object> keyStoreAttributes = new HashMap<>();
keyStoreAttributes.put("storeUrl", BROKER_KEYSTORE);
keyStoreAttributes.put("password", BROKER_KEYSTORE_PASSWORD);
keyStoreAttributes.put("keyStoreType", TestSSLConstants.JAVA_KEYSTORE_TYPE);
final String keyStoreName = providerName + "KeyStore";
createEntity(keyStoreName,
FileKeyStore.class.getName(),
keyStoreAttributes,
connection);
Map<String, Object> trustStoreSettings = new HashMap<>(trustStoreAttributes);
trustStoreSettings.put("trustStoreType", TestSSLConstants.JAVA_KEYSTORE_TYPE);
final String trustStoreName = providerName + "TrustStore";
createEntity(trustStoreName,
FileTrustStore.class.getName(),
trustStoreSettings,
connection);
String portName = getPortName();
Map<String, Object> sslPortAttributes = new HashMap<>();
sslPortAttributes.put(Port.TRANSPORTS, "[\"SSL\"]");
sslPortAttributes.put(Port.PORT, 0);
sslPortAttributes.put(Port.AUTHENTICATION_PROVIDER, providerName);
sslPortAttributes.put(Port.NEED_CLIENT_AUTH, true);
sslPortAttributes.put(Port.WANT_CLIENT_AUTH, false);
sslPortAttributes.put(Port.NAME, portName);
sslPortAttributes.put(Port.KEY_STORE, keyStoreName);
String trustStores = additionalTrustStore == null
? "[\"" + trustStoreName + "\"]"
: "[\"" + trustStoreName + "\",\"" + additionalTrustStore + "\"]";
sslPortAttributes.put(Port.TRUST_STORES, trustStores);
createEntity(portName,
"org.apache.qpid.AmqpPort",
sslPortAttributes,
connection);
Map<String, Object> portEffectiveAttributes =
readEntityUsingAmqpManagement(portName, "org.apache.qpid.AmqpPort", false, connection);
if (portEffectiveAttributes.containsKey("boundPort"))
{
return (int) portEffectiveAttributes.get("boundPort");
}
throw new RuntimeException("Bound port is not found");
}
finally
{
connection.close();
}
}
private String getPortName()
{
return getTestName() + "TlsPort";
}
private int createAuthenticationProviderAndUserAndPort(final String providerName,
final String providerType,
final String userName,
final String userPassword) throws Exception
{
Connection connection = getConnectionBuilder().setVirtualHost("$management").build();
try
{
connection.start();
createEntity(providerName,
AuthenticationProvider.class.getName(),
Collections.singletonMap("qpid-type", providerType),
connection);
final Map<String, Object> userAttributes = new HashMap<>();
userAttributes.put("qpid-type", "managed");
userAttributes.put(User.PASSWORD, userPassword);
userAttributes.put("object-path", providerName);
createEntity(userName, User.class.getName(), userAttributes, connection);
String portName = providerName + "Port";
final Map<String, Object> portAttributes = new HashMap<>();
portAttributes.put(Port.AUTHENTICATION_PROVIDER, providerName);
portAttributes.put(Port.PORT, 0);
createEntity(portName, "org.apache.qpid.AmqpPort", portAttributes, connection);
Map<String, Object> portEffectiveAttributes =
readEntityUsingAmqpManagement(portName, "org.apache.qpid.AmqpPort", false, connection);
if (portEffectiveAttributes.containsKey("boundPort"))
{
return (int) portEffectiveAttributes.get("boundPort");
}
throw new RuntimeException("Bound port is not found");
}
finally
{
connection.close();
}
}
private void assertConnectivity(final int port,
final String userName,
final String userPassword,
final String mechanism) throws Exception
{
Connection connection = getConnectionBuilder().setPort(port)
.setUsername(userName)
.setPassword(userPassword)
.setSaslMechanisms(mechanism)
.build();
try
{
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
TemporaryQueue queue = session.createTemporaryQueue();
assertNotNull("Temporary queue was not created", queue);
}
finally
{
connection.close();
}
try
{
getConnectionBuilder().setPort(port)
.setUsername(userName)
.setPassword("invalid" + userPassword)
.setSaslMechanisms(mechanism)
.build();
fail("Connection is established for invalid password");
}
catch (JMSException e)
{
// pass
}
try
{
getConnectionBuilder().setPort(port)
.setUsername("invalid" + userName)
.setPassword(userPassword)
.setSaslMechanisms(mechanism)
.build();
fail("Connection is established for invalid user name");
}
catch (JMSException e)
{
// pass
}
}
}