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;
final class SupportedGroupsExtension extends HelloExtension {
private static final Debug debug = Debug.getInstance("ssl");
private static final int ARBITRARY_PRIME = 0xff01;
private static final int ARBITRARY_CHAR2 = 0xff02;
private static final Map<NamedGroup,
AlgorithmParameters> namedGroupParams = new HashMap<>();
private static final NamedGroup[] supportedNamedGroups;
private final int[] requestedNamedGroupIds;
static {
boolean requireFips = SunJSSE.isFIPS();
String property = AccessController.doPrivileged(
new GetPropertyAction("jdk.tls.namedGroups"));
if (property != null && property.length() != 0) {
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) {
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);
}
}
}
}
if (groupList.isEmpty() && JsseJce.isEcAvailable()) {
throw new IllegalArgumentException(
"System property jdk.tls.namedGroups(" + property + ") " +
"contains no supported elliptic curves");
}
} else {
NamedGroup[] groups;
if (requireFips) {
groups = new NamedGroup[] {
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,
NamedGroup.FFDHE_2048,
NamedGroup.FFDHE_3072,
NamedGroup.FFDHE_4096,
NamedGroup.FFDHE_6144,
NamedGroup.FFDHE_8192,
};
} else {
groups = new NamedGroup[] {
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,
NamedGroup.SECP256_K1,
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;
}
}
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;
}
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");
}
requestedNamedGroupIds = new int[k >> 1];
for (int i = 0; i < requestedNamedGroupIds.length; i++) {
requestedNamedGroupIds[i] = s.getInt16();
}
}
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;
}
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;
}
}
}
if (!hasFFDHEGroups && (type == NamedGroupType.NAMED_GROUP_FFDHE)) {
return true;
}
return false;
}
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;
}
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) {
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(", ");
}
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) {
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) {
return getPredefinedDHParameterSpec(namedGroup);
}
}
}