Skip to content

Commit

Permalink
Replace blob type with string
Browse files Browse the repository at this point in the history
Replace blob type with string
  • Loading branch information
cp-megh-l authored Jan 22, 2025
2 parents 98766ad + 6a79b75 commit 0aa1874
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
import com.canopas.yourspace.data.models.user.ApiUserSession
import com.canopas.yourspace.data.repository.SpaceRepository
import com.canopas.yourspace.data.service.auth.AuthService
import com.canopas.yourspace.data.service.location.toBytes
import com.canopas.yourspace.data.service.user.ApiUserService
import com.canopas.yourspace.data.storage.UserPreferences
import com.canopas.yourspace.data.utils.AppDispatcher
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.canopas.yourspace.data.service.auth.AuthService
import com.canopas.yourspace.data.service.auth.FirebaseAuthService
import com.canopas.yourspace.data.service.location.toBytes
import com.canopas.yourspace.data.storage.UserPreferences
import com.canopas.yourspace.data.utils.AppDispatcher
import com.canopas.yourspace.domain.utils.ConnectivityObserver
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.canopas.yourspace.data.models.location

import androidx.annotation.Keep
import com.google.firebase.firestore.Blob
import com.squareup.moshi.JsonClass
import java.util.UUID

Expand All @@ -20,8 +19,8 @@ data class ApiLocation(
data class EncryptedApiLocation(
val id: String = UUID.randomUUID().toString(),
val user_id: String = "",
val latitude: Blob = Blob.fromBytes(ByteArray(0)), // Base64 encoded encrypted latitude
val longitude: Blob = Blob.fromBytes(ByteArray(0)), // Base64 encoded encrypted longitude
val latitude: String = "", // Base64 encoded encrypted latitude
val longitude: String = "", // Base64 encoded encrypted longitude
val created_at: Long = System.currentTimeMillis()
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.canopas.yourspace.data.models.location
import android.location.Location
import androidx.annotation.Keep
import com.google.android.gms.maps.model.LatLng
import com.google.firebase.firestore.Blob
import com.squareup.moshi.JsonClass
import java.util.UUID

Expand All @@ -30,10 +29,10 @@ data class LocationJourney(
data class EncryptedLocationJourney(
val id: String = UUID.randomUUID().toString(),
val user_id: String = "",
val from_latitude: Blob = Blob.fromBytes(ByteArray(0)), // Encrypted latitude - from
val from_longitude: Blob = Blob.fromBytes(ByteArray(0)), // Encrypted longitude - from
val to_latitude: Blob? = null, // Encrypted latitude - to
val to_longitude: Blob? = null, // Encrypted longitude - to
val from_latitude: String = "", // Encrypted latitude - from
val from_longitude: String = "", // Encrypted longitude - from
val to_latitude: String? = null, // Encrypted latitude - to
val to_longitude: String? = null, // Encrypted longitude - to
val route_distance: Double? = null,
val route_duration: Long? = null,
val routes: List<EncryptedJourneyRoute> = emptyList(), // Encrypted journey routes
Expand All @@ -49,8 +48,8 @@ data class JourneyRoute(val latitude: Double = 0.0, val longitude: Double = 0.0)
@Keep
@JsonClass(generateAdapter = true)
data class EncryptedJourneyRoute(
val latitude: Blob = Blob.fromBytes(ByteArray(0)), // Encrypted latitude
val longitude: Blob = Blob.fromBytes(ByteArray(0)) // Encrypted longitude
val latitude: String = "", // Encrypted latitude
val longitude: String = "" // Encrypted longitude
)

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.canopas.yourspace.data.models.space

import androidx.annotation.Keep
import com.google.firebase.firestore.Blob
import com.canopas.yourspace.data.service.location.toBytes
import com.google.firebase.firestore.Exclude
import java.util.UUID
import java.util.concurrent.TimeUnit
Expand All @@ -24,7 +24,7 @@ data class ApiSpaceMember(
val user_id: String = "",
val role: Int = SPACE_MEMBER_ROLE_MEMBER,
val location_enabled: Boolean = true,
val identity_key_public: Blob? = Blob.fromBytes(ByteArray(0)),
val identity_key_public: String? = null,
val created_at: Long? = System.currentTimeMillis()
)

Expand Down Expand Up @@ -76,9 +76,9 @@ data class MemberKeyData(
data class EncryptedDistribution(
val id: String = UUID.randomUUID().toString(),
val recipient_id: String = "",
val ephemeral_pub: Blob = Blob.fromBytes(ByteArray(0)), // 33 bytes (compressed distribution key)
val iv: Blob = Blob.fromBytes(ByteArray(0)), // 16 bytes
val ciphertext: Blob = Blob.fromBytes(ByteArray(0)), // AES/GCM ciphertext
val ephemeral_pub: String = "", // 33 bytes (compressed distribution key)
val iv: String = "", // 16 bytes
val ciphertext: String = "", // AES/GCM ciphertext
val created_at: Long = System.currentTimeMillis()
) {
init {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.canopas.yourspace.data.models.user

import androidx.annotation.Keep
import com.google.firebase.firestore.Blob
import com.google.firebase.firestore.Exclude
import com.squareup.moshi.JsonClass
import java.util.UUID
Expand All @@ -26,9 +25,9 @@ data class ApiUser(
val battery_pct: Float? = 0f,
val created_at: Long? = System.currentTimeMillis(),
val updated_at: Long? = System.currentTimeMillis(),
val identity_key_public: Blob? = Blob.fromBytes(ByteArray(0)),
val identity_key_private: Blob? = Blob.fromBytes(ByteArray(0)),
val identity_key_salt: Blob? = Blob.fromBytes(ByteArray(0)) // Salt for key derivation
val identity_key_public: String ? = null,
val identity_key_private: String ? = null,
val identity_key_salt: String ? = null // Salt for key derivation
) {
@get:Exclude
val fullName: String get() = "$first_name $last_name"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import com.canopas.yourspace.data.utils.Config.FIRESTORE_COLLECTION_SPACE_MEMBER
import com.canopas.yourspace.data.utils.EphemeralECDHUtils
import com.canopas.yourspace.data.utils.PrivateKeyUtils
import com.canopas.yourspace.data.utils.snapshotFlow
import com.google.firebase.firestore.Blob
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.Query
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -127,8 +126,8 @@ class ApiLocationService @Inject constructor(

val location = EncryptedApiLocation(
user_id = userId,
latitude = Blob.fromBytes(encryptedLatitude.serialize()),
longitude = Blob.fromBytes(encryptedLongitude.serialize()),
latitude = encryptedLatitude.serialize().encodeToString(),
longitude = encryptedLongitude.serialize().encodeToString(),
created_at = recordedAt
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,50 @@ import com.canopas.yourspace.data.models.location.EncryptedJourneyRoute
import com.canopas.yourspace.data.models.location.EncryptedLocationJourney
import com.canopas.yourspace.data.models.location.JourneyRoute
import com.canopas.yourspace.data.models.location.LocationJourney
import com.google.firebase.firestore.Blob
import org.signal.libsignal.protocol.groups.GroupCipher
import timber.log.Timber
import java.util.Base64
import java.util.UUID

fun String.toBytes(): ByteArray = try {
Base64.getDecoder().decode(this)
} catch (e: Exception) {
Timber.e(e, "Failed to decode base64 string")
ByteArray(0)
}

fun ByteArray.encodeToString(): String = Base64.getEncoder().encodeToString(this)

/**
* Convert an [EncryptedLocationJourney] to a [LocationJourney] using the provided [GroupCipher]
* Converts [EncryptedLocationJourney] to [LocationJourney] using [groupCipher].
* Returns null if *any* required decryption step fails.
*/
fun EncryptedLocationJourney.toDecryptedLocationJourney(groupCipher: GroupCipher): LocationJourney? {
val decryptedFromLat = groupCipher.decrypt(from_latitude) ?: return null
val decryptedFromLong = groupCipher.decrypt(from_longitude) ?: return null
val decryptedToLat = to_latitude?.let { groupCipher.decrypt(it) }
val decryptedToLong = to_longitude?.let { groupCipher.decrypt(it) }
fun EncryptedLocationJourney.toDecryptedLocationJourney(
groupCipher: GroupCipher
): LocationJourney? {
// Decrypt required points
val fromLat = groupCipher.decryptPoint(from_latitude.toBytes()) ?: return null
val fromLong = groupCipher.decryptPoint(from_longitude.toBytes()) ?: return null

val decryptedRoutes = routes.map {
JourneyRoute(
latitude = groupCipher.decrypt(it.latitude) ?: return null,
longitude = groupCipher.decrypt(it.longitude) ?: return null
)
// Decrypt optional points
val toLat = to_latitude?.toBytes()?.let { groupCipher.decryptPoint(it) }
val toLong = to_longitude?.toBytes()?.let { groupCipher.decryptPoint(it) }

// Decrypt route list; short-circuit if any route fails
val decryptedRoutes = routes.map { route ->
val lat = groupCipher.decryptPoint(route.latitude.toBytes()) ?: return null
val long = groupCipher.decryptPoint(route.longitude.toBytes()) ?: return null
JourneyRoute(lat, long)
}

// Construct the decrypted journey
return LocationJourney(
id = id,
user_id = user_id,
from_latitude = decryptedFromLat,
from_longitude = decryptedFromLong,
to_latitude = decryptedToLat,
to_longitude = decryptedToLong,
from_latitude = fromLat,
from_longitude = fromLong,
to_latitude = toLat,
to_longitude = toLong,
route_distance = route_distance,
route_duration = route_duration,
routes = decryptedRoutes,
Expand All @@ -43,31 +59,36 @@ fun EncryptedLocationJourney.toDecryptedLocationJourney(groupCipher: GroupCipher
}

/**
* Convert a [LocationJourney] to an [EncryptedLocationJourney] using the provided [GroupCipher]
* Converts [LocationJourney] to [EncryptedLocationJourney] using [groupCipher].
* Returns null if *any* required encryption step fails.
*/
fun LocationJourney.toEncryptedLocationJourney(
groupCipher: GroupCipher,
distributionId: UUID
): EncryptedLocationJourney? {
val encryptedFromLat = groupCipher.encrypt(distributionId, from_latitude) ?: return null
val encryptedFromLong = groupCipher.encrypt(distributionId, from_longitude) ?: return null
val encryptedToLat = to_latitude?.let { groupCipher.encrypt(distributionId, it) }
val encryptedToLong = to_longitude?.let { groupCipher.encrypt(distributionId, it) }
// Encrypt required points
val fromLat = groupCipher.encryptPoint(distributionId, from_latitude) ?: return null
val fromLong = groupCipher.encryptPoint(distributionId, from_longitude) ?: return null

// Encrypt optional points
val toLat = to_latitude?.let { groupCipher.encryptPoint(distributionId, it) }
val toLong = to_longitude?.let { groupCipher.encryptPoint(distributionId, it) }

val encryptedRoutes = routes.map {
EncryptedJourneyRoute(
latitude = groupCipher.encrypt(distributionId, it.latitude) ?: return null,
longitude = groupCipher.encrypt(distributionId, it.longitude) ?: return null
)
// Encrypt route list; short-circuit if any route fails
val encryptedRoutes = routes.map { route ->
val lat = groupCipher.encryptPoint(distributionId, route.latitude) ?: return null
val long = groupCipher.encryptPoint(distributionId, route.longitude) ?: return null
EncryptedJourneyRoute(lat, long)
}

// Construct the encrypted journey
return EncryptedLocationJourney(
id = id,
user_id = user_id,
from_latitude = encryptedFromLat,
from_longitude = encryptedFromLong,
to_latitude = encryptedToLat,
to_longitude = encryptedToLong,
from_latitude = fromLat,
from_longitude = fromLong,
to_latitude = toLat,
to_longitude = toLong,
route_distance = route_distance,
route_duration = route_duration,
routes = encryptedRoutes,
Expand All @@ -78,18 +99,20 @@ fun LocationJourney.toEncryptedLocationJourney(
)
}

fun GroupCipher.decrypt(data: Blob): Double? {
fun GroupCipher.decryptPoint(data: ByteArray): Double? {
return try {
decrypt(data.toBytes()).toString(Charsets.UTF_8).toDouble()
decrypt(data).toString(Charsets.UTF_8).toDoubleOrNull()
} catch (e: Exception) {
Timber.e(e, "Failed to decrypt double")
null
}
}

fun GroupCipher.encrypt(distributionId: UUID, data: Double): Blob? {
fun GroupCipher.encryptPoint(distributionId: UUID, data: Double): String? {
return try {
Blob.fromBytes(encrypt(distributionId, data.toString().toByteArray(Charsets.UTF_8)).serialize())
encrypt(distributionId, data.toString().toByteArray(Charsets.UTF_8))
.serialize()
.encodeToString()
} catch (e: Exception) {
Timber.e(e, "Failed to encrypt double")
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.canopas.yourspace.data.models.space.MemberKeyData
import com.canopas.yourspace.data.models.space.SPACE_MEMBER_ROLE_ADMIN
import com.canopas.yourspace.data.models.space.SPACE_MEMBER_ROLE_MEMBER
import com.canopas.yourspace.data.service.auth.AuthService
import com.canopas.yourspace.data.service.location.toBytes
import com.canopas.yourspace.data.service.place.ApiPlaceService
import com.canopas.yourspace.data.service.user.ApiUserService
import com.canopas.yourspace.data.storage.bufferedkeystore.BufferedSenderKeyStore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import com.canopas.yourspace.data.models.user.ApiUserSession
import com.canopas.yourspace.data.models.user.LOGIN_TYPE_APPLE
import com.canopas.yourspace.data.models.user.LOGIN_TYPE_GOOGLE
import com.canopas.yourspace.data.service.location.ApiLocationService
import com.canopas.yourspace.data.service.location.encodeToString
import com.canopas.yourspace.data.service.location.toBytes
import com.canopas.yourspace.data.storage.UserPreferences
import com.canopas.yourspace.data.utils.Config
import com.canopas.yourspace.data.utils.Config.FIRESTORE_COLLECTION_USERS
Expand All @@ -13,7 +15,6 @@ import com.canopas.yourspace.data.utils.PrivateKeyUtils
import com.canopas.yourspace.data.utils.snapshotFlow
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.firestore.Blob
import com.google.firebase.firestore.FieldValue
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.FirebaseFirestoreException
Expand Down Expand Up @@ -55,7 +56,7 @@ class ApiUserService @Inject constructor(
user == null -> null
currentUser == null || user.id != currentUser.id -> user
else -> decryptPrivateKey(user)?.let { decryptedKey ->
user.copy(identity_key_private = Blob.fromBytes(decryptedKey))
user.copy(identity_key_private = decryptedKey.encodeToString())
}
}
} catch (e: FirebaseFirestoreException) {
Expand All @@ -70,7 +71,7 @@ class ApiUserService @Inject constructor(
fun getUserFlow(userId: String) =
userRef.document(userId).snapshotFlow(ApiUser::class.java).map { user ->
user?.let { decryptPrivateKey(it) }?.let { decryptedKey ->
user.copy(identity_key_private = Blob.fromBytes(decryptedKey))
user.copy(identity_key_private = decryptedKey.encodeToString())
}
}

Expand Down Expand Up @@ -140,16 +141,16 @@ class ApiUserService @Inject constructor(
userRef.document(user.id).update(
mapOf(
"updated_at" to System.currentTimeMillis(),
"identity_key_public" to Blob.fromBytes(identityKeyPair.publicKey.publicKey.serialize()),
"identity_key_private" to Blob.fromBytes(encryptedPrivateKey),
"identity_key_salt" to Blob.fromBytes(salt)
"identity_key_public" to identityKeyPair.publicKey.publicKey.serialize().encodeToString(),
"identity_key_private" to encryptedPrivateKey.encodeToString(),
"identity_key_salt" to salt.encodeToString()
)
).await()
return user.copy(
updated_at = System.currentTimeMillis(),
identity_key_public = Blob.fromBytes(identityKeyPair.publicKey.publicKey.serialize()),
identity_key_private = Blob.fromBytes(identityKeyPair.privateKey.serialize()),
identity_key_salt = Blob.fromBytes(salt)
identity_key_public = identityKeyPair.publicKey.publicKey.serialize().encodeToString(),
identity_key_private = identityKeyPair.privateKey.serialize().encodeToString(),
identity_key_salt = salt.encodeToString()
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.canopas.yourspace.data.utils

import com.canopas.yourspace.data.models.space.EncryptedDistribution
import com.google.firebase.firestore.Blob
import com.canopas.yourspace.data.service.location.encodeToString
import com.canopas.yourspace.data.service.location.toBytes
import org.signal.libsignal.protocol.InvalidKeyException
import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.ecc.ECKeyPair
Expand Down Expand Up @@ -50,9 +51,9 @@ object EphemeralECDHUtils {

return EncryptedDistribution(
recipient_id = receiverId,
ephemeral_pub = Blob.fromBytes(ephemeralKeyPair.publicKey.serialize()),
iv = Blob.fromBytes(syntheticIv),
ciphertext = Blob.fromBytes(cipherText)
ephemeral_pub = ephemeralKeyPair.publicKey.serialize().encodeToString(),
iv = syntheticIv.encodeToString(),
ciphertext = cipherText.encodeToString()
)
}

Expand Down
Loading

0 comments on commit 0aa1874

Please sign in to comment.