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