2022-01-13 22:29:04 +00:00
using LibHac ;
using LibHac.Common ;
using LibHac.Fs ;
using LibHac.Fs.Fsa ;
using LibHac.FsSystem ;
2022-03-22 19:46:16 +00:00
using LibHac.Ncm ;
2022-01-13 22:29:04 +00:00
using LibHac.Tools.FsSystem ;
using LibHac.Tools.FsSystem.NcaUtils ;
using Ryujinx.Common.Configuration ;
using Ryujinx.Common.Logging ;
using Ryujinx.HLE.Exceptions ;
using Ryujinx.HLE.FileSystem ;
using Ryujinx.HLE.HOS.Services.Ssl.Types ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Runtime.CompilerServices ;
using System.Runtime.InteropServices ;
namespace Ryujinx.HLE.HOS.Services.Ssl
{
class BuiltInCertificateManager
{
private const long CertStoreTitleId = 0x0100000000000800 ;
private readonly string CertStoreTitleMissingErrorMessage = "CertStore system title not found! SSL CA retrieving will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide#initial-setup-continued---installation-of-firmware for more information)" ;
private static BuiltInCertificateManager _instance ;
public static BuiltInCertificateManager Instance
{
get
{
if ( _instance = = null )
{
_instance = new BuiltInCertificateManager ( ) ;
}
return _instance ;
}
}
private VirtualFileSystem _virtualFileSystem ;
private IntegrityCheckLevel _fsIntegrityCheckLevel ;
private ContentManager _contentManager ;
private bool _initialized ;
private Dictionary < CaCertificateId , CertStoreEntry > _certificates ;
private object _lock = new object ( ) ;
private struct CertStoreFileHeader
{
private const uint ValidMagic = 0x546C7373 ;
#pragma warning disable CS0649
public uint Magic ;
public uint EntriesCount ;
#pragma warning restore CS0649
public bool IsValid ( )
{
return Magic = = ValidMagic ;
}
}
private struct CertStoreFileEntry
{
#pragma warning disable CS0649
public CaCertificateId Id ;
public TrustedCertStatus Status ;
public uint DataSize ;
public uint DataOffset ;
#pragma warning restore CS0649
}
public class CertStoreEntry
{
public CaCertificateId Id ;
public TrustedCertStatus Status ;
public byte [ ] Data ;
}
public string GetCertStoreTitleContentPath ( )
{
2022-03-22 19:46:16 +00:00
return _contentManager . GetInstalledContentPath ( CertStoreTitleId , StorageId . BuiltInSystem , NcaContentType . Data ) ;
2022-01-13 22:29:04 +00:00
}
public bool HasCertStoreTitle ( )
{
return ! string . IsNullOrEmpty ( GetCertStoreTitleContentPath ( ) ) ;
}
private CertStoreEntry ReadCertStoreEntry ( ReadOnlySpan < byte > buffer , CertStoreFileEntry entry )
{
string customCertificatePath = System . IO . Path . Join ( AppDataManager . BaseDirPath , "system" , "ssl" , $"{entry.Id}.der" ) ;
byte [ ] data ;
if ( File . Exists ( customCertificatePath ) )
{
data = File . ReadAllBytes ( customCertificatePath ) ;
}
else
{
data = buffer . Slice ( ( int ) entry . DataOffset , ( int ) entry . DataSize ) . ToArray ( ) ;
}
return new CertStoreEntry
{
Id = entry . Id ,
Status = entry . Status ,
Data = data
} ;
}
public void Initialize ( Switch device )
{
lock ( _lock )
{
_certificates = new Dictionary < CaCertificateId , CertStoreEntry > ( ) ;
_initialized = false ;
_contentManager = device . System . ContentManager ;
_virtualFileSystem = device . FileSystem ;
_fsIntegrityCheckLevel = device . System . FsIntegrityCheckLevel ;
if ( HasCertStoreTitle ( ) )
{
using LocalStorage ncaFile = new LocalStorage ( _virtualFileSystem . SwitchPathToSystemPath ( GetCertStoreTitleContentPath ( ) ) , FileAccess . Read , FileMode . Open ) ;
Nca nca = new Nca ( _virtualFileSystem . KeySet , ncaFile ) ;
IFileSystem romfs = nca . OpenFileSystem ( NcaSectionType . Data , _fsIntegrityCheckLevel ) ;
using var trustedCertsFileRef = new UniqueRef < IFile > ( ) ;
2023-03-02 02:42:27 +00:00
Result result = romfs . OpenFile ( ref trustedCertsFileRef . Ref , "/ssl_TrustedCerts.bdf" . ToU8Span ( ) , OpenMode . Read ) ;
2022-01-13 22:29:04 +00:00
if ( ! result . IsSuccess ( ) )
{
// [1.0.0 - 2.3.0]
if ( ResultFs . PathNotFound . Includes ( result ) )
{
2023-03-02 02:42:27 +00:00
result = romfs . OpenFile ( ref trustedCertsFileRef . Ref , "/ssl_TrustedCerts.tcf" . ToU8Span ( ) , OpenMode . Read ) ;
2022-01-13 22:29:04 +00:00
}
if ( result . IsFailure ( ) )
{
Logger . Error ? . Print ( LogClass . ServiceSsl , CertStoreTitleMissingErrorMessage ) ;
return ;
}
}
using IFile trustedCertsFile = trustedCertsFileRef . Release ( ) ;
trustedCertsFile . GetSize ( out long fileSize ) . ThrowIfFailure ( ) ;
Span < byte > trustedCertsRaw = new byte [ fileSize ] ;
trustedCertsFile . Read ( out _ , 0 , trustedCertsRaw ) . ThrowIfFailure ( ) ;
CertStoreFileHeader header = MemoryMarshal . Read < CertStoreFileHeader > ( trustedCertsRaw ) ;
if ( ! header . IsValid ( ) )
{
Logger . Error ? . Print ( LogClass . ServiceSsl , "Invalid CertStore data found, skipping!" ) ;
return ;
}
ReadOnlySpan < byte > trustedCertsData = trustedCertsRaw [ Unsafe . SizeOf < CertStoreFileHeader > ( ) . . ] ;
ReadOnlySpan < CertStoreFileEntry > trustedCertsEntries = MemoryMarshal . Cast < byte , CertStoreFileEntry > ( trustedCertsData ) [ . . ( int ) header . EntriesCount ] ;
foreach ( CertStoreFileEntry entry in trustedCertsEntries )
{
_certificates . Add ( entry . Id , ReadCertStoreEntry ( trustedCertsData , entry ) ) ;
}
_initialized = true ;
}
}
}
2022-09-29 15:45:25 +00:00
public bool TryGetCertificates (
ReadOnlySpan < CaCertificateId > ids ,
out CertStoreEntry [ ] entries ,
out bool hasAllCertificates ,
out int requiredSize )
2022-01-13 22:29:04 +00:00
{
lock ( _lock )
{
if ( ! _initialized )
{
throw new InvalidSystemResourceException ( CertStoreTitleMissingErrorMessage ) ;
}
2022-09-29 15:45:25 +00:00
requiredSize = 0 ;
hasAllCertificates = false ;
2022-01-13 22:29:04 +00:00
foreach ( CaCertificateId id in ids )
{
if ( id = = CaCertificateId . All )
{
hasAllCertificates = true ;
break ;
}
}
if ( hasAllCertificates )
{
entries = new CertStoreEntry [ _certificates . Count ] ;
2022-09-29 15:45:25 +00:00
requiredSize = ( _certificates . Count + 1 ) * Unsafe . SizeOf < BuiltInCertificateInfo > ( ) ;
2022-01-13 22:29:04 +00:00
int i = 0 ;
foreach ( CertStoreEntry entry in _certificates . Values )
{
entries [ i + + ] = entry ;
2022-09-29 15:45:25 +00:00
requiredSize + = ( entry . Data . Length + 3 ) & ~ 3 ;
2022-01-13 22:29:04 +00:00
}
return true ;
}
else
{
entries = new CertStoreEntry [ ids . Length ] ;
2022-09-29 15:45:25 +00:00
requiredSize = ids . Length * Unsafe . SizeOf < BuiltInCertificateInfo > ( ) ;
2022-01-13 22:29:04 +00:00
for ( int i = 0 ; i < ids . Length ; i + + )
{
if ( ! _certificates . TryGetValue ( ids [ i ] , out CertStoreEntry entry ) )
{
return false ;
}
entries [ i ] = entry ;
2022-09-29 15:45:25 +00:00
requiredSize + = ( entry . Data . Length + 3 ) & ~ 3 ;
2022-01-13 22:29:04 +00:00
}
return true ;
}
}
}
}
}