mirror of https://git.sr.ht/~oppen/ariane
tls prefs wip
This commit is contained in:
parent
29f1ccc00e
commit
63081b2cf9
|
@ -46,7 +46,7 @@ dependencies {
|
||||||
implementation 'androidx.activity:activity-ktx:1.1.0'
|
implementation 'androidx.activity:activity-ktx:1.1.0'
|
||||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||||
implementation 'androidx.coordinatorlayout:coordinatorlayout: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-core:1.3.9'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android: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"
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
implementation "androidx.room:room-ktx:$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.ext:junit:1.1.2'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
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'
|
||||||
|
|
||||||
}
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,8 @@
|
||||||
<data android:scheme="gemini" />
|
<data android:scheme="gemini" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name="oppen.ariane.ui.settings.SettingsActivity" android:label="@string/settings" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -5,6 +5,7 @@ import android.os.CountDownTimer
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import androidx.core.content.ContextCompat.getSystemService
|
import androidx.core.content.ContextCompat.getSystemService
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
|
||||||
fun View.visible(visible: Boolean) = when {
|
fun View.visible(visible: Boolean) = when {
|
||||||
|
@ -22,6 +23,10 @@ fun View.hideKeyboard(){
|
||||||
imm?.hideSoftInputFromWindow(windowToken, 0)
|
imm?.hideSoftInputFromWindow(windowToken, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.toURI(): URI {
|
||||||
|
return URI.create(this)
|
||||||
|
}
|
||||||
|
|
||||||
fun delay(ms: Long, action: () -> Unit){
|
fun delay(ms: Long, action: () -> Unit){
|
||||||
object : CountDownTimer(ms, ms/2) {
|
object : CountDownTimer(ms, ms/2) {
|
||||||
override fun onTick(millisUntilFinished: Long) {}
|
override fun onTick(millisUntilFinished: Long) {}
|
||||||
|
|
|
@ -8,8 +8,8 @@ interface Datasource {
|
||||||
fun request(uri: URI, onUpdate: (state: GemState) -> Unit)
|
fun request(uri: URI, onUpdate: (state: GemState) -> Unit)
|
||||||
|
|
||||||
companion object{
|
companion object{
|
||||||
fun factory(context: Context): Datasource {
|
fun factory(context: Context, protocol: String): Datasource {
|
||||||
return GeminiDatasource(context)
|
return GeminiDatasource(context, protocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,7 +14,15 @@ import javax.net.ssl.*
|
||||||
|
|
||||||
const val GEMINI_SCHEME = "gemini"
|
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
|
private var last: URI? = null
|
||||||
override fun request(uri: URI, onUpdate: (state: GemState) -> Unit) {
|
override fun request(uri: URI, onUpdate: (state: GemState) -> Unit) {
|
||||||
|
@ -116,28 +124,15 @@ class GeminiDatasource(val context: Context): Datasource {
|
||||||
last = uri
|
last = uri
|
||||||
val port = if(uri.port == -1) 1965 else uri.port
|
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())
|
sslContext.init(null, trustAllCerts, SecureRandom())
|
||||||
|
|
||||||
val factory: SSLSocketFactory = sslContext.socketFactory
|
val factory: SSLSocketFactory = sslContext.socketFactory
|
||||||
|
|
||||||
val allCipher = factory.supportedCipherSuites
|
|
||||||
|
|
||||||
allCipher.forEach { suite ->
|
|
||||||
println("Supported cipher suite: $suite")
|
|
||||||
}
|
|
||||||
|
|
||||||
val socket: SSLSocket?
|
val socket: SSLSocket?
|
||||||
try {
|
try {
|
||||||
socket = factory.createSocket(uri.host, port) as SSLSocket
|
socket = factory.createSocket(uri.host, port) as SSLSocket
|
||||||
|
socket.enabledCipherSuites = factory.supportedCipherSuites
|
||||||
socket.supportedProtocols.forEach { protocol ->
|
|
||||||
println("Supported protocol $protocol")
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.enabledCipherSuites = allCipher
|
|
||||||
|
|
||||||
//socket.enabledProtocols = socket.supportedProtocols
|
|
||||||
socket.enabledProtocols = socket.supportedProtocols
|
socket.enabledProtocols = socket.supportedProtocols
|
||||||
socket.startHandshake()
|
socket.startHandshake()
|
||||||
}catch(ce: ConnectException){
|
}catch(ce: ConnectException){
|
||||||
|
|
|
@ -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.input.InputDialog
|
||||||
import oppen.ariane.ui.modals_menus.overflow.OverflowPopup
|
import oppen.ariane.ui.modals_menus.overflow.OverflowPopup
|
||||||
import oppen.ariane.ui.modals_menus.set_home.SetHomeDialog
|
import oppen.ariane.ui.modals_menus.set_home.SetHomeDialog
|
||||||
|
import oppen.ariane.ui.settings.SettingsActivity
|
||||||
import oppen.hideKeyboard
|
import oppen.hideKeyboard
|
||||||
import oppen.visibleRetainingSpace
|
import oppen.visibleRetainingSpace
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -87,7 +88,6 @@ class GemActivity : AppCompatActivity() {
|
||||||
|
|
||||||
bookmarkDatasource = BookmarksDatasource.getDefault(applicationContext)
|
bookmarkDatasource = BookmarksDatasource.getDefault(applicationContext)
|
||||||
|
|
||||||
|
|
||||||
binding = DataBindingUtil.setContentView(this, R.layout.activity_gem)
|
binding = DataBindingUtil.setContentView(this, R.layout.activity_gem)
|
||||||
binding.viewmodel = model
|
binding.viewmodel = model
|
||||||
binding.lifecycleOwner = this
|
binding.lifecycleOwner = this
|
||||||
|
@ -97,9 +97,13 @@ class GemActivity : AppCompatActivity() {
|
||||||
|
|
||||||
history = HistoryInterface.default(this)
|
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(
|
model.initialise(
|
||||||
Datasource.factory(this),
|
home = home ?: Ariane.DEFAULT_HOME_CAPSULE,
|
||||||
BookmarksDatasource.getDefault(applicationContext)
|
gemini = Datasource.factory(this, "TLSv1.2"),
|
||||||
|
bookmarks = BookmarksDatasource.getDefault(applicationContext)
|
||||||
){ state ->
|
){ state ->
|
||||||
|
|
||||||
binding.pullToRefresh.isRefreshing = false
|
binding.pullToRefresh.isRefreshing = false
|
||||||
|
@ -206,6 +210,9 @@ class GemActivity : AppCompatActivity() {
|
||||||
showAlert("Home capsule updated")
|
showAlert("Home capsule updated")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
R.id.overflow_menu_settings -> {
|
||||||
|
startActivity(Intent(this, SettingsActivity::class.java))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,12 @@ class GemViewModel: ViewModel() {
|
||||||
|
|
||||||
private val history = mutableListOf<URI>()
|
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.gemini = gemini
|
||||||
this.bookmarks = bookmarks
|
this.bookmarks = bookmarks
|
||||||
this.onState = onState
|
this.onState = onState
|
||||||
|
|
||||||
request(URI.create(Ariane.DEFAULT_HOME_CAPSULE))//todo - regression: should check prefs...
|
request(home)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun request(address: String) {
|
fun request(address: String) {
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -33,6 +33,10 @@
|
||||||
android:id="@+id/overflow_menu_set_home"
|
android:id="@+id/overflow_menu_set_home"
|
||||||
android:title="@string/set_home"
|
android:title="@string/set_home"
|
||||||
android:icon="@drawable/vector_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
|
<item
|
||||||
android:id="@+id/overflow_menu_about"
|
android:id="@+id/overflow_menu_about"
|
||||||
android:title="@string/about"
|
android:title="@string/about"
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
<string name="gemini_address">Gemini address</string>
|
<string name="gemini_address">Gemini address</string>
|
||||||
<string name="share">Share</string>
|
<string name="share">Share</string>
|
||||||
<string name="set_home">Set Home</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="home_icon_attribution">Home icon by Icongeek26 on FlatIcon.com</string>
|
||||||
<string name="about_body">Ariane: Gemini protocol client from Öppenlab</string>
|
<string name="about_body">Ariane: Gemini protocol client from Öppenlab</string>
|
||||||
<string name="gnu_link">GPL v3</string>
|
<string name="gnu_link">GPL v3</string>
|
||||||
|
|
Loading…
Reference in New Issue