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