diff --git a/fusee_cpp/program/source/fusee_package2.cpp b/fusee_cpp/program/source/fusee_package2.cpp
new file mode 100644
index 000000000..2c0d6c28c
--- /dev/null
+++ b/fusee_cpp/program/source/fusee_package2.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2018-2020 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 "fusee_package2.hpp"
+#include "fusee_key_derivation.hpp"
+#include "fusee_fatal.hpp"
+
+namespace ams::nxboot {
+
+ namespace {
+
+ alignas(se::AesBlockSize) constexpr inline const u8 Package2KeySource[se::AesBlockSize] = {
+ 0xFB, 0x8B, 0x6A, 0x9C, 0x79, 0x00, 0xC8, 0x49, 0xEF, 0xD2, 0x4D, 0x85, 0x4D, 0x30, 0xA0, 0xC7
+ };
+
+ void PreparePackage2Key(int pkg2_slot, int key_generation) {
+ /* Get keyslot for the desired master key. */
+ const int master_slot = PrepareMasterKey(key_generation);
+
+ /* Load the package2 key into the desired keyslot. */
+ se::SetEncryptedAesKey128(pkg2_slot, master_slot, Package2KeySource, sizeof(Package2KeySource));
+ }
+
+ void DecryptPackage2(void *dst, size_t dst_size, const void *src, size_t src_size, const void *iv, size_t iv_size, u8 key_generation) {
+ /* Ensure that the SE sees consistent data. */
+ hw::FlushDataCache(src, src_size);
+ if (src != dst) {
+ hw::FlushDataCache(dst, dst_size);
+ }
+
+ /* Load the package2 key into the temporary keyslot. */
+ PreparePackage2Key(pkg1::AesKeySlot_Temporary, key_generation);
+
+ /* Decrypt the data. */
+ se::ComputeAes128Ctr(dst, dst_size, pkg1::AesKeySlot_Temporary, src, src_size, iv, iv_size);
+
+ /* Clear the keyslot we just used. */
+ se::ClearAesKeySlot(pkg1::AesKeySlot_Temporary);
+
+ /* Ensure that the cpu sees consistent data. */
+ hw::InvalidateDataCache(dst, dst_size);
+ }
+
+ void DecryptPackage2Header(pkg2::Package2Meta *dst, const pkg2::Package2Meta &src) {
+ constexpr int IvSize = 0x10;
+
+ /* Decrypt the header. */
+ DecryptPackage2(dst, sizeof(*dst), std::addressof(src), sizeof(src), std::addressof(src), IvSize, src.GetKeyGeneration());
+
+ /* Copy back the iv, which encodes encrypted metadata. */
+ std::memcpy(dst, std::addressof(src), IvSize);
+ }
+
+ bool VerifyPackage2Meta(const pkg2::Package2Meta &meta) {
+ /* Get the obfuscated metadata. */
+ const size_t size = meta.GetSize();
+ const u8 key_generation = meta.GetKeyGeneration();
+
+ /* Check that size is big enough for the header. */
+ if (size <= sizeof(pkg2::Package2Header)) {
+ return false;
+ }
+
+ /* Check that the size isn't larger than what we allow. */
+ if (size > pkg2::Package2SizeMax) {
+ return false;
+ }
+
+ /* Check that the key generation is one that we can use. */
+ static_assert(pkg1::KeyGeneration_Count == 12);
+ if (key_generation >= pkg1::KeyGeneration_Count) {
+ return false;
+ }
+
+ /* Check the magic number. */
+ if (!crypto::IsSameBytes(meta.magic, pkg2::Package2Meta::Magic::String, sizeof(meta.magic))) {
+ return false;
+ }
+
+ /* Check the payload alignments. */
+ if ((meta.entrypoint % pkg2::PayloadAlignment) != 0) {
+ return false;
+ }
+
+ for (int i = 0; i < pkg2::PayloadCount; ++i) {
+ if ((meta.payload_sizes[i] % pkg2::PayloadAlignment) != 0) {
+ return false;
+ }
+ }
+
+ /* Check that the sizes sum to the total. */
+ if (size != sizeof(pkg2::Package2Header) + meta.payload_sizes[0] + meta.payload_sizes[1] + meta.payload_sizes[2]) {
+ return false;
+ }
+
+ /* Check that the payloads do not overflow. */
+ for (int i = 0; i < pkg2::PayloadCount; ++i) {
+ if (meta.payload_offsets[i] > meta.payload_offsets[i] + meta.payload_sizes[i]) {
+ return false;
+ }
+ }
+
+ /* Verify that no payloads overlap. */
+ for (int i = 0; i < pkg2::PayloadCount - 1; ++i) {
+ for (int j = i + 1; j < pkg2::PayloadCount; ++j) {
+ if (util::HasOverlap(meta.payload_offsets[i], meta.payload_sizes[i], meta.payload_offsets[j], meta.payload_sizes[j])) {
+ return false;
+ }
+ }
+ }
+
+ /* Check whether any payload contains the entrypoint. */
+ for (int i = 0; i < pkg2::PayloadCount; ++i) {
+ if (util::Contains(meta.payload_offsets[i], meta.payload_sizes[i], meta.entrypoint)) {
+ return true;
+ }
+ }
+
+ /* No payload contains the entrypoint, so we're not valid. */
+ return false;
+ }
+
+ }
+
+ void DecryptPackage2(u8 *package2) {
+ /* Decrypt package2 header. */
+ pkg2::Package2Header *header = reinterpret_cast(package2);
+ {
+ pkg2::Package2Header tmp = *header;
+ DecryptPackage2Header(std::addressof(header->meta), tmp.meta);
+ }
+
+ /* Check package2 magic. */
+ if (!VerifyPackage2Meta(header->meta)) {
+ ShowFatalError("Package2 meta is invalid!\n");
+ }
+
+ /* Decrypt package2 payloads. */
+ u8 *payload = package2 + sizeof(*header);
+ const u8 key_generation = header->meta.GetKeyGeneration();
+ for (int i = 0; i < pkg2::PayloadCount; ++i) {
+ if (header->meta.payload_sizes[i] == 0) {
+ continue;
+ }
+
+ DecryptPackage2(payload, header->meta.payload_sizes[i], payload, header->meta.payload_sizes[i], header->meta.payload_ivs[i], sizeof(header->meta.payload_ivs[i]), key_generation);
+
+ payload += header->meta.payload_sizes[i];
+ }
+ }
+
+}
diff --git a/fusee_cpp/program/source/fusee_package2.hpp b/fusee_cpp/program/source/fusee_package2.hpp
new file mode 100644
index 000000000..a595d5e27
--- /dev/null
+++ b/fusee_cpp/program/source/fusee_package2.hpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2018-2020 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
+#pragma once
+
+namespace ams::nxboot {
+
+ void DecryptPackage2(u8 *package2);
+
+}
\ No newline at end of file
diff --git a/fusee_cpp/program/source/fusee_setup_horizon.cpp b/fusee_cpp/program/source/fusee_setup_horizon.cpp
index 5620f3872..97ac13a3d 100644
--- a/fusee_cpp/program/source/fusee_setup_horizon.cpp
+++ b/fusee_cpp/program/source/fusee_setup_horizon.cpp
@@ -21,6 +21,7 @@
#include "fusee_emummc.hpp"
#include "fusee_mmc.hpp"
#include "fusee_fatal.hpp"
+#include "fusee_package2.hpp"
#include "fusee_malloc.hpp"
#include "fs/fusee_fs_api.hpp"
@@ -333,6 +334,39 @@ namespace ams::nxboot {
return target_firmware;
}
+ u8 *LoadBootConfigAndPackage2() {
+ Result result;
+
+ /* Load boot config. */
+ if (R_FAILED((result = ReadPackage2(0, secmon::MemoryRegionPhysicalIramBootConfig.GetPointer(), secmon::MemoryRegionPhysicalIramBootConfig.GetSize())))) {
+ ShowFatalError("Failed to read boot config: 0x%08" PRIx32 "!\n", result.GetValue());
+ }
+
+ /* Read package2 header. */
+ u8 *package2;
+ size_t package2_size;
+ {
+ constexpr s64 Package2Offset = __builtin_offsetof(pkg2::StorageLayout, package2_header);
+
+ pkg2::Package2Header header;
+ if (R_FAILED((result = ReadPackage2(Package2Offset, std::addressof(header), sizeof(header))))) {
+ ShowFatalError("Failed to read package2 header: 0x%08" PRIx32 "!\n", result.GetValue());
+ }
+
+ package2_size = header.meta.GetSize();
+ package2 = static_cast(AllocateAligned(util::AlignUp(package2_size, 0x4000), 0x4000));
+
+ if (R_FAILED((result = ReadPackage2(Package2Offset, package2, util::AlignUp(package2_size, 0x4000))))) {
+ ShowFatalError("Failed to read package2: 0x%08" PRIx32 "!\n", result.GetValue());
+ }
+ }
+
+ /* Decrypt package2. */
+ DecryptPackage2(package2);
+
+ return package2;
+ }
+
}
void SetupAndStartHorizon() {
@@ -356,7 +390,9 @@ namespace ams::nxboot {
const auto target_firmware = GetTargetFirmware(package1);
AMS_UNUSED(target_firmware);
- /* TODO: Read/decrypt package2. */
+ /* Read/decrypt package2. */
+ u8 * const package2 = LoadBootConfigAndPackage2();
+ AMS_UNUSED(package2);
/* TODO: Setup warmboot firmware. */
@@ -366,6 +402,7 @@ namespace ams::nxboot {
/* NOTE: Security Engine unusable past this point. */
/* TODO: Build modified package2. */
+ WaitForReboot();
}
}
\ No newline at end of file