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