001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. 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, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.transport.tcp; 018 019import java.io.IOException; 020import java.net.Socket; 021import java.net.SocketException; 022import java.net.URI; 023import java.net.UnknownHostException; 024import java.security.cert.X509Certificate; 025import java.util.HashMap; 026 027import javax.net.ssl.SSLParameters; 028import javax.net.ssl.SSLPeerUnverifiedException; 029import javax.net.ssl.SSLSession; 030import javax.net.ssl.SSLSocket; 031import javax.net.ssl.SSLSocketFactory; 032 033import org.apache.activemq.command.ConnectionInfo; 034import org.apache.activemq.util.IntrospectionSupport; 035import org.apache.activemq.wireformat.WireFormat; 036 037/** 038 * A Transport class that uses SSL and client-side certificate authentication. 039 * Client-side certificate authentication must be enabled through the 040 * constructor. By default, this class will have the same client authentication 041 * behavior as the socket it is passed. This class will set ConnectionInfo's 042 * transportContext to the SSL certificates of the client. NOTE: Accessor method 043 * for needClientAuth was not provided on purpose. This is because 044 * needClientAuth's value must be set before the socket is connected. Otherwise, 045 * unexpected situations may occur. 046 */ 047public class SslTransport extends TcpTransport { 048 049 /** 050 * Default to null as there are different defaults between server and client, initialiseSocket 051 * for more details 052 */ 053 private Boolean verifyHostName = null; 054 055 /** 056 * Connect to a remote node such as a Broker. 057 * 058 * @param wireFormat The WireFormat to be used. 059 * @param socketFactory The socket factory to be used. Forcing SSLSockets 060 * for obvious reasons. 061 * @param remoteLocation The remote location. 062 * @param localLocation The local location. 063 * @param needClientAuth If set to true, the underlying socket will need 064 * client certificate authentication. 065 * @throws UnknownHostException If TcpTransport throws. 066 * @throws IOException If TcpTransport throws. 067 */ 068 @SuppressWarnings({ "unchecked", "rawtypes" }) 069 public SslTransport(WireFormat wireFormat, SSLSocketFactory socketFactory, URI remoteLocation, URI localLocation, boolean needClientAuth) throws IOException { 070 super(wireFormat, socketFactory, remoteLocation, localLocation); 071 if (this.socket != null) { 072 ((SSLSocket)this.socket).setNeedClientAuth(needClientAuth); 073 074 // Lets try to configure the SSL SNI field. Handy in case your using 075 // a single proxy to route to different messaging apps. 076 077 // On java 1.7 it seems like it can only be configured via reflection. 078 // TODO: find out if this will work on java 1.8 079 HashMap props = new HashMap(); 080 props.put("host", remoteLocation.getHost()); 081 IntrospectionSupport.setProperties(this.socket, props); 082 } 083 } 084 085 @Override 086 protected void initialiseSocket(Socket sock) throws SocketException, IllegalArgumentException { 087 /** 088 * This needs to default to null because this transport class is used for both a server transport 089 * and a client connection and we have different defaults for both. 090 * If we default it to a value it might override the transport server setting 091 * that was configured inside TcpTransportServer (which sets a default to false for server side) 092 * 093 * The idea here is that if this is a server transport then verifyHostName will be set by the setter 094 * and not be null as TcpTransportServer will set a default value of false (or a user will set it 095 * using transport.verifyHostName) but if this is a client connection the value will be null by default 096 * and will stay null if the user uses socket.verifyHostName to set the value or doesn't use the setter 097 * If it is null then we can check socketOptions for the value and if not set there then we can 098 * just set a default of true as this will be a client 099 * 100 * Unfortunately we have to do this to stay consistent because every other SSL option on the client 101 * side can be configured using socket. but this particular option isn't actually part of the socket 102 * so it makes it tricky from a user standpoint. For consistency sake I think it makes sense to allow 103 * using the socket. prefix that has been established so users do not get confused (as well as 104 * allow using no prefix which just calls the setter directly) 105 * 106 * Because of this there are actually two ways a client can configure this value, the client can either use 107 * socket.verifyHostName=<value> as mentioned or just simply use verifyHostName=<value> without using the socket. 108 * prefix and that will also work as the value will be set using the setter on the transport 109 * 110 * example server transport config: 111 * ssl://localhost:61616?transport.verifyHostName=true 112 * 113 * example from client: 114 * ssl://localhost:61616?verifyHostName=true 115 * OR 116 * ssl://localhost:61616?socket.verifyHostName=true 117 * 118 */ 119 if (verifyHostName == null) { 120 //Check to see if the user included the value as part of socket options and if so then use that value 121 if (socketOptions != null && socketOptions.containsKey("verifyHostName")) { 122 verifyHostName = Boolean.parseBoolean(socketOptions.get("verifyHostName").toString()); 123 socketOptions.remove("verifyHostName"); 124 } else { 125 //If null and not set then this is a client so default to true 126 verifyHostName = true; 127 } 128 } 129 130 if (verifyHostName) { 131 SSLParameters sslParams = new SSLParameters(); 132 sslParams.setEndpointIdentificationAlgorithm("HTTPS"); 133 ((SSLSocket)this.socket).setSSLParameters(sslParams); 134 } 135 136 super.initialiseSocket(sock); 137 } 138 139 /** 140 * Initialize from a ServerSocket. No access to needClientAuth is given 141 * since it is already set within the provided socket. 142 * 143 * @param wireFormat The WireFormat to be used. 144 * @param socket The Socket to be used. Forcing SSL. 145 * @throws IOException If TcpTransport throws. 146 */ 147 public SslTransport(WireFormat wireFormat, SSLSocket socket) throws IOException { 148 super(wireFormat, socket); 149 } 150 151 public SslTransport(WireFormat format, SSLSocket socket, 152 InitBuffer initBuffer) throws IOException { 153 super(format, socket, initBuffer); 154 } 155 156 /** 157 * Overriding in order to add the client's certificates to ConnectionInfo 158 * Commmands. 159 * 160 * @param command The Command coming in. 161 */ 162 @Override 163 public void doConsume(Object command) { 164 // The instanceof can be avoided, but that would require modifying the 165 // Command clas tree and that would require too much effort right 166 // now. 167 if (command instanceof ConnectionInfo) { 168 ConnectionInfo connectionInfo = (ConnectionInfo)command; 169 connectionInfo.setTransportContext(getPeerCertificates()); 170 } 171 super.doConsume(command); 172 } 173 174 public void setVerifyHostName(Boolean verifyHostName) { 175 this.verifyHostName = verifyHostName; 176 } 177 178 /** 179 * @return peer certificate chain associated with the ssl socket 180 */ 181 @Override 182 public X509Certificate[] getPeerCertificates() { 183 184 SSLSocket sslSocket = (SSLSocket)this.socket; 185 186 SSLSession sslSession = sslSocket.getSession(); 187 188 X509Certificate[] clientCertChain; 189 try { 190 clientCertChain = (X509Certificate[])sslSession.getPeerCertificates(); 191 } catch (SSLPeerUnverifiedException e) { 192 clientCertChain = null; 193 } 194 195 return clientCertChain; 196 } 197 198 /** 199 * @return pretty print of 'this' 200 */ 201 @Override 202 public String toString() { 203 return "ssl://" + socket.getInetAddress() + ":" + socket.getPort(); 204 } 205}