From ae23266058f78f33aefe500ea015eff116692985 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 26 May 2021 16:34:08 +1000 Subject: [PATCH 1/8] wip: implement ons name --- .../libsession/snode/OnionRequestAPI.kt | 2 +- .../org/session/libsession/snode/SnodeAPI.kt | 58 ++++++++++++++++++- .../org/session/libsignal/utilities/Snode.kt | 3 +- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt index 4f6aca389d..a188b4a5a8 100644 --- a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt @@ -430,7 +430,7 @@ object OnionRequestAPI { /** * Sends an onion request to `snode`. Builds new paths as needed. */ - internal fun sendOnionRequest(method: Snode.Method, parameters: Map<*, *>, snode: Snode, publicKey: String): Promise, Exception> { + internal fun sendOnionRequest(method: Snode.Method, parameters: Map<*, *>, snode: Snode, publicKey: String? = null): Promise, Exception> { val payload = mapOf( "method" to method.rawValue, "params" to parameters ) return sendOnionRequest(Destination.Snode(snode), payload).recover { exception -> val httpRequestFailedException = exception as? HTTP.HTTPRequestFailedException diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 3f9115572c..429b2d0e48 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -3,6 +3,10 @@ package org.session.libsession.snode import android.os.Build +import com.goterl.lazysodium.LazySodiumAndroid +import com.goterl.lazysodium.SodiumAndroid +import com.goterl.lazysodium.interfaces.PwHash +import com.goterl.lazysodium.interfaces.SecretBox import nl.komponents.kovenant.* import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map @@ -17,10 +21,14 @@ import org.session.libsignal.utilities.prettifiedDescription import org.session.libsignal.utilities.removing05PrefixIfNeeded import org.session.libsignal.utilities.retryIfNeeded import org.session.libsignal.utilities.* +import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import java.security.SecureRandom +import java.util.* object SnodeAPI { + private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } + private val database: LokiAPIDatabaseProtocol get() = SnodeModule.shared.storage private val broadcaster: Broadcaster @@ -54,10 +62,14 @@ object SnodeAPI { internal sealed class Error(val description: String) : Exception(description) { object Generic : Error("An error occurred.") object ClockOutOfSync : Error("Your clock is out of sync with the Service Node network.") + // ONS + object DecryptionFailed : Error("Couldn't decrypt ONS name.") + object HashingFailed : Error("Couldn't compute ONS name hash.") + object ValidationFailed : Error("ONS name validation failed.") } // Internal API - internal fun invoke(method: Snode.Method, snode: Snode, publicKey: String, parameters: Map): RawResponsePromise { + internal fun invoke(method: Snode.Method, snode: Snode, publicKey: String? = null, parameters: Map): RawResponsePromise { val url = "${snode.address}:${snode.port}/storage_rpc/v1" if (useOnionRequests) { return OnionRequestAPI.sendOnionRequest(method, parameters, snode, publicKey) @@ -153,6 +165,50 @@ object SnodeAPI { } // Public API + fun getSessionIDFor(onsName: String): Promise { + val validationCount = 3 + val sessionIDByteCount = 33 + // Hash the ONS name using BLAKE2b + val name = onsName.toLowerCase(Locale.ENGLISH) + val nameHash = sodium.cryptoGenericHash(name) + val base64EncodedNameHash = nameHash + // Ask 3 different snodes for the Session ID associated with the given name hash + val parameters = mapOf( + "endpoint" to "ons_resolve", + "params" to mapOf( "type" to 0, "name_hash" to base64EncodedNameHash ) + ) + val promises = (0..validationCount).map { + getRandomSnode().bind { snode -> + invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters) + } + } + val deferred = deferred() + val promise = deferred.promise + all(promises).success { results -> + val sessionIDs = mutableListOf() + for (json in results) { + val intermediate = json["result"] as? Map<*, *> + val hexEncodedCiphertext = intermediate?.get("encrypted_value") as? String + if (hexEncodedCiphertext != null) { + val ciphertext = Hex.fromStringCondensed(hexEncodedCiphertext) + val isArgon2Based = (intermediate["nonce"] == null) + if (isArgon2Based) { + // Handle old Argon2-based encryption used before HF16 + val salt = ByteArray(PwHash.SALTBYTES) + val key = sodium.cryptoPwHash(name, SecretBox.KEYBYTES, salt, PwHash.OPSLIMIT_MODERATE, PwHash.MEMLIMIT_MODERATE, PwHash.Alg.PWHASH_ALG_ARGON2ID13) + val nonce = ByteArray(SecretBox.NONCEBYTES) + val sessionID = sodium.cryptoSecretBoxOpenEasy(ciphertext, nonce, key) + } else { + + } + } else { + deferred.reject(Error.Generic) + } + } + } + return promise + } + fun getTargetSnodes(publicKey: String): Promise, Exception> { // SecureRandom() should be cryptographically secure return getSwarm(publicKey).map { it.shuffled(SecureRandom()).take(targetSwarmSnodeCount) } diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt b/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt index af4a1f694f..92c30095ef 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt @@ -6,7 +6,8 @@ class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) { public enum class Method(val rawValue: String) { GetSwarm("get_snodes_for_pubkey"), GetMessages("retrieve"), - SendMessage("store") + SendMessage("store"), + OxenDaemonRPCCall("oxend_request") } data class KeySet(val ed25519Key: String, val x25519Key: String) From e6cdd3ee0e936f3759f5943eaa1114c2aabab5c5 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 27 May 2021 15:31:48 +1000 Subject: [PATCH 2/8] add logic and UI in create private chat view --- .../activities/CreatePrivateChatActivity.kt | 48 ++++++++++++++++++- .../layout/activity_create_private_chat.xml | 42 ++++++++++++---- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt index 25c6dec9a4..40947d1543 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.loki.activities +import android.animation.Animator +import android.animation.AnimatorListenerAdapter import android.content.ClipData import android.content.ClipboardManager import android.content.Context @@ -13,8 +15,14 @@ import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.Toast import kotlinx.android.synthetic.main.activity_create_private_chat.* +import kotlinx.android.synthetic.main.activity_create_private_chat.loader +import kotlinx.android.synthetic.main.activity_create_private_chat.tabLayout +import kotlinx.android.synthetic.main.activity_create_private_chat.viewPager +import kotlinx.android.synthetic.main.activity_join_public_chat.* import kotlinx.android.synthetic.main.fragment_enter_public_key.* +import kotlinx.android.synthetic.main.permissions_rationale_dialog.* import network.loki.messenger.R +import org.session.libsession.snode.SnodeAPI import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.ConversationActivity import org.session.libsession.utilities.Address @@ -48,6 +56,23 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC } // endregion + // region Updating + private fun showLoader() { + loader.visibility = View.VISIBLE + loader.animate().setDuration(150).alpha(1.0f).start() + } + + private fun hideLoader() { + loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() { + + override fun onAnimationEnd(animation: Animator?) { + super.onAnimationEnd(animation) + loader.visibility = View.GONE + } + }) + } + // endregion + // region Interaction override fun onOptionsItemSelected(item: MenuItem): Boolean { when(item.itemId) { @@ -60,8 +85,27 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC createPrivateChatIfPossible(hexEncodedPublicKey) } - fun createPrivateChatIfPossible(hexEncodedPublicKey: String) { - if (!PublicKeyValidation.isValid(hexEncodedPublicKey)) { return Toast.makeText(this, R.string.invalid_session_id, Toast.LENGTH_SHORT).show() } + fun createPrivateChatIfPossible(onsNameOrPublicKey: String) { + if (!PublicKeyValidation.isValid(onsNameOrPublicKey)) { + createPrivateChat(onsNameOrPublicKey) + } else { + // This could be an ONS name + showLoader() + SnodeAPI.getSessionIDFor(onsNameOrPublicKey).success { hexEncodedPublicKey -> + hideLoader() + this.createPrivateChat(hexEncodedPublicKey) + }.fail { exception -> + hideLoader() + var message = "Please check the Session ID or ONS name and try again." + exception.localizedMessage?.let { + message = it + } + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() + } + } + } + + private fun createPrivateChat(hexEncodedPublicKey: String) { val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false) val intent = Intent(this, ConversationActivity::class.java) intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address) diff --git a/app/src/main/res/layout/activity_create_private_chat.xml b/app/src/main/res/layout/activity_create_private_chat.xml index 6a1229648e..5d21096f01 100644 --- a/app/src/main/res/layout/activity_create_private_chat.xml +++ b/app/src/main/res/layout/activity_create_private_chat.xml @@ -1,14 +1,40 @@ - + android:layout_height="match_parent"> - + android:layout_height="match_parent" > - \ No newline at end of file + + + + + + + + + + + \ No newline at end of file From 592825dcc65d118dc5b1c4d2532760d7728df487 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 27 May 2021 16:23:15 +1000 Subject: [PATCH 3/8] implement get session id from ons name api --- .../org/session/libsession/snode/SnodeAPI.kt | 73 ++++++++++++++----- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 429b2d0e48..5856633cb7 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -5,26 +5,24 @@ package org.session.libsession.snode import android.os.Build import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.SodiumAndroid +import com.goterl.lazysodium.exceptions.SodiumException +import com.goterl.lazysodium.interfaces.AEAD +import com.goterl.lazysodium.interfaces.GenericHash import com.goterl.lazysodium.interfaces.PwHash import com.goterl.lazysodium.interfaces.SecretBox +import com.goterl.lazysodium.utils.Key import nl.komponents.kovenant.* import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsignal.crypto.getRandomElement -import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.utilities.Snode -import org.session.libsignal.utilities.HTTP import org.session.libsignal.database.LokiAPIDatabaseProtocol -import org.session.libsignal.utilities.Broadcaster -import org.session.libsignal.utilities.prettifiedDescription -import org.session.libsignal.utilities.removing05PrefixIfNeeded -import org.session.libsignal.utilities.retryIfNeeded +import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.* import org.session.libsignal.utilities.Base64 -import org.session.libsignal.utilities.Log import java.security.SecureRandom import java.util.* +import javax.crypto.AEADBadTagException object SnodeAPI { private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } @@ -166,12 +164,19 @@ object SnodeAPI { // Public API fun getSessionIDFor(onsName: String): Promise { + val deferred = deferred() + val promise = deferred.promise val validationCount = 3 val sessionIDByteCount = 33 // Hash the ONS name using BLAKE2b - val name = onsName.toLowerCase(Locale.ENGLISH) - val nameHash = sodium.cryptoGenericHash(name) - val base64EncodedNameHash = nameHash + val onsName = onsName.toLowerCase(Locale.ENGLISH) + val nameAsData = onsName.toByteArray() + val nameHash = ByteArray(GenericHash.BYTES) + if (!sodium.cryptoGenericHash(nameHash, nameHash.size, nameAsData, nameAsData.size.toLong())) { + deferred.reject(Error.HashingFailed) + return promise + } + val base64EncodedNameHash = Base64.encodeBytes(nameHash) // Ask 3 different snodes for the Session ID associated with the given name hash val parameters = mapOf( "endpoint" to "ons_resolve", @@ -182,29 +187,63 @@ object SnodeAPI { invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters) } } - val deferred = deferred() - val promise = deferred.promise all(promises).success { results -> val sessionIDs = mutableListOf() for (json in results) { val intermediate = json["result"] as? Map<*, *> val hexEncodedCiphertext = intermediate?.get("encrypted_value") as? String if (hexEncodedCiphertext != null) { - val ciphertext = Hex.fromStringCondensed(hexEncodedCiphertext) val isArgon2Based = (intermediate["nonce"] == null) if (isArgon2Based) { // Handle old Argon2-based encryption used before HF16 val salt = ByteArray(PwHash.SALTBYTES) - val key = sodium.cryptoPwHash(name, SecretBox.KEYBYTES, salt, PwHash.OPSLIMIT_MODERATE, PwHash.MEMLIMIT_MODERATE, PwHash.Alg.PWHASH_ALG_ARGON2ID13) + val key: String val nonce = ByteArray(SecretBox.NONCEBYTES) - val sessionID = sodium.cryptoSecretBoxOpenEasy(ciphertext, nonce, key) + val sessionID: String + try { + key = sodium.cryptoPwHash(onsName, SecretBox.KEYBYTES, salt, PwHash.OPSLIMIT_MODERATE, PwHash.MEMLIMIT_MODERATE, PwHash.Alg.PWHASH_ALG_ARGON2ID13) + } catch (e: SodiumException) { + deferred.reject(Error.HashingFailed) + return@success + } + try { + sessionID = sodium.cryptoSecretBoxOpenEasy(hexEncodedCiphertext, nonce, Key.fromHexString(key)) + } catch (e: SodiumException) { + deferred.reject(Error.DecryptionFailed) + return@success + } + sessionIDs.add(sessionID) } else { - + val hexEncodedNonce = intermediate["nonce"] as? String + if (hexEncodedNonce == null) { + deferred.reject(Error.Generic) + return@success + } + val nonce = Hex.fromStringCondensed(hexEncodedNonce) + val key = ByteArray(GenericHash.BYTES) + if (!sodium.cryptoGenericHash(key, key.size, nameAsData, nameAsData.size.toLong(), nameHash, nameHash.size)) { + deferred.reject(Error.HashingFailed) + return@success + } + val sessionID: String + try { + sessionID = sodium.decrypt(hexEncodedCiphertext, null, nonce, Key.fromBytes(key), AEAD.Method.CHACHA20_POLY1305_IETF) + } catch (e: Exception) { + deferred.reject(Error.DecryptionFailed) + return@success + } + sessionIDs.add(sessionID) } } else { deferred.reject(Error.Generic) + return@success } } + if (sessionIDs.size == validationCount && sessionIDs.toSet().size == 1) { + deferred.resolve(sessionIDs.first()) + } else { + deferred.reject(Error.ValidationFailed) + } } return promise } From 93dfbcaae2e29f4fc09f72206c84968c1e0d1a8b Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 27 May 2021 16:51:59 +1000 Subject: [PATCH 4/8] clean --- .../securesms/loki/activities/CreatePrivateChatActivity.kt | 3 --- .../securesms/loki/activities/LinkDeviceActivity.kt | 3 --- app/src/main/res/layout/activity_create_private_chat.xml | 1 - app/src/main/res/values/strings.xml | 2 +- 4 files changed, 1 insertion(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt index 40947d1543..eae640763b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt @@ -14,13 +14,10 @@ import android.view.* import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.Toast -import kotlinx.android.synthetic.main.activity_create_private_chat.* import kotlinx.android.synthetic.main.activity_create_private_chat.loader import kotlinx.android.synthetic.main.activity_create_private_chat.tabLayout import kotlinx.android.synthetic.main.activity_create_private_chat.viewPager -import kotlinx.android.synthetic.main.activity_join_public_chat.* import kotlinx.android.synthetic.main.fragment_enter_public_key.* -import kotlinx.android.synthetic.main.permissions_rationale_dialog.* import network.loki.messenger.R import org.session.libsession.snode.SnodeAPI import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt index 7e4210246b..20f8fcf795 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt @@ -13,9 +13,6 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentPagerAdapter import androidx.lifecycle.lifecycleScope import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.activity_create_private_chat.* -import kotlinx.android.synthetic.main.activity_create_private_chat.tabLayout -import kotlinx.android.synthetic.main.activity_create_private_chat.viewPager import kotlinx.android.synthetic.main.activity_link_device.* import kotlinx.android.synthetic.main.conversation_activity.* import kotlinx.android.synthetic.main.fragment_recovery_phrase.* diff --git a/app/src/main/res/layout/activity_create_private_chat.xml b/app/src/main/res/layout/activity_create_private_chat.xml index 5d21096f01..d437a230fc 100644 --- a/app/src/main/res/layout/activity_create_private_chat.xml +++ b/app/src/main/res/layout/activity_create_private_chat.xml @@ -6,7 +6,6 @@ android:layout_height="match_parent"> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8db4cfaf30..80eb6fd035 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -719,7 +719,7 @@ Scan QR Code Scan a user\'s QR code to start a session. QR codes can be found by tapping the QR code icon in account settings. - Enter Session ID of recipient + Enter Session ID or ONS name Users can share their Session ID by going into their account settings and tapping "Share Session ID", or by sharing their QR code. Session needs camera access to scan QR codes From c0d4dd741cfb5c981927ebf58f3e3ba1f89d83ec Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 28 May 2021 11:22:06 +1000 Subject: [PATCH 5/8] debug --- .../loki/activities/CreatePrivateChatActivity.kt | 6 ++++-- .../java/org/session/libsession/snode/SnodeAPI.kt | 15 +++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt index eae640763b..101a3a90b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt @@ -19,6 +19,7 @@ import kotlinx.android.synthetic.main.activity_create_private_chat.tabLayout import kotlinx.android.synthetic.main.activity_create_private_chat.viewPager import kotlinx.android.synthetic.main.fragment_enter_public_key.* import network.loki.messenger.R +import nl.komponents.kovenant.ui.failUi import org.session.libsession.snode.SnodeAPI import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.ConversationActivity @@ -29,6 +30,7 @@ import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.Util import org.session.libsignal.utilities.PublicKeyValidation @@ -83,7 +85,7 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC } fun createPrivateChatIfPossible(onsNameOrPublicKey: String) { - if (!PublicKeyValidation.isValid(onsNameOrPublicKey)) { + if (PublicKeyValidation.isValid(onsNameOrPublicKey)) { createPrivateChat(onsNameOrPublicKey) } else { // This could be an ONS name @@ -91,7 +93,7 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC SnodeAPI.getSessionIDFor(onsNameOrPublicKey).success { hexEncodedPublicKey -> hideLoader() this.createPrivateChat(hexEncodedPublicKey) - }.fail { exception -> + }.failUi { exception -> hideLoader() var message = "Please check the Session ID or ONS name and try again." exception.localizedMessage?.let { diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 5856633cb7..78878c6d3b 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -182,7 +182,7 @@ object SnodeAPI { "endpoint" to "ons_resolve", "params" to mapOf( "type" to 0, "name_hash" to base64EncodedNameHash ) ) - val promises = (0..validationCount).map { + val promises = (1..validationCount).map { getRandomSnode().bind { snode -> invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters) } @@ -193,26 +193,25 @@ object SnodeAPI { val intermediate = json["result"] as? Map<*, *> val hexEncodedCiphertext = intermediate?.get("encrypted_value") as? String if (hexEncodedCiphertext != null) { + val ciphertext = Hex.fromStringCondensed(hexEncodedCiphertext) val isArgon2Based = (intermediate["nonce"] == null) if (isArgon2Based) { // Handle old Argon2-based encryption used before HF16 val salt = ByteArray(PwHash.SALTBYTES) - val key: String + val key: ByteArray val nonce = ByteArray(SecretBox.NONCEBYTES) - val sessionID: String + val sessionIDAsData = ByteArray(sessionIDByteCount) try { - key = sodium.cryptoPwHash(onsName, SecretBox.KEYBYTES, salt, PwHash.OPSLIMIT_MODERATE, PwHash.MEMLIMIT_MODERATE, PwHash.Alg.PWHASH_ALG_ARGON2ID13) + key = Key.fromHexString(sodium.cryptoPwHash(onsName, SecretBox.KEYBYTES, salt, PwHash.OPSLIMIT_MODERATE, PwHash.MEMLIMIT_MODERATE, PwHash.Alg.PWHASH_ALG_ARGON2ID13)).asBytes } catch (e: SodiumException) { deferred.reject(Error.HashingFailed) return@success } - try { - sessionID = sodium.cryptoSecretBoxOpenEasy(hexEncodedCiphertext, nonce, Key.fromHexString(key)) - } catch (e: SodiumException) { + if (!sodium.cryptoSecretBoxOpenEasy(sessionIDAsData, ciphertext, ciphertext.size.toLong(), nonce, key)) { deferred.reject(Error.DecryptionFailed) return@success } - sessionIDs.add(sessionID) + sessionIDs.add(Hex.toStringCondensed(sessionIDAsData)) } else { val hexEncodedNonce = intermediate["nonce"] as? String if (hexEncodedNonce == null) { From 497405fe50615adab696ce5c9431461dd4e745fb Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 28 May 2021 11:22:46 +1000 Subject: [PATCH 6/8] clean --- .../securesms/loki/activities/CreatePrivateChatActivity.kt | 1 - .../src/main/java/org/session/libsession/snode/SnodeAPI.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt index 101a3a90b7..2dece4b9e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt @@ -30,7 +30,6 @@ import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.Util import org.session.libsignal.utilities.PublicKeyValidation diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 78878c6d3b..abcb132a2d 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -22,7 +22,6 @@ import org.session.libsignal.utilities.* import org.session.libsignal.utilities.Base64 import java.security.SecureRandom import java.util.* -import javax.crypto.AEADBadTagException object SnodeAPI { private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } From 4f7d26d86e2c9b9474d85da3ce45277e9f95ec5e Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 28 May 2021 12:04:37 +1000 Subject: [PATCH 7/8] debug for new encryption --- .../main/java/org/session/libsession/snode/SnodeAPI.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index abcb132a2d..d46ee4bcca 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -223,14 +223,12 @@ object SnodeAPI { deferred.reject(Error.HashingFailed) return@success } - val sessionID: String - try { - sessionID = sodium.decrypt(hexEncodedCiphertext, null, nonce, Key.fromBytes(key), AEAD.Method.CHACHA20_POLY1305_IETF) - } catch (e: Exception) { + val sessionIDAsData = ByteArray(sessionIDByteCount) + if (!sodium.cryptoAeadXChaCha20Poly1305IetfDecrypt(sessionIDAsData, null, null, ciphertext, ciphertext.size.toLong(), null, 0, nonce, key)) { deferred.reject(Error.DecryptionFailed) return@success } - sessionIDs.add(sessionID) + sessionIDs.add(Hex.toStringCondensed(sessionIDAsData)) } } else { deferred.reject(Error.Generic) From e8c52961aa646c1ced02690783262227a608810d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 28 May 2021 15:14:05 +1000 Subject: [PATCH 8/8] minor fix --- .../securesms/loki/activities/CreatePrivateChatActivity.kt | 5 +++-- app/src/main/res/values/strings.xml | 1 + .../src/main/java/org/session/libsession/snode/SnodeAPI.kt | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt index 2dece4b9e6..54c80227a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt @@ -20,6 +20,7 @@ import kotlinx.android.synthetic.main.activity_create_private_chat.viewPager import kotlinx.android.synthetic.main.fragment_enter_public_key.* import network.loki.messenger.R import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi import org.session.libsession.snode.SnodeAPI import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.ConversationActivity @@ -89,12 +90,12 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC } else { // This could be an ONS name showLoader() - SnodeAPI.getSessionIDFor(onsNameOrPublicKey).success { hexEncodedPublicKey -> + SnodeAPI.getSessionIDFor(onsNameOrPublicKey).successUi { hexEncodedPublicKey -> hideLoader() this.createPrivateChat(hexEncodedPublicKey) }.failUi { exception -> hideLoader() - var message = "Please check the Session ID or ONS name and try again." + var message = resources.getString(R.string.fragment_enter_public_key_error_message) exception.localizedMessage?.let { message = it } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 80eb6fd035..b8d6c578d4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -721,6 +721,7 @@ Enter Session ID or ONS name Users can share their Session ID by going into their account settings and tapping "Share Session ID", or by sharing their QR code. + Please check the Session ID or ONS name and try again. Session needs camera access to scan QR codes Grant Camera Access diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index d46ee4bcca..f126871a40 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -183,7 +183,10 @@ object SnodeAPI { ) val promises = (1..validationCount).map { getRandomSnode().bind { snode -> - invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters) + retryIfNeeded(maxRetryCount) { + invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters) + } + } } all(promises).success { results ->