--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,491 @@
+/*
+ * Copyright (c) 2006, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.io.IOException;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.security.AlgorithmParameters;
+import java.security.AlgorithmConstraints;
+import java.security.CryptoPrimitive;
+import java.security.AccessController;
+import java.security.spec.AlgorithmParameterSpec;
+import javax.crypto.spec.DHParameterSpec;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ArrayList;
+import javax.net.ssl.SSLProtocolException;
+
+import sun.security.action.GetPropertyAction;
+
+//
+// Note: Since RFC 7919, the extension's semantics are expanded from
+// "Supported Elliptic Curves" to "Supported Groups". The enum datatype
+// used in the extension has been renamed from NamedCurve to NamedGroup.
+// Its semantics are likewise expanded from "named curve" to "named group".
+//
+final class SupportedGroupsExtension extends HelloExtension {
+
+ /* Class and subclass dynamic debugging support */
+ private static final Debug debug = Debug.getInstance("ssl");
+
+ private static final int ARBITRARY_PRIME = 0xff01;
+ private static final int ARBITRARY_CHAR2 = 0xff02;
+
+ // cache to speed up the parameters construction
+ private static final Map<NamedGroup,
+ AlgorithmParameters> namedGroupParams = new HashMap<>();
+
+ // the supported named groups
+ private static final NamedGroup[] supportedNamedGroups;
+
+ // the named group presented in the extension
+ private final int[] requestedNamedGroupIds;
+
+ static {
+ boolean requireFips = SunJSSE.isFIPS();
+
+ // The value of the System Property defines a list of enabled named
+ // groups in preference order, separated with comma. For example:
+ //
+ // jdk.tls.namedGroups="secp521r1, secp256r1, ffdhe2048"
+ //
+ // If the System Property is not defined or the value is empty, the
+ // default groups and preferences will be used.
+ String property = AccessController.doPrivileged(
+ new GetPropertyAction("jdk.tls.namedGroups"));
+ if (property != null && property.length() != 0) {
+ // remove double quote marks from beginning/end of the property
+ if (property.length() > 1 && property.charAt(0) == '"' &&
+ property.charAt(property.length() - 1) == '"') {
+ property = property.substring(1, property.length() - 1);
+ }
+ }
+
+ ArrayList<NamedGroup> groupList;
+ if (property != null && property.length() != 0) { // customized groups
+ String[] groups = property.split(",");
+ groupList = new ArrayList<>(groups.length);
+ for (String group : groups) {
+ group = group.trim();
+ if (!group.isEmpty()) {
+ NamedGroup namedGroup = NamedGroup.nameOf(group);
+ if (namedGroup != null &&
+ (!requireFips || namedGroup.isFips)) {
+ if (isAvailableGroup(namedGroup)) {
+ groupList.add(namedGroup);
+ }
+ } // ignore unknown groups
+ }
+ }
+
+ if (groupList.isEmpty() && JsseJce.isEcAvailable()) {
+ throw new IllegalArgumentException(
+ "System property jdk.tls.namedGroups(" + property + ") " +
+ "contains no supported elliptic curves");
+ }
+ } else { // default groups
+ NamedGroup[] groups;
+ if (requireFips) {
+ groups = new NamedGroup[] {
+ // only NIST curves in FIPS mode
+ NamedGroup.SECP256_R1,
+ NamedGroup.SECP384_R1,
+ NamedGroup.SECP521_R1,
+ NamedGroup.SECT283_K1,
+ NamedGroup.SECT283_R1,
+ NamedGroup.SECT409_K1,
+ NamedGroup.SECT409_R1,
+ NamedGroup.SECT571_K1,
+ NamedGroup.SECT571_R1,
+
+ // FFDHE 2048
+ NamedGroup.FFDHE_2048,
+ NamedGroup.FFDHE_3072,
+ NamedGroup.FFDHE_4096,
+ NamedGroup.FFDHE_6144,
+ NamedGroup.FFDHE_8192,
+ };
+ } else {
+ groups = new NamedGroup[] {
+ // NIST curves first
+ NamedGroup.SECP256_R1,
+ NamedGroup.SECP384_R1,
+ NamedGroup.SECP521_R1,
+ NamedGroup.SECT283_K1,
+ NamedGroup.SECT283_R1,
+ NamedGroup.SECT409_K1,
+ NamedGroup.SECT409_R1,
+ NamedGroup.SECT571_K1,
+ NamedGroup.SECT571_R1,
+
+ // non-NIST curves
+ NamedGroup.SECP256_K1,
+
+ // FFDHE 2048
+ NamedGroup.FFDHE_2048,
+ NamedGroup.FFDHE_3072,
+ NamedGroup.FFDHE_4096,
+ NamedGroup.FFDHE_6144,
+ NamedGroup.FFDHE_8192,
+ };
+ }
+
+ groupList = new ArrayList<>(groups.length);
+ for (NamedGroup group : groups) {
+ if (isAvailableGroup(group)) {
+ groupList.add(group);
+ }
+ }
+ }
+
+ if (debug != null && groupList.isEmpty()) {
+ Debug.log(
+ "Initialized [jdk.tls.namedGroups|default] list contains " +
+ "no available elliptic curves. " +
+ (property != null ? "(" + property + ")" : "[Default]"));
+ }
+
+ supportedNamedGroups = new NamedGroup[groupList.size()];
+ int i = 0;
+ for (NamedGroup namedGroup : groupList) {
+ supportedNamedGroups[i++] = namedGroup;
+ }
+ }
+
+ // check whether the group is supported by the underlying providers
+ private static boolean isAvailableGroup(NamedGroup namedGroup) {
+ AlgorithmParameters params = null;
+ AlgorithmParameterSpec spec = null;
+ if ("EC".equals(namedGroup.algorithm)) {
+ if (namedGroup.oid != null) {
+ try {
+ params = JsseJce.getAlgorithmParameters("EC");
+ spec = new ECGenParameterSpec(namedGroup.oid);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+ } else if ("DiffieHellman".equals(namedGroup.algorithm)) {
+ try {
+ params = JsseJce.getAlgorithmParameters("DiffieHellman");
+ spec = getFFDHEDHParameterSpec(namedGroup);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ if ((params != null) && (spec != null)) {
+ try {
+ params.init(spec);
+ } catch (Exception e) {
+ return false;
+ }
+
+ // cache the parameters
+ namedGroupParams.put(namedGroup, params);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private static DHParameterSpec getFFDHEDHParameterSpec(
+ NamedGroup namedGroup) {
+ DHParameterSpec spec = null;
+ switch (namedGroup) {
+ case FFDHE_2048:
+ spec = PredefinedDHParameterSpecs.ffdheParams.get(2048);
+ break;
+ case FFDHE_3072:
+ spec = PredefinedDHParameterSpecs.ffdheParams.get(3072);
+ break;
+ case FFDHE_4096:
+ spec = PredefinedDHParameterSpecs.ffdheParams.get(4096);
+ break;
+ case FFDHE_6144:
+ spec = PredefinedDHParameterSpecs.ffdheParams.get(6144);
+ break;
+ case FFDHE_8192:
+ spec = PredefinedDHParameterSpecs.ffdheParams.get(8192);
+ }
+
+ return spec;
+ }
+
+ private static DHParameterSpec getPredefinedDHParameterSpec(
+ NamedGroup namedGroup) {
+ DHParameterSpec spec = null;
+ switch (namedGroup) {
+ case FFDHE_2048:
+ spec = PredefinedDHParameterSpecs.definedParams.get(2048);
+ break;
+ case FFDHE_3072:
+ spec = PredefinedDHParameterSpecs.definedParams.get(3072);
+ break;
+ case FFDHE_4096:
+ spec = PredefinedDHParameterSpecs.definedParams.get(4096);
+ break;
+ case FFDHE_6144:
+ spec = PredefinedDHParameterSpecs.definedParams.get(6144);
+ break;
+ case FFDHE_8192:
+ spec = PredefinedDHParameterSpecs.definedParams.get(8192);
+ }
+
+ return spec;
+ }
+
+ private SupportedGroupsExtension(int[] requestedNamedGroupIds) {
+ super(ExtensionType.EXT_SUPPORTED_GROUPS);
+
+ this.requestedNamedGroupIds = requestedNamedGroupIds;
+ }
+
+ SupportedGroupsExtension(HandshakeInStream s, int len) throws IOException {
+ super(ExtensionType.EXT_SUPPORTED_GROUPS);
+
+ int k = s.getInt16();
+ if (((len & 1) != 0) || (k == 0) || (k + 2 != len)) {
+ throw new SSLProtocolException("Invalid " + type + " extension");
+ }
+
+ // Note: unknown named group will be ignored later.
+ requestedNamedGroupIds = new int[k >> 1];
+ for (int i = 0; i < requestedNamedGroupIds.length; i++) {
+ requestedNamedGroupIds[i] = s.getInt16();
+ }
+ }
+
+ // Get a local preferred supported ECDHE group permitted by the constraints.
+ static NamedGroup getPreferredECGroup(AlgorithmConstraints constraints) {
+ for (NamedGroup namedGroup : supportedNamedGroups) {
+ if ((namedGroup.type == NamedGroupType.NAMED_GROUP_ECDHE) &&
+ constraints.permits(EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
+ namedGroup.algorithm, namedGroupParams.get(namedGroup))) {
+
+ return namedGroup;
+ }
+ }
+
+ return null;
+ }
+
+ // Is there any supported group permitted by the constraints?
+ static boolean isActivatable(
+ AlgorithmConstraints constraints, NamedGroupType type) {
+
+ boolean hasFFDHEGroups = false;
+ for (NamedGroup namedGroup : supportedNamedGroups) {
+ if (namedGroup.type == type) {
+ if (constraints.permits(
+ EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
+ namedGroup.algorithm,
+ namedGroupParams.get(namedGroup))) {
+
+ return true;
+ }
+
+ if (!hasFFDHEGroups &&
+ (type == NamedGroupType.NAMED_GROUP_FFDHE)) {
+
+ hasFFDHEGroups = true;
+ }
+ }
+ }
+
+ // For compatibility, if no FFDHE groups are defined, the non-FFDHE
+ // compatible mode (using DHE cipher suite without FFDHE extension)
+ // is allowed.
+ //
+ // Note that the constraints checking on DHE parameters will be
+ // performed during key exchanging in a handshake.
+ if (!hasFFDHEGroups && (type == NamedGroupType.NAMED_GROUP_FFDHE)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ // Create the default supported groups extension.
+ static SupportedGroupsExtension createExtension(
+ AlgorithmConstraints constraints,
+ CipherSuiteList cipherSuites, boolean enableFFDHE) {
+
+ ArrayList<Integer> groupList =
+ new ArrayList<>(supportedNamedGroups.length);
+ for (NamedGroup namedGroup : supportedNamedGroups) {
+ if ((!enableFFDHE) &&
+ (namedGroup.type == NamedGroupType.NAMED_GROUP_FFDHE)) {
+ continue;
+ }
+
+ if (cipherSuites.contains(namedGroup.type) &&
+ constraints.permits(EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
+ namedGroup.algorithm, namedGroupParams.get(namedGroup))) {
+
+ groupList.add(namedGroup.id);
+ }
+ }
+
+ if (!groupList.isEmpty()) {
+ int[] ids = new int[groupList.size()];
+ int i = 0;
+ for (Integer id : groupList) {
+ ids[i++] = id;
+ }
+
+ return new SupportedGroupsExtension(ids);
+ }
+
+ return null;
+ }
+
+ // get the preferred activated named group
+ NamedGroup getPreferredGroup(
+ AlgorithmConstraints constraints, NamedGroupType type) {
+
+ for (int groupId : requestedNamedGroupIds) {
+ NamedGroup namedGroup = NamedGroup.valueOf(groupId);
+ if ((namedGroup != null) && (namedGroup.type == type) &&
+ SupportedGroupsExtension.supports(namedGroup) &&
+ constraints.permits(EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
+ namedGroup.algorithm, namedGroupParams.get(namedGroup))) {
+
+ return namedGroup;
+ }
+ }
+
+ return null;
+ }
+
+ boolean hasFFDHEGroup() {
+ for (int groupId : requestedNamedGroupIds) {
+ /*
+ * [RFC 7919] Codepoints in the "Supported Groups Registry"
+ * with a high byte of 0x01 (that is, between 256 and 511,
+ * inclusive) are set aside for FFDHE groups.
+ */
+ if ((groupId >= 256) && (groupId <= 511)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ boolean contains(int index) {
+ for (int groupId : requestedNamedGroupIds) {
+ if (index == groupId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ int length() {
+ return 6 + (requestedNamedGroupIds.length << 1);
+ }
+
+ @Override
+ void send(HandshakeOutStream s) throws IOException {
+ s.putInt16(type.id);
+ int k = requestedNamedGroupIds.length << 1;
+ s.putInt16(k + 2);
+ s.putInt16(k);
+ for (int groupId : requestedNamedGroupIds) {
+ s.putInt16(groupId);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Extension " + type + ", group names: {");
+ boolean first = true;
+ for (int groupId : requestedNamedGroupIds) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ // first check if it is a known named group, then try other cases.
+ NamedGroup namedGroup = NamedGroup.valueOf(groupId);
+ if (namedGroup != null) {
+ sb.append(namedGroup.name);
+ } else if (groupId == ARBITRARY_PRIME) {
+ sb.append("arbitrary_explicit_prime_curves");
+ } else if (groupId == ARBITRARY_CHAR2) {
+ sb.append("arbitrary_explicit_char2_curves");
+ } else {
+ sb.append("unknown named group " + groupId);
+ }
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ static boolean supports(NamedGroup namedGroup) {
+ for (NamedGroup group : supportedNamedGroups) {
+ if (namedGroup.id == group.id) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ static ECGenParameterSpec getECGenParamSpec(NamedGroup namedGroup) {
+ if (namedGroup.type != NamedGroupType.NAMED_GROUP_ECDHE) {
+ throw new RuntimeException("Not a named EC group: " + namedGroup);
+ }
+
+ AlgorithmParameters params = namedGroupParams.get(namedGroup);
+ try {
+ return params.getParameterSpec(ECGenParameterSpec.class);
+ } catch (InvalidParameterSpecException ipse) {
+ // should be unlikely
+ return new ECGenParameterSpec(namedGroup.oid);
+ }
+ }
+
+ static DHParameterSpec getDHParameterSpec(NamedGroup namedGroup) {
+ if (namedGroup.type != NamedGroupType.NAMED_GROUP_FFDHE) {
+ throw new RuntimeException("Not a named DH group: " + namedGroup);
+ }
+
+ AlgorithmParameters params = namedGroupParams.get(namedGroup);
+ try {
+ return params.getParameterSpec(DHParameterSpec.class);
+ } catch (InvalidParameterSpecException ipse) {
+ // should be unlikely
+ return getPredefinedDHParameterSpec(namedGroup);
+ }
+ }
+}