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.math.BigInteger;
  30 import java.nio.ByteBuffer;
  31 import java.security.CryptoPrimitive;
  32 import java.security.GeneralSecurityException;
  33 import java.security.InvalidAlgorithmParameterException;
  34 import java.security.InvalidKeyException;
  35 import java.security.Key;
  36 import java.security.KeyFactory;
  37 import java.security.NoSuchAlgorithmException;
  38 import java.security.PrivateKey;
  39 import java.security.PublicKey;
  40 import java.security.Signature;
  41 import java.security.SignatureException;
  42 import java.text.MessageFormat;
  43 import java.util.EnumSet;
  44 import java.util.Locale;
  45 import java.util.Map;
  46 import javax.crypto.interfaces.DHPublicKey;
  47 import javax.crypto.spec.DHParameterSpec;
  48 import javax.crypto.spec.DHPublicKeySpec;
  49 import sun.security.ssl.DHKeyExchange.DHECredentials;
  50 import sun.security.ssl.DHKeyExchange.DHEPossession;
  51 import sun.security.ssl.SSLHandshake.HandshakeMessage;
  52 import sun.security.ssl.X509Authentication.X509Credentials;
  53 import sun.security.ssl.X509Authentication.X509Possession;
  54 import sun.security.util.HexDumpEncoder;
  55 import sun.security.util.KeyUtil;
  56 
  57 /**
  58  * Pack of the ServerKeyExchange handshake message.
  59  */
  60 final class DHServerKeyExchange {
  61     static final SSLConsumer dhHandshakeConsumer =
  62             new DHServerKeyExchangeConsumer();
  63     static final HandshakeProducer dhHandshakeProducer =
  64             new DHServerKeyExchangeProducer();
  65 
  66     /**
  67      * The DiffieHellman ServerKeyExchange handshake message.
  68      */
  69     private static final
  70             class DHServerKeyExchangeMessage extends HandshakeMessage {
  71         // public key encapsulated in this message
  72         private final byte[] p;        // 1 to 2^16 - 1 bytes
  73         private final byte[] g;        // 1 to 2^16 - 1 bytes
  74         private final byte[] y;        // 1 to 2^16 - 1 bytes
  75 
  76         // the signature algorithm used by this ServerKeyExchange message
  77         private final boolean useExplicitSigAlgorithm;
  78         private final SignatureScheme signatureScheme;
  79 
  80         // signature bytes, or null if anonymous
  81         private final byte[] paramsSignature;
  82 
  83         DHServerKeyExchangeMessage(
  84                 HandshakeContext handshakeContext) throws IOException {
  85             super(handshakeContext);
  86 
  87             // This happens in server side only.
  88             ServerHandshakeContext shc =
  89                     (ServerHandshakeContext)handshakeContext;
  90 
  91             DHEPossession dhePossession = null;
  92             X509Possession x509Possession = null;
  93             for (SSLPossession possession : shc.handshakePossessions) {
  94                 if (possession instanceof DHEPossession) {
  95                     dhePossession = (DHEPossession)possession;
  96                     if (x509Possession != null) {
  97                         break;
  98                     }
  99                 } else if (possession instanceof X509Possession) {
 100                     x509Possession = (X509Possession)possession;
 101                     if (dhePossession != null) {
 102                         break;
 103                     }
 104                 }
 105             }
 106 
 107             if (dhePossession == null) {
 108                 // unlikely
 109                 throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
 110                     "No DHE credentials negotiated for server key exchange");
 111             }
 112             DHPublicKey publicKey = dhePossession.publicKey;
 113             DHParameterSpec params = publicKey.getParams();
 114             this.p = Utilities.toByteArray(params.getP());
 115             this.g = Utilities.toByteArray(params.getG());
 116             this.y = Utilities.toByteArray(publicKey.getY());
 117 
 118             if (x509Possession == null) {
 119                 // anonymous, no authentication, no signature
 120                 paramsSignature = null;
 121                 signatureScheme = null;
 122                 useExplicitSigAlgorithm = false;
 123             } else {
 124                 useExplicitSigAlgorithm =
 125                         shc.negotiatedProtocol.useTLS12PlusSpec();
 126                 Signature signer = null;
 127                 if (useExplicitSigAlgorithm) {
 128                     Map.Entry<SignatureScheme, Signature> schemeAndSigner =
 129                             SignatureScheme.getSignerOfPreferableAlgorithm(
 130                                     shc.peerRequestedSignatureSchemes,
 131                                     x509Possession,
 132                                     shc.negotiatedProtocol);
 133                     if (schemeAndSigner == null) {
 134                         // Unlikely, the credentials generator should have
 135                         // selected the preferable signature algorithm properly.
 136                         throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
 137                                 "No supported signature algorithm for " +
 138                                 x509Possession.popPrivateKey.getAlgorithm() +
 139                                 "  key");
 140                     } else {
 141                         signatureScheme = schemeAndSigner.getKey();
 142                         signer = schemeAndSigner.getValue();
 143                     }
 144                 } else {
 145                     signatureScheme = null;
 146                     try {
 147                         signer = getSignature(
 148                                 x509Possession.popPrivateKey.getAlgorithm(),
 149                                 x509Possession.popPrivateKey);
 150                     } catch (NoSuchAlgorithmException | InvalidKeyException e) {
 151                         throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
 152                             "Unsupported signature algorithm: " +
 153                             x509Possession.popPrivateKey.getAlgorithm(), e);
 154                     }
 155                 }
 156 
 157                 byte[] signature = null;
 158                 try {
 159                     updateSignature(signer, shc.clientHelloRandom.randomBytes,
 160                             shc.serverHelloRandom.randomBytes);
 161                     signature = signer.sign();
 162                 } catch (SignatureException ex) {
 163                     throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
 164                         "Failed to sign dhe parameters: " +
 165                         x509Possession.popPrivateKey.getAlgorithm(), ex);
 166                 }
 167                 paramsSignature = signature;
 168             }
 169         }
 170 
 171         DHServerKeyExchangeMessage(HandshakeContext handshakeContext,
 172                 ByteBuffer m) throws IOException {
 173             super(handshakeContext);
 174 
 175             // This happens in client side only.
 176             ClientHandshakeContext chc =
 177                     (ClientHandshakeContext)handshakeContext;
 178 
 179             this.p = Record.getBytes16(m);
 180             this.g = Record.getBytes16(m);
 181             this.y = Record.getBytes16(m);
 182 
 183             try {
 184                 KeyUtil.validate(new DHPublicKeySpec(
 185                         new BigInteger(1, y),
 186                         new BigInteger(1, p),
 187                         new BigInteger(1, p)));
 188             } catch (InvalidKeyException ike) {
 189                 throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 190                     "Invalid DH ServerKeyExchange: invalid parameters", ike);
 191             }
 192 
 193             X509Credentials x509Credentials = null;
 194             for (SSLCredentials cd : chc.handshakeCredentials) {
 195                 if (cd instanceof X509Credentials) {
 196                     x509Credentials = (X509Credentials)cd;
 197                     break;
 198                 }
 199             }
 200 
 201             if (x509Credentials == null) {
 202                 // anonymous, no authentication, no signature
 203                 if (m.hasRemaining()) {
 204                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 205                         "Invalid DH ServerKeyExchange: unknown extra data");
 206                 }
 207 
 208                 this.signatureScheme = null;
 209                 this.paramsSignature = null;
 210                 this.useExplicitSigAlgorithm = false;
 211 
 212                 return;
 213             }
 214 
 215             this.useExplicitSigAlgorithm =
 216                     chc.negotiatedProtocol.useTLS12PlusSpec();
 217             if (useExplicitSigAlgorithm) {
 218                 int ssid = Record.getInt16(m);
 219                 signatureScheme = SignatureScheme.valueOf(ssid);
 220                 if (signatureScheme == null) {
 221                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 222                             "Invalid signature algorithm (" + ssid +
 223                             ") used in DH ServerKeyExchange handshake message");
 224                 }
 225 
 226                 if (!chc.localSupportedSignAlgs.contains(signatureScheme)) {
 227                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 228                             "Unsupported signature algorithm (" +
 229                             signatureScheme.name +
 230                             ") used in DH ServerKeyExchange handshake message");
 231                 }
 232             } else {
 233                 this.signatureScheme = null;
 234             }
 235 
 236             // read and verify the signature
 237             this.paramsSignature = Record.getBytes16(m);
 238             Signature signer;
 239             if (useExplicitSigAlgorithm) {
 240                 try {
 241                     signer = signatureScheme.getVerifier(
 242                             x509Credentials.popPublicKey);
 243                 } catch (NoSuchAlgorithmException | InvalidKeyException |
 244                         InvalidAlgorithmParameterException nsae) {
 245                     throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
 246                             "Unsupported signature algorithm: " +
 247                             signatureScheme.name, nsae);
 248                 }
 249             } else {
 250                 try {
 251                     signer = getSignature(
 252                             x509Credentials.popPublicKey.getAlgorithm(),
 253                             x509Credentials.popPublicKey);
 254                 } catch (NoSuchAlgorithmException | InvalidKeyException e) {
 255                     throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
 256                             "Unsupported signature algorithm: " +
 257                             x509Credentials.popPublicKey.getAlgorithm(), e);
 258                 }
 259             }
 260 
 261             try {
 262                 updateSignature(signer,
 263                         chc.clientHelloRandom.randomBytes,
 264                         chc.serverHelloRandom.randomBytes);
 265 
 266                 if (!signer.verify(paramsSignature)) {
 267                     throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 268                         "Invalid signature on DH ServerKeyExchange message");
 269                 }
 270             } catch (SignatureException ex) {
 271                 throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
 272                         "Cannot verify DH ServerKeyExchange signature", ex);
 273             }
 274         }
 275 
 276         @Override
 277         public SSLHandshake handshakeType() {
 278             return SSLHandshake.SERVER_KEY_EXCHANGE;
 279         }
 280 
 281         @Override
 282         public int messageLength() {
 283             int sigLen = 0;
 284             if (paramsSignature != null) {
 285                 sigLen = 2 + paramsSignature.length;
 286                 if (useExplicitSigAlgorithm) {
 287                     sigLen += SignatureScheme.sizeInRecord();
 288                 }
 289             }
 290 
 291             return 6 + p.length + g.length + y.length + sigLen;
 292                     // 6: overhead for p, g, y values
 293         }
 294 
 295         @Override
 296         public void send(HandshakeOutStream hos) throws IOException {
 297             hos.putBytes16(p);
 298             hos.putBytes16(g);
 299             hos.putBytes16(y);
 300 
 301             if (paramsSignature != null) {
 302                 if (useExplicitSigAlgorithm) {
 303                     hos.putInt16(signatureScheme.id);
 304                 }
 305 
 306                 hos.putBytes16(paramsSignature);
 307             }
 308         }
 309 
 310         @Override
 311         public String toString() {
 312             if (paramsSignature == null) {    // anonymous
 313                 MessageFormat messageFormat = new MessageFormat(
 314                     "\"DH ServerKeyExchange\": '{'\n" +
 315                     "  \"parameters\": '{'\n" +
 316                     "    \"dh_p\": '{'\n" +
 317                     "{0}\n" +
 318                     "    '}',\n" +
 319                     "    \"dh_g\": '{'\n" +
 320                     "{1}\n" +
 321                     "    '}',\n" +
 322                     "    \"dh_Ys\": '{'\n" +
 323                     "{2}\n" +
 324                     "    '}',\n" +
 325                     "  '}'\n" +
 326                     "'}'",
 327                     Locale.ENGLISH);
 328 
 329                 HexDumpEncoder hexEncoder = new HexDumpEncoder();
 330                 Object[] messageFields = {
 331                     Utilities.indent(
 332                             hexEncoder.encodeBuffer(p), "      "),
 333                     Utilities.indent(
 334                             hexEncoder.encodeBuffer(g), "      "),
 335                     Utilities.indent(
 336                             hexEncoder.encodeBuffer(y), "      "),
 337                 };
 338 
 339                 return messageFormat.format(messageFields);
 340             }
 341 
 342             if (useExplicitSigAlgorithm) {
 343                 MessageFormat messageFormat = new MessageFormat(
 344                     "\"DH ServerKeyExchange\": '{'\n" +
 345                     "  \"parameters\": '{'\n" +
 346                     "    \"dh_p\": '{'\n" +
 347                     "{0}\n" +
 348                     "    '}',\n" +
 349                     "    \"dh_g\": '{'\n" +
 350                     "{1}\n" +
 351                     "    '}',\n" +
 352                     "    \"dh_Ys\": '{'\n" +
 353                     "{2}\n" +
 354                     "    '}',\n" +
 355                     "  '}',\n" +
 356                     "  \"digital signature\":  '{'\n" +
 357                     "    \"signature algorithm\": \"{3}\"\n" +
 358                     "    \"signature\": '{'\n" +
 359                     "{4}\n" +
 360                     "    '}',\n" +
 361                     "  '}'\n" +
 362                     "'}'",
 363                     Locale.ENGLISH);
 364 
 365                 HexDumpEncoder hexEncoder = new HexDumpEncoder();
 366                 Object[] messageFields = {
 367                     Utilities.indent(
 368                             hexEncoder.encodeBuffer(p), "      "),
 369                     Utilities.indent(
 370                             hexEncoder.encodeBuffer(g), "      "),
 371                     Utilities.indent(
 372                             hexEncoder.encodeBuffer(y), "      "),
 373                     signatureScheme.name,
 374                     Utilities.indent(
 375                             hexEncoder.encodeBuffer(paramsSignature), "      ")
 376                 };
 377 
 378                 return messageFormat.format(messageFields);
 379             } else {
 380                 MessageFormat messageFormat = new MessageFormat(
 381                     "\"DH ServerKeyExchange\": '{'\n" +
 382                     "  \"parameters\": '{'\n" +
 383                     "    \"dh_p\": '{'\n" +
 384                     "{0}\n" +
 385                     "    '}',\n" +
 386                     "    \"dh_g\": '{'\n" +
 387                     "{1}\n" +
 388                     "    '}',\n" +
 389                     "    \"dh_Ys\": '{'\n" +
 390                     "{2}\n" +
 391                     "    '}',\n" +
 392                     "  '}',\n" +
 393                     "  \"signature\": '{'\n" +
 394                     "{3}\n" +
 395                     "  '}'\n" +
 396                     "'}'",
 397                     Locale.ENGLISH);
 398 
 399                 HexDumpEncoder hexEncoder = new HexDumpEncoder();
 400                 Object[] messageFields = {
 401                     Utilities.indent(
 402                             hexEncoder.encodeBuffer(p), "      "),
 403                     Utilities.indent(
 404                             hexEncoder.encodeBuffer(g), "      "),
 405                     Utilities.indent(
 406                             hexEncoder.encodeBuffer(y), "      "),
 407                     Utilities.indent(
 408                             hexEncoder.encodeBuffer(paramsSignature), "    ")
 409                 };
 410 
 411                 return messageFormat.format(messageFields);
 412             }
 413         }
 414 
 415         private static Signature getSignature(String keyAlgorithm,
 416                 Key key) throws NoSuchAlgorithmException, InvalidKeyException {
 417             Signature signer = null;
 418             switch (keyAlgorithm) {
 419                 case "DSA":
 420                     signer = JsseJce.getSignature(JsseJce.SIGNATURE_DSA);
 421                     break;
 422                 case "RSA":
 423                     signer = RSASignature.getInstance();
 424                     break;
 425                 default:
 426                     throw new NoSuchAlgorithmException(
 427                         "neither an RSA or a DSA key : " + keyAlgorithm);
 428             }
 429 
 430             if (signer != null) {
 431                 if (key instanceof PublicKey) {
 432                     signer.initVerify((PublicKey)(key));
 433                 } else {
 434                     signer.initSign((PrivateKey)key);
 435                 }
 436             }
 437 
 438             return signer;
 439         }
 440 
 441         /*
 442          * Update sig with nonces and Diffie-Hellman public key.
 443          */
 444         private void updateSignature(Signature sig, byte[] clntNonce,
 445                 byte[] svrNonce) throws SignatureException {
 446             int tmp;
 447 
 448             sig.update(clntNonce);
 449             sig.update(svrNonce);
 450 
 451             sig.update((byte)(p.length >> 8));
 452             sig.update((byte)(p.length & 0x0ff));
 453             sig.update(p);
 454 
 455             sig.update((byte)(g.length >> 8));
 456             sig.update((byte)(g.length & 0x0ff));
 457             sig.update(g);
 458 
 459             sig.update((byte)(y.length >> 8));
 460             sig.update((byte)(y.length & 0x0ff));
 461             sig.update(y);
 462         }
 463     }
 464 
 465     /**
 466      * The DiffieHellman "ServerKeyExchange" handshake message producer.
 467      */
 468     static final class DHServerKeyExchangeProducer
 469             implements HandshakeProducer {
 470         // Prevent instantiation of this class.
 471         private DHServerKeyExchangeProducer() {
 472             // blank
 473         }
 474 
 475         @Override
 476         public byte[] produce(ConnectionContext context,
 477                 HandshakeMessage message) throws IOException {
 478             // The producing happens in server side only.
 479             ServerHandshakeContext shc = (ServerHandshakeContext)context;
 480             DHServerKeyExchangeMessage skem =
 481                     new DHServerKeyExchangeMessage(shc);
 482             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 483                 SSLLogger.fine(
 484                     "Produced DH ServerKeyExchange handshake message", skem);
 485             }
 486 
 487             // Output the handshake message.
 488             skem.write(shc.handshakeOutput);
 489             shc.handshakeOutput.flush();
 490 
 491             // The handshake message has been delivered.
 492             return null;
 493         }
 494     }
 495 
 496     /**
 497      * The DiffieHellman "ServerKeyExchange" handshake message consumer.
 498      */
 499     static final class DHServerKeyExchangeConsumer implements SSLConsumer {
 500         // Prevent instantiation of this class.
 501         private DHServerKeyExchangeConsumer() {
 502             // blank
 503         }
 504 
 505         @Override
 506         public void consume(ConnectionContext context,
 507                 ByteBuffer message) throws IOException {
 508             // The consuming happens in client side only.
 509             ClientHandshakeContext chc = (ClientHandshakeContext)context;
 510 
 511             DHServerKeyExchangeMessage skem =
 512                     new DHServerKeyExchangeMessage(chc, message);
 513             if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 514                 SSLLogger.fine(
 515                     "Consuming DH ServerKeyExchange handshake message", skem);
 516             }
 517 
 518             //
 519             // validate
 520             //
 521             // check constraints of EC PublicKey
 522             DHPublicKey publicKey;
 523             try {
 524                 KeyFactory kf = JsseJce.getKeyFactory("DiffieHellman");
 525                 DHPublicKeySpec spec = new DHPublicKeySpec(
 526                         new BigInteger(1, skem.y),
 527                         new BigInteger(1, skem.p),
 528                         new BigInteger(1, skem.g));
 529                 publicKey = (DHPublicKey)kf.generatePublic(spec);
 530             } catch (GeneralSecurityException gse) {
 531                 throw chc.conContext.fatal(Alert.INSUFFICIENT_SECURITY,
 532                     "Could not generate DHPublicKey", gse);
 533             }
 534 
 535             if (!chc.algorithmConstraints.permits(
 536                     EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), publicKey)) {
 537                 throw chc.conContext.fatal(Alert.INSUFFICIENT_SECURITY,
 538                         "DH ServerKeyExchange does not comply to " +
 539                         "algorithm constraints");
 540             }
 541 
 542             //
 543             // update
 544             //
 545             NamedGroup namedGroup = NamedGroup.valueOf(publicKey.getParams());
 546             chc.handshakeCredentials.add(
 547                     new DHECredentials(publicKey, namedGroup));
 548 
 549             //
 550             // produce
 551             //
 552             // Need no new handshake message producers here.
 553         }
 554     }
 555 }
 556