|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.ssl; |
|
|
|
import java.net.Socket; |
|
import java.security.*; |
|
import java.security.cert.*; |
|
import java.util.*; |
|
import javax.net.ssl.*; |
|
import sun.security.util.AnchorCertificates; |
|
import sun.security.util.HostnameChecker; |
|
import sun.security.validator.*; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final class X509TrustManagerImpl extends X509ExtendedTrustManager |
|
implements X509TrustManager { |
|
|
|
private final String validatorType; |
|
|
|
|
|
|
|
*/ |
|
private final Collection<X509Certificate> trustedCerts; |
|
|
|
private final PKIXBuilderParameters pkixParams; |
|
|
|
// note that we need separate validator for client and server due to |
|
|
|
private volatile Validator clientValidator, serverValidator; |
|
|
|
X509TrustManagerImpl(String validatorType, |
|
Collection<X509Certificate> trustedCerts) { |
|
|
|
this.validatorType = validatorType; |
|
this.pkixParams = null; |
|
|
|
if (trustedCerts == null) { |
|
trustedCerts = Collections.<X509Certificate>emptySet(); |
|
} |
|
|
|
this.trustedCerts = trustedCerts; |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { |
|
SSLLogger.fine("adding as trusted certificates", |
|
(Object[])trustedCerts.toArray(new X509Certificate[0])); |
|
} |
|
} |
|
|
|
X509TrustManagerImpl(String validatorType, PKIXBuilderParameters params) { |
|
this.validatorType = validatorType; |
|
this.pkixParams = params; |
|
// create server validator eagerly so that we can conveniently |
|
// get the trusted certificates |
|
// clients need it anyway eventually, and servers will not mind |
|
|
|
Validator v = getValidator(Validator.VAR_TLS_SERVER); |
|
trustedCerts = v.getTrustedCertificates(); |
|
serverValidator = v; |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { |
|
SSLLogger.fine("adding as trusted certificates", |
|
(Object[])trustedCerts.toArray(new X509Certificate[0])); |
|
} |
|
} |
|
|
|
@Override |
|
public void checkClientTrusted(X509Certificate[] chain, String authType) |
|
throws CertificateException { |
|
checkTrusted(chain, authType, (Socket)null, true); |
|
} |
|
|
|
@Override |
|
public void checkServerTrusted(X509Certificate[] chain, String authType) |
|
throws CertificateException { |
|
checkTrusted(chain, authType, (Socket)null, false); |
|
} |
|
|
|
@Override |
|
public X509Certificate[] getAcceptedIssuers() { |
|
X509Certificate[] certsArray = new X509Certificate[trustedCerts.size()]; |
|
trustedCerts.toArray(certsArray); |
|
return certsArray; |
|
} |
|
|
|
@Override |
|
public void checkClientTrusted(X509Certificate[] chain, String authType, |
|
Socket socket) throws CertificateException { |
|
checkTrusted(chain, authType, socket, true); |
|
} |
|
|
|
@Override |
|
public void checkServerTrusted(X509Certificate[] chain, String authType, |
|
Socket socket) throws CertificateException { |
|
checkTrusted(chain, authType, socket, false); |
|
} |
|
|
|
@Override |
|
public void checkClientTrusted(X509Certificate[] chain, String authType, |
|
SSLEngine engine) throws CertificateException { |
|
checkTrusted(chain, authType, engine, true); |
|
} |
|
|
|
@Override |
|
public void checkServerTrusted(X509Certificate[] chain, String authType, |
|
SSLEngine engine) throws CertificateException { |
|
checkTrusted(chain, authType, engine, false); |
|
} |
|
|
|
private Validator checkTrustedInit(X509Certificate[] chain, |
|
String authType, boolean isClient) { |
|
if (chain == null || chain.length == 0) { |
|
throw new IllegalArgumentException( |
|
"null or zero-length certificate chain"); |
|
} |
|
|
|
if (authType == null || authType.length() == 0) { |
|
throw new IllegalArgumentException( |
|
"null or zero-length authentication type"); |
|
} |
|
|
|
Validator v = null; |
|
if (isClient) { |
|
v = clientValidator; |
|
if (v == null) { |
|
synchronized (this) { |
|
v = clientValidator; |
|
if (v == null) { |
|
v = getValidator(Validator.VAR_TLS_CLIENT); |
|
clientValidator = v; |
|
} |
|
} |
|
} |
|
} else { |
|
// assume double checked locking with a volatile flag works |
|
|
|
v = serverValidator; |
|
if (v == null) { |
|
synchronized (this) { |
|
v = serverValidator; |
|
if (v == null) { |
|
v = getValidator(Validator.VAR_TLS_SERVER); |
|
serverValidator = v; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return v; |
|
} |
|
|
|
private void checkTrusted(X509Certificate[] chain, String authType, |
|
Socket socket, boolean isClient) throws CertificateException { |
|
Validator v = checkTrustedInit(chain, authType, isClient); |
|
|
|
X509Certificate[] trustedChain = null; |
|
if ((socket != null) && socket.isConnected() && |
|
(socket instanceof SSLSocket)) { |
|
|
|
SSLSocket sslSocket = (SSLSocket)socket; |
|
SSLSession session = sslSocket.getHandshakeSession(); |
|
if (session == null) { |
|
throw new CertificateException("No handshake session"); |
|
} |
|
|
|
|
|
boolean isExtSession = (session instanceof ExtendedSSLSession); |
|
AlgorithmConstraints constraints; |
|
if (isExtSession && |
|
ProtocolVersion.useTLS12PlusSpec(session.getProtocol())) { |
|
ExtendedSSLSession extSession = (ExtendedSSLSession)session; |
|
String[] localSupportedSignAlgs = |
|
extSession.getLocalSupportedSignatureAlgorithms(); |
|
|
|
constraints = new SSLAlgorithmConstraints( |
|
sslSocket, localSupportedSignAlgs, false); |
|
} else { |
|
constraints = new SSLAlgorithmConstraints(sslSocket, false); |
|
} |
|
|
|
|
|
List<byte[]> responseList = Collections.emptyList(); |
|
if (!isClient && isExtSession) { |
|
responseList = |
|
((ExtendedSSLSession)session).getStatusResponses(); |
|
} |
|
trustedChain = validate(v, chain, responseList, |
|
constraints, isClient ? null : authType); |
|
|
|
// check if EE certificate chains to a public root CA (as |
|
|
|
boolean chainsToPublicCA = AnchorCertificates.contains( |
|
trustedChain[trustedChain.length-1]); |
|
|
|
|
|
String identityAlg = sslSocket.getSSLParameters(). |
|
getEndpointIdentificationAlgorithm(); |
|
if (identityAlg != null && identityAlg.length() != 0) { |
|
checkIdentity(session, trustedChain[0], identityAlg, isClient, |
|
getRequestedServerNames(socket), chainsToPublicCA); |
|
} |
|
} else { |
|
trustedChain = validate(v, chain, Collections.emptyList(), |
|
null, isClient ? null : authType); |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { |
|
SSLLogger.fine("Found trusted certificate", |
|
trustedChain[trustedChain.length - 1]); |
|
} |
|
} |
|
|
|
private void checkTrusted(X509Certificate[] chain, String authType, |
|
SSLEngine engine, boolean isClient) throws CertificateException { |
|
Validator v = checkTrustedInit(chain, authType, isClient); |
|
|
|
X509Certificate[] trustedChain = null; |
|
if (engine != null) { |
|
SSLSession session = engine.getHandshakeSession(); |
|
if (session == null) { |
|
throw new CertificateException("No handshake session"); |
|
} |
|
|
|
|
|
boolean isExtSession = (session instanceof ExtendedSSLSession); |
|
AlgorithmConstraints constraints; |
|
if (isExtSession && |
|
ProtocolVersion.useTLS12PlusSpec(session.getProtocol())) { |
|
ExtendedSSLSession extSession = (ExtendedSSLSession)session; |
|
String[] localSupportedSignAlgs = |
|
extSession.getLocalSupportedSignatureAlgorithms(); |
|
|
|
constraints = new SSLAlgorithmConstraints( |
|
engine, localSupportedSignAlgs, false); |
|
} else { |
|
constraints = new SSLAlgorithmConstraints(engine, false); |
|
} |
|
|
|
|
|
List<byte[]> responseList = Collections.emptyList(); |
|
if (!isClient && isExtSession) { |
|
responseList = |
|
((ExtendedSSLSession)session).getStatusResponses(); |
|
} |
|
trustedChain = validate(v, chain, responseList, |
|
constraints, isClient ? null : authType); |
|
|
|
// check if EE certificate chains to a public root CA (as |
|
|
|
boolean chainsToPublicCA = AnchorCertificates.contains( |
|
trustedChain[trustedChain.length-1]); |
|
|
|
|
|
String identityAlg = engine.getSSLParameters(). |
|
getEndpointIdentificationAlgorithm(); |
|
if (identityAlg != null && identityAlg.length() != 0) { |
|
checkIdentity(session, trustedChain[0], identityAlg, isClient, |
|
getRequestedServerNames(engine), chainsToPublicCA); |
|
} |
|
} else { |
|
trustedChain = validate(v, chain, Collections.emptyList(), |
|
null, isClient ? null : authType); |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { |
|
SSLLogger.fine("Found trusted certificate", |
|
trustedChain[trustedChain.length - 1]); |
|
} |
|
} |
|
|
|
private Validator getValidator(String variant) { |
|
Validator v; |
|
if (pkixParams == null) { |
|
v = Validator.getInstance(validatorType, variant, trustedCerts); |
|
} else { |
|
v = Validator.getInstance(validatorType, variant, pkixParams); |
|
} |
|
return v; |
|
} |
|
|
|
private static X509Certificate[] validate(Validator v, |
|
X509Certificate[] chain, List<byte[]> responseList, |
|
AlgorithmConstraints constraints, String authType) |
|
throws CertificateException { |
|
Object o = JsseJce.beginFipsProvider(); |
|
try { |
|
return v.validate(chain, null, responseList, constraints, authType); |
|
} finally { |
|
JsseJce.endFipsProvider(o); |
|
} |
|
} |
|
|
|
// Get string representation of HostName from a list of server names. |
|
// |
|
|
|
private static String getHostNameInSNI(List<SNIServerName> sniNames) { |
|
|
|
SNIHostName hostname = null; |
|
for (SNIServerName sniName : sniNames) { |
|
if (sniName.getType() != StandardConstants.SNI_HOST_NAME) { |
|
continue; |
|
} |
|
|
|
if (sniName instanceof SNIHostName) { |
|
hostname = (SNIHostName)sniName; |
|
} else { |
|
try { |
|
hostname = new SNIHostName(sniName.getEncoded()); |
|
} catch (IllegalArgumentException iae) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { |
|
SSLLogger.fine("Illegal server name: " + sniName); |
|
} |
|
} |
|
} |
|
|
|
|
|
break; |
|
} |
|
|
|
if (hostname != null) { |
|
return hostname.getAsciiName(); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
|
|
static List<SNIServerName> getRequestedServerNames(Socket socket) { |
|
if (socket != null && socket.isConnected() && |
|
socket instanceof SSLSocket) { |
|
|
|
SSLSocket sslSocket = (SSLSocket)socket; |
|
SSLSession session = sslSocket.getHandshakeSession(); |
|
|
|
if (session != null && (session instanceof ExtendedSSLSession)) { |
|
ExtendedSSLSession extSession = (ExtendedSSLSession)session; |
|
return extSession.getRequestedServerNames(); |
|
} |
|
} |
|
|
|
return Collections.<SNIServerName>emptyList(); |
|
} |
|
|
|
|
|
static List<SNIServerName> getRequestedServerNames(SSLEngine engine) { |
|
if (engine != null) { |
|
SSLSession session = engine.getHandshakeSession(); |
|
|
|
if (session != null && (session instanceof ExtendedSSLSession)) { |
|
ExtendedSSLSession extSession = (ExtendedSSLSession)session; |
|
return extSession.getRequestedServerNames(); |
|
} |
|
} |
|
|
|
return Collections.<SNIServerName>emptyList(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static void checkIdentity(SSLSession session, |
|
X509Certificate cert, |
|
String algorithm, |
|
boolean isClient, |
|
List<SNIServerName> sniNames, |
|
boolean chainsToPublicCA) throws CertificateException { |
|
|
|
boolean identifiable = false; |
|
String peerHost = session.getPeerHost(); |
|
if (isClient) { |
|
String hostname = getHostNameInSNI(sniNames); |
|
if (hostname != null) { |
|
try { |
|
checkIdentity(hostname, cert, algorithm, chainsToPublicCA); |
|
identifiable = true; |
|
} catch (CertificateException ce) { |
|
if (hostname.equalsIgnoreCase(peerHost)) { |
|
throw ce; |
|
} |
|
|
|
// otherwisw, failover to check peer host |
|
} |
|
} |
|
} |
|
|
|
if (!identifiable) { |
|
checkIdentity(peerHost, cert, algorithm, chainsToPublicCA); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static void checkIdentity(String hostname, X509Certificate cert, |
|
String algorithm) throws CertificateException { |
|
checkIdentity(hostname, cert, algorithm, false); |
|
} |
|
|
|
private static void checkIdentity(String hostname, X509Certificate cert, |
|
String algorithm, boolean chainsToPublicCA) |
|
throws CertificateException { |
|
if (algorithm != null && algorithm.length() != 0) { |
|
|
|
if ((hostname != null) && hostname.startsWith("[") && |
|
hostname.endsWith("]")) { |
|
hostname = hostname.substring(1, hostname.length() - 1); |
|
} |
|
|
|
if (algorithm.equalsIgnoreCase("HTTPS")) { |
|
HostnameChecker.getInstance(HostnameChecker.TYPE_TLS).match( |
|
hostname, cert, chainsToPublicCA); |
|
} else if (algorithm.equalsIgnoreCase("LDAP") || |
|
algorithm.equalsIgnoreCase("LDAPS")) { |
|
HostnameChecker.getInstance(HostnameChecker.TYPE_LDAP).match( |
|
hostname, cert, chainsToPublicCA); |
|
} else { |
|
throw new CertificateException( |
|
"Unknown identification algorithm: " + algorithm); |
|
} |
|
} |
|
} |
|
} |
|
|