1 /*
2  * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.security.ssl;
27 
28 import java.io.IOException;
29 import java.nio.ByteBuffer;
30 import java.security.MessageDigest;
31 import java.text.MessageFormat;
32 import java.util.Arrays;
33 import java.util.Locale;
34 import javax.net.ssl.SSLProtocolException;
35 import sun.security.ssl.ClientHello.ClientHelloMessage;
36 import static sun.security.ssl.SSLExtension.CH_RENEGOTIATION_INFO;
37 import sun.security.ssl.SSLExtension.ExtensionConsumer;
38 import static sun.security.ssl.SSLExtension.SH_RENEGOTIATION_INFO;
39 import sun.security.ssl.SSLExtension.SSLExtensionSpec;
40 import sun.security.ssl.SSLHandshake.HandshakeMessage;
41 import sun.security.util.ByteArrays;
42 
43 /**
44  * Pack of the "renegotiation_info" extensions [RFC 5746].
45  */
46 final class RenegoInfoExtension {
47     static final HandshakeProducer chNetworkProducer =
48             new CHRenegotiationInfoProducer();
49     static final ExtensionConsumer chOnLoadConsumer =
50             new CHRenegotiationInfoConsumer();
51     static final HandshakeAbsence chOnLoadAbsence =
52             new CHRenegotiationInfoAbsence();
53 
54     static final HandshakeProducer shNetworkProducer =
55             new SHRenegotiationInfoProducer();
56     static final ExtensionConsumer shOnLoadConsumer =
57             new SHRenegotiationInfoConsumer();
58     static final HandshakeAbsence shOnLoadAbsence =
59             new SHRenegotiationInfoAbsence();
60 
61     static final SSLStringizer rniStringizer =
62             new RenegotiationInfoStringizer();
63 
64     /**
65      * The "renegotiation_info" extension.
66      */
67     static final class RenegotiationInfoSpec implements SSLExtensionSpec {
68         // A nominal object that does not holding any real renegotiation info.
69         static final RenegotiationInfoSpec NOMINAL =
70                 new RenegotiationInfoSpec(new byte[0]);
71 
72         private final byte[] renegotiatedConnection;
73 
RenegotiationInfoSpec(byte[] renegotiatedConnection)74         private RenegotiationInfoSpec(byte[] renegotiatedConnection) {
75             this.renegotiatedConnection = Arrays.copyOf(
76                     renegotiatedConnection, renegotiatedConnection.length);
77         }
78 
RenegotiationInfoSpec(ByteBuffer m)79         private RenegotiationInfoSpec(ByteBuffer m) throws IOException {
80             // Parse the extension.
81             if (!m.hasRemaining() || m.remaining() < 1) {
82                 throw new SSLProtocolException(
83                     "Invalid renegotiation_info extension data: " +
84                     "insufficient data");
85             }
86             this.renegotiatedConnection = Record.getBytes8(m);
87         }
88 
89         @Override
toString()90         public String toString() {
91             MessageFormat messageFormat = new MessageFormat(
92                 "\"renegotiated connection\": '['{0}']'", Locale.ENGLISH);
93             if (renegotiatedConnection.length == 0) {
94                 Object[] messageFields = {
95                         "<no renegotiated connection>"
96                     };
97                 return messageFormat.format(messageFields);
98             } else {
99                 Object[] messageFields = {
100                         Utilities.toHexString(renegotiatedConnection)
101                     };
102                 return messageFormat.format(messageFields);
103             }
104         }
105     }
106 
107     private static final
108             class RenegotiationInfoStringizer implements SSLStringizer {
109         @Override
toString(ByteBuffer buffer)110         public String toString(ByteBuffer buffer) {
111             try {
112                 return (new RenegotiationInfoSpec(buffer)).toString();
113             } catch (IOException ioe) {
114                 // For debug logging only, so please swallow exceptions.
115                 return ioe.getMessage();
116             }
117         }
118     }
119 
120     /**
121      * Network data producer of a "renegotiation_info" extension in
122      * the ClientHello handshake message.
123      */
124     private static final
125             class CHRenegotiationInfoProducer implements HandshakeProducer {
126         // Prevent instantiation of this class.
CHRenegotiationInfoProducer()127         private CHRenegotiationInfoProducer() {
128             // blank
129         }
130 
131         @Override
produce(ConnectionContext context, HandshakeMessage message)132         public byte[] produce(ConnectionContext context,
133                 HandshakeMessage message) throws IOException {
134             // The producing happens in client side only.
135             ClientHandshakeContext chc = (ClientHandshakeContext)context;
136 
137             // Is it a supported and enabled extension?
138             if (!chc.sslConfig.isAvailable(CH_RENEGOTIATION_INFO)) {
139                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
140                     SSLLogger.fine(
141                             "Ignore unavailable renegotiation_info extension");
142                 }
143 
144                 return null;
145             }
146 
147             if (!chc.conContext.isNegotiated) {
148                 if (chc.activeCipherSuites.contains(
149                         CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
150                     // Using the the TLS_EMPTY_RENEGOTIATION_INFO_SCSV instead.
151                     return null;
152                 }
153 
154                 // initial handshaking.
155                 //
156                 // If this is the initial handshake for a connection, then the
157                 // "renegotiated_connection" field is of zero length in both
158                 // the ClientHello and the ServerHello. [RFC 5746]
159                 byte[] extData = new byte[] { 0x00 };
160                 chc.handshakeExtensions.put(
161                         CH_RENEGOTIATION_INFO, RenegotiationInfoSpec.NOMINAL);
162 
163                 return extData;
164             } else if (chc.conContext.secureRenegotiation) {
165                 // secure renegotiation
166                 //
167                 // For ClientHello handshake message in renegotiation, this
168                 // field contains the "client_verify_data".
169                 byte[] extData =
170                         new byte[chc.conContext.clientVerifyData.length + 1];
171                 ByteBuffer m = ByteBuffer.wrap(extData);
172                 Record.putBytes8(m, chc.conContext.clientVerifyData);
173 
174                 // The conContext.clientVerifyData will be used for further
175                 // processing, so it does not matter to save whatever in the
176                 // RenegotiationInfoSpec object.
177                 chc.handshakeExtensions.put(
178                         CH_RENEGOTIATION_INFO, RenegotiationInfoSpec.NOMINAL);
179 
180                 return extData;
181             } else {    // not secure renegotiation
182                 if (HandshakeContext.allowUnsafeRenegotiation) {
183                     if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
184                         SSLLogger.warning("Using insecure renegotiation");
185                     }
186 
187                     return null;
188                 } else {
189                     // terminate the session.
190                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
191                             "insecure renegotiation is not allowed");
192                 }
193             }
194         }
195     }
196 
197     /**
198      * Network data producer of a "renegotiation_info" extension in
199      * the ServerHello handshake message.
200      */
201     private static final
202             class CHRenegotiationInfoConsumer implements ExtensionConsumer {
203         // Prevent instantiation of this class.
CHRenegotiationInfoConsumer()204         private CHRenegotiationInfoConsumer() {
205             // blank
206         }
207 
208         @Override
consume(ConnectionContext context, HandshakeMessage message, ByteBuffer buffer)209         public void consume(ConnectionContext context,
210             HandshakeMessage message, ByteBuffer buffer) throws IOException {
211 
212             // The consuming happens in server side only.
213             ServerHandshakeContext shc = (ServerHandshakeContext)context;
214 
215             // Is it a supported and enabled extension?
216             if (!shc.sslConfig.isAvailable(CH_RENEGOTIATION_INFO)) {
217                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
218                     SSLLogger.fine("Ignore unavailable extension: " +
219                             CH_RENEGOTIATION_INFO.name);
220                 }
221                 return;     // ignore the extension
222             }
223 
224             // Parse the extension.
225             RenegotiationInfoSpec spec;
226             try {
227                 spec = new RenegotiationInfoSpec(buffer);
228             } catch (IOException ioe) {
229                 throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
230             }
231 
232             if (!shc.conContext.isNegotiated) {
233                 // initial handshaking.
234                 if (spec.renegotiatedConnection.length != 0) {
235                     throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
236                         "Invalid renegotiation_info extension data: not empty");
237                 }
238                 shc.conContext.secureRenegotiation = true;
239             } else {
240                 if (!shc.conContext.secureRenegotiation) {
241                     // Unexpected RI extension for insecure renegotiation,
242                     // abort the handshake with a fatal handshake_failure alert.
243                     throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
244                             "The renegotiation_info is present in a insecure " +
245                             "renegotiation");
246                 } else {
247                     // verify the client_verify_data value
248                     if (!MessageDigest.isEqual(shc.conContext.clientVerifyData,
249                             spec.renegotiatedConnection)) {
250                         throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
251                             "Invalid renegotiation_info extension data: " +
252                             "incorrect verify data in ClientHello");
253                     }
254                 }
255             }
256 
257             // Update the context.
258             //
259             // The conContext.clientVerifyData will be used for further
260             // processing, so it does not matter to save whatever in the
261             // RenegotiationInfoSpec object.
262             shc.handshakeExtensions.put(
263                     CH_RENEGOTIATION_INFO, RenegotiationInfoSpec.NOMINAL);
264 
265             // No impact on session resumption.
266         }
267     }
268 
269     /**
270      * The absence processing if a "renegotiation_info" extension is
271      * not present in the ClientHello handshake message.
272      */
273     private static final
274             class CHRenegotiationInfoAbsence implements HandshakeAbsence {
275         @Override
absent(ConnectionContext context, HandshakeMessage message)276         public void absent(ConnectionContext context,
277                 HandshakeMessage message) throws IOException {
278             // The producing happens in server side only.
279             ServerHandshakeContext shc = (ServerHandshakeContext)context;
280             ClientHelloMessage clientHello = (ClientHelloMessage)message;
281 
282             if (!shc.conContext.isNegotiated) {
283                 // initial handshaking.
284                 for (int id : clientHello.cipherSuiteIds) {
285                     if (id ==
286                             CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV.id) {
287                         if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
288                             SSLLogger.finest(
289                                 "Safe renegotiation, using the SCSV signgling");
290                         }
291                         shc.conContext.secureRenegotiation = true;
292                         return;
293                     }
294                 }
295 
296                 if (!HandshakeContext.allowLegacyHelloMessages) {
297                     throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
298                         "Failed to negotiate the use of secure renegotiation");
299                 }   // otherwise, allow legacy hello message
300 
301                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
302                     SSLLogger.warning("Warning: No renegotiation " +
303                         "indication in ClientHello, allow legacy ClientHello");
304                 }
305 
306                 shc.conContext.secureRenegotiation = false;
307             } else if (shc.conContext.secureRenegotiation) {
308                 // Require secure renegotiation, terminate the connection.
309                 throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
310                         "Inconsistent secure renegotiation indication");
311             } else {    // renegotiation, not secure
312                 if (HandshakeContext.allowUnsafeRenegotiation) {
313                     if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
314                         SSLLogger.warning("Using insecure renegotiation");
315                     }
316                 } else {
317                     // Unsafe renegotiation should have been aborted in
318                     // ealier processes.
319                     if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
320                         SSLLogger.fine("Terminate insecure renegotiation");
321                     }
322                     throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
323                         "Unsafe renegotiation is not allowed");
324                 }
325             }
326         }
327     }
328 
329     /**
330      * Network data producer of a "renegotiation_info" extension in
331      * the ServerHello handshake message.
332      */
333     private static final
334             class SHRenegotiationInfoProducer implements HandshakeProducer {
335         // Prevent instantiation of this class.
SHRenegotiationInfoProducer()336         private SHRenegotiationInfoProducer() {
337             // blank
338         }
339 
340         @Override
produce(ConnectionContext context, HandshakeMessage message)341         public byte[] produce(ConnectionContext context,
342                 HandshakeMessage message) throws IOException {
343             // The producing happens in server side only.
344             ServerHandshakeContext shc = (ServerHandshakeContext)context;
345 
346             // In response to "renegotiation_info" extension request only.
347             RenegotiationInfoSpec requestedSpec = (RenegotiationInfoSpec)
348                     shc.handshakeExtensions.get(CH_RENEGOTIATION_INFO);
349             if (requestedSpec == null && !shc.conContext.secureRenegotiation) {
350                 // Ignore, no renegotiation_info extension or SCSV signgling
351                 // requested.
352                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
353                     SSLLogger.finest(
354                         "Ignore unavailable renegotiation_info extension");
355                 }
356                 return null;        // ignore the extension
357             }
358 
359             if (!shc.conContext.secureRenegotiation) {
360                 // Ignore, no secure renegotiation is negotiated.
361                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
362                     SSLLogger.finest(
363                         "No secure renegotiation has been negotiated");
364                 }
365                 return null;        // ignore the extension
366             }
367 
368             if (!shc.conContext.isNegotiated) {
369                 // initial handshaking.
370                 //
371                 // If this is the initial handshake for a connection, then the
372                 // "renegotiated_connection" field is of zero length in both
373                 // the ClientHello and the ServerHello. [RFC 5746]
374                 byte[] extData = new byte[] { 0x00 };
375 
376                 // The conContext.client/serverVerifyData will be used for
377                 // further processing, so it does not matter to save whatever
378                 // in the RenegotiationInfoSpec object.
379                 shc.handshakeExtensions.put(
380                         SH_RENEGOTIATION_INFO, RenegotiationInfoSpec.NOMINAL);
381 
382                 return extData;
383             } else {
384                 // secure renegotiation
385                 //
386                 // For secure renegotiation, the server MUST include a
387                 // "renegotiation_info" extension containing the saved
388                 // client_verify_data and server_verify_data in the ServerHello.
389                 int infoLen = shc.conContext.clientVerifyData.length +
390                               shc.conContext.serverVerifyData.length;
391                 byte[] extData = new byte[infoLen + 1];
392                 ByteBuffer m = ByteBuffer.wrap(extData);
393                 Record.putInt8(m, infoLen);
394                 m.put(shc.conContext.clientVerifyData);
395                 m.put(shc.conContext.serverVerifyData);
396 
397                 // The conContext.client/serverVerifyData will be used for
398                 // further processing, so it does not matter to save whatever
399                 // in the RenegotiationInfoSpec object.
400                 shc.handshakeExtensions.put(
401                         SH_RENEGOTIATION_INFO, RenegotiationInfoSpec.NOMINAL);
402 
403                 return extData;
404             }
405         }
406     }
407 
408     /**
409      * Network data consumer of a "renegotiation_info" extension in
410      * the ServerHello handshake message.
411      */
412     private static final
413             class SHRenegotiationInfoConsumer implements ExtensionConsumer {
414         // Prevent instantiation of this class.
SHRenegotiationInfoConsumer()415         private SHRenegotiationInfoConsumer() {
416             // blank
417         }
418 
419         @Override
consume(ConnectionContext context, HandshakeMessage message, ByteBuffer buffer)420         public void consume(ConnectionContext context,
421             HandshakeMessage message, ByteBuffer buffer) throws IOException {
422             // The producing happens in client side only.
423             ClientHandshakeContext chc = (ClientHandshakeContext)context;
424 
425             // In response to the client renegotiation_info extension request
426             // or SCSV signling, which is mandatory for ClientHello message.
427             RenegotiationInfoSpec requestedSpec = (RenegotiationInfoSpec)
428                     chc.handshakeExtensions.get(CH_RENEGOTIATION_INFO);
429             if (requestedSpec == null &&
430                     !chc.activeCipherSuites.contains(
431                             CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
432                 throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
433                     "Missing renegotiation_info and SCSV detected in " +
434                     "ClientHello");
435             }
436 
437             // Parse the extension.
438             RenegotiationInfoSpec spec;
439             try {
440                 spec = new RenegotiationInfoSpec(buffer);
441             } catch (IOException ioe) {
442                 throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
443             }
444 
445 
446             if (!chc.conContext.isNegotiated) {     // initial handshake
447                 // If the extension is present, set the secure_renegotiation
448                 // flag to TRUE.  The client MUST then verify that the
449                 // length of the "renegotiated_connection" field is zero,
450                 // and if it is not, MUST abort the handshake (by sending
451                 // a fatal handshake_failure alert). [RFC 5746]
452                 if (spec.renegotiatedConnection.length != 0) {
453                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
454                         "Invalid renegotiation_info in ServerHello: " +
455                         "not empty renegotiated_connection");
456                 }
457 
458                 chc.conContext.secureRenegotiation = true;
459             } else {        // renegotiation
460                 // The client MUST then verify that the first half of the
461                 // "renegotiated_connection" field is equal to the saved
462                 // client_verify_data value, and the second half is equal to the
463                 // saved server_verify_data value.  If they are not, the client
464                 // MUST abort the handshake. [RFC 5746]
465                 int infoLen = chc.conContext.clientVerifyData.length +
466                               chc.conContext.serverVerifyData.length;
467                 if (spec.renegotiatedConnection.length != infoLen) {
468                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
469                         "Invalid renegotiation_info in ServerHello: " +
470                         "invalid renegotiated_connection length (" +
471                         spec.renegotiatedConnection.length + ")");
472                 }
473 
474                 byte[] cvd = chc.conContext.clientVerifyData;
475                 if (!ByteArrays.isEqual(spec.renegotiatedConnection,
476                         0, cvd.length, cvd, 0, cvd.length)) {
477                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
478                         "Invalid renegotiation_info in ServerHello: " +
479                         "unmatched client_verify_data value");
480                 }
481                 byte[] svd = chc.conContext.serverVerifyData;
482                 if (!ByteArrays.isEqual(spec.renegotiatedConnection,
483                         cvd.length, infoLen, svd, 0, svd.length)) {
484                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
485                         "Invalid renegotiation_info in ServerHello: " +
486                         "unmatched server_verify_data value");
487                 }
488             }
489 
490             // Update the context.
491             chc.handshakeExtensions.put(
492                     SH_RENEGOTIATION_INFO, RenegotiationInfoSpec.NOMINAL);
493 
494             // No impact on session resumption.
495         }
496     }
497 
498     /**
499      * The absence processing if a "renegotiation_info" extension is
500      * not present in the ServerHello handshake message.
501      */
502     private static final
503             class SHRenegotiationInfoAbsence implements HandshakeAbsence {
504         @Override
absent(ConnectionContext context, HandshakeMessage message)505         public void absent(ConnectionContext context,
506                 HandshakeMessage message) throws IOException {
507             // The producing happens in client side only.
508             ClientHandshakeContext chc = (ClientHandshakeContext)context;
509 
510             // In response to the client renegotiation_info extension request
511             // or SCSV signling, which is mandatory for ClientHello message.
512             RenegotiationInfoSpec requestedSpec = (RenegotiationInfoSpec)
513                     chc.handshakeExtensions.get(CH_RENEGOTIATION_INFO);
514             if (requestedSpec == null &&
515                     !chc.activeCipherSuites.contains(
516                             CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
517                 throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
518                     "Missing renegotiation_info and SCSV detected in " +
519                     "ClientHello");
520             }
521 
522             if (!chc.conContext.isNegotiated) {
523                 // initial handshaking.
524                 if (!HandshakeContext.allowLegacyHelloMessages) {
525                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
526                         "Failed to negotiate the use of secure renegotiation");
527                 }   // otherwise, allow legacy hello message
528 
529                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
530                     SSLLogger.warning("Warning: No renegotiation " +
531                         "indication in ServerHello, allow legacy ServerHello");
532                 }
533 
534                 chc.conContext.secureRenegotiation = false;
535             } else if (chc.conContext.secureRenegotiation) {
536                 // Require secure renegotiation, terminate the connection.
537                 throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
538                         "Inconsistent secure renegotiation indication");
539             } else {    // renegotiation, not secure
540                 if (HandshakeContext.allowUnsafeRenegotiation) {
541                     if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
542                         SSLLogger.warning("Using insecure renegotiation");
543                     }
544                 } else {
545                     // Unsafe renegotiation should have been aborted in
546                     // ealier processes.
547                     if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
548                         SSLLogger.fine("Terminate insecure renegotiation");
549                     }
550                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
551                         "Unsafe renegotiation is not allowed");
552                 }
553             }
554         }
555     }
556 }
557