aboutsummaryrefslogtreecommitdiff
path: root/src/core/file_sys/xts_archive.cpp
blob: 7c5da5e7e3b37c9d54ffeee9043c53e21fc259b0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <array>
#include <cstring>
#include <regex>
#include <string>

#include <openssl/err.h>
#include <openssl/evp.h>

#include "common/fs/path_util.h"
#include "common/hex_util.h"
#include "common/string_util.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/xts_encryption_layer.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/vfs/vfs_offset.h"
#include "core/file_sys/xts_archive.h"
#include "core/loader/loader.h"

namespace FileSys {

constexpr u64 NAX_HEADER_PADDING_SIZE = 0x4000;

template <typename SourceData, typename SourceKey, typename Destination>
static bool CalculateHMAC256(Destination* out, const SourceKey* key, std::size_t key_length,
                             const SourceData* data, std::size_t data_length) {
    size_t out_len = 0;

    static EVP_MAC* mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
    if (!mac) return false;

    static EVP_MAC_CTX* ctx = EVP_MAC_CTX_new(mac);
    if (!ctx) return false;

    static OSSL_PARAM params[] = {
        OSSL_PARAM_construct_utf8_string("digest", (char*)"SHA256", 0),
        OSSL_PARAM_construct_end()
    };

    if (!EVP_MAC_init(ctx, reinterpret_cast<const unsigned char*>(key), key_length, params))
        return false;

    return EVP_MAC_update(ctx, reinterpret_cast<const unsigned char*>(data), data_length) &&
           EVP_MAC_final(ctx, reinterpret_cast<unsigned char*>(out), &out_len, 32);
}

NAX::NAX(VirtualFile file_)
    : header(std::make_unique<NAXHeader>()),
      file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
    std::string path = Common::FS::SanitizePath(file->GetFullPath());
    static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca",
                                           std::regex_constants::ECMAScript |
                                               std::regex_constants::icase);
    std::smatch match;
    if (!std::regex_search(path, match, nax_path_regex)) {
        status = Loader::ResultStatus::ErrorBadNAXFilePath;
        return;
    }

    const std::string two_dir = Common::ToUpper(std::string{match[1]});
    const std::string nca_id = Common::ToLower(std::string{match[2]});
    status = Parse(fmt::format("/registered/{}/{}.nca", two_dir, nca_id));
}

NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id)
    : header(std::make_unique<NAXHeader>()),
      file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
    Core::Crypto::SHA256Hash hash{};

    u32 hash_len = 0;
    EVP_Digest(nca_id.data(), nca_id.size(), hash.data(), &hash_len, EVP_sha256(), nullptr);

    LOG_DEBUG(Loader, "Decoded {} bytes, nca id {}", hash_len, nca_id);

    status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0],
                               Common::HexToString(nca_id, false)));
}

NAX::~NAX() = default;

Loader::ResultStatus NAX::Parse(std::string_view path) {
    if (file == nullptr) {
        return Loader::ResultStatus::ErrorNullFile;
    }
    if (file->ReadObject(header.get()) != sizeof(NAXHeader)) {
        return Loader::ResultStatus::ErrorBadNAXHeader;
    }
    if (header->magic != Common::MakeMagic('N', 'A', 'X', '0')) {
        return Loader::ResultStatus::ErrorBadNAXHeader;
    }
    if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size) {
        return Loader::ResultStatus::ErrorIncorrectNAXFileSize;
    }

    keys.DeriveSDSeedLazy();
    std::array<Core::Crypto::Key256, 2> sd_keys{};
    const auto sd_keys_res = Core::Crypto::DeriveSDKeys(sd_keys, keys);
    if (sd_keys_res != Loader::ResultStatus::Success) {
        return sd_keys_res;
    }

    const auto enc_keys = header->key_area;

    std::size_t i = 0;
    for (; i < sd_keys.size(); ++i) {
        std::array<Core::Crypto::Key128, 2> nax_keys{};
        if (!CalculateHMAC256(nax_keys.data(), sd_keys[i].data(), 0x10, path.data(), path.size())) {
            return Loader::ResultStatus::ErrorNAXKeyHMACFailed;
        }

        for (std::size_t j = 0; j < nax_keys.size(); ++j) {
            Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(nax_keys[j],
                                                                 Core::Crypto::Mode::ECB);
            cipher.Transcode(enc_keys[j].data(), 0x10, header->key_area[j].data(),
                             Core::Crypto::Op::Decrypt);
        }

        Core::Crypto::SHA256Hash validation{};
        if (!CalculateHMAC256(validation.data(), &header->magic, 0x60, sd_keys[i].data() + 0x10,
                              0x10)) {
            return Loader::ResultStatus::ErrorNAXValidationHMACFailed;
        }
        if (header->hmac == validation)
            break;
    }

    if (i == 2) {
        return Loader::ResultStatus::ErrorNAXKeyDerivationFailed;
    }

    type = static_cast<NAXContentType>(i);

    Core::Crypto::Key256 final_key{};
    std::memcpy(final_key.data(), &header->key_area, final_key.size());
    const auto enc_file =
        std::make_shared<OffsetVfsFile>(file, header->file_size, NAX_HEADER_PADDING_SIZE);
    dec_file = std::make_shared<Core::Crypto::XTSEncryptionLayer>(enc_file, final_key);

    return Loader::ResultStatus::Success;
}

Loader::ResultStatus NAX::GetStatus() const {
    return status;
}

VirtualFile NAX::GetDecrypted() const {
    return dec_file;
}

std::unique_ptr<NCA> NAX::AsNCA() const {
    if (type == NAXContentType::NCA)
        return std::make_unique<NCA>(GetDecrypted());
    return nullptr;
}

NAXContentType NAX::GetContentType() const {
    return type;
}

std::vector<VirtualFile> NAX::GetFiles() const {
    return {dec_file};
}

std::vector<VirtualDir> NAX::GetSubdirectories() const {
    return {};
}

std::string NAX::GetName() const {
    return file->GetName();
}

VirtualDir NAX::GetParentDirectory() const {
    return file->GetContainingDirectory();
}

} // namespace FileSys