Skip to content
Draft
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
12 changes: 12 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,24 @@ jobs:
- name: Linter (gradle)
run: ./gradlew ktlintCheck

- name: Android lint (gradle)
run: ./gradlew lint --no-daemon

- name: Test (gradle)
run: ./gradlew test --no-daemon

- name: Python security regressions
run: |
if [ -d tests ]; then
python3 -m unittest discover -s tests -p 'test_*.py' -v
fi

- name: Build APK (gradle)
run: ./gradlew assembleDebug --no-daemon

- name: Build free release APK (gradle)
run: ./gradlew :app:assembleFreeRelease --no-daemon

- name: Upload APK
uses: actions/upload-artifact@v7
with:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.greenart7c3.nostrsigner.service

import com.greenart7c3.nostrsigner.okhttp.TaggedSocketFactory
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.models.TorMode
import com.greenart7c3.nostrsigner.okhttp.HttpClientManager
import java.io.IOException
import java.net.Proxy
import java.util.concurrent.TimeUnit
import javax.net.SocketFactory
import okhttp3.Credentials
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
Expand All @@ -13,12 +15,19 @@ import okhttp3.RequestBody.Companion.toRequestBody
object WebDavService {
private val MEDIA_TYPE = "application/octet-stream".toMediaType()

private fun createClient(): OkHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.socketFactory(TaggedSocketFactory(SocketFactory.getDefault()))
.build()
private fun createClient(): OkHttpClient {
check(Amber.instance.isSettingsLoaded) { "Network settings are not loaded yet" }
val useProxy = Amber.instance.settings.torMode != TorMode.DISABLED
val client = HttpClientManager.getHttpClient(useProxy)
check(!useProxy || client.proxy?.type() == Proxy.Type.SOCKS) { "Tor proxy is not configured yet" }
return client.newBuilder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.followRedirects(false)
.followSslRedirects(false)
.build()
}

fun uploadFile(
serverUrl: String,
Expand All @@ -27,6 +36,7 @@ object WebDavService {
fileName: String,
content: String,
): Result<Unit> = try {
WebDavTransportPolicy.requireSecureServerUrl(serverUrl)
val client = createClient()
val url = serverUrl.trimEnd('/') + "/" + fileName
val credential = Credentials.basic(username, password)
Expand Down Expand Up @@ -54,6 +64,7 @@ object WebDavService {
password: String,
fileName: String,
): Result<String> = try {
WebDavTransportPolicy.requireSecureServerUrl(serverUrl)
val client = createClient()
val url = serverUrl.trimEnd('/') + "/" + fileName
val credential = Credentials.basic(username, password)
Expand Down Expand Up @@ -82,6 +93,7 @@ object WebDavService {
username: String,
password: String,
): Result<Unit> = try {
WebDavTransportPolicy.requireSecureServerUrl(serverUrl)
val client = createClient()
val url = serverUrl.trimEnd('/')
val credential = Credentials.basic(username, password)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.greenart7c3.nostrsigner.service

import java.net.URI

object WebDavTransportPolicy {
fun requireSecureServerUrl(serverUrl: String) {
val uri = URI(serverUrl)
require(uri.scheme.equals("https", ignoreCase = true) && !uri.host.isNullOrBlank()) {
"WebDAV requires an HTTPS server URL"
}
require(uri.userInfo == null && uri.query == null && uri.fragment == null) {
"WebDAV server URLs must not contain credentials, a query, or a fragment"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.greenart7c3.nostrsigner.service

import org.junit.Assert.assertThrows
import org.junit.Test

class WebDavTransportPolicyTest {
@Test
fun `credentials cannot be sent to cleartext or ambiguous URLs`() {
listOf("http://backup.example", "https://user:pass@backup.example", "https://backup.example?token=secret", "https://backup.example#fragment").forEach {
assertThrows(IllegalArgumentException::class.java) { WebDavTransportPolicy.requireSecureServerUrl(it) }
}
}

@Test
fun `secure server with a base path is accepted`() {
WebDavTransportPolicy.requireSecureServerUrl("https://backup.example:8443/remote.php/dav/files/user")
}
}
Loading