I need to ignore the PKIX path building exception
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException:
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderExc
ption: unable to find valid certification path to requested target
I know how to do this by writing my own class implementing X509TrustManager
where I always return true
from isServerTrusted
.
However, I don't want to trust all servers & all clients.
How can I achieve something like this - i.e. pass on part of the verification to whatever was the X509TrustFactory object before I replaced it.
i.e. this is what I want to do
public boolean isServerTrusted(X509Certificate[] chain)
{
if(chain[0].getIssuerDN().getName().equals("MyTrustedServer") && chain[0].getSubjectDN().getName().equals("MyTrustedServer"))
return true;
// else I want to do whatever verification is normally done
}
Also I don't want to disturb the existing isClientTrusted
verification.
How can I do this?
To avoid problems when validating the SSL certificate, change the code of the checkServerTrusted method in the X509TrustManager interface so that a CertificateException or IllegalArgumentException is thrown when it detects suspicious certificates.
Interface X509TrustManager Instance of this interface manage which X509 certificates may be used to authenticate the remote side of a secure socket. Decisions may be based on trusted certificate authorities, certificate revocation lists, online status checking or other means.
TrustManager s are responsible for managing the trust material that is used when making trust decisions, and for deciding whether credentials presented by a peer should be accepted. TrustManager s are created by either using a TrustManagerFactory , or by implementing one of the TrustManager subclasses.
You can get hold of the existing default trust manager and wrap it in your own using something like this:
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// Using null here initialises the TMF with the default trust store.
tmf.init((KeyStore) null);
// Get hold of the default trust manager
X509TrustManager x509Tm = null;
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
x509Tm = (X509TrustManager) tm;
break;
}
}
// Wrap it in your own class.
final X509TrustManager finalTm = x509Tm;
X509TrustManager customTm = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return finalTm.getAcceptedIssuers();
}
@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
finalTm.checkServerTrusted(chain, authType);
}
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
finalTm.checkClientTrusted(chain, authType);
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { customTm }, null);
// You don't have to set this as the default context,
// it depends on the library you're using.
SSLContext.setDefault(sslContext);
You can then implement your own logic around finalTm.checkServerTrusted(chain, authType);
.
However, you should make sure you're making an exception for the specific certificate you want to ignore.
What you're doing in the following is letting through any certificate with these Issuer DN and Subject DN (which isn't difficult to forge):
if(chain[0].getIssuerDN().getName().equals("MyTrustedServer") && chain[0].getSubjectDN().getName().equals("MyTrustedServer"))
return true;
You could instead load the X509Certificate
instance from a known reference and compare the actual value in the chain.
In addition, checkClientTrusted
and checkServerTrusted
are not methods that return true
or false
, but void
methods that will succeed silently by default. If there's something wrong with the certificate you expect, throw a CertificateException
explicitly.
Instead of implementing X509TrustManager
to trust any certificate, you can create a trust manager from the specific certificate in question. Load the certificate from a .p12
or .jks
keystore or from a .crt
-file (you can copy a certificate from the browser into a file, in Chrome by clicking the padlock and selecting Certificate). The code is shorter than implementing your own X509TrustManager
:
private static SSLSocketFactory createSSLSocketFactory(File crtFile) throws GeneralSecurityException, IOException {
SSLContext sslContext = SSLContext.getInstance("SSL");
// Create a new trust store, use getDefaultType for .jks files or "pkcs12" for .p12 files
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
// You can supply a FileInputStream to a .jks or .p12 file and the keystore password as an alternative to loading the crt file
trustStore.load(null, null);
// Read the certificate from disk
X509Certificate result;
try (InputStream input = new FileInputStream(crtFile)) {
result = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(input);
}
// Add it to the trust store
trustStore.setCertificateEntry(crtFile.getName(), result);
// Convert the trust store to trust managers
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
TrustManager[] trustManagers = tmf.getTrustManagers();
sslContext.init(null, trustManagers, null);
return sslContext.getSocketFactory();
}
You can use it by calling HttpsURLConnection.setSSLSocketFactory(createSSLSocketFactory(crtFile))
(you probably want to initialize the socket factory once and reuse it, though).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With