tls prefs wip

This commit is contained in:
Jonathan Fisher 2020-11-09 22:02:23 +00:00
parent 29f1ccc00e
commit 63081b2cf9
14 changed files with 253 additions and 25 deletions

View File

@ -46,7 +46,7 @@ dependencies {
implementation 'androidx.activity:activity-ktx:1.1.0'
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
@ -60,8 +60,11 @@ dependencies {
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
testImplementation 'junit:junit:4.13'
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'com.google.truth:truth:1.0'
}

View File

@ -0,0 +1,44 @@
package oppen.ariane.io.gemini
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.security.SecureRandom
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
@RunWith(AndroidJUnit4::class)
class DeviceTLSTests {
lateinit var socket: SSLSocket
@Before
fun setupSocket(){
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, null, SecureRandom())
val factory: SSLSocketFactory = sslContext.socketFactory
socket = factory.createSocket() as SSLSocket
}
@Test
fun supportsTLSv1(){
socket.supportedProtocols.contains("TLSv1")
}
@Test
fun supportsTLSv1_1(){
socket.supportedProtocols.contains("TLSv1.1")
}
@Test
fun supportsTLSv1_2(){
socket.supportedProtocols.contains("TLSv1.2")
}
@Test
fun supportsTLSv1_3(){
socket.supportedProtocols.contains("TLSv1.3")
}
}

View File

@ -0,0 +1,86 @@
package oppen.ariane.io.gemini
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import oppen.ariane.Ariane
import oppen.toURI
import com.google.common.truth.Truth.assertThat
import oppen.ariane.io.GemState
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class GeminiDatasourceTests {
private lateinit var gemini: Datasource
private val capsules = listOf(
"gemini://gemini.circumlunar.space",
"gemini://rawtext.club",
"gemini://drewdevault.com",
"gemini://talon.computer",
"gemini://tilde.team",
"gemini://tilde.pink",
"gemini://gemini.conman.org",
"gemini://idiomdrottning.org"
)
private val capsuleIndex = 3
private fun setTLSProtocol(protocol: String){
gemini = Datasource.factory(InstrumentationRegistry.getInstrumentation().targetContext, protocol)
}
@Test
fun arianeHomePageTest(){
setTLSProtocol("TLSv1")
var hasRequested = false
var hasResponded = false
gemini.request(Ariane.DEFAULT_HOME_CAPSULE.toURI()){ state ->
when(state){
is GemState.Requesting -> {
assertThat(state.uri.toString()).isEqualTo(Ariane.DEFAULT_HOME_CAPSULE)
hasRequested = true
}
is GemState.ResponseGemtext -> {
assertThat(state.uri.toString()).isEqualTo(Ariane.DEFAULT_HOME_CAPSULE)
hasResponded = true
}
else -> {
//This will cause a failed test if request fails
assertThat(hasRequested).isTrue()
assertThat(hasResponded).isTrue()
}
}
}
}
@Test
fun aCapsuleTest(){
setTLSProtocol("TLSv1.3")
var hasRequested = false
var hasResponded = false
gemini.request(capsules[capsuleIndex].toURI()){ state ->
when(state){
is GemState.Requesting -> {
assertThat(state.uri.toString()).isEqualTo(capsules[capsuleIndex])
hasRequested = true
}
is GemState.ResponseGemtext -> {
assertThat(state.uri.toString()).isEqualTo(capsules[capsuleIndex])
hasResponded = true
}
else -> {
//This will cause a failed test if request fails
assertThat(hasRequested).isTrue()
assertThat(hasResponded).isTrue()
}
}
}
}
}

View File

@ -34,6 +34,8 @@
<data android:scheme="gemini" />
</intent-filter>
</activity>
<activity android:name="oppen.ariane.ui.settings.SettingsActivity" android:label="@string/settings" />
</application>
</manifest>

View File

@ -5,6 +5,7 @@ import android.os.CountDownTimer
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.core.content.ContextCompat.getSystemService
import java.net.URI
fun View.visible(visible: Boolean) = when {
@ -22,6 +23,10 @@ fun View.hideKeyboard(){
imm?.hideSoftInputFromWindow(windowToken, 0)
}
fun String.toURI(): URI {
return URI.create(this)
}
fun delay(ms: Long, action: () -> Unit){
object : CountDownTimer(ms, ms/2) {
override fun onTick(millisUntilFinished: Long) {}

View File

@ -8,8 +8,8 @@ interface Datasource {
fun request(uri: URI, onUpdate: (state: GemState) -> Unit)
companion object{
fun factory(context: Context): Datasource {
return GeminiDatasource(context)
fun factory(context: Context, protocol: String): Datasource {
return GeminiDatasource(context, protocol)
}
}
}

View File

@ -14,7 +14,15 @@ import javax.net.ssl.*
const val GEMINI_SCHEME = "gemini"
class GeminiDatasource(val context: Context): Datasource {
/**
*
*
* @param protocol see: https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
*
*/
class GeminiDatasource(
private val context: Context,
private val protocol: String): Datasource {
private var last: URI? = null
override fun request(uri: URI, onUpdate: (state: GemState) -> Unit) {
@ -116,28 +124,15 @@ class GeminiDatasource(val context: Context): Datasource {
last = uri
val port = if(uri.port == -1) 1965 else uri.port
val sslContext = SSLContext.getInstance("TLS")
val sslContext = SSLContext.getInstance(protocol)
sslContext.init(null, trustAllCerts, SecureRandom())
val factory: SSLSocketFactory = sslContext.socketFactory
val allCipher = factory.supportedCipherSuites
allCipher.forEach { suite ->
println("Supported cipher suite: $suite")
}
val socket: SSLSocket?
try {
socket = factory.createSocket(uri.host, port) as SSLSocket
socket.supportedProtocols.forEach { protocol ->
println("Supported protocol $protocol")
}
socket.enabledCipherSuites = allCipher
//socket.enabledProtocols = socket.supportedProtocols
socket.enabledCipherSuites = factory.supportedCipherSuites
socket.enabledProtocols = socket.supportedProtocols
socket.startHandshake()
}catch(ce: ConnectException){

View File

@ -34,6 +34,7 @@ import oppen.ariane.ui.modals_menus.history.HistoryDialog
import oppen.ariane.ui.modals_menus.input.InputDialog
import oppen.ariane.ui.modals_menus.overflow.OverflowPopup
import oppen.ariane.ui.modals_menus.set_home.SetHomeDialog
import oppen.ariane.ui.settings.SettingsActivity
import oppen.hideKeyboard
import oppen.visibleRetainingSpace
import java.io.File
@ -87,7 +88,6 @@ class GemActivity : AppCompatActivity() {
bookmarkDatasource = BookmarksDatasource.getDefault(applicationContext)
binding = DataBindingUtil.setContentView(this, R.layout.activity_gem)
binding.viewmodel = model
binding.lifecycleOwner = this
@ -97,9 +97,13 @@ class GemActivity : AppCompatActivity() {
history = HistoryInterface.default(this)
val prefs = getSharedPreferences("oppen.tva.ui.dialogs.set_home", Context.MODE_PRIVATE)
val home = prefs.getString("home", Ariane.DEFAULT_HOME_CAPSULE)
model.initialise(
Datasource.factory(this),
BookmarksDatasource.getDefault(applicationContext)
home = home ?: Ariane.DEFAULT_HOME_CAPSULE,
gemini = Datasource.factory(this, "TLSv1.2"),
bookmarks = BookmarksDatasource.getDefault(applicationContext)
){ state ->
binding.pullToRefresh.isRefreshing = false
@ -206,6 +210,9 @@ class GemActivity : AppCompatActivity() {
showAlert("Home capsule updated")
}
}
R.id.overflow_menu_settings -> {
startActivity(Intent(this, SettingsActivity::class.java))
}
}
}
}

View File

@ -15,12 +15,12 @@ class GemViewModel: ViewModel() {
private val history = mutableListOf<URI>()
fun initialise(gemini: Datasource, bookmarks: BookmarksDatasource, onState: (state: GemState) -> Unit){
fun initialise(home: String, gemini: Datasource, bookmarks: BookmarksDatasource, onState: (state: GemState) -> Unit){
this.gemini = gemini
this.bookmarks = bookmarks
this.onState = onState
request(URI.create(Ariane.DEFAULT_HOME_CAPSULE))//todo - regression: should check prefs...
request(home)
}
fun request(address: String) {

View File

@ -0,0 +1,14 @@
package oppen.ariane.ui.settings
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import oppen.ariane.R
class SettingsActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
supportFragmentManager.beginTransaction().replace(R.id.settings_container, SettingsFragment()).commit()
}
}

View File

@ -0,0 +1,59 @@
package oppen.ariane.ui.settings
import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import java.security.SecureRandom
import java.util.*
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
class SettingsFragment: PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener {
lateinit var protocols: Array<String>
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
val context = preferenceManager.context
val screen = preferenceManager.createPreferenceScreen(context)
val tlsCategory = PreferenceCategory(context)
tlsCategory.key = "tls_category"
tlsCategory.title = "TLS Config"
screen.addPreference(tlsCategory)
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, null, SecureRandom())
val factory: SSLSocketFactory = sslContext.socketFactory
val socket = factory.createSocket() as SSLSocket
protocols = socket.supportedProtocols
protocols.forEach { protocol ->
val tlsPreference = SwitchPreferenceCompat(context)
tlsPreference.key = "tls_${protocol.toLowerCase(Locale.getDefault())}"
tlsPreference.title = protocol
tlsPreference.onPreferenceChangeListener = this
tlsCategory.addPreference(tlsPreference)
}
preferenceScreen = screen
}
override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean {
preference?.key?.let{ key ->
if(key.startsWith("tls_")){
protocols.forEach {protocol ->
val tlsSwitchKey = "tls_${protocol.toLowerCase(Locale.getDefault())}"
if(tlsSwitchKey != key){
val otherTLSSwitch = preferenceScreen.findPreference<SwitchPreferenceCompat>(tlsSwitchKey)
otherTLSSwitch?.isChecked = false
}
}
}
}
return true
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/settings_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>

View File

@ -33,6 +33,10 @@
android:id="@+id/overflow_menu_set_home"
android:title="@string/set_home"
android:icon="@drawable/vector_set_home"/>
<item
android:id="@+id/overflow_menu_settings"
android:title="@string/settings"
android:icon="@drawable/vector_set_home"/>
<item
android:id="@+id/overflow_menu_about"
android:title="@string/about"

View File

@ -10,6 +10,7 @@
<string name="gemini_address">Gemini address</string>
<string name="share">Share</string>
<string name="set_home">Set Home</string>
<string name="settings">Settings</string>
<string name="home_icon_attribution">Home icon by Icongeek26 on FlatIcon.com</string>
<string name="about_body">Ariane: Gemini protocol client from Öppenlab</string>
<string name="gnu_link">GPL v3</string>