Files
XboxCurl/libcurl/lib/vtls/bearssl.c
T
2026-02-16 08:59:39 -07:00

1669 lines
50 KiB
C

/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* BearSSL TLS backend for libcurl
* Designed for Xbox (Visual Studio 2003 / C89 compatible)
*
* This file implements the libcurl vtls interface using BearSSL.
* BearSSL is ideal for Xbox because:
* - Written in C89/C90 (VS 2003 compatible)
* - No dynamic memory allocation
* - Small footprint
* - No external dependencies
*
* Xbox-specific enhancements:
* - Trust anchors loaded from embedded CA database (750+ CAs)
* - Session caching for TLS resumption
* - Descriptive error messages
*
***************************************************************************/
#include "curl_setup.h"
#ifdef USE_BEARSSL
#include <bearssl.h>
#include "urldata.h"
#include "sendf.h"
#include "inet_pton.h"
#include "vtls.h"
#include "connect.h"
#include "select.h"
#include "multiif.h"
#ifdef _DEBUG
#define debug_log OutputDebugStringA
#else
#define debug_log(...) ((void)0)
#endif
/* Xbox-specific BearSSL integration */
#ifdef _XBOX
#include "bearssl_xbox.h"
#define BEARSSL_XBOX_INTEGRATION 1
#else
#define BEARSSL_XBOX_INTEGRATION 0
#endif
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
/*
* Xbox time validation callback for X.509 certificate checking.
*
* The Xbox's internal RTC is unreliable: no NTP (Xbox Live for original Xbox
* is defunct), CMOS battery may be dead after 20+ years, and the clock can
* reset to 2000-01-01 or similar. A wrong system clock causes BearSSL's
* time(NULL)-based date validation to reject perfectly valid certificates.
*
* We skip date validation entirely. Trust anchor verification and optional
* certificate pinning provide the real security guarantees.
*
* Return: 0 = within validity period (always)
*/
#ifdef _XBOX
static int bearssl_xbox_time_check(void *tctx,
uint32_t not_before_days, uint32_t not_before_seconds,
uint32_t not_after_days, uint32_t not_after_seconds)
{
(void)tctx;
(void)not_before_days;
(void)not_before_seconds;
(void)not_after_days;
(void)not_after_seconds;
return 0;
}
#endif
/* Secure cipher suites - ECDHE with AEAD only */
static const uint16_t bearssl_secure_suites[] = {
BR_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
BR_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
BR_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
BR_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
BR_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
};
#define BEARSSL_SECURE_SUITES_COUNT 6
/*
* ALPN protocol names for HTTP/1.1
*
* ALPN (Application-Layer Protocol Negotiation) is a TLS extension that allows
* the client and server to negotiate which application protocol to use over
* the encrypted connection. Modern HTTPS servers (especially CDNs like
* Cloudflare) require ALPN to be present in the TLS handshake.
*
* Without ALPN, many servers will:
* - Reject the connection outright
* - Assume HTTP/2 and fail when receiving HTTP/1.1
* - Reset the connection after handshake completes
*
* We advertise "http/1.1" since this libcurl build doesn't support HTTP/2.
*/
static const char *bearssl_alpn_protocols[] = { "http/1.1" };
#define BEARSSL_ALPN_PROTOCOLS_COUNT 1
/*
* "No Anchor" X509 validator - wraps br_x509_minimal but accepts all certs.
*
* This approach uses BearSSL's proven certificate parsing code (br_x509_minimal)
* but overrides the validation result to always succeed. This is safer than
* implementing our own certificate decoder.
*
* Used when SSL_VERIFYPEER is disabled.
*
* NOTE: We use a POINTER to the minimal context (not embedded) to avoid
* memory layout issues with the union in ssl_backend_data.
*/
typedef struct {
const br_x509_class *vtable;
br_x509_minimal_context *minimal; /* Pointer to minimal validator */
/* Certificate pinning: hash the leaf (first) certificate */
br_sha256_context pin_sha256; /* SHA-256 running hash of leaf cert */
unsigned char leaf_hash[32]; /* Final SHA-256 of leaf certificate */
int cert_index; /* 0 = leaf cert, increments per cert */
int leaf_hash_valid; /* 1 if leaf_hash has been computed */
char pin_hostname[256]; /* Hostname for pin lookup */
} br_x509_noanchor_context;
/* Forward the call to minimal validator */
static void xc_noanchor_start_chain(const br_x509_class **ctx,
const char *server_name)
{
br_x509_noanchor_context *xc = (br_x509_noanchor_context *)ctx;
#ifdef _XBOX
{
char dbg[256];
msnprintf(dbg, sizeof(dbg),
"BearSSL noanchor X509: start_chain called, ctx=%p, xc=%p, minimal=%p\n",
(void*)ctx, (void*)xc, (void*)xc->minimal);
debug_log(dbg);
if(xc->minimal) {
msnprintf(dbg, sizeof(dbg),
"BearSSL noanchor X509: server=%s, minimal->vtable=%p\n",
server_name ? server_name : "(null)",
(void*)xc->minimal->vtable);
debug_log(dbg);
}
}
#endif
if(!xc->minimal || !xc->minimal->vtable) {
debug_log("BearSSL noanchor X509: ERROR - minimal or vtable is NULL!\n");
return;
}
/* Reset pinning state for this new chain */
xc->cert_index = 0;
xc->leaf_hash_valid = 0;
memset(xc->leaf_hash, 0, sizeof(xc->leaf_hash));
if(server_name) {
strncpy(xc->pin_hostname, server_name, sizeof(xc->pin_hostname) - 1);
xc->pin_hostname[sizeof(xc->pin_hostname) - 1] = '\0';
}
else {
xc->pin_hostname[0] = '\0';
}
xc->minimal->vtable->start_chain(&xc->minimal->vtable, server_name);
debug_log("BearSSL noanchor X509: start_chain forwarded OK\n");
}
static void xc_noanchor_start_cert(const br_x509_class **ctx, uint32_t length)
{
br_x509_noanchor_context *xc = (br_x509_noanchor_context *)ctx;
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg), "BearSSL noanchor X509: start_cert[%d], length=%u\n",
xc->cert_index, (unsigned)length);
debug_log(dbg);
}
#endif
/* For the leaf certificate (index 0), start hashing for pin check */
if(xc->cert_index == 0) {
br_sha256_init(&xc->pin_sha256);
}
xc->minimal->vtable->start_cert(&xc->minimal->vtable, length);
}
static void xc_noanchor_append(const br_x509_class **ctx,
const unsigned char *buf, size_t len)
{
br_x509_noanchor_context *xc = (br_x509_noanchor_context *)ctx;
/* Feed leaf certificate bytes into SHA-256 for pinning */
if(xc->cert_index == 0) {
br_sha256_update(&xc->pin_sha256, buf, len);
}
xc->minimal->vtable->append(&xc->minimal->vtable, buf, len);
}
static void xc_noanchor_end_cert(const br_x509_class **ctx)
{
br_x509_noanchor_context *xc = (br_x509_noanchor_context *)ctx;
#ifdef _XBOX
{
char dbg[64];
msnprintf(dbg, sizeof(dbg), "BearSSL noanchor X509: end_cert[%d]\n",
xc->cert_index);
debug_log(dbg);
}
#endif
xc->minimal->vtable->end_cert(&xc->minimal->vtable);
/* Finalize the SHA-256 hash of the leaf certificate */
if(xc->cert_index == 0) {
br_sha256_out(&xc->pin_sha256, xc->leaf_hash);
xc->leaf_hash_valid = 1;
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg),
"BearSSL noanchor X509: leaf cert SHA-256: "
"%02X%02X%02X%02X%02X%02X%02X%02X...\n",
xc->leaf_hash[0], xc->leaf_hash[1],
xc->leaf_hash[2], xc->leaf_hash[3],
xc->leaf_hash[4], xc->leaf_hash[5],
xc->leaf_hash[6], xc->leaf_hash[7]);
debug_log(dbg);
}
#endif
}
xc->cert_index++;
#ifdef _XBOX
/* Log the minimal validator's state after processing certificate */
{
char dbg[256];
msnprintf(dbg, sizeof(dbg),
"BearSSL noanchor X509: minimal->err=%d, pkey.key_type=%d\n",
xc->minimal->err, xc->minimal->pkey.key_type);
debug_log(dbg);
if(xc->minimal->pkey.key_type == BR_KEYTYPE_RSA) {
msnprintf(dbg, sizeof(dbg),
"BearSSL noanchor X509: RSA key - n=%p nlen=%u e=%p elen=%u\n",
(void*)xc->minimal->pkey.key.rsa.n,
(unsigned)xc->minimal->pkey.key.rsa.nlen,
(void*)xc->minimal->pkey.key.rsa.e,
(unsigned)xc->minimal->pkey.key.rsa.elen);
debug_log(dbg);
}
else if(xc->minimal->pkey.key_type == BR_KEYTYPE_EC) {
msnprintf(dbg, sizeof(dbg),
"BearSSL noanchor X509: EC key - curve=%d q=%p qlen=%u\n",
xc->minimal->pkey.key.ec.curve,
(void*)xc->minimal->pkey.key.ec.q,
(unsigned)xc->minimal->pkey.key.ec.qlen);
debug_log(dbg);
}
}
#endif
}
static unsigned xc_noanchor_end_chain(const br_x509_class **ctx)
{
br_x509_noanchor_context *xc = (br_x509_noanchor_context *)ctx;
unsigned result;
/* Call the minimal validator's end_chain */
result = xc->minimal->vtable->end_chain(&xc->minimal->vtable);
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg),
"BearSSL noanchor X509: end_chain, minimal returned %u (err=%d)\n",
result, xc->minimal->err);
debug_log(dbg);
}
#endif
/*
* Override the result: accept certificates in insecure mode.
*
* When SSL_VERIFYPEER is disabled, we accept all validation errors:
* - BR_ERR_X509_OK (32) - success
* - BR_ERR_X509_EXPIRED (54) - certificate expired or not yet valid
* - BR_ERR_X509_DN_MISMATCH (55) - issuer/subject DN mismatch
* - BR_ERR_X509_BAD_SERVER_NAME (56) - server name doesn't match cert
* - BR_ERR_X509_NOT_TRUSTED (62) - no matching trust anchor
*
* We only fail on actual parsing errors (33-52) which indicate a
* malformed certificate that cannot be used.
*/
if(result == BR_ERR_X509_OK ||
result == BR_ERR_X509_EXPIRED ||
result == BR_ERR_X509_DN_MISMATCH ||
result == BR_ERR_X509_BAD_SERVER_NAME ||
result == BR_ERR_X509_CRITICAL_EXTENSION ||
result == BR_ERR_X509_NOT_CA ||
result == BR_ERR_X509_FORBIDDEN_KEY_USAGE ||
result == BR_ERR_X509_WEAK_PUBLIC_KEY ||
result == BR_ERR_X509_NOT_TRUSTED) {
/*
* Certificate pinning check: if a pin is registered for this hostname,
* verify the leaf certificate hash matches. This runs even in insecure
* mode because pinning is an explicit trust decision by the application.
*/
#if BEARSSL_XBOX_INTEGRATION
if(xc->leaf_hash_valid && xc->pin_hostname[0] != '\0') {
if(!bearssl_xbox_pin_verify_hash(xc->pin_hostname, xc->leaf_hash)) {
#ifdef _XBOX
debug_log("BearSSL noanchor X509: CERTIFICATE PIN MISMATCH - rejecting!\n");
#endif
return BR_ERR_X509_NOT_TRUSTED;
}
}
#endif
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg),
"BearSSL noanchor X509: accepting certificate (insecure mode, result=%u)\n",
result);
debug_log(dbg);
}
#endif
return 0; /* Success */
}
/* For actual parsing errors (bad certificate format, etc.), we still fail */
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg),
"BearSSL noanchor X509: certificate parsing error %u\n", result);
debug_log(dbg);
}
#endif
return result;
}
static const br_x509_pkey *xc_noanchor_get_pkey(
const br_x509_class *const *ctx, unsigned *usages)
{
br_x509_noanchor_context *xc = (br_x509_noanchor_context *)ctx;
const br_x509_pkey *pk;
/* Get the public key from the minimal validator */
pk = xc->minimal->vtable->get_pkey(&xc->minimal->vtable, usages);
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg),
"BearSSL noanchor X509: get_pkey returned %p (err=%d, key_type=%d)\n",
(void*)pk, xc->minimal->err, xc->minimal->pkey.key_type);
debug_log(dbg);
}
#endif
/*
* The minimal validator only returns the key if err is OK (32) or
* NOT_TRUSTED (62). For other validation errors like BAD_SERVER_NAME (56),
* EXPIRED (54), etc., it returns NULL even though the key was parsed.
*
* In insecure mode, we need to return the key for any validation error
* as long as the key itself was successfully parsed.
*/
if(!pk) {
/*
* Minimal validator didn't return key. Check if the key was parsed
* successfully - if so, return it directly. This handles validation
* errors like BAD_SERVER_NAME where the cert is valid but doesn't
* match requirements we're choosing to ignore.
*/
if(xc->minimal->pkey.key_type == BR_KEYTYPE_RSA ||
xc->minimal->pkey.key_type == BR_KEYTYPE_EC) {
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg),
"BearSSL noanchor X509: returning key directly (err=%d bypassed)\n",
xc->minimal->err);
debug_log(dbg);
}
#endif
if(usages)
*usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN;
return &xc->minimal->pkey;
}
#ifdef _XBOX
debug_log("BearSSL noanchor X509: ERROR - no valid key available!\n");
#endif
}
return pk;
}
static const br_x509_class br_x509_noanchor_vtable = {
sizeof(br_x509_noanchor_context),
xc_noanchor_start_chain,
xc_noanchor_start_cert,
xc_noanchor_append,
xc_noanchor_end_cert,
xc_noanchor_end_chain,
xc_noanchor_get_pkey
};
/*
* Legacy insecure validator kept for reference but not used.
* The noanchor approach above is preferred.
*/
#if 0 /* DISABLED - using noanchor instead */
typedef struct {
const br_x509_class *vtable;
br_x509_decoder_context decoder;
br_x509_pkey pkey;
unsigned char pkey_data[BR_EC_KBUF_PUB_MAX_SIZE];
unsigned char rsa_n[512];
unsigned char rsa_e[8];
int first_cert;
int pkey_valid;
uint32_t expected_len;
uint32_t received_len;
} br_x509_insecure_context;
static void xc_insecure_start_chain(const br_x509_class **ctx,
const char *server_name)
{
br_x509_insecure_context *xc = (br_x509_insecure_context *)ctx;
(void)server_name;
xc->first_cert = 1;
xc->pkey_valid = 0;
}
/* ... rest of insecure implementation ... */
#endif /* DISABLED */
/* BearSSL backend data structure */
struct ssl_backend_data {
br_ssl_client_context sc;
br_x509_minimal_context xc_minimal; /* Minimal X509 validator */
br_x509_noanchor_context xc_noanchor; /* Noanchor wrapper (small, just vtable + pointer) */
int using_noanchor; /* Are we using the noanchor (no-verify) validator? */
unsigned char iobuf[BR_SSL_BUFSIZE_BIDI];
br_sslio_context ioc;
int active; /* Is the SSL connection active? */
int session_resumed; /* Was session resumed? */
unsigned char resume_master_secret[48]; /* Master secret from resume attempt */
int io_error; /* Have we had a socket I/O error? Skip graceful close if so */
unsigned char leaf_cert_hash[32]; /* SHA-256 of leaf cert (from X.509 callback) */
int leaf_cert_hash_valid; /* 1 if hash was captured */
char hostname[256]; /* Hostname for session caching */
#if BEARSSL_XBOX_INTEGRATION
br_x509_trust_anchor *file_anchors; /* Per-connection CAs from CURLOPT_CAINFO */
size_t file_anchor_count;
#endif
};
#define BACKEND connssl->backend
/* Forward declarations for send/recv functions */
static Curl_recv bearssl_recv;
static Curl_send bearssl_send;
/* Global state for trust anchor initialization */
static int bearssl_trust_anchors_loaded = 0;
#if BEARSSL_XBOX_INTEGRATION
static int bearssl_trust_store_updated = 0;
#endif
/* Low-level socket read callback for BearSSL */
static int
sock_read(void *ctx, unsigned char *buf, size_t len)
{
curl_socket_t fd = *(curl_socket_t *)ctx;
ssize_t rlen;
/*
* Simple blocking read - no retry loop.
* The socket should already be in blocking mode, and libcurl manages
* timeouts at a higher level. We just do a single recv() call.
*/
rlen = recv(fd, (char *)buf, (int)len, 0);
if(rlen > 0) {
/* Success - got data */
#ifdef BEARSSL_VERBOSE_DEBUG
{
char dbg[64];
msnprintf(dbg, sizeof(dbg), "BearSSL sock_read: got %d bytes\n", (int)rlen);
debug_log(dbg);
}
#endif
return (int)rlen;
}
if(rlen == 0) {
/* Connection closed by peer */
#ifdef BEARSSL_VERBOSE_DEBUG
debug_log("BearSSL sock_read: connection closed by peer\n");
#endif
return -1;
}
/* rlen < 0 - error */
{
int err = SOCKERRNO;
#ifdef BEARSSL_VERBOSE_DEBUG
{
char dbg[64];
msnprintf(dbg, sizeof(dbg), "BearSSL sock_read: recv error %d\n", err);
debug_log(dbg);
}
#endif
/* For EINTR, we could retry, but it's simpler to let the caller handle it */
if(err == EWOULDBLOCK || err == EAGAIN) {
/* Would block - return 0 to indicate no data available yet */
/* BearSSL will handle this appropriately */
return 0;
}
/* Real error - return -1 */
return -1;
}
}
/* Low-level socket write callback for BearSSL */
static int
sock_write(void *ctx, const unsigned char *buf, size_t len)
{
curl_socket_t fd = *(curl_socket_t *)ctx;
ssize_t wlen;
/*
* Simple blocking write - no retry loop.
* The socket should already be in blocking mode, and libcurl manages
* timeouts at a higher level. We just do a single send() call.
*/
wlen = send(fd, (const char *)buf, (int)len, 0);
if(wlen > 0) {
/* Success - data sent */
#ifdef BEARSSL_VERBOSE_DEBUG
{
char dbg[64];
msnprintf(dbg, sizeof(dbg), "BearSSL sock_write: sent %d bytes\n", (int)wlen);
debug_log(dbg);
}
#endif
return (int)wlen;
}
if(wlen == 0) {
/* No data sent - treat as would-block */
return 0;
}
/* wlen < 0 - error */
{
int err = SOCKERRNO;
#ifdef BEARSSL_VERBOSE_DEBUG
{
char dbg[64];
msnprintf(dbg, sizeof(dbg), "BearSSL sock_write: send error %d\n", err);
debug_log(dbg);
}
#endif
if(err == EWOULDBLOCK || err == EAGAIN) {
/* Would block - return 0 to indicate no data sent yet */
return 0;
}
/* Real error - return -1 */
return -1;
}
}
/* Get descriptive error message for BearSSL error code */
static const char *
bearssl_error_string(int err)
{
#if BEARSSL_XBOX_INTEGRATION
return bearssl_xbox_error_string(err);
#else
/* Fallback error messages for non-Xbox builds */
switch(err) {
case BR_ERR_OK: return "Success";
case BR_ERR_BAD_PARAM: return "Bad parameter";
case BR_ERR_BAD_STATE: return "Invalid state";
case BR_ERR_UNSUPPORTED_VERSION: return "Unsupported TLS version";
case BR_ERR_BAD_VERSION: return "Bad TLS version";
case BR_ERR_TOO_LARGE: return "Record too large";
case BR_ERR_BAD_MAC: return "MAC verification failed";
case BR_ERR_NO_RANDOM: return "No random seed";
case BR_ERR_UNKNOWN_TYPE: return "Unknown record type";
case BR_ERR_UNEXPECTED: return "Unexpected message";
case BR_ERR_BAD_CCS: return "Bad ChangeCipherSpec";
case BR_ERR_BAD_ALERT: return "Bad alert";
case BR_ERR_BAD_HANDSHAKE: return "Bad handshake message";
case BR_ERR_OVERSIZED_ID: return "Oversized ID";
case BR_ERR_BAD_CIPHER_SUITE: return "Bad cipher suite";
case BR_ERR_BAD_COMPRESSION: return "Bad compression";
case BR_ERR_BAD_FRAGLEN: return "Bad fragment length";
case BR_ERR_BAD_SECRENEG: return "Bad secure renegotiation";
case BR_ERR_EXTRA_EXTENSION: return "Extra extension";
case BR_ERR_BAD_SNI: return "Bad SNI";
case BR_ERR_BAD_HELLO_DONE: return "Bad HelloDone";
case BR_ERR_LIMIT_EXCEEDED: return "Limit exceeded";
case BR_ERR_BAD_FINISHED: return "Bad Finished message";
case BR_ERR_RESUME_MISMATCH: return "Session resume mismatch";
case BR_ERR_INVALID_ALGORITHM: return "Invalid algorithm";
case BR_ERR_BAD_SIGNATURE: return "Bad signature";
case BR_ERR_WRONG_KEY_USAGE: return "Wrong key usage";
case BR_ERR_NO_CLIENT_AUTH: return "No client auth";
case BR_ERR_IO: return "I/O error";
default:
if(err >= 256 && err < 512)
return "Received fatal alert from server";
if(err >= 512)
return "Sent fatal alert to server";
return "Unknown SSL error";
}
#endif
}
static int Curl_bearssl_init(void)
{
#if BEARSSL_XBOX_INTEGRATION
/* Initialize trust anchors from Xbox certificate store */
if(!bearssl_trust_anchors_loaded) {
bearssl_xbox_init_trust_anchors();
bearssl_xbox_session_init();
bearssl_trust_anchors_loaded = 1;
}
#endif
return 1;
}
static void Curl_bearssl_cleanup(void)
{
#if BEARSSL_XBOX_INTEGRATION
if(bearssl_trust_anchors_loaded) {
bearssl_xbox_session_shutdown();
bearssl_xbox_cleanup_trust_anchors();
bearssl_trust_anchors_loaded = 0;
bearssl_trust_store_updated = 0;
}
#endif
}
static size_t Curl_bearssl_version(char *buffer, size_t size)
{
return msnprintf(buffer, size, "BearSSL/0.6-Xbox");
}
static CURLcode Curl_bearssl_random(struct Curl_easy *data,
unsigned char *entropy, size_t length)
{
br_hmac_drbg_context rng;
br_prng_seeder seeder;
(void)data;
seeder = br_prng_seeder_system(NULL);
if(!seeder) {
return CURLE_SSL_CONNECT_ERROR;
}
br_hmac_drbg_init(&rng, &br_sha256_vtable, NULL, 0);
if(!seeder(&rng.vtable)) {
return CURLE_SSL_CONNECT_ERROR;
}
br_hmac_drbg_generate(&rng, entropy, length);
return CURLE_OK;
}
static CURLcode
bearssl_connect_step1(struct connectdata *conn, int sockindex)
{
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
curl_socket_t sockfd = conn->sock[sockindex];
const char *hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
conn->host.name;
br_x509_trust_anchor *trust_anchors = NULL;
size_t trust_anchor_count = 0;
int session_resume = 0;
#if BEARSSL_XBOX_INTEGRATION
br_ssl_session_parameters session_params;
#endif
/*
* NOTE: BACKEND memory is pre-allocated by the vtls layer in url.c as part
* of a single contiguous block for all SSL contexts. We must NOT allocate
* or free it ourselves - that would corrupt the heap.
*
* See url.c:allocate_conn() which does:
* conn->ssl[0].backend = (void *)ssl;
* conn->ssl[1].backend = (void *)(ssl + sslsize);
* conn->proxy_ssl[0].backend = (void *)(ssl + 2 * sslsize);
* conn->proxy_ssl[1].backend = (void *)(ssl + 3 * sslsize);
*/
DEBUGASSERT(BACKEND != NULL);
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg),
"BearSSL step1: Using pre-allocated backend at %p, size=%u bytes\n",
(void*)BACKEND, (unsigned)sizeof(struct ssl_backend_data));
debug_log(dbg);
}
#endif
/* Clear the backend structure for fresh initialization */
memset(BACKEND, 0, sizeof(struct ssl_backend_data));
/* Store hostname for session caching */
strncpy(BACKEND->hostname, hostname, sizeof(BACKEND->hostname) - 1);
BACKEND->hostname[sizeof(BACKEND->hostname) - 1] = '\0';
BACKEND->session_resumed = 0;
#if BEARSSL_XBOX_INTEGRATION
/* Auto-download cacert.pem on first connection if not on disk.
* Done here (not in Curl_bearssl_init) because XNet needs time
* after DHCP before TCP connects work reliably. */
if(!bearssl_trust_store_updated) {
bearssl_trust_store_updated = 1;
bearssl_xbox_update_trust_store();
}
#endif
/* Check if certificate verification is disabled */
if(!SSL_CONN_CONFIG(verifypeer)) {
/*
* No verification - use the "noanchor" validator that wraps br_x509_minimal
* but accepts all certificates. This leverages BearSSL's proven certificate
* parsing code while bypassing the trust validation.
*/
debug_log("BearSSL: Certificate verification DISABLED - using noanchor validator\n");
BACKEND->using_noanchor = 1;
#ifdef _XBOX
{
char dbg[512];
msnprintf(dbg, sizeof(dbg),
"BearSSL: BACKEND=%p, sizeof(ssl_backend_data)=%u\n",
(void*)BACKEND, (unsigned)sizeof(struct ssl_backend_data));
debug_log(dbg);
msnprintf(dbg, sizeof(dbg),
"BearSSL: sizeof(br_ssl_client_context)=%u\n",
(unsigned)sizeof(br_ssl_client_context));
debug_log(dbg);
msnprintf(dbg, sizeof(dbg),
"BearSSL: sizeof(br_x509_minimal_context)=%u\n",
(unsigned)sizeof(br_x509_minimal_context));
debug_log(dbg);
msnprintf(dbg, sizeof(dbg),
"BearSSL: &BACKEND->sc=%p, &BACKEND->xc_minimal=%p\n",
(void*)&BACKEND->sc, (void*)&BACKEND->xc_minimal);
debug_log(dbg);
}
#endif
debug_log("BearSSL: Calling br_ssl_client_init_full...\n");
/*
* Initialize the SSL client with the minimal X509 validator.
* We pass NULL trust anchors - the minimal validator will fail with
* NOT_TRUSTED, but our noanchor wrapper overrides that to accept anyway.
*/
br_ssl_client_init_full(&BACKEND->sc, &BACKEND->xc_minimal, NULL, 0);
debug_log("BearSSL: br_ssl_client_init_full done\n");
#ifdef _XBOX
{
char dbg[256];
msnprintf(dbg, sizeof(dbg),
"BearSSL: xc_minimal at %p, xc_minimal.vtable = %p\n",
(void*)&BACKEND->xc_minimal,
(void*)BACKEND->xc_minimal.vtable);
debug_log(dbg);
}
#endif
/* Set up the noanchor wrapper to point to the minimal validator */
BACKEND->xc_noanchor.vtable = &br_x509_noanchor_vtable;
BACKEND->xc_noanchor.minimal = &BACKEND->xc_minimal;
debug_log("BearSSL: Setting X509 engine to noanchor wrapper...\n");
#ifdef _XBOX
{
char dbg[256];
msnprintf(dbg, sizeof(dbg),
"BearSSL: xc_noanchor at %p, vtable=%p, minimal=%p\n",
(void*)&BACKEND->xc_noanchor,
(void*)BACKEND->xc_noanchor.vtable,
(void*)BACKEND->xc_noanchor.minimal);
debug_log(dbg);
}
#endif
/* Replace the X509 engine with our noanchor validator wrapper */
br_ssl_engine_set_x509(&BACKEND->sc.eng, &BACKEND->xc_noanchor.vtable);
debug_log("BearSSL: Noanchor X509 validator installed\n");
}
else {
#if BEARSSL_XBOX_INTEGRATION
{
/* Check for CURLOPT_CAINFO or default Xbox CA path */
const char *cafile = SSL_CONN_CONFIG(CAfile);
/* If no explicit CA file, try the default Xbox TDATA path */
if(!cafile || !cafile[0]) {
cafile = BEARSSL_XBOX_DEFAULT_CA_FILE;
}
/* Try loading CA bundle from PEM file */
if(bearssl_xbox_load_ca_file(cafile,
&BACKEND->file_anchors, &BACKEND->file_anchor_count) == 0) {
trust_anchors = BACKEND->file_anchors;
trust_anchor_count = BACKEND->file_anchor_count;
infof(data, "BearSSL: Loaded %zu trust anchors from %s\n",
trust_anchor_count, cafile);
}
/* Fall back to compiled-in trust anchors */
if(!trust_anchors) {
trust_anchors = bearssl_xbox_get_trust_anchors();
trust_anchor_count = bearssl_xbox_get_trust_anchor_count();
}
}
if(!trust_anchors || trust_anchor_count == 0) {
failf(data, "BearSSL: No trust anchors available for certificate verification");
return CURLE_SSL_CACERT_BADFILE;
}
infof(data, "BearSSL: Using %zu trust anchors for verification\n",
trust_anchor_count);
#else
/* No trust anchors available - cannot verify */
failf(data, "BearSSL: Certificate verification requested but no trust anchors available");
return CURLE_SSL_CACERT_BADFILE;
#endif
BACKEND->using_noanchor = 0;
/* Initialize the BearSSL client context with trust anchors */
br_ssl_client_init_full(&BACKEND->sc, &BACKEND->xc_minimal,
trust_anchors, trust_anchor_count);
#ifdef _XBOX
/* Skip certificate date validation - Xbox clock is unreliable */
br_x509_minimal_set_time_callback(&BACKEND->xc_minimal,
NULL, bearssl_xbox_time_check);
#endif
}
debug_log("BearSSL: Setting protocol versions...\n");
/* Set protocol versions based on CURLOPT_SSLVERSION */
{
/*
* Default to TLS 1.2 minimum for security.
* TLS 1.0 and 1.1 are deprecated and have known vulnerabilities.
*/
int min_ver = BR_TLS12;
int max_ver = BR_TLS12;
switch(SSL_CONN_CONFIG(version)) {
case CURL_SSLVERSION_DEFAULT:
/* Default: TLS 1.2 only (most compatible + secure) */
min_ver = BR_TLS12;
break;
case CURL_SSLVERSION_TLSv1:
case CURL_SSLVERSION_TLSv1_0:
/* Allow TLS 1.0 if explicitly requested (legacy support) */
min_ver = BR_TLS10;
break;
case CURL_SSLVERSION_TLSv1_1:
min_ver = BR_TLS11;
break;
case CURL_SSLVERSION_TLSv1_2:
min_ver = BR_TLS12;
break;
case CURL_SSLVERSION_TLSv1_3:
/* BearSSL doesn't support TLS 1.3 */
failf(data, "BearSSL does not support TLS 1.3");
return CURLE_SSL_CONNECT_ERROR;
default:
failf(data, "Unsupported SSL version");
return CURLE_SSL_CONNECT_ERROR;
}
br_ssl_engine_set_versions(&BACKEND->sc.eng, min_ver, max_ver);
}
debug_log("BearSSL: Protocol versions set\n");
/* Use secure cipher suites only */
br_ssl_engine_set_suites(&BACKEND->sc.eng,
bearssl_secure_suites, BEARSSL_SECURE_SUITES_COUNT);
debug_log("BearSSL: Cipher suites set\n");
/*
* Set ALPN (Application-Layer Protocol Negotiation) protocols.
*
* This tells the server we want to speak HTTP/1.1. Without ALPN, modern
* servers like Cloudflare may reset the connection after handshake because
* they don't know what protocol the client expects.
*/
br_ssl_engine_set_protocol_names(&BACKEND->sc.eng,
bearssl_alpn_protocols,
BEARSSL_ALPN_PROTOCOLS_COUNT);
debug_log("BearSSL: ALPN protocols set (http/1.1)\n");
/* Set I/O buffer */
br_ssl_engine_set_buffer(&BACKEND->sc.eng, BACKEND->iobuf,
sizeof(BACKEND->iobuf), 1);
debug_log("BearSSL: I/O buffer set\n");
#if BEARSSL_XBOX_INTEGRATION
/* Try to resume previous session */
if(bearssl_xbox_session_get(hostname, &session_params)) {
br_ssl_engine_set_session_parameters(&BACKEND->sc.eng, &session_params);
memcpy(BACKEND->resume_master_secret, session_params.master_secret, 48);
session_resume = 1;
infof(data, "BearSSL: Attempting session resumption for %s\n", hostname);
}
#endif
debug_log("BearSSL: Calling br_ssl_client_reset...\n");
/* Reset the client context for the new connection */
br_ssl_client_reset(&BACKEND->sc, hostname, session_resume);
debug_log("BearSSL: br_ssl_client_reset done\n");
#ifdef _XBOX
{
char dbg[256];
msnprintf(dbg, sizeof(dbg),
"BearSSL step1: hostname=%s, socket=%d, sockindex=%d\n",
hostname, (int)conn->sock[sockindex], sockindex);
debug_log(dbg);
/* Verify socket is valid */
if(conn->sock[sockindex] == CURL_SOCKET_BAD) {
debug_log("BearSSL step1: ERROR - socket is INVALID!\n");
}
}
#endif
debug_log("BearSSL: Calling br_sslio_init...\n");
/* Initialize the I/O wrapper */
br_sslio_init(&BACKEND->ioc, &BACKEND->sc.eng,
sock_read, &conn->sock[sockindex],
sock_write, &conn->sock[sockindex]);
debug_log("BearSSL: br_sslio_init done\n");
/* Note: BACKEND->active is set in step3 after handshake completes */
connssl->connecting_state = ssl_connect_2;
debug_log("BearSSL step1: complete, moving to step2\n");
return CURLE_OK;
}
static CURLcode
bearssl_connect_step2(struct connectdata *conn, int sockindex)
{
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
unsigned state;
int err;
state = br_ssl_engine_current_state(&BACKEND->sc.eng);
#ifdef _XBOX
{
static int step2_call_count = 0;
char dbg[256];
step2_call_count++;
msnprintf(dbg, sizeof(dbg),
"BearSSL step2 [%d]: state=0x%x (CLOSED=%d SENDREC=%d RECVREC=%d SENDAPP=%d)\n",
step2_call_count, state,
(state & BR_SSL_CLOSED) ? 1 : 0,
(state & BR_SSL_SENDREC) ? 1 : 0,
(state & BR_SSL_RECVREC) ? 1 : 0,
(state & BR_SSL_SENDAPP) ? 1 : 0);
debug_log(dbg);
}
#endif
/* Check for errors */
err = br_ssl_engine_last_error(&BACKEND->sc.eng);
if(err != BR_ERR_OK) {
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg), "BearSSL step2: engine error %d\n", err);
debug_log(dbg);
}
#endif
failf(data, "BearSSL: %s (error %d)", bearssl_error_string(err), err);
return CURLE_SSL_CONNECT_ERROR;
}
/* Check if handshake is complete */
if(state & BR_SSL_SENDAPP) {
/* Handshake complete, application data can be sent */
connssl->connecting_state = ssl_connect_3;
return CURLE_OK;
}
/* Handshake still in progress */
if(state & BR_SSL_SENDREC) {
/* Need to send data */
int ret;
debug_log("BearSSL step2: flushing send buffer...\n");
ret = br_sslio_flush(&BACKEND->ioc);
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg), "BearSSL step2: flush returned %d\n", ret);
debug_log(dbg);
}
#endif
if(ret < 0) {
err = br_ssl_engine_last_error(&BACKEND->sc.eng);
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg), "BearSSL step2: flush error, engine err=%d\n", err);
debug_log(dbg);
}
#endif
if(err != BR_ERR_OK) {
failf(data, "BearSSL handshake: %s (error %d)",
bearssl_error_string(err), err);
return CURLE_SSL_CONNECT_ERROR;
}
}
}
if(state & BR_SSL_RECVREC) {
/* Need to receive data - handled by sslio layer */
unsigned char tmp[1];
int ret;
debug_log("BearSSL step2: attempting to receive...\n");
ret = br_sslio_read(&BACKEND->ioc, tmp, 0);
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg), "BearSSL step2: read returned %d\n", ret);
debug_log(dbg);
}
#endif
if(ret < 0) {
err = br_ssl_engine_last_error(&BACKEND->sc.eng);
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg), "BearSSL step2: read error, engine err=%d\n", err);
debug_log(dbg);
}
#endif
if(err != BR_ERR_OK && err != BR_ERR_IO) {
failf(data, "BearSSL handshake: %s (error %d)",
bearssl_error_string(err), err);
return CURLE_SSL_CONNECT_ERROR;
}
}
}
/* Check state again */
state = br_ssl_engine_current_state(&BACKEND->sc.eng);
if(state & BR_SSL_SENDAPP) {
connssl->connecting_state = ssl_connect_3;
return CURLE_OK;
}
/* Still connecting */
connssl->connecting_state = ssl_connect_2;
return CURLE_OK;
}
static CURLcode
bearssl_connect_step3(struct connectdata *conn, int sockindex)
{
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
br_ssl_session_parameters session_params;
unsigned version;
const char *version_str;
/* Get negotiated TLS version */
version = br_ssl_engine_get_version(&BACKEND->sc.eng);
switch(version) {
case BR_TLS10: version_str = "TLS 1.0"; break;
case BR_TLS11: version_str = "TLS 1.1"; break;
case BR_TLS12: version_str = "TLS 1.2"; break;
default: version_str = "Unknown"; break;
}
/* Log ALPN negotiation result */
{
const char *alpn_protocol;
alpn_protocol = br_ssl_engine_get_selected_protocol(&BACKEND->sc.eng);
if(alpn_protocol) {
infof(data, "BearSSL: ALPN negotiated protocol: %s\n", alpn_protocol);
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg), "BearSSL: ALPN selected: %s\n", alpn_protocol);
debug_log(dbg);
}
#endif
}
else {
infof(data, "BearSSL: ALPN not negotiated (server may not support it)\n");
#ifdef _XBOX
debug_log("BearSSL: ALPN not negotiated by server\n");
#endif
}
}
/* Check if session was resumed by comparing master secrets.
* Both ID-based and ticket-based (RFC 5077) resumption reuse the
* original master secret; a full handshake always derives a new one. */
br_ssl_engine_get_session_parameters(&BACKEND->sc.eng, &session_params);
if(memcmp(BACKEND->resume_master_secret, session_params.master_secret,
48) == 0
&& session_params.master_secret[0] != 0) {
BACKEND->session_resumed = 1;
}
infof(data, "BearSSL: %s connection using %s\n",
BACKEND->session_resumed ? "Resumed" : "New", version_str);
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg), "BearSSL: %s connection using %s\n",
BACKEND->session_resumed ? "Resumed" : "New", version_str);
debug_log(dbg);
}
#endif
#if BEARSSL_XBOX_INTEGRATION
/* Store session for future resumption.
* Store if we have a session ID or a session ticket (RFC 5077). */
if(session_params.session_id_len > 0
|| session_params.session_ticket_len > 0) {
bearssl_xbox_session_store(BACKEND->hostname, &session_params);
}
/* Copy captured leaf certificate hash from X.509 callback.
* On resumed sessions, no X.509 validation runs so the hash won't
* be available — this is expected and not an error. */
if(BACKEND->using_noanchor && BACKEND->xc_noanchor.leaf_hash_valid) {
memcpy(BACKEND->leaf_cert_hash, BACKEND->xc_noanchor.leaf_hash, 32);
BACKEND->leaf_cert_hash_valid = 1;
#ifdef _XBOX
{
char dbg[256];
msnprintf(dbg, sizeof(dbg),
"BearSSL step3: Leaf cert SHA-256: "
"%02X%02X%02X%02X %02X%02X%02X%02X "
"%02X%02X%02X%02X %02X%02X%02X%02X "
"%02X%02X%02X%02X %02X%02X%02X%02X "
"%02X%02X%02X%02X %02X%02X%02X%02X\n",
BACKEND->leaf_cert_hash[0], BACKEND->leaf_cert_hash[1],
BACKEND->leaf_cert_hash[2], BACKEND->leaf_cert_hash[3],
BACKEND->leaf_cert_hash[4], BACKEND->leaf_cert_hash[5],
BACKEND->leaf_cert_hash[6], BACKEND->leaf_cert_hash[7],
BACKEND->leaf_cert_hash[8], BACKEND->leaf_cert_hash[9],
BACKEND->leaf_cert_hash[10], BACKEND->leaf_cert_hash[11],
BACKEND->leaf_cert_hash[12], BACKEND->leaf_cert_hash[13],
BACKEND->leaf_cert_hash[14], BACKEND->leaf_cert_hash[15],
BACKEND->leaf_cert_hash[16], BACKEND->leaf_cert_hash[17],
BACKEND->leaf_cert_hash[18], BACKEND->leaf_cert_hash[19],
BACKEND->leaf_cert_hash[20], BACKEND->leaf_cert_hash[21],
BACKEND->leaf_cert_hash[22], BACKEND->leaf_cert_hash[23],
BACKEND->leaf_cert_hash[24], BACKEND->leaf_cert_hash[25],
BACKEND->leaf_cert_hash[26], BACKEND->leaf_cert_hash[27],
BACKEND->leaf_cert_hash[28], BACKEND->leaf_cert_hash[29],
BACKEND->leaf_cert_hash[30], BACKEND->leaf_cert_hash[31]);
debug_log(dbg);
}
#endif
}
else if(BACKEND->session_resumed) {
#ifdef _XBOX
debug_log("BearSSL step3: Leaf cert hash not available (resumed session)\n");
#endif
}
#endif
/* Connection is now established - mark SSL as active for proper cleanup */
/*
* CRITICAL: Register our SSL send/recv functions with libcurl.
*
* libcurl uses conn->recv[sockindex] and conn->send[sockindex] for all
* data transfer. If we don't set these to our BearSSL functions, libcurl
* will send raw unencrypted data over the TLS connection, causing the
* server to reset the connection.
*
* This was the root cause of the "connection reset after handshake" bug.
*/
conn->recv[sockindex] = bearssl_recv;
conn->send[sockindex] = bearssl_send;
#ifdef _XBOX
debug_log("BearSSL: Registered send/recv handlers with libcurl\n");
#endif
BACKEND->active = 1;
connssl->connecting_state = ssl_connect_done;
connssl->state = ssl_connection_complete;
return CURLE_OK;
}
static CURLcode
bearssl_connect_common(struct connectdata *conn, int sockindex,
bool nonblocking, bool *done)
{
CURLcode result;
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
curl_socket_t sockfd = conn->sock[sockindex];
long timeout_ms;
int what;
/* Check if already connected */
if(ssl_connection_complete == connssl->state) {
*done = TRUE;
return CURLE_OK;
}
if(ssl_connect_1 == connssl->connecting_state) {
timeout_ms = Curl_timeleft(data, NULL, TRUE);
if(timeout_ms < 0) {
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEDOUT;
}
result = bearssl_connect_step1(conn, sockindex);
if(result)
return result;
}
while(ssl_connect_2 == connssl->connecting_state) {
timeout_ms = Curl_timeleft(data, NULL, TRUE);
if(timeout_ms < 0) {
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEDOUT;
}
/* Check socket readiness */
what = Curl_socket_check(sockfd, CURL_SOCKET_BAD, sockfd,
nonblocking ? 0 : timeout_ms);
if(what < 0) {
failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
return CURLE_SSL_CONNECT_ERROR;
}
else if(0 == what) {
if(nonblocking) {
*done = FALSE;
return CURLE_OK;
}
else {
failf(data, "SSL connection timeout");
return CURLE_OPERATION_TIMEDOUT;
}
}
result = bearssl_connect_step2(conn, sockindex);
if(result)
return result;
}
if(ssl_connect_3 == connssl->connecting_state) {
result = bearssl_connect_step3(conn, sockindex);
if(result)
return result;
}
if(ssl_connect_done == connssl->connecting_state) {
connssl->state = ssl_connection_complete;
*done = TRUE;
}
else {
*done = FALSE;
}
return CURLE_OK;
}
static CURLcode
Curl_bearssl_connect_blocking(struct connectdata *conn, int sockindex)
{
bool done = FALSE;
CURLcode result;
result = bearssl_connect_common(conn, sockindex, FALSE, &done);
if(result)
return result;
DEBUGASSERT(done);
return CURLE_OK;
}
static CURLcode
Curl_bearssl_connect_nonblocking(struct connectdata *conn, int sockindex,
bool *done)
{
return bearssl_connect_common(conn, sockindex, TRUE, done);
}
static void Curl_bearssl_close(struct connectdata *conn, int sockindex)
{
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg),
"BearSSL close: sockindex=%d, state=%d, backend=%p\n",
sockindex, (int)connssl->state, (void*)BACKEND);
debug_log(dbg);
}
#endif
/* Free per-connection file-loaded trust anchors even if handshake didn't
* complete (e.g. step1 failed after loading CAs but before connecting).
* Must happen before the early-return guard below. */
#if BEARSSL_XBOX_INTEGRATION
if(BACKEND && BACKEND->file_anchors) {
bearssl_xbox_free_loaded_anchors(BACKEND->file_anchors,
BACKEND->file_anchor_count);
BACKEND->file_anchors = NULL;
BACKEND->file_anchor_count = 0;
}
#endif
/* Only cleanup SSL state if it was actually initialized */
if(connssl->state == ssl_connection_none)
return;
if(BACKEND) {
if(BACKEND->active) {
/*
* Attempt graceful TLS close only if the connection is still healthy.
* br_sslio_close() sends a close_notify alert, which calls sock_write().
* If the socket is dead, sock_write() fails and BearSSL's I/O state
* becomes inconsistent, causing heap corruption. Guard against this by
* checking io_error (set by bearssl_send/recv on failure), the engine
* error code, and the engine closed flag.
*/
int err = br_ssl_engine_last_error(&BACKEND->sc.eng);
unsigned state = br_ssl_engine_current_state(&BACKEND->sc.eng);
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg),
"BearSSL close: io_error=%d, err=%d, state=0x%x\n",
BACKEND->io_error, err, state);
debug_log(dbg);
}
#endif
if(!BACKEND->io_error && err == BR_ERR_OK && !(state & BR_SSL_CLOSED)) {
br_sslio_close(&BACKEND->ioc);
}
BACKEND->active = 0;
}
/*
* NOTE: Do NOT free BACKEND! The vtls layer allocates all backend memory
* as a single block in url.c:allocate_conn(). Freeing BACKEND here would
* corrupt the heap because it's an offset into a larger allocation.
*
* Just clear the backend data to prepare for potential reuse.
*/
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg),
"BearSSL: Clearing backend at %p (NOT freeing - vtls manages memory)\n",
(void*)BACKEND);
debug_log(dbg);
}
#endif
/* file_anchors already freed above (before early-return guard) */
/* Clear backend data but do NOT free (vtls owns the memory) */
memset(BACKEND, 0, sizeof(struct ssl_backend_data));
}
/* Reset state */
connssl->state = ssl_connection_none;
connssl->connecting_state = ssl_connect_1;
}
static void Curl_bearssl_close_all(struct Curl_easy *data)
{
(void)data;
}
static int Curl_bearssl_shutdown(struct connectdata *conn, int sockindex)
{
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
#ifdef _XBOX
{
char dbg[128];
msnprintf(dbg, sizeof(dbg),
"BearSSL shutdown: sockindex=%d, state=%d, backend=%p\n",
sockindex, (int)connssl->state, (void*)BACKEND);
debug_log(dbg);
}
#endif
/* Only shutdown if SSL was actually initialized */
if(connssl->state == ssl_connection_none)
return 0;
/* Check BACKEND exists before accessing it */
if(!BACKEND)
return 0;
if(BACKEND->active) {
/*
* Only attempt graceful TLS close if no I/O errors occurred.
* Avoid trying to send close_notify on a dead socket.
*/
int err = br_ssl_engine_last_error(&BACKEND->sc.eng);
unsigned state = br_ssl_engine_current_state(&BACKEND->sc.eng);
if(!BACKEND->io_error && err == BR_ERR_OK && !(state & BR_SSL_CLOSED)) {
br_sslio_close(&BACKEND->ioc);
}
BACKEND->active = 0;
}
return 0;
}
static ssize_t bearssl_send(struct connectdata *conn, int sockindex,
const void *mem, size_t len, CURLcode *curlcode)
{
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
int ret;
int flush_ret;
/* Defensive check - BACKEND must be valid */
if(!BACKEND || !BACKEND->active) {
debug_log("bearssl_send: BACKEND invalid, returning error\n");
*curlcode = CURLE_SEND_ERROR;
return -1;
}
#ifdef BEARSSL_VERBOSE_DEBUG
{
char dbg[128];
msnprintf(dbg, sizeof(dbg), "BearSSL send: len=%u, active=%d\n",
(unsigned)len, BACKEND->active);
debug_log(dbg);
}
#endif
ret = br_sslio_write(&BACKEND->ioc, mem, len);
#ifdef BEARSSL_VERBOSE_DEBUG
{
char dbg[128];
int err = br_ssl_engine_last_error(&BACKEND->sc.eng);
unsigned state = br_ssl_engine_current_state(&BACKEND->sc.eng);
msnprintf(dbg, sizeof(dbg), "BearSSL send: write returned %d, err=%d, state=0x%x\n",
ret, err, state);
debug_log(dbg);
}
#endif
if(ret < 0) {
int err = br_ssl_engine_last_error(&BACKEND->sc.eng);
/* Mark that we've had an I/O error - skip graceful close later */
BACKEND->io_error = 1;
if(err == BR_ERR_OK || err == BR_ERR_IO) {
*curlcode = CURLE_AGAIN;
}
else {
*curlcode = CURLE_SEND_ERROR;
}
return -1;
}
/* Flush the data to ensure it's sent immediately */
flush_ret = br_sslio_flush(&BACKEND->ioc);
#ifdef BEARSSL_VERBOSE_DEBUG
{
char dbg[128];
msnprintf(dbg, sizeof(dbg), "BearSSL send: flush returned %d\n", flush_ret);
debug_log(dbg);
}
#endif
if(flush_ret < 0) {
BACKEND->io_error = 1;
}
return (ssize_t)ret;
}
static ssize_t bearssl_recv(struct connectdata *conn, int sockindex,
char *buf, size_t buffersize, CURLcode *curlcode)
{
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
int ret;
/* Defensive check - BACKEND must be valid */
if(!BACKEND || !BACKEND->active) {
debug_log("bearssl_recv: BACKEND invalid, returning error\n");
*curlcode = CURLE_RECV_ERROR;
return -1;
}
#ifdef BEARSSL_VERBOSE_DEBUG
{
char dbg[128];
msnprintf(dbg, sizeof(dbg), "BearSSL recv: buffersize=%u, active=%d\n",
(unsigned)buffersize, BACKEND->active);
debug_log(dbg);
}
#endif
ret = br_sslio_read(&BACKEND->ioc, buf, buffersize);
#ifdef BEARSSL_VERBOSE_DEBUG
{
char dbg[128];
int err = br_ssl_engine_last_error(&BACKEND->sc.eng);
unsigned state = br_ssl_engine_current_state(&BACKEND->sc.eng);
msnprintf(dbg, sizeof(dbg), "BearSSL recv: read returned %d, err=%d, state=0x%x\n",
ret, err, state);
debug_log(dbg);
}
#endif
if(ret < 0) {
int err = br_ssl_engine_last_error(&BACKEND->sc.eng);
/* Mark that we've had an I/O error - skip graceful close later */
BACKEND->io_error = 1;
#ifdef BEARSSL_VERBOSE_DEBUG
{
char dbg[128];
msnprintf(dbg, sizeof(dbg), "BearSSL recv: ERROR - setting io_error=1, err=%d\n", err);
debug_log(dbg);
}
#endif
if(err == BR_ERR_OK || err == BR_ERR_IO) {
*curlcode = CURLE_AGAIN;
}
else {
*curlcode = CURLE_RECV_ERROR;
}
return -1;
}
return (ssize_t)ret;
}
static bool Curl_bearssl_data_pending(const struct connectdata *conn,
int sockindex)
{
const struct ssl_connect_data *connssl = &conn->ssl[sockindex];
unsigned state;
/* Check if SSL was initialized before accessing BACKEND */
if(connssl->state == ssl_connection_none)
return FALSE;
if(!BACKEND || !BACKEND->active)
return FALSE;
state = br_ssl_engine_current_state(&BACKEND->sc.eng);
return (state & BR_SSL_RECVAPP) ? TRUE : FALSE;
}
static int Curl_bearssl_check_cxn(struct connectdata *conn)
{
struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET];
int err;
/* Check if SSL was initialized before accessing BACKEND */
if(connssl->state == ssl_connection_none)
return 0;
if(!BACKEND || !BACKEND->active)
return 0;
err = br_ssl_engine_last_error(&BACKEND->sc.eng);
return (err == BR_ERR_OK) ? 1 : 0;
}
static void *Curl_bearssl_get_internals(struct ssl_connect_data *connssl,
CURLINFO info)
{
(void)info;
/* Check if SSL was initialized before accessing BACKEND */
if(connssl->state == ssl_connection_none)
return NULL;
return BACKEND ? &BACKEND->sc : NULL;
}
static void Curl_bearssl_session_free(void *ptr)
{
(void)ptr;
/* BearSSL session handling is different; no dynamic session objects */
}
const struct Curl_ssl Curl_ssl_bearssl = {
{ CURLSSLBACKEND_BEARSSL, "bearssl" }, /* info */
SSLSUPP_CA_PATH |
SSLSUPP_SSL_CTX, /* supports */
sizeof(struct ssl_backend_data),
Curl_bearssl_init, /* init */
Curl_bearssl_cleanup, /* cleanup */
Curl_bearssl_version, /* version */
Curl_bearssl_check_cxn, /* check_cxn */
Curl_bearssl_shutdown, /* shutdown */
Curl_bearssl_data_pending, /* data_pending */
Curl_bearssl_random, /* random */
Curl_none_cert_status_request, /* cert_status_request */
Curl_bearssl_connect_blocking, /* connect_blocking */
Curl_bearssl_connect_nonblocking, /* connect_nonblocking */
Curl_bearssl_get_internals, /* get_internals */
Curl_bearssl_close, /* close_one */
Curl_bearssl_close_all, /* close_all */
Curl_bearssl_session_free, /* session_free */
Curl_none_set_engine, /* set_engine */
Curl_none_set_engine_default, /* set_engine_default */
Curl_none_engines_list, /* engines_list */
Curl_none_false_start, /* false_start */
Curl_none_md5sum, /* md5sum */
NULL /* sha256sum */
};
#endif /* USE_BEARSSL */