View Javadoc
1   /*
2    * Copyright 2019 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.ssl;
17  
18  import io.netty.buffer.ByteBufUtil;
19  import io.netty.channel.ChannelHandlerContext;
20  import io.netty.channel.ChannelInboundHandlerAdapter;
21  import io.netty.util.internal.ReflectionUtil;
22  import io.netty.util.internal.SystemPropertyUtil;
23  import io.netty.util.internal.logging.InternalLogger;
24  import io.netty.util.internal.logging.InternalLoggerFactory;
25  
26  import javax.crypto.SecretKey;
27  import javax.crypto.spec.SecretKeySpec;
28  import javax.net.ssl.SSLEngine;
29  import javax.net.ssl.SSLSession;
30  import java.lang.reflect.Field;
31  
32  /**
33   * The {@link SslMasterKeyHandler} is a channel-handler you can include in your pipeline to consume the master key
34   * & session identifier for a TLS session.
35   * This can be very useful, for instance the {@link WiresharkSslMasterKeyHandler} implementation will
36   * log the secret & identifier in a format that is consumable by Wireshark -- allowing easy decryption of pcap/tcpdumps.
37   */
38  public abstract class SslMasterKeyHandler extends ChannelInboundHandlerAdapter {
39  
40      private static final InternalLogger logger = InternalLoggerFactory.getInstance(SslMasterKeyHandler.class);
41  
42      /**
43       * The JRE SSLSessionImpl cannot be imported
44       */
45      private static final Class<?> SSL_SESSIONIMPL_CLASS;
46  
47      /**
48       * The master key field in the SSLSessionImpl
49       */
50      private static final Field SSL_SESSIONIMPL_MASTER_SECRET_FIELD;
51  
52      /**
53       * A system property that can be used to turn on/off the {@link SslMasterKeyHandler} dynamically without having
54       * to edit your pipeline.
55       * <code>-Dio.netty.ssl.masterKeyHandler=true</code>
56       */
57      public static final String SYSTEM_PROP_KEY = "io.netty.ssl.masterKeyHandler";
58  
59      /**
60       * The unavailability cause of whether the private Sun implementation of SSLSessionImpl is available.
61       */
62      private static final Throwable UNAVAILABILITY_CAUSE;
63  
64      static {
65          Throwable cause;
66          Class<?> clazz = null;
67          Field field = null;
68          try {
69              clazz = Class.forName("sun.security.ssl.SSLSessionImpl");
70              field = clazz.getDeclaredField("masterSecret");
71              cause = ReflectionUtil.trySetAccessible(field, true);
72          } catch (Throwable e) {
73              cause = e;
74              if (logger.isTraceEnabled()) {
75                  logger.debug("sun.security.ssl.SSLSessionImpl is unavailable.", e);
76              } else {
77                  logger.debug("sun.security.ssl.SSLSessionImpl is unavailable: {}", e.getMessage());
78              }
79          }
80          UNAVAILABILITY_CAUSE = cause;
81          SSL_SESSIONIMPL_CLASS = clazz;
82          SSL_SESSIONIMPL_MASTER_SECRET_FIELD = field;
83      }
84  
85      /**
86       * Constructor.
87      */
88      protected SslMasterKeyHandler() {
89      }
90  
91      /**
92       * Ensure that SSLSessionImpl is available.
93       * @throws UnsatisfiedLinkError if unavailable
94       */
95      public static void ensureSunSslEngineAvailability() {
96          if (UNAVAILABILITY_CAUSE != null) {
97              throw new IllegalStateException(
98                      "Failed to find SSLSessionImpl on classpath", UNAVAILABILITY_CAUSE);
99          }
100     }
101 
102     /**
103      * Returns the cause of unavailability.
104      *
105      * @return the cause if unavailable. {@code null} if available.
106      */
107     public static Throwable sunSslEngineUnavailabilityCause() {
108         return UNAVAILABILITY_CAUSE;
109     }
110 
111     /* Returns {@code true} if and only if sun.security.ssl.SSLSessionImpl exists in the runtime.
112      */
113     public static boolean isSunSslEngineAvailable() {
114         return UNAVAILABILITY_CAUSE == null;
115     }
116 
117     /**
118      * Consume the master key for the session and the sessionId
119      * @param masterKey A 48-byte secret shared between the client and server.
120      * @param session The current TLS session
121      */
122     protected abstract void accept(SecretKey masterKey, SSLSession session);
123 
124     @Override
125     public final void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
126         //only try to log the session info if the ssl handshake has successfully completed.
127         if (evt == SslHandshakeCompletionEvent.SUCCESS && masterKeyHandlerEnabled()) {
128             final SslHandler handler = ctx.pipeline().get(SslHandler.class);
129             final SSLEngine engine = handler.engine();
130             final SSLSession sslSession = engine.getSession();
131 
132             //the OpenJDK does not expose a way to get the master secret, so try to use reflection to get it.
133             if (isSunSslEngineAvailable() && sslSession.getClass().equals(SSL_SESSIONIMPL_CLASS)) {
134                 final SecretKey secretKey;
135                 try {
136                     secretKey = (SecretKey) SSL_SESSIONIMPL_MASTER_SECRET_FIELD.get(sslSession);
137                 } catch (IllegalAccessException e) {
138                     throw new IllegalArgumentException("Failed to access the field 'masterSecret' " +
139                             "via reflection.", e);
140                 }
141                 accept(secretKey, sslSession);
142             } else if (OpenSsl.isAvailable() && engine instanceof ReferenceCountedOpenSslEngine) {
143                 SecretKeySpec secretKey = ((ReferenceCountedOpenSslEngine) engine).masterKey();
144                 accept(secretKey, sslSession);
145             }
146         }
147 
148         ctx.fireUserEventTriggered(evt);
149     }
150 
151     /**
152      * Checks if the handler is set up to actually handle/accept the event.
153      * By default the {@link #SYSTEM_PROP_KEY} property is checked, but any implementations of this class are
154      * free to override if they have different mechanisms of checking.
155      *
156      * @return true if it should handle, false otherwise.
157      */
158     protected boolean masterKeyHandlerEnabled() {
159         return SystemPropertyUtil.getBoolean(SYSTEM_PROP_KEY, false);
160     }
161 
162     /**
163      * Create a {@link WiresharkSslMasterKeyHandler} instance.
164      * This TLS master key handler logs the master key and session-id in a format
165      * understood by Wireshark -- this can be especially useful if you need to ever
166      * decrypt a TLS session and are using perfect forward secrecy (i.e. Diffie-Hellman)
167      * The key and session identifier are forwarded to the log named 'io.netty.wireshark'.
168      */
169     public static SslMasterKeyHandler newWireSharkSslMasterKeyHandler() {
170         return new WiresharkSslMasterKeyHandler();
171     }
172 
173     /**
174      * Record the session identifier and master key to the {@link InternalLogger} named <code>io.netty.wireshark</code>.
175      * ex. <code>RSA Session-ID:XXX Master-Key:YYY</code>
176      * This format is understood by Wireshark 1.6.0.
177      * https://code.wireshark.org/review/gitweb?p=wireshark.git;a=commit;h=686d4cabb41185591c361f9ec6b709034317144b
178      * The key and session identifier are forwarded to the log named 'io.netty.wireshark'.
179      */
180     private static final class WiresharkSslMasterKeyHandler extends SslMasterKeyHandler {
181 
182         private static final InternalLogger wireshark_logger =
183                 InternalLoggerFactory.getInstance("io.netty.wireshark");
184 
185         @Override
186         protected void accept(SecretKey masterKey, SSLSession session) {
187             if (masterKey.getEncoded().length != 48) {
188                 throw new IllegalArgumentException("An invalid length master key was provided.");
189             }
190             final byte[] sessionId = session.getId();
191             wireshark_logger.warn("RSA Session-ID:{} Master-Key:{}",
192                     ByteBufUtil.hexDump(sessionId).toLowerCase(),
193                     ByteBufUtil.hexDump(masterKey.getEncoded()).toLowerCase());
194         }
195     }
196 
197 }