commit 07f4e2473dee1de6cd58164f158e0753b48ebd85 Author: Jonathan Barrow Date: Sun Oct 24 12:38:23 2021 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..800816f --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# custom +build +.env \ No newline at end of file diff --git a/database.go b/database.go new file mode 100644 index 0000000..abae4c2 --- /dev/null +++ b/database.go @@ -0,0 +1,41 @@ +package main + +import ( + "context" + "os" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +var mongoClient *mongo.Client +var mongoContext context.Context +var mongoDatabase *mongo.Database +var mongoCollection *mongo.Collection + +func connectMongo() { + mongoClient, _ = mongo.NewClient(options.Client().ApplyURI(os.Getenv("MONGO_URI"))) + mongoContext, _ = context.WithTimeout(context.Background(), 10*time.Second) + _ = mongoClient.Connect(mongoContext) + + mongoDatabase = mongoClient.Database("pretendo") + mongoCollection = mongoDatabase.Collection("nexaccounts") +} + +func getNEXAccountByPID(pid uint32) bson.M { + var result bson.M + + err := mongoCollection.FindOne(context.TODO(), bson.D{{Key: "pid", Value: pid}}, options.FindOne()).Decode(&result) + + if err != nil { + if err == mongo.ErrNoDocuments { + return nil + } + + panic(err) + } + + return result +} diff --git a/init.go b/init.go new file mode 100644 index 0000000..962daff --- /dev/null +++ b/init.go @@ -0,0 +1,22 @@ +package main + +import ( + "log" + + "github.com/joho/godotenv" +) + +type Config struct { + Mongo struct { + } + Cassandra struct{} +} + +func init() { + err := godotenv.Load() + if err != nil { + log.Fatal("Error loading .env file") + } + + connectMongo() +} diff --git a/kerberos.go b/kerberos.go new file mode 100644 index 0000000..0e911b4 --- /dev/null +++ b/kerberos.go @@ -0,0 +1,79 @@ +package main + +import ( + nex "github.com/PretendoNetwork/nex-go" +) + +func generateKerberosTicket(userPID uint32, serverPID uint32, keySize int) ([]byte, int) { + user := getNEXAccountByPID(userPID) + if user == nil { + return []byte{}, 0x80030064 // RendezVous::InvalidUsername + } + + userPassword := user["password"].(string) + serverPassword := "password" + + // Create session key and ticket keys + sessionKey := make([]byte, keySize) + + ticketInfoKey := make([]byte, 16) // key for encrypting the internal ticket info. Only used by server. TODO: Make this random! + userKey := deriveKey(userPID, []byte(userPassword)) // Key for encrypting entire ticket. Used by client and server + serverKey := deriveKey(serverPID, []byte(serverPassword)) + finalKey := nex.MD5Hash(append(serverKey, ticketInfoKey...)) + + //rand.Read(sessionKey) // Create a random session key + + //fmt.Println("Using Session Key: " + hex.EncodeToString(sessionKey)) + + //////////////////////////////// + // Build internal ticket info // + //////////////////////////////// + + expiration := nex.NewDateTime(0) + ticketInfoStream := nex.NewStreamOut(nexServer) + + ticketInfoStream.WriteUInt64LE(expiration.Now()) + ticketInfoStream.WriteUInt32LE(userPID) + ticketInfoStream.Grow(int64(keySize)) + ticketInfoStream.WriteBytesNext(sessionKey) + + // Encrypt internal ticket info + + ticketInfoEncryption := nex.NewKerberosEncryption(nex.MD5Hash(finalKey)) + encryptedTicketInfo := ticketInfoEncryption.Encrypt(ticketInfoStream.Bytes()) + + /////////////////////////////////// + // Build ticket data New Version // + /////////////////////////////////// + + ticketDataStream := nex.NewStreamOut(nexServer) + + ticketDataStream.WriteBuffer(ticketInfoKey) + ticketDataStream.WriteBuffer(encryptedTicketInfo) + + /////////////////////////// + // Build Kerberos Ticket // + /////////////////////////// + + ticketStream := nex.NewStreamOut(nexServer) + + // Write session key + ticketStream.Grow(int64(keySize)) + ticketStream.WriteBytesNext(sessionKey) + ticketStream.WriteUInt32LE(serverPID) + ticketStream.WriteBuffer(ticketDataStream.Bytes()) + + // Encrypt the ticket + ticketEncryption := nex.NewKerberosEncryption(userKey) + encryptedTicket := ticketEncryption.Encrypt(ticketStream.Bytes()) + + return encryptedTicket, 0 +} + +func deriveKey(pid uint32, password []byte) []byte { + for i := 0; i < 65000+int(pid)%1024; i++ { + password = nex.MD5Hash(password) + } + + return password +} diff --git a/login_ex.go b/login_ex.go new file mode 100644 index 0000000..c279adc --- /dev/null +++ b/login_ex.go @@ -0,0 +1,69 @@ +package main + +import ( + "fmt" + "strconv" + + nex "github.com/PretendoNetwork/nex-go" + nexproto "github.com/PretendoNetwork/nex-protocols-go" +) + +func loginEx(err error, client *nex.Client, callID uint32, username string, authenticationInfo *nexproto.AuthenticationInfo) { + // TODO: Verify auth info + + if err != nil { + fmt.Println(err) + return + } + + userPID, _ := strconv.Atoi(username) + + serverPID := 1 // Quazal Rendez-Vous + + encryptedTicket, errorCode := generateKerberosTicket(uint32(userPID), uint32(serverPID), nexServer.KerberosKeySize()) + + if errorCode != 0 { + fmt.Println(errorCode) + return + } + + // Build the response body + stationURL := "prudps:/address=66.177.0.8;port=60005;CID=1;PID=2;sid=1;stream=10;type=2" + serverName := "Pretendo WiiU Chat" + + rvConnectionData := nex.NewRVConnectionData() + rvConnectionData.SetStationURL(stationURL) + rvConnectionData.SetSpecialProtocols([]byte{}) + rvConnectionData.SetStationURLSpecialProtocols("") + serverTime := nex.NewDateTime(0) + rvConnectionData.SetTime(serverTime.Now()) + + rmcResponseStream := nex.NewStreamOut(nexServer) + + rmcResponseStream.WriteUInt32LE(0x10001) // success + rmcResponseStream.WriteUInt32LE(uint32(userPID)) + rmcResponseStream.WriteBuffer(encryptedTicket) + rmcResponseStream.WriteStructure(rvConnectionData) + rmcResponseStream.WriteString(serverName) + + rmcResponseBody := rmcResponseStream.Bytes() + + // Build response packet + rmcResponse := nex.NewRMCResponse(nexproto.AuthenticationProtocolID, callID) + rmcResponse.SetSuccess(nexproto.AuthenticationMethodLoginEx, rmcResponseBody) + + rmcResponseBytes := rmcResponse.Bytes() + + responsePacket, _ := nex.NewPacketV1(client, nil) + + responsePacket.SetVersion(1) + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + nexServer.Send(responsePacket) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..4b9702a --- /dev/null +++ b/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + + nex "github.com/PretendoNetwork/nex-go" + nexproto "github.com/PretendoNetwork/nex-protocols-go" +) + +var nexServer *nex.Server + +func main() { + nexServer = nex.NewServer() + nexServer.SetPrudpVersion(1) + nexServer.SetNexVersion(2) + nexServer.SetKerberosKeySize(32) + nexServer.SetAccessKey("e7a47214") + + nexServer.On("Data", func(packet *nex.PacketV1) { + request := packet.RMCRequest() + + fmt.Println("==WiiU Chat - Auth==") + fmt.Printf("Protocol ID: %#v\n", request.ProtocolID()) + fmt.Printf("Method ID: %#v\n", request.MethodID()) + fmt.Println("====================") + }) + + authenticationServer := nexproto.NewAuthenticationProtocol(nexServer) + + authenticationServer.LoginEx(loginEx) + authenticationServer.RequestTicket(requestTicket) + + nexServer.Listen(":60004") +} diff --git a/request_ticket.go b/request_ticket.go new file mode 100644 index 0000000..887646e --- /dev/null +++ b/request_ticket.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + + nex "github.com/PretendoNetwork/nex-go" + nexproto "github.com/PretendoNetwork/nex-protocols-go" +) + +func requestTicket(err error, client *nex.Client, callID uint32, userPID uint32, serverPID uint32) { + if err != nil { + fmt.Println(err) + return + } + + encryptedTicket, errorCode := generateKerberosTicket(userPID, serverPID, nexServer.KerberosKeySize()) + + if errorCode != 0 { + fmt.Println(errorCode) + return + } + + // Build the response body + rmcResponseStream := nex.NewStreamOut(nexServer) + + rmcResponseStream.WriteUInt32LE(0x10001) // success + rmcResponseStream.WriteBuffer(encryptedTicket) + + rmcResponseBody := rmcResponseStream.Bytes() + + // Build response packet + rmcResponse := nex.NewRMCResponse(nexproto.AuthenticationProtocolID, callID) + rmcResponse.SetSuccess(nexproto.AuthenticationMethodRequestTicket, rmcResponseBody) + + rmcResponseBytes := rmcResponse.Bytes() + + responsePacket, _ := nex.NewPacketV1(client, nil) + + responsePacket.SetVersion(1) + responsePacket.SetSource(0xA1) + responsePacket.SetDestination(0xAF) + responsePacket.SetType(nex.DataPacket) + responsePacket.SetPayload(rmcResponseBytes) + + responsePacket.AddFlag(nex.FlagNeedsAck) + responsePacket.AddFlag(nex.FlagReliable) + + nexServer.Send(responsePacket) +}