与Android应用程序中的客户端证书的HTTPS连接

Kev*_*vek 31 java ssl https android certificate

我正在尝试用我正在编写的Android应用程序中的HTTPS连接替换当前正在运行的HTTP连接.HTTPS连接的额外安全性是必要的,因此我不能忽略此步骤.

我有以下内容:

  1. 服务器配置为建立HTTPS连接,并需要客户端证书
    • 此服务器具有由标准大型CA颁发的证书.简而言之,如果我通过Android中的浏览器访问此连接,它可以正常工作,因为设备信任库可以识别CA. (所以它不是自签名的)
  2. 客户端证书,基本上是自签名的.(由内部CA发布)
  3. 一个Android应用程序,它加载此客户端证书并尝试连接到上述服务器,但具有以下问题/属性:
    • 当服务器被配置为客户端可以连接到服务器要求客户端证书.基本上,如果我使用SSLSocketFactory.getSocketFactory()连接工作正常,但客户端证书是此应用程序规范的必需部分,因此:
    • javax.net.ssl.SSLPeerUnverifiedException: No peer certificate当我尝试连接我的自定义时SSLSocketFactory,客户端会产生异常,但我不完全确定原因.在互联网上搜索各种解决方案后,这个例外似乎有点模棱两可.

以下是客户端的相关代码:

SSLSocketFactory socketFactory = null;

public void onCreate(Bundle savedInstanceState) {
    loadCertificateData();
}

private void loadCertificateData() {
    try {
        File[] pfxFiles = Environment.getExternalStorageDirectory().listFiles(new FileFilter() {
            public boolean accept(File file) {
                if (file.getName().toLowerCase().endsWith("pfx")) {
                    return true;
                }
                return false;
            }
        });

        InputStream certificateStream = null;
        if (pfxFiles.length==1) {
            certificateStream = new FileInputStream(pfxFiles[0]);
        }

        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        char[] password = "somePassword".toCharArray();
        keyStore.load(certificateStream, password);

        System.out.println("I have loaded [" + keyStore.size() + "] certificates");

        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, password);

        socketFactory = new SSLSocketFactory(keyStore);
    } catch (Exceptions e) {
        // Actually a bunch of catch blocks here, but shortened!
    }
}

private void someMethodInvokedToEstablishAHttpsConnection() {
    try {
        HttpParams standardParams = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(standardParams, 5000);
        HttpConnectionParams.setSoTimeout(standardParams, 30000);

        SchemeRegistry schRegistry = new SchemeRegistry();
        schRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schRegistry.register(new Scheme("https", socketFactory, 443));
        ClientConnectionManager connectionManager = new ThreadSafeClientConnManager(standardParams, schRegistry);

        HttpClient client = new DefaultHttpClient(connectionManager, standardParams);
        HttpPost request = new HttpPost();
        request.setURI(new URI("https://TheUrlOfTheServerIWantToConnectTo));
        request.setEntity("Some set of data used by the server serialized into string format");
        HttpResponse response = client.execute(request);
        resultData = EntityUtils.toString(response.getEntity());
    } catch (Exception e) {
        // Catch some exceptions (Actually multiple catch blocks, shortened)
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经验证过,确实是keyStore加载证书并且对此感到满意.

关于HTTPS/SSL连接的阅读,我有两个理论,但是由于这是我的第一次尝试,我对解决这个问题的实际需要感到有些困惑.

据我所知,第一种可能性是我需要使用包含所有标准中间证书和终端证书颁发机构的设备信任库来配置此SSLSocketFactory.也就是说,设备的默认设置是SSLSocketFactory.getSocketFactory()将一些CA加载到工厂的信任库中,用于在发送证书时信任服务器,这就是我的代码失败的原因,因为我没有正确加载信任库.如果这是真的,我最好如何加载这些数据?

第二种可能性是由于客户端证书是自签名的(或由内部证书颁发机构颁发 - 如果我错了,请纠正我,但这些确实相同,对于所有意图和目的) .事实上,我缺少这个信任库,基本上我需要为服务器提供一种方法来使用内部CA验证证书,并且还验证这个内部CA实际上是"可信任的".如果这是真的,我到底想要什么样的东西?我已经看到一些提及这个让我相信这可能是我的问题,就像在这里,但我真的不确定.如果这确实是我的问题,我会从维护内部CA的人那里要求什么,然后我将如何将其添加到我的代码中以便我的HTTPS连接可以工作?

第三个,也是希望不太可能的解决方案,就是我在这里完全错误并错过了一个关键步骤,或者完全忽略了我目前还不知道的部分HTTPS/SSL.如果是这样的话,你能不能给我一点方向,这样我就可以去了解我需要学习什么?

谢谢阅读!

Poo*_*oks 8

有一种更简单的方法来实现@jglouie的解决方案.基本上,如果您使用a SSLContext并将其初始化null为trust manager参数,则应使用默认信任管理器获取SSL上下文.请注意,Android文档中没有记录这一点,但SSLContext.initJava文档说明了这一点

前两个参数中的任何一个都可以为空,在这种情况下,将搜索已安装的安全提供程序以寻找适当工厂的最高优先级实现.

这是代码的样子:

// This can be any protocol supported by your target devices.
// For example "TLSv1.2" is supported by the latest versions of Android
final String SSL_PROTOCOL = "TLS";

try {               
   sslContext = SSLContext.getInstance(SSL_PROTOCOL);

   // Initialize the context with your key manager and the default trust manager 
   // and randomness source
   sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
} catch (NoSuchAlgorithmException e) {
   Log.e(TAG, "Specified SSL protocol not supported! Protocol=" + SSL_PROTOCOL);
   e.printStackTrace();
} catch (KeyManagementException e) {
   Log.e(TAG, "Error setting up the SSL context!");
   e.printStackTrace();
}

// Get the socket factory
socketFactory = sslContext.getSocketFactory();
Run Code Online (Sandbox Code Playgroud)


jgl*_*uie 7

我认为这确实是个问题.

据我所知,第一种可能性是我需要使用包含所有标准中间证书和端点证书颁发机构的设备信任库来配置此SSLSocketFactory

如果这是真的,我最好如何加载这些数据?

尝试这样的事情(你需要让你的套接字工厂使用这个默认的信任管理器):

X509TrustManager manager = null;
FileInputStream fs = null;

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());

try
{
    fs = new FileInputStream(System.getProperty("javax.net.ssl.trustStore")); 
    keyStore.load(fs, null);
}
finally
{
    if (fs != null) { fs.close(); }
}

trustManagerFactory.init(keyStore);
TrustManager[] managers = trustManagerFactory.getTrustManagers();

for (TrustManager tm : managers)
{
    if (tm instanceof X509TrustManager) 
    {
        manager = (X509TrustManager) tm;
        break;
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:请在此处使用代码之前查看Pooks的答案.听起来现在有更好的方法.