This commit is contained in:
Him188 2020-02-19 14:15:38 +08:00
parent 0508e8d894
commit 9de13ca6fd
5 changed files with 81 additions and 53 deletions

View File

@ -14,7 +14,9 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.buildPacket import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.cryptor.ECDH import net.mamoe.mirai.utils.cryptor.ECDH
import net.mamoe.mirai.utils.cryptor.ECDHKeyPair
import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeShortLVByteArray import net.mamoe.mirai.utils.io.writeShortLVByteArray
@ -65,17 +67,25 @@ inline class EncryptMethodSessionKeyLoginState3(override val sessionKey: ByteArr
override val currentLoginState: Int get() = 3 override val currentLoginState: Int get() = 3
} }
inline class EncryptMethodECDH135(override val ecdh: ECDH) : internal inline class EncryptMethodECDH135(override val ecdh: ECDH) :
EncryptMethodECDH { EncryptMethodECDH {
override val id: Int get() = 135 override val id: Int get() = 135
} }
inline class EncryptMethodECDH7(override val ecdh: ECDH) : internal inline class EncryptMethodECDH7(override val ecdh: ECDH) :
EncryptMethodECDH { EncryptMethodECDH {
override val id: Int get() = 7 override val id: Int get() = 7
} }
internal interface EncryptMethodECDH : EncryptMethod { internal interface EncryptMethodECDH : EncryptMethod {
companion object {
operator fun invoke(ecdh: ECDH): EncryptMethodECDH {
return if (ecdh.keyPair === ECDHKeyPair.DefaultStub) {
EncryptMethodECDH135(ecdh)
} else EncryptMethodECDH7(ecdh)
}
}
val ecdh: ECDH val ecdh: ECDH
/** /**
@ -97,13 +107,19 @@ internal interface EncryptMethodECDH : EncryptMethod {
// writeShortLVByteArray("04 CB 36 66 98 56 1E 93 6E 80 C1 57 E0 74 CA B1 3B 0B B6 8D DE B2 82 45 48 A1 B1 8D D4 FB 61 22 AF E1 2F E4 8C 52 66 D8 D7 26 9D 76 51 A8 EB 6F E7".hexToBytes()) // writeShortLVByteArray("04 CB 36 66 98 56 1E 93 6E 80 C1 57 E0 74 CA B1 3B 0B B6 8D DE B2 82 45 48 A1 B1 8D D4 FB 61 22 AF E1 2F E4 8C 52 66 D8 D7 26 9D 76 51 A8 EB 6F E7".hexToBytes())
writeShortLVByteArray(ecdh.keyPair.publicKey.getEncoded().drop(23).take(49).toByteArray().also { if (ecdh.keyPair === ECDHKeyPair.DefaultStub) {
// it.toUHexString().debugPrint("PUBLIC KEY") writeShortLVByteArray(ECDHKeyPair.DefaultStub.defaultPublicKey)
check(it[0].toInt() == 0x04) { "Bad publicKey generated. Expected first element=0x04, got${it[0]}" } encryptAndWrite(ECDHKeyPair.DefaultStub.defaultShareKey, body)
//check(ecdh.calculateShareKeyByPeerPublicKey(it.adjustToPublicKey()).contentEquals(ecdh.keyPair.shareKey)) { "PublicKey Validation failed" } } else {
}) MiraiLogger.info("Using custom")
writeShortLVByteArray(ecdh.keyPair.publicKey.getEncoded().drop(23).take(49).toByteArray().also {
// it.toUHexString().debugPrint("PUBLIC KEY")
check(it[0].toInt() == 0x04) { "Bad publicKey generated. Expected first element=0x04, got${it[0]}" }
//check(ecdh.calculateShareKeyByPeerPublicKey(it.adjustToPublicKey()).contentEquals(ecdh.keyPair.shareKey)) { "PublicKey Validation failed" }
})
// encryptAndWrite("26 33 BA EC 86 EB 79 E6 BC E0 20 06 5E A9 56 6C".hexToBytes(), body) // encryptAndWrite("26 33 BA EC 86 EB 79 E6 BC E0 20 06 5E A9 56 6C".hexToBytes(), body)
encryptAndWrite(ecdh.keyPair.initialShareKey, body) encryptAndWrite(ecdh.keyPair.initialShareKey, body)
}
} }
} }

View File

@ -47,7 +47,7 @@ internal class WtLogin {
ticket: String ticket: String
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(2) // subCommand writeShort(2) // subCommand
writeShort(4) // count of TLVs writeShort(4) // count of TLVs
t193(ticket) t193(ticket)
@ -64,7 +64,7 @@ internal class WtLogin {
captchaAnswer: String captchaAnswer: String
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(2) // subCommand writeShort(2) // subCommand
writeShort(4) // count of TLVs writeShort(4) // count of TLVs
t2(captchaAnswer, captchaSign, 0) t2(captchaAnswer, captchaSign, 0)
@ -83,7 +83,7 @@ internal class WtLogin {
t402: ByteArray t402: ByteArray
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(20) // subCommand writeShort(20) // subCommand
writeShort(4) // count of TLVs, probably ignored by server? writeShort(4) // count of TLVs, probably ignored by server?
t8(2052) t8(2052)
@ -103,7 +103,7 @@ internal class WtLogin {
client: QQAndroidClient client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId, unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00") { writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId, unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00") {
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(8) // subCommand writeShort(8) // subCommand
writeShort(6) // count of TLVs, probably ignored by server?TODO writeShort(6) // count of TLVs, probably ignored by server?TODO
t8(2052) t8(2052)
@ -131,7 +131,7 @@ internal class WtLogin {
client: QQAndroidClient client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(9) // subCommand writeShort(9) // subCommand
writeShort(17) // count of TLVs, probably ignored by server? writeShort(17) // count of TLVs, probably ignored by server?
//writeShort(LoginType.PASSWORD.value.toShort()) //writeShort(LoginType.PASSWORD.value.toShort())

View File

@ -19,13 +19,13 @@ import javax.crypto.KeyAgreement
actual typealias ECDHPrivateKey = PrivateKey actual typealias ECDHPrivateKey = PrivateKey
actual typealias ECDHPublicKey = PublicKey actual typealias ECDHPublicKey = PublicKey
actual class ECDHKeyPair( internal actual class ECDHKeyPairImpl(
private val delegate: KeyPair private val delegate: KeyPair
) { ) : ECDHKeyPair {
actual val privateKey: ECDHPrivateKey get() = delegate.private override val privateKey: ECDHPrivateKey get() = delegate.private
actual val publicKey: ECDHPublicKey get() = delegate.public override val publicKey: ECDHPublicKey get() = delegate.public
actual val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey) override val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey)
} }
@Suppress("FunctionName") @Suppress("FunctionName")
@ -33,6 +33,10 @@ actual fun ECDH() = ECDH(ECDH.generateKeyPair())
actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) { actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
actual companion object { actual companion object {
@Suppress("ObjectPropertyName")
private var _isECDHAvailable: Boolean = false // because `runCatching` has no contract.
actual val isECDHAvailable: Boolean get() = _isECDHAvailable
init { init {
kotlin.runCatching { kotlin.runCatching {
@SuppressLint("PrivateApi") @SuppressLint("PrivateApi")
@ -48,13 +52,19 @@ actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
Security.removeProvider(providerName) Security.removeProvider(providerName)
} }
Security.addProvider(clazz.newInstance() as Provider) Security.addProvider(clazz.newInstance() as Provider)
generateKeyPair()
_isECDHAvailable = true
}.exceptionOrNull()?.let { }.exceptionOrNull()?.let {
throw IllegalStateException("cannot init BouncyCastle", it) throw IllegalStateException("cannot init BouncyCastle", it)
} }
_isECDHAvailable = false
} }
actual fun generateKeyPair(): ECDHKeyPair { actual fun generateKeyPair(): ECDHKeyPair {
return ECDHKeyPair(KeyPairGenerator.getInstance("ECDH").genKeyPair()) if (!isECDHAvailable) {
return ECDHKeyPair.DefaultStub
}
return ECDHKeyPairImpl(KeyPairGenerator.getInstance("ECDH").genKeyPair())
} }
actual fun calculateShareKey( actual fun calculateShareKey(

View File

@ -19,7 +19,9 @@ expect interface ECDHPublicKey {
fun getEncoded(): ByteArray fun getEncoded(): ByteArray
} }
expect class ECDHKeyPair { internal expect class ECDHKeyPairImpl : ECDHKeyPair
interface ECDHKeyPair {
val privateKey: ECDHPrivateKey val privateKey: ECDHPrivateKey
val publicKey: ECDHPublicKey val publicKey: ECDHPublicKey
@ -27,6 +29,15 @@ expect class ECDHKeyPair {
* 私匙和固定公匙([initialPublicKey]) 计算得到的 shareKey * 私匙和固定公匙([initialPublicKey]) 计算得到的 shareKey
*/ */
val initialShareKey: ByteArray val initialShareKey: ByteArray
object DefaultStub : ECDHKeyPair {
val defaultPublicKey = "020b03cf3d99541f29ffec281bebbd4ea211292ac1f53d7128".chunkedHexToBytes()
val defaultShareKey = "4da0f614fc9f29c2054c77048a6566d7".chunkedHexToBytes()
override val privateKey: Nothing get() = error("stub!")
override val publicKey: Nothing get() = error("stub!")
override val initialShareKey: ByteArray get() = defaultShareKey
}
} }
/** /**
@ -41,6 +52,8 @@ expect class ECDH(keyPair: ECDHKeyPair) {
fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray
companion object { companion object {
val isECDHAvailable: Boolean
/** /**
* 由完整的 publicKey ByteArray 得到 [ECDHPublicKey] * 由完整的 publicKey ByteArray 得到 [ECDHPublicKey]
*/ */
@ -60,9 +73,6 @@ expect class ECDH(keyPair: ECDHKeyPair) {
override fun toString(): String override fun toString(): String
} }
/**
*
*/
@Suppress("FunctionName") @Suppress("FunctionName")
expect fun ECDH(): ECDH expect fun ECDH(): ECDH

View File

@ -9,11 +9,9 @@
package net.mamoe.mirai.utils.cryptor package net.mamoe.mirai.utils.cryptor
import net.mamoe.mirai.utils.io.chunkedHexToBytes
import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.md5
import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.* import java.security.*
import java.security.spec.ECGenParameterSpec
import java.security.spec.X509EncodedKeySpec import java.security.spec.X509EncodedKeySpec
import javax.crypto.KeyAgreement import javax.crypto.KeyAgreement
@ -21,20 +19,13 @@ import javax.crypto.KeyAgreement
actual typealias ECDHPrivateKey = PrivateKey actual typealias ECDHPrivateKey = PrivateKey
actual typealias ECDHPublicKey = PublicKey actual typealias ECDHPublicKey = PublicKey
actual class ECDHKeyPair( internal actual class ECDHKeyPairImpl(
private val delegate: KeyPair? private val delegate: KeyPair
) { ) : ECDHKeyPair {
actual val privateKey: ECDHPrivateKey get() = delegate?.private ?: error("ECDH is not available") override val privateKey: ECDHPrivateKey get() = delegate.private
actual val publicKey: ECDHPublicKey get() = delegate?.public ?: defaultPublicKey override val publicKey: ECDHPublicKey get() = delegate.public
actual val initialShareKey: ByteArray = if (delegate == null) { override val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey)
defaultShareKey
} else ECDH.calculateShareKey(privateKey, initialPublicKey)
companion object {
internal val defaultPublicKey = "020b03cf3d99541f29ffec281bebbd4ea211292ac1f53d7128".chunkedHexToBytes().adjustToPublicKey()
internal val defaultShareKey = "4da0f614fc9f29c2054c77048a6566d7".chunkedHexToBytes()
}
} }
@Suppress("FunctionName") @Suppress("FunctionName")
@ -42,33 +33,35 @@ actual fun ECDH() = ECDH(ECDH.generateKeyPair())
actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) { actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
actual companion object { actual companion object {
private var isECDHAvailable = true @Suppress("ObjectPropertyName")
private val _isECDHAvailable: Boolean
actual val isECDHAvailable: Boolean get() = _isECDHAvailable
init { init {
isECDHAvailable = kotlin.runCatching { _isECDHAvailable = kotlin.runCatching {
if (Security.getProvider("BouncyCastle") != null) {
Security.removeProvider("BouncyCastle")
}
Security.addProvider(BouncyCastleProvider()) Security.addProvider(BouncyCastleProvider())
generateKeyPair() // try if it is working generateKeyPair() // try if it is working
}.isSuccess }.isSuccess
} }
actual fun generateKeyPair(): ECDHKeyPair { actual fun generateKeyPair(): ECDHKeyPair {
return if (!isECDHAvailable) { if (!isECDHAvailable) {
ECDHKeyPair(null) return ECDHKeyPair.DefaultStub
} else ECDHKeyPair(KeyPairGenerator.getInstance("EC", "BC").apply { initialize(ECGenParameterSpec("secp192k1")) }.genKeyPair()) }
return ECDHKeyPairImpl(KeyPairGenerator.getInstance("ECDH").genKeyPair())
} }
actual fun calculateShareKey( actual fun calculateShareKey(
privateKey: ECDHPrivateKey, privateKey: ECDHPrivateKey,
publicKey: ECDHPublicKey publicKey: ECDHPublicKey
): ByteArray { ): ByteArray {
return if (!isECDHAvailable) { val instance = KeyAgreement.getInstance("ECDH", "BC")
ECDHKeyPair.defaultShareKey instance.init(privateKey)
} else { instance.doPhase(publicKey, true)
val instance = KeyAgreement.getInstance("ECDH", "BC") return md5(instance.generateSecret())
instance.init(privateKey)
instance.doPhase(publicKey, true)
md5(instance.generateSecret())
}
} }
actual fun constructPublicKey(key: ByteArray): ECDHPublicKey { actual fun constructPublicKey(key: ByteArray): ECDHPublicKey {
@ -77,7 +70,6 @@ actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
} }
actual fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray { actual fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray {
if (!isECDHAvailable) return keyPair.initialShareKey
return calculateShareKey(keyPair.privateKey, peerPublicKey) return calculateShareKey(keyPair.privateKey, peerPublicKey)
} }