Repository: tlspuffin/tlspuffin
Pull request #383: wolfSSL cput
Author: login: odemengeon
Labels: name: enhancement
description: New feature or request
name: ci:full
description: Run full CI checks on labeled PR
name: refactoring
Created: 2025-03-18T12:21:48Z | Merged: 2025-05-22T11:21:55Z

start of implementation


Diff:
diff --git a/.github/actions/mk_vendor/action.yml b/.github/actions/mk_vendor/action.yml
index 12357f887..5f795600c 100644
--- a/.github/actions/mk_vendor/action.yml
+++ b/.github/actions/mk_vendor/action.yml
@@ -20,7 +20,7 @@ runs:
       shell: bash
       run: |
         #!/usr/bin/env bash
-        SCRIPTS=${{ hashFiles(format('puffin-build/vendors/{0}/**',inputs.vendor), 'puffin-build/builder/**') }}
+        SCRIPTS=${{ hashFiles(format('puffin-build/vendors/{0}/**',inputs.vendor), 'puffin-build/builder/**', 'tlspuffin-claims/claim-interface.h') }}
 
         printf 'scripts=%s\n' "${SCRIPTS}" >> "${GITHUB_OUTPUT}"
         printf 'confuid=%s\n' "${CONFUID}" >> "${GITHUB_OUTPUT}"
diff --git a/.github/tlspuffin.matrix.json b/.github/tlspuffin.matrix.json
index baf8c3b28..675a257ab 100644
--- a/.github/tlspuffin.matrix.json
+++ b/.github/tlspuffin.matrix.json
@@ -175,8 +175,8 @@
       "preset": "wolfssl510"
     },
     "cargo": {
-      "name": "wolfssl510",
-      "features": []
+      "name": "",
+      "features": ["cputs"]
     }
   },
   {
@@ -187,8 +187,9 @@
       "preset": "wolfssl510-sig"
     },
     "cargo": {
-      "name": "wolfssl510",
+      "name": "",
       "features": [
+        "cputs",
         "fix-CVE-2022-25640",
         "fix-CVE-2022-39173"
       ]
@@ -202,8 +203,9 @@
       "preset": "wolfssl510-skip"
     },
     "cargo": {
-      "name": "wolfssl510",
+      "name": "",
       "features": [
+        "cputs",
         "fix-CVE-2022-25638",
         "fix-CVE-2022-39173"
       ]
@@ -217,8 +219,8 @@
       "preset": "wolfssl520"
     },
     "cargo": {
-      "name": "wolfssl520",
-      "features": []
+      "name": "",
+      "features": ["cputs"]
     }
   },
   {
@@ -229,9 +231,9 @@
       "preset": "wolfssl520-asan"
     },
     "cargo": {
-      "name": "wolfssl520",
+      "name": "",
       "features": [
-        "asan"
+        "cputs"
       ]
     }
   },
@@ -245,8 +247,8 @@
       "preset": "wolfssl530"
     },
     "cargo": {
-      "name": "wolfssl530",
-      "features": []
+      "name": "",
+      "features": ["cputs"]
     }
   },
   {
@@ -259,9 +261,9 @@
       "preset": "wolfssl530-asan"
     },
     "cargo": {
-      "name": "wolfssl530",
+      "name": "",
       "features": [
-        "asan"
+        "cputs"
       ]
     }
   },
@@ -275,8 +277,8 @@
       "preset": "wolfssl540"
     },
     "cargo": {
-      "name": "wolfssl540",
-      "features": []
+      "name": "",
+      "features": ["cputs"]
     }
   },
   {
@@ -287,8 +289,9 @@
       "preset": "wolfssl540-sdos2"
     },
     "cargo": {
-      "name": "wolfssl540",
+      "name": "",
       "features": [
+        "cputs",
         "wolfssl-disable-postauth",
         "fix-CVE-2022-39173"
       ]
@@ -302,8 +305,9 @@
       "preset": "wolfssl530-cdos"
     },
     "cargo": {
-      "name": "wolfssl530",
+      "name": "",
       "features": [
+        "cputs",
         "fix-CVE-2022-39173"
       ]
     }
@@ -316,9 +320,9 @@
       "preset": "wolfssl540-asan"
     },
     "cargo": {
-      "name": "wolfssl540",
+      "name": "",
       "features": [
-        "asan"
+        "cputs"
       ]
     }
   },
@@ -330,8 +334,9 @@
       "preset": "wolfssl540-buf"
     },
     "cargo": {
-      "name": "wolfssl540",
+      "name": "",
       "features": [
+        "cputs",
         "fix-CVE-2022-42905"
       ]
     }
@@ -344,9 +349,9 @@
       "preset": "wolfssl540-heap"
     },
     "cargo": {
-      "name": "wolfssl540",
+      "name": "",
       "features": [
-        "asan",
+        "cputs",
         "fix-CVE-2022-39173"
       ]
     }
@@ -359,9 +364,9 @@
       "preset": "wolfssl540-asan-perf"
     },
     "cargo": {
-      "name": "wolfssl540",
+      "name": "",
       "features": [
-        "asan",
+        "cputs",
         "fix-CVE-2022-39173",
         "fix-CVE-2022-42905"
       ]
@@ -375,8 +380,9 @@
       "preset": "wolfssl540-perf"
     },
     "cargo": {
-      "name": "wolfssl540",
+      "name": "",
       "features": [
+        "cputs",
         "fix-CVE-2022-39173",
         "fix-CVE-2022-42905"
       ]
diff --git a/puffin-build/vendors/wolfssl/builder.cmake b/puffin-build/vendors/wolfssl/builder.cmake
index cf0aba504..ab4f4fc7f 100644
--- a/puffin-build/vendors/wolfssl/builder.cmake
+++ b/puffin-build/vendors/wolfssl/builder.cmake
@@ -23,6 +23,10 @@ if(VENDOR_VERSION VERSION_LESS "5.5.2")
   declare_vulnerability("CVE-2022-42905" PATCH ${CMAKE_CURRENT_LIST_DIR}/patches/fix-CVE-2022-42905.patch)
 endif()
 
+set(v500_or_later "$<VERSION_GREATER_EQUAL:${VENDOR_VERSION},5.0.0>")
+set(before_v520 "$<VERSION_LESS:${VENDOR_VERSION},5.2.0>")
+set(require_define_xtime "$<AND:${v500_or_later},${before_v520}>")
+
 foreach(CVE IN LISTS fix)
   if(NOT HAS_${CVE})
     message(FATAL_ERROR "Requested fix for unknown CVE '${CVE}'")
@@ -53,6 +57,8 @@ autotools_builder(
     --enable-secure-renegotiation
     --enable-psk # FIXME only 4.3.0
     --disable-examples
+    --disable-crypttests # to be able to build with -DUSER_TICKS
+    $<$<VERSION_GREATER_EQUAL:${VENDOR_VERSION},5.0.0>:--enable-cryptocb>
 
     $<$<VERSION_GREATER_EQUAL:${VENDOR_VERSION},5.0.0>:--enable-context-extra-user-data>
     $<$<VERSION_GREATER_EQUAL:${VENDOR_VERSION},5.0.0>:--enable-dtls-mtu>
@@ -72,6 +78,8 @@ autotools_builder(
     -DHAVE_EX_DATA                # FIXME only 4.3.0
     -DWOLFSSL_CALLBACKS           # FIXME else some msg callbacks are not called
     -DHAVE_CURVE25519
+    $<$<VERSION_GREATER_EQUAL:${VENDOR_VERSION},5.0.0>:-DUSER_TICKS> # to ensure deterministic behaviour
+    $<${require_define_xtime}:-DXTIME=time_cb>    # to ensure deterministic behaviour with version >= 5.0.0 and < 5.2.0
     # FIXME broken: -DHAVE_EX_DATA_CLEANUP_HOOKS  # required for cleanup of ex data
     # FIXME broken: -DWC_RNG_SEED_CB              # makes test test_seed_cve_2022_38153 fail, but should be used when evaluating coverage to get same coverage than other fuzzers which use this flag to disable determinism
     # FIXME broken: -DWOLFSSL_GENSEED_FORTEST     # makes test test_seed_cve_2022_38153 fail, but should be used when evaluating coverage to get same coverage than other fuzzers which use this flag to disable determinism
@@ -94,6 +102,11 @@ autotools_builder(
     $<$<BOOL:${gcov}>:-O0>
 )
 
+# Extract internal.h from source to install include location
+list(APPEND INSTALL_COMMANDS
+  COMMAND ${CMAKE_COMMAND} -E copy "<SOURCE_DIR>/wolfssl/internal.h" "<INSTALL_DIR>/include/wolfssl/"
+)
+
 set(tls12 yes)
 set(tls13 yes)
 set(transcript_extraction yes)
diff --git a/puffin/include/puffin/puffin.h b/puffin/include/puffin/puffin.h
index 053f7b1d6..bddd2d3bf 100644
--- a/puffin/include/puffin/puffin.h
+++ b/puffin/include/puffin/puffin.h
@@ -60,7 +60,7 @@ extern "C"
         /*
          * In-place reset of the agent.
          */
-        RESULT (*const reset)(AGENT agent, uint8_t new_name);
+        RESULT (*const reset)(AGENT agent, uint8_t new_name, uint8_t use_clear);
 
         /*
          * Produce a textual description of the current agent's state.
diff --git a/puffin/src/execution.rs b/puffin/src/execution.rs
index 46263d621..bab1de8fc 100644
--- a/puffin/src/execution.rs
+++ b/puffin/src/execution.rs
@@ -18,6 +18,14 @@ pub trait TraceRunner {
     type E;
 
     fn execute<T>(self, trace: T) -> Result<Self::R, Self::E>
+    where
+        Self: Sized,
+        T: AsRef<Trace<<Self::PB as ProtocolBehavior>::ProtocolTypes>>,
+    {
+        self.execute_config(trace, true)
+    }
+
+    fn execute_config<T>(self, trace: T, with_reseed: bool) -> Result<Self::R, Self::E>
     where
         T: AsRef<Trace<<Self::PB as ProtocolBehavior>::ProtocolTypes>>;
 }
@@ -42,12 +50,14 @@ impl<PB: ProtocolBehavior> TraceRunner for &Runner<PB> {
     type PB = PB;
     type R = TraceContext<Self::PB>;
 
-    fn execute<T>(self, trace: T) -> Result<Self::R, Self::E>
+    fn execute_config<T>(self, trace: T, with_reseed: bool) -> Result<Self::R, Self::E>
     where
         T: AsRef<Trace<<Self::PB as ProtocolBehavior>::ProtocolTypes>>,
     {
-        // We reseed all PUTs before executing a trace!
-        self.registry.determinism_reseed_all_factories();
+        if with_reseed {
+            // We reseed all PUTs before executing a trace!
+            self.registry.determinism_reseed_all_factories();
+        }
 
         let mut ctx = TraceContext::new(self.spawner.clone());
         trace.as_ref().execute(&mut ctx)?;
@@ -89,7 +99,7 @@ impl<T: TraceRunner + Clone> TraceRunner for &ForkedRunner<T> {
     type PB = T::PB;
     type R = ExecutionStatus;
 
-    fn execute<Tr>(self, trace: Tr) -> Result<Self::R, Self::E>
+    fn execute_config<Tr>(self, trace: Tr, with_reseed: bool) -> Result<Self::R, Self::E>
     where
         Tr: AsRef<Trace<<Self::PB as ProtocolBehavior>::ProtocolTypes>>,
     {
@@ -97,7 +107,7 @@ impl<T: TraceRunner + Clone> TraceRunner for &ForkedRunner<T> {
 
         run_in_subprocess(
             || {
-                let ret = match runner.execute(trace) {
+                let ret = match runner.execute_config(trace, with_reseed) {
                     Ok(_) => 0,
                     Err(_) => 1,
                 };
diff --git a/tlspuffin-claims/claim-interface.h b/tlspuffin-claims/claim-interface.h
index f941ec927..2b19f9300 100644
--- a/tlspuffin-claims/claim-interface.h
+++ b/tlspuffin-claims/claim-interface.h
@@ -1,9 +1,12 @@
 #ifndef TLSPUFFIN_CLAIM_INTERFACE_H
 #define TLSPUFFIN_CLAIM_INTERFACE_H
 
+#include <stdint.h>
+
 #define CLAIM_MAX_AVAILABLE_CIPHERS 256
 #define CLAIM_MAX_SECRET_SIZE 64 /* longest known is SHA512 */
 #define CLAIM_SESSION_ID_LENGTH 32
+#define CLAIM_MAX_CERTIFICATE_LENGTH 10240
 
 typedef enum ClaimTLSVersion
 {
@@ -72,6 +75,8 @@ typedef struct ClaimCertData
 {
     ClaimKeyType key_type;
     int key_length;
+    int data_length;
+    unsigned char data[CLAIM_MAX_CERTIFICATE_LENGTH];
 } ClaimCertData;
 
 typedef struct ClaimCipher
@@ -118,6 +123,7 @@ typedef struct Claim
     ClaimVersion version;
 
     int server;
+    uint8_t peer_authentication;
 
     // Session ID
     ClaimSessionId session_id;
diff --git a/tlspuffin/harness/openssl/src/put.c b/tlspuffin/harness/openssl/src/put.c
index 04596ad2a..e19a93114 100644
--- a/tlspuffin/harness/openssl/src/put.c
+++ b/tlspuffin/harness/openssl/src/put.c
@@ -36,7 +36,7 @@ AGENT openssl_create_client(const TLS_AGENT_DESCRIPTOR *descriptor);
 AGENT openssl_create_server(const TLS_AGENT_DESCRIPTOR *descriptor);
 void openssl_destroy(AGENT agent);
 RESULT openssl_progress(AGENT agent);
-RESULT openssl_reset(AGENT agent, uint8_t new_name);
+RESULT openssl_reset(AGENT agent, uint8_t new_name, uint8_t use_clear);
 bool openssl_is_successful(AGENT agent);
 const char *openssl_describe_state(AGENT agent);
 bool openssl_is_successful(AGENT agent);
@@ -155,7 +155,7 @@ RESULT openssl_progress(AGENT agent)
     return get_result(agent, ret, true);
 }
 
-RESULT openssl_reset(AGENT agent, uint8_t new_name)
+RESULT openssl_reset(AGENT agent, uint8_t new_name, uint8_t use_clear)
 {
     agent->name = new_name;
 
diff --git a/tlspuffin/harness/wolfssl/src/put.c b/tlspuffin/harness/wolfssl/src/put.c
new file mode 100644
index 000000000..15fea7ce6
--- /dev/null
+++ b/tlspuffin/harness/wolfssl/src/put.c
@@ -0,0 +1,1305 @@
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+
+#include <wolfssl/internal.h>
+#include <wolfssl/wolfcrypt/cryptocb.h>
+#include <wolfssl/wolfcrypt/settings.h>
+
+#include <puffin/puffin.h>
+#include <puffin/tls.h>
+
+#include <claim-interface.h>
+
+#include <string.h>
+#include <sys/time.h>
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x05007000
+#define TYPETIME sword64
+#else
+#define TYPETIME word32
+#endif
+
+//#define TIME_CHANGE
+#define USE_CUSTOM_PRNG
+#define CLOCKVALUE_DEFAULT 1742309173;
+
+// static int file = -1;
+//#define CREATE(filename) { file = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+//} #define ENTER() fprintf(stderr, "Entering %s\n", __func__) #define ENTER() { char buf[128] = {};
+// int s = snprintf(buf, 128, "Entering %s\n", __func__); write(file, buf, s); }
+
+static uint8_t rng_have_custom_seed = 0;
+#define CUSTOM_SEED_SIZE 256
+#ifdef USE_CUSTOM_PRNG
+static TYPETIME clock_value = 0;
+#endif
+
+static AGENT make_agent(AGENT agent, TLS_AGENT_DESCRIPTOR const *descriptor);
+
+struct AGENT_TYPE
+{
+    uint8_t name;
+
+    TLS_AGENT_DESCRIPTOR descriptor;
+
+    WOLFSSL_CTX *ctx;
+    WOLFSSL *ssl;
+
+    WOLFSSL_BIO *in;
+    WOLFSSL_BIO *out;
+
+    CLAIMER_CB const claimer;
+
+    bool handshake_done;
+    enum ClaimType transcriptType;
+    bool peer_authentication;
+};
+
+// Map a key type enum to the corresponding claim key type.
+static ClaimKeyType map_keysum_claimkeytype(enum Key_Sum key)
+{
+    switch (key)
+    {
+    case DSAk:
+        return CLAIM_KEY_TYPE_DSA;
+    case RSAk:
+        return CLAIM_KEY_TYPE_RSA;
+    case ECDSAk:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    case ED25519k:
+        return CLAIM_KEY_TYPE_ED25519;
+    case X25519k:
+        return CLAIM_KEY_TYPE_X25519;
+    case ED448k:
+        return CLAIM_KEY_TYPE_ED448;
+    case X448k:
+        return CLAIM_KEY_TYPE_X448;
+    case DHk:
+        return CLAIM_KEY_TYPE_DH;
+    case FALCON_LEVEL1k:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    case FALCON_LEVEL5k:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    default:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    }
+}
+
+// Extract the current handshake transcript hash from a TLS agent.
+// Returns the SHA-256 digest size on success, or 0 on failure.
+static int extract_current_transcript(AGENT agent, unsigned char *buffer, int bufferSize)
+{
+    if (agent == NULL)
+    {
+        _log(PUFFIN.warn, "agent is NULL");
+        return 0;
+    }
+    if (agent->ssl == NULL)
+    {
+        _log(PUFFIN.warn, "agent->ssl is NULL");
+        return 0;
+    }
+    if (agent->ssl->hsHashes == NULL)
+    {
+        //_log(PUFFIN.warn, "agent->ssl->hsHashes is NULL");
+        return 0;
+    }
+    if (bufferSize < WC_SHA256_DIGEST_SIZE)
+    {
+        _log(PUFFIN.warn, "buffer size for SHA256 digest is too small");
+        return 0;
+    }
+
+    if (wc_Sha256GetHash(&agent->ssl->hsHashes->hashSha256, buffer) == 0)
+    {
+        return WC_SHA256_DIGEST_SIZE;
+    }
+    else
+    {
+        _log(PUFFIN.warn, "wc_Sha256GetHash failed");
+        return 0;
+    }
+}
+
+// Populate a pre-allocated Claim structure from the current wolfSSL state
+static void fill_claim(AGENT agent, struct Claim *claim)
+{
+    char *error_msg = "no error";
+
+    if (agent->ssl->version.major != SSLv3_MAJOR)
+    {
+        _log(PUFFIN.warn, "not a tls ssl object");
+        return;
+    }
+    switch (agent->ssl->version.minor)
+    {
+    case TLSv1_2_MINOR:
+        claim->version.data = CLAIM_TLS_VERSION_V1_2;
+        break;
+    case TLSv1_3_MINOR:
+        claim->version.data = CLAIM_TLS_VERSION_V1_3;
+        break;
+    default:
+        _log(PUFFIN.warn, "unsupported tls version");
+        return;
+    }
+
+    claim->server = agent->ssl->options.side == WOLFSSL_SERVER_END;
+    claim->peer_authentication = agent->peer_authentication;
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x05003000
+    WOLFSSL_SESSION *session = agent->ssl->session;
+#else
+    WOLFSSL_SESSION *session = &(agent->ssl->session);
+#endif
+    byte session_size = session->sessionIDSz;
+    if (session_size > CLAIM_SESSION_ID_LENGTH)
+    {
+        session_size = CLAIM_SESSION_ID_LENGTH;
+        _log(PUFFIN.warn, "not enough space in session buffer in claim");
+    }
+    // Working ?
+    claim->session_id.length = session_size;
+    memcpy(claim->session_id.data, session->sessionID, claim->session_id.length);
+
+    int buffer_size = wolfSSL_get_client_random(agent->ssl, NULL, 0);
+    if (buffer_size > 0)
+    {
+        if (buffer_size > CLAIM_SESSION_ID_LENGTH)
+        {
+            buffer_size = CLAIM_SESSION_ID_LENGTH;
+            _log(PUFFIN.warn, "not enough space in client random buffer in claim");
+        }
+        wolfSSL_get_client_random(agent->ssl, claim->client_random.data, buffer_size);
+    }
+    else
+    {
+        _log(PUFFIN.warn, "unable to get client random buffer");
+    }
+
+    buffer_size = wolfSSL_get_server_random(agent->ssl, NULL, 0);
+    if (buffer_size > 0)
+    {
+        if (buffer_size > CLAIM_SESSION_ID_LENGTH)
+        {
+            buffer_size = CLAIM_SESSION_ID_LENGTH;
+            _log(PUFFIN.warn, "not enough space in server random buffer in claim");
+        }
+        wolfSSL_get_server_random(agent->ssl, claim->server_random.data, buffer_size);
+    }
+    else
+    {
+        _log(PUFFIN.warn, "unable to get server random buffer");
+    }
+
+    STACK_OF(SSL_CIPHER) *ciphers = wolfSSL_get_ciphers_compat(agent->ssl);
+    if (ciphers != NULL)
+    {
+        int available_ciphers_len = wolfSSL_sk_SSL_CIPHER_num(ciphers);
+        if (available_ciphers_len != WOLFSSL_FATAL_ERROR)
+        {
+            if (available_ciphers_len > CLAIM_MAX_AVAILABLE_CIPHERS)
+            {
+                available_ciphers_len = CLAIM_MAX_AVAILABLE_CIPHERS;
+                _log(PUFFIN.warn, "not enough space in ciphers list in claim");
+            }
+            claim->available_ciphers.length = available_ciphers_len;
+            for (int i = 0; i < available_ciphers_len; ++i)
+            {
+                SSL_CIPHER const *cipher = wolfSSL_sk_SSL_CIPHER_value(ciphers, i);
+                if (cipher != NULL)
+                {
+                    claim->available_ciphers.ciphers[i].data =
+                        (unsigned short)((((short)cipher->cipherSuite0) << 8) +
+                                         cipher->cipherSuite);
+                }
+                else
+                {
+                    _log(PUFFIN.warn, "wolfSSL_sk_SSL_CIPHER_value return a NULL value");
+                }
+            }
+        }
+        else
+        {
+            _log(PUFFIN.warn, "sk_SSL_CIPHER_num return WOLFSSL_FATAL_ERROR");
+        }
+    }
+    else
+    {
+        _log(PUFFIN.warn, "wolfSSL_get_ciphers_compat return NULL");
+    }
+
+    // cert
+    claim->cert.key_length = 0;
+    claim->cert.data_length = 0;
+    WOLFSSL_X509 *cert = wolfSSL_get_certificate(agent->ssl);
+    if (cert != NULL)
+    {
+        uint8_t *data = NULL;
+        int cert_lenght = wolfSSL_i2d_X509(cert, &data);
+        if (cert_lenght > 0)
+        {
+            claim->cert.data_length = MIN(cert_lenght, CLAIM_MAX_CERTIFICATE_LENGTH);
+            memcpy(claim->cert.data, data, claim->cert.data_length);
+        }
+        else
+        {
+            //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate returned an error a ask for a too big
+            // buffer");
+        }
+        wolfSSL_Free(data);
+        int key_type = wolfSSL_X509_get_pubkey_type(cert);
+        if (key_type != WOLFSSL_FAILURE)
+        {
+            WOLFSSL_EVP_PKEY *cert_pkey = wolfSSL_X509_get_pubkey(cert);
+            if (cert_pkey != NULL)
+            {
+                claim->cert.key_length = wolfSSL_EVP_PKEY_bits(cert_pkey);
+                if (claim->cert.key_length != 0)
+                {
+                    claim->cert.key_type = map_keysum_claimkeytype((enum Key_Sum)key_type);
+                }
+                wolfSSL_EVP_PKEY_free(cert_pkey);
+            }
+            else
+            {
+                //_log(PUFFIN.warn, "wolfSSL_X509_get_pubkey return NULL");
+            }
+        }
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "wolfSSL_get_certificate return NULL");
+    }
+
+    // peer cert
+    claim->peer_cert.key_length = 0;
+    claim->peer_cert.data_length = 0;
+    WOLFSSL_X509 *peer_cert = wolfSSL_get_peer_certificate(agent->ssl);
+    if (peer_cert != NULL)
+    {
+        uint8_t *data = NULL;
+        int cert_lenght = wolfSSL_i2d_X509(peer_cert, &data);
+        if (cert_lenght > 0)
+        {
+            claim->peer_cert.data_length = MIN(cert_lenght, CLAIM_MAX_CERTIFICATE_LENGTH);
+            memcpy(claim->peer_cert.data, data, claim->peer_cert.data_length);
+        }
+        else
+        {
+            //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate returned an error a ask for a too big
+            // buffer");
+        }
+        wolfSSL_Free(data);
+        int key_type = wolfSSL_X509_get_pubkey_type(cert);
+        if (key_type != WOLFSSL_FAILURE)
+        {
+            WOLFSSL_EVP_PKEY const *peer_cert_pkey = wolfSSL_X509_get_pubkey(peer_cert);
+            if (peer_cert_pkey != NULL)
+            {
+                claim->peer_cert.key_length = wolfSSL_EVP_PKEY_bits(peer_cert_pkey);
+                if (claim->peer_cert.key_length != 0)
+                {
+                    claim->peer_cert.key_type = map_keysum_claimkeytype((enum Key_Sum)key_type);
+                }
+            }
+            else
+            {
+                //_log(PUFFIN.warn, "wolfSSL_X509_get_pubkey return NULL");
+            }
+        }
+        wolfSSL_X509_free(peer_cert);
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate return NULL");
+    }
+
+    if (agent->ssl->arrays != NULL)
+    {
+        /*if (claim->version.data == CLAIM_TLS_VERSION_V1_2) {
+          memcpy(claim->master_secret_12.secret, agent->ssl->arrays->masterSecret,
+            MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        } else {
+          memcpy(claim->master_secret.secret, agent->ssl->arrays->masterSecret,
+              MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        }*/
+        memcpy(claim->handshake_secret.secret,
+               agent->ssl->arrays->secret,
+               MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        /*memcpy(claim->handshake_secret.secret, agent->ssl->arrays->exporterSecret,
+            MIN(WC_MAX_DIGEST_SIZE, CLAIM_MAX_SECRET_SIZE));*/
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "ssl->arrays is NULL");
+    }
+
+    claim->chosen_cipher.data = wolfSSL_get_current_cipher_suite(agent->ssl);
+    if (claim->chosen_cipher.data == 0)
+    {
+        _log(PUFFIN.warn, "wolfSSL_get_current_cipher returned NULL");
+    }
+
+    /*int nid = -1;
+    int int_retval = wolfSSL_get_signature_nid(agent->ssl, &nid);
+    if (int_retval == WOLFSSL_SUCCESS) {
+      claim->signature_algorithm = nid;
+    } else {
+      claim->signature_algorithm = 0;
+      _log(PUFFIN.warn, "wolfSSL_get_signature_nid failed");
+    }*/
+
+    /*int_retval = wolfSSL_get_peer_signature_nid(agent->ssl, &nid);
+    if (int_retval == WOLFSSL_SUCCESS) {
+      claim->peer_signature_algorithm = nid;
+    } else {
+      claim->peer_signature_algorithm = 0;
+      _log(PUFFIN.warn, "wolfSSL_get_peer_signature_nid failed");
+    }*/
+
+    claim->transcript.length = 0;
+    if (agent->ssl->hsHashes != NULL)
+    {
+        if (wc_Sha256GetHash(&agent->ssl->hsHashes->hashSha256, claim->transcript.data) == 0)
+        {
+            claim->transcript.length = WC_SHA256_DIGEST_SIZE;
+        }
+        else
+        {
+            _log(PUFFIN.warn, "wc_Sha256GetHash failed");
+        }
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "agent->ssl->hsHashes is NULL");
+    }
+
+    return;
+}
+
+static void default_claimer_notify(void *context, Claim *claim)
+{
+    _log(PUFFIN.trace, "call to default claimer `notify`");
+};
+
+static void default_claimer_destroy(void *context)
+{
+    _log(PUFFIN.trace, "call to default claimer `destroy`");
+};
+
+static const CLAIMER_CB DEFAULT_CLAIMER_CB = {.context = NULL,
+                                              .notify = default_claimer_notify,
+                                              .destroy = default_claimer_destroy};
+
+// Translate the result of a wolfSSL operation into a human-readable message and a RESULT_CODE.
+// Returns a dynamically allocated error string describing the last wolfSSL error.
+// If result_code is not NULL, sets it based on the error type.
+// Caller is responsible for freeing the returned string.
+static char *get_result_information(WOLFSSL *ssl, int retval, RESULT_CODE *result_code)
+{
+    int error_code = wolfSSL_get_error(ssl, retval);
+
+    if (result_code != NULL)
+    {
+        switch (error_code)
+        {
+        case WOLFSSL_ERROR_NONE:
+            *result_code = RESULT_OK;
+            break;
+        case WOLFSSL_ERROR_WANT_READ:
+        case WOLFSSL_ERROR_WANT_WRITE:
+            *result_code = RESULT_IO_WOULD_BLOCK;
+            break;
+        default:
+            *result_code = RESULT_ERROR_OTHER;
+            break;
+        }
+    }
+
+    if (error_code == WOLFSSL_ERROR_NONE)
+    {
+        return strdup("no wolfssl error");
+    }
+    char *error_msg = (char *)calloc(1, WOLFSSL_MAX_ERROR_SZ);
+    wolfSSL_ERR_error_string_n(error_code, error_msg, WOLFSSL_MAX_ERROR_SZ);
+    return error_msg;
+}
+
+static RESULT
+wolfssl_take_outbound(AGENT agent, uint8_t *bytes, size_t max_length, size_t *readbytes)
+{
+    int ret = wolfSSL_BIO_read(agent->out, bytes, max_length);
+    *readbytes = ret > 0 ? ret : 0;
+    /* ToDo check if it bring something
+    if (((ret <= 0) && wolfSSL_BIO_should_retry(agent->out)) || (ret > 0)) {
+      ret = 0;
+    }*/
+    RESULT_CODE result_code = RESULT_ERROR_OTHER;
+    char *reason = get_result_information(agent->ssl,
+                                          ret >= 0 ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE,
+                                          &result_code);
+    RESULT result = PUFFIN.make_result(result_code, reason);
+    free(reason);
+    return result;
+}
+
+static RESULT wolfssl_add_inbound(AGENT agent, const uint8_t *bytes, size_t length, size_t *written)
+{
+    int ret = wolfSSL_BIO_write(agent->in, bytes, length);
+    *written = ret > 0 ? ret : 0;
+    /* ToDo check if it bring something
+    if (((ret <= 0) && wolfSSL_BIO_should_retry(agent->out)) || (ret > 0)) {
+      ret = 0;
+    }*/
+    RESULT_CODE result_code = RESULT_ERROR_OTHER;
+    char *reason = get_result_information(agent->ssl,
+                                          ret >= 0 ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE,
+                                          &result_code);
+    RESULT result = PUFFIN.make_result(result_code, reason);
+    free(reason);
+    return result;
+}
+
+static void wolfssl_message_callback(int write_p,
+                                     int version,
+                                     int content_type,
+                                     const void *buf,
+                                     size_t len,
+                                     WOLFSSL *ssl,
+                                     void *arg)
+{
+    AGENT agent = (AGENT)arg;
+    if (agent == NULL)
+    {
+        return;
+    }
+
+    uint8_t type = 0;
+    if (content_type == 22)
+    {
+        type = *((uint8_t *)buf);
+    }
+    if (write_p != 1) // packet being read
+    {
+        switch (type)
+        {
+        case 0x0b:
+            agent->transcriptType = CLAIM_TRANSCRIPT_CH_CERT;
+            break;
+        case 0x0f:
+            agent->transcriptType = CLAIM_CERTIFICATE_VERIFY;
+            break;
+        case 0x14:
+            agent->transcriptType = CLAIM_TRANSCRIPT_CH_CLIENT_FIN;
+            {
+                struct Claim claim = {};
+                claim.typ = CLAIM_FINISHED;
+                fill_claim(agent, &claim);
+                agent->claimer.notify(agent->claimer.context, &claim);
+            }
+            break;
+        default:
+            break;
+        }
+    }
+
+    unsigned char buffer[WC_SHA256_DIGEST_SIZE] = {};
+    int transcript_lenght = extract_current_transcript(agent, buffer, WC_SHA256_DIGEST_SIZE);
+    if (transcript_lenght != 0)
+    {
+        if (agent->ssl->options.serverState == SERVER_HELLO_COMPLETE)
+        {
+            agent->transcriptType = CLAIM_CERTIFICATE_VERIFY;
+            struct Claim claim = {};
+            claim.typ = CLAIM_TRANSCRIPT_CH_SH;
+            fill_claim(agent, &claim);
+            agent->claimer.notify(agent->claimer.context, &claim);
+        }
+    }
+}
+
+// Register a claimer. If claimer is NULL, then register DEFAULT_CLAIMER_CB
+static void wolfssl_register_claimer(AGENT agent, const CLAIMER_CB *claimer)
+{
+    if (agent->claimer.destroy != NULL)
+    {
+        agent->claimer.destroy(agent->claimer.context);
+    }
+    if (claimer != NULL)
+    {
+        memcpy((void *)&agent->claimer, claimer, sizeof(CLAIMER_CB));
+    }
+    else
+    {
+        memcpy((void *)&agent->claimer, &DEFAULT_CLAIMER_CB, sizeof(CLAIMER_CB));
+    }
+}
+
+char const *map_state_statestr(enum states state)
+{
+    switch (state)
+    {
+    case NULL_STATE:
+        return "NULL_STATE";
+    case SERVER_HELLOVERIFYREQUEST_COMPLETE:
+        return "SERVER_HELLOVERIFYREQUEST_COMPLETE";
+    case SERVER_HELLO_RETRY_REQUEST_COMPLETE:
+        return "SERVER_HELLO_RETRY_REQUEST_COMPLETE";
+    case SERVER_HELLO_COMPLETE:
+        return "SERVER_HELLO_COMPLETE";
+    case SERVER_ENCRYPTED_EXTENSIONS_COMPLETE:
+        return "SERVER_ENCRYPTED_EXTENSIONS_COMPLETE";
+    case SERVER_CERT_COMPLETE:
+        return "SERVER_CERT_COMPLETE";
+    case SERVER_CERT_VERIFY_COMPLETE:
+        return "SERVER_CERT_VERIFY_COMPLETE";
+    case SERVER_KEYEXCHANGE_COMPLETE:
+        return "SERVER_KEYEXCHANGE_COMPLETE";
+    case SERVER_HELLODONE_COMPLETE:
+        return "SERVER_HELLODONE_COMPLETE";
+    case SERVER_CHANGECIPHERSPEC_COMPLETE:
+        return "SERVER_CHANGECIPHERSPEC_COMPLETE";
+    case SERVER_FINISHED_COMPLETE:
+        return "SERVER_FINISHED_COMPLETE";
+    case CLIENT_HELLO_RETRY:
+        return "CLIENT_HELLO_RETRY";
+    case CLIENT_HELLO_COMPLETE:
+        return "CLIENT_HELLO_COMPLETE";
+    case CLIENT_KEYEXCHANGE_COMPLETE:
+        return "CLIENT_KEYEXCHANGE_COMPLETE";
+    case CLIENT_CHANGECIPHERSPEC_COMPLETE:
+        return "CLIENT_CHANGECIPHERSPEC_COMPLETE";
+    case CLIENT_FINISHED_COMPLETE:
+        return "CLIENT_FINISHED_COMPLETE";
+    case HANDSHAKE_DONE:
+        return "HANDSHAKE_DONE";
+    default:
+        return "UNKNOWN STATE";
+    }
+}
+
+static enum states wolfssl_query_state(AGENT agent)
+{
+    if (wolfSSL_is_server(agent->ssl))
+    {
+        return (enum states)agent->ssl->options.serverState;
+    }
+    else
+    {
+        return (enum states)agent->ssl->options.clientState;
+    }
+}
+
+static const char *wolfssl_describe_state(AGENT agent)
+{
+    enum states state = wolfssl_query_state(agent);
+    char const *state_string = map_state_statestr(state);
+    return state_string;
+}
+
+static RESULT wolfssl_reset(AGENT agent, uint8_t new_name, uint8_t use_clear)
+{
+    if (agent == NULL)
+    {
+        _log(PUFFIN.error, "fatal error wolfssl_reset called with agent == NULL");
+        return PUFFIN.make_result(RESULT_ERROR_OTHER,
+                                  "fatal error wolfssl_reset called with agent == NULL");
+    }
+
+#if 0
+    // Was in rust harness
+    CLAIMER_CB current_claimer_cb = {};
+    memcpy(&current_claimer_cb, (void*)&agent->claimer, sizeof(CLAIMER_CB));
+    wolfssl_register_claimer(agent, NULL);
+#endif
+
+    if (use_clear)
+    {
+        int ret = wolfSSL_clear(agent->ssl);
+        memset((void *)&agent->claimer, 0, sizeof(CLAIMER_CB));
+        agent->name = new_name;
+        if (ret != WOLFSSL_SUCCESS)
+        {
+            char *reason = get_result_information(agent->ssl, ret, NULL);
+            RESULT result = PUFFIN.make_result(RESULT_ERROR_OTHER, reason);
+            free(reason);
+            return result;
+        }
+    }
+    else
+    {
+        wolfSSL_free(agent->ssl);
+        agent->descriptor.name = new_name;
+        agent = make_agent(agent, &(agent->descriptor));
+        if (agent == NULL)
+        {
+            _log(PUFFIN.error, "fatal error in wolfssl_reset, make_agent returned NULL");
+            return PUFFIN.make_result(RESULT_ERROR_OTHER,
+                                      "fatal error in wolfssl_reset, make_agent returned NULL");
+        }
+    }
+
+#if 0
+    // Was in rust harness
+    wolfssl_register_claimer(agent, &current_claimer_cb);
+#endif
+
+    return PUFFIN.make_result(RESULT_OK, NULL);
+}
+
+static inline bool wolfssl_is_successful(AGENT agent)
+{
+    return wolfSSL_get_state(agent->ssl) == HANDSHAKE_DONE;
+}
+
+static RESULT wolfssl_progress(AGENT agent)
+{
+    RESULT_CODE result_code = RESULT_ERROR_OTHER;
+    RESULT result = NULL;
+
+    if (wolfSSL_is_init_finished(agent->ssl))
+    {
+        // trigger another read
+        uint8_t buf[128];
+        int ret = wolfSSL_read(agent->ssl, &buf, 128);
+        if (ret > 0)
+        {
+            result = PUFFIN.make_result(RESULT_OK, NULL);
+        }
+        else
+        {
+            char *reason = get_result_information(agent->ssl, ret, &result_code);
+            result =
+                PUFFIN.make_result(result_code == RESULT_IO_WOULD_BLOCK ? RESULT_OK : result_code,
+                                   reason);
+            free(reason);
+        }
+    }
+    else
+    {
+        // not connected yet -> do handshake
+#if 1
+        int ret = wolfSSL_SSL_do_handshake(agent->ssl);
+#else
+        // Rust harness use it, but not working for now in C harness
+        int ret = wolfSSL_accept(agent->ssl);
+#endif
+        if (ret == WOLFSSL_SUCCESS)
+        {
+            agent->handshake_done = true;
+            result = PUFFIN.make_result(RESULT_OK, "handshake done");
+        }
+        else
+        {
+            char *reason = get_result_information(agent->ssl, ret, &result_code);
+            result =
+                PUFFIN.make_result(result_code == RESULT_IO_WOULD_BLOCK ? RESULT_OK : result_code,
+                                   reason);
+            free(reason);
+        }
+    }
+
+    // deferred_transcript_extraction
+    if ((agent->claimer.notify != NULL) && (agent->transcriptType != CLAIM_NOT_SET))
+    {
+        struct Claim claim = {};
+        claim.typ = agent->transcriptType;
+        fill_claim(agent, &claim);
+        agent->claimer.notify(agent->claimer.context, &claim);
+    }
+
+    return result;
+}
+
+static void wolfssl_destroy(AGENT agent)
+{
+    if (agent == NULL)
+    {
+        return;
+    }
+    wolfssl_register_claimer(agent, NULL);
+    if (agent->ssl != NULL)
+    {
+        wolfSSL_free(agent->ssl);
+        agent->ssl = NULL;
+    }
+    if (agent->ctx != NULL)
+    {
+        wolfSSL_CTX_free(agent->ctx);
+        agent->ctx = NULL;
+    }
+    free(agent);
+    agent = NULL;
+}
+
+static AGENT make_agent(AGENT agent, TLS_AGENT_DESCRIPTOR const *descriptor)
+{
+    char error_msg[128] = {};
+    snprintf(error_msg, sizeof(error_msg), "no error");
+    int int_retval = 0;
+    bool is_server = descriptor->role == SERVER;
+
+    agent->ssl = wolfSSL_new(agent->ctx);
+    if (agent->ssl == NULL)
+    {
+        strncpy(error_msg, "wolfSSL_new returned NULL", 128);
+        goto ERROR__make_agent;
+    }
+
+    agent->name = descriptor->name;
+
+    if (!is_server)
+    {
+        int_retval = wolfSSL_UseSessionTicket(agent->ssl);
+        if (int_retval != WOLFSSL_SUCCESS)
+        {
+            strncpy(error_msg,
+                    "fatal error in wolfssl_register_claimer, wolfSSL_UseSessionTicket failed",
+                    128);
+            goto ERROR__make_agent;
+        }
+    }
+
+    int_retval = wolfSSL_set_msg_callback(agent->ssl, wolfssl_message_callback);
+    if (int_retval != WOLFSSL_SUCCESS)
+    {
+        strncpy(error_msg,
+                "fatal error in wolfssl_register_claimer, unable to register callback",
+                128);
+        goto ERROR__make_agent;
+    }
+    int_retval = wolfSSL_set_msg_callback_arg(agent->ssl, agent);
+    if (int_retval != WOLFSSL_SUCCESS)
+    {
+        strncpy(error_msg,
+                "fatal error in wolfssl_register_claimer, unable to register arg callback",
+                128);
+        goto ERROR__make_agent;
+    }
+
+    agent->in = wolfSSL_BIO_new(wolfSSL_BIO_s_mem());
+    if (agent->in == NULL)
+    {
+        strncpy(error_msg, "wolfSSL_BIO_new returned NULL", 128);
+        goto ERROR__make_agent;
+    }
+    agent->out = wolfSSL_BIO_new(wolfSSL_BIO_s_mem());
+    if (agent->out == NULL)
+    {
+        strncpy(error_msg, "wolfSSL_BIO_new returned NULL", 128);
+        goto ERROR__make_agent;
+    }
+
+    agent->handshake_done = false;
+    agent->transcriptType = CLAIM_NOT_SET;
+    agent->peer_authentication = descriptor->role == CLIENT ? descriptor->server_authentication
+                                                            : descriptor->client_authentication;
+
+    memset((void *)&agent->claimer, 0, sizeof(CLAIMER_CB));
+    wolfssl_register_claimer(agent, &DEFAULT_CLAIMER_CB);
+
+    wolfSSL_set_bio(agent->ssl, agent->in, agent->out);
+
+    if (is_server)
+    {
+        wolfSSL_set_accept_state(agent->ssl);
+    }
+    else
+    {
+        wolfSSL_set_connect_state(agent->ssl);
+    }
+
+    memcpy(&(agent->descriptor), descriptor, sizeof(TLS_AGENT_DESCRIPTOR));
+
+    return agent;
+
+ERROR__make_agent:
+    _log(PUFFIN.error, "fatal error in make_agent: %s", error_msg);
+    wolfssl_destroy(agent);
+    return NULL;
+}
+
+#ifdef USE_CUSTOM_PRNG
+// Callback used by wolfssl to to create seeds
+static int myCryptoCb_Func(int devId, wc_CryptoInfo *info, void *ctx)
+{
+    if ((rng_have_custom_seed == 0) || (info->algo_type != WC_ALGO_TYPE_SEED))
+    {
+        return CRYPTOCB_UNAVAILABLE;
+    }
+
+    if (info->seed.sz > CUSTOM_SEED_SIZE)
+    {
+        _log(PUFFIN.error,
+             "fatal error in myCryptoCb_Func, requested seed size (%d) > %d",
+             info->seed.sz,
+             CUSTOM_SEED_SIZE);
+        return CRYPTOCB_UNAVAILABLE;
+    }
+
+    // Generate a new seed, with unique bytes
+    uint8_t seen[256] = {};
+    for (size_t i = 0; i < info->seed.sz; ++i)
+    {
+        uint8_t byte = ((double)rand() / ((double)RAND_MAX + 1.0)) * 255.0;
+        if (seen[byte] != 0)
+        {
+            while (seen[++byte] != 0)
+                ;
+        }
+        seen[byte] = 1;
+        info->seed.seed[i] = byte;
+    }
+    return 0;
+}
+#endif
+
+static AGENT wolfssl_create_agent(TLS_AGENT_DESCRIPTOR const *descriptor,
+                                  WOLFSSL_METHOD *tls_method,
+                                  bool peer_authentication)
+{
+    char error_msg[128] = {};
+    snprintf(error_msg, sizeof(error_msg), "no error");
+    int int_retval = WOLFSSL_FAILURE;
+    AGENT agent = NULL;
+    bool is_server = descriptor->role == SERVER;
+    const char *cipher_list = NULL;
+
+    agent = (AGENT)calloc(1, sizeof(struct AGENT_TYPE));
+    if (agent == NULL)
+    {
+        strncpy(error_msg, "calloc returned NULL", 128);
+        goto ERROR__wolfssl_create_agent;
+    }
+
+    if (tls_method == NULL)
+    {
+        strncpy(error_msg, "retrieving wolfssl method failed", 128);
+        goto ERROR__wolfssl_create_agent;
+    }
+    agent->ctx = wolfSSL_CTX_new(tls_method);
+    if (agent->ctx == NULL)
+    {
+        strncpy(error_msg, "wolfssl create context failed", 128);
+        goto ERROR__wolfssl_create_agent;
+    }
+
+    int_retval = wolfSSL_CTX_set_msg_callback(agent->ctx, wolfssl_message_callback);
+    if (int_retval != WOLFSSL_SUCCESS)
+    {
+        strncpy(error_msg, "wolfSSL_CTX_set_msg_callback failed", 128);
+        goto ERROR__wolfssl_create_agent;
+    }
+    int_retval = wolfSSL_CTX_set_msg_callback_arg(agent->ctx, agent);
+    if (int_retval != WOLFSSL_SUCCESS)
+    {
+        strncpy(error_msg, "wolfSSL_CTX_set_msg_callback_arg failed", 128);
+        goto ERROR__wolfssl_create_agent;
+    }
+
+#ifdef USE_CUSTOM_PRNG
+    // Register a device to provide seed (for determinism)
+    int_retval = wc_CryptoCb_RegisterDevice(1, myCryptoCb_Func, agent->ctx);
+    if (int_retval != 0)
+    {
+        strncpy(error_msg, "wolfssl register device failed", 128);
+        goto ERROR__wolfssl_create_agent;
+    }
+    int_retval = wolfSSL_CTX_SetDevId(agent->ctx, 1);
+    if (int_retval != WOLFSSL_SUCCESS)
+    {
+        strncpy(error_msg, "wolfssl set device failed", 128);
+        goto ERROR__wolfssl_create_agent;
+    }
+#endif
+
+    int_retval = wolfSSL_CTX_set_session_cache_mode(agent->ctx, SSL_SESS_CACHE_OFF);
+    if (int_retval != WOLFSSL_SUCCESS)
+    {
+        strncpy(error_msg, "wolfssl set session cache mode failed", 128);
+        goto ERROR__wolfssl_create_agent;
+    }
+
+    char const *cipher_string = NULL;
+    switch (descriptor->tls_version)
+    {
+    case V1_3:
+        cipher_string = descriptor->cipher_string_tls13;
+        break;
+    case V1_2:
+        cipher_string = descriptor->cipher_string_tls12;
+        break;
+    default:
+        break;
+    }
+    if (cipher_string != NULL)
+    {
+        // Allow EXPORT in server
+        // Disallow EXPORT in client
+        int_retval = wolfSSL_CTX_set_cipher_list(agent->ctx, cipher_string);
+        if (int_retval != WOLFSSL_SUCCESS)
+        {
+            snprintf(error_msg, 128, "wolfssl set cipher list %s failed", cipher_string);
+            goto ERROR__wolfssl_create_agent;
+        }
+    }
+    else
+    {
+        _log(PUFFIN.warn, "wolfssl warning no cipher string");
+    }
+
+    if (descriptor->group_list != NULL)
+    {
+        int_retval = wolfSSL_CTX_set1_groups_list(agent->ctx, (char *)(descriptor->group_list));
+        if (int_retval != WOLFSSL_SUCCESS)
+        {
+            snprintf(error_msg, 128, "wolfssl set group list %s failed", descriptor->group_list);
+            goto ERROR__wolfssl_create_agent;
+        }
+    }
+
+    if (peer_authentication)
+    {
+        wolfSSL_CTX_set_verify(agent->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
+
+        for (size_t i = 0; i < descriptor->store_length; ++i)
+        {
+            int_retval = wolfSSL_CTX_load_verify_buffer(agent->ctx,
+                                                        descriptor->store[i]->bytes,
+                                                        descriptor->store[i]->length,
+                                                        SSL_FILETYPE_PEM);
+            if (int_retval != WOLFSSL_SUCCESS)
+            {
+                break;
+            }
+        }
+        if (int_retval != WOLFSSL_SUCCESS)
+        {
+            snprintf(error_msg, 128, "wolfssl setting store failed: %d", int_retval);
+            goto ERROR__wolfssl_create_agent;
+        }
+    }
+    else
+    {
+        wolfSSL_CTX_set_verify(agent->ctx, SSL_VERIFY_NONE, NULL);
+    }
+
+    if (is_server || descriptor->client_authentication)
+    {
+#if 1
+        // Mimic rust harness
+        WOLFSSL_X509 *x509 = NULL;
+        WOLFSSL_BIO *bio = wolfSSL_BIO_new(wolfSSL_BIO_s_mem());
+        if (bio == NULL)
+        {
+            snprintf(error_msg, 128, "wolfssl use certificat failed: %d", int_retval);
+            goto ERROR__wolfssl_create_agent;
+        }
+        int_retval = wolfSSL_BIO_write(bio, descriptor->cert->bytes, descriptor->cert->length);
+        if (int_retval != descriptor->cert->length)
+        {
+            snprintf(error_msg,
+                     128,
+                     "wolfSSL_BIO_write failed to write descriptor->cert: %d",
+                     int_retval);
+            goto ERROR__wolfssl_create_agent;
+        }
+        x509 = wolfSSL_PEM_read_bio_X509(bio, NULL, NULL, NULL);
+        if (x509 == NULL)
+        {
+            snprintf(error_msg, 128, "wolfSSL_PEM_read_bio_X509 failed to create certificat");
+            goto ERROR__wolfssl_create_agent;
+        }
+        wolfSSL_BIO_free(bio);
+        int_retval = wolfSSL_CTX_use_certificate(agent->ctx, x509);
+        wolfSSL_X509_free(x509);
+#else
+        int_retval = wolfSSL_CTX_use_certificate_buffer(agent->ctx,
+                                                        descriptor->cert->bytes,
+                                                        descriptor->cert->length,
+                                                        SSL_FILETYPE_PEM);
+        * /
+#endif
+        if (int_retval != WOLFSSL_SUCCESS)
+        {
+            snprintf(error_msg, 128, "wolfssl use certificat failed: %d", int_retval);
+            goto ERROR__wolfssl_create_agent;
+        }
+        int_retval = wolfSSL_CTX_use_PrivateKey_buffer(agent->ctx,
+                                                       descriptor->pkey->bytes,
+                                                       descriptor->pkey->length,
+                                                       SSL_FILETYPE_PEM);
+        if (int_retval != WOLFSSL_SUCCESS)
+        {
+            snprintf(error_msg, 128, "wolfssl use private key failed: %d", int_retval);
+            goto ERROR__wolfssl_create_agent;
+        }
+    }
+
+    if (is_server)
+    {
+        int_retval = wolfSSL_CTX_set_num_tickets(agent->ctx, 2);
+        if (int_retval != WOLFSSL_SUCCESS)
+        {
+            snprintf(error_msg, 128, "wolfSSL_CTX_set_num_tickets");
+            goto ERROR__wolfssl_create_agent;
+        }
+    }
+
+    agent = make_agent(agent, descriptor);
+    if (agent == NULL)
+    {
+        strncpy(error_msg, "creating wolfssl agent failed", 128);
+        goto ERROR__wolfssl_create_agent;
+    }
+
+    return agent;
+
+ERROR__wolfssl_create_agent:
+    _log(PUFFIN.error, "fatal error in wolfssl_create_agent: %s", error_msg);
+    wolfssl_destroy(agent);
+    return NULL;
+}
+
+static AGENT wolfssl_create(TLS_AGENT_DESCRIPTOR const *descriptor)
+{
+    WOLFSSL_METHOD *(*tls_methods[2])();
+    char const *tls_version_str = NULL;
+    switch (descriptor->tls_version)
+    {
+    case V1_3:
+        tls_version_str = "V1_3";
+        tls_methods[0] = wolfTLSv1_3_client_method;
+        tls_methods[1] = wolfTLSv1_3_server_method;
+        break;
+    case V1_2:
+        tls_version_str = "V1_2";
+        tls_methods[0] = wolfTLSv1_2_client_method;
+        tls_methods[1] = wolfTLSv1_2_server_method;
+        break;
+    default:
+        _log(PUFFIN.error,
+             "tls version is unknown, enum= %d, for %u: %u",
+             descriptor->tls_version,
+             descriptor->name,
+             descriptor->role);
+        return NULL;
+        break;
+    }
+
+    switch (descriptor->role)
+    {
+    case CLIENT:
+        _log(PUFFIN.info,
+             "descriptor %u version: %s type: client",
+             descriptor->name,
+             tls_version_str);
+        return wolfssl_create_agent(descriptor,
+                                    tls_methods[0](),
+                                    descriptor->server_authentication);
+        break;
+    case SERVER:
+        _log(PUFFIN.info,
+             "descriptor %u version: %s type: server",
+             descriptor->name,
+             tls_version_str);
+        return wolfssl_create_agent(descriptor,
+                                    tls_methods[1](),
+                                    descriptor->client_authentication);
+        break;
+    default:
+        _log(PUFFIN.error,
+             "unknown agent type for descriptor %u: %u",
+             descriptor->name,
+             descriptor->role);
+        return NULL;
+        break;
+    }
+}
+
+static void wolfssl_rng_reseed(uint8_t const *buffer, size_t length)
+{
+#ifdef USE_CUSTOM_PRNG
+    if ((buffer != NULL) && (length > 0))
+    {
+        clock_value = CLOCKVALUE_DEFAULT;
+        unsigned int seed = 0;
+        if (length <= sizeof(unsigned int))
+        {
+            seed = *((unsigned int *)buffer);
+        }
+        else
+        {
+            memcpy(&seed, buffer, sizeof(unsigned int));
+        }
+        srand(seed);
+        rng_have_custom_seed = 1;
+    }
+    else
+    {
+        clock_value = 0;
+        srand(time(NULL));
+        rng_have_custom_seed = 0;
+    }
+#endif
+}
+
+static TLS_PUT_INTERFACE const WOLFSSL_PUT = {
+    .create = wolfssl_create,
+    .rng_reseed = wolfssl_rng_reseed,
+    .supports = NULL,
+
+    .agent_interface =
+        {
+            .destroy = wolfssl_destroy,
+            .progress = wolfssl_progress,
+            .reset = wolfssl_reset,
+            .describe_state = wolfssl_describe_state,
+            .is_state_successful = wolfssl_is_successful,
+            .register_claimer = wolfssl_register_claimer,
+
+            .add_inbound = wolfssl_add_inbound,
+            .take_outbound = wolfssl_take_outbound,
+        },
+};
+
+time_t time_cb(time_t *t)
+{
+#ifdef USE_CUSTOM_PRNG
+    if (clock_value != 0)
+    {
+#ifdef TIME_CHANGE
+        ++clock_value;
+#endif
+        if (t != NULL)
+        {
+            *t = clock_value;
+            *t = 0;
+        }
+        return clock_value;
+    }
+#endif
+    return time(t);
+}
+
+word32 LowResTimer(void)
+{
+#ifdef USE_CUSTOM_PRNG
+    if (clock_value != 0)
+    {
+#ifdef TIME_CHANGE
+        ++clock_value;
+#endif
+        return clock_value;
+    }
+#endif
+    return (word32)time(NULL);
+}
+
+TYPETIME TimeNowInMilliseconds(void)
+{
+#ifdef USE_CUSTOM_PRNG
+    if (clock_value != 0)
+    {
+#ifdef TIME_CHANGE
+        ++clock_value;
+#endif
+        return 1000 * clock_value;
+    }
+#endif
+    struct timeval now;
+    if (gettimeofday(&now, NULL) < 0)
+        return (TYPETIME)GETTIME_ERROR;
+    return (TYPETIME)(now.tv_sec * 1000 + now.tv_usec / 1000);
+}
+
+static void AnalyseLogLevel()
+{
+    char const *logLevelCst = getenv("RUST_LOG");
+    if (logLevelCst == NULL)
+    {
+        return;
+    }
+    char *logLevel = strdup(logLevelCst);
+    char const *module[] = {"tlspuffin::*", "tlspuffin::put"};
+    size_t curIndex = 0;
+    char *start[256] = {};
+    size_t logLevelSz = strlen(logLevel);
+    start[0] = logLevel;
+
+    for (size_t i = 0; i < logLevelSz; ++i)
+    {
+        if ((logLevel[i] == '=') || (logLevel[i] == ','))
+        {
+            if ((i + 1) < logLevelSz)
+            {
+                curIndex++;
+                start[curIndex] = &(logLevel[i + 1]);
+            }
+            logLevel[i] = 0;
+        }
+    }
+
+    char *level = NULL;
+    if (curIndex != 0)
+    {
+        uint8_t done = 0;
+        for (size_t i = 0; i < curIndex; i += 2)
+        {
+            for (int j = 0; j < 2; ++j)
+            {
+                if (strcmp(start[i], module[j]) == 0)
+                {
+                    level = start[i + 1];
+                    done = j;
+                    break;
+                }
+            }
+            if (done == 1)
+            {
+                break;
+            }
+        }
+    }
+    else
+    {
+        level = start[0];
+    }
+
+    if (level != NULL)
+    {
+        size_t levelSz = strlen(level);
+        for (size_t i = 0; i < levelSz; ++i)
+        {
+            level[i] = toupper(level[i]);
+        }
+        if ((strcmp(level, "TRACE") == 0) || (strcmp(level, "DEBUG") == 0))
+        {
+            wolfSSL_Debugging_ON();
+        }
+    }
+
+    free(logLevel);
+}
+
+TLS_PUT_INTERFACE const *REGISTER()
+{
+    AnalyseLogLevel();
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x05002000
+#ifdef USE_CUSTOM_PRNG
+    wc_SetTimeCb(time_cb);
+#endif
+#endif
+
+    _log(PUFFIN.info, "wolfssl version %s", LIBWOLFSSL_VERSION_STRING);
+    int int_retval = wolfSSL_Init();
+    if (int_retval != WOLFSSL_SUCCESS)
+    {
+        _log(PUFFIN.error, "wolfssl init failed");
+        return NULL; // ToDo check if possible to return NULL
+    }
+
+    return &WOLFSSL_PUT;
+}
diff --git a/tlspuffin/src/claims.rs b/tlspuffin/src/claims.rs
index 5ff44117d..20da85ae3 100644
--- a/tlspuffin/src/claims.rs
+++ b/tlspuffin/src/claims.rs
@@ -375,6 +375,15 @@ pub mod claims_helpers {
             // Transcripts in these messages are not up-to-date. They get updated after the Message
             // has been processed
             security_claims::ClaimType::CLAIM_FINISHED => {
+                let peer_certificate = if claim.peer_authentication == 1
+                    && matches!(claim.peer_cert.data_length, 1..10240)
+                {
+                    SmallVec::from_slice(
+                        &claim.peer_cert.data[..claim.peer_cert.data_length as usize],
+                    )
+                } else {
+                    Default::default()
+                };
                 Some(ClaimData::Message(ClaimDataMessage::Finished(Finished {
                     outbound: claim.write > 0,
                     client_random: SmallVec::from(claim.client_random.data),
@@ -382,8 +391,8 @@ pub mod claims_helpers {
                     session_id: SmallVec::from_slice(
                         &claim.session_id.data[..claim.session_id.length as usize],
                     ),
-                    authenticate_peer: false,             // FIXME
-                    peer_certificate: Default::default(), // FIXME
+                    authenticate_peer: claim.peer_authentication == 1,
+                    peer_certificate,
                     master_secret: match protocol_version {
                         TLSVersion::V1_3 => SmallVec::from_slice(&claim.master_secret.secret),
                         TLSVersion::V1_2 => SmallVec::from_slice(&claim.master_secret_12.secret),
diff --git a/tlspuffin/src/put.rs b/tlspuffin/src/put.rs
index 98833e967..877d8803f 100644
--- a/tlspuffin/src/put.rs
+++ b/tlspuffin/src/put.rs
@@ -283,7 +283,13 @@ impl Put<TLSProtocolBehavior> for CAgent {
 
     fn reset(&mut self, new_name: puffin::agent::AgentName) -> Result<(), Error> {
         self.config.descriptor.name = new_name;
-        r_ccall!(self.put, reset, self.c_agent, new_name.into())?;
+        r_ccall!(
+            self.put,
+            reset,
+            self.c_agent,
+            new_name.into(),
+            self.config.use_clear as u8
+        )?;
         self.register_claimer();
         Ok(())
     }
diff --git a/tlspuffin/tests/determinism.rs b/tlspuffin/tests/determinism.rs
index ca5fd9da5..730e11739 100644
--- a/tlspuffin/tests/determinism.rs
+++ b/tlspuffin/tests/determinism.rs
@@ -1,6 +1,15 @@
+use std::thread;
+use std::time::Duration;
+
 use tlspuffin::test_utils::prelude::*;
 
-#[apply(test_puts, filter = all(tls13, boringssl))]
+/*
+OpenSSL is currently excuded because:
+  (i) we currently don't have a working RNG reseed for the OpenSSL CPUT (see Issue #393),
+  (ii) we don't have a time hook at the moment for the OpenSSL CPUT.
+*/
+#[cfg(not(feature = "wolfssl430"))]
+#[apply(test_puts, filter = not(openssl))]
 fn test_attacker_full_det_recreate(put: &str) {
     use tlspuffin::tls::seeds::seed_client_attacker_full;
 
@@ -9,6 +18,12 @@ fn test_attacker_full_det_recreate(put: &str) {
 
     let ctx_1 = runner.execute(&trace);
 
+    /*
+    Sleep to introduce a time difference between executions.
+    This test validates determinism: even with environmental variations
+    (e.g., current time), the execution trace should produce identical results.
+    */
+    thread::sleep(Duration::from_secs(2));
     for i in 0..200 {
         println!("Attempt #{i}...");
         let ctx_2 = runner.execute(&trace);
diff --git a/tlspuffin/tests/vulnerabilities.rs b/tlspuffin/tests/vulnerabilities.rs
index c3b2e5eda..c978293e6 100644
--- a/tlspuffin/tests/vulnerabilities.rs
+++ b/tlspuffin/tests/vulnerabilities.rs
@@ -120,8 +120,21 @@ fn test_seed_cve_2022_38153(put: &str) {
     let runner = default_runner_for(put);
     let trace = seed_successful12_with_tickets.build_trace();
 
-    for _ in 0..50 {
-        let _ = runner.execute(trace.clone()).unwrap();
+    let _ = runner.execute_config(trace.clone(), true).unwrap();
+    /*
+    Originally, puffin found this bug because wolfssl was not made deterministic at all. The bug requires that the
+    shared (across sessions) ticket map gets filled until a collision happen (a key refers to two tickets). For this to
+    happen, we need to create several **different** tickets. This won't happen, now that wolfssl is made
+    deterministic (the same ticket will be created at the end of a single handshake). We mimick the old behavior
+    here, using `.execute_config`.
+
+    Theoretically, this attack can still be found with a deterministic WolfSSL: mutations could repeat a full
+    handshake multiple times (hence yielding different tickets) and then appending the malicious handshake.
+    This is extremely unlikely to happen though; or even impossible given the bounds on the trace lengths. In
+    the future, we might want to reconsider whether we **always** want to reseed prior to executing a trace.
+    */
+    for _ in 1..50 {
+        let _ = runner.execute_config(trace.clone(), false).unwrap();
     }
 
     expect_trace_crash(


Reviews:
user: login: LCBH
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T09:13:35Z
user: login: LCBH
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T16:57:18Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR initiates the implementation for the "wolfSSL cput" feature by updating test executions, integrating a new reseed parameter for agent reset calls, and adjusting build configurations for improved compatibility with different wolfSSL versions.  
- Modified test logic in vulnerabilities and determinism tests to use the new execute_config API.  
- Updated agent reset interfaces and corresponding C implementations to accept an additional parameter.  
- Adjusted build scripts and configuration files (CMake, matrix, GitHub Actions) to support the new features and compatibility with wolfSSL.

### Reviewed Changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File                                               | Description                                                             |
| -------------------------------------------------- | ----------------------------------------------------------------------- |
| tlspuffin/tests/vulnerabilities.rs                 | Updated execution loop to separate reseed call from subsequent runs.     |
| tlspuffin/tests/determinism.rs                     | Modified test filters and added a sleep to improve determinism.          |
| tlspuffin/src/put.rs                               | Updated reset call to include the new use_clear argument.                |
| tlspuffin/src/claims.rs                            | Replaced FIXME comments by introducing actual value resolution.          |
| tlspuffin/harness/openssl/src/put.c                | Updated openssl_reset signature to include the new use_clear parameter.  |
| tlspuffin-claims/claim-interface.h                 | Added certificate-related fields and definitions with new macros.        |
| puffin/src/execution.rs                            | Refactored execution methods to include execute_config with reseed logic.  |
| puffin/include/puffin/puffin.h                     | Updated reset function signature declaration to include new parameter.   |
| puffin-build/vendors/wolfssl/builder.cmake         | Introduced new build flags and installation commands for deterministic behavior. |
| .github/tlspuffin.matrix.json                      | Adjusted cargo configurations for wolfSSL presets and included cputs feature. |
| .github/actions/mk_vendor/action.yml               | Updated hashFiles parameters to include additional files.                |
</details>





state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T08:25:59Z
user: login: LCBH
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T08:39:08Z
user: login: LCBH
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T08:40:39Z
user: login: aeyno
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-21T10:50:30Z
user: login: odemengeon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-21T11:44:39Z
user: login: odemengeon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-21T11:52:48Z
user: login: LCBH
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T13:11:30Z
user: login: LCBH
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T13:18:46Z
user: login: LCBH
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T13:50:57Z
user: login: LCBH
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T11:08:15Z

Review comments:
diff_hunk: @@ -25,13 +25,6 @@ runs:
         printf 'scripts=%s\n' "${SCRIPTS}" >> "${GITHUB_OUTPUT}"
         printf 'confuid=%s\n' "${CONFUID}" >> "${GITHUB_OUTPUT}"
 
-    - uses: actions/cache@v4
path: .github/actions/mk_vendor/action.yml
user: login: LCBH
body: You should try to reactivate now
created_at: 2025-05-15T10:11:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -26,8 +26,6 @@ wolfssl510 = []
 wolfssl520 = []
 wolfssl530 = []
 wolfssl540 = []
-wolfssl552 = []
path: crates/wolfssl-sys/Cargo.toml
user: login: LCBH
body: Why removing those Rust put?
created_at: 2025-05-15T10:14:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,8 +33,6 @@ declare -a CAPABILITIES=(
     $<$<BOOL:@tls13@>:tls13>
     $<$<BOOL:@tls12_session_resumption@>:tls12_session_resumption>
     $<$<BOOL:@tls13_session_resumption@>:tls13_session_resumption>
-    $<$<BOOL:@allow_setting_tls12_ciphers@>:allow_setting_tls12_ciphers>
path: puffin-build/cmake/builder/cmake/vendorinfo.sh.in
user: login: LCBH
body: Why removing those lines recently added by a PR?
created_at: 2025-05-15T10:15:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -139,12 +139,6 @@ if(VENDOR_VERSION VERSION_GREATER_EQUAL "1.1.1")
   set(tls13 yes)
   set(tls13_session_resumption yes)
 endif()
-if(VENDOR_VERSION VERSION_GREATER_EQUAL "3.4.0")
path: puffin-build/vendors/openssl/builder.cmake
user: login: LCBH
body: Same here
created_at: 2025-05-15T10:15:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -102,9 +115,6 @@ if(VENDOR_VERSION VERSION_GREATER_EQUAL "5.0")
   set(tls13_session_resumption yes)
   set(client_authentication_transcript_extraction yes)
 endif()
-set(allow_setting_tls12_ciphers yes)
path: puffin-build/vendors/wolfssl/builder.cmake
user: login: LCBH
body: Same here
created_at: 2025-05-15T10:16:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,113 +0,0 @@
-# Scan this file for changes every 30 seconds
path: client_log_config.yml
user: login: LCBH
body: Why removing this file?
created_at: 2025-05-16T16:56:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,9 +1,12 @@
 #ifndef TLSPUFFIN_CLAIM_INTERFACE_H
 #define TLSPUFFIN_CLAIM_INTERFACE_H
 
+#include <stdint.h>
+
 #define CLAIM_MAX_AVAILABLE_CIPHERS 256
 #define CLAIM_MAX_SECRET_SIZE 64 /* longest known is SHA512 */
 #define CLAIM_SESSION_ID_LENGTH 32
+#define CLAIM_MAX_CERTIFICATE_LENGHT 10240
path: tlspuffin-claims/claim-interface.h
user: login: Copilot
body: Typo in macro name 'CLAIM_MAX_CERTIFICATE_LENGHT'; consider renaming it to 'CLAIM_MAX_CERTIFICATE_LENGTH' for clarity.
```suggestion
#define CLAIM_MAX_CERTIFICATE_LENGTH 10240
```
created_at: 2025-05-21T08:25:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -259,9 +261,9 @@
       "preset": "wolfssl530-asan"
     },
     "cargo": {
-      "name": "wolfssl530",
+      "name": "",
path: .github/tlspuffin.matrix.json
user: login: LCBH
body: Please double check that the same CI tests get triggered (now with CPUT instead of Rust PUT); i.e., make sure the tests filtering nehave the same for CPUT and Rust PUT.
created_at: 2025-05-21T08:27:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -123,4 +123,4 @@ sancov = true
 [wolfssl430]
 sources = { repo = "https://github.com/wolfSSL/wolfssl.git", branch = "v4.3.0-stable", version = "4.3.0" }
 builder = { type = "builtin", name = "wolfssl" }
-sancov = true
\ No newline at end of file
+sancov = true
path: puffin-build/vendors/wolfssl/presets.toml
user: login: LCBH
body: What's here?
created_at: 2025-05-21T08:28:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,6 +1,10 @@
+use std::thread;
+use std::time::Duration;
+
 use tlspuffin::test_utils::prelude::*;
 
-#[apply(test_puts, filter = all(tls13, boringssl))]
+#[cfg(not(feature = "wolfssl430"))]
+#[apply(test_puts, filter = all(tls13, any(boringssl, wolfssl)))]
path: tlspuffin/tests/determinism.rs
user: login: LCBH
body:  - Replace filtering by `not(openssl)` (better when we'll add new PUTs in teh future).
 - Please add a comment explaining why we currently exclude openssl.
created_at: 2025-05-21T08:32:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -120,8 +120,9 @@ fn test_seed_cve_2022_38153(put: &str) {
     let runner = default_runner_for(put);
     let trace = seed_successful12_with_tickets.build_trace();
 
-    for _ in 0..50 {
-        let _ = runner.execute(trace.clone()).unwrap();
+    let _ = runner.execute_config(trace.clone(), true).unwrap();
path: tlspuffin/tests/vulnerabilities.rs
user: login: LCBH
body: Please add a comment here:
```
/* 
Originally, puffin found this bug because wolfssl was not made deterministic at all. The bug requires that the
shared (across sessions) ticket map gets filled until a collision happen (a key refers to two tickets). For this to
happen, we need to create several **different** tickets. This won't happen, now that wolfssl is made
deterministic (the same ticket will be created at the end of a single handshake). We mimick the old behavior
here, using `.execute_config`.

Theoretically, this attack can still be found with a deterministic WolfSSL: mutations could repeat a full
handshake multiple times (hence yielding different tickets) and then appending the malicious handshake.
This is extremely unlikely to happen though; or even impossible given the bounds on the trace lengths. In
the future, we might want to reconsider whether we **always** want to reseed prior to executing a trace.
*/

```
created_at: 2025-05-21T08:38:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -129,4 +129,4 @@ def main():
             print("\t\t%s" % dup)
 
 if __name__=='__main__':
-    main()
\ No newline at end of file
+    main()
path: tools/asanalyzer.py
user: login: LCBH
body: What's here?
created_at: 2025-05-21T08:39:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,9 +1,12 @@
 #ifndef TLSPUFFIN_CLAIM_INTERFACE_H
 #define TLSPUFFIN_CLAIM_INTERFACE_H
 
+#include <stdint.h>
+
 #define CLAIM_MAX_AVAILABLE_CIPHERS 256
 #define CLAIM_MAX_SECRET_SIZE 64 /* longest known is SHA512 */
 #define CLAIM_SESSION_ID_LENGTH 32
+#define CLAIM_MAX_CERTIFICATE_LENGHT 10240
path: tlspuffin-claims/claim-interface.h
user: login: LCBH
body: Yes!
created_at: 2025-05-21T08:40:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,1305 @@
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+
+#include <wolfssl/internal.h>
+#include <wolfssl/wolfcrypt/cryptocb.h>
+#include <wolfssl/wolfcrypt/settings.h>
+
+#include <puffin/puffin.h>
+#include <puffin/tls.h>
+
+#include <claim-interface.h>
+
+#include <string.h>
+#include <sys/time.h>
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x05007000
+#define TYPETIME sword64
+#else
+#define TYPETIME word32
+#endif
+
+//#define TIME_CHANGE
+#define USE_CUSTOM_PRNG
+#define CLOCKVALUE_DEFAULT 1742309173;
+
+// static int file = -1;
+//#define CREATE(filename) { file = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+//} #define ENTER() fprintf(stderr, "Entering %s\n", __func__) #define ENTER() { char buf[128] = {};
+// int s = snprintf(buf, 128, "Entering %s\n", __func__); write(file, buf, s); }
+
+static uint8_t rng_have_custom_seed = 0;
+#define CUSTOM_SEED_SIZE 256
+#ifdef USE_CUSTOM_PRNG
+static TYPETIME clock_value = 0;
+#endif
+
+static AGENT make_agent(AGENT agent, TLS_AGENT_DESCRIPTOR const *descriptor);
+
+struct AGENT_TYPE
+{
+    uint8_t name;
+
+    TLS_AGENT_DESCRIPTOR descriptor;
+
+    WOLFSSL_CTX *ctx;
+    WOLFSSL *ssl;
+
+    WOLFSSL_BIO *in;
+    WOLFSSL_BIO *out;
+
+    CLAIMER_CB const claimer;
+
+    bool handshake_done;
+    enum ClaimType transcriptType;
+    bool peer_authentication;
+};
+
+// Map a key type enum to the corresponding claim key type.
+static ClaimKeyType map_keysum_claimkeytype(enum Key_Sum key)
+{
+    switch (key)
+    {
+    case DSAk:
+        return CLAIM_KEY_TYPE_DSA;
+    case RSAk:
+        return CLAIM_KEY_TYPE_RSA;
+    case ECDSAk:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    case ED25519k:
+        return CLAIM_KEY_TYPE_ED25519;
+    case X25519k:
+        return CLAIM_KEY_TYPE_X25519;
+    case ED448k:
+        return CLAIM_KEY_TYPE_ED448;
+    case X448k:
+        return CLAIM_KEY_TYPE_X448;
+    case DHk:
+        return CLAIM_KEY_TYPE_DH;
+    case FALCON_LEVEL1k:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    case FALCON_LEVEL5k:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    default:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    }
+}
+
+// Extract the current handshake transcript hash from a TLS agent.
+// Returns the SHA-256 digest size on success, or 0 on failure.
+static int extract_current_transcript(AGENT agent, unsigned char *buffer, int bufferSize)
+{
+    if (agent == NULL)
+    {
+        _log(PUFFIN.warn, "agent is NULL");
+        return 0;
+    }
+    if (agent->ssl == NULL)
+    {
+        _log(PUFFIN.warn, "agent->ssl is NULL");
+        return 0;
+    }
+    if (agent->ssl->hsHashes == NULL)
+    {
+        //_log(PUFFIN.warn, "agent->ssl->hsHashes is NULL");
+        return 0;
+    }
+    if (bufferSize < WC_SHA256_DIGEST_SIZE)
+    {
+        _log(PUFFIN.warn, "buffer size for SHA256 digest is too small");
+        return 0;
+    }
+
+    if (wc_Sha256GetHash(&agent->ssl->hsHashes->hashSha256, buffer) == 0)
+    {
+        return WC_SHA256_DIGEST_SIZE;
+    }
+    else
+    {
+        _log(PUFFIN.warn, "wc_Sha256GetHash failed");
+        return 0;
+    }
+}
+
+// Populate a pre-allocated Claim structure from the current wolfSSL state
+static void fill_claim(AGENT agent, struct Claim *claim)
+{
+    char *error_msg = "no error";
+
+    if (agent->ssl->version.major != SSLv3_MAJOR)
+    {
+        _log(PUFFIN.warn, "not a tls ssl object");
+        return;
+    }
+    switch (agent->ssl->version.minor)
+    {
+    case TLSv1_2_MINOR:
+        claim->version.data = CLAIM_TLS_VERSION_V1_2;
+        break;
+    case TLSv1_3_MINOR:
+        claim->version.data = CLAIM_TLS_VERSION_V1_3;
+        break;
+    default:
+        _log(PUFFIN.warn, "unsupported tls version");
+        return;
+    }
+
+    claim->server = agent->ssl->options.side == WOLFSSL_SERVER_END;
+    claim->peer_authentication = agent->peer_authentication;
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x05003000
+    WOLFSSL_SESSION *session = agent->ssl->session;
+#else
+    WOLFSSL_SESSION *session = &(agent->ssl->session);
+#endif
+    byte session_size = session->sessionIDSz;
+    if (session_size > CLAIM_SESSION_ID_LENGTH)
+    {
+        session_size = CLAIM_SESSION_ID_LENGTH;
+        _log(PUFFIN.warn, "not enough space in session buffer in claim");
+    }
+    // Working ?
+    claim->session_id.length = session_size;
+    memcpy(claim->session_id.data, session->sessionID, claim->session_id.length);
+
+    int buffer_size = wolfSSL_get_client_random(agent->ssl, NULL, 0);
+    if (buffer_size > 0)
+    {
+        if (buffer_size > CLAIM_SESSION_ID_LENGTH)
+        {
+            buffer_size = CLAIM_SESSION_ID_LENGTH;
+            _log(PUFFIN.warn, "not enough space in client random buffer in claim");
+        }
+        wolfSSL_get_client_random(agent->ssl, claim->client_random.data, buffer_size);
+    }
+    else
+    {
+        _log(PUFFIN.warn, "unable to get client random buffer");
+    }
+
+    buffer_size = wolfSSL_get_server_random(agent->ssl, NULL, 0);
+    if (buffer_size > 0)
+    {
+        if (buffer_size > CLAIM_SESSION_ID_LENGTH)
+        {
+            buffer_size = CLAIM_SESSION_ID_LENGTH;
+            _log(PUFFIN.warn, "not enough space in server random buffer in claim");
+        }
+        wolfSSL_get_server_random(agent->ssl, claim->server_random.data, buffer_size);
+    }
+    else
+    {
+        _log(PUFFIN.warn, "unable to get server random buffer");
+    }
+
+    STACK_OF(SSL_CIPHER) *ciphers = wolfSSL_get_ciphers_compat(agent->ssl);
+    if (ciphers != NULL)
+    {
+        int available_ciphers_len = wolfSSL_sk_SSL_CIPHER_num(ciphers);
+        if (available_ciphers_len != WOLFSSL_FATAL_ERROR)
+        {
+            if (available_ciphers_len > CLAIM_MAX_AVAILABLE_CIPHERS)
+            {
+                available_ciphers_len = CLAIM_MAX_AVAILABLE_CIPHERS;
+                _log(PUFFIN.warn, "not enough space in ciphers list in claim");
+            }
+            claim->available_ciphers.length = available_ciphers_len;
+            for (int i = 0; i < available_ciphers_len; ++i)
+            {
+                SSL_CIPHER const *cipher = wolfSSL_sk_SSL_CIPHER_value(ciphers, i);
+                if (cipher != NULL)
+                {
+                    claim->available_ciphers.ciphers[i].data =
+                        (unsigned short)((((short)cipher->cipherSuite0) << 8) +
+                                         cipher->cipherSuite);
+                }
+                else
+                {
+                    _log(PUFFIN.warn, "wolfSSL_sk_SSL_CIPHER_value return a NULL value");
+                }
+            }
+        }
+        else
+        {
+            _log(PUFFIN.warn, "sk_SSL_CIPHER_num return WOLFSSL_FATAL_ERROR");
+        }
+    }
+    else
+    {
+        _log(PUFFIN.warn, "wolfSSL_get_ciphers_compat return NULL");
+    }
+
+    // cert
+    claim->cert.key_length = 0;
+    claim->cert.data_length = 0;
+    WOLFSSL_X509 *cert = wolfSSL_get_certificate(agent->ssl);
+    if (cert != NULL)
+    {
+        uint8_t *data = NULL;
+        int cert_lenght = wolfSSL_i2d_X509(cert, &data);
+        if (cert_lenght > 0)
+        {
+            claim->cert.data_length = MIN(cert_lenght, CLAIM_MAX_CERTIFICATE_LENGHT);
+            memcpy(claim->cert.data, data, claim->cert.data_length);
+        }
+        else
+        {
+            //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate returned an error a ask for a too big
+            // buffer");
+        }
+        wolfSSL_Free(data);
+        int key_type = wolfSSL_X509_get_pubkey_type(cert);
+        if (key_type != WOLFSSL_FAILURE)
+        {
+            WOLFSSL_EVP_PKEY *cert_pkey = wolfSSL_X509_get_pubkey(cert);
+            if (cert_pkey != NULL)
+            {
+                claim->cert.key_length = wolfSSL_EVP_PKEY_bits(cert_pkey);
+                if (claim->cert.key_length != 0)
+                {
+                    claim->cert.key_type = map_keysum_claimkeytype((enum Key_Sum)key_type);
+                }
+                wolfSSL_EVP_PKEY_free(cert_pkey);
+            }
+            else
+            {
+                //_log(PUFFIN.warn, "wolfSSL_X509_get_pubkey return NULL");
+            }
+        }
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "wolfSSL_get_certificate return NULL");
+    }
+
+    // peer cert
+    claim->peer_cert.key_length = 0;
+    claim->peer_cert.data_length = 0;
+    WOLFSSL_X509 *peer_cert = wolfSSL_get_peer_certificate(agent->ssl);
+    if (peer_cert != NULL)
+    {
+        uint8_t *data = NULL;
+        int cert_lenght = wolfSSL_i2d_X509(peer_cert, &data);
+        if (cert_lenght > 0)
+        {
+            claim->peer_cert.data_length = MIN(cert_lenght, CLAIM_MAX_CERTIFICATE_LENGHT);
+            memcpy(claim->peer_cert.data, data, claim->peer_cert.data_length);
+        }
+        else
+        {
+            //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate returned an error a ask for a too big
+            // buffer");
+        }
+        wolfSSL_Free(data);
+        int key_type = wolfSSL_X509_get_pubkey_type(cert);
+        if (key_type != WOLFSSL_FAILURE)
+        {
+            WOLFSSL_EVP_PKEY const *peer_cert_pkey = wolfSSL_X509_get_pubkey(peer_cert);
+            if (peer_cert_pkey != NULL)
+            {
+                claim->peer_cert.key_length = wolfSSL_EVP_PKEY_bits(peer_cert_pkey);
+                if (claim->peer_cert.key_length != 0)
+                {
+                    claim->peer_cert.key_type = map_keysum_claimkeytype((enum Key_Sum)key_type);
+                }
+            }
+            else
+            {
+                //_log(PUFFIN.warn, "wolfSSL_X509_get_pubkey return NULL");
+            }
+        }
+        wolfSSL_X509_free(peer_cert);
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate return NULL");
+    }
+
+    if (agent->ssl->arrays != NULL)
+    {
+        /*if (claim->version.data == CLAIM_TLS_VERSION_V1_2) {
+          memcpy(claim->master_secret_12.secret, agent->ssl->arrays->masterSecret,
+            MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        } else {
+          memcpy(claim->master_secret.secret, agent->ssl->arrays->masterSecret,
+              MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        }*/
+        memcpy(claim->handshake_secret.secret,
+               agent->ssl->arrays->secret,
+               MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        /*memcpy(claim->handshake_secret.secret, agent->ssl->arrays->exporterSecret,
+            MIN(WC_MAX_DIGEST_SIZE, CLAIM_MAX_SECRET_SIZE));*/
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "ssl->arrays is NULL");
+    }
+
+    claim->chosen_cipher.data = wolfSSL_get_current_cipher_suite(agent->ssl);
+    if (claim->chosen_cipher.data == 0)
+    {
+        _log(PUFFIN.warn, "wolfSSL_get_current_cipher returned NULL");
+    }
+
+    /*int nid = -1;
+    int int_retval = wolfSSL_get_signature_nid(agent->ssl, &nid);
+    if (int_retval == WOLFSSL_SUCCESS) {
+      claim->signature_algorithm = nid;
+    } else {
+      claim->signature_algorithm = 0;
+      _log(PUFFIN.warn, "wolfSSL_get_signature_nid failed");
+    }*/
+
+    /*int_retval = wolfSSL_get_peer_signature_nid(agent->ssl, &nid);
+    if (int_retval == WOLFSSL_SUCCESS) {
+      claim->peer_signature_algorithm = nid;
+    } else {
+      claim->peer_signature_algorithm = 0;
+      _log(PUFFIN.warn, "wolfSSL_get_peer_signature_nid failed");
+    }*/
+
+    claim->transcript.length = 0;
+    if (agent->ssl->hsHashes != NULL)
+    {
+        if (wc_Sha256GetHash(&agent->ssl->hsHashes->hashSha256, claim->transcript.data) == 0)
+        {
+            claim->transcript.length = WC_SHA256_DIGEST_SIZE;
+        }
+        else
+        {
+            _log(PUFFIN.warn, "wc_Sha256GetHash failed");
+        }
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "agent->ssl->hsHashes is NULL");
+    }
+
+    return;
+}
+
+static void default_claimer_notify(void *context, Claim *claim)
+{
+    _log(PUFFIN.trace, "call to default claimer `notify`");
+};
+
+static void default_claimer_destroy(void *context)
+{
+    _log(PUFFIN.trace, "call to default claimer `destroy`");
+};
+
+static const CLAIMER_CB DEFAULT_CLAIMER_CB = {.context = NULL,
+                                              .notify = default_claimer_notify,
+                                              .destroy = default_claimer_destroy};
+
+// Translate the result of a wolfSSL operation into a human-readable message and a RESULT_CODE.
+// Returns a dynamically allocated error string describing the last wolfSSL error.
+// If result_code is not NULL, sets it based on the error type.
+// Caller is responsible for freeing the returned string.
+static char *get_result_information(WOLFSSL *ssl, int retval, RESULT_CODE *result_code)
+{
+    int error_code = wolfSSL_get_error(ssl, retval);
+
+    if (result_code != NULL)
+    {
+        switch (error_code)
+        {
+        case WOLFSSL_ERROR_NONE:
+            *result_code = RESULT_OK;
+            break;
+        case WOLFSSL_ERROR_WANT_READ:
+        case WOLFSSL_ERROR_WANT_WRITE:
+            *result_code = RESULT_IO_WOULD_BLOCK;
+            break;
+        default:
+            *result_code = RESULT_ERROR_OTHER;
+            break;
+        }
+    }
+
+    if (error_code == WOLFSSL_ERROR_NONE)
+    {
+        return strdup("no wolfssl error");
+    }
+    char *error_msg = (char *)calloc(1, 81);
path: tlspuffin/harness/wolfssl/src/put.c
user: login: aeyno
body: The error buffer seem a bit small, is it possible to get the size of the error and allocate the message buffer dynamically ?

Currently, I'm dealing with errors that are way longer than 80 char, eg. :

```txt
error in PUT : error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000157::peer sent close notify alert:src/ssl.c:9305,
error:00000158::wrong client/server type:src/internal.c:21825,
error:00000158::wrong client/server type:src/ssl.c:984
```
created_at: 2025-05-21T09:49:45Z
author_association: COLLABORATOR
diff_hunk: @@ -9,6 +13,7 @@ fn test_attacker_full_det_recreate(put: &str) {
 
     let ctx_1 = runner.execute(&trace);
 
+    thread::sleep(Duration::from_secs(2));
path: tlspuffin/tests/determinism.rs
user: login: aeyno
body: Why do we need sleep ?
created_at: 2025-05-21T10:50:00Z
author_association: COLLABORATOR
diff_hunk: @@ -9,6 +13,7 @@ fn test_attacker_full_det_recreate(put: &str) {
 
     let ctx_1 = runner.execute(&trace);
 
+    thread::sleep(Duration::from_secs(2));
path: tlspuffin/tests/determinism.rs
user: login: odemengeon
body: Sleep to introduce a time difference between executions.
This test validates determinism: even with environmental variations
(e.g., current time), the execution trace should produce identical results.
created_at: 2025-05-21T11:44:39Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,1305 @@
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+
+#include <wolfssl/internal.h>
+#include <wolfssl/wolfcrypt/cryptocb.h>
+#include <wolfssl/wolfcrypt/settings.h>
+
+#include <puffin/puffin.h>
+#include <puffin/tls.h>
+
+#include <claim-interface.h>
+
+#include <string.h>
+#include <sys/time.h>
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x05007000
+#define TYPETIME sword64
+#else
+#define TYPETIME word32
+#endif
+
+//#define TIME_CHANGE
+#define USE_CUSTOM_PRNG
+#define CLOCKVALUE_DEFAULT 1742309173;
+
+// static int file = -1;
+//#define CREATE(filename) { file = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+//} #define ENTER() fprintf(stderr, "Entering %s\n", __func__) #define ENTER() { char buf[128] = {};
+// int s = snprintf(buf, 128, "Entering %s\n", __func__); write(file, buf, s); }
+
+static uint8_t rng_have_custom_seed = 0;
+#define CUSTOM_SEED_SIZE 256
+#ifdef USE_CUSTOM_PRNG
+static TYPETIME clock_value = 0;
+#endif
+
+static AGENT make_agent(AGENT agent, TLS_AGENT_DESCRIPTOR const *descriptor);
+
+struct AGENT_TYPE
+{
+    uint8_t name;
+
+    TLS_AGENT_DESCRIPTOR descriptor;
+
+    WOLFSSL_CTX *ctx;
+    WOLFSSL *ssl;
+
+    WOLFSSL_BIO *in;
+    WOLFSSL_BIO *out;
+
+    CLAIMER_CB const claimer;
+
+    bool handshake_done;
+    enum ClaimType transcriptType;
+    bool peer_authentication;
+};
+
+// Map a key type enum to the corresponding claim key type.
+static ClaimKeyType map_keysum_claimkeytype(enum Key_Sum key)
+{
+    switch (key)
+    {
+    case DSAk:
+        return CLAIM_KEY_TYPE_DSA;
+    case RSAk:
+        return CLAIM_KEY_TYPE_RSA;
+    case ECDSAk:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    case ED25519k:
+        return CLAIM_KEY_TYPE_ED25519;
+    case X25519k:
+        return CLAIM_KEY_TYPE_X25519;
+    case ED448k:
+        return CLAIM_KEY_TYPE_ED448;
+    case X448k:
+        return CLAIM_KEY_TYPE_X448;
+    case DHk:
+        return CLAIM_KEY_TYPE_DH;
+    case FALCON_LEVEL1k:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    case FALCON_LEVEL5k:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    default:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    }
+}
+
+// Extract the current handshake transcript hash from a TLS agent.
+// Returns the SHA-256 digest size on success, or 0 on failure.
+static int extract_current_transcript(AGENT agent, unsigned char *buffer, int bufferSize)
+{
+    if (agent == NULL)
+    {
+        _log(PUFFIN.warn, "agent is NULL");
+        return 0;
+    }
+    if (agent->ssl == NULL)
+    {
+        _log(PUFFIN.warn, "agent->ssl is NULL");
+        return 0;
+    }
+    if (agent->ssl->hsHashes == NULL)
+    {
+        //_log(PUFFIN.warn, "agent->ssl->hsHashes is NULL");
+        return 0;
+    }
+    if (bufferSize < WC_SHA256_DIGEST_SIZE)
+    {
+        _log(PUFFIN.warn, "buffer size for SHA256 digest is too small");
+        return 0;
+    }
+
+    if (wc_Sha256GetHash(&agent->ssl->hsHashes->hashSha256, buffer) == 0)
+    {
+        return WC_SHA256_DIGEST_SIZE;
+    }
+    else
+    {
+        _log(PUFFIN.warn, "wc_Sha256GetHash failed");
+        return 0;
+    }
+}
+
+// Populate a pre-allocated Claim structure from the current wolfSSL state
+static void fill_claim(AGENT agent, struct Claim *claim)
+{
+    char *error_msg = "no error";
+
+    if (agent->ssl->version.major != SSLv3_MAJOR)
+    {
+        _log(PUFFIN.warn, "not a tls ssl object");
+        return;
+    }
+    switch (agent->ssl->version.minor)
+    {
+    case TLSv1_2_MINOR:
+        claim->version.data = CLAIM_TLS_VERSION_V1_2;
+        break;
+    case TLSv1_3_MINOR:
+        claim->version.data = CLAIM_TLS_VERSION_V1_3;
+        break;
+    default:
+        _log(PUFFIN.warn, "unsupported tls version");
+        return;
+    }
+
+    claim->server = agent->ssl->options.side == WOLFSSL_SERVER_END;
+    claim->peer_authentication = agent->peer_authentication;
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x05003000
+    WOLFSSL_SESSION *session = agent->ssl->session;
+#else
+    WOLFSSL_SESSION *session = &(agent->ssl->session);
+#endif
+    byte session_size = session->sessionIDSz;
+    if (session_size > CLAIM_SESSION_ID_LENGTH)
+    {
+        session_size = CLAIM_SESSION_ID_LENGTH;
+        _log(PUFFIN.warn, "not enough space in session buffer in claim");
+    }
+    // Working ?
+    claim->session_id.length = session_size;
+    memcpy(claim->session_id.data, session->sessionID, claim->session_id.length);
+
+    int buffer_size = wolfSSL_get_client_random(agent->ssl, NULL, 0);
+    if (buffer_size > 0)
+    {
+        if (buffer_size > CLAIM_SESSION_ID_LENGTH)
+        {
+            buffer_size = CLAIM_SESSION_ID_LENGTH;
+            _log(PUFFIN.warn, "not enough space in client random buffer in claim");
+        }
+        wolfSSL_get_client_random(agent->ssl, claim->client_random.data, buffer_size);
+    }
+    else
+    {
+        _log(PUFFIN.warn, "unable to get client random buffer");
+    }
+
+    buffer_size = wolfSSL_get_server_random(agent->ssl, NULL, 0);
+    if (buffer_size > 0)
+    {
+        if (buffer_size > CLAIM_SESSION_ID_LENGTH)
+        {
+            buffer_size = CLAIM_SESSION_ID_LENGTH;
+            _log(PUFFIN.warn, "not enough space in server random buffer in claim");
+        }
+        wolfSSL_get_server_random(agent->ssl, claim->server_random.data, buffer_size);
+    }
+    else
+    {
+        _log(PUFFIN.warn, "unable to get server random buffer");
+    }
+
+    STACK_OF(SSL_CIPHER) *ciphers = wolfSSL_get_ciphers_compat(agent->ssl);
+    if (ciphers != NULL)
+    {
+        int available_ciphers_len = wolfSSL_sk_SSL_CIPHER_num(ciphers);
+        if (available_ciphers_len != WOLFSSL_FATAL_ERROR)
+        {
+            if (available_ciphers_len > CLAIM_MAX_AVAILABLE_CIPHERS)
+            {
+                available_ciphers_len = CLAIM_MAX_AVAILABLE_CIPHERS;
+                _log(PUFFIN.warn, "not enough space in ciphers list in claim");
+            }
+            claim->available_ciphers.length = available_ciphers_len;
+            for (int i = 0; i < available_ciphers_len; ++i)
+            {
+                SSL_CIPHER const *cipher = wolfSSL_sk_SSL_CIPHER_value(ciphers, i);
+                if (cipher != NULL)
+                {
+                    claim->available_ciphers.ciphers[i].data =
+                        (unsigned short)((((short)cipher->cipherSuite0) << 8) +
+                                         cipher->cipherSuite);
+                }
+                else
+                {
+                    _log(PUFFIN.warn, "wolfSSL_sk_SSL_CIPHER_value return a NULL value");
+                }
+            }
+        }
+        else
+        {
+            _log(PUFFIN.warn, "sk_SSL_CIPHER_num return WOLFSSL_FATAL_ERROR");
+        }
+    }
+    else
+    {
+        _log(PUFFIN.warn, "wolfSSL_get_ciphers_compat return NULL");
+    }
+
+    // cert
+    claim->cert.key_length = 0;
+    claim->cert.data_length = 0;
+    WOLFSSL_X509 *cert = wolfSSL_get_certificate(agent->ssl);
+    if (cert != NULL)
+    {
+        uint8_t *data = NULL;
+        int cert_lenght = wolfSSL_i2d_X509(cert, &data);
+        if (cert_lenght > 0)
+        {
+            claim->cert.data_length = MIN(cert_lenght, CLAIM_MAX_CERTIFICATE_LENGHT);
+            memcpy(claim->cert.data, data, claim->cert.data_length);
+        }
+        else
+        {
+            //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate returned an error a ask for a too big
+            // buffer");
+        }
+        wolfSSL_Free(data);
+        int key_type = wolfSSL_X509_get_pubkey_type(cert);
+        if (key_type != WOLFSSL_FAILURE)
+        {
+            WOLFSSL_EVP_PKEY *cert_pkey = wolfSSL_X509_get_pubkey(cert);
+            if (cert_pkey != NULL)
+            {
+                claim->cert.key_length = wolfSSL_EVP_PKEY_bits(cert_pkey);
+                if (claim->cert.key_length != 0)
+                {
+                    claim->cert.key_type = map_keysum_claimkeytype((enum Key_Sum)key_type);
+                }
+                wolfSSL_EVP_PKEY_free(cert_pkey);
+            }
+            else
+            {
+                //_log(PUFFIN.warn, "wolfSSL_X509_get_pubkey return NULL");
+            }
+        }
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "wolfSSL_get_certificate return NULL");
+    }
+
+    // peer cert
+    claim->peer_cert.key_length = 0;
+    claim->peer_cert.data_length = 0;
+    WOLFSSL_X509 *peer_cert = wolfSSL_get_peer_certificate(agent->ssl);
+    if (peer_cert != NULL)
+    {
+        uint8_t *data = NULL;
+        int cert_lenght = wolfSSL_i2d_X509(peer_cert, &data);
+        if (cert_lenght > 0)
+        {
+            claim->peer_cert.data_length = MIN(cert_lenght, CLAIM_MAX_CERTIFICATE_LENGHT);
+            memcpy(claim->peer_cert.data, data, claim->peer_cert.data_length);
+        }
+        else
+        {
+            //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate returned an error a ask for a too big
+            // buffer");
+        }
+        wolfSSL_Free(data);
+        int key_type = wolfSSL_X509_get_pubkey_type(cert);
+        if (key_type != WOLFSSL_FAILURE)
+        {
+            WOLFSSL_EVP_PKEY const *peer_cert_pkey = wolfSSL_X509_get_pubkey(peer_cert);
+            if (peer_cert_pkey != NULL)
+            {
+                claim->peer_cert.key_length = wolfSSL_EVP_PKEY_bits(peer_cert_pkey);
+                if (claim->peer_cert.key_length != 0)
+                {
+                    claim->peer_cert.key_type = map_keysum_claimkeytype((enum Key_Sum)key_type);
+                }
+            }
+            else
+            {
+                //_log(PUFFIN.warn, "wolfSSL_X509_get_pubkey return NULL");
+            }
+        }
+        wolfSSL_X509_free(peer_cert);
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate return NULL");
+    }
+
+    if (agent->ssl->arrays != NULL)
+    {
+        /*if (claim->version.data == CLAIM_TLS_VERSION_V1_2) {
+          memcpy(claim->master_secret_12.secret, agent->ssl->arrays->masterSecret,
+            MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        } else {
+          memcpy(claim->master_secret.secret, agent->ssl->arrays->masterSecret,
+              MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        }*/
+        memcpy(claim->handshake_secret.secret,
+               agent->ssl->arrays->secret,
+               MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        /*memcpy(claim->handshake_secret.secret, agent->ssl->arrays->exporterSecret,
+            MIN(WC_MAX_DIGEST_SIZE, CLAIM_MAX_SECRET_SIZE));*/
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "ssl->arrays is NULL");
+    }
+
+    claim->chosen_cipher.data = wolfSSL_get_current_cipher_suite(agent->ssl);
+    if (claim->chosen_cipher.data == 0)
+    {
+        _log(PUFFIN.warn, "wolfSSL_get_current_cipher returned NULL");
+    }
+
+    /*int nid = -1;
+    int int_retval = wolfSSL_get_signature_nid(agent->ssl, &nid);
+    if (int_retval == WOLFSSL_SUCCESS) {
+      claim->signature_algorithm = nid;
+    } else {
+      claim->signature_algorithm = 0;
+      _log(PUFFIN.warn, "wolfSSL_get_signature_nid failed");
+    }*/
+
+    /*int_retval = wolfSSL_get_peer_signature_nid(agent->ssl, &nid);
+    if (int_retval == WOLFSSL_SUCCESS) {
+      claim->peer_signature_algorithm = nid;
+    } else {
+      claim->peer_signature_algorithm = 0;
+      _log(PUFFIN.warn, "wolfSSL_get_peer_signature_nid failed");
+    }*/
+
+    claim->transcript.length = 0;
+    if (agent->ssl->hsHashes != NULL)
+    {
+        if (wc_Sha256GetHash(&agent->ssl->hsHashes->hashSha256, claim->transcript.data) == 0)
+        {
+            claim->transcript.length = WC_SHA256_DIGEST_SIZE;
+        }
+        else
+        {
+            _log(PUFFIN.warn, "wc_Sha256GetHash failed");
+        }
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "agent->ssl->hsHashes is NULL");
+    }
+
+    return;
+}
+
+static void default_claimer_notify(void *context, Claim *claim)
+{
+    _log(PUFFIN.trace, "call to default claimer `notify`");
+};
+
+static void default_claimer_destroy(void *context)
+{
+    _log(PUFFIN.trace, "call to default claimer `destroy`");
+};
+
+static const CLAIMER_CB DEFAULT_CLAIMER_CB = {.context = NULL,
+                                              .notify = default_claimer_notify,
+                                              .destroy = default_claimer_destroy};
+
+// Translate the result of a wolfSSL operation into a human-readable message and a RESULT_CODE.
+// Returns a dynamically allocated error string describing the last wolfSSL error.
+// If result_code is not NULL, sets it based on the error type.
+// Caller is responsible for freeing the returned string.
+static char *get_result_information(WOLFSSL *ssl, int retval, RESULT_CODE *result_code)
+{
+    int error_code = wolfSSL_get_error(ssl, retval);
+
+    if (result_code != NULL)
+    {
+        switch (error_code)
+        {
+        case WOLFSSL_ERROR_NONE:
+            *result_code = RESULT_OK;
+            break;
+        case WOLFSSL_ERROR_WANT_READ:
+        case WOLFSSL_ERROR_WANT_WRITE:
+            *result_code = RESULT_IO_WOULD_BLOCK;
+            break;
+        default:
+            *result_code = RESULT_ERROR_OTHER;
+            break;
+        }
+    }
+
+    if (error_code == WOLFSSL_ERROR_NONE)
+    {
+        return strdup("no wolfssl error");
+    }
+    char *error_msg = (char *)calloc(1, 81);
path: tlspuffin/harness/wolfssl/src/put.c
user: login: odemengeon
body:  In wolfSSL 5.x, error messages have a maximum size defined by WOLFSSL_MAX_ERROR_SZ,
 which defaults to 80. Since WOLFSSL_MAX_ERROR_SZ is user-overridable, I will replace
 the hardcoded value 80 with the macro WOLFSSL_MAX_ERROR_SZ for better compatibility.
created_at: 2025-05-21T11:52:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,1305 @@
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+
+#include <wolfssl/internal.h>
+#include <wolfssl/wolfcrypt/cryptocb.h>
+#include <wolfssl/wolfcrypt/settings.h>
+
+#include <puffin/puffin.h>
+#include <puffin/tls.h>
+
+#include <claim-interface.h>
+
+#include <string.h>
+#include <sys/time.h>
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x05007000
+#define TYPETIME sword64
+#else
+#define TYPETIME word32
+#endif
+
+//#define TIME_CHANGE
+#define USE_CUSTOM_PRNG
+#define CLOCKVALUE_DEFAULT 1742309173;
+
+// static int file = -1;
+//#define CREATE(filename) { file = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+//} #define ENTER() fprintf(stderr, "Entering %s\n", __func__) #define ENTER() { char buf[128] = {};
+// int s = snprintf(buf, 128, "Entering %s\n", __func__); write(file, buf, s); }
+
+static uint8_t rng_have_custom_seed = 0;
+#define CUSTOM_SEED_SIZE 256
+#ifdef USE_CUSTOM_PRNG
+static TYPETIME clock_value = 0;
+#endif
+
+static AGENT make_agent(AGENT agent, TLS_AGENT_DESCRIPTOR const *descriptor);
+
+struct AGENT_TYPE
+{
+    uint8_t name;
+
+    TLS_AGENT_DESCRIPTOR descriptor;
+
+    WOLFSSL_CTX *ctx;
+    WOLFSSL *ssl;
+
+    WOLFSSL_BIO *in;
+    WOLFSSL_BIO *out;
+
+    CLAIMER_CB const claimer;
+
+    bool handshake_done;
+    enum ClaimType transcriptType;
+    bool peer_authentication;
+};
+
+// Map a key type enum to the corresponding claim key type.
+static ClaimKeyType map_keysum_claimkeytype(enum Key_Sum key)
+{
+    switch (key)
+    {
+    case DSAk:
+        return CLAIM_KEY_TYPE_DSA;
+    case RSAk:
+        return CLAIM_KEY_TYPE_RSA;
+    case ECDSAk:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    case ED25519k:
+        return CLAIM_KEY_TYPE_ED25519;
+    case X25519k:
+        return CLAIM_KEY_TYPE_X25519;
+    case ED448k:
+        return CLAIM_KEY_TYPE_ED448;
+    case X448k:
+        return CLAIM_KEY_TYPE_X448;
+    case DHk:
+        return CLAIM_KEY_TYPE_DH;
+    case FALCON_LEVEL1k:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    case FALCON_LEVEL5k:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    default:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    }
+}
+
+// Extract the current handshake transcript hash from a TLS agent.
+// Returns the SHA-256 digest size on success, or 0 on failure.
+static int extract_current_transcript(AGENT agent, unsigned char *buffer, int bufferSize)
+{
+    if (agent == NULL)
+    {
+        _log(PUFFIN.warn, "agent is NULL");
+        return 0;
+    }
+    if (agent->ssl == NULL)
+    {
+        _log(PUFFIN.warn, "agent->ssl is NULL");
+        return 0;
+    }
+    if (agent->ssl->hsHashes == NULL)
+    {
+        //_log(PUFFIN.warn, "agent->ssl->hsHashes is NULL");
+        return 0;
+    }
+    if (bufferSize < WC_SHA256_DIGEST_SIZE)
+    {
+        _log(PUFFIN.warn, "buffer size for SHA256 digest is too small");
+        return 0;
+    }
+
+    if (wc_Sha256GetHash(&agent->ssl->hsHashes->hashSha256, buffer) == 0)
+    {
+        return WC_SHA256_DIGEST_SIZE;
+    }
+    else
+    {
+        _log(PUFFIN.warn, "wc_Sha256GetHash failed");
+        return 0;
+    }
+}
+
+// Populate a pre-allocated Claim structure from the current wolfSSL state
+static void fill_claim(AGENT agent, struct Claim *claim)
+{
+    char *error_msg = "no error";
+
+    if (agent->ssl->version.major != SSLv3_MAJOR)
+    {
+        _log(PUFFIN.warn, "not a tls ssl object");
+        return;
+    }
+    switch (agent->ssl->version.minor)
+    {
+    case TLSv1_2_MINOR:
+        claim->version.data = CLAIM_TLS_VERSION_V1_2;
+        break;
+    case TLSv1_3_MINOR:
+        claim->version.data = CLAIM_TLS_VERSION_V1_3;
+        break;
+    default:
+        _log(PUFFIN.warn, "unsupported tls version");
+        return;
+    }
+
+    claim->server = agent->ssl->options.side == WOLFSSL_SERVER_END;
+    claim->peer_authentication = agent->peer_authentication;
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x05003000
+    WOLFSSL_SESSION *session = agent->ssl->session;
+#else
+    WOLFSSL_SESSION *session = &(agent->ssl->session);
+#endif
+    byte session_size = session->sessionIDSz;
+    if (session_size > CLAIM_SESSION_ID_LENGTH)
+    {
+        session_size = CLAIM_SESSION_ID_LENGTH;
+        _log(PUFFIN.warn, "not enough space in session buffer in claim");
+    }
+    // Working ?
+    claim->session_id.length = session_size;
+    memcpy(claim->session_id.data, session->sessionID, claim->session_id.length);
+
+    int buffer_size = wolfSSL_get_client_random(agent->ssl, NULL, 0);
+    if (buffer_size > 0)
+    {
+        if (buffer_size > CLAIM_SESSION_ID_LENGTH)
+        {
+            buffer_size = CLAIM_SESSION_ID_LENGTH;
+            _log(PUFFIN.warn, "not enough space in client random buffer in claim");
+        }
+        wolfSSL_get_client_random(agent->ssl, claim->client_random.data, buffer_size);
+    }
+    else
+    {
+        _log(PUFFIN.warn, "unable to get client random buffer");
+    }
+
+    buffer_size = wolfSSL_get_server_random(agent->ssl, NULL, 0);
+    if (buffer_size > 0)
+    {
+        if (buffer_size > CLAIM_SESSION_ID_LENGTH)
+        {
+            buffer_size = CLAIM_SESSION_ID_LENGTH;
+            _log(PUFFIN.warn, "not enough space in server random buffer in claim");
+        }
+        wolfSSL_get_server_random(agent->ssl, claim->server_random.data, buffer_size);
+    }
+    else
+    {
+        _log(PUFFIN.warn, "unable to get server random buffer");
+    }
+
+    STACK_OF(SSL_CIPHER) *ciphers = wolfSSL_get_ciphers_compat(agent->ssl);
+    if (ciphers != NULL)
+    {
+        int available_ciphers_len = wolfSSL_sk_SSL_CIPHER_num(ciphers);
+        if (available_ciphers_len != WOLFSSL_FATAL_ERROR)
+        {
+            if (available_ciphers_len > CLAIM_MAX_AVAILABLE_CIPHERS)
+            {
+                available_ciphers_len = CLAIM_MAX_AVAILABLE_CIPHERS;
+                _log(PUFFIN.warn, "not enough space in ciphers list in claim");
+            }
+            claim->available_ciphers.length = available_ciphers_len;
+            for (int i = 0; i < available_ciphers_len; ++i)
+            {
+                SSL_CIPHER const *cipher = wolfSSL_sk_SSL_CIPHER_value(ciphers, i);
+                if (cipher != NULL)
+                {
+                    claim->available_ciphers.ciphers[i].data =
+                        (unsigned short)((((short)cipher->cipherSuite0) << 8) +
+                                         cipher->cipherSuite);
+                }
+                else
+                {
+                    _log(PUFFIN.warn, "wolfSSL_sk_SSL_CIPHER_value return a NULL value");
+                }
+            }
+        }
+        else
+        {
+            _log(PUFFIN.warn, "sk_SSL_CIPHER_num return WOLFSSL_FATAL_ERROR");
+        }
+    }
+    else
+    {
+        _log(PUFFIN.warn, "wolfSSL_get_ciphers_compat return NULL");
+    }
+
+    // cert
+    claim->cert.key_length = 0;
+    claim->cert.data_length = 0;
+    WOLFSSL_X509 *cert = wolfSSL_get_certificate(agent->ssl);
+    if (cert != NULL)
+    {
+        uint8_t *data = NULL;
+        int cert_lenght = wolfSSL_i2d_X509(cert, &data);
+        if (cert_lenght > 0)
+        {
+            claim->cert.data_length = MIN(cert_lenght, CLAIM_MAX_CERTIFICATE_LENGHT);
+            memcpy(claim->cert.data, data, claim->cert.data_length);
+        }
+        else
+        {
+            //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate returned an error a ask for a too big
+            // buffer");
+        }
+        wolfSSL_Free(data);
+        int key_type = wolfSSL_X509_get_pubkey_type(cert);
+        if (key_type != WOLFSSL_FAILURE)
+        {
+            WOLFSSL_EVP_PKEY *cert_pkey = wolfSSL_X509_get_pubkey(cert);
+            if (cert_pkey != NULL)
+            {
+                claim->cert.key_length = wolfSSL_EVP_PKEY_bits(cert_pkey);
+                if (claim->cert.key_length != 0)
+                {
+                    claim->cert.key_type = map_keysum_claimkeytype((enum Key_Sum)key_type);
+                }
+                wolfSSL_EVP_PKEY_free(cert_pkey);
+            }
+            else
+            {
+                //_log(PUFFIN.warn, "wolfSSL_X509_get_pubkey return NULL");
+            }
+        }
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "wolfSSL_get_certificate return NULL");
+    }
+
+    // peer cert
+    claim->peer_cert.key_length = 0;
+    claim->peer_cert.data_length = 0;
+    WOLFSSL_X509 *peer_cert = wolfSSL_get_peer_certificate(agent->ssl);
+    if (peer_cert != NULL)
+    {
+        uint8_t *data = NULL;
+        int cert_lenght = wolfSSL_i2d_X509(peer_cert, &data);
+        if (cert_lenght > 0)
+        {
+            claim->peer_cert.data_length = MIN(cert_lenght, CLAIM_MAX_CERTIFICATE_LENGHT);
+            memcpy(claim->peer_cert.data, data, claim->peer_cert.data_length);
+        }
+        else
+        {
+            //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate returned an error a ask for a too big
+            // buffer");
+        }
+        wolfSSL_Free(data);
+        int key_type = wolfSSL_X509_get_pubkey_type(cert);
+        if (key_type != WOLFSSL_FAILURE)
+        {
+            WOLFSSL_EVP_PKEY const *peer_cert_pkey = wolfSSL_X509_get_pubkey(peer_cert);
+            if (peer_cert_pkey != NULL)
+            {
+                claim->peer_cert.key_length = wolfSSL_EVP_PKEY_bits(peer_cert_pkey);
+                if (claim->peer_cert.key_length != 0)
+                {
+                    claim->peer_cert.key_type = map_keysum_claimkeytype((enum Key_Sum)key_type);
+                }
+            }
+            else
+            {
+                //_log(PUFFIN.warn, "wolfSSL_X509_get_pubkey return NULL");
+            }
+        }
+        wolfSSL_X509_free(peer_cert);
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate return NULL");
+    }
+
+    if (agent->ssl->arrays != NULL)
+    {
+        /*if (claim->version.data == CLAIM_TLS_VERSION_V1_2) {
+          memcpy(claim->master_secret_12.secret, agent->ssl->arrays->masterSecret,
+            MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        } else {
+          memcpy(claim->master_secret.secret, agent->ssl->arrays->masterSecret,
+              MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        }*/
+        memcpy(claim->handshake_secret.secret,
+               agent->ssl->arrays->secret,
+               MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        /*memcpy(claim->handshake_secret.secret, agent->ssl->arrays->exporterSecret,
+            MIN(WC_MAX_DIGEST_SIZE, CLAIM_MAX_SECRET_SIZE));*/
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "ssl->arrays is NULL");
+    }
+
+    claim->chosen_cipher.data = wolfSSL_get_current_cipher_suite(agent->ssl);
+    if (claim->chosen_cipher.data == 0)
+    {
+        _log(PUFFIN.warn, "wolfSSL_get_current_cipher returned NULL");
+    }
+
+    /*int nid = -1;
+    int int_retval = wolfSSL_get_signature_nid(agent->ssl, &nid);
+    if (int_retval == WOLFSSL_SUCCESS) {
+      claim->signature_algorithm = nid;
+    } else {
+      claim->signature_algorithm = 0;
+      _log(PUFFIN.warn, "wolfSSL_get_signature_nid failed");
+    }*/
+
+    /*int_retval = wolfSSL_get_peer_signature_nid(agent->ssl, &nid);
+    if (int_retval == WOLFSSL_SUCCESS) {
+      claim->peer_signature_algorithm = nid;
+    } else {
+      claim->peer_signature_algorithm = 0;
+      _log(PUFFIN.warn, "wolfSSL_get_peer_signature_nid failed");
+    }*/
+
+    claim->transcript.length = 0;
+    if (agent->ssl->hsHashes != NULL)
+    {
+        if (wc_Sha256GetHash(&agent->ssl->hsHashes->hashSha256, claim->transcript.data) == 0)
+        {
+            claim->transcript.length = WC_SHA256_DIGEST_SIZE;
+        }
+        else
+        {
+            _log(PUFFIN.warn, "wc_Sha256GetHash failed");
+        }
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "agent->ssl->hsHashes is NULL");
+    }
+
+    return;
+}
+
+static void default_claimer_notify(void *context, Claim *claim)
+{
+    _log(PUFFIN.trace, "call to default claimer `notify`");
+};
+
+static void default_claimer_destroy(void *context)
+{
+    _log(PUFFIN.trace, "call to default claimer `destroy`");
+};
+
+static const CLAIMER_CB DEFAULT_CLAIMER_CB = {.context = NULL,
+                                              .notify = default_claimer_notify,
+                                              .destroy = default_claimer_destroy};
+
+// Translate the result of a wolfSSL operation into a human-readable message and a RESULT_CODE.
+// Returns a dynamically allocated error string describing the last wolfSSL error.
+// If result_code is not NULL, sets it based on the error type.
+// Caller is responsible for freeing the returned string.
+static char *get_result_information(WOLFSSL *ssl, int retval, RESULT_CODE *result_code)
+{
+    int error_code = wolfSSL_get_error(ssl, retval);
+
+    if (result_code != NULL)
+    {
+        switch (error_code)
+        {
+        case WOLFSSL_ERROR_NONE:
+            *result_code = RESULT_OK;
+            break;
+        case WOLFSSL_ERROR_WANT_READ:
+        case WOLFSSL_ERROR_WANT_WRITE:
+            *result_code = RESULT_IO_WOULD_BLOCK;
+            break;
+        default:
+            *result_code = RESULT_ERROR_OTHER;
+            break;
+        }
+    }
+
+    if (error_code == WOLFSSL_ERROR_NONE)
+    {
+        return strdup("no wolfssl error");
+    }
+    char *error_msg = (char *)calloc(1, 81);
path: tlspuffin/harness/wolfssl/src/put.c
user: login: LCBH
body: @aeyno Are you sure your error example originates from `wolfSSL_ERR_error_string_n`? (Because it is true that its output is capped by WOLFSSL_MAX_ERROR_SZ (80 by default)).
created_at: 2025-05-21T13:11:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,6 +1,10 @@
+use std::thread;
+use std::time::Duration;
+
 use tlspuffin::test_utils::prelude::*;
 
-#[apply(test_puts, filter = all(tls13, boringssl))]
+#[cfg(not(feature = "wolfssl430"))]
+#[apply(test_puts, filter = all(tls13, any(boringssl, wolfssl)))]
path: tlspuffin/tests/determinism.rs
user: login: LCBH
body: Comment to add:
```rust
/*
OpenSSL is currently excuded because: (i) we currently don't have a working RNG reseed for the
OpenSSL CPUT (see Issue #393), (ii) we don't have a time hook at the moment for the OpenSSL CPUT.
*/
```
created_at: 2025-05-21T13:18:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,1305 @@
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+
+#include <wolfssl/internal.h>
+#include <wolfssl/wolfcrypt/cryptocb.h>
+#include <wolfssl/wolfcrypt/settings.h>
+
+#include <puffin/puffin.h>
+#include <puffin/tls.h>
+
+#include <claim-interface.h>
+
+#include <string.h>
+#include <sys/time.h>
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x05007000
+#define TYPETIME sword64
+#else
+#define TYPETIME word32
+#endif
+
+//#define TIME_CHANGE
+#define USE_CUSTOM_PRNG
+#define CLOCKVALUE_DEFAULT 1742309173;
+
+// static int file = -1;
+//#define CREATE(filename) { file = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+//} #define ENTER() fprintf(stderr, "Entering %s\n", __func__) #define ENTER() { char buf[128] = {};
+// int s = snprintf(buf, 128, "Entering %s\n", __func__); write(file, buf, s); }
+
+static uint8_t rng_have_custom_seed = 0;
+#define CUSTOM_SEED_SIZE 256
+#ifdef USE_CUSTOM_PRNG
+static TYPETIME clock_value = 0;
+#endif
+
+static AGENT make_agent(AGENT agent, TLS_AGENT_DESCRIPTOR const *descriptor);
+
+struct AGENT_TYPE
+{
+    uint8_t name;
+
+    TLS_AGENT_DESCRIPTOR descriptor;
+
+    WOLFSSL_CTX *ctx;
+    WOLFSSL *ssl;
+
+    WOLFSSL_BIO *in;
+    WOLFSSL_BIO *out;
+
+    CLAIMER_CB const claimer;
+
+    bool handshake_done;
+    enum ClaimType transcriptType;
+    bool peer_authentication;
+};
+
+// Map a key type enum to the corresponding claim key type.
+static ClaimKeyType map_keysum_claimkeytype(enum Key_Sum key)
+{
+    switch (key)
+    {
+    case DSAk:
+        return CLAIM_KEY_TYPE_DSA;
+    case RSAk:
+        return CLAIM_KEY_TYPE_RSA;
+    case ECDSAk:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    case ED25519k:
+        return CLAIM_KEY_TYPE_ED25519;
+    case X25519k:
+        return CLAIM_KEY_TYPE_X25519;
+    case ED448k:
+        return CLAIM_KEY_TYPE_ED448;
+    case X448k:
+        return CLAIM_KEY_TYPE_X448;
+    case DHk:
+        return CLAIM_KEY_TYPE_DH;
+    case FALCON_LEVEL1k:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    case FALCON_LEVEL5k:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    default:
+        return CLAIM_KEY_TYPE_UNKNOWN;
+    }
+}
+
+// Extract the current handshake transcript hash from a TLS agent.
+// Returns the SHA-256 digest size on success, or 0 on failure.
+static int extract_current_transcript(AGENT agent, unsigned char *buffer, int bufferSize)
+{
+    if (agent == NULL)
+    {
+        _log(PUFFIN.warn, "agent is NULL");
+        return 0;
+    }
+    if (agent->ssl == NULL)
+    {
+        _log(PUFFIN.warn, "agent->ssl is NULL");
+        return 0;
+    }
+    if (agent->ssl->hsHashes == NULL)
+    {
+        //_log(PUFFIN.warn, "agent->ssl->hsHashes is NULL");
+        return 0;
+    }
+    if (bufferSize < WC_SHA256_DIGEST_SIZE)
+    {
+        _log(PUFFIN.warn, "buffer size for SHA256 digest is too small");
+        return 0;
+    }
+
+    if (wc_Sha256GetHash(&agent->ssl->hsHashes->hashSha256, buffer) == 0)
+    {
+        return WC_SHA256_DIGEST_SIZE;
+    }
+    else
+    {
+        _log(PUFFIN.warn, "wc_Sha256GetHash failed");
+        return 0;
+    }
+}
+
+// Populate a pre-allocated Claim structure from the current wolfSSL state
+static void fill_claim(AGENT agent, struct Claim *claim)
+{
+    char *error_msg = "no error";
+
+    if (agent->ssl->version.major != SSLv3_MAJOR)
+    {
+        _log(PUFFIN.warn, "not a tls ssl object");
+        return;
+    }
+    switch (agent->ssl->version.minor)
+    {
+    case TLSv1_2_MINOR:
+        claim->version.data = CLAIM_TLS_VERSION_V1_2;
+        break;
+    case TLSv1_3_MINOR:
+        claim->version.data = CLAIM_TLS_VERSION_V1_3;
+        break;
+    default:
+        _log(PUFFIN.warn, "unsupported tls version");
+        return;
+    }
+
+    claim->server = agent->ssl->options.side == WOLFSSL_SERVER_END;
+    claim->peer_authentication = agent->peer_authentication;
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x05003000
+    WOLFSSL_SESSION *session = agent->ssl->session;
+#else
+    WOLFSSL_SESSION *session = &(agent->ssl->session);
+#endif
+    byte session_size = session->sessionIDSz;
+    if (session_size > CLAIM_SESSION_ID_LENGTH)
+    {
+        session_size = CLAIM_SESSION_ID_LENGTH;
+        _log(PUFFIN.warn, "not enough space in session buffer in claim");
+    }
+    // Working ?
+    claim->session_id.length = session_size;
+    memcpy(claim->session_id.data, session->sessionID, claim->session_id.length);
+
+    int buffer_size = wolfSSL_get_client_random(agent->ssl, NULL, 0);
+    if (buffer_size > 0)
+    {
+        if (buffer_size > CLAIM_SESSION_ID_LENGTH)
+        {
+            buffer_size = CLAIM_SESSION_ID_LENGTH;
+            _log(PUFFIN.warn, "not enough space in client random buffer in claim");
+        }
+        wolfSSL_get_client_random(agent->ssl, claim->client_random.data, buffer_size);
+    }
+    else
+    {
+        _log(PUFFIN.warn, "unable to get client random buffer");
+    }
+
+    buffer_size = wolfSSL_get_server_random(agent->ssl, NULL, 0);
+    if (buffer_size > 0)
+    {
+        if (buffer_size > CLAIM_SESSION_ID_LENGTH)
+        {
+            buffer_size = CLAIM_SESSION_ID_LENGTH;
+            _log(PUFFIN.warn, "not enough space in server random buffer in claim");
+        }
+        wolfSSL_get_server_random(agent->ssl, claim->server_random.data, buffer_size);
+    }
+    else
+    {
+        _log(PUFFIN.warn, "unable to get server random buffer");
+    }
+
+    STACK_OF(SSL_CIPHER) *ciphers = wolfSSL_get_ciphers_compat(agent->ssl);
+    if (ciphers != NULL)
+    {
+        int available_ciphers_len = wolfSSL_sk_SSL_CIPHER_num(ciphers);
+        if (available_ciphers_len != WOLFSSL_FATAL_ERROR)
+        {
+            if (available_ciphers_len > CLAIM_MAX_AVAILABLE_CIPHERS)
+            {
+                available_ciphers_len = CLAIM_MAX_AVAILABLE_CIPHERS;
+                _log(PUFFIN.warn, "not enough space in ciphers list in claim");
+            }
+            claim->available_ciphers.length = available_ciphers_len;
+            for (int i = 0; i < available_ciphers_len; ++i)
+            {
+                SSL_CIPHER const *cipher = wolfSSL_sk_SSL_CIPHER_value(ciphers, i);
+                if (cipher != NULL)
+                {
+                    claim->available_ciphers.ciphers[i].data =
+                        (unsigned short)((((short)cipher->cipherSuite0) << 8) +
+                                         cipher->cipherSuite);
+                }
+                else
+                {
+                    _log(PUFFIN.warn, "wolfSSL_sk_SSL_CIPHER_value return a NULL value");
+                }
+            }
+        }
+        else
+        {
+            _log(PUFFIN.warn, "sk_SSL_CIPHER_num return WOLFSSL_FATAL_ERROR");
+        }
+    }
+    else
+    {
+        _log(PUFFIN.warn, "wolfSSL_get_ciphers_compat return NULL");
+    }
+
+    // cert
+    claim->cert.key_length = 0;
+    claim->cert.data_length = 0;
+    WOLFSSL_X509 *cert = wolfSSL_get_certificate(agent->ssl);
+    if (cert != NULL)
+    {
+        uint8_t *data = NULL;
+        int cert_lenght = wolfSSL_i2d_X509(cert, &data);
+        if (cert_lenght > 0)
+        {
+            claim->cert.data_length = MIN(cert_lenght, CLAIM_MAX_CERTIFICATE_LENGHT);
+            memcpy(claim->cert.data, data, claim->cert.data_length);
+        }
+        else
+        {
+            //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate returned an error a ask for a too big
+            // buffer");
+        }
+        wolfSSL_Free(data);
+        int key_type = wolfSSL_X509_get_pubkey_type(cert);
+        if (key_type != WOLFSSL_FAILURE)
+        {
+            WOLFSSL_EVP_PKEY *cert_pkey = wolfSSL_X509_get_pubkey(cert);
+            if (cert_pkey != NULL)
+            {
+                claim->cert.key_length = wolfSSL_EVP_PKEY_bits(cert_pkey);
+                if (claim->cert.key_length != 0)
+                {
+                    claim->cert.key_type = map_keysum_claimkeytype((enum Key_Sum)key_type);
+                }
+                wolfSSL_EVP_PKEY_free(cert_pkey);
+            }
+            else
+            {
+                //_log(PUFFIN.warn, "wolfSSL_X509_get_pubkey return NULL");
+            }
+        }
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "wolfSSL_get_certificate return NULL");
+    }
+
+    // peer cert
+    claim->peer_cert.key_length = 0;
+    claim->peer_cert.data_length = 0;
+    WOLFSSL_X509 *peer_cert = wolfSSL_get_peer_certificate(agent->ssl);
+    if (peer_cert != NULL)
+    {
+        uint8_t *data = NULL;
+        int cert_lenght = wolfSSL_i2d_X509(peer_cert, &data);
+        if (cert_lenght > 0)
+        {
+            claim->peer_cert.data_length = MIN(cert_lenght, CLAIM_MAX_CERTIFICATE_LENGHT);
+            memcpy(claim->peer_cert.data, data, claim->peer_cert.data_length);
+        }
+        else
+        {
+            //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate returned an error a ask for a too big
+            // buffer");
+        }
+        wolfSSL_Free(data);
+        int key_type = wolfSSL_X509_get_pubkey_type(cert);
+        if (key_type != WOLFSSL_FAILURE)
+        {
+            WOLFSSL_EVP_PKEY const *peer_cert_pkey = wolfSSL_X509_get_pubkey(peer_cert);
+            if (peer_cert_pkey != NULL)
+            {
+                claim->peer_cert.key_length = wolfSSL_EVP_PKEY_bits(peer_cert_pkey);
+                if (claim->peer_cert.key_length != 0)
+                {
+                    claim->peer_cert.key_type = map_keysum_claimkeytype((enum Key_Sum)key_type);
+                }
+            }
+            else
+            {
+                //_log(PUFFIN.warn, "wolfSSL_X509_get_pubkey return NULL");
+            }
+        }
+        wolfSSL_X509_free(peer_cert);
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "wolfSSL_get_peer_certificate return NULL");
+    }
+
+    if (agent->ssl->arrays != NULL)
+    {
+        /*if (claim->version.data == CLAIM_TLS_VERSION_V1_2) {
+          memcpy(claim->master_secret_12.secret, agent->ssl->arrays->masterSecret,
+            MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        } else {
+          memcpy(claim->master_secret.secret, agent->ssl->arrays->masterSecret,
+              MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        }*/
+        memcpy(claim->handshake_secret.secret,
+               agent->ssl->arrays->secret,
+               MIN(SECRET_LEN, CLAIM_MAX_SECRET_SIZE));
+        /*memcpy(claim->handshake_secret.secret, agent->ssl->arrays->exporterSecret,
+            MIN(WC_MAX_DIGEST_SIZE, CLAIM_MAX_SECRET_SIZE));*/
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "ssl->arrays is NULL");
+    }
+
+    claim->chosen_cipher.data = wolfSSL_get_current_cipher_suite(agent->ssl);
+    if (claim->chosen_cipher.data == 0)
+    {
+        _log(PUFFIN.warn, "wolfSSL_get_current_cipher returned NULL");
+    }
+
+    /*int nid = -1;
+    int int_retval = wolfSSL_get_signature_nid(agent->ssl, &nid);
+    if (int_retval == WOLFSSL_SUCCESS) {
+      claim->signature_algorithm = nid;
+    } else {
+      claim->signature_algorithm = 0;
+      _log(PUFFIN.warn, "wolfSSL_get_signature_nid failed");
+    }*/
+
+    /*int_retval = wolfSSL_get_peer_signature_nid(agent->ssl, &nid);
+    if (int_retval == WOLFSSL_SUCCESS) {
+      claim->peer_signature_algorithm = nid;
+    } else {
+      claim->peer_signature_algorithm = 0;
+      _log(PUFFIN.warn, "wolfSSL_get_peer_signature_nid failed");
+    }*/
+
+    claim->transcript.length = 0;
+    if (agent->ssl->hsHashes != NULL)
+    {
+        if (wc_Sha256GetHash(&agent->ssl->hsHashes->hashSha256, claim->transcript.data) == 0)
+        {
+            claim->transcript.length = WC_SHA256_DIGEST_SIZE;
+        }
+        else
+        {
+            _log(PUFFIN.warn, "wc_Sha256GetHash failed");
+        }
+    }
+    else
+    {
+        //_log(PUFFIN.warn, "agent->ssl->hsHashes is NULL");
+    }
+
+    return;
+}
+
+static void default_claimer_notify(void *context, Claim *claim)
+{
+    _log(PUFFIN.trace, "call to default claimer `notify`");
+};
+
+static void default_claimer_destroy(void *context)
+{
+    _log(PUFFIN.trace, "call to default claimer `destroy`");
+};
+
+static const CLAIMER_CB DEFAULT_CLAIMER_CB = {.context = NULL,
+                                              .notify = default_claimer_notify,
+                                              .destroy = default_claimer_destroy};
+
+// Translate the result of a wolfSSL operation into a human-readable message and a RESULT_CODE.
+// Returns a dynamically allocated error string describing the last wolfSSL error.
+// If result_code is not NULL, sets it based on the error type.
+// Caller is responsible for freeing the returned string.
+static char *get_result_information(WOLFSSL *ssl, int retval, RESULT_CODE *result_code)
+{
+    int error_code = wolfSSL_get_error(ssl, retval);
+
+    if (result_code != NULL)
+    {
+        switch (error_code)
+        {
+        case WOLFSSL_ERROR_NONE:
+            *result_code = RESULT_OK;
+            break;
+        case WOLFSSL_ERROR_WANT_READ:
+        case WOLFSSL_ERROR_WANT_WRITE:
+            *result_code = RESULT_IO_WOULD_BLOCK;
+            break;
+        default:
+            *result_code = RESULT_ERROR_OTHER;
+            break;
+        }
+    }
+
+    if (error_code == WOLFSSL_ERROR_NONE)
+    {
+        return strdup("no wolfssl error");
+    }
+    char *error_msg = (char *)calloc(1, 81);
path: tlspuffin/harness/wolfssl/src/put.c
user: login: LCBH
body: After investigation: Rust PUT computes a stack of error codes here https://github.com/tlspuffin/tlspuffin/blob/84cbf310e042e773472747ce0b1f0bba5e7ead9f/crates/wolfssl/src/error.rs#L19
created_at: 2025-05-21T13:50:52Z
author_association: CONTRIBUTOR

PR comments:
user: login: LCBH
created_at: 2025-05-15T10:18:07Z
body: I started to comment the PR but it seems that your rebase removed some recent changes from other PRs.
author_association: CONTRIBUTOR
user: login: LCBH
created_at: 2025-05-21T08:39:36Z
body: Please close the old, outdated comments and resolve the new ones. We can merge after that.
author_association: CONTRIBUTOR