--- old/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java 2018-05-11 15:06:18.451047500 -0700 +++ new/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java 2018-05-11 15:06:17.975273700 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, 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 @@ -26,466 +26,1012 @@ package sun.security.ssl; import java.io.IOException; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.InvalidParameterSpecException; -import java.security.AlgorithmParameters; +import java.nio.ByteBuffer; +import java.security.AccessController; import java.security.AlgorithmConstraints; +import java.security.AlgorithmParameters; import java.security.CryptoPrimitive; -import java.security.AccessController; +import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; -import javax.crypto.spec.DHParameterSpec; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; import java.util.Map; -import java.util.ArrayList; +import javax.crypto.spec.DHParameterSpec; import javax.net.ssl.SSLProtocolException; - import sun.security.action.GetPropertyAction; +import static sun.security.ssl.SSLExtension.CH_SUPPORTED_GROUPS; +import static sun.security.ssl.SSLExtension.EE_SUPPORTED_GROUPS; +import sun.security.ssl.SSLExtension.ExtensionConsumer; +import sun.security.ssl.SSLExtension.SSLExtensionSpec; +import sun.security.ssl.SSLHandshake.HandshakeMessage; -// -// 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 namedGroupParams = new HashMap<>(); - - // the supported named groups - private static final NamedGroup[] supportedNamedGroups; - - // the named group presented in the extension - private final int[] requestedNamedGroupIds; +/** + * Pack of the "supported_groups" extensions [RFC 4492/7919]. + */ +final class SupportedGroupsExtension { + static final HandshakeProducer chNetworkProducer = + new CHSupportedGroupsProducer(); + static final ExtensionConsumer chOnLoadConcumer = + new CHSupportedGroupsConsumer(); + static final SSLStringize sgsStringize = + new SupportedGroupsStringize(); + + static final HandshakeProducer eeNetworkProducer = + new EESupportedGroupsProducer(); + static final ExtensionConsumer eeOnLoadConcumer = + new EESupportedGroupsConsumer(); + + /** + * The "supported_groups" extension. + */ + static final class SupportedGroupsSpec implements SSLExtensionSpec { + final int[] namedGroupsIds; - static { - boolean requireFips = SunJSSE.isFIPS(); + private SupportedGroupsSpec(int[] namedGroupsIds) { + this.namedGroupsIds = namedGroupsIds; + } - // 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); + private SupportedGroupsSpec(List namedGroups) { + this.namedGroupsIds = new int[namedGroups.size()]; + int i = 0; + for (NamedGroup ng : namedGroups) { + namedGroupsIds[i++] = ng.id; } } - ArrayList 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 - } + private SupportedGroupsSpec(ByteBuffer m) throws IOException { + if (m.remaining() < 2) { // 2: the length of the list + throw new SSLProtocolException( + "Invalid supported_groups extension: insufficient data"); + } + + byte[] ngs = Record.getBytes16(m); + if (m.hasRemaining()) { + throw new SSLProtocolException( + "Invalid supported_groups extension: unknown extra data"); + } + + if ((ngs == null) || (ngs.length == 0) || (ngs.length % 2 != 0)) { + throw new SSLProtocolException( + "Invalid supported_groups extension: incomplete data"); } - 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, - }; + int[] ids = new int[ngs.length / 2]; + for (int i = 0, j = 0; i < ngs.length;) { + ids[j++] = ((ngs[i++] & 0xFF) << 8) | (ngs[i++] & 0xFF); + } + + this.namedGroupsIds = ids; + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"versions\": '['{0}']'", Locale.ENGLISH); + + if (namedGroupsIds == null || namedGroupsIds.length == 0) { + Object[] messageFields = { + "" + }; + return messageFormat.format(messageFields); } 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); + StringBuilder builder = new StringBuilder(512); + boolean isFirst = true; + for (int ngid : namedGroupsIds) { + if (isFirst) { + isFirst = false; + } else { + builder.append(", "); + } + + builder.append(NamedGroup.nameOf(ngid)); } + + Object[] messageFields = { + builder.toString() + }; + + return messageFormat.format(messageFields); } } + } - 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; + private static final + class SupportedGroupsStringize implements SSLStringize { + @Override + public String toString(ByteBuffer buffer) { + try { + return (new SupportedGroupsSpec(buffer)).toString(); + } catch (IOException ioe) { + // For debug logging only, so please swallow exceptions. + return ioe.getMessage(); + } } } - // 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; + static enum NamedGroupType { + NAMED_GROUP_ECDHE, // Elliptic Curve Groups (ECDHE) + NAMED_GROUP_FFDHE, // Finite Field Groups (DHE) + NAMED_GROUP_XDH, // Finite Field Groups (XDH) + NAMED_GROUP_ARBITRARY, // arbitrary prime and curves (ECDHE) + NAMED_GROUP_NONE; // Not predefined named group + + boolean isSupported(List cipherSuites) { + for (CipherSuite cs : cipherSuites) { + if (cs.keyExchange == null || cs.keyExchange.groupType == this) { + return true; } } - } else if ("DiffieHellman".equals(namedGroup.algorithm)) { - try { - params = JsseJce.getAlgorithmParameters("DiffieHellman"); - spec = getFFDHEDHParameterSpec(namedGroup); - } catch (Exception e) { - return false; - } + + return false; } + } - if ((params != null) && (spec != null)) { - try { - params.init(spec); - } catch (Exception e) { - return false; - } + static enum NamedGroup { + // Elliptic Curves (RFC 4492) + // + // See sun.security.util.CurveDB for the OIDs + // NIST K-163 + SECT163_K1 (0x0001, "sect163k1", "1.3.132.0.1", true, + ProtocolVersion.PROTOCOLS_TO_12), + SECT163_R1 (0x0002, "sect163r1", "1.3.132.0.2", false, + ProtocolVersion.PROTOCOLS_TO_12), + + // NIST B-163 + SECT163_R2 (0x0003, "sect163r2", "1.3.132.0.15", true, + ProtocolVersion.PROTOCOLS_TO_12), + SECT193_R1 (0x0004, "sect193r1", "1.3.132.0.24", false, + ProtocolVersion.PROTOCOLS_TO_12), + SECT193_R2 (0x0005, "sect193r2", "1.3.132.0.25", false, + ProtocolVersion.PROTOCOLS_TO_12), + + // NIST K-233 + SECT233_K1 (0x0006, "sect233k1", "1.3.132.0.26", true, + ProtocolVersion.PROTOCOLS_TO_12), + + // NIST B-233 + SECT233_R1 (0x0007, "sect233r1", "1.3.132.0.27", true, + ProtocolVersion.PROTOCOLS_TO_12), + SECT239_K1 (0x0008, "sect239k1", "1.3.132.0.3", false, + ProtocolVersion.PROTOCOLS_TO_12), + + // NIST K-283 + SECT283_K1 (0x0009, "sect283k1", "1.3.132.0.16", true, + ProtocolVersion.PROTOCOLS_TO_12), + + // NIST B-283 + SECT283_R1 (0x000A, "sect283r1", "1.3.132.0.17", true, + ProtocolVersion.PROTOCOLS_TO_12), + + // NIST K-409 + SECT409_K1 (0x000B, "sect409k1", "1.3.132.0.36", true, + ProtocolVersion.PROTOCOLS_TO_12), + + // NIST B-409 + SECT409_R1 (0x000C, "sect409r1", "1.3.132.0.37", true, + ProtocolVersion.PROTOCOLS_TO_12), + + // NIST K-571 + SECT571_K1 (0x000D, "sect571k1", "1.3.132.0.38", true, + ProtocolVersion.PROTOCOLS_TO_12), + + // NIST B-571 + SECT571_R1 (0x000E, "sect571r1", "1.3.132.0.39", true, + ProtocolVersion.PROTOCOLS_TO_12), + SECP160_K1 (0x000F, "secp160k1", "1.3.132.0.9", false, + ProtocolVersion.PROTOCOLS_TO_12), + SECP160_R1 (0x0010, "secp160r1", "1.3.132.0.8", false, + ProtocolVersion.PROTOCOLS_TO_12), + SECP160_R2 (0x0011, "secp160r2", "1.3.132.0.30", false, + ProtocolVersion.PROTOCOLS_TO_12), + SECP192_K1 (0x0012, "secp192k1", "1.3.132.0.31", false, + ProtocolVersion.PROTOCOLS_TO_12), + + // NIST P-192 + SECP192_R1 (0x0013, "secp192r1", "1.2.840.10045.3.1.1", true, + ProtocolVersion.PROTOCOLS_TO_12), + SECP224_K1 (0x0014, "secp224k1", "1.3.132.0.32", false, + ProtocolVersion.PROTOCOLS_TO_12), + // NIST P-224 + SECP224_R1 (0x0015, "secp224r1", "1.3.132.0.33", true, + ProtocolVersion.PROTOCOLS_TO_12), + SECP256_K1 (0x0016, "secp256k1", "1.3.132.0.10", false, + ProtocolVersion.PROTOCOLS_TO_12), + + // NIST P-256 + SECP256_R1 (0x0017, "secp256r1", "1.2.840.10045.3.1.7", true, + ProtocolVersion.PROTOCOLS_TO_13), + + // NIST P-384 + SECP384_R1 (0x0018, "secp384r1", "1.3.132.0.34", true, + ProtocolVersion.PROTOCOLS_TO_13), + + // NIST P-521 + SECP521_R1 (0x0019, "secp521r1", "1.3.132.0.35", true, + ProtocolVersion.PROTOCOLS_TO_13), + + // x25519 and x448 + X25519 (0x001D, "x25519", true, "x25519", + ProtocolVersion.PROTOCOLS_TO_13), + X448 (0x001E, "x448", true, "x448", + ProtocolVersion.PROTOCOLS_TO_13), + + // Finite Field Diffie-Hellman Ephemeral Parameters (RFC 7919) + FFDHE_2048 (0x0100, "ffdhe2048", true, + ProtocolVersion.PROTOCOLS_TO_13), + FFDHE_3072 (0x0101, "ffdhe3072", true, + ProtocolVersion.PROTOCOLS_TO_13), + FFDHE_4096 (0x0102, "ffdhe4096", true, + ProtocolVersion.PROTOCOLS_TO_13), + FFDHE_6144 (0x0103, "ffdhe6144", true, + ProtocolVersion.PROTOCOLS_TO_13), + FFDHE_8192 (0x0104, "ffdhe8192", true, + ProtocolVersion.PROTOCOLS_TO_13), - // cache the parameters - namedGroupParams.put(namedGroup, params); + // Elliptic Curves (RFC 4492) + // + // arbitrary prime and characteristic-2 curves + ARBITRARY_PRIME (0xFF01, "arbitrary_explicit_prime_curves", + ProtocolVersion.PROTOCOLS_TO_12), + ARBITRARY_CHAR2 (0xFF02, "arbitrary_explicit_char2_curves", + ProtocolVersion.PROTOCOLS_TO_12); + + final int id; // hash + signature + final NamedGroupType type; // group type + final String name; // literal name + final String oid; // object identifier of the named group + final String algorithm; // signature algorithm + final boolean isFips; // can be used in FIPS mode? + final ProtocolVersion[] supportedProtocols; + + // Constructor used for Elliptic Curve Groups (ECDHE) + private NamedGroup(int id, String name, String oid, boolean isFips, + ProtocolVersion[] supportedProtocols) { + this.id = id; + this.type = NamedGroupType.NAMED_GROUP_ECDHE; + this.name = name; + this.oid = oid; + this.algorithm = "EC"; + this.isFips = isFips; + this.supportedProtocols = supportedProtocols; + } + + // Constructor used for Elliptic Curve Groups (XDH) + private NamedGroup(int id, String name, + boolean isFips, String algorithm, + ProtocolVersion[] supportedProtocols) { + this.id = id; + this.type = NamedGroupType.NAMED_GROUP_XDH; + this.name = name; + this.oid = null; + this.algorithm = algorithm; + this.isFips = isFips; + this.supportedProtocols = supportedProtocols; + } + + // Constructor used for Finite Field Diffie-Hellman Groups (FFDHE) + private NamedGroup(int id, String name, boolean isFips, + ProtocolVersion[] supportedProtocols) { + this.id = id; + this.type = NamedGroupType.NAMED_GROUP_FFDHE; + this.name = name; + this.oid = null; + this.algorithm = "DiffieHellman"; + this.isFips = isFips; + this.supportedProtocols = supportedProtocols; + } + + // Constructor used for arbitrary prime and curves (ECDHE) + private NamedGroup(int id, String name, + ProtocolVersion[] supportedProtocols) { + this.id = id; + this.type = NamedGroupType.NAMED_GROUP_ARBITRARY; + this.name = name; + this.oid = null; + this.algorithm = "EC"; + this.isFips = false; + this.supportedProtocols = supportedProtocols; + } + + static NamedGroup valueOf(int id) { + for (NamedGroup group : NamedGroup.values()) { + if (group.id == id) { + return group; + } + } - return true; + return null; } - return false; - } + static NamedGroup valueOf(ECParameterSpec params) { + String oid = JsseJce.getNamedCurveOid(params); + if ((oid != null) && (!oid.isEmpty())) { + for (NamedGroup group : NamedGroup.values()) { + if ((group.type == NamedGroupType.NAMED_GROUP_ECDHE) && + oid.equals(group.oid)) { + return group; + } + } + } - 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 null; } - return spec; - } + static NamedGroup valueOf(DHParameterSpec params) { + for (Map.Entry me : + SupportedGroups.namedGroupParams.entrySet()) { + NamedGroup ng = me.getKey(); + if (ng.type != NamedGroupType.NAMED_GROUP_FFDHE) { + continue; + } - 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); - } + DHParameterSpec ngParams = null; + AlgorithmParameters aps = me.getValue(); + try { + ngParams = aps.getParameterSpec(DHParameterSpec.class); + } catch (InvalidParameterSpecException ipse) { + // should be unlikely + } - return spec; - } + if (ngParams == null) { + continue; + } - private SupportedGroupsExtension(int[] requestedNamedGroupIds) { - super(ExtensionType.EXT_SUPPORTED_GROUPS); + if (ngParams.getP().equals(params.getP()) && + ngParams.getG().equals(params.getG())) { + return ng; + } + } - this.requestedNamedGroupIds = requestedNamedGroupIds; - } + return null; + } - SupportedGroupsExtension(HandshakeInStream s, int len) throws IOException { - super(ExtensionType.EXT_SUPPORTED_GROUPS); + static NamedGroup nameOf(String name) { + for (NamedGroup group : NamedGroup.values()) { + if (group.name.equals(name)) { + return group; + } + } - int k = s.getInt16(); - if (((len & 1) != 0) || (k == 0) || (k + 2 != len)) { - throw new SSLProtocolException("Invalid " + type + " extension"); + return null; } - // 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(); + static String nameOf(int id) { + for (NamedGroup group : NamedGroup.values()) { + if (group.id == id) { + return group.name; + } + } + + return "UNDEFINED-NAMED-GROUP(" + id + ")"; } - } - // 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))) { + boolean isAvailable(List protocolVersions) { + for (ProtocolVersion pv : supportedProtocols) { + if (protocolVersions.contains(pv)) { + return true; + } + } + return false; + } + + boolean isAvailable(ProtocolVersion protocolVersion) { + for (ProtocolVersion pv : supportedProtocols) { + if (protocolVersion == pv) { + return true; + } + } + return false; + } - return namedGroup; + boolean isSupported(List cipherSuites) { + for (CipherSuite cs : cipherSuites) { + boolean isMatch = isAvailable(cs.supportedProtocols); + if (isMatch && (cs.keyExchange == null || + cs.keyExchange.groupType == type)) { + return true; + } } + return false; } - return null; + // lazy loading of parameters + AlgorithmParameters getParameters() { + return SupportedGroups.namedGroupParams.get(this); + } + + AlgorithmParameterSpec getParameterSpec() { + if (this.type == NamedGroupType.NAMED_GROUP_ECDHE) { + return SupportedGroups.getECGenParamSpec(this); + } else if (this.type == NamedGroupType.NAMED_GROUP_FFDHE) { + return SupportedGroups.getDHParameterSpec(this); + } + + 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))) { + static class SupportedGroups { + // To switch off the supported_groups extension for DHE cipher suite. + static final boolean enableFFDHE = + Utilities.getBooleanProperty("jsse.enableFFDHE", true); + + // cache to speed up the parameters construction + static final Map namedGroupParams = new HashMap<>(); + + // the supported named groups + static final NamedGroup[] supportedNamedGroups; + + 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); + } + } - return true; + ArrayList groupList; + if (property != null && property.length() != 0) { + 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 (!hasFFDHEGroups && - (type == NamedGroupType.NAMED_GROUP_FFDHE)) { + if (groupList.isEmpty()) { + throw new IllegalArgumentException( + "System property jdk.tls.namedGroups(" + + property + ") contains no supported named groups"); + } + } 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); + } + } - hasFFDHEGroups = true; + if (groupList.isEmpty() && + SSLLogger.isOn && SSLLogger.isOn("ssl")) { + SSLLogger.warning("No default named groups"); } } - } - // 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; + supportedNamedGroups = new NamedGroup[groupList.size()]; + int i = 0; + for (NamedGroup namedGroup : groupList) { + supportedNamedGroups[i++] = namedGroup; + } } - return false; - } + // check whether the group is supported by the underlying providers + private static boolean isAvailableGroup(NamedGroup namedGroup) { + AlgorithmParameters params = null; + AlgorithmParameterSpec spec = null; + if (namedGroup.type == NamedGroupType.NAMED_GROUP_ECDHE) { + if (namedGroup.oid != null) { + try { + params = JsseJce.getAlgorithmParameters("EC"); + spec = new ECGenParameterSpec(namedGroup.oid); + } catch (NoSuchAlgorithmException e) { + return false; + } + } + } else if (namedGroup.type == NamedGroupType.NAMED_GROUP_FFDHE) { + try { + params = JsseJce.getAlgorithmParameters("DiffieHellman"); + spec = getFFDHEDHParameterSpec(namedGroup); + } catch (NoSuchAlgorithmException e) { + return false; + } + } // Otherwise, unsupported. + + if ((params != null) && (spec != null)) { + try { + params.init(spec); + } catch (InvalidParameterSpecException e) { + return false; + } - // Create the default supported groups extension. - static SupportedGroupsExtension createExtension( - AlgorithmConstraints constraints, - CipherSuiteList cipherSuites, boolean enableFFDHE) { + // cache the parameters + namedGroupParams.put(namedGroup, params); - ArrayList groupList = - new ArrayList<>(supportedNamedGroups.length); - for (NamedGroup namedGroup : supportedNamedGroups) { - if ((!enableFFDHE) && - (namedGroup.type == NamedGroupType.NAMED_GROUP_FFDHE)) { - continue; + return true; } - if (cipherSuites.contains(namedGroup.type) && - constraints.permits(EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), - namedGroup.algorithm, namedGroupParams.get(namedGroup))) { + return false; + } - groupList.add(namedGroup.id); + 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; } - if (!groupList.isEmpty()) { - int[] ids = new int[groupList.size()]; - int i = 0; - for (Integer id : groupList) { - ids[i++] = id; + 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 new SupportedGroupsExtension(ids); + return spec; } - return null; - } + static ECGenParameterSpec getECGenParamSpec(NamedGroup namedGroup) { + if (namedGroup.type != NamedGroupType.NAMED_GROUP_ECDHE) { + throw new RuntimeException( + "Not a named EC group: " + namedGroup); + } - // get the preferred activated named group - NamedGroup getPreferredGroup( - AlgorithmConstraints constraints, NamedGroupType type) { + AlgorithmParameters params = namedGroupParams.get(namedGroup); + try { + return params.getParameterSpec(ECGenParameterSpec.class); + } catch (InvalidParameterSpecException ipse) { + // should be unlikely + return new ECGenParameterSpec(namedGroup.oid); + } + } - 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))) { + static DHParameterSpec getDHParameterSpec(NamedGroup namedGroup) { + if (namedGroup.type != NamedGroupType.NAMED_GROUP_FFDHE) { + throw new RuntimeException( + "Not a named DH group: " + namedGroup); + } - return namedGroup; + AlgorithmParameters params = namedGroupParams.get(namedGroup); + try { + return params.getParameterSpec(DHParameterSpec.class); + } catch (InvalidParameterSpecException ipse) { + // should be unlikely + return getPredefinedDHParameterSpec(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; + } + } + } - 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; + // 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. + return !hasFFDHEGroups && type == NamedGroupType.NAMED_GROUP_FFDHE; + } + + // Is the named group permitted by the constraints? + static boolean isActivatable( + AlgorithmConstraints constraints, NamedGroup namedGroup) { + if (!isSupported(namedGroup)) { + return false; } + + return constraints.permits( + EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), + namedGroup.algorithm, + namedGroupParams.get(namedGroup)); } - return false; - } + // Is there any supported group permitted by the constraints? + static boolean isSupported(NamedGroup namedGroup) { + for (NamedGroup group : supportedNamedGroups) { + if (namedGroup.id == group.id) { + return true; + } + } - boolean contains(int index) { - for (int groupId : requestedNamedGroupIds) { - if (index == groupId) { - return true; + return false; + } + + static NamedGroup getPreferredGroup( + ProtocolVersion negotiatedProtocol, + AlgorithmConstraints constraints, NamedGroupType type, + List requestedNamedGroups) { + for (NamedGroup namedGroup : requestedNamedGroups) { + if ((namedGroup.type == type) && + namedGroup.isAvailable(negotiatedProtocol) && + constraints.permits( + EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), + namedGroup.algorithm, + namedGroupParams.get(namedGroup))) { + return namedGroup; + } } + + return null; } - return false; - } - @Override - int length() { - return 6 + (requestedNamedGroupIds.length << 1); - } + static NamedGroup getPreferredGroup( + ProtocolVersion negotiatedProtocol, + AlgorithmConstraints constraints, NamedGroupType type) { + for (NamedGroup namedGroup : supportedNamedGroups) { + if ((namedGroup.type == type) && + namedGroup.isAvailable(negotiatedProtocol) && + constraints.permits( + EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), + namedGroup.algorithm, + namedGroupParams.get(namedGroup))) { + return namedGroup; + } + } - @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); + return null; } } - @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(", "); + /** + * Network data producer of a "supported_groups" extension in + * the ClientHello handshake message. + */ + private static final class CHSupportedGroupsProducer + extends SupportedGroups implements HandshakeProducer { + // Prevent instantiation of this class. + private CHSupportedGroupsProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!chc.sslConfig.isAvailable(CH_SUPPORTED_GROUPS)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable supported_groups extension"); + } + return null; } - // 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); + + // Produce the extension. + ArrayList namedGroups = + new ArrayList<>(SupportedGroups.supportedNamedGroups.length); + for (NamedGroup ng : SupportedGroups.supportedNamedGroups) { + if ((!SupportedGroups.enableFFDHE) && + (ng.type == NamedGroupType.NAMED_GROUP_FFDHE)) { + continue; + } + + if (ng.isAvailable(chc.activeProtocols) && + ng.isSupported(chc.activeCipherSuites) && + chc.algorithmConstraints.permits( + EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), + ng.algorithm, namedGroupParams.get(ng))) { + namedGroups.add(ng); + } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore inactive or disabled named group: " + ng.name); + } + } + + if (namedGroups.isEmpty()) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning("no available named group"); + } + + return null; + } + + int vectorLen = namedGroups.size() << 1; + byte[] extData = new byte[vectorLen + 2]; + ByteBuffer m = ByteBuffer.wrap(extData); + Record.putInt16(m, vectorLen); + for (NamedGroup namedGroup : namedGroups) { + Record.putInt16(m, namedGroup.id); } + + // Update the context. + chc.clientRequestedNamedGroups = + Collections.unmodifiableList(namedGroups); + chc.handshakeExtensions.put(CH_SUPPORTED_GROUPS, + new SupportedGroupsSpec(namedGroups)); + + return extData; } - sb.append("}"); - return sb.toString(); } - static boolean supports(NamedGroup namedGroup) { - for (NamedGroup group : supportedNamedGroups) { - if (namedGroup.id == group.id) { - return true; + /** + * Network data producer of a "supported_groups" extension in + * the ClientHello handshake message. + */ + private static final + class CHSupportedGroupsConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private CHSupportedGroupsConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + // The comsuming happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!shc.sslConfig.isAvailable(CH_SUPPORTED_GROUPS)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable supported_groups extension"); + } + return; // ignore the extension } - } - return false; - } + // Parse the extension. + SupportedGroupsSpec spec; + try { + spec = new SupportedGroupsSpec(buffer); + } catch (IOException ioe) { + shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); + return; // fatal() always throws, make the compiler happy. + } - static ECGenParameterSpec getECGenParamSpec(NamedGroup namedGroup) { - if (namedGroup.type != NamedGroupType.NAMED_GROUP_ECDHE) { - throw new RuntimeException("Not a named EC group: " + namedGroup); - } + // Update the context. + List knownNamedGroups = new LinkedList<>(); + for (int id : spec.namedGroupsIds) { + NamedGroup ng = NamedGroup.valueOf(id); + if (ng != null) { + knownNamedGroups.add(ng); + } + } + + shc.clientRequestedNamedGroups = knownNamedGroups; + shc.handshakeExtensions.put(CH_SUPPORTED_GROUPS, spec); - AlgorithmParameters params = namedGroupParams.get(namedGroup); - try { - return params.getParameterSpec(ECGenParameterSpec.class); - } catch (InvalidParameterSpecException ipse) { - // should be unlikely - return new ECGenParameterSpec(namedGroup.oid); + // No impact on session resumption. } } - static DHParameterSpec getDHParameterSpec(NamedGroup namedGroup) { - if (namedGroup.type != NamedGroupType.NAMED_GROUP_FFDHE) { - throw new RuntimeException("Not a named DH group: " + namedGroup); + /** + * Network data producer of a "supported_groups" extension in + * the EncryptedExtensions handshake message. + */ + private static final class EESupportedGroupsProducer + extends SupportedGroups implements HandshakeProducer { + + // Prevent instantiation of this class. + private EESupportedGroupsProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!shc.sslConfig.isAvailable(EE_SUPPORTED_GROUPS)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable supported_groups extension"); + } + return null; + } + + // Produce the extension. + // + // Contains all groups the server supports, regardless of whether + // they are currently supported by the client. + ArrayList namedGroups = new ArrayList<>( + SupportedGroups.supportedNamedGroups.length); + for (NamedGroup ng : SupportedGroups.supportedNamedGroups) { + if ((!SupportedGroups.enableFFDHE) && + (ng.type == NamedGroupType.NAMED_GROUP_FFDHE)) { + continue; + } + + if (ng.isAvailable(shc.activeProtocols) && + ng.isSupported(shc.activeCipherSuites) && + shc.algorithmConstraints.permits( + EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), + ng.algorithm, namedGroupParams.get(ng))) { + namedGroups.add(ng); + } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore inactive or disabled named group: " + ng.name); + } + } + + if (namedGroups.isEmpty()) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning("no available named group"); + } + + return null; + } + + int vectorLen = namedGroups.size() << 1; + byte[] extData = new byte[vectorLen + 2]; + ByteBuffer m = ByteBuffer.wrap(extData); + Record.putInt16(m, vectorLen); + for (NamedGroup namedGroup : namedGroups) { + Record.putInt16(m, namedGroup.id); + } + + // Update the context. + shc.conContext.serverRequestedNamedGroups = + Collections.unmodifiableList(namedGroups); + SupportedGroupsSpec spec = new SupportedGroupsSpec(namedGroups); + shc.handshakeExtensions.put(EE_SUPPORTED_GROUPS, spec); + + return extData; } + } + + private static final + class EESupportedGroupsConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private EESupportedGroupsConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + // The comsuming happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!chc.sslConfig.isAvailable(EE_SUPPORTED_GROUPS)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable supported_groups extension"); + } + return; // ignore the extension + } + + // Parse the extension. + SupportedGroupsSpec spec; + try { + spec = new SupportedGroupsSpec(buffer); + } catch (IOException ioe) { + chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); + return; // fatal() always throws, make the compiler happy. + } + + // Update the context. + List knownNamedGroups = + new ArrayList<>(spec.namedGroupsIds.length); + for (int id : spec.namedGroupsIds) { + NamedGroup ng = NamedGroup.valueOf(id); + if (ng != null) { + knownNamedGroups.add(ng); + } + } + + chc.conContext.serverRequestedNamedGroups = knownNamedGroups; + chc.handshakeExtensions.put(EE_SUPPORTED_GROUPS, spec); - AlgorithmParameters params = namedGroupParams.get(namedGroup); - try { - return params.getParameterSpec(DHParameterSpec.class); - } catch (InvalidParameterSpecException ipse) { - // should be unlikely - return getPredefinedDHParameterSpec(namedGroup); + // No impact on session resumption. } } }