so I understand the changes made to Android Nougat in regards to how user added CA certificates are being handled.
I have an android application that opens an SSLSocket to a server instance running on some self installed server. That concept has been working fine until Android 6. I can still reproduce this in the emulator. Now the weird thing is that it isn't working with version 7, although I made all required changes:
This is an excerpt from my application's manifest:
<application
...
android:networkSecurityConfig="@xml/network_security_config" >
And this is the xml file being referenced:
<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>
However I'm still getting the famous trust anchor not found message.
The root CA is installed on the device. I've tried this with both "VPN and apps" and "Wifi" as purpose - unsuccessful in both cases. However I'm still a bit unsure what this "purpose" really changes inside Android. The server's certificate is signed by that root CA, is accessed via the hostname stated in the common name field of its cert and is inside the validity date fields, so not expired. I have been working with certificates for a long time, so I haven't heard the concepts for the first time.
I am not using any intermediate authorities. The root CA is my own and is therefore signing server certs directly.
Still I am unable to establish a connection in version 7.
Another excerpt from the manifest:
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="25" />
And one from the gradle config:
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.jens.homecontrol.client.android"
minSdkVersion 11
targetSdkVersion 25
}
Does anybody have an idea what's wrong here?
Edit: Included code to establish connection:
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;
}
First the connectToServer routine is called. Then the startHandshake function. I know I wouldn't have to manually do that. But since they are being called from AsyncTasks I wanted to have a better control over the status so I can display a better status message.
Well, this is embarassing...
I followed your very good suggestion to use an HttpURLConnection to narrow it down. As that also failed I used a webservice to do a broad check against the server certificate. It didn't reveal any real problems, but mentioned the server cert was still using SHA1. That reminded me I had renewed the root certificate quite a while ago. Unfortunately that node's server certificate was still signed by the OLD root cert (which technically is still valid in terms of dates). Installed on the clients (including the emulator) was only the NEW root cert. Because of equal common names I hadn't noticed this. It was only the v6 emulator which also still had the old root cert installed. After generating a new server certificate signed by the new CA the connection was established.
Sorry for wasting your time.