fr.ardeconnect.proxy.Service.java Source code

Java tutorial

Introduction

Here is the source code for fr.ardeconnect.proxy.Service.java

Source

/*
* 
* Copyright (C) 2015 Orange Labs
* 
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 
*    http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 
*/

package fr.ardeconnect.proxy;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.json.JSONException;
import org.json.JSONObject;

import fr.ardeconnect.proxy.IRemoteListener;
import fr.ardeconnect.proxy.IRemoteService;
import fr.ardeconnect.proxy.IRemoteServiceInternal;
import fr.ardeconnect.proxy.R;
import fr.ardeconnect.proxy.WebViewActivity;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

/**
 * Openid Connect proxy service class
 *
 */
public class Service extends android.app.Service {

    protected static final String TAG = Service.class.getName();

    final static String EMPTY = "";

    static {
        Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
    }

    int idList = 0;

    class RemoteListener {
        IRemoteListener listener;
        String id;
        String spEnrollUrl;

        RemoteListener(IRemoteListener r) {
            listener = r;
            idList++;
            id = "" + idList;
        }
    };

    List<RemoteListener> RemoteListenerList = new ArrayList<RemoteListener>();

    public static Service theService = null;

    public Service() {
        // android.os.Debug.waitForDebugger();
        System.setProperty("http.keepAlive", "false");
        if (theService == null) {
            theService = this;
        }

    }

    // private final IRemoteServiceBinder mBinder = new IRemoteServiceBinder();

    public class ServiceBinder extends Binder {
        Service getService() {
            return Service.this;
        }
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {

        @Override
        public boolean enrollWithArdeco(IRemoteListener listener, String spEnrollUrl) throws RemoteException {

            // check access
            if (!checkCallingSignature())
                return false;

            // check parameters
            if (listener == null || isEmpty(spEnrollUrl)) {
                // invalid parameters
                return false;
            }

            // android.os.Debug.waitForDebugger();

            Logd(TAG, "enrollWithArdeco begin");

            showNotProtectedNotifIcon();

            RemoteListener rl;
            synchronized (RemoteListenerList) {
                rl = new RemoteListener(listener);
                rl.spEnrollUrl = spEnrollUrl;
                RemoteListenerList.add(rl);
            }

            // launch request
            // launch webview

            // init intent
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setClass(theService, WebViewActivity.class);

            // prepare request parameters
            intent.putExtra("id", rl.id);

            intent.putExtra("spEnrollUrl", spEnrollUrl);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
                    | Intent.FLAG_ACTIVITY_NO_ANIMATION);

            // display webview
            theService.startActivity(intent);

            Logd(TAG, "enrollWithArdeco end");

            return true;

        }

        private boolean FORCE_CHECK = true;

        // check calling process signature, if not valid return false
        // possibility of hack
        private boolean checkCallingSignature() {
            if (FORCE_CHECK)
                return true;

            // get package name
            String pName = getPackageManager().getNameForUid(Binder.getCallingUid());
            X509Certificate certCalling = PackInfo.getCertificate(Service.this, pName);
            X509Certificate certProxy = PackInfo.getCertificate(Service.this, Service.this.getPackageName());

            Logd(TAG, "certCalling : " + pName);
            Logd(TAG, "" + certCalling.toString());
            Logd(TAG, "certProxy : " + Service.this.getPackageName());
            Logd(TAG, "" + certProxy.toString());

            //certCalling.
            try {
                certCalling.verify(RsaKeyRootCA.pubRsaKey);
            } catch (Exception e) {
                Logd(TAG, "checkCallingSignature failed");
                return false;
            }

            // uid are not the same
            Logd(TAG, "checkCallingSignature success");
            return true;
        }
    };

    void resetCookies() {
        // init intent
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setClass(Service.this, WebViewActivity.class);

        intent.putExtra("resetcookies", "true");
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
                | Intent.FLAG_ACTIVITY_NO_ANIMATION);

        // launch webview
        startActivity(intent);
    }

    long getSecretPathThreshold() {
        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
        long l = sharedPrefs.getLong("threshold", 80);
        return l;
    }

    void setSecretPathThreshold(long val) {
        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
        Editor editor = sharedPrefs.edit();
        editor.putLong("threshold", val);
        editor.commit();
        Logd(TAG, "threshold: " + val);
    }

    // internal remote service for internal webview
    private final IRemoteServiceInternal.Stub mBinderInternal = new IRemoteServiceInternal.Stub() {

        @Override
        public void cancel(String id, boolean user) throws RemoteException {

            // check caller uid
            if (checkCallingUid() == false) {
                // caller are not from inside this app
                Logd(TAG, "setTokens not from inside this app");
                return;
            }

            RemoteListener rl = null;
            synchronized (RemoteListenerList) {
                for (int i = RemoteListenerList.size() - 1; i >= 0; i--) {
                    RemoteListener r = RemoteListenerList.get(i);
                    if (r.id.compareTo(id) == 0) {
                        rl = r;
                        RemoteListenerList.remove(i);
                        break;
                    }
                }
            }

            if (rl != null) {
                rl.listener.handleSpCode(EMPTY, null, EMPTY, user);
            }

            // hideNotifIcon();
        }

        @Override
        public void setSpCode(String id, String spCode, Bundle spParameter) {

            // check caller uid
            if (checkCallingUid() == false) {
                // caller are not from inside this app
                Logd(TAG, "doRedirect not from inside this app");
                return;
            }

            Logd(TAG, "getSpCode begin");

            if (isEmpty(id)) {
                Logd(TAG, "getSpCode end no ID");
                // hideNotifIcon();
                return;
            }

            RemoteListener rl = null;
            synchronized (RemoteListenerList) {
                for (int i = RemoteListenerList.size() - 1; i >= 0; i--) {
                    RemoteListener r = RemoteListenerList.get(i);
                    if (r.id.compareTo(id) == 0) {
                        RemoteListenerList.remove(i);
                        rl = r;
                        break;
                    }
                }
            }

            if (rl != null) {
                // get code from SP
                try {
                    rl.listener.handleSpCode(spCode, spParameter, EMPTY, false);
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

            Logd(TAG, "getSpCode end");
            hideNotifIcon();
        }

        @Override
        public void resetCookies() throws RemoteException {

            // check caller uid
            if (checkCallingUid() == false) {
                // caller are not from inside this app
                Logd(TAG, "resetCookies not from inside this app");
                return;
            }

            // clear cookies
            // android.webkit.CookieManager.getInstance().removeAllCookie();
        }

        // check calling process uid for possibility of hack,
        // if different from current service uid then return false
        private boolean checkCallingUid() {
            // check uid
            if (android.os.Process.myUid() == Binder.getCallingUid()) {
                return true;
            }

            // uid are not the same
            return false;
        }

    };

    @Override
    public void onCreate() {
        super.onCreate();
    }

    // service termination
    @Override
    public void onDestroy() {
        hideNotifIcon();
        super.onDestroy();
    }

    // service starting
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return Service.START_NOT_STICKY;
    }

    // new connection to service
    @Override
    public IBinder onBind(Intent intent) {
        // Select the interface to return.  If your service only implements
        // a single interface, you can just return it here without checking
        // the Intent.
        if (IRemoteService.class.getName().equals(intent.getAction())) {
            showProtectedNotifIcon();
            Logd(TAG, "onBind Service");
            return mBinder;
        }
        if (IRemoteServiceInternal.class.getName().equals(intent.getAction())) {
            Logd(TAG, "onBind ServiceInternal");
            return mBinderInternal;
        }
        return null;
    }

    // get a string from a json object
    String getFromJS(JSONObject jo, String name) {
        Log.d(TAG, "getFromJS " + name);
        if (jo != null) {
            try {
                return jo.getString(name);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    void toast(final String msg, final int duration) {
        new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                android.widget.Toast.makeText(theService, msg,
                        duration == 0 ? android.widget.Toast.LENGTH_SHORT : android.widget.Toast.LENGTH_LONG)
                        .show();
            }
        });
    }

    private void showProtectedNotifIcon() {
        showNotification(true);
    }

    private void showNotProtectedNotifIcon() {
        showNotification(false);
    }

    private void showNotification(boolean bProtect) {
        Logd(TAG, "show protected icon " + bProtect);

        // this is it, we'll build the notification!
        // in the addAction method, if you don't want any icon, just set the first param to 0
        Notification mNotification = null;

        if (bProtect) {
            mNotification = new Notification.Builder(this)

                    .setContentTitle("ARDECO PROXY").setContentText("idle").setSmallIcon(R.drawable.ardeco_off)
                    .setAutoCancel(false).build();
        } else {
            mNotification = new Notification.Builder(this)

                    .setContentTitle("ARDECO PROXY").setContentText("active").setSmallIcon(R.drawable.ardeco_on)
                    .setAutoCancel(false).build();
        }

        // to make it non clearable
        mNotification.flags |= Notification.FLAG_NO_CLEAR;

        NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        // If you want to hide the notification after it was selected, do the code below
        // myNotification.flags |= Notification.FLAG_AUTO_CANCEL;

        notificationManager.notify(0, mNotification);
    }

    private void hideNotifIcon() {
        Logd(TAG, "hideNotifIcon");

        if (Context.NOTIFICATION_SERVICE != null) {
            String ns = Context.NOTIFICATION_SERVICE;
            NotificationManager nMgr = (NotificationManager) getApplicationContext().getSystemService(ns);
            nMgr.cancel(0);
        }
    }

    void Logd(String tag, String msg) {
        if (tag != null && msg != null)
            Log.d(tag, msg);
    }

    // RSA key used by the Secure Proxy
    // here for demo only, must be stored in a secure element
    final static class RsaKeyRootCA {

        // RSA 512
        // static private String _rsaNs = "174534779388221555027193756296996728577785706931882582500865462191276991828908964783626120005370658240337053195460241456112314652761908126414596521509072923990652465127860275450913335454113631551447701718552251290927858625416498836268258006496356478412330739471783248956660708147504866504092613087913865419941";
        static private String _rsaNs = "137615577516537198807817310937859988960161111436453997102401245051154742289581142838915071683987098078139191437791365924492551201544861234198101701401783768985281927492472872690006772264611369208880786262064142563461367635639697166679608227153800046014276971655300012833498776331150965310640667300049423086361";
        static private String _rsaEs = "65537";

        static public PublicKey pubRsaKey;
    }

    // init the key from the big numbers above
    static {
        BigInteger rsaN = null;
        BigInteger rsaE = null;
        try {
            rsaN = new BigInteger(RsaKeyRootCA._rsaNs);
            rsaE = new BigInteger(RsaKeyRootCA._rsaEs);
        } catch (Exception e) {
            e.printStackTrace();
        }

        RSAPublicKeySpec pubRsaSpec = new RSAPublicKeySpec(rsaN, rsaE);
        RsaKeyRootCA.pubRsaKey = null;

        try {
            KeyFactory keyfact = KeyFactory.getInstance("RSA", "SC");
            RsaKeyRootCA.pubRsaKey = keyfact.generatePublic(pubRsaSpec);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // get a HTTP connector
    static public HttpURLConnection getHUC(String address) {
        HttpURLConnection http = null;
        try {
            URL url = new URL(address);

            if (url.getProtocol().equalsIgnoreCase("https")) {
                // only use trustAllHosts and DO_NOT_VERIFY in development
                // process
                trustAllHosts();
                HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
                https.setHostnameVerifier(DO_NOT_VERIFY);
                http = https;
            } else {
                http = (HttpURLConnection) url.openConnection();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return http;
    }

    // always verify the host - dont check for certificate
    final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    };

    /**
     * WARNING : only use in development environment,
     * DO NOT USE in production or commercial environments !!!
     * Trust every server - do not check for any certificate
     */
    private static void trustAllHosts() {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s)
                    throws CertificateException {

            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certificates, String s)
                    throws CertificateException {

            }

            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[] {};
            }

        } };

        // Install the all-trusting trust manager
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String convertStreamToString(InputStream is) {
        /*
         * To convert the InputStream to String we use the
         * BufferedReader.readLine() method. We iterate until the BufferedReader
         * return null which means there's no more data to read. Each line will
         * appended to a StringBuilder and returned as String.
         */
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

    // return true is string is null or empty, false otherwise
    private static boolean isEmpty(String s) {
        if (s == null || s.length() == 0)
            return true;
        return false;
    }
}