PSF-BotServer/pslogin/src/main/scala/LoginSessionActor.scala
2016-03-04 15:14:50 -05:00

262 lines
9.6 KiB
Scala

// Copyright (c) 2016 PSForever.net to present
import akka.actor.{Actor, ActorLogging}
import psforever.crypto.CryptoInterface.{CryptoStateWithMAC, CryptoState}
import psforever.crypto.CryptoInterface
import psforever.net._
import scodec.Attempt.{Successful, Failure}
import scodec.bits._
import scodec.{Err, Attempt, Codec}
import scodec.codecs.{uint16L, uint8L, bytes}
import java.security.SecureRandom
/**
* Actor that stores crypto state for a connection and filters away any packet metadata.
* Also decrypts and handles packet retries using the sequence numbers.
* @param session Per session state
*/
class LoginSessionActor(session : LoginSession) extends Actor with ActorLogging {
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 = NewClient
def NewClient : Receive = {
case RawPacket(msg) =>
// PacketCoding.DecodePacket
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("Unexpected packet type " + p)
}
}
case default => log.error(s"Invalid message received ${default}")
}
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("Unexpected packet type " + p)
}
}
case default => log.error(s"Invalid message received ${default}")
}
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("Unexpected packet type " + default)
}
}
case default => failWithError(s"Invalid message received ${default}")
}
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) =>
println("Failed to decode encrypted packet: " + e)
}
case default => failWithError("Unexpected packet type " + default)
}
case Failure(e) => println("Could not decode raw packet: " + e)
}
case ctrl @ ControlPacket(_, pkt) => pkt match {
case SlottedMetaPacket(innerPacket) =>
PacketCoding.DecodePacket(innerPacket) match {
case Successful(p) =>
println("RECV[INNER]: " + p)
val packet = PacketCoding.encryptPacket(cryptoState.get, PacketCoding.CreateGamePacket(3,
LoginRespMessage("AAAABBBBCCCCDDDD",
hex"00000000 18FABE0C 00000000 00000000",
0, 1, 2, 685276011,
"AAAAAAAA", 0, false
))).require
sendResponse(packet)
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ",
Vector(
WorldInformation("gemini", WorldStatus.Up, ServerType.Released, Vector(), EmpireNeed.NC)
))
sendResponse(PacketCoding.encryptPacket(cryptoState.get, PacketCoding.CreateGamePacket(4,
msg
)).require)
case Failure(e) => println("Failed to decode inner packet " + e)
}
}
case default => failWithError(s"Invalid message received ${default}")
}
def failWithError(error : String) = {
log.error(error)
sendResponse(PacketCoding.CreateControlPacket(ConnectionClose()))
}
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 sendResponse(cont : PlanetSidePacketContainer) : ByteVector = {
println("SEND: " + cont)
val pkt = PacketCoding.MarshalPacket(cont).require
session.send(pkt)
pkt.toByteVector
}
def getRandBytes(amount : Int) : ByteVector = {
val array = Array.ofDim[Byte](amount)
random.nextBytes(array)
ByteVector.view(array)
}
}