mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-03-01 03:03:39 +00:00
282 lines
10 KiB
Scala
282 lines
10 KiB
Scala
// Copyright (c) 2016 PSForever.net to present
|
|
import java.net.{InetAddress, InetSocketAddress}
|
|
|
|
import akka.actor.{Actor, ActorLogging, ActorRef, DiagnosticActorLogging, Identify, MDCContextAware}
|
|
import net.psforever.crypto.CryptoInterface.{CryptoState, CryptoStateWithMAC}
|
|
import net.psforever.crypto.CryptoInterface
|
|
import net.psforever.packet._
|
|
import scodec.Attempt.{Failure, Successful}
|
|
import scodec.bits._
|
|
import scodec.{Attempt, Codec, Err}
|
|
import scodec.codecs.{bytes, uint16L, uint8L}
|
|
import java.security.SecureRandom
|
|
|
|
import net.psforever.packet.control.{ClientStart, ServerStart}
|
|
import net.psforever.packet.crypto._
|
|
|
|
/**
|
|
* Actor that stores crypto state for a connection and filters away any packet metadata.
|
|
*/
|
|
class CryptoSessionActor extends Actor with MDCContextAware {
|
|
private[this] val log = org.log4s.getLogger
|
|
|
|
var leftRef : ActorRef = ActorRef.noSender
|
|
var rightRef : ActorRef = ActorRef.noSender
|
|
|
|
var cryptoDHState = new CryptoInterface.CryptoDHState()
|
|
var cryptoState : Option[CryptoInterface.CryptoStateWithMAC] = None
|
|
val random = new SecureRandom()
|
|
|
|
// crypto handshake state
|
|
var serverChallenge = ByteVector.empty
|
|
var serverChallengeResult = ByteVector.empty
|
|
var serverMACBuffer = ByteVector.empty
|
|
|
|
var clientPublicKey = ByteVector.empty
|
|
var clientChallenge = ByteVector.empty
|
|
var clientChallengeResult = ByteVector.empty
|
|
|
|
def receive = Initializing
|
|
|
|
def Initializing : Receive = {
|
|
case HelloFriend(right) =>
|
|
import MDCContextAware.Implicits._
|
|
leftRef = sender()
|
|
rightRef = right.asInstanceOf[ActorRef]
|
|
|
|
// who ever we send to has to send something back to us
|
|
rightRef !> HelloFriend(self)
|
|
|
|
log.trace(s"Left sender ${leftRef.path.name}")
|
|
|
|
context.become(NewClient)
|
|
case default =>
|
|
log.error("Unknown message " + default)
|
|
context.stop(self)
|
|
}
|
|
|
|
def NewClient : Receive = {
|
|
case RawPacket(msg) =>
|
|
PacketCoding.UnmarshalPacket(msg) match {
|
|
case Failure(e) => log.error("Could not decode packet: " + e)
|
|
case Successful(p) =>
|
|
//println("RECV: " + p)
|
|
|
|
p match {
|
|
case ControlPacket(_, ClientStart(nonce)) =>
|
|
sendResponse(PacketCoding.CreateControlPacket(ServerStart(nonce, Math.abs(random.nextInt()))))
|
|
|
|
context.become(CryptoExchange)
|
|
case default =>
|
|
log.error(s"Unexpected packet type ${p} in state NewClient")
|
|
}
|
|
}
|
|
case default => log.error(s"Invalid message '$default' received in state NewClient")
|
|
}
|
|
|
|
def CryptoExchange : Receive = {
|
|
case RawPacket(msg) =>
|
|
PacketCoding.UnmarshalPacket(msg, CryptoPacketOpcode.ClientChallengeXchg) match {
|
|
case Failure(e) => log.error("Could not decode packet: " + e)
|
|
case Successful(p) =>
|
|
//println("RECV: " + p)
|
|
|
|
p match {
|
|
case CryptoPacket(seq, ClientChallengeXchg(time, challenge, p, g)) =>
|
|
// initialize our crypto state from the client's P and G
|
|
cryptoDHState.start(p, g)
|
|
|
|
// save the client challenge
|
|
clientChallenge = ServerChallengeXchg.getCompleteChallenge(time, challenge)
|
|
|
|
// save the packet we got for a MAC check later. drop the first 3 bytes
|
|
serverMACBuffer ++= msg.drop(3)
|
|
|
|
val serverTime = System.currentTimeMillis() / 1000L
|
|
val randomChallenge = getRandBytes(0xc)
|
|
|
|
// store the complete server challenge for later
|
|
serverChallenge = ServerChallengeXchg.getCompleteChallenge(serverTime, randomChallenge)
|
|
|
|
val packet = PacketCoding.CreateCryptoPacket(seq,
|
|
ServerChallengeXchg(serverTime, randomChallenge, cryptoDHState.getPublicKey))
|
|
|
|
val sentPacket = sendResponse(packet)
|
|
|
|
// save the sent packet a MAC check
|
|
serverMACBuffer ++= sentPacket.drop(3)
|
|
|
|
context.become(CryptoSetupFinishing)
|
|
case default => log.error(s"Unexpected packet type $p in state CryptoExchange")
|
|
}
|
|
}
|
|
case default => log.error(s"Invalid message '$default' received in state CryptoExchange")
|
|
}
|
|
|
|
def CryptoSetupFinishing : Receive = {
|
|
case RawPacket(msg) =>
|
|
PacketCoding.UnmarshalPacket(msg, CryptoPacketOpcode.ClientFinished) match {
|
|
case Failure(e) => log.error("Could not decode packet: " + e)
|
|
case Successful(p) =>
|
|
//println("RECV: " + p)
|
|
|
|
p match {
|
|
case CryptoPacket(seq, ClientFinished(clientPubKey, clientChalResult)) =>
|
|
clientPublicKey = clientPubKey
|
|
clientChallengeResult = clientChalResult
|
|
|
|
// save the packet we got for a MAC check later
|
|
serverMACBuffer ++= msg.drop(3)
|
|
|
|
val agreedValue = cryptoDHState.agree(clientPublicKey)
|
|
|
|
/*println("Agreed: " + agreedValue)
|
|
println(s"Client challenge: $clientChallenge")*/
|
|
val agreedMessage = ByteVector("master secret".getBytes) ++ clientChallenge ++
|
|
hex"00000000" ++ serverChallenge ++ hex"00000000"
|
|
|
|
//println("In message: " + agreedMessage)
|
|
|
|
val masterSecret = CryptoInterface.MD5MAC(agreedValue,
|
|
agreedMessage,
|
|
20)
|
|
|
|
//println("Master secret: " + masterSecret)
|
|
|
|
serverChallengeResult = CryptoInterface.MD5MAC(masterSecret,
|
|
ByteVector("server finished".getBytes) ++ serverMACBuffer ++ hex"01",
|
|
0xc)
|
|
|
|
val clientChallengeResultCheck = CryptoInterface.MD5MAC(masterSecret,
|
|
ByteVector("client finished".getBytes) ++ serverMACBuffer ++ hex"01" ++ clientChallengeResult ++ hex"01",
|
|
0xc)
|
|
|
|
//println("Check result: " + CryptoInterface.verifyMAC(clientChallenge, clientChallengeResult))
|
|
|
|
val decExpansion = ByteVector("client expansion".getBytes) ++ hex"0000" ++ serverChallenge ++
|
|
hex"00000000" ++ clientChallenge ++ hex"00000000"
|
|
|
|
val encExpansion = ByteVector("server expansion".getBytes) ++ hex"0000" ++ serverChallenge ++
|
|
hex"00000000" ++ clientChallenge ++ hex"00000000"
|
|
|
|
/*println("DecExpansion: " + decExpansion)
|
|
println("EncExpansion: " + encExpansion)*/
|
|
|
|
// expand the encryption and decryption keys
|
|
// The first 20 bytes are for RC5, and the next 16 are for the MAC'ing keys
|
|
val expandedDecKey = CryptoInterface.MD5MAC(masterSecret,
|
|
decExpansion,
|
|
0x40) // this is what is visible in IDA
|
|
|
|
val expandedEncKey = CryptoInterface.MD5MAC(masterSecret,
|
|
encExpansion,
|
|
0x40)
|
|
|
|
val decKey = expandedDecKey.take(20)
|
|
val encKey = expandedEncKey.take(20)
|
|
val decMACKey = expandedDecKey.drop(20).take(16)
|
|
val encMACKey = expandedEncKey.drop(20).take(16)
|
|
|
|
/*println("**** DecKey: " + decKey)
|
|
println("**** EncKey: " + encKey)
|
|
println("**** DecMacKey: " + decMACKey)
|
|
println("**** EncMacKey: " + encMACKey)*/
|
|
|
|
// spin up our encryption program
|
|
cryptoState = Some(new CryptoStateWithMAC(decKey, encKey, decMACKey, encMACKey))
|
|
|
|
val packet = PacketCoding.CreateCryptoPacket(seq,
|
|
ServerFinished(serverChallengeResult))
|
|
|
|
sendResponse(packet)
|
|
|
|
context.become(Established)
|
|
case default => failWithError(s"Unexpected packet type $default in state CryptoSetupFinished")
|
|
}
|
|
}
|
|
case default => failWithError(s"Invalid message '$default' received in state CryptoSetupFinished")
|
|
}
|
|
|
|
def Established : Receive = {
|
|
case RawPacket(msg) =>
|
|
PacketCoding.UnmarshalPacket(msg) match {
|
|
case Successful(p) =>
|
|
p match {
|
|
case encPacket @ EncryptedPacket(seq, _) =>
|
|
//println("Decrypting packet..." + encPacket)
|
|
PacketCoding.decryptPacket(cryptoState.get, encPacket) match {
|
|
case Successful(packet) =>
|
|
//println("RECV[E]: " + packet)
|
|
|
|
self ! packet
|
|
case Failure(e) =>
|
|
log.error("Failed to decode encrypted packet: " + e)
|
|
}
|
|
case default => failWithError(s"Unexpected packet type $default in state Established")
|
|
|
|
}
|
|
case Failure(e) => log.error("Could not decode raw packet: " + e)
|
|
}
|
|
case ctrl @ ControlPacket(_, _) =>
|
|
val from = sender()
|
|
|
|
handleEstablishedPacket(from, ctrl)
|
|
case game @ GamePacket(_, _, _) =>
|
|
val from = sender()
|
|
|
|
handleEstablishedPacket(from, game)
|
|
case default => failWithError(s"Invalid message '$default' received in state Established")
|
|
}
|
|
|
|
def failWithError(error : String) = {
|
|
log.error(error)
|
|
}
|
|
|
|
def resetState() : Unit = {
|
|
context.become(receive)
|
|
|
|
// reset the crypto primitives
|
|
cryptoDHState.close
|
|
cryptoDHState = new CryptoInterface.CryptoDHState()
|
|
|
|
if(cryptoState.isDefined) {
|
|
cryptoState.get.close
|
|
cryptoState = None
|
|
}
|
|
|
|
serverChallenge = ByteVector.empty
|
|
serverChallengeResult = ByteVector.empty
|
|
serverMACBuffer = ByteVector.empty
|
|
clientPublicKey = ByteVector.empty
|
|
clientChallenge = ByteVector.empty
|
|
clientChallengeResult = ByteVector.empty
|
|
}
|
|
|
|
def handleEstablishedPacket(from : ActorRef, cont : PlanetSidePacketContainer) = {
|
|
// we are processing a packet we decrypted
|
|
if(from == self) {
|
|
rightRef ! cont
|
|
} else if(from == rightRef) { // processing a completed packet from the right. encrypt
|
|
val packet = PacketCoding.encryptPacket(cryptoState.get, cont).require
|
|
sendResponse(packet)
|
|
} else {
|
|
log.error("Invalid sender")
|
|
}
|
|
}
|
|
|
|
def sendResponse(cont : PlanetSidePacketContainer) : ByteVector = {
|
|
//println("CRYPTO SEND: " + cont)
|
|
val pkt = PacketCoding.MarshalPacket(cont).require
|
|
val bytes = pkt.toByteVector
|
|
|
|
leftRef ! ResponsePacket(bytes)
|
|
|
|
bytes
|
|
}
|
|
|
|
def getRandBytes(amount : Int) : ByteVector = {
|
|
val array = Array.ofDim[Byte](amount)
|
|
random.nextBytes(array)
|
|
ByteVector.view(array)
|
|
}
|
|
}
|