From e7148c39ea973f1e9e2e236f8ced6fe603620bbf Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 10 Jul 2018 23:01:34 +0800 Subject: [PATCH] [nxtool] Inital reading of ES tickets and certificates. --- programs/nstool/source/EsCertProcess.cpp | 249 +++++++++++++++++++++++ programs/nstool/source/EsCertProcess.h | 46 +++++ programs/nstool/source/EsTikProcess.cpp | 187 +++++++++++++++++ programs/nstool/source/EsTikProcess.h | 38 ++++ programs/nstool/source/main.cpp | 24 +++ 5 files changed, 544 insertions(+) create mode 100644 programs/nstool/source/EsCertProcess.cpp create mode 100644 programs/nstool/source/EsCertProcess.h create mode 100644 programs/nstool/source/EsTikProcess.cpp create mode 100644 programs/nstool/source/EsTikProcess.h diff --git a/programs/nstool/source/EsCertProcess.cpp b/programs/nstool/source/EsCertProcess.cpp new file mode 100644 index 0000000..7bab43f --- /dev/null +++ b/programs/nstool/source/EsCertProcess.cpp @@ -0,0 +1,249 @@ +#include +#include "OffsetAdjustedIFile.h" +#include "EsCertProcess.h" + +EsCertProcess::EsCertProcess() : + mFile(nullptr), + mOwnIFile(false), + mCliOutputMode(_BIT(OUTPUT_BASIC)), + mVerify(false) +{ +} + +EsCertProcess::~EsCertProcess() +{ + if (mOwnIFile) + { + delete mFile; + } +} + +void EsCertProcess::process() +{ + if (mFile == nullptr) + { + throw fnd::Exception(kModuleName, "No file reader set."); + } + + importCerts(); + if (mVerify) + validateCerts(); + + if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC)) + displayCerts(); +} + +void EsCertProcess::setInputFile(fnd::IFile* file, bool ownIFile) +{ + mFile = file; + mOwnIFile = ownIFile; +} + +void EsCertProcess::setKeyset(const sKeyset* keyset) +{ + mKeyset = keyset; +} + +void EsCertProcess::setCliOutputMode(CliOutputMode mode) +{ + mCliOutputMode = mode; +} + +void EsCertProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} + +void EsCertProcess::importCerts() +{ + fnd::Vec scratch; + + scratch.alloc(mFile->size()); + mFile->read(scratch.data(), 0, scratch.size()); + + es::SignedData cert; + for (size_t f_pos = 0; f_pos < scratch.size(); f_pos += cert.getBytes().size()) + { + cert.fromBytes(scratch.data() + f_pos, scratch.size() - f_pos); + mCert.addElement(cert); + } +} + +void EsCertProcess::validateCerts() +{ + for (size_t i = 0; i < mCert.size(); i++) + { + EsCertProcess::validateCert(mCert[i]); + } +} + +void EsCertProcess::validateCert(const es::SignedData& cert) +{ + std::string cert_ident = ""; + cert_ident += cert.getBody().getSubject(); + cert_ident += cert.getBody().getSubject(); + cert_ident += cert.getBody().getSubject(); + + try + { + if (cert.getBody().getIssuer() == es::sign::kRootIssuerStr) + { + throw fnd::Exception(kModuleName, "Signed by Root"); + } + + const es::CertificateBody& issuer = getIssuerCert(cert.getBody().getIssuer()).getBody(); + + if (issuer.getPublicKeyType() == es::cert::RSA4096 && (cert.getSignature().getSignType() == es::sign::SIGN_RSA4096_SHA1 || cert.getSignature().getSignType() == es::sign::SIGN_RSA4096_SHA256)) + { + throw fnd::Exception(kModuleName, "RSA4096 signatures are not supported"); + } + else if (issuer.getPublicKeyType() == es::cert::RSA2048 && (cert.getSignature().getSignType() == es::sign::SIGN_RSA2048_SHA1 || cert.getSignature().getSignType() == es::sign::SIGN_RSA2048_SHA256)) + { + throw fnd::Exception(kModuleName, "RSA2048 signatures are not supported"); + } + else if (issuer.getPublicKeyType() == es::cert::ECDSA240 && (cert.getSignature().getSignType() == es::sign::SIGN_ECDSA240_SHA1 || cert.getSignature().getSignType() == es::sign::SIGN_ECDSA240_SHA256)) + { + throw fnd::Exception(kModuleName, "ECDSA signatures are not supported"); + } + else + { + throw fnd::Exception(kModuleName, "Mismatch between issuer public key and signature type"); + } + } + catch (const fnd::Exception& e) + { + printf("[WARNING] Failed to validate %s (%s)\n", cert_ident.c_str(), e.error()); + return; + } + +} + +void EsCertProcess::displayCerts() +{ + for (size_t i = 0; i < mCert.size(); i++) + { + displayCert(mCert[i]); + } +} + +void EsCertProcess::displayCert(const es::SignedData& cert) +{ +#define _SPLIT_VER(ver) ( (ver>>26) & 0x3f), ( (ver>>20) & 0x3f), ( (ver>>16) & 0xf), (ver & 0xffff) +#define _HEXDUMP_U(var, len) do { for (size_t a__a__A = 0; a__a__A < len; a__a__A++) printf("%02X", var[a__a__A]); } while(0) +#define _HEXDUMP_L(var, len) do { for (size_t a__a__A = 0; a__a__A < len; a__a__A++) printf("%02x", var[a__a__A]); } while(0) + + printf("[ES Certificate]\n"); + printf(" SignType: %s", getSignTypeStr(cert.getSignature().getSignType())); + if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED)) + printf(" (0x%" PRIx32 ") (%s)", cert.getSignature().getSignType(), getEndiannessStr(cert.getSignature().isLittleEndian())); + printf("\n"); + printf(" Issuer: %s\n", cert.getBody().getIssuer().c_str()); + printf(" Subject: %s\n", cert.getBody().getSubject().c_str()); + printf(" PublicKeyType: %s", getPublicKeyType(cert.getBody().getPublicKeyType())); + if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED)) + printf(" (%d)", cert.getBody().getPublicKeyType()); + printf("\n"); + printf(" CertID: 0x%" PRIx32 " \n", cert.getBody().getCertId()); + + if (cert.getBody().getPublicKeyType() == es::cert::RSA4096) + { + printf(" PublicKey:\n"); + printf(" Modulus:\n"); + fnd::SimpleTextOutput::hexDump(cert.getBody().getRsa4098PublicKey().modulus, _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED) ? crypto::rsa::kRsa4096Size : 0x10, 0x10, 6); + printf(" Public Exponent:\n"); + fnd::SimpleTextOutput::hexDump(cert.getBody().getRsa4098PublicKey().public_exponent, crypto::rsa::kRsaPublicExponentSize, 0x10, 6); + } + else if (cert.getBody().getPublicKeyType() == es::cert::RSA2048) + { + printf(" PublicKey:\n"); + printf(" Public Exponent:\n"); + fnd::SimpleTextOutput::hexDump(cert.getBody().getRsa2048PublicKey().modulus, _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED) ? crypto::rsa::kRsa2048Size : 0x10, 0x10, 6); + printf(" Modulus:\n"); + fnd::SimpleTextOutput::hexDump(cert.getBody().getRsa2048PublicKey().public_exponent, crypto::rsa::kRsaPublicExponentSize, 0x10, 6); + } + else if (cert.getBody().getPublicKeyType() == es::cert::ECDSA240) + { + printf(" PublicKey:\n"); + printf(" R:\n"); + fnd::SimpleTextOutput::hexDump(cert.getBody().getEcdsa240PublicKey().r, _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED) ? crypto::ecdsa::kEcdsa240Size : 0x10, 0x10, 6); + printf(" S:\n"); + fnd::SimpleTextOutput::hexDump(cert.getBody().getEcdsa240PublicKey().s, _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED) ? crypto::ecdsa::kEcdsa240Size : 0x10, 0x10, 6); + } + + + +#undef _HEXDUMP_L +#undef _HEXDUMP_U +#undef _SPLIT_VER +} + +const es::SignedData& EsCertProcess::getIssuerCert(const std::string& issuer_name) const +{ + std::string full_cert_name; + for (size_t i = 0; i < mCert.size(); i++) + { + full_cert_name = mCert[i].getBody().getIssuer() + es::sign::kIdentDelimiter + mCert[i].getBody().getSubject(); + if (full_cert_name == issuer_name) + { + return mCert[i]; + } + } + + throw fnd::Exception(kModuleName, "Issuer certificate does not exist"); +} + +const char* EsCertProcess::getSignTypeStr(es::sign::SignType type) const +{ + const char* str; + switch (type) + { + case (es::sign::SIGN_RSA4096_SHA1): + str = "RSA4096-SHA1"; + break; + case (es::sign::SIGN_RSA2048_SHA1): + str = "RSA2048-SHA1"; + break; + case (es::sign::SIGN_ECDSA240_SHA1): + str = "ECDSA240-SHA1"; + break; + case (es::sign::SIGN_RSA4096_SHA256): + str = "RSA4096-SHA256"; + break; + case (es::sign::SIGN_RSA2048_SHA256): + str = "RSA2048-SHA256"; + break; + case (es::sign::SIGN_ECDSA240_SHA256): + str = "ECDSA240-SHA256"; + break; + default: + str = "Unknown"; + break; + } + return str; +} + +const char* EsCertProcess::getEndiannessStr(bool isLittleEndian) const +{ + return isLittleEndian ? "LittleEndian" : "BigEndian"; +} + +const char* EsCertProcess::getPublicKeyType(es::cert::PublicKeyType type) const +{ + const char* str; + switch (type) + { + case (es::cert::RSA4096): + str = "RSA4096"; + break; + case (es::cert::RSA2048): + str = "RSA2048"; + break; + case (es::cert::ECDSA240): + str = "ECDSA240"; + break; + default: + str = "Unknown"; + break; + } + return str; +} \ No newline at end of file diff --git a/programs/nstool/source/EsCertProcess.h b/programs/nstool/source/EsCertProcess.h new file mode 100644 index 0000000..11c2430 --- /dev/null +++ b/programs/nstool/source/EsCertProcess.h @@ -0,0 +1,46 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include "nstool.h" + +class EsCertProcess +{ +public: + EsCertProcess(); + ~EsCertProcess(); + + void process(); + + void setInputFile(fnd::IFile* file, bool ownIFile); + void setKeyset(const sKeyset* keyset); + void setCliOutputMode(CliOutputMode type); + void setVerifyMode(bool verify); + +private: + const std::string kModuleName = "EsCertProcess"; + + fnd::IFile* mFile; + bool mOwnIFile; + const sKeyset* mKeyset; + CliOutputMode mCliOutputMode; + bool mVerify; + + fnd::List> mCert; + + void importCerts(); + void validateCerts(); + void validateCert(const es::SignedData& cert); + void displayCerts(); + void displayCert(const es::SignedData& cert); + + const es::SignedData& getIssuerCert(const std::string& issuer_name) const; + + const char* getSignTypeStr(es::sign::SignType type) const; + const char* getEndiannessStr(bool isLittleEndian) const; + const char* getPublicKeyType(es::cert::PublicKeyType type) const; +}; \ No newline at end of file diff --git a/programs/nstool/source/EsTikProcess.cpp b/programs/nstool/source/EsTikProcess.cpp new file mode 100644 index 0000000..909979a --- /dev/null +++ b/programs/nstool/source/EsTikProcess.cpp @@ -0,0 +1,187 @@ +#include +#include "OffsetAdjustedIFile.h" +#include "EsTikProcess.h" + +EsTikProcess::EsTikProcess() : + mFile(nullptr), + mOwnIFile(false), + mCliOutputMode(_BIT(OUTPUT_BASIC)), + mVerify(false) +{ +} + +EsTikProcess::~EsTikProcess() +{ + if (mOwnIFile) + { + delete mFile; + } +} + +void EsTikProcess::process() +{ + fnd::Vec scratch; + + if (mFile == nullptr) + { + throw fnd::Exception(kModuleName, "No file reader set."); + } + + scratch.alloc(mFile->size()); + mFile->read(scratch.data(), 0, scratch.size()); + + mTik.fromBytes(scratch.data(), scratch.size()); + + if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC)) + displayTicket(); +} + +void EsTikProcess::setInputFile(fnd::IFile* file, bool ownIFile) +{ + mFile = file; + mOwnIFile = ownIFile; +} + +void EsTikProcess::setKeyset(const sKeyset* keyset) +{ + mKeyset = keyset; +} + +void EsTikProcess::setCliOutputMode(CliOutputMode mode) +{ + mCliOutputMode = mode; +} + +void EsTikProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} + +void EsTikProcess::displayTicket() +{ +#define _SPLIT_VER(ver) ( (ver>>10) & 0x3f), ( (ver>>4) & 0x3f), ( (ver>>0) & 0xf) +#define _HEXDUMP_U(var, len) do { for (size_t a__a__A = 0; a__a__A < len; a__a__A++) printf("%02X", var[a__a__A]); } while(0) +#define _HEXDUMP_L(var, len) do { for (size_t a__a__A = 0; a__a__A < len; a__a__A++) printf("%02x", var[a__a__A]); } while(0) + + const es::TicketBody_V2& body = mTik.getBody(); + + printf("[ES Ticket]\n"); + printf(" SignType: 0x%" PRIx32 " (%s)\n", mTik.getSignature().getSignType(), mTik.getSignature().isLittleEndian()? "LittleEndian" : "BigEndian"); + printf(" Issuer: %s\n", body.getIssuer().c_str()); + printf(" Title Key:\n"); + printf(" EncMode: %s\n", getTitleKeyPersonalisationStr(body.getTitleKeyEncType())); + printf(" CommonKeyId: %02X\n", body.getCommonKeyId()); + printf(" EncData:\n"); + size_t size = body.getTitleKeyEncType() == es::ticket::RSA2048 ? crypto::rsa::kRsa2048Size : crypto::aes::kAes128KeySize; + fnd::SimpleTextOutput::hexDump(body.getEncTitleKey(), size, 0x10, 6); + + /* + if (body.getTitleKeyEncType() == es::ticket::AES128_CBC && body.getCommonKeyId() == 0) + { + byte_t iv[crypto::aes::kAesBlockSize]; + byte_t key[crypto::aes::kAes128KeySize]; + memcpy(iv, body.getRightsId(), crypto::aes::kAesBlockSize); + crypto::aes::AesCbcDecrypt(body.getEncTitleKey(), crypto::aes::kAes128KeySize, eticket_common_key, iv, key); + size = crypto::aes::kAes128KeySize; + printf(" TitleKey:\n"); + fnd::SimpleTextOutput::hexDump(key, size, 0x10, 6); + } + */ + printf(" Version: v%d.%d.%d (%d)\n", _SPLIT_VER(body.getTicketVersion()), body.getTicketVersion()); + printf(" License Type: %s\n", getLicenseTypeStr(body.getLicenseType())); + + if (body.getPropertyFlags().size() > 0) + { + printf(" Flags:\n"); + for (size_t i = 0; i < body.getPropertyFlags().size(); i++) + { + printf(" %s\n", getPropertyFlagStr(body.getPropertyFlags()[i])); + } + } + + printf(" Reserved Region:\n"); + fnd::SimpleTextOutput::hexDump(body.getReservedRegion(), 8, 0x10, 4); + printf(" TicketId: 0x%016" PRIx64 "\n", body.getTicketId()); + printf(" DeviceId: 0x%016" PRIx64 "\n", body.getDeviceId()); + printf(" RightsId: "); + fnd::SimpleTextOutput::hexDump(body.getRightsId(), 16); + + printf(" SectionTotalSize: 0x%x\n", body.getSectionTotalSize()); + printf(" SectionHeaderOffset: 0x%x\n", body.getSectionHeaderOffset()); + printf(" SectionNum: 0x%x\n", body.getSectionNum()); + printf(" SectionEntrySize: 0x%x\n", body.getSectionEntrySize()); + + +#undef _HEXDUMP_L +#undef _HEXDUMP_U +#undef _SPLIT_VER +} + +const char* EsTikProcess::getTitleKeyPersonalisationStr(byte_t flag) const +{ + const char* str = nullptr; + switch(flag) + { + case (es::ticket::AES128_CBC): + str = "Generic (AESCBC)"; + break; + case (es::ticket::RSA2048): + str = "Personalised (RSA2048)"; + break; + default: + str = "Unknown"; + break; + } + return str; +} + +const char* EsTikProcess::getLicenseTypeStr(byte_t flag) const +{ + const char* str = nullptr; + switch(flag) + { + case (es::ticket::LICENSE_PERMANENT): + str = "Permanent"; + break; + case (es::ticket::LICENSE_DEMO): + str = "Demo"; + break; + case (es::ticket::LICENSE_TRIAL): + str = "Trial"; + break; + case (es::ticket::LICENSE_RENTAL): + str = "Rental"; + break; + case (es::ticket::LICENSE_SUBSCRIPTION): + str = "Subscription"; + break; + case (es::ticket::LICENSE_SERVICE): + str = "Service"; + break; + default: + str = "Unknown"; + break; + } + return str; +} + +const char* EsTikProcess::getPropertyFlagStr(byte_t flag) const +{ + const char* str = nullptr; + switch(flag) + { + case (es::ticket::FLAG_PRE_INSTALL): + str = "PreInstall"; + break; + case (es::ticket::FLAG_SHARED_TITLE): + str = "SharedTitle"; + break; + case (es::ticket::FLAG_ALLOW_ALL_CONTENT): + str = "AllContent"; + break; + default: + str = "Unknown"; + break; + } + return str; +} \ No newline at end of file diff --git a/programs/nstool/source/EsTikProcess.h b/programs/nstool/source/EsTikProcess.h new file mode 100644 index 0000000..97fb2fb --- /dev/null +++ b/programs/nstool/source/EsTikProcess.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include "nstool.h" + +class EsTikProcess +{ +public: + EsTikProcess(); + ~EsTikProcess(); + + void process(); + + void setInputFile(fnd::IFile* file, bool ownIFile); + void setKeyset(const sKeyset* keyset); + void setCliOutputMode(CliOutputMode mode); + void setVerifyMode(bool verify); + +private: + const std::string kModuleName = "EsTikProcess"; + + fnd::IFile* mFile; + bool mOwnIFile; + const sKeyset* mKeyset; + CliOutputMode mCliOutputMode; + bool mVerify; + + es::SignedData mTik; + + void displayTicket(); + const char* getTitleKeyPersonalisationStr(byte_t flag) const; + const char* getLicenseTypeStr(byte_t flag) const; + const char* getPropertyFlagStr(byte_t flag) const; +}; \ No newline at end of file diff --git a/programs/nstool/source/main.cpp b/programs/nstool/source/main.cpp index 9039e59..fac17b5 100644 --- a/programs/nstool/source/main.cpp +++ b/programs/nstool/source/main.cpp @@ -10,6 +10,8 @@ #include "NsoProcess.h" #include "NroProcess.h" #include "NacpProcess.h" +#include "EsCertProcess.h" +#include "EsTikProcess.h" #include "AssetProcess.h" int main(int argc, char** argv) @@ -158,6 +160,28 @@ int main(int argc, char** argv) nacp.process(); } + else if (user_set.getFileType() == FILE_ES_CERT) + { + EsCertProcess cert; + + cert.setInputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read), OWN_IFILE); + cert.setKeyset(&user_set.getKeyset()); + cert.setCliOutputMode(user_set.getCliOutputMode()); + cert.setVerifyMode(user_set.isVerifyFile()); + + cert.process(); + } + else if (user_set.getFileType() == FILE_ES_TIK) + { + EsTikProcess tik; + + tik.setInputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read), OWN_IFILE); + tik.setKeyset(&user_set.getKeyset()); + tik.setCliOutputMode(user_set.getCliOutputMode()); + tik.setVerifyMode(user_set.isVerifyFile()); + + tik.process(); + } else if (user_set.getFileType() == FILE_HB_ASSET) { AssetProcess obj;