From 66c6e6028a7e8ccbbc105eb602460cc2d8e2da60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B3pez=20Guimaraes?= Date: Thu, 17 Apr 2025 20:44:56 +0100 Subject: [PATCH] feat: Implement token validation from common protocols We have to do special handling for guest accounts, so the insecure method is enabled on both TicketGranting and SecureConnection. Alongside that, we override the RegisterEx function to do the additional processing required by friends. --- go.mod | 28 ++- go.sum | 76 ++++---- .../nintendo_create_account.go | 42 +---- ...register_common_secure_server_protocols.go | 6 +- nex/secure-connection/register_ex.go | 167 ++++++++++++------ utility/authentication.go | 70 ++++++++ utility/crypto.go | 56 ------ 7 files changed, 249 insertions(+), 196 deletions(-) create mode 100644 utility/authentication.go delete mode 100644 utility/crypto.go diff --git a/go.mod b/go.mod index c21b383..d3b761c 100644 --- a/go.mod +++ b/go.mod @@ -7,35 +7,33 @@ toolchain go1.23.6 require ( github.com/PretendoNetwork/grpc-go v1.0.2 github.com/PretendoNetwork/nex-go/v2 v2.1.3 - github.com/PretendoNetwork/nex-protocols-common-go/v2 v2.2.2 + github.com/PretendoNetwork/nex-protocols-common-go/v2 v2.4.0 github.com/PretendoNetwork/nex-protocols-go/v2 v2.2.1 - github.com/PretendoNetwork/plogger-go v1.0.4 + github.com/PretendoNetwork/plogger-go v1.1.0 github.com/PretendoNetwork/sql-manager v1.0.0 github.com/golang/protobuf v1.5.4 github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 - google.golang.org/grpc v1.70.0 + google.golang.org/grpc v1.71.1 ) -replace google.golang.org/genproto => google.golang.org/genproto v0.0.0-20240520151616-dc85e6b867a5 - require ( github.com/dolthub/maphash v0.1.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/jwalton/go-supportscolor v1.2.0 // indirect - github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/lxzan/gws v1.8.8 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e // indirect github.com/superwhiskers/crunch/v3 v3.5.7 // indirect - golang.org/x/exp v0.0.0-20250215185904-eff6e970281f // indirect - golang.org/x/mod v0.23.0 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b // indirect - google.golang.org/protobuf v1.36.5 // indirect + golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/term v0.31.0 // indirect + golang.org/x/text v0.24.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect + google.golang.org/protobuf v1.36.6 // indirect ) diff --git a/go.sum b/go.sum index d4cc502..bfc8b28 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,13 @@ github.com/PretendoNetwork/grpc-go v1.0.2 h1:9TvKmX7dCOANyoHEra1MMYqS1N/RGav66TRG4SHInvo= github.com/PretendoNetwork/grpc-go v1.0.2/go.mod h1:XZjEsij9lL7HJBNkH6JPbBIkUSq/1rjflvjGdv+DAj0= -github.com/PretendoNetwork/nex-go/v2 v2.1.2 h1:OJFAS6U6VNzZ4YzteKqUEZ5aJMwWIHODeRrLwNbN7nw= -github.com/PretendoNetwork/nex-go/v2 v2.1.2/go.mod h1:3LyJzsv3AataJW8D0binp15Q8ZH22MWTYly1VNtXi64= github.com/PretendoNetwork/nex-go/v2 v2.1.3 h1:hdi8PbJIWbpr3WOc1fGJ5ssF76kTxFJ3Wnz46WJYvVs= github.com/PretendoNetwork/nex-go/v2 v2.1.3/go.mod h1:3LyJzsv3AataJW8D0binp15Q8ZH22MWTYly1VNtXi64= -github.com/PretendoNetwork/nex-protocols-common-go/v2 v2.2.2 h1:rBJNZDJ92pa9fU3Og0sanyizJTWnELPoGR0Tjz8zlws= -github.com/PretendoNetwork/nex-protocols-common-go/v2 v2.2.2/go.mod h1:iuNMuBK/zww+44d6ajfLsOusXx/6Llj3zSkmhJwMuuM= +github.com/PretendoNetwork/nex-protocols-common-go/v2 v2.4.0 h1:EhXj1EDbNgdg40BPx/7n1HHsAy/DayGIWthu81UNyvI= +github.com/PretendoNetwork/nex-protocols-common-go/v2 v2.4.0/go.mod h1:tNtZly5sL3wfy4LVgybS2efm00L/wNgyvcrBV59S/YM= github.com/PretendoNetwork/nex-protocols-go/v2 v2.2.1 h1:/dsuP0W7bZNvrXoXH0ZRdxpxonfbWmmson51WCQdpEQ= github.com/PretendoNetwork/nex-protocols-go/v2 v2.2.1/go.mod h1:+soBHmwX6ixGxj6cphLuCvfJqxcZPuowc/5e7Qi9Bz0= -github.com/PretendoNetwork/plogger-go v1.0.4 h1:PF7xHw9eDRHH+RsAP9tmAE7fG0N0p6H4iPwHKnsoXwc= -github.com/PretendoNetwork/plogger-go v1.0.4/go.mod h1:7kD6M4vPq1JL4LTuPg6kuB1OvUBOwQOtAvTaUwMbwvU= +github.com/PretendoNetwork/plogger-go v1.1.0 h1:x2XgyeeM8zDFGy+NcIZd3SYC2fNrVWpBBbkqTejOfiM= +github.com/PretendoNetwork/plogger-go v1.1.0/go.mod h1:wpltahp91IXr9nOvWgwep8zGtUKDeCVwm+/Wa484lQ4= github.com/PretendoNetwork/sql-manager v1.0.0 h1:g0SYpQgi6Kk4ptufrLTSmDxvqaYioTcfXaDH+uXC+a0= github.com/PretendoNetwork/sql-manager v1.0.0/go.mod h1:NaEdDC0S/9J8eoxCDvuHB8fofv0svh44lWvgCdtuMq0= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -32,8 +30,8 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jwalton/go-supportscolor v1.2.0 h1:g6Ha4u7Vm3LIsQ5wmeBpS4gazu0UP1DRDE8y6bre4H8= github.com/jwalton/go-supportscolor v1.2.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lxzan/gws v1.8.8 h1:st193ZG8qN8sSw8/g/UituFhs7etmKzS7jUqhijg5wM= @@ -50,39 +48,41 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/superwhiskers/crunch/v3 v3.5.7 h1:N9RLxaR65C36i26BUIpzPXGy2f6pQ7wisu2bawbKNqg= github.com/superwhiskers/crunch/v3 v3.5.7/go.mod h1:4ub2EKgF1MAhTjoOCTU4b9uLMsAweHEa89aRrfAypXA= -go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= -go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= -go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= -go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= -go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= -go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= -go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= -golang.org/x/exp v0.0.0-20250215185904-eff6e970281f h1:oFMYAjX0867ZD2jcNiLBrI9BdpmEkvPyi5YrBGXbamg= -golang.org/x/exp v0.0.0-20250215185904-eff6e970281f/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b h1:FQtJ1MxbXoIIrZHZ33M+w5+dAP9o86rgpjoKr/ZmT7k= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= +google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/nex/account-management/nintendo_create_account.go b/nex/account-management/nintendo_create_account.go index 62aaab0..e74784f 100644 --- a/nex/account-management/nintendo_create_account.go +++ b/nex/account-management/nintendo_create_account.go @@ -3,56 +3,26 @@ package nex_account_management import ( "crypto/hmac" "crypto/md5" - "encoding/base64" "encoding/binary" "encoding/hex" - "strings" "github.com/PretendoNetwork/friends/globals" "github.com/PretendoNetwork/friends/utility" nex "github.com/PretendoNetwork/nex-go/v2" "github.com/PretendoNetwork/nex-go/v2/types" account_management "github.com/PretendoNetwork/nex-protocols-go/v2/account-management" - account_management_types "github.com/PretendoNetwork/nex-protocols-go/v2/account-management/types" ) func NintendoCreateAccount(err error, packet nex.PacketInterface, callID uint32, strPrincipalName types.String, strKey types.String, uiGroups types.UInt32, strEmail types.String, oAuthData types.DataHolder) (*nex.RMCMessage, *nex.Error) { if err != nil { globals.Logger.Error(err.Error()) - return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "") // TODO - Add error message + return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, err.Error()) } - var tokenBase64 string - - oAuthDataType := oAuthData.Object.DataObjectID().(types.String) - - switch oAuthDataType { - case "NintendoCreateAccountData": // * Wii U - nintendoCreateAccountData := oAuthData.Object.Copy().(account_management_types.NintendoCreateAccountData) - - tokenBase64 = string(nintendoCreateAccountData.Token) - case "AccountExtraInfo": // * 3DS - accountExtraInfo := oAuthData.Object.Copy().(account_management_types.AccountExtraInfo) - - tokenBase64 = string(accountExtraInfo.NEXToken) - tokenBase64 = strings.Replace(tokenBase64, ".", "+", -1) - tokenBase64 = strings.Replace(tokenBase64, "-", "/", -1) - tokenBase64 = strings.Replace(tokenBase64, "*", "=", -1) - default: - globals.Logger.Errorf("Invalid oAuthData data type %s!", oAuthDataType) - return nil, nex.NewError(nex.ResultCodes.Authentication.TokenParseError, "") // TODO - Add error message - } - - encryptedToken, err := base64.StdEncoding.DecodeString(tokenBase64) - if err != nil { - globals.Logger.Error(err.Error()) - return nil, nex.NewError(nex.ResultCodes.Authentication.TokenParseError, "") // TODO - Add error message - } - - decryptedToken, err := utility.DecryptToken(encryptedToken) - if err != nil { - globals.Logger.Error(err.Error()) - return nil, nex.NewError(nex.ResultCodes.Authentication.TokenParseError, "") // TODO - Add error message + decryptedToken, nexError := utility.ValidateNintendoCreateAccountToken(oAuthData) + if nexError != nil { + globals.Logger.Error(nexError.Error()) + return nil, nexError } pid := types.NewPID(uint64(decryptedToken.UserPID)) @@ -64,7 +34,7 @@ func NintendoCreateAccount(err error, packet nex.PacketInterface, callID uint32, _, err = mac.Write(pidByteArray) if err != nil { globals.Logger.Error(err.Error()) - return nil, nex.NewError(nex.ResultCodes.Authentication.Unknown, "") // TODO - Add error message + return nil, nex.NewError(nex.ResultCodes.Authentication.Unknown, err.Error()) } pidHmac := types.NewString(hex.EncodeToString(mac.Sum(nil))) diff --git a/nex/register_common_secure_server_protocols.go b/nex/register_common_secure_server_protocols.go index ccb7ee5..49580d0 100644 --- a/nex/register_common_secure_server_protocols.go +++ b/nex/register_common_secure_server_protocols.go @@ -9,8 +9,12 @@ import ( func registerCommonSecureServerProtocols() { secureConnectionProtocol := secure_connection.NewProtocol() - common_secure_connection.NewCommonProtocol(secureConnectionProtocol) + commonSecureConnectionProtocol := common_secure_connection.NewCommonProtocol(secureConnectionProtocol) + // * On account creation the console logs in with a guest account and uses the Register method + commonSecureConnectionProtocol.EnableInsecureRegister() + + // * Override RegisterEx so that we can register the user into the ConnectedUsers map secureConnectionProtocol.RegisterEx = nex_secure_connection.RegisterEx globals.SecureEndpoint.RegisterServiceProtocol(secureConnectionProtocol) diff --git a/nex/secure-connection/register_ex.go b/nex/secure-connection/register_ex.go index a4f96d5..ac9cb66 100644 --- a/nex/secure-connection/register_ex.go +++ b/nex/secure-connection/register_ex.go @@ -3,77 +3,144 @@ package nex_secure_connection import ( "net" + "github.com/PretendoNetwork/nex-go/v2" + "github.com/PretendoNetwork/nex-go/v2/constants" + "github.com/PretendoNetwork/nex-go/v2/types" + secure_connection "github.com/PretendoNetwork/nex-protocols-go/v2/secure-connection" + common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals" + database_3ds "github.com/PretendoNetwork/friends/database/3ds" database_wiiu "github.com/PretendoNetwork/friends/database/wiiu" "github.com/PretendoNetwork/friends/globals" friends_types "github.com/PretendoNetwork/friends/types" - nex "github.com/PretendoNetwork/nex-go/v2" - "github.com/PretendoNetwork/nex-go/v2/types" - secure_connection "github.com/PretendoNetwork/nex-protocols-go/v2/secure-connection" ) func RegisterEx(err error, packet nex.PacketInterface, callID uint32, vecMyURLs types.List[types.StationURL], hCustomData types.DataHolder) (*nex.RMCMessage, *nex.Error) { if err != nil { - globals.Logger.Error(err.Error()) - return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "") + common_globals.Logger.Error(err.Error()) + return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, err.Error()) } connection := packet.Sender().(*nex.PRUDPConnection) + endpoint := connection.Endpoint() - retval := types.NewQResultSuccess(nex.ResultCodes.Core.Unknown) - - // TODO - Validate loginData - pid := uint32(connection.PID()) - - user := friends_types.NewConnectedUser() - user.PID = pid - user.Connection = connection - - lastOnline := types.NewDateTime(0).Now() - loginDataType := hCustomData.Object.DataObjectID().(types.String) - - switch loginDataType { - case "NintendoLoginData": - user.Platform = friends_types.WUP // * Platform is Wii U - - err = database_wiiu.UpdateUserLastOnlineTime(pid, lastOnline) - if err != nil { - globals.Logger.Critical(err.Error()) - retval = types.NewQResultError(nex.ResultCodes.Authentication.Unknown) - } - case "AccountExtraInfo": - user.Platform = friends_types.CTR // * Platform is 3DS - - err = database_3ds.UpdateUserLastOnlineTime(pid, lastOnline) - if err != nil { - globals.Logger.Critical(err.Error()) - retval = types.NewQResultError(nex.ResultCodes.Authentication.Unknown) - } - default: - globals.Logger.Errorf("Unknown loginData data type %s!", loginDataType) - retval = types.NewQResultError(nex.ResultCodes.Authentication.ValidationFailed) - } - + var retval types.QResult pidConnectionID := types.NewUInt32(0) urlPublic := types.NewString("") - if retval.IsSuccess() { - globals.ConnectedUsers.Set(pid, user) + errorCode := common_globals.ValidatePretendoLoginData(connection.PID(), hCustomData, globals.AESKey) + if errorCode != nil { + common_globals.Logger.Error(errorCode.Message) + retval = types.NewQResultError(errorCode.ResultCode) + } else { + // * vecMyURLs may contain multiple StationURLs. Search them all + var localStation *types.StationURL + var publicStation *types.StationURL - localStation := vecMyURLs[0] + for _, stationURL := range vecMyURLs { + natf, ok := stationURL.NATFiltering() + if !ok { + continue + } - address := connection.Address().(*net.UDPAddr) + natm, ok := stationURL.NATMapping() + if !ok { + continue + } - localStation.SetAddress(address.IP.String()) - localStation.SetPortNumber(uint16(address.Port)) + // * Station reports itself as being non-public (local) + if localStation == nil && !stationURL.IsPublic() { + localStation = &stationURL + } - localStationURL := localStation.URL() + // * Still did not find the station, trying heuristics + if localStation == nil && natf == constants.UnknownNATFiltering && natm == constants.UnknownNATMapping { + localStation = &stationURL + } - pidConnectionID = types.NewUInt32(connection.ID) - urlPublic = types.NewString(localStationURL) + if publicStation == nil && stationURL.IsPublic() { + publicStation = &stationURL + } + } + + if localStation == nil { + common_globals.Logger.Error("Failed to find local station") + return nil, nex.NewError(nex.ResultCodes.Core.InvalidArgument, "change_error") + } + + if publicStation == nil { + publicStation = localStation + + var address string + var port uint16 + + // * We have to duplicate this because Go automatically breaks on switch statements + switch clientAddress := connection.Address().(type) { + case *net.UDPAddr: + address = clientAddress.IP.String() + port = uint16(clientAddress.Port) + case *net.TCPAddr: + address = clientAddress.IP.String() + port = uint16(clientAddress.Port) + } + + publicStation.SetAddress(address) + publicStation.SetPortNumber(port) + publicStation.SetNATFiltering(constants.UnknownNATFiltering) + publicStation.SetNATMapping(constants.UnknownNATMapping) + publicStation.SetType(uint8(constants.StationURLFlagPublic) | uint8(constants.StationURLFlagBehindNAT)) + } + + localStation.SetPrincipalID(connection.PID()) + publicStation.SetPrincipalID(connection.PID()) + + localStation.SetRVConnectionID(connection.ID) + publicStation.SetRVConnectionID(connection.ID) + + connection.StationURLs = append(connection.StationURLs, *localStation) + connection.StationURLs = append(connection.StationURLs, *publicStation) + + pid := uint32(connection.PID()) + + user := friends_types.NewConnectedUser() + user.PID = pid + user.Connection = connection + + lastOnline := types.NewDateTime(0).Now() + loginDataType := hCustomData.Object.DataObjectID().(types.String) + + switch loginDataType { + case "NintendoLoginData": + user.Platform = friends_types.WUP // * Platform is Wii U + + err = database_wiiu.UpdateUserLastOnlineTime(pid, lastOnline) + if err != nil { + globals.Logger.Critical(err.Error()) + retval = types.NewQResultError(nex.ResultCodes.Authentication.Unknown) + } + case "AccountExtraInfo": + user.Platform = friends_types.CTR // * Platform is 3DS + + err = database_3ds.UpdateUserLastOnlineTime(pid, lastOnline) + if err != nil { + globals.Logger.Critical(err.Error()) + retval = types.NewQResultError(nex.ResultCodes.Authentication.Unknown) + } + default: + globals.Logger.Errorf("Unknown loginData data type %s!", loginDataType) + retval = types.NewQResultError(nex.ResultCodes.Authentication.ValidationFailed) + } + + if !retval.IsError() { + globals.ConnectedUsers.Set(pid, user) + + retval = types.NewQResultSuccess(nex.ResultCodes.Core.Unknown) + pidConnectionID = types.NewUInt32(connection.ID) + urlPublic = types.NewString(publicStation.URL()) + } } - rmcResponseStream := nex.NewByteStreamOut(globals.SecureEndpoint.LibraryVersions(), globals.SecureEndpoint.ByteStreamSettings()) + rmcResponseStream := nex.NewByteStreamOut(endpoint.LibraryVersions(), endpoint.ByteStreamSettings()) retval.WriteTo(rmcResponseStream) pidConnectionID.WriteTo(rmcResponseStream) @@ -81,7 +148,7 @@ func RegisterEx(err error, packet nex.PacketInterface, callID uint32, vecMyURLs rmcResponseBody := rmcResponseStream.Bytes() - rmcResponse := nex.NewRMCSuccess(globals.SecureEndpoint, rmcResponseBody) + rmcResponse := nex.NewRMCSuccess(endpoint, rmcResponseBody) rmcResponse.ProtocolID = secure_connection.ProtocolID rmcResponse.MethodID = secure_connection.MethodRegisterEx rmcResponse.CallID = callID diff --git a/utility/authentication.go b/utility/authentication.go new file mode 100644 index 0000000..11ff686 --- /dev/null +++ b/utility/authentication.go @@ -0,0 +1,70 @@ +package utility + +import ( + "encoding/base64" + "fmt" + "strings" + "time" + + "github.com/PretendoNetwork/nex-go/v2" + "github.com/PretendoNetwork/nex-go/v2/types" + common_globals "github.com/PretendoNetwork/nex-protocols-common-go/v2/globals" + account_management_types "github.com/PretendoNetwork/nex-protocols-go/v2/account-management/types" + + "github.com/PretendoNetwork/friends/globals" +) + +// ValidateNintendoCreateAccountToken validates the given Pretendo token for account creation +func ValidateNintendoCreateAccountToken(token types.DataHolder) (*common_globals.NEXToken, *nex.Error) { + var tokenBase64 string + + tokenDataType := token.Object.DataObjectID().(types.String) + + switch tokenDataType { + case "NintendoCreateAccountData": // * Wii U + nintendoCreateAccountData := token.Object.Copy().(account_management_types.NintendoCreateAccountData) + + tokenBase64 = string(nintendoCreateAccountData.Token) + case "AccountExtraInfo": // * 3DS + accountExtraInfo := token.Object.Copy().(account_management_types.AccountExtraInfo) + + tokenBase64 = string(accountExtraInfo.NEXToken) + tokenBase64 = strings.Replace(tokenBase64, ".", "+", -1) + tokenBase64 = strings.Replace(tokenBase64, "-", "/", -1) + tokenBase64 = strings.Replace(tokenBase64, "*", "=", -1) + default: + globals.Logger.Errorf("Invalid token data type %s!", tokenDataType) + return nil, nex.NewError(nex.ResultCodes.Authentication.ValidationFailed, fmt.Sprintf("Invalid token data type %s!", tokenDataType)) + } + + encryptedToken, err := base64.StdEncoding.DecodeString(tokenBase64) + if err != nil { + globals.Logger.Error(err.Error()) + return nil, nex.NewError(nex.ResultCodes.Authentication.ValidationFailed, err.Error()) + } + + decryptedToken, nexError := common_globals.DecryptToken(encryptedToken, globals.AESKey) + if nexError != nil { + return nil, nexError + } + + // Check for NEX token type + if decryptedToken.TokenType != 3 { + return nil, nex.NewError(nex.ResultCodes.Authentication.ValidationFailed, "Invalid token type") + } + + // Expire time is in milliseconds + expireTime := time.Unix(int64(decryptedToken.ExpireTime / 1000), 0) + + if expireTime.Before(time.Now()) { + return nil, nex.NewError(nex.ResultCodes.Authentication.TokenExpired, "Token expired") + } + + // PID isn't checked since account creation is done with a guest account + + if decryptedToken.AccessLevel < 0 { + return nil, nex.NewError(nex.ResultCodes.RendezVous.AccountDisabled, fmt.Sprintf("Account %d is banned", decryptedToken.UserPID)) + } + + return decryptedToken, nil +} diff --git a/utility/crypto.go b/utility/crypto.go deleted file mode 100644 index 4b14f48..0000000 --- a/utility/crypto.go +++ /dev/null @@ -1,56 +0,0 @@ -package utility - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "encoding/binary" - "errors" - "fmt" - "hash/crc32" - - "github.com/PretendoNetwork/friends/globals" - "github.com/PretendoNetwork/friends/types" -) - -func DecryptToken(encryptedToken []byte) (*types.NEXToken, error) { - // Decrypt the token body - block, err := aes.NewCipher(globals.AESKey) - if err != nil { - return nil, err - } - - expectedChecksum := binary.BigEndian.Uint32(encryptedToken[0:4]) - encryptedBody := encryptedToken[4:] - - decrypted := make([]byte, len(encryptedBody)) - iv := make([]byte, 16) - mode := cipher.NewCBCDecrypter(block, iv) - mode.CryptBlocks(decrypted, encryptedBody) - - paddingSize := int(decrypted[len(decrypted)-1]) - - if paddingSize < 0 || paddingSize >= len(decrypted) { - return nil, fmt.Errorf("Invalid padding size %d for token %x", paddingSize, encryptedToken) - } - - decrypted = decrypted[:len(decrypted)-paddingSize] - - table := crc32.MakeTable(crc32.IEEE) - calculatedChecksum := crc32.Checksum(decrypted, table) - - if expectedChecksum != calculatedChecksum { - return nil, errors.New("Checksum did not match. Failed decrypt. Are you using the right key?") - } - - // Unpack the token body to struct - token := &types.NEXToken{} - tokenReader := bytes.NewBuffer(decrypted) - - err = binary.Read(tokenReader, binary.LittleEndian, token) - if err != nil { - return nil, err - } - - return token, nil -}