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