可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
My application has a personal keystore containing trusted self-signed certificates for use in the local network - say mykeystore.jks
. I wish to be able to connect to public sites(say google.com) as well as ones in my local network using self-signed certificates which have been provisioned locally.
The problem here is that, when I connect to https://google.com, path building fails, because setting my own keystore overrides the default keystore containing root CAs bundled with the JRE, reporting the exception
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
However, if I import a CA certificate into my own keystore(mykeystore.jks
) it works fine. Is there a way to support both?
I have my own TrustManger for this purpose,
public class CustomX509TrustManager implements X509TrustManager {
X509TrustManager defaultTrustManager;
public MyX509TrustManager(KeyStore keystore) {
TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustMgrFactory.init(keystore);
TrustManager trustManagers[] = trustMgrFactory.getTrustManagers();
for (int i = 0; i < trustManagers.length; i++) {
if (trustManagers[i] instanceof X509TrustManager) {
defaultTrustManager = (X509TrustManager) trustManagers[i];
return;
}
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
try {
defaultTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ce) {
/* Handle untrusted certificates */
}
}
}
I then initialize the SSLContext,
TrustManager[] trustManagers =
new TrustManager[] { new CustomX509TrustManager(keystore) };
SSLContext customSSLContext =
SSLContext.getInstance("TLS");
customSSLContext.init(null, trustManagers, null);
and set the socket factory,
HttpsURLConnection.setDefaultSSLSocketFactory(customSSLContext.getSocketFactory());
The main program,
URL targetServer = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) targetServer.openConnection();
If I don't set my own trust managers, it connects to https://google.com just fine. How do I get a "default trust manager" which points to the default key store?
回答1:
In trustMgrFactory.init(keystore);
you're configuring defaultTrustManager with your own personal keystore, not the system default keystore.
Based on reading the source code for sun.security.ssl.TrustManagerFactoryImpl, it looks like trustMgrFactory.init((KeyStore) null);
would do exactly what you need (load the system default keystore), and based on quick testing, it seems to work for me.
回答2:
The answer here is how I came to understand how to do this. If you just want to accept the system CA certs plus a custom keystore of certs I simplified it into a single class with some convenience methods. Full code available here:
https://gist.github.com/HughJeffner/6eac419b18c6001aeadb
KeyStore keystore; // Get your own keystore here
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManager[] tm = CompositeX509TrustManager.getTrustManagers(keystore);
sslContext.init(null, tm, null);
回答3:
I've run into the same issue with Commons HttpClient. Working solution for my case was to create delegation chain for PKIX TrustManagers in following way:
public class TrustManagerDelegate implements X509TrustManager {
private final X509TrustManager mainTrustManager;
private final X509TrustManager trustManager;
private final TrustStrategy trustStrategy;
public TrustManagerDelegate(X509TrustManager mainTrustManager, X509TrustManager trustManager, TrustStrategy trustStrategy) {
this.mainTrustManager = mainTrustManager;
this.trustManager = trustManager;
this.trustStrategy = trustStrategy;
}
@Override
public void checkClientTrusted(
final X509Certificate[] chain, final String authType) throws CertificateException {
this.trustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkServerTrusted(
final X509Certificate[] chain, final String authType) throws CertificateException {
if (!this.trustStrategy.isTrusted(chain, authType)) {
try {
mainTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ex) {
this.trustManager.checkServerTrusted(chain, authType);
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return this.trustManager.getAcceptedIssuers();
}
}
And initialize HttpClient in following way (yes it's ugly):
final SSLContext sslContext;
try {
sslContext = SSLContext.getInstance("TLS");
final TrustManagerFactory javaDefaultTrustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
javaDefaultTrustManager.init((KeyStore)null);
final TrustManagerFactory customCaTrustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
customCaTrustManager.init(getKeyStore());
sslContext.init(
null,
new TrustManager[]{
new TrustManagerDelegate(
(X509TrustManager)customCaTrustManager.getTrustManagers()[0],
(X509TrustManager)javaDefaultTrustManager.getTrustManagers()[0],
new TrustSelfSignedStrategy()
)
},
secureRandom
);
} catch (final NoSuchAlgorithmException ex) {
throw new SSLInitializationException(ex.getMessage(), ex);
} catch (final KeyManagementException ex) {
throw new SSLInitializationException(ex.getMessage(), ex);
}
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactory)
.build()
);
//maximum parallel requests is 500
cm.setMaxTotal(500);
cm.setDefaultMaxPerRoute(500);
CredentialsProvider cp = new BasicCredentialsProvider();
cp.setCredentials(
new AuthScope(apiSettings.getIdcApiUrl(), 443),
new UsernamePasswordCredentials(apiSettings.getAgencyId(), apiSettings.getAgencyPassword())
);
client = HttpClients.custom()
.setConnectionManager(cm)
.build();
In your case with simple HttpsURLConnection you may get by with simplified version of delegating class:
public class TrustManagerDelegate implements X509TrustManager {
private final X509TrustManager mainTrustManager;
private final X509TrustManager trustManager;
public TrustManagerDelegate(X509TrustManager mainTrustManager, X509TrustManager trustManager) {
this.mainTrustManager = mainTrustManager;
this.trustManager = trustManager;
}
@Override
public void checkClientTrusted(
final X509Certificate[] chain, final String authType) throws CertificateException {
this.trustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkServerTrusted(
final X509Certificate[] chain, final String authType) throws CertificateException {
try {
mainTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ex) {
this.trustManager.checkServerTrusted(chain, authType);
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return this.trustManager.getAcceptedIssuers();
}
}
回答4:
For Android developers, this can be much easier. In summary, you can add a xml res file to config your custom certs.
Step 1: open your manifest xml add an attribute.
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>
Step 2: Add network_security_config.xml to res/xml, config certs as you want.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="@raw/extracas"/>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>
Note: this xml can support many other usage, and this solution only works on api24+.
Official reference: here
回答5:
When you initialize an SSLContext you supply an array of TrustManagers. You supply two: a default one that uses the JRE truststore, and another one that uses yours. The delegation model s the wrong answer here.