mirror of
https://github.com/PretendoNetwork/nex-go.git
synced 2025-04-02 11:02:14 -04:00
prudp: rework Kerberos ticket generation to use user accounts properly
This commit is contained in:
parent
00d12db583
commit
64b26b3eed
12 changed files with 126 additions and 81 deletions
27
account.go
Normal file
27
account.go
Normal 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,
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
21
test/auth.go
21
test/auth.go
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
47
test/main.go
47
test/main.go
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue