diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/AsyncBackend.kt b/app/src/main/java/de/kuschku/quasseldroid/service/AsyncBackend.kt
index c14d452887cf91785a089a1af13919c26c18d46d..cfc6ccc0dd6ae8e6b0c0f252e83fa4dbc07e07fb 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/service/AsyncBackend.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/service/AsyncBackend.kt
@@ -1,7 +1,7 @@
 package de.kuschku.quasseldroid.service
 
+import de.kuschku.libquassel.connection.SocketAddress
 import de.kuschku.libquassel.session.Backend
-import de.kuschku.libquassel.session.SocketAddress
 import de.kuschku.libquassel.util.compatibility.HandlerService
 
 class AsyncBackend(
diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
index 89e74d0d4b4b1adc4b939cac12db79444dcc84f8..9908f53660d59eab57cecdd238b18d2fec0c887e 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
@@ -1,15 +1,19 @@
 package de.kuschku.quasseldroid.service
 
-import android.annotation.SuppressLint
 import android.arch.lifecycle.Observer
 import android.content.*
 import android.net.ConnectivityManager
+import de.kuschku.libquassel.connection.ConnectionState
+import de.kuschku.libquassel.connection.HostnameVerifier
+import de.kuschku.libquassel.connection.SocketAddress
 import de.kuschku.libquassel.protocol.ClientData
 import de.kuschku.libquassel.protocol.Protocol
 import de.kuschku.libquassel.protocol.Protocol_Feature
 import de.kuschku.libquassel.protocol.Protocol_Features
 import de.kuschku.libquassel.quassel.QuasselFeatures
-import de.kuschku.libquassel.session.*
+import de.kuschku.libquassel.session.Backend
+import de.kuschku.libquassel.session.ISession
+import de.kuschku.libquassel.session.SessionManager
 import de.kuschku.malheur.CrashHandler
 import de.kuschku.quasseldroid.BuildConfig
 import de.kuschku.quasseldroid.Keys
@@ -19,6 +23,10 @@ import de.kuschku.quasseldroid.persistence.QuasselBacklogStorage
 import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.settings.ConnectionSettings
 import de.kuschku.quasseldroid.settings.Settings
+import de.kuschku.quasseldroid.ssl.QuasselHostnameVerifier
+import de.kuschku.quasseldroid.ssl.QuasselTrustManager
+import de.kuschku.quasseldroid.ssl.custom.QuasselCertificateManager
+import de.kuschku.quasseldroid.ssl.custom.QuasselHostnameManager
 import de.kuschku.quasseldroid.util.QuasseldroidNotificationManager
 import de.kuschku.quasseldroid.util.backport.DaggerLifecycleService
 import de.kuschku.quasseldroid.util.compatibility.AndroidHandlerService
@@ -28,7 +36,6 @@ import de.kuschku.quasseldroid.util.helper.sharedPreferences
 import de.kuschku.quasseldroid.util.helper.toLiveData
 import io.reactivex.subjects.PublishSubject
 import org.threeten.bp.Instant
-import java.security.cert.X509Certificate
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 import javax.net.ssl.X509TrustManager
@@ -158,15 +165,11 @@ class QuasselService : DaggerLifecycleService(),
 
   private lateinit var clientData: ClientData
 
-  private val trustManager = object : X509TrustManager {
-    @SuppressLint("TrustAllX509TrustManager")
-    override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) = Unit
+  private lateinit var trustManager: X509TrustManager
 
-    @SuppressLint("TrustAllX509TrustManager")
-    override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) = Unit
+  private lateinit var hostnameVerifier: HostnameVerifier
 
-    override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
-  }
+  private lateinit var certificateManager: QuasselCertificateManager
 
   private val backendImplementation = object : Backend {
     override fun updateUserDataAndLogin(user: String, pass: String) {
@@ -188,7 +191,7 @@ class QuasselService : DaggerLifecycleService(),
     override fun connect(address: SocketAddress, user: String, pass: String, reconnect: Boolean) {
       disconnect()
       sessionManager.connect(
-        clientData, trustManager, address, user to pass, reconnect
+        clientData, trustManager, hostnameVerifier, address, user to pass, reconnect
       )
     }
 
@@ -228,6 +231,11 @@ class QuasselService : DaggerLifecycleService(),
 
   override fun onCreate() {
     super.onCreate()
+
+    certificateManager = QuasselCertificateManager(database.validityWhitelist())
+    hostnameVerifier = QuasselHostnameVerifier(QuasselHostnameManager(database.hostnameWhitelist()))
+    trustManager = QuasselTrustManager(certificateManager)
+
     sessionManager = SessionManager(
       ISession.NULL,
       QuasselBacklogStorage(database),
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ssl/BrowserCompatibleHostnameVerifier.kt b/app/src/main/java/de/kuschku/quasseldroid/ssl/BrowserCompatibleHostnameVerifier.kt
new file mode 100644
index 0000000000000000000000000000000000000000..baf8bb6fb8174a2dac8b9a3f9bae4cea2023abea
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ssl/BrowserCompatibleHostnameVerifier.kt
@@ -0,0 +1,45 @@
+package de.kuschku.quasseldroid.ssl
+
+import de.kuschku.libquassel.connection.HostnameVerifier
+import de.kuschku.libquassel.connection.SocketAddress
+import java.net.IDN
+import java.security.cert.X509Certificate
+import javax.net.ssl.SSLException
+
+class BrowserCompatibleHostnameVerifier : HostnameVerifier {
+  override fun checkValid(address: SocketAddress, chain: Array<out X509Certificate>) {
+    val leafCertificate = chain.firstOrNull() ?: throw SSLException("No Certificate found")
+    val hostnames = hostnames(leafCertificate).toList()
+    if (hostnames.none { matches(it, address.host) })
+      throw SSLException("Hostname does not match")
+  }
+
+  private fun matches(name: String, host: String): Boolean {
+    val normalizedName = IDN.toASCII(name).trimEnd('.')
+    val normalizedHost = IDN.toASCII(host).trimEnd('.')
+    return normalizedName.equals(normalizedHost, ignoreCase = true)
+  }
+
+  private fun hostnames(certificate: X509Certificate): Sequence<String> =
+    (sequenceOf(commonName(certificate)) + subjectAlternativeNames(certificate))
+      .filterNotNull()
+      .distinct()
+
+  private val COMMON_NAME = Regex("""(?:^|,\s?)(?:CN=("(?:[^"]|"")+"|[^,]+))""")
+  private fun commonName(certificate: X509Certificate): String? {
+    return COMMON_NAME.find(certificate.subjectX500Principal.name)?.groups?.get(1)?.value
+  }
+
+  private fun subjectAlternativeNames(certificate: X509Certificate): Sequence<String> =
+    certificate.subjectAlternativeNames.orEmpty().asSequence().mapNotNull {
+      val type = it[0] as? Int
+      val name = it[1] as? String
+      if (type != null && name != null) Pair(type, name)
+      else null
+    }.filter { (type, _) ->
+      // 2 is DNS Name
+      type == 2
+    }.map { (_, name) ->
+      name
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ssl/QuasselHostnameVerifier.kt b/app/src/main/java/de/kuschku/quasseldroid/ssl/QuasselHostnameVerifier.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e22d23dac188b295bd8b36ba27dacd64ebd27414
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ssl/QuasselHostnameVerifier.kt
@@ -0,0 +1,23 @@
+package de.kuschku.quasseldroid.ssl
+
+import de.kuschku.libquassel.connection.HostnameVerifier
+import de.kuschku.libquassel.connection.QuasselSecurityException
+import de.kuschku.libquassel.connection.SocketAddress
+import de.kuschku.quasseldroid.ssl.custom.QuasselHostnameManager
+import java.security.cert.X509Certificate
+import javax.net.ssl.SSLException
+
+class QuasselHostnameVerifier(
+  private val hostnameManager: QuasselHostnameManager,
+  private val hostnameVerifier: HostnameVerifier = BrowserCompatibleHostnameVerifier()
+) : HostnameVerifier {
+  override fun checkValid(address: SocketAddress, chain: Array<out X509Certificate>) {
+    try {
+      if (!hostnameManager.isValid(address, chain)) {
+        hostnameVerifier.checkValid(address, chain)
+      }
+    } catch (e: SSLException) {
+      throw QuasselSecurityException.Hostname(chain, address, e)
+    }
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ssl/QuasselTrustManager.kt b/app/src/main/java/de/kuschku/quasseldroid/ssl/QuasselTrustManager.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0d5dbeee9dd6e2d5f9ca802625ea01da29e2b8cf
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ssl/QuasselTrustManager.kt
@@ -0,0 +1,50 @@
+package de.kuschku.quasseldroid.ssl
+
+import de.kuschku.libquassel.connection.QuasselSecurityException
+import de.kuschku.quasseldroid.ssl.custom.QuasselCertificateManager
+import java.security.GeneralSecurityException
+import java.security.KeyStore
+import java.security.cert.X509Certificate
+import javax.net.ssl.KeyManagerFactory
+import javax.net.ssl.TrustManagerFactory
+import javax.net.ssl.X509TrustManager
+
+class QuasselTrustManager private constructor(
+  private val certificateManager: QuasselCertificateManager,
+  private val trustManager: X509TrustManager?
+) : X509TrustManager {
+  constructor(
+    certificateManager: QuasselCertificateManager,
+    factory: TrustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply {
+      init(null as KeyStore?)
+    }
+  ) : this(
+    certificateManager,
+    factory.trustManagers.mapNotNull {
+      it as? X509TrustManager
+    }.firstOrNull()
+  )
+
+  override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
+    try {
+      trustManager?.checkClientTrusted(chain, authType)
+      ?: throw GeneralSecurityException("No TrustManager available")
+    } catch (e: GeneralSecurityException) {
+      throw QuasselSecurityException.Certificate(chain, e)
+    }
+  }
+
+  override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
+    try {
+      if (!certificateManager.isServerTrusted(chain)) {
+        trustManager?.checkServerTrusted(chain, authType)
+        ?: throw GeneralSecurityException("No TrustManager available")
+      }
+    } catch (e: GeneralSecurityException) {
+      throw QuasselSecurityException.Certificate(chain, e)
+    }
+  }
+
+  override fun getAcceptedIssuers(): Array<X509Certificate> =
+    trustManager?.acceptedIssuers ?: emptyArray()
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ssl/custom/QuasselCertificateManager.kt b/app/src/main/java/de/kuschku/quasseldroid/ssl/custom/QuasselCertificateManager.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bdb899c072b6ec02ebecb46c96d780ad445bd099
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ssl/custom/QuasselCertificateManager.kt
@@ -0,0 +1,24 @@
+package de.kuschku.quasseldroid.ssl.custom
+
+import de.kuschku.quasseldroid.persistence.QuasselDatabase
+import de.kuschku.quasseldroid.util.helper.fingerprint
+import de.kuschku.quasseldroid.util.helper.isValid
+import java.security.cert.X509Certificate
+
+class QuasselCertificateManager(
+  private val validityWhitelist: QuasselDatabase.SslValidityWhitelistDao
+) {
+  fun isServerTrusted(chain: Array<out X509Certificate>?): Boolean {
+    // Verify input conditions
+    // If no certificate exists, this can’t be valid
+    val leafCertificate = chain?.lastOrNull() ?: return false
+    return isServerTrusted(leafCertificate)
+  }
+
+  private fun isServerTrusted(leafCertificate: X509Certificate): Boolean {
+    // Verify if a whitelist entry exists
+    return validityWhitelist.find(leafCertificate.fingerprint)?.let {
+      it.ignoreDate || leafCertificate.isValid
+    } ?: false
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ssl/custom/QuasselHostnameManager.kt b/app/src/main/java/de/kuschku/quasseldroid/ssl/custom/QuasselHostnameManager.kt
new file mode 100644
index 0000000000000000000000000000000000000000..88320e9719794b975837e14b2d943f2695a9f484
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ssl/custom/QuasselHostnameManager.kt
@@ -0,0 +1,17 @@
+package de.kuschku.quasseldroid.ssl.custom
+
+import de.kuschku.libquassel.connection.SocketAddress
+import de.kuschku.quasseldroid.persistence.QuasselDatabase
+import de.kuschku.quasseldroid.util.helper.fingerprint
+import java.security.cert.X509Certificate
+
+class QuasselHostnameManager(
+  private val hostnameWhitelist: QuasselDatabase.SslHostnameWhitelistDao
+) {
+  fun isValid(address: SocketAddress, chain: Array<out X509Certificate>): Boolean {
+    val leafCertificate = chain.firstOrNull() ?: return false
+    val whitelistEntry = hostnameWhitelist.find(leafCertificate.fingerprint, address.host)
+    val all = hostnameWhitelist.all()
+    return whitelistEntry != null
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt
index 0af0dc4077f2661af288ebf3fab4d2dfcce78f86..82b9bff21f47cf751916f9898444d540417d2eff 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt
@@ -21,11 +21,13 @@ import butterknife.BindView
 import butterknife.ButterKnife
 import com.afollestad.materialdialogs.MaterialDialog
 import com.sothree.slidinguppanel.SlidingUpPanelLayout
+import de.kuschku.libquassel.connection.ConnectionState
+import de.kuschku.libquassel.connection.QuasselSecurityException
 import de.kuschku.libquassel.protocol.Buffer_Type
 import de.kuschku.libquassel.protocol.Message
 import de.kuschku.libquassel.protocol.Message_Type
 import de.kuschku.libquassel.protocol.message.HandshakeMessage
-import de.kuschku.libquassel.session.ConnectionState
+import de.kuschku.libquassel.session.Error
 import de.kuschku.libquassel.util.flag.and
 import de.kuschku.libquassel.util.flag.hasFlag
 import de.kuschku.libquassel.util.flag.or
@@ -39,14 +41,17 @@ import de.kuschku.quasseldroid.ui.chat.input.AutoCompleteAdapter
 import de.kuschku.quasseldroid.ui.chat.input.ChatlineFragment
 import de.kuschku.quasseldroid.ui.clientsettings.app.AppSettingsActivity
 import de.kuschku.quasseldroid.ui.coresettings.CoreSettingsActivity
-import de.kuschku.quasseldroid.util.helper.editCommit
-import de.kuschku.quasseldroid.util.helper.invoke
-import de.kuschku.quasseldroid.util.helper.retint
-import de.kuschku.quasseldroid.util.helper.toLiveData
+import de.kuschku.quasseldroid.util.helper.*
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid.util.service.ServiceBoundActivity
 import de.kuschku.quasseldroid.util.ui.MaterialContentLoadingProgressBar
 import de.kuschku.quasseldroid.viewmodel.data.BufferData
+import org.threeten.bp.Instant
+import org.threeten.bp.ZoneId
+import org.threeten.bp.format.DateTimeFormatter
+import org.threeten.bp.format.FormatStyle
+import java.security.cert.CertificateExpiredException
+import java.security.cert.CertificateNotYetValidException
 import javax.inject.Inject
 
 class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
@@ -154,83 +159,231 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
       }
     }
 
+    val dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
+
     viewModel.errors.toLiveData().observe(this, Observer { error ->
       error?.orNull()?.let {
         when (it) {
-          is HandshakeMessage.ClientInitReject  ->
-            MaterialDialog.Builder(this)
-              .title(R.string.label_error_init)
-              .content(Html.fromHtml(it.errorString))
-              .neutralText(R.string.label_close)
-              .titleColorAttr(R.attr.colorTextPrimary)
-              .backgroundColorAttr(R.attr.colorBackgroundCard)
-              .contentColorAttr(R.attr.colorTextPrimary)
-              .build()
-              .show()
-          is HandshakeMessage.CoreSetupReject   ->
-            MaterialDialog.Builder(this)
-              .title(R.string.label_error_setup)
-              .content(Html.fromHtml(it.errorString))
-              .neutralText(R.string.label_close)
-              .titleColorAttr(R.attr.colorTextPrimary)
-              .backgroundColorAttr(R.attr.colorBackgroundCard)
-              .contentColorAttr(R.attr.colorTextPrimary)
-              .build()
-              .show()
-          is HandshakeMessage.ClientLoginReject ->
-            MaterialDialog.Builder(this)
-              .title(R.string.label_error_login)
-              .content(Html.fromHtml(it.errorString))
-              .negativeText(R.string.label_disconnect)
-              .positiveText("Change User/Password")
-              .onNegative { _, _ ->
-                disconnect()
-              }
-              .onPositive { _, _ ->
-                runInBackground {
-                  val account = accountDatabase.accounts().findById(accountId)
-
-                  runOnUiThread {
-                    val dialog = MaterialDialog.Builder(this)
-                      .title("Login Required")
-                      .customView(R.layout.setup_account_user, false)
-                      .negativeText(R.string.label_disconnect)
-                      .positiveText(R.string.label_save)
-                      .onNegative { _, _ ->
-                        disconnect()
-                      }
-                      .onPositive { dialog, _ ->
+          is Error.HandshakeError -> it.message.let {
+            when (it) {
+              is HandshakeMessage.ClientInitReject  ->
+                MaterialDialog.Builder(this)
+                  .title(R.string.label_error_init)
+                  .content(Html.fromHtml(it.errorString))
+                  .neutralText(R.string.label_close)
+                  .titleColorAttr(R.attr.colorTextPrimary)
+                  .backgroundColorAttr(R.attr.colorBackgroundCard)
+                  .contentColorAttr(R.attr.colorTextPrimary)
+                  .build()
+                  .show()
+              is HandshakeMessage.CoreSetupReject   ->
+                MaterialDialog.Builder(this)
+                  .title(R.string.label_error_setup)
+                  .content(Html.fromHtml(it.errorString))
+                  .neutralText(R.string.label_close)
+                  .titleColorAttr(R.attr.colorTextPrimary)
+                  .backgroundColorAttr(R.attr.colorBackgroundCard)
+                  .contentColorAttr(R.attr.colorTextPrimary)
+                  .build()
+                  .show()
+              is HandshakeMessage.ClientLoginReject ->
+                MaterialDialog.Builder(this)
+                  .title(R.string.label_error_login)
+                  .content(Html.fromHtml(it.errorString))
+                  .negativeText(R.string.label_disconnect)
+                  .positiveText(R.string.label_update_user_password)
+                  .onNegative { _, _ ->
+                    disconnect()
+                  }
+                  .onPositive { _, _ ->
+                    runInBackground {
+                      val account = accountDatabase.accounts().findById(accountId)
+
+                      runOnUiThread {
+                        val dialog = MaterialDialog.Builder(this)
+                          .title(R.string.label_error_login)
+                          .customView(R.layout.setup_account_user, false)
+                          .negativeText(R.string.label_disconnect)
+                          .positiveText(R.string.label_save)
+                          .onNegative { _, _ ->
+                            disconnect()
+                          }
+                          .onPositive { dialog, _ ->
+                            dialog.customView?.run {
+                              val userField = findViewById<EditText>(R.id.user)
+                              val passField = findViewById<EditText>(R.id.pass)
+
+                              val user = userField.text.toString()
+                              val pass = passField.text.toString()
+
+                              backend.value.orNull()?.updateUserDataAndLogin(user, pass)
+                            }
+                          }
+                          .titleColorAttr(R.attr.colorTextPrimary)
+                          .backgroundColorAttr(R.attr.colorBackgroundCard)
+                          .contentColorAttr(R.attr.colorTextPrimary)
+                          .build()
                         dialog.customView?.run {
                           val userField = findViewById<EditText>(R.id.user)
                           val passField = findViewById<EditText>(R.id.pass)
 
-                          val user = userField.text.toString()
-                          val pass = passField.text.toString()
-
-                          backend.value.orNull()?.updateUserDataAndLogin(user, pass)
+                          account?.let {
+                            userField.setText(it.user)
+                          }
+                        }
+                        dialog.show()
+                      }
+                    }
+                  }
+                  .titleColorAttr(R.attr.colorTextPrimary)
+                  .backgroundColorAttr(R.attr.colorBackgroundCard)
+                  .contentColorAttr(R.attr.colorTextPrimary)
+                  .build()
+                  .show()
+            }
+          }
+          is Error.SslError       -> {
+            it.exception.let {
+              val leafCertificate = it.certificateChain?.firstOrNull()
+              if (leafCertificate == null) {
+                MaterialDialog.Builder(this)
+                  .title(R.string.label_error_certificate)
+                  .content(R.string.label_error_certificate_no_certificate)
+                  .neutralText(R.string.label_close)
+                  .titleColorAttr(R.attr.colorTextPrimary)
+                  .backgroundColorAttr(R.attr.colorBackgroundCard)
+                  .contentColorAttr(R.attr.colorTextPrimary)
+                  .build()
+                  .show()
+              } else {
+                when {
+                  it is QuasselSecurityException.Certificate &&
+                  (it.cause is CertificateNotYetValidException ||
+                   it.cause is CertificateExpiredException)  -> {
+                    MaterialDialog.Builder(this)
+                      .title(R.string.label_error_certificate)
+                      .content(
+                        Html.fromHtml(
+                          getString(
+                            R.string.label_error_certificate_invalid,
+                            leafCertificate.fingerprint,
+                            dateTimeFormatter.format(Instant.ofEpochMilli(leafCertificate.notBefore.time)
+                                                       .atZone(ZoneId.systemDefault())),
+                            dateTimeFormatter.format(Instant.ofEpochMilli(leafCertificate.notAfter.time)
+                                                       .atZone(ZoneId.systemDefault()))
+                          )
+                        )
+                      )
+                      .negativeText(R.string.label_disconnect)
+                      .positiveText(R.string.label_whitelist)
+                      .onNegative { _, _ ->
+                        disconnect()
+                      }
+                      .onPositive { _, _ ->
+                        runInBackground {
+                          database.validityWhitelist().save(
+                            QuasselDatabase.SslValidityWhitelistEntry(
+                              fingerprint = leafCertificate.fingerprint,
+                              ignoreDate = true
+                            )
+                          )
+
+                          runOnUiThread {
+                            backend.value.orNull()?.reconnect()
+                          }
                         }
                       }
                       .titleColorAttr(R.attr.colorTextPrimary)
                       .backgroundColorAttr(R.attr.colorBackgroundCard)
                       .contentColorAttr(R.attr.colorTextPrimary)
                       .build()
-                    dialog.customView?.run {
-                      val userField = findViewById<EditText>(R.id.user)
-                      val passField = findViewById<EditText>(R.id.pass)
-
-                      account?.let {
-                        userField.setText(it.user)
+                      .show()
+                  }
+                  it is QuasselSecurityException.Certificate -> {
+                    MaterialDialog.Builder(this)
+                      .title(R.string.label_error_certificate)
+                      .content(
+                        Html.fromHtml(
+                          getString(
+                            R.string.label_error_certificate_untrusted,
+                            leafCertificate.fingerprint
+                          )
+                        )
+                      )
+                      .negativeText(R.string.label_disconnect)
+                      .positiveText(R.string.label_whitelist)
+                      .onNegative { _, _ ->
+                        disconnect()
                       }
-                    }
-                    dialog.show()
+                      .onPositive { _, _ ->
+                        runInBackground {
+                          database.validityWhitelist().save(
+                            QuasselDatabase.SslValidityWhitelistEntry(
+                              fingerprint = leafCertificate.fingerprint,
+                              ignoreDate = !leafCertificate.isValid
+                            )
+                          )
+                          accountDatabase.accounts().findById(accountId)?.let {
+                            database.hostnameWhitelist().save(
+                              QuasselDatabase.SslHostnameWhitelistEntry(
+                                fingerprint = leafCertificate.fingerprint,
+                                hostname = it.host
+                              )
+                            )
+                          }
+
+                          runOnUiThread {
+                            backend.value.orNull()?.reconnect()
+                          }
+                        }
+                      }
+                      .titleColorAttr(R.attr.colorTextPrimary)
+                      .backgroundColorAttr(R.attr.colorBackgroundCard)
+                      .contentColorAttr(R.attr.colorTextPrimary)
+                      .build()
+                      .show()
+                  }
+                  it is QuasselSecurityException.Hostname    -> {
+                    MaterialDialog.Builder(this)
+                      .title(R.string.label_error_certificate)
+                      .content(
+                        Html.fromHtml(
+                          getString(
+                            R.string.label_error_certificate_no_match,
+                            leafCertificate.fingerprint,
+                            it.address.host
+                          )
+                        )
+                      )
+                      .negativeText(R.string.label_disconnect)
+                      .positiveText(R.string.label_whitelist)
+                      .onNegative { _, _ ->
+                        disconnect()
+                      }
+                      .onPositive { _, _ ->
+                        runInBackground {
+                          database.hostnameWhitelist().save(
+                            QuasselDatabase.SslHostnameWhitelistEntry(
+                              fingerprint = leafCertificate.fingerprint,
+                              hostname = it.address.host
+                            )
+                          )
+
+                          runOnUiThread {
+                            backend.value.orNull()?.reconnect()
+                          }
+                        }
+                      }
+                      .titleColorAttr(R.attr.colorTextPrimary)
+                      .backgroundColorAttr(R.attr.colorBackgroundCard)
+                      .contentColorAttr(R.attr.colorTextPrimary)
+                      .build()
+                      .show()
                   }
                 }
               }
-              .titleColorAttr(R.attr.colorTextPrimary)
-              .backgroundColorAttr(R.attr.colorBackgroundCard)
-              .contentColorAttr(R.attr.colorTextPrimary)
-              .build()
-              .show()
+            }
+          }
         }
       }
     })
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/helper/X509CertificateHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/helper/X509CertificateHelper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b4f58f40135acda41211b154b3a1a271f8e98f69
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/helper/X509CertificateHelper.kt
@@ -0,0 +1,26 @@
+package de.kuschku.quasseldroid.util.helper
+
+import org.apache.commons.codec.digest.DigestUtils
+import java.security.cert.CertificateExpiredException
+import java.security.cert.CertificateNotYetValidException
+import java.security.cert.X509Certificate
+
+val X509Certificate.isValid: Boolean
+  get() = try {
+    checkValidity()
+    true
+  } catch (e: CertificateExpiredException) {
+    false
+  } catch (e: CertificateNotYetValidException) {
+    false
+  }
+
+val X509Certificate.fingerprint: String
+  get() = DigestUtils.sha1(encoded).joinToString(":") {
+    (it.toInt() and 0xff).toString(16)
+  }
+
+val javax.security.cert.X509Certificate.fingerprint: String
+  get() = DigestUtils.sha1(encoded).joinToString(":") {
+    (it.toInt() and 0xff).toString(16)
+  }
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 5423e39630e41fb086139006a53e8b9925af1a90..651de5611c19892b00584b79418f04eba91bc146 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -12,6 +12,7 @@
   <string name="label_back">Zurück</string>
   <string name="label_buffer_name">Chatname</string>
   <string name="label_cancel">Abbrechen</string>
+  <string name="label_update_user_password">Benutzernamen/Passwort ändern</string>
   <string name="label_close">Schließen</string>
   <string name="label_colors_custom">Anpassen</string>
   <string name="label_colors_mirc">mIRC</string>
@@ -72,6 +73,7 @@
   <string name="label_topic">Kanal-Thema</string>
   <string name="label_unhide">Nicht mehr ausblenden</string>
   <string name="label_website">Webseite</string>
+  <string name="label_whitelist">Ignorieren</string>
   <string name="label_who">Who</string>
   <string name="label_who_long">Informationen aller Nutzer aktualisieren</string>
   <string name="label_whois">Whois</string>
diff --git a/app/src/main/res/values-de/strings_error.xml b/app/src/main/res/values-de/strings_error.xml
index 0ff779063466fb2b4c17de34565b56424fd8279b..b18e43f925c8f928c66af0c17966b7263adb386a 100644
--- a/app/src/main/res/values-de/strings_error.xml
+++ b/app/src/main/res/values-de/strings_error.xml
@@ -3,4 +3,16 @@
   <string name="label_error_login">Loginfehler</string>
   <string name="label_error_setup">Einrichtungsfehler</string>
   <string name="label_error_init">Verbindungsfehler</string>
-</resources>
\ No newline at end of file
+  <string name="label_error_certificate">Zertifikatsfehler</string>
+  <string name="label_error_certificate_no_certificate">Kein Zertifikat verfügbar</string>
+  <string name="label_error_certificate_no_hostname">Kein Rechnername verfügbar</string>
+  <string name="label_error_certificate_invalid"><![CDATA[
+    <p>Das Zertifikat mit Fingerabdruck <code>%1$s</code> ist nur gültig im Zeitraum von %2$s bis %3$s</p>
+  ]]></string>
+  <string name="label_error_certificate_untrusted"><![CDATA[
+    <p>Das Zertifikat mit Fingerabdruck <code>%1$s</code> ist nicht vertrauenswürdig.</p>
+  ]]></string>
+  <string name="label_error_certificate_no_match"><![CDATA[
+    <p>Das Zertifikat mit Fingerabdruck <code>%1$s</code> ist nicht gültig für %2$s.</p>
+  ]]></string>
+</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3cf9462591b1997fbe07593f862bda9a73966f9d..97b10b2f9a01b1f5598a6c27b5feb74efa362e5f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -12,6 +12,7 @@
   <string name="label_back">Back</string>
   <string name="label_buffer_name">Buffer Name</string>
   <string name="label_cancel">Cancel</string>
+  <string name="label_update_user_password">Update User/Password</string>
   <string name="label_close">Close</string>
   <string name="label_colors_custom">Custom</string>
   <string name="label_colors_mirc">mIRC</string>
@@ -72,6 +73,7 @@
   <string name="label_topic">Channel Topic</string>
   <string name="label_unhide">Make Visible</string>
   <string name="label_website">Website</string>
+  <string name="label_whitelist">Ignore</string>
   <string name="label_who">Who</string>
   <string name="label_who_long">Update user information of all users</string>
   <string name="label_whois">Whois</string>
diff --git a/app/src/main/res/values/strings_error.xml b/app/src/main/res/values/strings_error.xml
index 7e9e24a255f8dc64a7fccae8f3425fe513e3659e..4e9bdca3a71713fcfdadccaaefee475cec2939fc 100644
--- a/app/src/main/res/values/strings_error.xml
+++ b/app/src/main/res/values/strings_error.xml
@@ -3,4 +3,16 @@
   <string name="label_error_login">Login Error</string>
   <string name="label_error_setup">Setup Error</string>
   <string name="label_error_init">Connection Error</string>
-</resources>
\ No newline at end of file
+  <string name="label_error_certificate">Certificate Error</string>
+  <string name="label_error_certificate_no_certificate">No certificate available</string>
+  <string name="label_error_certificate_no_hostname">No hostname available</string>
+  <string name="label_error_certificate_invalid"><![CDATA[
+    <p>The certificate <code>%1$s</code> is only valid from %2$s to %3$s</p>
+  ]]></string>
+  <string name="label_error_certificate_untrusted"><![CDATA[
+    <p>The certificate <code>%1$s</code> is not trusted.</p>
+  ]]></string>
+  <string name="label_error_certificate_no_match"><![CDATA[
+    <p>The certificate <code>%1$s</code> is not valid for %2$s.</p>
+  ]]></string>
+</resources>
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ConnectionState.kt b/lib/src/main/java/de/kuschku/libquassel/connection/ConnectionState.kt
similarity index 71%
rename from lib/src/main/java/de/kuschku/libquassel/session/ConnectionState.kt
rename to lib/src/main/java/de/kuschku/libquassel/connection/ConnectionState.kt
index 69fdb7f0ddca6f257a46e30c9471ddba0f1b4470..af1876f5b52f7bc5d3e9986f488ef78301c7b768 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/ConnectionState.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/connection/ConnectionState.kt
@@ -1,4 +1,4 @@
-package de.kuschku.libquassel.session
+package de.kuschku.libquassel.connection
 
 enum class ConnectionState {
   DISCONNECTED,
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/CoreConnection.kt b/lib/src/main/java/de/kuschku/libquassel/connection/CoreConnection.kt
similarity index 85%
rename from lib/src/main/java/de/kuschku/libquassel/session/CoreConnection.kt
rename to lib/src/main/java/de/kuschku/libquassel/connection/CoreConnection.kt
index 69b4dd3b0888da4c344b055788159359c0ca4bc9..83755afa7e31a13f6a32b9b966bc3b6e1c7a0881 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/CoreConnection.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/connection/CoreConnection.kt
@@ -1,4 +1,4 @@
-package de.kuschku.libquassel.session
+package de.kuschku.libquassel.connection
 
 import de.kuschku.libquassel.protocol.ClientData
 import de.kuschku.libquassel.protocol.message.HandshakeMessage
@@ -8,6 +8,7 @@ import de.kuschku.libquassel.protocol.primitive.serializer.IntSerializer
 import de.kuschku.libquassel.protocol.primitive.serializer.ProtocolInfoSerializer
 import de.kuschku.libquassel.protocol.primitive.serializer.VariantListSerializer
 import de.kuschku.libquassel.quassel.ProtocolFeature
+import de.kuschku.libquassel.session.ProtocolHandler
 import de.kuschku.libquassel.util.compatibility.CompatibilityUtils
 import de.kuschku.libquassel.util.compatibility.HandlerService
 import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log
@@ -33,8 +34,10 @@ class CoreConnection(
   private val clientData: ClientData,
   private val features: Features,
   private val trustManager: X509TrustManager,
+  private val hostnameVerifier: HostnameVerifier,
   private val address: SocketAddress,
-  private val handlerService: HandlerService
+  private val handlerService: HandlerService,
+  private val securityExceptionCallback: (QuasselSecurityException) -> Unit
 ) : Thread(), Closeable {
   companion object {
     private const val TAG = "CoreConnection"
@@ -93,7 +96,7 @@ class CoreConnection(
 
     // Wrap socket in SSL context if ssl is enabled
     if (protocol.flags.hasFlag(ProtocolFeature.TLS)) {
-      channel = channel?.withSSL(trustManager, address)
+      channel = channel?.withSSL(trustManager, hostnameVerifier, address)
     }
 
     // Wrap socket in deflater if compression is enabled
@@ -126,7 +129,8 @@ class CoreConnection(
       setState(ConnectionState.CLOSED)
       interrupt()
     } catch (e: Throwable) {
-      log(WARN, TAG, "Error encountered while closing connection", e)
+      log(WARN,
+          TAG, "Error encountered while closing connection", e)
     }
   }
 
@@ -141,7 +145,8 @@ class CoreConnection(
           )
         )
       } catch (e: Throwable) {
-        log(WARN, TAG, "Error encountered while serializing handshake message", e)
+        log(WARN,
+            TAG, "Error encountered while serializing handshake message", e)
       }
     }
   }
@@ -157,7 +162,8 @@ class CoreConnection(
           )
         )
       } catch (e: Throwable) {
-        log(WARN, TAG, "Error encountered while serializing sigproxy message", e)
+        log(WARN,
+            TAG, "Error encountered while serializing sigproxy message", e)
       }
     }
   }
@@ -194,9 +200,14 @@ class CoreConnection(
           }
         }
       }
+    } catch (e: QuasselSecurityException) {
+      close()
+      securityExceptionCallback(e)
     } catch (e: Throwable) {
-      log(WARN, TAG, "Error encountered in connection", e)
-      log(WARN, TAG, "Last sent message: ${MessageRunnable.lastSent.get()}")
+      log(WARN,
+          TAG, "Error encountered in connection", e)
+      log(WARN,
+          TAG, "Last sent message: ${MessageRunnable.lastSent.get()}")
       close()
     }
   }
@@ -210,13 +221,15 @@ class CoreConnection(
         try {
           handler.handle(msg)
         } catch (e: Throwable) {
-          log(WARN, TAG, "Error encountered while handling sigproxy message", e)
+          log(WARN,
+              TAG, "Error encountered while handling sigproxy message", e)
           log(WARN, TAG, msg.toString())
         }
       }
 
     } catch (e: Throwable) {
-      log(WARN, TAG, "Error encountered while parsing sigproxy message", e)
+      log(WARN,
+          TAG, "Error encountered while parsing sigproxy message", e)
       dataBuffer.hexDump()
     }
   }
@@ -228,12 +241,14 @@ class CoreConnection(
     try {
       handler.handle(msg)
     } catch (e: Throwable) {
-      log(WARN, TAG, "Error encountered while handling handshake message", e)
+      log(WARN,
+          TAG, "Error encountered while handling handshake message", e)
       log(WARN, TAG, msg.toString())
     }
   } catch (e: Throwable) {
     log(
-      WARN, TAG, "Error encountered while parsing handshake message", e
+      WARN,
+      TAG, "Error encountered while parsing handshake message", e
     )
   }
 
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Features.kt b/lib/src/main/java/de/kuschku/libquassel/connection/Features.kt
similarity index 88%
rename from lib/src/main/java/de/kuschku/libquassel/session/Features.kt
rename to lib/src/main/java/de/kuschku/libquassel/connection/Features.kt
index e8a1d04b04dda0e5c4f2bb6f627b7f39a7d9727e..bdde0458b3850cdbfd1f7895139b29aaa3c19708 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/Features.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/connection/Features.kt
@@ -1,4 +1,4 @@
-package de.kuschku.libquassel.session
+package de.kuschku.libquassel.connection
 
 import de.kuschku.libquassel.quassel.QuasselFeatures
 
diff --git a/lib/src/main/java/de/kuschku/libquassel/connection/HostnameVerifier.kt b/lib/src/main/java/de/kuschku/libquassel/connection/HostnameVerifier.kt
new file mode 100644
index 0000000000000000000000000000000000000000..94309d66562a1e79cc839f88d684a44c5a439aac
--- /dev/null
+++ b/lib/src/main/java/de/kuschku/libquassel/connection/HostnameVerifier.kt
@@ -0,0 +1,9 @@
+package de.kuschku.libquassel.connection
+
+import java.security.cert.X509Certificate
+import javax.net.ssl.SSLException
+
+interface HostnameVerifier {
+  @Throws(SSLException::class)
+  fun checkValid(address: SocketAddress, chain: Array<out X509Certificate>)
+}
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/MessageRunnable.kt b/lib/src/main/java/de/kuschku/libquassel/connection/MessageRunnable.kt
similarity index 96%
rename from lib/src/main/java/de/kuschku/libquassel/session/MessageRunnable.kt
rename to lib/src/main/java/de/kuschku/libquassel/connection/MessageRunnable.kt
index 5ac489f341cb4077af93c68043b865cb541919db..deb00128a3f9fa8dc56f7447ca87a1e42f15724f 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/MessageRunnable.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/connection/MessageRunnable.kt
@@ -1,4 +1,4 @@
-package de.kuschku.libquassel.session
+package de.kuschku.libquassel.connection
 
 import de.kuschku.libquassel.protocol.primitive.serializer.Serializer
 import de.kuschku.libquassel.quassel.QuasselFeatures
diff --git a/lib/src/main/java/de/kuschku/libquassel/connection/QuasselSecurityException.kt b/lib/src/main/java/de/kuschku/libquassel/connection/QuasselSecurityException.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a3c40f33e10d354dd3defd1110d1efd8cfe5ff06
--- /dev/null
+++ b/lib/src/main/java/de/kuschku/libquassel/connection/QuasselSecurityException.kt
@@ -0,0 +1,20 @@
+package de.kuschku.libquassel.connection
+
+import java.security.GeneralSecurityException
+import java.security.cert.X509Certificate
+
+sealed class QuasselSecurityException(
+  val certificateChain: Array<out X509Certificate>?,
+  cause: Throwable
+) : GeneralSecurityException(cause) {
+  class Certificate(
+    certificateChain: Array<out X509Certificate>?,
+    cause: Exception
+  ) : QuasselSecurityException(certificateChain, cause)
+
+  class Hostname(
+    certificateChain: Array<out X509Certificate>?,
+    val address: SocketAddress,
+    cause: Exception
+  ) : QuasselSecurityException(certificateChain, cause)
+}
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SocketAddress.kt b/lib/src/main/java/de/kuschku/libquassel/connection/SocketAddress.kt
similarity index 77%
rename from lib/src/main/java/de/kuschku/libquassel/session/SocketAddress.kt
rename to lib/src/main/java/de/kuschku/libquassel/connection/SocketAddress.kt
index 3f7214c598a7c37dddc3617a5daaffe761c30ba3..77cf96c3416bb87d2004f30eb6a884a95f49a55d 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/SocketAddress.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/connection/SocketAddress.kt
@@ -1,4 +1,4 @@
-package de.kuschku.libquassel.session
+package de.kuschku.libquassel.connection
 
 import java.net.InetSocketAddress
 
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Backend.kt b/lib/src/main/java/de/kuschku/libquassel/session/Backend.kt
index 2af74cb885c257690ecfe9b414079ac684c7d152..6945b68a55224cee05ade42b4331996d9558fef3 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/Backend.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/Backend.kt
@@ -1,5 +1,7 @@
 package de.kuschku.libquassel.session
 
+import de.kuschku.libquassel.connection.SocketAddress
+
 interface Backend {
   fun connectUnlessConnected(address: SocketAddress, user: String, pass: String, reconnect: Boolean)
   fun connect(address: SocketAddress, user: String, pass: String, reconnect: Boolean)
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Error.kt b/lib/src/main/java/de/kuschku/libquassel/session/Error.kt
new file mode 100644
index 0000000000000000000000000000000000000000..51ca4601638f28f8e7c96da86c6a35e7e39d1975
--- /dev/null
+++ b/lib/src/main/java/de/kuschku/libquassel/session/Error.kt
@@ -0,0 +1,9 @@
+package de.kuschku.libquassel.session
+
+import de.kuschku.libquassel.connection.QuasselSecurityException
+import de.kuschku.libquassel.protocol.message.HandshakeMessage
+
+sealed class Error {
+  data class HandshakeError(val message: HandshakeMessage) : Error()
+  data class SslError(val exception: QuasselSecurityException) : Error()
+}
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt
index 6891eb9c558f7ce535ecec1c73797b339974b73b..32929ca1cf11ef95291e689882ee1bcfeb1d73fd 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt
@@ -1,8 +1,9 @@
 package de.kuschku.libquassel.session
 
+import de.kuschku.libquassel.connection.ConnectionState
+import de.kuschku.libquassel.connection.Features
 import de.kuschku.libquassel.protocol.IdentityId
 import de.kuschku.libquassel.protocol.NetworkId
-import de.kuschku.libquassel.protocol.message.HandshakeMessage
 import de.kuschku.libquassel.quassel.QuasselFeatures
 import de.kuschku.libquassel.quassel.syncables.*
 import io.reactivex.BackpressureStrategy
@@ -36,7 +37,7 @@ interface ISession : Closeable {
   val initStatus: Observable<Pair<Int, Int>>
 
   val proxy: SignalProxy
-  val error: Flowable<HandshakeMessage>
+  val error: Flowable<Error>
   val lag: Observable<Long>
 
   fun login(user: String, pass: String)
@@ -44,10 +45,11 @@ interface ISession : Closeable {
   companion object {
     val NULL = object : ISession {
       override val proxy: SignalProxy = SignalProxy.NULL
-      override val error = BehaviorSubject.create<HandshakeMessage>()
-        .toFlowable(BackpressureStrategy.BUFFER)
+      override val error = BehaviorSubject.create<Error>().toFlowable(BackpressureStrategy.BUFFER)
       override val state = BehaviorSubject.createDefault(ConnectionState.DISCONNECTED)
-      override val features: Features = Features(QuasselFeatures.empty(), QuasselFeatures.empty())
+      override val features: Features = Features(
+        QuasselFeatures.empty(),
+        QuasselFeatures.empty())
       override val sslSession: SSLSession? = null
 
       override val rpcHandler: RpcHandler? = null
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
index e9c44c094d2615f0c0f1482b2da18f1017fcb8a4..8c049f7765f01d22f30a11aef659fc5dac3f56a9 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
@@ -1,5 +1,6 @@
 package de.kuschku.libquassel.session
 
+import de.kuschku.libquassel.connection.*
 import de.kuschku.libquassel.protocol.*
 import de.kuschku.libquassel.protocol.message.HandshakeMessage
 import de.kuschku.libquassel.protocol.message.SignalProxyMessage
@@ -17,6 +18,7 @@ import javax.net.ssl.X509TrustManager
 class Session(
   clientData: ClientData,
   trustManager: X509TrustManager,
+  hostnameVerifier: HostnameVerifier,
   address: SocketAddress,
   private val handlerService: HandlerService,
   backlogStorage: BacklogStorage,
@@ -26,17 +28,18 @@ class Session(
 ) : ProtocolHandler(exceptionHandler), ISession {
   override val objectStorage: ObjectStorage = ObjectStorage(this)
   override val proxy: SignalProxy = this
-  override val features = Features(clientData.clientFeatures, QuasselFeatures.empty())
+  override val features = Features(clientData.clientFeatures,
+                                   QuasselFeatures.empty())
 
   override val sslSession
     get() = coreConnection.sslSession
 
   private val coreConnection = CoreConnection(
-    this, clientData, features, trustManager, address, handlerService
+    this, clientData, features, trustManager, hostnameVerifier, address, handlerService, ::handle
   )
   override val state = coreConnection.state
 
-  private val _error = PublishSubject.create<HandshakeMessage>()
+  private val _error = PublishSubject.create<Error>()
   override val error = _error.toFlowable(BackpressureStrategy.BUFFER)
 
   override val aliasManager = AliasManager(this)
@@ -77,7 +80,7 @@ class Session(
     if (f.coreConfigured == true) {
       login()
     } else {
-      _error.onNext(f)
+      _error.onNext(Error.HandshakeError(f))
     }
     return true
   }
@@ -102,20 +105,24 @@ class Session(
   }
 
   override fun handle(f: HandshakeMessage.ClientInitReject): Boolean {
-    _error.onNext(f)
+    _error.onNext(Error.HandshakeError(f))
     return true
   }
 
   override fun handle(f: HandshakeMessage.CoreSetupReject): Boolean {
-    _error.onNext(f)
+    _error.onNext(Error.HandshakeError(f))
     return true
   }
 
   override fun handle(f: HandshakeMessage.ClientLoginReject): Boolean {
-    _error.onNext(f)
+    _error.onNext(Error.HandshakeError(f))
     return true
   }
 
+  fun handle(f: QuasselSecurityException) {
+    _error.onNext(Error.SslError(f))
+  }
+
   fun addNetwork(networkId: NetworkId) {
     val network = Network(networkId, this)
     networks[networkId] = network
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
index 5dd5851a24ebad8ed4ee0028dd445aeeff0fa1be..9adb12b36212b7b23c4520f5cd606c2b273d0df6 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
@@ -1,7 +1,9 @@
 package de.kuschku.libquassel.session
 
+import de.kuschku.libquassel.connection.ConnectionState
+import de.kuschku.libquassel.connection.HostnameVerifier
+import de.kuschku.libquassel.connection.SocketAddress
 import de.kuschku.libquassel.protocol.ClientData
-import de.kuschku.libquassel.protocol.message.HandshakeMessage
 import de.kuschku.libquassel.quassel.syncables.interfaces.invokers.Invokers
 import de.kuschku.libquassel.util.compatibility.HandlerService
 import de.kuschku.libquassel.util.compatibility.LoggingHandler
@@ -24,6 +26,7 @@ class SessionManager(
 
   private var lastClientData: ClientData? = null
   private var lastTrustManager: X509TrustManager? = null
+  private var lastHostnameVerifier: HostnameVerifier? = null
   private var lastAddress: SocketAddress? = null
   private var lastUserData: Pair<String, String>? = null
   private var lastShouldReconnect = false
@@ -39,7 +42,7 @@ class SessionManager(
     else
       lastSession
   }
-  val error: Observable<HandshakeMessage>
+  val error: Observable<Error>
     get() = inProgressSession
       .toFlowable(BackpressureStrategy.LATEST)
       .switchMap(ISession::error)
@@ -75,6 +78,7 @@ class SessionManager(
   fun connect(
     clientData: ClientData,
     trustManager: X509TrustManager,
+    hostnameVerifier: HostnameVerifier,
     address: SocketAddress,
     userData: Pair<String, String>,
     shouldReconnect: Boolean = false
@@ -82,6 +86,7 @@ class SessionManager(
     inProgressSession.value.close()
     lastClientData = clientData
     lastTrustManager = trustManager
+    lastHostnameVerifier = hostnameVerifier
     lastAddress = address
     lastUserData = userData
     lastShouldReconnect = shouldReconnect
@@ -89,6 +94,7 @@ class SessionManager(
       Session(
         clientData,
         trustManager,
+        hostnameVerifier,
         address,
         handlerService,
         backlogStorage,
@@ -103,11 +109,12 @@ class SessionManager(
     if (lastShouldReconnect || forceReconnect) {
       val clientData = lastClientData
       val trustManager = lastTrustManager
+      val hostnameVerifier = lastHostnameVerifier
       val address = lastAddress
       val userData = lastUserData
 
-      if (clientData != null && trustManager != null && address != null && userData != null) {
-        connect(clientData, trustManager, address, userData, forceReconnect)
+      if (clientData != null && trustManager != null && hostnameVerifier != null && address != null && userData != null) {
+        connect(clientData, trustManager, hostnameVerifier, address, userData, forceReconnect)
       }
     }
   }
diff --git a/lib/src/main/java/de/kuschku/libquassel/util/nio/WrappedChannel.kt b/lib/src/main/java/de/kuschku/libquassel/util/nio/WrappedChannel.kt
index 558603f3670193d19bf388138684f820cb51f251..79f2c76b6a666fcfe7ed12c6d145e583fc42faf9 100644
--- a/lib/src/main/java/de/kuschku/libquassel/util/nio/WrappedChannel.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/util/nio/WrappedChannel.kt
@@ -1,6 +1,7 @@
 package de.kuschku.libquassel.util.nio
 
-import de.kuschku.libquassel.session.SocketAddress
+import de.kuschku.libquassel.connection.HostnameVerifier
+import de.kuschku.libquassel.connection.SocketAddress
 import de.kuschku.libquassel.util.compatibility.CompatibilityUtils
 import de.kuschku.libquassel.util.compatibility.StreamChannelFactory
 import java.io.Flushable
@@ -15,9 +16,11 @@ import java.nio.channels.InterruptibleChannel
 import java.nio.channels.ReadableByteChannel
 import java.nio.channels.WritableByteChannel
 import java.security.GeneralSecurityException
+import java.security.cert.X509Certificate
 import java.util.zip.InflaterInputStream
 import javax.net.ssl.SSLContext
 import javax.net.ssl.SSLSocket
+import javax.net.ssl.SSLSocketFactory
 import javax.net.ssl.X509TrustManager
 
 class WrappedChannel(
@@ -57,13 +60,22 @@ class WrappedChannel(
   }
 
   @Throws(GeneralSecurityException::class, IOException::class)
-  fun withSSL(certificateManager: X509TrustManager, address: SocketAddress): WrappedChannel {
+  fun withSSL(certificateManager: X509TrustManager, hostnameVerifier: HostnameVerifier,
+              address: SocketAddress): WrappedChannel {
     val context = SSLContext.getInstance("TLSv1.2")
     val managers = arrayOf(certificateManager)
     context.init(null, managers, null)
     val factory = context.socketFactory
+    SSLSocketFactory.getDefault()
+
     val socket = factory.createSocket(socket, address.host, address.port, true) as SSLSocket
     socket.useClientMode = true
+    socket.addHandshakeCompletedListener {
+      hostnameVerifier.checkValid(
+        address,
+        socket.session.peerCertificates.map { it as X509Certificate }.toTypedArray()
+      )
+    }
     socket.startHandshake()
     return WrappedChannel.ofSocket(socket)
   }
diff --git a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt
index 9ba9c3abfc9ab74cce163590f427a47beb2cb0d3..d76b9060fa3c11af659937dc00279c2dc5c4bc46 100644
--- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt
+++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt
@@ -14,12 +14,14 @@ import de.kuschku.quasseldroid.persistence.QuasselDatabase.*
 import io.reactivex.Flowable
 import org.threeten.bp.Instant
 
-@Database(entities = [DatabaseMessage::class, Filtered::class, SslException::class], version = 9)
+@Database(entities = [DatabaseMessage::class, Filtered::class, SslValidityWhitelistEntry::class, SslHostnameWhitelistEntry::class],
+          version = 13)
 @TypeConverters(DatabaseMessage.MessageTypeConverters::class)
 abstract class QuasselDatabase : RoomDatabase() {
   abstract fun message(): MessageDao
   abstract fun filtered(): FilteredDao
-  abstract fun sslExceptions(): SslExceptionDao
+  abstract fun validityWhitelist(): SslValidityWhitelistDao
+  abstract fun hostnameWhitelist(): SslHostnameWhitelistDao
 
   @Entity(tableName = "message", indices = [Index("bufferId"), Index("ignored")])
   data class DatabaseMessage(
@@ -142,29 +144,41 @@ abstract class QuasselDatabase : RoomDatabase() {
     fun clear(accountId: Long, bufferId: Int)
   }
 
-  @Entity(tableName = "ssl_exception", primaryKeys = ["accountId", "certificateFingerprint"])
-  data class SslException(
-    var accountId: Long,
-    var certificateFingerprint: String,
-    var ignoreValidityDate: Boolean
+  @Entity(tableName = "ssl_validity_whitelist")
+  data class SslValidityWhitelistEntry(
+    @PrimaryKey
+    var fingerprint: String,
+    var ignoreDate: Boolean
   )
 
   @Dao
-  interface SslExceptionDao {
+  interface SslValidityWhitelistDao {
     @Insert(onConflict = OnConflictStrategy.REPLACE)
-    fun save(vararg entities: SslException)
+    fun save(vararg entities: SslValidityWhitelistEntry)
 
-    @Query("SELECT * FROM ssl_exception WHERE accountId = :accountId AND certificateFingerprint = :certificateFingerprint")
-    fun all(accountId: Long, certificateFingerprint: String): List<SslException>
+    @Query("SELECT * FROM ssl_validity_whitelist")
+    fun all(): List<SslValidityWhitelistEntry>
 
-    @Query("DELETE FROM ssl_exception")
-    fun clear()
+    @Query("SELECT * FROM ssl_validity_whitelist WHERE fingerprint = :fingerprint")
+    fun find(fingerprint: String): SslValidityWhitelistEntry?
+  }
 
-    @Query("DELETE FROM ssl_exception WHERE accountId = :accountId")
-    fun clear(accountId: Long)
+  @Entity(tableName = "ssl_hostname_whitelist", primaryKeys = ["fingerprint", "hostname"])
+  data class SslHostnameWhitelistEntry(
+    var fingerprint: String,
+    var hostname: String
+  )
 
-    @Query("DELETE FROM ssl_exception WHERE accountId = :accountId AND certificateFingerprint = :certificateFingerprint")
-    fun clear(accountId: Long, certificateFingerprint: String)
+  @Dao
+  interface SslHostnameWhitelistDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun save(vararg entities: SslHostnameWhitelistEntry)
+
+    @Query("SELECT * FROM ssl_hostname_whitelist")
+    fun all(): List<SslHostnameWhitelistEntry>
+
+    @Query("SELECT * FROM ssl_hostname_whitelist WHERE fingerprint = :fingerprint AND hostname = :hostname")
+    fun find(fingerprint: String, hostname: String): SslHostnameWhitelistEntry?
   }
 
   object Creator {
@@ -223,6 +237,33 @@ abstract class QuasselDatabase : RoomDatabase() {
                 override fun migrate(database: SupportSQLiteDatabase) {
                   database.execSQL("create table ssl_exception (accountId INTEGER not null, certificateFingerprint TEXT not null, ignoreValidityDate INTEGER not null, primary key(accountId, certificateFingerprint));")
                 }
+              },
+              object : Migration(9, 10) {
+                override fun migrate(database: SupportSQLiteDatabase) {
+                  database.execSQL("drop table ssl_exception;")
+                  database.execSQL("create table ssl_exception (accountId INTEGER not null, hostName TEXT not null, certificateFingerprint TEXT not null, ignoreValidityDate INTEGER not null, primary key(accountId, hostName, certificateFingerprint));")
+                }
+              },
+              object : Migration(10, 11) {
+                override fun migrate(database: SupportSQLiteDatabase) {
+                  database.execSQL("drop table ssl_exception;")
+                  database.execSQL("create table ssl_exception (accountId INTEGER not null, certificateFingerprint TEXT not null, ignoreValidityDate INTEGER not null, primary key(accountId, certificateFingerprint));")
+                }
+              },
+              object : Migration(11, 12) {
+                override fun migrate(database: SupportSQLiteDatabase) {
+                  database.execSQL("drop table ssl_exception;")
+                  database.execSQL("create table ssl_validity_whitelist (fingerprint TEXT not null, ignoreDate INTEGER not null, primary key(fingerprint));")
+                  database.execSQL("create table ssl_hostname_whitelist (fingerprint TEXT not null, hostname TEXT not null, primary key(fingerprint, hostname));")
+                }
+              },
+              object : Migration(12, 13) {
+                override fun migrate(database: SupportSQLiteDatabase) {
+                  database.execSQL("drop table ssl_validity_whitelist;")
+                  database.execSQL("drop table ssl_hostname_whitelist;")
+                  database.execSQL("create table ssl_validity_whitelist (fingerprint TEXT not null, ignoreDate INTEGER not null, primary key(fingerprint));")
+                  database.execSQL("create table ssl_hostname_whitelist (fingerprint TEXT not null, hostname TEXT not null, primary key(fingerprint, hostname));")
+                }
               }
             ).build()
           }
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
index 623d7e6668633ff7e21e3b42aa28d6416e3ae0ff..eda7456ad462841dfac89913e7c4dd73f942c5e8 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
@@ -1,6 +1,7 @@
 package de.kuschku.quasseldroid.viewmodel
 
 import android.arch.lifecycle.ViewModel
+import de.kuschku.libquassel.connection.ConnectionState
 import de.kuschku.libquassel.protocol.*
 import de.kuschku.libquassel.quassel.BufferInfo
 import de.kuschku.libquassel.quassel.syncables.BufferViewConfig
@@ -8,7 +9,6 @@ import de.kuschku.libquassel.quassel.syncables.IrcChannel
 import de.kuschku.libquassel.quassel.syncables.IrcUser
 import de.kuschku.libquassel.quassel.syncables.Network
 import de.kuschku.libquassel.session.Backend
-import de.kuschku.libquassel.session.ConnectionState
 import de.kuschku.libquassel.session.ISession
 import de.kuschku.libquassel.session.SessionManager
 import de.kuschku.libquassel.util.Optional