我正在尝试了解Android与服务器的TLS连接。任何人都可以纠正我吗?
启动TLS连接有两种。首先,只有服务器有证书,客户决定信任与否。其次,客户端和服务器都有证书。我是对的吗?
如何在Android设备上为TLS连接生成自定义唯一证书并将其用于连接服务器?我发现只实现了第一种连接。
有人能帮助我吗?
答案 0 :(得分:5)
要通过TLS实现双方身份验证,您需要在服务器端和客户端上拥有密钥库。
我通常使用Portecle工具来创建密钥库,它是一个非常容易使用的基于Java的工具。您可以在portecle.sourceforge.net上下载它
您需要使用该工具为服务器端创建JKS格式的密钥库,并为客户端创建BKS格式的另一个密钥库。格式不同,因为Android本身不支持使用JKS密钥库,只支持BKS,反之亦然。
创建了两个密钥库后,您需要为每个密钥库生成密钥对(公钥和私钥)。 Portecle在工具栏上有一个按钮,名为'工具'这个子菜单。公共密钥算法和大小是RSA 2048位。之后,您需要为证书设置一些参数,例如组织单位,名称,地点等。
现在您有两个密钥库和两个密钥对。
要允许客户端解密服务器接收的消息,必须为客户端密钥库提供服务器密钥库的公钥。要做到这一点,只需右键单击服务器密钥库上的密钥对,然后单击'导出'选项。然后选择证书链'并选择文件系统上的一个位置来存储证书。
现在,您已准备好在客户端密钥库中导入公钥。为此,请点击工具'工具栏并选择“导入可信证书”,然后查找上一步导出的证书文件。将出现一些警告消息,表明您无法建立信任路径,暂且不用担心。
因此,现在您拥有带密钥对的客户端密钥库和服务器证书。在客户端这已经足够了。
现在,有必要将客户端密钥对导入服务器密钥库。在客户端密钥库上,右键单击密钥对,然后选择“导出”。在打开的弹出窗口中选择“私钥和证书'”,然后选择PKCS#12格式。
然后,打开服务器密钥库并使用'导入密钥对'工具的子菜单'工具栏,然后选择上一步导出的密钥对。
请记住,以BKS格式保存客户端密钥库以及以JKS格式保存服务器密钥库非常重要。
好的,这些都是密钥库,现在是时候编写代码了。
让我们从服务器代码开始。下一个片段是从我运行的java Spring项目中提取的。它是一个嵌入式tomcat,所以配置是纯java,很容易找到如何在传统的tomcat配置上配置ssl连接器,所以我只会放入嵌入式版本。
private Connector createSslConnector() {
//print the client keystore
printClientKeystore();
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
try {
//read the keystore from the jar
//and write it to a tmp file
Resource keystoreResource = context.getResource("classpath:config/server.keystore");
byte[] keystoreData = readKeystore(keystoreResource.getInputStream());
File tmpKeystoreFile = File.createTempFile("keystore", "");
writeKeystore(tmpKeystoreFile, keystoreData);
//keystore information
final String keystoreFile = tmpKeystoreFile.getAbsolutePath();
final String keystorePass = "yourKeystorePass";
final String keystoreType = "pkcs12";
final String keystoreProvider = "SunJSSE";
final String keystoreAlias = "comics_tomcat";
connector.setScheme("https");
connector.setAttribute("clientAuth", "true");
connector.setPort(HTTPS_PORT);
connector.setSecure(true);
protocol.setSSLEnabled(true);
//keystore
protocol.setKeystoreFile(keystoreFile);
protocol.setKeystorePass(keystorePass);
protocol.setKeystoreType(keystoreType);
protocol.setProperty("keystoreProvider", keystoreProvider);
protocol.setKeyAlias(keystoreAlias);
//truststore
protocol.setTruststoreFile(keystoreFile);
protocol.setTruststorePass(keystorePass);
protocol.setPort(HTTPS_PORT);
return connector;
}
catch (IOException e) {
LOGGER.error(e.getMessage(), e);
throw new IllegalStateException("cant access keystore: [" + "keystore"
+ "] or truststore: [" + "keystore" + "]", e);
}
}
对于服务器端而言,通过使用' setSecure(true)',你有一个带有安全标志的ssl连接器为true。方法。这允许您使用私钥/公钥身份验证要求用户身份验证。
以下代码用于客户端,它加载密钥库,信任库(通过使用相同的密钥库),配置主机名验证程序以允许连接到特定域,然后打开连接。
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
//load keystore stream
byte[] keystoreData = readInputStream(getAssets().open("client.keystore"));
//load keystore
ByteArrayInputStream bais = new ByteArrayInputStream(keystoreData);
keyStore.load(bais, KEYSTORE_PASSWORD.toCharArray());
//load truststore
bais = new ByteArrayInputStream(keystoreData);
trustStore.load(bais, KEYSTORE_PASSWORD.toCharArray());
//load trustmanager
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
//init keymanager
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, KEYSTORE_PASSWORD.toCharArray());
//create ssl context
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
HostnameVerifier HOSTNAME_VERIFIER = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
List<String> allowedHostnames = new ArrayList<String>();
allowedHostnames.add("pinterest.com");
allowedHostnames.add("192.168.1.43");
allowedHostnames.add("10.0.2.2");
return allowedHostnames.indexOf(hostname) != -1;
}
};
//open https connection
URL url = new URL("https://" + SERVER_URL + ":" + SERVER_PORT + "/api/v1/publication/getDescriptor/" + publicationId);
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
urlConnection.setHostnameVerifier(HOSTNAME_VERIFIER);
//read server response
byte[] serverResult = readInputStream(urlConnection.getInputStream());
如果您有任何疑问,请与我联系!