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.AlgorithmConstraints;
  31 import java.security.AlgorithmParameters;
  32 import java.security.CryptoPrimitive;
  33 import java.security.spec.ECGenParameterSpec;
  34 import java.security.spec.InvalidParameterSpecException;
  35 import java.text.MessageFormat;
  36 import java.util.ArrayList;
  37 import java.util.Collections;
  38 import java.util.EnumSet;
  39 import java.util.LinkedList;
  40 import java.util.List;
  41 import java.util.Locale;
  42 import javax.net.ssl.SSLProtocolException;
  43 import sun.security.action.GetPropertyAction;
  44 import sun.security.ssl.NamedGroup.NamedGroupType;
  45 import static sun.security.ssl.SSLExtension.CH_SUPPORTED_GROUPS;
  46 import static sun.security.ssl.SSLExtension.EE_SUPPORTED_GROUPS;
  47 import sun.security.ssl.SSLExtension.ExtensionConsumer;
  48 import sun.security.ssl.SSLExtension.SSLExtensionSpec;
  49 import sun.security.ssl.SSLHandshake.HandshakeMessage;
  50 
  51 
  52 /**
  53  * Pack of the "supported_groups" extensions [RFC 4492/7919].
  54  */
  55 final class SupportedGroupsExtension {
  56     static final HandshakeProducer chNetworkProducer =
  57             new CHSupportedGroupsProducer();
  58     static final ExtensionConsumer chOnLoadConsumer =
  59             new CHSupportedGroupsConsumer();
  60     static final SSLStringizer sgsStringizer =
  61             new SupportedGroupsStringizer();
  62 
  63     static final HandshakeProducer eeNetworkProducer =
  64             new EESupportedGroupsProducer();
  65     static final ExtensionConsumer eeOnLoadConsumer =
  66             new EESupportedGroupsConsumer();
  67 
  68     /**
  69      * The "supported_groups" extension.
  70      */
  71     static final class SupportedGroupsSpec implements SSLExtensionSpec {
  72         final int[] namedGroupsIds;
  73 
  74         private SupportedGroupsSpec(int[] namedGroupsIds) {
  75             this.namedGroupsIds = namedGroupsIds;
  76         }
  77 
  78         private SupportedGroupsSpec(List<NamedGroup> namedGroups) {
  79             this.namedGroupsIds = new int[namedGroups.size()];
  80             int i = 0;
  81             for (NamedGroup ng : namedGroups) {
  82                 namedGroupsIds[i++] = ng.id;
  83             }
  84         }
  85 
  86         private SupportedGroupsSpec(ByteBuffer m) throws IOException  {
  87             if (m.remaining() < 2) {      // 2: the length of the list
  88                 throw new SSLProtocolException(
  89                     "Invalid supported_groups extension: insufficient data");
  90             }
  91 
  92             byte[] ngs = Record.getBytes16(m);
  93             if (m.hasRemaining()) {
  94                 throw new SSLProtocolException(
  95                     "Invalid supported_groups extension: unknown extra data");
  96             }
  97 
  98             if ((ngs == null) || (ngs.length == 0) || (ngs.length % 2 != 0)) {
  99                 throw new SSLProtocolException(
 100                     "Invalid supported_groups extension: incomplete data");
 101             }
 102 
 103             int[] ids = new int[ngs.length / 2];
 104             for (int i = 0, j = 0; i < ngs.length;) {
 105                 ids[j++] = ((ngs[i++] & 0xFF) << 8) | (ngs[i++] & 0xFF);
 106             }
 107 
 108             this.namedGroupsIds = ids;
 109         }
 110 
 111         @Override
 112         public String toString() {
 113             MessageFormat messageFormat = new MessageFormat(
 114                 "\"versions\": '['{0}']'", Locale.ENGLISH);
 115 
 116             if (namedGroupsIds == null || namedGroupsIds.length == 0) {
 117                 Object[] messageFields = {
 118                         "<no supported named group specified>"
 119                     };
 120                 return messageFormat.format(messageFields);
 121             } else {
 122                 StringBuilder builder = new StringBuilder(512);
 123                 boolean isFirst = true;
 124                 for (int ngid : namedGroupsIds) {
 125                     if (isFirst) {
 126                         isFirst = false;
 127                     } else {
 128                         builder.append(", ");
 129                     }
 130 
 131                     builder.append(NamedGroup.nameOf(ngid));
 132                 }
 133 
 134                 Object[] messageFields = {
 135                         builder.toString()
 136                     };
 137 
 138                 return messageFormat.format(messageFields);
 139             }
 140         }
 141     }
 142 
 143     private static final
 144             class SupportedGroupsStringizer implements SSLStringizer {
 145         @Override
 146         public String toString(ByteBuffer buffer) {
 147             try {
 148                 return (new SupportedGroupsSpec(buffer)).toString();
 149             } catch (IOException ioe) {
 150                 // For debug logging only, so please swallow exceptions.
 151                 return ioe.getMessage();
 152             }
 153         }
 154     }
 155 
 156     static class SupportedGroups {
 157         // To switch off the supported_groups extension for DHE cipher suite.
 158         static final boolean enableFFDHE =
 159                 Utilities.getBooleanProperty("jsse.enableFFDHE", true);
 160 
 161         // the supported named groups
 162         static final NamedGroup[] supportedNamedGroups;
 163 
 164         static {
 165             // The value of the System Property defines a list of enabled named
 166             // groups in preference order, separated with comma.  For example:
 167             //
 168             //      jdk.tls.namedGroups="secp521r1, secp256r1, ffdhe2048"
 169             //
 170             // If the System Property is not defined or the value is empty, the
 171             // default groups and preferences will be used.
 172             String property = GetPropertyAction
 173                     .privilegedGetProperty("jdk.tls.namedGroups");
 174             if (property != null && !property.isEmpty()) {
 175                 // remove double quote marks from beginning/end of the property
 176                 if (property.length() > 1 && property.charAt(0) == '"' &&
 177                         property.charAt(property.length() - 1) == '"') {
 178                     property = property.substring(1, property.length() - 1);
 179                 }
 180             }
 181 
 182             ArrayList<NamedGroup> groupList;
 183             if (property != null && !property.isEmpty()) {
 184                 String[] groups = property.split(",");
 185                 groupList = new ArrayList<>(groups.length);
 186                 for (String group : groups) {
 187                     group = group.trim();
 188                     if (!group.isEmpty()) {
 189                         NamedGroup namedGroup = NamedGroup.nameOf(group);
 190                         if (namedGroup != null) {
 191                             if (isAvailableGroup(namedGroup)) {
 192                                 groupList.add(namedGroup);
 193                             }
 194                         }   // ignore unknown groups
 195                     }
 196                 }
 197 
 198                 if (groupList.isEmpty()) {
 199                     throw new IllegalArgumentException(
 200                             "System property jdk.tls.namedGroups(" +
 201                             property + ") contains no supported named groups");
 202                 }
 203             } else {        // default groups
 204                 NamedGroup[] groups = new NamedGroup[] {
 205 
 206                         // Primary XDH (RFC 7748) curves
 207                         NamedGroup.X25519,
 208 
 209                         // Primary NIST curves (e.g. used in TLSv1.3)
 210                         NamedGroup.SECP256_R1,
 211                         NamedGroup.SECP384_R1,
 212                         NamedGroup.SECP521_R1,
 213 
 214                         // Secondary XDH curves
 215                         NamedGroup.X448,
 216 
 217                         // Secondary NIST curves
 218                         NamedGroup.SECT283_K1,
 219                         NamedGroup.SECT283_R1,
 220                         NamedGroup.SECT409_K1,
 221                         NamedGroup.SECT409_R1,
 222                         NamedGroup.SECT571_K1,
 223                         NamedGroup.SECT571_R1,
 224 
 225                         // non-NIST curves
 226                         NamedGroup.SECP256_K1,
 227 
 228                         // FFDHE (RFC 7919)
 229                         NamedGroup.FFDHE_2048,
 230                         NamedGroup.FFDHE_3072,
 231                         NamedGroup.FFDHE_4096,
 232                         NamedGroup.FFDHE_6144,
 233                         NamedGroup.FFDHE_8192,
 234                     };
 235 
 236                 groupList = new ArrayList<>(groups.length);
 237                 for (NamedGroup group : groups) {
 238                     if (isAvailableGroup(group)) {
 239                         groupList.add(group);
 240                     }
 241                 }
 242 
 243                 if (groupList.isEmpty() &&
 244                         SSLLogger.isOn && SSLLogger.isOn("ssl")) {
 245                     SSLLogger.warning("No default named groups");
 246                 }
 247             }
 248 
 249             supportedNamedGroups = new NamedGroup[groupList.size()];
 250             int i = 0;
 251             for (NamedGroup namedGroup : groupList) {
 252                 supportedNamedGroups[i++] = namedGroup;
 253             }
 254         }
 255 
 256         // check whether the group is supported by the underlying providers
 257         private static boolean isAvailableGroup(NamedGroup namedGroup) {
 258             return namedGroup.isAvailableGroup();
 259         }
 260 
 261         static ECGenParameterSpec getECGenParamSpec(NamedGroup ng) {
 262             if (ng.type != NamedGroupType.NAMED_GROUP_ECDHE) {
 263                  throw new RuntimeException(
 264                          "Not a named EC group: " + ng);
 265             }
 266 
 267             // parameters are non-null
 268             AlgorithmParameters params = ng.getParameters();
 269             try {
 270                 return params.getParameterSpec(ECGenParameterSpec.class);
 271             } catch (InvalidParameterSpecException ipse) {
 272                 // should be unlikely
 273                 return new ECGenParameterSpec(ng.oid);
 274             }
 275         }
 276 
 277         static AlgorithmParameters getParameters(NamedGroup ng) {
 278             return ng.getParameters();
 279         }
 280 
 281         // Is there any supported group permitted by the constraints?
 282         static boolean isActivatable(
 283                 AlgorithmConstraints constraints, NamedGroupType type) {
 284 
 285             boolean hasFFDHEGroups = false;
 286             for (NamedGroup namedGroup : supportedNamedGroups) {
 287                 if (namedGroup.type == type) {
 288                     if (constraints.permits(
 289                             EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
 290                             namedGroup.algorithm,
 291                             getParameters(namedGroup))) {
 292 
 293                         return true;
 294                     }
 295 
 296                     if (!hasFFDHEGroups &&
 297                             (type == NamedGroupType.NAMED_GROUP_FFDHE)) {
 298                         hasFFDHEGroups = true;
 299                     }
 300                 }
 301             }
 302 
 303             // For compatibility, if no FFDHE groups are defined, the non-FFDHE
 304             // compatible mode (using DHE cipher suite without FFDHE extension)
 305             // is allowed.
 306             //
 307             // Note that the constraints checking on DHE parameters will be
 308             // performed during key exchanging in a handshake.
 309             return !hasFFDHEGroups && type == NamedGroupType.NAMED_GROUP_FFDHE;
 310         }
 311 
 312         // Is the named group permitted by the constraints?
 313         static boolean isActivatable(
 314                 AlgorithmConstraints constraints, NamedGroup namedGroup) {
 315             if (!isSupported(namedGroup)) {
 316                 return false;
 317             }
 318 
 319             return constraints.permits(
 320                             EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
 321                             namedGroup.algorithm,
 322                             getParameters(namedGroup));
 323         }
 324 
 325         // Is the named group supported?
 326         static boolean isSupported(NamedGroup namedGroup) {
 327             for (NamedGroup group : supportedNamedGroups) {
 328                 if (namedGroup.id == group.id) {
 329                     return true;
 330                 }
 331             }
 332 
 333             return false;
 334         }
 335 
 336         static NamedGroup getPreferredGroup(
 337                 ProtocolVersion negotiatedProtocol,
 338                 AlgorithmConstraints constraints, NamedGroupType[] types,
 339                 List<NamedGroup> requestedNamedGroups) {
 340             for (NamedGroup namedGroup : requestedNamedGroups) {
 341                 if ((NamedGroupType.arrayContains(types, namedGroup.type)) &&
 342                         namedGroup.isAvailable(negotiatedProtocol) &&
 343                         isSupported(namedGroup) &&
 344                         constraints.permits(
 345                                 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
 346                                 namedGroup.algorithm,
 347                                 getParameters(namedGroup))) {
 348                     return namedGroup;
 349                 }
 350             }
 351 
 352             return null;
 353         }
 354 
 355         static NamedGroup getPreferredGroup(
 356                 ProtocolVersion negotiatedProtocol,
 357                 AlgorithmConstraints constraints, NamedGroupType[] types) {
 358             for (NamedGroup namedGroup : supportedNamedGroups) {
 359                 if ((NamedGroupType.arrayContains(types, namedGroup.type)) &&
 360                         namedGroup.isAvailable(negotiatedProtocol) &&
 361                         constraints.permits(
 362                                 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
 363                                 namedGroup.algorithm,
 364                                 getParameters(namedGroup))) {
 365                     return namedGroup;
 366                 }
 367             }
 368 
 369             return null;
 370         }
 371     }
 372 
 373     /**
 374      * Network data producer of a "supported_groups" extension in
 375      * the ClientHello handshake message.
 376      */
 377     private static final class CHSupportedGroupsProducer
 378             extends SupportedGroups implements HandshakeProducer {
 379         // Prevent instantiation of this class.
 380         private CHSupportedGroupsProducer() {
 381             // blank
 382         }
 383 
 384         @Override
 385         public byte[] produce(ConnectionContext context,
 386                 HandshakeMessage message) throws IOException {
 387             // The producing happens in client side only.
 388             ClientHandshakeContext chc = (ClientHandshakeContext)context;
 389 
 390             // Is it a supported and enabled extension?
 391             if (!chc.sslConfig.isAvailable(CH_SUPPORTED_GROUPS)) {
 392                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 393                     SSLLogger.fine(
 394                         "Ignore unavailable supported_groups extension");
 395                 }
 396                 return null;
 397             }
 398 
 399             // Produce the extension.
 400             ArrayList<NamedGroup> namedGroups =
 401                 new ArrayList<>(SupportedGroups.supportedNamedGroups.length);
 402             for (NamedGroup ng : SupportedGroups.supportedNamedGroups) {
 403                 if ((!SupportedGroups.enableFFDHE) &&
 404                     (ng.type == NamedGroupType.NAMED_GROUP_FFDHE)) {
 405                     continue;
 406                 }
 407 
 408                 if (ng.isAvailable(chc.activeProtocols) &&
 409                         ng.isSupported(chc.activeCipherSuites) &&
 410                         chc.algorithmConstraints.permits(
 411                             EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
 412                             ng.algorithm, getParameters(ng))) {
 413                     namedGroups.add(ng);
 414                 } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 415                     SSLLogger.fine(
 416                         "Ignore inactive or disabled named group: " + ng.name);
 417                 }
 418             }
 419 
 420             if (namedGroups.isEmpty()) {
 421                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 422                     SSLLogger.warning("no available named group");
 423                 }
 424 
 425                 return null;
 426             }
 427 
 428             int vectorLen = namedGroups.size() << 1;
 429             byte[] extData = new byte[vectorLen + 2];
 430             ByteBuffer m = ByteBuffer.wrap(extData);
 431             Record.putInt16(m, vectorLen);
 432             for (NamedGroup namedGroup : namedGroups) {
 433                     Record.putInt16(m, namedGroup.id);
 434             }
 435 
 436             // Update the context.
 437             chc.clientRequestedNamedGroups =
 438                     Collections.<NamedGroup>unmodifiableList(namedGroups);
 439             chc.handshakeExtensions.put(CH_SUPPORTED_GROUPS,
 440                     new SupportedGroupsSpec(namedGroups));
 441 
 442             return extData;
 443         }
 444     }
 445 
 446     /**
 447      * Network data producer of a "supported_groups" extension in
 448      * the ClientHello handshake message.
 449      */
 450     private static final
 451             class CHSupportedGroupsConsumer implements ExtensionConsumer {
 452         // Prevent instantiation of this class.
 453         private CHSupportedGroupsConsumer() {
 454             // blank
 455         }
 456 
 457         @Override
 458         public void consume(ConnectionContext context,
 459             HandshakeMessage message, ByteBuffer buffer) throws IOException {
 460             // The consuming happens in server side only.
 461             ServerHandshakeContext shc = (ServerHandshakeContext)context;
 462 
 463             // Is it a supported and enabled extension?
 464             if (!shc.sslConfig.isAvailable(CH_SUPPORTED_GROUPS)) {
 465                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 466                     SSLLogger.fine(
 467                         "Ignore unavailable supported_groups extension");
 468                 }
 469                 return;     // ignore the extension
 470             }
 471 
 472             // Parse the extension.
 473             SupportedGroupsSpec spec;
 474             try {
 475                 spec = new SupportedGroupsSpec(buffer);
 476             } catch (IOException ioe) {
 477                 throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
 478             }
 479 
 480             // Update the context.
 481             List<NamedGroup> knownNamedGroups = new LinkedList<>();
 482             for (int id : spec.namedGroupsIds) {
 483                 NamedGroup ng = NamedGroup.valueOf(id);
 484                 if (ng != null) {
 485                     knownNamedGroups.add(ng);
 486                 }
 487             }
 488 
 489             shc.clientRequestedNamedGroups = knownNamedGroups;
 490             shc.handshakeExtensions.put(CH_SUPPORTED_GROUPS, spec);
 491 
 492             // No impact on session resumption.
 493         }
 494     }
 495 
 496     /**
 497      * Network data producer of a "supported_groups" extension in
 498      * the EncryptedExtensions handshake message.
 499      */
 500     private static final class EESupportedGroupsProducer
 501             extends SupportedGroups implements HandshakeProducer {
 502 
 503         // Prevent instantiation of this class.
 504         private EESupportedGroupsProducer() {
 505             // blank
 506         }
 507 
 508         @Override
 509         public byte[] produce(ConnectionContext context,
 510                 HandshakeMessage message) throws IOException {
 511             // The producing happens in server side only.
 512             ServerHandshakeContext shc = (ServerHandshakeContext)context;
 513 
 514             // Is it a supported and enabled extension?
 515             if (!shc.sslConfig.isAvailable(EE_SUPPORTED_GROUPS)) {
 516                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 517                     SSLLogger.fine(
 518                         "Ignore unavailable supported_groups extension");
 519                 }
 520                 return null;
 521             }
 522 
 523             // Produce the extension.
 524             //
 525             // Contains all groups the server supports, regardless of whether
 526             // they are currently supported by the client.
 527             ArrayList<NamedGroup> namedGroups = new ArrayList<>(
 528                     SupportedGroups.supportedNamedGroups.length);
 529             for (NamedGroup ng : SupportedGroups.supportedNamedGroups) {
 530                 if ((!SupportedGroups.enableFFDHE) &&
 531                     (ng.type == NamedGroupType.NAMED_GROUP_FFDHE)) {
 532                     continue;
 533                 }
 534 
 535                 if (ng.isAvailable(shc.activeProtocols) &&
 536                         ng.isSupported(shc.activeCipherSuites) &&
 537                         shc.algorithmConstraints.permits(
 538                             EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
 539                             ng.algorithm, getParameters(ng))) {
 540                     namedGroups.add(ng);
 541                 } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 542                     SSLLogger.fine(
 543                         "Ignore inactive or disabled named group: " + ng.name);
 544                 }
 545             }
 546 
 547             if (namedGroups.isEmpty()) {
 548                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 549                     SSLLogger.warning("no available named group");
 550                 }
 551 
 552                 return null;
 553             }
 554 
 555             int vectorLen = namedGroups.size() << 1;
 556             byte[] extData = new byte[vectorLen + 2];
 557             ByteBuffer m = ByteBuffer.wrap(extData);
 558             Record.putInt16(m, vectorLen);
 559             for (NamedGroup namedGroup : namedGroups) {
 560                     Record.putInt16(m, namedGroup.id);
 561             }
 562 
 563             // Update the context.
 564             shc.conContext.serverRequestedNamedGroups =
 565                     Collections.<NamedGroup>unmodifiableList(namedGroups);
 566             SupportedGroupsSpec spec = new SupportedGroupsSpec(namedGroups);
 567             shc.handshakeExtensions.put(EE_SUPPORTED_GROUPS, spec);
 568 
 569             return extData;
 570         }
 571     }
 572 
 573     private static final
 574             class EESupportedGroupsConsumer implements ExtensionConsumer {
 575         // Prevent instantiation of this class.
 576         private EESupportedGroupsConsumer() {
 577             // blank
 578         }
 579 
 580         @Override
 581         public void consume(ConnectionContext context,
 582             HandshakeMessage message, ByteBuffer buffer) throws IOException {
 583             // The consuming happens in client side only.
 584             ClientHandshakeContext chc = (ClientHandshakeContext)context;
 585 
 586             // Is it a supported and enabled extension?
 587             if (!chc.sslConfig.isAvailable(EE_SUPPORTED_GROUPS)) {
 588                 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
 589                     SSLLogger.fine(
 590                         "Ignore unavailable supported_groups extension");
 591                 }
 592                 return;     // ignore the extension
 593             }
 594 
 595             // Parse the extension.
 596             SupportedGroupsSpec spec;
 597             try {
 598                 spec = new SupportedGroupsSpec(buffer);
 599             } catch (IOException ioe) {
 600                 throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
 601             }
 602 
 603             // Update the context.
 604             List<NamedGroup> knownNamedGroups =
 605                     new ArrayList<>(spec.namedGroupsIds.length);
 606             for (int id : spec.namedGroupsIds) {
 607                 NamedGroup ng = NamedGroup.valueOf(id);
 608                 if (ng != null) {
 609                     knownNamedGroups.add(ng);
 610                 }
 611             }
 612 
 613             chc.conContext.serverRequestedNamedGroups = knownNamedGroups;
 614             chc.handshakeExtensions.put(EE_SUPPORTED_GROUPS, spec);
 615 
 616             // No impact on session resumption.
 617         }
 618     }
 619 }