org.bouncycastle.jsse.provider.NamedGroupInfo Maven / Gradle / Ivy
The newest version!
package org.bouncycastle.jsse.provider;
import java.security.AlgorithmParameters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
import org.bouncycastle.jsse.java.security.BCAlgorithmConstraints;
import org.bouncycastle.jsse.java.security.BCCryptoPrimitive;
import org.bouncycastle.tls.NamedGroup;
import org.bouncycastle.tls.ProtocolVersion;
import org.bouncycastle.tls.TlsUtils;
import org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCrypto;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Integers;
import org.bouncycastle.util.Properties;
class NamedGroupInfo
{
private static final Logger LOG = Logger.getLogger(NamedGroupInfo.class.getName());
private static final String PROPERTY_NAMED_GROUPS = "jdk.tls.namedGroups";
// NOTE: Not all of these are necessarily enabled/supported; it will be checked at runtime
private enum All
{
sect163k1(NamedGroup.sect163k1, "EC"),
sect163r1(NamedGroup.sect163r1, "EC"),
sect163r2(NamedGroup.sect163r2, "EC"),
sect193r1(NamedGroup.sect193r1, "EC"),
sect193r2(NamedGroup.sect193r2, "EC"),
sect233k1(NamedGroup.sect233k1, "EC"),
sect233r1(NamedGroup.sect233r1, "EC"),
sect239k1(NamedGroup.sect239k1, "EC"),
sect283k1(NamedGroup.sect283k1, "EC"),
sect283r1(NamedGroup.sect283r1, "EC"),
sect409k1(NamedGroup.sect409k1, "EC"),
sect409r1(NamedGroup.sect409r1, "EC"),
sect571k1(NamedGroup.sect571k1, "EC"),
sect571r1(NamedGroup.sect571r1, "EC"),
secp160k1(NamedGroup.secp160k1, "EC"),
secp160r1(NamedGroup.secp160r1, "EC"),
secp160r2(NamedGroup.secp160r2, "EC"),
secp192k1(NamedGroup.secp192k1, "EC"),
secp192r1(NamedGroup.secp192r1, "EC"),
secp224k1(NamedGroup.secp224k1, "EC"),
secp224r1(NamedGroup.secp224r1, "EC"),
secp256k1(NamedGroup.secp256k1, "EC"),
secp256r1(NamedGroup.secp256r1, "EC"),
secp384r1(NamedGroup.secp384r1, "EC"),
secp521r1(NamedGroup.secp521r1, "EC"),
brainpoolP256r1(NamedGroup.brainpoolP256r1, "EC"),
brainpoolP384r1(NamedGroup.brainpoolP384r1, "EC"),
brainpoolP512r1(NamedGroup.brainpoolP512r1, "EC"),
x25519(NamedGroup.x25519, "XDH"),
x448(NamedGroup.x448, "XDH"),
brainpoolP256r1tls13(NamedGroup.brainpoolP256r1tls13, "EC"),
brainpoolP384r1tls13(NamedGroup.brainpoolP384r1tls13, "EC"),
brainpoolP512r1tls13(NamedGroup.brainpoolP512r1tls13, "EC"),
curveSM2(NamedGroup.curveSM2, "EC"),
ffdhe2048(NamedGroup.ffdhe2048, "DiffieHellman"),
ffdhe3072(NamedGroup.ffdhe3072, "DiffieHellman"),
ffdhe4096(NamedGroup.ffdhe4096, "DiffieHellman"),
ffdhe6144(NamedGroup.ffdhe6144, "DiffieHellman"),
ffdhe8192(NamedGroup.ffdhe8192, "DiffieHellman"),
OQS_mlkem512(NamedGroup.OQS_mlkem512, "ML-KEM"),
OQS_mlkem768(NamedGroup.OQS_mlkem768, "ML-KEM"),
OQS_mlkem1024(NamedGroup.OQS_mlkem1024, "ML-KEM"),
DRAFT_mlkem768(NamedGroup.DRAFT_mlkem768, "ML-KEM"),
DRAFT_mlkem1024(NamedGroup.DRAFT_mlkem1024, "ML-KEM");
private final int namedGroup;
private final String name;
private final String text;
private final String jcaAlgorithm;
private final String jcaGroup;
private final boolean char2;
private final boolean supportedPost13;
private final boolean supportedPre13;
private final int bitsECDH;
private final int bitsFFDHE;
private All(int namedGroup, String jcaAlgorithm)
{
this.namedGroup = namedGroup;
this.name = NamedGroup.getName(namedGroup);
this.text = NamedGroup.getText(namedGroup);
this.jcaAlgorithm = jcaAlgorithm;
this.jcaGroup = NamedGroup.getStandardName(namedGroup);
this.supportedPost13 = NamedGroup.canBeNegotiated(namedGroup, ProtocolVersion.TLSv13);
this.supportedPre13 = NamedGroup.canBeNegotiated(namedGroup, ProtocolVersion.TLSv12);
this.char2 = NamedGroup.isChar2Curve(namedGroup);
this.bitsECDH = NamedGroup.getCurveBits(namedGroup);
this.bitsFFDHE = NamedGroup.getFiniteFieldBits(namedGroup);
}
}
private static final int[] CANDIDATES_DEFAULT = {
NamedGroup.x25519,
NamedGroup.x448,
NamedGroup.secp256r1,
NamedGroup.secp384r1,
NamedGroup.secp521r1,
NamedGroup.brainpoolP256r1tls13,
NamedGroup.brainpoolP384r1tls13,
NamedGroup.brainpoolP512r1tls13,
NamedGroup.ffdhe2048,
NamedGroup.ffdhe3072,
NamedGroup.ffdhe4096,
};
static class PerConnection
{
private final LinkedHashMap local;
private final boolean localECDSA;
private final AtomicReference> peer;
PerConnection(LinkedHashMap local, boolean localECDSA)
{
if (local == null)
{
throw new NullPointerException("local");
}
this.local = local;
this.localECDSA = localECDSA;
this.peer = new AtomicReference>();
}
List getPeer()
{
return peer.get();
}
void notifyPeerData(int[] namedGroups)
{
// TODO[jsse] Is there any reason to preserve the unrecognized/disabled groups?
List namedGroupInfos = getNamedGroupInfos(local, namedGroups);
peer.set(namedGroupInfos);
}
}
static class PerContext
{
private final Map index;
private final int[] candidates;
PerContext(Map index, int[] candidates)
{
this.index = index;
this.candidates = candidates;
}
}
static class DefaultedResult
{
private final int result;
private final boolean defaulted;
DefaultedResult(int result, boolean defaulted)
{
this.result = result;
this.defaulted = defaulted;
}
int getResult()
{
return result;
}
boolean isDefaulted()
{
return defaulted;
}
}
static PerConnection createPerConnectionClient(PerContext perContext, ProvSSLParameters sslParameters,
ProtocolVersion[] activeProtocolVersions)
{
ProtocolVersion latest = ProtocolVersion.getLatestTLS(activeProtocolVersions);
ProtocolVersion earliest = ProtocolVersion.getEarliestTLS(activeProtocolVersions);
return createPerConnection(perContext, sslParameters, earliest, latest);
}
static PerConnection createPerConnectionServer(PerContext perContext, ProvSSLParameters sslParameters,
ProtocolVersion negotiatedVersion)
{
return createPerConnection(perContext, sslParameters, negotiatedVersion, negotiatedVersion);
}
private static PerConnection createPerConnection(PerContext perContext, ProvSSLParameters sslParameters,
ProtocolVersion earliest, ProtocolVersion latest)
{
String[] namedGroups = sslParameters.getNamedGroups();
int[] candidates;
if (namedGroups == null)
{
candidates = perContext.candidates;
}
else
{
candidates = createCandidates(perContext.index, namedGroups, "SSLParameters.namedGroups");
}
BCAlgorithmConstraints algorithmConstraints = sslParameters.getAlgorithmConstraints();
boolean post13Active = TlsUtils.isTLSv13(latest);
boolean pre13Active = !TlsUtils.isTLSv13(earliest);
int count = candidates.length;
LinkedHashMap local = new LinkedHashMap(count);
for (int i = 0; i < count; ++i)
{
Integer candidate = Integers.valueOf(candidates[i]);
NamedGroupInfo namedGroupInfo = perContext.index.get(candidate);
if (null != namedGroupInfo
&& namedGroupInfo.isActive(algorithmConstraints, post13Active, pre13Active))
{
// NOTE: Re-insertion doesn't affect iteration order for insertion-order LinkedHashMap
local.put(candidate, namedGroupInfo);
}
}
boolean localECDSA = hasAnyECDSA(local);
return new PerConnection(local, localECDSA);
}
static PerContext createPerContext(boolean isFipsContext, JcaTlsCrypto crypto)
{
Map index = createIndex(isFipsContext, crypto);
int[] candidates = createCandidatesFromProperty(index, PROPERTY_NAMED_GROUPS);
return new PerContext(index, candidates);
}
static DefaultedResult getMaximumBitsServerECDH(PerConnection perConnection)
{
int maxBits = 0;
List peer = perConnection.getPeer();
if (peer != null)
{
for (NamedGroupInfo namedGroupInfo : peer)
{
int bits = namedGroupInfo.getBitsECDH();
if (bits > maxBits)
{
if (perConnection.local.containsKey(namedGroupInfo.getNamedGroup()))
{
maxBits = bits;
}
}
}
}
else
{
/*
* RFC 4492 4. A client that proposes ECC cipher suites may choose not to include these
* extensions. In this case, the server is free to choose any one of the elliptic curves or point
* formats [...].
*/
for (NamedGroupInfo namedGroupInfo : perConnection.local.values())
{
maxBits = Math.max(maxBits, namedGroupInfo.getBitsECDH());
}
}
return new DefaultedResult(maxBits, peer == null);
}
static DefaultedResult getMaximumBitsServerFFDHE(PerConnection perConnection)
{
int maxBits = 0;
boolean anyPeerFF = false;
List peer = perConnection.getPeer();
if (peer != null)
{
for (NamedGroupInfo namedGroupInfo : peer)
{
int namedGroup = namedGroupInfo.getNamedGroup();
anyPeerFF |= NamedGroup.isFiniteField(namedGroup);
int bits = namedGroupInfo.getBitsFFDHE();
if (bits > maxBits)
{
if (perConnection.local.containsKey(namedGroup))
{
maxBits = bits;
}
}
}
}
if (!anyPeerFF)
{
/*
* RFC 7919 4. If [...] the Supported Groups extension is either absent from the ClientHello
* entirely or contains no FFDHE groups (i.e., no codepoints between 256 and 511, inclusive), then
* the server [...] MAY select an FFDHE cipher suite and offer an FFDHE group of its choice [...].
*/
for (NamedGroupInfo namedGroupInfo : perConnection.local.values())
{
maxBits = Math.max(maxBits, namedGroupInfo.getBitsFFDHE());
}
}
return new DefaultedResult(maxBits, !anyPeerFF);
}
static NamedGroupInfo getNamedGroup(PerContext perContext, int namedGroup)
{
return perContext.index.get(namedGroup);
}
static Vector getSupportedGroupsLocalClient(PerConnection perConnection)
{
return new Vector(perConnection.local.keySet());
}
static int[] getSupportedGroupsLocalServer(PerConnection perConnection)
{
Set keys = perConnection.local.keySet();
int count = keys.size(), pos = 0;
int[] result = new int[count];
for (Integer key : keys)
{
result[pos++] = key.intValue();
}
return result;
}
static boolean hasAnyECDSALocal(PerConnection perConnection)
{
return perConnection.localECDSA;
}
static boolean hasLocal(PerConnection perConnection, int namedGroup)
{
return perConnection.local.containsKey(namedGroup);
}
static DefaultedResult selectServerECDH(PerConnection perConnection, int minimumBitsECDH)
{
List peer = perConnection.getPeer();
if (peer != null)
{
for (NamedGroupInfo namedGroupInfo : peer)
{
if (namedGroupInfo.getBitsECDH() >= minimumBitsECDH)
{
int namedGroup = namedGroupInfo.getNamedGroup();
if (perConnection.local.containsKey(namedGroup))
{
return new DefaultedResult(namedGroup, false);
}
}
}
}
else
{
/*
* RFC 4492 4. A client that proposes ECC cipher suites may choose not to include these
* extensions. In this case, the server is free to choose any one of the elliptic curves or point
* formats [...].
*/
for (NamedGroupInfo namedGroupInfo : perConnection.local.values())
{
if (namedGroupInfo.getBitsECDH() >= minimumBitsECDH)
{
return new DefaultedResult(namedGroupInfo.getNamedGroup(), true);
}
}
}
return new DefaultedResult(-1, peer == null);
}
static DefaultedResult selectServerFFDHE(PerConnection perConnection, int minimumBitsFFDHE)
{
boolean anyPeerFF = false;
List peer = perConnection.getPeer();
if (peer != null)
{
for (NamedGroupInfo namedGroupInfo : peer)
{
int namedGroup = namedGroupInfo.getNamedGroup();
anyPeerFF |= NamedGroup.isFiniteField(namedGroup);
if (namedGroupInfo.getBitsFFDHE() >= minimumBitsFFDHE)
{
if (perConnection.local.containsKey(namedGroup))
{
return new DefaultedResult(namedGroup, false);
}
}
}
}
if (!anyPeerFF)
{
/*
* RFC 7919 4. If [...] the Supported Groups extension is either absent from the ClientHello
* entirely or contains no FFDHE groups (i.e., no codepoints between 256 and 511, inclusive), then
* the server [...] MAY select an FFDHE cipher suite and offer an FFDHE group of its choice [...].
*/
for (NamedGroupInfo namedGroupInfo : perConnection.local.values())
{
if (namedGroupInfo.getBitsFFDHE() >= minimumBitsFFDHE)
{
return new DefaultedResult(namedGroupInfo.getNamedGroup(), true);
}
}
}
return new DefaultedResult(-1, !anyPeerFF);
}
private static void addNamedGroup(boolean isFipsContext, JcaTlsCrypto crypto, boolean disableChar2,
boolean disableFFDHE, Map ng, All all)
{
final int namedGroup = all.namedGroup;
if (isFipsContext && !FipsUtils.isFipsNamedGroup(namedGroup))
{
// In FIPS mode, non-FIPS groups are currently not even entered into the map
return;
}
boolean disable = (disableChar2 && all.char2) || (disableFFDHE && all.bitsFFDHE > 0);
boolean enabled = !disable && (null != all.jcaGroup) && crypto.hasNamedGroup(namedGroup);
AlgorithmParameters algorithmParameters = null;
if (enabled)
{
// TODO[jsse] Consider also fetching 'jcaAlgorithm'
try
{
algorithmParameters = crypto.getNamedGroupAlgorithmParameters(namedGroup);
}
catch (Exception e)
{
enabled = false;
}
}
NamedGroupInfo namedGroupInfo = new NamedGroupInfo(all, algorithmParameters, enabled);
if (null != ng.put(namedGroup, namedGroupInfo))
{
throw new IllegalStateException("Duplicate entries for NamedGroupInfo");
}
}
private static int[] createCandidatesFromProperty(Map index, String propertyName)
{
String[] names = PropertyUtils.getStringArraySystemProperty(propertyName);
if (null == names)
{
return CANDIDATES_DEFAULT;
}
return createCandidates(index, names, propertyName);
}
private static int[] createCandidates(Map index, String[] names, String description)
{
int[] result = new int[names.length];
int count = 0;
for (String name : names)
{
int namedGroup = getNamedGroupByName(name);
if (namedGroup < 0)
{
LOG.warning("'" + description + "' contains unrecognised NamedGroup: " + name);
continue;
}
NamedGroupInfo namedGroupInfo = index.get(namedGroup);
if (null == namedGroupInfo)
{
LOG.warning("'" + description + "' contains unsupported NamedGroup: " + name);
continue;
}
if (!namedGroupInfo.isEnabled())
{
LOG.warning("'" + description + "' contains disabled NamedGroup: " + name);
continue;
}
result[count++] = namedGroup;
}
if (count < result.length)
{
result = Arrays.copyOf(result, count);
}
if (result.length < 1)
{
LOG.severe("'" + description + "' contained no usable NamedGroup values");
}
return result;
}
private static Map createIndex(boolean isFipsContext, JcaTlsCrypto crypto)
{
Map ng = new TreeMap();
final boolean disableChar2 =
PropertyUtils.getBooleanSystemProperty("org.bouncycastle.jsse.ec.disableChar2", false) ||
Properties.isOverrideSet("org.bouncycastle.ec.disable_f2m");
final boolean disableFFDHE = !PropertyUtils.getBooleanSystemProperty("jsse.enableFFDHE", true);
for (All all : All.values())
{
addNamedGroup(isFipsContext, crypto, disableChar2, disableFFDHE, ng, all);
}
return ng;
}
private static int getNamedGroupByName(String name)
{
for (All all : All.values())
{
if (all.name.equalsIgnoreCase(name))
{
return all.namedGroup;
}
}
return -1;
}
private static List getNamedGroupInfos(Map namedGroupInfos,
int[] namedGroups)
{
if (namedGroups == null)
{
return null;
}
if (namedGroups.length < 1)
{
return Collections.emptyList();
}
int count = namedGroups.length;
ArrayList result = new ArrayList(count);
for (int i = 0; i < count; ++i)
{
int namedGroup = namedGroups[i];
NamedGroupInfo namedGroupInfo = namedGroupInfos.get(namedGroup);
if (null != namedGroupInfo)
{
result.add(namedGroupInfo);
}
}
if (result.isEmpty())
{
return Collections.emptyList();
}
result.trimToSize();
return result;
}
private static boolean hasAnyECDSA(Map local)
{
for (NamedGroupInfo namedGroupInfo : local.values())
{
if (NamedGroup.refersToAnECDSACurve(namedGroupInfo.getNamedGroup()))
{
return true;
}
}
return false;
}
private final All all;
private final AlgorithmParameters algorithmParameters;
private final boolean enabled;
NamedGroupInfo(All all, AlgorithmParameters algorithmParameters, boolean enabled)
{
this.all = all;
this.algorithmParameters = algorithmParameters;
this.enabled = enabled;
}
int getBitsECDH()
{
return all.bitsECDH;
}
int getBitsFFDHE()
{
return all.bitsFFDHE;
}
String getJcaAlgorithm()
{
return all.jcaAlgorithm;
}
String getJcaGroup()
{
return all.jcaGroup;
}
int getNamedGroup()
{
return all.namedGroup;
}
boolean isActive(BCAlgorithmConstraints algorithmConstraints, boolean post13Active, boolean pre13Active)
{
return enabled
&& ((post13Active && isSupportedPost13()) || (pre13Active && isSupportedPre13()))
&& isPermittedBy(algorithmConstraints);
}
boolean isEnabled()
{
return enabled;
}
boolean isSupportedPost13()
{
return all.supportedPost13;
}
boolean isSupportedPre13()
{
return all.supportedPre13;
}
@Override
public String toString()
{
return all.text;
}
private boolean isPermittedBy(BCAlgorithmConstraints algorithmConstraints)
{
Set primitives = JsseUtils.KEY_AGREEMENT_CRYPTO_PRIMITIVES_BC;
return algorithmConstraints.permits(primitives, getJcaGroup(), null)
&& algorithmConstraints.permits(primitives, getJcaAlgorithm(), algorithmParameters);
}
}