使用Retrofit 2访问安全Web服务时获取CertPathValidatorException

时间:2017-06-30 17:48:16

标签: android web-services security ssl retrofit2

我有一个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配置它的教程和文章,所以我认为我做得对,但显然我不是。

我做错了什么?我很感激任何帮助。

1 个答案:

答案 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;
}
}