From 7d3e03d5035e13fd8acc147a0e5d741ebd37b4fa Mon Sep 17 00:00:00 2001 From: Robert Gemmell Date: Thu, 14 Mar 2013 17:19:20 +0000 Subject: QPID-4636: add support for a broker 'peerStore' that can be used to perform SSL client auth based on specific 'trusted peer' certs existing in it, rather than via use of a trusted CA cert. Applied patch from Michal Zerola git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1456554 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/qpid/server/model/Broker.java | 4 + .../org/apache/qpid/server/model/TrustStore.java | 2 + .../model/adapter/AbstractKeyStoreAdapter.java | 13 ++- .../qpid/server/model/adapter/AmqpPortAdapter.java | 31 +++--- .../qpid/server/model/adapter/BrokerAdapter.java | 16 +++ .../org/apache/qpid/ssl/SSLContextFactory.java | 111 +++++++++++++++++---- .../security/ssl/QpidMultipleTrustManager.java | 103 +++++++++++++++++++ .../security/ssl/QpidPeersOnlyTrustManager.java | 81 +++++++++++++++ 8 files changed, 324 insertions(+), 37 deletions(-) create mode 100644 qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidMultipleTrustManager.java create mode 100644 qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidPeersOnlyTrustManager.java diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java index c2b8b9886f..1d2fdd0452 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java @@ -87,6 +87,8 @@ public interface Broker extends ConfiguredObject String KEY_STORE_CERT_ALIAS = "keyStoreCertAlias"; String TRUST_STORE_PATH = "trustStorePath"; String TRUST_STORE_PASSWORD = "trustStorePassword"; + String PEER_STORE_PATH = "peerStorePath"; + String PEER_STORE_PASSWORD = "peerStorePassword"; /* * A temporary attributes to set the broker group file. @@ -136,6 +138,8 @@ public interface Broker extends ConfiguredObject KEY_STORE_CERT_ALIAS, TRUST_STORE_PATH, TRUST_STORE_PASSWORD, + PEER_STORE_PATH, + PEER_STORE_PASSWORD, GROUP_FILE )); diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/TrustStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/TrustStore.java index 0c322ae02f..53498ab431 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/TrustStore.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/TrustStore.java @@ -38,6 +38,7 @@ public interface TrustStore extends ConfiguredObject String PATH = "path"; String PASSWORD = "password"; + String PEERS_ONLY = "peersOnly"; String TYPE = "type"; String KEY_MANAGER_FACTORY_ALGORITHM = "keyManagerFactoryAlgorithm"; @@ -55,6 +56,7 @@ public interface TrustStore extends ConfiguredObject DESCRIPTION, PATH, PASSWORD, + PEERS_ONLY, TYPE, KEY_MANAGER_FACTORY_ALGORITHM )); diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.java index ebd98f915d..80196c395e 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.java @@ -47,6 +47,7 @@ public abstract class AbstractKeyStoreAdapter extends AbstractAdapter _name = MapValueConverter.getStringAttribute(TrustStore.NAME, attributes); _password = MapValueConverter.getStringAttribute(TrustStore.PASSWORD, attributes); setMandatoryAttribute(TrustStore.PATH, attributes); + setOptionalAttribute(TrustStore.PEERS_ONLY, attributes); setOptionalAttribute(TrustStore.TYPE, attributes); setOptionalAttribute(TrustStore.KEY_MANAGER_FACTORY_ALGORITHM, attributes); setOptionalAttribute(TrustStore.DESCRIPTION, attributes); @@ -190,9 +191,17 @@ public abstract class AbstractKeyStoreAdapter extends AbstractAdapter private void setOptionalAttribute(String name, Map attributeValues) { - if (attributeValues.get(name) != null) + Object attrValue = attributeValues.get(name); + if (attrValue != null) { - changeAttribute(name, null, MapValueConverter.getStringAttribute(name, attributeValues)); + if (attrValue instanceof Boolean) + { + changeAttribute(name, null, MapValueConverter.getBooleanAttribute(name, attributeValues)); + } + else + { + changeAttribute(name, null, MapValueConverter.getStringAttribute(name, attributeValues)); + } } } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java index 95aafa9ceb..e7057f89d3 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java @@ -24,6 +24,7 @@ import static org.apache.qpid.transport.ConnectionSettings.WILDCARD_ADDRESS; import java.io.IOException; import java.net.InetSocketAddress; import java.security.GeneralSecurityException; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Map; @@ -139,8 +140,8 @@ public class AmqpPortAdapter extends PortAdapter + this.getName() + "' but no key store defined"); } - TrustStore trustStore = _broker.getDefaultTrustStore(); - if (((Boolean)getAttribute(NEED_CLIENT_AUTH) || (Boolean)getAttribute(WANT_CLIENT_AUTH)) && trustStore == null) + Collection trustStores = _broker.getTrustStores(); + if (((Boolean)getAttribute(NEED_CLIENT_AUTH) || (Boolean)getAttribute(WANT_CLIENT_AUTH)) && trustStores.isEmpty()) { throw new IllegalConfigurationException("Client certificate authentication is enabled on AMQP port '" + this.getName() + "' but no trust store defined"); @@ -155,20 +156,20 @@ public class AmqpPortAdapter extends PortAdapter final SSLContext sslContext; try { - if(trustStore != null) + if(! trustStores.isEmpty()) { - String trustStorePassword = trustStore.getPassword(); - String trustStoreType = (String)trustStore.getAttribute(TrustStore.TYPE); - String trustManagerFactoryAlgorithm = (String)trustStore.getAttribute(TrustStore.KEY_MANAGER_FACTORY_ALGORITHM); - String trustStorePath = (String)trustStore.getAttribute(TrustStore.PATH); - - sslContext = SSLContextFactory.buildClientContext(trustStorePath, - trustStorePassword, - trustStoreType, - trustManagerFactoryAlgorithm, - keystorePath, - keystorePassword, keystoreType, keyManagerFactoryAlgorithm, - certAlias); + Collection trstWrappers = new ArrayList(); + for (TrustStore trustStore : trustStores) + { + trstWrappers.add(new SSLContextFactory.TrustStoreWrapper((String)trustStore.getAttribute(TrustStore.PATH), + trustStore.getPassword(), + (String)trustStore.getAttribute(TrustStore.TYPE), + (Boolean) trustStore.getAttribute(TrustStore.PEERS_ONLY), + (String)trustStore.getAttribute(TrustStore.KEY_MANAGER_FACTORY_ALGORITHM))); + } + sslContext = SSLContextFactory.buildClientContext(trstWrappers, keystorePath, + keystorePassword, keystoreType, + keyManagerFactoryAlgorithm, certAlias); } else { diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java index 291f751a5c..8c0ac06fdd 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java @@ -101,6 +101,8 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat put(KEY_STORE_CERT_ALIAS, String.class); put(TRUST_STORE_PATH, String.class); put(TRUST_STORE_PASSWORD, String.class); + put(PEER_STORE_PATH, String.class); + put(PEER_STORE_PASSWORD, String.class); put(GROUP_FILE, String.class); }}); @@ -231,6 +233,20 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat TrustStoreAdapter trustStore = new TrustStoreAdapter(_defaultTrustStoreId, this, trsustStoreAttributes); addTrustStore(trustStore); } + String peerStorePath = (String) getAttribute(PEER_STORE_PATH); + if (peerStorePath != null) + { + Map peerStoreAttributes = new HashMap(); + UUID peerStoreId = UUID.randomUUID(); + peerStoreAttributes.put(TrustStore.NAME, peerStoreId.toString()); + peerStoreAttributes.put(TrustStore.PATH, peerStorePath); + peerStoreAttributes.put(TrustStore.PEERS_ONLY, Boolean.TRUE); + peerStoreAttributes.put(TrustStore.PASSWORD, (String) actualAttributes.get(PEER_STORE_PASSWORD)); + peerStoreAttributes.put(TrustStore.TYPE, java.security.KeyStore.getDefaultType()); + peerStoreAttributes.put(TrustStore.KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm()); + TrustStoreAdapter trustStore = new TrustStoreAdapter(peerStoreId, this, peerStoreAttributes); + addTrustStore(trustStore); + } } public Collection getVirtualHosts() diff --git a/qpid/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java index b2967bb0bb..01381ad23f 100644 --- a/qpid/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java +++ b/qpid/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java @@ -21,6 +21,8 @@ package org.apache.qpid.ssl; import org.apache.qpid.transport.network.security.ssl.QpidClientX509KeyManager; +import org.apache.qpid.transport.network.security.ssl.QpidMultipleTrustManager; +import org.apache.qpid.transport.network.security.ssl.QpidPeersOnlyTrustManager; import org.apache.qpid.transport.network.security.ssl.SSLUtil; import javax.net.ssl.KeyManager; @@ -28,9 +30,16 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.security.cert.X509Certificate; + import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyStore; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; /** * Factory used to create SSLContexts. SSL needs to be configured @@ -40,6 +49,26 @@ import java.security.KeyStore; public class SSLContextFactory { public static final String TRANSPORT_LAYER_SECURITY_CODE = "TLS"; + + public static class TrustStoreWrapper + { + private final String trustStorePath; + private final String trustStorePassword; + private final String trustStoreType; + private final Boolean trustStorePeersOnly; + private String trustManagerFactoryAlgorithm; + + public TrustStoreWrapper(final String trustStorePath, final String trustStorePassword, + final String trustStoreType, final Boolean trustStorePeersOnly, + final String trustManagerFactoryAlgorithm) + { + this.trustStorePath = trustStorePath; + this.trustStorePassword = trustStorePassword; + this.trustStoreType = trustStoreType; + this.trustStorePeersOnly = trustStorePeersOnly; + this.trustManagerFactoryAlgorithm = trustManagerFactoryAlgorithm; + } + } private SSLContextFactory() { @@ -51,25 +80,34 @@ public class SSLContextFactory final String keyManagerFactoryAlgorithm) throws GeneralSecurityException, IOException { - return buildContext(null, null, null, null, keyStorePath, keyStorePassword, keyStoreType, - keyManagerFactoryAlgorithm, null); + return buildContext(Collections.emptyList(), keyStorePath, + keyStorePassword, keyStoreType, keyManagerFactoryAlgorithm, null); + } + + public static SSLContext buildClientContext(Collection trustStores, + final String keyStorePath, final String keyStorePassword, + final String keyStoreType, final String keyManagerFactoryAlgorithm, + final String certAlias) throws GeneralSecurityException, IOException + { + return buildContext(trustStores, keyStorePath, keyStorePassword, keyStoreType, + keyManagerFactoryAlgorithm, certAlias); } public static SSLContext buildClientContext(final String trustStorePath, final String trustStorePassword, final String trustStoreType, - final String trustManagerFactoryAlgorithm, final String keyStorePath, - final String keyStorePassword, final String keyStoreType, + final String trustManagerFactoryAlgorithm, final String keyStorePath, + final String keyStorePassword, final String keyStoreType, final String keyManagerFactoryAlgorithm, final String certAlias) throws GeneralSecurityException, IOException { - return buildContext(trustStorePath, trustStorePassword, trustStoreType, - trustManagerFactoryAlgorithm, keyStorePath, keyStorePassword, keyStoreType, - keyManagerFactoryAlgorithm, certAlias); + TrustStoreWrapper trstWrapper = new TrustStoreWrapper(trustStorePath, trustStorePassword, + trustStoreType, Boolean.FALSE, + trustManagerFactoryAlgorithm); + return buildContext(Collections.singletonList(trstWrapper), keyStorePath, + keyStorePassword, keyStoreType, keyManagerFactoryAlgorithm, certAlias); } - private static SSLContext buildContext(final String trustStorePath, - final String trustStorePassword, final String trustStoreType, - final String trustManagerFactoryAlgorithm, + private static SSLContext buildContext(final Collection trstWrappers, final String keyStorePath, final String keyStorePassword, final String keyStoreType, final String keyManagerFactoryAlgorithm, final String certAlias) @@ -81,21 +119,54 @@ public class SSLContextFactory final TrustManager[] trustManagers; final KeyManager[] keyManagers; - - if (trustStorePath != null) + + final Collection trustManagersCol = new ArrayList(); + final QpidMultipleTrustManager mulTrustManager = new QpidMultipleTrustManager(); + for (TrustStoreWrapper tsw : trstWrappers) { - final KeyStore ts = SSLUtil.getInitializedKeyStore(trustStorePath, - trustStorePassword, trustStoreType); - final TrustManagerFactory tmf = TrustManagerFactory - .getInstance(trustManagerFactoryAlgorithm); - tmf.init(ts); - - trustManagers = tmf.getTrustManagers(); + if (tsw.trustStorePath != null) + { + final KeyStore ts = SSLUtil.getInitializedKeyStore(tsw.trustStorePath, + tsw.trustStorePassword, tsw.trustStoreType); + final TrustManagerFactory tmf = TrustManagerFactory + .getInstance(tsw.trustManagerFactoryAlgorithm); + tmf.init(ts); + TrustManager[] delegateManagers = tmf.getTrustManagers(); + for (TrustManager tm : delegateManagers) + { + if (tm instanceof X509TrustManager) + { + if (Boolean.TRUE.equals(tsw.trustStorePeersOnly)) + { + // truststore is supposed to trust only clients which peers certificates + // are directly in the store. CA signing will not be considered. + mulTrustManager.addTrustManager(new QpidPeersOnlyTrustManager(ts, (X509TrustManager) tm)); + } + else + { + mulTrustManager.addTrustManager((X509TrustManager) tm); + } + } + else + { + trustManagersCol.add(tm); + } + } + } } - else + if (! mulTrustManager.isEmpty()) + { + trustManagersCol.add(mulTrustManager); + } + + if (trustManagersCol.isEmpty()) { trustManagers = null; } + else + { + trustManagers = trustManagersCol.toArray(new TrustManager[trustManagersCol.size()]); + } if (keyStorePath != null) { diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidMultipleTrustManager.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidMultipleTrustManager.java new file mode 100644 index 0000000000..0705f31fcb --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidMultipleTrustManager.java @@ -0,0 +1,103 @@ +/* +* +* 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.transport.network.security.ssl; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import javax.net.ssl.X509TrustManager; + +/** + * Supports multiple X509TrustManager(s). Check succeeds if any of the + * underlying managers succeeds. + */ +public class QpidMultipleTrustManager implements X509TrustManager { + + private List trustManagers; + + public QpidMultipleTrustManager() { + this.trustManagers = new ArrayList(); + } + + public boolean isEmpty() + { + return trustManagers.isEmpty(); + } + + public void addTrustManager(final X509TrustManager trustManager) + { + this.trustManagers.add(trustManager); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + for (X509TrustManager trustManager : this.trustManagers) + { + try + { + trustManager.checkClientTrusted(chain, authType); + // this trustManager check succeeded, no need to check another one + return; + } + catch(CertificateException ex) + { + // do nothing, try another one in a loop + } + } + // no trustManager call succeeded, throw an exception + throw new CertificateException(); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + for (X509TrustManager trustManager : this.trustManagers) + { + try + { + trustManager.checkServerTrusted(chain, authType); + // this trustManager check succeeded, no need to check another one + return; + } + catch(CertificateException ex) + { + // do nothing, try another one in a loop + } + } + // no trustManager call succeeded, throw an exception + throw new CertificateException(); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + final Collection accIssuersCol = new ArrayList(); + for (X509TrustManager trustManager : this.trustManagers) + { + accIssuersCol.addAll(Arrays.asList(trustManager.getAcceptedIssuers())); + } + return accIssuersCol.toArray(new X509Certificate[accIssuersCol.size()]); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidPeersOnlyTrustManager.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidPeersOnlyTrustManager.java new file mode 100644 index 0000000000..c988ff8d69 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidPeersOnlyTrustManager.java @@ -0,0 +1,81 @@ +/* +* +* 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.transport.network.security.ssl; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import javax.net.ssl.X509TrustManager; + +/** + * TrustManager implementation which accepts the client certificate + * only if the underlying check by the delegate pass through and + * the certificate is physically saved in the truststore. + */ +public class QpidPeersOnlyTrustManager implements X509TrustManager { + + final private KeyStore ts; + final private X509TrustManager delegate; + final List trustedCerts = new ArrayList(); + + public QpidPeersOnlyTrustManager(KeyStore ts, X509TrustManager trustManager) throws KeyStoreException { + this.ts = ts; + this.delegate = trustManager; + Enumeration aliases = this.ts.aliases(); + while (aliases.hasMoreElements()) + { + trustedCerts.add(ts.getCertificate(aliases.nextElement())); + } + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + this.delegate.checkClientTrusted(chain, authType); + for (Certificate serverTrustedCert : this.trustedCerts) + { + // first position in the chain contains the peer's own certificate + if (chain[0].equals(serverTrustedCert)) + return; // peer's certificate found in the store + } + // peer's certificate was not found in the store, do not trust the client + throw new CertificateException(); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + this.delegate.checkServerTrusted(chain, authType); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + // return empty array since this implementation of TrustManager doesn't + // rely on certification authorities + return new X509Certificate[0]; + } +} -- cgit v1.2.1