我有一个Android应用程序,可以毫无问题地访问安全的Web服务。它使用我自己的类,它扩展了Apache DefaultClientHttp 。
class CustomHttpClient extends DefaultHttpClient {
private final String alias;
private final X509Certificate[] certificates;
private final PrivateKey privateKey;
public CustomHttpClient(String alias, X509Certificate[] certificates, PrivateKey privateKey) {
this.alias = alias;
this.certificates = certificates;
this.privateKey = privateKey;
}
@Override
protected ClientConnectionManager createClientConnectionManager() {
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", newSslSocketFactory(), 443));
return new SingleClientConnManager(getParams(), schemeRegistry);
}
private SSLSocketFactory newSslSocketFactory() {
try {
KeyStore trusted = KeyStore.getInstance("pkcs12");
trusted.load(null);
if (this.alias != null) {
trusted.setKeyEntry(this.alias, privateKey, Constants.KEYSTORE_PASS.toCharArray(), this.certificates);
}
SSLSocketFactory socketFactory = new SSLSocketFactory(trusted, Constants.KEYSTORE_PASS);
socketFactory.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
return socketFactory;
} catch (Exception ex) {
throw new AssertionError(ex);
}
}
}
从 KeyChain 获取别名,证书和 privateKey (我不想合并证书)在应用程序中作为原始文件)。应用程序正常运行,但现在我想重构Http客户端并使用 Retrofit2 。这是我使用 OkHttpClient 的新课程。
public class SslOkHttpClient {
private static final String LOGGER = SslOkHttpClient.class.getName();
private static String alias;
private static Key privateKey;
private static X509Certificate[] certificates;
private static String KEYSTORE_PASS = "password";
public static OkHttpClient getOkHttpClient(Context context) throws GeneralSecurityException, IOException, IllegalStateException {
if (alias == null)
alias = HttpsUtils.getAlias(context);
if (privateKey == null)
privateKey = HttpsUtils.getPrivateKey(context, alias);
if (certificates == null)
certificates = HttpsUtils.getCertificateChain(context, alias);
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.build();
KeyStore ks = getKeystore();
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(getSslSocketFactory(ks), (X509TrustManager) getTrustManager(ks)[0])
.connectionSpecs(Collections.singletonList(spec))
.addInterceptor(interceptor)
.build();
return client;
}
private static SSLSocketFactory getSslSocketFactory(KeyStore keystore) throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, KeyManagementException, IllegalStateException, NoSuchProviderException {
SSLContext sslContext;
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, getTrustManager(keystore), null);
return sslContext.getSocketFactory();
}
private static TrustManager[] getTrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, IllegalStateException, NoSuchProviderException {
TrustManager[] trustManagers;
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keystore);
trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
return trustManagers;
}
private static KeyStore getKeystore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, NoSuchProviderException {
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(null, null);
if (alias != null) {
keystore.setKeyEntry(alias, privateKey, KEYSTORE_PASS.toCharArray(), certificates);
}
return keystore;
}
}
我使用这样的新安全客户端:
private static ServiceData service;
public static ServiceAtestado getInstance(Context context) throws GeneralSecurityException, IOException, IllegalStateException {
if (service == null) {
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(Constants.URL);
Retrofit retrofit = builder
.addConverterFactory(GsonConverterFactory.create())
.client(SslOkHttpClient.getOkHttpClient(context))
.build();
service = retrofit.create(ServiceData.class);
}
return service;
}
尽管私有证书和CA证书与我当前使用的相同,但每次我尝试访问Web服务中的资源时,都会遇到以下异常:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
我认为问题与证书本身无关,因为我可以使用它从设备的浏览器访问web服务,所以我认为问题是Retrofit2的配置。我已经阅读了很多关于使用SSLSocketFactory配置它的教程和文章,所以我认为我做得对,但显然我不是。
我做错了什么?我很感激任何帮助。
答案 0 :(得分:0)
经过几天的努力,我终于意识到旧的代码(有效的代码)与我正在尝试使用Retrofit 2进行的代码并不完全相同。那个代码不使用自定义信任管理器,但系统默认。 对于谁可能感兴趣(包括我自己在未来;-)),在这里你是最后的,简化的工人阶级。
public class SslOkHttpClient {
private static final String LOGGER = SslOkHttpClient.class.getName();
private static String alias;
private static Key privateKey;
private static X509Certificate[] certificates;
private static String KEYSTORE_PASS = "secret";
public static OkHttpClient getOkHttpClient(Context context) throws GeneralSecurityException, IOException, IllegalStateException {
if (alias == null)
alias = HttpsUtils.getAlias(context);
if (privateKey == null)
privateKey = HttpsUtils.getPrivateKey(context, alias);
if (certificates == null)
certificates = HttpsUtils.getCertificateChain(context, alias);
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(getSslSocketFactory(), getAllTrustManager())
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
})
.build();
return client;
}
private static SSLSocketFactory getSslSocketFactory() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, KeyManagementException, IllegalStateException, NoSuchProviderException, UnrecoverableKeyException {
SSLContext sslContext;
sslContext = SSLContext.getInstance("TLSv1.2");
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(null, null);
if (alias != null) {
keystore.setKeyEntry(alias, privateKey, KEYSTORE_PASS.toCharArray(), certificates);
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keystore, KEYSTORE_PASS.toCharArray());
sslContext.init(kmf.getKeyManagers(), null, null);
return sslContext.getSocketFactory();
}
private static X509TrustManager getAllTrustManager() throws KeyStoreException, NoSuchAlgorithmException {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
}
}
HttpUtils 只是一个实用工具类。
public class HttpsUtils {
private static final String KEYCHAIN_PREF_ALIAS = "alias";
private static final String KEYCHAIN_PREF = "keychain";
private static final int MODE_PRIVATE = 0;
private static final String DEFAULT_ALIAS = "";
private static final String LOGGER = HttpsUtils.class.getName();
private HttpsUtils() {
}
/**
* This method returns the alias of the key chain from the application
* preference
*
* @return The alias of the key chain
*/
public static String getAlias(Context context) {
SharedPreferences pref = context.getSharedPreferences(KEYCHAIN_PREF, MODE_PRIVATE);
return pref.getString(KEYCHAIN_PREF_ALIAS, DEFAULT_ALIAS);
}
/**
* This method sets the alias of the key chain to the application preference
*/
public static void setAlias(Context context, String alias) {
SharedPreferences pref = context.getSharedPreferences(KEYCHAIN_PREF, MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putString(KEYCHAIN_PREF_ALIAS, alias);
editor.apply();
}
public static X509Certificate[] getCertificateChain(Context context, String alias) {
try {
return KeyChain.getCertificateChain(context, alias);
} catch (KeyChainException | InterruptedException ex) {
Log.e(LOGGER, ex.getMessage() == null ? context.getResources().getString(R.string.err_no_especificado) : ex.getMessage());
}
return null;
}
public static Key getPrivateKey(Context context, String alias) {
try {
return KeyChain.getPrivateKey(context, alias);
} catch (KeyChainException | InterruptedException ex) {
Log.e(LOGGER, ex.getMessage() == null ? context.getResources().getString(R.string.err_no_especificado) : ex.getMessage());
}
return null;
}
}