diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 17a1a7e..e996876 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -10,8 +10,10 @@ "/usr/include", "${workspaceRoot}", "${workspaceRoot}/lib/libcrypto/include", + "${workspaceRoot}/lib/libcrypto/source/polarssl/libinclude", "${workspaceRoot}/lib/libcompress/include", "${workspaceRoot}/lib/libes/include", + "${workspaceRoot}/lib/libpki/include", "${workspaceRoot}/lib/libfnd/include", "${workspaceRoot}/lib/libnx/include", "${workspaceRoot}/lib/libnx-hb/include" diff --git a/.vscode/settings.json b/.vscode/settings.json index d1f6ffb..768b573 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,7 +27,14 @@ "memory": "cpp", "tuple": "cpp", "__locale": "cpp", - "cinttypes": "cpp" + "cinttypes": "cpp", "__bit_reference": "cpp", + "algorithm": "cpp", + "__functional_base_03": "cpp", + "__tuple": "cpp", + "chrono": "cpp", + "functional": "cpp", + "limits": "cpp", + "ratio": "cpp" } } \ No newline at end of file diff --git a/KEYS.md b/KEYS.md index bd1b706..0a3098b 100644 --- a/KEYS.md +++ b/KEYS.md @@ -25,6 +25,10 @@ package2_sign_key_private : RSA2048 Private Exponent (0x100 bytes) ; Ticket Keys ticket_commonkey_## : AES128 Key (0x10 bytes) +; PKI Root Signing Key +pki_root_sign_key_modulus : RSA4096 Modulus (0x200 bytes) +pki_root_sign_key_private : RSA4096 Private Exponent (0x200 bytes) + ; NCA Keys nca_header_key : AES128-XTS Key (0x20 bytes) nca_header_sign_key_modulus : RSA2048 Modulus (0x100 bytes) diff --git a/NXTools.sln b/NXTools.sln index ade6849..2305e4c 100644 --- a/NXTools.sln +++ b/NXTools.sln @@ -44,6 +44,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcompress", "lib\libcompr EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnx-hb", "lib\libnx-hb\libnx-hb.vcxproj", "{738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpki", "lib\libpki\libpki.vcxproj", "{B9113734-6E84-44FF-8CF7-58199AA815C5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -108,6 +110,14 @@ Global {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}.Release|x64.Build.0 = Release|x64 {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}.Release|x86.ActiveCfg = Release|Win32 {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}.Release|x86.Build.0 = Release|Win32 + {B9113734-6E84-44FF-8CF7-58199AA815C5}.Debug|x64.ActiveCfg = Debug|x64 + {B9113734-6E84-44FF-8CF7-58199AA815C5}.Debug|x64.Build.0 = Debug|x64 + {B9113734-6E84-44FF-8CF7-58199AA815C5}.Debug|x86.ActiveCfg = Debug|Win32 + {B9113734-6E84-44FF-8CF7-58199AA815C5}.Debug|x86.Build.0 = Debug|Win32 + {B9113734-6E84-44FF-8CF7-58199AA815C5}.Release|x64.ActiveCfg = Release|x64 + {B9113734-6E84-44FF-8CF7-58199AA815C5}.Release|x64.Build.0 = Release|x64 + {B9113734-6E84-44FF-8CF7-58199AA815C5}.Release|x86.ActiveCfg = Release|Win32 + {B9113734-6E84-44FF-8CF7-58199AA815C5}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -120,6 +130,7 @@ Global {AF09FA96-4463-417D-8FE6-526063F41349} = {E0863FCC-8E72-490D-BE1B-458F12CA8298} {CF01B5B7-730A-447F-9BB2-5EDA9B082177} = {170B4A09-1B67-4A62-93AB-116EBCFF4A8C} {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63} = {170B4A09-1B67-4A62-93AB-116EBCFF4A8C} + {B9113734-6E84-44FF-8CF7-58199AA815C5} = {170B4A09-1B67-4A62-93AB-116EBCFF4A8C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {07DCCACC-D10D-47C9-85AE-FB9C54DB7D62} diff --git a/README.md b/README.md index 1bb4f5a..377b1a2 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,13 @@ Tools & Libraries for NX (Nintendo Switch). # Libraries -* __libfnd__ - Foundation library. -* __libcrypto__ - Cryptographic functions (AES,SHA,RSA). Wrapper for [mbedTLS](https://github.com/ARMmbed/mbedtls) -* __libcompress__ - Compression algorithms (LZ4). Wrapper for [lz4](https://github.com/lz4/lz4) -* __libes__ - Handling of (NX relevant) eShop file type processing. (eTickets, etc) -* __libnx__ - Handling of NX file types. -* __libnx-hb__ - Handling of NX (homebrew extensions) file types. +* __libfnd__ - Foundation library. +* __libcrypto__ - Cryptographic functions (AES,SHA,RSA). Wrapper for [mbedTLS](https://github.com/ARMmbed/mbedtls) +* __libcompress__ - Compression algorithms (LZ4). Wrapper for [lz4](https://github.com/lz4/lz4) +* __libpki__ - Processes Nintendo's proprietary PKI. +* __libes__ - Processes Nintendo's eShop file types. +* __libnx__ - Processes NX file types. +* __libnx-hb__ - Processes NX file types (homebrew extensions). # Building diff --git a/lib/libcrypto/crypto.vcxproj b/lib/libcrypto/crypto.vcxproj index 72a1f38..06dfa98 100644 --- a/lib/libcrypto/crypto.vcxproj +++ b/lib/libcrypto/crypto.vcxproj @@ -121,6 +121,7 @@ + @@ -137,6 +138,7 @@ + diff --git a/lib/libcrypto/crypto.vcxproj.filters b/lib/libcrypto/crypto.vcxproj.filters index 09c9b93..0110282 100644 --- a/lib/libcrypto/crypto.vcxproj.filters +++ b/lib/libcrypto/crypto.vcxproj.filters @@ -66,6 +66,9 @@ Header Files\crypto + + Header Files\crypto + @@ -101,6 +104,9 @@ Source Files\polarssl + + Source Files + diff --git a/lib/libcrypto/include/crypto/base64.h b/lib/libcrypto/include/crypto/base64.h new file mode 100644 index 0000000..67c6ecd --- /dev/null +++ b/lib/libcrypto/include/crypto/base64.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include + +namespace crypto +{ +namespace base64 +{ + size_t B64_GetEncodeLen(const uint8_t* src, size_t slen); + void B64_Encode(const uint8_t* src, size_t slen, uint8_t* dst, size_t dlen); + size_t B64_GetDecodeLen(const uint8_t* src, size_t slen); + void B64_Decode(const uint8_t* src, size_t slen, uint8_t* dst, size_t dlen); +} +} \ No newline at end of file diff --git a/lib/libcrypto/source/base64_wrapper.cpp b/lib/libcrypto/source/base64_wrapper.cpp new file mode 100644 index 0000000..1bc6f66 --- /dev/null +++ b/lib/libcrypto/source/base64_wrapper.cpp @@ -0,0 +1,30 @@ +#include +#include + +size_t crypto::base64::B64_GetEncodeLen(const uint8_t* src, size_t slen) +{ + size_t dlen = 0; + + base64_encode(nullptr, &dlen, src, slen); + + return dlen; +} + +void crypto::base64::B64_Encode(const uint8_t* src, size_t slen, uint8_t* dst, size_t dlen) +{ + base64_encode(dst, &dlen, src, slen); +} + +size_t crypto::base64::B64_GetDecodeLen(const uint8_t* src, size_t slen) +{ + size_t dlen = 0; + + base64_decode(nullptr, &dlen, src, slen); + + return dlen; +} + +void crypto::base64::B64_Decode(const uint8_t* src, size_t slen, uint8_t* dst, size_t dlen) +{ + base64_decode(dst, &dlen, src, slen); +} \ No newline at end of file diff --git a/lib/libes/es.vcxproj b/lib/libes/es.vcxproj index ac4d213..ae9f9c3 100644 --- a/lib/libes/es.vcxproj +++ b/lib/libes/es.vcxproj @@ -122,21 +122,14 @@ - - - - - - - - - - - + + + + diff --git a/lib/libes/es.vcxproj.filters b/lib/libes/es.vcxproj.filters index 1f0bc28..f933a32 100644 --- a/lib/libes/es.vcxproj.filters +++ b/lib/libes/es.vcxproj.filters @@ -18,38 +18,9 @@ - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - Header Files - - Header Files - - - Header Files - - - Header Files - Header Files @@ -57,4 +28,12 @@ Header Files + + + Source Files + + + Source Files + + \ No newline at end of file diff --git a/lib/libes/include/es/TicketBody_V2.h b/lib/libes/include/es/TicketBody_V2.h index 6b0be01..4741cac 100644 --- a/lib/libes/include/es/TicketBody_V2.h +++ b/lib/libes/include/es/TicketBody_V2.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include namespace es @@ -42,14 +43,8 @@ namespace es byte_t getCommonKeyId() const; void setCommonKeyId(byte_t id); - bool isPreInstall() const; - void setIsPreInstall(bool isPreInstall); - - bool isSharedTitle() const; - void setIsSharedTitle(bool isSharedTitle); - - bool allowAllContent() const; - void setAllowAllContent(bool allowAllContent); + const fnd::List& getPropertyFlags() const; + void setPropertyFlags(const fnd::List& flags); const byte_t* getReservedRegion() const; void setReservedRegion(const byte_t* data, size_t len); @@ -91,9 +86,7 @@ namespace es uint16_t mTicketVersion; ticket::LicenseType mLicenseType; byte_t mCommonKeyId; - bool mPreInstall; - bool mSharedTitle; - bool mAllowAllContent; + fnd::List mPropertyFlags; byte_t mReservedRegion[ticket::kReservedRegionSize]; // explicitly reserved uint64_t mTicketId; uint64_t mDeviceId; diff --git a/lib/libes/source/TicketBody_V2.cpp b/lib/libes/source/TicketBody_V2.cpp index a9bd29c..93816c7 100644 --- a/lib/libes/source/TicketBody_V2.cpp +++ b/lib/libes/source/TicketBody_V2.cpp @@ -26,9 +26,8 @@ void es::TicketBody_V2::operator=(const TicketBody_V2 & other) mEncType = other.mEncType; mTicketVersion = other.mTicketVersion; mLicenseType = other.mLicenseType; - mPreInstall = other.mPreInstall; - mSharedTitle = other.mSharedTitle; - mAllowAllContent = other.mAllowAllContent; + mCommonKeyId = other.mCommonKeyId; + mPropertyFlags = other.mPropertyFlags; memcpy(mReservedRegion, other.mReservedRegion, ticket::kReservedRegionSize); mTicketId = other.mTicketId; mDeviceId = other.mDeviceId; @@ -48,9 +47,7 @@ bool es::TicketBody_V2::operator==(const TicketBody_V2 & other) const && (mEncType == other.mEncType) \ && (mTicketVersion == other.mTicketVersion) \ && (mLicenseType == other.mLicenseType) \ - && (mPreInstall == other.mPreInstall) \ - && (mSharedTitle == other.mSharedTitle) \ - && (mAllowAllContent == other.mAllowAllContent) \ + && (mPropertyFlags == other.mPropertyFlags) \ && (memcmp(mReservedRegion, other.mReservedRegion, ticket::kReservedRegionSize) == 0) \ && (mTicketId == other.mTicketId) \ && (mDeviceId == other.mDeviceId) \ @@ -74,19 +71,22 @@ void es::TicketBody_V2::toBytes() body->format_version = (ticket::kFormatVersion); - strncmp(body->issuer, mIssuer.c_str(), ticket::kIssuerSize); + strncpy(body->issuer, mIssuer.c_str(), ticket::kIssuerSize); memcpy(body->enc_title_key, mEncTitleKey, ticket::kEncTitleKeySize); body->title_key_enc_type = (mEncType); body->ticket_version = (mTicketVersion); + body->license_type = mLicenseType; + body->common_key_id = mCommonKeyId; byte_t property_mask = 0; - property_mask |= mPreInstall ? _BIT(ticket::FLAG_PRE_INSTALL) : 0; - property_mask |= mSharedTitle ? _BIT(ticket::FLAG_SHARED_TITLE) : 0; - property_mask |= mAllowAllContent ? _BIT(ticket::FLAG_ALLOW_ALL_CONTENT) : 0; + for (size_t i = 0; i < mPropertyFlags.size(); i++) + { + property_mask |= _BIT(mPropertyFlags[i]); + } body->property_mask = (property_mask); memcpy(body->reserved_region, mReservedRegion, ticket::kReservedRegionSize); body->ticket_id = (mTicketId); body->device_id = (mDeviceId); - memcmp(body->rights_id, mRightsId, ticket::kRightsIdSize); + memcpy(body->rights_id, mRightsId, ticket::kRightsIdSize); body->account_id = (mAccountId); body->sect_total_size = (mSectTotalSize); body->sect_header_offset = (mSectHeaderOffset); @@ -112,14 +112,17 @@ void es::TicketBody_V2::fromBytes(const byte_t * bytes, size_t len) throw fnd::Exception(kModuleName, "Unsupported format version"); } - mIssuer.append(body->issuer, ticket::kIssuerSize); + mIssuer = std::string(body->issuer, _MIN(strlen(body->issuer), ticket::kIssuerSize)); memcpy(mEncTitleKey, body->enc_title_key, ticket::kEncTitleKeySize); mEncType = (ticket::TitleKeyEncType)body->title_key_enc_type; mTicketVersion = body->ticket_version.get(); mLicenseType = (ticket::LicenseType)body->license_type; - mPreInstall = _HAS_BIT(body->property_mask, ticket::FLAG_PRE_INSTALL); - mSharedTitle = _HAS_BIT(body->property_mask, ticket::FLAG_SHARED_TITLE); - mAllowAllContent = _HAS_BIT(body->property_mask, ticket::FLAG_ALLOW_ALL_CONTENT); + mCommonKeyId = body->common_key_id; + for (size_t i = 0; i < mPropertyFlags.size(); i++) + { + if (_HAS_BIT(body->property_mask, i)) + mPropertyFlags.addElement((ticket::PropertyMaskFlags)i); + } memcpy(mReservedRegion, body->reserved_region, ticket::kReservedRegionSize); mTicketId = body->ticket_id.get(); mDeviceId = body->device_id.get(); @@ -144,9 +147,8 @@ void es::TicketBody_V2::clear() mEncType = ticket::AES128_CBC; mTicketVersion = 0; mLicenseType = ticket::LICENSE_PERMANENT; - mPreInstall = false; - mSharedTitle = false; - mAllowAllContent = false; + mCommonKeyId = 0; + mPropertyFlags.clear(); memset(mReservedRegion, 0, ticket::kReservedRegionSize); mTicketId = 0; mDeviceId = 0; @@ -224,34 +226,14 @@ void es::TicketBody_V2::setCommonKeyId(byte_t id) mCommonKeyId = id; } -bool es::TicketBody_V2::isPreInstall() const +const fnd::List& es::TicketBody_V2::getPropertyFlags() const { - return mPreInstall; + return mPropertyFlags; } -void es::TicketBody_V2::setIsPreInstall(bool isPreInstall) +void es::TicketBody_V2::setPropertyFlags(const fnd::List& flags) { - mPreInstall = isPreInstall; -} - -bool es::TicketBody_V2::isSharedTitle() const -{ - return mSharedTitle; -} - -void es::TicketBody_V2::setIsSharedTitle(bool isSharedTitle) -{ - mSharedTitle = isSharedTitle; -} - -bool es::TicketBody_V2::allowAllContent() const -{ - return mAllowAllContent; -} - -void es::TicketBody_V2::setAllowAllContent(bool allowAllContent) -{ - mAllowAllContent = allowAllContent; + mPropertyFlags = flags; } const byte_t * es::TicketBody_V2::getReservedRegion() const diff --git a/lib/libfnd/include/fnd/Vec.h b/lib/libfnd/include/fnd/Vec.h index 6cc2ed3..667027a 100644 --- a/lib/libfnd/include/fnd/Vec.h +++ b/lib/libfnd/include/fnd/Vec.h @@ -146,6 +146,11 @@ namespace fnd { fnd::Exception("Vec", "Failed to allocate memory for vector"); } + for (size_t i = 0; i < new_size; i++) + { + m_Vec[i] = 0; + } + m_Size = new_size; } @@ -159,6 +164,10 @@ namespace fnd { fnd::Exception("Vec", "Failed to allocate memory for vector"); } + for (size_t i = 0; i < new_size; i++) + { + new_vec[i] = 0; + } for (size_t i = 0; i < _MIN(m_Size, new_size); i++) { diff --git a/lib/libes/include/es/CertificateBody.h b/lib/libpki/include/pki/CertificateBody.h similarity index 94% rename from lib/libes/include/es/CertificateBody.h rename to lib/libpki/include/pki/CertificateBody.h index 20cbbe9..c02494c 100644 --- a/lib/libes/include/es/CertificateBody.h +++ b/lib/libpki/include/pki/CertificateBody.h @@ -1,9 +1,9 @@ #pragma once #include #include -#include +#include -namespace es +namespace pki { class CertificateBody : public fnd::ISerialisable @@ -27,7 +27,7 @@ namespace es const std::string& getIssuer() const; void setIssuer(const std::string& issuer); - es::cert::PublicKeyType getPublicKeyType() const; + pki::cert::PublicKeyType getPublicKeyType() const; void setPublicKeyType(cert::PublicKeyType type); const std::string& getSubject() const; diff --git a/lib/libpki/include/pki/SignUtils.h b/lib/libpki/include/pki/SignUtils.h new file mode 100644 index 0000000..dc71e2a --- /dev/null +++ b/lib/libpki/include/pki/SignUtils.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include + +namespace pki +{ + +namespace sign +{ + pki::sign::SignatureAlgo getSignatureAlgo(pki::sign::SignatureId sign_id); + pki::sign::HashAlgo getHashAlgo(pki::sign::SignatureId sign_id); +} + +} \ No newline at end of file diff --git a/lib/libes/include/es/SignatureBlock.h b/lib/libpki/include/pki/SignatureBlock.h similarity index 84% rename from lib/libes/include/es/SignatureBlock.h rename to lib/libpki/include/pki/SignatureBlock.h index 46d5c98..1fcad69 100644 --- a/lib/libes/include/es/SignatureBlock.h +++ b/lib/libpki/include/pki/SignatureBlock.h @@ -1,9 +1,9 @@ #pragma once #include #include -#include +#include -namespace es +namespace pki { class SignatureBlock : public fnd::ISerialisable @@ -24,8 +24,8 @@ namespace es // variables void clear(); - es::sign::SignType getSignType() const; - void setSignType(es::sign::SignType type); + pki::sign::SignatureId getSignType() const; + void setSignType(pki::sign::SignatureId type); bool isLittleEndian() const; void setLittleEndian(bool isLE); @@ -41,7 +41,7 @@ namespace es fnd::Vec mRawBinary; // variables - es::sign::SignType mSignType; + pki::sign::SignatureId mSignType; bool mIsLittleEndian; fnd::Vec mSignature; }; diff --git a/lib/libes/include/es/SignedData.h b/lib/libpki/include/pki/SignedData.h similarity index 94% rename from lib/libes/include/es/SignedData.h rename to lib/libpki/include/pki/SignedData.h index 366b350..abe38d6 100644 --- a/lib/libes/include/es/SignedData.h +++ b/lib/libpki/include/pki/SignedData.h @@ -1,9 +1,9 @@ #pragma once #include #include -#include +#include -namespace es +namespace pki { template class SignedData @@ -25,7 +25,7 @@ namespace es // variables void clear(); - const es::SignatureBlock& getSignature() const; + const pki::SignatureBlock& getSignature() const; void setSignature(const SignatureBlock& signature); const T& getBody() const; @@ -111,7 +111,7 @@ namespace es } template - inline const es::SignatureBlock& SignedData::getSignature() const + inline const pki::SignatureBlock& SignedData::getSignature() const { return mSignature; } diff --git a/lib/libes/include/es/cert.h b/lib/libpki/include/pki/cert.h similarity index 98% rename from lib/libes/include/es/cert.h rename to lib/libpki/include/pki/cert.h index ab4ec0a..c7e3b52 100644 --- a/lib/libes/include/es/cert.h +++ b/lib/libpki/include/pki/cert.h @@ -5,7 +5,7 @@ #include #include -namespace es +namespace pki { namespace cert { diff --git a/lib/libes/include/es/sign.h b/lib/libpki/include/pki/sign.h similarity index 57% rename from lib/libes/include/es/sign.h rename to lib/libpki/include/pki/sign.h index 61d352f..27442cc 100644 --- a/lib/libes/include/es/sign.h +++ b/lib/libpki/include/pki/sign.h @@ -5,21 +5,37 @@ #include #include -namespace es +namespace pki { namespace sign { - enum SignType + enum SignatureId { - SIGN_RSA4096_SHA1 = 0x10000, - SIGN_RSA2048_SHA1, - SIGN_ECDSA240_SHA1, - SIGN_RSA4096_SHA256, - SIGN_RSA2048_SHA256, - SIGN_ECDSA240_SHA256, + SIGN_ID_RSA4096_SHA1 = 0x10000, + SIGN_ID_RSA2048_SHA1, + SIGN_ID_ECDSA240_SHA1, + SIGN_ID_RSA4096_SHA256, + SIGN_ID_RSA2048_SHA256, + SIGN_ID_ECDSA240_SHA256, + }; + + enum SignatureAlgo + { + SIGN_ALGO_RSA4096, + SIGN_ALGO_RSA2048, + SIGN_ALGO_ECDSA240 + }; + + enum HashAlgo + { + HASH_ALGO_SHA1, + HASH_ALGO_SHA256 }; static const size_t kEcdsaSigSize = 0x3C; + + static const std::string kRootIssuerStr = "Root"; + static const std::string kIdentDelimiter = "-"; } #pragma pack(push,1) struct sRsa4096SignBlock diff --git a/lib/libpki/libpki.vcxproj b/lib/libpki/libpki.vcxproj new file mode 100644 index 0000000..7347aca --- /dev/null +++ b/lib/libpki/libpki.vcxproj @@ -0,0 +1,144 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {B9113734-6E84-44FF-8CF7-58199AA815C5} + libpki + 10.0.16299.0 + + + + StaticLibrary + true + v141 + MultiByte + + + StaticLibrary + false + v141 + true + MultiByte + + + StaticLibrary + true + v141 + MultiByte + + + StaticLibrary + false + v141 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + MaxSpeed + true + true + true + true + ..\libfnd\include;..\libcrypto\include;..\libpki\include; + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + + + true + true + + + + + Level3 + Disabled + true + true + ..\libfnd\include;..\libcrypto\include;..\libpki\include; + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + + + + + Level3 + Disabled + true + true + ..\libfnd\include;..\libcrypto\include;..\libpki\include; + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + + + + + Level3 + MaxSpeed + true + true + true + true + ..\libfnd\include;..\libcrypto\include;..\libpki\include; + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + + + true + true + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/libpki/libpki.vcxproj.filters b/lib/libpki/libpki.vcxproj.filters new file mode 100644 index 0000000..ab6f249 --- /dev/null +++ b/lib/libpki/libpki.vcxproj.filters @@ -0,0 +1,51 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + + \ No newline at end of file diff --git a/lib/libpki/libpki.vcxproj.user b/lib/libpki/libpki.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/lib/libpki/libpki.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/lib/libpki/makefile b/lib/libpki/makefile new file mode 100644 index 0000000..ba0b6e8 --- /dev/null +++ b/lib/libpki/makefile @@ -0,0 +1,47 @@ +# Sources +SRC_DIR = source +OBJS = $(foreach dir,$(SRC_DIR),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(SRC_DIR),$(subst .c,.o,$(wildcard $(dir)/*.c))) + +# External dependencies +DEPENDS = fnd crypto +LIB_DIR = .. +INCS = -I"include" $(foreach dep,$(DEPENDS), -I"$(LIB_DIR)/lib$(dep)/include") + + +# Compiler Settings +CXXFLAGS = -std=c++11 $(INCS) -D__STDC_FORMAT_MACROS -Wall -Wno-unused-value +CFLAGS = -std=c11 $(INCS) -Wall -Wno-unused-value +ARFLAGS = cr -o +ifeq ($(OS),Windows_NT) + # Windows Only Flags/Libs + CC = x86_64-w64-mingw32-gcc + CXX = x86_64-w64-mingw32-g++ + CFLAGS += -Wno-unused-but-set-variable + CXXFLAGS += -Wno-unused-but-set-variable +else + UNAME = $(shell uname -s) + ifeq ($(UNAME), Darwin) + # MacOS Only Flags/Libs + CFLAGS += -Wno-unused-private-field + CXXFLAGS += -Wno-unused-private-field + ARFLAGS = rc + else + # *nix Only Flags/Libs + CFLAGS += -Wno-unused-but-set-variable + CXXFLAGS += -Wno-unused-but-set-variable + endif + +endif + +# Output +OUTPUT = $(shell basename $(CURDIR)).a + +main: build + +rebuild: clean build + +build: $(OBJS) + ar $(ARFLAGS) $(OUTPUT) $(OBJS) + +clean: + rm -rf $(OUTPUT) $(OBJS) \ No newline at end of file diff --git a/lib/libes/source/CertificateBody.cpp b/lib/libpki/source/CertificateBody.cpp similarity index 75% rename from lib/libes/source/CertificateBody.cpp rename to lib/libpki/source/CertificateBody.cpp index 11bec02..d844101 100644 --- a/lib/libes/source/CertificateBody.cpp +++ b/lib/libpki/source/CertificateBody.cpp @@ -1,16 +1,16 @@ -#include +#include -es::CertificateBody::CertificateBody() +pki::CertificateBody::CertificateBody() { clear(); } -es::CertificateBody::CertificateBody(const CertificateBody& other) +pki::CertificateBody::CertificateBody(const CertificateBody& other) { *this = other; } -void es::CertificateBody::operator=(const CertificateBody& other) +void pki::CertificateBody::operator=(const CertificateBody& other) { mRawBinary = other.mRawBinary; mIssuer = other.mIssuer; @@ -22,7 +22,7 @@ void es::CertificateBody::operator=(const CertificateBody& other) mEcdsa240PublicKey = other.mEcdsa240PublicKey; } -bool es::CertificateBody::operator==(const CertificateBody& other) const +bool pki::CertificateBody::operator==(const CertificateBody& other) const { return (mIssuer == other.mIssuer) \ && (mSubject == other.mSubject) \ @@ -33,12 +33,12 @@ bool es::CertificateBody::operator==(const CertificateBody& other) const && (mEcdsa240PublicKey == other.mEcdsa240PublicKey); } -bool es::CertificateBody::operator!=(const CertificateBody& other) const +bool pki::CertificateBody::operator!=(const CertificateBody& other) const { return !(*this == other); } -void es::CertificateBody::toBytes() +void pki::CertificateBody::toBytes() { // get public key size size_t pubkeySize = 0; @@ -86,7 +86,7 @@ void es::CertificateBody::toBytes() } } -void es::CertificateBody::fromBytes(const byte_t* src, size_t size) +void pki::CertificateBody::fromBytes(const byte_t* src, size_t size) { clear(); @@ -129,10 +129,10 @@ void es::CertificateBody::fromBytes(const byte_t* src, size_t size) hdr = (const sCertificateHeader*)mRawBinary.data(); if (hdr->issuer[0] != 0) - mIssuer = std::string(hdr->issuer, cert::kIssuerSize); + mIssuer = std::string(hdr->issuer, _MIN(strlen(hdr->issuer), cert::kIssuerSize)); mPublicKeyType = (cert::PublicKeyType)hdr->key_type.get(); if (hdr->subject[0] != 0) - mSubject = std::string(hdr->subject, cert::kSubjectSize); + mSubject = std::string(hdr->subject, _MIN(strlen(hdr->subject), cert::kSubjectSize)); mCertId = hdr->cert_id.get(); // save public key @@ -155,13 +155,13 @@ void es::CertificateBody::fromBytes(const byte_t* src, size_t size) } } -const fnd::Vec& es::CertificateBody::getBytes() const +const fnd::Vec& pki::CertificateBody::getBytes() const { return mRawBinary; } -void es::CertificateBody::clear() +void pki::CertificateBody::clear() { mIssuer.clear(); mSubject.clear(); @@ -173,12 +173,12 @@ void es::CertificateBody::clear() memset(&mEcdsa240PublicKey, 0, sizeof(crypto::ecdsa::sEcdsa240Point)); } -const std::string& es::CertificateBody::getIssuer() const +const std::string& pki::CertificateBody::getIssuer() const { return mIssuer; } -void es::CertificateBody::setIssuer(const std::string& issuer) +void pki::CertificateBody::setIssuer(const std::string& issuer) { if (issuer.size() > cert::kIssuerSize) { @@ -188,22 +188,22 @@ void es::CertificateBody::setIssuer(const std::string& issuer) mIssuer = issuer; } -es::cert::PublicKeyType es::CertificateBody::getPublicKeyType() const +pki::cert::PublicKeyType pki::CertificateBody::getPublicKeyType() const { return mPublicKeyType; } -void es::CertificateBody::setPublicKeyType(cert::PublicKeyType type) +void pki::CertificateBody::setPublicKeyType(cert::PublicKeyType type) { mPublicKeyType = type; } -const std::string& es::CertificateBody::getSubject() const +const std::string& pki::CertificateBody::getSubject() const { return mSubject; } -void es::CertificateBody::setSubject(const std::string& subject) +void pki::CertificateBody::setSubject(const std::string& subject) { if (subject.size() > cert::kSubjectSize) { @@ -213,42 +213,42 @@ void es::CertificateBody::setSubject(const std::string& subject) mSubject = subject; } -uint32_t es::CertificateBody::getCertId() const +uint32_t pki::CertificateBody::getCertId() const { return mCertId; } -void es::CertificateBody::setCertId(uint32_t id) +void pki::CertificateBody::setCertId(uint32_t id) { mCertId = id; } -const crypto::rsa::sRsa4096Key& es::CertificateBody::getRsa4098PublicKey() const +const crypto::rsa::sRsa4096Key& pki::CertificateBody::getRsa4098PublicKey() const { return mRsa4096PublicKey; } -void es::CertificateBody::setRsa4098PublicKey(const crypto::rsa::sRsa4096Key& key) +void pki::CertificateBody::setRsa4098PublicKey(const crypto::rsa::sRsa4096Key& key) { mRsa4096PublicKey = key; } -const crypto::rsa::sRsa2048Key& es::CertificateBody::getRsa2048PublicKey() const +const crypto::rsa::sRsa2048Key& pki::CertificateBody::getRsa2048PublicKey() const { return mRsa2048PublicKey; } -void es::CertificateBody::setRsa2048PublicKey(const crypto::rsa::sRsa2048Key& key) +void pki::CertificateBody::setRsa2048PublicKey(const crypto::rsa::sRsa2048Key& key) { mRsa2048PublicKey = key; } -const crypto::ecdsa::sEcdsa240Point& es::CertificateBody::getEcdsa240PublicKey() const +const crypto::ecdsa::sEcdsa240Point& pki::CertificateBody::getEcdsa240PublicKey() const { return mEcdsa240PublicKey; } -void es::CertificateBody::setEcdsa240PublicKey(const crypto::ecdsa::sEcdsa240Point& key) +void pki::CertificateBody::setEcdsa240PublicKey(const crypto::ecdsa::sEcdsa240Point& key) { mEcdsa240PublicKey = key; } \ No newline at end of file diff --git a/lib/libpki/source/SignUtils.cpp b/lib/libpki/source/SignUtils.cpp new file mode 100644 index 0000000..43cfb1e --- /dev/null +++ b/lib/libpki/source/SignUtils.cpp @@ -0,0 +1,45 @@ +#include + +pki::sign::SignatureAlgo pki::sign::getSignatureAlgo(pki::sign::SignatureId sign_id) +{ + SignatureAlgo sign_algo = SIGN_ALGO_RSA4096; + + switch (sign_id) + { + case (pki::sign::SIGN_ID_RSA4096_SHA1): + case (pki::sign::SIGN_ID_RSA4096_SHA256): + sign_algo = SIGN_ALGO_RSA4096; + break; + case (pki::sign::SIGN_ID_RSA2048_SHA1): + case (pki::sign::SIGN_ID_RSA2048_SHA256): + sign_algo = SIGN_ALGO_RSA2048; + break; + case (pki::sign::SIGN_ID_ECDSA240_SHA1): + case (pki::sign::SIGN_ID_ECDSA240_SHA256): + sign_algo = SIGN_ALGO_ECDSA240; + break; + }; + + return sign_algo; +} + +pki::sign::HashAlgo pki::sign::getHashAlgo(pki::sign::SignatureId sign_id) +{ + HashAlgo hash_algo = HASH_ALGO_SHA1; + + switch (sign_id) + { + case (pki::sign::SIGN_ID_RSA4096_SHA1): + case (pki::sign::SIGN_ID_RSA2048_SHA1): + case (pki::sign::SIGN_ID_ECDSA240_SHA1): + hash_algo = HASH_ALGO_SHA1; + break; + case (pki::sign::SIGN_ID_RSA4096_SHA256): + case (pki::sign::SIGN_ID_RSA2048_SHA256): + case (pki::sign::SIGN_ID_ECDSA240_SHA256): + hash_algo = HASH_ALGO_SHA256; + break; + }; + + return hash_algo; +} \ No newline at end of file diff --git a/lib/libes/source/SignatureBlock.cpp b/lib/libpki/source/SignatureBlock.cpp similarity index 60% rename from lib/libes/source/SignatureBlock.cpp rename to lib/libpki/source/SignatureBlock.cpp index 4aa591d..bf19b20 100644 --- a/lib/libes/source/SignatureBlock.cpp +++ b/lib/libpki/source/SignatureBlock.cpp @@ -1,16 +1,16 @@ -#include +#include -es::SignatureBlock::SignatureBlock() +pki::SignatureBlock::SignatureBlock() { clear(); } -es::SignatureBlock::SignatureBlock(const SignatureBlock& other) +pki::SignatureBlock::SignatureBlock(const SignatureBlock& other) { *this = other; } -void es::SignatureBlock::operator=(const SignatureBlock& other) +void pki::SignatureBlock::operator=(const SignatureBlock& other) { mRawBinary = other.mRawBinary; mSignType = other.mSignType; @@ -18,37 +18,37 @@ void es::SignatureBlock::operator=(const SignatureBlock& other) mSignature = other.mSignature; } -bool es::SignatureBlock::operator==(const SignatureBlock& other) const +bool pki::SignatureBlock::operator==(const SignatureBlock& other) const { return (mSignType == other.mSignType) \ && (mIsLittleEndian == other.mIsLittleEndian) \ && (mSignature == other.mSignature); } -bool es::SignatureBlock::operator!=(const SignatureBlock& other) const +bool pki::SignatureBlock::operator!=(const SignatureBlock& other) const { return !(*this == other); } -void es::SignatureBlock::toBytes() +void pki::SignatureBlock::toBytes() { size_t totalSize = 0; size_t sigSize = 0; switch (mSignType) { - case (sign::SIGN_RSA4096_SHA1): - case (sign::SIGN_RSA4096_SHA256): + case (sign::SIGN_ID_RSA4096_SHA1): + case (sign::SIGN_ID_RSA4096_SHA256): totalSize = sizeof(sRsa4096SignBlock); sigSize = crypto::rsa::kRsa4096Size; break; - case (sign::SIGN_RSA2048_SHA1): - case (sign::SIGN_RSA2048_SHA256): + case (sign::SIGN_ID_RSA2048_SHA1): + case (sign::SIGN_ID_RSA2048_SHA256): totalSize = sizeof(sRsa2048SignBlock); sigSize = crypto::rsa::kRsa2048Size; break; - case (sign::SIGN_ECDSA240_SHA1): - case (sign::SIGN_ECDSA240_SHA256): + case (sign::SIGN_ID_ECDSA240_SHA1): + case (sign::SIGN_ID_ECDSA240_SHA256): totalSize = sizeof(sEcdsa240SignBlock); sigSize = sign::kEcdsaSigSize; break; @@ -68,7 +68,7 @@ void es::SignatureBlock::toBytes() memcpy(mRawBinary.data() + 4, mSignature.data(), sigSize); } -void es::SignatureBlock::fromBytes(const byte_t* src, size_t size) +void pki::SignatureBlock::fromBytes(const byte_t* src, size_t size) { clear(); @@ -80,18 +80,18 @@ void es::SignatureBlock::fromBytes(const byte_t* src, size_t size) signType = ((be_uint32_t*)src)->get(); switch (signType) { - case (sign::SIGN_RSA4096_SHA1): - case (sign::SIGN_RSA4096_SHA256): + case (sign::SIGN_ID_RSA4096_SHA1): + case (sign::SIGN_ID_RSA4096_SHA256): totalSize = sizeof(sRsa4096SignBlock); sigSize = crypto::rsa::kRsa4096Size; break; - case (sign::SIGN_RSA2048_SHA1): - case (sign::SIGN_RSA2048_SHA256): + case (sign::SIGN_ID_RSA2048_SHA1): + case (sign::SIGN_ID_RSA2048_SHA256): totalSize = sizeof(sRsa2048SignBlock); sigSize = crypto::rsa::kRsa2048Size; break; - case (sign::SIGN_ECDSA240_SHA1): - case (sign::SIGN_ECDSA240_SHA256): + case (sign::SIGN_ID_ECDSA240_SHA1): + case (sign::SIGN_ID_ECDSA240_SHA256): totalSize = sizeof(sEcdsa240SignBlock); sigSize = sign::kEcdsaSigSize; break; @@ -103,18 +103,18 @@ void es::SignatureBlock::fromBytes(const byte_t* src, size_t size) signType = ((le_uint32_t*)src)->get(); switch (signType) { - case (sign::SIGN_RSA4096_SHA1): - case (sign::SIGN_RSA4096_SHA256): + case (sign::SIGN_ID_RSA4096_SHA1): + case (sign::SIGN_ID_RSA4096_SHA256): totalSize = sizeof(sRsa4096SignBlock); sigSize = crypto::rsa::kRsa4096Size; break; - case (sign::SIGN_RSA2048_SHA1): - case (sign::SIGN_RSA2048_SHA256): + case (sign::SIGN_ID_RSA2048_SHA1): + case (sign::SIGN_ID_RSA2048_SHA256): totalSize = sizeof(sRsa2048SignBlock); sigSize = crypto::rsa::kRsa2048Size; break; - case (sign::SIGN_ECDSA240_SHA1): - case (sign::SIGN_ECDSA240_SHA256): + case (sign::SIGN_ID_ECDSA240_SHA1): + case (sign::SIGN_ID_ECDSA240_SHA256): totalSize = sizeof(sEcdsa240SignBlock); sigSize = sign::kEcdsaSigSize; break; @@ -133,50 +133,50 @@ void es::SignatureBlock::fromBytes(const byte_t* src, size_t size) mRawBinary.alloc(totalSize); memcpy(mRawBinary.data(), src, totalSize); - mSignType = (sign::SignType)signType; + mSignType = (sign::SignatureId)signType; mSignature.alloc(sigSize); memcpy(mSignature.data(), mRawBinary.data() + 4, sigSize); } -const fnd::Vec& es::SignatureBlock::getBytes() const +const fnd::Vec& pki::SignatureBlock::getBytes() const { return mRawBinary; } -void es::SignatureBlock::clear() +void pki::SignatureBlock::clear() { mRawBinary.clear(); - mSignType = sign::SIGN_RSA4096_SHA1; + mSignType = sign::SIGN_ID_RSA4096_SHA1; mIsLittleEndian = false; mSignature.clear(); } -es::sign::SignType es::SignatureBlock::getSignType() const +pki::sign::SignatureId pki::SignatureBlock::getSignType() const { return mSignType; } -void es::SignatureBlock::setSignType(es::sign::SignType type) +void pki::SignatureBlock::setSignType(pki::sign::SignatureId type) { mSignType = type; } -bool es::SignatureBlock::isLittleEndian() const +bool pki::SignatureBlock::isLittleEndian() const { return mIsLittleEndian; } -void es::SignatureBlock::setLittleEndian(bool isLE) +void pki::SignatureBlock::setLittleEndian(bool isLE) { mIsLittleEndian = isLE; } -const fnd::Vec& es::SignatureBlock::getSignature() const +const fnd::Vec& pki::SignatureBlock::getSignature() const { return mSignature; } -void es::SignatureBlock::setSignature(const fnd::Vec& signature) +void pki::SignatureBlock::setSignature(const fnd::Vec& signature) { mSignature = signature; } diff --git a/lib/makefile b/lib/makefile index 89636d0..7928e45 100644 --- a/lib/makefile +++ b/lib/makefile @@ -1,4 +1,4 @@ -LIBS = libfnd libcrypto libcompress libes libnx libnx-hb +LIBS = libfnd libcrypto libcompress libes libpki libnx libnx-hb main: build rebuild: clean build diff --git a/programs/nstool/makefile b/programs/nstool/makefile index 85aa304..ddbdcd3 100644 --- a/programs/nstool/makefile +++ b/programs/nstool/makefile @@ -3,7 +3,7 @@ SRC_DIR = source OBJS = $(foreach dir,$(SRC_DIR),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(SRC_DIR),$(subst .c,.o,$(wildcard $(dir)/*.c))) # External dependencies -DEPENDS = nx-hb nx crypto compress fnd +DEPENDS = nx-hb nx es pki crypto compress fnd LIB_DIR = ../../lib LIBS = $(foreach dep,$(DEPENDS), -L"$(LIB_DIR)/lib$(dep)" -l$(dep)) INCS = $(foreach dep,$(DEPENDS), -I"$(LIB_DIR)/lib$(dep)/include") diff --git a/programs/nstool/nstool.vcxproj b/programs/nstool/nstool.vcxproj index 35b393f..848e899 100644 --- a/programs/nstool/nstool.vcxproj +++ b/programs/nstool/nstool.vcxproj @@ -90,7 +90,7 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - ..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;..\..\lib\libnx-hb\include + ..\..\lib\libpki\include;..\..\lib\libes\include;..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;..\..\lib\libnx-hb\include Console @@ -105,7 +105,7 @@ true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - ..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;..\..\lib\libnx-hb\include + ..\..\lib\libpki\include;..\..\lib\libes\include;..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;..\..\lib\libnx-hb\include Console @@ -122,7 +122,7 @@ true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - ..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;..\..\lib\libnx-hb\include + ..\..\lib\libpki\include;..\..\lib\libes\include;..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;..\..\lib\libnx-hb\include Console @@ -141,7 +141,7 @@ true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - ..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;..\..\lib\libnx-hb\include + ..\..\lib\libpki\include;..\..\lib\libes\include;..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;..\..\lib\libnx-hb\include Console @@ -157,6 +157,9 @@ {6adbb60d-dba0-411d-bd2d-a355ef8e0fe1} + + {7be99936-0d40-410d-944b-4513c2eff8dc} + {4d27edb9-5110-44fe-8ce2-d46c5ad3c55b} @@ -166,12 +169,19 @@ {91ba9e79-8242-4f7d-b997-0dfec95ea22b} + + {b9113734-6e84-44ff-8cf7-58199aa815c5} + + + + + @@ -182,6 +192,8 @@ + + @@ -194,6 +206,7 @@ + @@ -204,15 +217,14 @@ + + - - - diff --git a/programs/nstool/nstool.vcxproj.filters b/programs/nstool/nstool.vcxproj.filters index 4d94f58..3d906bf 100644 --- a/programs/nstool/nstool.vcxproj.filters +++ b/programs/nstool/nstool.vcxproj.filters @@ -15,21 +15,69 @@ + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + Header Files Header Files + + Header Files + + + Header Files + Header Files + + Header Files + Header Files + + Header Files + + + Header Files + + + Header Files + Header Files + + Header Files + Header Files @@ -39,103 +87,73 @@ Header Files - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - Source Files - - Source Files - - + Source Files Source Files + + Source Files + + + Source Files + Source Files Source Files - - Source Files - - - Source Files - - - Source Files - - + Source Files Source Files - + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + Source Files Source Files - - - + + Source Files + + + Source Files + + + Source Files + + + Source Files + \ 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..22402a8 --- /dev/null +++ b/programs/nstool/source/EsTikProcess.cpp @@ -0,0 +1,269 @@ +#include +#include + +#include +#include +#include "OffsetAdjustedIFile.h" +#include "EsTikProcess.h" +#include "PkiValidator.h" + + + +EsTikProcess::EsTikProcess() : + mFile(nullptr), + mOwnIFile(false), + mCliOutputMode(_BIT(OUTPUT_BASIC)), + mVerify(false) +{ +} + +EsTikProcess::~EsTikProcess() +{ + if (mOwnIFile) + { + delete mFile; + } +} + +void EsTikProcess::process() +{ + importTicket(); + + if (mVerify) + verifyTicket(); + + 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::setCertificateChain(const fnd::List>& certs) +{ + mCerts = certs; +} + +void EsTikProcess::setCliOutputMode(CliOutputMode mode) +{ + mCliOutputMode = mode; +} + +void EsTikProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} + +void EsTikProcess::importTicket() +{ + if (mFile == nullptr) + { + throw fnd::Exception(kModuleName, "No file reader set."); + } + + fnd::Vec 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 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) +#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(); + + std::cout << "[ES Ticket]" << std::endl; + + std::cout << " SignType: " << getSignTypeStr(mTik.getSignature().getSignType()); + if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED)) + std::cout << " (0x" << std::hex << mTik.getSignature().getSignType() << ")"; + std::cout << std::endl; + + std::cout << " Issuer: " << body.getIssuer() << std::endl; + std::cout << " Title Key:" << std::endl; + std::cout << " EncMode: " << getTitleKeyPersonalisationStr(body.getTitleKeyEncType()) << std::endl; + std::cout << " KeyGeneration: " << std::dec << (uint32_t)body.getCommonKeyId() << std::endl; + std::cout << " Data:" << std::endl; + size_t size = body.getTitleKeyEncType() == es::ticket::RSA2048 ? crypto::rsa::kRsa2048Size : crypto::aes::kAes128KeySize; + fnd::SimpleTextOutput::hexDump(body.getEncTitleKey(), size, 0x10, 6); + + printf(" Version: v%d.%d.%d", _SPLIT_VER(body.getTicketVersion())); + if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED)) + printf(" (%d)", body.getTicketVersion()); + printf("\n"); + + std::cout << " License Type: " << getLicenseTypeStr(body.getLicenseType()) << std::endl; + + if (body.getPropertyFlags().size() > 0) + { + std::cout << " Flags:" << std::endl; + for (size_t i = 0; i < body.getPropertyFlags().size(); i++) + { + std::cout << " " << getPropertyFlagStr(body.getPropertyFlags()[i]) << std::endl; + } + } + + if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED)) + { + std::cout << " Reserved Region:" << std::endl; + fnd::SimpleTextOutput::hexDump(body.getReservedRegion(), 8, 0x10, 4); + } + + if (body.getTicketId() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED)) + std::cout << " TicketId: 0x" << std::hex << std::setw(16) << std::setfill('0') << body.getTicketId() << std::endl; + + if (body.getDeviceId() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED)) + std::cout << " DeviceId: 0x" << std::hex << std::setw(16) << std::setfill('0') << body.getDeviceId() << std::endl; + + std::cout << " RightsId: " << std::endl << " "; + fnd::SimpleTextOutput::hexDump(body.getRightsId(), 16); + + std::cout << " SectionTotalSize: 0x" << std::hex << body.getSectionTotalSize() << std::endl; + std::cout << " SectionHeaderOffset: 0x" << std::hex << body.getSectionHeaderOffset() << std::endl; + std::cout << " SectionNum: 0x" << std::hex << body.getSectionNum() << std::endl; + std::cout << " SectionEntrySize: 0x" << std::hex << body.getSectionEntrySize() << std::endl; + + +#undef _HEXDUMP_L +#undef _HEXDUMP_U +#undef _SPLIT_VER +} + +const char* EsTikProcess::getSignTypeStr(uint32_t type) const +{ + const char* str = nullptr; + switch(type) + { + case (pki::sign::SIGN_ID_RSA4096_SHA1): + str = "RSA4096-SHA1"; + break; + case (pki::sign::SIGN_ID_RSA2048_SHA1): + str = "RSA2048-SHA1"; + break; + case (pki::sign::SIGN_ID_ECDSA240_SHA1): + str = "ECDSA240-SHA1"; + break; + case (pki::sign::SIGN_ID_RSA4096_SHA256): + str = "RSA4096-SHA256"; + break; + case (pki::sign::SIGN_ID_RSA2048_SHA256): + str = "RSA2048-SHA256"; + break; + case (pki::sign::SIGN_ID_ECDSA240_SHA256): + str = "ECDSA240-SHA256"; + break; + default: + str = "Unknown"; + break; + } + return str; +} + +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..2ada90c --- /dev/null +++ b/programs/nstool/source/EsTikProcess.h @@ -0,0 +1,45 @@ +#pragma once +#include +#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 setCertificateChain(const fnd::List>& certs); + 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; + + fnd::List> mCerts; + + pki::SignedData mTik; + + void importTicket(); + void verifyTicket(); + void displayTicket(); + const char* getSignTypeStr(uint32_t type) const; + 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/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index 89f5d80..5080f06 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -286,27 +286,6 @@ void NcaProcess::process() // process partition processPartitions(); - - /* - NCA is a file container - A hashed and signed file container - - To verify a NCA: (R=regular step) - 1 - decrypt header (R) - 2 - verify signature[0] - 3 - validate hashes of fs_headers - 4 - determine how to read/decrypt the partitions (R) - 5 - validate the partitions depending on their hash method - 6 - if this NCA is a Program or Patch, open main.npdm from partition0 - 7 - validate ACID - 8 - use public key in ACID to verify NCA signature[1] - - Things to consider - * because of the manditory steps between verifcation steps - the NCA should be ready to be pulled to pieces before any printing is done - so the verification text can be presented without interuption - - */ } void NcaProcess::setInputFile(fnd::IFile* file, bool ownIFile) @@ -695,9 +674,10 @@ void NcaProcess::displayHeader() printf(" Partitions:\n"); for (size_t i = 0; i < mHdr.getPartitions().size(); i++) { - sPartitionInfo& info = mPartitions[i]; + size_t index = mHdr.getPartitions()[i].index; + sPartitionInfo& info = mPartitions[index]; - printf(" %d:\n", (int)i); + printf(" %d:\n", (int)index); printf(" Offset: 0x%" PRIx64 "\n", (uint64_t)info.offset); printf(" Size: 0x%" PRIx64 "\n", (uint64_t)info.size); printf(" Format Type: %s\n", getFormatTypeStr(info.format_type)); @@ -790,7 +770,7 @@ void NcaProcess::processPartitions() pfs.setListFs(mListFs); if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM) { - pfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/" + std::string(getProgramPartitionNameStr(i))); + pfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/" + std::string(getProgramPartitionNameStr(index))); } else { @@ -811,7 +791,7 @@ void NcaProcess::processPartitions() romfs.setListFs(mListFs); if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM) { - romfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/" + std::string(getProgramPartitionNameStr(i))); + romfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/" + std::string(getProgramPartitionNameStr(index))); } else { diff --git a/programs/nstool/source/PkiCertProcess.cpp b/programs/nstool/source/PkiCertProcess.cpp new file mode 100644 index 0000000..80f3b20 --- /dev/null +++ b/programs/nstool/source/PkiCertProcess.cpp @@ -0,0 +1,213 @@ +#include +#include + +#include +#include +#include "OffsetAdjustedIFile.h" +#include "PkiCertProcess.h" +#include "PkiValidator.h" + +PkiCertProcess::PkiCertProcess() : + mFile(nullptr), + mOwnIFile(false), + mCliOutputMode(_BIT(OUTPUT_BASIC)), + mVerify(false) +{ +} + +PkiCertProcess::~PkiCertProcess() +{ + if (mOwnIFile) + { + delete mFile; + } +} + +void PkiCertProcess::process() +{ + if (mFile == nullptr) + { + throw fnd::Exception(kModuleName, "No file reader set."); + } + + importCerts(); + if (mVerify) + validateCerts(); + + if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC)) + displayCerts(); +} + +void PkiCertProcess::setInputFile(fnd::IFile* file, bool ownIFile) +{ + mFile = file; + mOwnIFile = ownIFile; +} + +void PkiCertProcess::setKeyset(const sKeyset* keyset) +{ + mKeyset = keyset; +} + +void PkiCertProcess::setCliOutputMode(CliOutputMode mode) +{ + mCliOutputMode = mode; +} + +void PkiCertProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} + +void PkiCertProcess::importCerts() +{ + fnd::Vec scratch; + + scratch.alloc(mFile->size()); + mFile->read(scratch.data(), 0, scratch.size()); + + pki::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 PkiCertProcess::validateCerts() +{ + PkiValidator pki; + + try + { + pki.setRootKey(mKeyset->pki.root_sign_key); + pki.addCertificates(mCert); + } + catch (const fnd::Exception& e) + { + std::cout << "[WARNING] " << e.error() << std::endl; + return; + } +} + +void PkiCertProcess::displayCerts() +{ + for (size_t i = 0; i < mCert.size(); i++) + { + displayCert(mCert[i]); + } +} + +void PkiCertProcess::displayCert(const pki::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) + + std::cout << "[NNPKI Certificate]" << std::endl; + + std::cout << " SignType " << getSignTypeStr(cert.getSignature().getSignType()); + if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED)) + std::cout << " (0x" << std::hex << cert.getSignature().getSignType() << ") (" << getEndiannessStr(cert.getSignature().isLittleEndian()); + std::cout << std::endl; + + std::cout << " Issuer: " << cert.getBody().getIssuer() << std::endl; + std::cout << " Subject: " << cert.getBody().getSubject() << std::endl; + std::cout << " PublicKeyType: " << getPublicKeyTypeStr(cert.getBody().getPublicKeyType()); + if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED)) + std::cout << " (" << std::dec << cert.getBody().getPublicKeyType() << ")"; + std::cout << std::endl; + std::cout << " CertID: 0x" << std::hex << cert.getBody().getCertId() << std::endl; + + if (cert.getBody().getPublicKeyType() == pki::cert::RSA4096) + { + std::cout << " PublicKey:" << std::endl; + std::cout << " Modulus:" << std::endl; + fnd::SimpleTextOutput::hexDump(cert.getBody().getRsa4098PublicKey().modulus, getHexDumpLen(crypto::rsa::kRsa4096Size), 0x10, 6); + std::cout << " Public Exponent:" << std::endl; + fnd::SimpleTextOutput::hexDump(cert.getBody().getRsa4098PublicKey().public_exponent, crypto::rsa::kRsaPublicExponentSize, 0x10, 6); + } + else if (cert.getBody().getPublicKeyType() == pki::cert::RSA2048) + { + std::cout << " PublicKey:" << std::endl; + std::cout << " Public Exponent:" << std::endl; + fnd::SimpleTextOutput::hexDump(cert.getBody().getRsa2048PublicKey().modulus, getHexDumpLen(crypto::rsa::kRsa2048Size), 0x10, 6); + std::cout << " Modulus:" << std::endl; + fnd::SimpleTextOutput::hexDump(cert.getBody().getRsa2048PublicKey().public_exponent, crypto::rsa::kRsaPublicExponentSize, 0x10, 6); + } + else if (cert.getBody().getPublicKeyType() == pki::cert::ECDSA240) + { + std::cout << " PublicKey:" << std::endl; + std::cout << " R:" << std::endl; + fnd::SimpleTextOutput::hexDump(cert.getBody().getEcdsa240PublicKey().r, getHexDumpLen(crypto::ecdsa::kEcdsa240Size), 0x10, 6); + std::cout << " S:" << std::endl; + fnd::SimpleTextOutput::hexDump(cert.getBody().getEcdsa240PublicKey().s, getHexDumpLen(crypto::ecdsa::kEcdsa240Size), 0x10, 6); + } + + + +#undef _HEXDUMP_L +#undef _HEXDUMP_U +#undef _SPLIT_VER +} + +size_t PkiCertProcess::getHexDumpLen(size_t max_size) const +{ + return _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED) ? max_size : kSmallHexDumpLen; +} + +const char* PkiCertProcess::getSignTypeStr(pki::sign::SignatureId type) const +{ + const char* str; + switch (type) + { + case (pki::sign::SIGN_ID_RSA4096_SHA1): + str = "RSA4096-SHA1"; + break; + case (pki::sign::SIGN_ID_RSA2048_SHA1): + str = "RSA2048-SHA1"; + break; + case (pki::sign::SIGN_ID_ECDSA240_SHA1): + str = "ECDSA240-SHA1"; + break; + case (pki::sign::SIGN_ID_RSA4096_SHA256): + str = "RSA4096-SHA256"; + break; + case (pki::sign::SIGN_ID_RSA2048_SHA256): + str = "RSA2048-SHA256"; + break; + case (pki::sign::SIGN_ID_ECDSA240_SHA256): + str = "ECDSA240-SHA256"; + break; + default: + str = "Unknown"; + break; + } + return str; +} + +const char* PkiCertProcess::getEndiannessStr(bool isLittleEndian) const +{ + return isLittleEndian ? "LittleEndian" : "BigEndian"; +} + +const char* PkiCertProcess::getPublicKeyTypeStr(pki::cert::PublicKeyType type) const +{ + const char* str; + switch (type) + { + case (pki::cert::RSA4096): + str = "RSA4096"; + break; + case (pki::cert::RSA2048): + str = "RSA2048"; + break; + case (pki::cert::ECDSA240): + str = "ECDSA240"; + break; + default: + str = "Unknown"; + break; + } + return str; +} \ No newline at end of file diff --git a/programs/nstool/source/PkiCertProcess.h b/programs/nstool/source/PkiCertProcess.h new file mode 100644 index 0000000..31c63b2 --- /dev/null +++ b/programs/nstool/source/PkiCertProcess.h @@ -0,0 +1,45 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include "nstool.h" + +class PkiCertProcess +{ +public: + PkiCertProcess(); + ~PkiCertProcess(); + + 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 = "PkiCertProcess"; + static const size_t kSmallHexDumpLen = 0x10; + + fnd::IFile* mFile; + bool mOwnIFile; + const sKeyset* mKeyset; + CliOutputMode mCliOutputMode; + bool mVerify; + + fnd::List> mCert; + + void importCerts(); + void validateCerts(); + void displayCerts(); + void displayCert(const pki::SignedData& cert); + + size_t getHexDumpLen(size_t max_size) const; + const char* getSignTypeStr(pki::sign::SignatureId type) const; + const char* getEndiannessStr(bool isLittleEndian) const; + const char* getPublicKeyTypeStr(pki::cert::PublicKeyType type) const; +}; \ No newline at end of file diff --git a/programs/nstool/source/PkiValidator.cpp b/programs/nstool/source/PkiValidator.cpp new file mode 100644 index 0000000..00b2088 --- /dev/null +++ b/programs/nstool/source/PkiValidator.cpp @@ -0,0 +1,197 @@ +#include "PkiValidator.h" +#include +#include +#include +#include + +PkiValidator::PkiValidator() +{ + clearCertificates(); +} + +void PkiValidator::setRootKey(const crypto::rsa::sRsa4096Key& root_key) +{ + // save a copy of the certificate bank + fnd::List> old_certs = mCertificateBank; + + // clear the certificate bank + mCertificateBank.clear(); + + // overwrite the root key + mRootKey = root_key; + + // if there were certificates before, reimport them (so they are checked against the new root key) + if (old_certs.size() > 0) + { + addCertificates(old_certs); + } +} + +void PkiValidator::addCertificates(const fnd::List>& certs) +{ + for (size_t i = 0; i < certs.size(); i++) + { + addCertificate(certs[i]); + } +} + +void PkiValidator::addCertificate(const pki::SignedData& cert) +{ + std::string cert_ident; + pki::sign::SignatureAlgo cert_sign_algo; + pki::sign::HashAlgo cert_hash_algo; + fnd::Vec cert_hash; + + try + { + makeCertIdent(cert, cert_ident); + + if (doesCertExist(cert_ident) == true) + { + throw fnd::Exception(kModuleName, "Certificate already exists"); + } + + cert_sign_algo = pki::sign::getSignatureAlgo(cert.getSignature().getSignType()); + cert_hash_algo = pki::sign::getHashAlgo(cert.getSignature().getSignType()); + + // get cert hash + switch (cert_hash_algo) + { + case (pki::sign::HASH_ALGO_SHA1): + cert_hash.alloc(crypto::sha::kSha1HashLen); + crypto::sha::Sha1(cert.getBody().getBytes().data(), cert.getBody().getBytes().size(), cert_hash.data()); + break; + case (pki::sign::HASH_ALGO_SHA256): + cert_hash.alloc(crypto::sha::kSha256HashLen); + crypto::sha::Sha256(cert.getBody().getBytes().data(), cert.getBody().getBytes().size(), cert_hash.data()); + break; + default: + throw fnd::Exception(kModuleName, "Unrecognised hash type"); + } + + validateSignature(cert.getBody().getIssuer(), cert.getSignature().getSignType(), cert.getSignature().getSignature(), cert_hash); + + mCertificateBank.addElement(cert); + } + catch (const fnd::Exception& e) + { + std::stringstream ss; + ss << "Failed to add certificate " << cert_ident << " (" << e.error() << ")"; + throw fnd::Exception(kModuleName, ss.str()); + } +} + +void PkiValidator::clearCertificates() +{ + mCertificateBank.clear(); +} + +void PkiValidator::validateSignature(const std::string& issuer, pki::sign::SignatureId signature_id, const fnd::Vec& signature, const fnd::Vec& hash) const +{ + pki::sign::SignatureAlgo sign_algo = pki::sign::getSignatureAlgo(signature_id); + pki::sign::HashAlgo hash_algo = pki::sign::getHashAlgo(signature_id); + + + // validate signature + int sig_validate_res = -1; + + // special case if signed by Root + if (issuer == pki::sign::kRootIssuerStr) + { + if (sign_algo != pki::sign::SIGN_ALGO_RSA4096) + { + throw fnd::Exception(kModuleName, "Issued by Root, but does not have a RSA4096 signature"); + } + sig_validate_res = crypto::rsa::pkcs::rsaVerify(mRootKey, getCryptoHashAlgoFromEsSignHashAlgo(hash_algo), hash.data(), signature.data()); + } + else + { + // try to find issuer cert + const pki::CertificateBody& issuer_cert = getCert(issuer).getBody(); + pki::cert::PublicKeyType issuer_pubk_type = issuer_cert.getPublicKeyType(); + + if (issuer_pubk_type == pki::cert::RSA4096 && sign_algo == pki::sign::SIGN_ALGO_RSA4096) + { + sig_validate_res = crypto::rsa::pkcs::rsaVerify(issuer_cert.getRsa4098PublicKey(), getCryptoHashAlgoFromEsSignHashAlgo(hash_algo), hash.data(), signature.data()); + } + else if (issuer_pubk_type == pki::cert::RSA2048 && sign_algo == pki::sign::SIGN_ALGO_RSA2048) + { + sig_validate_res = crypto::rsa::pkcs::rsaVerify(issuer_cert.getRsa2048PublicKey(), getCryptoHashAlgoFromEsSignHashAlgo(hash_algo), hash.data(), signature.data()); + } + else if (issuer_pubk_type == pki::cert::ECDSA240 && sign_algo == pki::sign::SIGN_ALGO_ECDSA240) + { + throw fnd::Exception(kModuleName, "ECDSA signatures are not supported"); + } + else + { + throw fnd::Exception(kModuleName, "Mismatch between issuer public key and signature type"); + } + } + + if (sig_validate_res != 0) + { + throw fnd::Exception(kModuleName, "Incorrect signature"); + } + + +} + +void PkiValidator::makeCertIdent(const pki::SignedData& cert, std::string& ident) const +{ + makeCertIdent(cert.getBody().getIssuer(), cert.getBody().getSubject(), ident); +} + +void PkiValidator::makeCertIdent(const std::string& issuer, const std::string& subject, std::string& ident) const +{ + ident = issuer + pki::sign::kIdentDelimiter + subject; + ident = ident.substr(0, _MIN(ident.length(),64)); +} + +bool PkiValidator::doesCertExist(const std::string& ident) const +{ + bool exists = false; + std::string full_cert_name; + for (size_t i = 0; i < mCertificateBank.size(); i++) + { + makeCertIdent(mCertificateBank[i], full_cert_name); + if (full_cert_name == ident) + { + exists = true; + break; + } + } + + return exists; +} + +const pki::SignedData& PkiValidator::getCert(const std::string& ident) const +{ + std::string full_cert_name; + for (size_t i = 0; i < mCertificateBank.size(); i++) + { + makeCertIdent(mCertificateBank[i], full_cert_name); + if (full_cert_name == ident) + { + return mCertificateBank[i]; + } + } + + throw fnd::Exception(kModuleName, "Issuer certificate does not exist"); +} + +crypto::sha::HashType PkiValidator::getCryptoHashAlgoFromEsSignHashAlgo(pki::sign::HashAlgo hash_algo) const +{ + crypto::sha::HashType hash_type = crypto::sha::HASH_SHA1; + + switch (hash_algo) + { + case (pki::sign::HASH_ALGO_SHA1): + hash_type = crypto::sha::HASH_SHA1; + break; + case (pki::sign::HASH_ALGO_SHA256): + hash_type = crypto::sha::HASH_SHA256; + break; + }; + + return hash_type; +} \ No newline at end of file diff --git a/programs/nstool/source/PkiValidator.h b/programs/nstool/source/PkiValidator.h new file mode 100644 index 0000000..217dfad --- /dev/null +++ b/programs/nstool/source/PkiValidator.h @@ -0,0 +1,34 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +class PkiValidator +{ +public: + PkiValidator(); + + void setRootKey(const crypto::rsa::sRsa4096Key& root_key); + void addCertificates(const fnd::List>& certs); + void addCertificate(const pki::SignedData& cert); + void clearCertificates(); + + void validateSignature(const std::string& issuer, pki::sign::SignatureId signature_id, const fnd::Vec& signature, const fnd::Vec& hash) const; + +private: + const std::string kModuleName = "NNPkiValidator"; + + + crypto::rsa::sRsa4096Key mRootKey; + fnd::List> mCertificateBank; + + void makeCertIdent(const pki::SignedData& cert, std::string& ident) const; + void makeCertIdent(const std::string& issuer, const std::string& subject, std::string& ident) const; + bool doesCertExist(const std::string& ident) const; + const pki::SignedData& getCert(const std::string& ident) const; + crypto::sha::HashType getCryptoHashAlgoFromEsSignHashAlgo(pki::sign::HashAlgo hash_algo) const; +}; \ No newline at end of file diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index 7b844f5..f2c5297 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -1,5 +1,6 @@ #include "UserSettings.h" #include "version.h" +#include "PkiValidator.h" #include #include #include @@ -22,6 +23,10 @@ #include #include #include +#include +#include +#include +#include UserSettings::UserSettings() {} @@ -41,34 +46,36 @@ void UserSettings::showHelp() printf("Usage: nstool [options... ] \n"); printf("\n General Options:\n"); - printf(" -d, --dev Use devkit keyset\n"); - printf(" -k, --keyset Specify keyset file\n"); - printf(" -t, --type Specify input file type [xci, pfs, romfs, nca, npdm, cnmt, nso, nro, nacp, aset]\n"); - printf(" -y, --verify Verify file\n"); + printf(" -d, --dev Use devkit keyset.\n"); + printf(" -k, --keyset Specify keyset file.\n"); + printf(" -t, --type Specify input file type. [xci, pfs, romfs, nca, npdm, cnmt, nso, nro, nacp, aset, cert, tik]\n"); + printf(" -y, --verify Verify file.\n"); printf("\n Output Options:\n"); - printf(" --showkeys Show keys generated\n"); - printf(" --showlayout Show layout metadata\n"); - printf(" -v, --verbose Verbose output\n"); + printf(" --showkeys Show keys generated.\n"); + printf(" --showlayout Show layout metadata.\n"); + printf(" -v, --verbose Verbose output.\n"); printf("\n XCI (GameCard Image)\n"); printf(" nstool [--listfs] [--update --logo --normal --secure ] <.xci file>\n"); - printf(" --listfs Print file system in embedded partitions\n"); - printf(" --update Extract \"update\" partition to directory\n"); - printf(" --logo Extract \"logo\" partition to directory\n"); - printf(" --normal Extract \"normal\" partition to directory\n"); - printf(" --secure Extract \"secure\" partition to directory\n"); + printf(" --listfs Print file system in embedded partitions.\n"); + printf(" --update Extract \"update\" partition to directory.\n"); + printf(" --logo Extract \"logo\" partition to directory.\n"); + printf(" --normal Extract \"normal\" partition to directory.\n"); + printf(" --secure Extract \"secure\" partition to directory.\n"); printf("\n PFS0/HFS0 (PartitionFs), RomFs, NSP (Ninendo Submission Package)\n"); printf(" nstool [--listfs] [--fsdir ] \n"); - printf(" --listfs Print file system\n"); - printf(" --fsdir Extract file system to directory\n"); + printf(" --listfs Print file system.\n"); + printf(" --fsdir Extract file system to directory.\n"); printf("\n NCA (Nintendo Content Archive)\n"); printf(" nstool [--listfs] [--bodykey --titlekey ] [--part0 ...] <.nca file>\n"); - printf(" --listfs Print file system in embedded partitions\n"); - printf(" --titlekey Specify title key extracted from ticket\n"); - printf(" --bodykey Specify body encryption key\n"); - printf(" --part0 Extract \"partition 0\" to directory \n"); - printf(" --part1 Extract \"partition 1\" to directory \n"); - printf(" --part2 Extract \"partition 2\" to directory \n"); - printf(" --part3 Extract \"partition 3\" to directory \n"); + printf(" --listfs Print file system in embedded partitions.\n"); + printf(" --titlekey Specify title key extracted from ticket.\n"); + printf(" --bodykey Specify body encryption key.\n"); + printf(" --tik Specify ticket to source title key.\n"); + printf(" --cert Specify certificate chain to verify ticket.\n"); + printf(" --part0 Extract \"partition 0\" to directory.\n"); + printf(" --part1 Extract \"partition 1\" to directory.\n"); + printf(" --part2 Extract \"partition 2\" to directory.\n"); + printf(" --part3 Extract \"partition 3\" to directory.\n"); printf("\n NSO (Nintendo Software Object), NRO (Nintendo Relocatable Object)\n"); printf(" nstool [--listapi --listsym] [--insttype ] \n"); printf(" --listapi Print SDK API List.\n"); @@ -182,6 +189,11 @@ const sOptional& UserSettings::getAssetNacpPath() const return mAssetNacpPath; } +const fnd::List>& UserSettings::getCertificateChain() const +{ + return mCertChain; +} + void UserSettings::populateCmdArgs(const std::vector& arg_list, sCmdArgs& cmd_args) { // show help text @@ -296,6 +308,18 @@ void UserSettings::populateCmdArgs(const std::vector& arg_list, sCm cmd_args.nca_bodykey = arg_list[i+1]; } + else if (arg_list[i] == "--tik") + { + if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); + 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."); @@ -415,6 +439,7 @@ void UserSettings::populateKeyset(sCmdArgs& args) const std::string kKekGenSource = "aes_kek_generation"; const std::string kKeyGenSource = "aes_key_generation"; const std::string kAcidBase = "acid"; + const std::string kPkiRootBase = "pki_root"; const std::string kTicketCommonKeyBase[2] = { "titlekek", "ticket_commonkey" }; const std::string kNcaBodyBase[2] = {"key_area_key", "nca_body_keak"}; const std::string kNcaBodyKeakIndexName[3] = {"application", "ocean", "system"}; @@ -484,17 +509,21 @@ void UserSettings::populateKeyset(sCmdArgs& args) _SAVE_KEYDATA(_CONCAT_2_STRINGS(kXciHeaderBase, kKeyStr), mKeyset.xci.header_key.key, 0x10); // store rsa keys - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kNcaHeaderBase[1], kRsaKeySuffix[0]), mKeyset.nca.header_sign_key.priv_exponent, 0x100); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kNcaHeaderBase[1], kRsaKeySuffix[1]), mKeyset.nca.header_sign_key.modulus, 0x100); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kNcaHeaderBase[1], kRsaKeySuffix[0]), mKeyset.nca.header_sign_key.priv_exponent, crypto::rsa::kRsa2048Size); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kNcaHeaderBase[1], kRsaKeySuffix[1]), mKeyset.nca.header_sign_key.modulus, crypto::rsa::kRsa2048Size); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kXciHeaderBase, kRsaKeySuffix[0]), mKeyset.xci.header_sign_key.priv_exponent, 0x100); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kXciHeaderBase, kRsaKeySuffix[1]), mKeyset.xci.header_sign_key.modulus, 0x100); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kXciHeaderBase, kRsaKeySuffix[0]), mKeyset.xci.header_sign_key.priv_exponent, crypto::rsa::kRsa2048Size); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kXciHeaderBase, kRsaKeySuffix[1]), mKeyset.xci.header_sign_key.modulus, crypto::rsa::kRsa2048Size); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kAcidBase, kRsaKeySuffix[0]), mKeyset.acid_sign_key.priv_exponent, 0x100); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kAcidBase, kRsaKeySuffix[1]), mKeyset.acid_sign_key.modulus, 0x100); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kAcidBase, kRsaKeySuffix[0]), mKeyset.acid_sign_key.priv_exponent, crypto::rsa::kRsa2048Size); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kAcidBase, kRsaKeySuffix[1]), mKeyset.acid_sign_key.modulus, crypto::rsa::kRsa2048Size); + + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPackage2Base, kRsaKeySuffix[0]), mKeyset.package2_sign_key.priv_exponent, crypto::rsa::kRsa2048Size); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPackage2Base, kRsaKeySuffix[1]), mKeyset.package2_sign_key.modulus, crypto::rsa::kRsa2048Size); + + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPkiRootBase, kRsaKeySuffix[0]), mKeyset.pki.root_sign_key.priv_exponent, crypto::rsa::kRsa4096Size); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPkiRootBase, kRsaKeySuffix[1]), mKeyset.pki.root_sign_key.modulus, crypto::rsa::kRsa4096Size); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPackage2Base, kRsaKeySuffix[0]), mKeyset.package2_sign_key.priv_exponent, 0x100); - _SAVE_KEYDATA(_CONCAT_2_STRINGS(kPackage2Base, kRsaKeySuffix[1]), mKeyset.package2_sign_key.modulus, 0x100); // save keydata from input args if (args.nca_bodykey.isSet) @@ -521,6 +550,79 @@ void UserSettings::populateKeyset(sCmdArgs& args) } } + // import certificate chain + if (args.cert_path.isSet) + { + fnd::SimpleFile cert_file; + fnd::Vec cert_raw; + pki::SignedData 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) + { + fnd::SimpleFile tik_file; + fnd::Vec tik_raw; + pki::SignedData 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 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); + } + else + { + std::cout << "[WARNING] Titlekey not imported from ticket because it is personalised" << std::endl; + } + } + #undef _SAVE_KEYDATA #undef _CONCAT_3_STRINGS #undef _CONCAT_2_STRINGS @@ -688,6 +790,10 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str) type = FILE_NRO; else if (str == "nacp") type = FILE_NACP; + else if (str == "cert") + type = FILE_PKI_CERT; + else if (str == "tik") + type = FILE_ES_TIK; else if (str == "aset" || str == "asset") type = FILE_HB_ASSET; else @@ -746,6 +852,12 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path) // test nso else if (_ASSERT_SIZE(sizeof(nx::sNroHeader)) && _TYPE_PTR(nx::sNroHeader)->st_magic.get() == nx::nro::kNroStructMagic) file_type = FILE_NRO; + // test pki certificate + else if (determineValidEsCertFromSample(scratch)) + file_type = FILE_PKI_CERT; + // test ticket + else if (determineValidEsTikFromSample(scratch)) + file_type = FILE_ES_TIK; // test hb asset else if (_ASSERT_SIZE(sizeof(nx::sAssetHeader)) && _TYPE_PTR(nx::sAssetHeader)->st_magic.get() == nx::aset::kAssetStructMagic) file_type = FILE_HB_ASSET; @@ -848,6 +960,50 @@ bool UserSettings::determineValidNacpFromSample(const fnd::Vec& sample) return true; } +bool UserSettings::determineValidEsCertFromSample(const fnd::Vec& sample) const +{ + pki::SignatureBlock sign; + + try + { + sign.fromBytes(sample.data(), sample.size()); + } + catch (...) + { + return false; + } + + if (sign.isLittleEndian() == true) + return false; + + if (sign.getSignType() != pki::sign::SIGN_ID_RSA4096_SHA256 && sign.getSignType() != pki::sign::SIGN_ID_RSA2048_SHA256 && sign.getSignType() != pki::sign::SIGN_ID_ECDSA240_SHA256) + return false; + + return true; +} + +bool UserSettings::determineValidEsTikFromSample(const fnd::Vec& sample) const +{ + pki::SignatureBlock sign; + + try + { + sign.fromBytes(sample.data(), sample.size()); + } + catch (...) + { + return false; + } + + if (sign.isLittleEndian() == false) + return false; + + if (sign.getSignType() != pki::sign::SIGN_ID_RSA2048_SHA256) + return false; + + return true; +} + nx::npdm::InstructionType UserSettings::getInstructionTypeFromString(const std::string & type_str) { std::string str = type_str; diff --git a/programs/nstool/source/UserSettings.h b/programs/nstool/source/UserSettings.h index f7e1969..fd53077 100644 --- a/programs/nstool/source/UserSettings.h +++ b/programs/nstool/source/UserSettings.h @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include #include #include "nstool.h" @@ -39,6 +42,7 @@ public: const sOptional& getNcaPart3Path() const; const sOptional& getAssetIconPath() const; const sOptional& getAssetNacpPath() const; + const fnd::List>& getCertificateChain() const; private: const std::string kModuleName = "UserSettings"; @@ -62,6 +66,8 @@ private: sOptional fs_path; sOptional nca_titlekey; sOptional nca_bodykey; + sOptional ticket_path; + sOptional cert_path; sOptional part0_path; sOptional part1_path; sOptional part2_path; @@ -94,6 +100,8 @@ private: sOptional mAssetIconPath; sOptional mAssetNacpPath; + fnd::List> mCertChain; + bool mListApi; bool mListSymbols; nx::npdm::InstructionType mInstructionType; @@ -107,5 +115,7 @@ private: bool determineValidNcaFromSample(const fnd::Vec& sample) const; bool determineValidCnmtFromSample(const fnd::Vec& sample) const; bool determineValidNacpFromSample(const fnd::Vec& sample) const; + bool determineValidEsCertFromSample(const fnd::Vec& sample) const; + bool determineValidEsTikFromSample(const fnd::Vec& sample) const; nx::npdm::InstructionType getInstructionTypeFromString(const std::string& type_str); }; \ No newline at end of file diff --git a/programs/nstool/source/main.cpp b/programs/nstool/source/main.cpp index ef1fafb..7eefe54 100644 --- a/programs/nstool/source/main.cpp +++ b/programs/nstool/source/main.cpp @@ -11,6 +11,8 @@ #include "NsoProcess.h" #include "NroProcess.h" #include "NacpProcess.h" +#include "PkiCertProcess.h" +#include "EsTikProcess.h" #include "AssetProcess.h" #ifdef _WIN32 @@ -173,6 +175,29 @@ int main(int argc, char** argv) nacp.process(); } + else if (user_set.getFileType() == FILE_PKI_CERT) + { + PkiCertProcess 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.setCertificateChain(user_set.getCertificateChain()); + tik.setCliOutputMode(user_set.getCliOutputMode()); + tik.setVerifyMode(user_set.isVerifyFile()); + + tik.process(); + } else if (user_set.getFileType() == FILE_HB_ASSET) { AssetProcess obj; diff --git a/programs/nstool/source/nstool.h b/programs/nstool/source/nstool.h index cf6505c..a52c571 100644 --- a/programs/nstool/source/nstool.h +++ b/programs/nstool/source/nstool.h @@ -27,6 +27,8 @@ enum FileType FILE_NSO, FILE_NRO, FILE_NACP, + FILE_PKI_CERT, + FILE_ES_TIK, FILE_HB_ASSET, FILE_INVALID = -1, }; @@ -62,7 +64,6 @@ struct sOptional struct sKeyset { crypto::rsa::sRsa2048Key acid_sign_key; - crypto::aes::sAes128Key package1_key[kMasterKeyNum]; crypto::rsa::sRsa2048Key package2_sign_key; crypto::aes::sAes128Key package2_key[kMasterKeyNum]; @@ -84,12 +85,17 @@ struct sKeyset crypto::rsa::sRsa2048Key header_sign_key; crypto::aes::sAes128Key header_key; } xci; - + struct sTicketData { crypto::rsa::sRsa2048Key sign_key; crypto::aes::sAes128Key titlekey_kek[kMasterKeyNum]; } ticket; + + struct sPkiData + { + crypto::rsa::sRsa4096Key root_sign_key; + } pki; }; inline byte_t charToByte(char chr)