mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Merge pull request #676 from oxen-io/authentication
Implement Authenticated Message Retrieval
This commit is contained in:
commit
ea5a41af52
@ -8,7 +8,6 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.*
|
||||
import android.view.inputmethod.EditorInfo
|
||||
@ -24,7 +23,6 @@ import nl.komponents.kovenant.ui.failUi
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.DistributionTypes
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.utilities.PublicKeyValidation
|
||||
@ -96,7 +94,7 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC
|
||||
} else {
|
||||
// This could be an ONS name
|
||||
showLoader()
|
||||
SnodeAPI.getSessionIDFor(onsNameOrPublicKey).successUi { hexEncodedPublicKey ->
|
||||
SnodeAPI.getSessionID(onsNameOrPublicKey).successUi { hexEncodedPublicKey ->
|
||||
hideLoader()
|
||||
this.createPrivateChat(hexEncodedPublicKey)
|
||||
}.failUi { exception ->
|
||||
|
@ -104,7 +104,7 @@ class ClearAllDataDialog : BaseDialog() {
|
||||
} else {
|
||||
// finish
|
||||
val result = try {
|
||||
SnodeAPI.deleteAllMessages(requireContext()).get()
|
||||
SnodeAPI.deleteAllMessages().get()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
@ -9,16 +9,14 @@ class MessagingModuleConfiguration(
|
||||
val context: Context,
|
||||
val storage: StorageProtocol,
|
||||
val messageDataProvider: MessageDataProvider,
|
||||
val keyPairProvider: ()-> KeyPair?
|
||||
val getUserED25519KeyPair: ()-> KeyPair?
|
||||
) {
|
||||
|
||||
companion object {
|
||||
lateinit var shared: MessagingModuleConfiguration
|
||||
|
||||
fun configure(context: Context,
|
||||
storage: StorageProtocol,
|
||||
messageDataProvider: MessageDataProvider,
|
||||
keyPairProvider: () -> KeyPair?
|
||||
fun configure(context: Context, storage: StorageProtocol,
|
||||
messageDataProvider: MessageDataProvider, keyPairProvider: () -> KeyPair?
|
||||
) {
|
||||
if (Companion::shared.isInitialized) { return }
|
||||
shared = MessagingModuleConfiguration(context, storage, messageDataProvider, keyPairProvider)
|
||||
|
@ -23,8 +23,7 @@ object MessageEncrypter {
|
||||
* @return the encrypted message.
|
||||
*/
|
||||
internal fun encrypt(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray {
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val userED25519KeyPair = MessagingModuleConfiguration.shared.keyPairProvider() ?: throw Error.NoUserED25519KeyPair
|
||||
val userED25519KeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: throw Error.NoUserED25519KeyPair
|
||||
val recipientX25519PublicKey = Hex.fromStringCondensed(recipientHexEncodedX25519PublicKey.removing05PrefixIfNeeded())
|
||||
|
||||
val verificationData = plaintext + userED25519KeyPair.publicKey.asBytes + recipientX25519PublicKey
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
package org.session.libsession.snode
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.goterl.lazysodium.LazySodiumAndroid
|
||||
import com.goterl.lazysodium.SodiumAndroid
|
||||
@ -66,6 +65,8 @@ 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.")
|
||||
object NoKeyPair : Error("Missing user key pair.")
|
||||
object SigningFailed : Error("Couldn't sign verification data.")
|
||||
// ONS
|
||||
object DecryptionFailed : Error("Couldn't decrypt ONS name.")
|
||||
object HashingFailed : Error("Couldn't compute ONS name hash.")
|
||||
@ -169,7 +170,7 @@ object SnodeAPI {
|
||||
}
|
||||
|
||||
// Public API
|
||||
fun getSessionIDFor(onsName: String): Promise<String, Exception> {
|
||||
fun getSessionID(onsName: String): Promise<String, Exception> {
|
||||
val deferred = deferred<String, Exception>()
|
||||
val promise = deferred.promise
|
||||
val validationCount = 3
|
||||
@ -193,7 +194,6 @@ object SnodeAPI {
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
all(promises).success { results ->
|
||||
@ -278,8 +278,27 @@ object SnodeAPI {
|
||||
}
|
||||
|
||||
fun getRawMessages(snode: Snode, publicKey: String): RawResponsePromise {
|
||||
val userED25519KeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return Promise.ofFail(Error.NoKeyPair)
|
||||
// Get last message hash
|
||||
val lastHashValue = database.getLastMessageHashValue(snode, publicKey) ?: ""
|
||||
val parameters = mapOf( "pubKey" to if (useTestnet) publicKey.removing05PrefixIfNeeded() else publicKey, "lastHash" to lastHashValue )
|
||||
// Construct signature
|
||||
val timestamp = Date().time + SnodeAPI.clockOffset
|
||||
val ed25519PublicKey = userED25519KeyPair.publicKey.asHexString
|
||||
val verificationData = "retrieve$timestamp".toByteArray()
|
||||
val signature = ByteArray(Sign.BYTES)
|
||||
try {
|
||||
sodium.cryptoSignDetached(signature, verificationData, verificationData.size.toLong(), userED25519KeyPair.secretKey.asBytes)
|
||||
} catch (exception: Exception) {
|
||||
return Promise.ofFail(Error.SigningFailed)
|
||||
}
|
||||
// Make the request
|
||||
val parameters = mapOf(
|
||||
"pubKey" to if (useTestnet) publicKey.removing05PrefixIfNeeded() else publicKey,
|
||||
"lastHash" to lastHashValue,
|
||||
"timestamp" to timestamp,
|
||||
"pubkey_ed25519" to ed25519PublicKey,
|
||||
"signature" to Base64.encodeBytes(signature)
|
||||
)
|
||||
return invoke(Snode.Method.GetMessages, snode, publicKey, parameters)
|
||||
}
|
||||
|
||||
@ -291,7 +310,7 @@ object SnodeAPI {
|
||||
}
|
||||
}
|
||||
|
||||
fun getNetworkTime(snode: Snode): Promise<Pair<Snode,Long>, Exception> {
|
||||
private fun getNetworkTime(snode: Snode): Promise<Pair<Snode,Long>, Exception> {
|
||||
return invoke(Snode.Method.Info, snode, null, emptyMap()).map { rawResponse ->
|
||||
val timestamp = rawResponse["timestamp"] as? Long ?: -1
|
||||
snode to timestamp
|
||||
@ -335,27 +354,26 @@ object SnodeAPI {
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAllMessages(context: Context): Promise<Map<String,Boolean>, Exception> {
|
||||
|
||||
fun deleteAllMessages(): Promise<Map<String,Boolean>, Exception> {
|
||||
return retryIfNeeded(maxRetryCount) {
|
||||
// considerations: timestamp off in retrying logic, not being able to re-sign with latest timestamp? do we just not retry this as it will be synchronous
|
||||
val module = MessagingModuleConfiguration.shared
|
||||
val userED25519KeyPair = module.keyPairProvider() ?: return@retryIfNeeded Promise.ofFail(Error.Generic)
|
||||
val userPublicKey = module.storage.getUserPublicKey() ?: return@retryIfNeeded Promise.ofFail(Error.Generic)
|
||||
|
||||
val userED25519KeyPair = module.getUserED25519KeyPair() ?: return@retryIfNeeded Promise.ofFail(Error.NoKeyPair)
|
||||
val userPublicKey = module.storage.getUserPublicKey() ?: return@retryIfNeeded Promise.ofFail(Error.NoKeyPair)
|
||||
getSingleTargetSnode(userPublicKey).bind { snode ->
|
||||
retryIfNeeded(maxRetryCount) {
|
||||
getNetworkTime(snode).bind { (_, timestamp) ->
|
||||
val signature = ByteArray(Sign.BYTES)
|
||||
val data = (Snode.Method.DeleteAll.rawValue + timestamp.toString()).toByteArray()
|
||||
sodium.cryptoSignDetached(signature, data, data.size.toLong(), userED25519KeyPair.secretKey.asBytes)
|
||||
val verificationData = (Snode.Method.DeleteAll.rawValue + timestamp.toString()).toByteArray()
|
||||
sodium.cryptoSignDetached(signature, verificationData, verificationData.size.toLong(), userED25519KeyPair.secretKey.asBytes)
|
||||
val deleteMessageParams = mapOf(
|
||||
"pubkey" to userPublicKey,
|
||||
"pubkey_ed25519" to userED25519KeyPair.publicKey.asHexString,
|
||||
"timestamp" to timestamp,
|
||||
"signature" to Base64.encodeBytes(signature)
|
||||
)
|
||||
invoke(Snode.Method.DeleteAll, snode, userPublicKey, deleteMessageParams).map { rawResponse -> parseDeletions(userPublicKey, timestamp, rawResponse) }.fail { e ->
|
||||
invoke(Snode.Method.DeleteAll, snode, userPublicKey, deleteMessageParams).map {
|
||||
rawResponse -> parseDeletions(userPublicKey, timestamp, rawResponse)
|
||||
}.fail { e ->
|
||||
Log.e("Loki", "Failed to clear data", e)
|
||||
}
|
||||
}
|
||||
@ -425,37 +443,24 @@ object SnodeAPI {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun parseDeletions(userPublicKey: String, timestamp: Long, rawResponse: RawResponse): Map<String, Boolean> {
|
||||
val swarms = rawResponse["swarm"] as? Map<String, Any> ?: return mapOf()
|
||||
val swarmResponsesValid = swarms.mapNotNull { (nodePubKeyHex, rawMap) ->
|
||||
val map = rawMap as? Map<String, Any> ?: return@mapNotNull null
|
||||
|
||||
/** Deletes all messages owned by the given pubkey on this SN and broadcasts the delete request to
|
||||
* all other swarm members.
|
||||
* Returns dict of:
|
||||
* - "swarms" dict mapping ed25519 pubkeys (in hex) of swarm members to dict values of:
|
||||
* - "failed" and other failure keys -- see `recursive`.
|
||||
* - "deleted": hashes of deleted messages.
|
||||
* - "signature": signature of ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] ), signed
|
||||
* by the node's ed25519 pubkey.
|
||||
*/
|
||||
// failure
|
||||
val failed = map["failed"] as? Boolean ?: false
|
||||
val code = map["code"] as? String
|
||||
val reason = map["reason"] as? String
|
||||
|
||||
nodePubKeyHex to if (failed) {
|
||||
Log.e("Loki", "Failed to delete all from $nodePubKeyHex with error code $code and reason $reason")
|
||||
val result = swarms.mapNotNull { (hexSnodePublicKey, rawJSON) ->
|
||||
val json = rawJSON as? Map<String, Any> ?: return@mapNotNull null
|
||||
val isFailed = json["failed"] as? Boolean ?: false
|
||||
val statusCode = json["code"] as? String
|
||||
val reason = json["reason"] as? String
|
||||
hexSnodePublicKey to if (isFailed) {
|
||||
Log.e("Loki", "Failed to delete all messages from: $hexSnodePublicKey due to error: $reason ($statusCode).")
|
||||
false
|
||||
} else {
|
||||
// success
|
||||
val deleted = map["deleted"] as List<String> // list of deleted hashes
|
||||
val signature = map["signature"] as String
|
||||
val nodePubKey = Key.fromHexString(nodePubKeyHex)
|
||||
// signature of ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] )
|
||||
val message = (userPublicKey + timestamp.toString() + deleted.fold("") { a, v -> a + v }).toByteArray()
|
||||
sodium.cryptoSignVerifyDetached(Base64.decode(signature), message, message.size, nodePubKey.asBytes)
|
||||
val hashes = json["deleted"] as List<String> // Hashes of deleted messages
|
||||
val signature = json["signature"] as String
|
||||
val snodePublicKey = Key.fromHexString(hexSnodePublicKey)
|
||||
// The signature looks like ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] )
|
||||
val message = (userPublicKey + timestamp.toString() + hashes.fold("") { a, v -> a + v }).toByteArray()
|
||||
sodium.cryptoSignVerifyDetached(Base64.decode(signature), message, message.size, snodePublicKey.asBytes)
|
||||
}
|
||||
}
|
||||
return swarmResponsesValid.toMap()
|
||||
return result.toMap()
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
Loading…
Reference in New Issue
Block a user