diff --git a/app/src/main/java/se/leap/bitmaskclient/BaseConfigurationWizard.java b/app/src/main/java/se/leap/bitmaskclient/BaseConfigurationWizard.java
index 21520dc44cffef70be8c27df26a54125d81aee80..1d6754993cfffe6510aaca0b78280a5d06fcd36b 100644
--- a/app/src/main/java/se/leap/bitmaskclient/BaseConfigurationWizard.java
+++ b/app/src/main/java/se/leap/bitmaskclient/BaseConfigurationWizard.java
@@ -56,6 +56,7 @@ import se.leap.bitmaskclient.userstatus.SessionDialog;
 import static android.view.View.GONE;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
+import static se.leap.bitmaskclient.ProviderApiBase.ERRORS;
 
 /**
  * abstract base Activity that builds and shows the list of known available providers.
@@ -184,10 +185,10 @@ public abstract class BaseConfigurationWizard extends Activity
         // by the height of mProgressbar (and the height of the first list item)
         mProgressBar.setVisibility(INVISIBLE);
         progressbar_description.setVisibility(INVISIBLE);
-
+        mProgressBar.setProgress(0);
     }
 
-    private void showProgressBar() {
+    protected void showProgressBar() {
         mProgressBar.setVisibility(VISIBLE);
         progressbar_description.setVisibility(VISIBLE);
     }
@@ -231,12 +232,11 @@ public abstract class BaseConfigurationWizard extends Activity
             }
         } else if (resultCode == ProviderAPI.PROVIDER_NOK) {
             mConfigState.setAction(PROVIDER_NOT_SET);
-            hideProgressBar();
             preferences.edit().remove(Provider.KEY).apply();
 
             setResult(RESULT_CANCELED, mConfigState);
 
-            String reason_to_fail = resultData.getString(ProviderAPI.ERRORS);
+            String reason_to_fail = resultData.getString(ERRORS);
             showDownloadFailedDialog(reason_to_fail);
         } else if (resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) {
             mProgressBar.incrementProgressBy(1);
@@ -293,7 +293,9 @@ public abstract class BaseConfigurationWizard extends Activity
         cancelSettingUpProvider();
     }
 
+    @Override
     public void cancelSettingUpProvider() {
+        hideProgressBar();
         mConfigState.setAction(PROVIDER_NOT_SET);
         adapter.showAllProviders();
         preferences.edit().remove(Provider.KEY).remove(Constants.PROVIDER_ALLOW_ANONYMOUS).remove(Constants.PROVIDER_KEY).apply();
@@ -369,18 +371,24 @@ public abstract class BaseConfigurationWizard extends Activity
     /**
      * Shows an error dialog, if configuring of a provider failed.
      *
-     * @param reason_to_fail
+     * @param reasonToFail
      */
-    public void showDownloadFailedDialog(String reason_to_fail) {
+    public void showDownloadFailedDialog(String reasonToFail) {
         try {
             FragmentTransaction fragment_transaction = fragment_manager.removePreviousFragment(DownloadFailedDialog.TAG);
-
-            DialogFragment newFragment = DownloadFailedDialog.newInstance(reason_to_fail);
+            DialogFragment newFragment;
+            try {
+                JSONObject errorJson = new JSONObject(reasonToFail);
+                newFragment = DownloadFailedDialog.newInstance(errorJson);
+            } catch (JSONException e) {
+                e.printStackTrace();
+                newFragment = DownloadFailedDialog.newInstance(reasonToFail);
+            }
             newFragment.show(fragment_transaction, DownloadFailedDialog.TAG);
         } catch (IllegalStateException e) {
             e.printStackTrace();
             mConfigState.setAction(PENDING_SHOW_FAILED_DIALOG);
-            mConfigState.putExtra(REASON_TO_FAIL, reason_to_fail);
+            mConfigState.putExtra(REASON_TO_FAIL, reasonToFail);
         }
 
     }
diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java
index fd1e2080ecf85588befddb52ed4b6280eaa26b78..ed527a54a676603650984906116870d1992d4fab 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java
@@ -27,6 +27,8 @@ import java.security.cert.*;
 import java.security.interfaces.*;
 import java.security.spec.*;
 
+import static android.R.attr.name;
+
 /**
  * Stores constants, and implements auxiliary methods used across all LEAP Android classes.
  *
@@ -34,6 +36,7 @@ import java.security.spec.*;
  * @author MeanderingCode
  */
 public class ConfigHelper {
+    private static final String TAG = ConfigHelper.class.getName();
     private static KeyStore keystore_trusted;
 
     final public static String NG_1024 =
@@ -42,7 +45,7 @@ public class ConfigHelper {
 
     public static boolean checkErroneousDownload(String downloaded_string) {
         try {
-            if (new JSONObject(downloaded_string).has(ProviderAPI.ERRORS) || downloaded_string.isEmpty()) {
+            if (downloaded_string == null || downloaded_string.isEmpty() || new JSONObject(downloaded_string).has(ProviderAPI.ERRORS)) {
                 return true;
             } else {
                 return false;
@@ -99,6 +102,38 @@ public class ConfigHelper {
         return (X509Certificate) certificate;
     }
 
+
+    public static String loadInputStreamAsString(InputStream inputStream) {
+        BufferedReader in = null;
+        try {
+            StringBuilder buf = new StringBuilder();
+            in = new BufferedReader(new InputStreamReader(inputStream));
+
+            String str;
+            boolean isFirst = true;
+            while ( (str = in.readLine()) != null ) {
+                if (isFirst)
+                    isFirst = false;
+                else
+                    buf.append('\n');
+                buf.append(str);
+            }
+            return buf.toString();
+        } catch (IOException e) {
+            Log.e(TAG, "Error opening asset " + name);
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "Error closing asset " + name);
+                }
+            }
+        }
+
+        return null;
+    }
+
     protected static RSAPrivateKey parseRsaKeyFromString(String rsaKeyString) {
         RSAPrivateKey key = null;
         try {
@@ -126,6 +161,18 @@ public class ConfigHelper {
         return key;
     }
 
+    public static String base64toHex(String base64_input) {
+        byte[] byteArray = Base64.decode(base64_input, Base64.DEFAULT);
+        int readBytes = byteArray.length;
+        StringBuffer hexData = new StringBuffer();
+        int onebyte;
+        for (int i = 0; i < readBytes; i++) {
+            onebyte = ((0x000000ff & byteArray[i]) | 0xffffff00);
+            hexData.append(Integer.toHexString(onebyte).substring(6));
+        }
+        return hexData.toString();
+    }
+    
     /**
      * Adds a new X509 certificate given its input stream and its provider name
      *
diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
index f1e7b3bdee957b281f9b2cc82dfa77334a2c25c5..861ce801ffa0ba3eeae6f9f2efb14bda8f401ae7 100644
--- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
+++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
@@ -38,9 +38,13 @@ import org.json.JSONObject;
 
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 import butterknife.ButterKnife;
 import butterknife.InjectView;
+import de.blinkt.openvpn.core.VpnStatus;
 import se.leap.bitmaskclient.userstatus.SessionDialog;
 import se.leap.bitmaskclient.userstatus.User;
 import se.leap.bitmaskclient.userstatus.UserStatusFragment;
@@ -104,6 +108,11 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
             handleVersion();
         }
 
+        // initialize app necessities
+        ProviderAPICommand.initialize(this);
+        VpnStatus.initLogCache(getApplicationContext().getCacheDir());
+        User.init(getString(R.string.default_username));
+
         prepareEIP(savedInstanceState);
     }
 
@@ -147,6 +156,7 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
         try {
             provider.setUrl(new URL(preferences.getString(Provider.MAIN_URL, "")));
             provider.define(new JSONObject(preferences.getString(Provider.KEY, "")));
+            provider.setCACert(preferences.getString(Provider.CA_CERT, ""));
         } catch (MalformedURLException | JSONException e) {
             e.printStackTrace();
         }
@@ -246,10 +256,9 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
     }
     @SuppressLint("CommitPrefEdits")
     private void providerToPreferences(Provider provider) {
-        //FIXME: figure out why .commit() is used and try refactor that cause, currently runs on UI thread
-        preferences.edit().putBoolean(Constants.PROVIDER_CONFIGURED, true).commit();
-        preferences.edit().putString(Provider.MAIN_URL, provider.mainUrl().toString()).apply();
-        preferences.edit().putString(Provider.KEY, provider.definition().toString()).apply();
+        preferences.edit().putBoolean(Constants.PROVIDER_CONFIGURED, true).
+                putString(Provider.MAIN_URL, provider.getMainUrl().toString()).
+                putString(Provider.KEY, provider.getDefinition().toString()).apply();
     }
 
     private void configErrorDialog() {
@@ -399,7 +408,25 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
     private void switchProvider() {
         if (provider.hasEIP()) eip_fragment.stopEipIfPossible();
 
-        preferences.edit().clear().apply();
+        Map<String, ?> allEntries = preferences.getAll();
+        List<String> lastProvidersKeys = new ArrayList<>();
+        for (Map.Entry<String, ?> entry : allEntries.entrySet()) {
+            //sort out all preferences that don't belong to the last provider
+            if (entry.getKey().startsWith(Provider.KEY + ".") ||
+                    entry.getKey().startsWith(Provider.CA_CERT + ".") ||
+                    entry.getKey().startsWith(Provider.CA_CERT_FINGERPRINT + ".")
+                    ) {
+                continue;
+            }
+            lastProvidersKeys.add(entry.getKey());
+        }
+
+        SharedPreferences.Editor preferenceEditor = preferences.edit();
+        for (String key : lastProvidersKeys) {
+            preferenceEditor.remove(key);
+        }
+        preferenceEditor.apply();
+
         switching_provider = false;
         startActivityForResult(new Intent(this, ConfigurationWizard.class), SWITCH_PROVIDER);
     }
diff --git a/app/src/main/java/se/leap/bitmaskclient/DefaultedURL.java b/app/src/main/java/se/leap/bitmaskclient/DefaultedURL.java
index 8daa7d8c27081bc7871225b7f01758ffcd2e8882..52c797a4d22c6c87a6e84b6c932e227ea96cad59 100644
--- a/app/src/main/java/se/leap/bitmaskclient/DefaultedURL.java
+++ b/app/src/main/java/se/leap/bitmaskclient/DefaultedURL.java
@@ -31,6 +31,7 @@ public class DefaultedURL {
         return url;
     }
 
+    @Override
     public String toString() {
         return url.toString();
     }
diff --git a/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java b/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java
index da32dbd42a32927791433c36ef2b5fe40db91872..6f6a14de1632185fbbea32415575839f2612836f 100644
--- a/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java
+++ b/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java
@@ -20,6 +20,16 @@ import android.app.*;
 import android.content.*;
 import android.os.*;
 
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import se.leap.bitmaskclient.userstatus.SessionDialog;
+
+import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.DEFAULT;
+import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.valueOf;
+import static se.leap.bitmaskclient.ProviderApiBase.ERRORID;
+import static se.leap.bitmaskclient.ProviderApiBase.ERRORS;
+
 /**
  * Implements a dialog to show why a download failed.
  *
@@ -29,6 +39,13 @@ public class DownloadFailedDialog extends DialogFragment {
 
     public static String TAG = "downloaded_failed_dialog";
     private String reason_to_fail;
+    private DOWNLOAD_ERRORS downloadError = DEFAULT;
+    public enum DOWNLOAD_ERRORS {
+        DEFAULT,
+        ERROR_CORRUPTED_PROVIDER_JSON,
+        ERROR_INVALID_CERTIFICATE,
+        ERROR_CERTIFICATE_PINNING
+    }
 
     /**
      * @return a new instance of this DialogFragment.
@@ -39,32 +56,79 @@ public class DownloadFailedDialog extends DialogFragment {
         return dialog_fragment;
     }
 
+    /**
+     * @return a new instance of this DialogFragment.
+     */
+    public static DialogFragment newInstance(JSONObject errorJson) {
+        DownloadFailedDialog dialog_fragment = new DownloadFailedDialog();
+        try {
+            if (errorJson.has(ERRORS)) {
+                dialog_fragment.reason_to_fail = errorJson.getString(ERRORS);
+            } else {
+                //default error msg
+                dialog_fragment.reason_to_fail = dialog_fragment.getString(R.string.error_io_exception_user_message);
+            }
+
+            if (errorJson.has(ERRORID)) {
+                dialog_fragment.downloadError = valueOf(errorJson.getString(ERRORID));
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            dialog_fragment.reason_to_fail = dialog_fragment.getString(R.string.error_io_exception_user_message);
+        }
+        return dialog_fragment;
+    }
+
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
-
         builder.setMessage(reason_to_fail)
-                .setPositiveButton(R.string.retry, new DialogInterface.OnClickListener() {
-                    public void onClick(DialogInterface dialog, int id) {
+                .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int id) {
+                interface_with_ConfigurationWizard.cancelSettingUpProvider();
+                dialog.dismiss();
+            }
+        });
+        switch (downloadError) {
+            case ERROR_CORRUPTED_PROVIDER_JSON:
+                builder.setPositiveButton(R.string.update_provider_details, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
                         dismiss();
-                        interface_with_ConfigurationWizard.retrySetUpProvider();
+                        interface_with_ConfigurationWizard.updateProviderDetails();
                     }
-                })
-                .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
-                    public void onClick(DialogInterface dialog, int id) {
-                        interface_with_ConfigurationWizard.cancelSettingUpProvider();
-                        dialog.dismiss();
+                });
+                break;
+            case ERROR_CERTIFICATE_PINNING:
+            case ERROR_INVALID_CERTIFICATE:
+                builder.setPositiveButton(R.string.update_certificate, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        dismiss();
+                        interface_with_ConfigurationWizard.updateProviderDetails();
                     }
                 });
+                break;
+            default:
+                builder.setPositiveButton(R.string.retry, new DialogInterface.OnClickListener() {
+                            public void onClick(DialogInterface dialog, int id) {
+                                dismiss();
+                                interface_with_ConfigurationWizard.retrySetUpProvider();
+                            }
+                        });
+                break;
+        }
 
         // Create the AlertDialog object and return it
         return builder.create();
     }
 
     public interface DownloadFailedDialogInterface {
-        public void retrySetUpProvider();
+        void retrySetUpProvider();
+
+        void cancelSettingUpProvider();
 
-        public void cancelSettingUpProvider();
+        void updateProviderDetails();
     }
 
     DownloadFailedDialogInterface interface_with_ConfigurationWizard;
diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java
index 559b47d1851dfd8554be5b93072fad3eb3efffc0..71a0e1497bf283a88bfbd981313ac340aaf16eff 100644
--- a/app/src/main/java/se/leap/bitmaskclient/Provider.java
+++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java
@@ -18,9 +18,11 @@ package se.leap.bitmaskclient;
 
 import android.os.*;
 
+import com.google.gson.Gson;
+
 import org.json.*;
 
-import java.io.*;
+import java.io.Serializable;
 import java.net.*;
 import java.util.*;
 
@@ -31,8 +33,11 @@ import java.util.*;
 public final class Provider implements Parcelable {
 
     private JSONObject definition = new JSONObject(); // Represents our Provider's provider.json
-    private DefaultedURL main_url = new DefaultedURL();
-    private String certificate_pin = "";
+    private DefaultedURL mainUrl = new DefaultedURL();
+    private DefaultedURL apiUrl = new DefaultedURL();
+    private String certificatePin = "";
+    private String certificatePinEncoding = "";
+    private String caCert = "";
 
     final public static String
             API_URL = "api_uri",
@@ -61,13 +66,20 @@ public final class Provider implements Parcelable {
 
     public Provider() { }
 
-    public Provider(URL main_url) {
-        this.main_url.setUrl(main_url);
+    public Provider(URL mainUrl) {
+        this.mainUrl.setUrl(mainUrl);
     }
 
-    public Provider(URL main_url, String certificate_pin) {
-        this.main_url.setUrl(main_url);
-        this.certificate_pin = certificate_pin;
+    public Provider(URL mainUrl, String caCert,  /*String certificatePin,*/ String definition) {
+        this.mainUrl.setUrl(mainUrl);
+        this.caCert = caCert;
+        try {
+            this.definition = new JSONObject(definition);
+            parseDefinition(this.definition);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+
     }
 
     public static final Parcelable.Creator<Provider> CREATOR
@@ -81,42 +93,57 @@ public final class Provider implements Parcelable {
         }
     };
 
-    private Provider(Parcel in) {
-        try {
-            main_url.setUrl(new URL(in.readString()));
-            String definition_string = in.readString();
-            if (!definition_string.isEmpty())
-                definition = new JSONObject((definition_string));
-        } catch (MalformedURLException | JSONException e) {
-            e.printStackTrace();
-        }
-    }
-
     public boolean isConfigured() {
-        return !main_url.isDefault() && definition.length() > 0;
+        return !mainUrl.isDefault() &&
+                definition.length() > 0 &&
+                !apiUrl.isDefault() &&
+                caCert != null &&
+                !caCert.isEmpty();
     }
 
     protected void setUrl(URL url) {
-        main_url.setUrl(url);
+        mainUrl.setUrl(url);
     }
 
     protected void define(JSONObject provider_json) {
         definition = provider_json;
+        parseDefinition(definition);
     }
 
-    protected JSONObject definition() {
+    protected JSONObject getDefinition() {
         return definition;
     }
 
     protected String getDomain() {
-        return main_url.getDomain();
+        return mainUrl.getDomain();
+    }
+
+    protected DefaultedURL getMainUrl() {
+        return mainUrl;
+    }
+
+    protected DefaultedURL getApiUrl() {
+        return apiUrl;
+    }
+
+    protected String certificatePin() { return certificatePin; }
+
+    protected boolean hasCertificatePin() {
+        return certificatePin != null && !certificatePin.isEmpty();
+    }
+
+    boolean hasCaCert() {
+        return caCert != null && !caCert.isEmpty();
     }
 
-    protected DefaultedURL mainUrl() {
-        return main_url;
+    public boolean hasDefinition() {
+        return definition != null && definition.length() > 0;
     }
 
-    protected String certificatePin() { return certificate_pin; }
+
+    public String getCaCert() {
+        return caCert;
+    }
 
     public String getName() {
         // Should we pass the locale in, or query the system here?
@@ -127,8 +154,8 @@ public final class Provider implements Parcelable {
                 name = definition.getJSONObject(API_TERM_NAME).getString(lang);
             else throw new JSONException("Provider not defined");
         } catch (JSONException e) {
-            if (main_url != null) {
-                String host = main_url.getDomain();
+            if (mainUrl != null) {
+                String host = mainUrl.getDomain();
                 name = host.substring(0, host.indexOf("."));
             }
         }
@@ -191,24 +218,29 @@ public final class Provider implements Parcelable {
 
     @Override
     public void writeToParcel(Parcel parcel, int i) {
-        if(main_url != null)
-            parcel.writeString(main_url.toString());
+        if(mainUrl != null)
+            parcel.writeString(mainUrl.toString());
         if (definition != null)
             parcel.writeString(definition.toString());
+        if (caCert != null)
+            parcel.writeString(caCert);
     }
 
     @Override
     public boolean equals(Object o) {
         if (o instanceof Provider) {
             Provider p = (Provider) o;
-            return p.mainUrl().getDomain().equals(mainUrl().getDomain());
+            return p.getMainUrl().getDomain().equals(getMainUrl().getDomain());
         } else return false;
     }
 
     public JSONObject toJson() {
         JSONObject json = new JSONObject();
         try {
-            json.put(Provider.MAIN_URL, main_url);
+            json.put(Provider.MAIN_URL, mainUrl);
+            //TODO: add other fields here?
+            //this is used to save custom providers as json. I guess this doesn't work correctly
+            //TODO 2: verify that
         } catch (JSONException e) {
             e.printStackTrace();
         }
@@ -217,6 +249,44 @@ public final class Provider implements Parcelable {
 
     @Override
     public int hashCode() {
-        return mainUrl().getDomain().hashCode();
+        return getMainUrl().getDomain().hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return new Gson().toJson(this);
+    }
+
+    private Provider(Parcel in) {
+        try {
+            mainUrl.setUrl(new URL(in.readString()));
+            String definitionString = in.readString();
+            if (!definitionString.isEmpty()) {
+                definition = new JSONObject((definitionString));
+                parseDefinition(definition);
+            }
+            String caCert = in.readString();
+            if (!caCert.isEmpty()) {
+                this.caCert = caCert;
+            }
+        } catch (MalformedURLException | JSONException e) {
+            e.printStackTrace();
+        }
     }
+
+    private void parseDefinition(JSONObject definition) {
+        try {
+            String pin =  definition.getString(CA_CERT_FINGERPRINT);
+            this.certificatePin = pin.split(":")[1].trim();
+            this.certificatePinEncoding = pin.split(":")[0].trim();
+            this.apiUrl.setUrl(new URL(definition.getString(API_URL)));
+        } catch (JSONException | ArrayIndexOutOfBoundsException | MalformedURLException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void setCACert(String cert) {
+        this.caCert = cert;
+    }
+
 }
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderApiBase.java b/app/src/main/java/se/leap/bitmaskclient/ProviderApiBase.java
index 6e3b8b08f96e700b42ddb47cb50fb3c3333d88b7..dfc48beec47baf61f448e380422958b7143a09f7 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ProviderApiBase.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ProviderApiBase.java
@@ -45,6 +45,8 @@ import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.X509Certificate;
 import java.security.interfaces.RSAPrivateKey;
 import java.util.ArrayList;
@@ -71,6 +73,12 @@ import se.leap.bitmaskclient.userstatus.SessionDialog;
 import se.leap.bitmaskclient.userstatus.User;
 import se.leap.bitmaskclient.userstatus.UserStatus;
 
+import static android.text.TextUtils.isEmpty;
+import static se.leap.bitmaskclient.ConfigHelper.base64toHex;
+import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING;
+import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON;
+import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_INVALID_CERTIFICATE;
+import static se.leap.bitmaskclient.Provider.MAIN_URL;
 import static se.leap.bitmaskclient.R.string.certificate_error;
 import static se.leap.bitmaskclient.R.string.error_io_exception_user_message;
 import static se.leap.bitmaskclient.R.string.error_json_exception_user_message;
@@ -96,6 +104,7 @@ public abstract class ProviderApiBase extends IntentService {
     final public static String
             TAG = ProviderAPI.class.getSimpleName(),
             SET_UP_PROVIDER = "setUpProvider",
+            UPDATE_PROVIDER_DETAILS = "updateProviderDetails",
             DOWNLOAD_NEW_PROVIDER_DOTJSON = "downloadNewProviderDotJSON",
             SIGN_UP = "srpRegister",
             LOG_IN = "srpAuth",
@@ -105,6 +114,7 @@ public abstract class ProviderApiBase extends IntentService {
             RESULT_KEY = "result",
             RECEIVER_KEY = "receiver",
             ERRORS = "errors",
+            ERRORID = "errorId",
             UPDATE_PROGRESSBAR = "update_progressbar",
             CURRENT_PROGRESS = "current_progress",
             DOWNLOAD_EIP_SERVICE = TAG + ".DOWNLOAD_EIP_SERVICE";
@@ -123,16 +133,18 @@ public abstract class ProviderApiBase extends IntentService {
             CORRECTLY_DOWNLOADED_EIP_SERVICE = 13,
             INCORRECTLY_DOWNLOADED_EIP_SERVICE = 14;
 
-    public static boolean
+    protected static boolean
             CA_CERT_DOWNLOADED = false,
             PROVIDER_JSON_DOWNLOADED = false,
             EIP_SERVICE_JSON_DOWNLOADED = false;
 
-    protected static String last_provider_main_url;
+    protected static String lastProviderMainUrl;
     protected static boolean go_ahead = true;
     protected static SharedPreferences preferences;
-    protected static String provider_api_url;
-    protected static String provider_ca_cert_fingerprint;
+    protected static String providerApiUrl;
+    protected static String providerCaCertFingerprint;
+    protected static String providerCaCert;
+    protected static JSONObject providerDefinition;
     protected Resources resources;
 
     public static void stop() {
@@ -155,7 +167,7 @@ public abstract class ProviderApiBase extends IntentService {
     }
 
     public static String lastProviderMainUrl() {
-        return last_provider_main_url;
+        return lastProviderMainUrl;
     }
 
     @Override
@@ -164,17 +176,26 @@ public abstract class ProviderApiBase extends IntentService {
         String action = command.getAction();
         Bundle parameters = command.getBundleExtra(PARAMETERS);
 
-        if (provider_api_url == null && preferences.contains(Provider.KEY)) {
+        if (providerApiUrl == null && preferences.contains(Provider.KEY)) {
             try {
                 JSONObject provider_json = new JSONObject(preferences.getString(Provider.KEY, ""));
-                provider_api_url = provider_json.getString(Provider.API_URL) + "/" + provider_json.getString(Provider.API_VERSION);
+                providerApiUrl = provider_json.getString(Provider.API_URL) + "/" + provider_json.getString(Provider.API_VERSION);
                 go_ahead = true;
             } catch (JSONException e) {
                 go_ahead = false;
             }
         }
-
-        if (action.equalsIgnoreCase(SET_UP_PROVIDER)) {
+        if (action.equals(UPDATE_PROVIDER_DETAILS)) {
+            resetProviderDetails();
+            Bundle task = new Bundle();
+            task.putString(MAIN_URL, lastProviderMainUrl);
+            Bundle result = setUpProvider(task);
+            if (result.getBoolean(RESULT_KEY)) {
+                receiver.send(PROVIDER_OK, result);
+            } else {
+                receiver.send(PROVIDER_NOK, result);
+            }
+        } else if (action.equalsIgnoreCase(SET_UP_PROVIDER)) {
             Bundle result = setUpProvider(parameters);
             if (go_ahead) {
                 if (result.getBoolean(RESULT_KEY)) {
@@ -226,6 +247,13 @@ public abstract class ProviderApiBase extends IntentService {
         }
     }
 
+    protected void resetProviderDetails() {
+        CA_CERT_DOWNLOADED = PROVIDER_JSON_DOWNLOADED = false;
+        deleteProviderDetailsFromPreferences(providerDefinition);
+        providerCaCert = "";
+        providerDefinition = new JSONObject();
+    }
+
     protected String formatErrorMessage(final int toastStringId) {
         return formatErrorMessage(getResources().getString(toastStringId));
     }
@@ -243,22 +271,31 @@ public abstract class ProviderApiBase extends IntentService {
         }
     }
 
-    private JSONObject getErrorMessageAsJson(String message) {
+    protected void addErrorMessageToJson(JSONObject jsonObject, String errorMessage) {
         try {
-            return new JSONObject(formatErrorMessage(message));
+            jsonObject.put(ERRORS, errorMessage);
         } catch (JSONException e) {
             e.printStackTrace();
-            return new JSONObject();
         }
     }
-    private OkHttpClient initHttpClient(JSONObject initError, boolean isSelfSigned) {
+
+    protected void addErrorMessageToJson(JSONObject jsonObject, String errorMessage, String errorId) {
+        try {
+            jsonObject.put(ERRORS, errorMessage);
+            jsonObject.put(ERRORID, errorId);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private OkHttpClient initHttpClient(JSONObject initError, String certificate) {
         try {
             TLSCompatSocketFactory sslCompatFactory;
             ConnectionSpec spec = getConnectionSpec();
             OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
-            if (isSelfSigned) {
-                sslCompatFactory = new TLSCompatSocketFactory(preferences.getString(Provider.CA_CERT, ""));
 
+            if (!isEmpty(certificate)) {
+                sslCompatFactory = new TLSCompatSocketFactory(certificate);
             } else {
                 sslCompatFactory = new TLSCompatSocketFactory();
             }
@@ -266,40 +303,39 @@ public abstract class ProviderApiBase extends IntentService {
             clientBuilder.cookieJar(getCookieJar())
                     .connectionSpecs(Collections.singletonList(spec));
             return clientBuilder.build();
-        } catch (IllegalStateException e) {
-            e.printStackTrace();
-            initError = getErrorMessageAsJson(String.format(resources.getString(keyChainAccessError), e.getLocalizedMessage()));
-        } catch (KeyStoreException e) {
+        } catch (IllegalArgumentException e) {
             e.printStackTrace();
-            initError = getErrorMessageAsJson(String.format(resources.getString(keyChainAccessError), e.getLocalizedMessage()));
-        } catch (KeyManagementException e) {
+            addErrorMessageToJson(initError, resources.getString(R.string.certificate_error));
+        } catch (IllegalStateException | KeyManagementException | KeyStoreException e) {
             e.printStackTrace();
-            initError = getErrorMessageAsJson(String.format(resources.getString(keyChainAccessError), e.getLocalizedMessage()));
-        } catch (NoSuchAlgorithmException e) {
+            addErrorMessageToJson(initError, String.format(resources.getString(keyChainAccessError), e.getLocalizedMessage()));
+        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
             e.printStackTrace();
-            initError = getErrorMessageAsJson(resources.getString(error_no_such_algorithm_exception_user_message));
+            addErrorMessageToJson(initError, resources.getString(error_no_such_algorithm_exception_user_message));
         } catch (CertificateException e) {
             e.printStackTrace();
-            initError = getErrorMessageAsJson(resources.getString(certificate_error));
+            addErrorMessageToJson(initError, resources.getString(certificate_error));
         } catch (UnknownHostException e) {
             e.printStackTrace();
-            initError = getErrorMessageAsJson(resources.getString(server_unreachable_message));
+            addErrorMessageToJson(initError, resources.getString(server_unreachable_message));
         } catch (IOException e) {
             e.printStackTrace();
-            initError = getErrorMessageAsJson(resources.getString(error_io_exception_user_message));
-        } catch (NoSuchProviderException e) {
-            e.printStackTrace();
-            initError = getErrorMessageAsJson(resources.getString(error_no_such_algorithm_exception_user_message));
+            addErrorMessageToJson(initError, resources.getString(error_io_exception_user_message));
         }
         return null;
     }
 
     protected OkHttpClient initCommercialCAHttpClient(JSONObject initError) {
-        return initHttpClient(initError, false);
+        return initHttpClient(initError, null);
     }
 
     protected OkHttpClient initSelfSignedCAHttpClient(JSONObject initError) {
-        return initHttpClient(initError, true);
+        String certificate = preferences.getString(Provider.CA_CERT, "");
+        return initHttpClient(initError, certificate);
+    }
+
+    protected OkHttpClient initSelfSignedCAHttpClient(JSONObject initError, String certificate) {
+        return initHttpClient(initError, certificate);
     }
 
     @NonNull
@@ -376,7 +412,7 @@ public abstract class ProviderApiBase extends IntentService {
 
         BigInteger password_verifier = client.calculateV(username, password, salt);
 
-        JSONObject api_result = sendNewUserDataToSRPServer(provider_api_url, username, new BigInteger(1, salt).toString(16), password_verifier.toString(16), okHttpClient);
+        JSONObject api_result = sendNewUserDataToSRPServer(providerApiUrl, username, new BigInteger(1, salt).toString(16), password_verifier.toString(16), okHttpClient);
 
         Bundle result = new Bundle();
         if (api_result.has(ERRORS))
@@ -431,13 +467,13 @@ public abstract class ProviderApiBase extends IntentService {
         LeapSRPSession client = new LeapSRPSession(username, password);
         byte[] A = client.exponential();
 
-        JSONObject step_result = sendAToSRPServer(provider_api_url, username, new BigInteger(1, A).toString(16), okHttpClient);
+        JSONObject step_result = sendAToSRPServer(providerApiUrl, username, new BigInteger(1, A).toString(16), okHttpClient);
         try {
             String salt = step_result.getString(LeapSRPSession.SALT);
             byte[] Bbytes = new BigInteger(step_result.getString("B"), 16).toByteArray();
             byte[] M1 = client.response(new BigInteger(salt, 16).toByteArray(), Bbytes);
             if (M1 != null) {
-                step_result = sendM1ToSRPServer(provider_api_url, username, M1, okHttpClient);
+                step_result = sendM1ToSRPServer(providerApiUrl, username, M1, okHttpClient);
                 setTokenIfAvailable(step_result);
                 byte[] M2 = new BigInteger(step_result.getString(LeapSRPSession.M2), 16).toByteArray();
                 if (client.verify(M2)) {
@@ -629,6 +665,9 @@ public abstract class ProviderApiBase extends IntentService {
 
         try {
             response = okHttpClient.newCall(request).execute();
+            if (!response.isSuccessful()){
+                 return formatErrorMessage(error_json_exception_user_message);
+            }
 
             InputStream inputStream = response.body().byteStream();
             Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
@@ -638,12 +677,10 @@ public abstract class ProviderApiBase extends IntentService {
 
         } catch (NullPointerException npe) {
             plainResponseBody = formatErrorMessage(error_json_exception_user_message);
-        } catch (UnknownHostException e) {
+        } catch (UnknownHostException | SocketTimeoutException e) {
             plainResponseBody = formatErrorMessage(server_unreachable_message);
         } catch (MalformedURLException e) {
             plainResponseBody = formatErrorMessage(malformed_url);
-        } catch (SocketTimeoutException e) {
-            plainResponseBody = formatErrorMessage(server_unreachable_message);
         } catch (SSLHandshakeException e) {
             plainResponseBody = formatErrorMessage(certificate_error);
         } catch (ConnectException e) {
@@ -693,6 +730,7 @@ public abstract class ProviderApiBase extends IntentService {
         } catch(JSONException e) {
             return false;
         } catch(NullPointerException e) {
+            e.printStackTrace();
             return false;
         }
     }
@@ -714,11 +752,7 @@ public abstract class ProviderApiBase extends IntentService {
                     result = real_fingerprint.trim().equalsIgnoreCase(expected_fingerprint.trim());
                 } else
                     result = false;
-            } catch (JSONException e) {
-                result = false;
-            } catch (NoSuchAlgorithmException e) {
-                result = false;
-            } catch (CertificateEncodingException e) {
+            } catch (JSONException | NoSuchAlgorithmException | CertificateEncodingException e) {
                 result = false;
             }
         }
@@ -726,16 +760,179 @@ public abstract class ProviderApiBase extends IntentService {
         return result;
     }
 
-    private String base64toHex(String base64_input) {
-        byte[] byteArray = Base64.decode(base64_input, Base64.DEFAULT);
-        int readBytes = byteArray.length;
-        StringBuffer hexData = new StringBuffer();
-        int onebyte;
-        for (int i = 0; i < readBytes; i++) {
-            onebyte = ((0x000000ff & byteArray[i]) | 0xffffff00);
-            hexData.append(Integer.toHexString(onebyte).substring(6));
+    protected Bundle validateCertificateForProvider(String cert_string, JSONObject providerDefinition, String mainUrl) {
+        Bundle result = new Bundle();
+        result.putBoolean(RESULT_KEY, false);
+
+        if (ConfigHelper.checkErroneousDownload(cert_string)) {
+            return result;
+        }
+
+        X509Certificate certificate = ConfigHelper.parseX509CertificateFromString(cert_string);
+        if (certificate == null) {
+            return setErrorResult(result, getString(R.string.warning_corrupted_provider_cert), ERROR_INVALID_CERTIFICATE.toString());
+        }
+        try {
+            certificate.checkValidity();
+            String fingerprint = getCaCertFingerprint(providerDefinition);
+            String encoding = fingerprint.split(":")[0];
+            String expected_fingerprint = fingerprint.split(":")[1];
+            String real_fingerprint = base64toHex(Base64.encodeToString(
+                    MessageDigest.getInstance(encoding).digest(certificate.getEncoded()),
+                    Base64.DEFAULT));
+            if (!real_fingerprint.trim().equalsIgnoreCase(expected_fingerprint.trim())) {
+                return setErrorResult(result, getString(R.string.warning_corrupted_provider_cert), ERROR_CERTIFICATE_PINNING.toString());
+            }
+
+            if (!hasApiUrlExpectedDomain(providerDefinition, mainUrl)){
+                return setErrorResult(result, getString(R.string.warning_corrupted_provider_details), ERROR_CORRUPTED_PROVIDER_JSON.toString());
+            }
+
+            if (!canConnect(cert_string, providerDefinition, result)) {
+                return result;
+            }
+        } catch (NoSuchAlgorithmException e ) {
+            return setErrorResult(result, resources.getString(error_no_such_algorithm_exception_user_message), null);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            return setErrorResult(result, getString(R.string.warning_corrupted_provider_details), ERROR_CORRUPTED_PROVIDER_JSON.toString());
+        } catch (CertificateEncodingException | CertificateNotYetValidException | CertificateExpiredException e) {
+            return setErrorResult(result, getString(R.string.warning_expired_provider_cert), ERROR_INVALID_CERTIFICATE.toString());
+        }
+
+        result.putBoolean(RESULT_KEY, true);
+        return result;
+    }
+
+    protected Bundle setErrorResult(Bundle result, String errorMessage, String errorId) {
+        JSONObject errorJson = new JSONObject();
+        if (errorId != null) {
+            addErrorMessageToJson(errorJson, errorMessage, errorId);
+        } else {
+            addErrorMessageToJson(errorJson, errorMessage);
+        }
+        result.putString(ERRORS, errorJson.toString());
+        return result;
+    }
+
+    /**
+     * This method aims to prevent attacks where the provider.json file got manipulated by a third party.
+     * The main url is visible to the provider when setting up a new provider.
+     * The user is responsible to check that this is the provider main url he intends to connect to.
+     *
+     * @param providerDefinition
+     * @param mainUrlString
+     * @return
+     */
+    private boolean hasApiUrlExpectedDomain(JSONObject providerDefinition, String mainUrlString) {
+        //  fix against "api_uri": "https://calyx.net.malicious.url.net:4430",
+        String apiUrlString = getApiUrl(providerDefinition);
+        String providerDomain = getProviderDomain(providerDefinition);
+        if (mainUrlString.contains(providerDomain) && apiUrlString.contains(providerDomain  + ":")) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean canConnect(String caCert, JSONObject providerDefinition, Bundle result) {
+        JSONObject errorJson = new JSONObject();
+        String baseUrl = getApiUrl(providerDefinition);
+
+        OkHttpClient okHttpClient = initSelfSignedCAHttpClient(errorJson, caCert);
+        if (okHttpClient == null) {
+            result.putString(ERRORS, errorJson.toString());
+            return false;
         }
-        return hexData.toString();
+
+        List<Pair<String, String>> headerArgs = getAuthorizationHeader();
+        String plain_response = requestStringFromServer(baseUrl, "GET", null, headerArgs, okHttpClient);
+
+        try {
+            if (new JSONObject(plain_response).has(ERRORS)) {
+                result.putString(ERRORS, plain_response);
+                return false;
+            }
+        } catch (JSONException e) {
+            //eat me
+        }
+
+        return true;
+    }
+
+    protected String getCaCertFingerprint(JSONObject providerDefinition) {
+        try {
+            return providerDefinition.getString(Provider.CA_CERT_FINGERPRINT);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    protected String getApiUrl(JSONObject providerDefinition) {
+        try {
+            return providerDefinition.getString(Provider.API_URL);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    protected String getApiUrlWithVersion(JSONObject providerDefinition) {
+        try {
+            return providerDefinition.getString(Provider.API_URL) + "/" + providerDefinition.getString(Provider.API_VERSION);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    protected void deleteProviderDetailsFromPreferences(JSONObject providerDefinition) {
+        String providerDomain = getProviderDomain(providerDefinition);
+
+        if (preferences.contains(Provider.KEY + "." + providerDomain)) {
+            preferences.edit().remove(Provider.KEY + "." + providerDomain).apply();
+        }
+        if (preferences.contains(Provider.CA_CERT + "." + providerDomain)) {
+            preferences.edit().remove(Provider.CA_CERT + "." + providerDomain).apply();
+        }
+        if (preferences.contains(Provider.CA_CERT_FINGERPRINT + "." + providerDomain)) {
+            preferences.edit().remove(Provider.CA_CERT_FINGERPRINT + "." + providerDomain).apply();
+        }
+    }
+
+    protected String getPersistedCaCertFingerprint(String providerDomain) {
+        try {
+            return getPersistedProviderDefinition(providerDomain).getString(Provider.CA_CERT_FINGERPRINT);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    protected JSONObject getPersistedProviderDefinition(String providerDomain) {
+        try {
+            return new JSONObject(preferences.getString(Provider.KEY + "." + providerDomain, ""));
+        } catch (JSONException e) {
+            e.printStackTrace();
+            return new JSONObject();
+        }
+    }
+
+    protected String getPersistedProviderCA(String providerDomain) {
+        return preferences.getString(Provider.CA_CERT + "." + providerDomain, "");
+    }
+
+    protected String getProviderDomain(JSONObject providerDefinition) {
+        try {
+            return providerDefinition.getString(Provider.DOMAIN);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+
+        return "";
+    }
+
+    protected boolean hasUpdatedProviderDetails(String providerDomain) {
+        return preferences.contains(Provider.KEY + "." + providerDomain) && preferences.contains(Provider.CA_CERT + "." + providerDomain);
     }
 
     /**
@@ -774,7 +971,7 @@ public abstract class ProviderApiBase extends IntentService {
             return false;
         }
 
-        String deleteUrl = provider_api_url + "/logout";
+        String deleteUrl = providerApiUrl + "/logout";
         int progress = 0;
 
         Request.Builder requestBuilder = new Request.Builder()
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java
index abbdeb66a849739477b35f016a0b457b2374a567..cf70363102490de63b0f7cefe4400c39d42c707b 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java
@@ -1,20 +1,31 @@
 package se.leap.bitmaskclient;
 
-import android.content.res.*;
-
-import com.pedrogomez.renderers.*;
-
-import org.json.*;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
+import android.content.res.AssetManager;
+
+import com.pedrogomez.renderers.AdapteeCollection;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
 
 /**
  * Created by parmegv on 4/12/14.
  */
 public class ProviderManager implements AdapteeCollection<Provider> {
 
+    private static final String TAG = ProviderManager.class.getName();
     private AssetManager assets_manager;
     private File external_files_dir;
     private Set<Provider> default_providers;
@@ -49,13 +60,13 @@ public class ProviderManager implements AdapteeCollection<Provider> {
         Set<Provider> providers = new HashSet<Provider>();
         try {
             for (String file : relative_file_paths) {
+
+                String provider = file.substring(0, file.length() - ".url".length());
                 InputStream provider_file = assets_manager.open(directory + "/" + file);
-                String main_url = extractMainUrlFromInputStream(provider_file);
-                String certificate_pin = extractCertificatePinFromInputStream(provider_file);
-                if(certificate_pin.isEmpty())
-                    providers.add(new Provider(new URL(main_url)));
-                else
-                    providers.add(new Provider(new URL(main_url), certificate_pin));
+                String mainUrl = extractMainUrlFromInputStream(provider_file);
+                String certificate = ConfigHelper.loadInputStreamAsString(assets_manager.open(provider + ".pem"));
+                String providerDefinition = ConfigHelper.loadInputStreamAsString(assets_manager.open(provider + ".json"));
+                providers.add(new Provider(new URL(mainUrl), certificate, providerDefinition));
             }
         } catch (IOException e) {
             e.printStackTrace();
@@ -89,21 +100,11 @@ public class ProviderManager implements AdapteeCollection<Provider> {
         String main_url = "";
 
         JSONObject file_contents = inputStreamToJson(input_stream);
-        if(file_contents != null)
+        if (file_contents != null)
             main_url = file_contents.optString(Provider.MAIN_URL);
         return main_url;
     }
 
-    private String extractCertificatePinFromInputStream(InputStream input_stream) {
-        String certificate_pin = "";
-
-        JSONObject file_contents = inputStreamToJson(input_stream);
-        if(file_contents != null)
-            certificate_pin = file_contents.optString(Provider.CA_CERT_FINGERPRINT);
-
-        return certificate_pin;
-    }
-
     private JSONObject inputStreamToJson(InputStream input_stream) {
         JSONObject json = null;
         try {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 70f6f4ab2835328866d8e18ef426c1e1f0309753..680f92e16581b02208b37e958507bcad6e8bb725 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -82,4 +82,9 @@
     <string name="void_vpn_error_establish">Failed to establish blocking VPN.</string>
     <string name="void_vpn_stopped">Stopped blocking all outgoing internet traffic.</string>
     <string name="void_vpn_title">Blocking traffic</string>
+    <string name="update_provider_details">Update provider details</string>
+    <string name="update_certificate">Update certificate</string>
+    <string name="warning_corrupted_provider_details">Stored provider details are corrupted. You can either update Bitmask (recommended) or update the provider details using a commercial CA certificate.</string>
+    <string name="warning_corrupted_provider_cert">Stored provider certificate is corrupted. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.</string>
+    <string name="warning_expired_provider_cert">Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.</string>
 </resources>
diff --git a/app/src/production/java/se/leap/bitmaskclient/ConfigurationWizard.java b/app/src/production/java/se/leap/bitmaskclient/ConfigurationWizard.java
index fc2569b2309a3ad0629fd18f2140966dfc10fd09..363fa66c76a066e2db78db7f0c82c1ff489a31f1 100644
--- a/app/src/production/java/se/leap/bitmaskclient/ConfigurationWizard.java
+++ b/app/src/production/java/se/leap/bitmaskclient/ConfigurationWizard.java
@@ -63,8 +63,16 @@ public class ConfigurationWizard extends BaseConfigurationWizard {
         mConfigState.setAction(SETTING_UP_PROVIDER);
         Intent provider_API_command = new Intent(this, ProviderAPI.class);
         Bundle parameters = new Bundle();
-        parameters.putString(Provider.MAIN_URL, selected_provider.mainUrl().toString());
-        parameters.putString(Provider.CA_CERT_FINGERPRINT, selected_provider.certificatePin());
+        parameters.putString(Provider.MAIN_URL, selected_provider.getMainUrl().toString());
+        if (selected_provider.hasCertificatePin()){
+            parameters.putString(Provider.CA_CERT_FINGERPRINT, selected_provider.certificatePin());
+        }
+        if (selected_provider.hasCaCert()) {
+            parameters.putString(Provider.CA_CERT, selected_provider.getCaCert());
+        }
+        if (selected_provider.hasDefinition()) {
+            parameters.putString(Provider.KEY, selected_provider.getDefinition().toString());
+        }
 
         provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER);
         provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters);
@@ -73,18 +81,39 @@ public class ConfigurationWizard extends BaseConfigurationWizard {
         startService(provider_API_command);
     }
 
+    @Override
     public void retrySetUpProvider() {
         cancelSettingUpProvider();
         if (!ProviderAPI.caCertDownloaded()) {
             addAndSelectNewProvider(ProviderAPI.lastProviderMainUrl());
         } else {
-            Intent provider_API_command = new Intent(this, ProviderAPI.class);
+            showProgressBar();
+            adapter.hideAllBut(adapter.indexOf(selected_provider));
 
+
+            Intent provider_API_command = new Intent(this, ProviderAPI.class);
             provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER);
             provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver);
+            Bundle parameters = new Bundle();
+            parameters.putString(Provider.MAIN_URL, selected_provider.getMainUrl().toString());
+            provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters);
 
             startService(provider_API_command);
         }
     }
 
+    @Override
+    public void updateProviderDetails() {
+        mConfigState.setAction(SETTING_UP_PROVIDER);
+        Intent provider_API_command = new Intent(this, ProviderAPI.class);
+
+        provider_API_command.setAction(ProviderAPI.UPDATE_PROVIDER_DETAILS);
+        provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver);
+        Bundle parameters = new Bundle();
+        parameters.putString(Provider.MAIN_URL, selected_provider.getMainUrl().toString());
+        provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters);
+
+        startService(provider_API_command);
+    }
+
 }
diff --git a/app/src/production/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/production/java/se/leap/bitmaskclient/ProviderAPI.java
index fadb03c308c6c2cc3a7247fff60a873fd5f7e5a4..b27c3dcab282d19fd5d64122ae06d49edd80e116 100644
--- a/app/src/production/java/se/leap/bitmaskclient/ProviderAPI.java
+++ b/app/src/production/java/se/leap/bitmaskclient/ProviderAPI.java
@@ -22,20 +22,15 @@ import android.util.Pair;
 
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.thoughtcrime.ssl.pinning.util.PinningHelper;
 
 import java.io.IOException;
 import java.net.URL;
 import java.util.List;
-import java.util.Scanner;
-
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLHandshakeException;
 
 import okhttp3.OkHttpClient;
 import se.leap.bitmaskclient.eip.EIP;
 
-import static se.leap.bitmaskclient.R.string.error_io_exception_user_message;
+import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON;
 import static se.leap.bitmaskclient.R.string.malformed_url;
 
 /**
@@ -60,72 +55,139 @@ public class ProviderAPI extends ProviderApiBase {
     @Override
     protected Bundle setUpProvider(Bundle task) {
         int progress = 0;
-        Bundle current_download = new Bundle();
+        Bundle currentDownload = new Bundle();
 
-        if (task != null && task.containsKey(Provider.MAIN_URL)) {
-            last_provider_main_url = task.containsKey(Provider.MAIN_URL) ?
+        if (task != null) {
+            //FIXME: this should be refactored in order to avoid static variables all over here
+            lastProviderMainUrl = task.containsKey(Provider.MAIN_URL) ?
                     task.getString(Provider.MAIN_URL) :
                     "";
-            provider_ca_cert_fingerprint = task.containsKey(Provider.CA_CERT_FINGERPRINT) ?
+            providerCaCertFingerprint = task.containsKey(Provider.CA_CERT_FINGERPRINT) ?
                     task.getString(Provider.CA_CERT_FINGERPRINT) :
                     "";
+            providerCaCert = task.containsKey(Provider.CA_CERT) ?
+                    task.getString(Provider.CA_CERT) :
+                    "";
 
-            CA_CERT_DOWNLOADED = PROVIDER_JSON_DOWNLOADED = EIP_SERVICE_JSON_DOWNLOADED = false;
+            try {
+                providerDefinition = task.containsKey(Provider.KEY) ?
+                        new JSONObject(task.getString(Provider.KEY)) :
+                        new JSONObject();
+            } catch (JSONException e) {
+                e.printStackTrace();
+                providerDefinition = new JSONObject();
+            }
+            providerApiUrl = getApiUrlWithVersion(providerDefinition);
+
+            checkPersistedProviderUpdates();
+            currentDownload = validateProviderDetails();
+
+            //provider details invalid
+            if (currentDownload.containsKey(ERRORS)) {
+                return currentDownload;
+            }
+
+            //no provider certificate available
+            if (currentDownload.containsKey(RESULT_KEY) && !currentDownload.getBoolean(RESULT_KEY)) {
+                resetProviderDetails();
+            }
+
+            EIP_SERVICE_JSON_DOWNLOADED = false;
             go_ahead = true;
         }
 
         if (!PROVIDER_JSON_DOWNLOADED)
-            current_download = getAndSetProviderJson(last_provider_main_url, provider_ca_cert_fingerprint);
-        if (PROVIDER_JSON_DOWNLOADED || (current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY))) {
+            currentDownload = getAndSetProviderJson(lastProviderMainUrl, providerCaCert, providerDefinition);
+        if (PROVIDER_JSON_DOWNLOADED || (currentDownload.containsKey(RESULT_KEY) && currentDownload.getBoolean(RESULT_KEY))) {
             broadcastProgress(progress++);
             PROVIDER_JSON_DOWNLOADED = true;
 
             if (!CA_CERT_DOWNLOADED)
-                current_download = downloadCACert();
-            if (CA_CERT_DOWNLOADED || (current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY))) {
+                currentDownload = downloadCACert();
+            if (CA_CERT_DOWNLOADED || (currentDownload.containsKey(RESULT_KEY) && currentDownload.getBoolean(RESULT_KEY))) {
                 broadcastProgress(progress++);
                 CA_CERT_DOWNLOADED = true;
-                current_download = getAndSetEipServiceJson();
-                if (current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY)) {
+                currentDownload = getAndSetEipServiceJson();
+                if (currentDownload.containsKey(RESULT_KEY) && currentDownload.getBoolean(RESULT_KEY)) {
                     broadcastProgress(progress++);
                     EIP_SERVICE_JSON_DOWNLOADED = true;
                 }
             }
         }
 
-        return current_download;
+        return currentDownload;
     }
 
-    private Bundle getAndSetProviderJson(String provider_main_url, String provider_ca_cert_fingerprint) {
+
+    private Bundle validateProviderDetails() {
+        Bundle result = validateCertificateForProvider(providerCaCert, providerDefinition, lastProviderMainUrl);
+
+        //invalid certificate or no certificate
+        if (result.containsKey(ERRORS) || (result.containsKey(RESULT_KEY) && !result.getBoolean(RESULT_KEY)) ) {
+            return result;
+        }
+
+        //valid certificate: skip download, save loaded provider CA cert and provider definition directly
+        try {
+            preferences.edit().putString(Provider.KEY, providerDefinition.toString()).
+                    putBoolean(Constants.PROVIDER_ALLOW_ANONYMOUS, providerDefinition.getJSONObject(Provider.SERVICE).getBoolean(Constants.PROVIDER_ALLOW_ANONYMOUS)).
+                    putBoolean(Constants.PROVIDER_ALLOWED_REGISTERED, providerDefinition.getJSONObject(Provider.SERVICE).getBoolean(Constants.PROVIDER_ALLOWED_REGISTERED)).
+                    putString(Provider.CA_CERT, providerCaCert).commit();
+            CA_CERT_DOWNLOADED = true;
+            PROVIDER_JSON_DOWNLOADED = true;
+            result.putBoolean(RESULT_KEY, true);
+        } catch (JSONException e) {
+            e.printStackTrace();
+            result.putBoolean(RESULT_KEY, false);
+            result = setErrorResult(result,  getString(R.string.warning_corrupted_provider_details), ERROR_CORRUPTED_PROVIDER_JSON.toString());
+        }
+
+        return result;
+    }
+
+    private void checkPersistedProviderUpdates() {
+        String providerDomain = getProviderDomain(providerDefinition);
+        if (hasUpdatedProviderDetails(providerDomain)) {
+            providerCaCert = getPersistedProviderCA(providerDomain);
+            providerDefinition = getPersistedProviderDefinition(providerDomain);
+            providerCaCertFingerprint = getPersistedCaCertFingerprint(providerDomain);
+            providerApiUrl = getApiUrlWithVersion(providerDefinition);
+        }
+    }
+
+
+    private Bundle getAndSetProviderJson(String providerMainUrl, String caCert, JSONObject providerDefinition) {
         Bundle result = new Bundle();
 
         if (go_ahead) {
-            String provider_dot_json_string;
-            if(provider_ca_cert_fingerprint.isEmpty())
-                provider_dot_json_string = downloadWithCommercialCA(provider_main_url + "/provider.json");
-            else
-                provider_dot_json_string = downloadWithCommercialCA(provider_main_url + "/provider.json", provider_ca_cert_fingerprint);
+            String providerDotJsonString;
+            if(providerDefinition.length() == 0 || caCert.isEmpty())
+                providerDotJsonString = downloadWithCommercialCA(providerMainUrl + "/provider.json");
+            else {
+                providerDotJsonString = downloadFromApiUrlWithProviderCA("/provider.json", caCert, providerDefinition);
+            }
 
-                if (!isValidJson(provider_dot_json_string)) {
+                if (!isValidJson(providerDotJsonString)) {
                     result.putString(ERRORS, getString(malformed_url));
                     result.putBoolean(RESULT_KEY, false);
                     return result;
                 }
 
             try {
-                JSONObject provider_json = new JSONObject(provider_dot_json_string);
-                provider_api_url = provider_json.getString(Provider.API_URL) + "/" + provider_json.getString(Provider.API_VERSION);
-                String name = provider_json.getString(Provider.NAME);
+                JSONObject providerJson = new JSONObject(providerDotJsonString);
+                String providerDomain = providerJson.getString(Provider.DOMAIN);
+                providerApiUrl = getApiUrlWithVersion(providerJson);
+                String name = providerJson.getString(Provider.NAME);
                 //TODO setProviderName(name);
 
-                preferences.edit().putString(Provider.KEY, provider_json.toString()).commit();
-                preferences.edit().putBoolean(Constants.PROVIDER_ALLOW_ANONYMOUS, provider_json.getJSONObject(Provider.SERVICE).getBoolean(Constants.PROVIDER_ALLOW_ANONYMOUS)).commit();
-                preferences.edit().putBoolean(Constants.PROVIDER_ALLOWED_REGISTERED, provider_json.getJSONObject(Provider.SERVICE).getBoolean(Constants.PROVIDER_ALLOWED_REGISTERED)).commit();
-
+                preferences.edit().putString(Provider.KEY, providerJson.toString()).
+                        putBoolean(Constants.PROVIDER_ALLOW_ANONYMOUS, providerJson.getJSONObject(Provider.SERVICE).getBoolean(Constants.PROVIDER_ALLOW_ANONYMOUS)).
+                        putBoolean(Constants.PROVIDER_ALLOWED_REGISTERED, providerJson.getJSONObject(Provider.SERVICE).getBoolean(Constants.PROVIDER_ALLOWED_REGISTERED)).
+                        putString(Provider.KEY + "." + providerDomain, providerJson.toString()).commit();
                 result.putBoolean(RESULT_KEY, true);
             } catch (JSONException e) {
                 //TODO Error message should be contained in that provider_dot_json_string
-                String reason_to_fail = pickErrorMessage(provider_dot_json_string);
+                String reason_to_fail = pickErrorMessage(providerDotJsonString);
                 result.putString(ERRORS, reason_to_fail);
                 result.putBoolean(RESULT_KEY, false);
             }
@@ -176,7 +238,7 @@ public class ProviderAPI extends ProviderApiBase {
 
             String cert_string = downloadWithProviderCA(new_cert_string_url.toString());
 
-            if (cert_string == null || cert_string.isEmpty() || ConfigHelper.checkErroneousDownload(cert_string))
+            if (ConfigHelper.checkErroneousDownload(cert_string))
                 return false;
             else
                 return loadCertificate(cert_string);
@@ -194,13 +256,16 @@ public class ProviderAPI extends ProviderApiBase {
     private Bundle downloadCACert() {
         Bundle result = new Bundle();
         try {
-            JSONObject provider_json = new JSONObject(preferences.getString(Provider.KEY, ""));
-            String ca_cert_url = provider_json.getString(Provider.CA_CERT_URI);
-            String cert_string = downloadWithCommercialCA(ca_cert_url);
+            JSONObject providerJson = new JSONObject(preferences.getString(Provider.KEY, ""));
+            String caCertUrl = providerJson.getString(Provider.CA_CERT_URI);
+            String providerDomain = providerJson.getString(Provider.DOMAIN);
+
+            String cert_string = downloadWithCommercialCA(caCertUrl);
             result.putBoolean(RESULT_KEY, true);
 
             if (validCertificate(cert_string) && go_ahead) {
                 preferences.edit().putString(Provider.CA_CERT, cert_string).commit();
+                preferences.edit().putString(Provider.CA_CERT + "." + providerDomain, cert_string).commit();
                 result.putBoolean(RESULT_KEY, true);
             } else {
                 String reason_to_fail = pickErrorMessage(cert_string);
@@ -216,29 +281,6 @@ public class ProviderAPI extends ProviderApiBase {
         return result;
     }
 
-    //TODO: refactor with ticket #8773
-    private String downloadWithCommercialCA(String url_string, String ca_cert_fingerprint) {
-        String result = "";
-
-        int seconds_of_timeout = 2;
-        String[] pins = new String[] {ca_cert_fingerprint};
-        try {
-            URL url = new URL(url_string);
-            HttpsURLConnection connection = PinningHelper.getPinnedHttpsURLConnection(Dashboard.getContext(), pins, url);
-            connection.setConnectTimeout(seconds_of_timeout * 1000);
-            if (!LeapSRPSession.getToken().isEmpty())
-                connection.addRequestProperty(LeapSRPSession.AUTHORIZATION_HEADER, "Token token=" + LeapSRPSession.getToken());
-            result = new Scanner(connection.getInputStream()).useDelimiter("\\A").next();
-        } catch (IOException e) {
-            if(e instanceof SSLHandshakeException)
-                result = formatErrorMessage(R.string.error_security_pinnedcertificate);
-            else
-                result = formatErrorMessage(error_io_exception_user_message);
-        }
-
-        return result;
-    }
-
     /**
      * Tries to download the contents of the provided url using commercially validated CA certificate from chosen provider.
      *
@@ -273,6 +315,31 @@ public class ProviderAPI extends ProviderApiBase {
         return responseString;
     }
 
+
+    /**
+     * Tries to download the contents of the provided url using not commercially validated CA certificate from chosen provider.
+     *
+     * @return an empty string if it fails, the response body if not.
+     */
+    protected String downloadFromApiUrlWithProviderCA(String path, String caCert, JSONObject providerDefinition) {
+        String responseString;
+        JSONObject errorJson = new JSONObject();
+        String baseUrl = getApiUrl(providerDefinition);
+        OkHttpClient okHttpClient = initSelfSignedCAHttpClient(errorJson, caCert);
+        if (okHttpClient == null) {
+            return errorJson.toString();
+        }
+
+        String urlString = baseUrl + path;
+        List<Pair<String, String>> headerArgs = getAuthorizationHeader();
+        responseString = sendGetStringToServer(urlString, headerArgs, okHttpClient);
+
+        return responseString;
+
+    }
+
+
+
     /**
      * Tries to download the contents of the provided url using not commercially validated CA certificate from chosen provider.
      *