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.netty5.handler.ssl;
17  
18  import io.netty5.buffer.BufferUtil;
19  import io.netty5.channel.ChannelHandler;
20  import io.netty5.channel.ChannelHandlerContext;
21  import io.netty5.util.internal.ReflectionUtil;
22  import io.netty5.util.internal.SystemPropertyUtil;
23  import io.netty5.util.internal.logging.InternalLogger;
24  import io.netty5.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 implements ChannelHandler {
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.netty5.ssl.masterKeyHandler=true}
56       */
57      public static final String SYSTEM_PROP_KEY = "io.netty5.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 channelInboundEvent(ChannelHandlerContext ctx, Object evt) {
126         //only try to log the session info if the ssl handshake has successfully completed.
127         if (evt instanceof SslHandshakeCompletionEvent &&
128                 ((SslHandshakeCompletionEvent) evt).isSuccess() && masterKeyHandlerEnabled()) {
129             final SslHandler handler = ctx.pipeline().get(SslHandler.class);
130             final SSLEngine engine = handler.engine();
131             final SSLSession sslSession = engine.getSession();
132 
133             //the OpenJDK does not expose a way to get the master secret, so try to use reflection to get it.
134             if (isSunSslEngineAvailable() && sslSession.getClass().equals(SSL_SESSIONIMPL_CLASS)) {
135                 final SecretKey secretKey;
136                 try {
137                     secretKey = (SecretKey) SSL_SESSIONIMPL_MASTER_SECRET_FIELD.get(sslSession);
138                 } catch (IllegalAccessException e) {
139                     throw new IllegalArgumentException("Failed to access the field 'masterSecret' " +
140                             "via reflection.", e);
141                 }
142                 accept(secretKey, sslSession);
143             } else if (OpenSsl.isAvailable() && engine instanceof ReferenceCountedOpenSslEngine) {
144                 SecretKeySpec secretKey = ((ReferenceCountedOpenSslEngine) engine).masterKey();
145                 accept(secretKey, sslSession);
146             }
147         }
148 
149         ctx.fireChannelInboundEvent(evt);
150     }
151 
152     /**
153      * Checks if the handler is set up to actually handle/accept the event.
154      * By default the {@link #SYSTEM_PROP_KEY} property is checked, but any implementations of this class are
155      * free to override if they have different mechanisms of checking.
156      *
157      * @return true if it should handle, false otherwise.
158      */
159     protected boolean masterKeyHandlerEnabled() {
160         return SystemPropertyUtil.getBoolean(SYSTEM_PROP_KEY, false);
161     }
162 
163     /**
164      * Create a {@link WiresharkSslMasterKeyHandler} instance.
165      * This TLS master key handler logs the master key and session-id in a format
166      * understood by Wireshark -- this can be especially useful if you need to ever
167      * decrypt a TLS session and are using perfect forward secrecy (i.e. Diffie-Hellman)
168      * The key and session identifier are forwarded to the log named 'io.netty5.wireshark'.
169      */
170     public static SslMasterKeyHandler newWireSharkSslMasterKeyHandler() {
171         return new WiresharkSslMasterKeyHandler();
172     }
173 
174     /**
175      * Record the session identifier and master key to the {@link InternalLogger} named {@code io.netty5.wireshark}.
176      * ex. {@code RSA Session-ID:XXX Master-Key:YYY}
177      * This format is understood by Wireshark 1.6.0.
178      * https://code.wireshark.org/review/gitweb?p=wireshark.git;a=commit;h=686d4cabb41185591c361f9ec6b709034317144b
179      * The key and session identifier are forwarded to the log named 'io.netty5.wireshark'.
180      */
181     private static final class WiresharkSslMasterKeyHandler extends SslMasterKeyHandler {
182 
183         private static final InternalLogger wireshark_logger =
184                 InternalLoggerFactory.getInstance("io.netty5.wireshark");
185 
186         @Override
187         protected void accept(SecretKey masterKey, SSLSession session) {
188             if (masterKey.getEncoded().length != 48) {
189                 throw new IllegalArgumentException("An invalid length master key was provided.");
190             }
191             final byte[] sessionId = session.getId();
192             wireshark_logger.warn("RSA Session-ID:{} Master-Key:{}",
193                                   BufferUtil.hexDump(sessionId).toLowerCase(),
194                                   BufferUtil.hexDump(masterKey.getEncoded()).toLowerCase());
195         }
196     }
197 
198 }