2012/01/13

Android與Web Services以自簽憑證做雙向SSL

Android與Web Services以自簽憑證做雙向SSL



使用平台:
WebServices: axis1.4
WebServer: Tomcat6.0.20
Android: 2.1 2.2、2.3.1
android-soap lib: kSOAP2-android 2.5.7以上

首先在tomcat的conf/server.xml加入以下設定:
```xml
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11Protocol"
SSLEnabled="true"
maxThreads="150"
minSpareThreads="25"
maxSpareThreads="75"
enableLookups="false"
disableUploadTimeout="true"
acceptCount="100"
scheme="https"
secure="true"
clientAuth="true"
keystoreFile="/var/ks/server_ks.jks"
keystorePass="12345678"
truststoreFile="/var/ks/server_ts.jks"
truststorePass="12345678"
sslProtocol="TLS"
/>
```



第12行 為雙向SSL的重點,需設為true
第15~16行 既然為雙向SSL故server端需要一個含client憑證的truststore

目前我接觸到的keystore type有JKS BKS PKCS12三種

在tomcat是吃jks的(無深入研究,若有錯誤請來信指教),故要產生此格式的keystore

接下來就是產生SSL需要的公私密鑰了

這邊利用keytool在windows下產生

```
@echo off
set SERVER_DN="CN=WSServer, OU=MoA, O=MoA, L=Taipei, S=Taiwan, C=TW"
set CLIENT_DN="CN=MClient, OU=MoA, O=MoA, L=Taipei, S=Taiwan, C=TW"
set KS_PASS=-storepass 12345678
set KEYINFO=-keyalg RSA -keysize 1024 -sigalg MD5withRSA
set VALIDITY=-validity 365
set STORETYPE=-storetype PKCS12
set BKS_PROVIDER_PATH=-providerpath ./bcprov-jdk16-146.jar
set BKS_PROVIDER_CLASS=-providerclass org.bouncycastle.jce.provider.BouncyCastleProvider

echo "產生server用的keystore,內包含server公私密鑰"
keytool -genkey -alias Server -dname %SERVER_DN% %KS_PASS% -keystore server_ks.jks %KEYINFO% -keypass 12345678 %VALIDITY%
echo "產生server用的憑證檔,憑證檔內含server的公鑰"
keytool -export -alias Server -file test_s_axis.cer %KS_PASS% -keystore server_ks.jks
echo "將server用的憑證檔加入到client的信任庫"
keytool -import -alias serverkey -file test_s_axis.cer %KS_PASS% -keystore client_ts.jks -noprompt
echo "將client的信任庫轉換格式 for Android用"
keytool -importkeystore -srckeystore client_ts.jks -destkeystore client_ts.bks -srcstoretype JKS -deststoretype BKS -srcstorepass 12345678 -deststorepass 12345678 %BKS_PROVIDER_PATH% %BKS_PROVIDER_CLASS%
echo "產生client用的keystore"
keytool -genkey -alias Client -dname %CLIENT_DN% %KS_PASS% -keystore client_ks.p12 %KEYINFO% -keypass 12345678 %VALIDITY% %STORETYPE%
echo "產生client用的憑證檔"
keytool -export -alias Client -file test_c_axis.cer %KS_PASS% -keystore client_ks.p12 %STORETYPE%
echo "將client用的憑證檔加入到server的信任庫"
keytool -import -alias clientkey -file test_c_axis.cer %KS_PASS% -keystore server_ts.jks -noprompt
```


第8~9行 為將jks轉為bks的provider可以在這裡下載到
第20、22行 keystore使用pkcs12的類型for android使用

執行完後會產出
server_ks.jks、server_ts.jks、test_s_axis.cer、client_ts.jks、client_ks.p12 client_ts.bks、test_c_axis.cer
7個檔案,其中server_ks.jks、server_ts.jks需放置到tomcat設定檔所設定的位置
client_ks.p12、client_ts.bks需放至android project底下的res/raw路徑下

接下來是android端的部分

MoaX509TrustManager.java

```java
public class MoaX509TrustManager {

public static void allowMoaSSL(KeyStore ts, KeyStore ks) {
SSLContext context = null;
KeyStore trustStore = ts;
KeyStore keyStore = ks;

try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());//X509
tmf.init(trustStore);

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());//X509
kmf.init(keyStore, "12345678".toCharArray());

context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());

} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
}
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
}
}
```



MoaKeyStoreLoader.java

```java
public class MoaKeyStoreLoader {
public static final char[] truststorepass = {'1','2','3','4','5','6','7','8'};
public static final char[] keystorepass = {'1','2','3','4','5','6','7','8'};

public static KeyStore loadTrustStore(InputStream tsis) throws IOException {
KeyStore trustStore = null;
InputStream is = null;
try {
trustStore = KeyStore.getInstance("BKS");//BKS,JKS,PKCS12
is = tsis;
trustStore.load(is, truststorepass);
} catch (NoSuchAlgorithmException e) {
Log.e("SSL", "NoSuchAlgorithmException "+e.toString());
} catch (CertificateException e) {
Log.e("SSL", "CertificateException "+e.toString());
} catch (IOException e) {
Log.e("SSL", "IOException "+e.toString());
} catch (KeyStoreException e) {
Log.e("SSL", "KeyStoreException "+e.toString());
} finally{
is.close();
}
return trustStore;
}

public static KeyStore loadKeyStore(InputStream ksis) throws IOException {
KeyStore trustStore = null;
InputStream is = null;
try {
trustStore = KeyStore.getInstance("PKCS12");
is = ksis;
trustStore.load(is, keystorepass);
} catch (NoSuchAlgorithmException e) {
Log.e("SSL", "NoSuchAlgorithmException "+e.toString());
} catch (CertificateException e) {
Log.e("SSL", "CertificateException "+e.toString());
} catch (IOException e) {
Log.e("SSL", "IOException "+e.toString());
} catch (KeyStoreException e) {
Log.e("SSL", "KeyStoreException "+e.toString());
} finally{
is.close();
}
return trustStore;
}

}
```

第9行與第30行 重點來了

由於android只吃BKS不吃JKS
所以在用keytool產生truststore後
要用額外的provider將jks轉為bks
使android程式可以讀取
另外keystore我試過無法由jks轉為bks
故直接將他生成為PKCS12的格式


在相關的Activity中可用以下方式呼叫:
```java
public ServerConnector sc = ServerConnector.getConnector();

public void onResume(){
try {
sc.setAllowSSL(
MoaKeyStoreLoader.loadTrustStore(getResources().openRawResource(R.raw.client_ts)),
MoaKeyStoreLoader.loadKeyStore(getResources().openRawResource(R.raw.client_ks))
);
} catch (NotFoundException e) {
Log.e("SSL", "NotFoundException"+e.toString());
} catch (IOException e) {
Log.e("SSL", "IOException "+e.toString());
}
}
```


第6行與第7行中的R.raw.*是用keytool生成keystore後複製至project底下的res/raw路徑而android自行產生的


ServerConnector為kSOAP2與web services連接的設定
可能如下:

```java
public class ServerConnector {
private static ServerConnector connector = null;

private ServerConnector(){ }

public static ServerConnector getConnector(){
if(connector==null)
synchronized(ServerConnector.class){
if(connector==null)
connector = new ServerConnector();
}
return connector;
}

public void setAllowSSL(KeyStore ts, KeyStore ks){
KDX509TrustManager.allowKDSSL(ts, ks);
}

public synchronized String getDataFromServer(String METHOD,Proprety pro) throws Exception {
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER10);
envelope.setOutputSoapObject(request);
HttpsTransportSE httpsTransport = new HttpsTransportSE("192.168.2.4",8443,"/HelloAxis/services/MobileService",15000);

SoapPrimitive resultsRequestSOAP = (SoapPrimitive) envelope.getResponse();
ret = resultsRequestSOAP.toString();
}
```


第22行這邊注意是用HttpsTransportSE而不是HttpTransportSE


部分參考自
心情小站
Abhinava's blog
Android Explorations

沒有留言:

張貼留言