Database: Store and verify NG device ID

This commit is contained in:
mkwcat
2023-12-15 15:03:58 -05:00
parent 937722a871
commit dcc0a2f2db
4 changed files with 52 additions and 22 deletions

View File

@@ -2,30 +2,31 @@ package database
import (
"context"
"fmt"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/logrusorgru/aurora/v3"
"wwfc/common"
"wwfc/logging"
)
func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string, profileId uint32) (User, bool) {
func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string, profileId uint32, ngDeviceId uint32) (User, bool) {
var exists bool
err := pool.QueryRow(ctx, DoesUserExist, userId, gsbrcd).Scan(&exists)
if err != nil {
return User{}, false
}
uniqueNickname := common.Base32Encode(userId) + gsbrcd
user := User{
ProfileId: profileId,
UserId: userId,
GsbrCode: gsbrcd,
Email: uniqueNickname + "@nds",
UniqueNick: uniqueNickname,
UserId: userId,
GsbrCode: gsbrcd,
}
if !exists {
user.ProfileId = profileId
user.NgDeviceId = ngDeviceId
user.UniqueNick = common.Base32Encode(userId) + gsbrcd
user.Email = user.UniqueNick + "@nds"
// Create the GPCM account
err := user.CreateUser(pool, ctx)
if err != nil {
@@ -35,9 +36,24 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), aurora.Cyan(user.ProfileId))
} else {
err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName)
var expectedNgId *uint32
err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &expectedNgId, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName)
if err != nil {
return User{}, false
panic(err)
}
if expectedNgId != nil {
user.NgDeviceId = *expectedNgId
if ngDeviceId != 0 && user.NgDeviceId != ngDeviceId {
logging.Error("DATABASE", "NG device ID mismatch for profile", aurora.Cyan(user.ProfileId), "- expected", aurora.Cyan(fmt.Sprintf("%08x", user.NgDeviceId)), "but got", aurora.Cyan(fmt.Sprintf("%08x", ngDeviceId)))
return User{}, false
}
} else if ngDeviceId != 0 {
user.NgDeviceId = ngDeviceId
_, err := pool.Exec(ctx, UpdateUserNGDeviceID, user.ProfileId, ngDeviceId)
if err != nil {
panic(err)
}
}
if profileId != 0 && user.ProfileId != profileId {

View File

@@ -9,14 +9,15 @@ import (
const (
InsertUser = `INSERT INTO users (user_id, gsbrcd, password, email, unique_nick) VALUES ($1, $2, $3, $4, $5) RETURNING profile_id`
InsertUserWithProfileID = `INSERT INTO users (user_id, gsbrcd, password, email, unique_nick) VALUES ($1, $2, $3, $4, $5)`
InsertUserWithProfileID = `INSERT INTO users (user_id, gsbrcd, password, ng_device_id, email, unique_nick) VALUES ($1, $2, $3, $4, $5, $6)`
UpdateUserTable = `UPDATE users SET firstname = CASE WHEN $3 THEN $2 ELSE firstname END, lastname = CASE WHEN $5 THEN $4 ELSE lastname END WHERE profile_id = $1 RETURNING user_id, gsbrcd, email, unique_nick, firstname, lastname`
UpdateUserProfileID = `UPDATE users SET profile_id = $3 WHERE user_id = $1 AND gsbrcd = $2`
UpdateUserNGDeviceID = `UPDATE users SET ng_device_id = $2 WHERE profile_id = $1`
GetUser = `SELECT user_id, gsbrcd, email, unique_nick, firstname, lastname FROM users WHERE profile_id = $1`
DoesUserExist = `SELECT EXISTS(SELECT 1 FROM users WHERE user_id = $1 AND gsbrcd = $2)`
IsProfileIDInUse = `SELECT EXISTS(SELECT 1 FROM users WHERE profile_id = $1)`
DeleteUserSession = `DELETE FROM sessions WHERE profile_id = $1`
GetUserProfileID = `SELECT profile_id, email, unique_nick, firstname, lastname FROM users WHERE user_id = $1 AND gsbrcd = $2`
GetUserProfileID = `SELECT profile_id, ng_device_id, email, unique_nick, firstname, lastname FROM users WHERE user_id = $1 AND gsbrcd = $2`
GetMKWFriendInfoQuery = `SELECT mariokartwii_friend_info FROM users WHERE profile_id = $1`
UpdateMKWFriendInfoQuery = `UPDATE users SET mariokartwii_friend_info = $2 WHERE profile_id = $1`
@@ -26,6 +27,7 @@ type User struct {
ProfileId uint32
UserId uint64
GsbrCode string
NgDeviceId uint32
Email string
UniqueNick string
FirstName string
@@ -56,7 +58,7 @@ func (user *User) CreateUser(pool *pgxpool.Pool, ctx context.Context) error {
return ErrProfileIDInUse
}
_, err = pool.Exec(ctx, InsertUserWithProfileID, user.UserId, user.GsbrCode, "", user.Email, user.UniqueNick, user.ProfileId)
_, err = pool.Exec(ctx, InsertUserWithProfileID, user.UserId, user.GsbrCode, "", user.NgDeviceId, user.Email, user.UniqueNick, user.ProfileId)
return err
}

View File

@@ -3,6 +3,7 @@ package gpcm
import (
"crypto/md5"
"crypto/sha1"
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/logrusorgru/aurora/v3"
@@ -35,10 +36,10 @@ func generateProof(gpcmChallenge, nasChallenge, authToken, clientChallenge strin
return generateResponse(clientChallenge, nasChallenge, authToken, gpcmChallenge)
}
func verifySignature(authToken string, signature string) bool {
func verifySignature(authToken string, signature string) uint32 {
sigBytes, err := common.Base64DwcEncoding.DecodeString(signature)
if err != nil || len(sigBytes) != 0x144 {
return false
return 0
}
ngId := sigBytes[0x000:0x004]
@@ -69,7 +70,7 @@ func verifySignature(authToken string, signature string) bool {
if !verifyECDSA(msPublicKey, msSignature, ngCertBlobHash[:]) {
logging.Error("GPCM", "NG cert verify failed")
return false
return 0
}
logging.Info("GPCM", "NG cert verified")
@@ -88,18 +89,18 @@ func verifySignature(authToken string, signature string) bool {
if !verifyECDSA(ngPublicKey, ngSignature, apCertBlobHash[:]) {
logging.Error("GPCM", "AP cert verify failed")
return false
return 0
}
logging.Info("GPCM", "AP cert verified")
authTokenHash := sha1.Sum([]byte(authToken))
if !verifyECDSA(apPublicKey, apSignature, authTokenHash[:]) {
logging.Error("GPCM", "Auth token signature failed")
return false
return 0
}
logging.Notice("GPCM", "Auth token signature verified; NG ID:", aurora.Cyan(fmt.Sprintf("%08x", ngId)))
return true
return binary.BigEndian.Uint32(ngId)
}
func (g *GameSpySession) login(command common.GameSpyCommand) {
@@ -125,7 +126,17 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
}
signature, exists := command.OtherValues["wwfc_sig"]
if !exists || !verifySignature(authToken, signature) {
if !exists {
g.replyError(GPError{
ErrorCode: ErrLogin.ErrorCode,
ErrorString: "Missing authentication signature.",
Fatal: true,
})
return
}
var deviceId uint32
if deviceId = verifySignature(authToken, signature); deviceId == 0 {
g.replyError(GPError{
ErrorCode: ErrLogin.ErrorCode,
ErrorString: "The authentication signature is invalid.",
@@ -170,7 +181,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
}
// Perform the login with the database.
user, ok := database.LoginUserToGPCM(pool, ctx, userId, gsbrcd, cmdProfileId)
user, ok := database.LoginUserToGPCM(pool, ctx, userId, gsbrcd, cmdProfileId, deviceId)
if !ok {
// There was an error logging in to the GP backend.
g.replyError(ErrLogin)

View File

@@ -25,10 +25,11 @@ SET default_table_access_method = heap;
--
CREATE TABLE public.users (
profile_id integer NOT NULL,
profile_id bigint NOT NULL,
user_id bigint NOT NULL,
gsbrcd character varying NOT NULL,
password character varying NOT NULL,
ng_device_id bigint,
email character varying NOT NULL,
unique_nick character varying NOT NULL,
firstname character varying,