diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 6305606d9..ac692a128 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -220,6 +220,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) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index fe529f415..380c8ad5a 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -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 diff --git a/src/core/hle/service/ps/ps_ps.cpp b/src/core/hle/service/ps/ps_ps.cpp index e8d4d733c..a48903348 100644 --- a/src/core/hle/service/ps/ps_ps.cpp +++ b/src/core/hle/service/ps/ps_ps.cpp @@ -2,18 +2,148 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include +#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 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 dest_size = rp.Pop(); + + using CryptoPP::AES; + std::array iv; + rp.PopRaw(iv); + + AlgorithmType algorithm = rp.PopEnum(); + u8 key_type = rp.Pop(); + auto source = rp.PopMappedBuffer(); + auto destination = rp.PopMappedBuffer(); + + LOG_DEBUG(Service_PS, "called algorithm={} key_type={}", static_cast(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 src_buffer(src_size); + source.Read(src_buffer.data(), 0, src_buffer.size()); + + std::vector dst_buffer(src_buffer.size()); + switch (algorithm) { + case AlgorithmType::CTR_Encrypt: { + CryptoPP::CTR_Mode::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::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::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::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 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"}, diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 41156f858..e9aca4a2e 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -98,6 +98,7 @@ const std::array 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 service_module_map{ {"MCU", 0x00040130'00001F02, nullptr}, {"MP", 0x00040130'00002A02, nullptr}, {"PDN", 0x00040130'00002102, nullptr}, - {"PS", 0x00040130'00003102, nullptr}, {"SPI", 0x00040130'00002302, nullptr}}}; /** diff --git a/src/core/hw/aes/arithmetic128.cpp b/src/core/hw/aes/arithmetic128.cpp index 6a1308f2d..649f82a9b 100644 --- a/src/core/hw/aes/arithmetic128.cpp +++ b/src/core/hw/aes/arithmetic128.cpp @@ -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((b >> ((15 - i) * 8)) & 0xff) + carry; + carry = sum >> 8; + out[i] = static_cast(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<>()); diff --git a/src/core/hw/aes/arithmetic128.h b/src/core/hw/aes/arithmetic128.h index 62d110e16..6223d618e 100644 --- a/src/core/hw/aes/arithmetic128.h +++ b/src/core/hw/aes/arithmetic128.h @@ -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 diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h index 865c42c20..0e1530f0c 100644 --- a/src/core/hw/aes/key.h +++ b/src/core/hw/aes/key.h @@ -24,6 +24,21 @@ enum KeySlotID : std::size_t { // AES Keyslot used to generate the UDS data frame CCMP key. 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 APTWrap = 0x31,