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.Collections; 026 027import javax.net.ssl.SNIHostName; 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; 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 } 075 076 @Override 077 protected void initialiseSocket(Socket sock) throws SocketException, IllegalArgumentException { 078 /** 079 * This needs to default to null because this transport class is used for both a server transport 080 * and a client connection and we have different defaults for both. 081 * If we default it to a value it might override the transport server setting 082 * that was configured inside TcpTransportServer (which sets a default to false for server side) 083 * 084 * The idea here is that if this is a server transport then verifyHostName will be set by the setter 085 * and not be null as TcpTransportServer will set a default value of false (or a user will set it 086 * using transport.verifyHostName) but if this is a client connection the value will be null by default 087 * and will stay null if the user uses socket.verifyHostName to set the value or doesn't use the setter 088 * If it is null then we can check socketOptions for the value and if not set there then we can 089 * just set a default of true as this will be a client 090 * 091 * Unfortunately we have to do this to stay consistent because every other SSL option on the client 092 * side can be configured using socket. but this particular option isn't actually part of the socket 093 * so it makes it tricky from a user standpoint. For consistency sake I think it makes sense to allow 094 * using the socket. prefix that has been established so users do not get confused (as well as 095 * allow using no prefix which just calls the setter directly) 096 * 097 * Because of this there are actually two ways a client can configure this value, the client can either use 098 * socket.verifyHostName=<value> as mentioned or just simply use verifyHostName=<value> without using the socket. 099 * prefix and that will also work as the value will be set using the setter on the transport 100 * 101 * example server transport config: 102 * ssl://localhost:61616?transport.verifyHostName=true 103 * 104 * example from client: 105 * ssl://localhost:61616?verifyHostName=true 106 * OR 107 * ssl://localhost:61616?socket.verifyHostName=true 108 * 109 */ 110 if (verifyHostName == null) { 111 //Check to see if the user included the value as part of socket options and if so then use that value 112 if (socketOptions != null && socketOptions.containsKey("verifyHostName")) { 113 verifyHostName = Boolean.parseBoolean(socketOptions.get("verifyHostName").toString()); 114 socketOptions.remove("verifyHostName"); 115 } else { 116 //If null and not set then this is a client so default to true 117 verifyHostName = true; 118 } 119 } 120 121 // Lets try to configure the SSL SNI field. Handy in case your using 122 // a single proxy to route to different messaging apps. 123 final SSLParameters sslParams = new SSLParameters(); 124 if (remoteLocation != null) { 125 sslParams.setServerNames(Collections.singletonList(new SNIHostName(remoteLocation.getHost()))); 126 } 127 128 if (verifyHostName) { 129 sslParams.setEndpointIdentificationAlgorithm("HTTPS"); 130 } 131 132 if (remoteLocation != null || verifyHostName) { 133 // AMQ-8445 only set SSLParameters if it has been populated before 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 public SslTransport(WireFormat format, SSLSocket socket, 153 InitBuffer initBuffer) throws IOException { 154 super(format, socket, initBuffer); 155 } 156 157 /** 158 * Overriding in order to add the client's certificates to ConnectionInfo 159 * Commmands. 160 * 161 * @param command The Command coming in. 162 */ 163 @Override 164 public void doConsume(Object command) { 165 // The instanceof can be avoided, but that would require modifying the 166 // Command clas tree and that would require too much effort right 167 // now. 168 if (command instanceof ConnectionInfo) { 169 ConnectionInfo connectionInfo = (ConnectionInfo)command; 170 connectionInfo.setTransportContext(getPeerCertificates()); 171 } 172 super.doConsume(command); 173 } 174 175 public void setVerifyHostName(Boolean verifyHostName) { 176 this.verifyHostName = verifyHostName; 177 } 178 179 /** 180 * @return peer certificate chain associated with the ssl socket 181 */ 182 @Override 183 public X509Certificate[] getPeerCertificates() { 184 185 SSLSocket sslSocket = (SSLSocket)this.socket; 186 187 SSLSession sslSession = sslSocket.getSession(); 188 189 X509Certificate[] clientCertChain; 190 try { 191 clientCertChain = (X509Certificate[])sslSession.getPeerCertificates(); 192 } catch (SSLPeerUnverifiedException e) { 193 clientCertChain = null; 194 } 195 196 return clientCertChain; 197 } 198 199 /** 200 * @return pretty print of 'this' 201 */ 202 @Override 203 public String toString() { 204 return "ssl://" + socket.getInetAddress() + ":" + socket.getPort(); 205 } 206}