遇到的问题
前两天在 Andorid 上与使用自签名证书的服务器进行 https 网络通信遇到了问题,主要的问题出在于服务器端的证书不受客户端信任与认证,服务器端也不认识客户端,双方互不认识(在浏览器好歹也会提示用户添加安全证书)。
问题分析
根据需求分析,Android 平台上需要进行双向验证才能够进行正常的 https 通信。而在之前的代码结构中,由于不熟悉 https 沟通方式,错误使用了服务端证书来进行身份识别与验证。
解决方式
进行后续操作的调整,在 centOS 平台上使用 keytool 分别了生成了服务器端证书以及客户端证书,并将客户端证书放置 Android 上,用于连接服务器端时对服务器端的证书进行鉴别与认证。
具体操作如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | 1、生成服务器证书库
  keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore server.keystore -dname "CN=commonName,OU=organizationalUnit,O=Organization,L=Locality,ST=state,c=country" -storepass 123456 -keypass 123456 -keysize 2048
  2、生成客户端证书库
  keytool -validity 365 -genkey -v -alias client -keyalg RSA -storetype PKCS12 -keystore client.p12 -dname "CN=client,OU=organizationalUnit,O=Organization,L=Organization,ST=state,c=country" -storepass 123456 -keypass 123456 -keysize 2048
  3、从客户端证书库中导出客户端证书
  keytool -export -v -alias client -keystore client.p12 -storetype PKCS12 -storepass 123456 -rfc -file client.cer
  4、从服务器证书库中导出服务器证书
  keytool -export -v -alias server -keystore server.keystore -storepass 123456 -rfc -file server.cer
  5、生成客户端信任证书库(由服务端证书生成的证书库)
  keytool -import -v -alias server -file server.cer -keystore client.truststore -storepass 123456 -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
  6、将客户端证书导入到服务器证书库(使得服务器信任客户端证书)
  keytool -import -v -alias client -file client.cer -keystore server.keystore -storepass 123456
   | 
 
keytool 常用参数说明:
| 参数 | 
用途 | 
| -genkey | 
在用户主目录中创建一个默认文件“.keystore” | 
| -validity | 
指定创建的证书有效期为多少天 | 
| -alias | 
产生别名,每个 keystore 都关联一个独一无二的 alias | 
| -keystore | 
指定密钥库的名称 | 
| -keyalg | 
指定密钥的算法 | 
| -keysize | 
指定密钥的长度 | 
| -dname | 
指定证书发行者信息,其中: “CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名 称,ST=州或省份名称,C=单位的两字母国家代码” | 
| -storepass | 
指定密钥库的密码(.keystore密码) | 
| -keypass | 
指定别名条目的密码(私钥密码) | 
| -storetype | 
指定密钥库的存储类型 | 
| -export | 
将 alias 指定的证书导出到文件 | 
| -import | 
将已签名的证书导入密钥库中 | 
| -rfc | 
以Base64的编码格式打印证书 | 
| -file | 
指定导出到文件的文件名 | 
| -v | 
查看密钥库中的证书详细信息 | 
在代码结构上,在 http 通信代码中添加 SSL 通信机制,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
   | public static final String KEY_STORE_TYPE_BKS = "BKS"; public static final String KEY_STORE_TYPE_P12 = "PKCS12"; public static final String KEY_STORE_CLIENT_PATH = "swaypay.p12"; public static final String KEY_STORE_TRUST_PATH = "swaypay.truststore"; public static final String KEY_STORE_PASSWORD = "superssl1"; public static final String KEY_STORE_TRUST_PASSWORD = "superssl1"; public static final String KEY_STORE_TYPE_X509 = "X509"; public static final String SSL_CONTEXT_PROTOCOL = "TLS";
  public void setSSLConnection() {   		         KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);         KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);
    		         InputStream ksIn = ContextHolder.getContext().getAssets().open(KEY_STORE_CLIENT_PATH);         InputStream tsIn = ContextHolder.getContext().getAssets().open(KEY_STORE_TRUST_PATH);      		         keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());   		KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KEY_STORE_TYPE_X509);         keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());      		         trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());         TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());         trustManagerFactory.init(trustStore);      		   		   		sslContext = SSLContext.getInstance(SSL_CONTEXT_PROTOCOL);   		sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); }
  ...     private HttpURLConnection createHttpURLConnection(String targetUrl) {         ...                  if (httpURLConnection instanceof HttpsURLConnection) {             setSSLConnection();             ((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(sslContext.getSocketFactory());           	             ((HttpsURLConnection) httpURLConnection).setHostnameVerifier((String hostname, SSLSession session) -> {                 return true;             });         }         ... }
   | 
 
参考资料:
[1] http://frank-zhu.github.io/android/2014/12/26/android-https-ssl/