< prev index next >

src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java

Print this page

        

@@ -1,7 +1,7 @@
 /*
- * 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
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this

@@ -24,49 +24,455 @@
  */
 
 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;
+
+/**
+ * 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;
 
-//
-// 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 {
+        private SupportedGroupsSpec(int[] namedGroupsIds) {
+            this.namedGroupsIds = namedGroupsIds;
+        }
+
+        private SupportedGroupsSpec(List<NamedGroup> namedGroups) {
+            this.namedGroupsIds = new int[namedGroups.size()];
+            int i = 0;
+            for (NamedGroup ng : namedGroups) {
+                namedGroupsIds[i++] = ng.id;
+            }
+        }
+
+        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");
+            }
 
-    /* Class and subclass dynamic debugging support */
-    private static final Debug debug = Debug.getInstance("ssl");
+            if ((ngs == null) || (ngs.length == 0) || (ngs.length % 2 != 0)) {
+                throw new SSLProtocolException(
+                    "Invalid supported_groups extension: incomplete data");
+            }
+
+            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);
 
-    private static final int ARBITRARY_PRIME = 0xff01;
-    private static final int ARBITRARY_CHAR2 = 0xff02;
+            if (namedGroupsIds == null || namedGroupsIds.length == 0) {
+                Object[] messageFields = {
+                        "<no supported named group specified>"
+                    };
+                return messageFormat.format(messageFields);
+            } else {
+                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);
+            }
+        }
+    }
+
+    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();
+            }
+        }
+    }
+
+    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<CipherSuite> cipherSuites) {
+            for (CipherSuite cs : cipherSuites) {
+                if (cs.keyExchange == null || cs.keyExchange.groupType == this) {
+                    return true;
+                }
+            }
+
+            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),
+
+        // 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 null;
+        }
+
+        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;
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        static NamedGroup valueOf(DHParameterSpec params) {
+            for (Map.Entry<NamedGroup, AlgorithmParameters> me :
+                    SupportedGroups.namedGroupParams.entrySet()) {
+                NamedGroup ng = me.getKey();
+                if (ng.type != NamedGroupType.NAMED_GROUP_FFDHE) {
+                    continue;
+                }
+
+                DHParameterSpec ngParams = null;
+                AlgorithmParameters aps = me.getValue();
+                try {
+                    ngParams = aps.getParameterSpec(DHParameterSpec.class);
+                } catch (InvalidParameterSpecException ipse) {
+                    // should be unlikely
+                }
+
+                if (ngParams == null) {
+                    continue;
+                }
+
+                if (ngParams.getP().equals(params.getP()) &&
+                        ngParams.getG().equals(params.getG())) {
+                    return ng;
+                }
+            }
+
+            return null;
+        }
+
+        static NamedGroup nameOf(String name) {
+            for (NamedGroup group : NamedGroup.values()) {
+                if (group.name.equals(name)) {
+                    return group;
+                }
+            }
+
+            return null;
+        }
+
+        static String nameOf(int id) {
+            for (NamedGroup group : NamedGroup.values()) {
+                if (group.id == id) {
+                    return group.name;
+                }
+            }
+
+            return "UNDEFINED-NAMED-GROUP(" + id + ")";
+        }
+
+        boolean isAvailable(List<ProtocolVersion> 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;
+        }
+
+        boolean isSupported(List<CipherSuite> cipherSuites) {
+            for (CipherSuite cs : cipherSuites) {
+                boolean isMatch = isAvailable(cs.supportedProtocols);
+                if (isMatch && (cs.keyExchange == null ||
+                        cs.keyExchange.groupType == type)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // 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;
+        }
+    }
+
+    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
-    private static final Map<NamedGroup,
+        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 final NamedGroup[] supportedNamedGroups;
 
     static {
         boolean requireFips = SunJSSE.isFIPS();
 
         // The value of the System Property defines a list of enabled named

@@ -85,11 +491,11 @@
                 property = property.substring(1, property.length() - 1);
             }
         }
 
         ArrayList<NamedGroup> groupList;
-        if (property != null && property.length() != 0) {   // customized groups
+            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()) {

@@ -101,14 +507,14 @@
                         }
                     }   // ignore unknown groups
                 }
             }
 
-            if (groupList.isEmpty() && JsseJce.isEcAvailable()) {
+                if (groupList.isEmpty()) {
                 throw new IllegalArgumentException(
-                    "System property jdk.tls.namedGroups(" + property + ") " +
-                    "contains no supported elliptic curves");
+                            "System property jdk.tls.namedGroups(" +
+                            property + ") contains no supported named groups");
             }
         } else {        // default groups
             NamedGroup[] groups;
             if (requireFips) {
                 groups = new NamedGroup[] {

@@ -159,17 +565,15 @@
             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]"));
+                if (groupList.isEmpty() &&
+                        SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+                    SSLLogger.warning("No default named groups");
+                }
         }
 
         supportedNamedGroups = new NamedGroup[groupList.size()];
         int i = 0;
         for (NamedGroup namedGroup : groupList) {

@@ -179,32 +583,32 @@
 
     // 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.type == NamedGroupType.NAMED_GROUP_ECDHE) {
             if (namedGroup.oid != null) {
                 try {
                     params = JsseJce.getAlgorithmParameters("EC");
                     spec = new ECGenParameterSpec(namedGroup.oid);
-                } catch (Exception e) {
+                    } catch (NoSuchAlgorithmException e) {
                     return false;
                 }
             }
-        } else if ("DiffieHellman".equals(namedGroup.algorithm)) {
+            } else if (namedGroup.type == NamedGroupType.NAMED_GROUP_FFDHE) {
             try {
                 params = JsseJce.getAlgorithmParameters("DiffieHellman");
                 spec = getFFDHEDHParameterSpec(namedGroup);
-            } catch (Exception e) {
+                } catch (NoSuchAlgorithmException e) {
                 return false;
             }
-        }
+            }   // Otherwise, unsupported.
 
         if ((params != null) && (spec != null)) {
             try {
                 params.init(spec);
-            } catch (Exception e) {
+                } catch (InvalidParameterSpecException e) {
                 return false;
             }
 
             // cache the parameters
             namedGroupParams.put(namedGroup, params);

@@ -259,43 +663,38 @@
         }
 
         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");
+        static ECGenParameterSpec getECGenParamSpec(NamedGroup namedGroup) {
+            if (namedGroup.type != NamedGroupType.NAMED_GROUP_ECDHE) {
+                throw new RuntimeException(
+                        "Not a named EC group: " + namedGroup);
         }
 
-        // 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();
+            AlgorithmParameters params = namedGroupParams.get(namedGroup);
+            try {
+                return params.getParameterSpec(ECGenParameterSpec.class);
+            } catch (InvalidParameterSpecException ipse) {
+                // should be unlikely
+                return new ECGenParameterSpec(namedGroup.oid);
         }
     }
 
-    // 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;
-            }
+        static DHParameterSpec getDHParameterSpec(NamedGroup namedGroup) {
+            if (namedGroup.type != NamedGroupType.NAMED_GROUP_FFDHE) {
+                throw new RuntimeException(
+                        "Not a named DH group: " + namedGroup);
         }
 
-        return null;
+            AlgorithmParameters params = namedGroupParams.get(namedGroup);
+            try {
+                return params.getParameterSpec(DHParameterSpec.class);
+            } catch (InvalidParameterSpecException ipse) {
+                // should be unlikely
+                return getPredefinedDHParameterSpec(namedGroup);
+            }
     }
 
     // Is there any supported group permitted by the constraints?
     static boolean isActivatable(
             AlgorithmConstraints constraints, NamedGroupType type) {

@@ -311,11 +710,10 @@
                     return true;
                 }
 
                 if (!hasFFDHEGroups &&
                         (type == NamedGroupType.NAMED_GROUP_FFDHE)) {
-
                     hasFFDHEGroups = true;
                 }
             }
         }
 

@@ -323,169 +721,317 @@
         // 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 !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;
     }
 
-    // Create the default supported groups extension.
-    static SupportedGroupsExtension createExtension(
-            AlgorithmConstraints constraints,
-            CipherSuiteList cipherSuites, boolean enableFFDHE) {
+            return constraints.permits(
+                            EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
+                            namedGroup.algorithm,
+                            namedGroupParams.get(namedGroup));
+        }
 
-        ArrayList<Integer> groupList =
-                new ArrayList<>(supportedNamedGroups.length);
-        for (NamedGroup namedGroup : supportedNamedGroups) {
-            if ((!enableFFDHE) &&
-                (namedGroup.type == NamedGroupType.NAMED_GROUP_FFDHE)) {
-                continue;
+        // 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;
+                }
             }
 
-            if (cipherSuites.contains(namedGroup.type) &&
-                constraints.permits(EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
-                    namedGroup.algorithm, namedGroupParams.get(namedGroup))) {
+            return false;
+        }
 
-                groupList.add(namedGroup.id);
+        static NamedGroup getPreferredGroup(
+                ProtocolVersion negotiatedProtocol,
+                AlgorithmConstraints constraints, NamedGroupType type,
+                List<NamedGroup> 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;
             }
         }
 
-        if (!groupList.isEmpty()) {
-            int[] ids = new int[groupList.size()];
-            int i = 0;
-            for (Integer id : groupList) {
-                ids[i++] = id;
+            return null;
             }
 
-            return new SupportedGroupsExtension(ids);
+        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;
+                }
         }
 
         return null;
     }
+    }
 
-    // get the preferred activated named group
-    NamedGroup getPreferredGroup(
-            AlgorithmConstraints constraints, NamedGroupType type) {
+    /**
+     * 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
+        }
 
-        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))) {
+        @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;
+            }
 
-                return namedGroup;
+            // Produce the extension.
+            ArrayList<NamedGroup> 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;
     }
 
-    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.
+            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.<NamedGroup>unmodifiableList(namedGroups);
+            chc.handshakeExtensions.put(CH_SUPPORTED_GROUPS,
+                    new SupportedGroupsSpec(namedGroups));
+
+            return extData;
+        }
+    }
+
+    /**
+     * Network data producer of a "supported_groups" extension in
+     * the ClientHello handshake message.
              */
-            if ((groupId >= 256) && (groupId <= 511)) {
-                return true;
+    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.
     }
 
-    boolean contains(int index) {
-        for (int groupId : requestedNamedGroupIds) {
-            if (index == groupId) {
-                return true;
+            // Update the context.
+            List<NamedGroup> knownNamedGroups = new LinkedList<>();
+            for (int id : spec.namedGroupsIds) {
+                NamedGroup ng = NamedGroup.valueOf(id);
+                if (ng != null) {
+                    knownNamedGroups.add(ng);
             }
         }
-        return false;
+
+            shc.clientRequestedNamedGroups = knownNamedGroups;
+            shc.handshakeExtensions.put(CH_SUPPORTED_GROUPS, spec);
+
+            // No impact on session resumption.
+        }
     }
 
-    @Override
-    int length() {
-        return 6 + (requestedNamedGroupIds.length << 1);
+    /**
+     * 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
-    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);
+        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;
     }
 
-    @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(", ");
+            // Produce the extension.
+            //
+            // Contains all groups the server supports, regardless of whether
+            // they are currently supported by the client.
+            ArrayList<NamedGroup> namedGroups = new ArrayList<>(
+                    SupportedGroups.supportedNamedGroups.length);
+            for (NamedGroup ng : SupportedGroups.supportedNamedGroups) {
+                if ((!SupportedGroups.enableFFDHE) &&
+                    (ng.type == NamedGroupType.NAMED_GROUP_FFDHE)) {
+                    continue;
             }
-            // 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);
+
+                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);
             }
         }
-        sb.append("}");
-        return sb.toString();
+
+            if (namedGroups.isEmpty()) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.warning("no available named group");
     }
 
-    static boolean supports(NamedGroup namedGroup) {
-        for (NamedGroup group : supportedNamedGroups) {
-            if (namedGroup.id == group.id) {
-                return true;
+                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);
         }
 
-        return false;
+            // Update the context.
+            shc.conContext.serverRequestedNamedGroups =
+                    Collections.<NamedGroup>unmodifiableList(namedGroups);
+            SupportedGroupsSpec spec = new SupportedGroupsSpec(namedGroups);
+            shc.handshakeExtensions.put(EE_SUPPORTED_GROUPS, spec);
+
+            return extData;
+        }
     }
 
-    static ECGenParameterSpec getECGenParamSpec(NamedGroup namedGroup) {
-        if (namedGroup.type != NamedGroupType.NAMED_GROUP_ECDHE) {
-            throw new RuntimeException("Not a named EC group: " + namedGroup);
+    private static final
+            class EESupportedGroupsConsumer implements ExtensionConsumer {
+        // Prevent instantiation of this class.
+        private EESupportedGroupsConsumer() {
+            // blank
         }
 
-        AlgorithmParameters params = namedGroupParams.get(namedGroup);
-        try {
-            return params.getParameterSpec(ECGenParameterSpec.class);
-        } catch (InvalidParameterSpecException ipse) {
-            // should be unlikely
-            return new ECGenParameterSpec(namedGroup.oid);
+        @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
     }
 
-    static DHParameterSpec getDHParameterSpec(NamedGroup namedGroup) {
-        if (namedGroup.type != NamedGroupType.NAMED_GROUP_FFDHE) {
-            throw new RuntimeException("Not a named DH group: " + namedGroup);
+            // 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.
         }
 
-        AlgorithmParameters params = namedGroupParams.get(namedGroup);
-        try {
-            return params.getParameterSpec(DHParameterSpec.class);
-        } catch (InvalidParameterSpecException ipse) {
-            // should be unlikely
-            return getPredefinedDHParameterSpec(namedGroup);
+            // Update the context.
+            List<NamedGroup> 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);
+
+            // No impact on session resumption.
         }
     }
 }
< prev index next >