SSL相互认证的Android客户端FAIL接受服务器证书,但服务器不会获得客户端证书 [英] SSL mutual authentication FAIL on Android Client accepts servers certificate but server does not get the client cert
问题描述
我想建立一个Linux服务器和Android APP之间的相互认证的SSL。
到目前为止,我已经能够获取应用程序与服务器证书的工作通过SSL通信,但一旦我的服务器设置为只接受它停止工作,客户端证书。服务器配置似乎确定,但我有种被卡住。我最好的猜测是,客户端证书没有被正确presented到服务器,但还是不知道接下来该怎么进行测试。我试着用客户端的质子交换膜在我的OS X的钥匙串,但浏览器似乎并没有与该证书的工作。然后再次服务器证书完美的作品,因为我可以实现HTTPS连接和应用程序接受我的签名服务器证书。
I am trying to set up a mutually authenticated SSL between a Linux Server and Android APP. So far, I have been able to get the app to work with the server certificate communicate via SSL but once I set the server to only accept client certificates it stops working. The server configuration seems ok, but I am a kind of stuck. My best guess is that the client certificate is not correctly being presented to the server but got no idea how to test it next. I tried using the .pem for the client in my OS X keychain but the browsers does not seem to work with that certificate. Then again the Server certificate works perfectly because I can achieve https connection and the APP Accepts my unsigned server certificate.
在code我一直在我现在用的就是各种教程的结合,回答这是主要的我已经收藏:
The code I have been I am using is the combination of various tutorials, answers this is the main ones I have bookmarked:
- HTTP://www.androidhive .INFO / 2012/05 /如何做连接,Android的使用的PHP,MySQL的/
- 自签名的SSL接受Android的
- Securing通讯[真实性,隐私权与诚信]移动应用?
- (对于服务器配置)<一个href=\"http://security.stackexchange.com/questions/34897/configure-ssl-mutual-two-way-authentication\">http://security.stackexchange.com/questions/34897/configure-ssl-mutual-two-way-authentication
- http://www.androidhive.info/2012/05/how-to-connect-android-with-php-mysql/
- Self Signed SSL acceptance Android
- Securing communication [Authenticity, Privacy & Integrity] with mobile app?
- (For the Server Config) http://security.stackexchange.com/questions/34897/configure-ssl-mutual-two-way-authentication
这是我使用的连接两大类:
1)本类处理JSON解析并执行请求
This are the two main classes I am using for the connection: 1) This class handles the JSON parsing and does the REQUESTS
package edu.hci.additional;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.List;
import android.content.Context;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
public class JSONParser {
static InputStream is = null;
static JSONObject jObj = null;
static String json = "";
// constructor
public JSONParser() {
}
// function get json from url
// by making HTTP POST or GET mehtod
public JSONObject makeHttpRequest(String url, String method,
List<NameValuePair> params, Context context) {
// Making HTTP request
try {
// check for request method
if(method == "POST"){
// request method is POST
// defaultHttpClient
SecureHttpClient httpClient = new SecureHttpClient(context);
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new UrlEncodedFormEntity(params));
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
is = httpEntity.getContent();
}else if(method == "GET"){
// request method is GET
SecureHttpClient httpClient = new SecureHttpClient(context);
String paramString = URLEncodedUtils.format(params, "utf-8");
url += "?" + paramString;
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
is = httpEntity.getContent();
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
is, "iso-8859-1"), 8);
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
is.close();
json = sb.toString();
} catch (Exception e) {
Log.e("Buffer Error", "Error converting result " + e.toString());
}
// try parse the string to a JSON object
try {
jObj = new JSONObject(json);
} catch (JSONException e) {
Log.e("JSON Parser", "Error parsing data " + e.toString());
}
// return JSON String
return jObj;
}
}
这第二类处理SSL验证:
This second class handles the SSL Authentication:
package edu.hci.additional;
import android.content.Context;
import android.util.Log;
import edu.hci.R;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
public class SecureHttpClient extends DefaultHttpClient {
private static Context appContext = null;
private static HttpParams params = null;
private static SchemeRegistry schmReg = null;
private static Scheme httpsScheme = null;
private static Scheme httpScheme = null;
private static String TAG = "MyHttpClient";
public SecureHttpClient(Context myContext) {
appContext = myContext;
if (httpScheme == null || httpsScheme == null) {
httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
httpsScheme = new Scheme("https", mySSLSocketFactory(), 443);
}
getConnectionManager().getSchemeRegistry().register(httpScheme);
getConnectionManager().getSchemeRegistry().register(httpsScheme);
}
private SSLSocketFactory mySSLSocketFactory() {
SSLSocketFactory ret = null;
try {
final KeyStore clientCert = KeyStore.getInstance("BKS");
final KeyStore serverCert = KeyStore.getInstance("BKS");
final InputStream client_inputStream = appContext.getResources().openRawResource(R.raw.authclientcerts);
final InputStream server_inputStream = appContext.getResources().openRawResource(R.raw.certs);
clientCert.load(client_inputStream, appContext.getString(R.string.client_store_pass).toCharArray());
serverCert.load(server_inputStream, appContext.getString(R.string.server_store_pass).toCharArray());
String client_password = appContext.getString(R.string.client_store_pass);
server_inputStream.close();
client_inputStream.close();
ret = new SSLSocketFactory(clientCert,client_password,serverCert);
} catch (UnrecoverableKeyException ex) {
Log.d(TAG, ex.getMessage());
} catch (KeyStoreException ex) {
Log.d(TAG, ex.getMessage());
} catch (KeyManagementException ex) {
Log.d(TAG, ex.getMessage());
} catch (NoSuchAlgorithmException ex) {
Log.d(TAG, ex.getMessage());
} catch (IOException ex) {
Log.d(TAG, ex.getMessage());
} catch (Exception ex) {
Log.d(TAG, ex.getMessage());
} finally {
return ret;
}
}
}
要创建我用这个命令的OpenSSL键:
To Create the keys I used openssl with this command:
openssl req -nodes -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 500
要拿到钥匙BKS为Android我用充气城堡bcprov-jdk15on-150.jar位于:的 http://www.bouncycastle.org/latest_releases.html
To get the keys to BKS for Android I used the bouncy castle bcprov-jdk15on-150.jar located at: http://www.bouncycastle.org/latest_releases.html
和使用的命令:
keytool -import -v -trustcacerts -alias 0 -file ~/cert.pem -keystore ~/Downloads/authclientcerts.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath ~/Downloads/bcprov-jdk15on-150.jar -storepass passWORD
我加了
最后行/etc/httpd/conf.d/ssl.conf,要求客户端证书,并检查证书的有效性(匹配我创建了客户端证书,)在Fedora的19是:
Finally the lines I added to /etc/httpd/conf.d/ssl.conf to require the client certificate and check for the certificate validity (which match the client certificate I created) in Fedora 19 are:
...
SSLVerifyClient require
SSLVerifyDepth 5
...
<Location />
SSLRequire ( %{SSL_CLIENT_S_DN_O} eq "Develop" \
and %{SSL_CLIENT_S_DN_OU} in {"Staff", "Operations", "Dev"} )
</Location>
...
SSLOptions +FakeBasicAuth +StrictRequire
我尝试了很多组合对这个配置文件,并在同一个结果结束twrowing我一个SSLPeerUnverifiedException:没有对方的证书异常。我评论此线路上的服务器的SSL配置文件和所有的伟大工程,但服务器接受所有客户端这不是我所需要的。
I tried a lot of combinations on this config file and all ended in the same result twrowing me a "SSLPeerUnverifiedException: No peer certificate" Exception. I comment this lines on the SSL config file of the server and all works great but the server accepts all clients which is not what I need.
在此先感谢:)
更新
@EJP 的答案的伎俩
首先,我用的证书添加到正确的(在/ etc / PKI / TLS /证书/)路径并加载它:
重命名证书:MV CA-andr.pem CA-andr.crt
而现在加载证书:
First I had to add the certificate to the correct ( /etc/pki/tls/certs/ )path and load it by using: rename the cert: mv ca-andr.pem ca-andr.crt And now load the certificate:
ln -s ca-andr.crt $( openssl x509 -hash -noout -in ca-andr.crt )".0"
这将创建一个类似f3f24175.0
This will create a openSSL readable symlink with a name similar to "f3f24175.0"
然后我在/etc/httpd/conf.d/ssl.conf配置文件中设置新的证书文件。
Then I set the new Certificate file in the /etc/httpd/conf.d/ssl.conf Configuration file.
…
SSLCACertificateFile /etc/pki/tls/certs/f2f62175.0
…
现在重新启动HTTP服务和
如果证书加载测试:
Now restart the http service and test the if the certificate is loaded with:
openssl verify -CApath /etc/pki/tls/certs/ f2f62175.0
如果一切正常,你应该看到:
If all is ok you should see:
f3f24175.0:OK
f3f24175.0: OK
和可以结束与测试:
openssl s_client -connect example.com:443 -CApath /etc/pki/tls/certs
这应该返回信任的客户端证书列表(如果你看到你添加的,是工作)
This should return the list of trusted client certificates (If you see the one you added, is working)
现在问题的第二部分是,我的authclientcerts.BKS din't包含私钥,所以我提供的密码从未使用和服务器不会验证证书。所以我出口我的密钥和证书以PKCS12并相应更新了JAVA code。
Now the second part of the problem was that my authclientcerts.BKS din’t contain the private key, so the password I supplied was never used and the server would not authenticate the cert. So I exported my key and cert to pkcs12 and updated the JAVA code accordingly.
导出命令:
openssl pkcs12 -export -in ~/cert.pem -inkey ~/key.pem > android_client_p12.p12
然后,我改变了SecureHttpClient.java类的各个部分,使与PKCS12代替BKS客户端证书。
Then the I changed the parts of the SecureHttpClient.java Class to make the client certificate with PKCS12 instead of BKS.
要改变从BKS密钥库类型PKCS12
我取代:
To change the key store type from BKS to PKCS12 I Replaced:
final KeyStore clientCert = KeyStore.getInstance("BKS");
有关这一点:
final KeyStore clientCert = KeyStore.getInstance("PKCS12");
然后我更新到位于水库的实际密钥存储文件的引用/生/
通过更换:
And then I updated the references to the actual key store files located on res/raw/ By Replacing:
final InputStream client_inputStream = appContext.getResources().openRawResource(R.raw.authclientcerts);
有关这一点:
final InputStream client_inputStream = appContext.getResources().openRawResource(R.raw.android_client_p12);
这奏效了:D
推荐答案
在服务器要求客户端证书,它提供了CA的列表,它会接受签名的证书。如果客户端不具有由其中的一个签名的证书,它不会在答复发送证书。如果服务器配置为要求客户端证书,而不是只是想之一,它就会关闭连接。
When the server asks for the client certificate, it provides a list of CAs that it will accept certificates signed by. If the client doesn't have a certificate signed by one of them, it won't send a certificate in reply. If the server is configured to require a client certificate, as opposed to just wanting one, it will then close the connection.
所以,确保客户拥有一个证书是可以接受的服务器的信任。
So, ensure that the client has a certificate which is acceptable to the server's truststore.
这篇关于SSL相互认证的Android客户端FAIL接受服务器证书,但服务器不会获得客户端证书的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!