mirror of
				https://github.com/Atmosphere-NX/Atmosphere.git
				synced 2025-11-04 12:51:17 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			638 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			638 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
 * 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 <http://www.gnu.org/licenses/>.
 | 
						|
 */
 | 
						|
#include <stratosphere.hpp>
 | 
						|
#include "amsmitm_fs_utils.hpp"
 | 
						|
#include "amsmitm_prodinfo_utils.hpp"
 | 
						|
 | 
						|
namespace ams::mitm {
 | 
						|
 | 
						|
    namespace {
 | 
						|
 | 
						|
        constexpr inline u16 Crc16InitialValue = 0x55AA;
 | 
						|
 | 
						|
        constexpr inline u16 Crc16Table[] = {
 | 
						|
            0x0000, 0xCC01, 0xD801, 0x1400,
 | 
						|
            0xF001, 0x3C00, 0x2800, 0xE401,
 | 
						|
            0xA001, 0x6C00, 0x7800, 0xB401,
 | 
						|
            0x5000, 0x9C01, 0x8801, 0x4400,
 | 
						|
        };
 | 
						|
 | 
						|
        u16 GetCrc16(const void *data, size_t size) {
 | 
						|
            AMS_ASSERT(data != nullptr);
 | 
						|
            AMS_ASSERT(size > 0);
 | 
						|
 | 
						|
            const u8 *src = static_cast<const u8 *>(data);
 | 
						|
 | 
						|
            u16 crc = Crc16InitialValue;
 | 
						|
 | 
						|
            u16 tmp = 0;
 | 
						|
            while ((size--) > 0) {
 | 
						|
                tmp = Crc16Table[crc & 0xF];
 | 
						|
                crc = ((crc >> 4) & 0x0FFF) ^ tmp ^ Crc16Table[*src & 0xF];
 | 
						|
                tmp = Crc16Table[crc & 0xF];
 | 
						|
                crc = ((crc >> 4) & 0x0FFF) ^ tmp ^ Crc16Table[(*(src++) >> 4) & 0xF];
 | 
						|
            }
 | 
						|
            return crc;
 | 
						|
        }
 | 
						|
 | 
						|
        bool IsBlank(const void *data, size_t size) {
 | 
						|
            AMS_ASSERT(data != nullptr);
 | 
						|
            AMS_ASSERT(size > 0);
 | 
						|
 | 
						|
            const u8 *src = static_cast<const u8 *>(data);
 | 
						|
            while ((size--) > 0) {
 | 
						|
                if (*(src++) != 0) {
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
 | 
						|
        constexpr inline u32 CalibrationMagic = util::FourCC<'C','A','L','0'>::Code;
 | 
						|
 | 
						|
        struct Sha256Hash {
 | 
						|
            u8 data[crypto::Sha256Generator::HashSize];
 | 
						|
        };
 | 
						|
 | 
						|
        struct CalibrationInfoHeader {
 | 
						|
            u32 magic;
 | 
						|
            u32 version;
 | 
						|
            u32 body_size;
 | 
						|
            u16 model;
 | 
						|
            u16 update_count;
 | 
						|
            u8  pad[0xE];
 | 
						|
            u16 crc;
 | 
						|
            Sha256Hash body_hash;
 | 
						|
        };
 | 
						|
        static_assert(sizeof(CalibrationInfoHeader) == 0x40);
 | 
						|
 | 
						|
        constexpr inline size_t CalibrationInfoBodySizeMax = CalibrationBinarySize - sizeof(CalibrationInfoHeader);
 | 
						|
 | 
						|
        struct CalibrationInfo {
 | 
						|
            CalibrationInfoHeader header;
 | 
						|
            u8 body[CalibrationInfoBodySizeMax];   /* TODO: CalibrationInfoBody body; */
 | 
						|
 | 
						|
            template<typename Block>
 | 
						|
            Block &GetBlock() {
 | 
						|
                static_assert(Block::Offset >= sizeof(CalibrationInfoHeader));
 | 
						|
                static_assert(Block::Offset < sizeof(CalibrationInfo));
 | 
						|
                static_assert(Block::Offset + Block::Size <= sizeof(CalibrationInfo));
 | 
						|
                return *static_cast<Block *>(static_cast<void *>(std::addressof(this->body[Block::Offset - sizeof(this->header)])));
 | 
						|
            }
 | 
						|
 | 
						|
            template<typename Block>
 | 
						|
            const Block &GetBlock() const {
 | 
						|
                static_assert(Block::Offset >= sizeof(CalibrationInfoHeader));
 | 
						|
                static_assert(Block::Offset < sizeof(CalibrationInfo));
 | 
						|
                static_assert(Block::Offset + Block::Size <= sizeof(CalibrationInfo));
 | 
						|
                return *static_cast<const Block *>(static_cast<const void *>(std::addressof(this->body[Block::Offset - sizeof(this->header)])));
 | 
						|
            }
 | 
						|
        };
 | 
						|
        static_assert(sizeof(CalibrationInfo) == CalibrationBinarySize);
 | 
						|
 | 
						|
        struct SecureCalibrationInfoBackup  {
 | 
						|
            CalibrationInfo info;
 | 
						|
            Sha256Hash hash;
 | 
						|
            u8 pad[SecureCalibrationBinaryBackupSize - sizeof(info) - sizeof(hash)];
 | 
						|
        };
 | 
						|
        static_assert(sizeof(SecureCalibrationInfoBackup) == SecureCalibrationBinaryBackupSize);
 | 
						|
 | 
						|
        bool IsValidSha256Hash(const Sha256Hash &hash, const void *data, size_t data_size) {
 | 
						|
            Sha256Hash calc_hash;
 | 
						|
            ON_SCOPE_EXIT { ::ams::crypto::ClearMemory(std::addressof(calc_hash), sizeof(calc_hash)); };
 | 
						|
 | 
						|
            ::ams::crypto::GenerateSha256(std::addressof(calc_hash), sizeof(calc_hash), data, data_size);
 | 
						|
            return ::ams::crypto::IsSameBytes(std::addressof(calc_hash), std::addressof(hash), sizeof(Sha256Hash));
 | 
						|
        }
 | 
						|
 | 
						|
        bool IsValid(const CalibrationInfoHeader &header) {
 | 
						|
            return header.magic == CalibrationMagic && GetCrc16(std::addressof(header), AMS_OFFSETOF(CalibrationInfoHeader, crc)) == header.crc;
 | 
						|
        }
 | 
						|
 | 
						|
        bool IsValid(const CalibrationInfoHeader &header, const void *body) {
 | 
						|
            return IsValid(header) && IsValidSha256Hash(header.body_hash, body, header.body_size);
 | 
						|
        }
 | 
						|
 | 
						|
        #define DEFINE_CALIBRATION_CRC_BLOCK(_TypeName, _Offset, _Size, _Decl, _MemberName) \
 | 
						|
        struct _TypeName {                                                                  \
 | 
						|
            static constexpr size_t Offset   = _Offset;                                     \
 | 
						|
            static constexpr size_t Size     = _Size;                                       \
 | 
						|
            static constexpr bool IsCrcBlock = true;                                        \
 | 
						|
            static constexpr bool IsShaBlock = false;                                       \
 | 
						|
            _Decl;                                                                          \
 | 
						|
            static_assert(Size >= sizeof(_MemberName) + sizeof(u16));                       \
 | 
						|
            u8 pad[Size - sizeof(_MemberName) - sizeof(u16)];                               \
 | 
						|
            u16 crc;                                                                        \
 | 
						|
        };                                                                                  \
 | 
						|
        static_assert(sizeof(_TypeName) == _TypeName::Size)
 | 
						|
 | 
						|
        #define DEFINE_CALIBRATION_SHA_BLOCK(_TypeName, _Offset, _Size, _Decl, _MemberName) \
 | 
						|
        struct _TypeName {                                                                  \
 | 
						|
            static constexpr size_t Offset = _Offset;                                       \
 | 
						|
            static constexpr size_t Size   = _Size;                                         \
 | 
						|
            static constexpr bool IsCrcBlock = false;                                       \
 | 
						|
            static constexpr bool IsShaBlock = true;                                        \
 | 
						|
            _Decl;                                                                          \
 | 
						|
            static_assert(Size == sizeof(_MemberName) + sizeof(Sha256Hash));                \
 | 
						|
            Sha256Hash sha256_hash;                                                         \
 | 
						|
        };                                                                                  \
 | 
						|
        static_assert(sizeof(_TypeName) == _TypeName::Size)
 | 
						|
 | 
						|
        DEFINE_CALIBRATION_CRC_BLOCK(SerialNumberBlock,                     0x0250, 0x020, ::ams::settings::factory::SerialNumber serial_number, serial_number);
 | 
						|
        DEFINE_CALIBRATION_CRC_BLOCK(EccB233DeviceCertificateBlock,         0x0480, 0x190, ::ams::settings::factory::EccB233DeviceCertificate device_certificate, device_certificate);
 | 
						|
        DEFINE_CALIBRATION_CRC_BLOCK(SslKeyBlock,                           0x09B0, 0x120, u8 ssl_key[0x110], ssl_key);
 | 
						|
        DEFINE_CALIBRATION_CRC_BLOCK(SslCertificateSizeBlock,               0x0AD0, 0x010, u64 ssl_certificate_size, ssl_certificate_size);
 | 
						|
        DEFINE_CALIBRATION_SHA_BLOCK(SslCertificateBlock,                   0x0AE0, 0x820, u8 ssl_certificate[0x800], ssl_certificate);
 | 
						|
        DEFINE_CALIBRATION_CRC_BLOCK(EcqvEcdsaAmiiboRootCertificateBlock,   0x35A0, 0x080, u8 data[0x70], data);
 | 
						|
        DEFINE_CALIBRATION_CRC_BLOCK(EcqvBlsAmiiboRootCertificateBlock,     0x36A0, 0x0A0, u8 data[0x90], data);
 | 
						|
        DEFINE_CALIBRATION_CRC_BLOCK(ExtendedSslKeyBlock,                   0x3AE0, 0x140, u8 ssl_key[0x134], ssl_key);
 | 
						|
        DEFINE_CALIBRATION_CRC_BLOCK(Rsa2048DeviceKeyBlock,                 0x3D70, 0x250, u8 device_key[0x240], device_key);
 | 
						|
        DEFINE_CALIBRATION_CRC_BLOCK(Rsa2048DeviceCertificateBlock,         0x3FC0, 0x250, ::ams::settings::factory::Rsa2048DeviceCertificate device_certificate, device_certificate);
 | 
						|
 | 
						|
        #undef DEFINE_CALIBRATION_CRC_BLOCK
 | 
						|
        #undef DEFINE_CALIBRATION_SHA_BLOCK
 | 
						|
 | 
						|
        constexpr inline const char BlankSerialNumberString[] = "XAW00000000000";
 | 
						|
 | 
						|
        template<typename Block>
 | 
						|
        void Blank(Block &block) {
 | 
						|
            if constexpr (std::is_same<Block, SerialNumberBlock>::value) {
 | 
						|
                static_assert(sizeof(BlankSerialNumberString) <= sizeof(SerialNumberBlock::serial_number));
 | 
						|
                std::memset(std::addressof(block), 0, Block::Size - sizeof(block.crc));
 | 
						|
                std::memcpy(block.serial_number.str, BlankSerialNumberString, sizeof(BlankSerialNumberString));
 | 
						|
                block.crc = GetCrc16(std::addressof(block), Block::Size - sizeof(block.crc));
 | 
						|
            } else if constexpr (std::is_same<Block, SslCertificateBlock>::value) {
 | 
						|
                std::memset(std::addressof(block), 0, sizeof(block.ssl_certificate));
 | 
						|
            } else if constexpr (Block::IsCrcBlock) {
 | 
						|
                std::memset(std::addressof(block), 0, Block::Size - sizeof(block.crc));
 | 
						|
                block.crc = GetCrc16(std::addressof(block), Block::Size - sizeof(block.crc));
 | 
						|
            } else {
 | 
						|
                static_assert(Block::IsShaBlock);
 | 
						|
                std::memset(std::addressof(block), 0, Block::Size);
 | 
						|
                ::ams::crypto::GenerateSha256(std::addressof(block.sha256_hash), sizeof(block.sha256_hash), std::addressof(block), Block::Size - sizeof(block.sha256_hash));
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        template<typename Block>
 | 
						|
        bool IsBlank(const Block &block) {
 | 
						|
            if constexpr (std::is_same<Block, SerialNumberBlock>::value) {
 | 
						|
                static_assert(sizeof(BlankSerialNumberString) <= sizeof(SerialNumberBlock::serial_number));
 | 
						|
                return std::memcmp(block.serial_number.str, BlankSerialNumberString, sizeof(BlankSerialNumberString) - 1) == 0 || IsBlank(std::addressof(block), Block::Size - sizeof(block.crc));
 | 
						|
            } else if constexpr (Block::IsCrcBlock) {
 | 
						|
                return IsBlank(std::addressof(block), Block::Size - sizeof(block.crc));
 | 
						|
            } else {
 | 
						|
                return IsBlank(std::addressof(block), Block::Size - sizeof(block.sha256_hash));
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        template<typename Block>
 | 
						|
        bool IsValid(const Block &block, size_t size = 0) {
 | 
						|
            if constexpr (Block::IsCrcBlock) {
 | 
						|
                return GetCrc16(std::addressof(block), Block::Size - sizeof(block.crc)) == block.crc;
 | 
						|
            } else {
 | 
						|
                static_assert(Block::IsShaBlock);
 | 
						|
                return IsValidSha256Hash(block.sha256_hash, std::addressof(block), size != 0 ? size : Block::Size - sizeof(block.sha256_hash));
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
        void Blank(CalibrationInfo &info) {
 | 
						|
            /* Set header. */
 | 
						|
            info.header.magic       = CalibrationMagic;
 | 
						|
            info.header.body_size   = sizeof(info.body);
 | 
						|
            info.header.crc         = GetCrc16(std::addressof(info.header), AMS_OFFSETOF(CalibrationInfoHeader, crc));
 | 
						|
 | 
						|
            /* Set blocks. */
 | 
						|
            Blank(info.GetBlock<SerialNumberBlock>());
 | 
						|
            Blank(info.GetBlock<SslCertificateSizeBlock>());
 | 
						|
            Blank(info.GetBlock<SslCertificateBlock>());
 | 
						|
            Blank(info.GetBlock<EcqvEcdsaAmiiboRootCertificateBlock>());
 | 
						|
            Blank(info.GetBlock<EcqvBlsAmiiboRootCertificateBlock>());
 | 
						|
            Blank(info.GetBlock<ExtendedSslKeyBlock>());
 | 
						|
 | 
						|
            /* Set header hash. */
 | 
						|
            crypto::GenerateSha256(std::addressof(info.header.body_hash), sizeof(info.header.body_hash), std::addressof(info.body), sizeof(info.body));
 | 
						|
        }
 | 
						|
 | 
						|
        bool IsValidHeader(const CalibrationInfo &cal) {
 | 
						|
            return IsValid(cal.header) && cal.header.body_size <= CalibrationInfoBodySizeMax && IsValid(cal.header, cal.body);
 | 
						|
        }
 | 
						|
 | 
						|
        bool IsValidSerialNumber(const char *sn) {
 | 
						|
            for (size_t i = 0; i < std::strlen(sn); i++) {
 | 
						|
                if (!std::isalnum(static_cast<unsigned char>(sn[i]))) {
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
 | 
						|
        void GetSerialNumber(char *dst, const CalibrationInfo &info) {
 | 
						|
            std::memcpy(dst, std::addressof(info.GetBlock<SerialNumberBlock>()), sizeof(info.GetBlock<SerialNumberBlock>().serial_number));
 | 
						|
            dst[sizeof(info.GetBlock<SerialNumberBlock>().serial_number) + 1] = '\x00';
 | 
						|
        }
 | 
						|
 | 
						|
        bool IsValidSerialNumber(const CalibrationInfo &cal) {
 | 
						|
            char sn[0x20] = {};
 | 
						|
            ON_SCOPE_EXIT { std::memset(sn, 0, sizeof(sn)); };
 | 
						|
 | 
						|
            GetSerialNumber(sn, cal);
 | 
						|
            return IsValidSerialNumber(sn);
 | 
						|
        }
 | 
						|
 | 
						|
        bool IsValid(const CalibrationInfo &cal) {
 | 
						|
            return IsValidHeader(cal)                                                                                                          &&
 | 
						|
                   IsValid(cal.GetBlock<SerialNumberBlock>())                                                                                  &&
 | 
						|
                   IsValid(cal.GetBlock<EccB233DeviceCertificateBlock>())                                                                      &&
 | 
						|
                   IsValid(cal.GetBlock<SslKeyBlock>())                                                                                        &&
 | 
						|
                   IsValid(cal.GetBlock<SslCertificateSizeBlock>())                                                                            &&
 | 
						|
                   cal.GetBlock<SslCertificateSizeBlock>().ssl_certificate_size <= sizeof(cal.GetBlock<SslCertificateBlock>().ssl_certificate) &&
 | 
						|
                   IsValid(cal.GetBlock<SslCertificateBlock>(), cal.GetBlock<SslCertificateSizeBlock>().ssl_certificate_size)                  &&
 | 
						|
                   IsValid(cal.GetBlock<EcqvEcdsaAmiiboRootCertificateBlock>())                                                                &&
 | 
						|
                   IsValid(cal.GetBlock<EcqvBlsAmiiboRootCertificateBlock>())                                                                  &&
 | 
						|
                   IsValid(cal.GetBlock<ExtendedSslKeyBlock>())                                                                                &&
 | 
						|
                   IsValidSerialNumber(cal);
 | 
						|
        }
 | 
						|
 | 
						|
        bool ContainsCorrectDeviceId(const EccB233DeviceCertificateBlock &block, u64 device_id) {
 | 
						|
            static constexpr size_t DeviceIdOffset = 0xC6;
 | 
						|
            char found_device_id_str[sizeof("0011223344556677")] = {};
 | 
						|
            ON_SCOPE_EXIT { std::memset(found_device_id_str, 0, sizeof(found_device_id_str)); };
 | 
						|
            std::memcpy(found_device_id_str, std::addressof(block.device_certificate.data[DeviceIdOffset]), sizeof(found_device_id_str) - 1);
 | 
						|
 | 
						|
            static constexpr u64 DeviceIdLowMask = 0x00FFFFFFFFFFFFFFul;
 | 
						|
 | 
						|
            return (std::strtoul(found_device_id_str, nullptr, 16) & DeviceIdLowMask) == (device_id & DeviceIdLowMask);
 | 
						|
        }
 | 
						|
 | 
						|
        bool ContainsCorrectDeviceId(const CalibrationInfo &cal) {
 | 
						|
            return ContainsCorrectDeviceId(cal.GetBlock<EccB233DeviceCertificateBlock>(), exosphere::GetDeviceId());
 | 
						|
        }
 | 
						|
 | 
						|
        bool IsValidForSecureBackup(const CalibrationInfo &cal) {
 | 
						|
            return IsValid(cal) && ContainsCorrectDeviceId(cal);
 | 
						|
        }
 | 
						|
 | 
						|
        bool IsBlank(const CalibrationInfo &cal) {
 | 
						|
            return IsBlank(cal.GetBlock<SerialNumberBlock>())                   ||
 | 
						|
                   IsBlank(cal.GetBlock<SslCertificateSizeBlock>())             ||
 | 
						|
                   IsBlank(cal.GetBlock<SslCertificateBlock>())                 ||
 | 
						|
                   IsBlank(cal.GetBlock<EcqvEcdsaAmiiboRootCertificateBlock>()) ||
 | 
						|
                   IsBlank(cal.GetBlock<EcqvBlsAmiiboRootCertificateBlock>())   ||
 | 
						|
                   IsBlank(cal.GetBlock<ExtendedSslKeyBlock>());
 | 
						|
        }
 | 
						|
 | 
						|
        void ReadStorageCalibrationBinary(CalibrationInfo *out) {
 | 
						|
            FsStorage calibration_binary_storage;
 | 
						|
            R_ABORT_UNLESS(fsOpenBisStorage(std::addressof(calibration_binary_storage), FsBisPartitionId_CalibrationBinary));
 | 
						|
            ON_SCOPE_EXIT { fsStorageClose(std::addressof(calibration_binary_storage)); };
 | 
						|
 | 
						|
            R_ABORT_UNLESS(fsStorageRead(std::addressof(calibration_binary_storage), 0, out, sizeof(*out)));
 | 
						|
        }
 | 
						|
 | 
						|
        constexpr inline const u8 SecureCalibrationBinaryBackupIv[crypto::Aes128CtrDecryptor::IvSize] = {};
 | 
						|
 | 
						|
        void ReadStorageEncryptedSecureCalibrationBinaryBackupUnsafe(SecureCalibrationInfoBackup *out) {
 | 
						|
            FsStorage calibration_binary_storage;
 | 
						|
            R_ABORT_UNLESS(fsOpenBisStorage(std::addressof(calibration_binary_storage), FsBisPartitionId_CalibrationBinary));
 | 
						|
            ON_SCOPE_EXIT { fsStorageClose(std::addressof(calibration_binary_storage)); };
 | 
						|
 | 
						|
            R_ABORT_UNLESS(fsStorageRead(std::addressof(calibration_binary_storage), SecureCalibrationInfoBackupOffset, out, sizeof(*out)));
 | 
						|
        }
 | 
						|
 | 
						|
        void WriteStorageEncryptedSecureCalibrationBinaryBackupUnsafe(const SecureCalibrationInfoBackup *src) {
 | 
						|
            FsStorage calibration_binary_storage;
 | 
						|
            R_ABORT_UNLESS(fsOpenBisStorage(std::addressof(calibration_binary_storage), FsBisPartitionId_CalibrationBinary));
 | 
						|
            ON_SCOPE_EXIT { fsStorageClose(std::addressof(calibration_binary_storage)); };
 | 
						|
 | 
						|
            R_ABORT_UNLESS(fsStorageWrite(std::addressof(calibration_binary_storage), SecureCalibrationInfoBackupOffset, src, sizeof(*src)));
 | 
						|
        }
 | 
						|
 | 
						|
        void GenerateSecureCalibrationBinaryBackupKey(void *dst, size_t dst_size) {
 | 
						|
            static constexpr const u8 SecureCalibrationBinaryBackupKeySource[crypto::Aes128CtrDecryptor::KeySize] = { '|', '-', 'A', 'M', 'S', '-', 'C', 'A', 'L', '0', '-', 'K', 'E', 'Y', '-', '|' };
 | 
						|
            spl::AccessKey access_key;
 | 
						|
            ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(access_key), sizeof(access_key)); };
 | 
						|
 | 
						|
            /* Generate a personalized kek. */
 | 
						|
            R_ABORT_UNLESS(spl::GenerateAesKek(std::addressof(access_key), SecureCalibrationBinaryBackupKeySource, sizeof(SecureCalibrationBinaryBackupKeySource), 0, 1));
 | 
						|
 | 
						|
            /* Generate a personalized key. */
 | 
						|
            R_ABORT_UNLESS(spl::GenerateAesKey(dst, dst_size, access_key, SecureCalibrationBinaryBackupKeySource, sizeof(SecureCalibrationBinaryBackupKeySource)));
 | 
						|
        }
 | 
						|
 | 
						|
        bool ReadStorageSecureCalibrationBinaryBackup(SecureCalibrationInfoBackup *out) {
 | 
						|
            /* Read the data. */
 | 
						|
            ReadStorageEncryptedSecureCalibrationBinaryBackupUnsafe(out);
 | 
						|
 | 
						|
            /* Don't leak any data unless we validate. */
 | 
						|
            auto clear_guard = SCOPE_GUARD { std::memset(out, 0, sizeof(*out)); };
 | 
						|
 | 
						|
            {
 | 
						|
                /* Create a buffer to hold our key. */
 | 
						|
                u8 key[crypto::Aes128CtrDecryptor::KeySize];
 | 
						|
                ON_SCOPE_EXIT { crypto::ClearMemory(key, sizeof(key)); };
 | 
						|
 | 
						|
                /* Generate the key. */
 | 
						|
                GenerateSecureCalibrationBinaryBackupKey(key, sizeof(key));
 | 
						|
 | 
						|
                /* Decrypt the data in place. */
 | 
						|
                crypto::DecryptAes128Ctr(out, sizeof(*out), key, sizeof(key), SecureCalibrationBinaryBackupIv, sizeof(SecureCalibrationBinaryBackupIv), out, sizeof(*out));
 | 
						|
            }
 | 
						|
 | 
						|
            /* Generate a hash for the data. */
 | 
						|
            if (!IsValidSha256Hash(out->hash, std::addressof(out->info), sizeof(out->info))) {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            /* Validate the backup. */
 | 
						|
            if (!IsValidForSecureBackup(out->info)) {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            /* Our backup is valid. */
 | 
						|
            clear_guard.Cancel();
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
 | 
						|
        void WriteStorageSecureCalibrationBinaryBackup(SecureCalibrationInfoBackup *src) {
 | 
						|
            /* Clear the input once we've written it. */
 | 
						|
            ON_SCOPE_EXIT { std::memset(src, 0, sizeof(*src)); };
 | 
						|
 | 
						|
            /* Ensure that the input is valid. */
 | 
						|
            AMS_ABORT_UNLESS(IsValidForSecureBackup(src->info));
 | 
						|
 | 
						|
            /* Set the Sha256 hash. */
 | 
						|
            crypto::GenerateSha256(std::addressof(src->hash), sizeof(src->hash), std::addressof(src->info), sizeof(src->info));
 | 
						|
 | 
						|
            /* Validate the hash. */
 | 
						|
            AMS_ABORT_UNLESS(IsValidSha256Hash(src->hash, std::addressof(src->info), sizeof(src->info)));
 | 
						|
 | 
						|
            /* Encrypt the data. */
 | 
						|
            {
 | 
						|
                /* Create a buffer to hold our key. */
 | 
						|
                u8 key[crypto::Aes128CtrDecryptor::KeySize];
 | 
						|
                ON_SCOPE_EXIT { crypto::ClearMemory(key, sizeof(key)); };
 | 
						|
 | 
						|
                /* Generate the key. */
 | 
						|
                GenerateSecureCalibrationBinaryBackupKey(key, sizeof(key));
 | 
						|
 | 
						|
                /* Encrypt the data in place. */
 | 
						|
                crypto::EncryptAes128Ctr(src, sizeof(*src), key, sizeof(key), SecureCalibrationBinaryBackupIv, sizeof(SecureCalibrationBinaryBackupIv), src, sizeof(*src));
 | 
						|
            }
 | 
						|
 | 
						|
            /* Write the encrypted data. */
 | 
						|
            WriteStorageEncryptedSecureCalibrationBinaryBackupUnsafe(src);
 | 
						|
        }
 | 
						|
 | 
						|
        void GetBackupFileName(char *dst, size_t dst_size, const CalibrationInfo &info) {
 | 
						|
            char sn[0x20] = {};
 | 
						|
            ON_SCOPE_EXIT { std::memset(sn, 0, sizeof(sn)); };
 | 
						|
 | 
						|
 | 
						|
            if (IsValidForSecureBackup(info)) {
 | 
						|
                GetSerialNumber(sn, info);
 | 
						|
                util::SNPrintf(dst, dst_size, "automatic_backups/%s_PRODINFO.bin", sn);
 | 
						|
            } else {
 | 
						|
                Sha256Hash hash;
 | 
						|
                crypto::GenerateSha256(std::addressof(hash), sizeof(hash), std::addressof(info), sizeof(info));
 | 
						|
                ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(hash), sizeof(hash)); };
 | 
						|
 | 
						|
                if (IsValid(info)) {
 | 
						|
                    if (IsBlank(info)) {
 | 
						|
                        util::SNPrintf(dst, dst_size, "automatic_backups/BLANK_PRODINFO_%02X%02X%02X%02X.bin", hash.data[0], hash.data[1], hash.data[2], hash.data[3]);
 | 
						|
                    } else {
 | 
						|
                        GetSerialNumber(sn, info);
 | 
						|
                        util::SNPrintf(dst, dst_size, "automatic_backups/%s_PRODINFO_%02X%02X%02X%02X.bin", sn, hash.data[0], hash.data[1], hash.data[2], hash.data[3]);
 | 
						|
                    }
 | 
						|
                } else {
 | 
						|
                    util::SNPrintf(dst, dst_size, "automatic_backups/INVALID_PRODINFO_%02X%02X%02X%02X.bin", hash.data[0], hash.data[1], hash.data[2], hash.data[3]);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        void SafeRead(ams::fs::fsa::IFile *file, s64 offset, void *dst, size_t size) {
 | 
						|
            size_t read_size = 0;
 | 
						|
            R_ABORT_UNLESS(file->Read(std::addressof(read_size), offset, dst, size));
 | 
						|
            AMS_ABORT_UNLESS(read_size == size);
 | 
						|
        }
 | 
						|
 | 
						|
        alignas(os::MemoryPageSize) CalibrationInfo g_temp_calibration_info = {};
 | 
						|
 | 
						|
        void SaveProdInfoBackup(util::optional<ams::fs::FileStorage> *dst, const CalibrationInfo &info) {
 | 
						|
            char backup_fn[0x100];
 | 
						|
            GetBackupFileName(backup_fn, sizeof(backup_fn), info);
 | 
						|
 | 
						|
            /* Create the file, in case it does not exist. */
 | 
						|
            mitm::fs::CreateAtmosphereSdFile(backup_fn, sizeof(CalibrationInfo), ams::fs::CreateOption_None);
 | 
						|
 | 
						|
            /* Open the file. */
 | 
						|
            FsFile libnx_file;
 | 
						|
            R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(libnx_file), backup_fn, ams::fs::OpenMode_ReadWrite));
 | 
						|
 | 
						|
            /* Create our accessor. */
 | 
						|
            std::unique_ptr<ams::fs::fsa::IFile> file = std::make_unique<ams::fs::RemoteFile>(libnx_file);
 | 
						|
            AMS_ABORT_UNLESS(file != nullptr);
 | 
						|
 | 
						|
            /* Check if we're valid already. */
 | 
						|
            bool valid = false;
 | 
						|
            s64 size;
 | 
						|
            R_ABORT_UNLESS(file->GetSize(std::addressof(size)));
 | 
						|
            if (size == sizeof(CalibrationInfo)) {
 | 
						|
                SafeRead(file.get(), 0, std::addressof(g_temp_calibration_info), sizeof(g_temp_calibration_info));
 | 
						|
                ON_SCOPE_EXIT { std::memset(std::addressof(g_temp_calibration_info), 0, sizeof(g_temp_calibration_info)); };
 | 
						|
 | 
						|
                if (std::memcmp(std::addressof(info), std::addressof(g_temp_calibration_info), sizeof(CalibrationInfo)) == 0) {
 | 
						|
                    valid = true;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            /* If we're not valid, we need to save. */
 | 
						|
            if (!valid) {
 | 
						|
                R_ABORT_UNLESS(file->Write(0, std::addressof(info), sizeof(info), ams::fs::WriteOption::Flush));
 | 
						|
            }
 | 
						|
 | 
						|
            /* Save our storage to output. */
 | 
						|
            if (dst != nullptr) {
 | 
						|
                dst->emplace(std::move(file));
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        void GetRandomEntropy(Sha256Hash *dst) {
 | 
						|
            AMS_ASSERT(dst != nullptr);
 | 
						|
 | 
						|
            u64 data_buffer[3] = {};
 | 
						|
            ON_SCOPE_EXIT { crypto::ClearMemory(data_buffer, sizeof(data_buffer)); };
 | 
						|
 | 
						|
            data_buffer[0] = os::GetSystemTick().GetInt64Value();
 | 
						|
            R_ABORT_UNLESS(svc::GetInfo(data_buffer + 1, svc::InfoType_AliasRegionAddress, svc::PseudoHandle::CurrentProcess, 0));
 | 
						|
            if (hos::GetVersion() >= hos::Version_2_0_0) {
 | 
						|
                R_ABORT_UNLESS(svc::GetInfo(data_buffer + 2, svc::InfoType_RandomEntropy, svc::InvalidHandle, (data_buffer[0] ^ (data_buffer[1] >> 24)) & 3));
 | 
						|
            } else {
 | 
						|
                data_buffer[2] = os::GetSystemTick().GetInt64Value();
 | 
						|
            }
 | 
						|
 | 
						|
            return crypto::GenerateSha256(dst, sizeof(*dst), data_buffer, sizeof(data_buffer));
 | 
						|
        }
 | 
						|
 | 
						|
        void FillWithGarbage(void *dst, size_t dst_size) {
 | 
						|
            /* Get random entropy. */
 | 
						|
            Sha256Hash entropy;
 | 
						|
            ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(entropy), sizeof(entropy)); };
 | 
						|
            GetRandomEntropy(std::addressof(entropy));
 | 
						|
 | 
						|
            /* Clear dst. */
 | 
						|
            std::memset(dst, 0xCC, dst_size);
 | 
						|
 | 
						|
            /* Encrypt dst. */
 | 
						|
            static_assert(sizeof(entropy) == crypto::Aes128CtrEncryptor::KeySize + crypto::Aes128CtrEncryptor::IvSize);
 | 
						|
            crypto::EncryptAes128Ctr(dst, dst_size, entropy.data, crypto::Aes128CtrEncryptor::KeySize, entropy.data + crypto::Aes128CtrEncryptor::KeySize, crypto::Aes128CtrEncryptor::IvSize, dst, dst_size);
 | 
						|
        }
 | 
						|
 | 
						|
        alignas(os::MemoryPageSize) constinit CalibrationInfo g_calibration_info = {};
 | 
						|
        alignas(os::MemoryPageSize) constinit CalibrationInfo g_blank_calibration_info = {};
 | 
						|
        alignas(os::MemoryPageSize) constinit SecureCalibrationInfoBackup g_secure_calibration_info_backup = {};
 | 
						|
 | 
						|
        constinit util::optional<ams::fs::FileStorage> g_prodinfo_backup_file;
 | 
						|
        constinit util::optional<ams::fs::MemoryStorage> g_blank_prodinfo_storage;
 | 
						|
        constinit util::optional<ams::fs::MemoryStorage> g_fake_secure_backup_storage;
 | 
						|
 | 
						|
        constinit bool g_allow_writes     = false;
 | 
						|
        constinit bool g_has_secure_backup = false;
 | 
						|
 | 
						|
        constinit os::SdkMutex g_prodinfo_management_lock;
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    void InitializeProdInfoManagement() {
 | 
						|
        std::scoped_lock lk(g_prodinfo_management_lock);
 | 
						|
 | 
						|
        /* First, get our options. */
 | 
						|
        const bool should_blank = exosphere::ShouldBlankProdInfo();
 | 
						|
        bool allow_writes = exosphere::ShouldAllowWritesToProdInfo();
 | 
						|
 | 
						|
        /* Next, read our prodinfo. */
 | 
						|
        ReadStorageCalibrationBinary(std::addressof(g_calibration_info));
 | 
						|
 | 
						|
        /* Next, check if we have a secure backup. */
 | 
						|
        bool has_secure_backup = ReadStorageSecureCalibrationBinaryBackup(std::addressof(g_secure_calibration_info_backup));
 | 
						|
 | 
						|
        /* Only allow writes if we have a secure backup. */
 | 
						|
        if (allow_writes && !has_secure_backup) {
 | 
						|
            /* If we can make a secure backup, great. */
 | 
						|
            if (IsValidForSecureBackup(g_calibration_info)) {
 | 
						|
                g_secure_calibration_info_backup.info = g_calibration_info;
 | 
						|
                WriteStorageSecureCalibrationBinaryBackup(std::addressof(g_secure_calibration_info_backup));
 | 
						|
                g_secure_calibration_info_backup.info = g_calibration_info;
 | 
						|
                has_secure_backup = true;
 | 
						|
            } else {
 | 
						|
                /* Don't allow writes if we can't make a secure backup. */
 | 
						|
                allow_writes = false;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /* Ensure our preconditions are met. */
 | 
						|
        AMS_ABORT_UNLESS(!allow_writes || has_secure_backup);
 | 
						|
 | 
						|
        /* Set globals. */
 | 
						|
        g_allow_writes      = allow_writes;
 | 
						|
        g_has_secure_backup = has_secure_backup;
 | 
						|
 | 
						|
        /* If we should blank, do so. */
 | 
						|
        if (should_blank) {
 | 
						|
            g_blank_calibration_info = g_calibration_info;
 | 
						|
            Blank(g_blank_calibration_info);
 | 
						|
            g_blank_prodinfo_storage.emplace(std::addressof(g_blank_calibration_info), sizeof(g_blank_calibration_info));
 | 
						|
        }
 | 
						|
 | 
						|
        /* Ensure that we have a blank file only if we need one. */
 | 
						|
        AMS_ABORT_UNLESS(should_blank == static_cast<bool>(g_blank_prodinfo_storage));
 | 
						|
    }
 | 
						|
 | 
						|
    void SaveProdInfoBackupsAndWipeMemory(char *out_name, size_t out_name_size) {
 | 
						|
        std::scoped_lock lk(g_prodinfo_management_lock);
 | 
						|
 | 
						|
        ON_SCOPE_EXIT {
 | 
						|
            FillWithGarbage(std::addressof(g_calibration_info), sizeof(g_calibration_info));
 | 
						|
            FillWithGarbage(std::addressof(g_secure_calibration_info_backup), sizeof(g_secure_calibration_info_backup));
 | 
						|
        };
 | 
						|
 | 
						|
        /* Save our backup. We always prefer to save a secure copy of data over a non-secure one. */
 | 
						|
        if (g_has_secure_backup) {
 | 
						|
            GetSerialNumber(out_name, g_secure_calibration_info_backup.info);
 | 
						|
            SaveProdInfoBackup(std::addressof(g_prodinfo_backup_file), g_secure_calibration_info_backup.info);
 | 
						|
        } else {
 | 
						|
            if (IsValid(g_calibration_info) && !IsBlank(g_calibration_info)) {
 | 
						|
                GetSerialNumber(out_name, g_calibration_info);
 | 
						|
            } else {
 | 
						|
                Sha256Hash hash;
 | 
						|
                ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(hash), sizeof(hash)); };
 | 
						|
                crypto::GenerateSha256(std::addressof(hash), sizeof(hash), std::addressof(g_calibration_info), sizeof(g_calibration_info));
 | 
						|
 | 
						|
                util::SNPrintf(out_name, out_name_size, "%02X%02X%02X%02X", hash.data[0], hash.data[1], hash.data[2], hash.data[3]);
 | 
						|
            }
 | 
						|
            SaveProdInfoBackup(std::addressof(g_prodinfo_backup_file), g_calibration_info);
 | 
						|
        }
 | 
						|
 | 
						|
        /* Ensure we made our backup. */
 | 
						|
        AMS_ABORT_UNLESS(g_prodinfo_backup_file);
 | 
						|
 | 
						|
        /* Setup our memory storage. */
 | 
						|
        g_fake_secure_backup_storage.emplace(std::addressof(g_secure_calibration_info_backup), sizeof(g_secure_calibration_info_backup));
 | 
						|
 | 
						|
        /* Ensure that we have a fake storage. */
 | 
						|
        AMS_ABORT_UNLESS(static_cast<bool>(g_fake_secure_backup_storage));
 | 
						|
    }
 | 
						|
 | 
						|
    bool ShouldReadBlankCalibrationBinary() {
 | 
						|
        std::scoped_lock lk(g_prodinfo_management_lock);
 | 
						|
        return static_cast<bool>(g_blank_prodinfo_storage);
 | 
						|
    }
 | 
						|
 | 
						|
    bool IsWriteToCalibrationBinaryAllowed() {
 | 
						|
        std::scoped_lock lk(g_prodinfo_management_lock);
 | 
						|
        return g_allow_writes;
 | 
						|
    }
 | 
						|
 | 
						|
    void ReadFromBlankCalibrationBinary(s64 offset, void *dst, size_t size) {
 | 
						|
        AMS_ABORT_UNLESS(ShouldReadBlankCalibrationBinary());
 | 
						|
 | 
						|
        std::scoped_lock lk(g_prodinfo_management_lock);
 | 
						|
        R_ABORT_UNLESS(g_blank_prodinfo_storage->Read(offset, dst, size));
 | 
						|
    }
 | 
						|
 | 
						|
    void WriteToBlankCalibrationBinary(s64 offset, const void *src, size_t size) {
 | 
						|
        AMS_ABORT_UNLESS(ShouldReadBlankCalibrationBinary());
 | 
						|
 | 
						|
        std::scoped_lock lk(g_prodinfo_management_lock);
 | 
						|
        R_ABORT_UNLESS(g_blank_prodinfo_storage->Write(offset, src, size));
 | 
						|
    }
 | 
						|
 | 
						|
    void ReadFromFakeSecureBackupStorage(s64 offset, void *dst, size_t size) {
 | 
						|
        AMS_ABORT_UNLESS(IsWriteToCalibrationBinaryAllowed());
 | 
						|
 | 
						|
        std::scoped_lock lk(g_prodinfo_management_lock);
 | 
						|
        R_ABORT_UNLESS(g_fake_secure_backup_storage->Read(offset, dst, size));
 | 
						|
    }
 | 
						|
 | 
						|
    void WriteToFakeSecureBackupStorage(s64 offset, const void *src, size_t size) {
 | 
						|
        AMS_ABORT_UNLESS(IsWriteToCalibrationBinaryAllowed());
 | 
						|
 | 
						|
        std::scoped_lock lk(g_prodinfo_management_lock);
 | 
						|
        R_ABORT_UNLESS(g_fake_secure_backup_storage->Write(offset, src, size));
 | 
						|
    }
 | 
						|
 | 
						|
}
 |