mirror of
https://github.com/jakcron/nstool
synced 2024-12-27 15:11:12 +00:00
[nstool] Add ticket validation via provided cert chain file.
This commit is contained in:
parent
1266ba3976
commit
9fb6731294
5 changed files with 131 additions and 10 deletions
|
@ -2,8 +2,10 @@
|
|||
#include <iomanip>
|
||||
|
||||
#include <fnd/SimpleTextOutput.h>
|
||||
#include <pki/SignUtils.h>
|
||||
#include "OffsetAdjustedIFile.h"
|
||||
#include "EsTikProcess.h"
|
||||
#include "PkiValidator.h"
|
||||
|
||||
|
||||
|
||||
|
@ -25,17 +27,10 @@ EsTikProcess::~EsTikProcess()
|
|||
|
||||
void EsTikProcess::process()
|
||||
{
|
||||
fnd::Vec<byte_t> scratch;
|
||||
importTicket();
|
||||
|
||||
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 (mVerify)
|
||||
verifyTicket();
|
||||
|
||||
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
|
||||
displayTicket();
|
||||
|
@ -52,6 +47,11 @@ void EsTikProcess::setKeyset(const sKeyset* keyset)
|
|||
mKeyset = keyset;
|
||||
}
|
||||
|
||||
void EsTikProcess::setCertificateChain(const fnd::List<pki::SignedData<pki::CertificateBody>>& certs)
|
||||
{
|
||||
mCerts = certs;
|
||||
}
|
||||
|
||||
void EsTikProcess::setCliOutputMode(CliOutputMode mode)
|
||||
{
|
||||
mCliOutputMode = mode;
|
||||
|
@ -62,6 +62,48 @@ void EsTikProcess::setVerifyMode(bool verify)
|
|||
mVerify = verify;
|
||||
}
|
||||
|
||||
void EsTikProcess::importTicket()
|
||||
{
|
||||
if (mFile == nullptr)
|
||||
{
|
||||
throw fnd::Exception(kModuleName, "No file reader set.");
|
||||
}
|
||||
|
||||
fnd::Vec<byte_t> scratch;
|
||||
scratch.alloc(mFile->size());
|
||||
mFile->read(scratch.data(), 0, scratch.size());
|
||||
mTik.fromBytes(scratch.data(), scratch.size());
|
||||
}
|
||||
|
||||
void EsTikProcess::verifyTicket()
|
||||
{
|
||||
PkiValidator pki_validator;
|
||||
fnd::Vec<byte_t> tik_hash;
|
||||
|
||||
switch (pki::sign::getHashAlgo(mTik.getSignature().getSignType()))
|
||||
{
|
||||
case (pki::sign::HASH_ALGO_SHA1):
|
||||
tik_hash.alloc(crypto::sha::kSha1HashLen);
|
||||
crypto::sha::Sha1(mTik.getBody().getBytes().data(), mTik.getBody().getBytes().size(), tik_hash.data());
|
||||
break;
|
||||
case (pki::sign::HASH_ALGO_SHA256):
|
||||
tik_hash.alloc(crypto::sha::kSha256HashLen);
|
||||
crypto::sha::Sha256(mTik.getBody().getBytes().data(), mTik.getBody().getBytes().size(), tik_hash.data());
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
pki_validator.setRootKey(mKeyset->pki.root_sign_key);
|
||||
pki_validator.addCertificates(mCerts);
|
||||
pki_validator.validateSignature(mTik.getBody().getIssuer(), mTik.getSignature().getSignType(), mTik.getSignature().getSignature(), tik_hash);
|
||||
}
|
||||
catch (const fnd::Exception& e)
|
||||
{
|
||||
std::cout << "[WARNING] Ticket signature could not be validated (" << e.error() << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void EsTikProcess::displayTicket()
|
||||
{
|
||||
#define _SPLIT_VER(ver) ( (ver>>10) & 0x3f), ( (ver>>4) & 0x3f), ( (ver>>0) & 0xf)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <fnd/IFile.h>
|
||||
#include <fnd/Vec.h>
|
||||
#include <pki/SignedData.h>
|
||||
#include <pki/CertificateBody.h>
|
||||
#include <es/TicketBody_V2.h>
|
||||
#include "nstool.h"
|
||||
|
||||
|
@ -17,6 +18,7 @@ public:
|
|||
|
||||
void setInputFile(fnd::IFile* file, bool ownIFile);
|
||||
void setKeyset(const sKeyset* keyset);
|
||||
void setCertificateChain(const fnd::List<pki::SignedData<pki::CertificateBody>>& certs);
|
||||
void setCliOutputMode(CliOutputMode mode);
|
||||
void setVerifyMode(bool verify);
|
||||
|
||||
|
@ -28,9 +30,13 @@ private:
|
|||
const sKeyset* mKeyset;
|
||||
CliOutputMode mCliOutputMode;
|
||||
bool mVerify;
|
||||
|
||||
fnd::List<pki::SignedData<pki::CertificateBody>> mCerts;
|
||||
|
||||
pki::SignedData<es::TicketBody_V2> mTik;
|
||||
|
||||
void importTicket();
|
||||
void verifyTicket();
|
||||
void displayTicket();
|
||||
const char* getSignTypeStr(uint32_t type) const;
|
||||
const char* getTitleKeyPersonalisationStr(byte_t flag) const;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "UserSettings.h"
|
||||
#include "version.h"
|
||||
#include "PkiValidator.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
@ -23,6 +24,8 @@
|
|||
#include <nx/nro.h>
|
||||
#include <nx/aset.h>
|
||||
#include <pki/SignedData.h>
|
||||
#include <pki/CertificateBody.h>
|
||||
#include <pki/SignUtils.h>
|
||||
#include <es/TicketBody_V2.h>
|
||||
|
||||
UserSettings::UserSettings()
|
||||
|
@ -185,6 +188,11 @@ const sOptional<std::string>& UserSettings::getAssetNacpPath() const
|
|||
return mAssetNacpPath;
|
||||
}
|
||||
|
||||
const fnd::List<pki::SignedData<pki::CertificateBody>>& UserSettings::getCertificateChain() const
|
||||
{
|
||||
return mCertChain;
|
||||
}
|
||||
|
||||
void UserSettings::populateCmdArgs(const std::vector<std::string>& arg_list, sCmdArgs& cmd_args)
|
||||
{
|
||||
// show help text
|
||||
|
@ -305,6 +313,12 @@ void UserSettings::populateCmdArgs(const std::vector<std::string>& arg_list, sCm
|
|||
cmd_args.ticket_path = arg_list[i+1];
|
||||
}
|
||||
|
||||
else if (arg_list[i] == "--cert")
|
||||
{
|
||||
if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter.");
|
||||
cmd_args.cert_path = arg_list[i+1];
|
||||
}
|
||||
|
||||
else if (arg_list[i] == "--part0")
|
||||
{
|
||||
if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter.");
|
||||
|
@ -535,6 +549,24 @@ void UserSettings::populateKeyset(sCmdArgs& args)
|
|||
}
|
||||
}
|
||||
|
||||
// import certificate chain
|
||||
if (args.cert_path.isSet)
|
||||
{
|
||||
fnd::SimpleFile cert_file;
|
||||
fnd::Vec<byte_t> cert_raw;
|
||||
pki::SignedData<pki::CertificateBody> cert;
|
||||
|
||||
cert_file.open(args.cert_path.var, fnd::SimpleFile::Read);
|
||||
cert_raw.alloc(cert_file.size());
|
||||
cert_file.read(cert_raw.data(), cert_raw.size());
|
||||
|
||||
for (size_t i = 0; i < cert_raw.size(); i+= cert.getBytes().size())
|
||||
{
|
||||
cert.fromBytes(cert_raw.data() + i, cert_raw.size() - i);
|
||||
mCertChain.addElement(cert);
|
||||
}
|
||||
}
|
||||
|
||||
// get titlekey from ticket
|
||||
if (args.ticket_path.isSet)
|
||||
{
|
||||
|
@ -542,11 +574,44 @@ void UserSettings::populateKeyset(sCmdArgs& args)
|
|||
fnd::Vec<byte_t> tik_raw;
|
||||
pki::SignedData<es::TicketBody_V2> tik;
|
||||
|
||||
// open and import ticket
|
||||
tik_file.open(args.ticket_path.var, fnd::SimpleFile::Read);
|
||||
tik_raw.alloc(tik_file.size());
|
||||
tik_file.read(tik_raw.data(), tik_raw.size());
|
||||
tik.fromBytes(tik_raw.data(), tik_raw.size());
|
||||
|
||||
// validate ticket signature
|
||||
if (mCertChain.size() > 0)
|
||||
{
|
||||
PkiValidator pki_validator;
|
||||
fnd::Vec<byte_t> tik_hash;
|
||||
|
||||
switch (pki::sign::getHashAlgo(tik.getSignature().getSignType()))
|
||||
{
|
||||
case (pki::sign::HASH_ALGO_SHA1):
|
||||
tik_hash.alloc(crypto::sha::kSha1HashLen);
|
||||
crypto::sha::Sha1(tik.getBody().getBytes().data(), tik.getBody().getBytes().size(), tik_hash.data());
|
||||
break;
|
||||
case (pki::sign::HASH_ALGO_SHA256):
|
||||
tik_hash.alloc(crypto::sha::kSha256HashLen);
|
||||
crypto::sha::Sha256(tik.getBody().getBytes().data(), tik.getBody().getBytes().size(), tik_hash.data());
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
pki_validator.setRootKey(mKeyset.pki.root_sign_key);
|
||||
pki_validator.addCertificates(mCertChain);
|
||||
pki_validator.validateSignature(tik.getBody().getIssuer(), tik.getSignature().getSignType(), tik.getSignature().getSignature(), tik_hash);
|
||||
}
|
||||
catch (const fnd::Exception& e)
|
||||
{
|
||||
std::cout << "[WARNING] Ticket signature could not be validated (" << e.error() << ")" << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// extract title key
|
||||
if (tik.getBody().getTitleKeyEncType() == es::ticket::AES128_CBC)
|
||||
{
|
||||
memcpy(mKeyset.nca.manual_title_key_aesctr.key, tik.getBody().getEncTitleKey(), crypto::aes::kAes128KeySize);
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
#include <string>
|
||||
#include <fnd/types.h>
|
||||
#include <fnd/Vec.h>
|
||||
#include <fnd/List.h>
|
||||
#include <pki/SignedData.h>
|
||||
#include <pki/CertificateBody.h>
|
||||
#include <nx/npdm.h>
|
||||
#include "nstool.h"
|
||||
|
||||
|
@ -39,6 +42,7 @@ public:
|
|||
const sOptional<std::string>& getNcaPart3Path() const;
|
||||
const sOptional<std::string>& getAssetIconPath() const;
|
||||
const sOptional<std::string>& getAssetNacpPath() const;
|
||||
const fnd::List<pki::SignedData<pki::CertificateBody>>& getCertificateChain() const;
|
||||
|
||||
private:
|
||||
const std::string kModuleName = "UserSettings";
|
||||
|
@ -63,6 +67,7 @@ private:
|
|||
sOptional<std::string> nca_titlekey;
|
||||
sOptional<std::string> nca_bodykey;
|
||||
sOptional<std::string> ticket_path;
|
||||
sOptional<std::string> cert_path;
|
||||
sOptional<std::string> part0_path;
|
||||
sOptional<std::string> part1_path;
|
||||
sOptional<std::string> part2_path;
|
||||
|
@ -95,6 +100,8 @@ private:
|
|||
sOptional<std::string> mAssetIconPath;
|
||||
sOptional<std::string> mAssetNacpPath;
|
||||
|
||||
fnd::List<pki::SignedData<pki::CertificateBody>> mCertChain;
|
||||
|
||||
bool mListApi;
|
||||
bool mListSymbols;
|
||||
nx::npdm::InstructionType mInstructionType;
|
||||
|
|
|
@ -192,6 +192,7 @@ int main(int argc, char** argv)
|
|||
|
||||
tik.setInputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read), OWN_IFILE);
|
||||
tik.setKeyset(&user_set.getKeyset());
|
||||
tik.setCertificateChain(user_set.getCertificateChain());
|
||||
tik.setCliOutputMode(user_set.getCliOutputMode());
|
||||
tik.setVerifyMode(user_set.isVerifyFile());
|
||||
|
||||
|
|
Loading…
Reference in a new issue