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}.
175 * ex. {@code RSA Session-ID:XXX Master-Key:YYY}
176 * This format is understood by Wireshark 1.6.0.
177 * See: <a href=
178 * "https://code.wireshark.org/review/gitweb?p=wireshark.git;a=commit;h=686d4cabb41185591c361f9ec6b709034317144b"
179 * >Wireshark</a>
180 * The key and session identifier are forwarded to the log named 'io.netty.wireshark'.
181 */
182 private static final class WiresharkSslMasterKeyHandler extends SslMasterKeyHandler {
183
184 private static final InternalLogger wireshark_logger =
185 InternalLoggerFactory.getInstance("io.netty.wireshark");
186
187 @Override
188 protected void accept(SecretKey masterKey, SSLSession session) {
189 if (masterKey.getEncoded().length != 48) {
190 throw new IllegalArgumentException("An invalid length master key was provided.");
191 }
192 final byte[] sessionId = session.getId();
193 wireshark_logger.warn("RSA Session-ID:{} Master-Key:{}",
194 ByteBufUtil.hexDump(sessionId).toLowerCase(),
195 ByteBufUtil.hexDump(masterKey.getEncoded()).toLowerCase());
196 }
197 }
198
199 }