Skip to content

Commit

Permalink
feat: initDH method implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
y9san9 committed Oct 11, 2023
1 parent 3f5b5cb commit 47a4ed6
Show file tree
Hide file tree
Showing 50 changed files with 648 additions and 426 deletions.
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
# TODO
# ktproto - Kotlin Library for MTProto Protocol

⚠️ ALL OF THIS IS WORK IN PROGRESS ⚠️

`ktproto` is a Kotlin library designed to simplify working with Telegram's MTProto protocol. This library provides the tools you need to establish connections, perform authentication, and interact with the Telegram API using the MTProto protocol.

## Features

- Establish connections to Telegram's servers.
- Interact with the Telegram API using MTProto protocol.
- Built-in integration with TL (Maintained in a separate repo: https://github.com/kotlin-telegram/ktproto)

## Usage

```kotlin
@OngoingConnection
private suspend fun main(): Unit = weakCoroutineScope {
val transport = openKtorSocketTransport(
hostname = "149.154.167.51",
port = 443
)
val client = plainMTProtoClient(
transport = transport,
scope = this
)
repeat(10) {
createAuthorizationKey(client, keys)
}
// Sending encrypted requests are not supported ATM
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ kotlin {
jvm {
jvmToolchain(8)
}
js(IR) {
js {
browser()
nodejs()
}
Expand Down
1 change: 1 addition & 0 deletions client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins {
version = libs.versions.ktprotoVersion.get()

dependencies {
commonMainImplementation(projects.libs.crypto)
commonMainImplementation(libs.koTL.serialization)
commonMainImplementation(libs.kotlinxSerialization)
commonMainApi(projects.session)
Expand Down
1 change: 1 addition & 0 deletions client/ktor/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies {
commonMainImplementation(projects.types)
commonMainImplementation(libs.koTL.serialization)
commonMainApi(libs.ktor.client)
commonMainApi(libs.kotlinxSerialization)
jvmMainImplementation(libs.ktor.client.cio)
jvmMainImplementation(libs.ktor.client.logging)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package kotl.client.ktor
package ktproto.client.ktor

// todo: move file to kotel, since it's telegram, not MTProto
// ktproto may also be used for TON (lib name kton)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package kotl.client.ktor.websocket
package ktproto.client.ktor.websocket

import io.ktor.client.*
import io.ktor.client.plugins.websocket.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package kotl.client.ktor
package ktproto.client.ktor

internal actual fun isJS(): Boolean = false
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package kotl.client.ktor
package ktproto.client.ktor

internal actual fun isJS(): Boolean = true
41 changes: 0 additions & 41 deletions client/ktor/src/jvmMain/kotlin/kotl/client/ktor/ClientMain.kt

This file was deleted.

71 changes: 71 additions & 0 deletions client/ktor/src/jvmMain/kotlin/ktproto/client/ktor/ClientMain.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package ktproto.client.ktor

import io.ktor.client.*
import io.ktor.client.plugins.logging.*
import kotl.serialization.TL
import kotl.serialization.bare.bare
import kotl.serialization.bytes.Bytes
import kotlinx.serialization.encodeToByteArray
import ktproto.client.authorization.createAuthorizationKey
import ktproto.client.ktor.socket.openKtorSocketTransport
import ktproto.client.plain.plainMTProtoClient
import ktproto.client.rsa.RsaPublicKey
import ktproto.client.rsa.TLRsaPublicKey
import ktproto.client.rsa.fingerprint
import ktproto.io.annotation.OngoingConnection
import ktproto.stdlib.scope.weakCoroutineScope

private object DC : DataCenter {
override val name: String = "pluto"
override val isTest: Boolean = false
}

private val httpClient = HttpClient {
Logging {
level = LogLevel.ALL
logger = object : Logger {
override fun log(message: String) = println(message)
}
}
}

// Fingerprint: d09d1d85de64fd85
private val productionKey = RsaPublicKey("""
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA6LszBcC1LGzyr992NzE0ieY+BSaOW622Aa9Bd4ZHLl+TuFQ4lo4g
5nKaMBwK/BIb9xUfg0Q29/2mgIR6Zr9krM7HjuIcCzFvDtr+L0GQjae9H0pRB2OO
62cECs5HKhT5DZ98K33vmWiLowc621dQuwKWSQKjWf50XYFw42h21P2KXUGyp2y/
+aEyZ+uVgLLQbRA1dEjSDZ2iGRy12Mk5gpYc397aYp438fsJoHIgJ2lgMv5h7WY9
t6N/byY9Nw9p21Og3AoXSL2q/2IJ1WRUhebgAdGVMlV1fkuOQoEzR7EdpqtQD9Cs
5+bfo3Nhmcyvk5ftB0WkJ9z6bNZ7yxrP8wIDAQAB
-----END RSA PUBLIC KEY-----
""".trimIndent())

// Fingerprint: b25898df208d2603
private val testKey = RsaPublicKey("""
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAyMEdY1aR+sCR3ZSJrtztKTKqigvO/vBfqACJLZtS7QMgCGXJ6XIR
yy7mx66W0/sOFa7/1mAZtEoIokDP3ShoqF4fVNb6XeqgQfaUHd8wJpDWHcR2OFwv
plUUI1PLTktZ9uW2WE23b+ixNwJjJGwBDJPQEQFBE+vfmH0JP503wr5INS1poWg/
j25sIWeYPHYeOrFp/eXaqhISP6G+q2IeTaWTXpwZj4LzXq5YOpk4bYEQ6mvRq7D1
aHWfYmlEGepfaYR8Q0YqvvhYtMte3ITnuSJs171+GDqpdKcSwHnd6FudwGO4pcCO
j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
-----END RSA PUBLIC KEY-----
""".trimIndent())

private val keys = listOf(productionKey, testKey)

@OngoingConnection
private suspend fun main(): Unit = weakCoroutineScope {
val transport = openKtorSocketTransport(
hostname = "149.154.167.51",
port = 443
)
val client = plainMTProtoClient(
transport = transport,
scope = this
)
repeat(10) {
createAuthorizationKey(client, keys)
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package kotl.client.ktor
package ktproto.client.ktor

internal actual fun isJS(): Boolean = false
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package kotl.client.ktor.socket
package ktproto.client.ktor.socket

import io.ktor.network.selector.*
import io.ktor.network.sockets.*
Expand Down
8 changes: 7 additions & 1 deletion client/src/commonMain/kotlin/ktproto/client/MTProtoClient.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package ktproto.client

import kotl.core.descriptor.TLExpressionDescriptor
import kotl.core.element.TLExpression
import kotl.core.element.TLFunction
import kotlinx.coroutines.flow.Flow

public interface MTProtoClient {
public val updates: Flow<TLExpression>
public suspend fun execute(request: MTProtoRequest): TLExpression

public suspend fun execute(
function: TLFunction,
responseDescriptor: TLExpressionDescriptor
): TLExpression
}
9 changes: 0 additions & 9 deletions client/src/commonMain/kotlin/ktproto/client/MTProtoRequest.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,20 +1,57 @@
package ktproto.client.authorization

import ktproto.crypto.factorization.PollardRhoBrent
import kotl.serialization.int.Int128
import ktproto.client.MTProtoClient
import ktproto.client.requests.TLRequestPQ
import ktproto.client.serialization.execute
import ktproto.client.requests.requestPQ
import ktproto.client.rsa.RsaPublicKey
import ktproto.client.rsa.fingerprint
import ktproto.stdlib.bytes.decodeLong
import kotlin.random.Random

public suspend fun createAuthorizationKey(
client: MTProtoClient
client: MTProtoClient,
keys: List<RsaPublicKey>
) {
val (p, q, publicKey, serverNonce) = initDH(client, keys)

}

private data class InitDHResult(
val p: ULong,
val q: ULong,
val publicKey: RsaPublicKey,
val serverNonce: Int128
)

private suspend fun initDH(
client: MTProtoClient,
keys: List<RsaPublicKey>
): InitDHResult {
// 1)
val nonce = Random.nextInt128()
val request = client.execute(TLRequestPQ(nonce))
require(request.nonce.data.contentEquals(nonce.data)) {
"Server responded with invalid nonce (actual: $nonce, expected: ${request.nonce})"
val response = client.requestPQ(nonce)

require(response.nonce.data.contentEquals(nonce.data)) {
"Server responded with invalid nonce (actual: $nonce, expected: ${response.nonce})"
}
require(response.pq.payload.size <= 8) {
"Resulted payload size is more than is supported by ktproto. This should never happen, but if it did, then that is not your fault, just report it to https://github.com/ktproto/issues"
}
println(request)

// 2)
val publicKey = keys.firstOrNull { key ->
key.fingerprint() in response.serverPublicKeyFingerprints
} ?: error("Couldn't match any server_public_key_fingerprints. Response: ${response.serverPublicKeyFingerprints}, Expected: ${keys.map { key -> key.fingerprint() }}")

// 3)
val pq = response.pq.payload
.apply(ByteArray::reverse)
.decodeLong()
.toULong()

val (p, q) = PollardRhoBrent.factorize(pq)
return InitDHResult(p, q, publicKey, response.serverNonce)
}

private fun Random.nextInt128() = Int128(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package ktproto.client.plain

import kotl.core.decoder.decodeFromByteArray
import kotl.core.descriptor.TLExpressionDescriptor
import kotl.core.element.TLExpression
import kotl.core.element.TLFunction
import kotl.core.encoder.encodeToByteArray
import kotl.time.Clock
import ktproto.time.Clock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flow
import ktproto.client.MTProtoClient
import ktproto.client.MTProtoRequest
import ktproto.io.annotation.OngoingConnection
import ktproto.session.MTProtoSession
import ktproto.session.SessionConnector
Expand Down Expand Up @@ -61,12 +61,14 @@ private class PlainMTProtoClient(

override val updates: Flow<TLExpression> = flow { awaitCancellation() }

@OptIn(ExperimentalStdlibApi::class)
override suspend fun execute(
request: MTProtoRequest
function: TLFunction,
responseDescriptor: TLExpressionDescriptor
): TLExpression {
val bytes = request.function.encodeToByteArray()
val bytes = function.encodeToByteArray()
val message = MTProtoSession.Message(bytes)
val response = session.sendRequest(message) { it }
return request.responseDescriptor.decodeFromByteArray(response.bytes)
return responseDescriptor.decodeFromByteArray(response.bytes)
}
}
18 changes: 12 additions & 6 deletions client/src/commonMain/kotlin/ktproto/client/requests/RequestPQ.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,30 @@ package ktproto.client.requests

import kotl.serialization.annotation.Crc32
import kotl.serialization.annotation.TLRpc
import kotl.serialization.annotation.TLSize
import kotl.serialization.bytes.Bytes
import kotl.serialization.int.Int128
import kotlinx.serialization.Serializable
import ktproto.client.serialization.MTProtoRequestContainer
import ktproto.client.MTProtoClient
import ktproto.client.serialization.MTProtoRequest
import ktproto.client.serialization.execute

// req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
@Serializable
@TLRpc(crc32 = 0xbe7e8ef1_u)
public data class TLRequestPQ(
public val nonce: Int128
) : MTProtoRequestContainer<TLResponsePQ>
) : MTProtoRequest<TLResponsePQ>

// resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector long = ResPQ;
// resPQ#05162463 nonce:int128 server_nonce:int128 pq:bytes server_public_key_fingerprints:Vector long = ResPQ;
@Serializable
@Crc32(value = 0x05162463_u)
public data class TLResponsePQ(
public val nonce: Int128,
public val serverNonce: Int128,
public val pq: String,
public val serverPublicKey: List<Long>
public val pq: Bytes,
public val serverPublicKeyFingerprints: List<Long>
)

public suspend fun MTProtoClient.requestPQ(
nonce: Int128
): TLResponsePQ = execute(TLRequestPQ(nonce))
Loading

0 comments on commit 47a4ed6

Please sign in to comment.