fs: utilities for hac2l to print gc headers
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -35,8 +35,22 @@ namespace ams::gc::impl {
|
||||
static_assert(util::is_pod<CardInitialData>::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<KekIndex::Next, 4, u8>;
|
||||
|
||||
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<Ca10Certificate>::value);
|
||||
static_assert(sizeof(Ca10Certificate) == 0x400);
|
||||
|
||||
@@ -268,6 +268,68 @@ namespace ams::fs::impl {
|
||||
}
|
||||
}
|
||||
|
||||
template<> const char *IdString::ToString<gc::impl::MemoryCapacity>(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<int>(id));
|
||||
}
|
||||
}
|
||||
|
||||
template<> const char *IdString::ToString<gc::impl::SelSec>(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<int>(id));
|
||||
}
|
||||
}
|
||||
|
||||
template<> const char *IdString::ToString<gc::impl::KekIndex>(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<int>(id));
|
||||
}
|
||||
}
|
||||
|
||||
template<> const char *IdString::ToString<gc::impl::AccessControl1ClockRate>(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<int>(id));
|
||||
}
|
||||
}
|
||||
|
||||
template<> const char *IdString::ToString<gc::impl::FwVersion>(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<int>(id));
|
||||
}
|
||||
}
|
||||
|
||||
template<> const char *IdString::ToString<fs::GameCardCompatibilityType>(fs::GameCardCompatibilityType id) {
|
||||
switch (id) {
|
||||
using enum fs::GameCardCompatibilityType;
|
||||
ADD_ENUM_CASE(Normal);
|
||||
ADD_ENUM_CASE(Terra);
|
||||
default: return ToValueString(static_cast<int>(id));
|
||||
}
|
||||
}
|
||||
|
||||
template<> const char *IdString::ToString<fssrv::impl::AccessControlBits::Bits>(fssrv::impl::AccessControlBits::Bits id) {
|
||||
switch (id) {
|
||||
using enum fssrv::impl::AccessControlBits::Bits;
|
||||
|
||||
@@ -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<const CardInitialData *>(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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user