#include "KeyBag.h" #include "util.h" #include #include #include #include #include #include #include nstool::KeyBagInitializer::KeyBagInitializer(bool isDev, const tc::Optional& keyfile_path, const tc::Optional& tik_path, const tc::Optional& cert_path) { if (keyfile_path.isSet()) { importBaseKeyFile(keyfile_path.get(), isDev); } if (cert_path.isSet()) { importCertificateChain(cert_path.get()); } if (tik_path.isSet()) { importTicket(tik_path.get()); } } void nstool::KeyBagInitializer::importBaseKeyFile(const tc::io::Path& keyfile_path, bool isDev) { std::shared_ptr keyfile_stream = std::make_shared(tc::io::FileStream(keyfile_path, tc::io::FileMode::Open, tc::io::FileAccess::Read)); // import keyfile into a dictionary std::map keyfile_dict; processResFile(keyfile_stream, keyfile_dict); // sources for key derivation std::map master_key; tc::Optional package2_key_source; tc::Optional ticket_titlekek_source; std::array, 3> key_area_key_source; tc::Optional aes_kek_generation_source; tc::Optional aes_key_generation_source; tc::Optional nca_header_kek_source; tc::Optional nca_header_key_source; tc::Optional pki_root_sign_key; // macros for importing #define _SAVE_AES128KEY(key_name, dst) \ { \ std::string key,val; \ tc::ByteData dec_val; \ aes128_key_t tmp_aes128_key; \ key = (key_name); \ val = keyfile_dict[key]; \ if (val.empty() == false) { \ dec_val = tc::cli::FormatUtil::hexStringToBytes(val); \ if (dec_val.size() != tmp_aes128_key.size()) \ throw tc::ArgumentException("nstool::KeyBagInitializer", "Key: \"" + key_name + "\" has incorrect length"); \ memcpy(tmp_aes128_key.data(), dec_val.data(), tmp_aes128_key.size()); \ (dst) = tmp_aes128_key; \ } \ } #define _SAVE_AES128XTSKEY(key_name, dst) \ { \ std::string key,val; \ tc::ByteData dec_val; \ aes128_xtskey_t tmp_aes128_xtskey; \ key = (key_name); \ val = keyfile_dict[key]; \ if (val.empty() == false) { \ dec_val = tc::cli::FormatUtil::hexStringToBytes(val); \ if (dec_val.size() != sizeof(tmp_aes128_xtskey)) \ throw tc::ArgumentException("nstool::KeyBagInitializer", "Key: \"" + key_name + "\" has incorrect length"); \ memcpy(tmp_aes128_xtskey[0].data(), dec_val.data(), tmp_aes128_xtskey[0].size()); \ memcpy(tmp_aes128_xtskey[1].data(), dec_val.data()+tmp_aes128_xtskey[0].size(), tmp_aes128_xtskey[1].size()); \ (dst) = tmp_aes128_xtskey; \ } \ } #define _SAVE_RSAKEY(key_name, dst, bitsize) \ { \ std::string key_mod,key_prv,val_mod,val_prv; \ tc::ByteData dec_val; \ rsa_key_t tmp_rsa_key; \ key_mod = fmt::format("{:s}_modulus", (key_name)); \ key_prv = fmt::format("{:s}_private", (key_name)); \ val_mod = keyfile_dict[key_mod]; \ val_prv = keyfile_dict[key_prv]; \ if (val_mod.empty() == false) { \ dec_val = tc::cli::FormatUtil::hexStringToBytes(val_mod); \ if (dec_val.size() == (bitsize) >> 3) { \ tmp_rsa_key.n = dec_val; \ if (val_prv.empty() == false) { \ dec_val = tc::cli::FormatUtil::hexStringToBytes(val_prv); \ if (dec_val.size() == (bitsize) >> 3) { \ tmp_rsa_key.d = dec_val; \ (dst) = tc::crypto::RsaPrivateKey(tmp_rsa_key.n.data(), tmp_rsa_key.n.size(), tmp_rsa_key.d.data(), tmp_rsa_key.d.size()); \ } \ else { \ fmt::print("[WARNING] Key: \"{:s}\" has incorrect length (was: {:d}, expected {:d})\n", key_prv, val_prv.size(), ((bitsize) >> 3)*2); \ } \ } \ else { \ (dst) = tc::crypto::RsaPublicKey(tmp_rsa_key.n.data(), tmp_rsa_key.n.size()); \ } \ } \ else {\ fmt::print("[WARNING] Key: \"{:s}\" has incorrect length (was: {:d}, expected {:d})\n", key_mod, val_mod.size(), ((bitsize) >> 3)*2); \ } \ } \ } // keynames enum NameVariantIndex { NNTOOLS, LEGACY_HACTOOL, LEGACY_0 }; static const size_t kNameVariantNum = 3; std::vector kMasterBase = { "master" }; std::vector kPkg1Base = { "package1" }; std::vector kPkg2Base = { "package2" }; std::vector kXciHeaderBase = { "xci_header" }; std::vector kContentArchiveHeaderBase = { "nca_header", "header" }; std::vector kAcidBase = { "acid" }; std::vector kNrrCertBase = { "nrr_certificate" }; std::vector kPkiRootBase = { "pki_root" }; std::vector kTicketCommonKeyBase = { "ticket_commonkey", "titlekek" }; std::vector kNcaKeyAreaEncKeyBase = { "nca_key_area_key", "key_area_key", "nca_body_keak" }; std::vector kNcaKeyAreaEncKeyHwBase = { "nca_key_area_key_hw", "key_area_hw_key" }; std::vector kKekGenBase = { "aes_kek_generation" }; std::vector kKeyGenBase = { "aes_key_generation" }; // misc str const std::string kKeyStr = "key"; const std::string kKekStr = "kek"; const std::string kSourceStr = "source"; const std::string kSignKey = "sign_key"; const std::string kModulusStr = "modulus"; const std::string kPrivateStr = "private"; std::vector kNcaKeyAreaKeyIndexStr = { "application", "ocean", "system" }; static const size_t kMasterKeyMax = 0x20; /**/ // import key data for (size_t name_idx = 0; name_idx < kNameVariantNum; name_idx++) { /* internal key sources */ if (name_idx < kMasterBase.size()) { for (size_t mkey_rev = 0; mkey_rev < kMasterKeyMax; mkey_rev++) { // std::map master_key; //fmt::print("{:s}_key_{:02x}\n", kMasterBase[name_idx], mkey_rev); _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kMasterBase[name_idx], kKeyStr, mkey_rev), master_key[mkey_rev]); } } if (name_idx < kPkg2Base.size()) { // tc::Optional package2_key_source; //fmt::print("{:s}_key_source\n", kPkg2Base[name_idx]); _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:s}", kPkg2Base[name_idx], kKeyStr, kSourceStr), package2_key_source); } if (name_idx < kTicketCommonKeyBase.size()) { // tc::Optional ticket_titlekek_source; //fmt::print("{:s}_source\n", kTicketCommonKeyBase[name_idx]); _SAVE_AES128KEY(fmt::format("{:s}_{:s}", kTicketCommonKeyBase[name_idx], kSourceStr), ticket_titlekek_source); } if (name_idx < kNcaKeyAreaEncKeyBase.size()) { // std::array, 3> key_area_key_source; for (size_t keak_idx = 0; keak_idx < kNcaKeyAreaKeyIndexStr.size(); keak_idx++) { //fmt::print("{:s}_{:s}_source\n", kNcaKeyAreaEncKeyBase[name_idx], kNcaKeyAreaKeyIndexStr[keak_idx]); _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:s}", kNcaKeyAreaEncKeyBase[name_idx], kNcaKeyAreaKeyIndexStr[keak_idx], kSourceStr), key_area_key_source[keak_idx]); } } if (name_idx < kKekGenBase.size()) { // tc::Optional aes_kek_generation_source; //fmt::print("{:s}_source\n", kKekGenBase[name_idx]); _SAVE_AES128KEY(fmt::format("{:s}_{:s}", kKekGenBase[name_idx], kSourceStr), aes_kek_generation_source); } if (name_idx < kKeyGenBase.size()) { // tc::Optional aes_key_generation_source; //fmt::print("{:s}_source\n", kKeyGenBase[name_idx]); _SAVE_AES128KEY(fmt::format("{:s}_{:s}", kKeyGenBase[name_idx], kSourceStr), aes_key_generation_source); } if (name_idx < kContentArchiveHeaderBase.size()) { // tc::Optional nca_header_kek_source; //fmt::print("{:s}_kek_source\n", kContentArchiveHeaderBase[name_idx]); _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:s}", kContentArchiveHeaderBase[name_idx], kKekStr, kSourceStr), nca_header_kek_source); } if (name_idx < kContentArchiveHeaderBase.size()) { // tc::Optional nca_header_key_source; //fmt::print("{:s}_key_source\n", kContentArchiveHeaderBase[name_idx]); _SAVE_AES128XTSKEY(fmt::format("{:s}_{:s}_{:s}", kContentArchiveHeaderBase[name_idx], kKeyStr, kSourceStr), nca_header_key_source); } /* package1 */ // package1_key_xx if (name_idx < kPkg1Base.size()) { for (size_t mkey_rev = 0; mkey_rev < kMasterKeyMax; mkey_rev++) { //fmt::print("{:s}_key_{:02x}\n", kPkg1Base[name_idx], mkey_rev); _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kPkg1Base[name_idx], kKeyStr, mkey_rev), pkg1_key[mkey_rev]); } } /* package2 */ if (name_idx < kPkg2Base.size()) { // package2_key_xx for (size_t mkey_rev = 0; mkey_rev < kMasterKeyMax; mkey_rev++) { //fmt::print("{:s}_key_{:02x}\n", kPkg2Base[name_idx], mkey_rev); _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kPkg2Base[name_idx], kKeyStr, mkey_rev), pkg2_key[mkey_rev]); } // package2_sign_key //fmt::print("{:s}_{:s}_{:s}\n", kPkg2Base[name_idx], kSignKey, kPrivateStr); //fmt::print("{:s}_{:s}_{:s}\n", kPkg2Base[name_idx], kSignKey, kModulusStr); _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kPkg2Base[name_idx], kSignKey), pkg2_sign_key, 2048); } /* eticket */ // ticket common key if (name_idx < kTicketCommonKeyBase.size()) { for (size_t mkey_rev = 0; mkey_rev < kMasterKeyMax; mkey_rev++) { //fmt::print("{:s}_{:02x}\n", kTicketCommonKeyBase[name_idx], mkey_rev); _SAVE_AES128KEY(fmt::format("{:s}_{:02x}", kTicketCommonKeyBase[name_idx], mkey_rev), etik_common_key[mkey_rev]); } } /* NCA keys */ if (name_idx < kContentArchiveHeaderBase.size()) { // nca header key //fmt::print("{:s}_{:s}\n", kContentArchiveHeaderBase[name_idx], kKeyStr); //_SAVE_AES128XTSKEY(fmt::format("{:s}_{:s}", kContentArchiveHeaderBase[name_idx], kKeyStr), nca_header_key); // nca header sign0 key (generations) for (size_t mkey_rev = 0; mkey_rev < kMasterKeyMax; mkey_rev++) { //fmt::print("{:s}_{:s}_{:02x}_{:s}\n", kContentArchiveHeaderBase[name_idx], kSignKey, mkey_rev, kPrivateStr); //fmt::print("{:s}_{:s}_{:02x}_{:s}\n", kContentArchiveHeaderBase[name_idx], kSignKey, mkey_rev, kModulusStr); _SAVE_RSAKEY(fmt::format("{:s}_{:s}_{:02x}", kContentArchiveHeaderBase[name_idx], kSignKey, mkey_rev), nca_header_sign0_key[mkey_rev], 2048); } // nca header sign0 key (generation 0) //fmt::print("{:s}_{:s}_{:s}\n", kContentArchiveHeaderBase[name_idx], kSignKey, kPrivateStr); //fmt::print("{:s}_{:s}_{:s}\n", kContentArchiveHeaderBase[name_idx], kSignKey, kModulusStr); _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kContentArchiveHeaderBase[name_idx], kSignKey), nca_header_sign0_key[0], 2048); } // nca body key (unused since prototype format) // nca key area encryption keys if (name_idx < kNcaKeyAreaEncKeyBase.size()) { for (size_t mkey_rev = 0; mkey_rev < kMasterKeyMax; mkey_rev++) { for (size_t keak_idx = 0; keak_idx < kNcaKeyAreaKeyIndexStr.size(); keak_idx++) { //fmt::print("{:s}_{:s}_{:02x}\n", kNcaKeyAreaEncKeyBase[name_idx], kNcaKeyAreaKeyIndexStr[keak_idx], mkey_rev); _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kNcaKeyAreaEncKeyBase[name_idx], kNcaKeyAreaKeyIndexStr[keak_idx], mkey_rev), nca_key_area_encryption_key[keak_idx][mkey_rev]); } } } // nca key area "hw" encryption keys if (name_idx < kNcaKeyAreaEncKeyHwBase.size()) { for (size_t mkey_rev = 0; mkey_rev < kMasterKeyMax; mkey_rev++) { for (size_t keak_idx = 0; keak_idx < kNcaKeyAreaKeyIndexStr.size(); keak_idx++) { //fmt::print("{:s}_{:s}_{:02x}\n", kNcaKeyAreaEncKeyHwBase[name_idx], kNcaKeyAreaKeyIndexStr[keak_idx], mkey_rev); _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kNcaKeyAreaEncKeyHwBase[name_idx], kNcaKeyAreaKeyIndexStr[keak_idx], mkey_rev), nca_key_area_encryption_key_hw[keak_idx][mkey_rev]); } } } /* ACID */ if (name_idx < kAcidBase.size()) { // acid sign key (generations) for (size_t mkey_rev = 0; mkey_rev < kMasterKeyMax; mkey_rev++) { //fmt::print("{:s}_{:s}_{:02x}_{:s}\n", kAcidBase[name_idx], kSignKey, mkey_rev, kPrivateStr); //fmt::print("{:s}_{:s}_{:02x}_{:s}\n", kAcidBase[name_idx], kSignKey, mkey_rev, kModulusStr); _SAVE_RSAKEY(fmt::format("{:s}_{:s}_{:02x}", kAcidBase[name_idx], kSignKey, mkey_rev), acid_sign_key[mkey_rev], 2048); } // acid sign key (generation 0) //fmt::print("{:s}_{:s}_{:s}\n", kAcidBase[name_idx], kSignKey, kPrivateStr); //fmt::print("{:s}_{:s}_{:s}\n", kAcidBase[name_idx], kSignKey, kModulusStr); _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kAcidBase[name_idx], kSignKey), acid_sign_key[0], 2048); } /* NRR certificate */ if (name_idx < kNrrCertBase.size()) { // nrr certificate sign key (generations) for (size_t mkey_rev = 0; mkey_rev < kMasterKeyMax; mkey_rev++) { //fmt::print("{:s}_{:s}_{:02x}_{:s}\n", kNrrCertBase[name_idx], kSignKey, mkey_rev, kPrivateStr); //fmt::print("{:s}_{:s}_{:02x}_{:s}\n", kNrrCertBase[name_idx], kSignKey, mkey_rev, kModulusStr); _SAVE_RSAKEY(fmt::format("{:s}_{:s}_{:02x}", kNrrCertBase[name_idx], kSignKey, mkey_rev), nrr_certificate_sign_key[mkey_rev], 2048); } // nrr certificate sign key (generation 0) //fmt::print("{:s}_{:s}_{:s}\n", kNrrCertBase[name_idx], kSignKey, kPrivateStr); //fmt::print("{:s}_{:s}_{:s}\n", kNrrCertBase[name_idx], kSignKey, kModulusStr); _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kNrrCertBase[name_idx], kSignKey), nrr_certificate_sign_key[0], 2048); } /* XCI header */ if (name_idx < kXciHeaderBase.size()) { // xci header key (based on index) for (size_t kek_index = 0; kek_index < 8; kek_index++) { //fmt::print("{:s}_{:s}_{:02x}\n", kXciHeaderBase[name_idx], kKeyStr, kek_index); _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kXciHeaderBase[name_idx], kKeyStr, kek_index), xci_header_key[kek_index]); } // xci header key (old lable) //fmt::print("{:s}_{:s}\n", kXciHeaderBase[name_idx], kKeyStr); _SAVE_AES128KEY(fmt::format("{:s}_{:s}", kXciHeaderBase[name_idx], kKeyStr), xci_header_key[isDev ? nn::hac::gc::KEK_DEV : nn::hac::gc::KEK_PROD]); // xci header sign key //fmt::print("{:s}_{:s}_{:s}\n", kXciHeaderBase[name_idx], kSignKey, kPrivateStr); //fmt::print("{:s}_{:s}_{:s}\n", kXciHeaderBase[name_idx], kSignKey, kModulusStr); _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kXciHeaderBase[name_idx], kSignKey), xci_header_sign_key, 2048); } /* PKI */ if (name_idx < kPkiRootBase.size()) { // tc::Optional pki_root_sign_key; //fmt::print("{:s}_{:s}_{:s}\n", kPkiRootBase[name_idx], kSignKey, kPrivateStr); //fmt::print("{:s}_{:s}_{:s}\n", kPkiRootBase[name_idx], kSignKey, kModulusStr); _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kPkiRootBase[name_idx], kSignKey), pki_root_sign_key, 4096); } } #undef _SAVE_RSAKEY #undef _SAVE_AES128XTSKEY #undef _SAVE_AES128KEY // Derive Keys for (auto itr = master_key.begin(); itr != master_key.end(); itr++) { if (aes_kek_generation_source.isSet() && aes_key_generation_source.isSet()) { if (itr->first == 0 && nca_header_kek_source.isSet() && nca_header_key_source.isSet()) { if (nca_header_key.isNull()) { aes128_key_t nca_header_kek_tmp; nn::hac::AesKeygen::generateKey(nca_header_kek_tmp.data(), aes_kek_generation_source.get().data(), nca_header_kek_source.get().data(), aes_key_generation_source.get().data(), itr->second.data()); aes128_xtskey_t nca_header_key_tmp; nn::hac::AesKeygen::generateKey(nca_header_key_tmp[0].data(), nca_header_key_source.get()[0].data(), nca_header_kek_tmp.data()); nn::hac::AesKeygen::generateKey(nca_header_key_tmp[1].data(), nca_header_key_source.get()[1].data(), nca_header_kek_tmp.data()); nca_header_key = nca_header_key_tmp; } } for (size_t keak_idx = 0; keak_idx < nn::hac::nca::kKeyAreaEncryptionKeyNum; keak_idx++) { if (key_area_key_source[keak_idx].isSet() && nca_key_area_encryption_key[keak_idx].find(itr->first) != nca_key_area_encryption_key[keak_idx].end()) { aes128_key_t nca_key_area_encryption_key_tmp; nn::hac::AesKeygen::generateKey(nca_key_area_encryption_key_tmp.data(), aes_kek_generation_source.get().data(), key_area_key_source[keak_idx].get().data(), aes_key_generation_source.get().data(), itr->second.data()); nca_key_area_encryption_key[keak_idx][itr->first] = nca_key_area_encryption_key_tmp; } } } if (ticket_titlekek_source.isSet() && etik_common_key.find(itr->first) == etik_common_key.end()) { aes128_key_t etik_common_key_tmp; nn::hac::AesKeygen::generateKey(etik_common_key_tmp.data(), ticket_titlekek_source.get().data(), itr->second.data()); etik_common_key[itr->first] = etik_common_key_tmp; } if (package2_key_source.isSet() && pkg2_key.find(itr->first) == pkg2_key.end()) { aes128_key_t pkg2_key_tmp; nn::hac::AesKeygen::generateKey(pkg2_key_tmp.data(), package2_key_source.get().data(), itr->second.data()); pkg2_key[itr->first] = pkg2_key_tmp; } } // Save PKI Root Key if (pki_root_sign_key.isSet()) { broadon_signer["Root"] = { tc::ByteData(), nn::pki::sign::SIGN_ALGO_RSA4096, pki_root_sign_key.get() }; } } void nstool::KeyBagInitializer::importTitleKeyFile(const tc::io::Path& keyfile_path) { } void nstool::KeyBagInitializer::importCertificateChain(const tc::io::Path& cert_path) { // save file path string for error messages std::string cert_path_str; tc::io::PathUtil::pathToUnixUTF8(cert_path, cert_path_str); // open cert file std::shared_ptr certfile_stream; try { certfile_stream = std::make_shared(tc::io::FileStream(cert_path, tc::io::FileMode::Open, tc::io::FileAccess::Read)); } catch (tc::io::FileNotFoundException& e) { fmt::print("[WARNING] Failed to open certificate file \"{:s}\" ({:s}).\n", cert_path_str, e.error()); return; } // check size size_t cert_raw_size = tc::io::IOUtil::castInt64ToSize(certfile_stream->length()); if (cert_raw_size > 0x10000) { fmt::print("[WARNING] Certificate file \"{:s}\" was too large.\n", cert_path_str); return; } // import cert data tc::ByteData cert_raw = tc::ByteData(cert_raw_size); certfile_stream->seek(0, tc::io::SeekOrigin::Begin); certfile_stream->read(cert_raw.data(), cert_raw.size()); nn::pki::SignedData cert; try { for (size_t f_pos = 0; f_pos < cert_raw.size(); f_pos += cert.getBytes().size()) { cert.fromBytes(cert_raw.data() + f_pos, cert_raw.size() - f_pos); std::string cert_identity = fmt::format("{:s}-{:s}", cert.getBody().getIssuer(), cert.getBody().getSubject()); switch (cert.getBody().getPublicKeyType()) { case nn::pki::cert::PublicKeyType::RSA2048: broadon_signer[cert_identity] = { cert.getBytes(), nn::pki::sign::SIGN_ALGO_RSA2048, cert.getBody().getRsa2048PublicKey() }; break; case nn::pki::cert::PublicKeyType::RSA4096: broadon_signer[cert_identity] = { cert.getBytes(), nn::pki::sign::SIGN_ALGO_RSA4096, cert.getBody().getRsa4096PublicKey() }; break; case nn::pki::cert::PublicKeyType::ECDSA240: // broadon_signer[cert_identity] = { cert.getBytes(), nn::pki::sign::SIGN_ALGO_ECDSA240, cert.getBody().getRsa4096PublicKey() }; fmt::print("[WARNING] Certificate {:s} will not be imported. ecc233 public keys are not supported yet.\n", cert_identity); break; default: fmt::print("[WARNING] Certificate {:s} will not be imported. Unknown public key type.\n", cert_identity); } } } catch (tc::Exception& e) { fmt::print("[WARNING] Certificate file \"{:s}\" is corrupted ({:s}).\n", cert_path_str, e.error()); return; } } void nstool::KeyBagInitializer::importTicket(const tc::io::Path& tik_path) { // save file path string for error messages std::string tik_path_str; tc::io::PathUtil::pathToUnixUTF8(tik_path, tik_path_str); // open cert file std::shared_ptr tik_stream; try { tik_stream = std::make_shared(tc::io::FileStream(tik_path, tc::io::FileMode::Open, tc::io::FileAccess::Read)); } catch (tc::io::FileNotFoundException& e) { fmt::print("[WARNING] Failed to open ticket \"{:s}\" ({:s}).\n", tik_path_str, e.error()); return; } // check size size_t tik_raw_size = tc::io::IOUtil::castInt64ToSize(tik_stream->length()); if (tik_raw_size > 0x10000) { fmt::print("[WARNING] Ticket \"{:s}\" was too large.\n", tik_path_str); return; } // import cert data tc::ByteData tik_raw = tc::ByteData(tik_raw_size); tik_stream->seek(0, tc::io::SeekOrigin::Begin); tik_stream->read(tik_raw.data(), tik_raw.size()); nn::pki::SignedData tik; try { // de serialise ticket tik.fromBytes(tik_raw.data(), tik_raw.size()); // save rights id rights_id_t rights_id; memcpy(rights_id.data(), tik.getBody().getRightsId(), rights_id.size()); // check ticket is not personalised if (tik.getBody().getTitleKeyEncType() != nn::es::ticket::AES128_CBC) { fmt::print("[WARNING] Ticket \"{:s}\" will not be imported. Personalised tickets are not supported.\n", tc::cli::FormatUtil::formatBytesAsString(rights_id.data(), rights_id.size(), true, "")); return; } // save enc title key aes128_key_t enc_title_key; memcpy(enc_title_key.data(), tik.getBody().getEncTitleKey(), enc_title_key.size()); // save the encrypted title key as the fallback enc content key incase the ticket was malformed and workarounds to decrypt it in isolation fail if (fallback_enc_content_key.isNull()) { fallback_enc_content_key = enc_title_key; } // determine key to decrypt title key byte_t common_key_index = tik.getBody().getCommonKeyId(); // work around for bad scene tickets where they don't set the commonkey id field (detect scene ticket with ffff.... signature) if (common_key_index != rights_id[15] && tik.getSignature().getBytes()[0x00] == 0xff && tik.getSignature().getBytes()[0x01] == 0xff) { common_key_index = rights_id[15]; } if (etik_common_key.find(tik.getBody().getCommonKeyId()) == etik_common_key.end()) { fmt::print("[WARNING] Ticket \"{:s}\" will not be imported. Could not decrypt title key.\n", tc::cli::FormatUtil::formatBytesAsString(rights_id.data(), rights_id.size(), true, "")); return; } // decrypt title key aes128_key_t dec_title_key; tc::crypto::DecryptAes128Ecb(dec_title_key.data(), enc_title_key.data(), sizeof(aes128_key_t), etik_common_key[common_key_index].data(), sizeof(aes128_key_t)); // add to key dict external_content_keys[rights_id] = dec_title_key; } catch (tc::Exception& e) { fmt::print("[WARNING] Ticket \"{:s}\" is corrupted ({:s}).\n", tik_path_str, e.error()); return; } }