prudp: rework Kerberos ticket generation to use user accounts properly

This commit is contained in:
Jonathan Barrow 2024-01-24 12:42:11 -05:00
parent 00d12db583
commit 64b26b3eed
No known key found for this signature in database
GPG key ID: E86E9FE9049C741F
12 changed files with 126 additions and 81 deletions

27
account.go Normal file
View file

@ -0,0 +1,27 @@
package nex
import "github.com/PretendoNetwork/nex-go/types"
// Account represents a game server account.
//
// Game server accounts are separate from other accounts, like Uplay, Nintendo Accounts and NNIDs.
// These exist only on the game server. Account passwords are used as part of the servers Kerberos
// authentication. There are also a collection of non-user, special, accounts. These include a
// guest account, an account which represents the authentication server, and one which represents
// the secure server. See https://nintendo-wiki.pretendo.network/docs/nex/kerberos for more information.
type Account struct {
PID *types.PID // * The PID of the account. PIDs are unique IDs per account. NEX PIDs start at 1800000000 and decrement with each new account.
Username string // * The username for the account. For NEX user accounts this is the same as the accounts PID.
Password string // * The password for the account. For NEX accounts this is always 16 characters long using seemingly any ASCII character
}
// NewAccount returns a new instance of Account.
// This does not register an account, only creates a new
// struct instance.
func NewAccount(pid *types.PID, username, password string) *Account {
return &Account{
PID: pid,
Username: username,
Password: password,
}
}

View file

@ -91,13 +91,14 @@ func (p *HPPPacket) validatePasswordSignature(signature string) error {
}
func (p *HPPPacket) calculatePasswordSignature() ([]byte, error) {
pid := p.Sender().PID()
password, _ := p.Sender().Server().PasswordFromPID(pid)
if password == "" {
sender := p.Sender()
pid := sender.PID()
account, _ := sender.Server().(*HPPServer).AccountDetailsByPID(pid)
if account == nil {
return nil, errors.New("PID does not exist")
}
key := DeriveKerberosKey(pid, []byte(password))
key := DeriveKerberosKey(pid, []byte(account.Password))
signature, err := p.calculateSignature(p.payload, key)
if err != nil {

View file

@ -23,8 +23,9 @@ type HPPServer struct {
utilityProtocolVersion *LibraryVersion
natTraversalProtocolVersion *LibraryVersion
dataHandlers []func(packet PacketInterface)
passwordFromPIDHandler func(pid *types.PID) (string, uint32)
byteStreamSettings *ByteStreamSettings
AccountDetailsByPID func(pid *types.PID) (*Account, uint32)
AccountDetailsByUsername func(username string) (*Account, uint32)
}
// OnData adds an event handler which is fired when a new HPP request is received
@ -258,21 +259,6 @@ func (s *HPPServer) NATTraversalProtocolVersion() *LibraryVersion {
return s.natTraversalProtocolVersion
}
// PasswordFromPID calls the function set with SetPasswordFromPIDFunction and returns the result
func (s *HPPServer) PasswordFromPID(pid *types.PID) (string, uint32) {
if s.passwordFromPIDHandler == nil {
logger.Errorf("Missing PasswordFromPID handler. Set with SetPasswordFromPIDFunction")
return "", Errors.Core.NotImplemented
}
return s.passwordFromPIDHandler(pid)
}
// SetPasswordFromPIDFunction sets the function for HPP to get a NEX password using the PID
func (s *HPPServer) SetPasswordFromPIDFunction(handler func(pid *types.PID) (string, uint32)) {
s.passwordFromPIDHandler = handler
}
// ByteStreamSettings returns the settings to be used for ByteStreams
func (s *HPPServer) ByteStreamSettings() *ByteStreamSettings {
return s.byteStreamSettings

View file

@ -178,7 +178,7 @@ func (ti *KerberosTicketInternalData) Decrypt(stream *ByteStreamIn, key []byte)
ti.Issued = timestamp
ti.SourcePID = userPID
ti.SessionKey = stream.ReadBytesNext(int64(stream.Server.(*PRUDPServer).kerberosKeySize))
ti.SessionKey = stream.ReadBytesNext(int64(stream.Server.(*PRUDPServer).SessionKeyLength))
return nil
}

View file

@ -21,7 +21,9 @@ type PRUDPEndPoint struct {
packetEventHandlers map[string][]func(packet PacketInterface)
connectionEndedEventHandlers []func(connection *PRUDPConnection)
ConnectionIDCounter *Counter[uint32]
IsSecureEndpoint bool // TODO - Remove this? Assume if CONNECT packet has a body, it's the secure server?
ServerAccount *Account
AccountDetailsByPID func(pid *types.PID) (*Account, uint32)
AccountDetailsByUsername func(username string) (*Account, uint32)
}
// OnData adds an event handler which is fired when a new DATA packet is received
@ -285,7 +287,7 @@ func (pep *PRUDPEndPoint) handleConnect(packet PRUDPPacketInterface) {
payload := make([]byte, 0)
if pep.IsSecureEndpoint {
if len(packet.Payload()) != 0 {
sessionKey, pid, checkValue, err := pep.readKerberosTicket(packet.Payload())
if err != nil {
logger.Error(err.Error())
@ -358,7 +360,17 @@ func (pep *PRUDPEndPoint) readKerberosTicket(payload []byte) ([]byte, *types.PID
return nil, nil, 0, err
}
serverKey := DeriveKerberosKey(types.NewPID(2), pep.Server.kerberosPassword)
// * Sanity checks
serverAccount, _ := pep.AccountDetailsByUsername(pep.ServerAccount.Username)
if serverAccount == nil {
return nil, nil, 0, errors.New("Failed to find endpoint server account")
}
if serverAccount.Password != pep.ServerAccount.Password {
return nil, nil, 0, errors.New("Password for endpoint server account does not match the records from AccountDetailsByUsername")
}
serverKey := DeriveKerberosKey(serverAccount.PID, []byte(serverAccount.Password))
ticket := NewKerberosTicketInternalData()
if err := ticket.Decrypt(NewByteStreamIn(ticketData.Value, pep.Server), serverKey); err != nil {
@ -601,6 +613,5 @@ func NewPRUDPEndPoint(streamID uint8) *PRUDPEndPoint {
packetEventHandlers: make(map[string][]func(PacketInterface)),
connectionEndedEventHandlers: make([]func(connection *PRUDPConnection), 0),
ConnectionIDCounter: NewCounter[uint32](0),
IsSecureEndpoint: false,
}
}

View file

@ -8,7 +8,6 @@ import (
"runtime"
"time"
"github.com/PretendoNetwork/nex-go/types"
"github.com/lxzan/gws"
)
@ -20,9 +19,8 @@ type PRUDPServer struct {
Connections *MutexMap[string, *SocketConnection]
SupportedFunctions uint32
accessKey string
kerberosPassword []byte
kerberosTicketVersion int
kerberosKeySize int
SessionKeyLength int
FragmentSize int
version *LibraryVersion
datastoreProtocolVersion *LibraryVersion
@ -33,7 +31,6 @@ type PRUDPServer struct {
utilityProtocolVersion *LibraryVersion
natTraversalProtocolVersion *LibraryVersion
pingTimeout time.Duration
passwordFromPIDHandler func(pid *types.PID) (string, uint32)
PRUDPv1ConnectionSignatureKey []byte
byteStreamSettings *ByteStreamSettings
PRUDPV0Settings *PRUDPV0Settings
@ -318,16 +315,6 @@ func (ps *PRUDPServer) SetAccessKey(accessKey string) {
ps.accessKey = accessKey
}
// KerberosPassword returns the server kerberos password
func (ps *PRUDPServer) KerberosPassword() []byte {
return ps.kerberosPassword
}
// SetKerberosPassword sets the server kerberos password
func (ps *PRUDPServer) SetKerberosPassword(kerberosPassword []byte) {
ps.kerberosPassword = kerberosPassword
}
// SetFragmentSize sets the max size for a packets payload
func (ps *PRUDPServer) SetFragmentSize(fragmentSize int) {
// TODO - Derive this value from the MTU
@ -351,12 +338,12 @@ func (ps *PRUDPServer) SetKerberosTicketVersion(kerberosTicketVersion int) {
// KerberosKeySize gets the size for the kerberos session key
func (ps *PRUDPServer) KerberosKeySize() int {
return ps.kerberosKeySize
return ps.SessionKeyLength
}
// SetKerberosKeySize sets the size for the kerberos session key
func (ps *PRUDPServer) SetKerberosKeySize(kerberosKeySize int) {
ps.kerberosKeySize = kerberosKeySize
ps.SessionKeyLength = kerberosKeySize
}
// LibraryVersion returns the server NEX version
@ -446,21 +433,6 @@ func (ps *PRUDPServer) NATTraversalProtocolVersion() *LibraryVersion {
return ps.natTraversalProtocolVersion
}
// PasswordFromPID calls the function set with SetPasswordFromPIDFunction and returns the result
func (ps *PRUDPServer) PasswordFromPID(pid *types.PID) (string, uint32) {
if ps.passwordFromPIDHandler == nil {
logger.Errorf("Missing PasswordFromPID handler. Set with SetPasswordFromPIDFunction")
return "", Errors.Core.NotImplemented
}
return ps.passwordFromPIDHandler(pid)
}
// SetPasswordFromPIDFunction sets the function for the auth server to get a NEX password using the PID
func (ps *PRUDPServer) SetPasswordFromPIDFunction(handler func(pid *types.PID) (string, uint32)) {
ps.passwordFromPIDHandler = handler
}
// ByteStreamSettings returns the settings to be used for ByteStreams
func (ps *PRUDPServer) ByteStreamSettings() *ByteStreamSettings {
return ps.byteStreamSettings
@ -498,7 +470,7 @@ func NewPRUDPServer() *PRUDPServer {
return &PRUDPServer{
Endpoints: NewMutexMap[uint8, *PRUDPEndPoint](),
Connections: NewMutexMap[string, *SocketConnection](),
kerberosKeySize: 32,
SessionKeyLength: 32,
FragmentSize: 1300,
pingTimeout: time.Second * 15,
byteStreamSettings: NewByteStreamSettings(),

View file

@ -1,7 +1,5 @@
package nex
import "github.com/PretendoNetwork/nex-go/types"
// ServerInterface defines all the methods a server should have regardless of type
type ServerInterface interface {
AccessKey() string
@ -17,8 +15,6 @@ type ServerInterface interface {
SetDefaultLibraryVersion(version *LibraryVersion)
Send(packet PacketInterface)
OnData(handler func(packet PacketInterface))
PasswordFromPID(pid *types.PID) (string, uint32)
SetPasswordFromPIDFunction(handler func(pid *types.PID) (string, uint32))
ByteStreamSettings() *ByteStreamSettings
SetByteStreamSettings(settings *ByteStreamSettings)
}

View file

@ -3,7 +3,6 @@ package main
import (
"fmt"
"strconv"
"github.com/PretendoNetwork/nex-go"
"github.com/PretendoNetwork/nex-go/types"
@ -18,6 +17,10 @@ func startAuthenticationServer() {
endpoint := nex.NewPRUDPEndPoint(1)
endpoint.AccountDetailsByPID = accountDetailsByPID
endpoint.AccountDetailsByUsername = accountDetailsByUsername
endpoint.ServerAccount = authenticationServerAccount
endpoint.OnData(func(packet nex.PacketInterface) {
if packet, ok := packet.(nex.PRUDPPacketInterface); ok {
request := packet.RMCMessage()
@ -38,7 +41,6 @@ func startAuthenticationServer() {
authServer.SetFragmentSize(962)
authServer.SetDefaultLibraryVersion(nex.NewLibraryVersion(1, 1, 0))
authServer.SetKerberosPassword([]byte("password"))
authServer.SetKerberosKeySize(16)
authServer.SetAccessKey("ridfebb9")
authServer.BindPRUDPEndPoint(endpoint)
@ -58,14 +60,12 @@ func login(packet nex.PRUDPPacketInterface) {
panic(err)
}
converted, err := strconv.Atoi(strUserName.Value)
if err != nil {
panic(err)
}
sourceAccount, _ := accountDetailsByUsername(strUserName.Value)
targetAccount, _ := accountDetailsByUsername(secureServerAccount.Username)
retval := types.NewQResultSuccess(0x00010001)
pidPrincipal := types.NewPID(uint64(converted))
pbufResponse := types.NewBuffer(generateTicket(pidPrincipal, types.NewPID(2)))
pidPrincipal := sourceAccount.PID
pbufResponse := types.NewBuffer(generateTicket(sourceAccount, targetAccount))
pConnectionData := types.NewRVConnectionData()
strReturnMsg := types.NewString("Test Build")
@ -125,8 +125,11 @@ func requestTicket(packet nex.PRUDPPacketInterface) {
panic(err)
}
sourceAccount, _ := accountDetailsByPID(idSource)
targetAccount, _ := accountDetailsByPID(idTarget)
retval := types.NewQResultSuccess(0x00010001)
pbufResponse := types.NewBuffer(generateTicket(idSource, idTarget))
pbufResponse := types.NewBuffer(generateTicket(sourceAccount, targetAccount))
responseStream := nex.NewByteStreamOut(authServer)

View file

@ -7,9 +7,10 @@ import (
"github.com/PretendoNetwork/nex-go/types"
)
func generateTicket(userPID *types.PID, targetPID *types.PID) []byte {
userKey := nex.DeriveKerberosKey(userPID, []byte("z5sykuHnX0q5SCJN"))
targetKey := nex.DeriveKerberosKey(targetPID, []byte("password"))
// func generateTicket(userPID *types.PID, targetPID *types.PID) []byte {
func generateTicket(source *nex.Account, target *nex.Account) []byte {
sourceKey := nex.DeriveKerberosKey(source.PID, []byte(source.Password))
targetKey := nex.DeriveKerberosKey(target.PID, []byte(target.Password))
sessionKey := make([]byte, authServer.KerberosKeySize())
_, err := rand.Read(sessionKey)
@ -21,17 +22,17 @@ func generateTicket(userPID *types.PID, targetPID *types.PID) []byte {
serverTime := types.NewDateTime(0).Now()
ticketInternalData.Issued = serverTime
ticketInternalData.SourcePID = userPID
ticketInternalData.SourcePID = source.PID
ticketInternalData.SessionKey = sessionKey
encryptedTicketInternalData, _ := ticketInternalData.Encrypt(targetKey, nex.NewByteStreamOut(authServer))
ticket := nex.NewKerberosTicket()
ticket.SessionKey = sessionKey
ticket.TargetPID = targetPID
ticket.TargetPID = target.PID
ticket.InternalData = types.NewBuffer(encryptedTicketInternalData)
encryptedTicket, _ := ticket.Encrypt(userKey, nex.NewByteStreamOut(authServer))
encryptedTicket, _ := ticket.Encrypt(sourceKey, nex.NewByteStreamOut(authServer))
return encryptedTicket
}

View file

@ -79,7 +79,8 @@ func startHPPServer() {
hppServer.SetDefaultLibraryVersion(nex.NewLibraryVersion(2, 4, 1))
hppServer.SetAccessKey("76f26496")
hppServer.SetPasswordFromPIDFunction(passwordFromPID)
hppServer.AccountDetailsByPID = accountDetailsByPID
hppServer.AccountDetailsByUsername = accountDetailsByUsername
hppServer.Listen(12345)
}

View file

@ -1,10 +1,55 @@
package main
import "sync"
import (
"sync"
"github.com/PretendoNetwork/nex-go"
"github.com/PretendoNetwork/nex-go/types"
)
var wg sync.WaitGroup
var authenticationServerAccount *nex.Account
var secureServerAccount *nex.Account
var testUserAccount *nex.Account
func accountDetailsByPID(pid *types.PID) (*nex.Account, uint32) {
if pid.Equals(authenticationServerAccount.PID) {
return authenticationServerAccount, 0
}
if pid.Equals(secureServerAccount.PID) {
return secureServerAccount, 0
}
if pid.Equals(testUserAccount.PID) {
return testUserAccount, 0
}
return nil, nex.Errors.RendezVous.InvalidPID
}
func accountDetailsByUsername(username string) (*nex.Account, uint32) {
if username == authenticationServerAccount.Username {
return authenticationServerAccount, 0
}
if username == secureServerAccount.Username {
return secureServerAccount, 0
}
if username == testUserAccount.Username {
return testUserAccount, 0
}
return nil, nex.Errors.RendezVous.InvalidUsername
}
func main() {
authenticationServerAccount = nex.NewAccount(types.NewPID(1), "Quazal Authentication", "authpassword")
secureServerAccount = nex.NewAccount(types.NewPID(2), "Quazal Rendez-Vous", "securepassword")
testUserAccount = nex.NewAccount(types.NewPID(1800000000), "1800000000", "nexuserpassword")
wg.Add(3)
go startAuthenticationServer()

View file

@ -46,8 +46,11 @@ func startSecureServer() {
secureServer = nex.NewPRUDPServer()
endpoint := nex.NewPRUDPEndPoint(2)
endpoint.IsSecureEndpoint = true
endpoint := nex.NewPRUDPEndPoint(1)
endpoint.AccountDetailsByPID = accountDetailsByPID
endpoint.AccountDetailsByUsername = accountDetailsByUsername
endpoint.ServerAccount = secureServerAccount
endpoint.OnData(func(packet nex.PacketInterface) {
if packet, ok := packet.(nex.PRUDPPacketInterface); ok {
@ -77,7 +80,6 @@ func startSecureServer() {
secureServer.SetFragmentSize(962)
secureServer.SetDefaultLibraryVersion(nex.NewLibraryVersion(1, 1, 0))
secureServer.SetKerberosPassword([]byte("password"))
secureServer.SetKerberosKeySize(16)
secureServer.SetAccessKey("ridfebb9")
secureServer.BindPRUDPEndPoint(endpoint)