|
|
|
@@ -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"},
|
|
|
|
|