LowLevel
LowLevel

Reputation: 1095

Connecting to the Parse Server on a VPS using https (self-sined cert for SSL)

For some reasons Parse users must migrate their Parse environment to a VPS (this is the case for my question) or Heroku, AWS (don't need these platforms), etc. There is a new Parse SDK for Android (1.13.0) which allows to initialize connection using the new Parse interface, as follows:

Parse.initialize(new Parse.Configuration.Builder(this)
                .applicationId("myAppId")
                .clientKey(null) 
                .addNetworkInterceptor(new ParseLogInterceptor())
                .server("https://VPS_STATIC_IP_ADDRESS/parse/").build());

This kind of request is done using the port 443. The appropriate .js (nodejs) connector file has already been edited so that port 443 is locally connected to port 1337 (port-listener) and it works when accessing Parse Server in browser (remotely, of course: from outside VPS) where it's possible to apply a self-signed certificate and go further. But when an Android app (launcher) tries to connect it, it cannot because of self-signed certificate. Is there any possibility from within Parse SDK to apply a self-signed certificate?

P.S. Is it true that there's a bug concerning this issue and that this is the reason why 1.13.1 Parse version has been released? If yes, where is it possible to get the jar-library of this version?

Thank you!

Upvotes: 1

Views: 466

Answers (1)

Croc
Croc

Reputation: 495

I just solved this one - Parse SDK for android does not come with out of the box support in SelfSigned certificates. You need to modify the code yourself.

First Step - The relevant piece of code is in ParseHttpClient

  public static ParseHttpClient createClient(int socketOperationTimeout,
      SSLSessionCache sslSessionCache) {
    String httpClientLibraryName;
    ParseHttpClient httpClient;
    if (hasOkHttpOnClasspath()) {
      httpClientLibraryName = OKHTTP_NAME;
      httpClient =  new ParseOkHttpClient(socketOperationTimeout, sslSessionCache);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      httpClientLibraryName = URLCONNECTION_NAME;
      httpClient =  new ParseURLConnectionHttpClient(socketOperationTimeout, sslSessionCache);
    } else {
      httpClientLibraryName = APACHE_HTTPCLIENT_NAME;
      httpClient =  new ParseApacheHttpClient(socketOperationTimeout, sslSessionCache);
    }
    PLog.i(TAG, "Using " + httpClientLibraryName + " library for networking communication.");
    return httpClient;   }

If your target support is for version more advanced then KITKAT - Then you need to add in ParseURLConnectionHttpClient constructor:

    HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){

            public boolean verify(String hostname, SSLSession session) {
                if(hostname.equals("YOUR TARGET SERVER")) {
                    return true;
                }
                return false;
            }});

In other cases (older versions) the code will fall to the Apache, I was not able to work with it- so I did the following: I added the okhttp library to my app (take version 2.4 - the same one parse indicates in the build , the most recent has different package name) and then the code will step into the first condition since it will find okhttp on the Path. You should probably replace the if conditions order so it will happen only on versions less then KITKAT.

In ParseOkHttpClient add the following selfsigned certificate code:

   public void initCert() {
    try {
        Log.i("PARSE","initCert");

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        String yairCert = "-----BEGIN CERTIFICATE-----\n" +
                YOUR CERTIFICATE HERE
                "-----END CERTIFICATE-----\n";
        InputStream caInput = new ByteArrayInputStream(yairCert.getBytes());
        Certificate ca = null;
        try {
            ca = cf.generateCertificate(caInput);
            System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
        } catch (CertificateException e) {
            e.printStackTrace();
        } finally {
            try {
                caInput.close();
            } catch (IOException e) {
                Log.e("PARSE_BUG","Failure on Cert installing",e);
                e.printStackTrace();
            }
        }

        // Create a KeyStore containing our trusted CAs
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        // Create a TrustManager that trusts the CAs in our KeyStore
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        // Create an SSLContext that uses our TrustManager
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);
        Log.i("PARSE","Initiating Self Signed cert");
        okHttpClient.setSslSocketFactory(context.getSocketFactory());
        try {
            cf = CertificateFactory.getInstance("X.509");
        } catch (CertificateException e) {
            Log.e("PARSE_BUG","Failure on Cert installing",e);
            e.printStackTrace();
        }
    } catch (IOException e) {
        Log.e("PARSE_BUG","Failure on Cert installing",e);
        e.printStackTrace();
    } catch (CertificateException e) {
        Log.e("PARSE_BUG","Failure on Cert installing",e);
        e.printStackTrace();

    } catch (NoSuchAlgorithmException e) {
        Log.e("PARSE_BUG","Failure on Cert installing",e);
        e.printStackTrace();
    } catch (KeyStoreException e) {
        Log.e("PARSE_BUG","Failure on Cert installing",e);
        e.printStackTrace();
    } catch (KeyManagementException e) {
        Log.e("PARSE_BUG","Failure on Cert installing",e);
        e.printStackTrace();
    }

And the final part is the calling this method + verifying hostname , it should happen in the Constructor too. initCert(); okHttpClient.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslSession) { if(s.equals("YOUR TARGET SERVER")) { return true; } return false; } });

Thats it. Build PARSE locally and deploy to your app and it will work like a charm.

Enjoy

Upvotes: 1

Related Questions