| package org.apache.qpid.tests.http.endtoend.port; |
| |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| import static javax.servlet.http.HttpServletResponse.SC_CREATED; |
| import static javax.servlet.http.HttpServletResponse.SC_OK; |
| import static org.apache.qpid.test.utils.TestSSLConstants.JAVA_KEYSTORE_TYPE; |
| import static org.hamcrest.Matchers.equalTo; |
| import static org.hamcrest.Matchers.instanceOf; |
| import static org.hamcrest.Matchers.notNullValue; |
| import static org.hamcrest.core.Is.is; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertThat; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assume.assumeThat; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.security.KeyStoreException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.PrivateKey; |
| import java.security.cert.Certificate; |
| import java.security.cert.CertificateEncodingException; |
| import java.security.cert.CertificateException; |
| import java.security.cert.X509Certificate; |
| import java.time.Duration; |
| import java.time.Instant; |
| import java.time.temporal.ChronoUnit; |
| import java.util.Base64; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.jms.Connection; |
| import javax.jms.JMSException; |
| import javax.jms.Message; |
| import javax.jms.MessageConsumer; |
| import javax.jms.MessageProducer; |
| import javax.jms.Session; |
| import javax.jms.TextMessage; |
| import javax.naming.NamingException; |
| |
| import com.fasterxml.jackson.core.type.TypeReference; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| 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.Transport; |
| import org.apache.qpid.server.security.NonJavaKeyStore; |
| import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManager; |
| import org.apache.qpid.server.transport.network.security.ssl.SSLUtil; |
| import org.apache.qpid.server.util.DataUrlUtils; |
| import org.apache.qpid.systests.ConnectionBuilder; |
| import org.apache.qpid.tests.http.HttpTestBase; |
| import org.apache.qpid.tests.http.HttpTestHelper; |
| |
| public class PortTest extends HttpTestBase |
| { |
| private static final String PASS = "changeit"; |
| private static final String QUEUE_NAME = "testQueue"; |
| private static final TypeReference<Boolean> BOOLEAN = new TypeReference<Boolean>() |
| { |
| }; |
| private String _portName; |
| private String _authenticationProvider; |
| private String _keyStoreName; |
| private Set<File> _storeFiles; |
| private File _storeFile; |
| |
| @Before |
| public void setUp() throws Exception |
| { |
| assumeThat(SSLUtil.canGenerateCerts(), is(true)); |
| |
| _portName = getTestName(); |
| _authenticationProvider = _portName + "AuthenticationProvider"; |
| _keyStoreName = _portName + "KeyStore"; |
| createAnonymousAuthenticationProvider(); |
| final SSLUtil.KeyCertPair keyCertPair = createKeyStore(_keyStoreName); |
| final X509Certificate certificate = keyCertPair.getCertificate(); |
| |
| _storeFiles = new HashSet<>(); |
| _storeFile = createTrustStore(certificate); |
| |
| getBrokerAdmin().createQueue(QUEUE_NAME); |
| } |
| |
| |
| @After |
| public void tearDown() |
| { |
| _storeFiles.forEach(f -> assertTrue(f.delete())); |
| } |
| |
| @Test |
| public void testSwapKeyStoreAndUpdateTlsOnAmqpPort() throws Exception |
| { |
| final int port = createPort(Transport.SSL); |
| final Connection connection = createConnection(port, _storeFile.getAbsolutePath()); |
| try |
| { |
| final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); |
| final MessageProducer producer = session.createProducer(session.createQueue(QUEUE_NAME)); |
| producer.send(session.createTextMessage("A")); |
| |
| final SSLUtil.KeyCertPair keyCertPair = createKeyStoreAndUpdatePortTLS(); |
| final File storeFile = createTrustStore(keyCertPair.getCertificate()); |
| final Connection connection2 = createConnection(port, storeFile.getAbsolutePath()); |
| try |
| { |
| producer.send(session.createTextMessage("B")); |
| |
| final Session session2 = connection2.createSession(false, Session.AUTO_ACKNOWLEDGE); |
| final MessageConsumer consumer = session2.createConsumer(session2.createQueue(QUEUE_NAME)); |
| connection2.start(); |
| |
| assertMessage(consumer.receive(getReceiveTimeout()), "A"); |
| assertMessage(consumer.receive(getReceiveTimeout()), "B"); |
| } |
| finally |
| { |
| connection2.close(); |
| } |
| } |
| finally |
| { |
| connection.close(); |
| } |
| } |
| |
| @Test |
| public void testUpdateKeyStoreAndUpdateTlsOnAmqpPort() throws Exception |
| { |
| final int port = createPort(Transport.SSL); |
| final Connection connection = createConnection(port, _storeFile.getAbsolutePath()); |
| try |
| { |
| final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); |
| final MessageProducer producer = session.createProducer(session.createQueue(QUEUE_NAME)); |
| producer.send(session.createTextMessage("A")); |
| |
| final SSLUtil.KeyCertPair keyCertPair = updateKeyStoreAndUpdatePortTLS(); |
| final File storeFile = createTrustStore(keyCertPair.getCertificate()); |
| final Connection connection2 = createConnection(port, storeFile.getAbsolutePath()); |
| try |
| { |
| producer.send(session.createTextMessage("B")); |
| |
| final Session session2 = connection2.createSession(false, Session.AUTO_ACKNOWLEDGE); |
| final MessageConsumer consumer = session2.createConsumer(session2.createQueue(QUEUE_NAME)); |
| connection2.start(); |
| |
| assertMessage(consumer.receive(getReceiveTimeout()), "A"); |
| assertMessage(consumer.receive(getReceiveTimeout()), "B"); |
| } |
| finally |
| { |
| connection2.close(); |
| } |
| } |
| finally |
| { |
| connection.close(); |
| } |
| } |
| |
| @Test |
| public void testSwapKeyStoreAndUpdateTlsOnWssPort() throws Exception |
| { |
| assumeThat(getProtocol(), is(equalTo(Protocol.AMQP_1_0))); |
| final int port = createPort(Transport.WSS); |
| final Connection connection = createConnectionBuilder(port, _storeFile.getAbsolutePath()) |
| .setTransport("amqpws").build(); |
| try |
| { |
| final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); |
| final MessageProducer producer = session.createProducer(session.createQueue(QUEUE_NAME)); |
| producer.send(session.createTextMessage("A")); |
| |
| final SSLUtil.KeyCertPair keyCertPair = createKeyStoreAndUpdatePortTLS(); |
| final File storeFile = createTrustStore(keyCertPair.getCertificate()); |
| final Connection connection2 = createConnectionBuilder(port, storeFile.getAbsolutePath()) |
| .setTransport("amqpws").build(); |
| try |
| { |
| producer.send(session.createTextMessage("B")); |
| |
| final Session session2 = connection2.createSession(false, Session.AUTO_ACKNOWLEDGE); |
| final MessageConsumer consumer = session2.createConsumer(session2.createQueue(QUEUE_NAME)); |
| connection2.start(); |
| |
| assertMessage(consumer.receive(getReceiveTimeout()), "A"); |
| assertMessage(consumer.receive(getReceiveTimeout()), "B"); |
| } |
| finally |
| { |
| connection2.close(); |
| } |
| } |
| finally |
| { |
| connection.close(); |
| } |
| } |
| |
| @Test |
| public void testSwapKeyStoreAndUpdateTlsOnHttpPort() throws Exception |
| { |
| final int port = createHttpPort(); |
| |
| HttpTestHelper helper = new HttpTestHelper(getBrokerAdmin(), null, port); |
| helper.setTls(true); |
| helper.setKeyStore(_storeFile.getAbsolutePath(), PASS); |
| |
| final Map<String, Object> attributes = getHelper().getJsonAsMap("port/" + _portName); |
| final Map<String, Object> ownAttributes = helper.getJsonAsMap("port/" + _portName); |
| assertEquals(attributes, ownAttributes); |
| |
| final SSLUtil.KeyCertPair keyCertPair = createKeyStoreAndUpdatePortTLS(); |
| final File storeFile = createTrustStore(keyCertPair.getCertificate()); |
| helper.setKeyStore(storeFile.getAbsolutePath(), PASS); |
| |
| final Map<String, Object> attributes2 = getHelper().getJsonAsMap("port/" + _portName); |
| final Map<String, Object> ownAttributes2 = helper.getJsonAsMap("port/" + _portName); |
| assertEquals(attributes2, ownAttributes2); |
| } |
| |
| private void createAnonymousAuthenticationProvider() throws IOException |
| { |
| final Map<String, Object> data = Collections.singletonMap(ConfiguredObject.TYPE, |
| AnonymousAuthenticationManager.PROVIDER_TYPE); |
| getHelper().submitRequest("authenticationprovider/" + _authenticationProvider, "PUT", data, SC_CREATED); |
| } |
| |
| private SSLUtil.KeyCertPair createKeyStore(final String keyStoreName) throws Exception |
| { |
| return submitKeyStoreAttributes(keyStoreName, SC_CREATED); |
| } |
| |
| private SSLUtil.KeyCertPair submitKeyStoreAttributes(final String keyStoreName, final int status) throws Exception |
| { |
| final SSLUtil.KeyCertPair keyCertPair = generateSelfSignedCertificate(); |
| |
| final Map<String, Object> attributes = new HashMap<>(); |
| attributes.put(NonJavaKeyStore.NAME, keyStoreName); |
| attributes.put(NonJavaKeyStore.PRIVATE_KEY_URL, |
| DataUrlUtils.getDataUrlForBytes(toPEM(keyCertPair.getPrivateKey()).getBytes(UTF_8))); |
| attributes.put(NonJavaKeyStore.CERTIFICATE_URL, |
| DataUrlUtils.getDataUrlForBytes(toPEM(keyCertPair.getCertificate()).getBytes(UTF_8))); |
| attributes.put(NonJavaKeyStore.TYPE, "NonJavaKeyStore"); |
| |
| getHelper().submitRequest("keystore/" + keyStoreName, "PUT", attributes, status); |
| return keyCertPair; |
| } |
| |
| private ConnectionBuilder createConnectionBuilder(final int port, final String absolutePath) |
| { |
| return getConnectionBuilder().setPort(port) |
| .setTls(true) |
| .setVerifyHostName(false) |
| .setTrustStoreLocation(absolutePath) |
| .setTrustStorePassword(PASS); |
| } |
| |
| private Connection createConnection(final int port, final String absolutePath) |
| throws NamingException, JMSException |
| { |
| return createConnectionBuilder(port, absolutePath).build(); |
| } |
| |
| private int createPort(final Transport transport) throws IOException |
| { |
| return createPort("AMQP", transport); |
| } |
| |
| private int createHttpPort() throws IOException |
| { |
| return createPort("HTTP", Transport.SSL); |
| } |
| |
| private int createPort(final String type, final Transport transport) throws IOException |
| { |
| final Map<String, Object> port = new HashMap<>(); |
| port.put(Port.NAME, _portName); |
| port.put(Port.AUTHENTICATION_PROVIDER, _authenticationProvider); |
| port.put(Port.TYPE, type); |
| port.put(Port.PORT, 0); |
| port.put(Port.KEY_STORE, _keyStoreName); |
| port.put(Port.TRANSPORTS, Collections.singleton(transport)); |
| |
| getHelper().submitRequest("port/" + _portName, "PUT", port, SC_CREATED); |
| |
| return getBoundPort(); |
| } |
| |
| private int getBoundPort() throws IOException |
| { |
| final Map<String, Object> attributes = getHelper().getJsonAsMap("port/" + _portName); |
| assertTrue(attributes.containsKey("boundPort")); |
| assertTrue(attributes.get("boundPort") instanceof Number); |
| |
| return ((Number) attributes.get("boundPort")).intValue(); |
| } |
| |
| private File createTrustStore(final X509Certificate certificate) |
| throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException |
| { |
| final java.security.KeyStore ks = java.security.KeyStore.getInstance(JAVA_KEYSTORE_TYPE); |
| ks.load(null); |
| ks.setCertificateEntry("certificate", certificate); |
| final File storeFile = File.createTempFile(getTestName(), ".pkcs12"); |
| try (FileOutputStream fos = new FileOutputStream(storeFile)) |
| { |
| ks.store(fos, PASS.toCharArray()); |
| } |
| finally |
| { |
| _storeFiles.add(storeFile); |
| } |
| return storeFile; |
| } |
| |
| private SSLUtil.KeyCertPair generateSelfSignedCertificate() throws Exception |
| { |
| return SSLUtil.generateSelfSignedCertificate("RSA", |
| "SHA256WithRSA", |
| 2048, |
| Instant.now() |
| .minus(1, ChronoUnit.DAYS) |
| .toEpochMilli(), |
| Duration.of(365, ChronoUnit.DAYS) |
| .getSeconds(), |
| "CN=foo", |
| Collections.emptySet(), |
| Collections.emptySet()); |
| } |
| |
| private String toPEM(final Certificate pub) throws CertificateEncodingException |
| { |
| return toPEM(pub.getEncoded(), "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----"); |
| } |
| |
| private String toPEM(final PrivateKey key) |
| { |
| return toPEM(key.getEncoded(), "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----"); |
| } |
| |
| private String toPEM(final byte[] bytes, final String header, final String footer) |
| { |
| StringBuilder pem = new StringBuilder(); |
| pem.append(header).append("\n"); |
| String base64encoded = Base64.getEncoder().encodeToString(bytes); |
| while (base64encoded.length() > 76) |
| { |
| pem.append(base64encoded, 0, 76).append("\n"); |
| base64encoded = base64encoded.substring(76); |
| } |
| pem.append(base64encoded).append("\n"); |
| pem.append(footer).append("\n"); |
| return pem.toString(); |
| } |
| |
| private void assertMessage(final Message messageA, final String a) throws JMSException |
| { |
| assertThat(messageA, is(notNullValue())); |
| assertThat(messageA, is(instanceOf(TextMessage.class))); |
| assertThat(((TextMessage) messageA).getText(), is(equalTo(a))); |
| } |
| |
| private SSLUtil.KeyCertPair createKeyStoreAndUpdatePortTLS() throws Exception |
| { |
| final SSLUtil.KeyCertPair keyCertPair = createKeyStore(_keyStoreName + "_2"); |
| final Map<String, Object> data = Collections.singletonMap(Port.KEY_STORE, _keyStoreName + "_2"); |
| getHelper().submitRequest("port/" + _portName, "POST", data, SC_OK); |
| final boolean response = getHelper().postJson("port/" + _portName + "/updateTLS", |
| Collections.emptyMap(), |
| BOOLEAN, |
| SC_OK); |
| assertTrue(response); |
| |
| return keyCertPair; |
| } |
| |
| private SSLUtil.KeyCertPair updateKeyStoreAndUpdatePortTLS() throws Exception |
| { |
| final SSLUtil.KeyCertPair keyCertPair = submitKeyStoreAttributes(_keyStoreName, SC_OK); |
| final boolean response = getHelper().postJson("port/" + _portName + "/updateTLS", |
| Collections.emptyMap(), |
| BOOLEAN, |
| SC_OK); |
| assertTrue(response); |
| |
| return keyCertPair; |
| } |
| } |