Asked  7 Months ago    Answers:  5   Viewed   30 times

I'm trying to make HTTPS connections, using HttpClient lib, but the problem is that, since the certificate isn't signed by a recognized Certificate Authority (CA) like Verisign,GlobalSIgn, etc., listed on the set of Android Trusted Certificates, I keep getting javax.net.ssl.SSLException: Not trusted server certificate.

I've seen solutions where you simply accept all certificates, but what if I want to ask the user?

I want to get a dialog similar to that of the browser, letting the user decide to continue or not. Preferably I'd like to use the same certificatestore as the browser. Any ideas?

 Answers

92

The first thing you need to do is to set the level of verification. Such levels is not so much:

  • ALLOW_ALL_HOSTNAME_VERIFIER
  • BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
  • STRICT_HOSTNAME_VERIFIER

Although the method setHostnameVerifier() is obsolete for new library apache, but for version in Android SDK is normal. And so we take ALLOW_ALL_HOSTNAME_VERIFIER and set it in the method factory SSLSocketFactory.setHostnameVerifier().

Next, You need set our factory for the protocol to https. To do this, simply call the SchemeRegistry.register() method.

Then you need to create a DefaultHttpClient with SingleClientConnManager. Also in the code below you can see that on default will also use our flag (ALLOW_ALL_HOSTNAME_VERIFIER) by the method HttpsURLConnection.setDefaultHostnameVerifier()

Below code works for me:

HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;

DefaultHttpClient client = new DefaultHttpClient();

SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("https", socketFactory, 443));
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams());

// Set verifier     
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);

// Example send http request
final String url = "https://encrypted.google.com/";
HttpPost httpPost = new HttpPost(url);
HttpResponse response = httpClient.execute(httpPost);
Tuesday, June 1, 2021
 
simPod
answered 7 Months ago
45

By default, Cocoa refuses all SSL connections when the certificate is invalid.

However, you can force them to accept also invalid certificates. The method depends on which library/framework you are using. For example:

  • For NSURLConnection, check this answer.
  • For ASIHTTPRequest, you need to set the property validatesSecureCertificate to NO.
  • For AFNetworking, you can check the code to use in this page
  • For CFNetwork, the low-level Foundation framework, check this sample code.
  • For SURLConnection, which looks like you're using, you need to follow the same instructions for NSURLConnection. Indeed, SURLConnection is just a subclass of NSURLConnection.

Important note:
The code above, to accept any kind of SSL certificate, even if invalid, is a serious security risk. Basically, it makes the whole SSL useless. As a consequence, you should use that code only during development, if you really need to test with SSL connections.
Please also note that Apple will reject any application submitted to the App Store that accepts invalid SSL certificates.

Thursday, June 24, 2021
 
Parfait
answered 6 Months ago
25

Updates:

  • I've never been an expert at this matter, the following is only a workaround and might not be secure, use it at your own risk
  • This post is 3+ years old, so it may be outdated by now (code will not compile) but you should find be able to find the updated approach or official docs saying certain parts are deprecated or removed

Thank noloader for pointing me in the correction direction. I solved my issue using the following:

String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);// my question shows how to get 'ca'
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
    TrustManagerFactory.getDefaultAlgorithm());
// Initialise the TMF as you normally would, for example:
tmf.init(ca); 

TrustManager[] trustManagers = tmf.getTrustManagers();
final X509TrustManager origTrustmanager = (X509TrustManager)trustManagers[0];

TrustManager[] wrappedTrustManagers = new TrustManager[]{
   new X509TrustManager() {
       public java.security.cert.X509Certificate[] getAcceptedIssuers() {
          return origTrustmanager.getAcceptedIssuers();
       }

       public void checkClientTrusted(X509Certificate[] certs, String authType) {
           origTrustmanager.checkClientTrusted(certs, authType);
       }

       public void checkServerTrusted(X509Certificate[] certs, String authType) {
           try {
               origTrustmanager.checkServerTrusted(certs, authType);
           } catch (CertificateExpiredException e) {
               // Do what you need to do, log to Crashlytics?
           }
       }
   }
};

SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, wrappedTrustManagers, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());  

Out of the 3 certificates found for the site, mentioned in my question, the one that worked for me was the VeriSign Class 3 Secure Server CA - G3

Tuesday, June 29, 2021
 
Bono
answered 6 Months ago
53

When using the AndroidHttpClient to make REST requests via HTTPS, how can I specify which SSL protocols and ciphers to use?

I don't believe you can do it with AndroidHttpClient. Everything I've done to harden the channel (like cipher lists, certificate pinning, and public key pinning) required a custom class somewhere, whether it was SSLSocketFactory or X509TrustManager. That's Java and that's Android. See How to override the cipherlist sent to the server by Android when using HttpsURLConnection?.

Thursday, August 12, 2021
 
TV Nath
answered 4 Months ago
33

I have found a way to solve/simply this.

make-certs.sh

#!/bin/bash

FQDN=`hostname`
rm server.key server.crt
openssl genrsa -out server.key 2048
openssl req -nodes -newkey rsa:2048 -keyout server.key -out server.csr -subj "/C=GB/ST=Street/L=City/O=Organisation/OU=Authority/CN=${FQDN}"
openssl x509 -req -days 1024 -in server.csr -signkey server.key -out server.crt
rm server.csr

api-server.js

// Import libraries
var express = require('express');
var server = express();
var bodyParser = require('body-parser')
var https = require('https');
var fs = require('fs');

// Server setting
var port = process.env.PORT || 8080;

// Register body-parser
server.use(bodyParser.json());
server.use(bodyParser.urlencoded({ extended: true }));

// Configure router
var router = express.Router();
server.use('/api/v1', router);

// Create https server & run
https.createServer({
    key: fs.readFileSync('server.key'),
    cert: fs.readFileSync('server.crt')
}, server).listen(port, function() {
    console.log('API Server Started On Port %d', port);
});

// Register routes
router.get('/', function(req, res) {
    res.json({ success: true });
});

This now works.

Tuesday, November 23, 2021
 
Amumu
answered 2 Weeks ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share