Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sender HMAC to MessageV2 #164

Merged
merged 31 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
25664a0
Adding the way for adding should push and hmac
giovas17 Jan 26, 2024
bda8a7a
Adding new approach for codecs and crypto
giovas17 Jan 31, 2024
8286a3f
Solving test for conversation using new flag
giovas17 Jan 31, 2024
4001857
Update ConversationV2.kt
giovas17 Jan 31, 2024
fd117ab
do kotlin way of extending proto class
nplasterer Feb 1, 2024
92a5712
Adding the proper way to manage the codec changes
giovas17 Feb 8, 2024
97ea0c0
Update GroupMembershipChangeCodec.kt
giovas17 Feb 8, 2024
696e229
Update library/src/main/java/org/xmtp/android/library/Crypto.kt
nplasterer Feb 10, 2024
241992a
Update library/src/main/java/org/xmtp/android/library/Crypto.kt
nplasterer Feb 10, 2024
33d50f3
Update library/src/main/java/org/xmtp/android/library/Crypto.kt
nplasterer Feb 10, 2024
fd46f81
add method to get the hmac keys
nplasterer Feb 10, 2024
deec4b0
fix up the crypto
nplasterer Feb 10, 2024
c985c6a
add the get keys code for hmacs
nplasterer Feb 13, 2024
2127f77
remove unneeded crypto code
nplasterer Feb 13, 2024
ca3416c
write a test for it
nplasterer Feb 13, 2024
dd29da4
get the test to pass
nplasterer Feb 20, 2024
866b5a3
feat: integrate `shouldPush' for React Native (#184)
Feb 20, 2024
0279d00
Update GroupMembershipChangeTest.kt
giovas17 Feb 19, 2024
578e4b9
Removing issues and failing tests
giovas17 Feb 20, 2024
c5d97df
Update GroupMembershipChangeTest.kt
giovas17 Feb 20, 2024
50a413c
Updating shouldPush flag and removing issues in instrumental testing
giovas17 Feb 20, 2024
2f67cef
Ignoring failing tests
giovas17 Feb 20, 2024
2763b7e
remove all the ignored tests
nplasterer Feb 21, 2024
e113e3b
feat: add shouldPush property to MessageV2Builder
Feb 20, 2024
df578cf
fix up the lint issue
nplasterer Feb 21, 2024
f37d913
fix: deriveKey function and improvements
Feb 28, 2024
7f9394c
fix: getHmacKeys method
Feb 28, 2024
b04fc1e
fix: CodecText.kt
Feb 28, 2024
6f14b68
Revert "feat: integrate `shouldPush' for React Native (#184)"
Feb 28, 2024
5050c30
Revert "Removing issues and failing tests"
Feb 28, 2024
5bb1b5a
Remove @Ignore annotations from GroupTest
Mar 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.fail
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.xmtp.android.library.messages.PrivateKeyBuilder
Expand Down Expand Up @@ -202,7 +201,6 @@ class ClientTest {
}

@Test
@Ignore("CI Issues")
fun testPublicCanMessage() {
val aliceWallet = PrivateKeyBuilder()
val notOnNetwork = PrivateKeyBuilder()
Expand All @@ -218,7 +216,6 @@ class ClientTest {
}

@Test
@Ignore("CI Issues")
fun testPreEnableIdentityCallback() {
val fakeWallet = PrivateKeyBuilder()
val expectation = CompletableFuture<Unit>()
Expand All @@ -241,7 +238,6 @@ class ClientTest {
}

@Test
@Ignore("CI Issues")
fun testPreCreateIdentityCallback() {
val fakeWallet = PrivateKeyBuilder()
val expectation = CompletableFuture<Unit>()
Expand Down
114 changes: 106 additions & 8 deletions library/src/androidTest/java/org/xmtp/android/library/CodecTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,38 @@ package org.xmtp.android.library
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.protobuf.kotlin.toByteStringUtf8
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.xmtp.android.library.Crypto.Companion.verifyHmacSignature
import org.xmtp.android.library.codecs.CompositeCodec
import org.xmtp.android.library.codecs.ContentCodec
import org.xmtp.android.library.codecs.ContentTypeId
import org.xmtp.android.library.codecs.ContentTypeIdBuilder
import org.xmtp.android.library.codecs.DecodedComposite
import org.xmtp.android.library.codecs.EncodedContent
import org.xmtp.android.library.codecs.TextCodec
import org.xmtp.android.library.messages.InvitationV1ContextBuilder
import org.xmtp.android.library.messages.MessageV2Builder
import org.xmtp.android.library.messages.PrivateKeyBuilder
import org.xmtp.android.library.messages.walletAddress
import java.time.Instant

data class NumberCodec(
override var contentType: ContentTypeId = ContentTypeIdBuilder.builderFromAuthorityId(
authorityId = "example.com",
typeId = "number",
versionMajor = 1,
versionMinor = 1
)
versionMinor = 1,
),
) : ContentCodec<Double> {
override fun encode(content: Double): EncodedContent {
return EncodedContent.newBuilder().also {
it.type = ContentTypeIdBuilder.builderFromAuthorityId(
authorityId = "example.com",
typeId = "number",
versionMajor = 1,
versionMinor = 1
versionMinor = 1,
)
it.content = mapOf(Pair("number", content)).toString().toByteStringUtf8()
}.build()
Expand All @@ -37,10 +43,13 @@ data class NumberCodec(
override fun decode(content: EncodedContent): Double =
content.content.toStringUtf8().filter { it.isDigit() || it == '.' }.toDouble()

override fun shouldPush(content: Double): Boolean = false

override fun fallback(content: Double): String? {
return "Error: This app does not support numbers."
}
}

@RunWith(AndroidJUnit4::class)
class CodecTest {

Expand All @@ -53,7 +62,7 @@ class CodecTest {
aliceClient.conversations.newConversation(fixtures.bob.walletAddress)
aliceConversation.send(
content = 3.14,
options = SendOptions(contentType = NumberCodec().contentType)
options = SendOptions(contentType = NumberCodec().contentType),
)
val messages = aliceConversation.messages()
assertEquals(messages.size, 1)
Expand All @@ -75,7 +84,7 @@ class CodecTest {
val source = DecodedComposite(encodedContent = textContent)
aliceConversation.send(
content = source,
options = SendOptions(contentType = CompositeCodec().contentType)
options = SendOptions(contentType = CompositeCodec().contentType),
)
val messages = aliceConversation.messages()
val decoded: DecodedComposite? = messages[0].content()
Expand All @@ -95,12 +104,12 @@ class CodecTest {
val source = DecodedComposite(
parts = listOf(
DecodedComposite(encodedContent = textContent),
DecodedComposite(parts = listOf(DecodedComposite(encodedContent = numberContent)))
)
DecodedComposite(parts = listOf(DecodedComposite(encodedContent = numberContent))),
),
)
aliceConversation.send(
content = source,
options = SendOptions(contentType = CompositeCodec().contentType)
options = SendOptions(contentType = CompositeCodec().contentType),
)
val messages = aliceConversation.messages()
val decoded: DecodedComposite? = messages[0].content()
Expand All @@ -109,4 +118,93 @@ class CodecTest {
assertEquals("sup", part1.content())
assertEquals(3.14, part2.content())
}

@Test
fun testCanGetPushInfoBeforeDecoded() {
val codec = NumberCodec()
Client.register(codec = codec)
val fixtures = fixtures()
val aliceClient = fixtures.aliceClient!!
val aliceConversation =
aliceClient.conversations.newConversation(fixtures.bob.walletAddress)
aliceConversation.send(
content = 3.14,
options = SendOptions(contentType = codec.contentType),
)
val messages = aliceConversation.messages()
assert(messages.isNotEmpty())

val message = MessageV2Builder.buildEncode(
client = aliceClient,
encodedContent = messages[0].encodedContent,
topic = aliceConversation.topic,
keyMaterial = aliceConversation.keyMaterial!!,
codec = codec,
)

assertEquals(false, message.shouldPush)
assertEquals(true, message.senderHmac?.isNotEmpty())
}

@Test
fun testReturnsAllHMACKeys() {
val alix = PrivateKeyBuilder()
val clientOptions =
ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false))
val alixClient = Client().create(alix, clientOptions)
val conversations = mutableListOf<Conversation>()
repeat(5) {
val account = PrivateKeyBuilder()
val client = Client().create(account, clientOptions)
conversations.add(
alixClient.conversations.newConversation(
client.address,
context = InvitationV1ContextBuilder.buildFromConversation(conversationId = "hi")
)
)
}

val thirtyDayPeriodsSinceEpoch = Instant.now().epochSecond / 60 / 60 / 24 / 30

val hmacKeys = alixClient.conversations.getHmacKeys()

val topics = hmacKeys.hmacKeysMap.keys
conversations.forEach { convo ->
assertTrue(topics.contains(convo.topic))
}

val topicHmacs = mutableMapOf<String, ByteArray>()
val headerBytes = ByteArray(10)

conversations.forEach { conversation ->
val topic = conversation.topic
val payload = TextCodec().encode(content = "Hello, world!")

val message = MessageV2Builder.buildEncode(
client = alixClient,
encodedContent = payload,
topic = topic,
keyMaterial = headerBytes,
codec = TextCodec()
)

val keyMaterial = conversation.keyMaterial
val info = "$thirtyDayPeriodsSinceEpoch-${alixClient.address}"
val key = Crypto.deriveKey(keyMaterial!!, ByteArray(0), info.toByteArray())
val hmac = Crypto.calculateMac(key, headerBytes)

topicHmacs[topic] = hmac
}

hmacKeys.hmacKeysMap.forEach { (topic, hmacData) ->
hmacData.valuesList.forEachIndexed { idx, hmacKeyThirtyDayPeriod ->
val valid = verifyHmacSignature(
hmacKeyThirtyDayPeriod.hmacKey.toByteArray(),
topicHmacs[topic]!!,
headerBytes
)
assertTrue(valid == (idx == 1))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,16 @@ class ConversationTest {
additionalData = headerBytes,
)
val tamperedMessage =
MessageV2Builder.buildFromCipherText(headerBytes = headerBytes, ciphertext = ciphertext)
MessageV2Builder.buildFromCipherText(
headerBytes = headerBytes,
ciphertext = ciphertext,
senderHmac = null,
shouldPush = true,
)
val tamperedEnvelope = EnvelopeBuilder.buildFromString(
topic = aliceConversation.topic,
timestamp = Date(),
message = MessageBuilder.buildFromMessageV2(v2 = tamperedMessage).toByteArray(),
message = MessageBuilder.buildFromMessageV2(v2 = tamperedMessage.messageV2).toByteArray(),
)
aliceClient.publish(envelopes = listOf(tamperedEnvelope))
val bobConversation = bobClient.conversations.newConversation(
Expand Down Expand Up @@ -585,7 +590,8 @@ class ConversationTest {
encodedContent,
topic = conversation.topic,
keyMaterial = conversation.keyMaterial!!,
),
codec = encoder,
).messageV2,
).toByteArray(),
),
)
Expand Down Expand Up @@ -848,7 +854,6 @@ class ConversationTest {
val directMessageV1 = Topic.directMessageV1(invalidId, "sd").description
val directMessageV2 = Topic.directMessageV2(invalidId).description
val preferenceList = Topic.preferenceList(invalidId).description
val conversations = bobClient.conversations

// check if validation of topics no accept all types with invalid topic
assertFalse(Topic.isValidTopic(privateStore))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package org.xmtp.android.library

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
Expand All @@ -25,18 +26,17 @@ class GroupMembershipChangeTest {
lateinit var caro: PrivateKey
lateinit var caroClient: Client
lateinit var fixtures: Fixtures
val context = ApplicationProvider.getApplicationContext<Context>()

@Before
fun setUp() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
fixtures =
fixtures(
clientOptions = ClientOptions(
ClientOptions.Api(XMTPEnvironment.LOCAL, false),
enableAlphaMls = true,
appContext = context
)
fixtures = fixtures(
clientOptions = ClientOptions(
ClientOptions.Api(XMTPEnvironment.LOCAL, false),
enableAlphaMls = true,
appContext = context,
)
)
alixWallet = fixtures.aliceAccount
alix = fixtures.alice
boWallet = fixtures.bobAccount
Expand Down
Loading
Loading