1 /*
2  * Copyright (c) 2003, 2019, 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.GeneralSecurityException;
31 import java.security.PublicKey;
32 import java.security.interfaces.ECPublicKey;
33 import java.security.interfaces.XECPublicKey;
34 import java.security.spec.AlgorithmParameterSpec;
35 import java.security.spec.ECParameterSpec;
36 import java.security.spec.NamedParameterSpec;
37 import java.text.MessageFormat;
38 import java.util.Locale;
39 import javax.crypto.SecretKey;
40 import sun.security.ssl.SSLHandshake.HandshakeMessage;
41 import sun.security.ssl.X509Authentication.X509Credentials;
42 import sun.security.ssl.X509Authentication.X509Possession;
43 import sun.security.util.HexDumpEncoder;
44 
45 /**
46  * Pack of the "ClientKeyExchange" handshake message.
47  *
48  * This file is used by both the ECDH/ECDHE/XDH code since much of the
49  * code is the same between the EC named groups (i.e.
50  * x25519/x448/secp*r1), even though the APIs are very different (i.e.
51  * ECPublicKey/XECPublicKey, KeyExchange.getInstance("EC"/"XDH"), etc.).
52  */
53 final class ECDHClientKeyExchange {
54     static final SSLConsumer ecdhHandshakeConsumer =
55             new ECDHClientKeyExchangeConsumer();
56     static final HandshakeProducer ecdhHandshakeProducer =
57             new ECDHClientKeyExchangeProducer();
58 
59     static final SSLConsumer ecdheHandshakeConsumer =
60             new ECDHEClientKeyExchangeConsumer();
61     static final HandshakeProducer ecdheHandshakeProducer =
62             new ECDHEClientKeyExchangeProducer();
63 
64     /**
65      * The ECDH/ECDHE/XDH ClientKeyExchange handshake message.
66      */
67     private static final
68             class ECDHClientKeyExchangeMessage extends HandshakeMessage {
69         private final byte[] encodedPoint;
70 
ECDHClientKeyExchangeMessage(HandshakeContext handshakeContext, byte[] encodedPublicKey)71         ECDHClientKeyExchangeMessage(HandshakeContext handshakeContext,
72                 byte[] encodedPublicKey) {
73             super(handshakeContext);
74 
75             this.encodedPoint = encodedPublicKey;
76         }
77 
ECDHClientKeyExchangeMessage(HandshakeContext handshakeContext, ByteBuffer m)78         ECDHClientKeyExchangeMessage(HandshakeContext handshakeContext,
79                 ByteBuffer m) throws IOException {
80             super(handshakeContext);
81             if (m.remaining() != 0) {       // explicit PublicValueEncoding
82                 this.encodedPoint = Record.getBytes8(m);
83             } else {
84                 this.encodedPoint = new byte[0];
85             }
86         }
87 
88         @Override
handshakeType()89         public SSLHandshake handshakeType() {
90             return SSLHandshake.CLIENT_KEY_EXCHANGE;
91         }
92 
93         @Override
messageLength()94         public int messageLength() {
95             if (encodedPoint == null || encodedPoint.length == 0) {
96                 return 0;
97             } else {
98                 return 1 + encodedPoint.length;
99             }
100         }
101 
102         @Override
send(HandshakeOutStream hos)103         public void send(HandshakeOutStream hos) throws IOException {
104             if (encodedPoint != null && encodedPoint.length != 0) {
105                 hos.putBytes8(encodedPoint);
106             }
107         }
108 
109         @Override
toString()110         public String toString() {
111             MessageFormat messageFormat = new MessageFormat(
112                 "\"ECDH ClientKeyExchange\": '{'\n" +
113                 "  \"ecdh public\": '{'\n" +
114                 "{0}\n" +
115                 "  '}',\n" +
116                 "'}'",
117                 Locale.ENGLISH);
118             if (encodedPoint == null || encodedPoint.length == 0) {
119                 Object[] messageFields = {
120                     "    <implicit>"
121                 };
122                 return messageFormat.format(messageFields);
123             } else {
124                 HexDumpEncoder hexEncoder = new HexDumpEncoder();
125                 Object[] messageFields = {
126                     Utilities.indent(
127                             hexEncoder.encodeBuffer(encodedPoint), "    "),
128                 };
129                 return messageFormat.format(messageFields);
130             }
131         }
132     }
133 
134     /**
135      * The ECDH "ClientKeyExchange" handshake message producer.
136      */
137     private static final
138             class ECDHClientKeyExchangeProducer implements HandshakeProducer {
139         // Prevent instantiation of this class.
ECDHClientKeyExchangeProducer()140         private ECDHClientKeyExchangeProducer() {
141             // blank
142         }
143 
144         @Override
produce(ConnectionContext context, HandshakeMessage message)145         public byte[] produce(ConnectionContext context,
146                 HandshakeMessage message) throws IOException {
147             // The producing happens in client side only.
148             ClientHandshakeContext chc = (ClientHandshakeContext)context;
149 
150             X509Credentials x509Credentials = null;
151             for (SSLCredentials credential : chc.handshakeCredentials) {
152                 if (credential instanceof X509Credentials) {
153                     x509Credentials = (X509Credentials)credential;
154                     break;
155                 }
156             }
157 
158             if (x509Credentials == null) {
159                 throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
160                     "No server certificate for ECDH client key exchange");
161             }
162 
163             PublicKey publicKey = x509Credentials.popPublicKey;
164 
165             NamedGroup namedGroup = null;
166             String algorithm = publicKey.getAlgorithm();
167 
168             // Determine which NamedGroup we'll be using, then use
169             // the creator functions.
170             if (algorithm.equals("EC")) {
171                 ECParameterSpec params = ((ECPublicKey)publicKey).getParams();
172                 namedGroup = NamedGroup.valueOf(params);
173             } else if (algorithm.equals("XDH")) {
174                 AlgorithmParameterSpec params =
175                         ((XECPublicKey)publicKey).getParams();
176                 if (params instanceof NamedParameterSpec) {
177                     String name = ((NamedParameterSpec)params).getName();
178                     namedGroup = NamedGroup.nameOf(name);
179                 }
180             } else {
181                 throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
182                     "Not EC/XDH server certificate for " +
183                             "ECDH client key exchange");
184             }
185 
186             if (namedGroup == null) {
187                 throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
188                     "Unsupported EC/XDH server cert for " +
189                         "ECDH client key exchange");
190             }
191 
192             SSLPossession sslPossession = namedGroup.createPossession(
193                     chc.sslContext.getSecureRandom());
194 
195             chc.handshakePossessions.add(sslPossession);
196             ECDHClientKeyExchangeMessage cke =
197                     new ECDHClientKeyExchangeMessage(
198                             chc, sslPossession.encode());
199             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
200                 SSLLogger.fine(
201                     "Produced ECDH ClientKeyExchange handshake message", cke);
202             }
203 
204             // Output the handshake message.
205             cke.write(chc.handshakeOutput);
206             chc.handshakeOutput.flush();
207 
208             // update the states
209             SSLKeyExchange ke = SSLKeyExchange.valueOf(
210                     chc.negotiatedCipherSuite.keyExchange,
211                     chc.negotiatedProtocol);
212             if (ke == null) {
213                 // unlikely
214                 throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
215                         "Not supported key exchange type");
216             } else {
217                 SSLKeyDerivation masterKD = ke.createKeyDerivation(chc);
218                 SecretKey masterSecret =
219                         masterKD.deriveKey("MasterSecret", null);
220                 chc.handshakeSession.setMasterSecret(masterSecret);
221 
222                 SSLTrafficKeyDerivation kd =
223                         SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol);
224                 if (kd == null) {
225                     // unlikely
226                     throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
227                             "Not supported key derivation: " +
228                             chc.negotiatedProtocol);
229                 } else {
230                     chc.handshakeKeyDerivation =
231                         kd.createKeyDerivation(chc, masterSecret);
232                 }
233             }
234 
235             // The handshake message has been delivered.
236             return null;
237         }
238     }
239 
240     /**
241      * The ECDH "ClientKeyExchange" handshake message consumer.
242      */
243     private static final
244             class ECDHClientKeyExchangeConsumer implements SSLConsumer {
245         // Prevent instantiation of this class.
ECDHClientKeyExchangeConsumer()246         private ECDHClientKeyExchangeConsumer() {
247             // blank
248         }
249 
250         @Override
consume(ConnectionContext context, ByteBuffer message)251         public void consume(ConnectionContext context,
252                 ByteBuffer message) throws IOException {
253             // The consuming happens in server side only.
254             ServerHandshakeContext shc = (ServerHandshakeContext)context;
255 
256             X509Possession x509Possession = null;
257             for (SSLPossession possession : shc.handshakePossessions) {
258                 if (possession instanceof X509Possession) {
259                     x509Possession = (X509Possession)possession;
260                     break;
261                 }
262             }
263 
264             if (x509Possession == null) {
265                 // unlikely, have been checked during cipher suite negotiation.
266                 throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
267                     "No expected EC server cert for ECDH client key exchange");
268             }
269 
270             // Determine which NamedGroup we'll be using, then use
271             // the creator functions.
272             NamedGroup namedGroup = null;
273 
274             // Iteratively determine the X509Possession type's ParameterSpec.
275             ECParameterSpec ecParams = x509Possession.getECParameterSpec();
276             NamedParameterSpec namedParams = null;
277             if (ecParams != null) {
278                 namedGroup = NamedGroup.valueOf(ecParams);
279             }
280 
281             // Wasn't EC, try XEC.
282             if (ecParams == null) {
283                 namedParams = x509Possession.getXECParameterSpec();
284                 namedGroup = NamedGroup.nameOf(namedParams.getName());
285             }
286 
287             // Can't figure this out, bail.
288             if ((ecParams == null) && (namedParams == null)) {
289                 // unlikely, have been checked during cipher suite negotiation.
290                 throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
291                     "Not EC/XDH server cert for ECDH client key exchange");
292             }
293 
294             // unlikely, have been checked during cipher suite negotiation.
295             if (namedGroup == null) {
296                 throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
297                     "Unknown named group in server cert for " +
298                         "ECDH client key exchange");
299             }
300 
301             SSLKeyExchange ke = SSLKeyExchange.valueOf(
302                     shc.negotiatedCipherSuite.keyExchange,
303                     shc.negotiatedProtocol);
304             if (ke == null) {
305                 // unlikely
306                 throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
307                         "Not supported key exchange type");
308             }
309 
310             // parse either handshake message containing either EC/XEC.
311             ECDHClientKeyExchangeMessage cke =
312                     new ECDHClientKeyExchangeMessage(shc, message);
313             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
314                 SSLLogger.fine(
315                     "Consuming ECDH ClientKeyExchange handshake message", cke);
316             }
317 
318             // create the credentials
319             try {
320                 NamedGroup ng = namedGroup;  // "effectively final" the lambda
321                 // AlgorithmConstraints are checked internally.
322                 SSLCredentials sslCredentials = namedGroup.decodeCredentials(
323                         cke.encodedPoint, shc.algorithmConstraints,
324                         s -> shc.conContext.fatal(Alert.INSUFFICIENT_SECURITY,
325                         "ClientKeyExchange " + ng + ": " + s));
326 
327                 shc.handshakeCredentials.add(sslCredentials);
328             } catch (GeneralSecurityException e) {
329                 throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
330                         "Cannot decode ECDH PublicKey: " + namedGroup);
331             }
332 
333             // update the states
334             SSLKeyDerivation masterKD = ke.createKeyDerivation(shc);
335             SecretKey masterSecret =
336                     masterKD.deriveKey("MasterSecret", null);
337             shc.handshakeSession.setMasterSecret(masterSecret);
338 
339             SSLTrafficKeyDerivation kd =
340                     SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol);
341             if (kd == null) {
342                 // unlikely
343                 throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
344                     "Not supported key derivation: " + shc.negotiatedProtocol);
345             } else {
346                 shc.handshakeKeyDerivation =
347                     kd.createKeyDerivation(shc, masterSecret);
348             }
349         }
350     }
351 
352     /**
353      * The ECDHE "ClientKeyExchange" handshake message producer.
354      */
355     private static final
356             class ECDHEClientKeyExchangeProducer implements HandshakeProducer {
357         // Prevent instantiation of this class.
ECDHEClientKeyExchangeProducer()358         private ECDHEClientKeyExchangeProducer() {
359             // blank
360         }
361 
362         @Override
produce(ConnectionContext context, HandshakeMessage message)363         public byte[] produce(ConnectionContext context,
364                 HandshakeMessage message) throws IOException {
365             // The producing happens in client side only.
366             ClientHandshakeContext chc = (ClientHandshakeContext)context;
367 
368             SSLCredentials sslCredentials = null;
369             NamedGroup ng = null;
370             PublicKey publicKey = null;
371 
372             // Find a good EC/XEC credential to use, determine the
373             // NamedGroup to use for creating Possessions/Credentials/Keys.
374             for (SSLCredentials cd : chc.handshakeCredentials) {
375                 if (cd instanceof NamedGroupCredentials) {
376                     NamedGroupCredentials creds = (NamedGroupCredentials)cd;
377                     ng = creds.getNamedGroup();
378                     publicKey = creds.getPublicKey();
379                     sslCredentials = cd;
380                     break;
381                 }
382             }
383 
384             if (sslCredentials == null) {
385                 throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
386                     "No ECDHE credentials negotiated for client key exchange");
387             }
388 
389             SSLPossession sslPossession = ng.createPossession(
390                     chc.sslContext.getSecureRandom());
391 
392             chc.handshakePossessions.add(sslPossession);
393 
394             // Write the EC/XEC message.
395             ECDHClientKeyExchangeMessage cke =
396                     new ECDHClientKeyExchangeMessage(
397                             chc, sslPossession.encode());
398 
399             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
400                 SSLLogger.fine(
401                     "Produced ECDHE ClientKeyExchange handshake message", cke);
402             }
403 
404             // Output the handshake message.
405             cke.write(chc.handshakeOutput);
406             chc.handshakeOutput.flush();
407 
408             // update the states
409             SSLKeyExchange ke = SSLKeyExchange.valueOf(
410                     chc.negotiatedCipherSuite.keyExchange,
411                     chc.negotiatedProtocol);
412             if (ke == null) {
413                 // unlikely
414                 throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
415                         "Not supported key exchange type");
416             } else {
417                 SSLKeyDerivation masterKD = ke.createKeyDerivation(chc);
418                 SecretKey masterSecret =
419                         masterKD.deriveKey("MasterSecret", null);
420                 chc.handshakeSession.setMasterSecret(masterSecret);
421 
422                 SSLTrafficKeyDerivation kd =
423                         SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol);
424                 if (kd == null) {
425                     // unlikely
426                     throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
427                             "Not supported key derivation: " +
428                             chc.negotiatedProtocol);
429                 } else {
430                     chc.handshakeKeyDerivation =
431                         kd.createKeyDerivation(chc, masterSecret);
432                 }
433             }
434 
435             // The handshake message has been delivered.
436             return null;
437         }
438     }
439 
440     /**
441      * The ECDHE "ClientKeyExchange" handshake message consumer.
442      */
443     private static final
444             class ECDHEClientKeyExchangeConsumer implements SSLConsumer {
445         // Prevent instantiation of this class.
ECDHEClientKeyExchangeConsumer()446         private ECDHEClientKeyExchangeConsumer() {
447             // blank
448         }
449 
450         @Override
consume(ConnectionContext context, ByteBuffer message)451         public void consume(ConnectionContext context,
452                 ByteBuffer message) throws IOException {
453             // The consuming happens in server side only.
454             ServerHandshakeContext shc = (ServerHandshakeContext)context;
455 
456             SSLPossession sslPossession = null;
457             NamedGroup namedGroup = null;
458 
459            // Find a good EC/XEC credential to use, determine the
460            // NamedGroup to use for creating Possessions/Credentials/Keys.
461             for (SSLPossession possession : shc.handshakePossessions) {
462                 if (possession instanceof NamedGroupPossession) {
463                     NamedGroupPossession poss =
464                             (NamedGroupPossession)possession;
465                     namedGroup = poss.getNamedGroup();
466                     sslPossession = poss;
467                     break;
468                 }
469             }
470 
471             if (sslPossession == null) {
472                 // unlikely
473                 throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
474                     "No expected ECDHE possessions for client key exchange");
475             }
476 
477             if (namedGroup == null) {
478                 // unlikely, have been checked during cipher suite negotiation
479                 throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
480                     "Unsupported EC server cert for ECDHE client key exchange");
481             }
482 
483             SSLKeyExchange ke = SSLKeyExchange.valueOf(
484                     shc.negotiatedCipherSuite.keyExchange,
485                     shc.negotiatedProtocol);
486             if (ke == null) {
487                 // unlikely
488                 throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
489                         "Not supported key exchange type");
490             }
491 
492             // parse the EC/XEC handshake message
493             ECDHClientKeyExchangeMessage cke =
494                     new ECDHClientKeyExchangeMessage(shc, message);
495             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
496                 SSLLogger.fine(
497                     "Consuming ECDHE ClientKeyExchange handshake message", cke);
498             }
499 
500             // create the credentials
501             try {
502                 NamedGroup ng = namedGroup; // "effectively final" the lambda
503                 // AlgorithmConstraints are checked internally.
504                 SSLCredentials sslCredentials = namedGroup.decodeCredentials(
505                         cke.encodedPoint, shc.algorithmConstraints,
506                         s -> shc.conContext.fatal(Alert.INSUFFICIENT_SECURITY,
507                         "ClientKeyExchange " + ng + ": " + s));
508 
509                 shc.handshakeCredentials.add(sslCredentials);
510             } catch (GeneralSecurityException e) {
511                 throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
512                         "Cannot decode named group: " + namedGroup);
513             }
514 
515             // update the states
516             SSLKeyDerivation masterKD = ke.createKeyDerivation(shc);
517             SecretKey masterSecret =
518                     masterKD.deriveKey("MasterSecret", null);
519             shc.handshakeSession.setMasterSecret(masterSecret);
520 
521             SSLTrafficKeyDerivation kd =
522                     SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol);
523             if (kd == null) {
524                 // unlikely
525                 throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
526                     "Not supported key derivation: " + shc.negotiatedProtocol);
527             } else {
528                 shc.handshakeKeyDerivation =
529                     kd.createKeyDerivation(shc, masterSecret);
530             }
531         }
532     }
533 }
534