feat(libSceLibreSsl): implement EC groups and keys for named curves

EC_GROUP_new_by_curve_name, EC_GROUP_free, EC_GROUP_get_degree, EC_KEY_new,
EC_KEY_new_by_curve_name, EC_KEY_free, EC_KEY_set_group, EC_KEY_get0_group
and ECDSA_size follow LibreSSL 2.5.4 on top of mbedtls's named curves.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Matteo DonatoandClaude Opus 5.5 committed 2026-11-03 09:14:47 +01:00
1 parent 4f41f4c375
commit c882bb4df5
7 files changed
+268 -56

No files matched your search

+12 -1
View File
@@ -1,4 +1,4 @@
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)
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 ${CMAKE_CURRENT_SOURCE_DIR}/src/Ec.cpp)
target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto)
configure_windows_unwind(libSceLibreSsl)
@@ -181,6 +181,17 @@ if(BUILD_TESTING)
set_tests_properties(guest_libressl_evp_cipher_ctx PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibreSsl>")
endif()
add_test_executable(guest_libressl_ec_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslEc.cpp)
target_include_directories(guest_libressl_ec_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_libressl_ec_tests PRIVATE libSceLibreSsl libc)
configure_windows_unwind(guest_libressl_ec_tests)
add_test(NAME guest_libressl_ec COMMAND guest_libressl_ec_tests)
set_tests_properties(guest_libressl_ec PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(guest_libressl_ec PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibreSsl>")
endif()
endif()
target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp)
-45
View File
@@ -160,51 +160,21 @@ int APS5_VABI ECDSA_sign_nid_postfix(void) {
return 0;
}
int APS5_VABI ECDSA_size_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI ECDSA_verify_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EC_GROUP_free_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EC_GROUP_get_degree_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EC_GROUP_new_by_curve_name_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EC_KEY_check_key_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EC_KEY_free_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EC_KEY_generate_key_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EC_KEY_get0_group_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EC_KEY_get0_private_key_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -215,21 +185,6 @@ int APS5_VABI EC_KEY_get0_public_key_nid_postfix(void) {
return 0;
}
int APS5_VABI EC_KEY_new_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EC_KEY_new_by_curve_name_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EC_KEY_set_group_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI EC_KEY_set_private_key_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
+12 -9
View File
@@ -323,6 +323,9 @@ struct LibreSslEvpCipherCtx {
unsigned char final[32];
};
struct EcGroup;
struct EcKey;
extern "C" {
std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value);
@@ -380,20 +383,20 @@ int APS5_VABI DTLSv1_client_method_nid_postfix(void);
int APS5_VABI DTLSv1_server_method_nid_postfix(void);
int APS5_VABI ECDH_compute_key_nid_postfix(void);
int APS5_VABI ECDSA_sign_nid_postfix(void);
int APS5_VABI ECDSA_size_nid_postfix(void);
int APS5_VABI ECDSA_size_nid_postfix(const EcKey* key);
int APS5_VABI ECDSA_verify_nid_postfix(void);
int APS5_VABI EC_GROUP_free_nid_postfix(void);
int APS5_VABI EC_GROUP_get_degree_nid_postfix(void);
int APS5_VABI EC_GROUP_new_by_curve_name_nid_postfix(void);
void APS5_VABI EC_GROUP_free_nid_postfix(EcGroup* group);
int APS5_VABI EC_GROUP_get_degree_nid_postfix(const EcGroup* group);
EcGroup* APS5_VABI EC_GROUP_new_by_curve_name_nid_postfix(int nid);
int APS5_VABI EC_KEY_check_key_nid_postfix(void);
int APS5_VABI EC_KEY_free_nid_postfix(void);
void APS5_VABI EC_KEY_free_nid_postfix(EcKey* key);
int APS5_VABI EC_KEY_generate_key_nid_postfix(void);
int APS5_VABI EC_KEY_get0_group_nid_postfix(void);
const EcGroup* APS5_VABI EC_KEY_get0_group_nid_postfix(const EcKey* key);
int APS5_VABI EC_KEY_get0_private_key_nid_postfix(void);
int APS5_VABI EC_KEY_get0_public_key_nid_postfix(void);
int APS5_VABI EC_KEY_new_nid_postfix(void);
int APS5_VABI EC_KEY_new_by_curve_name_nid_postfix(void);
int APS5_VABI EC_KEY_set_group_nid_postfix(void);
EcKey* APS5_VABI EC_KEY_new_nid_postfix(void);
EcKey* APS5_VABI EC_KEY_new_by_curve_name_nid_postfix(int nid);
int APS5_VABI EC_KEY_set_group_nid_postfix(EcKey* key, const EcGroup* group);
int APS5_VABI EC_KEY_set_private_key_nid_postfix(void);
int APS5_VABI EC_KEY_set_public_key_nid_postfix(void);
int APS5_VABI EC_POINT_free_nid_postfix(void);
@@ -0,0 +1,24 @@
#pragma once
#include <memory>
#include "mbedtls/ecp.h"
struct NamedCurve {
int nid;
mbedtls_ecp_group_id id;
};
struct EcGroup {
explicit EcGroup(const NamedCurve& named);
~EcGroup();
EcGroup(const EcGroup&) = delete;
EcGroup& operator=(const EcGroup&) = delete;
const NamedCurve curve;
mbedtls_ecp_group group;
};
struct EcKey {
std::unique_ptr<EcGroup> group;
};
+110
View File
@@ -0,0 +1,110 @@
#include "prx/libSceLibreSsl/include/Api.hpp"
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include "prx/libSceLibreSsl/include/Ec.hpp"
#include "prx/libc/include/General.hpp"
#include "mbedtls/bignum.h"
#include "mbedtls/ecp.h"
EcGroup::EcGroup(const NamedCurve& named) : curve(named) {
mbedtls_ecp_group_init(&group);
if (mbedtls_ecp_group_load(&group, named.id) != 0) {
mbedtls_ecp_group_free(&group);
throw std::runtime_error("EC_GROUP: mbedtls failed to load curve " + std::to_string(named.nid));
}
}
EcGroup::~EcGroup() {
mbedtls_ecp_group_free(&group);
}
namespace {
constexpr NamedCurve NamedCurves[] = {
{409, MBEDTLS_ECP_DP_SECP192R1},
{415, MBEDTLS_ECP_DP_SECP256R1},
{711, MBEDTLS_ECP_DP_SECP192K1},
{712, MBEDTLS_ECP_DP_SECP224K1},
{713, MBEDTLS_ECP_DP_SECP224R1},
{714, MBEDTLS_ECP_DP_SECP256K1},
{715, MBEDTLS_ECP_DP_SECP384R1},
{716, MBEDTLS_ECP_DP_SECP521R1},
{928, MBEDTLS_ECP_DP_BP256R1},
{932, MBEDTLS_ECP_DP_BP384R1},
{934, MBEDTLS_ECP_DP_BP512R1},
};
constexpr std::pair<int, int> CurvesMissingFromMbedtls[] = {
{410, 414}, {684, 690}, {693, 696}, {699, 710}, {717, 745}, {749, 750},
{839, 844}, {922, 927}, {929, 931}, {933, 933}, {935, 936}, {943, 944},
};
std::unique_ptr<EcGroup> NewGroup(int nid) {
for (const NamedCurve& curve : NamedCurves) {
if (curve.nid == nid) return std::make_unique<EcGroup>(curve);
}
for (const auto& [first, last] : CurvesMissingFromMbedtls) {
if (nid >= first && nid <= last) NotImplemented_nid_no_patch("EC_GROUP_new_by_curve_name: a LibreSSL curve that mbedtls does not provide");
}
return nullptr;
}
int DerLengthSize(int length) {
int size = 1;
if (length > 127) {
for (; length > 0; length >>= 8) ++size;
}
return size;
}
}
extern "C" {
EcGroup* APS5_VABI EC_GROUP_new_by_curve_name_nid_postfix(int nid) {
return NewGroup(nid).release();
}
void APS5_VABI EC_GROUP_free_nid_postfix(EcGroup* group) {
delete group;
}
int APS5_VABI EC_GROUP_get_degree_nid_postfix(const EcGroup* group) {
if (group == nullptr) APS5_INVALID_ARG_EX;
return static_cast<int>(group->group.pbits);
}
EcKey* APS5_VABI EC_KEY_new_nid_postfix(void) {
return new EcKey;
}
EcKey* APS5_VABI EC_KEY_new_by_curve_name_nid_postfix(int nid) {
std::unique_ptr<EcGroup> group = NewGroup(nid);
if (!group) return nullptr;
return new EcKey{std::move(group)};
}
void APS5_VABI EC_KEY_free_nid_postfix(EcKey* key) {
delete key;
}
const EcGroup* APS5_VABI EC_KEY_get0_group_nid_postfix(const EcKey* key) {
if (key == nullptr) APS5_INVALID_ARG_EX;
return key->group.get();
}
int APS5_VABI EC_KEY_set_group_nid_postfix(EcKey* key, const EcGroup* group) {
if (key == nullptr) APS5_INVALID_ARG_EX;
key->group = group ? std::make_unique<EcGroup>(group->curve) : nullptr;
return key->group ? 1 : 0;
}
int APS5_VABI ECDSA_size_nid_postfix(const EcKey* key) {
if (key == nullptr || !key->group) return 0;
const int orderBytes = static_cast<int>((mbedtls_mpi_bitlen(&key->group->group.N) + 7) / 8);
const int integerSize = 1 + DerLengthSize(orderBytes + 1) + orderBytes + 1;
return 1 + DerLengthSize(2 * integerSize) + 2 * integerSize;
}
}
+108
View File
@@ -0,0 +1,108 @@
#include "prx/libSceLibreSsl/include/Api.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdio>
#include <cstdlib>
#include <stdexcept>
struct Curve {
int nid;
int degree;
int signatureSize;
};
static constexpr Curve Curves[] = {
{409, 192, 56},
{415, 256, 72},
{711, 192, 56},
{712, 224, 66},
{713, 224, 64},
{714, 256, 72},
{715, 384, 104},
{716, 521, 141},
{928, 256, 72},
{932, 384, 104},
{934, 512, 137},
};
static constexpr int UnknownNids[] = {0, -1, 1, 406, 691, 746, 921, 937, 948, 100000};
static void Require(bool value, const char* what) {
if (value) return;
std::fprintf(stderr, "failed: %s\n", what);
std::abort();
}
template<typename TAction> static void RequireThrows(TAction action, const char* what) {
try { action(); } catch (const std::exception&) { return; }
Require(false, what);
}
static void CheckNamedCurves() {
for (const Curve& curve : Curves) {
EcGroup* group = EC_GROUP_new_by_curve_name_nid_postfix(curve.nid);
Require(group != nullptr, "EC_GROUP_new_by_curve_name for a named curve");
Require(EC_GROUP_get_degree_nid_postfix(group) == curve.degree, "EC_GROUP_get_degree");
EcKey* key = EC_KEY_new_by_curve_name_nid_postfix(curve.nid);
Require(key != nullptr, "EC_KEY_new_by_curve_name for a named curve");
const EcGroup* keyGroup = EC_KEY_get0_group_nid_postfix(key);
Require(keyGroup != nullptr && keyGroup != group, "EC_KEY_get0_group of a key made from a curve name");
Require(EC_GROUP_get_degree_nid_postfix(keyGroup) == curve.degree, "degree of the key's group");
Require(ECDSA_size_nid_postfix(key) == curve.signatureSize, "ECDSA_size");
EC_KEY_free_nid_postfix(key);
EC_GROUP_free_nid_postfix(group);
}
}
static void CheckOtherNids() {
for (const int nid : UnknownNids) {
Require(EC_GROUP_new_by_curve_name_nid_postfix(nid) == nullptr, "EC_GROUP_new_by_curve_name for an unknown curve");
Require(EC_KEY_new_by_curve_name_nid_postfix(nid) == nullptr, "EC_KEY_new_by_curve_name for an unknown curve");
}
}
static void CheckSetGroup() {
EcKey* key = EC_KEY_new_nid_postfix();
Require(key != nullptr, "EC_KEY_new");
Require(EC_KEY_get0_group_nid_postfix(key) == nullptr, "EC_KEY_new has no group");
Require(ECDSA_size_nid_postfix(key) == 0, "ECDSA_size of a key without a group");
EcGroup* group = EC_GROUP_new_by_curve_name_nid_postfix(715);
Require(EC_KEY_set_group_nid_postfix(key, group) == 1, "EC_KEY_set_group");
const EcGroup* copy = EC_KEY_get0_group_nid_postfix(key);
Require(copy != nullptr && copy != group, "EC_KEY_set_group stores a copy");
EC_GROUP_free_nid_postfix(group);
Require(EC_GROUP_get_degree_nid_postfix(EC_KEY_get0_group_nid_postfix(key)) == 384, "the copy outlives the original group");
Require(ECDSA_size_nid_postfix(key) == 104, "ECDSA_size after EC_KEY_set_group");
Require(EC_KEY_set_group_nid_postfix(key, EC_KEY_get0_group_nid_postfix(key)) == 1, "EC_KEY_set_group with the key's own group");
Require(EC_GROUP_get_degree_nid_postfix(EC_KEY_get0_group_nid_postfix(key)) == 384, "the key's own group set again");
EcGroup* other = EC_GROUP_new_by_curve_name_nid_postfix(409);
Require(EC_KEY_set_group_nid_postfix(key, other) == 1, "EC_KEY_set_group replacing a group");
Require(EC_GROUP_get_degree_nid_postfix(EC_KEY_get0_group_nid_postfix(key)) == 192, "the replaced group");
EC_GROUP_free_nid_postfix(other);
Require(EC_KEY_set_group_nid_postfix(key, nullptr) == 0, "EC_KEY_set_group with a null group");
Require(EC_KEY_get0_group_nid_postfix(key) == nullptr, "a null group drops the previous one");
Require(ECDSA_size_nid_postfix(key) == 0, "ECDSA_size after a null group");
EC_KEY_free_nid_postfix(key);
}
static void CheckNullArguments() {
EC_GROUP_free_nid_postfix(nullptr);
EC_KEY_free_nid_postfix(nullptr);
Require(ECDSA_size_nid_postfix(nullptr) == 0, "ECDSA_size of a null key");
RequireThrows([] { EC_GROUP_get_degree_nid_postfix(nullptr); }, "EC_GROUP_get_degree of a null group");
RequireThrows([] { EC_KEY_get0_group_nid_postfix(nullptr); }, "EC_KEY_get0_group of a null key");
EcGroup* group = EC_GROUP_new_by_curve_name_nid_postfix(415);
RequireThrows([&] { EC_KEY_set_group_nid_postfix(nullptr, group); }, "EC_KEY_set_group of a null key");
EC_GROUP_free_nid_postfix(group);
}
int main() {
CheckNamedCurves();
CheckOtherNids();
CheckSetGroup();
CheckNullArguments();
return 0;
}
+2 -1
View File
@@ -7,7 +7,7 @@
### Unknown function info
- [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`.
- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 222 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.
@@ -18,6 +18,7 @@
- [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
- [EC_GROUP_new_by_curve_name, EC_GROUP_free, EC_GROUP_get_degree, EC_KEY_new, EC_KEY_new_by_curve_name, EC_KEY_free, EC_KEY_set_group, EC_KEY_get0_group, ECDSA_size](../../../core/libs/prx/libSceLibreSsl/src/Ec.cpp) (libSceLibreSsl) - not checked on the console. They follow LibreSSL 2.5.4's [ec](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ec) and [ecs_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ecdsa/ecs_lib.c) over mbedtls, which has 11 of LibreSSL's 90 curves (NIDs 409, 415, 711 to 716, 928, 932, 934). The other 79 throw; other NIDs return null without queuing `EC_R_UNKNOWN_GROUP`. A key holds only its group: no reference count, keys, conversion form or flags. Setting a key's own group keeps it, where LibreSSL frees it first. Null groups and keys throw where LibreSSL dereferences them
- [SSL_CIPHER_get_bits, SSL_CIPHER_get_name and SSL_CIPHER_get_version](../../../core/libs/prx/libSceLibreSsl/src/Cipher.cpp) - the 88-byte guest cipher layout and these calls are not console-verified. They follow [LibreSSL 2.5.4 ssl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl.h) and [ssl_ciph.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_ciph.c), matching the version in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). Console modifications and other firmware layouts remain unverified. Protocol labels use the full 64-bit cipher ID, not the protocol flags; TLS 1.2 ciphers still report `TLSv1/SSLv3`, as in that release.
- [sk_num, sk_value, sk_pop, sk_pop_free](../../../core/libs/prx/libSceLibreSsl/src/Stack.cpp) (libSceLibreSsl) - the 32-byte `_STACK` layout (`num`, `data`, `sorted`, `num_alloc`, `comp`) and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/stack) `stack.h` and `stack.c`, the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214); nothing is checked on the console. `sk_pop_free` releases the element array and the stack with the guest `free`, as the module would through libc. No export of this module creates a stack yet, so only stacks the caller builds reach them. A null element array that LibreSSL would read through, and a null free function with a non-null element, throw where LibreSSL faults