Add Session Id blinding (#862)

* feat: Add Session Id blinding

Including modified version of lazysodium-android to expose missing libsodium functions, we could build from a fork which we still need to setup.

* Add v4 onion request handling

* Update SOGS signature construction

* Fix SOGS signature construction

* Update onion request

* Update signature data

* Keep path prefixes for v4 endpoints

* Update SOGS signature message

* Rename to remove api version suffix

* Update onion response parsing

* Refactor file download paths

* Implement request batching

* Refactor batch response handling

* Handle batch endpoint responses

* Update batch endpoint responses

* Update attachment download handling

* Handle file downloads

* Handle inbox messages

* Fix issue with file downloads

* Preserve image bytearray encoding

* Refactor

* Open group message requests

* Check id blinding in user detail bottom sheet rather

* Message validation refactor

* Cache last inbox/outbox server ids

* Update message encryption/decryption

* Refactor

* Refactor

* Bypass user details bottom sheet in open groups for blinded session ids

* Fix capabilities call auth

* Refactor

* Revert default server details

* Update sodium dependency to forked repo

* Fix attachment upload

* Revert "Update sodium dependency to forked repo"

This reverts commit c7db9529f9.

* Add signed sodium lib

* Update contact id truncation and mention logic

* Open group inbox messaging fix

* Refactor

* Update blinded id check

* Fix open group message sends

* Fix crash on open group direct message send

* Direct message refactor

* Direct message encrypt/decrypt fixes

* Use updated curve25519 version

* Updated lazysodium dependency

* Update encryption/decryption calls

* Handle direct message parse errors

* Minor refactor

* Existing chat refactor

* Update encryption & decryption parameters

* Fix authenticated ciphertext size

* Set direct message sync target

* Update direct message thread lookup

* Add blinded id mapping table

* Add blinded id mapping table

* Update threads after sends

* Update open group message timestamp handling

* Filter unblinded contacts

* Format blinded id mentions

* Add message deleted field

* Hide open group inbox id

* Update message request response handling

* Update message request response sender handling

* Fix mentions of blinded ids

* Handle open group poll failure

* fix: add log for failed open group onion request, add decoding body for blinding required error at destination

* fix: change the error check

* Persist group members

* Reschedule polling after capabilities update

* Retry on other exceptions

* Minor refactor

* Open group profile fix

* Group member db schema update

* Fix ban request key

* Update ban response type

* Ban endpoint updates

* Ban endpoint updates

* Delete messages

Co-authored-by: charles <charles@oxen.io>
Co-authored-by: jubb <hjubb@users.noreply.github.com>
This commit is contained in:
ceokot
2022-08-10 18:17:48 +10:00
committed by GitHub
parent b1e954084c
commit bee287bb7e
90 changed files with 3192 additions and 1190 deletions

View File

@@ -74,26 +74,26 @@ object HTTP {
/**
* Sync. Don't call from the main thread.
*/
fun execute(verb: Verb, url: String, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): Map<*, *> {
fun execute(verb: Verb, url: String, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
return execute(verb = verb, url = url, body = null, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
}
/**
* Sync. Don't call from the main thread.
*/
fun execute(verb: Verb, url: String, parameters: Map<String, Any>?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): Map<*, *> {
if (parameters != null) {
fun execute(verb: Verb, url: String, parameters: Map<String, Any>?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
return if (parameters != null) {
val body = JsonUtil.toJson(parameters).toByteArray()
return execute(verb = verb, url = url, body = body, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
execute(verb = verb, url = url, body = body, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
} else {
return execute(verb = verb, url = url, body = null, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
execute(verb = verb, url = url, body = null, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
}
}
/**
* Sync. Don't call from the main thread.
*/
fun execute(verb: Verb, url: String, body: ByteArray?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): Map<*, *> {
fun execute(verb: Verb, url: String, body: ByteArray?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
val request = Request.Builder().url(url)
.removeHeader("User-Agent").addHeader("User-Agent", "WhatsApp") // Set a fake value
.removeHeader("Accept-Language").addHeader("Accept-Language", "en-us") // Set a fake value
@@ -109,14 +109,13 @@ object HTTP {
}
lateinit var response: Response
try {
val connection: OkHttpClient
if (timeout != HTTP.timeout) { // Custom timeout
val connection = if (timeout != HTTP.timeout) { // Custom timeout
if (useSeedNodeConnection) {
throw IllegalStateException("Setting a custom timeout is only allowed for requests to snodes.")
}
connection = getDefaultConnection(timeout)
getDefaultConnection(timeout)
} else {
connection = if (useSeedNodeConnection) seedNodeConnection else defaultConnection
if (useSeedNodeConnection) seedNodeConnection else defaultConnection
}
response = connection.newCall(request.build()).execute()
} catch (exception: Exception) {
@@ -124,14 +123,9 @@ object HTTP {
// Override the actual error so that we can correctly catch failed requests in OnionRequestAPI
throw HTTPRequestFailedException(0, null)
}
when (val statusCode = response.code()) {
return when (val statusCode = response.code()) {
200 -> {
val bodyAsString = response.body()?.string() ?: throw Exception("An error occurred.")
try {
return JsonUtil.fromJson(bodyAsString, Map::class.java)
} catch (exception: Exception) {
return mapOf( "result" to bodyAsString)
}
response.body()?.bytes() ?: throw Exception("An error occurred.")
}
else -> {
Log.d("Loki", "${verb.rawValue} request to $url failed with status code: $statusCode.")

View File

@@ -0,0 +1,15 @@
package org.session.libsignal.utilities
enum class IdPrefix(val value: String) {
STANDARD("05"), BLINDED("15"), UN_BLINDED("00");
companion object {
fun fromValue(rawValue: String): IdPrefix? = when(rawValue.take(2)) {
STANDARD.value -> STANDARD
BLINDED.value -> BLINDED
UN_BLINDED.value -> UN_BLINDED
else -> null
}
}
}

View File

@@ -1,6 +1,7 @@
package org.session.libsignal.utilities;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
@@ -30,6 +31,10 @@ public class JsonUtil {
return fromJson(new String(serialized), clazz);
}
public static <T> T fromJson(String serialized, TypeReference<T> typeReference) throws IOException {
return objectMapper.readValue(serialized, typeReference);
}
public static <T> T fromJson(String serialized, Class<T> clazz) throws IOException {
return objectMapper.readValue(serialized, clazz);
}

View File

@@ -1,12 +1,10 @@
package org.session.libsignal.utilities
import org.session.libsignal.utilities.Hex
fun String.removing05PrefixIfNeeded(): String {
return if (length == 66) removePrefix("05") else this
fun String.removingIdPrefixIfNeeded(): String {
return if (length == 66 && IdPrefix.fromValue(this) != null) removeRange(0..1) else this
}
fun ByteArray.removing05PrefixIfNeeded(): ByteArray {
val string = Hex.toStringCondensed(this).removing05PrefixIfNeeded()
fun ByteArray.removingIdPrefixIfNeeded(): ByteArray {
val string = Hex.toStringCondensed(this).removingIdPrefixIfNeeded()
return Hex.fromStringCondensed(string)
}

View File

@@ -10,9 +10,9 @@ object PublicKeyValidation {
@JvmStatic
fun isValid(candidate: String, expectedLength: Int, isPrefixRequired: Boolean): Boolean {
val hexCharacters = "0123456789ABCDEF".toSet()
val isValidHexEncoding = hexCharacters.containsAll(candidate.toUpperCase().toSet())
val isValidHexEncoding = hexCharacters.containsAll(candidate.uppercase().toSet())
val hasValidLength = candidate.length == expectedLength
val hasValidPrefix = if (isPrefixRequired) candidate.startsWith("05") else true
val hasValidPrefix = if (isPrefixRequired) IdPrefix.fromValue(candidate) != null else true
return isValidHexEncoding && hasValidLength && hasValidPrefix
}
}