diff --git a/Makefile b/Makefile index 8edd2a64..a8a75e94 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ include $(DEVKITPRO)/libnx/switch_rules # INCLUDES is a list of directories containing header files #--------------------------------------------------------------------------------- TARGET := $(notdir $(CURDIR)) -SOURCES := source +SOURCES := source source/spl source/spl/smc DATA := data INCLUDES := include diff --git a/include/stratosphere/spl.hpp b/include/stratosphere/spl.hpp new file mode 100644 index 00000000..d0b43e9d --- /dev/null +++ b/include/stratosphere/spl.hpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +#include "spl/spl_types.hpp" +#include "spl/spl_api.hpp" +#include "spl/smc/spl_smc.hpp" \ No newline at end of file diff --git a/include/stratosphere/spl/smc/spl_smc.hpp b/include/stratosphere/spl/smc/spl_smc.hpp new file mode 100644 index 00000000..46a011a3 --- /dev/null +++ b/include/stratosphere/spl/smc/spl_smc.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +#include "../spl_types.hpp" + +namespace sts::spl::smc { + + /* Helpers for converting arguments. */ + inline u32 GetCryptAesMode(CipherMode mode, u32 keyslot) { + return static_cast((static_cast(mode) << 4) | (keyslot & 7)); + } + + inline u32 GetUnwrapEsKeyOption(EsKeyType type, u32 generation) { + return static_cast((static_cast(type) << 6) | (generation & 0x3F)); + } + + /* Functions. */ + Result SetConfig(SplConfigItem which, const u64 *value, size_t num_qwords); + Result GetConfig(u64 *out, size_t num_qwords, SplConfigItem which); + Result CheckStatus(Result *out, AsyncOperationKey op); + Result GetResult(Result *out, void *out_buf, size_t out_buf_size, AsyncOperationKey op); + Result ExpMod(AsyncOperationKey *out_op, const void *base, const void *exp, size_t exp_size, const void *mod); + Result GenerateRandomBytes(void *out, size_t size); + Result GenerateAesKek(AccessKey *out, const KeySource &source, u32 generation, u32 option); + Result LoadAesKey(u32 keyslot, const AccessKey &access_key, const KeySource &source); + Result CryptAes(AsyncOperationKey *out_op, u32 mode, const IvCtr &iv_ctr, u32 dst_addr, u32 src_addr, size_t size); + Result GenerateSpecificAesKey(AesKey *out_key, const KeySource &source, u32 generation, u32 which); + Result ComputeCmac(Cmac *out_mac, u32 keyslot, const void *data, size_t size); + Result ReEncryptRsaPrivateKey(void *data, size_t size, const AccessKey &access_key_dec, const KeySource &source_dec, const AccessKey &access_key_enc, const KeySource &source_enc, u32 option); + Result DecryptOrImportRsaPrivateKey(void *data, size_t size, const AccessKey &access_key, const KeySource &source, DecryptOrImportMode mode); + Result SecureExpMod(AsyncOperationKey *out_op, const void *base, const void *mod, SecureExpModMode mode); + Result UnwrapTitleKey(AsyncOperationKey *out_op, const void *base, const void *mod, const void *label_digest, size_t label_digest_size, u32 option); + Result LoadTitleKey(u32 keyslot, const AccessKey &access_key); + Result UnwrapCommonTitleKey(AccessKey *out, const KeySource &source, u32 generation); + + /* Deprecated functions. */ + Result ImportEsKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option); + Result DecryptRsaPrivateKey(size_t *out_size, void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option); + Result ImportSecureExpModKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option); + +} diff --git a/include/stratosphere/spl/spl_api.hpp b/include/stratosphere/spl/spl_api.hpp new file mode 100644 index 00000000..62291f0d --- /dev/null +++ b/include/stratosphere/spl/spl_api.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "spl_types.hpp" + +namespace sts::spl { + + HardwareType GetHardwareType(); + bool IsRecoveryBoot(); + bool IsMariko(); + +} diff --git a/include/stratosphere/spl/spl_types.hpp b/include/stratosphere/spl/spl_types.hpp new file mode 100644 index 00000000..84680ab5 --- /dev/null +++ b/include/stratosphere/spl/spl_types.hpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include "../results.hpp" + +namespace sts::spl { + + namespace smc { + + enum class FunctionId : u32 { + SetConfig = 0xC3000401, + GetConfig = 0xC3000002, + CheckStatus = 0xC3000003, + GetResult = 0xC3000404, + ExpMod = 0xC3000E05, + GenerateRandomBytes = 0xC3000006, + GenerateAesKek = 0xC3000007, + LoadAesKey = 0xC3000008, + CryptAes = 0xC3000009, + GenerateSpecificAesKey = 0xC300000A, + ComputeCmac = 0xC300040B, + ReEncryptRsaPrivateKey = 0xC300D60C, + DecryptOrImportRsaPrivateKey = 0xC300100D, + + SecureExpMod = 0xC300060F, + UnwrapTitleKey = 0xC3000610, + LoadTitleKey = 0xC3000011, + UnwrapCommonTitleKey = 0xC3000012, + + /* Deprecated functions. */ + ImportEsKey = 0xC300100C, + DecryptRsaPrivateKey = 0xC300100D, + ImportSecureExpModKey = 0xC300100E, + }; + + enum class Result { + Success = 0, + NotImplemented = 1, + InvalidArgument = 2, + InProgress = 3, + NoAsyncOperation = 4, + InvalidAsyncOperation = 5, + Blacklisted = 6, + + Max = 99, + }; + + inline ::Result ConvertResult(Result result) { + if (result == Result::Success) { + return ResultSuccess; + } + if (result < Result::Max) { + return MAKERESULT(Module_Spl, static_cast(result)); + } + return ResultSplUnknownSmcResult; + } + + enum class CipherMode { + CbcEncrypt = 0, + CbcDecrypt = 1, + Ctr = 2, + }; + + enum class DecryptOrImportMode { + DecryptRsaPrivateKey = 0, + ImportLotusKey = 1, + ImportEsKey = 2, + ImportSslKey = 3, + ImportDrmKey = 4, + }; + + enum class SecureExpModMode { + Lotus = 0, + Ssl = 1, + Drm = 2, + }; + + enum class EsKeyType { + TitleKey = 0, + ElicenseKey = 1, + }; + + struct AsyncOperationKey { + u64 value; + }; + } + + enum class HardwareType { + Icosa = 0, + Copper = 1, + Hoag = 2, + Iowa = 3, + }; + + struct BootReasonValue { + union { + struct { + u8 power_intr; + u8 rtc_intr; + u8 nv_erc; + u8 boot_reason; + }; + u32 value; + }; + }; + static_assert(sizeof(BootReasonValue) == sizeof(u32), "BootReasonValue definition!"); + #pragma pack(push, 1) + + struct AesKey { + union { + u8 data[AES_128_KEY_SIZE]; + u64 data64[AES_128_KEY_SIZE / sizeof(u64)]; + }; + }; + static_assert(alignof(AesKey) == alignof(u8), "AesKey definition!"); + + struct IvCtr { + union { + u8 data[AES_128_KEY_SIZE]; + u64 data64[AES_128_KEY_SIZE / sizeof(u64)]; + }; + }; + static_assert(alignof(IvCtr) == alignof(u8), "IvCtr definition!"); + + struct Cmac { + union { + u8 data[AES_128_KEY_SIZE]; + u64 data64[AES_128_KEY_SIZE / sizeof(u64)]; + }; + }; + static_assert(alignof(Cmac) == alignof(u8), "Cmac definition!"); + + struct AccessKey { + union { + u8 data[AES_128_KEY_SIZE]; + u64 data64[AES_128_KEY_SIZE / sizeof(u64)]; + }; + }; + static_assert(alignof(AccessKey) == alignof(u8), "AccessKey definition!"); + + struct KeySource { + union { + u8 data[AES_128_KEY_SIZE]; + u64 data64[AES_128_KEY_SIZE / sizeof(u64)]; + }; + }; + static_assert(alignof(AccessKey) == alignof(u8), "KeySource definition!"); + #pragma pack(pop) + +} diff --git a/source/spl/smc/spl_smc.cpp b/source/spl/smc/spl_smc.cpp new file mode 100644 index 00000000..89e35067 --- /dev/null +++ b/source/spl/smc/spl_smc.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +namespace sts::spl::smc { + + Result SetConfig(SplConfigItem which, const u64 *value, size_t num_qwords) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::SetConfig); + args.X[1] = which; + args.X[2] = 0; + for (size_t i = 0; i < std::min(size_t(4), num_qwords); i++) { + args.X[3 + i] = value[i]; + } + svcCallSecureMonitor(&args); + + return static_cast(args.X[0]); + } + + Result GetConfig(u64 *out, size_t num_qwords, SplConfigItem which) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::GetConfig); + args.X[1] = which; + svcCallSecureMonitor(&args); + + for (size_t i = 0; i < std::min(size_t(4), num_qwords); i++) { + out[i] = args.X[1 + i]; + } + return static_cast(args.X[0]); + } + + Result CheckStatus(Result *out, AsyncOperationKey op) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::CheckStatus); + args.X[1] = op.value; + svcCallSecureMonitor(&args); + + *out = static_cast(args.X[1]); + return static_cast(args.X[0]); + } + + Result GetResult(Result *out, void *out_buf, size_t out_buf_size, AsyncOperationKey op) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::GetResult); + args.X[1] = op.value; + args.X[2] = reinterpret_cast(out_buf); + args.X[3] = out_buf_size; + svcCallSecureMonitor(&args); + + *out = static_cast(args.X[1]); + return static_cast(args.X[0]); + } + + Result ExpMod(AsyncOperationKey *out_op, const void *base, const void *exp, size_t exp_size, const void *mod) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::ExpMod); + args.X[1] = reinterpret_cast(base); + args.X[2] = reinterpret_cast(exp); + args.X[3] = reinterpret_cast(mod); + args.X[4] = exp_size; + svcCallSecureMonitor(&args); + + out_op->value = args.X[1]; + return static_cast(args.X[0]); + } + + Result GenerateRandomBytes(void *out, size_t size) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::GenerateRandomBytes); + args.X[1] = size; + svcCallSecureMonitor(&args); + + if (args.X[0] == static_cast(Result::Success) && (size <= sizeof(args) - sizeof(args.X[0]))) { + std::memcpy(out, &args.X[1], size); + } + return static_cast(args.X[0]); + } + + Result GenerateAesKek(AccessKey *out, const KeySource &source, u32 generation, u32 option) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::GenerateAesKek); + args.X[1] = source.data64[0]; + args.X[2] = source.data64[1]; + args.X[3] = generation; + args.X[4] = option; + svcCallSecureMonitor(&args); + + out->data64[0] = args.X[1]; + out->data64[1] = args.X[2]; + return static_cast(args.X[0]); + } + + Result LoadAesKey(u32 keyslot, const AccessKey &access_key, const KeySource &source) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::LoadAesKey); + args.X[1] = keyslot; + args.X[2] = access_key.data64[0]; + args.X[3] = access_key.data64[1]; + args.X[4] = source.data64[0]; + args.X[5] = source.data64[1]; + svcCallSecureMonitor(&args); + + return static_cast(args.X[0]); + } + + Result CryptAes(AsyncOperationKey *out_op, u32 mode, const IvCtr &iv_ctr, u32 dst_addr, u32 src_addr, size_t size) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::CryptAes); + args.X[1] = mode; + args.X[2] = iv_ctr.data64[0]; + args.X[3] = iv_ctr.data64[1]; + args.X[4] = src_addr; + args.X[5] = dst_addr; + args.X[6] = size; + svcCallSecureMonitor(&args); + + out_op->value = args.X[1]; + return static_cast(args.X[0]); + } + + Result GenerateSpecificAesKey(AesKey *out_key, const KeySource &source, u32 generation, u32 which) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::GenerateSpecificAesKey); + args.X[1] = source.data64[0]; + args.X[2] = source.data64[1]; + args.X[3] = generation; + args.X[4] = which; + svcCallSecureMonitor(&args); + + out_key->data64[0] = args.X[1]; + out_key->data64[1] = args.X[2]; + return static_cast(args.X[0]); + } + + Result ComputeCmac(Cmac *out_mac, u32 keyslot, const void *data, size_t size) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::ComputeCmac); + args.X[1] = keyslot; + args.X[2] = reinterpret_cast(data); + args.X[3] = size; + svcCallSecureMonitor(&args); + + out_mac->data64[0] = args.X[1]; + out_mac->data64[1] = args.X[2]; + return static_cast(args.X[0]); + } + + Result ReEncryptRsaPrivateKey(void *data, size_t size, const AccessKey &access_key_dec, const KeySource &source_dec, const AccessKey &access_key_enc, const KeySource &source_enc, u32 option) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::ReEncryptRsaPrivateKey); + args.X[1] = reinterpret_cast(&access_key_dec); + args.X[2] = reinterpret_cast(&access_key_enc); + args.X[3] = option; + args.X[4] = reinterpret_cast(data); + args.X[5] = size; + args.X[6] = reinterpret_cast(&source_dec); + args.X[7] = reinterpret_cast(&source_enc); + svcCallSecureMonitor(&args); + + return static_cast(args.X[0]); + } + + Result DecryptOrImportRsaPrivateKey(void *data, size_t size, const AccessKey &access_key, const KeySource &source, DecryptOrImportMode mode) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::DecryptOrImportRsaPrivateKey); + args.X[1] = access_key.data64[0]; + args.X[2] = access_key.data64[1]; + args.X[3] = static_cast(mode); + args.X[4] = reinterpret_cast(data); + args.X[5] = size; + args.X[6] = source.data64[0]; + args.X[7] = source.data64[1]; + svcCallSecureMonitor(&args); + + return static_cast(args.X[0]); + } + + Result SecureExpMod(AsyncOperationKey *out_op, const void *base, const void *mod, SecureExpModMode mode) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::SecureExpMod); + args.X[1] = reinterpret_cast(base); + args.X[2] = reinterpret_cast(mod); + args.X[3] = static_cast(mode); + svcCallSecureMonitor(&args); + + out_op->value = args.X[1]; + return static_cast(args.X[0]); + } + + Result UnwrapTitleKey(AsyncOperationKey *out_op, const void *base, const void *mod, const void *label_digest, size_t label_digest_size, u32 option) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::UnwrapTitleKey); + args.X[1] = reinterpret_cast(base); + args.X[2] = reinterpret_cast(mod); + std::memset(&args.X[3], 0, 4 * sizeof(args.X[3])); + std::memcpy(&args.X[3], label_digest, std::min(size_t(4 * sizeof(args.X[3])), label_digest_size)); + args.X[7] = option; + svcCallSecureMonitor(&args); + + out_op->value = args.X[1]; + return static_cast(args.X[0]); + } + + Result LoadTitleKey(u32 keyslot, const AccessKey &access_key) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::LoadTitleKey); + args.X[1] = keyslot; + args.X[2] = access_key.data64[0]; + args.X[3] = access_key.data64[1]; + svcCallSecureMonitor(&args); + + return static_cast(args.X[0]); + } + + Result UnwrapCommonTitleKey(AccessKey *out, const KeySource &source, u32 generation) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::UnwrapCommonTitleKey); + args.X[1] = source.data64[0]; + args.X[2] = source.data64[1]; + args.X[3] = generation; + svcCallSecureMonitor(&args); + + out->data64[0] = args.X[1]; + out->data64[1] = args.X[2]; + return static_cast(args.X[0]); + } + + + /* Deprecated functions. */ + Result ImportEsKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::ImportEsKey); + args.X[1] = access_key.data64[0]; + args.X[2] = access_key.data64[1]; + args.X[3] = option; + args.X[4] = reinterpret_cast(data); + args.X[5] = size; + args.X[6] = source.data64[0]; + args.X[7] = source.data64[1]; + svcCallSecureMonitor(&args); + + return static_cast(args.X[0]); + } + + Result DecryptRsaPrivateKey(size_t *out_size, void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::DecryptRsaPrivateKey); + args.X[1] = access_key.data64[0]; + args.X[2] = access_key.data64[1]; + args.X[3] = option; + args.X[4] = reinterpret_cast(data); + args.X[5] = size; + args.X[6] = source.data64[0]; + args.X[7] = source.data64[1]; + svcCallSecureMonitor(&args); + + *out_size = static_cast(args.X[1]); + return static_cast(args.X[0]); + } + + Result ImportSecureExpModKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) { + SecmonArgs args; + + args.X[0] = static_cast(FunctionId::ImportSecureExpModKey); + args.X[1] = access_key.data64[0]; + args.X[2] = access_key.data64[1]; + args.X[3] = option; + args.X[4] = reinterpret_cast(data); + args.X[5] = size; + args.X[6] = source.data64[0]; + args.X[7] = source.data64[1]; + svcCallSecureMonitor(&args); + + return static_cast(args.X[0]); + } + +} diff --git a/source/spl/spl_api.cpp b/source/spl/spl_api.cpp new file mode 100644 index 00000000..9d0cb604 --- /dev/null +++ b/source/spl/spl_api.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +namespace sts::spl { + + HardwareType GetHardwareType() { + u64 out_val = 0; + R_ASSERT(splGetConfig(SplConfigItem_HardwareType, &out_val)); + return static_cast(out_val); + } + + bool IsRecoveryBoot() { + u64 val = 0; + R_ASSERT(splGetConfig(SplConfigItem_IsRecoveryBoot, &val)); + return val != 0; + } + + bool IsMariko() { + const auto hw_type = GetHardwareType(); + switch (hw_type) { + case HardwareType::Icosa: + case HardwareType::Copper: + return false; + case HardwareType::Hoag: + case HardwareType::Iowa: + return true; + default: + std::abort(); + } + } + +}