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