| /* |
| * 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); |
| } |
| } |
| } |