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 private Boolean verifyHostName = null; 050 051 /** 052 * Connect to a remote node such as a Broker. 053 * 054 * @param wireFormat The WireFormat to be used. 055 * @param socketFactory The socket factory to be used. Forcing SSLSockets 056 * for obvious reasons. 057 * @param remoteLocation The remote location. 058 * @param localLocation The local location. 059 * @param needClientAuth If set to true, the underlying socket will need 060 * client certificate authentication. 061 * @throws UnknownHostException If TcpTransport throws. 062 * @throws IOException If TcpTransport throws. 063 */ 064 @SuppressWarnings({ "unchecked", "rawtypes" }) 065 public SslTransport(WireFormat wireFormat, SSLSocketFactory socketFactory, URI remoteLocation, URI localLocation, boolean needClientAuth) throws IOException { 066 super(wireFormat, socketFactory, remoteLocation, localLocation); 067 if (this.socket != null) { 068 ((SSLSocket)this.socket).setNeedClientAuth(needClientAuth); 069 070 // Lets try to configure the SSL SNI field. Handy in case your using 071 // a single proxy to route to different messaging apps. 072 073 // On java 1.7 it seems like it can only be configured via reflection. 074 // TODO: find out if this will work on java 1.8 075 HashMap props = new HashMap(); 076 props.put("host", remoteLocation.getHost()); 077 IntrospectionSupport.setProperties(this.socket, props); 078 } 079 } 080 081 @Override 082 protected void initialiseSocket(Socket sock) throws SocketException, IllegalArgumentException { 083 //This needs to default to null because this transport class is used for both a server transport 084 //and a client connection and if we default it to a value it might override the transport server setting 085 //that was configured inside TcpTransportServer 086 087 //The idea here is that if this is a server transport then verifyHostName will be set by the setter 088 //below and not be null (if using transport.verifyHostName) but if a client uses socket.verifyHostName 089 //then it will be null and we can check socketOptions 090 091 //Unfortunately we have to do this to stay consistent because every other SSL option on the client 092 //side is configured using socket. but this particular option isn't actually part of the socket 093 //so it makes it tricky 094 if (verifyHostName == null) { 095 if (socketOptions != null && socketOptions.containsKey("verifyHostName")) { 096 verifyHostName = Boolean.parseBoolean(socketOptions.get("verifyHostName").toString()); 097 socketOptions.remove("verifyHostName"); 098 } else { 099 //If null and not set then this is a client so default to true 100 verifyHostName = true; 101 } 102 } 103 104 if (verifyHostName) { 105 SSLParameters sslParams = new SSLParameters(); 106 sslParams.setEndpointIdentificationAlgorithm("HTTPS"); 107 ((SSLSocket)this.socket).setSSLParameters(sslParams); 108 } 109 110 super.initialiseSocket(sock); 111 } 112 113 /** 114 * Initialize from a ServerSocket. No access to needClientAuth is given 115 * since it is already set within the provided socket. 116 * 117 * @param wireFormat The WireFormat to be used. 118 * @param socket The Socket to be used. Forcing SSL. 119 * @throws IOException If TcpTransport throws. 120 */ 121 public SslTransport(WireFormat wireFormat, SSLSocket socket) throws IOException { 122 super(wireFormat, socket); 123 } 124 125 public SslTransport(WireFormat format, SSLSocket socket, 126 InitBuffer initBuffer) throws IOException { 127 super(format, socket, initBuffer); 128 } 129 130 /** 131 * Overriding in order to add the client's certificates to ConnectionInfo 132 * Commmands. 133 * 134 * @param command The Command coming in. 135 */ 136 @Override 137 public void doConsume(Object command) { 138 // The instanceof can be avoided, but that would require modifying the 139 // Command clas tree and that would require too much effort right 140 // now. 141 if (command instanceof ConnectionInfo) { 142 ConnectionInfo connectionInfo = (ConnectionInfo)command; 143 connectionInfo.setTransportContext(getPeerCertificates()); 144 } 145 super.doConsume(command); 146 } 147 148 public void setVerifyHostName(Boolean verifyHostName) { 149 this.verifyHostName = verifyHostName; 150 } 151 152 /** 153 * @return peer certificate chain associated with the ssl socket 154 */ 155 @Override 156 public X509Certificate[] getPeerCertificates() { 157 158 SSLSocket sslSocket = (SSLSocket)this.socket; 159 160 SSLSession sslSession = sslSocket.getSession(); 161 162 X509Certificate[] clientCertChain; 163 try { 164 clientCertChain = (X509Certificate[])sslSession.getPeerCertificates(); 165 } catch (SSLPeerUnverifiedException e) { 166 clientCertChain = null; 167 } 168 169 return clientCertChain; 170 } 171 172 /** 173 * @return pretty print of 'this' 174 */ 175 @Override 176 public String toString() { 177 return "ssl://" + socket.getInetAddress() + ":" + socket.getPort(); 178 } 179}