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