mirror of
https://github.com/citra-emu/citra-canary.git
synced 2024-12-25 21:35:34 +00:00
Merge pull request #4733 from zhaowenlan1779/ps
service/ps: Implement PS:EncryptDecryptAes
This commit is contained in:
commit
37861526ff
|
@ -220,6 +220,7 @@ void DebuggerBackend::Write(const Entry& entry) {
|
||||||
SUB(Service, SOC) \
|
SUB(Service, SOC) \
|
||||||
SUB(Service, IR) \
|
SUB(Service, IR) \
|
||||||
SUB(Service, Y2R) \
|
SUB(Service, Y2R) \
|
||||||
|
SUB(Service, PS) \
|
||||||
CLS(HW) \
|
CLS(HW) \
|
||||||
SUB(HW, Memory) \
|
SUB(HW, Memory) \
|
||||||
SUB(HW, LCD) \
|
SUB(HW, LCD) \
|
||||||
|
|
|
@ -82,6 +82,7 @@ enum class Class : ClassType {
|
||||||
Service_SOC, ///< The SOC (Socket) service
|
Service_SOC, ///< The SOC (Socket) service
|
||||||
Service_IR, ///< The IR service
|
Service_IR, ///< The IR service
|
||||||
Service_Y2R, ///< The Y2R (YUV to RGB conversion) service
|
Service_Y2R, ///< The Y2R (YUV to RGB conversion) service
|
||||||
|
Service_PS, ///< The PS (Process) service
|
||||||
HW, ///< Low-level hardware emulation
|
HW, ///< Low-level hardware emulation
|
||||||
HW_Memory, ///< Memory-map and address translation
|
HW_Memory, ///< Memory-map and address translation
|
||||||
HW_LCD, ///< LCD register emulation
|
HW_LCD, ///< LCD register emulation
|
||||||
|
|
|
@ -2,18 +2,148 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <cryptopp/aes.h>
|
||||||
|
#include <cryptopp/modes.h>
|
||||||
|
#include "common/logging/log.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
#include "core/hle/service/ps/ps_ps.h"
|
#include "core/hle/service/ps/ps_ps.h"
|
||||||
|
#include "core/hw/aes/arithmetic128.h"
|
||||||
|
#include "core/hw/aes/key.h"
|
||||||
|
|
||||||
namespace Service::PS {
|
namespace Service::PS {
|
||||||
|
|
||||||
|
enum class AlgorithmType : u8 {
|
||||||
|
CBC_Encrypt,
|
||||||
|
CBC_Decrypt,
|
||||||
|
CTR_Encrypt,
|
||||||
|
CTR_Decrypt,
|
||||||
|
CCM_Encrypt,
|
||||||
|
CCM_Decrypt,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::array<u8, 10> KeyTypes{{
|
||||||
|
HW::AES::SSLKey,
|
||||||
|
HW::AES::UDSDataKey,
|
||||||
|
HW::AES::APTWrap,
|
||||||
|
HW::AES::BOSSDataKey,
|
||||||
|
0x32, // unknown
|
||||||
|
HW::AES::DLPDataKey,
|
||||||
|
HW::AES::CECDDataKey,
|
||||||
|
0, // invalid
|
||||||
|
HW::AES::FRDKey,
|
||||||
|
// Note: According to 3dbrew the KeyY is overridden by Process9 when using this key type.
|
||||||
|
// TODO: implement this behaviour?
|
||||||
|
HW::AES::NFCKey,
|
||||||
|
}};
|
||||||
|
|
||||||
|
void PS_PS::EncryptDecryptAes(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx, 0x4, 8, 4);
|
||||||
|
u32 src_size = rp.Pop<u32>();
|
||||||
|
u32 dest_size = rp.Pop<u32>();
|
||||||
|
|
||||||
|
using CryptoPP::AES;
|
||||||
|
std::array<u8, AES::BLOCKSIZE> iv;
|
||||||
|
rp.PopRaw(iv);
|
||||||
|
|
||||||
|
AlgorithmType algorithm = rp.PopEnum<AlgorithmType>();
|
||||||
|
u8 key_type = rp.Pop<u8>();
|
||||||
|
auto source = rp.PopMappedBuffer();
|
||||||
|
auto destination = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_PS, "called algorithm={} key_type={}", static_cast<u8>(algorithm), key_type);
|
||||||
|
|
||||||
|
// TODO(zhaowenlan1779): Tests on a real 3DS shows that no error is returned in this case
|
||||||
|
// and encrypted data is actually returned, but the key used is unknown.
|
||||||
|
ASSERT_MSG(key_type != 7 && key_type < 10, "Key type is invalid");
|
||||||
|
|
||||||
|
if (!HW::AES::IsNormalKeyAvailable(KeyTypes[key_type])) {
|
||||||
|
LOG_ERROR(Service_PS,
|
||||||
|
"Key 0x{:2X} is not available, encryption/decryption will not be correct",
|
||||||
|
KeyTypes[key_type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
HW::AES::AESKey key = HW::AES::GetNormalKey(KeyTypes[key_type]);
|
||||||
|
|
||||||
|
if (algorithm == AlgorithmType::CCM_Encrypt || algorithm == AlgorithmType::CCM_Decrypt) {
|
||||||
|
// AES-CCM is not supported with this function
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 4);
|
||||||
|
rb.Push(ResultCode(ErrorDescription::InvalidSection, ErrorModule::PS,
|
||||||
|
ErrorSummary::WrongArgument, ErrorLevel::Status));
|
||||||
|
rb.PushMappedBuffer(source);
|
||||||
|
rb.PushMappedBuffer(destination);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (algorithm == AlgorithmType::CBC_Encrypt || algorithm == AlgorithmType::CBC_Decrypt) {
|
||||||
|
src_size &= 0xFFFFFFF0; // Clear the lowest 4 bits of the size (make it a multiple of 16)
|
||||||
|
ASSERT(src_size > 0); // Real 3DS calls svcBreak in this case
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> src_buffer(src_size);
|
||||||
|
source.Read(src_buffer.data(), 0, src_buffer.size());
|
||||||
|
|
||||||
|
std::vector<u8> dst_buffer(src_buffer.size());
|
||||||
|
switch (algorithm) {
|
||||||
|
case AlgorithmType::CTR_Encrypt: {
|
||||||
|
CryptoPP::CTR_Mode<AES>::Encryption aes;
|
||||||
|
aes.SetKeyWithIV(key.data(), AES::BLOCKSIZE, iv.data());
|
||||||
|
aes.ProcessData(dst_buffer.data(), src_buffer.data(), src_buffer.size());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AlgorithmType::CTR_Decrypt: {
|
||||||
|
CryptoPP::CTR_Mode<AES>::Decryption aes;
|
||||||
|
aes.SetKeyWithIV(key.data(), AES::BLOCKSIZE, iv.data());
|
||||||
|
aes.ProcessData(dst_buffer.data(), src_buffer.data(), src_buffer.size());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AlgorithmType::CBC_Encrypt: {
|
||||||
|
CryptoPP::CBC_Mode<AES>::Encryption aes;
|
||||||
|
aes.SetKeyWithIV(key.data(), AES::BLOCKSIZE, iv.data());
|
||||||
|
aes.ProcessData(dst_buffer.data(), src_buffer.data(), src_buffer.size());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AlgorithmType::CBC_Decrypt: {
|
||||||
|
CryptoPP::CBC_Mode<AES>::Decryption aes;
|
||||||
|
aes.SetKeyWithIV(key.data(), AES::BLOCKSIZE, iv.data());
|
||||||
|
aes.ProcessData(dst_buffer.data(), src_buffer.data(), src_buffer.size());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
destination.Write(dst_buffer.data(), 0, dst_buffer.size());
|
||||||
|
|
||||||
|
// We will need to calculate the resulting IV/CTR ourselves as CrytoPP does not
|
||||||
|
// provide an easy way to get them
|
||||||
|
std::array<u8, AES::BLOCKSIZE> new_iv;
|
||||||
|
if (algorithm == AlgorithmType::CTR_Encrypt || algorithm == AlgorithmType::CTR_Decrypt) {
|
||||||
|
new_iv = HW::AES::Add128(iv, src_size / 16);
|
||||||
|
} else if (algorithm == AlgorithmType::CBC_Encrypt) {
|
||||||
|
// For AES-CBC, The new IV is the last block of ciphertext
|
||||||
|
std::copy_n(dst_buffer.end() - new_iv.size(), new_iv.size(), new_iv.begin());
|
||||||
|
} else {
|
||||||
|
std::copy_n(src_buffer.end() - new_iv.size(), new_iv.size(), new_iv.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(5, 4);
|
||||||
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
rb.PushRaw(new_iv);
|
||||||
|
rb.PushMappedBuffer(source);
|
||||||
|
rb.PushMappedBuffer(destination);
|
||||||
|
}
|
||||||
|
|
||||||
PS_PS::PS_PS() : ServiceFramework("ps:ps", DefaultMaxSessions) {
|
PS_PS::PS_PS() : ServiceFramework("ps:ps", DefaultMaxSessions) {
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
{0x00010244, nullptr, "SignRsaSha256"},
|
{0x00010244, nullptr, "SignRsaSha256"},
|
||||||
{0x00020244, nullptr, "VerifyRsaSha256"},
|
{0x00020244, nullptr, "VerifyRsaSha256"},
|
||||||
{0x00040204, nullptr, "EncryptDecryptAes"},
|
{0x00040204, &PS_PS::EncryptDecryptAes, "EncryptDecryptAes"},
|
||||||
{0x00050284, nullptr, "EncryptSignDecryptVerifyAesCcm"},
|
{0x00050284, nullptr, "EncryptSignDecryptVerifyAesCcm"},
|
||||||
{0x00060040, nullptr, "GetRomId"},
|
{0x00060040, nullptr, "GetRomId"},
|
||||||
{0x00070040, nullptr, "GetRomId2"},
|
{0x00070040, nullptr, "GetRomId2"},
|
||||||
|
|
|
@ -98,6 +98,7 @@ const std::array<ServiceModuleInfo, 40> service_module_map{
|
||||||
{"HTTP", 0x00040130'00002902, HTTP::InstallInterfaces},
|
{"HTTP", 0x00040130'00002902, HTTP::InstallInterfaces},
|
||||||
{"SOC", 0x00040130'00002E02, SOC::InstallInterfaces},
|
{"SOC", 0x00040130'00002E02, SOC::InstallInterfaces},
|
||||||
{"SSL", 0x00040130'00002F02, SSL::InstallInterfaces},
|
{"SSL", 0x00040130'00002F02, SSL::InstallInterfaces},
|
||||||
|
{"PS", 0x00040130'00003102, PS::InstallInterfaces},
|
||||||
// no HLE implementation
|
// no HLE implementation
|
||||||
{"CDC", 0x00040130'00001802, nullptr},
|
{"CDC", 0x00040130'00001802, nullptr},
|
||||||
{"GPIO", 0x00040130'00001B02, nullptr},
|
{"GPIO", 0x00040130'00001B02, nullptr},
|
||||||
|
@ -105,7 +106,6 @@ const std::array<ServiceModuleInfo, 40> service_module_map{
|
||||||
{"MCU", 0x00040130'00001F02, nullptr},
|
{"MCU", 0x00040130'00001F02, nullptr},
|
||||||
{"MP", 0x00040130'00002A02, nullptr},
|
{"MP", 0x00040130'00002A02, nullptr},
|
||||||
{"PDN", 0x00040130'00002102, nullptr},
|
{"PDN", 0x00040130'00002102, nullptr},
|
||||||
{"PS", 0x00040130'00003102, nullptr},
|
|
||||||
{"SPI", 0x00040130'00002302, nullptr}}};
|
{"SPI", 0x00040130'00002302, nullptr}}};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -36,6 +36,20 @@ AESKey Add128(const AESKey& a, const AESKey& b) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AESKey Add128(const AESKey& a, u64 b) {
|
||||||
|
AESKey out = a;
|
||||||
|
u32 carry = 0;
|
||||||
|
u32 sum = 0;
|
||||||
|
|
||||||
|
for (int i = 15; i >= 8; i--) {
|
||||||
|
sum = a[i] + static_cast<u8>((b >> ((15 - i) * 8)) & 0xff) + carry;
|
||||||
|
carry = sum >> 8;
|
||||||
|
out[i] = static_cast<u8>(sum & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
AESKey Xor128(const AESKey& a, const AESKey& b) {
|
AESKey Xor128(const AESKey& a, const AESKey& b) {
|
||||||
AESKey out;
|
AESKey out;
|
||||||
std::transform(a.cbegin(), a.cend(), b.cbegin(), out.begin(), std::bit_xor<>());
|
std::transform(a.cbegin(), a.cend(), b.cbegin(), out.begin(), std::bit_xor<>());
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
namespace HW::AES {
|
namespace HW::AES {
|
||||||
AESKey Lrot128(const AESKey& in, u32 rot);
|
AESKey Lrot128(const AESKey& in, u32 rot);
|
||||||
AESKey Add128(const AESKey& a, const AESKey& b);
|
AESKey Add128(const AESKey& a, const AESKey& b);
|
||||||
|
AESKey Add128(const AESKey& a, u64 b);
|
||||||
AESKey Xor128(const AESKey& a, const AESKey& b);
|
AESKey Xor128(const AESKey& a, const AESKey& b);
|
||||||
|
|
||||||
} // namespace HW::AES
|
} // namespace HW::AES
|
||||||
|
|
|
@ -24,6 +24,21 @@ enum KeySlotID : std::size_t {
|
||||||
// AES Keyslot used to generate the UDS data frame CCMP key.
|
// AES Keyslot used to generate the UDS data frame CCMP key.
|
||||||
UDSDataKey = 0x2D,
|
UDSDataKey = 0x2D,
|
||||||
|
|
||||||
|
// AES Keyslot used to encrypt the BOSS container data.
|
||||||
|
BOSSDataKey = 0x38,
|
||||||
|
|
||||||
|
// AES Keyslot used to calculate DLP data frame checksum.
|
||||||
|
DLPDataKey = 0x39,
|
||||||
|
|
||||||
|
// AES Keyslot used to generate the StreetPass CCMP key.
|
||||||
|
CECDDataKey = 0x2E,
|
||||||
|
|
||||||
|
// AES Keyslot used by the friends module.
|
||||||
|
FRDKey = 0x36,
|
||||||
|
|
||||||
|
// AES Keyslot used by the NFC module.
|
||||||
|
NFCKey = 0x39,
|
||||||
|
|
||||||
// AES keyslot used for APT:Wrap/Unwrap functions
|
// AES keyslot used for APT:Wrap/Unwrap functions
|
||||||
APTWrap = 0x31,
|
APTWrap = 0x31,
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue