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