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.writeFully
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.ECDHKeyPair
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeShortLVByteArray
@ -65,17 +67,25 @@ inline class EncryptMethodSessionKeyLoginState3(override val sessionKey: ByteArr
override val currentLoginState: Int get() = 3
}
inline class EncryptMethodECDH135(override val ecdh: ECDH) :
internal inline class EncryptMethodECDH135(override val ecdh: ECDH) :
EncryptMethodECDH {
override val id: Int get() = 135
}
inline class EncryptMethodECDH7(override val ecdh: ECDH) :
internal inline class EncryptMethodECDH7(override val ecdh: ECDH) :
EncryptMethodECDH {
override val id: Int get() = 7
}
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
/**
@ -97,6 +107,11 @@ 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())
if (ecdh.keyPair === ECDHKeyPair.DefaultStub) {
writeShortLVByteArray(ECDHKeyPair.DefaultStub.defaultPublicKey)
encryptAndWrite(ECDHKeyPair.DefaultStub.defaultShareKey, body)
} 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]}" }
@ -107,3 +122,4 @@ internal interface EncryptMethodECDH : EncryptMethod {
encryptAndWrite(ecdh.keyPair.initialShareKey, body)
}
}
}

View File

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

View File

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

View File

@ -19,7 +19,9 @@ expect interface ECDHPublicKey {
fun getEncoded(): ByteArray
}
expect class ECDHKeyPair {
internal expect class ECDHKeyPairImpl : ECDHKeyPair
interface ECDHKeyPair {
val privateKey: ECDHPrivateKey
val publicKey: ECDHPublicKey
@ -27,6 +29,15 @@ expect class ECDHKeyPair {
* 私匙和固定公匙([initialPublicKey]) 计算得到的 shareKey
*/
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
companion object {
val isECDHAvailable: Boolean
/**
* 由完整的 publicKey ByteArray 得到 [ECDHPublicKey]
*/
@ -60,9 +73,6 @@ expect class ECDH(keyPair: ECDHKeyPair) {
override fun toString(): String
}
/**
*
*/
@Suppress("FunctionName")
expect fun ECDH(): ECDH

View File

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