mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-04-29 15:55:23 +00:00
New session pipeline and able to see server selection
This commit is contained in:
parent
0d986bcb29
commit
53488613d6
5 changed files with 411 additions and 253 deletions
302
pslogin/src/main/scala/CryptoSessionActor.scala
Normal file
302
pslogin/src/main/scala/CryptoSessionActor.scala
Normal file
|
|
@ -0,0 +1,302 @@
|
||||||
|
// Copyright (c) 2016 PSForever.net to present
|
||||||
|
import java.net.{InetAddress, InetSocketAddress}
|
||||||
|
|
||||||
|
import akka.actor.{ActorRef, Identify, 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.
|
||||||
|
*/
|
||||||
|
class CryptoSessionActor extends Actor with ActorLogging {
|
||||||
|
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) =>
|
||||||
|
leftRef = sender()
|
||||||
|
rightRef = right.asInstanceOf[ActorRef]
|
||||||
|
|
||||||
|
// who ever we send to has to send something back to us
|
||||||
|
rightRef ! HelloFriend(self)
|
||||||
|
|
||||||
|
context.become(NewClient)
|
||||||
|
case _ =>
|
||||||
|
log.error("Unknown message")
|
||||||
|
context.stop(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(_, _) =>
|
||||||
|
val from = sender()
|
||||||
|
|
||||||
|
handleEstablishedPacket(from, ctrl)
|
||||||
|
case game @ GamePacket(_, _, _) =>
|
||||||
|
val from = sender()
|
||||||
|
|
||||||
|
handleEstablishedPacket(from, game)
|
||||||
|
/* 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("PSForever", WorldStatus.Up, ServerType.Development,
|
||||||
|
Vector(WorldConnectionInfo(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 51001))), EmpireNeed.TR)
|
||||||
|
))
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
// Copyright (c) 2016 PSForever.net to present
|
|
||||||
import java.net.InetSocketAddress
|
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
|
||||||
import scodec.bits.{BitVector, ByteVector}
|
|
||||||
|
|
||||||
class LoginSession(id : Long, socket : ActorRef, address : InetSocketAddress) {
|
|
||||||
|
|
||||||
def send(msg : BitVector) = {
|
|
||||||
socket ! SendPacket(msg.toByteVector, address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,225 +1,65 @@
|
||||||
// Copyright (c) 2016 PSForever.net to present
|
// Copyright (c) 2016 PSForever.net to present
|
||||||
import akka.actor.{Actor, ActorLogging}
|
import java.net.{InetAddress, InetSocketAddress}
|
||||||
import psforever.crypto.CryptoInterface.{CryptoStateWithMAC, CryptoState}
|
|
||||||
import psforever.crypto.CryptoInterface
|
import akka.actor.{ActorRef, Identify, Actor, ActorLogging}
|
||||||
import psforever.net._
|
import psforever.net._
|
||||||
import scodec.Attempt.{Successful, Failure}
|
import scodec.Attempt.{Failure, Successful}
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
import scodec.{Err, Attempt, Codec}
|
|
||||||
import scodec.codecs.{uint16L, uint8L, bytes}
|
|
||||||
import java.security.SecureRandom
|
|
||||||
|
|
||||||
/**
|
class LoginSessionActor extends Actor with ActorLogging {
|
||||||
* Actor that stores crypto state for a connection and filters away any packet metadata.
|
var leftRef : ActorRef = ActorRef.noSender
|
||||||
* Also decrypts and handles packet retries using the sequence numbers.
|
var rightRef : ActorRef = ActorRef.noSender
|
||||||
* @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
|
def receive = Initializing
|
||||||
var serverChallenge = ByteVector.empty
|
|
||||||
var serverChallengeResult = ByteVector.empty
|
|
||||||
var serverMACBuffer = ByteVector.empty
|
|
||||||
|
|
||||||
var clientPublicKey = ByteVector.empty
|
def Initializing : Receive = {
|
||||||
var clientChallenge = ByteVector.empty
|
case HelloFriend(right) =>
|
||||||
var clientChallengeResult = ByteVector.empty
|
leftRef = sender()
|
||||||
|
rightRef = right.asInstanceOf[ActorRef]
|
||||||
|
|
||||||
def receive = NewClient
|
context.become(Started)
|
||||||
|
case _ =>
|
||||||
def NewClient : Receive = {
|
log.error("Unknown message")
|
||||||
case RawPacket(msg) =>
|
context.stop(self)
|
||||||
// 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 = {
|
def Started : Receive = {
|
||||||
case RawPacket(msg) =>
|
case ctrl @ ControlPacket(opcode, pkt) =>
|
||||||
PacketCoding.UnmarshalPacket(msg, CryptoPacketOpcode.ClientChallengeXchg) match {
|
handleControlPkt(pkt)
|
||||||
case Failure(e) => log.error("Could not decode packet: " + e)
|
case game @ GamePacket(opcode, seq, pkt) =>
|
||||||
case Successful(p) =>
|
handleGamePkt(pkt)
|
||||||
println("RECV: " + p)
|
case default => failWithError(s"Invalid message received $default")
|
||||||
|
|
||||||
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 = {
|
def handleControlPkt(pkt : PlanetSideControlPacket) = {
|
||||||
case RawPacket(msg) =>
|
pkt match {
|
||||||
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) =>
|
case SlottedMetaPacket(innerPacket) =>
|
||||||
PacketCoding.DecodePacket(innerPacket) match {
|
PacketCoding.DecodePacket(innerPacket) match {
|
||||||
case Successful(p) =>
|
case Successful(p) =>
|
||||||
println("RECV[INNER]: " + p)
|
println("RECV[INNER]: " + p)
|
||||||
|
|
||||||
val packet = PacketCoding.encryptPacket(cryptoState.get, PacketCoding.CreateGamePacket(3,
|
val packet = LoginRespMessage("AAAABBBBCCCCDDDD",
|
||||||
LoginRespMessage("AAAABBBBCCCCDDDD",
|
hex"00000000 18FABE0C 00000000 00000000",
|
||||||
hex"00000000 18FABE0C 00000000 00000000",
|
0, 1, 2, 685276011,
|
||||||
0, 1, 2, 685276011,
|
"AAAAAAAA", 0, false
|
||||||
"AAAAAAAA", 0, false
|
)
|
||||||
))).require
|
|
||||||
|
|
||||||
sendResponse(packet)
|
sendResponse(PacketCoding.CreateGamePacket(0, packet))
|
||||||
|
|
||||||
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ",
|
val msg = VNLWorldStatusMessage("Welcome to PlanetSide! ",
|
||||||
Vector(
|
Vector(
|
||||||
WorldInformation("gemini", WorldStatus.Up, ServerType.Released, Vector(), EmpireNeed.NC)
|
WorldInformation("PSForever", WorldStatus.Up, ServerType.Development,
|
||||||
|
Vector(WorldConnectionInfo(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 51001))), EmpireNeed.TR)
|
||||||
))
|
))
|
||||||
|
|
||||||
sendResponse(PacketCoding.encryptPacket(cryptoState.get, PacketCoding.CreateGamePacket(4,
|
sendResponse(PacketCoding.CreateGamePacket(0, msg))
|
||||||
msg
|
|
||||||
)).require)
|
|
||||||
case Failure(e) => println("Failed to decode inner packet " + e)
|
case Failure(e) => println("Failed to decode inner packet " + e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case default => failWithError(s"Invalid message received ${default}")
|
}
|
||||||
|
|
||||||
|
def handleGamePkt(pkt : PlanetSideGamePacket) = {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def failWithError(error : String) = {
|
def failWithError(error : String) = {
|
||||||
|
|
@ -227,36 +67,8 @@ class LoginSessionActor(session : LoginSession) extends Actor with ActorLogging
|
||||||
sendResponse(PacketCoding.CreateControlPacket(ConnectionClose()))
|
sendResponse(PacketCoding.CreateControlPacket(ConnectionClose()))
|
||||||
}
|
}
|
||||||
|
|
||||||
def resetState() : Unit = {
|
def sendResponse(cont : PlanetSidePacketContainer) = {
|
||||||
context.become(receive)
|
log.info("LOGIN SEND: " + cont)
|
||||||
|
rightRef ! cont
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,88 @@
|
||||||
// Copyright (c) 2016 PSForever.net to present
|
// Copyright (c) 2016 PSForever.net to present
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
|
||||||
import akka.actor.{Props, ActorRef, ActorLogging, Actor}
|
import akka.actor._
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
final case class RawPacket(data : ByteVector)
|
final case class RawPacket(data : ByteVector)
|
||||||
|
final case class ResponsePacket(data : ByteVector)
|
||||||
|
|
||||||
|
case class SessionState(id : Long, address : InetSocketAddress, pipeline : List[ActorRef]) {
|
||||||
|
def inject(pkt : RawPacket) = pipeline.head ! pkt
|
||||||
|
}
|
||||||
|
|
||||||
class SessionRouter extends Actor with ActorLogging {
|
class SessionRouter extends Actor with ActorLogging {
|
||||||
val sessions = mutable.Map[InetSocketAddress, ActorRef]()
|
val idBySocket = mutable.Map[InetSocketAddress, Long]()
|
||||||
var sessionId = 0L
|
val sessionById = mutable.Map[Long, SessionState]()
|
||||||
|
val sessionByActor = mutable.Map[ActorRef, SessionState]()
|
||||||
|
|
||||||
def receive = {
|
var sessionId = 0L // this is a connection session, not an actual logged in session ID
|
||||||
|
var inputRef : ActorRef = ActorRef.noSender
|
||||||
|
|
||||||
|
/*
|
||||||
|
Login sessions are divided between two actors. the crypto session actor transparently handles all of the cryptographic
|
||||||
|
setup of the connection. Once a correct crypto session has been established, all packets, after being decrypted
|
||||||
|
will be passed on to the login session actor. This actor has important state that is used to maintain the login
|
||||||
|
session.
|
||||||
|
|
||||||
|
> PlanetSide Session Pipeline <
|
||||||
|
|
||||||
|
read() route decrypt
|
||||||
|
UDP Socket -----> [Session Router] -----> [Crypto Actor] -----> [Session Actor]
|
||||||
|
^ | ^ | ^ |
|
||||||
|
| write() | | encrypt | | response |
|
||||||
|
+--------------+ +-----------+ +-----------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
def receive = initializing
|
||||||
|
|
||||||
|
def initializing : Receive = {
|
||||||
|
case Hello() =>
|
||||||
|
inputRef = sender()
|
||||||
|
context.become(started)
|
||||||
|
case _ =>
|
||||||
|
log.error("Unknown message")
|
||||||
|
context.stop(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
def started : Receive = {
|
||||||
case ReceivedPacket(msg, from) =>
|
case ReceivedPacket(msg, from) =>
|
||||||
if(sessions.contains(from)) {
|
if(idBySocket.contains(from)) {
|
||||||
sessions{from} ! RawPacket(msg)
|
sessionById{idBySocket{from}}.inject(RawPacket(msg))
|
||||||
} else {
|
} else {
|
||||||
log.info("New session from " + from.toString)
|
log.info("New session from " + from.toString)
|
||||||
|
|
||||||
val id = newSessionId
|
val session = createNewSession(from)
|
||||||
val loginSession = new LoginSession(id, sender(), from)
|
idBySocket{from} = session.id
|
||||||
val ref = context.actorOf(Props(new LoginSessionActor(loginSession)),
|
|
||||||
"login-session" + id.toString)
|
|
||||||
|
|
||||||
sessions{from} = ref
|
sessionById{session.id} = session
|
||||||
|
sessionByActor{session.pipeline.head} = session
|
||||||
|
|
||||||
ref ! RawPacket(msg)
|
sessionById{session.id}.inject(RawPacket(msg))
|
||||||
}
|
}
|
||||||
|
case ResponsePacket(msg) =>
|
||||||
|
val session = sessionByActor{sender()}
|
||||||
|
|
||||||
|
inputRef ! SendPacket(msg, session.address)
|
||||||
case _ => log.error("Unknown message")
|
case _ => log.error("Unknown message")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def createNewSession(address : InetSocketAddress) = {
|
||||||
|
val id = newSessionId
|
||||||
|
|
||||||
|
val cryptoSession = context.actorOf(Props[CryptoSessionActor],
|
||||||
|
"crypto-session" + id.toString)
|
||||||
|
val loginSession = context.actorOf(Props[LoginSessionActor],
|
||||||
|
"login-session" + id.toString)
|
||||||
|
|
||||||
|
// start the pipeline setup
|
||||||
|
cryptoSession ! HelloFriend(loginSession)
|
||||||
|
|
||||||
|
SessionState(id, address, List(cryptoSession, loginSession))
|
||||||
|
}
|
||||||
|
|
||||||
def newSessionId = {
|
def newSessionId = {
|
||||||
val oldId = sessionId
|
val oldId = sessionId
|
||||||
sessionId += 1
|
sessionId += 1
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
// Copyright (c) 2016 PSForever.net to present
|
// Copyright (c) 2016 PSForever.net to present
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
|
||||||
import akka.actor.{ActorLogging, Actor, ActorRef}
|
import akka.actor.{Identify, ActorLogging, Actor, ActorRef}
|
||||||
import akka.io._
|
import akka.io._
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
import scodec.interop.akka._
|
import scodec.interop.akka._
|
||||||
|
|
||||||
final case class ReceivedPacket(msg : ByteVector, from : InetSocketAddress)
|
final case class ReceivedPacket(msg : ByteVector, from : InetSocketAddress)
|
||||||
final case class SendPacket(msg : ByteVector, to : InetSocketAddress)
|
final case class SendPacket(msg : ByteVector, to : InetSocketAddress)
|
||||||
|
final case class Hello()
|
||||||
|
final case class HelloFriend(next: ActorRef)
|
||||||
|
|
||||||
class UdpListener(nextActor: ActorRef) extends Actor with ActorLogging {
|
class UdpListener(nextActor: ActorRef) extends Actor with ActorLogging {
|
||||||
import context.system
|
import context.system
|
||||||
|
|
@ -19,6 +21,8 @@ class UdpListener(nextActor: ActorRef) extends Actor with ActorLogging {
|
||||||
def receive = {
|
def receive = {
|
||||||
case Udp.Bound(local) =>
|
case Udp.Bound(local) =>
|
||||||
println("UDP bound: " + local)
|
println("UDP bound: " + local)
|
||||||
|
|
||||||
|
nextActor ! Hello()
|
||||||
context.become(ready(sender()))
|
context.become(ready(sender()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue