001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements. See the NOTICE file distributed with this
004     * work for additional information regarding copyright ownership. The ASF
005     * licenses this file to you under the Apache License, Version 2.0 (the
006     * "License"); you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     * http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014     * License for the specific language governing permissions and limitations under
015     * the License.
016     */
017    package org.apache.activemq.transport.https;
018    
019    import java.io.IOException;
020    import java.net.ServerSocket;
021    import java.security.KeyManagementException;
022    import java.security.NoSuchAlgorithmException;
023    import java.security.NoSuchProviderException;
024    import java.security.Principal;
025    import java.util.Collections;
026    import java.util.List;
027    import java.util.Random;
028    import javax.net.ssl.SSLContext;
029    import javax.net.ssl.SSLServerSocket;
030    import javax.net.ssl.SSLSocket;
031    
032    import org.eclipse.jetty.http.HttpSchemes;
033    import org.eclipse.jetty.io.EndPoint;
034    import org.eclipse.jetty.server.Request;
035    import org.eclipse.jetty.server.ssl.ServletSSL;
036    import org.eclipse.jetty.server.ssl.SslSocketConnector;
037    import org.eclipse.jetty.util.ssl.SslContextFactory;
038    import org.slf4j.Logger;
039    import org.slf4j.LoggerFactory;
040    
041    /**
042     * Extend Jetty's {@link SslSocketConnector} to optionally also provide
043     * Kerberos5ized SSL sockets. The only change in behavior from superclass is
044     * that we no longer honor requests to turn off NeedAuthentication when running
045     * with Kerberos support.
046     */
047    public class Krb5AndCertsSslSocketConnector extends SslSocketConnector {
048        public static final List<String> KRB5_CIPHER_SUITES = Collections.unmodifiableList(Collections.singletonList("TLS_KRB5_WITH_3DES_EDE_CBC_SHA"));
049        static {
050            System.setProperty("https.cipherSuites", KRB5_CIPHER_SUITES.get(0));
051        }
052    
053        private static final Logger LOG = LoggerFactory.getLogger(Krb5AndCertsSslSocketConnector.class);
054    
055        private static final String REMOTE_PRINCIPAL = "remote_principal";
056    
057        public enum MODE {
058            KRB, CERTS, BOTH
059        } // Support Kerberos, certificates or both?
060    
061        private boolean useKrb;
062        private boolean useCerts;
063    
064        public Krb5AndCertsSslSocketConnector() {
065            // By default, stick to cert based authentication
066            super();
067            useKrb = false;
068            useCerts = true;
069            setPasswords();
070        }
071    
072        public static boolean isKrb(String mode) {
073            return mode == MODE.KRB.toString() || mode == MODE.BOTH.toString();
074        }
075    
076        public void setMode(String mode) {
077            useKrb = mode == MODE.KRB.toString() || mode == MODE.BOTH.toString();
078            useCerts = mode == MODE.CERTS.toString() || mode == MODE.BOTH.toString();
079            logIfDebug("useKerb = " + useKrb + ", useCerts = " + useCerts);
080        }
081    
082        // If not using Certs, set passwords to random gibberish or else
083        // Jetty will actually prompt the user for some.
084        private void setPasswords() {
085            if (!useCerts) {
086                Random r = new Random();
087                System.setProperty("jetty.ssl.password", String.valueOf(r.nextLong()));
088                System.setProperty("jetty.ssl.keypassword", String.valueOf(r.nextLong()));
089            }
090        }
091    
092        @Override
093        public SslContextFactory getSslContextFactory() {
094            final SslContextFactory factory = super.getSslContextFactory();
095    
096            if (useCerts) {
097                return factory;
098            }
099    
100            try {
101                SSLContext context = factory.getProvider() == null ? SSLContext.getInstance(factory.getProtocol()) : SSLContext.getInstance(factory.getProtocol(),
102                    factory.getProvider());
103                context.init(null, null, null);
104                factory.setSslContext(context);
105            } catch (NoSuchAlgorithmException e) {
106            } catch (NoSuchProviderException e) {
107            } catch (KeyManagementException e) {
108            }
109    
110            return factory;
111        }
112    
113        /*
114         * (non-Javadoc)
115         *
116         * @see
117         * org.mortbay.jetty.security.SslSocketConnector#newServerSocket(java.lang
118         * .String, int, int)
119         */
120        @Override
121        protected ServerSocket newServerSocket(String host, int port, int backlog) throws IOException {
122            logIfDebug("Creating new KrbServerSocket for: " + host);
123            SSLServerSocket ss = null;
124    
125            if (useCerts) // Get the server socket from the SSL super impl
126                ss = (SSLServerSocket) super.newServerSocket(host, port, backlog);
127            else { // Create a default server socket
128                try {
129                    ss = (SSLServerSocket) super.newServerSocket(host, port, backlog);
130                } catch (Exception e) {
131                    LOG.warn("Could not create KRB5 Listener", e);
132                    throw new IOException("Could not create KRB5 Listener: " + e.toString());
133                }
134            }
135    
136            // Add Kerberos ciphers to this socket server if needed.
137            if (useKrb) {
138                ss.setNeedClientAuth(true);
139                String[] combined;
140                if (useCerts) { // combine the cipher suites
141                    String[] certs = ss.getEnabledCipherSuites();
142                    combined = new String[certs.length + KRB5_CIPHER_SUITES.size()];
143                    System.arraycopy(certs, 0, combined, 0, certs.length);
144                    System.arraycopy(KRB5_CIPHER_SUITES.toArray(new String[0]), 0, combined, certs.length, KRB5_CIPHER_SUITES.size());
145                } else { // Just enable Kerberos auth
146                    combined = KRB5_CIPHER_SUITES.toArray(new String[0]);
147                }
148    
149                ss.setEnabledCipherSuites(combined);
150            }
151            return ss;
152        };
153    
154        @Override
155        public void customize(EndPoint endpoint, Request request) throws IOException {
156            if (useKrb) { // Add Kerberos-specific info
157                SSLSocket sslSocket = (SSLSocket) endpoint.getTransport();
158                Principal remotePrincipal = sslSocket.getSession().getPeerPrincipal();
159                logIfDebug("Remote principal = " + remotePrincipal);
160                request.setScheme(HttpSchemes.HTTPS);
161                request.setAttribute(REMOTE_PRINCIPAL, remotePrincipal);
162    
163                if (!useCerts) { // Add extra info that would have been added by
164                                 // super
165                    String cipherSuite = sslSocket.getSession().getCipherSuite();
166                    Integer keySize = Integer.valueOf(ServletSSL.deduceKeyLength(cipherSuite));
167                    ;
168    
169                    request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite);
170                    request.setAttribute("javax.servlet.request.key_size", keySize);
171                }
172            }
173    
174            if (useCerts)
175                super.customize(endpoint, request);
176        }
177    
178        private void logIfDebug(String s) {
179            if (LOG.isDebugEnabled())
180                LOG.debug(s);
181        }
182    }