diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_game_card.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_game_card.hpp index 052a064bb..0b80a4f89 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_game_card.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_game_card.hpp @@ -38,7 +38,7 @@ namespace ams::fs { GameCardAttribute_DifferentRegionCupToTerraDeviceFlag = (1 << 3), GameCardAttribute_DifferentRegionCupToGlobalDeviceFlag = (1 << 4), - GameCardAttribute_HasHeaderSign2Flag = (1 << 7), + GameCardAttribute_HasCa10CertificateFlag = (1 << 7), }; enum class GameCardCompatibilityType : u8 { diff --git a/libraries/libstratosphere/include/stratosphere/gc/impl/gc_embedded_data_holder.hpp b/libraries/libstratosphere/include/stratosphere/gc/impl/gc_embedded_data_holder.hpp index 3dc274e28..5d2cbb483 100644 --- a/libraries/libstratosphere/include/stratosphere/gc/impl/gc_embedded_data_holder.hpp +++ b/libraries/libstratosphere/include/stratosphere/gc/impl/gc_embedded_data_holder.hpp @@ -40,8 +40,17 @@ namespace ams::gc::impl { static const void *s_ca10_modulus; static const void *s_ca10_certificate_modulus; static const void *s_card_header_key; + private: + static constinit inline u8 s_titlekey_keks[GcCrypto::GcTitleKeyKekIndexMax][GcCrypto::GcAesKeyLength] = {}; public: static Result SetLibraryEmbeddedKeys(bool is_dev = GcCrypto::CheckDevelopmentSpl()); + + static void SetLibraryTitleKeyKek(size_t kek_index, const void *kek, size_t kek_size) { + AMS_ASSERT(kek_index < GcCrypto::GcTitleKeyKekIndexMax); + AMS_ASSERT(kek_size == GcCrypto::GcAesKeyLength); + + std::memcpy(s_titlekey_keks[kek_index], kek, sizeof(s_titlekey_keks[kek_index])); + } private: static Result DecryptoEmbeddedKeys(ConcatenatedGcLibraryEmbeddedKeys *out, size_t out_size, bool is_dev = GcCrypto::CheckDevelopmentSpl()); }; diff --git a/libraries/libstratosphere/include/stratosphere/gc/impl/gc_gc_crypto.hpp b/libraries/libstratosphere/include/stratosphere/gc/impl/gc_gc_crypto.hpp index 725d7f3e7..9072e77e5 100644 --- a/libraries/libstratosphere/include/stratosphere/gc/impl/gc_gc_crypto.hpp +++ b/libraries/libstratosphere/include/stratosphere/gc/impl/gc_gc_crypto.hpp @@ -29,6 +29,7 @@ namespace ams::gc::impl { static constexpr size_t GcAesCbcIvLength = crypto::Aes128CbcEncryptor::IvSize; static constexpr size_t GcHmacKeyLength = 0x20; static constexpr size_t GcCvConstLength = 0x10; + static constexpr size_t GcTitleKeyKekIndexMax = 0x10; static constexpr size_t GcSha256HashLength = crypto::Sha256Generator::HashSize; public: static bool CheckDevelopmentSpl(); @@ -42,6 +43,8 @@ namespace ams::gc::impl { static Result VerifyT1CardCertificate(const void *cert_buffer, size_t cert_size); static Result VerifyCa10Certificate(const void *cert_buffer, size_t cert_size); + + static Result DecryptCardInitialData(void *dst, size_t dst_size, const void *initial_data, size_t data_size, size_t kek_index); }; } diff --git a/libraries/libstratosphere/include/stratosphere/gc/impl/gc_types.hpp b/libraries/libstratosphere/include/stratosphere/gc/impl/gc_types.hpp index 0aa3e78ed..98b56c8b2 100644 --- a/libraries/libstratosphere/include/stratosphere/gc/impl/gc_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/gc/impl/gc_types.hpp @@ -35,8 +35,22 @@ namespace ams::gc::impl { static_assert(util::is_pod::value); static_assert(sizeof(CardInitialData) == 0x200); + enum FwVersion : u8 { + FwVersion_ForDev = 0, + FwVersion_1_0_0 = 1, + FwVersion_4_0_0 = 2, + FwVersion_9_0_0 = 3, + FwVersion_11_0_0 = 4, + FwVersion_12_0_0 = 5, + }; + + enum KekIndex : u8 { + KekIndex_Version0 = 0, + KekIndex_VersionForDev = 1, + }; + struct CardHeaderKeyIndex { - using KekIndex = util::BitPack8::Field<0, 4, u8>; + using KekIndex = util::BitPack8::Field<0, 4, gc::impl::KekIndex>; using TitleKeyDecIndex = util::BitPack8::Field; static_assert(TitleKeyDecIndex::Next == BITSIZEOF(u8)); @@ -76,6 +90,11 @@ namespace ams::gc::impl { AccessControl1ClockRate_50MHz = 0x00A10010, }; + enum SelSec : u8 { + SelSec_T1 = 1, + SelSec_T2 = 2, + }; + struct CardHeader { static constexpr u32 Magic = util::FourCC<'H','E','A','D'>::Code; @@ -135,8 +154,9 @@ namespace ams::gc::impl { struct Ca10Certificate { u8 signature[crypto::Rsa2048Pkcs1Sha256Verifier::SignatureSize]; - u8 unk_100[0x200]; + u8 unk_100[0x30]; u8 modulus[crypto::Rsa2048Pkcs1Sha256Verifier::ModulusSize]; + u8 unk_230[0x1D0]; }; static_assert(util::is_pod::value); static_assert(sizeof(Ca10Certificate) == 0x400); diff --git a/libraries/libstratosphere/source/fs/fs_access_log.cpp b/libraries/libstratosphere/source/fs/fs_access_log.cpp index 3da798fd9..8427e0bb3 100644 --- a/libraries/libstratosphere/source/fs/fs_access_log.cpp +++ b/libraries/libstratosphere/source/fs/fs_access_log.cpp @@ -268,6 +268,68 @@ namespace ams::fs::impl { } } + template<> const char *IdString::ToString(gc::impl::MemoryCapacity id) { + switch (id) { + using enum gc::impl::MemoryCapacity; + case MemoryCapacity_1GB: return "1GB"; + case MemoryCapacity_2GB: return "2GB"; + case MemoryCapacity_4GB: return "4GB"; + case MemoryCapacity_8GB: return "8GB"; + case MemoryCapacity_16GB: return "16GB"; + case MemoryCapacity_32GB: return "32GB"; + default: return ToValueString(static_cast(id)); + } + } + + template<> const char *IdString::ToString(gc::impl::SelSec id) { + switch (id) { + using enum gc::impl::SelSec; + case SelSec_T1: return "T1"; + case SelSec_T2: return "T2"; + default: return ToValueString(static_cast(id)); + } + } + + template<> const char *IdString::ToString(gc::impl::KekIndex id) { + switch (id) { + using enum gc::impl::KekIndex; + case KekIndex_Version0: return "Version0"; + case KekIndex_VersionForDev: return "VersionForDev"; + default: return ToValueString(static_cast(id)); + } + } + + template<> const char *IdString::ToString(gc::impl::AccessControl1ClockRate id) { + switch (id) { + using enum gc::impl::AccessControl1ClockRate; + case AccessControl1ClockRate_25MHz: return "25 MHz"; + case AccessControl1ClockRate_50MHz: return "50 MHz"; + default: return ToValueString(static_cast(id)); + } + } + + template<> const char *IdString::ToString(gc::impl::FwVersion id) { + switch (id) { + using enum gc::impl::FwVersion; + case FwVersion_ForDev: return "ForDev"; + case FwVersion_1_0_0: return "1.0.0"; + case FwVersion_4_0_0: return "4.0.0"; + case FwVersion_9_0_0: return "9.0.0"; + case FwVersion_11_0_0: return "11.0.0"; + case FwVersion_12_0_0: return "12.0.0"; + default: return ToValueString(static_cast(id)); + } + } + + template<> const char *IdString::ToString(fs::GameCardCompatibilityType id) { + switch (id) { + using enum fs::GameCardCompatibilityType; + ADD_ENUM_CASE(Normal); + ADD_ENUM_CASE(Terra); + default: return ToValueString(static_cast(id)); + } + } + template<> const char *IdString::ToString(fssrv::impl::AccessControlBits::Bits id) { switch (id) { using enum fssrv::impl::AccessControlBits::Bits; diff --git a/libraries/libstratosphere/source/gc/impl/gc_gc_crypto.cpp b/libraries/libstratosphere/source/gc/impl/gc_gc_crypto.cpp index 8a77a8790..81b7fb602 100644 --- a/libraries/libstratosphere/source/gc/impl/gc_gc_crypto.cpp +++ b/libraries/libstratosphere/source/gc/impl/gc_gc_crypto.cpp @@ -132,4 +132,45 @@ namespace ams::gc::impl { R_SUCCEED(); } + Result GcCrypto::DecryptCardInitialData(void *dst, size_t dst_size, const void *initial_data, size_t data_size, size_t kek_index) { + /* Check pre-conditions. */ + R_UNLESS(data_size == sizeof(CardInitialData), fs::ResultGameCardPreconditionViolation()); + R_UNLESS(kek_index < GcTitleKeyKekIndexMax, fs::ResultGameCardPreconditionViolation()); + + /* Verify the kek is preset. */ + const void * const kek = EmbeddedDataHolder::s_titlekey_keks[kek_index]; + { + u8 zeros[GcAesKeyLength] = {}; + R_UNLESS(!crypto::IsSameBytes(kek, zeros, sizeof(zeros)), fs::ResultGameCardPreconditionViolation()); + } + + /*Generate the key. */ + u8 key[GcAesKeyLength]; + { + crypto::AesDecryptor128 aes; + aes.Initialize(kek, GcAesKeyLength); + aes.DecryptBlock(key, sizeof(key), initial_data, 0x10); + } + + /* Get data buffer as type. */ + const auto * const data = static_cast(initial_data); + R_UNLESS(dst_size == sizeof(data->payload.auth_data), fs::ResultGameCardPreconditionViolation()); + + /* Verify padding is all-zero. */ + bool any_nonzero = false; + for (size_t i = 0; i < util::size(data->padding); ++i) { + any_nonzero |= data->padding[i] != 0; + } + R_UNLESS(!any_nonzero, fs::ResultGameCardInitialNotFilledWithZero()); + + /* Decrypt the auth data. */ + u8 mac[sizeof(data->payload.auth_mac)]; + crypto::DecryptAes128Ccm(dst, dst_size, mac, sizeof(mac), key, GcAesKeyLength, data->payload.auth_nonce, sizeof(data->payload.auth_nonce), data->payload.auth_data, sizeof(data->payload.auth_data), nullptr, 0, sizeof(mac)); + + /* Check the mac. */ + R_UNLESS(crypto::IsSameBytes(mac, data->payload.auth_mac, sizeof(mac)), fs::ResultGameCardKekIndexMismatch()); + + R_SUCCEED(); + } + } diff --git a/libraries/libvapours/include/vapours/crypto.hpp b/libraries/libvapours/include/vapours/crypto.hpp index f96f624cb..63cfcb2e4 100644 --- a/libraries/libvapours/include/vapours/crypto.hpp +++ b/libraries/libvapours/include/vapours/crypto.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libvapours/include/vapours/crypto/crypto_aes_ccm_encryptor_decryptor.hpp b/libraries/libvapours/include/vapours/crypto/crypto_aes_ccm_encryptor_decryptor.hpp new file mode 100644 index 000000000..33d3b702d --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/crypto_aes_ccm_encryptor_decryptor.hpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 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 +#include +#include +#include +#include + +namespace ams::crypto { + + namespace impl { + + template typename _CcmImpl, typename _AesImpl> + class AesCcmCryptor { + NON_COPYABLE(AesCcmCryptor); + NON_MOVEABLE(AesCcmCryptor); + private: + using AesImpl = _AesImpl; + using CcmImpl = _CcmImpl; + public: + static constexpr size_t KeySize = CcmImpl::KeySize; + static constexpr size_t BlockSize = CcmImpl::BlockSize; + static constexpr size_t MaxMacSize = CcmImpl::MaxMacSize; + static constexpr size_t MaxNonceSize = CcmImpl::MaxNonceSize; + private: + AesImpl m_aes_impl; + CcmImpl m_ccm_impl; + public: + AesCcmCryptor() { /* ... */ } + + void Initialize(const void *key, size_t key_size, const void *nonce, size_t nonce_size, s64 aad_size, s64 data_size, size_t mac_size) { + AMS_ASSERT(key_size == KeySize); + AMS_ASSERT(nonce_size <= MaxNonceSize); + AMS_ASSERT(mac_size <= MaxMacSize); + + m_aes_impl.Initialize(key, key_size); + m_ccm_impl.Initialize(std::addressof(m_aes_impl), nonce, nonce_size, aad_size, data_size, mac_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_ccm_impl.Update(dst, dst_size, src, src_size); + } + + void UpdateAad(const void *aad, size_t aad_size) { + return m_ccm_impl.UpdateAad(aad, aad_size); + } + + void GetMac(void *dst, size_t dst_size) { + return m_ccm_impl.GetMac(dst, dst_size); + } + }; + + } + + using Aes128CcmEncryptor = impl::AesCcmCryptor; + using Aes128CcmDecryptor = impl::AesCcmCryptor; + + inline size_t EncryptAes128Ccm(void *dst, size_t dst_size, void *mac, size_t mac_buf_size, const void *key, size_t key_size, const void *nonce, size_t nonce_size, const void *src, size_t src_size, const void *aad, size_t aad_size, size_t mac_size) { + /* Create encryptor. */ + Aes128CcmEncryptor ccm; + ccm.Initialize(key, key_size, nonce, nonce_size, aad_size, src_size, mac_size); + + /* Process aad. */ + if (aad_size > 0) { + ccm.UpdateAad(aad, aad_size); + } + + /* Process data. */ + size_t processed = 0; + if (src_size > 0) { + processed = ccm.Update(dst, dst_size, src, src_size); + } + + /* Get mac. */ + ccm.GetMac(mac, mac_buf_size); + return processed; + } + + inline size_t DecryptAes128Ccm(void *dst, size_t dst_size, void *mac, size_t mac_buf_size, const void *key, size_t key_size, const void *nonce, size_t nonce_size, const void *src, size_t src_size, const void *aad, size_t aad_size, size_t mac_size) { + /* Create decryptor. */ + Aes128CcmDecryptor ccm; + ccm.Initialize(key, key_size, nonce, nonce_size, aad_size, src_size, mac_size); + + /* Process aad. */ + if (aad_size > 0) { + ccm.UpdateAad(aad, aad_size); + } + + /* Process data. */ + size_t processed = 0; + if (src_size > 0) { + processed = ccm.Update(dst, dst_size, src, src_size); + } + + /* Get mac. */ + ccm.GetMac(mac, mac_buf_size); + return processed; + } +} diff --git a/libraries/libvapours/include/vapours/crypto/crypto_ccm_decryptor.hpp b/libraries/libvapours/include/vapours/crypto/crypto_ccm_decryptor.hpp new file mode 100644 index 000000000..b8b9454df --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/crypto_ccm_decryptor.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 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 +#include +#include + +namespace ams::crypto { + + template + class CcmDecryptor { + NON_COPYABLE(CcmDecryptor); + NON_MOVEABLE(CcmDecryptor); + private: + using Impl = impl::CcmModeImpl; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t MaxMacSize = BlockSize; + static constexpr size_t MaxNonceSize = 13; + private: + Impl m_impl; + public: + CcmDecryptor() { /* ... */ } + + void Initialize(const BlockCipher *cipher, const void *nonce, size_t nonce_size, s64 aad_size, s64 data_size, size_t mac_size) { + m_impl.Initialize(cipher, nonce, nonce_size, aad_size, data_size, mac_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.UpdateDecryption(dst, dst_size, src, src_size); + } + + void UpdateAad(const void *aad, size_t aad_size) { + return m_impl.UpdateAad(aad, aad_size); + } + + void GetMac(void *dst, size_t dst_size) { + return m_impl.GetMac(dst, dst_size); + } + }; + +} diff --git a/libraries/libvapours/include/vapours/crypto/crypto_ccm_encryptor.hpp b/libraries/libvapours/include/vapours/crypto/crypto_ccm_encryptor.hpp new file mode 100644 index 000000000..20704ff64 --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/crypto_ccm_encryptor.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 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 +#include +#include + +namespace ams::crypto { + + template + class CcmEncryptor { + NON_COPYABLE(CcmEncryptor); + NON_MOVEABLE(CcmEncryptor); + private: + using Impl = impl::CcmModeImpl; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t MaxMacSize = BlockSize; + static constexpr size_t MaxNonceSize = 13; + private: + Impl m_impl; + public: + CcmEncryptor() { /* ... */ } + + void Initialize(const BlockCipher *cipher, const void *nonce, size_t nonce_size, s64 aad_size, s64 data_size, size_t mac_size) { + m_impl.Initialize(cipher, nonce, nonce_size, aad_size, data_size, mac_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.UpdateEncryption(dst, dst_size, src, src_size); + } + + void UpdateAad(const void *aad, size_t aad_size) { + return m_impl.UpdateAad(aad, aad_size); + } + + void GetMac(void *dst, size_t dst_size) { + return m_impl.GetMac(dst, dst_size); + } + }; + +} diff --git a/libraries/libvapours/include/vapours/crypto/impl/crypto_cbc_mac_impl.hpp b/libraries/libvapours/include/vapours/crypto/impl/crypto_cbc_mac_impl.hpp new file mode 100644 index 000000000..a8cfdce42 --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/impl/crypto_cbc_mac_impl.hpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 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 +#include +#include +#include + +namespace ams::crypto::impl { + + class CbcMacImpl { + NON_COPYABLE(CbcMacImpl); + NON_MOVEABLE(CbcMacImpl); + public: + static constexpr size_t BlockSize = 0x10; + private: + enum State { + State_None, + State_Initialized, + State_Done, + }; + private: + u8 m_mac[BlockSize]; + u8 m_buffer[BlockSize]; + size_t m_buffered_bytes; + const void *m_cipher_context; + void (*m_cipher_function)(void *dst, const void *src, const void *ctx); + State m_state; + public: + CbcMacImpl() : m_buffered_bytes(0), m_state(State_None) { /* ... */ } + + ~CbcMacImpl() { + ClearMemory(this, sizeof(*this)); + } + + template + void Initialize(const BlockCipher *block_cipher) { + static_assert(BlockCipher::BlockSize == BlockSize); + + /* Set our context. */ + m_cipher_context = block_cipher; + m_cipher_function = &EncryptBlockCallback; + m_buffered_bytes = 0; + + std::memset(m_mac, 0, sizeof(m_mac)); + + m_state = State_Initialized; + } + + template + void Update(const void *data, size_t size) { + this->UpdateGeneric(data, size); + } + + template + void ProcessBlocks(const void *data, size_t size) { + this->ProcessBlocksGeneric(data, size); + } + + size_t GetBlockSize() const { + return BlockSize; + } + + size_t GetBufferedDataSize() const { + return m_buffered_bytes; + } + + void UpdateGeneric(const void *data, size_t size); + void ProcessBlocksGeneric(const void *data, size_t num_blocks); + void ProcessPartialData(const void *data, size_t size); + void ProcessRemainingData(const void *data, size_t size); + + void GetMac(void *mac, size_t mac_size); + void MaskBufferedData(const void *data, size_t size); + private: + void ProcessBlock(const void *data); + + template + static void EncryptBlockCallback(void *dst, const void *src, const void *cipher) { + static_assert(BlockCipher::BlockSize == BlockSize); + static_cast(cipher)->EncryptBlock(dst, BlockCipher::BlockSize, src, BlockCipher::BlockSize); + } + }; + + template<> + void CbcMacImpl::Update(const void *data, size_t size); + +} diff --git a/libraries/libvapours/include/vapours/crypto/impl/crypto_ccm_mode_impl.hpp b/libraries/libvapours/include/vapours/crypto/impl/crypto_ccm_mode_impl.hpp new file mode 100644 index 000000000..6c3d7aa87 --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/impl/crypto_ccm_mode_impl.hpp @@ -0,0 +1,274 @@ +/* + * Copyright (c) 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 +#include +#include +#include +#include + +namespace ams::crypto::impl { + + template + class CcmModeImpl { + NON_COPYABLE(CcmModeImpl); + NON_MOVEABLE(CcmModeImpl); + public: + static constexpr size_t KeySize = BlockCipher::KeySize; + static constexpr size_t BlockSize = BlockCipher::BlockSize; + private: + enum State { + State_None, + State_ProcessingAad, + State_ProcessingData, + State_DataInputDone, + State_Done, + }; + private: + u8 m_mac[BlockSize]; + s64 m_given_data_size; + s64 m_given_aad_size; + size_t m_given_mac_size; + s64 m_processed_data_size; + s64 m_processed_aad_size; + State m_state; + CtrModeImpl m_ctr_mode_impl; + CbcMacImpl m_cbc_mac_impl; + public: + CcmModeImpl() : m_state(State_None) { /* ... */ } + + ~CcmModeImpl() { + ClearMemory(this, sizeof(*this)); + } + + void Initialize(const BlockCipher *cipher, const void *nonce, size_t nonce_size, s64 aad_size, s64 data_size, size_t mac_size) { + /* Check pre-conditions. */ + AMS_ASSERT(7 <= nonce_size && nonce_size <= 13); + AMS_ASSERT(4 <= mac_size && mac_size <= 16 && (mac_size % 2) == 0); + AMS_ASSERT(aad_size >= 0); + AMS_ASSERT(data_size >= 0); + if (nonce_size == 7) { + AMS_ASSERT(data_size <= std::numeric_limits::max()); + } else { + AMS_ASSERT(data_size < (INT64_C(1) << ((15 - nonce_size) * 8))); + } + + /* Set various size fields. */ + m_given_aad_size = aad_size; + m_given_data_size = data_size; + m_given_mac_size = mac_size; + m_processed_aad_size = 0; + m_processed_data_size = 0; + + /* Make the initial counter. */ + u8 tmp[BlockSize]; + MakeInitialCounter(tmp, nonce, nonce_size); + + /* Encrypt the block. */ + cipher->EncryptBlock(m_mac, BlockSize, tmp, BlockSize); + + /* Initialize our ctr mode impl. */ + m_ctr_mode_impl.Initialize(cipher, tmp, BlockSize); + m_ctr_mode_impl.IncrementCounter(); + + /* Make the header block. */ + MakeHeaderBlock(tmp, nonce, nonce_size, aad_size, data_size, mac_size); + + /* Initialize our cbc mac impl. */ + m_cbc_mac_impl.Initialize(cipher); + m_cbc_mac_impl.template Update(tmp, BlockSize); + + /* Process aad size block. */ + if (aad_size > 0) { + this->ProcessEncodedAadSize(); + m_state = State_ProcessingAad; + } else { + m_state = State_ProcessingData; + } + } + + void UpdateAad(const void *aad, size_t aad_size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_ProcessingAad); + AMS_ASSERT(m_processed_aad_size + static_cast(aad_size) <= m_given_aad_size); + + /* Update on the aad. */ + m_cbc_mac_impl.template Update(aad, aad_size); + m_processed_aad_size += aad_size; + + /* Check if we're done with aad. */ + if (m_processed_aad_size == m_given_aad_size) { + /* Pad the aad to block size. */ + this->ProcessPadding(); + + /* Update our state. */ + if (m_given_data_size > 0) { + m_state = State_ProcessingData; + } else { + m_state = State_DataInputDone; + } + } + } + + size_t UpdateEncryption(void *dst, size_t dst_size, const void *src, size_t src_size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_ProcessingData); + AMS_ASSERT(m_processed_data_size + static_cast(src_size) <= m_given_data_size); + AMS_ASSERT(dst_size >= src_size); + + /* Update mac on decrypted data. */ + m_cbc_mac_impl.template Update(src, src_size); + + /* Encrypt. */ + const size_t processed = m_ctr_mode_impl.Update(dst, dst_size, src, src_size); + m_processed_data_size += src_size; + + /* Check if we're done with data. */ + if (m_processed_data_size == m_given_data_size) { + /* Pad the data to block size. */ + this->ProcessPadding(); + + m_state = State_DataInputDone; + } + + return processed; + } + + size_t UpdateDecryption(void *dst, size_t dst_size, const void *src, size_t src_size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_ProcessingData); + AMS_ASSERT(m_processed_data_size + static_cast(src_size) <= m_given_data_size); + AMS_ASSERT(dst_size >= src_size); + + /* Decrypt. */ + const size_t processed = m_ctr_mode_impl.Update(dst, dst_size, src, src_size); + m_processed_data_size += src_size; + + /* Update mac on decrypted data. */ + m_cbc_mac_impl.template Update(dst, dst_size); + + /* Check if we're done with data. */ + if (m_processed_data_size == m_given_data_size) { + /* Pad the data to block size. */ + this->ProcessPadding(); + + m_state = State_DataInputDone; + } + + return processed; + } + + void GetMac(void *mac, size_t mac_size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_DataInputDone || m_state == State_Done); + AMS_ASSERT(mac_size >= m_given_mac_size); + AMS_UNUSED(mac_size); + + /* Generate the mac, if we haven't already. */ + if (m_state == State_DataInputDone) { + this->GenerateMac(); + m_state = State_Done; + } + + /* Copy out the mac. */ + std::memcpy(mac, m_mac, m_given_mac_size); + } + private: + void MakeInitialCounter(void *dst, const void *nonce, size_t nonce_size) { + /* Clear the counter. */ + u8 *ctr = static_cast(dst); + std::memset(ctr, 0, BlockSize); + + /* Set the nonce. */ + ctr[0] = (((BlockSize - 1 - nonce_size) & 0xFF) - 1) & 0x07; + std::memcpy(ctr + 1, nonce, nonce_size); + } + + void MakeHeaderBlock(void *dst, const void *nonce, size_t nonce_size, s64 aad_size, s64 data_size, size_t mac_size) { + /* Clear the block. */ + u8 *hdr = static_cast(dst); + std::memset(hdr, 0, BlockSize); + + /* Encode the flags. */ + hdr[0] = (((BlockSize - 1 - nonce_size) & 0xFF) - 1) & 0x07; + hdr[0] |= (((mac_size - 2) / 2) & 0x07) << 3; + hdr[0] |= (aad_size > 0) ? 0x40 : 0x00; + + /* Encode the data size. */ + for (size_t i = 0; i < sizeof(s64); ++i) { + hdr[BlockSize - 1 - i] = static_cast(data_size) >> (BITSIZEOF(u8) * i); + } + + /* Copy the nonce. */ + std::memcpy(hdr + 1, nonce, nonce_size); + } + + void ProcessEncodedAadSize() { + u8 encoded_aad[10]; + size_t encoded_aad_size; + + if (m_given_aad_size < ((1 << 16) - (1 << 8))) { + encoded_aad[0] = (m_given_aad_size >> (BITSIZEOF(u8) * 1)) & 0xFF; + encoded_aad[1] = (m_given_aad_size >> (BITSIZEOF(u8) * 0)) & 0xFF; + encoded_aad_size = 2; + } else if (m_given_aad_size <= 0xFFFFFFFFu) { + encoded_aad[0] = 0xFF; + encoded_aad[1] = 0xFE; + encoded_aad[2] = (m_given_aad_size >> (BITSIZEOF(u8) * 3)) & 0xFF; + encoded_aad[3] = (m_given_aad_size >> (BITSIZEOF(u8) * 2)) & 0xFF; + encoded_aad[4] = (m_given_aad_size >> (BITSIZEOF(u8) * 1)) & 0xFF; + encoded_aad[5] = (m_given_aad_size >> (BITSIZEOF(u8) * 0)) & 0xFF; + encoded_aad_size = 6; + } else { + encoded_aad[0] = 0xFF; + encoded_aad[1] = 0xFE; + encoded_aad[2] = (static_cast(m_given_aad_size) >> (BITSIZEOF(u8) * 7)) & 0xFF; + encoded_aad[3] = (static_cast(m_given_aad_size) >> (BITSIZEOF(u8) * 6)) & 0xFF; + encoded_aad[4] = (static_cast(m_given_aad_size) >> (BITSIZEOF(u8) * 5)) & 0xFF; + encoded_aad[5] = (static_cast(m_given_aad_size) >> (BITSIZEOF(u8) * 4)) & 0xFF; + encoded_aad[6] = (static_cast(m_given_aad_size) >> (BITSIZEOF(u8) * 3)) & 0xFF; + encoded_aad[7] = (static_cast(m_given_aad_size) >> (BITSIZEOF(u8) * 2)) & 0xFF; + encoded_aad[8] = (static_cast(m_given_aad_size) >> (BITSIZEOF(u8) * 1)) & 0xFF; + encoded_aad[9] = (static_cast(m_given_aad_size) >> (BITSIZEOF(u8) * 0)) & 0xFF; + encoded_aad_size = 10; + } + + m_cbc_mac_impl.template Update(encoded_aad, encoded_aad_size); + } + + void ProcessPadding() { + /* Process any remaining padding. */ + if (const auto buffered = m_cbc_mac_impl.GetBufferedDataSize(); buffered > 0) { + u8 zeros[BlockSize] = {}; + m_cbc_mac_impl.template Update(zeros, BlockSize - buffered); + } + } + + void GenerateMac() { + /* Get the cbc mac. */ + u8 tmp[BlockSize]; + m_cbc_mac_impl.GetMac(tmp, BlockSize); + + /* Xor into our mac. */ + for (size_t i = 0; i < BlockSize; ++i) { + m_mac[i] ^= tmp[i]; + } + } + }; + +} diff --git a/libraries/libvapours/include/vapours/results/fs_results.hpp b/libraries/libvapours/include/vapours/results/fs_results.hpp index 03ab62a57..25ba6769a 100644 --- a/libraries/libvapours/include/vapours/results/fs_results.hpp +++ b/libraries/libvapours/include/vapours/results/fs_results.hpp @@ -51,6 +51,11 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(GameCardPreconditionViolation, 2503); R_DEFINE_ERROR_RANGE(GameCardCardAccessFailure, 2530, 2559); + R_DEFINE_ERROR_RANGE(CameCardWrongCard, 2543, 2546); + R_DEFINE_ERROR_RESULT(GameCardInitialDataMismatch, 2544); + R_DEFINE_ERROR_RESULT(GameCardInitialNotFilledWithZero, 2545); + R_DEFINE_ERROR_RESULT(GameCardKekIndexMismatch, 2546); + R_DEFINE_ERROR_RESULT(GameCardInvalidCardHeader, 2554); R_DEFINE_ERROR_RESULT(GameCardInvalidT1CardCertificate, 2555); R_DEFINE_ERROR_RESULT(GameCardInvalidCa10Certificate, 2557); diff --git a/libraries/libvapours/source/crypto/impl/crypto_cbc_mac_impl.arch.generic.cpp b/libraries/libvapours/source/crypto/impl/crypto_cbc_mac_impl.arch.generic.cpp new file mode 100644 index 000000000..d561ff088 --- /dev/null +++ b/libraries/libvapours/source/crypto/impl/crypto_cbc_mac_impl.arch.generic.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 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 "crypto_update_impl.hpp" + +namespace ams::crypto::impl { + + void CbcMacImpl::UpdateGeneric(const void *data, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_Initialized); + + /* Update. */ + UpdateImpl(this, data, size); + } + + void CbcMacImpl::ProcessBlocksGeneric(const void *data, size_t num_blocks) { + /* If we have a block remaining, process it. */ + if (m_buffered_bytes == BlockSize) { + this->ProcessBlock(m_buffer); + m_buffered_bytes = 0; + } + + /* Process blocks. */ + const u8 *data8 = static_cast(data); + + u8 block[BlockSize]; + while ((--num_blocks) > 0) { + for (size_t i = 0; i < BlockSize; ++i) { + block[i] = data8[i] ^ m_mac[i]; + } + + m_cipher_function(m_mac, block, m_cipher_context); + + data8 += BlockSize; + } + + /* Process the last block. */ + std::memcpy(m_buffer, data8, BlockSize); + m_buffered_bytes = BlockSize; + } + + template<> + void CbcMacImpl::Update(const void *data, size_t size) { + this->UpdateGeneric(data, size); + } + + +} diff --git a/libraries/libvapours/source/crypto/impl/crypto_cbc_mac_impl.cpp b/libraries/libvapours/source/crypto/impl/crypto_cbc_mac_impl.cpp new file mode 100644 index 000000000..f4b047f6d --- /dev/null +++ b/libraries/libvapours/source/crypto/impl/crypto_cbc_mac_impl.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 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 + +namespace ams::crypto::impl { + + void CbcMacImpl::ProcessBlock(const void *data) { + /* Procses the block. */ + const u8 *data8 = static_cast(data); + + u8 block[BlockSize]; + for (size_t i = 0; i < BlockSize; ++i) { + block[i] = data8[i] ^ m_mac[i]; + } + + m_cipher_function(m_mac, block, m_cipher_context); + } + + void CbcMacImpl::ProcessPartialData(const void *data, size_t size) { + /* Copy in the data. */ + std::memcpy(m_buffer + m_buffered_bytes, data, size); + m_buffered_bytes += size; + } + + void CbcMacImpl::ProcessRemainingData(const void *data, size_t size) { + /* If we have a block remaining, process it. */ + if (m_buffered_bytes == BlockSize) { + this->ProcessBlock(m_buffer); + m_buffered_bytes = 0; + } + + /* Copy the remaining data. */ + std::memcpy(m_buffer, data, size); + m_buffered_bytes = size; + } + + void CbcMacImpl::GetMac(void *mac, size_t mac_size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_Initialized || m_state == State_Done); + AMS_ASSERT(mac_size >= BlockSize); + + /* Ensure we're done. */ + if (m_state == State_Initialized) { + if (m_buffered_bytes == BlockSize) { + this->ProcessBlock(m_buffer); + m_buffered_bytes = 0; + } + m_state = State_Done; + } + + /* Copy out the mac. */ + std::memcpy(mac, m_mac, sizeof(m_mac)); + } + + void CbcMacImpl::MaskBufferedData(const void *data, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_buffered_bytes == BlockSize); + AMS_ASSERT(size == BlockSize); + AMS_UNUSED(size); + + /* Mask the data. */ + for (size_t i = 0; i < BlockSize; ++i) { + m_buffer[i] ^= static_cast(data)[i]; + } + } + +}