service/ps: Implement PS:EncryptDecryptAES
This commit is contained in:
parent
14730ed560
commit
b34847d59e
|
@ -196,6 +196,7 @@ void DebuggerBackend::Write(const Entry& entry) {
|
|||
SUB(Service, SOC) \
|
||||
SUB(Service, IR) \
|
||||
SUB(Service, Y2R) \
|
||||
SUB(Service, PS) \
|
||||
CLS(HW) \
|
||||
SUB(HW, Memory) \
|
||||
SUB(HW, LCD) \
|
||||
|
|
|
@ -82,6 +82,7 @@ enum class Class : ClassType {
|
|||
Service_SOC, ///< The SOC (Socket) service
|
||||
Service_IR, ///< The IR service
|
||||
Service_Y2R, ///< The Y2R (YUV to RGB conversion) service
|
||||
Service_PS, ///< The PS (Process) service
|
||||
HW, ///< Low-level hardware emulation
|
||||
HW_Memory, ///< Memory-map and address translation
|
||||
HW_LCD, ///< LCD register emulation
|
||||
|
|
|
@ -2,18 +2,144 @@
|
|||
// Licensed under GPLv2 or any later version
|
||||
// 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/hle/ipc_helpers.h"
|
||||
#include "core/hle/service/ps/ps_ps.h"
|
||||
#include "core/hw/aes/arithmetic128.h"
|
||||
#include "core/hw/aes/key.h"
|
||||
|
||||
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{{
|
||||
0x0D,
|
||||
0x2D,
|
||||
0x31,
|
||||
0x38,
|
||||
0x32,
|
||||
0x39,
|
||||
0x2E,
|
||||
0, /* invalid */
|
||||
0x36,
|
||||
0x39,
|
||||
}};
|
||||
|
||||
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, 0);
|
||||
rb.Push(ResultCode(ErrorDescription::InvalidSection, ErrorModule::PS,
|
||||
ErrorSummary::WrongArgument, ErrorLevel::Status));
|
||||
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) {
|
||||
static const FunctionInfo functions[] = {
|
||||
// clang-format off
|
||||
{0x00010244, nullptr, "SignRsaSha256"},
|
||||
{0x00020244, nullptr, "VerifyRsaSha256"},
|
||||
{0x00040204, nullptr, "EncryptDecryptAes"},
|
||||
{0x00040204, &PS_PS::EncryptDecryptAes, "EncryptDecryptAes"},
|
||||
{0x00050284, nullptr, "EncryptSignDecryptVerifyAesCcm"},
|
||||
{0x00060040, nullptr, "GetRomId"},
|
||||
{0x00070040, nullptr, "GetRomId2"},
|
||||
|
|
|
@ -98,6 +98,7 @@ const std::array<ServiceModuleInfo, 40> service_module_map{
|
|||
{"HTTP", 0x00040130'00002902, HTTP::InstallInterfaces},
|
||||
{"SOC", 0x00040130'00002E02, SOC::InstallInterfaces},
|
||||
{"SSL", 0x00040130'00002F02, SSL::InstallInterfaces},
|
||||
{"PS", 0x00040130'00003102, PS::InstallInterfaces},
|
||||
// no HLE implementation
|
||||
{"CDC", 0x00040130'00001802, nullptr},
|
||||
{"GPIO", 0x00040130'00001B02, nullptr},
|
||||
|
@ -105,7 +106,6 @@ const std::array<ServiceModuleInfo, 40> service_module_map{
|
|||
{"MCU", 0x00040130'00001F02, nullptr},
|
||||
{"MP", 0x00040130'00002A02, nullptr},
|
||||
{"PDN", 0x00040130'00002102, nullptr},
|
||||
{"PS", 0x00040130'00003102, nullptr},
|
||||
{"SPI", 0x00040130'00002302, nullptr}}};
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,6 +36,20 @@ AESKey Add128(const AESKey& a, const AESKey& b) {
|
|||
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 out;
|
||||
std::transform(a.cbegin(), a.cend(), b.cbegin(), out.begin(), std::bit_xor<>());
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
namespace HW::AES {
|
||||
AESKey Lrot128(const AESKey& in, u32 rot);
|
||||
AESKey Add128(const AESKey& a, const AESKey& b);
|
||||
AESKey Add128(const AESKey& a, u64 b);
|
||||
AESKey Xor128(const AESKey& a, const AESKey& b);
|
||||
|
||||
} // namespace HW::AES
|
||||
|
|
Reference in New Issue