Android 牛轧糖上未找到信任锚
Trust anchor not found on Android Nougat
所以我理解 Android Nougat 在处理用户添加的 CA 证书方面所做的更改。
我有一个 android 应用程序,它打开一个 SSLSocket 到某个自行安装的服务器上的服务器实例 运行。这个概念在 Android 6 之前一直运行良好。我仍然可以在模拟器中重现它。
现在奇怪的是它不适用于版本 7,尽管我进行了所有必需的更改:
这是我的应用程序清单的摘录:
<application
...
android:networkSecurityConfig="@xml/network_security_config" >
这是正在引用的 xml 文件:
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<!-- Trust preinstalled CAs -->
<certificates src="system" />
<!-- Additionally trust user added CAs -->
<certificates src="user" />
</trust-anchors>
</base-config>
但是我仍然收到著名的“未找到信任锚”消息。
根 CA 已安装在设备上。我已经尝试将 "VPN and apps" 和 "Wifi" 作为目的 - 在这两种情况下都不成功。然而,我仍然有点不确定 "purpose" 在 Android 中到底发生了什么变化。
服务器的证书由该根 CA 签名,可通过其证书的通用名称字段中规定的主机名访问,并且在有效日期字段内,因此未过期。我从事证书工作很长时间了,所以我不是第一次听说这些概念。
我没有使用任何中间权限。根 CA 是我自己的,因此直接签署服务器证书。
我仍然无法在版本 7 中建立连接。
清单的另一段摘录:
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="25" />
还有一个来自 gradle 配置:
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.jens.homecontrol.client.android"
minSdkVersion 11
targetSdkVersion 25
}
有人知道这里出了什么问题吗?
编辑:包括建立连接的代码:
protected Socket serverSocket = null;
public boolean connectToServer(InetSocketAddress server, int timeout)
{
if(serverSocket == null)
{
Miscellaneous.logEvent("Trying server " + server.getHostName() + ":" + String.valueOf(server.getPort()) + " with timeout " + String.valueOf(timeout) + "...", 2);
try
{
if(this.useSsl)
{
Miscellaneous.logEvent("Connecting with SSL", 3);
SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
serverSocket = sslSocketFactory.createSocket();
/*
* Possible protocols in total:
* SSLv2Hello
* TLSv1
* TLSv1.1
* TLSv1.2
*/
SSLSocket sslSocket = ((SSLSocket)serverSocket);
String[] desiredProtocolsToSet = { "TLSv1.2", "TLSv1.1", "TLSv1" };
final String desiredCipherSuitesToSet[] = {
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA"
};
String[] supportedProtocols = sslSocket.getSupportedProtocols();
ArrayList<String> actualProtocolsToSet = new ArrayList<String>();
for(String dp : desiredProtocolsToSet)
for(String sp : supportedProtocols)
if(dp.equalsIgnoreCase(sp))
actualProtocolsToSet.add(dp);
String[] supportedCipherSuites = sslSocket.getSupportedCipherSuites();
ArrayList<String> actualCipherSuitesToSet = new ArrayList<String>();
for(String dcs : desiredCipherSuitesToSet)
for(String scs : supportedCipherSuites)
if(dcs.equalsIgnoreCase(scs))
actualCipherSuitesToSet.add(dcs);
sslSocket.setEnabledProtocols(actualProtocolsToSet.toArray(new String[actualProtocolsToSet.size()]));
sslSocket.setEnabledCipherSuites(actualCipherSuitesToSet.toArray(new String[actualCipherSuitesToSet.size()]));
Miscellaneous.logEvent("SUPPORTED PROTOCOLS:", 5);
for(String s : actualProtocolsToSet)
Miscellaneous.logEvent(s, 5);
Miscellaneous.logEvent("SUPPORTED CIPHER SUITES:", 5);
for(String s : actualCipherSuitesToSet)
Miscellaneous.logEvent(s, 5);
}
else
{
Miscellaneous.logEvent("Connecting without SSL", 3);
serverSocket = new Socket();
}
if(Settings.readTimeout > 0)
serverSocket.setSoTimeout(Settings.readTimeout); // Sets the timeout for read operations
serverSocket.connect(server, timeout); // Sets the timeout for the connection operation
if(serverSocket.isConnected())
{
Miscellaneous.logEvent("Connected to server.", 1);
return true;
}
else
return false;
}
catch (UnknownHostException e)
{
setLastErrorMessage(getErrorMessage("connectionFailedUnknownHost") + ": " + server.getHostName());
e.printStackTrace();
}
catch(ConnectException e)
{
String addition = "";
addition = "(" + server.getHostName() + ":" + String.valueOf(server.getPort()) + ")";
if(e.getMessage().contains("ENETUNREACH"))
setLastErrorMessage(getErrorMessage("connectionFailedNetworkUnreachable"));
else
setLastErrorMessage(getErrorMessage("connectionFailedPortClosed") + addition);
e.printStackTrace();
}
catch(SocketTimeoutException e)
{
setLastErrorMessage(getErrorMessage("timeoutWhileConnecting") + ": " + e.getMessage());
}
catch (IOException e)
{
setLastErrorMessage(getErrorMessage("connectionFailed") + ": " + e.getMessage());
e.printStackTrace();
}
catch(Exception e)
{
setLastErrorMessage(getErrorMessage("unknownProbemDuringConnection") + ": " + e.getMessage());
e.printStackTrace();
}
}
else
{
Miscellaneous.logEvent("Already connected.", 3);
return true;
}
return false;
}
}
public boolean startSslHandshake()
{
try
{
if(serverSocket instanceof SSLSocket)
{
Miscellaneous.logEvent("Initiating SSL handshake with server.", 1);
((SSLSocket)serverSocket).startHandshake();
return true;
}
}
catch(javax.net.ssl.SSLHandshakeException e)
{
setLastErrorMessage(getErrorMessage("sslHandShakeException") + ": " + e.getMessage());
e.printStackTrace();
}
catch (IOException e)
{
setLastErrorMessage(getErrorMessage("sslHandShakeException") + ": " + e.getMessage());
e.printStackTrace();
}
return false;
}
首先调用 connectToServer 例程。然后是startHandshake函数。我知道我不必手动执行此操作。但是因为它们是从 AsyncTasks 调用的,所以我想更好地控制状态,这样我就可以显示更好的状态消息。
嗯,这很尴尬...
我听从了你非常好的建议,使用 HttpURLConnection 来缩小范围。由于那也失败了,我使用网络服务对服务器证书进行了广泛检查。
它没有揭示任何真正的问题,但提到服务器证书仍在使用 SHA1。这让我想起我很久以前就更新了根证书。不幸的是,该节点的服务器证书仍然由旧根证书签名(从技术上讲,它在日期方面仍然有效)。安装在客户端(包括模拟器)上的只是新的根证书。由于相同的通用名称,我没有注意到这一点。只有 v6 模拟器还安装了旧的根证书。
生成由新 CA 签名的新服务器证书后,连接已建立。
抱歉浪费您的时间。
所以我理解 Android Nougat 在处理用户添加的 CA 证书方面所做的更改。
我有一个 android 应用程序,它打开一个 SSLSocket 到某个自行安装的服务器上的服务器实例 运行。这个概念在 Android 6 之前一直运行良好。我仍然可以在模拟器中重现它。 现在奇怪的是它不适用于版本 7,尽管我进行了所有必需的更改:
这是我的应用程序清单的摘录:
<application
...
android:networkSecurityConfig="@xml/network_security_config" >
这是正在引用的 xml 文件:
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<!-- Trust preinstalled CAs -->
<certificates src="system" />
<!-- Additionally trust user added CAs -->
<certificates src="user" />
</trust-anchors>
</base-config>
但是我仍然收到著名的“未找到信任锚”消息。
根 CA 已安装在设备上。我已经尝试将 "VPN and apps" 和 "Wifi" 作为目的 - 在这两种情况下都不成功。然而,我仍然有点不确定 "purpose" 在 Android 中到底发生了什么变化。 服务器的证书由该根 CA 签名,可通过其证书的通用名称字段中规定的主机名访问,并且在有效日期字段内,因此未过期。我从事证书工作很长时间了,所以我不是第一次听说这些概念。
我没有使用任何中间权限。根 CA 是我自己的,因此直接签署服务器证书。
我仍然无法在版本 7 中建立连接。
清单的另一段摘录:
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="25" />
还有一个来自 gradle 配置:
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.jens.homecontrol.client.android"
minSdkVersion 11
targetSdkVersion 25
}
有人知道这里出了什么问题吗?
编辑:包括建立连接的代码:
protected Socket serverSocket = null;
public boolean connectToServer(InetSocketAddress server, int timeout)
{
if(serverSocket == null)
{
Miscellaneous.logEvent("Trying server " + server.getHostName() + ":" + String.valueOf(server.getPort()) + " with timeout " + String.valueOf(timeout) + "...", 2);
try
{
if(this.useSsl)
{
Miscellaneous.logEvent("Connecting with SSL", 3);
SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
serverSocket = sslSocketFactory.createSocket();
/*
* Possible protocols in total:
* SSLv2Hello
* TLSv1
* TLSv1.1
* TLSv1.2
*/
SSLSocket sslSocket = ((SSLSocket)serverSocket);
String[] desiredProtocolsToSet = { "TLSv1.2", "TLSv1.1", "TLSv1" };
final String desiredCipherSuitesToSet[] = {
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA"
};
String[] supportedProtocols = sslSocket.getSupportedProtocols();
ArrayList<String> actualProtocolsToSet = new ArrayList<String>();
for(String dp : desiredProtocolsToSet)
for(String sp : supportedProtocols)
if(dp.equalsIgnoreCase(sp))
actualProtocolsToSet.add(dp);
String[] supportedCipherSuites = sslSocket.getSupportedCipherSuites();
ArrayList<String> actualCipherSuitesToSet = new ArrayList<String>();
for(String dcs : desiredCipherSuitesToSet)
for(String scs : supportedCipherSuites)
if(dcs.equalsIgnoreCase(scs))
actualCipherSuitesToSet.add(dcs);
sslSocket.setEnabledProtocols(actualProtocolsToSet.toArray(new String[actualProtocolsToSet.size()]));
sslSocket.setEnabledCipherSuites(actualCipherSuitesToSet.toArray(new String[actualCipherSuitesToSet.size()]));
Miscellaneous.logEvent("SUPPORTED PROTOCOLS:", 5);
for(String s : actualProtocolsToSet)
Miscellaneous.logEvent(s, 5);
Miscellaneous.logEvent("SUPPORTED CIPHER SUITES:", 5);
for(String s : actualCipherSuitesToSet)
Miscellaneous.logEvent(s, 5);
}
else
{
Miscellaneous.logEvent("Connecting without SSL", 3);
serverSocket = new Socket();
}
if(Settings.readTimeout > 0)
serverSocket.setSoTimeout(Settings.readTimeout); // Sets the timeout for read operations
serverSocket.connect(server, timeout); // Sets the timeout for the connection operation
if(serverSocket.isConnected())
{
Miscellaneous.logEvent("Connected to server.", 1);
return true;
}
else
return false;
}
catch (UnknownHostException e)
{
setLastErrorMessage(getErrorMessage("connectionFailedUnknownHost") + ": " + server.getHostName());
e.printStackTrace();
}
catch(ConnectException e)
{
String addition = "";
addition = "(" + server.getHostName() + ":" + String.valueOf(server.getPort()) + ")";
if(e.getMessage().contains("ENETUNREACH"))
setLastErrorMessage(getErrorMessage("connectionFailedNetworkUnreachable"));
else
setLastErrorMessage(getErrorMessage("connectionFailedPortClosed") + addition);
e.printStackTrace();
}
catch(SocketTimeoutException e)
{
setLastErrorMessage(getErrorMessage("timeoutWhileConnecting") + ": " + e.getMessage());
}
catch (IOException e)
{
setLastErrorMessage(getErrorMessage("connectionFailed") + ": " + e.getMessage());
e.printStackTrace();
}
catch(Exception e)
{
setLastErrorMessage(getErrorMessage("unknownProbemDuringConnection") + ": " + e.getMessage());
e.printStackTrace();
}
}
else
{
Miscellaneous.logEvent("Already connected.", 3);
return true;
}
return false;
}
}
public boolean startSslHandshake()
{
try
{
if(serverSocket instanceof SSLSocket)
{
Miscellaneous.logEvent("Initiating SSL handshake with server.", 1);
((SSLSocket)serverSocket).startHandshake();
return true;
}
}
catch(javax.net.ssl.SSLHandshakeException e)
{
setLastErrorMessage(getErrorMessage("sslHandShakeException") + ": " + e.getMessage());
e.printStackTrace();
}
catch (IOException e)
{
setLastErrorMessage(getErrorMessage("sslHandShakeException") + ": " + e.getMessage());
e.printStackTrace();
}
return false;
}
首先调用 connectToServer 例程。然后是startHandshake函数。我知道我不必手动执行此操作。但是因为它们是从 AsyncTasks 调用的,所以我想更好地控制状态,这样我就可以显示更好的状态消息。
嗯,这很尴尬...
我听从了你非常好的建议,使用 HttpURLConnection 来缩小范围。由于那也失败了,我使用网络服务对服务器证书进行了广泛检查。 它没有揭示任何真正的问题,但提到服务器证书仍在使用 SHA1。这让我想起我很久以前就更新了根证书。不幸的是,该节点的服务器证书仍然由旧根证书签名(从技术上讲,它在日期方面仍然有效)。安装在客户端(包括模拟器)上的只是新的根证书。由于相同的通用名称,我没有注意到这一点。只有 v6 模拟器还安装了旧的根证书。 生成由新 CA 签名的新服务器证书后,连接已建立。
抱歉浪费您的时间。