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 */
017package org.apache.activemq.transport.https;
018
019import java.io.IOException;
020import java.net.ServerSocket;
021import java.security.KeyManagementException;
022import java.security.NoSuchAlgorithmException;
023import java.security.NoSuchProviderException;
024import java.security.Principal;
025import java.util.Collections;
026import java.util.List;
027import java.util.Random;
028import javax.net.ssl.SSLContext;
029import javax.net.ssl.SSLServerSocket;
030import javax.net.ssl.SSLSocket;
031
032import org.eclipse.jetty.http.HttpSchemes;
033import org.eclipse.jetty.io.EndPoint;
034import org.eclipse.jetty.server.Request;
035import org.eclipse.jetty.server.ssl.ServletSSL;
036import org.eclipse.jetty.server.ssl.SslSocketConnector;
037import org.eclipse.jetty.util.ssl.SslContextFactory;
038import org.slf4j.Logger;
039import 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 */
047public 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    public Krb5AndCertsSslSocketConnector(SslContextFactory f, String auth) {
072        // By default, stick to cert based authentication
073        super(f);
074        useKrb = false;
075        useCerts = true;
076        setPasswords();
077        setMode(auth);
078    }
079
080    public static boolean isKrb(String mode) {
081        return mode == MODE.KRB.toString() || mode == MODE.BOTH.toString();
082    }
083
084    public void setMode(String mode) {
085        useKrb = mode == MODE.KRB.toString() || mode == MODE.BOTH.toString();
086        useCerts = mode == MODE.CERTS.toString() || mode == MODE.BOTH.toString();
087        logIfDebug("useKerb = " + useKrb + ", useCerts = " + useCerts);
088    }
089
090    // If not using Certs, set passwords to random gibberish or else
091    // Jetty will actually prompt the user for some.
092    private void setPasswords() {
093        if (!useCerts) {
094            Random r = new Random();
095            System.setProperty("jetty.ssl.password", String.valueOf(r.nextLong()));
096            System.setProperty("jetty.ssl.keypassword", String.valueOf(r.nextLong()));
097        }
098    }
099
100    @Override
101    public SslContextFactory getSslContextFactory() {
102        final SslContextFactory factory = super.getSslContextFactory();
103
104        if (useCerts) {
105            return factory;
106        }
107
108        try {
109            SSLContext context = factory.getProvider() == null ? SSLContext.getInstance(factory.getProtocol()) : SSLContext.getInstance(factory.getProtocol(),
110                factory.getProvider());
111            context.init(null, null, null);
112            factory.setSslContext(context);
113        } catch (NoSuchAlgorithmException e) {
114        } catch (NoSuchProviderException e) {
115        } catch (KeyManagementException e) {
116        }
117
118        return factory;
119    }
120
121    /*
122     * (non-Javadoc)
123     *
124     * @see
125     * org.mortbay.jetty.security.SslSocketConnector#newServerSocket(java.lang
126     * .String, int, int)
127     */
128    @Override
129    protected ServerSocket newServerSocket(String host, int port, int backlog) throws IOException {
130        logIfDebug("Creating new KrbServerSocket for: " + host);
131        SSLServerSocket ss = null;
132
133        if (useCerts) // Get the server socket from the SSL super impl
134            ss = (SSLServerSocket) super.newServerSocket(host, port, backlog);
135        else { // Create a default server socket
136            try {
137                ss = (SSLServerSocket) super.newServerSocket(host, port, backlog);
138            } catch (Exception e) {
139                LOG.warn("Could not create KRB5 Listener", e);
140                throw new IOException("Could not create KRB5 Listener: " + e.toString());
141            }
142        }
143
144        // Add Kerberos ciphers to this socket server if needed.
145        if (useKrb) {
146            ss.setNeedClientAuth(true);
147            String[] combined;
148            if (useCerts) { // combine the cipher suites
149                String[] certs = ss.getEnabledCipherSuites();
150                combined = new String[certs.length + KRB5_CIPHER_SUITES.size()];
151                System.arraycopy(certs, 0, combined, 0, certs.length);
152                System.arraycopy(KRB5_CIPHER_SUITES.toArray(new String[0]), 0, combined, certs.length, KRB5_CIPHER_SUITES.size());
153            } else { // Just enable Kerberos auth
154                combined = KRB5_CIPHER_SUITES.toArray(new String[0]);
155            }
156
157            ss.setEnabledCipherSuites(combined);
158        }
159        return ss;
160    };
161
162    @Override
163    public void customize(EndPoint endpoint, Request request) throws IOException {
164        if (useKrb) { // Add Kerberos-specific info
165            SSLSocket sslSocket = (SSLSocket) endpoint.getTransport();
166            Principal remotePrincipal = sslSocket.getSession().getPeerPrincipal();
167            logIfDebug("Remote principal = " + remotePrincipal);
168            request.setScheme(HttpSchemes.HTTPS);
169            request.setAttribute(REMOTE_PRINCIPAL, remotePrincipal);
170
171            if (!useCerts) { // Add extra info that would have been added by
172                             // super
173                String cipherSuite = sslSocket.getSession().getCipherSuite();
174                Integer keySize = Integer.valueOf(ServletSSL.deduceKeyLength(cipherSuite));
175                ;
176
177                request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite);
178                request.setAttribute("javax.servlet.request.key_size", keySize);
179            }
180        }
181
182        if (useCerts)
183            super.customize(endpoint, request);
184    }
185
186    private void logIfDebug(String s) {
187        if (LOG.isDebugEnabled())
188            LOG.debug(s);
189    }
190}