diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 91af95cff4..3621baad39 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp) target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) @@ -110,6 +110,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_ui PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_rsa_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslRsa.cpp) + target_include_directories(guest_libressl_rsa_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_rsa_tests PRIVATE libSceLibreSsl) + configure_windows_unwind(guest_libressl_rsa_tests) + add_test(NAME guest_libressl_rsa COMMAND guest_libressl_rsa_tests) + set_tests_properties(guest_libressl_rsa PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_rsa PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index bc950b9224..00ace935b0 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -909,11 +909,6 @@ int APS5_VABI PKCS12_parse_nid_postfix(void) { return 0; } -int APS5_VABI RSA_flags_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI RSA_free_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -959,11 +954,6 @@ int APS5_VABI RSA_sign_nid_postfix(void) { return 0; } -int APS5_VABI RSA_size_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI RSA_verify_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 02d0223f0e..a114265d0c 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -81,6 +81,39 @@ struct LibreSslUiMethod { UiPromptFunction constructPrompt; }; +struct LibreSslRsaMethod { + const char* name; + void* operations[8]; + int flags; + char* appData; + void* sign; + void* verify; + void* keygen; +}; + +struct LibreSslRsa { + int pad; + std::int64_t version; + const LibreSslRsaMethod* meth; + void* engine; + void* n; + void* e; + void* d; + void* p; + void* q; + void* dmp1; + void* dmq1; + void* iqmp; + void* exData; + int references; + int flags; + void* methodModN; + void* methodModP; + void* methodModQ; + void* blinding; + void* mtBlinding; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -270,7 +303,7 @@ int APS5_VABI PEM_write_bio_X509_nid_postfix(void); int APS5_VABI PKCS12_PBE_add_nid_postfix(void); int APS5_VABI PKCS12_free_nid_postfix(void); int APS5_VABI PKCS12_parse_nid_postfix(void); -int APS5_VABI RSA_flags_nid_postfix(void); +int APS5_VABI RSA_flags_nid_postfix(const LibreSslRsa* rsa); int APS5_VABI RSA_free_nid_postfix(void); int APS5_VABI RSA_generate_key_ex_nid_postfix(void); int APS5_VABI RSA_new_nid_postfix(void); @@ -280,7 +313,7 @@ int APS5_VABI RSA_private_encrypt_nid_postfix(void); int APS5_VABI RSA_public_decrypt_nid_postfix(void); int APS5_VABI RSA_public_encrypt_nid_postfix(void); int APS5_VABI RSA_sign_nid_postfix(void); -int APS5_VABI RSA_size_nid_postfix(void); +int APS5_VABI RSA_size_nid_postfix(const LibreSslRsa* rsa); int APS5_VABI RSA_verify_nid_postfix(void); int APS5_VABI RSA_verify_PKCS1_PSS_nid_postfix(void); int APS5_VABI SSL_CIPHER_get_bits_nid_postfix(const void* cipher, std::int32_t* algorithmBits); diff --git a/core/libs/prx/libSceLibreSsl/src/Rsa.cpp b/core/libs/prx/libSceLibreSsl/src/Rsa.cpp new file mode 100644 index 0000000000..66ff820ecc --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Rsa.cpp @@ -0,0 +1,54 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +struct LibreSslBignum { + std::uint64_t* d; + int top; + int dmax; + int neg; + int flags; +}; + +static_assert(sizeof(LibreSslBignum) == 24); +static_assert(offsetof(LibreSslBignum, top) == 8); +static_assert(sizeof(LibreSslRsaMethod) == 112); +static_assert(offsetof(LibreSslRsaMethod, flags) == 72); +static_assert(sizeof(LibreSslRsa) == 152); +static_assert(offsetof(LibreSslRsa, meth) == 16); +static_assert(offsetof(LibreSslRsa, n) == 32); +static_assert(offsetof(LibreSslRsa, flags) == 108); + +namespace { + +int ByteCount(const LibreSslBignum* number, const char* function) { + if (!number) throw std::invalid_argument(std::string(function) + ": null modulus"); + if (number->top == 0) return 0; + if (number->top < 0) throw std::invalid_argument(std::string(function) + ": negative modulus word count"); + if (!number->d) throw std::invalid_argument(std::string(function) + ": null modulus words"); + const std::int64_t bits = static_cast(number->top - 1) * 64 + std::bit_width(number->d[number->top - 1]); + if (bits > INT_MAX - 7) throw std::invalid_argument(std::string(function) + ": modulus bit count overflows int"); + return static_cast((bits + 7) / 8); +} + +} + +extern "C" { + +int APS5_VABI RSA_flags_nid_postfix(const LibreSslRsa* rsa) { + if (!rsa) return 0; + if (!rsa->meth) throw std::invalid_argument(std::string(__func__) + ": null method"); + return rsa->meth->flags; +} + +int APS5_VABI RSA_size_nid_postfix(const LibreSslRsa* rsa) { + if (!rsa) throw std::invalid_argument(std::string(__func__) + ": null key"); + return ByteCount(static_cast(rsa->n), __func__); +} + +} diff --git a/core/libs/tests/GuestLibreSslRsa.cpp b/core/libs/tests/GuestLibreSslRsa.cpp new file mode 100644 index 0000000000..80af7c5b46 --- /dev/null +++ b/core/libs/tests/GuestLibreSslRsa.cpp @@ -0,0 +1,118 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct TestBignum { + std::uint64_t* d; + int top; + int dmax; + int neg; + int flags; +}; + +struct TestRsaMethod { + const char* name; + void* operations[8]; + int flags; + char* appData; + void* sign; + void* verify; + void* keygen; +}; + +struct TestRsa { + int pad; + std::int64_t version; + const TestRsaMethod* meth; + void* engine; + void* n; + void* rest[15]; +}; + +static_assert(offsetof(TestRsaMethod, flags) == 72); +static_assert(offsetof(TestRsa, meth) == 16 && offsetof(TestRsa, n) == 32 && sizeof(TestRsa) == 160); + +static const LibreSslRsa* Rsa(const TestRsa* rsa) { + return reinterpret_cast(rsa); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL RSA check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestFlags() { + TestRsaMethod method {}; + TestRsa rsa {}; + rsa.meth = &method; + Require(RSA_flags_nid_postfix(nullptr) == 0); + Require(RSA_flags_nid_postfix(Rsa(&rsa)) == 0); + method.flags = 0x0400; + Require(RSA_flags_nid_postfix(Rsa(&rsa)) == 0x0400); + method.flags = -1; + Require(RSA_flags_nid_postfix(Rsa(&rsa)) == -1); + rsa.meth = nullptr; + RequireThrows([&] { RSA_flags_nid_postfix(Rsa(&rsa)); }); +} + +static int Size(TestBignum* modulus) { + TestRsa rsa {}; + rsa.n = modulus; + return RSA_size_nid_postfix(Rsa(&rsa)); +} + +static void TestSize() { + std::uint64_t words[32] {}; + TestBignum modulus {words, 0, 32, 0, 0}; + Require(Size(&modulus) == 0); + modulus.d = nullptr; + Require(Size(&modulus) == 0); + modulus.d = words; + modulus.top = 1; + words[0] = 1; + Require(Size(&modulus) == 1); + words[0] = 0xFF; + Require(Size(&modulus) == 1); + words[0] = 0x100; + Require(Size(&modulus) == 2); + words[0] = 0x8000000000000000ull; + Require(Size(&modulus) == 8); + modulus.top = 2; + words[1] = 1; + Require(Size(&modulus) == 9); + modulus.top = 32; + words[31] = 0xC000000000000000ull; + Require(Size(&modulus) == 256); + modulus.neg = 1; + Require(Size(&modulus) == 256); + modulus.neg = 0; + modulus.top = 16; + words[15] = 0x00FFFFFFFFFFFFFFull; + Require(Size(&modulus) == 127); + words[15] = 0; + Require(Size(&modulus) == 120); + modulus.top = -1; + RequireThrows([&] { Size(&modulus); }); + modulus.top = 1; + modulus.d = nullptr; + RequireThrows([&] { Size(&modulus); }); + RequireThrows([] { Size(nullptr); }); + RequireThrows([] { RSA_size_nid_postfix(nullptr); }); +} + +int main() { + TestFlags(); + TestSize(); + std::puts("libSceLibreSsl RSA tests passed"); +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index ac82d3fa4a..723c8f3d9d 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -2,7 +2,7 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 321 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 319 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. - [MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 Init/Update/Final and SHA256](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - signatures and context layouts follow the [LibreSSL headers](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha/sha.h), and buffering and `Final` follow its [sources](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/sha); nothing is checked on the console. Null contexts, null input with a nonzero length, null digest buffers in the 32-bit `Final` functions and a buffered byte count that is not below the block size, which LibreSSL does not check, throw - [DES_set_key, DES_ecb_encrypt, DES_set_odd_parity](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the 128-byte `DES_key_schedule` holds the 8-byte key followed by zeros instead of LibreSSL's precomputed subkeys, so a title reading its contents sees different bytes; `DES_ecb_encrypt` derives the subkeys through mbedtls on each call. Not compared with the console. Results follow LibreSSL [des_key.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des_key.c) and [des.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/des/des.c): `DES_set_key` skips the parity and weak-key checks and returns 0, as it does while `DES_check_key` is 0 (that variable is not in the PS5 export inventory), a nonzero `enc` encrypts, and `DES_set_odd_parity` sets each byte's low bit for odd parity. Null pointers throw where LibreSSL faults - [X509_verify_cert_error_string](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - no console call is verified. The 64-bit argument, low-32-bit lookup, messages and shared numeric fallback follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_txt.c), the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware versions remain unverified. Unknown-code results share an overwritten static buffer and are not safe for concurrent use, as in that release; code 1 and codes above 54 use the numeric fallback. @@ -14,6 +14,7 @@ - [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release. - [sk_num, sk_value, sk_pop, sk_pop_free](../../../core/libs/prx/libSceLibreSsl/src/Stack.cpp) (libSceLibreSsl) - the 32-byte `_STACK` layout (`num`, `data`, `sorted`, `num_alloc`, `comp`) and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/stack) `stack.h` and `stack.c`, the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214); nothing is checked on the console. `sk_pop_free` releases the element array and the stack with the guest `free`, as the module would through libc. No export of this module creates a stack yet, so only stacks the caller builds reach them. A null element array that LibreSSL would read through, and a null free function with a non-null element, throw where LibreSSL faults +- [RSA_flags, RSA_size](../../../core/libs/prx/libSceLibreSsl/src/Rsa.cpp) (libSceLibreSsl) - nothing is checked on the console. Layouts and results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto) `rsa.h`, `rsa_crpt.c` and `bn_lib.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). No export of this module creates a key yet, so only keys the caller builds reach them. `RSA_size` counts the modulus bytes from its top word, as `BN_num_bytes`, so a zero top word is not skipped. A null method, a null key or modulus, and a positive word count with null words, which LibreSSL dereferences, throw, as do a negative word count and a bit count that overflows `int` ### Functional