org.bouncycastle.jsse.provider.ProvX509KeyManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of impersonator Show documentation
Show all versions of impersonator Show documentation
Spoof TLS/JA3/JA4 and HTTP/2 fingerprints in Java
package org.bouncycastle.jsse.provider;
import java.lang.ref.SoftReference;
import java.net.Socket;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStore.ProtectionParameter;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPublicKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLEngine;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.jcajce.util.JcaJceHelper;
import org.bouncycastle.jsse.BCExtendedSSLSession;
import org.bouncycastle.jsse.BCSNIHostName;
import org.bouncycastle.jsse.BCX509ExtendedKeyManager;
import org.bouncycastle.jsse.BCX509Key;
import org.bouncycastle.jsse.java.security.BCAlgorithmConstraints;
import org.bouncycastle.tls.KeyExchangeAlgorithm;
import org.bouncycastle.tls.NamedGroup;
import org.bouncycastle.tls.ProtocolVersion;
import org.bouncycastle.tls.TlsUtils;
class ProvX509KeyManager
extends BCX509ExtendedKeyManager
{
private static final Logger LOG = Logger.getLogger(ProvX509KeyManager.class.getName());
private static final boolean provKeyManagerCheckEKU = PropertyUtils
.getBooleanSystemProperty("org.bouncycastle.jsse.keyManager.checkEKU", true);
private final AtomicLong versions = new AtomicLong();
private final boolean isInFipsMode;
private final JcaJceHelper helper;
private final List builders;
@SuppressWarnings("serial")
private final Map> cachedEntries = Collections.synchronizedMap(
new LinkedHashMap>(16, 0.75f, true)
{
protected boolean removeEldestEntry(Map.Entry> eldest)
{
return size() > 16;
}
});
private static final Map FILTERS_CLIENT = createFiltersClient();
private static final Map FILTERS_SERVER = createFiltersServer();
private static void addECFilter13(Map filters, int namedGroup13)
{
if (!NamedGroup.canBeNegotiated(namedGroup13, ProtocolVersion.TLSv13))
{
throw new IllegalStateException("Invalid named group for TLS 1.3 EC filter");
}
String curveName = NamedGroup.getCurveName(namedGroup13);
if (null != curveName)
{
ASN1ObjectIdentifier standardOID = ECNamedCurveTable.getOID(curveName);
if (null != standardOID)
{
String keyType = JsseUtils.getKeyType13("EC", namedGroup13);
PublicKeyFilter filter = new ECPublicKeyFilter13(standardOID);
addFilterToMap(filters, keyType, filter);
return;
}
}
LOG.warning("Failed to register public key filter for EC with " + NamedGroup.getText(namedGroup13));
}
private static void addFilter(Map filters, String keyType)
{
String algorithm = keyType;
addFilter(filters, ProvAlgorithmChecker.KU_DIGITAL_SIGNATURE, algorithm, null, keyType);
}
private static void addFilter(Map filters, Class extends PublicKey> clazz, String... keyTypes)
{
addFilter(filters, ProvAlgorithmChecker.KU_DIGITAL_SIGNATURE, null, clazz, keyTypes);
}
private static void addFilter(Map filters, int keyUsageBit, String algorithm,
Class extends PublicKey> clazz, String... keyTypes)
{
PublicKeyFilter filter = new DefaultPublicKeyFilter(algorithm, clazz, keyUsageBit);
for (String keyType : keyTypes)
{
addFilterToMap(filters, keyType, filter);
}
}
private static void addFilterLegacyServer(Map filters, String algorithm,
int... keyExchangeAlgorithms)
{
addFilterLegacyServer(filters, ProvAlgorithmChecker.KU_DIGITAL_SIGNATURE, algorithm, keyExchangeAlgorithms);
}
private static void addFilterLegacyServer(Map filters, int keyUsageBit, String algorithm,
int... keyExchangeAlgorithms)
{
addFilterLegacyServer(filters, keyUsageBit, algorithm, null, keyExchangeAlgorithms);
}
private static void addFilterLegacyServer(Map filters, Class extends PublicKey> clazz,
int... keyExchangeAlgorithms)
{
addFilterLegacyServer(filters, ProvAlgorithmChecker.KU_DIGITAL_SIGNATURE, null, clazz, keyExchangeAlgorithms);
}
private static void addFilterLegacyServer(Map filters, int keyUsageBit, String algorithm,
Class extends PublicKey> clazz, int... keyExchangeAlgorithms)
{
addFilter(filters, keyUsageBit, algorithm, clazz, getKeyTypesLegacyServer(keyExchangeAlgorithms));
}
private static void addFilterToMap(Map filters, String keyType, PublicKeyFilter filter)
{
if (null != filters.put(keyType, filter))
{
throw new IllegalStateException("Duplicate keys in filters");
}
}
private static Map createFiltersClient()
{
Map filters = new HashMap();
addFilter(filters, "Ed25519");
addFilter(filters, "Ed448");
addECFilter13(filters, NamedGroup.brainpoolP256r1tls13);
addECFilter13(filters, NamedGroup.brainpoolP384r1tls13);
addECFilter13(filters, NamedGroup.brainpoolP512r1tls13);
addECFilter13(filters, NamedGroup.secp256r1);
addECFilter13(filters, NamedGroup.secp384r1);
addECFilter13(filters, NamedGroup.secp521r1);
// TODO Perhaps check the public key OID explicitly for these
addFilter(filters, "RSA");
addFilter(filters, "RSASSA-PSS");
addFilter(filters, DSAPublicKey.class, "DSA");
addFilter(filters, ECPublicKey.class, "EC");
return Collections.unmodifiableMap(filters);
}
private static Map createFiltersServer()
{
Map filters = new HashMap();
addFilter(filters, "Ed25519");
addFilter(filters, "Ed448");
addECFilter13(filters, NamedGroup.brainpoolP256r1tls13);
addECFilter13(filters, NamedGroup.brainpoolP384r1tls13);
addECFilter13(filters, NamedGroup.brainpoolP512r1tls13);
addECFilter13(filters, NamedGroup.secp256r1);
addECFilter13(filters, NamedGroup.secp384r1);
addECFilter13(filters, NamedGroup.secp521r1);
// TODO Perhaps check the public key OID explicitly for these
addFilter(filters, "RSA");
addFilter(filters, "RSASSA-PSS");
addFilterLegacyServer(filters, DSAPublicKey.class, KeyExchangeAlgorithm.DHE_DSS, KeyExchangeAlgorithm.SRP_DSS);
addFilterLegacyServer(filters, ECPublicKey.class, KeyExchangeAlgorithm.ECDHE_ECDSA);
addFilterLegacyServer(filters, "RSA", KeyExchangeAlgorithm.DHE_RSA, KeyExchangeAlgorithm.ECDHE_RSA,
KeyExchangeAlgorithm.SRP_RSA);
addFilterLegacyServer(filters, ProvAlgorithmChecker.KU_KEY_ENCIPHERMENT, "RSA", KeyExchangeAlgorithm.RSA);
return Collections.unmodifiableMap(filters);
}
private static String[] getKeyTypesLegacyServer(int... keyExchangeAlgorithms)
{
int count = keyExchangeAlgorithms.length;
String[] keyTypes = new String[count];
for (int i = 0; i < count; ++i)
{
keyTypes[i] = JsseUtils.getKeyTypeLegacyServer(keyExchangeAlgorithms[i]);
}
return keyTypes;
}
ProvX509KeyManager(boolean isInFipsMode, JcaJceHelper helper, List builders)
{
this.isInFipsMode = isInFipsMode;
this.helper = helper;
this.builders = builders;
}
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket)
{
return chooseAlias(getKeyTypes(keyTypes), issuers, TransportData.from(socket), false);
}
@Override
public BCX509Key chooseClientKeyBC(String[] keyTypes, Principal[] issuers, Socket socket)
{
return chooseKeyBC(getKeyTypes(keyTypes), issuers, TransportData.from(socket), false);
}
public String chooseEngineClientAlias(String[] keyTypes, Principal[] issuers, SSLEngine engine)
{
return chooseAlias(getKeyTypes(keyTypes), issuers, TransportData.from(engine), false);
}
@Override
public BCX509Key chooseEngineClientKeyBC(String[] keyTypes, Principal[] issuers, SSLEngine engine)
{
return chooseKeyBC(getKeyTypes(keyTypes), issuers, TransportData.from(engine), false);
}
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)
{
return chooseAlias(getKeyTypes(keyType), issuers, TransportData.from(engine), true);
}
@Override
public BCX509Key chooseEngineServerKeyBC(String[] keyTypes, Principal[] issuers, SSLEngine engine)
{
return chooseKeyBC(getKeyTypes(keyTypes), issuers, TransportData.from(engine), true);
}
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
{
return chooseAlias(getKeyTypes(keyType), issuers, TransportData.from(socket), true);
}
@Override
public BCX509Key chooseServerKeyBC(String[] keyTypes, Principal[] issuers, Socket socket)
{
return chooseKeyBC(getKeyTypes(keyTypes), issuers, TransportData.from(socket), true);
}
public X509Certificate[] getCertificateChain(String alias)
{
KeyStore.PrivateKeyEntry entry = getPrivateKeyEntry(alias);
return null == entry ? null : (X509Certificate[])entry.getCertificateChain();
}
public String[] getClientAliases(String keyType, Principal[] issuers)
{
return getAliases(getKeyTypes(keyType), issuers, null, false);
}
public PrivateKey getPrivateKey(String alias)
{
KeyStore.PrivateKeyEntry entry = getPrivateKeyEntry(alias);
return null == entry ? null : entry.getPrivateKey();
}
public String[] getServerAliases(String keyType, Principal[] issuers)
{
return getAliases(getKeyTypes(keyType), issuers, null, true);
}
@Override
protected BCX509Key getKeyBC(String keyType, String alias)
{
KeyStore.PrivateKeyEntry entry = getPrivateKeyEntry(alias);
if (null == entry)
{
return null;
}
PrivateKey privateKey = entry.getPrivateKey();
if (null == privateKey)
{
return null;
}
X509Certificate[] certificateChain = JsseUtils.getX509CertificateChain(entry.getCertificateChain());
if (TlsUtils.isNullOrEmpty(certificateChain))
{
return null;
}
return new ProvX509Key(keyType, privateKey, certificateChain);
}
private String chooseAlias(List keyTypes, Principal[] issuers, TransportData transportData,
boolean forServer)
{
Match bestMatch = getBestMatch(keyTypes, issuers, transportData, forServer);
if (bestMatch.compareTo(Match.NOTHING) < 0)
{
String keyType = keyTypes.get(bestMatch.keyTypeIndex);
String alias = getAlias(bestMatch, getNextVersionSuffix());
if (LOG.isLoggable(Level.FINE))
{
LOG.fine("Found matching key of type: " + keyType + ", returning alias: " + alias);
}
return alias;
}
LOG.fine("No matching key found");
return null;
}
private BCX509Key chooseKeyBC(List keyTypes, Principal[] issuers, TransportData transportData,
boolean forServer)
{
Match bestMatch = getBestMatch(keyTypes, issuers, transportData, forServer);
if (bestMatch.compareTo(Match.NOTHING) < 0)
{
try
{
String keyType = keyTypes.get(bestMatch.keyTypeIndex);
BCX509Key keyBC = createKeyBC(keyType, bestMatch.builderIndex, bestMatch.localAlias,
bestMatch.cachedKeyStore, bestMatch.cachedCertificateChain);
if (null != keyBC)
{
if (LOG.isLoggable(Level.FINE))
{
LOG.fine("Found matching key of type: " + keyType + ", from alias: " + bestMatch.builderIndex
+ "." + bestMatch.localAlias);
}
return keyBC;
}
}
catch (Exception e)
{
LOG.log(Level.FINER, "Failed to load private key", e);
}
}
LOG.fine("No matching key found");
return null;
}
private BCX509Key createKeyBC(String keyType, int builderIndex, String localAlias, KeyStore keyStore,
X509Certificate[] certificateChain)
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException
{
KeyStore.Builder builder = builders.get(builderIndex);
ProtectionParameter protectionParameter = builder.getProtectionParameter(localAlias);
Key key = KeyStoreUtil.getKey(keyStore, localAlias, protectionParameter);
if (key instanceof PrivateKey)
{
return new ProvX509Key(keyType, (PrivateKey)key, certificateChain);
}
return null;
}
private String[] getAliases(List keyTypes, Principal[] issuers, TransportData transportData,
boolean forServer)
{
if (!builders.isEmpty() && !keyTypes.isEmpty())
{
int keyTypeLimit = keyTypes.size();
Set uniqueIssuers = getUniquePrincipals(issuers);
BCAlgorithmConstraints algorithmConstraints = TransportData.getAlgorithmConstraints(transportData, true);
Date atDate = new Date();
String requestedHostName = getRequestedHostName(transportData, forServer);
List matches = null;
for (int builderIndex = 0, count = builders.size(); builderIndex < count; ++builderIndex)
{
try
{
KeyStore.Builder builder = builders.get(builderIndex);
KeyStore keyStore = builder.getKeyStore();
if (null == keyStore)
{
continue;
}
for (Enumeration en = keyStore.aliases(); en.hasMoreElements();)
{
String localAlias = en.nextElement();
Match match = getPotentialMatch(builderIndex, builder, keyStore, localAlias, keyTypes,
keyTypeLimit, uniqueIssuers, algorithmConstraints, forServer, atDate, requestedHostName);
if (match.compareTo(Match.NOTHING) < 0)
{
matches = addToMatches(matches, match);
}
}
}
catch (KeyStoreException e)
{
LOG.log(Level.WARNING, "Failed to fully process KeyStore.Builder at index " + builderIndex, e);
}
}
if (null != matches && !matches.isEmpty())
{
// NOTE: We are relying on this being a stable sort
Collections.sort(matches);
return getAliases(matches, getNextVersionSuffix());
}
}
return null;
}
private Match getBestMatch(List keyTypes, Principal[] issuers, TransportData transportData,
boolean forServer)
{
Match bestMatchSoFar = Match.NOTHING;
if (!builders.isEmpty() && !keyTypes.isEmpty())
{
int keyTypeLimit = keyTypes.size();
Set uniqueIssuers = getUniquePrincipals(issuers);
BCAlgorithmConstraints algorithmConstraints = TransportData.getAlgorithmConstraints(transportData, true);
Date atDate = new Date();
String requestedHostName = getRequestedHostName(transportData, forServer);
for (int builderIndex = 0, count = builders.size(); builderIndex < count; ++builderIndex)
{
try
{
KeyStore.Builder builder = builders.get(builderIndex);
KeyStore keyStore = builder.getKeyStore();
if (null == keyStore)
{
continue;
}
for (Enumeration en = keyStore.aliases(); en.hasMoreElements();)
{
String localAlias = en.nextElement();
Match match = getPotentialMatch(builderIndex, builder, keyStore, localAlias, keyTypes,
keyTypeLimit, uniqueIssuers, algorithmConstraints, forServer, atDate, requestedHostName);
if (match.compareTo(bestMatchSoFar) < 0)
{
bestMatchSoFar = match;
if (bestMatchSoFar.isIdeal())
{
return bestMatchSoFar;
}
if (bestMatchSoFar.isValid())
{
keyTypeLimit = Math.min(keyTypeLimit, bestMatchSoFar.keyTypeIndex + 1);
}
}
}
}
catch (KeyStoreException e)
{
LOG.log(Level.WARNING, "Failed to fully process KeyStore.Builder at index " + builderIndex, e);
}
}
}
return bestMatchSoFar;
}
private String getNextVersionSuffix()
{
return "." + versions.incrementAndGet();
}
private Match getPotentialMatch(int builderIndex, KeyStore.Builder builder, KeyStore keyStore, String localAlias,
List keyTypes, int keyTypeLimit, Set uniqueIssuers,
BCAlgorithmConstraints algorithmConstraints, boolean forServer, Date atDate, String requestedHostName)
throws KeyStoreException
{
if (keyStore.isKeyEntry(localAlias))
{
X509Certificate[] chain = JsseUtils.getX509CertificateChain(keyStore.getCertificateChain(localAlias));
int keyTypeIndex = getPotentialKeyType(keyTypes, keyTypeLimit, uniqueIssuers, algorithmConstraints,
forServer, chain);
if (keyTypeIndex >= 0)
{
MatchQuality quality = getKeyTypeQuality(isInFipsMode, helper, keyTypes, algorithmConstraints,
forServer, atDate, requestedHostName, chain, keyTypeIndex);
if (MatchQuality.NONE != quality)
{
return new Match(quality, keyTypeIndex, builderIndex, localAlias, keyStore, chain);
}
}
}
return Match.NOTHING;
}
private KeyStore.PrivateKeyEntry getPrivateKeyEntry(String alias)
{
if (null == alias)
{
return null;
}
SoftReference entryRef = cachedEntries.get(alias);
if (null != entryRef)
{
KeyStore.PrivateKeyEntry cachedEntry = entryRef.get();
if (null != cachedEntry)
{
return cachedEntry;
}
}
KeyStore.PrivateKeyEntry result = loadPrivateKeyEntry(alias);
if (null != result)
{
cachedEntries.put(alias, new SoftReference(result));
}
return result;
}
private KeyStore.PrivateKeyEntry loadPrivateKeyEntry(String alias)
{
try
{
int builderIndexStart = 0;
int builderIndexEnd = alias.indexOf('.', builderIndexStart);
if (builderIndexEnd > builderIndexStart)
{
int localAliasStart = builderIndexEnd + 1;
int localAliasEnd = alias.lastIndexOf('.');
if (localAliasEnd > localAliasStart)
{
int builderIndex = Integer.parseInt(alias.substring(builderIndexStart, builderIndexEnd));
if (0 <= builderIndex && builderIndex < builders.size())
{
KeyStore.Builder builder = builders.get(builderIndex);
String localAlias = alias.substring(localAliasStart, localAliasEnd);
KeyStore keyStore = builder.getKeyStore();
if (null != keyStore)
{
ProtectionParameter protectionParameter = builder.getProtectionParameter(localAlias);
KeyStore.Entry entry = keyStore.getEntry(localAlias, protectionParameter);
if (entry instanceof KeyStore.PrivateKeyEntry)
{
return (KeyStore.PrivateKeyEntry)entry;
}
}
}
}
}
}
catch (Exception e)
{
LOG.log(Level.FINER, "Failed to load PrivateKeyEntry: " + alias, e);
}
return null;
}
static MatchQuality getKeyTypeQuality(boolean isInFipsMode, JcaJceHelper helper, List keyTypes,
BCAlgorithmConstraints algorithmConstraints, boolean forServer, Date atDate, String requestedHostName,
X509Certificate[] chain, int keyTypeIndex)
{
String keyType = keyTypes.get(keyTypeIndex);
LOG.finer("EE cert potentially usable for key type: " + keyType);
if (!isSuitableChain(isInFipsMode, helper, chain, algorithmConstraints, forServer))
{
LOG.finer("Unsuitable chain for key type: " + keyType);
return MatchQuality.NONE;
}
return getCertificateQuality(chain[0], atDate, requestedHostName);
}
static List getKeyTypes(String... keyTypes)
{
if (null != keyTypes && keyTypes.length > 0)
{
ArrayList result = new ArrayList(keyTypes.length);
for (String keyType : keyTypes)
{
if (null == keyType)
{
throw new IllegalArgumentException("Key types cannot be null");
}
if (!result.contains(keyType))
{
result.add(keyType);
}
}
return Collections.unmodifiableList(result);
}
return Collections.emptyList();
}
static int getPotentialKeyType(List keyTypes, int keyTypeLimit, Set uniqueIssuers,
BCAlgorithmConstraints algorithmConstraints, boolean forServer, X509Certificate[] chain)
{
if (!isSuitableChainForIssuers(chain, uniqueIssuers))
{
return -1;
}
return getSuitableKeyTypeForEECert(chain[0], keyTypes, keyTypeLimit, algorithmConstraints, forServer);
}
static String getRequestedHostName(TransportData transportData, boolean forServer)
{
if (null != transportData && forServer)
{
BCExtendedSSLSession sslSession = transportData.getHandshakeSession();
if (null != sslSession)
{
BCSNIHostName sniHostName = JsseUtils.getSNIHostName(sslSession.getRequestedServerNames());
if (null != sniHostName)
{
return sniHostName.getAsciiName();
}
}
}
return null;
}
static Set getUniquePrincipals(Principal[] principals)
{
if (null == principals)
{
return null;
}
if (principals.length > 0)
{
Set result = new HashSet();
for (int i = 0; i < principals.length; ++i)
{
Principal principal = principals[i];
if (null != principal)
{
result.add(principal);
}
}
if (!result.isEmpty())
{
return Collections.unmodifiableSet(result);
}
}
return Collections.emptySet();
}
static boolean isSuitableKeyType(boolean forServer, String keyType, X509Certificate eeCert,
TransportData transportData)
{
Map filters = forServer ? FILTERS_SERVER : FILTERS_CLIENT;
PublicKeyFilter filter = filters.get(keyType);
if (null == filter)
{
return false;
}
PublicKey publicKey = eeCert.getPublicKey();
boolean[] keyUsage = eeCert.getKeyUsage();
BCAlgorithmConstraints algorithmConstraints = TransportData.getAlgorithmConstraints(transportData, true);
return filter.accepts(publicKey, keyUsage, algorithmConstraints);
}
private static List addToMatches(List matches, Match match)
{
if (null == matches)
{
matches = new ArrayList();
}
matches.add(match);
return matches;
}
private static String getAlias(Match match, String versionSuffix)
{
return match.builderIndex + "." + match.localAlias + versionSuffix;
}
private static String[] getAliases(List matches, String versionSuffix)
{
int count = matches.size(), pos = 0;
String[] result = new String[count];
for (Match match : matches)
{
result[pos++] = getAlias(match, versionSuffix);
}
return result;
}
private static MatchQuality getCertificateQuality(X509Certificate certificate, Date atDate, String requestedHostName)
{
try
{
certificate.checkValidity(atDate);
}
catch (CertificateException e)
{
return MatchQuality.EXPIRED;
}
if (null != requestedHostName)
{
try
{
/*
* NOTE: For compatibility with SunJSSE, we also re-use HTTPS endpoint ID checks for
* SNI certificate selection.
*/
ProvX509TrustManager.checkEndpointID(requestedHostName, certificate, "HTTPS");
}
catch (CertificateException e)
{
return MatchQuality.MISMATCH_SNI;
}
}
/*
* Prefer RSA certificates with more specific KeyUsage over "multi-use" ones.
*/
if ("RSA".equalsIgnoreCase(JsseUtils.getPublicKeyAlgorithm(certificate.getPublicKey())))
{
boolean[] keyUsage = certificate.getKeyUsage();
if (ProvAlgorithmChecker.supportsKeyUsage(keyUsage, ProvAlgorithmChecker.KU_DIGITAL_SIGNATURE) &&
ProvAlgorithmChecker.supportsKeyUsage(keyUsage, ProvAlgorithmChecker.KU_KEY_ENCIPHERMENT))
{
return MatchQuality.RSA_MULTI_USE;
}
}
return MatchQuality.OK;
}
private static KeyPurposeId getRequiredExtendedKeyUsage(boolean forServer)
{
return !provKeyManagerCheckEKU
? null
: forServer
? KeyPurposeId.id_kp_serverAuth
: KeyPurposeId.id_kp_clientAuth;
}
private static int getSuitableKeyTypeForEECert(X509Certificate eeCert, List keyTypes, int keyTypeLimit,
BCAlgorithmConstraints algorithmConstraints, boolean forServer)
{
Map filters = forServer ? FILTERS_SERVER : FILTERS_CLIENT;
PublicKey publicKey = eeCert.getPublicKey();
boolean[] keyUsage = eeCert.getKeyUsage();
for (int keyTypeIndex = 0; keyTypeIndex < keyTypeLimit; ++keyTypeIndex)
{
String keyType = keyTypes.get(keyTypeIndex);
PublicKeyFilter filter = filters.get(keyType);
if (null != filter && filter.accepts(publicKey, keyUsage, algorithmConstraints))
{
return keyTypeIndex;
}
}
return -1;
}
private static boolean isSuitableChain(boolean isInFipsMode, JcaJceHelper helper, X509Certificate[] chain,
BCAlgorithmConstraints algorithmConstraints, boolean forServer)
{
try
{
Set trustedCerts = Collections.emptySet();
KeyPurposeId ekuOID = getRequiredExtendedKeyUsage(forServer);
int kuBit = -1; // i.e. no checks; we handle them in isSuitableEECert
ProvAlgorithmChecker.checkChain(isInFipsMode, helper, algorithmConstraints, trustedCerts, chain, ekuOID,
kuBit);
return true;
}
catch (CertPathValidatorException e)
{
LOG.log(Level.FINEST, "Certificate chain check failed", e);
return false;
}
}
private static boolean isSuitableChainForIssuers(X509Certificate[] chain, Set uniqueIssuers)
{
if (TlsUtils.isNullOrEmpty(chain))
{
return false;
}
if (null == uniqueIssuers || uniqueIssuers.isEmpty())
{
// NOTE: Empty issuers means same as absent issuers, per SunJSSE
return true;
}
int pos = chain.length;
while (--pos >= 0)
{
if (uniqueIssuers.contains(chain[pos].getIssuerX500Principal()))
{
return true;
}
}
X509Certificate eeCert = chain[0];
return eeCert.getBasicConstraints() >= 0
&& uniqueIssuers.contains(eeCert.getSubjectX500Principal());
}
// NOTE: We rely on these being in preference order.
static enum MatchQuality
{
OK,
RSA_MULTI_USE,
MISMATCH_SNI,
EXPIRED,
// TODO[jsse] Consider allowing certificates with invalid ExtendedKeyUsage and/or KeyUsage (as SunJSSE does)
// MISMATCH_EKU,
// MISMATCH_KU,
NONE
}
private static final class Match
implements Comparable
{
static final MatchQuality INVALID = MatchQuality.MISMATCH_SNI;
static final Match NOTHING = new Match(MatchQuality.NONE, Integer.MAX_VALUE, -1, null, null, null);
final MatchQuality quality;
final int keyTypeIndex;
final int builderIndex;
final String localAlias;
final KeyStore cachedKeyStore;
final X509Certificate[] cachedCertificateChain;
Match(MatchQuality quality, int keyTypeIndex, int builderIndex, String localAlias, KeyStore cachedKeyStore,
X509Certificate[] cachedCertificateChain)
{
this.quality = quality;
this.keyTypeIndex = keyTypeIndex;
this.builderIndex = builderIndex;
this.localAlias = localAlias;
this.cachedKeyStore = cachedKeyStore;
this.cachedCertificateChain = cachedCertificateChain;
}
public int compareTo(Match that)
{
boolean thisValid = this.isValid(), thatValid = that.isValid();
if (thisValid != thatValid)
{
return thisValid ? -1 : 1;
}
if (this.keyTypeIndex != that.keyTypeIndex)
{
return this.keyTypeIndex < that.keyTypeIndex ? -1 : 1;
}
return this.quality.compareTo(that.quality);
}
boolean isIdeal()
{
return MatchQuality.OK == quality && 0 == keyTypeIndex;
}
boolean isValid()
{
return quality.compareTo(INVALID) < 0;
}
}
static interface PublicKeyFilter
{
boolean accepts(PublicKey publicKey, boolean[] keyUsage, BCAlgorithmConstraints algorithmConstraints);
}
private static final class DefaultPublicKeyFilter
implements PublicKeyFilter
{
final String algorithm;
final Class extends PublicKey> clazz;
final int keyUsageBit;
DefaultPublicKeyFilter(String algorithm, Class extends PublicKey> clazz, int keyUsageBit)
{
this.algorithm = algorithm;
this.clazz = clazz;
this.keyUsageBit = keyUsageBit;
}
public boolean accepts(PublicKey publicKey, boolean[] keyUsage, BCAlgorithmConstraints algorithmConstraints)
{
return appliesTo(publicKey)
&& ProvAlgorithmChecker.permitsKeyUsage(publicKey, keyUsage, keyUsageBit, algorithmConstraints);
}
private boolean appliesTo(PublicKey publicKey)
{
return (null != algorithm && algorithm.equalsIgnoreCase(JsseUtils.getPublicKeyAlgorithm(publicKey)))
|| (null != clazz && clazz.isInstance(publicKey));
}
}
private static final class ECPublicKeyFilter13
implements PublicKeyFilter
{
final ASN1ObjectIdentifier standardOID;
ECPublicKeyFilter13(ASN1ObjectIdentifier standardOID)
{
this.standardOID = standardOID;
}
public boolean accepts(PublicKey publicKey, boolean[] keyUsage, BCAlgorithmConstraints algorithmConstraints)
{
return appliesTo(publicKey)
&& ProvAlgorithmChecker.permitsKeyUsage(publicKey, keyUsage, ProvAlgorithmChecker.KU_DIGITAL_SIGNATURE,
algorithmConstraints);
}
private boolean appliesTo(PublicKey publicKey)
{
if ("EC".equalsIgnoreCase(JsseUtils.getPublicKeyAlgorithm(publicKey))
|| ECPublicKey.class.isInstance(publicKey))
{
ASN1ObjectIdentifier oid = JsseUtils.getNamedCurveOID(publicKey);
if (standardOID.equals(oid))
{
return true;
}
}
return false;
}
}
}