在Android中使用自签名证书实现https连接的正确方法是什么?

时间:2018-09-12 11:56:22

标签: android ssl ssl-certificate self-signed

我几乎在Stack Overflow上看到了与此主题相关的所有问题,但找不到解决方案。

根据documentation,我们可以使用自签名证书,如下所示:

// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(new FileInputStream("mycert.crt"));
Certificate ca;
try {
    ca = cf.generateCertificate(caInput);
    System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
    caInput.close();
}

// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);

// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);

// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);

// Tell the URLConnection to use a SocketFactory from our SSLContext
URL url = new URL("https://ip/address");
HttpsURLConnection urlConnection =
    (HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);

但这会抛出java.security.cert.CertPathValidatorException:

  

javax.net.ssl.SSLHandshakeException:   java.security.cert.CertPathValidatorException:的信任锚   找不到认证路径。

也尝试过:

InputStream keystoreInput = context.getResources().openRawResource(R.raw.cert_keystore);

KeyStore keyStore = KeyStore.getInstance("bks");
keyStore.load(keystoreInput, "pass".toCharArray());

KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, "pass".toCharArray());

TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(keyStore);

SSLContext context= SSLContext.getInstance("TLS");


context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);


URL url = new URL("https://ip/address");
HttpsURLConnection urlConnection =
    (HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();

但这会产生相同的错误。

我还确定证书有效,因为我可以按以下方式发布请求:

curl -v -s -k --key mykey.key--cert mycert.crt -X GET "https://ip/path"

在Android中使用自签名证书实现https连接的正确方法是什么(无需额外的库,仅使用HttpsUrlConnection)?

注意:我不想使用任何库,包括OkHttpApache等。

更新

最后找到了解决方案,这是主要部分:

InputStream keystoreInput = context.getResources().openRawResource(R.raw.cert_keystore);

KeyStore keyStore = KeyStore.getInstance("bks");
keyStore.load(keystoreInput, "pass".toCharArray());

KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, "pass".toCharArray());


SSLContext context= SSLContext.getInstance("TLS");


context.init(kmf.getKeyManagers(),  new TrustManager[] {new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }}, null);


URL url = new URL("https://ip/address");
HttpsURLConnection urlConnection =
    (HttpsURLConnection)url.openConnection();

urlConnection.setSSLSocketFactory(context.getSocketFactory());

// skip hostname security check
urlConnection.setHostnameVerifier(new AllowAllHostnameVerifier());
InputStream in = urlConnection.getInputStream();

但是我不确定是否存在使用自签名证书实现HTTPS连接的更安全方法。

TrustManager是系统用来验证来自服务器的证书的系统,并且通过从具有一个或多个CA的KeyStore创建一个证书,这些将是该{{ 1}}。” doc

因此,我们需要实现TrustManager,但是我们应该怎么做呢? 任何建议都会有所帮助。

谢谢

1 个答案:

答案 0 :(得分:0)

在WebServiceHelper类中,请使用以下方法:

public class WebServiceHelper {
public static final String ENDPOINT = "your address";
private static WebServiceHelper instance;
private Services service;
private Context mContext;

private OkHttpClient getMyOkHttpClient(String serverUrl) {
    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    if (BuildConfig.DEBUG) {
        builder.interceptors().add((new HttpLoggingInterceptor()).setLevel(HttpLoggingInterceptor.Level.BODY));
    } else {
        builder.interceptors().add((new HttpLoggingInterceptor()).setLevel(HttpLoggingInterceptor.Level.NONE));
    }
    builder.cookieJar(new JavaNetCookieJar(new CookieManager()))
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(120, TimeUnit.SECONDS);

    if ((serverUrl.toLowerCase()).contains("https")) initUnSafeSSL(builder);
    return builder.build();
}

private void initUnSafeSSL(OkHttpClient.Builder builder1) {
    try {
        TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return new java.security.cert.X509Certificate[]{};
                    }
                }
        };
        final SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        builder1.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
        builder1.hostnameVerifier((hostname, session) -> true);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

private WebServiceHelper(Context context) {
    mContext = context;
    Retrofit retrofit = createAdapter(ENDPOINT).build();
    service = retrofit.create(Services.class);
}

public static WebServiceHelper getInstance(Context context) {
    if (instance == null) {
        instance = new WebServiceHelper(context);
    }
    return instance;
}

private Retrofit.Builder createAdapter(String serverChoice) {
    return new Retrofit.Builder()
            .baseUrl(serverChoice)
            .client(getMyOkHttpClient(serverChoice))
            .addConverterFactory(GsonConverterFactory.create());
}