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