Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@ android {

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.0.2'
Comment on lines -45 to 46
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I... am not sure why I needed to change this. Probably Android Studio was complaining. It's probably not needed, right?

I did check and I think the project has somewhat-outdated gradle and SDK versions... but I don't know if this change will be good.

implementation 'com.android.support:design:27.0.2'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.squareup.okhttp3:okhttp-tls:$okhttp_version"
}
157 changes: 157 additions & 0 deletions app/src/main/java/io/github/zadam/triliumsender/CustomTrustClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package io.github.zadam.triliumsender

import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.tls.HandshakeCertificates
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.security.KeyManagementException
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager

class CustomTrustClient {
Copy link
Author

@jkurei jkurei May 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class looked much prettier in Java, I think. I don't know Kotlin, but I guessed you wouldn't want to mix languages.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

companion object {
// PEM files for root certificates of Comodo and Entrust. These two CAs are sufficient to view
// https://publicobject.com (Comodo) and https://squareup.com (Entrust). But they aren't
// sufficient to connect to most HTTPS sites including https://godaddy.com and https://visa.com.
// Typically developers will need to get a PEM file from their organization's TLS administrator.
var certificateFactory: CertificateFactory? = null
var letsencryptPem = """
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----
""".trimIndent()

@Throws(CertificateException::class)
private fun getCertFromPem(pem: String): X509Certificate {
val buf = pem.toByteArray()
val `is`: InputStream = ByteArrayInputStream(buf)
return certificateFactory!!.generateCertificate(`is`) as X509Certificate
}

private var client: OkHttpClient? = null

@Throws(CertificateException::class)
fun initializeClientWithDefaultTrust(): OkHttpClient? {
val certificates = HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.addTrustedCertificate(getCertFromPem(letsencryptPem))
.build()
client = OkHttpClient.Builder()
.sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager())
.build()
return client
}

@Throws(CertificateException::class)
fun initializeClientWithCustomCert(pem: String): OkHttpClient? {
val certificates = HandshakeCertificates.Builder()
.addTrustedCertificate(getCertFromPem(pem))
.build()
client = OkHttpClient.Builder()
.sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager())
.build()
return client
}

@Throws(KeyManagementException::class, NoSuchAlgorithmException::class)
fun initializeClientAndTrustAll(): OkHttpClient {
// Create a trust manager that does not validate certificate chains
val trustAllCerts = arrayOf<TrustManager>(
object : X509TrustManager {
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
}

@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
}

override fun getAcceptedIssuers(): Array<X509Certificate> {
return arrayOf()
}
}
)

// Install the all-trusting trust manager
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, SecureRandom())
// Create an ssl socket factory with our all-trusting manager
val sslSocketFactory = sslContext.socketFactory
val builder = OkHttpClient.Builder()
builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
builder.hostnameVerifier { hostname, session -> true }
return builder.build()
}

@Throws(CertificateException::class)
fun getClient(): OkHttpClient? {
if (client == null) {
initializeClientWithDefaultTrust()
}
return client
}

@Throws(Exception::class)
@JvmStatic
fun main(args: Array<String>) {
CustomTrustClient().run()
}

init {
try {
certificateFactory = CertificateFactory.getInstance("X.509")
} catch (e: CertificateException) {
e.printStackTrace()
}
}
}

@Throws(Exception::class)
fun run() {
val request = Request.Builder()
.url("https://publicobject.com/helloworld.txt")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you pls explain what purpose has this call to this website?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely none, just test code. I'll remove it.

.build()
client!!.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
val responseHeaders = response.headers()
for (i in 0 until responseHeaders.size()) {
println(responseHeaders.name(i) + ": " + responseHeaders.value(i))
}
throw IOException("Unexpected code $response")
}
println(response.body()!!.string())
}
}
}
39 changes: 36 additions & 3 deletions app/src/main/java/io/github/zadam/triliumsender/LoginActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import android.text.TextUtils
import android.util.Log
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.RadioGroup
import android.widget.TextView
import android.widget.Toast
import io.github.zadam.triliumsender.services.TriliumSettings
import io.github.zadam.triliumsender.services.Utils
import kotlinx.android.synthetic.main.activity_login.*
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
Expand All @@ -37,7 +37,31 @@ class LoginActivity : AppCompatActivity() {
false
})

loginButton.setOnClickListener { attemptLogin() }
trustModeRadio.setOnCheckedChangeListener(
RadioGroup.OnCheckedChangeListener { radio, _ ->
val checked = radio.checkedRadioButtonId

if (checked == useDefaultTrust.id) {
trustAllWarning.visibility = View.GONE
customCertInput.visibility = View.GONE
} else if (checked == useCustomCert.id) {
trustAllWarning.visibility = View.GONE
customCertInput.visibility = View.VISIBLE
} else {
trustAllWarning.visibility = View.VISIBLE
customCertInput.visibility = View.GONE
}
}
)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps I just can't see it, but is the cert setting saved somewhere? I can see that the login will be attempted with the chosen cert setting, but then when I close the app and want to use it to send some text note to trilium server, how will the app know which cert settings has been chosen before?


loginButton.setOnClickListener {
if (trustModeRadio.checkedRadioButtonId == useCustomCert.id && customCertInput.text.isEmpty()) {
Toast.makeText(this, "Missing Cert", "Missing Cert".length).show()
}
else {
attemptLogin()
}
}
}

/**
Expand Down Expand Up @@ -92,7 +116,16 @@ class LoginActivity : AppCompatActivity() {

override fun doInBackground(vararg params: Void): LoginResult {

val client = OkHttpClient()
val client = if (trustModeRadio.checkedRadioButtonId == useDefaultTrust.id) {
CustomTrustClient.initializeClientWithDefaultTrust()
}
else if (trustModeRadio.checkedRadioButtonId == useCustomCert.id) {
CustomTrustClient.initializeClientWithCustomCert(customCertInput.text.toString())
}
else {
CustomTrustClient.initializeClientAndTrustAll()
}


val json = JSONObject()
json.put("username", username)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import io.github.zadam.triliumsender.services.HtmlConverter
import io.github.zadam.triliumsender.services.TriliumSettings
import io.github.zadam.triliumsender.services.Utils
import kotlinx.android.synthetic.main.activity_send_note.*
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import org.json.JSONObject
Expand All @@ -28,7 +27,7 @@ class SendNoteActivity : AppCompatActivity() {
return
}

sendNoteButton.setOnClickListener { view ->
sendNoteButton.setOnClickListener { _ ->
val sendImageTask = SendNoteTask(noteTitleEditText.text.toString(), noteContentEditText.text.toString(), settings.triliumAddress, settings.apiToken)
sendImageTask.execute(null as Void?)
}
Expand All @@ -42,7 +41,7 @@ class SendNoteActivity : AppCompatActivity() {
val TAG : String = "SendNoteTask"

override fun doInBackground(vararg params: Void): Boolean {
val client = OkHttpClient()
val client = CustomTrustClient.getClient()

val json = JSONObject()
json.put("title", noteTitle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import io.github.zadam.triliumsender.services.TriliumSettings
import io.github.zadam.triliumsender.services.Utils
import okhttp3.MediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request


Expand Down Expand Up @@ -51,7 +50,7 @@ class ShareActivity : AppCompatActivity() {

val (requestBody, contentLength) = buildRequestBody()

val client = OkHttpClient()
val client = CustomTrustClient.getClient()

val request = Request.Builder()
.url(triliumAddress + "/api/sender/image")
Expand Down
64 changes: 57 additions & 7 deletions app/src/main/res/layout/activity_login.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
<ScrollView
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:layout_weight="1">

<LinearLayout
android:id="@+id/email_login_form"
Expand Down Expand Up @@ -79,15 +80,64 @@

</android.support.design.widget.TextInputLayout>

<Button
android:id="@+id/loginButton"
style="?android:textAppearanceSmall"
<RadioGroup
android:id="@+id/trustModeRadio"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:checkedButton="@id/useDefaultTrust"
android:orientation="horizontal">

<RadioButton
android:id="@+id/useDefaultTrust"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Default trust" />

<RadioButton
android:id="@+id/useCustomCert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Use custom cert" />

<RadioButton
android:id="@+id/trustAll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Trust all certs" />

</RadioGroup>

<TextView
android:id="@+id/trustAllWarning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="This could expose you to man-in-the-middle attacks, among other security risk. You should at least use a self-signed cert."
android:visibility="invisible" />

<EditText
android:id="@+id/customCertInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_login"
android:textStyle="bold" />
android:ems="10"
android:gravity="start|top"
android:inputType="textMultiLine"
android:visibility="invisible" />

</LinearLayout>
</ScrollView>

<Button
android:id="@+id/loginButton"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/action_login"
android:textStyle="bold"
android:fitsSystemWindows="true"
android:layout_gravity="center_horizontal|bottom" />
</LinearLayout>
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
ext.kotlin_version = '1.2.20'
ext.okhttp_version = '3.12.11'

repositories {
google()
jcenter()
Expand Down