Merge pull request #9571 from Felipebmaitan/feat/libressl-cmac

feat(libSceLibreSsl): implement CMAC with AES-CBC methods
This commit is contained in:
Dean authored and GitHub committed 2026-11-03 21:07:18 +13:00
commit b9cd424291
15 files changed
+2405 -184

No files matched your search

+2
View File
@@ -302,6 +302,8 @@ include(prx/libSceFreeType/BuildInfo.cmake)
add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS})
include(prx/libSceLibreSsl/ErrorQueue.cmake)
include(prx/libSceLibreSsl/MemoryBio.cmake)
include(prx/libSceLibreSsl/Cmac.cmake)
include(prx/libSceLibreSsl/ProtocolSelection.cmake)
+12 -1
View File
@@ -1,10 +1,21 @@
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 ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipherCtx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Evp.cpp)
add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Bignum.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 ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipherCtx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Evp.cpp)
target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto)
configure_windows_unwind(libSceLibreSsl)
target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Asn1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ExData.cpp)
if(BUILD_TESTING)
add_test_executable(guest_libressl_bn_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslBn.cpp)
target_include_directories(guest_libressl_bn_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_libressl_bn_tests PRIVATE libSceLibreSsl libc)
configure_windows_unwind(guest_libressl_bn_tests)
add_test(NAME guest_libressl_bn COMMAND guest_libressl_bn_tests)
set_tests_properties(guest_libressl_bn PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(guest_libressl_bn PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibreSsl>")
endif()
add_test_executable(guest_libressl_x509_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Errors.cpp)
target_include_directories(guest_libressl_x509_errors_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_libressl_x509_errors_tests PRIVATE libSceLibreSsl libc)
+32
View File
@@ -0,0 +1,32 @@
target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/Cmac.cpp)
target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto)
if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_options(libSceLibreSsl PRIVATE LINKER:--exclude-libs,ALL)
endif()
if(BUILD_TESTING)
add_test_executable(guest_libressl_cmac_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslCmac.cpp)
target_include_directories(guest_libressl_cmac_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_libressl_cmac_tests PRIVATE libSceLibreSsl libc mbedcrypto)
configure_windows_unwind(guest_libressl_cmac_tests)
add_test(NAME guest_libressl_cmac COMMAND guest_libressl_cmac_tests)
add_test_executable(guest_libressl_cmac_patched_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslCmac.cpp)
target_include_directories(guest_libressl_cmac_patched_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_compile_definitions(guest_libressl_cmac_patched_tests PRIVATE ANYPS5_CMAC_DYNAMIC)
target_link_libraries(guest_libressl_cmac_patched_tests PRIVATE mbedcrypto ${CMAKE_DL_LIBS})
configure_windows_unwind(guest_libressl_cmac_patched_tests)
if(WIN32)
target_link_libraries(guest_libressl_cmac_patched_tests PRIVATE libc)
endif()
add_custom_target(guest_libressl_cmac_patch DEPENDS ${PATCHED_DIR}/libSceLibreSsl.prx ${PATCHED_DIR}/libc.prx)
add_dependencies(guest_libressl_cmac_patched_tests guest_libressl_cmac_patch)
add_test(NAME guest_libressl_cmac_patched COMMAND guest_libressl_cmac_patched_tests ${PATCHED_DIR})
set_tests_properties(guest_libressl_cmac guest_libressl_cmac_patched PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(guest_libressl_cmac PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibreSsl>")
set_tests_properties(guest_libressl_cmac_patched PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:${PATCHED_DIR}")
endif()
endif()
+3 -147
View File
@@ -8,6 +8,7 @@
#include <stdexcept>
#include <string>
#include "SceTypes.hpp"
#include "prx/libSceLibreSsl/include/Bio.hpp"
#include "prx/libc/include/General.hpp"
#include "mbedtls/des.h"
@@ -48,36 +49,16 @@ int APS5_VABI ASN1_TIME_print_nid_postfix(void) {
return 0;
}
int APS5_VABI BIO_ctrl_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BIO_f_ssl_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BIO_free_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BIO_new_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BIO_new_fp_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BIO_new_mem_buf_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BIO_new_socket_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -88,89 +69,13 @@ int APS5_VABI BIO_printf_nid_postfix(void) {
return 0;
}
int APS5_VABI BIO_puts_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BIO_s_file_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BIO_s_mem_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BIO_write_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_CTX_end_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_CTX_free_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_CTX_new_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_CTX_start_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_add_word_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_bin2bn_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_bn2bin_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_free_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_hex2bn_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_mod_inverse_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_mul_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_new_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_num_bits_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI BIO_write_nid_postfix(LibreSsl::Bio* bio, const void* source, int size) {
return LibreSslBioWrite_nid_no_patch(bio, source, size);
}
int APS5_VABI BN_print_nid_postfix(void) {
@@ -178,41 +83,6 @@ int APS5_VABI BN_print_nid_postfix(void) {
return 0;
}
int APS5_VABI BN_set_word_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI BN_sub_word_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI CMAC_CTX_free_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI CMAC_CTX_new_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI CMAC_Final_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI CMAC_Init_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI CMAC_Update_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI CONF_modules_free_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -570,10 +440,6 @@ int APS5_VABI EVP_PKEY_set1_RSA_nid_postfix(void) {
return 0;
}
int APS5_VABI EVP_aes_128_cbc_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EVP_aes_128_cfb8_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
@@ -590,11 +456,6 @@ int APS5_VABI EVP_aes_128_gcm_nid_postfix(void) {
return 0;
}
int APS5_VABI EVP_aes_192_cbc_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EVP_aes_192_cfb8_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -610,11 +471,6 @@ int APS5_VABI EVP_aes_192_gcm_nid_postfix(void) {
return 0;
}
int APS5_VABI EVP_aes_256_cbc_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EVP_aes_256_cfb8_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -0,0 +1,32 @@
target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/Bio.cpp)
if(BUILD_TESTING)
add_test_executable(guest_libressl_bio_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslBio.cpp)
target_include_directories(guest_libressl_bio_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_libressl_bio_tests PRIVATE libSceLibreSsl libc)
configure_windows_unwind(guest_libressl_bio_tests)
add_test(NAME guest_libressl_bio COMMAND guest_libressl_bio_tests)
set_tests_properties(guest_libressl_bio PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(guest_libressl_bio PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibreSsl>")
endif()
add_test_executable(guest_libressl_bio_patched_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslBio.cpp)
target_include_directories(guest_libressl_bio_patched_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_compile_definitions(guest_libressl_bio_patched_tests PRIVATE ANYPS5_BIO_DYNAMIC)
target_link_libraries(guest_libressl_bio_patched_tests PRIVATE ${CMAKE_DL_LIBS})
configure_windows_unwind(guest_libressl_bio_patched_tests)
if(WIN32)
target_link_libraries(guest_libressl_bio_patched_tests PRIVATE libc)
endif()
add_custom_target(libressl_bio_patched_libraries DEPENDS
${PATCHED_DIR}/libSceLibreSsl.prx ${PATCHED_DIR}/libc.prx ${PATCHED_DIR}/libkernel.prx)
add_dependencies(guest_libressl_bio_patched_tests libressl_bio_patched_libraries)
add_test(NAME guest_libressl_bio_patched COMMAND guest_libressl_bio_patched_tests ${PATCHED_DIR})
set_tests_properties(guest_libressl_bio_patched PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(guest_libressl_bio_patched PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:${PATCHED_DIR}")
endif()
endif()
+49 -34
View File
@@ -1,6 +1,9 @@
#pragma once
#include <vector>
#include "SceTypes.hpp"
#include "Bio.hpp"
#include "Cmac.hpp"
struct Asn1String {
std::int32_t length;
@@ -131,6 +134,18 @@ struct Sha512Context {
std::uint32_t num, mdLength;
};
struct Bignum {
std::uint64_t* d;
int top;
int dmax;
int neg;
int flags;
};
struct BignumContext {
std::vector<std::size_t> marks;
};
using StackCompare = int (APS5_VABI*)(const void*, const void*);
using StackElementFree = void (APS5_VABI*)(void*);
@@ -317,39 +332,39 @@ int APS5_VABI ASN1_STRING_print_nid_postfix(void);
int APS5_VABI ASN1_STRING_to_UTF8_nid_postfix(unsigned char** output, Asn1String* value);
int APS5_VABI ASN1_STRING_type_nid_postfix(Asn1String* value);
int APS5_VABI ASN1_TIME_print_nid_postfix(void);
int APS5_VABI BIO_ctrl_nid_postfix(void);
std::int64_t APS5_VABI BIO_ctrl_nid_postfix(LibreSsl::Bio* bio, int command, std::int64_t argument, void* pointer);
int APS5_VABI BIO_f_ssl_nid_postfix(void);
int APS5_VABI BIO_free_nid_postfix(void);
int APS5_VABI BIO_new_nid_postfix(void);
int APS5_VABI BIO_free_nid_postfix(LibreSsl::Bio* bio);
LibreSsl::Bio* APS5_VABI BIO_new_nid_postfix(const LibreSsl::BioMethod* method);
int APS5_VABI BIO_new_fp_nid_postfix(void);
int APS5_VABI BIO_new_mem_buf_nid_postfix(void);
LibreSsl::Bio* APS5_VABI BIO_new_mem_buf_nid_postfix(const void* source, int size);
int APS5_VABI BIO_new_socket_nid_postfix(void);
int APS5_VABI BIO_printf_nid_postfix(void);
int APS5_VABI BIO_puts_nid_postfix(void);
int APS5_VABI BIO_puts_nid_postfix(LibreSsl::Bio* bio, const char* source);
int APS5_VABI BIO_s_file_nid_postfix(void);
int APS5_VABI BIO_s_mem_nid_postfix(void);
int APS5_VABI BIO_write_nid_postfix(void);
int APS5_VABI BN_CTX_end_nid_postfix(void);
int APS5_VABI BN_CTX_free_nid_postfix(void);
int APS5_VABI BN_CTX_new_nid_postfix(void);
int APS5_VABI BN_CTX_start_nid_postfix(void);
int APS5_VABI BN_add_word_nid_postfix(void);
int APS5_VABI BN_bin2bn_nid_postfix(void);
int APS5_VABI BN_bn2bin_nid_postfix(void);
int APS5_VABI BN_free_nid_postfix(void);
int APS5_VABI BN_hex2bn_nid_postfix(void);
int APS5_VABI BN_mod_inverse_nid_postfix(void);
int APS5_VABI BN_mul_nid_postfix(void);
int APS5_VABI BN_new_nid_postfix(void);
int APS5_VABI BN_num_bits_nid_postfix(void);
const LibreSsl::BioMethod* APS5_VABI BIO_s_mem_nid_postfix();
int APS5_VABI BIO_write_nid_postfix(LibreSsl::Bio* bio, const void* source, int size);
void APS5_VABI BN_CTX_end_nid_postfix(BignumContext* ctx);
void APS5_VABI BN_CTX_free_nid_postfix(BignumContext* ctx);
BignumContext* APS5_VABI BN_CTX_new_nid_postfix(void);
void APS5_VABI BN_CTX_start_nid_postfix(BignumContext* ctx);
int APS5_VABI BN_add_word_nid_postfix(Bignum* a, std::uint64_t w);
Bignum* APS5_VABI BN_bin2bn_nid_postfix(const unsigned char* s, int len, Bignum* ret);
int APS5_VABI BN_bn2bin_nid_postfix(const Bignum* a, unsigned char* to);
void APS5_VABI BN_free_nid_postfix(Bignum* a);
int APS5_VABI BN_hex2bn_nid_postfix(Bignum** a, const char* str);
Bignum* APS5_VABI BN_mod_inverse_nid_postfix(Bignum* r, const Bignum* a, const Bignum* n, BignumContext*);
int APS5_VABI BN_mul_nid_postfix(Bignum* r, const Bignum* a, const Bignum* b, BignumContext*);
Bignum* APS5_VABI BN_new_nid_postfix(void);
int APS5_VABI BN_num_bits_nid_postfix(const Bignum* a);
int APS5_VABI BN_print_nid_postfix(void);
int APS5_VABI BN_set_word_nid_postfix(void);
int APS5_VABI BN_sub_word_nid_postfix(void);
int APS5_VABI CMAC_CTX_free_nid_postfix(void);
int APS5_VABI CMAC_CTX_new_nid_postfix(void);
int APS5_VABI CMAC_Final_nid_postfix(void);
int APS5_VABI CMAC_Init_nid_postfix(void);
int APS5_VABI CMAC_Update_nid_postfix(void);
int APS5_VABI BN_set_word_nid_postfix(Bignum* a, std::uint64_t w);
int APS5_VABI BN_sub_word_nid_postfix(Bignum* a, std::uint64_t w);
void APS5_VABI CMAC_CTX_free_nid_postfix(LibreSslCmac* context);
LibreSslCmac* APS5_VABI CMAC_CTX_new_nid_postfix();
int APS5_VABI CMAC_Final_nid_postfix(LibreSslCmac* context, unsigned char* output, std::size_t* length);
int APS5_VABI CMAC_Init_nid_postfix(LibreSslCmac* context, const void* key, std::size_t keyLength, const LibreSslCipher* cipher, void* engine);
int APS5_VABI CMAC_Update_nid_postfix(LibreSslCmac* context, const void* data, std::size_t length);
int APS5_VABI CONF_modules_free_nid_postfix(void);
int APS5_VABI CONF_modules_load_file_nid_postfix(void);
int APS5_VABI CRYPTO_THREADID_set_callback_nid_postfix(void* callback);
@@ -399,13 +414,13 @@ int APS5_VABI ENGINE_init_nid_postfix(void);
int APS5_VABI ENGINE_load_builtin_engines_nid_postfix(void);
int APS5_VABI ENGINE_load_private_key_nid_postfix(void);
int APS5_VABI ENGINE_set_default_nid_postfix(void);
void APS5_VABI ERR_clear_error_nid_postfix(void);
void APS5_VABI ERR_clear_error_nid_postfix();
char* APS5_VABI ERR_error_string_nid_postfix(std::uint64_t error, char* buffer);
void APS5_VABI ERR_error_string_n_nid_postfix(std::uint64_t error, char* buffer, std::size_t length);
void APS5_VABI ERR_free_strings_nid_postfix();
std::uint64_t APS5_VABI ERR_get_error_nid_postfix(void);
std::uint64_t APS5_VABI ERR_peek_error_nid_postfix(void);
void APS5_VABI ERR_remove_thread_state_nid_postfix(const void* threadId);
std::uint64_t APS5_VABI ERR_get_error_nid_postfix();
std::uint64_t APS5_VABI ERR_peek_error_nid_postfix();
void APS5_VABI ERR_remove_thread_state_nid_postfix(const void* id);
int APS5_VABI EVP_CIPHER_CTX_cleanup_nid_postfix(void);
int APS5_VABI EVP_CIPHER_CTX_ctrl_nid_postfix(void);
int APS5_VABI EVP_CIPHER_CTX_free_nid_postfix(void);
@@ -441,15 +456,15 @@ int APS5_VABI EVP_PKEY_id_nid_postfix(void);
int APS5_VABI EVP_PKEY_new_nid_postfix(void);
int APS5_VABI EVP_PKEY_set1_RSA_nid_postfix(void);
int APS5_VABI EVP_PKEY_type_nid_postfix(int type);
int APS5_VABI EVP_aes_128_cbc_nid_postfix(void);
const LibreSslCipher* APS5_VABI EVP_aes_128_cbc_nid_postfix();
int APS5_VABI EVP_aes_128_cfb8_nid_postfix(void);
int APS5_VABI EVP_aes_128_ctr_nid_postfix(void);
int APS5_VABI EVP_aes_128_gcm_nid_postfix(void);
int APS5_VABI EVP_aes_192_cbc_nid_postfix(void);
const LibreSslCipher* APS5_VABI EVP_aes_192_cbc_nid_postfix();
int APS5_VABI EVP_aes_192_cfb8_nid_postfix(void);
int APS5_VABI EVP_aes_192_ctr_nid_postfix(void);
int APS5_VABI EVP_aes_192_gcm_nid_postfix(void);
int APS5_VABI EVP_aes_256_cbc_nid_postfix(void);
const LibreSslCipher* APS5_VABI EVP_aes_256_cbc_nid_postfix();
int APS5_VABI EVP_aes_256_cfb8_nid_postfix(void);
int APS5_VABI EVP_aes_256_ctr_nid_postfix(void);
int APS5_VABI EVP_aes_256_gcm_nid_postfix(void);
@@ -0,0 +1,72 @@
#ifndef CORE_LIBS_PRX_LIBSCELIBRESSL_INCLUDE_BIO_HPP
#define CORE_LIBS_PRX_LIBSCELIBRESSL_INCLUDE_BIO_HPP
#include <cstddef>
#include <cstdint>
#include "prx/libc/include/general/VabiMacros.hpp"
namespace LibreSsl {
struct Bio;
using BioCallback = std::int64_t (APS5_VABI *)(Bio*, int, const char*, int, std::int64_t, std::int64_t);
using BioInfoCallback = void (APS5_VABI *)(Bio*, int, const char*, int, std::int64_t, std::int64_t);
struct BioMethod {
int type;
const char* name;
int (APS5_VABI *write)(Bio*, const char*, int);
int (APS5_VABI *read)(Bio*, char*, int);
int (APS5_VABI *puts)(Bio*, const char*);
int (APS5_VABI *gets)(Bio*, char*, int);
std::int64_t (APS5_VABI *control)(Bio*, int, std::int64_t, void*);
int (APS5_VABI *create)(Bio*);
int (APS5_VABI *destroy)(Bio*);
std::int64_t (APS5_VABI *callbackControl)(Bio*, int, BioInfoCallback);
};
struct Bio {
const BioMethod* method;
BioCallback callback;
char* callbackArgument;
int initialized;
int shutdown;
int flags;
int retryReason;
int number;
void* data;
Bio* next;
Bio* previous;
int references;
std::uint64_t bytesRead;
std::uint64_t bytesWritten;
void* extraData;
};
struct BioBuffer {
std::size_t length;
char* data;
std::size_t capacity;
};
static_assert(sizeof(BioMethod) == 80);
static_assert(offsetof(BioMethod, write) == 16);
static_assert(offsetof(BioMethod, control) == 48);
static_assert(offsetof(BioMethod, callbackControl) == 72);
static_assert(sizeof(Bio) == 104);
static_assert(offsetof(Bio, initialized) == 24);
static_assert(offsetof(Bio, flags) == 32);
static_assert(offsetof(Bio, number) == 40);
static_assert(offsetof(Bio, data) == 48);
static_assert(offsetof(Bio, references) == 72);
static_assert(offsetof(Bio, bytesRead) == 80);
static_assert(offsetof(Bio, bytesWritten) == 88);
static_assert(offsetof(Bio, extraData) == 96);
static_assert(sizeof(BioBuffer) == 24);
static_assert(offsetof(BioBuffer, data) == 8);
static_assert(offsetof(BioBuffer, capacity) == 16);
} // namespace LibreSsl
extern "C" int LibreSslBioWrite_nid_no_patch(LibreSsl::Bio* bio, const void* source, int size);
#endif
@@ -0,0 +1,46 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include "prx/libc/include/general/VabiMacros.hpp"
struct LibreSslCipherContext;
struct LibreSslCipher {
std::int32_t nid;
std::int32_t blockSize;
std::int32_t keyLength;
std::int32_t ivLength;
std::uint64_t flags;
int (APS5_VABI* init)(LibreSslCipherContext*, const unsigned char*, const unsigned char*, int);
int (APS5_VABI* cipher)(LibreSslCipherContext*, unsigned char*, const unsigned char*, std::size_t);
int (APS5_VABI* cleanup)(LibreSslCipherContext*);
std::int32_t contextSize;
int (APS5_VABI* setAsn1Parameters)(LibreSslCipherContext*, void*);
int (APS5_VABI* getAsn1Parameters)(LibreSslCipherContext*, void*);
int (APS5_VABI* control)(LibreSslCipherContext*, int, int, void*);
void* applicationData;
};
struct LibreSslCipherContext {
const LibreSslCipher* cipher;
void* engine;
std::int32_t encrypt;
std::int32_t bufferLength;
unsigned char originalIv[16];
unsigned char iv[16];
unsigned char buffer[32];
std::int32_t position;
void* applicationData;
std::int32_t keyLength;
std::uint64_t flags;
void* cipherData;
std::int32_t finalUsed;
std::int32_t blockMask;
unsigned char final[32];
};
static_assert(sizeof(LibreSslCipher) == 88 && offsetof(LibreSslCipher, init) == 24 && offsetof(LibreSslCipher, contextSize) == 48);
static_assert(sizeof(LibreSslCipherContext) == 168 && offsetof(LibreSslCipherContext, iv) == 40 && offsetof(LibreSslCipherContext, flags) == 112 && offsetof(LibreSslCipherContext, cipherData) == 120);
struct LibreSslCmac;
+378
View File
@@ -0,0 +1,378 @@
#include "prx/libSceLibreSsl/libSceLibreSsl.hpp"
#include "prx/libc/include/Api.hpp"
#include <bit>
#include <cstddef>
#include <cstdint>
#include <new>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "prx/libc/include/General.hpp"
namespace {
static_assert(sizeof(Bignum) == 24);
static_assert(offsetof(Bignum, d) == 0);
static_assert(offsetof(Bignum, top) == 8);
static_assert(offsetof(Bignum, dmax) == 12);
static_assert(offsetof(Bignum, neg) == 16);
static_assert(offsetof(Bignum, flags) == 20);
constexpr int FlagMalloced = 1;
constexpr int FlagStaticData = 2;
using Mag = std::vector<std::uint64_t>;
struct Int {
Mag mag;
bool neg = false;
};
void Trim(Mag& m) {
while (!m.empty() && m.back() == 0) m.pop_back();
}
int Compare(const Mag& a, const Mag& b) {
if (a.size() != b.size()) return a.size() < b.size() ? -1 : 1;
for (std::size_t i = a.size(); i-- > 0;) {
if (a[i] != b[i]) return a[i] < b[i] ? -1 : 1;
}
return 0;
}
Mag AddMag(const Mag& a, const Mag& b) {
const std::size_t count = a.size() > b.size() ? a.size() : b.size();
Mag r;
r.reserve(count + 1);
std::uint64_t carry = 0;
for (std::size_t i = 0; i < count; i++) {
const unsigned __int128 sum = static_cast<unsigned __int128>(i < a.size() ? a[i] : 0) + (i < b.size() ? b[i] : 0) + carry;
r.push_back(static_cast<std::uint64_t>(sum));
carry = static_cast<std::uint64_t>(sum >> 64);
}
if (carry) r.push_back(carry);
return r;
}
Mag SubMag(const Mag& a, const Mag& b) {
Mag r(a.size());
std::uint64_t borrow = 0;
for (std::size_t i = 0; i < a.size(); i++) {
const std::uint64_t x = a[i];
const std::uint64_t y = i < b.size() ? b[i] : 0;
const std::uint64_t diff = x - y;
const std::uint64_t first = x < y;
r[i] = diff - borrow;
borrow = first | (diff < borrow);
}
Trim(r);
return r;
}
Mag MulMag(const Mag& a, const Mag& b) {
if (a.empty() || b.empty()) return {};
Mag r(a.size() + b.size(), 0);
for (std::size_t i = 0; i < a.size(); i++) {
std::uint64_t carry = 0;
for (std::size_t j = 0; j < b.size(); j++) {
const unsigned __int128 product = static_cast<unsigned __int128>(a[i]) * b[j] + r[i + j] + carry;
r[i + j] = static_cast<std::uint64_t>(product);
carry = static_cast<std::uint64_t>(product >> 64);
}
r[i + b.size()] = carry;
}
Trim(r);
return r;
}
std::size_t BitLength(const Mag& m) {
if (m.empty()) return 0;
return (m.size() - 1) * 64 + std::bit_width(m.back());
}
Mag ShiftLeft(const Mag& m, std::size_t bits) {
if (m.empty()) return {};
const std::size_t words = bits / 64;
const unsigned rest = static_cast<unsigned>(bits % 64);
Mag r(m.size() + words + 1, 0);
for (std::size_t i = 0; i < m.size(); i++) {
r[i + words] |= m[i] << rest;
if (rest) r[i + words + 1] |= m[i] >> (64 - rest);
}
Trim(r);
return r;
}
void ShiftRightOne(Mag& m) {
for (std::size_t i = 0; i < m.size(); i++) {
m[i] >>= 1;
if (i + 1 < m.size()) m[i] |= m[i + 1] << 63;
}
Trim(m);
}
void DivMod(const Mag& a, const Mag& b, Mag& quotient, Mag& remainder) {
remainder = a;
quotient.clear();
const std::size_t aBits = BitLength(a);
const std::size_t bBits = BitLength(b);
if (aBits < bBits) return;
const std::size_t shift = aBits - bBits;
Mag divisor = ShiftLeft(b, shift);
quotient.assign(shift / 64 + 1, 0);
for (std::size_t s = shift + 1; s-- > 0;) {
if (Compare(remainder, divisor) >= 0) {
remainder = SubMag(remainder, divisor);
quotient[s / 64] |= std::uint64_t(1) << (s % 64);
}
ShiftRightOne(divisor);
}
Trim(quotient);
}
Int AddSigned(const Int& x, const Int& y) {
Int r;
if (x.neg == y.neg) {
r.mag = AddMag(x.mag, y.mag);
r.neg = x.neg;
} else {
const int order = Compare(x.mag, y.mag);
if (order > 0) {
r.mag = SubMag(x.mag, y.mag);
r.neg = x.neg;
} else if (order < 0) {
r.mag = SubMag(y.mag, x.mag);
r.neg = y.neg;
}
}
if (r.mag.empty()) r.neg = false;
return r;
}
bool InverseMod(const Int& a, const Mag& n, Mag& out) {
if (n.size() == 1 && n[0] == 1) {
out.clear();
return true;
}
Mag quotient;
Mag r1;
DivMod(a.mag, n, quotient, r1);
if (a.neg && !r1.empty()) r1 = SubMag(n, r1);
Mag r0 = n;
Int t0;
Int t1;
t1.mag = {1};
while (!r1.empty()) {
Mag remainder;
DivMod(r0, r1, quotient, remainder);
Int scaled;
scaled.mag = MulMag(quotient, t1.mag);
scaled.neg = !t1.neg && !scaled.mag.empty();
Int t2 = AddSigned(t0, scaled);
r0 = std::move(r1);
r1 = std::move(remainder);
t0 = std::move(t1);
t1 = std::move(t2);
}
if (r0.size() != 1 || r0[0] != 1) return false;
out = t0.neg ? SubMag(n, t0.mag) : t0.mag;
return true;
}
Bignum* Checked(Bignum* value, const char* function) {
if (!value) throw std::invalid_argument(std::string(function) + ": null BIGNUM");
return value;
}
const Bignum* Checked(const Bignum* value, const char* function) {
if (!value) throw std::invalid_argument(std::string(function) + ": null BIGNUM");
return value;
}
Int Load(const Bignum* bn) {
Int r;
if (bn->top > 0) r.mag.assign(bn->d, bn->d + bn->top);
Trim(r.mag);
r.neg = bn->neg && !r.mag.empty();
return r;
}
bool Store(Bignum* bn, const Mag& m, bool neg) {
if (m.empty()) {
bn->top = 0;
bn->neg = 0;
return true;
}
const std::size_t count = m.size();
if (!bn->d || static_cast<std::size_t>(bn->dmax) < count) {
if (bn->flags & FlagStaticData) return false;
if (count > 0x7FFFFFFF) return false;
auto* grown = static_cast<std::uint64_t*>(calloc_nid_postfix(count, sizeof(std::uint64_t)));
if (!grown) return false;
if (bn->d) free_nid_postfix(bn->d);
bn->d = grown;
bn->dmax = static_cast<int>(count);
}
for (std::size_t i = 0; i < count; i++) bn->d[i] = m[i];
for (std::size_t i = count; i < static_cast<std::size_t>(bn->dmax); i++) bn->d[i] = 0;
bn->top = static_cast<int>(count);
bn->neg = neg ? 1 : 0;
return true;
}
Bignum* NewBignum() {
auto* bn = static_cast<Bignum*>(calloc_nid_postfix(1, sizeof(Bignum)));
if (bn) bn->flags = FlagMalloced;
return bn;
}
void FreeBignum(Bignum* bn) {
if (!bn) return;
if (bn->d && !(bn->flags & FlagStaticData)) free_nid_postfix(bn->d);
if (bn->flags & FlagMalloced) {
free_nid_postfix(bn);
} else {
bn->d = nullptr;
}
}
int HexValue(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
bool ApplyWord(Bignum* a, std::uint64_t w, bool subtract) {
Int word;
if (w) word.mag = {w};
word.neg = subtract && w;
const Int sum = AddSigned(Load(a), word);
return Store(a, sum.mag, sum.neg);
}
}
extern "C" {
Bignum* APS5_VABI BN_new_nid_postfix(void) {
return NewBignum();
}
void APS5_VABI BN_free_nid_postfix(Bignum* a) {
FreeBignum(a);
}
BignumContext* APS5_VABI BN_CTX_new_nid_postfix(void) {
return new (std::nothrow) BignumContext();
}
void APS5_VABI BN_CTX_free_nid_postfix(BignumContext* ctx) {
delete ctx;
}
void APS5_VABI BN_CTX_start_nid_postfix(BignumContext* ctx) {
if (!ctx) throw std::invalid_argument(std::string(__func__) + ": null context");
ctx->marks.push_back(0);
}
void APS5_VABI BN_CTX_end_nid_postfix(BignumContext* ctx) {
if (!ctx) throw std::invalid_argument(std::string(__func__) + ": null context");
if (!ctx->marks.empty()) ctx->marks.pop_back();
}
Bignum* APS5_VABI BN_bin2bn_nid_postfix(const unsigned char* s, int len, Bignum* ret) {
if (len < 0) return nullptr;
if (len > 0 && !s) throw std::invalid_argument(std::string(__func__) + ": null input");
Bignum* target = ret ? ret : NewBignum();
if (!target) return nullptr;
Mag m((static_cast<std::size_t>(len) + 7) / 8, 0);
for (int i = 0; i < len; i++) {
const std::size_t bit = static_cast<std::size_t>(len - 1 - i);
m[bit / 8] |= static_cast<std::uint64_t>(s[i]) << (bit % 8 * 8);
}
Trim(m);
if (!Store(target, m, false)) {
if (!ret) FreeBignum(target);
return nullptr;
}
return target;
}
int APS5_VABI BN_num_bits_nid_postfix(const Bignum* a) {
return static_cast<int>(BitLength(Load(Checked(a, __func__)).mag));
}
int APS5_VABI BN_bn2bin_nid_postfix(const Bignum* a, unsigned char* to) {
const Mag m = Load(Checked(a, __func__)).mag;
const std::size_t length = (BitLength(m) + 7) / 8;
if (length && !to) throw std::invalid_argument(std::string(__func__) + ": null output");
for (std::size_t i = 0; i < length; i++) {
const std::size_t bit = length - 1 - i;
to[i] = static_cast<unsigned char>(m[bit / 8] >> (bit % 8 * 8));
}
return static_cast<int>(length);
}
int APS5_VABI BN_hex2bn_nid_postfix(Bignum** a, const char* str) {
if (!str || !*str) return 0;
const bool negative = *str == '-';
const char* digits = str + negative;
std::size_t count = 0;
while (HexValue(digits[count]) >= 0) count++;
if (count == 0 || count > 0x7FFFFFFF / 4) return 0;
const int consumed = static_cast<int>(count + negative);
if (!a) return consumed;
Mag m((count + 15) / 16, 0);
for (std::size_t i = 0; i < count; i++) {
const std::size_t nibble = count - 1 - i;
m[nibble / 16] |= static_cast<std::uint64_t>(HexValue(digits[i])) << (nibble % 16 * 4);
}
Trim(m);
Bignum* target = *a ? *a : NewBignum();
if (!target) return 0;
if (!Store(target, m, negative)) {
if (!*a) FreeBignum(target);
return 0;
}
*a = target;
return consumed;
}
int APS5_VABI BN_set_word_nid_postfix(Bignum* a, std::uint64_t w) {
Checked(a, __func__);
Mag m;
if (w) m = {w};
return Store(a, m, false);
}
int APS5_VABI BN_add_word_nid_postfix(Bignum* a, std::uint64_t w) {
return ApplyWord(Checked(a, __func__), w, false);
}
int APS5_VABI BN_sub_word_nid_postfix(Bignum* a, std::uint64_t w) {
return ApplyWord(Checked(a, __func__), w, true);
}
int APS5_VABI BN_mul_nid_postfix(Bignum* r, const Bignum* a, const Bignum* b, BignumContext*) {
Checked(r, __func__);
const Int x = Load(Checked(a, __func__));
const Int y = Load(Checked(b, __func__));
const Mag product = MulMag(x.mag, y.mag);
return Store(r, product, x.neg != y.neg);
}
Bignum* APS5_VABI BN_mod_inverse_nid_postfix(Bignum* r, const Bignum* a, const Bignum* n, BignumContext*) {
const Int value = Load(Checked(a, __func__));
const Int modulus = Load(Checked(n, __func__));
if (modulus.mag.empty()) return nullptr;
Mag inverse;
if (!InverseMod(value, modulus.mag, inverse)) return nullptr;
Bignum* target = r ? r : NewBignum();
if (!target) return nullptr;
if (!Store(target, inverse, false)) {
if (!r) FreeBignum(target);
return nullptr;
}
return target;
}
}
+284
View File
@@ -0,0 +1,284 @@
#include "prx/libSceLibreSsl/libSceLibreSsl.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libSceLibreSsl/ErrorQueue.hpp"
#include "prx/libc/include/General.hpp"
#include <algorithm>
#include <atomic>
#include <cstring>
#include <limits>
#include <stdexcept>
namespace {
using LibreSsl::Bio;
using LibreSsl::BioBuffer;
using LibreSsl::BioMethod;
constexpr int ReadOnly = 0x200;
constexpr int RetryFlags = 0xf;
constexpr int RetryRead = 0x9;
constexpr std::size_t MaximumGrowth = 0x5ffffffc;
constexpr int BioLibrary = 32;
constexpr int BufferLibrary = 7;
constexpr int AllocationFailure = 65;
constexpr int NullParameter = 115;
constexpr int Uninitialized = 120;
constexpr int UnsupportedMethod = 121;
constexpr int ReadOnlyWrite = 126;
int queueError(int library, int reason, int result) {
LibreSslPutError_nid_no_patch(library, 0xfff, reason, __FILE__, __LINE__);
return result;
}
void clearBuffer(BioBuffer& buffer) {
auto* bytes = reinterpret_cast<volatile unsigned char*>(buffer.data);
for (std::size_t i = 0; i < buffer.capacity; ++i) bytes[i] = 0;
}
BioBuffer& memoryBuffer(Bio* bio) {
if (!bio || !bio->data) throw std::invalid_argument("BIO: missing memory buffer");
return *static_cast<BioBuffer*>(bio->data);
}
int APS5_VABI createMemory(Bio* bio) {
auto* buffer = static_cast<BioBuffer*>(calloc_nid_postfix(1, sizeof(BioBuffer)));
if (!buffer) return queueError(BufferLibrary, AllocationFailure, 0);
bio->data = buffer;
bio->initialized = 1;
bio->shutdown = 1;
bio->number = -1;
return 1;
}
int APS5_VABI destroyMemory(Bio* bio) {
if (!bio) return 0;
if (bio->shutdown && bio->initialized && bio->data) {
auto* buffer = static_cast<BioBuffer*>(bio->data);
if (!(bio->flags & ReadOnly)) {
clearBuffer(*buffer);
free_nid_postfix(buffer->data);
}
free_nid_postfix(buffer);
bio->data = nullptr;
}
return 1;
}
int APS5_VABI readMemory(Bio* bio, char* destination, int size) {
auto& buffer = memoryBuffer(bio);
bio->flags &= ~RetryFlags;
const int count = size >= 0 ? static_cast<int>(std::min<std::size_t>(size, buffer.length)) : size;
if (destination && count > 0) {
std::memcpy(destination, buffer.data, count);
buffer.length -= count;
if (bio->flags & ReadOnly) buffer.data += count;
else std::memmove(buffer.data, buffer.data + count, buffer.length);
return count;
}
if (buffer.length != 0) return count;
if (bio->number != 0) bio->flags |= RetryRead;
return bio->number;
}
int APS5_VABI writeMemory(Bio* bio, const char* source, int size) {
auto& buffer = memoryBuffer(bio);
if (!source) return queueError(BioLibrary, NullParameter, -1);
if (bio->flags & ReadOnly) return queueError(BioLibrary, ReadOnlyWrite, -1);
if (size < 0) throw std::invalid_argument("BIO: negative memory write size");
bio->flags &= ~RetryFlags;
const auto count = static_cast<std::size_t>(size);
if (buffer.length > std::numeric_limits<std::size_t>::max() - count) NotImplemented_nid_no_patch(__func__);
const std::size_t length = buffer.length + count;
if (length > buffer.capacity) {
if (length > MaximumGrowth) return queueError(BufferLibrary, AllocationFailure, -1);
const std::size_t capacity = (length + 3) / 3 * 4;
auto* data = static_cast<char*>(calloc_nid_postfix(capacity, 1));
if (!data) return queueError(BufferLibrary, AllocationFailure, -1);
if (buffer.length != 0) std::memcpy(data, buffer.data, buffer.length);
if (count != 0) std::memcpy(data + buffer.length, source, count);
clearBuffer(buffer);
free_nid_postfix(buffer.data);
buffer.data = data;
buffer.capacity = capacity;
} else if (count != 0) {
std::memmove(buffer.data + buffer.length, source, count);
}
buffer.length = length;
return size;
}
int APS5_VABI putsMemory(Bio* bio, const char* source) {
if (!source) throw std::invalid_argument("BIO_puts: null string");
const std::size_t size = std::strlen(source);
if (size > static_cast<std::size_t>(std::numeric_limits<int>::max())) NotImplemented_nid_no_patch(__func__);
return writeMemory(bio, source, static_cast<int>(size));
}
int APS5_VABI getsMemory(Bio* bio, char* destination, int size) {
auto& buffer = memoryBuffer(bio);
if (!destination || size == std::numeric_limits<int>::min()) throw std::invalid_argument("BIO: invalid line buffer");
bio->flags &= ~RetryFlags;
if (size <= 1 || buffer.length == 0) {
*destination = '\0';
return 0;
}
const auto limit = std::min<std::size_t>(size - 1, buffer.length);
const void* newline = std::memchr(buffer.data, '\n', limit);
const std::size_t count = newline ? static_cast<const char*>(newline) - buffer.data + 1 : limit;
const int result = readMemory(bio, destination, static_cast<int>(count));
destination[result] = '\0';
return result;
}
std::int64_t APS5_VABI controlMemory(Bio* bio, int command, std::int64_t argument, void* pointer) {
if (!bio) throw std::invalid_argument("BIO: null memory BIO");
switch (command) {
case 1: {
auto& buffer = memoryBuffer(bio);
if (buffer.data) {
if (bio->flags & ReadOnly) {
if (buffer.capacity < buffer.length) throw std::invalid_argument("BIO: invalid read-only buffer length");
buffer.data -= buffer.capacity - buffer.length;
buffer.length = buffer.capacity;
} else {
clearBuffer(buffer);
buffer.length = 0;
}
}
return 1;
}
case 2:
return memoryBuffer(bio).length == 0;
case 3:
if (pointer) *static_cast<char**>(pointer) = memoryBuffer(bio).data;
return static_cast<std::int64_t>(memoryBuffer(bio).length);
case 8:
return bio->shutdown;
case 9:
bio->shutdown = static_cast<int>(argument);
return 1;
case 10:
return static_cast<std::int64_t>(memoryBuffer(bio).length);
case 11:
case 12:
return 1;
case 13:
return 0;
case 114:
destroyMemory(bio);
bio->data = pointer;
bio->shutdown = static_cast<int>(argument);
return 1;
case 115:
if (pointer) std::memcpy(pointer, &bio->data, sizeof(bio->data));
return 1;
case 130:
bio->number = static_cast<int>(argument);
return 1;
default:
return 0;
}
}
const BioMethod MemoryMethod{0x401, "memory buffer", writeMemory, readMemory, putsMemory, getsMemory, controlMemory, createMemory, destroyMemory, nullptr};
} // namespace
extern "C" {
const LibreSsl::BioMethod* APS5_VABI BIO_s_mem_nid_postfix() {
return &MemoryMethod;
}
LibreSsl::Bio* APS5_VABI BIO_new_nid_postfix(const LibreSsl::BioMethod* method) {
if (!method) throw std::invalid_argument("BIO_new: null method");
auto* bio = static_cast<Bio*>(calloc_nid_postfix(1, sizeof(Bio)));
if (!bio) {
queueError(BioLibrary, AllocationFailure, 0);
return nullptr;
}
bio->method = method;
bio->shutdown = 1;
bio->references = 1;
try {
if (!method->create || method->create(bio)) return bio;
} catch (...) {
free_nid_postfix(bio);
throw;
}
free_nid_postfix(bio);
return nullptr;
}
LibreSsl::Bio* APS5_VABI BIO_new_mem_buf_nid_postfix(const void* source, int size) {
if (!source) {
queueError(BioLibrary, NullParameter, 0);
return nullptr;
}
auto* bio = BIO_new_nid_postfix(&MemoryMethod);
if (!bio) return nullptr;
auto& buffer = memoryBuffer(bio);
buffer.data = const_cast<char*>(static_cast<const char*>(source));
buffer.length = size < 0 ? std::strlen(buffer.data) : static_cast<std::size_t>(size);
buffer.capacity = buffer.length;
bio->flags |= ReadOnly;
bio->number = 0;
return bio;
}
int APS5_VABI BIO_free_nid_postfix(LibreSsl::Bio* bio) {
if (!bio) return 0;
if (bio->extraData) NotImplemented_nid_no_patch(__func__);
if (std::atomic_ref(bio->references).fetch_sub(1) > 1) return 1;
if (bio->callback) {
const int result = static_cast<int>(bio->callback(bio, 1, nullptr, 0, 0, 1));
if (result <= 0) return result;
}
if (bio->extraData) NotImplemented_nid_no_patch(__func__);
if (bio->method && bio->method->destroy) bio->method->destroy(bio);
free_nid_postfix(bio);
return 1;
}
std::int64_t APS5_VABI BIO_ctrl_nid_postfix(LibreSsl::Bio* bio, int command, std::int64_t argument, void* pointer) {
if (!bio) return 0;
if (!bio->method || !bio->method->control) return queueError(BioLibrary, UnsupportedMethod, -2);
const auto callback = bio->callback;
if (callback) {
const auto result = callback(bio, 6, static_cast<const char*>(pointer), command, argument, 1);
if (result <= 0) return result;
}
const auto result = bio->method->control(bio, command, argument, pointer);
return callback ? callback(bio, 0x86, static_cast<const char*>(pointer), command, argument, result) : result;
}
int LibreSslBioWrite_nid_no_patch(LibreSsl::Bio* bio, const void* source, int size) {
if (!bio || size < 0) return 0;
if (!bio->method || !bio->method->write) return queueError(BioLibrary, UnsupportedMethod, -2);
const auto callback = bio->callback;
if (callback) {
const int result = static_cast<int>(callback(bio, 3, static_cast<const char*>(source), size, 0, 1));
if (result <= 0) return result;
}
if (!bio->initialized) return queueError(BioLibrary, Uninitialized, -2);
const int result = bio->method->write(bio, static_cast<const char*>(source), size);
if (result > 0) bio->bytesWritten += static_cast<std::uint64_t>(result);
return callback ? static_cast<int>(callback(bio, 0x83, static_cast<const char*>(source), size, 0, result)) : result;
}
int APS5_VABI BIO_puts_nid_postfix(LibreSsl::Bio* bio, const char* source) {
if (!bio || !bio->method || !bio->method->puts) return queueError(BioLibrary, UnsupportedMethod, -2);
const auto callback = bio->callback;
if (callback) {
const int result = static_cast<int>(callback(bio, 4, source, 0, 0, 1));
if (result <= 0) return result;
}
if (!bio->initialized) return queueError(BioLibrary, Uninitialized, -2);
const int result = bio->method->puts(bio, source);
if (result > 0) bio->bytesWritten += static_cast<std::uint64_t>(result);
return callback ? static_cast<int>(callback(bio, 0x84, source, 0, 0, result)) : result;
}
}
+271
View File
@@ -0,0 +1,271 @@
#include "prx/libSceLibreSsl/libSceLibreSsl.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libSceLibreSsl/include/Cmac.hpp"
#include "prx/libSceLibreSsl/include/Error.hpp"
#include "prx/libc/include/General.hpp"
#include <algorithm>
#include <bit>
#include <cstring>
#include <stdexcept>
#include <mbedtls/aes.h>
struct LibreSslCmac {
LibreSslCipherContext cipherContext;
unsigned char firstSubkey[32];
unsigned char secondSubkey[32];
unsigned char previousBlock[32];
unsigned char lastBlock[32];
std::int32_t lastLength;
};
static_assert(sizeof(LibreSslCmac) == 304);
namespace {
void Cleanse(void* data, std::size_t length) {
auto* bytes = static_cast<volatile unsigned char*>(data);
while (length-- != 0) *bytes++ = 0;
}
int Error(int reason) {
LibreSslPutError_nid_no_patch(6, 0xfff, reason, __FILE__, __LINE__);
return 0;
}
void RequireContext(const LibreSslCmac* context) {
if (context == nullptr) throw std::invalid_argument("CMAC: null context");
}
int Cleanup(LibreSslCipherContext& context) {
if (context.engine != nullptr) NotImplemented_nid_no_patch(__func__);
if (context.cipher != nullptr) {
if (context.cipher->cleanup != nullptr && !context.cipher->cleanup(&context)) return 0;
if (context.cipherData != nullptr) Cleanse(context.cipherData, context.cipher->contextSize);
}
free_nid_postfix(context.cipherData);
Cleanse(&context, sizeof(context));
return 1;
}
int Control(LibreSslCipherContext& context, int command, int argument) {
if (context.cipher == nullptr) return Error(131);
if (context.cipher->control == nullptr) return Error(132);
const int result = context.cipher->control(&context, command, argument, nullptr);
return result == -1 ? Error(133) : result;
}
int InitializeCipher(LibreSslCipherContext& context, const LibreSslCipher* cipher, const unsigned char* key, const unsigned char* iv, void* engine) {
context.encrypt = 1;
if (context.engine != nullptr || (cipher != nullptr && engine != nullptr)) NotImplemented_nid_no_patch(__func__);
if (cipher != nullptr) {
if (cipher->contextSize < 0 || cipher->ivLength < 0) throw std::invalid_argument("CMAC: invalid cipher dimensions");
if (context.cipher != nullptr) {
const auto flags = context.flags;
Cleanup(context);
context.encrypt = 1;
context.flags = flags;
}
context.cipher = cipher;
context.cipherData = nullptr;
if (cipher->contextSize != 0) {
context.cipherData = calloc_nid_postfix(1, cipher->contextSize);
if (context.cipherData == nullptr) return Error(65);
}
context.keyLength = cipher->keyLength;
context.flags &= 1;
if ((cipher->flags & 0x40) != 0 && !Control(context, 0, 0)) return Error(134);
}
if (context.cipher == nullptr) return Error(131);
const auto& method = *context.cipher;
if (method.blockSize != 1 && method.blockSize != 8 && method.blockSize != 16) return Error(136);
const auto mode = method.flags & 0xf0007;
if ((context.flags & 1) == 0 && mode == 0x10002) return Error(170);
if ((method.flags & 0x10) == 0) {
if (mode == 2 || mode == 3 || mode == 4 || mode == 5) {
if (method.ivLength > 16) return Error(102);
if (mode != 2) context.position = 0;
if (mode == 5) {
if (iv != nullptr) std::memcpy(context.iv, iv, method.ivLength);
} else {
if (iv != nullptr) std::memcpy(context.originalIv, iv, method.ivLength);
std::memcpy(context.iv, context.originalIv, method.ivLength);
}
} else if (mode != 0 && mode != 1) {
return 0;
}
}
if (key != nullptr || (method.flags & 0x20) != 0) {
if (method.init == nullptr) NotImplemented_nid_no_patch(__func__);
if (!method.init(&context, key, iv, 1)) return 0;
}
context.bufferLength = 0;
context.finalUsed = 0;
context.blockMask = method.blockSize - 1;
return 1;
}
int SetKeyLength(LibreSslCipherContext& context, std::size_t length) {
const int narrowed = std::bit_cast<std::int32_t>(static_cast<std::uint32_t>(length));
if ((context.cipher->flags & 0x80) != 0) return Control(context, 1, narrowed);
if (context.keyLength == narrowed) return 1;
if (narrowed > 0 && (context.cipher->flags & 8) != 0) {
context.keyLength = narrowed;
return 1;
}
return Error(130);
}
int EncryptBlock(LibreSslCmac& context, unsigned char* output, const unsigned char* input) {
if (context.cipherContext.cipher->cipher == nullptr) NotImplemented_nid_no_patch(__func__);
return context.cipherContext.cipher->cipher(&context.cipherContext, output, input, context.cipherContext.cipher->blockSize);
}
void DoubleBlock(unsigned char* output, const unsigned char* input, int length) {
unsigned char carry = 0;
for (int index = length; index-- > 0;) {
const auto value = input[index];
output[index] = static_cast<unsigned char>((value << 1) | carry);
carry = value >> 7;
}
output[length - 1] ^= static_cast<unsigned char>((length == 16 ? 0x87 : 0x1b) & -static_cast<int>(carry));
}
int APS5_VABI AesInit(LibreSslCipherContext* context, const unsigned char* key, const unsigned char*, int encrypt) {
if (key == nullptr || context->cipherData == nullptr) throw std::invalid_argument("CMAC AES: missing key storage");
if (context->keyLength != 16 && context->keyLength != 24 && context->keyLength != 32) return Error(143);
auto* aes = static_cast<mbedtls_aes_context*>(context->cipherData);
mbedtls_aes_init(aes);
const int result = encrypt ? mbedtls_aes_setkey_enc(aes, key, context->keyLength * 8) : mbedtls_aes_setkey_dec(aes, key, context->keyLength * 8);
if (result != 0) throw std::runtime_error("CMAC AES: backend key setup failed");
return 1;
}
int APS5_VABI AesCipher(LibreSslCipherContext* context, unsigned char* output, const unsigned char* input, std::size_t length) {
if (context->cipherData == nullptr || (length != 0 && (output == nullptr || input == nullptr))) throw std::invalid_argument("CMAC AES: null data");
if (length == 0) return 1;
auto* aes = static_cast<mbedtls_aes_context*>(context->cipherData);
const int mode = context->encrypt ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT;
const auto complete = length - length % 16;
if (complete != 0 && mbedtls_aes_crypt_cbc(aes, mode, complete, context->iv, input, output) != 0)
throw std::runtime_error("CMAC AES: backend encryption failed");
const auto remaining = length - complete;
if (remaining == 0) return 1;
input += complete;
output += complete;
unsigned char block[16];
unsigned char nextIv[16];
if (context->encrypt) {
std::memcpy(block, context->iv, 16);
for (std::size_t index = 0; index < remaining; ++index) block[index] ^= input[index];
if (mbedtls_aes_crypt_ecb(aes, mode, block, output) != 0) throw std::runtime_error("CMAC AES: backend encryption failed");
std::memcpy(context->iv, output, 16);
} else {
std::memcpy(nextIv, input, 16);
if (mbedtls_aes_crypt_ecb(aes, mode, input, block) != 0) throw std::runtime_error("CMAC AES: backend decryption failed");
for (std::size_t index = 0; index < remaining; ++index) output[index] = block[index] ^ context->iv[index];
std::memcpy(context->iv, nextIv, 16);
}
Cleanse(block, sizeof(block));
Cleanse(nextIv, sizeof(nextIv));
return 1;
}
const LibreSslCipher aes128{419, 16, 16, 16, 0x5002, AesInit, AesCipher, nullptr, sizeof(mbedtls_aes_context), nullptr, nullptr, nullptr, nullptr};
const LibreSslCipher aes192{423, 16, 24, 16, 0x5002, AesInit, AesCipher, nullptr, sizeof(mbedtls_aes_context), nullptr, nullptr, nullptr, nullptr};
const LibreSslCipher aes256{427, 16, 32, 16, 0x5002, AesInit, AesCipher, nullptr, sizeof(mbedtls_aes_context), nullptr, nullptr, nullptr, nullptr};
}
extern "C" {
LibreSslCmac* APS5_VABI CMAC_CTX_new_nid_postfix() {
auto* context = static_cast<LibreSslCmac*>(malloc_nid_postfix(sizeof(LibreSslCmac)));
if (context != nullptr) {
context->cipherContext = {};
context->lastLength = -1;
}
return context;
}
void APS5_VABI CMAC_CTX_free_nid_postfix(LibreSslCmac* context) {
if (context == nullptr) return;
Cleanup(context->cipherContext);
Cleanse(context, sizeof(*context));
free_nid_postfix(context);
}
int APS5_VABI CMAC_Init_nid_postfix(LibreSslCmac* context, const void* key, std::size_t keyLength, const LibreSslCipher* cipher, void* engine) {
RequireContext(context);
const unsigned char zero[32]{};
if (key == nullptr && keyLength == 0 && cipher == nullptr && engine == nullptr) {
if (context->lastLength == -1) return 0;
if (!InitializeCipher(context->cipherContext, nullptr, nullptr, zero, nullptr)) return 0;
std::memset(context->previousBlock, 0, context->cipherContext.cipher->blockSize);
context->lastLength = 0;
return 1;
}
if (cipher != nullptr && !InitializeCipher(context->cipherContext, cipher, nullptr, nullptr, engine)) return 0;
if (key == nullptr) return 1;
if (context->cipherContext.cipher == nullptr) return 0;
if (!SetKeyLength(context->cipherContext, keyLength)) return 0;
if (!InitializeCipher(context->cipherContext, nullptr, static_cast<const unsigned char*>(key), zero, nullptr)) return 0;
if (!EncryptBlock(*context, context->previousBlock, zero)) return 0;
const auto blockSize = context->cipherContext.cipher->blockSize;
DoubleBlock(context->firstSubkey, context->previousBlock, blockSize);
DoubleBlock(context->secondSubkey, context->firstSubkey, blockSize);
Cleanse(context->previousBlock, blockSize);
if (!InitializeCipher(context->cipherContext, nullptr, nullptr, zero, nullptr)) return 0;
context->lastLength = 0;
return 1;
}
int APS5_VABI CMAC_Update_nid_postfix(LibreSslCmac* context, const void* data, std::size_t length) {
RequireContext(context);
if (context->lastLength == -1) return 0;
if (length == 0) return 1;
if (data == nullptr) throw std::invalid_argument("CMAC_Update: null input");
const auto* bytes = static_cast<const unsigned char*>(data);
const auto blockSize = context->cipherContext.cipher->blockSize;
if (context->lastLength != 0) {
const auto take = std::min(length, static_cast<std::size_t>(blockSize - context->lastLength));
std::memcpy(context->lastBlock + context->lastLength, bytes, take);
context->lastLength += static_cast<std::int32_t>(take);
bytes += take;
length -= take;
if (length == 0) return 1;
if (!EncryptBlock(*context, context->previousBlock, context->lastBlock)) return 0;
}
while (length > static_cast<std::size_t>(blockSize)) {
if (!EncryptBlock(*context, context->previousBlock, bytes)) return 0;
bytes += blockSize;
length -= blockSize;
}
std::memcpy(context->lastBlock, bytes, length);
context->lastLength = static_cast<std::int32_t>(length);
return 1;
}
int APS5_VABI CMAC_Final_nid_postfix(LibreSslCmac* context, unsigned char* output, std::size_t* length) {
RequireContext(context);
if (context->lastLength == -1) return 0;
if (length == nullptr) throw std::invalid_argument("CMAC_Final: null output length");
const auto blockSize = context->cipherContext.cipher->blockSize;
*length = blockSize;
if (output == nullptr) return 1;
const bool full = context->lastLength == blockSize;
if (!full) {
std::memset(context->lastBlock + context->lastLength, 0, blockSize - context->lastLength);
context->lastBlock[context->lastLength] = 0x80;
}
const auto* subkey = full ? context->firstSubkey : context->secondSubkey;
for (int index = 0; index < blockSize; ++index) output[index] = context->lastBlock[index] ^ subkey[index];
if (EncryptBlock(*context, output, output)) return 1;
Cleanse(output, blockSize);
return 0;
}
const LibreSslCipher* APS5_VABI EVP_aes_128_cbc_nid_postfix() { return &aes128; }
const LibreSslCipher* APS5_VABI EVP_aes_192_cbc_nid_postfix() { return &aes192; }
const LibreSslCipher* APS5_VABI EVP_aes_256_cbc_nid_postfix() { return &aes256; }
}
+564
View File
@@ -0,0 +1,564 @@
#include "prx/libSceLibreSsl/libSceLibreSsl.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libSceLibreSsl/ErrorQueue.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include <algorithm>
#include <array>
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <limits>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
#ifdef ANYPS5_BIO_DYNAMIC
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#endif
using LibreSsl::Bio;
using LibreSsl::BioBuffer;
using LibreSsl::BioMethod;
#ifdef ANYPS5_BIO_DYNAMIC
namespace {
decltype(&BIO_s_mem_nid_postfix) bioMemoryMethod;
decltype(&BIO_new_nid_postfix) bioNew;
decltype(&BIO_new_mem_buf_nid_postfix) bioNewBuffer;
decltype(&BIO_free_nid_postfix) bioFree;
decltype(&BIO_ctrl_nid_postfix) bioControl;
decltype(&BIO_write_nid_postfix) bioWrite;
decltype(&BIO_puts_nid_postfix) bioPuts;
decltype(&calloc_nid_postfix) guestCalloc;
decltype(&free_nid_postfix) guestFree;
decltype(&ApplicationHeapRegister_nid_no_patch) registerHeap;
decltype(&ERR_clear_error_nid_postfix) clearError;
decltype(&ERR_peek_error_nid_postfix) peekError;
decltype(&ERR_get_error_nid_postfix) getError;
void* Load(const std::filesystem::path& path) {
#ifdef _WIN32
auto* module = LoadLibraryExW(path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
#else
auto* module = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
#endif
if (!module) throw std::runtime_error("failed to load patched BIO dependency: " + path.string());
return module;
}
template<class TFunction>
void Resolve(TFunction& function, void* module, const char* name) {
#ifdef _WIN32
function = reinterpret_cast<TFunction>(GetProcAddress(static_cast<HMODULE>(module), name));
#else
function = reinterpret_cast<TFunction>(dlsym(module, name));
#endif
if (!function) throw std::runtime_error(std::string("missing patched BIO dependency: ") + name);
}
void InitializePatched(const std::filesystem::path& directory) {
void* libc = Load(directory / "libc.prx");
void* bio = Load(directory / "libSceLibreSsl.prx");
Resolve(bioMemoryMethod, bio, "R86sXhW699g");
Resolve(bioNew, bio, "ARlY6zhSacE");
Resolve(bioNewBuffer, bio, "GMzjS9jvyDQ");
Resolve(bioFree, bio, "lpJ3zhgftCg");
Resolve(bioControl, bio, "RHpq9hQfTj8");
Resolve(bioWrite, bio, "nH7XM+HhXWc");
Resolve(bioPuts, bio, "eVcWmVlimPA");
Resolve(guestCalloc, libc, "2X5agFjKxMc");
Resolve(guestFree, libc, "tIhsqj0qsFE");
Resolve(registerHeap, libc, "ApplicationHeapRegister_nid_no_patch");
Resolve(clearError, bio, "sieQ--PINWg");
Resolve(peekError, bio, "Aytjey6FPTU");
Resolve(getError, bio, "DJg+qWX1rwY");
}
} // namespace
#define BIO_s_mem_nid_postfix bioMemoryMethod
#define BIO_new_nid_postfix bioNew
#define BIO_new_mem_buf_nid_postfix bioNewBuffer
#define BIO_free_nid_postfix bioFree
#define BIO_ctrl_nid_postfix bioControl
#define BIO_write_nid_postfix bioWrite
#define BIO_puts_nid_postfix bioPuts
#define calloc_nid_postfix guestCalloc
#define free_nid_postfix guestFree
#define ApplicationHeapRegister_nid_no_patch registerHeap
#define ERR_clear_error_nid_postfix clearError
#define ERR_peek_error_nid_postfix peekError
#define ERR_get_error_nid_postfix getError
#endif
namespace {
void Require(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "%s\n", message);
std::abort();
}
}
int allocationsUntilFailure = -1;
std::atomic<int> allocations{0};
void* wipePointer = nullptr;
std::size_t wipeSize = 0;
void* APS5_VABI Allocate(std::size_t) { throw std::logic_error("unexpected nonzeroed BIO allocation"); }
void* APS5_VABI Reallocate(void*, std::size_t) { throw std::logic_error("unexpected BIO reallocation"); }
void* APS5_VABI Align(std::size_t, std::size_t) { throw std::logic_error("unexpected BIO aligned allocation"); }
void* APS5_VABI Realign(void*, std::size_t, std::size_t) { throw std::logic_error("unexpected BIO realignment"); }
int APS5_VABI PosixAlign(void**, std::size_t, std::size_t) { throw std::logic_error("unexpected BIO aligned allocation"); }
void* APS5_VABI Calloc(std::size_t count, std::size_t size) {
if (allocationsUntilFailure == 0) return nullptr;
if (allocationsUntilFailure > 0) --allocationsUntilFailure;
void* result = std::calloc(count, size);
if (result) ++allocations;
return result;
}
void APS5_VABI Free(void* pointer) {
if (pointer && pointer == wipePointer) {
for (std::size_t i = 0; i < wipeSize; ++i) Require(static_cast<unsigned char*>(pointer)[i] == 0, "freed buffer was not wiped");
wipePointer = nullptr;
}
if (pointer) --allocations;
std::free(pointer);
}
void InitializeHeap() {
const std::array<void*, 10> api{
reinterpret_cast<void*>(Allocate), reinterpret_cast<void*>(Free), reinterpret_cast<void*>(Calloc),
reinterpret_cast<void*>(Reallocate), reinterpret_cast<void*>(Align), reinterpret_cast<void*>(Realign),
reinterpret_cast<void*>(PosixAlign)};
ApplicationHeapRegister_nid_no_patch(api.data());
}
template<class TFunction>
void RequireUnsupported(TFunction function) {
bool threw = false;
try {
function();
} catch (const std::runtime_error&) {
threw = true;
}
Require(threw, "unsupported BIO path did not throw");
}
void RequireError(std::uint64_t expected) {
Require(ERR_peek_error_nid_postfix() == expected, "BIO error code or ordering");
Require(ERR_get_error_nid_postfix() == expected, "consume BIO error");
Require(ERR_get_error_nid_postfix() == 0, "unexpected extra BIO error");
}
BioBuffer* Buffer(Bio* bio) {
BioBuffer* buffer = nullptr;
Require(BIO_ctrl_nid_postfix(bio, 115, 0, &buffer) == 1 && buffer, "get buffer");
return buffer;
}
void RequireData(Bio* bio, const std::string& expected) {
char* bytes = nullptr;
Require(BIO_ctrl_nid_postfix(bio, 3, 0, &bytes) == static_cast<std::int64_t>(expected.size()), "buffer data length");
Require(expected.empty() || (bytes && std::memcmp(bytes, expected.data(), expected.size()) == 0), "buffer data bytes");
Require(BIO_ctrl_nid_postfix(bio, 10, 0, nullptr) == static_cast<std::int64_t>(expected.size()), "pending bytes");
Require(BIO_ctrl_nid_postfix(bio, 2, 0, nullptr) == expected.empty(), "EOF flag");
}
void TestWritable() {
const auto* method = BIO_s_mem_nid_postfix();
Require(method && method == BIO_s_mem_nid_postfix(), "stable method pointer");
Require(method->type == 0x401 && std::strcmp(method->name, "memory buffer") == 0, "method identity");
Require(method->write && method->read && method->puts && method->gets && method->control && method->create && method->destroy, "method callbacks");
Require(method->callbackControl == nullptr, "no callback control method");
Bio* bio = BIO_new_nid_postfix(method);
Require(bio && bio->method == method && bio->initialized == 1 && bio->references == 1, "new BIO state");
Require(!bio->callback && !bio->callbackArgument && !bio->flags && !bio->retryReason && !bio->next && !bio->previous && !bio->extraData, "new BIO zero fields");
Require(bio->number == -1 && bio->bytesRead == 0 && bio->bytesWritten == 0, "new BIO counters");
Require(BIO_write_nid_postfix(bio, nullptr, 7) == -1, "null write reaches memory method");
RequireError(0x20fff073);
Require(BIO_write_nid_postfix(bio, "x", 0) == 0, "zero-length owned write");
RequireError(0);
RequireData(bio, "");
auto* buffer = Buffer(bio);
Require(buffer->capacity == 0 && buffer->data == nullptr, "new buffer storage");
char output[16]{};
Require(method->read(bio, output, sizeof(output)) == -1 && (bio->flags & 0xf) == 9, "empty writable BIO retry");
Require(BIO_write_nid_postfix(bio, "abc", 3) == 3 && (bio->flags & 0xf) == 0, "write clears retry flags");
RequireData(bio, "abc");
Require(buffer->capacity == 8 && buffer->data[3] == 0, "first buffer expansion");
std::memset(buffer->data + buffer->length, 0xa5, buffer->capacity - buffer->length);
wipePointer = buffer->data;
wipeSize = buffer->capacity;
Require(BIO_write_nid_postfix(bio, "0123456789", 10) == 10, "second write");
Require(wipePointer == nullptr, "growth frees the wiped allocation");
Require(buffer->capacity == 20 && buffer->data[13] == 0, "second buffer expansion");
RequireData(bio, "abc0123456789");
Require(method->read(bio, output, 4) == 4 && std::memcmp(output, "abc0", 4) == 0, "partial memory read");
RequireData(bio, "123456789");
Require(BIO_puts_nid_postfix(bio, "xy") == 2 && bio->bytesWritten == 15, "puts and byte accounting");
RequireData(bio, "123456789xy");
Require(method->read(bio, nullptr, 3) == 3, "null raw read reports available count");
RequireData(bio, "123456789xy");
Require(method->read(bio, output, -3) == -3, "negative raw read");
Require(BIO_write_nid_postfix(bio, nullptr, 5) == -1, "reject null public source");
RequireError(0x20fff073);
RequireData(bio, "123456789xy");
Require(BIO_ctrl_nid_postfix(bio, 11, 0, nullptr) == 1 && BIO_ctrl_nid_postfix(bio, 12, 0, nullptr) == 1, "flush and duplicate controls");
Require(BIO_ctrl_nid_postfix(bio, 13, 0, nullptr) == 0, "no pending output");
Require(BIO_ctrl_nid_postfix(bio, 6, 0, nullptr) == 0 && BIO_ctrl_nid_postfix(bio, 7, 0, nullptr) == 0, "push and pop controls");
Require(BIO_ctrl_nid_postfix(bio, 0x7fffffff, 0, nullptr) == 0, "unknown memory control");
Require(BIO_ctrl_nid_postfix(bio, 1, 0, nullptr) == 1, "reset writable BIO");
RequireData(bio, "");
for (std::size_t i = 0; i < buffer->capacity; ++i) Require(buffer->data[i] == 0, "reset wipes full allocation");
Require(BIO_ctrl_nid_postfix(bio, 130, 7, nullptr) == 1, "set EOF return");
Require(method->read(bio, output, 1) == 7 && (bio->flags & 0xf) == 9, "configured EOF return and retry");
Require(BIO_ctrl_nid_postfix(bio, 130, 0, nullptr) == 1, "set terminal EOF");
Require(method->read(bio, output, 1) == 0 && (bio->flags & 0xf) == 0, "terminal EOF clears retry");
Require(BIO_puts_nid_postfix(bio, "secret") == 6, "write before destruction");
wipePointer = buffer->data;
wipeSize = buffer->capacity;
Require(BIO_free_nid_postfix(bio) == 1, "free writable BIO");
Require(wipePointer == nullptr, "destruction frees wiped storage");
}
void TestReadOnlyAndLines() {
const std::array<char, 8> source{'a', '\0', 'b', '\n', 'c', 'd', '\n', 'e'};
Bio* bio = BIO_new_mem_buf_nid_postfix(source.data(), source.size());
Require(bio && (bio->flags & 0x200) && bio->number == 0, "read-only BIO state");
auto* buffer = Buffer(bio);
Require(buffer->data == source.data() && buffer->capacity == source.size(), "read-only buffer is borrowed");
char output[16];
std::memset(output, 0x55, sizeof(output));
Require(bio->method->gets(bio, output, sizeof(output)) == 4 && std::memcmp(output, source.data(), 4) == 0 && output[4] == 0, "line includes embedded NUL and newline");
Require(buffer->data == source.data() + 4 && buffer->length == 4, "borrowed buffer advances");
Require(bio->method->gets(bio, output, 3) == 2 && std::strcmp(output, "cd") == 0, "line length bound");
Require(bio->method->gets(bio, output, 1) == 0 && output[0] == 0 && buffer->length == 2, "one-byte line buffer");
Require(bio->method->gets(bio, output, sizeof(output)) == 1 && std::strcmp(output, "\n") == 0, "newline on next read");
Require(bio->method->read(bio, output, sizeof(output)) == 1 && output[0] == 'e', "last read-only byte");
Require(bio->method->read(bio, output, 1) == 0 && (bio->flags & 0xf) == 0, "read-only EOF");
Require(BIO_ctrl_nid_postfix(bio, 1, 0, nullptr) == 1, "read-only reset");
Require(buffer->data == source.data() && buffer->length == source.size(), "read-only reset restores original range");
Require(BIO_write_nid_postfix(bio, "x", 1) == -1, "reject read-only write");
RequireError(0x20fff07e);
Require(buffer->data == source.data() && buffer->length == source.size(), "rejected write preserves input");
Require(BIO_free_nid_postfix(bio) == 1 && source[0] == 'a' && source[7] == 'e', "borrowed bytes survive free");
for (const int size : {-1, -99}) {
bio = BIO_new_mem_buf_nid_postfix("ab\0cd", size);
RequireData(bio, "ab");
Require(BIO_free_nid_postfix(bio) == 1, "free string BIO");
}
bio = BIO_new_mem_buf_nid_postfix("", 0);
RequireData(bio, "");
output[0] = 'x';
Require(bio->method->gets(bio, output, 8) == 0 && output[0] == 0, "empty gets");
Require(BIO_free_nid_postfix(bio) == 1, "free empty BIO");
}
void TestBufferOwnership() {
Bio* bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix());
Require(BIO_puts_nid_postfix(bio, "retained") == 8, "write retained buffer");
auto* retained = Buffer(bio);
Require(BIO_ctrl_nid_postfix(bio, 8, 0, nullptr) == 1, "default close flag");
Require(BIO_ctrl_nid_postfix(bio, 9, 0, nullptr) == 1 && BIO_ctrl_nid_postfix(bio, 8, 0, nullptr) == 0, "disable close");
Require(BIO_free_nid_postfix(bio) == 1, "free BIO without its buffer");
Require(retained->length == 8 && std::memcmp(retained->data, "retained", 8) == 0, "detached storage remains valid");
free_nid_postfix(retained->data);
free_nid_postfix(retained);
bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix());
Require(BIO_puts_nid_postfix(bio, "old") == 3, "write old buffer");
auto* replacement = static_cast<BioBuffer*>(calloc_nid_postfix(1, sizeof(BioBuffer)));
replacement->data = static_cast<char*>(calloc_nid_postfix(8, 1));
replacement->length = 3;
replacement->capacity = 8;
std::memcpy(replacement->data, "new", 3);
Require(BIO_ctrl_nid_postfix(bio, 114, 0, replacement) == 1 && Buffer(bio) == replacement, "replace buffer");
RequireData(bio, "new");
Require(BIO_write_nid_postfix(bio, "!", 1) == 1, "write replacement buffer");
RequireData(bio, "new!");
Require(BIO_free_nid_postfix(bio) == 1, "free BIO retaining replacement");
Require(replacement->length == 4 && std::memcmp(replacement->data, "new!", 4) == 0, "caller owns replacement");
free_nid_postfix(replacement->data);
free_nid_postfix(replacement);
bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix());
Require(BIO_ctrl_nid_postfix(bio, 114, 1, nullptr) == 1, "detach without replacement");
Require(BIO_ctrl_nid_postfix(bio, 8, 0, nullptr) == 1, "close control without storage");
Require(BIO_ctrl_nid_postfix(bio, 11, 0, nullptr) == 1, "flush without storage");
void* missing = bio;
Require(BIO_ctrl_nid_postfix(bio, 115, 0, &missing) == 1 && missing == nullptr, "query detached storage");
Require(BIO_free_nid_postfix(bio) == 1, "free detached BIO");
}
void TestInterleavedTransfers() {
Bio* bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix());
std::string pending;
std::uint32_t sequence = 0x346de821;
std::uint64_t written = 0;
for (int step = 0; step < 2000; ++step) {
sequence = sequence * 1664525 + 1013904223;
const int count = static_cast<int>((sequence >> 16) % 65);
if ((sequence & 3) != 0) {
std::array<char, 65> input{};
for (int i = 0; i < count; ++i) input[i] = static_cast<char>(sequence + i * 37);
Require(BIO_write_nid_postfix(bio, input.data(), count) == count, "interleaved write count");
pending.append(input.data(), count);
written += count;
} else {
std::array<char, 66> output;
output.fill(static_cast<char>(0xa5));
const int expected = static_cast<int>(std::min<std::size_t>(count, pending.size()));
const int result = bio->method->read(bio, output.data(), count);
Require(result == (pending.empty() ? -1 : expected), "interleaved read count");
if (expected != 0) Require(std::memcmp(output.data(), pending.data(), expected) == 0, "interleaved read bytes");
Require(output[expected] == static_cast<char>(0xa5), "read preserves destination tail");
pending.erase(0, expected);
}
RequireData(bio, pending);
Require(bio->bytesWritten == written && bio->bytesRead == 0, "interleaved transfer counters");
if (step % 127 == 0) {
Require(BIO_ctrl_nid_postfix(bio, 1, 0, nullptr) == 1, "interleaved reset");
pending.clear();
}
}
Require(BIO_free_nid_postfix(bio) == 1, "free interleaved BIO");
}
struct WriteEvent {
int operation;
const char* source;
int size;
std::int64_t result;
int flags;
std::uint64_t bytesWritten;
};
struct WriteTrace {
std::vector<WriteEvent> events;
int veto = 0;
int postAdjustment = 0;
};
std::int64_t APS5_VABI TraceWrite(Bio* bio, int operation, const char* source, int size, std::int64_t, std::int64_t result) {
auto& trace = *reinterpret_cast<WriteTrace*>(bio->callbackArgument);
trace.events.push_back({operation, source, size, result, bio->flags, bio->bytesWritten});
if (operation == 3 && trace.veto) return trace.veto;
return (operation & 0x80) ? result + trace.postAdjustment : result;
}
void TestWriteBoundaries() {
const char source[] = "abc";
for (const bool readOnly : {false, true}) {
Bio* bio = readOnly ? BIO_new_mem_buf_nid_postfix(source, 3) : BIO_new_nid_postfix(BIO_s_mem_nid_postfix());
WriteTrace trace;
bio->callback = TraceWrite;
bio->callbackArgument = reinterpret_cast<char*>(&trace);
for (const int size : {0, 1}) {
bio->flags |= 0x4009;
const int flags = bio->flags;
trace.events.clear();
Require(BIO_write_nid_postfix(bio, nullptr, size) == -1, "null source dispatch result");
RequireError(0x20fff073);
Require(trace.events.size() == 2, "null source has pre/post callbacks");
Require(trace.events[0].operation == 3 && trace.events[1].operation == 0x83, "null source callback operations");
Require(trace.events[0].source == nullptr && trace.events[1].source == nullptr &&
trace.events[0].size == size && trace.events[1].size == size, "null source callback arguments");
Require(trace.events[0].result == 1 && trace.events[1].result == -1, "null source callback results");
Require(trace.events[0].flags == flags && trace.events[1].flags == flags && bio->flags == flags, "null source preserves retry flags");
Require(trace.events[0].bytesWritten == 0 && trace.events[1].bytesWritten == 0 && bio->bytesWritten == 0, "failed write does not count bytes");
}
bio->flags |= 0x4009;
const int flags = bio->flags;
trace.events.clear();
Require(BIO_write_nid_postfix(bio, source, 0) == (readOnly ? -1 : 0), "zero-length method result");
RequireError(readOnly ? 0x20fff07e : 0);
Require(trace.events.size() == 2 && trace.events[0].operation == 3 && trace.events[1].operation == 0x83, "zero-length write callback operations");
Require(trace.events[0].source == source && trace.events[1].source == source &&
trace.events[0].size == 0 && trace.events[1].size == 0, "zero-length callback arguments");
Require(trace.events[0].result == 1 && trace.events[1].result == (readOnly ? -1 : 0), "zero-length callback results");
Require(trace.events[0].flags == flags && trace.events[1].flags == (readOnly ? flags : flags & ~0xf), "retry flags change during the method");
Require(bio->flags == (readOnly ? flags : flags & ~0xf) && bio->bytesWritten == 0, "zero-length retry flags and byte count");
RequireData(bio, readOnly ? "abc" : "");
trace.events.clear();
trace.veto = -7;
Require(BIO_write_nid_postfix(bio, nullptr, 0) == -7 && trace.events.size() == 1, "null write callback veto prevents method");
RequireError(0);
trace.veto = 0;
trace.postAdjustment = 5;
trace.events.clear();
Require(BIO_write_nid_postfix(bio, nullptr, 0) == 4 && bio->bytesWritten == 0, "post callback can override failure without counting bytes");
RequireError(0x20fff073);
bio->callback = nullptr;
Require(BIO_free_nid_postfix(bio) == 1, "free boundary BIO");
}
Bio* bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix());
WriteTrace trace;
bio->callback = TraceWrite;
bio->callbackArgument = reinterpret_cast<char*>(&trace);
const auto* method = bio->method;
bio->method = nullptr;
Require(BIO_write_nid_postfix(bio, nullptr, 0) == -2 && trace.events.empty(), "method validation precedes callback");
RequireError(0x20fff079);
bio->method = method;
bio->initialized = 0;
Require(BIO_write_nid_postfix(bio, nullptr, 0) == -2 && trace.events.size() == 1 && trace.events[0].operation == 3, "initialization validation follows pre-callback");
RequireError(0x20fff078);
bio->initialized = 1;
bio->callback = nullptr;
Require(BIO_free_nid_postfix(bio) == 1, "free dispatch-order BIO");
}
struct CallbackState {
std::vector<int> events;
std::int64_t argument = 0;
int veto = 0;
};
std::int64_t APS5_VABI Callback(Bio* bio, int operation, const char*, int, std::int64_t argument, std::int64_t result) {
auto& state = *reinterpret_cast<CallbackState*>(bio->callbackArgument);
state.events.push_back(operation);
state.argument = argument;
if (state.veto && !(operation & 0x80)) return state.veto;
return (operation & 0x80) ? result + 2 : 1;
}
int APS5_VABI CreateCustom(Bio* bio) {
bio->initialized = 1;
return 1;
}
int APS5_VABI WriteCustom(Bio*, const char*, int count) {
return count - 1;
}
std::int64_t APS5_VABI ControlCustom(Bio*, int command, std::int64_t argument, void*) {
return argument + command;
}
int APS5_VABI RejectCreate(Bio*) {
return 0;
}
void TestDispatchAndReferences() {
Bio* bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix());
CallbackState state;
bio->callback = Callback;
bio->callbackArgument = reinterpret_cast<char*>(&state);
Require(BIO_write_nid_postfix(bio, "abc", 3) == 5 && bio->bytesWritten == 3, "callback changes result after accounting");
Require(state.events == std::vector<int>{3, 0x83}, "write callback ordering");
state.events.clear();
Require(BIO_puts_nid_postfix(bio, "d") == 3 && state.events == std::vector<int>{4, 0x84}, "puts callbacks");
state.events.clear();
Require(BIO_ctrl_nid_postfix(bio, 10, 0x1234567887654321LL, nullptr) == 6, "control callback result");
Require(state.argument == 0x1234567887654321LL && state.events == std::vector<int>{6, 0x86}, "64-bit control argument");
state.events.clear();
state.veto = -7;
Require(BIO_write_nid_postfix(bio, "x", 1) == -7 && state.events == std::vector<int>{3}, "write callback veto");
Require(bio->bytesWritten == 4 && static_cast<BioBuffer*>(bio->data)->length == 4, "veto prevents transfer");
state.events.clear();
Require(BIO_free_nid_postfix(bio) == -7 && state.events == std::vector<int>{1} && bio->references == 0, "free callback veto");
bio->callback = nullptr;
Require(BIO_free_nid_postfix(bio) == 1, "free after veto");
BioMethod custom{0x480, "test", WriteCustom, nullptr, nullptr, nullptr, ControlCustom, CreateCustom, nullptr, nullptr};
bio = BIO_new_nid_postfix(&custom);
Require(bio && bio->method == &custom, "title-defined method");
Require(BIO_write_nid_postfix(bio, "abc", 3) == 2 && bio->bytesWritten == 2, "title-defined writer");
Require(BIO_write_nid_postfix(bio, nullptr, 3) == 2 && bio->bytesWritten == 4, "custom method receives null source");
Require(BIO_write_nid_postfix(bio, "", 0) == -1 && bio->bytesWritten == 4, "custom method receives zero length");
RequireError(0);
Require(BIO_ctrl_nid_postfix(bio, 17, 0x1234567800000000LL, nullptr) == 0x1234567800000011LL, "64-bit method result");
constexpr int referenceCount = 8;
std::atomic_ref(bio->references).store(referenceCount + 1);
std::array<std::thread, referenceCount> threads;
for (auto& thread : threads) thread = std::thread([bio] { Require(BIO_free_nid_postfix(bio) == 1, "release shared reference"); });
for (auto& thread : threads) thread.join();
Require(bio->references == 1 && BIO_free_nid_postfix(bio) == 1, "last reference release");
custom.create = RejectCreate;
Require(BIO_new_nid_postfix(&custom) == nullptr, "custom create can reject allocation");
}
void TestUnsupported() {
Require(BIO_free_nid_postfix(nullptr) == 0, "free null");
Require(BIO_ctrl_nid_postfix(nullptr, 1, 0, nullptr) == 0, "control null");
Require(BIO_write_nid_postfix(nullptr, "a", 1) == 0, "write null BIO");
Require(BIO_new_mem_buf_nid_postfix(nullptr, 0) == nullptr, "reject null borrowed input");
RequireError(0x20fff073);
Require(BIO_puts_nid_postfix(nullptr, "a") == -2, "reject puts without a method");
RequireError(0x20fff079);
Bio* bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix());
bio->initialized = 0;
Require(BIO_write_nid_postfix(bio, "a", 1) == -2, "reject uninitialized write");
RequireError(0x20fff078);
bio->initialized = 1;
const BioMethod* method = bio->method;
bio->method = nullptr;
Require(BIO_ctrl_nid_postfix(bio, 1, 0, nullptr) == -2, "reject control without a method");
RequireError(0x20fff079);
Require(BIO_write_nid_postfix(bio, "a", 1) == -2, "reject write without a method");
RequireError(0x20fff079);
bio->method = method;
auto* buffer = Buffer(bio);
buffer->length = 0x5ffffffd;
Require(BIO_write_nid_postfix(bio, "a", 1) == -1, "reject oversized growth");
RequireError(0x07fff041);
buffer->length = 0;
Require(BIO_free_nid_postfix(bio) == 1, "free after unsupported cases");
for (const int failAfter : {0, 1}) {
allocationsUntilFailure = failAfter;
Require(BIO_new_nid_postfix(BIO_s_mem_nid_postfix()) == nullptr, "failed BIO allocation");
RequireError(failAfter == 0 ? 0x20fff041 : 0x07fff041);
Require(allocations == 0, "failed creation leaked guest heap storage");
}
allocationsUntilFailure = -1;
bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix());
Require(BIO_puts_nid_postfix(bio, "abc") == 3, "write before growth failure");
allocationsUntilFailure = 0;
Require(BIO_puts_nid_postfix(bio, "0123456789") == -1, "failed buffer expansion");
RequireError(0x07fff041);
RequireData(bio, "abc");
allocationsUntilFailure = -1;
Require(BIO_free_nid_postfix(bio) == 1, "free after growth failure");
bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix());
bio->initialized = 0;
Require(BIO_puts_nid_postfix(bio, "a") == -2, "reject uninitialized puts");
RequireError(0x20fff078);
bio->initialized = 1;
Require(bio->method->write(bio, nullptr, 1) == -1, "reject null raw write source");
RequireError(0x20fff073);
Require(BIO_free_nid_postfix(bio) == 1, "free after method errors");
}
} // namespace
int main(int argc, char** argv) {
#ifdef ANYPS5_BIO_DYNAMIC
Require(argc == 2, "patched BIO test needs the libraries directory");
InitializePatched(std::filesystem::absolute(argv[1]));
#else
static_cast<void>(argc);
static_cast<void>(argv);
#endif
InitializeHeap();
ERR_clear_error_nid_postfix();
TestWritable();
TestReadOnlyAndLines();
TestBufferOwnership();
TestInterleavedTransfers();
TestWriteBoundaries();
TestDispatchAndReferences();
TestUnsupported();
Require(allocations == 0, "BIO tests leaked guest heap storage");
Require(ERR_get_error_nid_postfix() == 0, "BIO tests left an unexpected error");
}
+204
View File
@@ -0,0 +1,204 @@
#include "prx/libSceLibreSsl/libSceLibreSsl.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <source_location>
#include <string>
using Bn = Bignum;
using BnCtx = BignumContext;
static_assert(sizeof(Bn) == 24);
static_assert(offsetof(Bn, top) == 8);
static_assert(offsetof(Bn, neg) == 16);
static void Require(bool value, int line = __builtin_LINE()) {
if (value) return;
std::fprintf(stderr, "check failed at line %d\n", line);
std::abort();
}
static Bn* FromHex(const char* text) {
Bn* value = nullptr;
Require(BN_hex2bn_nid_postfix(&value, text) > 0 && value);
return value;
}
static std::string Hex(const Bn* value) {
unsigned char bytes[64];
const int length = BN_bn2bin_nid_postfix(value, bytes);
Require(length >= 0 && length <= 64);
static constexpr char digits[] = "0123456789ABCDEF";
std::string text;
for (int i = 0; i < length; i++) {
text += digits[bytes[i] >> 4];
text += digits[bytes[i] & 15];
}
while (text.size() > 1 && text[0] == '0') text.erase(0, 1);
if (text.empty()) text = "0";
return value->neg ? "-" + text : text;
}
static void InitializeHeap() {
void* const api[10] {};
ApplicationHeapRegister_nid_no_patch(api);
}
int main() {
InitializeHeap();
Bn* fresh = BN_new_nid_postfix();
Require(fresh && fresh->top == 0 && fresh->neg == 0 && BN_num_bits_nid_postfix(fresh) == 0);
BN_free_nid_postfix(fresh);
BN_free_nid_postfix(nullptr);
BnCtx* ctx = BN_CTX_new_nid_postfix();
Require(ctx);
BN_CTX_start_nid_postfix(ctx);
BN_CTX_end_nid_postfix(ctx);
BN_CTX_free_nid_postfix(ctx);
BN_CTX_free_nid_postfix(nullptr);
ctx = BN_CTX_new_nid_postfix();
Require(ctx);
{
Bn* a = FromHex("FFFFFFFFFFFFFFFF");
Bn* b = FromHex("FFFFFFFFFFFFFFFF");
Bn* r = BN_new_nid_postfix();
Require(BN_mul_nid_postfix(r, a, b, ctx) == 1);
Require(Hex(r) == "FFFFFFFFFFFFFFFE0000000000000001");
Require(r->top == 2 && r->neg == 0);
BN_free_nid_postfix(a);
BN_free_nid_postfix(b);
BN_free_nid_postfix(r);
}
{
Bn* a = FromHex("0123456789ABCDEFFEDCBA98765432100F1E2D3C4B5A6978");
Bn* b = FromHex("00FF00FF00FF00FF00FF00FF00FF00FF00FF");
Bn* r = BN_new_nid_postfix();
Require(BN_mul_nid_postfix(r, a, b, ctx) == 1);
Require(Hex(r) == "12223444566678876665444322210000F0F1CFC09E8F6D55CB34DD56FF79219B43BB52CA61D970E88");
Require(BN_mul_nid_postfix(a, a, a, ctx) == 1);
Require(Hex(a) == "14B66DC33F6ACDEE9D609EBE253A6424AD884E9A04269A9706601BB096443363E2CE44634913DF9C3B9FAA3D3A840");
Bn* c = FromHex("-3");
Bn* d = FromHex("5");
Require(BN_mul_nid_postfix(d, c, d, ctx) == 1 && Hex(d) == "-F" && d->neg == 1);
Require(BN_mul_nid_postfix(c, c, c, ctx) == 1 && Hex(c) == "9" && c->neg == 0);
Bn* zero = FromHex("0");
Require(BN_mul_nid_postfix(d, d, zero, ctx) == 1 && d->top == 0 && d->neg == 0);
BN_free_nid_postfix(a);
BN_free_nid_postfix(b);
BN_free_nid_postfix(c);
BN_free_nid_postfix(d);
BN_free_nid_postfix(r);
BN_free_nid_postfix(zero);
}
{
const unsigned char bytes[] = {0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
Bn* value = BN_bin2bn_nid_postfix(bytes, sizeof(bytes), nullptr);
Require(value && value->neg == 0 && value->top == 2);
Require(Hex(value) == "10203040506070809");
unsigned char out[16];
std::memset(out, 0xEE, sizeof(out));
Require(BN_bn2bin_nid_postfix(value, out) == 9);
Require(std::memcmp(out, bytes + 2, 9) == 0 && out[9] == 0xEE);
Require(BN_bin2bn_nid_postfix(bytes, 4, value) == value && Hex(value) == "102" && value->top == 1);
const unsigned char zeros[] = {0, 0, 0};
Require(BN_bin2bn_nid_postfix(zeros, 3, value) == value && value->top == 0);
std::memset(out, 0xEE, sizeof(out));
Require(BN_bn2bin_nid_postfix(value, out) == 0 && out[0] == 0xEE);
Require(BN_bin2bn_nid_postfix(bytes, 0, value) == value && value->top == 0);
Require(BN_bin2bn_nid_postfix(bytes, -1, value) == nullptr);
BN_free_nid_postfix(value);
}
{
Bn* value = BN_new_nid_postfix();
Require(BN_num_bits_nid_postfix(value) == 0);
Require(BN_set_word_nid_postfix(value, 1) == 1 && BN_num_bits_nid_postfix(value) == 1);
Require(BN_set_word_nid_postfix(value, 0x100) == 1 && BN_num_bits_nid_postfix(value) == 9);
Bn* big = FromHex("10000000000000000");
Require(BN_num_bits_nid_postfix(big) == 65 && big->top == 2);
Require(BN_set_word_nid_postfix(big, 0) == 1 && BN_num_bits_nid_postfix(big) == 0 && big->top == 0 && big->neg == 0);
Require(BN_set_word_nid_postfix(value, 0xFFFFFFFFFFFFFFFFull) == 1 && BN_num_bits_nid_postfix(value) == 64);
BN_free_nid_postfix(value);
BN_free_nid_postfix(big);
}
{
Bn* value = nullptr;
Require(BN_hex2bn_nid_postfix(&value, "-1F") == 3 && value);
Require(value->neg == 1 && value->top == 1 && value->d[0] == 0x1F);
Bn* same = value;
Require(BN_hex2bn_nid_postfix(&value, "ABC") == 3 && value == same);
Require(value->neg == 0 && value->d[0] == 0xABC);
Require(BN_hex2bn_nid_postfix(&value, "FFzz") == 2 && value->d[0] == 0xFF);
Require(BN_hex2bn_nid_postfix(&value, "") == 0 && value->d[0] == 0xFF);
Require(BN_hex2bn_nid_postfix(&value, "zz") == 0 && BN_hex2bn_nid_postfix(&value, "-") == 0);
Require(BN_hex2bn_nid_postfix(&value, "-0") == 2 && value->top == 0 && value->neg == 0);
Require(BN_hex2bn_nid_postfix(&value, "deadBEEF0123456789abcdef") == 24);
Require(Hex(value) == "DEADBEEF0123456789ABCDEF" && value->top == 2);
Require(BN_hex2bn_nid_postfix(nullptr, "12G") == 2);
BN_free_nid_postfix(value);
}
{
Bn* value = FromHex("FFFFFFFFFFFFFFFF");
Require(BN_add_word_nid_postfix(value, 1) == 1);
Require(value->top == 2 && value->d[0] == 0 && value->d[1] == 1 && Hex(value) == "10000000000000000");
Require(BN_sub_word_nid_postfix(value, 1) == 1);
Require(value->top == 1 && value->d[0] == 0xFFFFFFFFFFFFFFFFull && value->neg == 0);
Require(BN_set_word_nid_postfix(value, 3) == 1);
Require(BN_sub_word_nid_postfix(value, 5) == 1 && value->neg == 1 && value->top == 1 && value->d[0] == 2);
Require(BN_add_word_nid_postfix(value, 2) == 1 && value->top == 0 && value->neg == 0);
Require(BN_sub_word_nid_postfix(value, 7) == 1 && value->neg == 1 && Hex(value) == "-7");
Require(BN_add_word_nid_postfix(value, 10) == 1 && value->neg == 0 && Hex(value) == "3");
Require(BN_sub_word_nid_postfix(value, 0) == 1 && Hex(value) == "3");
Bn* wide = FromHex("-10000000000000000");
Require(BN_sub_word_nid_postfix(wide, 1) == 1 && wide->neg == 1 && Hex(wide) == "-10000000000000001");
Require(BN_add_word_nid_postfix(wide, 2) == 1 && wide->neg == 1 && Hex(wide) == "-FFFFFFFFFFFFFFFF" && wide->top == 1);
Bn* chain = FromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
Require(BN_add_word_nid_postfix(chain, 1) == 1 && chain->top == 3 && Hex(chain) == "100000000000000000000000000000000");
Require(BN_sub_word_nid_postfix(chain, 1) == 1 && chain->top == 2 && Hex(chain) == "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
BN_free_nid_postfix(value);
BN_free_nid_postfix(wide);
BN_free_nid_postfix(chain);
}
{
Bn* r = BN_new_nid_postfix();
Bn* three = FromHex("3");
Bn* eleven = FromHex("B");
Require(BN_mod_inverse_nid_postfix(r, three, eleven, ctx) == r && Hex(r) == "4");
Bn* seven = FromHex("7");
Bn* forty = FromHex("28");
Require(BN_mod_inverse_nid_postfix(r, seven, forty, ctx) == r && Hex(r) == "17");
Bn* four = FromHex("4");
Bn* eight = FromHex("8");
Require(BN_mod_inverse_nid_postfix(r, four, eight, ctx) == nullptr);
Bn* allocated = BN_mod_inverse_nid_postfix(nullptr, three, eleven, ctx);
Require(allocated && Hex(allocated) == "4");
Bn* minusThree = FromHex("-3");
Require(BN_mod_inverse_nid_postfix(r, minusThree, eleven, ctx) == r && Hex(r) == "7");
Bn* fifteen = FromHex("F");
Require(BN_mod_inverse_nid_postfix(r, fifteen, eleven, ctx) == r && Hex(r) == "3");
Bn* inPlace = FromHex("3");
Require(BN_mod_inverse_nid_postfix(inPlace, inPlace, eleven, ctx) == inPlace && Hex(inPlace) == "4");
Bn* prime = FromHex("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
Bn* a = FromHex("123456789ABCDEF0FEDCBA9876543210");
Bn* inverse = BN_mod_inverse_nid_postfix(nullptr, a, prime, ctx);
Require(inverse && Hex(inverse) == "62CFEFE6EB41053969967BD711C5169F");
Bn* product = BN_new_nid_postfix();
Require(BN_mul_nid_postfix(product, a, inverse, ctx) == 1);
Bn* quotient = FromHex("E0DA4F6BE78BFD74B8C9F95AF178811");
Bn* check = BN_new_nid_postfix();
Require(BN_mul_nid_postfix(check, quotient, prime, ctx) == 1);
Require(BN_add_word_nid_postfix(check, 1) == 1);
Require(Hex(check) == Hex(product));
Bn* all[] = {r, three, eleven, seven, forty, four, eight, allocated, minusThree, fifteen, inPlace, prime, a, inverse, product, quotient, check};
for (Bn* value : all) BN_free_nid_postfix(value);
}
BN_CTX_free_nid_postfix(ctx);
return 0;
}
+444
View File
@@ -0,0 +1,444 @@
#include "prx/libSceLibreSsl/libSceLibreSsl.hpp"
#include "prx/libSceLibreSsl/include/Cmac.hpp"
#include "prx/libSceLibreSsl/ErrorQueue.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <mbedtls/cipher.h>
#include <mbedtls/cmac.h>
#include <mbedtls/aes.h>
#include <mbedtls/des.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#ifdef ANYPS5_CMAC_DYNAMIC
namespace {
decltype(&CMAC_CTX_new_nid_postfix) cmacNew;
decltype(&CMAC_CTX_free_nid_postfix) cmacFree;
decltype(&CMAC_Init_nid_postfix) cmacInit;
decltype(&CMAC_Update_nid_postfix) cmacUpdate;
decltype(&CMAC_Final_nid_postfix) cmacFinal;
decltype(&EVP_aes_128_cbc_nid_postfix) aes128Method;
decltype(&EVP_aes_192_cbc_nid_postfix) aes192Method;
decltype(&EVP_aes_256_cbc_nid_postfix) aes256Method;
decltype(&ERR_clear_error_nid_postfix) clearError;
decltype(&ERR_get_error_nid_postfix) getError;
decltype(&ApplicationHeapRegister_nid_no_patch) registerHeap;
void* Load(const std::string& path) {
#ifdef _WIN32
auto* library = LoadLibraryExA(path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
#else
auto* library = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
#endif
if (library == nullptr) throw std::runtime_error("failed to load patched CMAC dependency: " + path);
return library;
}
template<typename TFunction> void Resolve(TFunction& destination, void* library, const char* name) {
#ifdef _WIN32
destination = reinterpret_cast<TFunction>(GetProcAddress(static_cast<HMODULE>(library), name));
#else
destination = reinterpret_cast<TFunction>(dlsym(library, name));
#endif
if (destination == nullptr) throw std::runtime_error(std::string("missing patched CMAC dependency: ") + name);
}
void InitializePatched(const char* directory) {
const auto root = std::string(directory) + "/";
auto* libc = Load(root + "libc.prx");
auto* library = Load(root + "libSceLibreSsl.prx");
Resolve(cmacNew, library, "mwJYST4Bb3U");
Resolve(cmacFree, library, "cBNGAG1DkXc");
Resolve(cmacInit, library, "LATrF+dIZYI");
Resolve(cmacUpdate, library, "XpAJ1Ckg-q4");
Resolve(cmacFinal, library, "6MM7ng9P+18");
Resolve(aes128Method, library, "rV07jlO-+6Q");
Resolve(aes192Method, library, "0X5DnA3olsk");
Resolve(aes256Method, library, "J44dsZUcK4I");
Resolve(clearError, library, "sieQ--PINWg");
Resolve(getError, library, "DJg+qWX1rwY");
Resolve(registerHeap, libc, "ApplicationHeapRegister_nid_no_patch");
}
}
#define CMAC_CTX_new_nid_postfix cmacNew
#define CMAC_CTX_free_nid_postfix cmacFree
#define CMAC_Init_nid_postfix cmacInit
#define CMAC_Update_nid_postfix cmacUpdate
#define CMAC_Final_nid_postfix cmacFinal
#define EVP_aes_128_cbc_nid_postfix aes128Method
#define EVP_aes_192_cbc_nid_postfix aes192Method
#define EVP_aes_256_cbc_nid_postfix aes256Method
#define ERR_clear_error_nid_postfix clearError
#define ERR_get_error_nid_postfix getError
#define ApplicationHeapRegister_nid_no_patch registerHeap
#endif
namespace {
void Require(bool condition, const char* message) {
if (condition) return;
std::fprintf(stderr, "failed: %s\n", message);
std::abort();
}
int allocationsUntilFailure = -1;
std::vector<std::pair<void*, std::size_t>> allocations;
void* APS5_VABI Allocate(std::size_t size) {
if (allocationsUntilFailure == 0) return nullptr;
if (allocationsUntilFailure > 0) --allocationsUntilFailure;
auto* pointer = std::malloc(size);
if (pointer != nullptr) allocations.emplace_back(pointer, size);
return pointer;
}
void* APS5_VABI Reallocate(void*, std::size_t) { throw std::logic_error("unexpected CMAC reallocation"); }
void* APS5_VABI Align(std::size_t, std::size_t) { throw std::logic_error("unexpected CMAC aligned allocation"); }
void* APS5_VABI Realign(void*, std::size_t, std::size_t) { throw std::logic_error("unexpected CMAC realignment"); }
int APS5_VABI PosixAlign(void**, std::size_t, std::size_t) { throw std::logic_error("unexpected CMAC aligned allocation"); }
void* APS5_VABI Calloc(std::size_t count, std::size_t size) {
if (allocationsUntilFailure == 0) return nullptr;
if (allocationsUntilFailure > 0) --allocationsUntilFailure;
auto* pointer = std::calloc(count, size);
if (pointer != nullptr) allocations.emplace_back(pointer, count * size);
return pointer;
}
void APS5_VABI Free(void* pointer) {
if (pointer == nullptr) return;
const auto found = std::find_if(allocations.begin(), allocations.end(), [&](const auto& entry) { return entry.first == pointer; });
Require(found != allocations.end(), "free a guest-owned allocation");
const auto* bytes = static_cast<const unsigned char*>(pointer);
Require(std::all_of(bytes, bytes + found->second, [](auto value) { return value == 0; }), "wipe contexts and key storage before freeing");
allocations.erase(found);
std::free(pointer);
}
void InitializeHeap() {
const std::array<void*, 10> api{
reinterpret_cast<void*>(Allocate), reinterpret_cast<void*>(Free), reinterpret_cast<void*>(Calloc),
reinterpret_cast<void*>(Reallocate), reinterpret_cast<void*>(Align), reinterpret_cast<void*>(Realign),
reinterpret_cast<void*>(PosixAlign)};
ApplicationHeapRegister_nid_no_patch(api.data());
}
template<typename TAction> void RequireThrows(TAction action) {
try { action(); } catch (const std::exception&) { return; }
Require(false, "unsupported input must throw");
}
std::vector<unsigned char> Hex(std::string_view text) {
Require(text.size() % 2 == 0, "hex fixture has complete bytes");
std::vector<unsigned char> bytes;
for (std::size_t index = 0; index < text.size(); index += 2) {
const auto nibble = [](char value) { return value <= '9' ? value - '0' : value - 'a' + 10; };
bytes.push_back(static_cast<unsigned char>((nibble(text[index]) << 4) | nibble(text[index + 1])));
}
return bytes;
}
using CmacFree = void (APS5_VABI*)(LibreSslCmac*);
using Context = std::unique_ptr<LibreSslCmac, CmacFree>;
Context NewContext() {
Context context(CMAC_CTX_new_nid_postfix(), CMAC_CTX_free_nid_postfix);
Require(context != nullptr, "allocate CMAC context");
return context;
}
std::array<unsigned char, 16> Final(LibreSslCmac* context) {
std::array<unsigned char, 18> output;
output.fill(0xa5);
std::size_t length = 99;
Require(CMAC_Final_nid_postfix(context, output.data() + 1, &length) == 1, "finalize CMAC");
Require(length == 16 && output.front() == 0xa5 && output.back() == 0xa5, "tag size and output bounds");
std::array<unsigned char, 16> result;
std::copy_n(output.data() + 1, 16, result.data());
return result;
}
const LibreSslCipher* Methods[3];
void RfcVectors() {
const auto key = Hex("2b7e151628aed2a6abf7158809cf4f3c");
const auto message = Hex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710");
const std::size_t lengths[] = {0, 16, 40, 64};
const char* tags[] = {"bb1d6929e95937287fa37d129b756746", "070a16b46b4d4144f79bdd9dd04a287c", "dfa66747de9ae63030ca32611497c827", "51f0bebf7e3b9d92fc49741779363cfe"};
auto context = NewContext();
for (std::size_t vector = 0; vector < 4; ++vector) {
const auto expected = Hex(tags[vector]);
for (std::size_t split = 0; split <= lengths[vector]; ++split) {
Require(CMAC_Init_nid_postfix(context.get(), key.data(), key.size(), Methods[0], nullptr) == 1, "RFC key initialization");
Require(CMAC_Update_nid_postfix(context.get(), message.data(), split) == 1, "RFC first update");
Require(CMAC_Update_nid_postfix(context.get(), nullptr, 0) == 1, "empty update");
Require(CMAC_Update_nid_postfix(context.get(), message.data() + split, lengths[vector] - split) == 1, "RFC second update");
std::size_t length = 99;
Require(CMAC_Final_nid_postfix(context.get(), nullptr, &length) == 1 && length == 16, "length-only final");
Require(CMAC_Final_nid_postfix(context.get(), nullptr, &length) == 1 && length == 16, "repeat length query");
const auto actual = Final(context.get());
if (!std::equal(actual.begin(), actual.end(), expected.begin())) {
std::fprintf(stderr, "RFC vector %zu split %zu: ", vector, split);
for (auto byte : actual) std::fprintf(stderr, "%02x", byte);
std::fputc('\n', stderr);
}
Require(std::equal(actual.begin(), actual.end(), expected.begin()), "RFC 4493 tag");
}
}
}
void Differential() {
const mbedtls_cipher_type_t types[] = {MBEDTLS_CIPHER_AES_128_ECB, MBEDTLS_CIPHER_AES_192_ECB, MBEDTLS_CIPHER_AES_256_ECB};
std::array<unsigned char, 32> key;
std::array<unsigned char, 257> message;
for (std::size_t index = 0; index < key.size(); ++index) key[index] = static_cast<unsigned char>(index * 73 + 41);
for (std::size_t index = 0; index < message.size(); ++index) message[index] = static_cast<unsigned char>(index * 197 + 17);
auto context = NewContext();
for (std::size_t method = 0; method < 3; ++method) {
const auto* cipher = Methods[method];
Require(cipher->nid == 419 + static_cast<int>(method) * 4 && cipher->blockSize == 16 && cipher->keyLength == 16 + static_cast<int>(method) * 8 && cipher->ivLength == 16 && cipher->flags == 0x5002, "AES descriptor metadata");
const auto* reference = mbedtls_cipher_info_from_type(types[method]);
Require(reference != nullptr, "reference cipher available");
for (std::size_t length = 0; length <= message.size(); ++length) {
std::array<unsigned char, 16> expected;
Require(mbedtls_cipher_cmac(reference, key.data(), cipher->keyLength * 8, message.data(), length, expected.data()) == 0, "reference CMAC");
for (const auto chunk : {1u, 7u, 15u, 16u, 17u, 64u, 257u}) {
Require(CMAC_Init_nid_postfix(context.get(), key.data(), cipher->keyLength, cipher, nullptr) == 1, "differential initialization");
for (std::size_t offset = 0; offset < length;) {
const auto take = std::min(static_cast<std::size_t>(chunk), length - offset);
Require(CMAC_Update_nid_postfix(context.get(), message.data() + offset, take) == 1, "differential update");
offset += take;
}
Require(Final(context.get()) == expected, "CMAC agrees with independent backend");
}
}
}
}
void Lifecycle() {
auto context = NewContext();
const std::array<unsigned char, 32> key{};
const std::array<unsigned char, 19> message{};
std::array<unsigned char, 16> output;
output.fill(0xa5);
std::size_t length = 99;
Require(CMAC_Update_nid_postfix(context.get(), nullptr, 1) == 0, "uninitialized update");
Require(CMAC_Final_nid_postfix(context.get(), output.data(), &length) == 0 && length == 99 && output[0] == 0xa5, "uninitialized final preserves outputs");
Require(CMAC_Final_nid_postfix(context.get(), nullptr, nullptr) == 0, "uninitialized final precedes null output length");
Require(CMAC_Init_nid_postfix(context.get(), nullptr, 0, nullptr, nullptr) == 0, "uninitialized restart");
Require(CMAC_Init_nid_postfix(context.get(), key.data(), 16, nullptr, nullptr) == 0, "key without cipher");
Require(CMAC_Init_nid_postfix(context.get(), nullptr, 1, nullptr, nullptr) == 1, "nonzero key length without a key is not restart");
Require(CMAC_Init_nid_postfix(context.get(), nullptr, 0, Methods[0], nullptr) == 1, "stage cipher selection");
Require(CMAC_Update_nid_postfix(context.get(), message.data(), 1) == 0, "cipher-only context has no key");
ERR_clear_error_nid_postfix();
Require(CMAC_Init_nid_postfix(context.get(), key.data(), 15, nullptr, nullptr) == 0, "reject invalid key length");
Require(ERR_get_error_nid_postfix() == 0x06fff082, "invalid key length publishes EVP error");
Require(CMAC_Init_nid_postfix(context.get(), key.data(), 16, nullptr, nullptr) == 1, "finish staged initialization");
Require(CMAC_Update_nid_postfix(context.get(), message.data(), message.size()) == 1, "first message");
const auto expected = Final(context.get());
const auto repeated = Final(context.get());
Require(repeated != expected, "final advances the CBC state");
Require(CMAC_Init_nid_postfix(context.get(), nullptr, 0, nullptr, nullptr) == 1, "restart reuses the key");
Require(CMAC_Update_nid_postfix(context.get(), message.data(), message.size()) == 1 && Final(context.get()) == expected, "restart resets state");
Require(CMAC_Init_nid_postfix(context.get(), key.data(), (std::size_t{1} << 32) + 16, nullptr, nullptr) == 1, "upstream key length narrows to int");
Require(CMAC_Update_nid_postfix(context.get(), message.data(), message.size()) == 1 && Final(context.get()) == expected, "narrowed key length tag");
Require(CMAC_Init_nid_postfix(context.get(), key.data(), 24, Methods[1], nullptr) == 1, "switch AES key size");
Require(CMAC_Init_nid_postfix(context.get(), key.data(), 32, Methods[2], nullptr) == 1, "switch AES key size again");
RequireThrows([&] { CMAC_Init_nid_postfix(nullptr, key.data(), 16, Methods[0], nullptr); });
RequireThrows([&] { CMAC_Update_nid_postfix(nullptr, nullptr, 0); });
RequireThrows([&] { CMAC_Final_nid_postfix(nullptr, output.data(), &length); });
RequireThrows([&] { CMAC_Update_nid_postfix(context.get(), nullptr, 1); });
RequireThrows([&] { CMAC_Final_nid_postfix(context.get(), output.data(), nullptr); });
CMAC_CTX_free_nid_postfix(nullptr);
}
void AllocationFailures() {
allocationsUntilFailure = 0;
Require(CMAC_CTX_new_nid_postfix() == nullptr, "context allocation failure");
allocationsUntilFailure = 1;
auto context = NewContext();
const std::array<unsigned char, 16> key{};
ERR_clear_error_nid_postfix();
Require(CMAC_Init_nid_postfix(context.get(), key.data(), key.size(), Methods[0], nullptr) == 0, "cipher allocation failure");
Require(ERR_get_error_nid_postfix() == 0x06fff041, "cipher allocation failure publishes EVP error");
allocationsUntilFailure = -1;
Require(CMAC_Init_nid_postfix(context.get(), key.data(), key.size(), Methods[0], nullptr) == 1, "recover from failed allocation");
}
void CipherCallbacks() {
std::array<unsigned char, 32> key{};
std::array<unsigned char, 16> iv;
for (std::size_t index = 0; index < iv.size(); ++index) iv[index] = static_cast<unsigned char>(index + 1);
for (const auto* method : Methods) {
for (std::size_t length = 0; length <= 48; ++length) {
const auto padded = (length + 15) / 16 * 16;
std::vector<unsigned char> input(padded);
for (std::size_t index = 0; index < length; ++index) input[index] = static_cast<unsigned char>(index * 31 + 17);
std::vector<unsigned char> expected(padded);
auto referenceIv = iv;
mbedtls_aes_context reference;
mbedtls_aes_init(&reference);
Require(mbedtls_aes_setkey_enc(&reference, key.data(), method->keyLength * 8) == 0, "reference AES key");
Require(mbedtls_aes_crypt_cbc(&reference, MBEDTLS_AES_ENCRYPT, padded, referenceIv.data(), input.data(), expected.data()) == 0, "reference CBC");
mbedtls_aes_free(&reference);
std::vector<unsigned char> storage(method->contextSize);
LibreSslCipherContext context{};
context.cipher = method;
context.cipherData = storage.data();
context.keyLength = method->keyLength;
for (const bool inPlace : {false, true}) {
context.encrypt = 1;
std::copy(iv.begin(), iv.end(), context.iv);
Require(method->init(&context, key.data(), nullptr, 1) == 1, "AES descriptor encryption setup");
auto output = input;
output.push_back(0xa5);
Require(method->cipher(&context, output.data(), inPlace ? output.data() : input.data(), length) == 1, "AES descriptor partial encryption");
Require(output.back() == 0xa5 && std::equal(expected.begin(), expected.end(), output.begin()), "CBC partial encryption and bounds");
Require(std::equal(referenceIv.begin(), referenceIv.end(), context.iv), "CBC encryption IV");
context.encrypt = 0;
std::copy(iv.begin(), iv.end(), context.iv);
Require(method->init(&context, key.data(), nullptr, 0) == 1, "AES descriptor decryption setup");
if (!inPlace) output.assign(padded + 1, 0xa5);
const auto before = output;
Require(method->cipher(&context, output.data(), inPlace ? output.data() : expected.data(), length) == 1, "AES descriptor partial decryption");
Require(std::equal(input.begin(), input.begin() + length, output.begin()), "CBC decrypted prefix");
Require(std::equal(before.begin() + length, before.end(), output.begin() + length), "partial decryption preserves trailing bytes");
Require(std::equal(referenceIv.begin(), referenceIv.end(), context.iv), "CBC decryption IV");
}
}
}
}
int APS5_VABI DesInit(LibreSslCipherContext* context, const unsigned char* key, const unsigned char*, int) {
auto* des = static_cast<mbedtls_des3_context*>(context->cipherData);
mbedtls_des3_init(des);
return mbedtls_des3_set3key_enc(des, key) == 0;
}
int APS5_VABI DesCipher(LibreSslCipherContext* context, unsigned char* output, const unsigned char* input, std::size_t length) {
return mbedtls_des3_crypt_cbc(static_cast<mbedtls_des3_context*>(context->cipherData), MBEDTLS_DES_ENCRYPT, length, context->iv, input, output) == 0;
}
void GuestDes() {
const LibreSslCipher method{44, 8, 24, 8, 2, DesInit, DesCipher, nullptr, sizeof(mbedtls_des3_context), nullptr, nullptr, nullptr, nullptr};
std::array<unsigned char, 24> key;
std::array<unsigned char, 33> message;
for (std::size_t index = 0; index < key.size(); ++index) key[index] = static_cast<unsigned char>(index * 31 + 7);
for (std::size_t index = 0; index < message.size(); ++index) message[index] = static_cast<unsigned char>(index * 71 + 3);
const auto* reference = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_DES_EDE3_ECB);
Require(reference != nullptr, "reference 64-bit cipher");
auto context = NewContext();
for (std::size_t length = 0; length <= message.size(); ++length) {
std::array<unsigned char, 8> expected;
Require(mbedtls_cipher_cmac(reference, key.data(), 192, message.data(), length, expected.data()) == 0, "reference 64-bit CMAC");
Require(CMAC_Init_nid_postfix(context.get(), key.data(), key.size(), &method, nullptr) == 1, "guest 64-bit cipher initialization");
for (std::size_t index = 0; index < length; ++index) Require(CMAC_Update_nid_postfix(context.get(), message.data() + index, 1) == 1, "guest 64-bit cipher update");
std::array<unsigned char, 10> actual;
actual.fill(0xa5);
std::size_t outputLength = 99;
Require(CMAC_Final_nid_postfix(context.get(), actual.data() + 1, &outputLength) == 1, "guest 64-bit cipher final");
Require(outputLength == 8 && actual.front() == 0xa5 && actual.back() == 0xa5 && std::equal(expected.begin(), expected.end(), actual.begin() + 1), "64-bit CMAC tag and output bounds");
}
}
const LibreSslCipher* delegated;
int initCalls;
int cipherCalls;
int cleanupCalls;
int failAt;
int APS5_VABI GuestInit(LibreSslCipherContext* context, const unsigned char* key, const unsigned char* iv, int encrypt) {
++initCalls;
Require(context->encrypt == 1 && context->keyLength == 16 && context->cipherData != nullptr, "guest callback context");
return delegated->init(context, key, iv, encrypt);
}
int APS5_VABI GuestCipher(LibreSslCipherContext* context, unsigned char* output, const unsigned char* input, std::size_t length) {
++cipherCalls;
if (failAt == cipherCalls) {
std::memset(output, 0x5a, length);
return 0;
}
return delegated->cipher(context, output, input, length);
}
int APS5_VABI GuestCleanup(LibreSslCipherContext*) {
++cleanupCalls;
return 1;
}
int APS5_VABI GuestControl(LibreSslCipherContext* context, int command, int argument, void*) {
if (command == 0) return 1;
if (command == 1 && argument == 16) {
context->keyLength = argument;
return 1;
}
return -1;
}
void Callbacks() {
delegated = Methods[0];
auto method = *delegated;
method.init = GuestInit;
method.cipher = GuestCipher;
method.cleanup = GuestCleanup;
method.control = GuestControl;
method.flags |= 0xc0;
std::array<unsigned char, 16> key{};
std::array<unsigned char, 33> message{};
{
auto context = NewContext();
Require(CMAC_Init_nid_postfix(context.get(), key.data(), key.size(), &method, nullptr) == 1 && initCalls == 1, "guest initialization callback");
Require(CMAC_Update_nid_postfix(context.get(), message.data(), message.size()) == 1 && cipherCalls == 3, "guest cipher callbacks");
const auto tag = Final(context.get());
Require(CMAC_Init_nid_postfix(context.get(), nullptr, 0, nullptr, nullptr) == 1, "guest callback restart");
Require(CMAC_Update_nid_postfix(context.get(), message.data(), message.size()) == 1 && Final(context.get()) == tag, "guest-defined cipher matches builtin");
failAt = cipherCalls + 1;
std::array<unsigned char, 16> output;
output.fill(0xa5);
std::size_t length = 99;
Require(CMAC_Final_nid_postfix(context.get(), output.data(), &length) == 0 && length == 16, "cipher failure propagates");
Require(std::all_of(output.begin(), output.end(), [](auto value) { return value == 0; }), "failed final clears output");
failAt = 0;
}
Require(cleanupCalls == 1, "guest cleanup callback");
auto context = NewContext();
method.blockSize = 32;
ERR_clear_error_nid_postfix();
Require(CMAC_Init_nid_postfix(context.get(), key.data(), key.size(), &method, nullptr) == 0, "reject unsupported block size");
Require(ERR_get_error_nid_postfix() == 0x06fff088, "bad block length publishes EVP error");
}
}
int main(int argc, char** argv) {
#ifdef ANYPS5_CMAC_DYNAMIC
Require(argc == 2, "patched test needs the library directory");
InitializePatched(argv[1]);
#endif
InitializeHeap();
Methods[0] = EVP_aes_128_cbc_nid_postfix();
Methods[1] = EVP_aes_192_cbc_nid_postfix();
Methods[2] = EVP_aes_256_cbc_nid_postfix();
RfcVectors();
Differential();
Lifecycle();
Callbacks();
AllocationFailures();
CipherCallbacks();
GuestDes();
Require(allocations.empty(), "all guest allocations released");
std::puts("CMAC vectors, streaming, lifecycle and callback checks passed");
}
+12 -2
View File
@@ -7,14 +7,14 @@
### Unknown function info
- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 261 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) - 231 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. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c)
- [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.
- [CRYPTO_num_locks, CRYPTO_set_locking_callback, CRYPTO_set_add_lock_callback, CRYPTO_THREADID_set_callback, CRYPTO_THREADID_set_numeric](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the locking, add-lock and thread id callbacks are stored but never called: LibreSSL calls them from `CRYPTO_lock`, `CRYPTO_add_lock` and `CRYPTO_THREADID_current`, which the module does not export, around its own shared state. Nothing is checked on the console. Results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/cryptlib.c), the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): 41 locks, the first non-null thread id callback is kept and later ones return 0, and `CRYPTO_THREADID_set_numeric` clears the 16-byte `{ptr, val}` structure; a null structure throws, as LibreSSL writes through it unchecked
- [RAND_add, RAND_bytes, RAND_status, RAND_load_file, RAND_file_name, CRYPTO_malloc, CRYPTO_free, CRYPTO_set_mem_functions](../../../core/libs/prx/libSceLibreSsl/src/Runtime.cpp) (libSceLibreSsl) - follow LibreSSL's [rand_lib.c, randfile.c](https://github.com/libressl/openbsd/tree/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/rand) and [malloc-wrapper.c](https://github.com/libressl/openbsd/blob/0f0b16dc977f6ca499f0f4540078d992c96da597/src/lib/libcrypto/malloc-wrapper.c), which are the same in 2.6.5 (3.4.3 linked); nothing is checked on the console. `RAND_bytes` reads host entropy and `CRYPTO_malloc` and `CRYPTO_free` use the guest heap. `CRYPTO_malloc` returns null for a size of zero or less, as LibreSSL 2.5.4 to 3.4 do; later versions take a `size_t` size and call `malloc`. A null buffer with a positive length throws
- [AES_set_encrypt_key, AES_set_decrypt_key, AES_encrypt, AES_decrypt, AES_ecb_encrypt, AES_cbc_encrypt, AES_cfb128_encrypt, AES_ctr128_encrypt](../../../core/libs/prx/libSceLibreSsl/src/Aes.cpp) (libSceLibreSsl) - follow LibreSSL's [aes.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/aes/aes.h) and [mode code](https://github.com/libressl/openbsd/tree/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/modes). Nothing is checked on the console. Key setup follows [LibreSSL 2.5.4's aes_core.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/aes_core.c), the version the `X509_verify_cert_error_string` entry names: a null pointer returns -1 and a key length other than 128, 192 or 256 bits returns -2 before the key is written, and only the 44, 52 or 60 words the key length needs are written. The `AES_KEY` schedule uses that file's generic C layout (big-endian words, `rounds` 10, 12 or 14). The 2.5.4 amd64 build uses [aes-x86_64](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/aes/asm/aes-x86_64.pl) instead, which copies the key bytes without swapping, so each word is byte-reversed compared with the C layout (OpenSSL 3.0.13's x86-64 code gives the same schedules byte-reversed); the console's build options are unknown, so a schedule the title reads or fills is unverified. CTR leaves the last keystream block in `ecount_buf` even at a block boundary. Null pointers in the cipher and mode functions, other `rounds` values and a CFB or CTR position outside 0-15 throw
- [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) have no console execution verification or production error producers. The 16-slot calling-thread queue retains 15 codes following LibreSSL err.c and err.h. [Firmware 13.42 module comparisons on a PC](https://github.com/boykopovar/AnyPS5/pull/9535#issuecomment-6407060914) matched libSceLibreSsl3 queue ordering and overflow for 0-20 publications; whether the two firmware libraries share a queue was not tested. Explicit thread IDs throw because guest thread identity mapping is unverified; null clears the calling thread. File/line metadata is stored by the native producer helper but no guest export exposes it.
- [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) have no console execution verification. Memory BIO and CMAC failures publish to this queue. The 16-slot calling-thread queue retains 15 codes following LibreSSL err.c and err.h. [Firmware 13.42 module comparisons on a PC](https://github.com/boykopovar/AnyPS5/pull/9535#issuecomment-6407060914) matched libSceLibreSsl3 queue ordering and overflow for 0-20 publications; whether the two firmware libraries share a queue was not tested. Explicit thread IDs throw because guest thread identity mapping is unverified; null clears the calling thread. File/line metadata is stored by the native producer helper but no guest export exposes it.
- [OCSP_response_status_str, OCSP_cert_status_str and OCSP_crl_reason_str](../../../core/libs/prx/libSceLibreSsl/src/Ocsp.cpp) (libSceLibreSsl) - the module's strings are not measured on the console. The tables follow LibreSSL [ocsp_prn.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ocsp/ocsp_prn.c), unchanged from revision 1.8 (2015) to 1.12 (2025), with codes from `ocsp.h`; the argument is a 64-bit `long` and every value not in a table returns `(UNKNOWN)`
- [SSL_get_ex_new_index, SSL_CTX_get_ex_new_index, SSL_get_ex_data_X509_STORE_CTX_idx and CRYPTO_cleanup_all_ex_data](../../../core/libs/prx/libSceLibreSsl/src/ExData.cpp) (libSceLibreSsl) - unverified on a PS5. The stored `argl`, `argp` and callbacks are never called, because no export creates an `SSL` or `SSL_CTX` and `SSL_get_ex_data`/`SSL_set_ex_data` still throw. Index numbering follows LibreSSL 2.5.4's [ex_data.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ex_data.c#L310-L406), [ssl_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_lib.c#L2828-L2854) and [ssl_cert.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_cert.c#L133-L160): per class from 0, the verify-callback index allocated once and kept across `CRYPTO_cleanup_all_ex_data`, which restarts every class at 0.
- [EVP digest and HMAC functions](../../../core/libs/prx/libSceLibreSsl/src/Evp.cpp) (libSceLibreSsl) - follow LibreSSL 2.5.4's [digest.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp/digest.c) and [hmac.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/hmac/hmac.c). They and the `EVP_MD`, `EVP_MD_CTX` and `HMAC_CTX` layouts behave the same up to 3.4.1, so this holds for 2.5.4 (the firmware 13.40 measurements) and for 2.7 or later (its `SSL_get0_param` export). Nothing is checked on the console. `sign` and `verify` in the `EVP_MD` tables are null as `RSA_sign` and `RSA_verify` still throw, failures add nothing to the error queue, and a failed allocation leaves the context without a digest, where LibreSSL keeps it. An `ENGINE`, an `EVP_PKEY_CTX`, a null context, `EVP_DigestInit_ex` without a type on a context that has a digest, and an update or final without a digest throw
@@ -31,6 +31,16 @@
### Functional
- [Error publication](../../../core/libs/prx/libSceLibreSsl/ErrorQueue.hpp) is integrated with memory BIO and CMAC cipher errors. The digest, low-level AES, allocator/RAND, DES, BIGNUM and EVP/HMAC implementations require separate error-publication integration. `LibreSslPutError_nid_no_patch(int library, int function, int reason, const char* file, int line)` has C linkage and the native host calling convention, accepts upstream numeric fields and publishes to the calling thread's queue. Three-argument source calls default file/line to null/zero. It stores file/line without taking ownership; producers must supply a filename with static lifetime. Guest ERR entry points use `APS5_VABI`. Unsupported producer exports continue throwing; the local tests publish through this helper.
- [CMAC and AES-CBC descriptors](../../../core/libs/prx/libSceLibreSsl/src/Cmac.cpp) have no PS5 equivalence verification. Signatures, the 88-byte cipher method and 168-byte cipher context follow [LibreSSL 3.4.1](https://github.com/libressl/openbsd/blob/078b3d1df8ca2011c1605cd40418c58fa0f5b143/src/lib/libcrypto/cmac/cmac.c); the console version remains unknown. AES cipher data uses Mbed TLS storage instead of LibreSSL's private key schedule. Guest-defined cipher methods run through guest-ABI callbacks. Other built-in cipher selectors and the general EVP cipher exports still throw. ENGINE-backed methods throw. Null contexts, null nonempty update buffers, null initialized-final length pointers, negative cipher dimensions and missing required callbacks throw where upstream dereferences unchecked pointers. Error metadata names host source locations.
- [BN_new, BN_free, BN_CTX_new, BN_CTX_free, BN_CTX_start, BN_CTX_end, BN_bin2bn, BN_bn2bin, BN_hex2bn, BN_num_bits, BN_set_word, BN_add_word, BN_sub_word, BN_mul and BN_mod_inverse](../../../core/libs/prx/libSceLibreSsl/src/Bignum.cpp) (libSceLibreSsl) - the `BIGNUM` layout (24 bytes, 64-bit limbs, `top`, `dmax`, `neg`, `flags`) and the documented semantics follow the public LibreSSL 2.x API; the module's LibreSSL version is unknown and nothing is checked on the console. `BN_CTX` is opaque and only counts `BN_CTX_start` and `BN_CTX_end`, since `BN_CTX_get` is not implemented, and `BN_mul` and `BN_mod_inverse` ignore it. `BN_mod_inverse` treats a negative modulus by its magnitude and returns 0 for a modulus of 1, which is not compared with LibreSSL. `BN_hex2bn` rejects a string without a hex digit, such as `-`, which older LibreSSL versions accept. Null `BIGNUM` pointers, which LibreSSL does not check, throw. `BN_print` still throws because it needs a `BIO`.
- [Memory BIOs](../../../core/libs/prx/libSceLibreSsl/src/Bio.cpp) have no console execution verification. [Firmware 13.42 module comparisons on a PC](https://github.com/boykopovar/AnyPS5/pull/9536#issuecomment-6407192928) cover 1,048 sequences with stand-in allocators, including null/zero writes, callbacks and growth wiping. They support dispatching nonnegative writes to the method even with null source or zero length; LibreSSL 3.4.1 instead guards those calls. Negative public lengths retain that source's early return because the module report did not measure them. The 104-byte BIO, 80-byte method and 24-byte buffer use LP64 layouts. Allocator padding is zeroed rather than retaining the module allocator's fill. Other firmware and console imports remain unverified. CRYPTO ex-data, file, socket and SSL BIOs remain unsupported.
- [Memory BIO unchecked inputs](../../../core/libs/prx/libSceLibreSsl/src/Bio.cpp) throw for null method or raw line-buffer pointers, negative raw method write sizes, an overflowing raw line-buffer size, inconsistent read-only buffer lengths, and memory write lengths that overflow size_t. Upstream does not validate these inputs. An unrepresentable string length throws instead of narrowing into an unchecked write.
- [ERR string formatting](../../../core/libs/prx/libSceLibreSsl/ErrorQueue.cpp) is not console-verified. [Firmware 13.42 modules run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/9535#issuecomment-6407060914) produced numeric-only strings before and after their loaders. Formatting, truncation and caller/static buffers matched with a standard-C snprintf stand-in; other firmware, console imports and concurrent static-buffer use remain unverified. Loading and freeing do not enable named strings or alter queued errors. Historical tables are generated only as regression-test inputs.
- [ASN1_INTEGER_get](../../../core/libs/prx/libSceLibreSsl/src/Asn1.cpp) has no console verification for nonpositive lengths. It follows [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/a_int.c#L363-L389): valid integer types return zero without reading data when length is zero or negative; invalid types return -1 first. Later LibreSSL versions reject negative lengths through `ASN1_INTEGER_get_int64`; that later contract is not used for this library.