import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import java.io.EOFException
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.channels.AsynchronousSocketChannel
import java.nio.channels.CompletionHandler
import java.util.Base64
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLEngine
import javax.net.ssl.SSLEngineResult
import javax.net.ssl.SSLEngineResult.Status
object WhatsappTryHandshake {
const val WEB_SOCKET_HOST: String = "web.whatsapp.com"
const val WEB_SOCKET_PORT: Int = 443
const val KEY_LENGTH = 16
lateinit var socketChannel: AsynchronousSocketChannel
lateinit var sslEngine: SSLEngine
lateinit var clientKey: String
lateinit var sslReadBuffer: ByteBuffer
lateinit var netDataWrite: ByteBuffer
lateinit var sslOutputBuffer: ByteBuffer
private var isHandshakeCompleted = false
suspend fun connectToWhatsAppWebSocket() {
clientKey = Base64.getEncoder().encodeToString(ByteArray(KEY_LENGTH).apply { java.util.Random().nextBytes(this) })
// SSL context for secure connection
val sslContext = SSLContext.getInstance("TLSv1.3")
sslContext.init(null, null, null)
socketChannel = AsynchronousSocketChannel.open()
val address = InetSocketAddress(WEB_SOCKET_HOST, WEB_SOCKET_PORT)
socketChannel.connect(address).get()
// Set up SSL Engine
sslEngine = sslContext.createSSLEngine(WEB_SOCKET_HOST, WEB_SOCKET_PORT)
sslEngine.useClientMode = true
sslEngine.beginHandshake()
// SSL Handshake
netDataWrite = ByteBuffer.allocate(sslEngine.session.packetBufferSize)
sslReadBuffer = ByteBuffer.allocate(sslEngine.session.packetBufferSize)
sslReadBuffer.position(sslReadBuffer.limit())
sslOutputBuffer = ByteBuffer.allocate(sslEngine.session.packetBufferSize)
doHandshake()
}
private fun doHandshake(status: Status? = null) {
println("${sslEngine.handshakeStatus}")
when (sslEngine.handshakeStatus) {
SSLEngineResult.HandshakeStatus.NEED_WRAP -> {
netDataWrite.clear()
val result = sslEngine.wrap(sslOutputBuffer, netDataWrite)
val isHandshakeFinished = isHandshakeFinished(result, true)
netDataWrite.flip()
socketChannel.write(netDataWrite, null, object : CompletionHandler{
override fun completed(result: Int?, attachment: Any?) {
println("completed: $result")
if (isHandshakeFinished){
finishSslHandshake()
}else{
doHandshake()
}
}
override fun failed(exc: Throwable?, attachment: Any?) {
println("failed: ${exc?.message}")
}
})
}
SSLEngineResult.HandshakeStatus.NEED_UNWRAP -> {
sslReadBuffer.compact()
if (status != Status.BUFFER_UNDERFLOW && sslReadBuffer.position() != 0){
sslReadBuffer.flip()
doSSlHandshakeUnwrapOperation()
}else{
readPlain(sslReadBuffer, true){
doSSlHandshakeUnwrapOperation()
}
}
}
SSLEngineResult.HandshakeStatus.FINISHED -> {
finishSslHandshake()
}
SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING -> {
finishSslHandshake()
println("Cannot complete Handshake")
}
SSLEngineResult.HandshakeStatus.NEED_TASK -> {
var runnable: Runnable? = sslEngine.delegatedTask
while (runnable != null) {
runnable.run()
runnable = sslEngine.delegatedTask
}
println("Handshake Status " + sslEngine.handshakeStatus )
doHandshake()
}
else -> throw IllegalStateException("Unknown handshake status")
}
}
private fun doSSlHandshakeUnwrapOperation() {
try {
val result: SSLEngineResult = sslEngine.unwrap(sslReadBuffer, sslOutputBuffer)
println(result.toString())
if (isHandshakeFinished(result, false)) {
finishSslHandshake()
} else {
doHandshake(result.status)
}
} catch (throwable: Throwable) {
throwable.printStackTrace()
finishSslHandshake()
}
}
private fun finishSslHandshake() {
isHandshakeCompleted = true
sslOutputBuffer.clear()
}
private fun isHandshakeFinished(result: SSLEngineResult, wrap: Boolean): Boolean {
val sslEngineStatus = result.status
check(!(sslEngineStatus != SSLEngineResult.Status.OK && (wrap || sslEngineStatus != SSLEngineResult.Status.BUFFER_UNDERFLOW))) { "SSL handshake operation failed with status: $sslEngineStatus" }
check(!(wrap && result.bytesConsumed() != 0)) {
throw IllegalStateException("SSL handshake operation failed with status: no bytes consumed")
}
check(!(!wrap && result.bytesProduced() != 0)) { throw IllegalStateException("SSL handshake operation failed with status: no bytes produced") }
val sslHandshakeStatus = result.handshakeStatus
return sslHandshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED
}
private fun readPlain(buffer: ByteBuffer, lastRead: Boolean, proceed: () -> Unit) {
val outerCaller = RuntimeException()
socketChannel.read(buffer, null, object : CompletionHandler {
override fun completed(bytesRead: Int, attachment: Any?) {
if (bytesRead == -1) {
val eof = EOFException()
eof.addSuppressed(outerCaller)
println("Failed: ${eof.message}")
return
}
if (lastRead) {
buffer.flip()
}
proceed()
}
override fun failed(exc: Throwable, attachment: Any?) {
exc.addSuppressed(outerCaller)
println("Failed: ${exc.message}")
}
})
}
}
fun main(){
CoroutineScope(IO).launch {
WhatsappTryHandshake.connectToWhatsAppWebSocket()
}
while (true){
//..
}
}
Описание проблемы
- Поведение среды JVM
- Код успешно выполняется в автономной среде Kotlin/JVM.
- Переходы состояний рукопожатия включают NEED_WRAP, NEED_UNWRAP и NEED_TASK > как ожидается.
- Окончательное рукопожатие завершается успешно, как показано в журналах ниже.
- При подтверждении связи возникает исключение на последнем этапе NEED_WRAP.
- Байты, полученные в результате Операция переноса различается в JVM и Android.
JVM: 74 байта. - Android: 16470 байтов .
Журналы JVM
NEED_WRAP
completed: 489
NEED_UNWRAP
Status = OK HandshakeStatus = NEED_TASK
bytesConsumed = 127 bytesProduced = 0
NEED_TASK
Handshake Status NEED_WRAP
NEED_WRAP
completed: 6
NEED_UNWRAP
Status = OK HandshakeStatus = NEED_UNWRAP
bytesConsumed = 6 bytesProduced = 0
NEED_UNWRAP
Status = OK HandshakeStatus = NEED_TASK
bytesConsumed = 1022 bytesProduced = 0
NEED_TASK
Handshake Status NEED_UNWRAP
NEED_UNWRAP
Status = OK HandshakeStatus = NEED_UNWRAP
bytesConsumed = 1522 bytesProduced = 0
NEED_UNWRAP
Status = BUFFER_UNDERFLOW HandshakeStatus = NEED_UNWRAP
bytesConsumed = 0 bytesProduced = 0
NEED_UNWRAP
Status = OK HandshakeStatus = NEED_TASK
bytesConsumed = 492 bytesProduced = 0
NEED_TASK
Handshake Status NEED_WRAP
NEED_WRAP
completed: 74
Журналы Android
NEED_WRAP
completed: 517
NEED_UNWRAP
Status = OK HandshakeStatus = NEED_UNWRAP
bytesConsumed = 127 bytesProduced = 0
NEED_UNWRAP
Status = OK HandshakeStatus = NEED_UNWRAP
bytesConsumed = 6 bytesProduced = 0
NEED_UNWRAP
Status = OK HandshakeStatus = NEED_UNWRAP
bytesConsumed = 1022 bytesProduced = 0
NEED_UNWRAP
Status = OK HandshakeStatus = NEED_UNWRAP
bytesConsumed = 1522 bytesProduced = 0
NEED_UNWRAP
Status = OK HandshakeStatus = NEED_WRAP
bytesConsumed = 492 bytesProduced = 0
NEED_WRAP
java.lang.IllegalStateException: SSL handshake operation failed with status: no bytes consumed
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake.isHandshakeFinished(Whatsapp_transport_handshake.kt:145)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake.doHandshake(Whatsapp_transport_handshake.kt:64)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake.doSSlHandshakeUnwrapOperation(Whatsapp_transport_handshake.kt:127)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake.doHandshake(Whatsapp_transport_handshake.kt:89)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake.doSSlHandshakeUnwrapOperation(Whatsapp_transport_handshake.kt:127)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake.doHandshake(Whatsapp_transport_handshake.kt:89)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake.doSSlHandshakeUnwrapOperation(Whatsapp_transport_handshake.kt:127)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake.doHandshake(Whatsapp_transport_handshake.kt:89)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake.doSSlHandshakeUnwrapOperation(Whatsapp_transport_handshake.kt:127)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake.doHandshake(Whatsapp_transport_handshake.kt:89)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake.doSSlHandshakeUnwrapOperation(Whatsapp_transport_handshake.kt:127)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake.access$doSSlHandshakeUnwrapOperation(Whatsapp_transport_handshake.kt:17)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake$doHandshake$2.invoke(Whatsapp_transport_handshake.kt:93)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake$doHandshake$2.invoke(Whatsapp_transport_handshake.kt:92)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake$readPlain$1.completed(Whatsapp_transport_handshake.kt:168)
at com.example.whatsappwrapperdemo.whatsapp.WhatsappTryHandshake$readPlain$1.completed(Whatsapp_transport_handshake.kt:156)
at sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:126)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finishRead(UnixAsynchronousSocketChannelImpl.java:454)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finish(UnixAsynchronousSocketChannelImpl.java:201)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.onEvent(UnixAsynchronousSocketChannelImpl.java:223)
at sun.nio.ch.EPollPort$EventHandlerTask.run(EPollPort.java:293)
at sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at java.lang.Thread.run(Thread.java:1012)
Наблюдения
- В среде JVM рукопожатие включает NEED_TASK, который не отображается в Android .
- Байты, полученные в результате окончательной операции переноса, значительно различаются между JVM и Android.
- Я думал, что могут быть проблемы с сертификатами поэтому я также добавил файлы сертификатов в приложение, добавив сеть файл конфигурации ниже.
web.whatsapp.com
Подробнее здесь: https://stackoverflow.com/questions/792 ... red-to-jvm