PSF-BotServer/pslogin/src/main/scala/CryptoSessionActor.scala
Chord ff4ea792ce Organize packets in to their own packages
Also remove legacy CryptoStateManager
2016-05-03 20:11:45 -04:00

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)
}
}