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 签名的新服务器证书后,连接已建立。

抱歉浪费您的时间。