2024-01-13 10:39:00 +00:00
using Microsoft.IdentityModel.JsonWebTokens ;
2023-12-04 14:17:13 +01:00
using Microsoft.IdentityModel.Tokens ;
2022-01-11 09:22:20 +01:00
using Ryujinx.Common.Logging ;
2020-08-18 21:24:54 +02:00
using Ryujinx.HLE.HOS.Kernel.Threading ;
using Ryujinx.HLE.HOS.Services.Account.Acc.AsyncContext ;
2022-01-11 09:22:20 +01:00
using System ;
2024-01-13 10:39:00 +00:00
using System.Collections.Generic ;
2025-05-27 16:46:54 -05:00
using System.Security.Claims ;
2022-01-11 09:22:20 +01:00
using System.Security.Cryptography ;
using System.Text ;
2020-08-18 21:24:54 +02:00
using System.Threading ;
using System.Threading.Tasks ;
2018-02-09 21:14:55 -03:00
2021-01-02 23:34:28 +01:00
namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
2018-02-09 21:14:55 -03:00
{
2021-01-02 23:34:28 +01:00
class ManagerServer
2018-02-09 21:14:55 -03:00
{
2020-08-18 21:24:54 +02:00
// TODO: Determine where and how NetworkServiceAccountId is set.
private const long NetworkServiceAccountId = 0xcafe ;
2023-07-16 19:31:14 +02:00
private readonly UserId _userId ;
2018-10-13 23:16:02 +00:00
2024-02-06 22:11:20 +00:00
private byte [ ] _cachedTokenData ;
private DateTime _cachedTokenExpiry ;
2021-01-02 23:34:28 +01:00
public ManagerServer ( UserId userId )
2018-02-09 21:14:55 -03:00
{
2021-01-02 23:34:28 +01:00
_userId = userId ;
2018-02-09 21:14:55 -03:00
}
2022-01-11 09:22:20 +01:00
private static string GenerateIdToken ( )
{
using RSA provider = RSA . Create ( 2048 ) ;
RSAParameters parameters = provider . ExportParameters ( true ) ;
2023-07-16 19:31:14 +02:00
RsaSecurityKey secKey = new ( parameters ) ;
2022-01-11 09:22:20 +01:00
2025-05-27 16:46:54 -05:00
SigningCredentials credentials = new ( secKey , SecurityAlgorithms . RsaSha256 ) ;
2022-01-11 09:22:20 +01:00
credentials . Key . KeyId = parameters . ToString ( ) ;
byte [ ] rawUserId = new byte [ 0x10 ] ;
RandomNumberGenerator . Fill ( rawUserId ) ;
byte [ ] deviceId = new byte [ 0x10 ] ;
RandomNumberGenerator . Fill ( deviceId ) ;
byte [ ] deviceAccountId = new byte [ 0x10 ] ;
2025-05-27 16:46:54 -05:00
RandomNumberGenerator . Fill ( deviceAccountId ) ;
2022-01-11 09:22:20 +01:00
2025-01-26 15:15:26 -06:00
SecurityTokenDescriptor descriptor = new ( )
2022-01-11 09:22:20 +01:00
{
2025-05-27 16:46:54 -05:00
Subject = new ClaimsIdentity ( [ new Claim ( JwtRegisteredClaimNames . Sub , Convert . ToHexString ( rawUserId ) . ToLower ( ) ) ] ) ,
2024-01-13 10:39:00 +00:00
SigningCredentials = credentials ,
Audience = "ed9e2f05d286f7b8" ,
Issuer = "https://e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com" ,
TokenType = "id_token" ,
IssuedAt = DateTime . UtcNow ,
Expires = DateTime . UtcNow + TimeSpan . FromHours ( 3 ) ,
Claims = new Dictionary < string , object >
{
{ "jku" , "https://e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com/1.0.0/certificates" } ,
{ "di" , Convert . ToHexString ( deviceId ) . ToLower ( ) } ,
{ "sn" , "XAW10000000000" } ,
{ "bs:did" , Convert . ToHexString ( deviceAccountId ) . ToLower ( ) }
}
2022-01-11 09:22:20 +01:00
} ;
2024-01-13 10:39:00 +00:00
return new JsonWebTokenHandler ( ) . CreateToken ( descriptor ) ;
2022-01-11 09:22:20 +01:00
}
2019-07-14 21:04:38 +02:00
public ResultCode CheckAvailability ( ServiceCtx context )
2018-04-17 03:24:42 +03:00
{
2020-08-18 21:24:54 +02:00
// NOTE: This opens the file at "su/baas/USERID_IN_UUID_STRING.dat" where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x".
// Then it searches the Availability of Online Services related to the UserId in this file and returns it.
2020-08-04 05:02:53 +05:30
Logger . Stub ? . PrintStub ( LogClass . ServiceAcc ) ;
2018-04-17 03:24:42 +03:00
2020-08-18 21:24:54 +02:00
// NOTE: Even if we try to return different error codes here, the guest still needs other calls.
2019-07-14 21:04:38 +02:00
return ResultCode . Success ;
2018-02-09 21:14:55 -03:00
}
2019-07-14 21:04:38 +02:00
public ResultCode GetAccountId ( ServiceCtx context )
2018-02-09 21:14:55 -03:00
{
2020-12-12 00:06:20 -03:00
// NOTE: This opens the file at "su/baas/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted
2020-08-18 21:24:54 +02:00
// as "%08x-%04x-%04x-%02x%02x-%08x%04x") in the account:/ savedata.
// Then it searches the NetworkServiceAccountId related to the UserId in this file and returns it.
2020-08-04 05:02:53 +05:30
Logger . Stub ? . PrintStub ( LogClass . ServiceAcc , new { NetworkServiceAccountId } ) ;
2020-03-02 15:07:27 +01:00
context . ResponseData . Write ( NetworkServiceAccountId ) ;
return ResultCode . Success ;
}
2021-01-02 23:34:28 +01:00
public ResultCode EnsureIdTokenCacheAsync ( ServiceCtx context , out IAsyncContext asyncContext )
2020-08-18 21:24:54 +02:00
{
2023-07-16 19:31:14 +02:00
KEvent asyncEvent = new ( context . Device . System . KernelContext ) ;
AsyncExecution asyncExecution = new ( asyncEvent ) ;
2020-08-18 21:24:54 +02:00
asyncExecution . Initialize ( 1000 , EnsureIdTokenCacheAsyncImpl ) ;
2021-01-02 23:34:28 +01:00
asyncContext = new IAsyncContext ( asyncExecution ) ;
2020-08-18 21:24:54 +02:00
// return ResultCode.NullObject if the IAsyncContext pointer is null. Doesn't occur in our case.
return ResultCode . Success ;
}
private async Task EnsureIdTokenCacheAsyncImpl ( CancellationToken token )
{
// NOTE: This open the file at "su/baas/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x")
// in the "account:/" savedata.
// Then its read data, use dauth API with this data to get the Token Id and probably store the dauth response
// in "su/cache/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x") in the "account:/" savedata.
// Since we don't support online services, we can stub it.
Logger . Stub ? . PrintStub ( LogClass . ServiceAcc ) ;
// TODO: Use a real function instead, with the CancellationToken.
await Task . CompletedTask ;
}
public ResultCode LoadIdTokenCache ( ServiceCtx context )
{
2021-04-24 12:16:01 +02:00
ulong bufferPosition = context . Request . ReceiveBuff [ 0 ] . Position ;
2025-06-11 17:58:27 -05:00
2023-07-16 19:31:14 +02:00
ulong bufferSize = context . Request . ReceiveBuff [ 0 ] . Size ;
2020-08-18 21:24:54 +02:00
// NOTE: This opens the file at "su/cache/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x")
// in the "account:/" savedata and writes some data in the buffer.
// Since we don't support online services, we can stub it.
Logger . Stub ? . PrintStub ( LogClass . ServiceAcc ) ;
/ *
if ( internal_object ! = null )
{
if ( bufferSize > 0xC00 )
{
return ResultCode . InvalidIdTokenCacheBufferSize ;
}
}
* /
2024-02-06 22:11:20 +00:00
if ( _cachedTokenData = = null | | DateTime . UtcNow > _cachedTokenExpiry )
{
_cachedTokenExpiry = DateTime . UtcNow + TimeSpan . FromHours ( 3 ) ;
_cachedTokenData = Encoding . ASCII . GetBytes ( GenerateIdToken ( ) ) ;
}
byte [ ] tokenData = _cachedTokenData ;
2020-08-18 21:24:54 +02:00
2025-06-11 17:58:27 -05:00
if ( ( ulong ) tokenData . Length > bufferSize )
{
return ResultCode . InvalidIdTokenCacheBufferSize ;
}
2022-01-11 09:22:20 +01:00
context . Memory . Write ( bufferPosition , tokenData ) ;
context . ResponseData . Write ( tokenData . Length ) ;
2020-08-18 21:24:54 +02:00
return ResultCode . Success ;
}
2020-03-02 15:07:27 +01:00
public ResultCode GetNintendoAccountUserResourceCacheForApplication ( ServiceCtx context )
{
2020-08-04 05:02:53 +05:30
Logger . Stub ? . PrintStub ( LogClass . ServiceAcc , new { NetworkServiceAccountId } ) ;
2018-10-13 23:16:02 +00:00
2020-03-02 15:07:27 +01:00
context . ResponseData . Write ( NetworkServiceAccountId ) ;
2018-04-17 03:24:42 +03:00
2020-12-12 08:10:12 -03:00
// TODO: determine and fill the output IPC buffer.
2018-02-09 21:14:55 -03:00
2019-07-14 21:04:38 +02:00
return ResultCode . Success ;
2018-02-09 21:14:55 -03:00
}
2020-09-21 05:45:30 +02:00
public ResultCode StoreOpenContext ( ServiceCtx context )
{
2022-09-27 21:24:52 -03:00
context . Device . System . AccountManager . StoreOpenedUsers ( ) ;
2020-09-21 05:45:30 +02:00
return ResultCode . Success ;
}
2022-08-30 20:52:45 +02:00
public ResultCode LoadNetworkServiceLicenseKindAsync ( ServiceCtx context , out IAsyncNetworkServiceLicenseKindContext asyncContext )
{
2023-07-16 19:31:14 +02:00
KEvent asyncEvent = new ( context . Device . System . KernelContext ) ;
AsyncExecution asyncExecution = new ( asyncEvent ) ;
2022-08-30 20:52:45 +02:00
Logger . Stub ? . PrintStub ( LogClass . ServiceAcc ) ;
// NOTE: This is an extension of the data retrieved from the id token cache.
asyncExecution . Initialize ( 1000 , EnsureIdTokenCacheAsyncImpl ) ;
asyncContext = new IAsyncNetworkServiceLicenseKindContext ( asyncExecution , NetworkServiceLicenseKind . Subscribed ) ;
// return ResultCode.NullObject if the IAsyncNetworkServiceLicenseKindContext pointer is null. Doesn't occur in our case.
return ResultCode . Success ;
}
2018-02-09 21:14:55 -03:00
}
2023-07-16 19:31:14 +02:00
}