src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java
changeset 47216 71c04702a3d5
parent 46188 4586bc5d28d1
child 50768 68fa3d4026ea
child 56542 56aaa6cb3693
--- /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);
+        }
+    }
+}