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}