Merge branch 'dev' into merge/1.19.0

This commit is contained in:
ThomasSession
2024-08-08 10:55:55 +10:00
110 changed files with 882 additions and 1328 deletions

View File

@@ -63,7 +63,6 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
// return a list of batch request objects
val snodeMessage = MessageSender.buildConfigMessageToSnode(destination.destinationPublicKey(), message)
val authenticated = SnodeAPI.buildAuthenticatedStoreBatchInfo(
destination.destinationPublicKey(),
config.configNamespace(),
snodeMessage
) ?: return@map null // this entry will be null otherwise

View File

@@ -12,11 +12,11 @@ import org.session.libsession.utilities.Util.equals
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.streams.ProfileCipherInputStream
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Util.SECURE_RANDOM
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.security.SecureRandom
import java.util.concurrent.ConcurrentSkipListSet
class RetrieveProfileAvatarJob(private val profileAvatar: String?, val recipientAddress: Address): Job {
@@ -64,7 +64,7 @@ class RetrieveProfileAvatarJob(private val profileAvatar: String?, val recipient
Log.w(TAG, "Removing profile avatar for: " + recipient.address.serialize())
if (recipient.isLocalNumber) {
setProfileAvatarId(context, SecureRandom().nextInt())
setProfileAvatarId(context, SECURE_RANDOM.nextInt())
setProfilePictureURL(context, null)
}
@@ -83,7 +83,7 @@ class RetrieveProfileAvatarJob(private val profileAvatar: String?, val recipient
decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.address))
if (recipient.isLocalNumber) {
setProfileAvatarId(context, SecureRandom().nextInt())
setProfileAvatarId(context, SECURE_RANDOM.nextInt())
setProfilePictureURL(context, profileAvatar)
}

View File

@@ -10,7 +10,7 @@ import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.MessageReceiveParameters
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.GroupUtil
import org.session.libsignal.crypto.getRandomElementOrNull
import org.session.libsignal.crypto.secureRandomOrNull
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Namespace
import org.session.libsignal.utilities.defaultRequiresAuth
@@ -104,7 +104,7 @@ class ClosedGroupPollerV2 {
fun poll(groupPublicKey: String): Promise<Unit, Exception> {
if (!isPolling(groupPublicKey)) { return Promise.of(Unit) }
val promise = SnodeAPI.getSwarm(groupPublicKey).bind { swarm ->
val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure
val snode = swarm.secureRandomOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure
if (!isPolling(groupPublicKey)) { throw PollingCanceledException() }
val currentForkInfo = SnodeAPI.forkInfo
when {

View File

@@ -19,8 +19,6 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.MessageReceiveParameters
import org.session.libsession.messaging.messages.control.SharedConfigurationMessage
import org.session.libsession.messaging.sending_receiving.MessageReceiver
import org.session.libsession.snode.RawResponse
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeModule
@@ -29,7 +27,7 @@ import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Namespace
import org.session.libsignal.utilities.Snode
import java.security.SecureRandom
import org.session.libsignal.utilities.Util.SECURE_RANDOM
import java.util.Timer
import java.util.TimerTask
import kotlin.time.Duration.Companion.days
@@ -106,7 +104,7 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
val swarm = SnodeModule.shared.storage.getSwarm(userPublicKey) ?: setOf()
val unusedSnodes = swarm.subtract(usedSnodes)
if (unusedSnodes.isNotEmpty()) {
val index = SecureRandom().nextInt(unusedSnodes.size)
val index = SECURE_RANDOM.nextInt(unusedSnodes.size)
val nextSnode = unusedSnodes.elementAt(index)
usedSnodes.add(nextSnode)
Log.d(TAG, "Polling $nextSnode.")
@@ -142,8 +140,7 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
val messages = rawMessages["messages"] as? List<*>
val processed = if (!messages.isNullOrEmpty()) {
SnodeAPI.updateLastMessageHashValueIfPossible(snode, userPublicKey, messages, namespace)
SnodeAPI.removeDuplicates(userPublicKey, messages, namespace, true).mapNotNull { messageBody ->
val rawMessageAsJSON = messageBody as? Map<*, *> ?: return@mapNotNull null
SnodeAPI.removeDuplicates(userPublicKey, messages, namespace, true).mapNotNull { rawMessageAsJSON ->
val hashValue = rawMessageAsJSON["hash"] as? String ?: return@mapNotNull null
val b64EncodedBody = rawMessageAsJSON["data"] as? String ?: return@mapNotNull null
val timestamp = rawMessageAsJSON["t"] as? Long ?: SnodeAPI.nowWithOffset

View File

@@ -28,7 +28,7 @@ object MessageWrapper {
val webSocketMessage = createWebSocketMessage(envelope)
return webSocketMessage.toByteArray()
} catch (e: Exception) {
throw if (e is Error) { e } else { Error.FailedToWrapData }
throw if (e is Error) e else Error.FailedToWrapData
}
}
@@ -49,15 +49,15 @@ object MessageWrapper {
private fun createWebSocketMessage(envelope: Envelope): WebSocketMessage {
try {
val requestBuilder = WebSocketRequestMessage.newBuilder()
requestBuilder.verb = "PUT"
requestBuilder.path = "/api/v1/message"
requestBuilder.id = SecureRandom.getInstance("SHA1PRNG").nextLong()
requestBuilder.body = envelope.toByteString()
val messageBuilder = WebSocketMessage.newBuilder()
messageBuilder.request = requestBuilder.build()
messageBuilder.type = WebSocketMessage.Type.REQUEST
return messageBuilder.build()
return WebSocketMessage.newBuilder().apply {
request = WebSocketRequestMessage.newBuilder().apply {
verb = "PUT"
path = "/api/v1/message"
id = SecureRandom.getInstance("SHA1PRNG").nextLong()
body = envelope.toByteString()
}.build()
type = WebSocketMessage.Type.REQUEST
}.build()
} catch (e: Exception) {
Log.d("Loki", "Failed to wrap envelope in web socket message: ${e.message}.")
throw Error.FailedToWrapEnvelopeInWebSocketMessage

View File

@@ -10,11 +10,10 @@ import okhttp3.Request
import org.session.libsession.messaging.file_server.FileServerApi
import org.session.libsession.utilities.AESGCM
import org.session.libsession.utilities.AESGCM.EncryptionResult
import org.session.libsession.utilities.Util
import org.session.libsession.utilities.getBodyForOnionRequest
import org.session.libsession.utilities.getHeadersForOnionRequest
import org.session.libsignal.crypto.getRandomElement
import org.session.libsignal.crypto.getRandomElementOrNull
import org.session.libsignal.crypto.secureRandom
import org.session.libsignal.crypto.secureRandomOrNull
import org.session.libsignal.database.LokiAPIDatabaseProtocol
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Broadcaster
@@ -149,7 +148,7 @@ object OnionRequestAPI {
val reusableGuardSnodeCount = reusableGuardSnodes.count()
if (unusedSnodes.count() < (targetGuardSnodeCount - reusableGuardSnodeCount)) { throw InsufficientSnodesException() }
fun getGuardSnode(): Promise<Snode, Exception> {
val candidate = unusedSnodes.getRandomElementOrNull()
val candidate = unusedSnodes.secureRandomOrNull()
?: return Promise.ofFail(InsufficientSnodesException())
unusedSnodes = unusedSnodes.minus(candidate)
Log.d("Loki", "Testing guard snode: $candidate.")
@@ -191,7 +190,7 @@ object OnionRequestAPI {
// Don't test path snodes as this would reveal the user's IP to them
guardSnodes.minus(reusableGuardSnodes).map { guardSnode ->
val result = listOf( guardSnode ) + (0 until (pathSize - 1)).mapIndexed() { index, _ ->
var pathSnode = unusedSnodes.getRandomElement()
var pathSnode = unusedSnodes.secureRandom()
// remove the snode from the unused list and return it
unusedSnodes = unusedSnodes.minus(pathSnode)
@@ -228,9 +227,9 @@ object OnionRequestAPI {
OnionRequestAPI.guardSnodes = guardSnodes
fun getPath(paths: List<Path>): Path {
return if (snodeToExclude != null) {
paths.filter { !it.contains(snodeToExclude) }.getRandomElement()
paths.filter { !it.contains(snodeToExclude) }.secureRandom()
} else {
paths.getRandomElement()
paths.secureRandom()
}
}
when {
@@ -273,7 +272,7 @@ object OnionRequestAPI {
path.removeAt(snodeIndex)
val unusedSnodes = SnodeAPI.snodePool.minus(oldPaths.flatten())
if (unusedSnodes.isEmpty()) { throw InsufficientSnodesException() }
path.add(unusedSnodes.getRandomElement())
path.add(unusedSnodes.secureRandom())
// Don't test the new snode as this would reveal the user's IP
oldPaths.removeAt(pathIndex)
val newPaths = oldPaths + listOf( path )

View File

@@ -13,6 +13,7 @@ import android.text.TextUtils
import android.text.style.StyleSpan
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Util.SECURE_RANDOM
import java.io.*
import java.nio.charset.StandardCharsets
import java.security.SecureRandom
@@ -292,15 +293,10 @@ object Util {
@JvmStatic
fun getSecretBytes(size: Int): ByteArray {
val secret = ByteArray(size)
getSecureRandom().nextBytes(secret)
SECURE_RANDOM.nextBytes(secret)
return secret
}
@JvmStatic
fun getSecureRandom(): SecureRandom {
return SecureRandom()
}
@JvmStatic
fun getFirstNonEmpty(vararg values: String?): String? {
for (value in values) {
@@ -317,18 +313,14 @@ object Util {
}
@JvmStatic
fun <T> getRandomElement(elements: Array<T>): T {
return elements[SecureRandom().nextInt(elements.size)]
}
fun <T> getRandomElement(elements: Array<T>): T = elements[SECURE_RANDOM.nextInt(elements.size)]
@JvmStatic
fun getBoldedString(value: String?): CharSequence {
if (value.isNullOrEmpty()) { return "" }
val spanned = SpannableString(value)
spanned.setSpan(StyleSpan(Typeface.BOLD), 0,
spanned.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spanned
return SpannableString(value).also {
it.setSpan(StyleSpan(Typeface.BOLD), 0, it.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
@JvmStatic
@@ -366,34 +358,6 @@ object Util {
val digitGroups = (Math.log10(sizeBytes.toDouble()) / Math.log10(1024.0)).toInt()
return DecimalFormat("#,##0.#").format(sizeBytes / Math.pow(1024.0, digitGroups.toDouble())) + " " + units[digitGroups]
}
/**
* Compares two version strings (for example "1.8.0")
*
* @param version1 the first version string to compare.
* @param version2 the second version string to compare.
* @return an integer indicating the result of the comparison:
* - 0 if the versions are equal
* - a positive number if version1 is greater than version2
* - a negative number if version1 is less than version2
*/
@JvmStatic
fun compareVersions(version1: String, version2: String): Int {
val parts1 = version1.split(".").map { it.toIntOrNull() ?: 0 }
val parts2 = version2.split(".").map { it.toIntOrNull() ?: 0 }
val maxLength = maxOf(parts1.size, parts2.size)
val paddedParts1 = parts1 + List(maxLength - parts1.size) { 0 }
val paddedParts2 = parts2 + List(maxLength - parts2.size) { 0 }
for (i in 0 until maxLength) {
val compare = paddedParts1[i].compareTo(paddedParts2[i])
if (compare != 0) {
return compare
}
}
return 0
}
}
fun <T, R> T.runIf(condition: Boolean, block: T.() -> R): R where T: R = if (condition) block() else this
@@ -430,6 +394,12 @@ fun <E, K: Any, V: Any> Iterable<E>.associateByNotNull(
for(e in this) { it[keySelector(e) ?: continue] = valueTransform(e) ?: continue }
}
fun <K: Any, V: Any, W : Any> Map<K, V>.mapValuesNotNull(
valueTransform: (Map.Entry<K, V>) -> W?
): Map<K, W> = mutableMapOf<K, W>().also {
for(e in this) { it[e.key] = valueTransform(e) ?: continue }
}
/**
* Groups elements of the original collection by the key returned by the given [keySelector] function
* applied to each element and returns a map where each group key is associated with a list of
@@ -440,3 +410,23 @@ fun <E, K: Any, V: Any> Iterable<E>.associateByNotNull(
inline fun <E, K> Iterable<E>.groupByNotNull(keySelector: (E) -> K?): Map<K, List<E>> = LinkedHashMap<K, MutableList<E>>().also {
forEach { e -> keySelector(e)?.let { k -> it.getOrPut(k) { mutableListOf() } += e } }
}
/**
* Analogous to [buildMap], this function creates a [MutableMap] and populates it using the given [action].
*/
inline fun <K, V> buildMutableMap(action: MutableMap<K, V>.() -> Unit): MutableMap<K, V> =
mutableMapOf<K, V>().apply(action)
/**
* Converts a list of Pairs into a Map, filtering out any Pairs where the value is null.
*
* @param pairs The list of Pairs to convert.
* @return A Map with non-null values.
*/
fun <K : Any, V : Any> Iterable<Pair<K, V?>>.toMapNotNull(): Map<K, V> =
associateByNotNull(Pair<K, V?>::first, Pair<K, V?>::second)
fun Sequence<String>.toByteArray(): ByteArray = ByteArrayOutputStream().use { output ->
forEach { it.byteInputStream().use { input -> input.copyTo(output) } }
output.toByteArray()
}

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="enable_alarm_manager">true</bool>
<bool name="enable_job_service">false</bool>
<bool name="screen_security_default">true</bool>
</resources>