I'm writing a small SSL proxy server and keep getting ssl.SSLError: [SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] sslv3 alert certificate unknown (_ssl.c:661) from an android app client but not a browser. I did set ssl.CERT_NONE. Here is my test code:
SSLcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
SSLcontext.load_cert_chain('server.crt', 'server.key')
SSLcontext.verify_mode = ssl.CERT_NONE
SSLcontext.check_hostname = False
s = socket.socket()
s.bind(('127.0.0.1', 443))
s.listen(5)
c = s.accept()[0]
c = SSLcontext.wrap_socket(c, server_side = True)
print c.recv(1024)
Is this because of certificate pinning on the android app or I'm doing something wrong ?
I did set ssl.CERT_NONE
This does not affect how the client verifies the server certificate at all. The server can not instruct the client to not verify the certificate and it would be a serious security issue if the server could do this.
SSLV3_ALERT_CERTIFICATE_UNKNOWN ... from an android app client but not a browser.
It is unknown what kind of certificate you use here. If this is a self-signed one you have probably added it once as trusted to the browser or added an explicit exception - but you did not do this for the Android app. If this is a certificate issued by a public CA then you are probably missing the chain certificates. Desktop browsers often work around this server side problem while most other clients don't.
Related
We need to perform certificate based client authentication to a server accepting only TLS 1.3 connections.
The server is using Apache 2 and HTTP 1.1 and is configured to allow client cert auth but not to enforce it because some resources require client auth while others don't.
We're using OkHttp 4.9.1 on Android 11 to perform the call and we're following the standard docs on how to perform client auth: https://github.com/square/okhttp/tree/master/okhttp-tls
The server however replies with: 403 (auth renegotiation not allowed)
This is in line with TLS 1.3 specification which does not allow auth renegotation unless agreed upon during the initial handshake.
So far we've debugged the connection and inside OkHttp the class RealConnection and it actually performs the Handshake without negotiating a client cert.
Our research so far indicates that this might be due to the server using OPTIONAL ssl client auth but this is not somethign we can change so....
Is there any other option we can pass during OkHttp initialization to force it to perform the initial handshake using the client cert?
in case OkHttp is not a viable option is there any other HTTP client implementation which would allow us to force such authetnication during the initial handshake?
Create a HandshakeCertificates object using its builder. You'll need your private key, client cert and any intermediates on the client side.
The client will also need a root certificate for your server to trust it. The builder has a function to use the built in certificate authorities for that if you like.
private OkHttpClient buildClient(
HeldCertificate heldCertificate, X509Certificate... intermediates) {
HandshakeCertificates.Builder builder = new HandshakeCertificates.Builder()
.addTrustedCertificate(serverRootCa.certificate());
if (heldCertificate != null) {
builder.heldCertificate(heldCertificate, intermediates);
}
HandshakeCertificates handshakeCertificates = builder.build();
return clientTestRule.newClientBuilder()
.sslSocketFactory(
handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
.build();
}
https://github.com/square/okhttp/blob/1ce86f35a9d957bae711fb81cec60abe9f43dda0/okhttp/src/test/java/okhttp3/internal/tls/ClientAuthTest.java#L126
Optional client certificate authentication is no different from required client certificate authentication from the perspective of the client. All what the client sees is a CertificateRequest and as a response it will send some certificates (leaf and chain), which might also be an empty list of certificates. It is up to the server to decide if this is acceptable, i.e. optional simply means that the server accepts that the client sends an empty list of certificates.
The problem thus is not optional or required client certificates, but that the server has not requested a certificate at all in the initial TLS handshake and likely only requests a certificate when accessing a specific path (the requested path can only be seen after the initial handshake). With Apache this is typically the case if client certificate is not requested in the server global configuration but instead in a path specific .htaccess file. Fix would be to move the requirement at the level of the domain and not the path only.
I'm trying to open a secure websocket connection from a Xamarin Android application to an IIS server. It works fine and dandy on every device I've ever used except Samsung Tab E. Even other Samsung devices work fine.
I'm using the Websockets.PCL library and the regular code:
var _webSocket = WebSocketFactory.Create();
_webSocket.OnOpened += _webSocket_Opened;
_webSocket.OnError += _webSocket_Error;
_webSocket.Open("wss://server.name.here/path/also/");
In this case the error handler is called with:
[websockets] javax.net.ssl.SSLException
[websockets] javax.net.ssl.SSLException: Error occured in delegated task:javax.net.ssl.SSLException: Unexpected exception
The device has no issues with SSL connections via Chrome or other apps, not to this server or others. It also has no issues connecting via SignalR to the same IIS server from this application.
It does have an issue when another third party library is trying to check license from a separate website and an error is logged:
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x803b3b28: Failure in SSL library, usually a protocol error
I don't know which address it's trying to contact so can't check what is happening there. But this also works from other devices.
This leads me to believe the Android in the Tab E (v4.4.4, latest available) has something wrong with its SSL implementation since these two connection attempts through javax.net.ssl are failing. But the unexpected exception isn't giving much information.
How to get around this? I wouldn't mind using the websockets implementation SignalR uses (since it works), but as far as I know it's not really exposed as a general use system ready to use.
Additional info
The server does talk TLS1 nicely and sends the whole certificate path as far as I know it, so that shouldn't be the issue, unless the root isn't known (and I would expect to get the handshake failure if that was the case). Testing with openssl shows:
Certificate chain
0 s:/OU=Domain Control Validated/OU=PositiveSSL/CN=our.domain.com
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
1 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
No client certificate CA names sent
Server Temp Key: ECDH, P-256, 256 bits
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-SHA
Server public key is 2048 bit
Unfortunately openssl doesn't support the ยด-ssl2` flag anymore. Online tools say that SSL2 is disabled, SSL3, TLS1, TLS1.1 and TLS1.2 are enabled.
I could try the custom SSLSocketFactory route but have to see how it would go through Xamarin and Websockets.PCL.
I found a solution for my case after searching for SSL issues in Android. Florian Krauthan has written about these things and talks about using the SSLSocketFactory on Android 4.1+ to allow use of TLS1.1 etc.
This led me to another post where he explains how Google Play Services can be used to inject a newer OpenSSL library into the whole application. This method also worked for my issue.
The only thing that needs to be done is to add the following code to the beginning of the main activity's OnCreate() method:
Android.Gms.Security.ProviderInstaller.InstallIfNeeded(this);
After this the application will use Google Play Services' OpenSSL library so all connections work without issues and possible known bugs in the built-in Android OpenSSL library get mitigated since a newer version is used.
Of course this might not be a solution for anyone not already using Google Play Services since it increases the application size tremendously.
Recently I acquired a new domain name to use with an existing Android application. I also bought SSL certificate from a trusted CA (Comodo). When I browse to the new domain with a web browser, everything works as expected - no errors about SSL certificate. Same with HttpUrlConnection, but for some reason Apaches HttpClient generates an SSLException:
javax.net.ssl.SSLException: hostname in certificate didn't match: my.new.domain != my.old.domain OR my.old.domain
What's more interesting, some of the devices that I tried with worked fine for about a week after the change, and stopped working after that. Other devices stopped working right away.
If I use the old domain name in client code, everything works correctly.
I'm using version 4.3.3 from here of the HttpClient for Android. I realize that converting to HttpUrlConnection would indeed solve my problem, but I'm interested in WHY this is happening - from where does the HttpClient pick up the old domain name? Is it some misconfiguration on the server, or does Apaches HttpClient have some sort of internal dns cache? Testing with a fresh emulator instance raised the same exception, so the problem is not related to caching.
My own investigations got stuck - all I could find was instructions for disabling hostname verification completely, or instructions for self-signed certificates.
That's probably because Apache HttpClient does not support SNI (server name indication), where you can have multiple certificates behind the same IP address. This means, that it does not send the target hostname inside the SSL handshake and thus the server has only the target IP address to decide which certificate it should use and just uses the default certificate for the IP - which is probably the wrong one.
I have two domains: foo.net and bar.com. They both have SSL certificates, and they work well in all desktop and mobile browsers. They are hosted on the same server configured with nginx.
However, when I make a request to a domain from within a native android app, it somehow gets the certificate from the wrong domain! This results in an IO Exception:
request = new HttpPost("https://foo.net/api/v1/baz");
request.setHeader("Authorization", "user:pass");
response = httpClient.execute(request);
...
javax.net.ssl.SSLException: hostname in certificate didn't match: <foo.net> != <bar.com> OR <bar.com> OR <www.bar.com>
What would cause android/java to try using the certificate from bar.com when every other measure seems to indicate that the server is correctly configured? Nothing appears in the nginx access or error log. There is no mention of bar.com anywhere in my android project.
Edit: I'm not sure why, but it appears that the server is using the certificate for bar.com for the server IP https://198.245.xx.xxx
The most likely cause for this problem is that the server uses Server Name Indication to choose which certificate to send. If the client doesn't support SNI, the server cannot choose which certificate to send during the SSL/TLS handshake (before any HTTP traffic is sent). SNI is required when you want to use multiple certificates on the same IP address and port, but not all clients support it (notoriously, IE on any version of Windows XP, and a number of mobile browsers).
You're also visibly using the Apache HTTP Client library (not HttpsURLConnection, for which there can be SNI support with some Android versions.
Support for SNI in the Apache HTTP Client library is quite recent, and certainly hasn't made it into the Android stack.
You may find the workaround described in this article useful (although it seems only to work for Android 4.2+).
Another two options would be:
to use a distinct IP address for each host (so as not to need SNI), if you're in control of server, or
to use another HTTP Client library (e.g. HttpsURLConnection).
A solution for Apache, more like a trick:
the SSL certificates are loaded based on the vhost name from /etc/apache2/sites-enabled. So, to trick that check make sure the problematic certificate is loaded first (remember that the vhosts are loaded by name).
It looks like the certificate of foo.net is misconfigured, and is using the same hostname as bar.com
Try to run an online certificate validation tool, like https://www.digicert.com/help/ on foo.net, just to be sure.
I think that you need to regenerate the certificate of foo.net with the right hostname, or reconfigure ngix to make sure that nginx serve the right certificate for the right host.
I'm trying to make a connection to an LDAP server in my Android app, and am using the UnboundID SDK. Recently, a change was made from unsecured to secured LDAP, and I have to change the app accordingly. I have been given the SSL certificates file to validate against. I've already used the file to make a keystore as described here. I've got this keystore file in the assets folder of my app, and am pulling from that. The code below does not currently work, and throws the exception:
LDAPException(resultCode=01 (connect error), errorMessage=('An error occurred while attempting to connect to server place.myserver.com:636: javax.net.ssl.SSLHandShakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found
// code from above link
AssetManager assetManager = getApplicationContext().getAssets();
InputStream keyStoreInputStream = assetManager.open("yourapp.store");
KeyStore trustStore = KeyStore.getInstance("BKS");
trustStore.load(keyStoreInputStream, "myPassword".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);
// my code
SSLUtil sslUtil = new SSLUtil(tmf.getTrustManagers());
LDAPConnection connection = new LDAPConnection(sslUtil.createSSLSocketFactory());
connection.connect("place.myserver.com", 636);
However, the code segment:
SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
LDAPConnection connection = new LDAPConnection(sslUtil.createSSLSocketFactory());
connection.connect("place.myserver.com", 636);
does work (although I was informed by the higher-ups that this would be insecure).
I'm not quite sure as to what exactly I'm doing wrong here, so any help would be appreciated. Also, if there is a better way of accomplishing this than what I'm attempting to do above, feel free to let me know :) I would like to stick with the UnboundID library though, since the rest of the code is already written using that as well, and everything works if I use the TrustAllTrustManager.
It's true that the trust all trust manager isn't secure. It's convenient for testing purposes, but it will allow a bad guy to set up his own server with a certificate he generates for himself and use it to impersonate the real server, or to operate as a man in the middle, intercepting and potentially alerting any communication between the client and the real server. With a more strict trust manager in place, the client should reject the bogus certificate that the fake server will present.
Unfortunately, though, it looks like the trust manager you're trying to use in this case doesn't like the certificate that your server is presenting to it. Because the trust all trust manager allows you to establish the connection, that means that your server does have a certificate and is capable of performing SSL communication, but there's something about that certificate that your trust manager doesn't like. It's almost certainly not an issue with the LDAP SDK, since the same problem should arise with any other LDAP API if you're using the same trust store.
If you look at the result, it has a message of "Trust anchor for certification path not found". This implies that neither the certificate the server is using nor those of any of its issuers was found in the trust store. You'll need to import the server certificate (or the certificate of one of its issuers) into the trust store that you're using. It sounds like you've tried to do that, but since it's not working then something must not be quite right with the way it was done. I'd recommend working wit the directory server administrator to ensure that you're trying to import the right certificate based on the server configuration.