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.math.BigInteger;
  30 import java.nio.ByteBuffer;
  31 import java.security.CryptoPrimitive;
  32 import java.security.GeneralSecurityException;
  33 import java.security.KeyFactory;
  34 import java.text.MessageFormat;
  35 import java.util.EnumSet;
  36 import java.util.Locale;
  37 import javax.crypto.SecretKey;
  38 import javax.crypto.interfaces.DHPublicKey;
  39 import javax.crypto.spec.DHParameterSpec;
  40 import javax.crypto.spec.DHPublicKeySpec;
  41 import javax.net.ssl.SSLHandshakeException;
  42 import sun.security.ssl.DHKeyExchange.DHECredentials;
  43 import sun.security.ssl.DHKeyExchange.DHEPossession;
  44 import sun.security.ssl.SSLHandshake.HandshakeMessage;
  45 import sun.security.util.HexDumpEncoder;
  46 
  47 /**
  48  * Pack of the "ClientKeyExchange" handshake message.
  49  */
  50 final class DHClientKeyExchange {
  51     static final DHClientKeyExchangeConsumer dhHandshakeConsumer =
  52             new DHClientKeyExchangeConsumer();
  53     static final DHClientKeyExchangeProducer dhHandshakeProducer =
  54             new DHClientKeyExchangeProducer();
  55 
  56     /**
  57      * The DiffieHellman ClientKeyExchange handshake message.
  58      *
  59      * If the client has sent a certificate which contains a suitable
  60      * DiffieHellman key (for fixed_dh client authentication), then the
  61      * client public value is implicit and does not need to be sent again.
  62      * In this case, the client key exchange message will be sent, but it
  63      * MUST be empty.
  64      *
  65      * Currently, we don't support cipher suite that requires implicit public
  66      * key of client.
  67      */
  68     private static final
  69             class DHClientKeyExchangeMessage extends HandshakeMessage {
  70         private byte[] y;        // 1 to 2^16 - 1 bytes
  71 
  72         DHClientKeyExchangeMessage(
  73                 HandshakeContext handshakeContext) throws IOException {
  74             super(handshakeContext);
  75             // This happens in client side only.
  76             ClientHandshakeContext chc =
  77                     (ClientHandshakeContext)handshakeContext;
  78 
  79             DHEPossession dhePossession = null;
  80             for (SSLPossession possession : chc.handshakePossessions) {
  81                 if (possession instanceof DHEPossession) {
  82                     dhePossession = (DHEPossession)possession;
  83                     break;
  84                 }
  85             }
  86 
  87             if (dhePossession == null) {
  88                 // unlikely
  89                 throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
  90                     "No DHE credentials negotiated for client key exchange");
  91             }
  92 
  93             DHPublicKey publicKey = dhePossession.publicKey;
  94             DHParameterSpec params = publicKey.getParams();
  95             this.y = Utilities.toByteArray(publicKey.getY());
  96         }
  97 
  98         DHClientKeyExchangeMessage(HandshakeContext handshakeContext,
  99                 ByteBuffer m) throws IOException {
 100             super(handshakeContext);
 101             // This happens in server side only.
 102             ServerHandshakeContext shc =
 103                     (ServerHandshakeContext)handshakeContext;
 104 
 105             if (m.remaining() < 3) {
 106                 throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 107                     "Invalid DH ClientKeyExchange message: insufficient data");
 108             }
 109 
 110             this.y = Record.getBytes16(m);
 111 
 112             if (m.hasRemaining()) {
 113                 throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 114                     "Invalid DH ClientKeyExchange message: unknown extra data");
 115             }
 116         }
 117 
 118         @Override
 119         public SSLHandshake handshakeType() {
 120             return SSLHandshake.CLIENT_KEY_EXCHANGE;
 121         }
 122 
 123         @Override
 124         public int messageLength() {
 125             return y.length + 2;    // 2: length filed
 126         }
 127 
 128         @Override
 129         public void send(HandshakeOutStream hos) throws IOException {
 130             hos.putBytes16(y);
 131         }
 132 
 133         @Override
 134         public String toString() {
 135             MessageFormat messageFormat = new MessageFormat(
 136                 "\"DH ClientKeyExchange\": '{'\n" +
 137                 "  \"parameters\": '{'\n" +
 138                 "    \"dh_Yc\": '{'\n" +
 139                 "{0}\n" +
 140                 "    '}',\n" +
 141                 "  '}'\n" +
 142                 "'}'",
 143                 Locale.ENGLISH);
 144 
 145             HexDumpEncoder hexEncoder = new HexDumpEncoder();
 146             Object[] messageFields = {
 147                 Utilities.indent(
 148                         hexEncoder.encodeBuffer(y), "      "),
 149             };
 150             return messageFormat.format(messageFields);
 151         }
 152     }
 153 
 154     /**
 155      * The DiffieHellman "ClientKeyExchange" handshake message producer.
 156      */
 157     private static final
 158             class DHClientKeyExchangeProducer implements HandshakeProducer {
 159         // Prevent instantiation of this class.
 160         private DHClientKeyExchangeProducer() {
 161             // blank
 162         }
 163 
 164         @Override
 165         public byte[] produce(ConnectionContext context,
 166                 HandshakeMessage message) throws IOException {
 167             // The producing happens in client side only.
 168             ClientHandshakeContext chc = (ClientHandshakeContext)context;
 169 
 170             DHECredentials dheCredentials = null;
 171             for (SSLCredentials cd : chc.handshakeCredentials) {
 172                 if (cd instanceof DHECredentials) {
 173                     dheCredentials = (DHECredentials)cd;
 174                     break;
 175                 }
 176             }
 177 
 178             if (dheCredentials == null) {
 179                 throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 180                     "No DHE credentials negotiated for client key exchange");
 181             }
 182 
 183 
 184             DHEPossession dhePossession = new DHEPossession(
 185                     dheCredentials, chc.sslContext.getSecureRandom());
 186             chc.handshakePossessions.add(dhePossession);
 187             DHClientKeyExchangeMessage ckem =
 188                     new DHClientKeyExchangeMessage(chc);
 189             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 190                 SSLLogger.fine(
 191                     "Produced DH ClientKeyExchange handshake message", ckem);
 192             }
 193 
 194             // Output the handshake message.
 195             ckem.write(chc.handshakeOutput);
 196             chc.handshakeOutput.flush();
 197 
 198             // update the states
 199             SSLKeyExchange ke = SSLKeyExchange.valueOf(
 200                     chc.negotiatedCipherSuite.keyExchange,
 201                     chc.negotiatedProtocol);
 202             if (ke == null) {
 203                 // unlikely
 204                 throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
 205                         "Not supported key exchange type");
 206             } else {
 207                 SSLKeyDerivation masterKD = ke.createKeyDerivation(chc);
 208                 SecretKey masterSecret =
 209                         masterKD.deriveKey("MasterSecret", null);
 210                 chc.handshakeSession.setMasterSecret(masterSecret);
 211 
 212                 SSLTrafficKeyDerivation kd =
 213                         SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol);
 214                 if (kd == null) {
 215                     // unlikely
 216                     throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
 217                             "Not supported key derivation: " +
 218                             chc.negotiatedProtocol);
 219                 } else {
 220                     chc.handshakeKeyDerivation =
 221                         kd.createKeyDerivation(chc, masterSecret);
 222                 }
 223             }
 224 
 225             // The handshake message has been delivered.
 226             return null;
 227         }
 228     }
 229 
 230     /**
 231      * The DiffieHellman "ClientKeyExchange" handshake message consumer.
 232      */
 233     private static final
 234             class DHClientKeyExchangeConsumer implements SSLConsumer {
 235         // Prevent instantiation of this class.
 236         private DHClientKeyExchangeConsumer() {
 237             // blank
 238         }
 239 
 240         @Override
 241         public void consume(ConnectionContext context,
 242                 ByteBuffer message) throws IOException {
 243             // The consuming happens in server side only.
 244             ServerHandshakeContext shc = (ServerHandshakeContext)context;
 245 
 246             DHEPossession dhePossession = null;
 247             for (SSLPossession possession : shc.handshakePossessions) {
 248                 if (possession instanceof DHEPossession) {
 249                     dhePossession = (DHEPossession)possession;
 250                     break;
 251                 }
 252             }
 253 
 254             if (dhePossession == null) {
 255                 // unlikely
 256                 throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 257                     "No expected DHE possessions for client key exchange");
 258             }
 259 
 260             SSLKeyExchange ke = SSLKeyExchange.valueOf(
 261                     shc.negotiatedCipherSuite.keyExchange,
 262                     shc.negotiatedProtocol);
 263             if (ke == null) {
 264                 // unlikely
 265                 throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
 266                         "Not supported key exchange type");
 267             }
 268 
 269             DHClientKeyExchangeMessage ckem =
 270                     new DHClientKeyExchangeMessage(shc, message);
 271             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 272                 SSLLogger.fine(
 273                     "Consuming DH ClientKeyExchange handshake message", ckem);
 274             }
 275 
 276             // create the credentials
 277             try {
 278                 DHParameterSpec params = dhePossession.publicKey.getParams();
 279                 DHPublicKeySpec spec = new DHPublicKeySpec(
 280                         new BigInteger(1, ckem.y),
 281                         params.getP(), params.getG());
 282                 KeyFactory kf = KeyFactory.getInstance("DiffieHellman");
 283                 DHPublicKey peerPublicKey =
 284                         (DHPublicKey)kf.generatePublic(spec);
 285 
 286                 // check constraints of peer DHPublicKey
 287                 if (!shc.algorithmConstraints.permits(
 288                         EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
 289                         peerPublicKey)) {
 290                     throw new SSLHandshakeException(
 291                         "DHPublicKey does not comply to algorithm constraints");
 292                 }
 293 
 294                 NamedGroup namedGroup = NamedGroup.valueOf(params);
 295                 shc.handshakeCredentials.add(
 296                         new DHECredentials(peerPublicKey, namedGroup));
 297             } catch (GeneralSecurityException | java.io.IOException e) {
 298                 throw (SSLHandshakeException)(new SSLHandshakeException(
 299                         "Could not generate DHPublicKey").initCause(e));
 300             }
 301 
 302             // update the states
 303             SSLKeyDerivation masterKD = ke.createKeyDerivation(shc);
 304             SecretKey masterSecret =
 305                     masterKD.deriveKey("MasterSecret", null);
 306             shc.handshakeSession.setMasterSecret(masterSecret);
 307 
 308             SSLTrafficKeyDerivation kd =
 309                     SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol);
 310             if (kd == null) {
 311                 // unlikely
 312                 throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
 313                     "Not supported key derivation: " + shc.negotiatedProtocol);
 314             } else {
 315                 shc.handshakeKeyDerivation =
 316                     kd.createKeyDerivation(shc, masterSecret);
 317             }
 318         }
 319     }
 320 }