mirror of
https://github.com/DarkStore-3DS/Project_CTR.git
synced 2026-07-07 00:39:42 +00:00
Add source code for ctrtool
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
#include <ntd/n3ds/es/Certificate.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <brd/es/es_cert.h>
|
||||
#include <tc/ByteData.h>
|
||||
#include <tc/crypto/Sha256Generator.h>
|
||||
|
||||
#include <tc/cli.h>
|
||||
|
||||
size_t ntd::n3ds::es::getCertificateSize(byte_t* data)
|
||||
{
|
||||
size_t signature_size = 0;
|
||||
size_t public_key_size = 0;
|
||||
|
||||
if (data == nullptr) { return 0; }
|
||||
|
||||
signature_size = getCertificateSignatureSize(data);
|
||||
if (signature_size == 0) { return 0; }
|
||||
|
||||
brd::es::ESCertHeader* header = (brd::es::ESCertHeader*)(data + signature_size);
|
||||
brd::es::ESCertPubKeyType public_key_type = header->pubKeyType.unwrap();
|
||||
switch (public_key_type)
|
||||
{
|
||||
case brd::es::ESCertPubKeyType::RSA4096:
|
||||
public_key_size = sizeof(brd::es::ESCertRsa4096PublicKey);
|
||||
break;
|
||||
case brd::es::ESCertPubKeyType::RSA2048:
|
||||
public_key_size = sizeof(brd::es::ESCertRsa2048PublicKey);
|
||||
break;
|
||||
case brd::es::ESCertPubKeyType::ECC:
|
||||
public_key_size = sizeof(brd::es::ESCertEcc233PublicKey);
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return signature_size + sizeof(brd::es::ESCertHeader) + public_key_size;
|
||||
}
|
||||
|
||||
size_t ntd::n3ds::es::getCertificateSignatureSize(byte_t* data)
|
||||
{
|
||||
size_t signature_size = 0;
|
||||
|
||||
if (data == nullptr) { return 0; }
|
||||
|
||||
brd::es::ESSigType sig_type = ((tc::bn::be32<brd::es::ESSigType>*)data)->unwrap();
|
||||
switch (sig_type)
|
||||
{
|
||||
case brd::es::ESSigType::RSA4096_SHA1:
|
||||
case brd::es::ESSigType::RSA4096_SHA256:
|
||||
signature_size = sizeof(brd::es::ESSigRsa4096);
|
||||
break;
|
||||
case brd::es::ESSigType::RSA2048_SHA1:
|
||||
case brd::es::ESSigType::RSA2048_SHA256:
|
||||
signature_size = sizeof(brd::es::ESSigRsa2048);
|
||||
break;
|
||||
case brd::es::ESSigType::ECC_SHA1:
|
||||
case brd::es::ESSigType::ECC_SHA256:
|
||||
signature_size = sizeof(brd::es::ESSigEcc233);
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return signature_size;
|
||||
}
|
||||
|
||||
size_t ntd::n3ds::es::getCertificateSignableOffset(byte_t* data)
|
||||
{
|
||||
size_t signature_size = 0;
|
||||
|
||||
if (data == nullptr) { return 0; }
|
||||
|
||||
signature_size = getCertificateSignatureSize(data);
|
||||
if (signature_size == 0) { return 0; }
|
||||
|
||||
return signature_size - sizeof(brd::es::ESIssuer);
|
||||
}
|
||||
|
||||
size_t ntd::n3ds::es::getCertificateSignableSize(byte_t* data)
|
||||
{
|
||||
return getCertificateSize(data) - getCertificateSignableOffset(data);
|
||||
}
|
||||
|
||||
void* ntd::n3ds::es::getCertificateHeaderPtr(byte_t* data)
|
||||
{
|
||||
size_t signature_size = 0;
|
||||
|
||||
if (data == nullptr) { return nullptr; }
|
||||
|
||||
signature_size = getCertificateSignatureSize(data);
|
||||
if (signature_size == 0) { return nullptr; }
|
||||
|
||||
return (data + signature_size);
|
||||
}
|
||||
|
||||
void* ntd::n3ds::es::getCertificatePublicKeyPtr(byte_t* data)
|
||||
{
|
||||
size_t signature_size = 0;
|
||||
|
||||
if (data == nullptr) { return nullptr; }
|
||||
|
||||
signature_size = getCertificateSignatureSize(data);
|
||||
if (signature_size == 0) { return nullptr; }
|
||||
|
||||
return (data + signature_size + sizeof(brd::es::ESCertHeader));
|
||||
}
|
||||
|
||||
ntd::n3ds::es::CertificateDeserialiser::CertificateDeserialiser(const std::shared_ptr<tc::io::IStream>& cert_stream) :
|
||||
Certificate(),
|
||||
mModuleLabel("ntd::n3ds::es::CertificateDeserialiser")
|
||||
{
|
||||
if (cert_stream == nullptr)
|
||||
{
|
||||
throw tc::ArgumentNullException(mModuleLabel, "Stream was null.");
|
||||
}
|
||||
|
||||
// process signature
|
||||
this->signature = SignatureDeserialiser(cert_stream);
|
||||
switch (this->signature.sig_type)
|
||||
{
|
||||
case brd::es::ESSigType::RSA4096_SHA1:
|
||||
case brd::es::ESSigType::RSA4096_SHA256:
|
||||
case brd::es::ESSigType::RSA2048_SHA1:
|
||||
case brd::es::ESSigType::RSA2048_SHA256:
|
||||
case brd::es::ESSigType::ECC_SHA1:
|
||||
case brd::es::ESSigType::ECC_SHA256:
|
||||
break;
|
||||
default:
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected signature type.");
|
||||
}
|
||||
size_t signature_size = getSignatureSizeFromSigType(this->signature.sig_type);
|
||||
|
||||
// get certificate header
|
||||
brd::es::ESCertHeader header;
|
||||
cert_stream->seek(signature_size, tc::io::SeekOrigin::Begin);
|
||||
if (cert_stream->read((byte_t*)&header, sizeof(brd::es::ESCertHeader)) < sizeof(brd::es::ESCertHeader))
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected size after reading.");
|
||||
}
|
||||
|
||||
// get public key
|
||||
union CertificatePublicKey
|
||||
{
|
||||
brd::es::ESCertRsa4096PublicKey rsa4096;
|
||||
brd::es::ESCertRsa2048PublicKey rsa2048;
|
||||
brd::es::ESCertEcc233PublicKey ecc233;
|
||||
} public_key;
|
||||
size_t public_key_size;
|
||||
|
||||
switch (header.pubKeyType.unwrap())
|
||||
{
|
||||
case brd::es::ESCertPubKeyType::RSA4096:
|
||||
public_key_size = sizeof(brd::es::ESCertRsa4096PublicKey);
|
||||
break;
|
||||
case brd::es::ESCertPubKeyType::RSA2048:
|
||||
public_key_size = sizeof(brd::es::ESCertRsa2048PublicKey);
|
||||
break;
|
||||
case brd::es::ESCertPubKeyType::ECC:
|
||||
public_key_size = sizeof(brd::es::ESCertEcc233PublicKey);
|
||||
break;
|
||||
default:
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected public key type.");
|
||||
}
|
||||
|
||||
cert_stream->seek(signature_size + sizeof(brd::es::ESCertHeader), tc::io::SeekOrigin::Begin);
|
||||
if (cert_stream->read((byte_t*)&public_key, public_key_size) < public_key_size)
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected size after reading.");
|
||||
}
|
||||
|
||||
// calculate hash for optional signature validation later
|
||||
tc::ByteData total_cert_data = tc::ByteData(signature_size + sizeof(brd::es::ESCertHeader) + public_key_size);
|
||||
cert_stream->seek(0, tc::io::SeekOrigin::Begin);
|
||||
if (cert_stream->read(total_cert_data.data(), total_cert_data.size()) < total_cert_data.size())
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected size after reading.");
|
||||
}
|
||||
tc::crypto::GenerateSha256Hash(calculated_hash.data(), total_cert_data.data() + getCertificateSignableOffset(total_cert_data.data()), ntd::n3ds::es::getCertificateSignableSize(total_cert_data.data()));
|
||||
|
||||
|
||||
// store properties
|
||||
//this->signature = SignatureDeserialiser(cert_stream);
|
||||
this->subject = header.name.deviceId.decode();
|
||||
this->date = header.date.unwrap();
|
||||
this->public_key_type = header.pubKeyType.unwrap();
|
||||
switch (header.pubKeyType.unwrap())
|
||||
{
|
||||
case brd::es::ESCertPubKeyType::RSA4096:
|
||||
memcpy(&this->rsa4096_public_key, &public_key.rsa4096.pubKey, sizeof(brd::es::Rsa4096PublicKey));
|
||||
break;
|
||||
case brd::es::ESCertPubKeyType::RSA2048:
|
||||
memcpy(&this->rsa2048_public_key, &public_key.rsa2048.pubKey, sizeof(brd::es::Rsa2048PublicKey));
|
||||
break;
|
||||
case brd::es::ESCertPubKeyType::ECC:
|
||||
memcpy(&this->ecc233_public_key, &public_key.ecc233.pubKey, sizeof(brd::es::Ecc233PublicKey));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#include <ntd/n3ds/es/RsaSigner.h>
|
||||
|
||||
#include <tc/crypto/RsaPkcs1Sha1Signer.h>
|
||||
#include <tc/crypto/RsaPkcs1Sha256Signer.h>
|
||||
|
||||
ntd::n3ds::es::RsaSigner::RsaSigner(brd::es::ESSigType sig_type, const std::string& issuer, const tc::crypto::RsaKey& rsa_key) :
|
||||
mSigType(sig_type),
|
||||
mIssuer(issuer),
|
||||
mRsaKey(rsa_key)
|
||||
{
|
||||
switch (mSigType) {
|
||||
case brd::es::ESSigType::RSA4096_SHA1:
|
||||
case brd::es::ESSigType::RSA4096_SHA256:
|
||||
if ((mRsaKey.n.size() << 3) != 4096) throw tc::ArgumentOutOfRangeException("ntd::n3ds::es::RsaSigner::RsaSigner()", "Key size inferred from SigType did not match actual key size.");
|
||||
break;
|
||||
case brd::es::ESSigType::RSA2048_SHA1:
|
||||
case brd::es::ESSigType::RSA2048_SHA256:
|
||||
if ((mRsaKey.n.size() << 3) != 2048) throw tc::ArgumentOutOfRangeException("ntd::n3ds::es::RsaSigner::RsaSigner()", "Key size inferred from SigType did not match actual key size.");
|
||||
break;
|
||||
default:
|
||||
throw tc::ArgumentOutOfRangeException("ntd::n3ds::es::RsaSigner::RsaSigner()", "SigType not supported for RsaSigner.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& ntd::n3ds::es::RsaSigner::getIssuer()
|
||||
{
|
||||
return mIssuer;
|
||||
}
|
||||
|
||||
brd::es::ESSigType ntd::n3ds::es::RsaSigner::getSigType()
|
||||
{
|
||||
return mSigType;
|
||||
}
|
||||
|
||||
bool ntd::n3ds::es::RsaSigner::signHash(const byte_t* hash, byte_t* signature)
|
||||
{
|
||||
bool signSucceed = false;
|
||||
|
||||
switch (mSigType) {
|
||||
case brd::es::ESSigType::RSA4096_SHA1:
|
||||
signSucceed = tc::crypto::SignRsa4096Pkcs1Sha1(signature, hash, mRsaKey);
|
||||
break;
|
||||
case brd::es::ESSigType::RSA2048_SHA1:
|
||||
signSucceed = tc::crypto::SignRsa2048Pkcs1Sha1(signature, hash, mRsaKey);
|
||||
break;
|
||||
case brd::es::ESSigType::RSA4096_SHA256:
|
||||
signSucceed = tc::crypto::SignRsa4096Pkcs1Sha256(signature, hash, mRsaKey);
|
||||
break;
|
||||
case brd::es::ESSigType::RSA2048_SHA256:
|
||||
signSucceed = tc::crypto::SignRsa2048Pkcs1Sha256(signature, hash, mRsaKey);
|
||||
break;
|
||||
default:
|
||||
signSucceed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return signSucceed;
|
||||
}
|
||||
|
||||
bool ntd::n3ds::es::RsaSigner::verifyHash(const byte_t* hash, const byte_t* signature)
|
||||
{
|
||||
bool verifySucceed = false;
|
||||
|
||||
switch (mSigType) {
|
||||
case brd::es::ESSigType::RSA4096_SHA1:
|
||||
verifySucceed = tc::crypto::VerifyRsa4096Pkcs1Sha1(signature, hash, mRsaKey);
|
||||
break;
|
||||
case brd::es::ESSigType::RSA2048_SHA1:
|
||||
verifySucceed = tc::crypto::VerifyRsa2048Pkcs1Sha1(signature, hash, mRsaKey);
|
||||
break;
|
||||
case brd::es::ESSigType::RSA4096_SHA256:
|
||||
verifySucceed = tc::crypto::VerifyRsa4096Pkcs1Sha256(signature, hash, mRsaKey);
|
||||
break;
|
||||
case brd::es::ESSigType::RSA2048_SHA256:
|
||||
verifySucceed = tc::crypto::VerifyRsa2048Pkcs1Sha256(signature, hash, mRsaKey);
|
||||
break;
|
||||
default:
|
||||
verifySucceed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return verifySucceed;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
#include <ntd/n3ds/es/Signature.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <brd/es/es_cert.h>
|
||||
#include <tc/ByteData.h>
|
||||
#include <tc/crypto/Sha256Generator.h>
|
||||
|
||||
#include <tc/cli.h>
|
||||
|
||||
size_t ntd::n3ds::es::getSignatureSizeFromSigType(brd::es::ESSigType sig_type)
|
||||
{
|
||||
size_t signature_size = 0;
|
||||
switch (sig_type)
|
||||
{
|
||||
case brd::es::ESSigType::RSA4096_SHA1:
|
||||
case brd::es::ESSigType::RSA4096_SHA256:
|
||||
signature_size = sizeof(brd::es::ESSigRsa4096);
|
||||
break;
|
||||
case brd::es::ESSigType::RSA2048_SHA1:
|
||||
case brd::es::ESSigType::RSA2048_SHA256:
|
||||
signature_size = sizeof(brd::es::ESSigRsa2048);
|
||||
break;
|
||||
case brd::es::ESSigType::ECC_SHA1:
|
||||
case brd::es::ESSigType::ECC_SHA256:
|
||||
signature_size = sizeof(brd::es::ESSigEcc233);
|
||||
break;
|
||||
default:
|
||||
signature_size = 0;
|
||||
}
|
||||
|
||||
return signature_size;
|
||||
}
|
||||
|
||||
size_t ntd::n3ds::es::getSignatureIssuerOffset(brd::es::ESSigType sig_type)
|
||||
{
|
||||
size_t issuer_offset = 0;
|
||||
switch (sig_type)
|
||||
{
|
||||
case brd::es::ESSigType::RSA4096_SHA1:
|
||||
case brd::es::ESSigType::RSA4096_SHA256:
|
||||
issuer_offset = sizeof(brd::es::ESSigRsa4096) - sizeof(brd::es::ESIssuer);
|
||||
break;
|
||||
case brd::es::ESSigType::RSA2048_SHA1:
|
||||
case brd::es::ESSigType::RSA2048_SHA256:
|
||||
issuer_offset = sizeof(brd::es::ESSigRsa2048) - sizeof(brd::es::ESIssuer);
|
||||
break;
|
||||
case brd::es::ESSigType::ECC_SHA1:
|
||||
case brd::es::ESSigType::ECC_SHA256:
|
||||
issuer_offset = sizeof(brd::es::ESSigEcc233) - sizeof(brd::es::ESIssuer);
|
||||
break;
|
||||
default:
|
||||
issuer_offset = 0;
|
||||
}
|
||||
|
||||
return issuer_offset;
|
||||
}
|
||||
|
||||
ntd::n3ds::es::SignatureDeserialiser::SignatureDeserialiser(const std::shared_ptr<tc::io::IStream>& stream) :
|
||||
Signature(),
|
||||
mModuleLabel("ntd::n3ds::es::SignatureDeserialiser")
|
||||
{
|
||||
if (stream == nullptr)
|
||||
{
|
||||
throw tc::ArgumentNullException(mModuleLabel, "Stream was null.");
|
||||
}
|
||||
|
||||
// must have at least 4 bytes for signature magic code
|
||||
if (stream->length() < sizeof(uint32_t))
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Stream was too small to import signature.");
|
||||
}
|
||||
|
||||
// get signature
|
||||
enum class SignType {
|
||||
RSA4096,
|
||||
RSA2048,
|
||||
ECDSA233,
|
||||
};
|
||||
union SignatureSignature
|
||||
{
|
||||
tc::bn::be32<brd::es::ESSigType> sigType;
|
||||
brd::es::ESSigRsa4096 rsa4096;
|
||||
brd::es::ESSigRsa2048 rsa2048;
|
||||
brd::es::ESSigEcc233 ecdsa233;
|
||||
} signature_data;
|
||||
size_t signature_size = 0;
|
||||
|
||||
stream->seek(0, tc::io::SeekOrigin::Begin);
|
||||
if (stream->read((byte_t*)&signature_data, sizeof(uint32_t)) < sizeof(uint32_t))
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Unexpected size after reading.");
|
||||
}
|
||||
switch (signature_data.sigType.unwrap())
|
||||
{
|
||||
case brd::es::ESSigType::RSA4096_SHA1:
|
||||
case brd::es::ESSigType::RSA4096_SHA256:
|
||||
signature_size = sizeof(brd::es::ESSigRsa4096);
|
||||
break;
|
||||
case brd::es::ESSigType::RSA2048_SHA1:
|
||||
case brd::es::ESSigType::RSA2048_SHA256:
|
||||
signature_size = sizeof(brd::es::ESSigRsa2048);
|
||||
break;
|
||||
case brd::es::ESSigType::ECC_SHA1:
|
||||
case brd::es::ESSigType::ECC_SHA256:
|
||||
signature_size = sizeof(brd::es::ESSigEcc233);
|
||||
break;
|
||||
default:
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Unexpected signature type.");
|
||||
}
|
||||
stream->seek(0, tc::io::SeekOrigin::Begin);
|
||||
if (stream->read((byte_t*)&signature_data, signature_size) < signature_size)
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Unexpected size after reading.");
|
||||
}
|
||||
|
||||
// store properties
|
||||
this->sig_type = signature_data.sigType.unwrap();
|
||||
switch (signature_data.sigType.unwrap())
|
||||
{
|
||||
case brd::es::ESSigType::RSA4096_SHA1:
|
||||
case brd::es::ESSigType::RSA4096_SHA256:
|
||||
this->sig = tc::ByteData(signature_data.rsa4096.sig.data(), signature_data.rsa4096.sig.size());
|
||||
this->issuer = signature_data.rsa4096.issuer.decode();
|
||||
break;
|
||||
case brd::es::ESSigType::RSA2048_SHA1:
|
||||
case brd::es::ESSigType::RSA2048_SHA256:
|
||||
this->sig = tc::ByteData(signature_data.rsa2048.sig.data(), signature_data.rsa2048.sig.size());
|
||||
this->issuer = signature_data.rsa2048.issuer.decode();
|
||||
break;
|
||||
case brd::es::ESSigType::ECC_SHA1:
|
||||
case brd::es::ESSigType::ECC_SHA256:
|
||||
this->sig = tc::ByteData((byte_t*)&signature_data.ecdsa233.sig, sizeof(brd::es::Ecc233Sig));
|
||||
this->issuer = signature_data.ecdsa233.issuer.decode();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
#include <ntd/n3ds/es/Ticket.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <brd/es/es_ticket.h>
|
||||
#include <tc/ByteData.h>
|
||||
#include <tc/crypto/Sha256Generator.h>
|
||||
|
||||
#include <tc/cli.h>
|
||||
|
||||
ntd::n3ds::es::TicketDeserialiser::TicketDeserialiser(const std::shared_ptr<tc::io::IStream>& tik_stream) :
|
||||
Ticket(),
|
||||
mModuleLabel("ntd::n3ds::es::TicketDeserialiser")
|
||||
{
|
||||
if (tik_stream == nullptr)
|
||||
{
|
||||
throw tc::ArgumentNullException(mModuleLabel, "Stream was null.");
|
||||
}
|
||||
|
||||
if (tik_stream->length() < sizeof(brd::es::ESV1Ticket))
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Stream was too small to import ticket.");
|
||||
}
|
||||
|
||||
// import TIK v1 data (less the section headers)
|
||||
tc::ByteData tik_data = tc::ByteData(sizeof(brd::es::ESV1Ticket));
|
||||
tik_stream->seek(0, tc::io::SeekOrigin::Begin);
|
||||
if (tik_stream->read(tik_data.data(), tik_data.size()) < tik_data.size())
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK had unexpected size after reading.");
|
||||
}
|
||||
|
||||
// get pointer
|
||||
brd::es::ESV1Ticket* tik = (brd::es::ESV1Ticket*)tik_data.data();
|
||||
if (tik->head.sig.sigType.unwrap() != brd::es::ESSigType::RSA2048_SHA256)
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK had unexpected signature type.");
|
||||
}
|
||||
if (tik->head.version != 1)
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK had unexpected format version.");
|
||||
}
|
||||
if (tik->v1Head.hdrVersion.unwrap() != 1)
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK v1 header extension had unexpected format version.");
|
||||
}
|
||||
if (tik->v1Head.hdrSize.unwrap() != sizeof(brd::es::ESV1TicketHeader))
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK v1 header extension had header size.");
|
||||
}
|
||||
if (tik->v1Head.sectHdrOfst.unwrap() != sizeof(brd::es::ESV1TicketHeader))
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK v1 header extension had poorly aligned sectHdrOfst.");
|
||||
}
|
||||
if (tik->v1Head.sectHdrEntrySize.unwrap() != sizeof(brd::es::ESV1SectionHeader))
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK v1 header extension had unexpected size for section headers.");
|
||||
}
|
||||
if (tik_stream->length() < static_cast<int64_t>(sizeof(brd::es::ESTicket) + tik->v1Head.ticketSize.unwrap()))
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Stream was too small for calculated ticket size.");
|
||||
}
|
||||
|
||||
// import full TIK v1 data
|
||||
tik_data = tc::ByteData(sizeof(brd::es::ESTicket) + tik->v1Head.ticketSize.unwrap());
|
||||
tik_stream->seek(0, tc::io::SeekOrigin::Begin);
|
||||
if (tik_stream->read(tik_data.data(), tik_data.size()) < tik_data.size())
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK had unexpected size after reading.");
|
||||
}
|
||||
|
||||
// update pointer
|
||||
tik = (brd::es::ESV1Ticket*)tik_data.data();
|
||||
|
||||
/*
|
||||
fmt::print("ESTicket:\n");
|
||||
fmt::print("sigType: {:x}\n", tik->head.sig.sigType.unwrap());
|
||||
fmt::print("issuer: {}\n", tik->head.sig.issuer.str());
|
||||
fmt::print("version: {:d}\n", tik->head.version);
|
||||
fmt::print("caCrlVersion: {:d}\n", tik->head.caCrlVersion);
|
||||
fmt::print("signerCrlVersion: {:d}\n", tik->head.signerCrlVersion);
|
||||
fmt::print("titleKey: {}\n", tc::cli::FormatUtil::formatBytesAsString(tik->head.titleKey.data(), tik->head.titleKey.size(), true, ""));
|
||||
fmt::print("ticketId: {:016x}\n", tik->head.ticketId.unwrap());
|
||||
fmt::print("deviceId: {:08x}\n", tik->head.deviceId.unwrap());
|
||||
fmt::print("titleId: {:016x}\n", tik->head.titleId.unwrap());
|
||||
fmt::print("sysAccessMask: {}\n", tc::cli::FormatUtil::formatBytesAsString(tik->head.sysAccessMask.data(), tik->head.sysAccessMask.size(), true, ""));
|
||||
fmt::print("ticketVersion: {:d}\n", tik->head.ticketVersion.unwrap());
|
||||
fmt::print("accessTitleId: {:08x}\n", tik->head.accessTitleId.unwrap());
|
||||
fmt::print("accessTitleMask: {:08x}\n", tik->head.accessTitleMask.unwrap());
|
||||
fmt::print("licenseType: {:02x}\n", tik->head.licenseType);
|
||||
fmt::print("keyId: {:02x}\n", tik->head.keyId);
|
||||
fmt::print("propertyMask: {:04x}\n", tik->head.propertyMask.unwrap());
|
||||
fmt::print("customData: {}\n", tc::cli::FormatUtil::formatBytesAsString(tik->head.customData.data(), tik->head.customData.size(), true, ""));
|
||||
fmt::print("audit: {:02x}\n", tik->head.audit);
|
||||
fmt::print("cidxMask: {}\n", tc::cli::FormatUtil::formatBytesAsString(tik->head.cidxMask.data(), tik->head.cidxMask.size(), true, ""));
|
||||
for (size_t i = 0; i < tik->head.limits.size(); i++)
|
||||
{
|
||||
fmt::print("lp entry {}: code={:08x}, limit={:08x}\n", i, tik->head.limits[i].code.unwrap(), tik->head.limits[i].limit.unwrap());
|
||||
}
|
||||
fmt::print("ESV1TicketHeader:\n");
|
||||
fmt::print("hdrVersion: {:04x}\n", tik->v1Head.hdrVersion.unwrap());
|
||||
fmt::print("hdrSize: {:04x}\n", tik->v1Head.hdrSize.unwrap());
|
||||
fmt::print("ticketSize: {:08x}\n", tik->v1Head.ticketSize.unwrap());
|
||||
fmt::print("sectHdrOfst: {:08x}\n", tik->v1Head.sectHdrOfst.unwrap());
|
||||
fmt::print("nSectHdrs: {:04x}\n", tik->v1Head.nSectHdrs.unwrap());
|
||||
fmt::print("sectHdrEntrySize: {:04x}\n", tik->v1Head.sectHdrEntrySize.unwrap());
|
||||
fmt::print("flags: {:08x}\n", tik->v1Head.flags.unwrap());
|
||||
size_t sect_hdr_num = tik->v1Head.nSectHdrs.unwrap();
|
||||
//brd::es::ESV1SectionHeader* sect_hdr = (brd::es::ESV1SectionHeader*)(tik_data.data() + sizeof(brd::es::ESTicket) + tik->v1Head.sectHdrOfst.unwrap());
|
||||
for (size_t i = 0; i < sect_hdr_num; i++)
|
||||
{
|
||||
fmt::print("sectHdr {:d}:\n", i);
|
||||
fmt::print(" sectOfst: {:08x}\n", tik->sectHdrs[i].sectOfst.unwrap());
|
||||
fmt::print(" nRecords: {:08x}\n", tik->sectHdrs[i].nRecords.unwrap());
|
||||
fmt::print(" recordSize: {:08x}\n", tik->sectHdrs[i].recordSize.unwrap());
|
||||
fmt::print(" sectionSize: {:08x}\n", tik->sectHdrs[i].sectionSize.unwrap());
|
||||
fmt::print(" sectionType: {:04x}\n", tik->sectHdrs[i].sectionType.unwrap());
|
||||
fmt::print(" flags: {:04x}\n", tik->sectHdrs[i].flags.unwrap());
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
struct TicketReservedForCtr
|
||||
{
|
||||
tc::bn::pad<0x14> reserved_00;
|
||||
tc::bn::le32<uint32_t> ec_account_id;
|
||||
tc::bn::pad<0x01> reserved_01;
|
||||
};
|
||||
|
||||
// generate hash
|
||||
byte_t* tik_hash_begin = (byte_t*)&tik->head.sig.issuer;
|
||||
size_t tik_hash_size = tik_data.size() - size_t(tik_hash_begin - tik_data.data());
|
||||
tc::crypto::GenerateSha256Hash(this->calculated_hash.data(), tik_hash_begin, tik_hash_size);
|
||||
|
||||
// basic fields
|
||||
this->signature.sig_type = tik->head.sig.sigType.unwrap();
|
||||
this->signature.sig = tc::ByteData(tik->head.sig.sig.data(), tik->head.sig.sig.size());
|
||||
this->signature.issuer = tik->head.sig.issuer.decode();
|
||||
this->title_key = tik->head.titleKey;
|
||||
this->ticket_id = tik->head.ticketId.unwrap();
|
||||
this->device_id = tik->head.deviceId.unwrap();
|
||||
this->title_id = tik->head.titleId.unwrap();
|
||||
this->ticket_version = tik->head.ticketVersion.unwrap();
|
||||
this->license_type = tik->head.licenseType;
|
||||
this->key_id = tik->head.keyId;
|
||||
|
||||
// process data from reserved field
|
||||
auto custom_data = (TicketReservedForCtr*)tik->head.reserved.data();
|
||||
this->ec_account_id = custom_data->ec_account_id.unwrap();
|
||||
|
||||
// find the demo launch limit
|
||||
for (size_t i = 0; i < tik->head.limits.size(); i++)
|
||||
{
|
||||
if (tik->head.limits[i].code.unwrap() == (uint32_t)brd::es::ESLimitCode::NUM_LAUNCH)
|
||||
{
|
||||
launch_count = tik->head.limits[i].limit.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// process content index
|
||||
for (size_t i = 0; i < tik->v1Head.nSectHdrs.unwrap(); i++)
|
||||
{
|
||||
if (tik->sectHdrs[i].sectionType.unwrap() == (uint32_t)brd::es::ESItemType::CONTENT)
|
||||
{
|
||||
if (tik->sectHdrs[i].recordSize.unwrap() != sizeof(brd::es::ESV1ContentRecord))
|
||||
{
|
||||
throw tc::InvalidOperationException(mModuleLabel, fmt::format("Invalid size for ESV1ContentRecord. (expected: 0x{:x}, got 0x{:x})", sizeof(brd::es::ESV1ContentRecord), tik->sectHdrs[i].recordSize.unwrap()));
|
||||
}
|
||||
brd::es::ESV1ContentRecord* content_records = (brd::es::ESV1ContentRecord*)(tik_data.data() + sizeof(brd::es::ESTicket) + tik->sectHdrs[i].sectOfst.unwrap());
|
||||
for (size_t j = 0; j < tik->sectHdrs[i].nRecords.unwrap(); j++)
|
||||
{
|
||||
tc::bn::bitarray<0x80>* access_mask = (tc::bn::bitarray<0x80>*)content_records[j].accessMask.data();
|
||||
for (size_t bit = 0; bit < access_mask->bit_size(); bit++)
|
||||
{
|
||||
if (access_mask->test(bit))
|
||||
{
|
||||
this->enabled_content.set(content_records[j].offset.unwrap() + bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
#include <ntd/n3ds/es/TitleMetaData.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <brd/es/es_tmd.h>
|
||||
#include <tc/ByteData.h>
|
||||
#include <tc/crypto/Sha256Generator.h>
|
||||
|
||||
ntd::n3ds::es::TitleMetaDataDeserialiser::TitleMetaDataDeserialiser(const std::shared_ptr<tc::io::IStream>& tmd_stream) :
|
||||
TitleMetaData(),
|
||||
mModuleLabel("ntd::n3ds::es::TitleMetaDataDeserialiser")
|
||||
{
|
||||
if (tmd_stream == nullptr)
|
||||
{
|
||||
throw tc::ArgumentNullException(mModuleLabel, "TMD stream was null.");
|
||||
}
|
||||
|
||||
if (tmd_stream->length() < (sizeof(brd::es::ESV1TitleMeta) + sizeof(brd::es::ESV1ContentMeta)))
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD was too small.");
|
||||
}
|
||||
|
||||
// import TMD v1 data
|
||||
if (tc::is_int64_t_too_large_for_size_t(tmd_stream->length()))
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD was too large to read into memory.");
|
||||
}
|
||||
tc::ByteData tmd_data = tc::ByteData(static_cast<size_t>(tmd_stream->length()));
|
||||
tmd_stream->seek(0, tc::io::SeekOrigin::Begin);
|
||||
if (tmd_stream->read(tmd_data.data(), tmd_data.size()) < tmd_data.size())
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had unexpected size after reading.");
|
||||
}
|
||||
|
||||
|
||||
// get pointer
|
||||
brd::es::ESV1TitleMeta* tmd = (brd::es::ESV1TitleMeta*)tmd_data.data();
|
||||
if (tmd->sig.sigType.unwrap() != brd::es::ESSigType::RSA2048_SHA256)
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had unexpected signature type.");
|
||||
}
|
||||
if (tmd->head.version != 1)
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had unexpected format version.");
|
||||
}
|
||||
|
||||
// hash for staged validiation
|
||||
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> hash;
|
||||
|
||||
// calculate hash for optional signature validation later
|
||||
tc::crypto::GenerateSha256Hash(calculated_hash.data(), (byte_t*)&tmd->sig.issuer, (size_t)((byte_t*)&tmd->v1Head.cmdGroups - (byte_t*)&tmd->sig.issuer));
|
||||
|
||||
// verify v1 ESV1ContentMetaGroup array using hash in v1 header
|
||||
tc::crypto::GenerateSha256Hash(hash.data(), (byte_t*)&tmd->v1Head.cmdGroups, sizeof(tmd->v1Head.cmdGroups));
|
||||
if (memcmp(hash.data(), tmd->v1Head.hash.data(), hash.size()) != 0)
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had invalid CMD group hash.");
|
||||
}
|
||||
|
||||
/*
|
||||
std::cout << "[Tmd]" << std::endl;
|
||||
std::cout << " > Issuer: " << tmd->sig.issuer.data() << std::endl;
|
||||
std::cout << " > Version: " << (int)tmd->head.version << std::endl;
|
||||
std::cout << " > CACrl Version: " << (int)tmd->head.caCrlVersion << std::endl;
|
||||
std::cout << " > SignerCrl Version: " << (int)tmd->head.signerCrlVersion << std::endl;
|
||||
std::cout << " > TitleId: " << std::hex << std::setfill('0') << std::setw(16) << tmd->head.titleId.unwrap() << std::endl;
|
||||
*/
|
||||
|
||||
// determine if the TMD is the correct size given expected number of ESV1ContentMeta entries
|
||||
size_t cmd_table_num = tmd->v1Head.cmdGroups[0].nCmds.unwrap();
|
||||
if (tmd_data.size() != (sizeof(brd::es::ESV1TitleMeta) + (cmd_table_num * sizeof(brd::es::ESV1ContentMeta))))
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had unexpected size");
|
||||
}
|
||||
|
||||
// verify ESV1ContentMeta array
|
||||
tc::crypto::GenerateSha256Hash(hash.data(), (byte_t*)&tmd->contents, cmd_table_num * sizeof(brd::es::ESV1ContentMeta));
|
||||
if (memcmp(hash.data(), tmd->v1Head.cmdGroups[0].groupHash.data(), hash.size()) != 0)
|
||||
{
|
||||
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had invalid CMD group[0] hash.");
|
||||
}
|
||||
|
||||
// verify other fields
|
||||
if (tmd->head.sysVersion.unwrap() != 0)
|
||||
{
|
||||
throw tc::InvalidOperationException(mModuleLabel, "TMD sysVersion had unexpected value.");
|
||||
}
|
||||
if (tmd->head.type.unwrap() != brd::es::ESTitleType::CT_TITLE)
|
||||
{
|
||||
throw tc::InvalidOperationException(mModuleLabel, "TMD type had unexpected value.");
|
||||
}
|
||||
|
||||
// process header now that it is verified
|
||||
this->signature.sig_type = tmd->sig.sigType.unwrap();
|
||||
this->signature.sig = tc::ByteData(tmd->sig.sig.data(), tmd->sig.sig.size());
|
||||
this->signature.issuer = tmd->sig.issuer.decode();
|
||||
this->title_id = tmd->head.titleId.unwrap();
|
||||
this->title_version = tmd->head.titleVersion.unwrap();
|
||||
struct TmdCustomDataForCtr
|
||||
{
|
||||
struct TwlCustomData
|
||||
{
|
||||
tc::bn::le32<uint32_t> public_save_data_size;
|
||||
tc::bn::le32<uint32_t> private_save_data_size;
|
||||
tc::bn::pad<4> padding;
|
||||
byte_t flag;
|
||||
};
|
||||
struct CtrCustomData
|
||||
{
|
||||
tc::bn::le32<uint32_t> save_data_size;
|
||||
tc::bn::bitarray<1> flag;
|
||||
};
|
||||
union {
|
||||
TwlCustomData twl;
|
||||
CtrCustomData ctr;
|
||||
};
|
||||
};
|
||||
auto custom_data = (TmdCustomDataForCtr*)tmd->head.customData.data();
|
||||
this->twl_custom_data.public_save_data_size = custom_data->twl.public_save_data_size;
|
||||
this->twl_custom_data.private_save_data_size = custom_data->twl.private_save_data_size;
|
||||
this->twl_custom_data.flag = custom_data->twl.flag;
|
||||
this->ctr_custom_data.save_data_size = custom_data->ctr.save_data_size;
|
||||
this->ctr_custom_data.is_snake_only = custom_data->ctr.flag.test(0);
|
||||
|
||||
// process ESV1ContentMeta entries
|
||||
for (size_t i = 0; i < cmd_table_num; i++)
|
||||
{
|
||||
this->content_info.push_back(ContentInfo(
|
||||
tmd->contents[i].cid.unwrap(),
|
||||
tmd->contents[i].index.unwrap(),
|
||||
(tmd->contents[i].type.unwrap() & (uint16_t)brd::es::ESContentType_ENCRYPTED) == (uint16_t)brd::es::ESContentType_ENCRYPTED,
|
||||
(tmd->contents[i].type.unwrap() & (uint16_t)brd::es::ESContentType_OPTIONAL) == (uint16_t)brd::es::ESContentType_OPTIONAL,
|
||||
(int64_t)tmd->contents[i].size.unwrap(),
|
||||
tmd->contents[i].hash));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user