Merge remote-tracking branch 'origin/pr/574' into baremetal
diff --git a/ChangeLog b/ChangeLog
index 1196de5..e769dc2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -20,6 +20,10 @@
      changed its IP or port. The feature is enabled at compile-time by setting
      MBEDTLS_SSL_DTLS_CONNECTION_ID (disabled by default), and at run-time
      through the new APIs mbedtls_ssl_conf_cid() and mbedtls_ssl_set_cid().
+   * Add new API functions mbedtls_ssl_session_save() and
+     mbedtls_ssl_session_load() to allow serializing a session, for example to
+     store it in non-volatile storage, and later using it for TLS session
+     resumption.
 
 Bugfix
    * Server's RSA certificate in certs.c was SHA-1 signed. In the default
diff --git a/include/mbedtls/error.h b/include/mbedtls/error.h
index 765fd42..31f294f 100644
--- a/include/mbedtls/error.h
+++ b/include/mbedtls/error.h
@@ -100,6 +100,7 @@
  * ECP       4   10 (Started from top)
  * MD        5   5
  * HKDF      5   1 (Started from top)
+ * SSL       5   1 (Started from 0x5F00)
  * CIPHER    6   8 (Started from 0x6080)
  * SSL       6   24 (Started from top, plus 0x6000)
  * SSL       7   32
diff --git a/include/mbedtls/ssl.h b/include/mbedtls/ssl.h
index 7571a3f..7f073af 100644
--- a/include/mbedtls/ssl.h
+++ b/include/mbedtls/ssl.h
@@ -123,6 +123,7 @@
 #define MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS                 -0x6500  /**< The asynchronous operation is not completed yet. */
 #define MBEDTLS_ERR_SSL_EARLY_MESSAGE                     -0x6480  /**< Internal-only message signaling that a message arrived early. */
 #define MBEDTLS_ERR_SSL_UNEXPECTED_CID                    -0x6000  /**< An encrypted DTLS-frame with an unexpected CID was received. */
+#define MBEDTLS_ERR_SSL_VERSION_MISMATCH                  -0x5F00  /**< An operation failed due to an unexpected version or configuration. */
 #define MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS                -0x7000  /**< A cryptographic operation is in progress. Try again later. */
 
 /*
@@ -810,6 +811,14 @@
 
 /*
  * This structure is used for storing current session data.
+ *
+ * Note: when changing this definition, we need to check and update:
+ *  - in tests/suites/test_suite_ssl.function:
+ *      ssl_populate_session() and ssl_serialize_session_save_load()
+ *  - in library/ssl_tls.c:
+ *      mbedtls_ssl_session_init() and mbedtls_ssl_session_free()
+ *      mbedtls_ssl_session_save() and ssl_session_load()
+ *      ssl_session_copy()
  */
 struct mbedtls_ssl_session
 {
@@ -2146,6 +2155,90 @@
 #endif /* MBEDTLS_SSL_CLI_C */
 
 /**
+ * \brief          Load serialized session data into a session structure.
+ *                 On client, this can be used for loading saved sessions
+ *                 before resuming them with mbedstls_ssl_set_session().
+ *                 On server, this can be used for alternative implementations
+ *                 of session cache or session tickets.
+ *
+ * \warning        If a peer certificate chain is associated with the session,
+ *                 the serialized state will only contain the peer's
+ *                 end-entity certificate and the result of the chain
+ *                 verification (unless verification was disabled), but not
+ *                 the rest of the chain.
+ *
+ * \see            mbedtls_ssl_session_save()
+ * \see            mbedtls_ssl_set_session()
+ *
+ * \param session  The session structure to be populated. It must have been
+ *                 initialised with mbedtls_ssl_session_init() but not
+ *                 populated yet.
+ * \param buf      The buffer holding the serialized session data. It must be a
+ *                 readable buffer of at least \p len bytes.
+ * \param len      The size of the serialized data in bytes.
+ *
+ * \return         \c 0 if successful.
+ * \return         #MBEDTLS_ERR_SSL_ALLOC_FAILED if memory allocation failed.
+ * \return         #MBEDTLS_ERR_SSL_BAD_INPUT_DATA if input data is invalid.
+ * \return         #MBEDTLS_ERR_SSL_VERSION_MISMATCH if the serialized data
+ *                 was generated in a different version or configuration of
+ *                 Mbed TLS.
+ * \return         Another negative value for other kinds of errors (for
+ *                 example, unsupported features in the embedded certificate).
+ */
+int mbedtls_ssl_session_load( mbedtls_ssl_session *session,
+                              const unsigned char *buf,
+                              size_t len );
+
+/**
+ * \brief          Save session structure as serialized data in a buffer.
+ *                 On client, this can be used for saving session data,
+ *                 potentially in non-volatile storage, for resuming later.
+ *                 On server, this can be used for alternative implementations
+ *                 of session cache or session tickets.
+ *
+ * \see            mbedtls_ssl_session_load()
+ * \see            mbedtls_ssl_get_session_pointer()
+ *
+ * \param session  The session structure to be saved.
+ * \param buf      The buffer to write the serialized data to. It must be a
+ *                 writeable buffer of at least \p len bytes, or may be \c
+ *                 NULL if \p len is \c 0.
+ * \param buf_len  The number of bytes available for writing in \p buf.
+ * \param olen     The size in bytes of the data that has been or would have
+ *                 been written. It must point to a valid \c size_t.
+ *
+ * \note           \p olen is updated to the correct value regardless of
+ *                 whether \p buf_len was large enough. This makes it possible
+ *                 to determine the necessary size by calling this function
+ *                 with \p buf set to \c NULL and \p buf_len to \c 0.
+ *
+ * \return         \c 0 if successful.
+ * \return         #MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL if \p buf is too small.
+ */
+int mbedtls_ssl_session_save( const mbedtls_ssl_session *session,
+                              unsigned char *buf,
+                              size_t buf_len,
+                              size_t *olen );
+
+/**
+ * \brief          Get a pointer to the current session structure, for example
+ *                 to serialize it.
+ *
+ * \warning        Ownership of the session remains with the SSL context, and
+ *                 the returned pointer is only guaranteed to be valid until
+ *                 the next API call operating on the same \p ssl context.
+ *
+ * \see            mbedtls_ssl_session_save()
+ *
+ * \param ssl      The SSL context.
+ *
+ * \return         A pointer to the current session if successful.
+ * \return         \c NULL if no session is active.
+ */
+const mbedtls_ssl_session *mbedtls_ssl_get_session_pointer( const mbedtls_ssl_context *ssl );
+
+/**
  * \brief               Set the list of allowed ciphersuites and the preference
  *                      order. First in the list has the highest preference.
  *                      (Overrides all version-specific lists)
diff --git a/library/error.c b/library/error.c
index 0a9baeb..546fa49 100644
--- a/library/error.c
+++ b/library/error.c
@@ -525,6 +525,8 @@
             mbedtls_snprintf( buf, buflen, "SSL - Internal-only message signaling that a message arrived early" );
         if( use_ret == -(MBEDTLS_ERR_SSL_UNEXPECTED_CID) )
             mbedtls_snprintf( buf, buflen, "SSL - An encrypted DTLS-frame with an unexpected CID was received" );
+        if( use_ret == -(MBEDTLS_ERR_SSL_VERSION_MISMATCH) )
+            mbedtls_snprintf( buf, buflen, "SSL - An operation failed due to an unexpected version or configuration" );
         if( use_ret == -(MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS) )
             mbedtls_snprintf( buf, buflen, "SSL - A cryptographic operation is in progress. Try again later" );
 #endif /* MBEDTLS_SSL_TLS_C */
diff --git a/library/ssl_ticket.c b/library/ssl_ticket.c
index 8492c19..5fe693c 100644
--- a/library/ssl_ticket.c
+++ b/library/ssl_ticket.c
@@ -157,115 +157,6 @@
 }
 
 /*
- * Serialize a session in the following format:
- *  0   .   n-1     session structure, n = sizeof(mbedtls_ssl_session)
- *  n   .   n+2     peer_cert length = m (0 if no certificate)
- *  n+3 .   n+2+m   peer cert ASN.1
- */
-static int ssl_save_session( const mbedtls_ssl_session *session,
-                             unsigned char *buf, size_t buf_len,
-                             size_t *olen )
-{
-    unsigned char *p = buf;
-    size_t left = buf_len;
-#if defined(MBEDTLS_X509_CRT_PARSE_C)
-    size_t cert_len;
-#endif /* MBEDTLS_X509_CRT_PARSE_C */
-
-    if( left < sizeof( mbedtls_ssl_session ) )
-        return( MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL );
-
-    memcpy( p, session, sizeof( mbedtls_ssl_session ) );
-    p += sizeof( mbedtls_ssl_session );
-    left -= sizeof( mbedtls_ssl_session );
-
-#if defined(MBEDTLS_X509_CRT_PARSE_C)
-    if( session->peer_cert == NULL )
-        cert_len = 0;
-    else
-        cert_len = session->peer_cert->raw.len;
-
-    if( left < 3 + cert_len )
-        return( MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL );
-
-    *p++ = (unsigned char)( ( cert_len >> 16 ) & 0xFF );
-    *p++ = (unsigned char)( ( cert_len >>  8 ) & 0xFF );
-    *p++ = (unsigned char)( ( cert_len       ) & 0xFF );
-
-    if( session->peer_cert != NULL )
-        memcpy( p, session->peer_cert->raw.p, cert_len );
-
-    p += cert_len;
-#endif /* MBEDTLS_X509_CRT_PARSE_C */
-
-    *olen = p - buf;
-
-    return( 0 );
-}
-
-/*
- * Unserialise session, see ssl_save_session()
- */
-static int ssl_load_session( mbedtls_ssl_session *session,
-                             const unsigned char *buf, size_t len )
-{
-    const unsigned char *p = buf;
-    const unsigned char * const end = buf + len;
-#if defined(MBEDTLS_X509_CRT_PARSE_C)
-    size_t cert_len;
-#endif /* MBEDTLS_X509_CRT_PARSE_C */
-
-    if( sizeof( mbedtls_ssl_session ) > (size_t)( end - p ) )
-        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
-
-    memcpy( session, p, sizeof( mbedtls_ssl_session ) );
-    p += sizeof( mbedtls_ssl_session );
-
-#if defined(MBEDTLS_X509_CRT_PARSE_C)
-    if( 3 > (size_t)( end - p ) )
-        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
-
-    cert_len = ( p[0] << 16 ) | ( p[1] << 8 ) | p[2];
-    p += 3;
-
-    if( cert_len == 0 )
-    {
-        session->peer_cert = NULL;
-    }
-    else
-    {
-        int ret;
-
-        if( cert_len > (size_t)( end - p ) )
-            return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
-
-        session->peer_cert = mbedtls_calloc( 1, sizeof( mbedtls_x509_crt ) );
-
-        if( session->peer_cert == NULL )
-            return( MBEDTLS_ERR_SSL_ALLOC_FAILED );
-
-        mbedtls_x509_crt_init( session->peer_cert );
-
-        if( ( ret = mbedtls_x509_crt_parse_der( session->peer_cert,
-                                                p, cert_len ) ) != 0 )
-        {
-            mbedtls_x509_crt_free( session->peer_cert );
-            mbedtls_free( session->peer_cert );
-            session->peer_cert = NULL;
-            return( ret );
-        }
-
-        p += cert_len;
-    }
-#endif /* MBEDTLS_X509_CRT_PARSE_C */
-
-    if( p != end )
-        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
-
-    return( 0 );
-}
-
-/*
  * Create session ticket, with the following structure:
  *
  *    struct {
@@ -323,8 +214,9 @@
         goto cleanup;
 
     /* Dump session state */
-    if( ( ret = ssl_save_session( session,
-                                  state, end - state, &clear_len ) ) != 0 ||
+    if( ( ret = mbedtls_ssl_session_save( session,
+                                          state, end - state,
+                                          &clear_len ) ) != 0 ||
         (unsigned long) clear_len > 65535 )
     {
          goto cleanup;
@@ -441,7 +333,7 @@
     }
 
     /* Actually load session */
-    if( ( ret = ssl_load_session( session, ticket, clear_len ) ) != 0 )
+    if( ( ret = mbedtls_ssl_session_load( session, ticket, clear_len ) ) != 0 )
         goto cleanup;
 
 #if defined(MBEDTLS_HAVE_TIME)
diff --git a/library/ssl_tls.c b/library/ssl_tls.c
index 81bed84..b61453f 100644
--- a/library/ssl_tls.c
+++ b/library/ssl_tls.c
@@ -47,6 +47,7 @@
 #include "mbedtls/ssl.h"
 #include "mbedtls/ssl_internal.h"
 #include "mbedtls/platform_util.h"
+#include "mbedtls/version.h"
 
 #include <string.h>
 
@@ -8767,6 +8768,493 @@
 }
 #endif /* MBEDTLS_SSL_CLI_C */
 
+const mbedtls_ssl_session *mbedtls_ssl_get_session_pointer( const mbedtls_ssl_context *ssl )
+{
+    if( ssl == NULL )
+        return( NULL );
+
+    return( ssl->session );
+}
+
+/*
+ * Define ticket header determining Mbed TLS version
+ * and structure of the ticket.
+ */
+
+/*
+ * Define bitflag determining compile-time settings influencing
+ * structure of serialized SSL sessions.
+ */
+
+#if defined(MBEDTLS_HAVE_TIME)
+#define SSL_SERIALIZED_SESSION_CONFIG_TIME 1
+#else
+#define SSL_SERIALIZED_SESSION_CONFIG_TIME 0
+#endif /* MBEDTLS_HAVE_TIME */
+
+#if defined(MBEDTLS_X509_CRT_PARSE_C)
+#define SSL_SERIALIZED_SESSION_CONFIG_CRT 1
+#else
+#define SSL_SERIALIZED_SESSION_CONFIG_CRT 0
+#endif /* MBEDTLS_X509_CRT_PARSE_C */
+
+#if defined(MBEDTLS_SSL_CLI_C) && defined(MBEDTLS_SSL_SESSION_TICKETS)
+#define SSL_SERIALIZED_SESSION_CONFIG_CLIENT_TICKET 1
+#else
+#define SSL_SERIALIZED_SESSION_CONFIG_CLIENT_TICKET 0
+#endif /* MBEDTLS_SSL_CLI_C && MBEDTLS_SSL_SESSION_TICKETS */
+
+#if defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH)
+#define SSL_SERIALIZED_SESSION_CONFIG_MFL 1
+#else
+#define SSL_SERIALIZED_SESSION_CONFIG_MFL 0
+#endif /* MBEDTLS_SSL_MAX_FRAGMENT_LENGTH */
+
+#if defined(MBEDTLS_SSL_TRUNCATED_HMAC)
+#define SSL_SERIALIZED_SESSION_CONFIG_TRUNC_HMAC 1
+#else
+#define SSL_SERIALIZED_SESSION_CONFIG_TRUNC_HMAC 0
+#endif /* MBEDTLS_SSL_TRUNCATED_HMAC */
+
+#if defined(MBEDTLS_SSL_ENCRYPT_THEN_MAC)
+#define SSL_SERIALIZED_SESSION_CONFIG_ETM 1
+#else
+#define SSL_SERIALIZED_SESSION_CONFIG_ETM 0
+#endif /* MBEDTLS_SSL_ENCRYPT_THEN_MAC */
+
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+#define SSL_SERIALIZED_SESSION_CONFIG_TICKET 1
+#else
+#define SSL_SERIALIZED_SESSION_CONFIG_TICKET 0
+#endif /* MBEDTLS_SSL_SESSION_TICKETS */
+
+#define SSL_SERIALIZED_SESSION_CONFIG_TIME_BIT          0
+#define SSL_SERIALIZED_SESSION_CONFIG_CRT_BIT           1
+#define SSL_SERIALIZED_SESSION_CONFIG_CLIENT_TICKET_BIT 2
+#define SSL_SERIALIZED_SESSION_CONFIG_MFL_BIT           3
+#define SSL_SERIALIZED_SESSION_CONFIG_TRUNC_HMAC_BIT    4
+#define SSL_SERIALIZED_SESSION_CONFIG_ETM_BIT           5
+#define SSL_SERIALIZED_SESSION_CONFIG_TICKET_BIT        6
+
+#define SSL_SERIALIZED_SESSION_CONFIG_BITFLAG                           \
+    ( (uint16_t) (                                                      \
+        ( SSL_SERIALIZED_SESSION_CONFIG_TIME          << SSL_SERIALIZED_SESSION_CONFIG_TIME_BIT          ) | \
+        ( SSL_SERIALIZED_SESSION_CONFIG_CRT           << SSL_SERIALIZED_SESSION_CONFIG_CRT_BIT           ) | \
+        ( SSL_SERIALIZED_SESSION_CONFIG_CLIENT_TICKET << SSL_SERIALIZED_SESSION_CONFIG_CLIENT_TICKET_BIT ) | \
+        ( SSL_SERIALIZED_SESSION_CONFIG_MFL           << SSL_SERIALIZED_SESSION_CONFIG_MFL_BIT           ) | \
+        ( SSL_SERIALIZED_SESSION_CONFIG_TRUNC_HMAC    << SSL_SERIALIZED_SESSION_CONFIG_TRUNC_HMAC_BIT    ) | \
+        ( SSL_SERIALIZED_SESSION_CONFIG_ETM           << SSL_SERIALIZED_SESSION_CONFIG_ETM_BIT           ) | \
+        ( SSL_SERIALIZED_SESSION_CONFIG_TICKET        << SSL_SERIALIZED_SESSION_CONFIG_TICKET_BIT        ) ) )
+
+static unsigned char ssl_serialized_session_header[] = {
+    MBEDTLS_VERSION_MAJOR,
+    MBEDTLS_VERSION_MINOR,
+    MBEDTLS_VERSION_PATCH,
+    ( SSL_SERIALIZED_SESSION_CONFIG_BITFLAG >> 8 ) & 0xFF,
+    ( SSL_SERIALIZED_SESSION_CONFIG_BITFLAG >> 0 ) & 0xFF,
+};
+
+/*
+ * Serialize a session in the following format:
+ * (in the presentation language of TLS, RFC 8446 section 3)
+ *
+ *  opaque mbedtls_version[3];   // major, minor, patch
+ *  opaque session_format[2];    // version-specific 16-bit field determining
+ *                               // the format of the remaining
+ *                               // serialized data.
+ *
+ *  Note: When updating the format, remember to keep
+ *        these version+format bytes.
+ *
+ *                               // In this version, `session_format` determines
+ *                               // the setting of those compile-time
+ *                               // configuration options which influence
+ *                               // the structure of mbedtls_ssl_session.
+ *  uint64 start_time;
+ *  uint8 ciphersuite[2];        // defined by the standard
+ *  uint8 compression;           // 0 or 1
+ *  uint8 session_id_len;        // at most 32
+ *  opaque session_id[32];
+ *  opaque master[48];           // fixed length in the standard
+ *  uint32 verify_result;
+ *  opaque peer_cert<0..2^24-1>; // length 0 means no peer cert
+ *  opaque ticket<0..2^24-1>;    // length 0 means no ticket
+ *  uint32 ticket_lifetime;
+ *  uint8 mfl_code;              // up to 255 according to standard
+ *  uint8 trunc_hmac;            // 0 or 1
+ *  uint8 encrypt_then_mac;      // 0 or 1
+ *
+ * The order is the same as in the definition of the structure, except
+ * verify_result is put before peer_cert so that all mandatory fields come
+ * together in one block.
+ */
+int mbedtls_ssl_session_save( const mbedtls_ssl_session *session,
+                              unsigned char *buf,
+                              size_t buf_len,
+                              size_t *olen )
+{
+    unsigned char *p = buf;
+    size_t used = 0;
+#if defined(MBEDTLS_HAVE_TIME)
+    uint64_t start;
+#endif
+#if defined(MBEDTLS_X509_CRT_PARSE_C)
+    size_t cert_len;
+#endif
+
+    /*
+     * Add version identifier
+     */
+
+    used += sizeof( ssl_serialized_session_header );
+
+    if( used <= buf_len )
+    {
+        memcpy( p, ssl_serialized_session_header,
+                sizeof( ssl_serialized_session_header ) );
+        p += sizeof( ssl_serialized_session_header );
+    }
+
+    /*
+     * Time
+     */
+#if defined(MBEDTLS_HAVE_TIME)
+    used += 8;
+
+    if( used <= buf_len )
+    {
+        start = (uint64_t) session->start;
+
+        *p++ = (unsigned char)( ( start >> 56 ) & 0xFF );
+        *p++ = (unsigned char)( ( start >> 48 ) & 0xFF );
+        *p++ = (unsigned char)( ( start >> 40 ) & 0xFF );
+        *p++ = (unsigned char)( ( start >> 32 ) & 0xFF );
+        *p++ = (unsigned char)( ( start >> 24 ) & 0xFF );
+        *p++ = (unsigned char)( ( start >> 16 ) & 0xFF );
+        *p++ = (unsigned char)( ( start >>  8 ) & 0xFF );
+        *p++ = (unsigned char)( ( start       ) & 0xFF );
+    }
+#endif /* MBEDTLS_HAVE_TIME */
+
+    /*
+     * Basic mandatory fields
+     */
+    used += 2   /* ciphersuite */
+          + 1   /* compression */
+          + 1   /* id_len */
+          + sizeof( session->id )
+          + sizeof( session->master )
+          + 4;  /* verify_result */
+
+    if( used <= buf_len )
+    {
+        *p++ = (unsigned char)( ( session->ciphersuite >> 8 ) & 0xFF );
+        *p++ = (unsigned char)( ( session->ciphersuite      ) & 0xFF );
+
+        *p++ = (unsigned char)( session->compression & 0xFF );
+
+        *p++ = (unsigned char)( session->id_len & 0xFF );
+        memcpy( p, session->id, 32 );
+        p += 32;
+
+        memcpy( p, session->master, 48 );
+        p += 48;
+
+        *p++ = (unsigned char)( ( session->verify_result >> 24 ) & 0xFF );
+        *p++ = (unsigned char)( ( session->verify_result >> 16 ) & 0xFF );
+        *p++ = (unsigned char)( ( session->verify_result >>  8 ) & 0xFF );
+        *p++ = (unsigned char)( ( session->verify_result       ) & 0xFF );
+    }
+
+    /*
+     * Peer's end-entity certificate
+     */
+#if defined(MBEDTLS_X509_CRT_PARSE_C)
+    if( session->peer_cert == NULL )
+        cert_len = 0;
+    else
+        cert_len = session->peer_cert->raw.len;
+
+    used += 3 + cert_len;
+
+    if( used <= buf_len )
+    {
+        *p++ = (unsigned char)( ( cert_len >> 16 ) & 0xFF );
+        *p++ = (unsigned char)( ( cert_len >>  8 ) & 0xFF );
+        *p++ = (unsigned char)( ( cert_len       ) & 0xFF );
+
+        if( session->peer_cert != NULL )
+        {
+            memcpy( p, session->peer_cert->raw.p, cert_len );
+            p += cert_len;
+        }
+    }
+#endif /* MBEDTLS_X509_CRT_PARSE_C */
+
+    /*
+     * Session ticket if any, plus associated data
+     */
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+    used += 3 + session->ticket_len + 4; /* len + ticket + lifetime */
+
+    if( used <= buf_len )
+    {
+        *p++ = (unsigned char)( ( session->ticket_len >> 16 ) & 0xFF );
+        *p++ = (unsigned char)( ( session->ticket_len >>  8 ) & 0xFF );
+        *p++ = (unsigned char)( ( session->ticket_len       ) & 0xFF );
+
+        if( session->ticket != NULL )
+        {
+            memcpy( p, session->ticket, session->ticket_len );
+            p += session->ticket_len;
+        }
+
+        *p++ = (unsigned char)( ( session->ticket_lifetime >> 24 ) & 0xFF );
+        *p++ = (unsigned char)( ( session->ticket_lifetime >> 16 ) & 0xFF );
+        *p++ = (unsigned char)( ( session->ticket_lifetime >>  8 ) & 0xFF );
+        *p++ = (unsigned char)( ( session->ticket_lifetime       ) & 0xFF );
+    }
+#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
+
+    /*
+     * Misc extension-related info
+     */
+#if defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH)
+    used += 1;
+
+    if( used <= buf_len )
+        *p++ = session->mfl_code;
+#endif
+
+#if defined(MBEDTLS_SSL_TRUNCATED_HMAC)
+    used += 1;
+
+    if( used <= buf_len )
+        *p++ = (unsigned char)( ( session->trunc_hmac ) & 0xFF );
+#endif
+
+#if defined(MBEDTLS_SSL_ENCRYPT_THEN_MAC)
+    used += 1;
+
+    if( used <= buf_len )
+        *p++ = (unsigned char)( ( session->encrypt_then_mac ) & 0xFF );
+#endif
+
+    /* Done */
+    *olen = used;
+
+    if( used > buf_len )
+        return( MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL );
+
+    return( 0 );
+}
+
+/*
+ * Unserialize session, see mbedtls_ssl_session_save() for format.
+ *
+ * This internal version is wrapped by a public function that cleans up in
+ * case of error.
+ */
+static int ssl_session_load( mbedtls_ssl_session *session,
+                             const unsigned char *buf,
+                             size_t len )
+{
+    const unsigned char *p = buf;
+    const unsigned char * const end = buf + len;
+#if defined(MBEDTLS_HAVE_TIME)
+    uint64_t start;
+#endif
+#if defined(MBEDTLS_X509_CRT_PARSE_C)
+    size_t cert_len;
+#endif /* MBEDTLS_X509_CRT_PARSE_C */
+
+    /*
+     * Check version identifier
+     */
+
+    if( (size_t)( end - p ) < sizeof( ssl_serialized_session_header ) )
+        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+
+    if( memcmp( p, ssl_serialized_session_header,
+                sizeof( ssl_serialized_session_header ) ) != 0 )
+    {
+        return( MBEDTLS_ERR_SSL_VERSION_MISMATCH );
+    }
+    p += sizeof( ssl_serialized_session_header );
+
+    /*
+     * Time
+     */
+#if defined(MBEDTLS_HAVE_TIME)
+    if( 8 > (size_t)( end - p ) )
+        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+
+    start = ( (uint64_t) p[0] << 56 ) |
+            ( (uint64_t) p[1] << 48 ) |
+            ( (uint64_t) p[2] << 40 ) |
+            ( (uint64_t) p[3] << 32 ) |
+            ( (uint64_t) p[4] << 24 ) |
+            ( (uint64_t) p[5] << 16 ) |
+            ( (uint64_t) p[6] <<  8 ) |
+            ( (uint64_t) p[7]       );
+    p += 8;
+
+    session->start = (time_t) start;
+#endif /* MBEDTLS_HAVE_TIME */
+
+    /*
+     * Basic mandatory fields
+     */
+    if( 2 + 1 + 1 + 32 + 48 + 4 > (size_t)( end - p ) )
+        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+
+    session->ciphersuite = ( p[0] << 8 ) | p[1];
+    p += 2;
+
+    session->compression = *p++;
+
+    session->id_len = *p++;
+    memcpy( session->id, p, 32 );
+    p += 32;
+
+    memcpy( session->master, p, 48 );
+    p += 48;
+
+    session->verify_result = ( (uint32_t) p[0] << 24 ) |
+                             ( (uint32_t) p[1] << 16 ) |
+                             ( (uint32_t) p[2] <<  8 ) |
+                             ( (uint32_t) p[3]       );
+    p += 4;
+
+    /* Immediately clear invalid pointer values that have been read, in case
+     * we exit early before we replaced them with valid ones. */
+#if defined(MBEDTLS_X509_CRT_PARSE_C)
+    session->peer_cert = NULL;
+#endif
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+    session->ticket = NULL;
+#endif
+
+    /*
+     * Peer certificate
+     */
+#if defined(MBEDTLS_X509_CRT_PARSE_C)
+    if( 3 > (size_t)( end - p ) )
+        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+
+    cert_len = ( p[0] << 16 ) | ( p[1] << 8 ) | p[2];
+    p += 3;
+
+    if( cert_len == 0 )
+    {
+        session->peer_cert = NULL;
+    }
+    else
+    {
+        int ret;
+
+        if( cert_len > (size_t)( end - p ) )
+            return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+
+        session->peer_cert = mbedtls_calloc( 1, sizeof( mbedtls_x509_crt ) );
+
+        if( session->peer_cert == NULL )
+            return( MBEDTLS_ERR_SSL_ALLOC_FAILED );
+
+        mbedtls_x509_crt_init( session->peer_cert );
+
+        if( ( ret = mbedtls_x509_crt_parse_der( session->peer_cert,
+                                                p, cert_len ) ) != 0 )
+        {
+            mbedtls_x509_crt_free( session->peer_cert );
+            mbedtls_free( session->peer_cert );
+            session->peer_cert = NULL;
+            return( ret );
+        }
+
+        p += cert_len;
+    }
+#endif /* MBEDTLS_X509_CRT_PARSE_C */
+
+    /*
+     * Session ticket and associated data
+     */
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+    if( 3 > (size_t)( end - p ) )
+        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+
+    session->ticket_len = ( p[0] << 16 ) | ( p[1] << 8 ) | p[2];
+    p += 3;
+
+    if( session->ticket_len != 0 )
+    {
+        if( session->ticket_len > (size_t)( end - p ) )
+            return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+
+        session->ticket = mbedtls_calloc( 1, session->ticket_len );
+        if( session->ticket == NULL )
+            return( MBEDTLS_ERR_SSL_ALLOC_FAILED );
+
+        memcpy( session->ticket, p, session->ticket_len );
+        p += session->ticket_len;
+    }
+
+    if( 4 > (size_t)( end - p ) )
+        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+
+    session->ticket_lifetime = ( (uint32_t) p[0] << 24 ) |
+                               ( (uint32_t) p[1] << 16 ) |
+                               ( (uint32_t) p[2] <<  8 ) |
+                               ( (uint32_t) p[3]       );
+    p += 4;
+#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
+
+    /*
+     * Misc extension-related info
+     */
+#if defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH)
+    if( 1 > (size_t)( end - p ) )
+        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+
+    session->mfl_code = *p++;
+#endif
+
+#if defined(MBEDTLS_SSL_TRUNCATED_HMAC)
+    if( 1 > (size_t)( end - p ) )
+        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+
+    session->trunc_hmac = *p++;
+#endif
+
+#if defined(MBEDTLS_SSL_ENCRYPT_THEN_MAC)
+    if( 1 > (size_t)( end - p ) )
+        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+
+    session->encrypt_then_mac = *p++;
+#endif
+
+    /* Done, should have consumed entire buffer */
+    if( p != end )
+        return( MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+
+    return( 0 );
+}
+
+/*
+ * Unserialize session: public wrapper for error cleaning
+ */
+int mbedtls_ssl_session_load( mbedtls_ssl_session *session,
+                              const unsigned char *buf,
+                              size_t len )
+{
+    int ret = ssl_session_load( session, buf, len );
+
+    if( ret != 0 )
+        mbedtls_ssl_session_free( session );
+
+    return( ret );
+}
+
 /*
  * Perform a single step of the SSL handshake
  */
diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c
index 5ac3985..38c94be 100644
--- a/programs/ssl/ssl_client2.c
+++ b/programs/ssl/ssl_client2.c
@@ -36,6 +36,8 @@
 #define mbedtls_fprintf    fprintf
 #define mbedtls_snprintf   snprintf
 #define mbedtls_exit            exit
+#define mbedtls_calloc      calloc
+#define mbedtls_free        free
 #define MBEDTLS_EXIT_SUCCESS    EXIT_SUCCESS
 #define MBEDTLS_EXIT_FAILURE    EXIT_FAILURE
 #endif
@@ -103,6 +105,7 @@
 #define DFL_DHMLEN              -1
 #define DFL_RECONNECT           0
 #define DFL_RECO_DELAY          0
+#define DFL_RECO_MODE           1
 #define DFL_CID_ENABLED         0
 #define DFL_CID_VALUE           ""
 #define DFL_CID_ENABLED_RENEGO  -1
@@ -309,8 +312,11 @@
     "    allow_legacy=%%d     default: (library default: no)\n"   \
     USAGE_RENEGO                                            \
     "    exchanges=%%d        default: 1\n"                 \
-    "    reconnect=%%d        default: 0 (disabled)\n"      \
+    "    reconnect=%%d        number of reconnections using session resumption\n" \
+    "                        default: 0 (disabled)\n"      \
     "    reco_delay=%%d       default: 0 seconds\n"         \
+    "    reco_mode=%%d        0: copy session, 1: serialize session\n" \
+    "                        default: 1\n"      \
     "    reconnect_hard=%%d   default: 0 (disabled)\n"      \
     USAGE_TICKETS                                           \
     USAGE_MAX_FRAG_LEN                                      \
@@ -392,6 +398,7 @@
     int dhmlen;                 /* minimum DHM params len in bits           */
     int reconnect;              /* attempt to resume session                */
     int reco_delay;             /* delay in seconds before resuming session */
+    int reco_mode;              /* how to keep the session around           */
     int reconnect_hard;         /* unexpectedly reconnect from the same port */
     int tickets;                /* enable / disable session tickets         */
     const char *curves;         /* list of supported elliptic curves        */
@@ -716,6 +723,8 @@
     mbedtls_ssl_context ssl;
     mbedtls_ssl_config conf;
     mbedtls_ssl_session saved_session;
+    unsigned char *session_data = NULL;
+    size_t session_data_len = 0;
 #if defined(MBEDTLS_TIMING_C)
     mbedtls_timing_delay_context timer;
 #endif
@@ -805,6 +814,7 @@
     opt.dhmlen              = DFL_DHMLEN;
     opt.reconnect           = DFL_RECONNECT;
     opt.reco_delay          = DFL_RECO_DELAY;
+    opt.reco_mode           = DFL_RECO_MODE;
     opt.reconnect_hard      = DFL_RECONNECT_HARD;
     opt.tickets             = DFL_TICKETS;
     opt.alpn_string         = DFL_ALPN_STRING;
@@ -971,6 +981,12 @@
             if( opt.reco_delay < 0 )
                 goto usage;
         }
+        else if( strcmp( p, "reco_mode" ) == 0 )
+        {
+            opt.reco_mode = atoi( q );
+            if( opt.reco_mode < 0 )
+                goto usage;
+        }
         else if( strcmp( p, "reconnect_hard" ) == 0 )
         {
             opt.reconnect_hard = atoi( q );
@@ -1863,14 +1879,55 @@
         mbedtls_printf("  . Saving session for reuse..." );
         fflush( stdout );
 
-        if( ( ret = mbedtls_ssl_get_session( &ssl, &saved_session ) ) != 0 )
+        if( opt.reco_mode == 1 )
         {
-            mbedtls_printf( " failed\n  ! mbedtls_ssl_get_session returned -0x%x\n\n",
-                            -ret );
-            goto exit;
+            /* free any previously saved data */
+            if( session_data != NULL )
+            {
+                mbedtls_platform_zeroize( session_data, session_data_len );
+                mbedtls_free( session_data );
+                session_data = NULL;
+            }
+
+            /* get size of the buffer needed */
+            mbedtls_ssl_session_save( mbedtls_ssl_get_session_pointer( &ssl ),
+                                      NULL, 0, &session_data_len );
+            session_data = mbedtls_calloc( 1, session_data_len );
+            if( session_data == NULL )
+            {
+                mbedtls_printf( " failed\n  ! alloc %u bytes for session data\n",
+                                (unsigned) session_data_len );
+                ret = MBEDTLS_ERR_SSL_ALLOC_FAILED;
+                goto exit;
+            }
+
+            /* actually save session data */
+            if( ( ret = mbedtls_ssl_session_save( mbedtls_ssl_get_session_pointer( &ssl ),
+                                                  session_data, session_data_len,
+                                                  &session_data_len ) ) != 0 )
+            {
+                mbedtls_printf( " failed\n  ! mbedtls_ssl_session_saved returned -0x%04x\n\n",
+                                -ret );
+                goto exit;
+            }
+        }
+        else
+        {
+            if( ( ret = mbedtls_ssl_get_session( &ssl, &saved_session ) ) != 0 )
+            {
+                mbedtls_printf( " failed\n  ! mbedtls_ssl_get_session returned -0x%x\n\n",
+                                -ret );
+                goto exit;
+            }
         }
 
         mbedtls_printf( " ok\n" );
+
+        if( opt.reco_mode == 1 )
+        {
+            mbedtls_printf( "    [ Saved %u bytes of session data]\n",
+                            (unsigned) session_data_len );
+        }
     }
 
 #if defined(MBEDTLS_X509_CRT_PARSE_C)
@@ -2306,10 +2363,22 @@
             goto exit;
         }
 
+        if( opt.reco_mode == 1 )
+        {
+            if( ( ret = mbedtls_ssl_session_load( &saved_session,
+                                                  session_data,
+                                                  session_data_len ) ) != 0 )
+            {
+                mbedtls_printf( " failed\n  ! mbedtls_ssl_session_load returned -0x%x\n\n",
+                                -ret );
+                goto exit;
+            }
+        }
+
         if( ( ret = mbedtls_ssl_set_session( &ssl, &saved_session ) ) != 0 )
         {
-            mbedtls_printf( " failed\n  ! mbedtls_ssl_conf_session returned %d\n\n",
-                            ret );
+            mbedtls_printf( " failed\n  ! mbedtls_ssl_set_session returned -0x%x\n\n",
+                            -ret );
             goto exit;
         }
 
@@ -2376,6 +2445,9 @@
     mbedtls_ssl_config_free( &conf );
     mbedtls_ctr_drbg_free( &ctr_drbg );
     mbedtls_entropy_free( &entropy );
+    if( session_data != NULL )
+        mbedtls_platform_zeroize( session_data, session_data_len );
+    mbedtls_free( session_data );
 
 #if defined(_WIN32)
     mbedtls_printf( "  + Press Enter to exit this program.\n" );
diff --git a/tests/scripts/all.sh b/tests/scripts/all.sh
index 5150cd6..9af040f 100755
--- a/tests/scripts/all.sh
+++ b/tests/scripts/all.sh
@@ -702,7 +702,7 @@
 
 component_test_small_mbedtls_ssl_dtls_max_buffering () {
     msg "build: small MBEDTLS_SSL_DTLS_MAX_BUFFERING #1"
-    scripts/config.pl set MBEDTLS_SSL_DTLS_MAX_BUFFERING 240
+    scripts/config.pl set MBEDTLS_SSL_DTLS_MAX_BUFFERING 190
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh
index 2ea177c..da89642 100755
--- a/tests/ssl-opt.sh
+++ b/tests/ssl-opt.sh
@@ -2071,6 +2071,20 @@
             -S "a session has been resumed" \
             -C "a session has been resumed"
 
+run_test    "Session resume using tickets: session copy" \
+            "$P_SRV debug_level=3 tickets=1 cache_max=0" \
+            "$P_CLI debug_level=3 tickets=1 reconnect=1 reco_mode=0" \
+            0 \
+            -c "client hello, adding session ticket extension" \
+            -s "found session ticket extension" \
+            -s "server hello, adding session ticket extension" \
+            -c "found session_ticket extension" \
+            -c "parse new session ticket" \
+            -S "session successfully restored from cache" \
+            -s "session successfully restored from ticket" \
+            -s "a session has been resumed" \
+            -c "a session has been resumed"
+
 run_test    "Session resume using tickets: openssl server" \
             "$O_SRV" \
             "$P_CLI debug_level=3 tickets=1 reconnect=1" \
@@ -2136,6 +2150,20 @@
             -S "a session has been resumed" \
             -C "a session has been resumed"
 
+run_test    "Session resume using tickets, DTLS: session copy" \
+            "$P_SRV debug_level=3 dtls=1 tickets=1 cache_max=0" \
+            "$P_CLI debug_level=3 dtls=1 tickets=1 reconnect=1 reco_mode=0" \
+            0 \
+            -c "client hello, adding session ticket extension" \
+            -s "found session ticket extension" \
+            -s "server hello, adding session ticket extension" \
+            -c "found session_ticket extension" \
+            -c "parse new session ticket" \
+            -S "session successfully restored from cache" \
+            -s "session successfully restored from ticket" \
+            -s "a session has been resumed" \
+            -c "a session has been resumed"
+
 run_test    "Session resume using tickets, DTLS: openssl server" \
             "$O_SRV -dtls1" \
             "$P_CLI dtls=1 debug_level=3 tickets=1 reconnect=1" \
@@ -2232,6 +2260,15 @@
             -s "a session has been resumed" \
             -c "a session has been resumed"
 
+run_test    "Session resume using cache: session copy" \
+            "$P_SRV debug_level=3 tickets=0" \
+            "$P_CLI debug_level=3 tickets=0 reconnect=1 reco_mode=0" \
+            0 \
+            -s "session successfully restored from cache" \
+            -S "session successfully restored from ticket" \
+            -s "a session has been resumed" \
+            -c "a session has been resumed"
+
 run_test    "Session resume using cache: openssl client" \
             "$P_SRV debug_level=3 tickets=0" \
             "( $O_CLI -sess_out $SESSION; \
@@ -2327,6 +2364,15 @@
             -s "a session has been resumed" \
             -c "a session has been resumed"
 
+run_test    "Session resume using cache, DTLS: session copy" \
+            "$P_SRV dtls=1 debug_level=3 tickets=0" \
+            "$P_CLI dtls=1 debug_level=3 tickets=0 reconnect=1 reco_mode=0" \
+            0 \
+            -s "session successfully restored from cache" \
+            -S "session successfully restored from ticket" \
+            -s "a session has been resumed" \
+            -c "a session has been resumed"
+
 run_test    "Session resume using cache, DTLS: openssl client" \
             "$P_SRV dtls=1 debug_level=3 tickets=0" \
             "( $O_CLI -dtls1 -sess_out $SESSION; \
@@ -7665,11 +7711,11 @@
 #   without fragmentation or be reassembled within the bounds of
 #   MBEDTLS_SSL_DTLS_MAX_BUFFERING. Achieve this by testing with a PSK-based
 #   handshake, omitting CRTs.
-requires_config_value_at_least "MBEDTLS_SSL_DTLS_MAX_BUFFERING" 240
-requires_config_value_at_most "MBEDTLS_SSL_DTLS_MAX_BUFFERING" 280
+requires_config_value_at_least "MBEDTLS_SSL_DTLS_MAX_BUFFERING" 190
+requires_config_value_at_most "MBEDTLS_SSL_DTLS_MAX_BUFFERING" 230
 run_test    "DTLS reordering: Buffer encrypted Finished message, drop for fragmented NewSessionTicket" \
             -p "$P_PXY delay_srv=NewSessionTicket delay_srv=NewSessionTicket delay_ccs=1" \
-            "$P_SRV mtu=190 dgram_packing=0 psk=abc123 psk_identity=foo cookies=0 dtls=1 debug_level=2" \
+            "$P_SRV mtu=140 response_size=90 dgram_packing=0 psk=abc123 psk_identity=foo cookies=0 dtls=1 debug_level=2" \
             "$P_CLI dgram_packing=0 dtls=1 debug_level=2 force_ciphersuite=TLS-PSK-WITH-AES-128-CCM-8 psk=abc123 psk_identity=foo" \
             0 \
             -s "Buffer record from epoch 1" \
diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data
index 5b2025f..edb87d8 100644
--- a/tests/suites/test_suite_ssl.data
+++ b/tests/suites/test_suite_ssl.data
@@ -58,6 +58,18 @@
 SSL SET_HOSTNAME memory leak: call ssl_set_hostname twice
 ssl_set_hostname_twice:"server0":"server1"
 
+SSL session serialization: Wrong major version
+ssl_session_serialize_version_check:1:0:0:0
+
+SSL session serialization: Wrong minor version
+ssl_session_serialize_version_check:0:1:0:0
+
+SSL session serialization: Wrong patch version
+ssl_session_serialize_version_check:0:0:1:0
+
+SSL session serialization: Wrong config
+ssl_session_serialize_version_check:0:0:0:1
+
 Record crypt, AES-128-CBC, 1.2, SHA-384
 depends_on:MBEDTLS_CIPHER_MODE_CBC:MBEDTLS_AES_C:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_SHA512_C
 ssl_crypt_record:MBEDTLS_CIPHER_AES_128_CBC:MBEDTLS_MD_SHA384:0:0:MBEDTLS_SSL_MINOR_VERSION_3:0:0
@@ -8713,3 +8725,95 @@
 Record crypt, little space, NULL cipher, SSL3, MD5, short tag, EtM
 depends_on:MBEDTLS_CIPHER_NULL_CIPHER:MBEDTLS_SSL_PROTO_SSL3:MBEDTLS_MD5_C:MBEDTLS_SSL_ENCRYPT_THEN_MAC
 ssl_crypt_record_small:MBEDTLS_CIPHER_NULL:MBEDTLS_MD_MD5:1:1:MBEDTLS_SSL_MINOR_VERSION_0:0:0
+
+Session serialization, save-load: no ticket, no cert
+ssl_serialize_session_save_load:0:""
+
+Session serialization, save-load: small ticket, no cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C
+ssl_serialize_session_save_load:42:""
+
+Session serialization, save-load: large ticket, no cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C
+ssl_serialize_session_save_load:1023:""
+
+Session serialization, save-load: no ticket, cert
+depends_on:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
+ssl_serialize_session_save_load:0:"data_files/server5.crt"
+
+Session serialization, save-load: small ticket, cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
+ssl_serialize_session_save_load:42:"data_files/server5.crt"
+
+Session serialization, save-load: large ticket, cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
+ssl_serialize_session_save_load:1023:"data_files/server5.crt"
+
+Session serialization, load-save: no ticket, no cert
+ssl_serialize_session_load_save:0:""
+
+Session serialization, load-save: small ticket, no cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C
+ssl_serialize_session_load_save:42:""
+
+Session serialization, load-save: large ticket, no cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C
+ssl_serialize_session_load_save:1023:""
+
+Session serialization, load-save: no ticket, cert
+depends_on:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
+ssl_serialize_session_load_save:0:"data_files/server5.crt"
+
+Session serialization, load-save: small ticket, cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
+ssl_serialize_session_load_save:42:"data_files/server5.crt"
+
+Session serialization, load-save: large ticket, cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
+ssl_serialize_session_load_save:1023:"data_files/server5.crt"
+
+Session serialization, save buffer size: no ticket, no cert
+ssl_serialize_session_save_buf_size:0:""
+
+Session serialization, save buffer size: small ticket, no cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C
+ssl_serialize_session_save_buf_size:42:""
+
+Session serialization, save buffer size: large ticket, no cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C
+ssl_serialize_session_save_buf_size:1023:""
+
+Session serialization, save buffer size: no ticket, cert
+depends_on:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
+ssl_serialize_session_save_buf_size:0:"data_files/server5.crt"
+
+Session serialization, save buffer size: small ticket, cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
+ssl_serialize_session_save_buf_size:42:"data_files/server5.crt"
+
+Session serialization, save buffer size: large ticket, cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
+ssl_serialize_session_save_buf_size:1023:"data_files/server5.crt"
+
+Session serialization, load buffer size: no ticket, no cert
+ssl_serialize_session_load_buf_size:0:""
+
+Session serialization, load buffer size: small ticket, no cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C
+ssl_serialize_session_load_buf_size:42:""
+
+Session serialization, load buffer size: large ticket, no cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C
+ssl_serialize_session_load_buf_size:1023:""
+
+Session serialization, load buffer size: no ticket, cert
+depends_on:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
+ssl_serialize_session_load_buf_size:0:"data_files/server5.crt"
+
+Session serialization, load buffer size: small ticket, cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
+ssl_serialize_session_load_buf_size:42:"data_files/server5.crt"
+
+Session serialization, load buffer size: large ticket, cert
+depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
+ssl_serialize_session_load_buf_size:1023:"data_files/server5.crt"
diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function
index d599b6d..65f5852 100644
--- a/tests/suites/test_suite_ssl.function
+++ b/tests/suites/test_suite_ssl.function
@@ -266,6 +266,68 @@
     return( ret );
 }
 
+/*
+ * Populate a session structure for serialization tests.
+ * Choose dummy values, mostly non-0 to distinguish from the init default.
+ */
+static int ssl_populate_session( mbedtls_ssl_session *session,
+                                 int ticket_len,
+                                 const char *crt_file )
+{
+#if defined(MBEDTLS_HAVE_TIME)
+    session->start = mbedtls_time( NULL ) - 42;
+#endif
+    session->ciphersuite = 0xabcd;
+    session->compression = 1;
+    session->id_len = sizeof( session->id );
+    memset( session->id, 66, session->id_len );
+    memset( session->master, 17, sizeof( session->master ) );
+
+#if defined(MBEDTLS_X509_CRT_PARSE_C) && defined(MBEDTLS_FS_IO)
+    if( strlen( crt_file ) != 0 )
+    {
+        int ret;
+
+        session->peer_cert = mbedtls_calloc( 1, sizeof( *session->peer_cert ) );
+        if( session->peer_cert == NULL )
+            return( -1 );
+
+        ret = mbedtls_x509_crt_parse_file( session->peer_cert, crt_file );
+        if( ret != 0 )
+            return( ret );
+    }
+#else
+    (void) crt_file;
+#endif
+    session->verify_result = 0xdeadbeef;
+
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+    if( ticket_len != 0 )
+    {
+        session->ticket = mbedtls_calloc( 1, ticket_len );
+        if( session->ticket == NULL )
+            return( -1 );
+        memset( session->ticket, 33, ticket_len );
+    }
+    session->ticket_len = ticket_len;
+    session->ticket_lifetime = 86401;
+#else
+    (void) ticket_len;
+#endif
+
+#if defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH)
+    session->mfl_code = 1;
+#endif
+#if defined(MBEDTLS_SSL_TRUNCATED_HMAC)
+    session->trunc_hmac = 1;
+#endif
+#if defined(MBEDTLS_SSL_ENCRYPT_THEN_MAC)
+    session->encrypt_then_mac = 1;
+#endif
+
+    return( 0 );
+}
+
 /* END_HEADER */
 
 /* BEGIN_DEPENDENCIES
@@ -575,3 +637,280 @@
     mbedtls_free( buf );
 }
 /* END_CASE */
+
+/* BEGIN_CASE */
+void ssl_serialize_session_save_load( int ticket_len, char *crt_file )
+{
+    mbedtls_ssl_session original, restored;
+    unsigned char *buf = NULL;
+    size_t len;
+
+    /*
+     * Test that a save-load pair is the identity
+     */
+
+    mbedtls_ssl_session_init( &original );
+    mbedtls_ssl_session_init( &restored );
+
+    /* Prepare a dummy session to work on */
+    TEST_ASSERT( ssl_populate_session( &original, ticket_len, crt_file ) == 0 );
+
+    /* Serialize it */
+    TEST_ASSERT( mbedtls_ssl_session_save( &original, NULL, 0, &len )
+                 == MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL );
+    TEST_ASSERT( ( buf = mbedtls_calloc( 1, len ) ) != NULL );
+    TEST_ASSERT( mbedtls_ssl_session_save( &original, buf, len, &len )
+                 == 0 );
+
+    /* Restore session from serialized data */
+    TEST_ASSERT( mbedtls_ssl_session_load( &restored, buf, len) == 0 );
+
+    /*
+     * Make sure both session structures are identical
+     */
+#if defined(MBEDTLS_HAVE_TIME)
+    TEST_ASSERT( original.start == restored.start );
+#endif
+    TEST_ASSERT( original.ciphersuite == restored.ciphersuite );
+    TEST_ASSERT( original.compression == restored.compression );
+    TEST_ASSERT( original.id_len == restored.id_len );
+    TEST_ASSERT( memcmp( original.id,
+                         restored.id, sizeof( original.id ) ) == 0 );
+    TEST_ASSERT( memcmp( original.master,
+                         restored.master, sizeof( original.master ) ) == 0 );
+
+#if defined(MBEDTLS_X509_CRT_PARSE_C)
+    TEST_ASSERT( ( original.peer_cert == NULL ) ==
+                 ( restored.peer_cert == NULL ) );
+    if( original.peer_cert != NULL )
+    {
+        TEST_ASSERT( original.peer_cert->raw.len ==
+                     restored.peer_cert->raw.len );
+        TEST_ASSERT( memcmp( original.peer_cert->raw.p,
+                             restored.peer_cert->raw.p,
+                             original.peer_cert->raw.len ) == 0 );
+    }
+#endif
+    TEST_ASSERT( original.verify_result == restored.verify_result );
+
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+    TEST_ASSERT( original.ticket_len == restored.ticket_len );
+    if( original.ticket_len != 0 )
+    {
+        TEST_ASSERT( original.ticket != NULL );
+        TEST_ASSERT( restored.ticket != NULL );
+        TEST_ASSERT( memcmp( original.ticket,
+                             restored.ticket, original.ticket_len ) == 0 );
+    }
+    TEST_ASSERT( original.ticket_lifetime == restored.ticket_lifetime );
+#endif
+
+#if defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH)
+    TEST_ASSERT( original.mfl_code == restored.mfl_code );
+#endif
+
+#if defined(MBEDTLS_SSL_TRUNCATED_HMAC)
+    TEST_ASSERT( original.trunc_hmac == restored.trunc_hmac );
+#endif
+
+#if defined(MBEDTLS_SSL_ENCRYPT_THEN_MAC)
+    TEST_ASSERT( original.encrypt_then_mac == restored.encrypt_then_mac );
+#endif
+
+exit:
+    mbedtls_ssl_session_free( &original );
+    mbedtls_ssl_session_free( &restored );
+    mbedtls_free( buf );
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
+void ssl_serialize_session_load_save( int ticket_len, char *crt_file )
+{
+    mbedtls_ssl_session session;
+    unsigned char *buf1 = NULL, *buf2 = NULL;
+    size_t len0, len1, len2;
+
+    /*
+     * Test that a load-save pair is the identity
+     */
+
+    mbedtls_ssl_session_init( &session );
+
+    /* Prepare a dummy session to work on */
+    TEST_ASSERT( ssl_populate_session( &session, ticket_len, crt_file ) == 0 );
+
+    /* Get desired buffer size for serializing */
+    TEST_ASSERT( mbedtls_ssl_session_save( &session, NULL, 0, &len0 )
+                 == MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL );
+
+    /* Allocate first buffer */
+    buf1 = mbedtls_calloc( 1, len0 );
+    TEST_ASSERT( buf1 != NULL );
+
+    /* Serialize to buffer and free live session */
+    TEST_ASSERT( mbedtls_ssl_session_save( &session, buf1, len0, &len1 )
+                 == 0 );
+    TEST_ASSERT( len0 == len1 );
+    mbedtls_ssl_session_free( &session );
+
+    /* Restore session from serialized data */
+    TEST_ASSERT( mbedtls_ssl_session_load( &session, buf1, len1 ) == 0 );
+
+    /* Allocate second buffer and serialize to it */
+    buf2 = mbedtls_calloc( 1, len0 );
+    TEST_ASSERT( buf2 != NULL );
+    TEST_ASSERT( mbedtls_ssl_session_save( &session, buf2, len0, &len2 )
+                 == 0 );
+
+    /* Make sure both serialized versions are identical */
+    TEST_ASSERT( len1 == len2 );
+    TEST_ASSERT( memcmp( buf1, buf2, len1 ) == 0 );
+
+exit:
+    mbedtls_ssl_session_free( &session );
+    mbedtls_free( buf1 );
+    mbedtls_free( buf2 );
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
+void ssl_serialize_session_save_buf_size( int ticket_len, char *crt_file )
+{
+    mbedtls_ssl_session session;
+    unsigned char *buf = NULL;
+    size_t good_len, bad_len, test_len;
+
+    /*
+     * Test that session_save() fails cleanly on small buffers
+     */
+
+    mbedtls_ssl_session_init( &session );
+
+    /* Prepare dummy session and get serialized size */
+    TEST_ASSERT( ssl_populate_session( &session, ticket_len, crt_file ) == 0 );
+    TEST_ASSERT( mbedtls_ssl_session_save( &session, NULL, 0, &good_len )
+                 == MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL );
+
+    /* Try all possible bad lengths */
+    for( bad_len = 1; bad_len < good_len; bad_len++ )
+    {
+        /* Allocate exact size so that asan/valgrind can detect any overwrite */
+        mbedtls_free( buf );
+        TEST_ASSERT( ( buf = mbedtls_calloc( 1, bad_len ) ) != NULL );
+        TEST_ASSERT( mbedtls_ssl_session_save( &session, buf, bad_len,
+                                               &test_len )
+                     == MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL );
+        TEST_ASSERT( test_len == good_len );
+    }
+
+exit:
+    mbedtls_ssl_session_free( &session );
+    mbedtls_free( buf );
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
+void ssl_serialize_session_load_buf_size( int ticket_len, char *crt_file )
+{
+    mbedtls_ssl_session session;
+    unsigned char *good_buf = NULL, *bad_buf = NULL;
+    size_t good_len, bad_len;
+
+    /*
+     * Test that session_load() fails cleanly on small buffers
+     */
+
+    mbedtls_ssl_session_init( &session );
+
+    /* Prepare serialized session data */
+    TEST_ASSERT( ssl_populate_session( &session, ticket_len, crt_file ) == 0 );
+    TEST_ASSERT( mbedtls_ssl_session_save( &session, NULL, 0, &good_len )
+                 == MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL );
+    TEST_ASSERT( ( good_buf = mbedtls_calloc( 1, good_len ) ) != NULL );
+    TEST_ASSERT( mbedtls_ssl_session_save( &session, good_buf, good_len,
+                                           &good_len ) == 0 );
+    mbedtls_ssl_session_free( &session );
+
+    /* Try all possible bad lengths */
+    for( bad_len = 0; bad_len < good_len; bad_len++ )
+    {
+        /* Allocate exact size so that asan/valgrind can detect any overread */
+        mbedtls_free( bad_buf );
+        bad_buf = mbedtls_calloc( 1, bad_len ? bad_len : 1 );
+        TEST_ASSERT( bad_buf != NULL );
+        memcpy( bad_buf, good_buf, bad_len );
+
+        TEST_ASSERT( mbedtls_ssl_session_load( &session, bad_buf, bad_len )
+                     == MBEDTLS_ERR_SSL_BAD_INPUT_DATA );
+    }
+
+exit:
+    mbedtls_ssl_session_free( &session );
+    mbedtls_free( good_buf );
+    mbedtls_free( bad_buf );
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
+void ssl_session_serialize_version_check( int corrupt_major,
+                                          int corrupt_minor,
+                                          int corrupt_patch,
+                                          int corrupt_config )
+{
+    unsigned char serialized_session[ 2048 ];
+    size_t serialized_session_len;
+    unsigned cur_byte;
+    mbedtls_ssl_session session;
+    uint8_t should_corrupt_byte[] = { corrupt_major  == 1,
+                                      corrupt_minor  == 1,
+                                      corrupt_patch  == 1,
+                                      corrupt_config == 1,
+                                      corrupt_config == 1 };
+
+    mbedtls_ssl_session_init( &session );
+
+    /* Infer length of serialized session. */
+    TEST_ASSERT( mbedtls_ssl_session_save( &session,
+                                           serialized_session,
+                                           sizeof( serialized_session ),
+                                           &serialized_session_len ) == 0 );
+
+    mbedtls_ssl_session_free( &session );
+
+    /* Without any modification, we should be able to successfully
+     * de-serialize the session - double-check that. */
+    TEST_ASSERT( mbedtls_ssl_session_load( &session,
+                                           serialized_session,
+                                           serialized_session_len ) == 0 );
+    mbedtls_ssl_session_free( &session );
+
+    /* Go through the bytes in the serialized session header and
+     * corrupt them bit-by-bit. */
+    for( cur_byte = 0; cur_byte < sizeof( should_corrupt_byte ); cur_byte++ )
+    {
+        int cur_bit;
+        unsigned char * const byte = &serialized_session[ cur_byte ];
+
+        if( should_corrupt_byte[ cur_byte ] == 0 )
+            continue;
+
+        for( cur_bit = 0; cur_bit < CHAR_BIT; cur_bit++ )
+        {
+            unsigned char const corrupted_bit = 0x1u << cur_bit;
+            /* Modify a single bit in the serialized session. */
+            *byte ^= corrupted_bit;
+
+            /* Attempt to deserialize */
+            TEST_ASSERT( mbedtls_ssl_session_load( &session,
+                                                   serialized_session,
+                                                   serialized_session_len ) ==
+                         MBEDTLS_ERR_SSL_VERSION_MISMATCH );
+
+            /* Undo the change */
+            *byte ^= corrupted_bit;
+        }
+    }
+
+}
+/* END_CASE */