Merge pull request #4963 from ronald-cron-arm/tls13-mvp

Define TLS 1.3 MVP and document coding rules
diff --git a/ChangeLog.d/do-not-use-obsolete-header.txt b/ChangeLog.d/do-not-use-obsolete-header.txt
new file mode 100644
index 0000000..9a57ef1
--- /dev/null
+++ b/ChangeLog.d/do-not-use-obsolete-header.txt
@@ -0,0 +1,5 @@
+Bugfix
+   * Don't use the obsolete header path sys/fcntl.h in unit tests.
+     These header files cause compilation errors in musl.
+     Fixes #4969.
+
diff --git a/ChangeLog.d/muladdc-amd64-memory.txt b/ChangeLog.d/muladdc-amd64-memory.txt
deleted file mode 100644
index b834331..0000000
--- a/ChangeLog.d/muladdc-amd64-memory.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Bugfix
-   * Fix missing constraints on x86_64 assembly code for bignum multiplication
-     that broke some bignum operations with (at least) Clang 12.
-     Fixes #4116, #4786, #4917.
diff --git a/ChangeLog.d/muladdc-memory.txt b/ChangeLog.d/muladdc-memory.txt
new file mode 100644
index 0000000..218be5a
--- /dev/null
+++ b/ChangeLog.d/muladdc-memory.txt
@@ -0,0 +1,5 @@
+Bugfix
+   * Fix missing constraints on x86_64 and aarch64 assembly code
+     for bignum multiplication that broke some bignum operations with
+     (at least) Clang 12.
+     Fixes #4116, #4786, #4917, #4962.
diff --git a/docs/use-psa-crypto.md b/docs/use-psa-crypto.md
new file mode 100644
index 0000000..6ec2dca
--- /dev/null
+++ b/docs/use-psa-crypto.md
@@ -0,0 +1,204 @@
+This document describes the compile-time configuration option
+`MBEDTLS_USE_PSA_CRYPTO` from a user's perspective, more specifically its
+current effects as well as the parts that aren't covered yet.
+
+Current effects
+===============
+
+General limitations
+-------------------
+
+Compile-time: enabling `MBEDTLS_USE_PSA_CRYPTO` requires
+`MBEDTLS_ECP_RESTARTABLE` and
+`MBEDTLS_PSA_CRYPTO_KEY_ID_ENCODES_OWNER` to be disabled.
+
+Effect: `MBEDTLS_USE_PSA_CRYPTO` currently has no effect on TLS 1.3 (which is
+itself experimental and only partially supported so far): TLS 1.3 always uses
+the legacy APIs even when this option is set.
+
+Stability: any API that's only available when `MBEDTLS_USE_PSA_CRYPTO` is
+defined is considered experimental and may change in incompatible ways at any
+time. Said otherwise, these APIs are explicitly excluded from the usual API
+stability promises.
+
+New APIs / API extensions
+-------------------------
+
+Some of these APIs are meant for the application to use in place of
+pre-existing APIs, in order to get access to the benefits; in the sub-sections
+below these are indicated by "Use in (X.509 and) TLS: opt-in", meaning that
+this requires changes to the application code for the (X.509 and) TLS layers
+to pick up the improvements.
+
+Some of these APIs are mostly meant for internal use by the TLS (and X.509)
+layers; they are indicated below by "Use in (X.509 and) TLS: automatic",
+meaning that no changes to the application code are required for the TLS (and
+X.509) layers to pick up the improvements.
+
+### PSA-held (opaque) keys in the PK layer
+
+There is a new API function `mbedtls_pk_setup_opaque()` that can be used to
+wrap a PSA keypair into a PK context. The key can be used for private-key
+operations and its public part can be exported.
+
+Benefits: isolation of long-term secrets, use of PSA Crypto drivers.
+
+Limitations: only for private keys, only ECC. (That is, only ECDSA signature
+generation. Note: currently this will use randomized ECDSA while Mbed TLS uses
+deterministic ECDSA by default.) The following operations are not supported
+with a context set this way, while they would be available with a normal
+`ECKEY` context: `mbedtls_pk_verify()`, `mbedtls_pk_check_pair()`,
+`mbedtls_pk_debug()`.
+
+Use in X.509 and TLS: opt-in. The application needs to construct the PK context
+using the new API in order to get the benefits; it can then pass the
+resulting context to the following existing APIs:
+
+- `mbedtls_ssl_conf_own_cert()` or `mbedtls_ssl_set_hs_own_cert()` to use the
+  key together with a certificate for ECDSA-based key exchanges (note: while
+this is supported on both sides, it's currently only tested client-side);
+- `mbedtls_x509write_csr_set_key()` to generate a CSR (certificate signature
+  request).
+
+In the TLS and X.509 API, there are two other functions which accept a key or
+keypair as a PK context: `mbedtls_x509write_crt_set_subject_key()` and
+`mbedtls_x509write_crt_set_issuer_key()`. Use of opaque contexts here probably
+works but is so far untested.
+
+### PSA-held (opaque) keys for TLS pre-shared keys (PSK)
+
+There are two new API functions `mbedtls_ssl_conf_psk_opaque()` and
+`mbedtls_ssl_set_hs_psk_opaque()`. Call one of these from an application to
+register a PSA key for use with a PSK key exchange.
+
+Benefits: isolation of long-term secrets.
+
+Limitations: the key can only be used with "pure"
+PSK key exchanges (ciphersuites starting with `TLS_PSK_WITH_`), to the
+exclusion of RSA-PSK, DHE-PSK and ECDHE-PSK key exchanges. It is the responsibility of
+the user to make sure that when provisioning an opaque pre-shared key, the
+only PSK ciphersuites that can be negotiated are "pure" PSK; other XXX-PSK key
+exchanges will result in a handshake failure with the handshake function
+returning `MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE`.
+
+Use in TLS: opt-in. The application needs to register the key using the new
+APIs to get the benefits.
+
+### PSA-based operations in the Cipher layer
+
+There is a new API function `mbedtls_cipher_setup_psa()` to set up a context
+that will call PSA to store the key and perform the operations.
+
+Benefits: use of PSA Crypto drivers; partial isolation of short-term secrets
+(still generated outside of PSA, but then held by PSA).
+
+Limitations: the key is still passed in the clear by the application. The
+multi-part APIs are not supported, only the one-shot APIs. The only modes
+supported are ECB, CBC without padding, GCM and CCM (this excludes stream
+ciphers and ChachaPoly); the only cipher supported is AES (this excludes Aria,
+Camellia, and ChachaPoly). (Note: ECB is currently not tested.) (Note: it is
+possible to perform multiple one-shot operations with the same context;
+however this is not unit-tested, only tested via usage in TLS.)
+
+Use in TLS: automatic. Used when the cipher and mode is supported (with
+gracious fallback to the legacy API otherwise) in all places where a cipher is
+used. There are two such places: in `ssl_tls.c` for record protection, and in
+`ssl_ticket.c` for protecting tickets we issue.
+
+Internal changes
+----------------
+
+All of these internal changes are active as soon as `MBEDTLS_USE_PSA_CRYPTO`
+is enabled, no change required on the application side.
+
+### TLS: cipher operations based on PSA
+
+See "PSA-based operations in the Cipher layer" above.
+
+### PK layer: ECDSA verification based on PSA
+
+Scope: `mbedtls_pk_verify()` will call to PSA for ECDSA signature
+verification.
+
+Benefits: use of PSA Crypto drivers.
+
+Use in TLS and X.509: in all places where an ECDSA signature is verified.
+
+### TLS: ECDHE computation based on PSA
+
+Scope: Client-side, for ECDHE-RSA and ECDHE-ECDSA key exchanges, the
+computation of the ECDHE key exchange is done by PSA.
+
+Limitations: client-side only, ECDHE-PSK not covered
+
+Benefits: use of PSA Crypto drivers.
+
+### TLS: handshake hashes and PRF computed with PSA
+
+Scope: with TLS 1.2, the following are computed with PSA:
+- the running handshake hashes;
+- the hash of the ServerKeyExchange part that is signed;
+- the `verify_data` part of the Finished message;
+- the TLS PRF.
+
+Benefits: use of PSA Crypto drivers.
+
+### X.509: some hashes computed with PSA
+
+Scope: the following hashes are computed with PSA:
+- when verifying a certificate chain, hash of the child for verifying the
+  parent's signature;
+- when writing a CSR, hash of the request for self-signing the request.
+
+Benefits: use of PSA Crypto drivers.
+
+Parts that are not covered yet
+==============================
+
+This is only a high-level overview, grouped by theme
+
+TLS: 1.3 experimental support
+-----------------------------
+
+No part of the experimental support for TLS 1.3 is covered at the moment.
+
+TLS: key exchanges / asymmetric crypto
+--------------------------------------
+
+The following key exchanges are not covered at all:
+
+- RSA
+- DHE-RSA
+- DHE-PSK
+- RSA-PSK
+- ECDHE-PSK
+- ECDH-RSA
+- ECDH-ECDSA
+- ECJPAKE
+
+The following key exchanges are only partially covered:
+
+- ECDHE-RSA: RSA operations are not covered and, server-side, the ECDHE
+  operation isn't either
+- ECDHE-ECDSA: server-side, the ECDHE operation isn't covered. (ECDSA
+  signature generation is only covered if using `mbedtls_pk_setup_opaque()`.)
+
+PSK if covered when the application uses `mbedtls_ssl_conf_psk_opaque()` or
+`mbedtls_ssl_set_hs_psk_opaque()`.
+
+TLS: symmetric crypto
+---------------------
+
+- some ciphers not supported via PSA yet: ARIA, Camellia, ChachaPoly (silent
+  fallback to the legacy APIs)
+- the HMAC part of the CBC and NULL ciphersuites
+- the HMAC computation in `ssl_cookie.c`
+
+X.509
+-----
+
+- most hash operations are still done via the legacy API, except the few that
+  are documented above as using PSA
+- RSA PKCS#1 v1.5 signature generation (from PSA-held keys)
+- RSA PKCS#1 v1.5 signature verification
+- RSA-PSS signature verification
diff --git a/include/mbedtls/config_psa.h b/include/mbedtls/config_psa.h
index 9080cd1..3b01b78 100644
--- a/include/mbedtls/config_psa.h
+++ b/include/mbedtls/config_psa.h
@@ -586,7 +586,7 @@
 #define MBEDTLS_PSA_BUILTIN_ALG_RSA_PKCS1V15_SIGN 1
 #define PSA_WANT_ALG_RSA_PKCS1V15_SIGN 1
 #define PSA_WANT_ALG_RSA_PKCS1V15_SIGN_RAW 1
-#endif /* MBEDTLSS_PKCS1_V15 */
+#endif /* MBEDTLS_PKCS1_V15 */
 #if defined(MBEDTLS_PKCS1_V21)
 #define MBEDTLS_PSA_BUILTIN_ALG_RSA_OAEP 1
 #define PSA_WANT_ALG_RSA_OAEP 1
diff --git a/include/mbedtls/mbedtls_config.h b/include/mbedtls/mbedtls_config.h
index d470c00..adc317d 100644
--- a/include/mbedtls/mbedtls_config.h
+++ b/include/mbedtls/mbedtls_config.h
@@ -1718,15 +1718,13 @@
  * will still continue to work as usual, so enabling this option should not
  * break backwards compatibility.
  *
- * \warning The PSA Crypto API is in beta stage. While you're welcome to
- * experiment using it, incompatible API changes are still possible, and some
- * parts may not have reached the same quality as the rest of Mbed TLS yet.
+ * \note See docs/use-psa-crypto.md for a complete description of what this
+ * option currently does, and of parts that are not affected by it so far.
  *
- * \warning This option enables new Mbed TLS APIs that are dependent on the
- * PSA Crypto API, so can't come with the same stability guarantees as the
- * rest of the Mbed TLS APIs. You're welcome to experiment with them, but for
- * now, access to these APIs is opt-in (via enabling the present option), in
- * order to clearly differentiate them from the stable Mbed TLS APIs.
+ * \warning This option enables new Mbed TLS APIs which are currently
+ * considered experimental and may change in incompatible ways at any time.
+ * That is, the APIs enabled by this option are not covered by the usual
+ * promises of API stability.
  *
  * Requires: MBEDTLS_PSA_CRYPTO_C.
  *
@@ -2592,10 +2590,6 @@
  *
  * Enable the Platform Security Architecture cryptography API.
  *
- * \warning The PSA Crypto API is still beta status. While you're welcome to
- * experiment using it, incompatible API changes are still possible, and some
- * parts may not have reached the same quality as the rest of Mbed TLS yet.
- *
  * Module:  library/psa_crypto.c
  *
  * Requires: either MBEDTLS_CTR_DRBG_C and MBEDTLS_ENTROPY_C,
@@ -3179,7 +3173,7 @@
  * Maximum number of heap-allocated bytes for the purpose of
  * DTLS handshake message reassembly and future message buffering.
  *
- * This should be at least 9/8 * MBEDTLSSL_IN_CONTENT_LEN
+ * This should be at least 9/8 * MBEDTLS_SSL_IN_CONTENT_LEN
  * to account for a reassembled handshake message of maximum size,
  * together with its reassembly bitmap.
  *
diff --git a/include/mbedtls/ssl.h b/include/mbedtls/ssl.h
index f533859..822205e 100644
--- a/include/mbedtls/ssl.h
+++ b/include/mbedtls/ssl.h
@@ -170,6 +170,37 @@
 #define MBEDTLS_ERR_SSL_BAD_CONFIG                        -0x5E80
 
 /*
+ * TLS 1.3 NamedGroup values
+ *
+ * From RF 8446
+ *    enum {
+ *         // Elliptic Curve Groups (ECDHE)
+ *         secp256r1(0x0017), secp384r1(0x0018), secp521r1(0x0019),
+ *         x25519(0x001D), x448(0x001E),
+ *         // Finite Field Groups (DHE)
+ *         ffdhe2048(0x0100), ffdhe3072(0x0101), ffdhe4096(0x0102),
+ *         ffdhe6144(0x0103), ffdhe8192(0x0104),
+ *         // Reserved Code Points
+ *         ffdhe_private_use(0x01FC..0x01FF),
+ *         ecdhe_private_use(0xFE00..0xFEFF),
+ *         (0xFFFF)
+ *     } NamedGroup;
+ *
+ */
+/* Elliptic Curve Groups (ECDHE) */
+#define MBEDTLS_SSL_TLS13_NAMED_GROUP_SECP256R1     0x0017
+#define MBEDTLS_SSL_TLS13_NAMED_GROUP_SECP384R1     0x0018
+#define MBEDTLS_SSL_TLS13_NAMED_GROUP_SECP521R1     0x0019
+#define MBEDTLS_SSL_TLS13_NAMED_GROUP_X25519        0x001D
+#define MBEDTLS_SSL_TLS13_NAMED_GROUP_X448          0x001E
+/* Finite Field Groups (DHE) */
+#define MBEDTLS_SSL_TLS13_NAMED_GROUP_FFDHE2048     0x0100
+#define MBEDTLS_SSL_TLS13_NAMED_GROUP_FFDHE3072     0x0101
+#define MBEDTLS_SSL_TLS13_NAMED_GROUP_FFDHE4096     0x0102
+#define MBEDTLS_SSL_TLS13_NAMED_GROUP_FFDHE6144     0x0103
+#define MBEDTLS_SSL_TLS13_NAMED_GROUP_FFDHE8192     0x0104
+
+/*
  * TLS 1.3 Key Exchange Modes
  *
  * Mbed TLS internal identifiers for use with the SSL configuration API
@@ -1495,6 +1526,19 @@
     int MBEDTLS_PRIVATE(keep_current_message);   /*!< drop or reuse current message
                                      on next call to record layer? */
 
+    /* The following three variables indicate if and, if yes,
+     * what kind of alert is pending to be sent.
+     */
+    unsigned char MBEDTLS_PRIVATE(send_alert);   /*!< Determines if a fatal alert
+                                                should be sent. Values:
+                                                - \c 0 , no alert is to be sent.
+                                                - \c 1 , alert is to be sent. */
+    unsigned char MBEDTLS_PRIVATE(alert_type);   /*!< Type of alert if send_alert
+                                                 != 0 */
+    int MBEDTLS_PRIVATE(alert_reason);           /*!< The error code to be returned
+                                                 to the user once the fatal alert
+                                                 has been sent. */
+
 #if defined(MBEDTLS_SSL_PROTO_DTLS)
     uint8_t MBEDTLS_PRIVATE(disable_datagram_packing);  /*!< Disable packing multiple records
                                         *   within a single datagram.  */
diff --git a/library/bn_mul.h b/library/bn_mul.h
index 328e765..b71ddd8 100644
--- a/library/bn_mul.h
+++ b/library/bn_mul.h
@@ -224,7 +224,7 @@
         "adcq   %%rdx, %%rcx\n"             \
         "addq   $8, %%rdi\n"
 
-#define MULADDC_STOP                        \
+#define MULADDC_STOP                                                 \
         : "+c" (c), "+D" (d), "+S" (s), "+m" (*(uint64_t (*)[16]) d) \
         : "b" (b), "m" (*(const uint64_t (*)[16]) s)                 \
         : "rax", "rdx", "r8"                                         \
@@ -240,18 +240,18 @@
 #define MULADDC_CORE                \
         "ldr x4, [%2], #8   \n\t"   \
         "ldr x5, [%1]       \n\t"   \
-        "mul x6, x4, %3     \n\t"   \
-        "umulh x7, x4, %3   \n\t"   \
+        "mul x6, x4, %4     \n\t"   \
+        "umulh x7, x4, %4   \n\t"   \
         "adds x5, x5, x6    \n\t"   \
         "adc x7, x7, xzr    \n\t"   \
         "adds x5, x5, %0    \n\t"   \
         "adc %0, x7, xzr    \n\t"   \
         "str x5, [%1], #8   \n\t"
 
-#define MULADDC_STOP                        \
-         : "+r" (c),  "+r" (d), "+r" (s)    \
-         : "r" (b)                          \
-         : "x4", "x5", "x6", "x7", "cc"     \
+#define MULADDC_STOP                                                    \
+         : "+r" (c),  "+r" (d), "+r" (s), "+m" (*(uint64_t (*)[16]) d)  \
+         : "r" (b), "m" (*(const uint64_t (*)[16]) s)                   \
+         : "x4", "x5", "x6", "x7", "cc"                                 \
     );
 
 #endif /* Aarch64 */
diff --git a/library/ecdh.c b/library/ecdh.c
index 9dfa868..b72bd1f 100644
--- a/library/ecdh.c
+++ b/library/ecdh.c
@@ -32,6 +32,8 @@
 #include "mbedtls/platform_util.h"
 #include "mbedtls/error.h"
 
+#include "ecdh_misc.h"
+
 #include <string.h>
 
 /* Parameter validation macros based on platform_util.h */
@@ -726,4 +728,84 @@
 #endif
 }
 
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL)
+
+static int ecdh_tls13_make_params_internal( mbedtls_ecdh_context_mbed *ctx,
+                size_t *olen, int point_format, unsigned char *buf, size_t blen,
+                int ( *f_rng )( void *, unsigned char *, size_t), void *p_rng )
+{
+    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+
+    if( ctx->grp.pbits == 0 )
+        return( MBEDTLS_ERR_ECP_BAD_INPUT_DATA );
+
+    if( ( ret = mbedtls_ecdh_gen_public( &ctx->grp, &ctx->d, &ctx->Q,
+                                         f_rng, p_rng ) ) != 0 )
+        return( ret );
+
+    ret = mbedtls_ecp_point_write_binary( &ctx->grp, &ctx->Q, point_format,
+                                          olen, buf, blen );
+    if( ret != 0 )
+        return( ret );
+
+    return( 0 );
+}
+
+int mbedtls_ecdh_tls13_make_params( mbedtls_ecdh_context *ctx, size_t *olen,
+                            unsigned char *buf, size_t blen,
+                            int ( *f_rng )( void *, unsigned char *, size_t ),
+                            void *p_rng )
+{
+    ECDH_VALIDATE_RET( ctx != NULL );
+    ECDH_VALIDATE_RET( olen != NULL );
+    ECDH_VALIDATE_RET( buf != NULL );
+    ECDH_VALIDATE_RET( f_rng != NULL );
+
+
+#if defined(MBEDTLS_ECP_RESTARTABLE)
+    if( ctx-> restart_enabled )
+        return( MBEDTLS_ERR_ECP_FEATURE_UNAVAILABLE );
+#endif
+
+#if defined(MBEDTLS_ECDH_LEGACY_CONTEXT)
+    return( ecdh_tls13_make_params_internal( ctx, olen, ctx->point_format,
+                                             buf, blen, f_rng, p_rng ) );
+#else
+    switch( ctx->var )
+    {
+#if defined(MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED)
+        case MBEDTLS_ECDH_VARIANT_EVEREST:
+            return( MBEDTLS_ERR_ECP_FEATURE_UNAVAILABLE );
+#endif
+        case MBEDTLS_ECDH_VARIANT_MBEDTLS_2_0:
+            return( ecdh_tls13_make_params_internal( &ctx->ctx.mbed_ecdh, olen,
+                                               ctx->point_format, buf, blen,
+                                               f_rng, p_rng ) );
+        default:
+            return MBEDTLS_ERR_ECP_BAD_INPUT_DATA;
+    }
+#endif
+}
+
+/*
+ * Setup context without Everest
+ */
+int mbedtls_ecdh_setup_no_everest( mbedtls_ecdh_context *ctx,
+                                   mbedtls_ecp_group_id grp_id )
+{
+    ECDH_VALIDATE_RET( ctx != NULL );
+
+#if defined(MBEDTLS_ECDH_LEGACY_CONTEXT)
+    return( ecdh_setup_internal( ctx, grp_id ) );
+#else
+    ctx->point_format = MBEDTLS_ECP_PF_UNCOMPRESSED;
+    ctx->var = MBEDTLS_ECDH_VARIANT_MBEDTLS_2_0;
+    ctx->grp_id = grp_id;
+    ecdh_init_internal( &ctx->ctx.mbed_ecdh );
+    return( ecdh_setup_internal( &ctx->ctx.mbed_ecdh, grp_id ) );
+#endif
+}
+
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL */
+
 #endif /* MBEDTLS_ECDH_C */
diff --git a/library/ecdh_misc.h b/library/ecdh_misc.h
new file mode 100644
index 0000000..d1342f8
--- /dev/null
+++ b/library/ecdh_misc.h
@@ -0,0 +1,51 @@
+/**
+ * \file ecdh_misc.h
+ *
+ * \brief Internal functions shared by the ECDH module
+ */
+/*
+ *  Copyright The Mbed TLS Contributors
+ *  SPDX-License-Identifier: Apache-2.0
+ *
+ *  Licensed under the Apache License, Version 2.0 ( the "License" ); you may
+ *  not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+#if !defined(MBEDTLS_ECDH_MISC_H)
+#define MBEDTLS_ECDH_MISC_H
+
+#include "mbedtls/ecdh.h"
+#include "mbedtls/ecp.h"
+
+#if defined(MBEDTLS_ECDH_C)
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL)
+
+/*
+ * Setup context without Everest
+ */
+int mbedtls_ecdh_setup_no_everest( mbedtls_ecdh_context *ctx,
+                                   mbedtls_ecp_group_id grp_id );
+
+/*
+ * TLS 1.3 version of mbedtls_ecdh_make_params in ecdh.h
+ */
+int mbedtls_ecdh_tls13_make_params( mbedtls_ecdh_context *ctx, size_t *olen,
+                                    unsigned char *buf, size_t blen,
+                                    int ( *f_rng )( void *, unsigned char *, size_t ),
+                                    void *p_rng );
+
+
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL */
+
+#endif /* MBEDTLS_ECDH_C */
+
+#endif /* !MBEDTLS_ECDH_MISC_H */
diff --git a/library/ssl_misc.h b/library/ssl_misc.h
index 7035c27..a1128ed 100644
--- a/library/ssl_misc.h
+++ b/library/ssl_misc.h
@@ -649,6 +649,16 @@
     void (*calc_finished)(mbedtls_ssl_context *, unsigned char *, int);
     mbedtls_ssl_tls_prf_cb *tls_prf;
 
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL)
+    uint16_t offered_group_id; /* The NamedGroup value for the group
+                                * that is being used for ephemeral
+                                * key exchange.
+                                *
+                                * On the client: Defaults to the first
+                                * entry in the client's group list,
+                                * but can be overwritten by the HRR. */
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL */
+
     mbedtls_ssl_ciphersuite_t const *ciphersuite_info;
 
     size_t pmslen;                      /*!<  premaster length        */
@@ -1332,6 +1342,22 @@
 
 int mbedtls_ssl_session_reset_int( mbedtls_ssl_context *ssl, int partial );
 
+/*
+ * Send pending alert
+ */
+int mbedtls_ssl_handle_pending_alert( mbedtls_ssl_context *ssl );
+
+/*
+ * Set pending fatal alert flag.
+ */
+void mbedtls_ssl_pend_fatal_alert( mbedtls_ssl_context *ssl,
+                                   unsigned char alert_type,
+                                   int alert_reason );
+
+/* Alias of mbedtls_ssl_pend_fatal_alert */
+#define MBEDTLS_SSL_PEND_FATAL_ALERT( type, user_return_value )         \
+            mbedtls_ssl_pend_fatal_alert( ssl, type, user_return_value )
+
 #if defined(MBEDTLS_SSL_DTLS_ANTI_REPLAY)
 void mbedtls_ssl_dtls_replay_reset( mbedtls_ssl_context *ssl );
 #endif
@@ -1348,6 +1374,49 @@
 void mbedtls_ssl_flight_free( mbedtls_ssl_flight_item *flight );
 #endif /* MBEDTLS_SSL_PROTO_DTLS */
 
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL)
+
+/*
+ * Helper functions around key exchange modes.
+ */
+static inline unsigned mbedtls_ssl_conf_tls13_check_kex_modes( mbedtls_ssl_context *ssl,
+                                                               int kex_mode_mask )
+{
+    return( ( ssl->conf->tls13_kex_modes & kex_mode_mask ) != 0 );
+}
+
+static inline int mbedtls_ssl_conf_tls13_psk_enabled( mbedtls_ssl_context *ssl )
+{
+    return( mbedtls_ssl_conf_tls13_check_kex_modes( ssl,
+                   MBEDTLS_SSL_TLS13_KEY_EXCHANGE_MODE_PSK ) );
+}
+
+static inline int mbedtls_ssl_conf_tls13_psk_ephemeral_enabled( mbedtls_ssl_context *ssl )
+{
+    return( mbedtls_ssl_conf_tls13_check_kex_modes( ssl,
+                   MBEDTLS_SSL_TLS13_KEY_EXCHANGE_MODE_PSK_EPHEMERAL ) );
+}
+
+static inline int mbedtls_ssl_conf_tls13_ephemeral_enabled( mbedtls_ssl_context *ssl )
+{
+    return( mbedtls_ssl_conf_tls13_check_kex_modes( ssl,
+                   MBEDTLS_SSL_TLS13_KEY_EXCHANGE_MODE_EPHEMERAL ) );
+}
+
+static inline int mbedtls_ssl_conf_tls13_some_ephemeral_enabled( mbedtls_ssl_context *ssl )
+{
+    return( mbedtls_ssl_conf_tls13_check_kex_modes( ssl,
+                   MBEDTLS_SSL_TLS13_KEY_EXCHANGE_MODE_EPHEMERAL_ALL ) );
+}
+
+static inline int mbedtls_ssl_conf_tls13_some_psk_enabled( mbedtls_ssl_context *ssl )
+{
+    return( mbedtls_ssl_conf_tls13_check_kex_modes( ssl,
+                   MBEDTLS_SSL_TLS13_KEY_EXCHANGE_MODE_PSK_ALL ) );
+}
+
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL */
+
 /**
  * ssl utils functions for checking configuration.
  */
@@ -1396,6 +1465,24 @@
 
 #if defined(MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL)
 
+/*
+ * Helper functions for NamedGroup.
+ */
+static inline int mbedtls_ssl_tls13_named_group_is_ecdhe( uint16_t named_group )
+{
+    return( named_group == MBEDTLS_SSL_TLS13_NAMED_GROUP_SECP256R1 ||
+            named_group == MBEDTLS_SSL_TLS13_NAMED_GROUP_SECP384R1 ||
+            named_group == MBEDTLS_SSL_TLS13_NAMED_GROUP_SECP521R1 ||
+            named_group == MBEDTLS_SSL_TLS13_NAMED_GROUP_X25519    ||
+            named_group == MBEDTLS_SSL_TLS13_NAMED_GROUP_X448 );
+}
+
+static inline int mbedtls_ssl_tls13_named_group_is_dhe( uint16_t named_group )
+{
+    return( named_group >= MBEDTLS_SSL_TLS13_NAMED_GROUP_FFDHE2048 &&
+            named_group <= MBEDTLS_SSL_TLS13_NAMED_GROUP_FFDHE8192 );
+}
+
 static inline void mbedtls_ssl_handshake_set_state( mbedtls_ssl_context *ssl,
                                                     mbedtls_ssl_states state )
 {
@@ -1430,6 +1517,7 @@
                                          unsigned char *buf,
                                          unsigned char *end,
                                          size_t *olen);
+
 #endif /* MBEDTLS_KEY_EXCHANGE_WITH_CERT_ENABLED */
 
 #endif /* MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL */
diff --git a/library/ssl_msg.c b/library/ssl_msg.c
index 2fe801a..3bf4a60 100644
--- a/library/ssl_msg.c
+++ b/library/ssl_msg.c
@@ -5639,4 +5639,48 @@
     }
 }
 
+/*
+ * Send pending fatal alert.
+ * 0,   No alert message.
+ * !0,  if mbedtls_ssl_send_alert_message() returned in error, the error code it
+ *      returned, ssl->alert_reason otherwise.
+ */
+int mbedtls_ssl_handle_pending_alert( mbedtls_ssl_context *ssl )
+{
+    int ret;
+
+    /* No pending alert, return success*/
+    if( ssl->send_alert == 0 )
+        return( 0 );
+
+    ret = mbedtls_ssl_send_alert_message( ssl,
+                                MBEDTLS_SSL_ALERT_LEVEL_FATAL,
+                                ssl->alert_type );
+
+    /* If mbedtls_ssl_send_alert_message() returned with MBEDTLS_ERR_SSL_WANT_WRITE,
+     * do not clear the alert to be able to send it later.
+     */
+    if( ret != MBEDTLS_ERR_SSL_WANT_WRITE )
+    {
+        ssl->send_alert = 0;
+    }
+
+    if( ret != 0 )
+        return( ret );
+
+    return( ssl->alert_reason );
+}
+
+/*
+ * Set pending fatal alert flag.
+ */
+void mbedtls_ssl_pend_fatal_alert( mbedtls_ssl_context *ssl,
+                                   unsigned char alert_type,
+                                   int alert_reason )
+{
+    ssl->send_alert = 1;
+    ssl->alert_type = alert_type;
+    ssl->alert_reason = alert_reason;
+}
+
 #endif /* MBEDTLS_SSL_TLS_C */
diff --git a/library/ssl_tls.c b/library/ssl_tls.c
index 3604192..21a058d 100644
--- a/library/ssl_tls.c
+++ b/library/ssl_tls.c
@@ -5170,6 +5170,10 @@
     if( ret != 0 )
         return( ret );
 
+    ret = mbedtls_ssl_handle_pending_alert( ssl );
+    if( ret != 0 )
+        goto cleanup;
+
 #if defined(MBEDTLS_SSL_CLI_C)
     if( ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT )
     {
@@ -5199,6 +5203,19 @@
     }
 #endif
 
+    if( ret != 0 )
+    {
+        /* handshake_step return error. And it is same
+         * with alert_reason.
+         */
+        if( ssl->send_alert )
+        {
+            ret = mbedtls_ssl_handle_pending_alert( ssl );
+            goto cleanup;
+        }
+    }
+
+cleanup:
     return( ret );
 }
 
diff --git a/library/ssl_tls13_client.c b/library/ssl_tls13_client.c
index 41c7a4d..13e932c 100644
--- a/library/ssl_tls13_client.c
+++ b/library/ssl_tls13_client.c
@@ -27,8 +27,11 @@
 
 #include <string.h>
 
+#include "mbedtls/debug.h"
+#include "mbedtls/error.h"
+
 #include "ssl_misc.h"
-#include <mbedtls/debug.h>
+#include "ecdh_misc.h"
 
 #define CLIENT_HELLO_RANDOM_LEN 32
 
@@ -52,13 +55,11 @@
 
     MBEDTLS_SSL_DEBUG_MSG( 3, ( "client hello, adding supported versions extension" ) );
 
-    /*
-     * Check space for extension header.
-     *
-     * extension_type           2
-     * extension_data_length    2
-     * version_length           1
-     * versions                 2
+    /* Check if we have space to write the extension:
+     * - extension_type         (2 bytes)
+     * - extension_data_length  (2 bytes)
+     * - versions_length        (1 byte )
+     * - versions               (2 bytes)
      */
     MBEDTLS_SSL_CHK_BUF_PTR( p, end, 7 );
 
@@ -93,35 +94,366 @@
 
 #if defined(MBEDTLS_KEY_EXCHANGE_WITH_CERT_ENABLED)
 
+/*
+ * Functions for writing supported_groups extension.
+ *
+ * Stucture of supported_groups:
+ *      enum {
+ *          secp256r1(0x0017), secp384r1(0x0018), secp521r1(0x0019),
+ *          x25519(0x001D), x448(0x001E),
+ *          ffdhe2048(0x0100), ffdhe3072(0x0101), ffdhe4096(0x0102),
+ *          ffdhe6144(0x0103), ffdhe8192(0x0104),
+ *          ffdhe_private_use(0x01FC..0x01FF),
+ *          ecdhe_private_use(0xFE00..0xFEFF),
+ *          (0xFFFF)
+ *      } NamedGroup;
+ *      struct {
+ *          NamedGroup named_group_list<2..2^16-1>;
+ *      } NamedGroupList;
+ */
+#if defined(MBEDTLS_ECDH_C)
+/*
+ * In versions of TLS prior to TLS 1.3, this extension was named
+ * 'elliptic_curves' and only contained elliptic curve groups.
+ */
+static int ssl_tls13_write_named_group_list_ecdhe( mbedtls_ssl_context *ssl,
+                                            unsigned char *buf,
+                                            unsigned char *end,
+                                            size_t *olen )
+{
+    unsigned char *p = buf;
+
+    *olen = 0;
+
+    if( ssl->conf->curve_list == NULL )
+        return( MBEDTLS_ERR_SSL_BAD_CONFIG );
+
+    for ( const mbedtls_ecp_group_id *grp_id = ssl->conf->curve_list;
+          *grp_id != MBEDTLS_ECP_DP_NONE;
+          grp_id++ )
+    {
+        const mbedtls_ecp_curve_info *info;
+        info = mbedtls_ecp_curve_info_from_grp_id( *grp_id );
+        if( info == NULL )
+            continue;
+
+        if( !mbedtls_ssl_tls13_named_group_is_ecdhe( info->tls_id ) )
+            continue;
+
+        MBEDTLS_SSL_CHK_BUF_PTR( p, end, 2);
+        MBEDTLS_PUT_UINT16_BE( info->tls_id, p, 0 );
+        p += 2;
+
+        MBEDTLS_SSL_DEBUG_MSG( 3, ( "NamedGroup: %s ( %x )",
+                  mbedtls_ecp_curve_info_from_tls_id( info->tls_id )->name,
+                  info->tls_id ) );
+    }
+
+    *olen = p - buf;
+
+    return( 0 );
+}
+#else
+static int ssl_tls13_write_named_group_list_ecdhe( mbedtls_ssl_context *ssl,
+                                            unsigned char *buf,
+                                            unsigned char *end,
+                                            size_t *olen )
+{
+    ((void) ssl);
+    ((void) buf);
+    ((void) end);
+    *olen = 0;
+    return( MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE );
+}
+#endif /* MBEDTLS_ECDH_C */
+
+static int ssl_tls13_write_named_group_list_dhe( mbedtls_ssl_context *ssl,
+                                        unsigned char *buf,
+                                        unsigned char *end,
+                                        size_t *olen )
+{
+    ((void) ssl);
+    ((void) buf);
+    ((void) end);
+    *olen = 0;
+    MBEDTLS_SSL_DEBUG_MSG( 3, ( "write_named_group_dhe is not implemented" ) );
+    return( MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE );
+}
+
 static int ssl_tls13_write_supported_groups_ext( mbedtls_ssl_context *ssl,
                                                  unsigned char *buf,
                                                  unsigned char *end,
                                                  size_t *olen )
 {
-    ((void) ssl);
-    ((void) buf);
-    ((void) end);
-    ((void) olen);
-    return( MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE );
+    unsigned char *p = buf ;
+    unsigned char *named_group_list_ptr; /* Start of named_group_list */
+    size_t named_group_list_len;         /* Length of named_group_list */
+    size_t output_len = 0;
+    int ret_ecdhe, ret_dhe;
+
+    *olen = 0;
+
+    if( !mbedtls_ssl_conf_tls13_some_ephemeral_enabled( ssl ) )
+        return( 0 );
+
+    MBEDTLS_SSL_DEBUG_MSG( 3, ( "client hello, adding supported_groups extension" ) );
+
+    /* Check if we have space for header and length fields:
+     * - extension_type         (2 bytes)
+     * - extension_data_length  (2 bytes)
+     * - named_group_list_length   (2 bytes)
+     */
+    MBEDTLS_SSL_CHK_BUF_PTR( p, end, 6 );
+    p += 6;
+
+    named_group_list_ptr = p;
+    ret_ecdhe = ssl_tls13_write_named_group_list_ecdhe( ssl, p, end, &output_len );
+    if( ret_ecdhe != 0 )
+    {
+        MBEDTLS_SSL_DEBUG_RET( 1, "ssl_tls13_write_named_group_list_ecdhe", ret_ecdhe );
+    }
+    p += output_len;
+
+    ret_dhe = ssl_tls13_write_named_group_list_dhe( ssl, p, end, &output_len );
+    if( ret_dhe != 0 )
+    {
+        MBEDTLS_SSL_DEBUG_RET( 1, "ssl_tls13_write_named_group_list_dhe", ret_dhe );
+    }
+    p += output_len;
+
+    /* Both ECDHE and DHE failed. */
+    if( ret_ecdhe != 0 && ret_dhe != 0 )
+    {
+        MBEDTLS_SSL_DEBUG_MSG( 1, ( "Both ECDHE and DHE groups are fail. " ) );
+        return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
+    }
+
+    /* Length of named_group_list*/
+    named_group_list_len = p - named_group_list_ptr;
+    if( named_group_list_len == 0 )
+    {
+        MBEDTLS_SSL_DEBUG_MSG( 1, ( "No group available." ) );
+        return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
+    }
+
+    /* Write extension_type */
+    MBEDTLS_PUT_UINT16_BE( MBEDTLS_TLS_EXT_SUPPORTED_GROUPS, buf, 0 );
+    /* Write extension_data_length */
+    MBEDTLS_PUT_UINT16_BE( named_group_list_len + 2, buf, 2 );
+    /* Write length of named_group_list */
+    MBEDTLS_PUT_UINT16_BE( named_group_list_len, buf, 4 );
+
+    MBEDTLS_SSL_DEBUG_BUF( 3, "Supported groups extension", buf + 4, named_group_list_len + 2 );
+
+    *olen = p - buf;
+
+    ssl->handshake->extensions_present |= MBEDTLS_SSL_EXT_SUPPORTED_GROUPS;
+
+    return( 0 );
 }
 
-static int ssl_tls13_write_key_shares_ext( mbedtls_ssl_context *ssl,
-                                           unsigned char *buf,
-                                           unsigned char *end,
-                                           size_t *olen )
+/*
+ * Functions for writing key_share extension.
+ */
+#if defined(MBEDTLS_ECDH_C)
+static int ssl_tls13_generate_and_write_ecdh_key_exchange(
+                mbedtls_ssl_context *ssl,
+                uint16_t named_group,
+                unsigned char *buf,
+                unsigned char *end,
+                size_t *olen )
 {
+    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+    const mbedtls_ecp_curve_info *curve_info =
+        mbedtls_ecp_curve_info_from_tls_id( named_group );
+
+    if( curve_info == NULL )
+        return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
+
+    MBEDTLS_SSL_DEBUG_MSG( 3, ( "offer curve %s", curve_info->name ) );
+
+    if( ( ret = mbedtls_ecdh_setup_no_everest( &ssl->handshake->ecdh_ctx,
+                                               curve_info->grp_id ) ) != 0 )
+    {
+        MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_ecdh_setup_no_everest", ret );
+        return( ret );
+    }
+
+    ret = mbedtls_ecdh_tls13_make_params( &ssl->handshake->ecdh_ctx, olen,
+                                           buf, end - buf,
+                                           ssl->conf->f_rng, ssl->conf->p_rng );
+    if( ret != 0 )
+    {
+        MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_ecdh_tls13_make_params", ret );
+        return( ret );
+    }
+
+    MBEDTLS_SSL_DEBUG_ECDH( 3, &ssl->handshake->ecdh_ctx,
+                            MBEDTLS_DEBUG_ECDH_Q );
+    return( 0 );
+}
+#endif /* MBEDTLS_ECDH_C */
+
+static int ssl_tls13_get_default_group_id( mbedtls_ssl_context *ssl,
+                                           uint16_t *group_id )
+{
+    int ret = MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE;
+
+
+#if defined(MBEDTLS_ECDH_C)
+    /* Pick first available ECDHE group compatible with TLS 1.3 */
+    if( ssl->conf->curve_list == NULL )
+        return( MBEDTLS_ERR_SSL_BAD_CONFIG );
+
+    for ( const mbedtls_ecp_group_id *grp_id = ssl->conf->curve_list;
+          *grp_id != MBEDTLS_ECP_DP_NONE;
+          grp_id++ )
+    {
+        const mbedtls_ecp_curve_info *info;
+        info = mbedtls_ecp_curve_info_from_grp_id( *grp_id );
+        if( info != NULL &&
+            mbedtls_ssl_tls13_named_group_is_ecdhe( info->tls_id ) )
+        {
+            *group_id = info->tls_id;
+            return( 0 );
+        }
+    }
+#else
     ((void) ssl);
-    ((void) buf);
-    ((void) end);
-    ((void) olen);
-    return( MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE );
+    ((void) group_id);
+#endif /* MBEDTLS_ECDH_C */
+
+    /*
+     * Add DHE named groups here.
+     * Pick first available DHE group compatible with TLS 1.3
+     */
+
+    return( ret );
+}
+
+/*
+ * ssl_tls13_write_key_share_ext
+ *
+ * Structure of key_share extension in ClientHello:
+ *
+ *  struct {
+ *          NamedGroup group;
+ *          opaque key_exchange<1..2^16-1>;
+ *      } KeyShareEntry;
+ *  struct {
+ *          KeyShareEntry client_shares<0..2^16-1>;
+ *      } KeyShareClientHello;
+ */
+static int ssl_tls13_write_key_share_ext( mbedtls_ssl_context *ssl,
+                                          unsigned char *buf,
+                                          unsigned char *end,
+                                          size_t *olen )
+{
+    unsigned char *p = buf;
+    unsigned char *client_shares_ptr; /* Start of client_shares */
+    size_t client_shares_len;         /* Length of client_shares */
+    uint16_t group_id;
+    int ret = MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE;
+
+    *olen = 0;
+
+    if( !mbedtls_ssl_conf_tls13_some_ephemeral_enabled( ssl ) )
+        return( 0 );
+
+    /* Check if we have space for header and length fields:
+     * - extension_type         (2 bytes)
+     * - extension_data_length  (2 bytes)
+     * - client_shares_length   (2 bytes)
+     */
+    MBEDTLS_SSL_CHK_BUF_PTR( p, end, 6 );
+    p += 6;
+
+    MBEDTLS_SSL_DEBUG_MSG( 3, ( "client hello: adding key share extension" ) );
+
+    /* HRR could already have requested something else. */
+    group_id = ssl->handshake->offered_group_id;
+    if( !mbedtls_ssl_tls13_named_group_is_ecdhe( group_id ) &&
+        !mbedtls_ssl_tls13_named_group_is_dhe( group_id ) )
+    {
+        MBEDTLS_SSL_PROC_CHK( ssl_tls13_get_default_group_id( ssl,
+                                                              &group_id ) );
+    }
+
+    /*
+     * Dispatch to type-specific key generation function.
+     *
+     * So far, we're only supporting ECDHE. With the introduction
+     * of PQC KEMs, we'll want to have multiple branches, one per
+     * type of KEM, and dispatch to the corresponding crypto. And
+     * only one key share entry is allowed.
+     */
+    client_shares_ptr = p;
+#if defined(MBEDTLS_ECDH_C)
+    if( mbedtls_ssl_tls13_named_group_is_ecdhe( group_id ) )
+    {
+        /* Pointer to group */
+        unsigned char *group_ptr = p;
+        /* Length of key_exchange */
+        size_t key_exchange_len;
+
+        /* Check there is space for header of KeyShareEntry
+         * - group                  (2 bytes)
+         * - key_exchange_length    (2 bytes)
+         */
+        MBEDTLS_SSL_CHK_BUF_PTR( p, end, 4 );
+        p += 4;
+        ret = ssl_tls13_generate_and_write_ecdh_key_exchange( ssl, group_id,
+                                                              p, end,
+                                                              &key_exchange_len );
+        p += key_exchange_len;
+        if( ret != 0 )
+            return( ret );
+
+        /* Write group */
+        MBEDTLS_PUT_UINT16_BE( group_id, group_ptr, 0 );
+        /* Write key_exchange_length */
+        MBEDTLS_PUT_UINT16_BE( key_exchange_len, group_ptr, 2 );
+    }
+    else
+#endif /* MBEDTLS_ECDH_C */
+    if( 0 /* other KEMs? */ )
+    {
+        /* Do something */
+    }
+    else
+        return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
+
+    /* Length of client_shares */
+    client_shares_len = p - client_shares_ptr;
+    if( client_shares_len == 0)
+    {
+        MBEDTLS_SSL_DEBUG_MSG( 1, ( "No key share defined." ) );
+        return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
+    }
+    /* Write extension_type */
+    MBEDTLS_PUT_UINT16_BE( MBEDTLS_TLS_EXT_KEY_SHARE, buf, 0 );
+    /* Write extension_data_length */
+    MBEDTLS_PUT_UINT16_BE( client_shares_len + 2, buf, 2 );
+    /* Write client_shares_length */
+    MBEDTLS_PUT_UINT16_BE( client_shares_len, buf, 4 );
+
+    /* Update offered_group_id field */
+    ssl->handshake->offered_group_id = group_id;
+
+    /* Output the total length of key_share extension. */
+    *olen = p - buf;
+
+    MBEDTLS_SSL_DEBUG_BUF( 3, "client hello, key_share extension", buf, *olen );
+
+    ssl->handshake->extensions_present |= MBEDTLS_SSL_EXT_KEY_SHARE;
+
+cleanup:
+
+    return( ret );
 }
 
 #endif /* MBEDTLS_KEY_EXCHANGE_WITH_CERT_ENABLED */
 
-/*
- * Functions for writing ClientHello message.
- */
 /* Write cipher_suites
  * CipherSuite cipher_suites<2..2^16-2>;
  */
@@ -311,7 +643,7 @@
      * 3) Or, in case all ciphers are supported ( which includes #1 and #2
      *    from above )
      */
-    ret = ssl_tls13_write_key_shares_ext( ssl, p, end, &output_len );
+    ret = ssl_tls13_write_key_share_ext( ssl, p, end, &output_len );
     if( ret != 0 )
         return( ret );
     p += output_len;
@@ -340,7 +672,7 @@
     return( 0 );
 }
 
-static int ssl_tls13_finalize_client_hello( mbedtls_ssl_context* ssl )
+static int ssl_tls13_finalize_client_hello( mbedtls_ssl_context *ssl )
 {
     mbedtls_ssl_handshake_set_state( ssl, MBEDTLS_SSL_SERVER_HELLO );
     return( 0 );
diff --git a/library/ssl_tls13_generic.c b/library/ssl_tls13_generic.c
index ca4c167..5c20f29 100644
--- a/library/ssl_tls13_generic.c
+++ b/library/ssl_tls13_generic.c
@@ -24,6 +24,7 @@
 #if defined(MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL)
 
 #include "mbedtls/error.h"
+#include "mbedtls/debug.h"
 
 #include "ssl_misc.h"
 
@@ -99,17 +100,70 @@
  *
  * Only if we handle at least one key exchange that needs signatures.
  */
-
 int mbedtls_ssl_tls13_write_sig_alg_ext( mbedtls_ssl_context *ssl,
                                          unsigned char *buf,
                                          unsigned char *end,
                                          size_t *olen )
 {
-    ((void) ssl);
-    ((void) buf);
-    ((void) end);
-    ((void) olen);
-    return( MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE );
+    unsigned char *p = buf;
+    unsigned char *supported_sig_alg_ptr; /* Start of supported_signature_algorithms */
+    size_t supported_sig_alg_len = 0;     /* Length of supported_signature_algorithms */
+
+    *olen = 0;
+
+    /* Skip the extension on the client if all allowed key exchanges
+     * are PSK-based. */
+#if defined(MBEDTLS_SSL_CLI_C)
+    if( ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT &&
+        !mbedtls_ssl_conf_tls13_some_ephemeral_enabled( ssl ) )
+    {
+        return( 0 );
+    }
+#endif /* MBEDTLS_SSL_CLI_C */
+
+    MBEDTLS_SSL_DEBUG_MSG( 3, ( "adding signature_algorithms extension" ) );
+
+    /* Check if we have space for header and length field:
+     * - extension_type         (2 bytes)
+     * - extension_data_length  (2 bytes)
+     * - supported_signature_algorithms_length   (2 bytes)
+     */
+    MBEDTLS_SSL_CHK_BUF_PTR( p, end, 6 );
+    p += 6;
+
+    /*
+     * Write supported_signature_algorithms
+     */
+    supported_sig_alg_ptr = p;
+    for( const uint16_t *sig_alg = ssl->conf->tls13_sig_algs;
+         *sig_alg != MBEDTLS_TLS13_SIG_NONE; sig_alg++ )
+    {
+        MBEDTLS_SSL_CHK_BUF_PTR( p, end, 2 );
+        MBEDTLS_PUT_UINT16_BE( *sig_alg, p, 0 );
+        p += 2;
+        MBEDTLS_SSL_DEBUG_MSG( 3, ( "signature scheme [%x]", *sig_alg ) );
+    }
+
+    /* Length of supported_signature_algorithms */
+    supported_sig_alg_len = p - supported_sig_alg_ptr;
+    if( supported_sig_alg_len == 0 )
+    {
+        MBEDTLS_SSL_DEBUG_MSG( 1, ( "No signature algorithms defined." ) );
+        return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
+    }
+
+    /* Write extension_type */
+    MBEDTLS_PUT_UINT16_BE( MBEDTLS_TLS_EXT_SIG_ALG, buf, 0 );
+    /* Write extension_data_length */
+    MBEDTLS_PUT_UINT16_BE( supported_sig_alg_len + 2, buf, 2 );
+    /* Write length of supported_signature_algorithms */
+    MBEDTLS_PUT_UINT16_BE( supported_sig_alg_len, buf, 4 );
+
+    /* Output the total length of signature algorithms extension. */
+    *olen = p - buf;
+
+    ssl->handshake->extensions_present |= MBEDTLS_SSL_EXT_SIG_ALG;
+    return( 0 );
 }
 
 #endif /* MBEDTLS_KEY_EXCHANGE_WITH_CERT_ENABLED */
diff --git a/tests/compat.sh b/tests/compat.sh
index c2bef26..f4c611a 100755
--- a/tests/compat.sh
+++ b/tests/compat.sh
@@ -236,60 +236,46 @@
     G_CIPHERS=""
 }
 
+check_translation()
+{
+    if [ $1 -ne 0 ]; then
+        echo "translate_ciphers.py failed with exit code $1" >&2
+        echo "$2" >&2
+        exit 1
+    fi
+}
+
 # Ciphersuites that can be used with all peers.
 # Since we currently have three possible peers, each ciphersuite should appear
 # three times: in each peer's list (with the name that this peer uses).
 add_common_ciphersuites()
 {
+    CIPHERS=""
     case $TYPE in
 
         "ECDSA")
             if [ `minor_ver "$MODE"` -gt 0 ]
             then
-                M_CIPHERS="$M_CIPHERS                       \
+                CIPHERS="$CIPHERS                           \
                     TLS-ECDHE-ECDSA-WITH-NULL-SHA           \
                     TLS-ECDHE-ECDSA-WITH-3DES-EDE-CBC-SHA   \
                     TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA    \
                     TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA    \
                     "
-                G_CIPHERS="$G_CIPHERS                       \
-                    +ECDHE-ECDSA:+NULL:+SHA1                \
-                    +ECDHE-ECDSA:+3DES-CBC:+SHA1            \
-                    +ECDHE-ECDSA:+AES-128-CBC:+SHA1         \
-                    +ECDHE-ECDSA:+AES-256-CBC:+SHA1         \
-                    "
-                O_CIPHERS="$O_CIPHERS               \
-                    ECDHE-ECDSA-NULL-SHA            \
-                    ECDHE-ECDSA-DES-CBC3-SHA        \
-                    ECDHE-ECDSA-AES128-SHA          \
-                    ECDHE-ECDSA-AES256-SHA          \
-                    "
             fi
             if [ `minor_ver "$MODE"` -ge 3 ]
             then
-                M_CIPHERS="$M_CIPHERS                               \
+                CIPHERS="$CIPHERS                                   \
                     TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256         \
                     TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384         \
                     TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256         \
                     TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384         \
                     "
-                G_CIPHERS="$G_CIPHERS                               \
-                    +ECDHE-ECDSA:+AES-128-CBC:+SHA256               \
-                    +ECDHE-ECDSA:+AES-256-CBC:+SHA384               \
-                    +ECDHE-ECDSA:+AES-128-GCM:+AEAD                 \
-                    +ECDHE-ECDSA:+AES-256-GCM:+AEAD                 \
-                    "
-                O_CIPHERS="$O_CIPHERS               \
-                    ECDHE-ECDSA-AES128-SHA256       \
-                    ECDHE-ECDSA-AES256-SHA384       \
-                    ECDHE-ECDSA-AES128-GCM-SHA256   \
-                    ECDHE-ECDSA-AES256-GCM-SHA384   \
-                    "
             fi
             ;;
 
         "RSA")
-            M_CIPHERS="$M_CIPHERS                       \
+            CIPHERS="$CIPHERS                           \
                 TLS-DHE-RSA-WITH-AES-128-CBC-SHA        \
                 TLS-DHE-RSA-WITH-AES-256-CBC-SHA        \
                 TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA   \
@@ -303,58 +289,18 @@
                 TLS-RSA-WITH-NULL-MD5                   \
                 TLS-RSA-WITH-NULL-SHA                   \
                 "
-            G_CIPHERS="$G_CIPHERS                       \
-                +DHE-RSA:+AES-128-CBC:+SHA1             \
-                +DHE-RSA:+AES-256-CBC:+SHA1             \
-                +DHE-RSA:+CAMELLIA-128-CBC:+SHA1        \
-                +DHE-RSA:+CAMELLIA-256-CBC:+SHA1        \
-                +DHE-RSA:+3DES-CBC:+SHA1                \
-                +RSA:+AES-256-CBC:+SHA1                 \
-                +RSA:+CAMELLIA-256-CBC:+SHA1            \
-                +RSA:+AES-128-CBC:+SHA1                 \
-                +RSA:+CAMELLIA-128-CBC:+SHA1            \
-                +RSA:+3DES-CBC:+SHA1                    \
-                +RSA:+NULL:+MD5                         \
-                +RSA:+NULL:+SHA1                        \
-                "
-            O_CIPHERS="$O_CIPHERS               \
-                DHE-RSA-AES128-SHA              \
-                DHE-RSA-AES256-SHA              \
-                DHE-RSA-CAMELLIA128-SHA         \
-                DHE-RSA-CAMELLIA256-SHA         \
-                EDH-RSA-DES-CBC3-SHA            \
-                AES256-SHA                      \
-                CAMELLIA256-SHA                 \
-                AES128-SHA                      \
-                CAMELLIA128-SHA                 \
-                DES-CBC3-SHA                    \
-                NULL-MD5                        \
-                NULL-SHA                        \
-                "
             if [ `minor_ver "$MODE"` -gt 0 ]
             then
-                M_CIPHERS="$M_CIPHERS                       \
+                CIPHERS="$CIPHERS                           \
                     TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA      \
                     TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA      \
                     TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA     \
                     TLS-ECDHE-RSA-WITH-NULL-SHA             \
                     "
-                G_CIPHERS="$G_CIPHERS                       \
-                    +ECDHE-RSA:+AES-128-CBC:+SHA1           \
-                    +ECDHE-RSA:+AES-256-CBC:+SHA1           \
-                    +ECDHE-RSA:+3DES-CBC:+SHA1              \
-                    +ECDHE-RSA:+NULL:+SHA1                  \
-                    "
-                O_CIPHERS="$O_CIPHERS               \
-                    ECDHE-RSA-AES256-SHA            \
-                    ECDHE-RSA-AES128-SHA            \
-                    ECDHE-RSA-DES-CBC3-SHA          \
-                    ECDHE-RSA-NULL-SHA              \
-                    "
             fi
             if [ `minor_ver "$MODE"` -ge 3 ]
             then
-                M_CIPHERS="$M_CIPHERS                       \
+                CIPHERS="$CIPHERS                           \
                     TLS-RSA-WITH-AES-128-CBC-SHA256         \
                     TLS-DHE-RSA-WITH-AES-128-CBC-SHA256     \
                     TLS-RSA-WITH-AES-256-CBC-SHA256         \
@@ -367,62 +313,36 @@
                     TLS-DHE-RSA-WITH-AES-256-GCM-SHA384     \
                     TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256   \
                     TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384   \
-                    "
-                G_CIPHERS="$G_CIPHERS                       \
-                    +RSA:+AES-128-CBC:+SHA256               \
-                    +DHE-RSA:+AES-128-CBC:+SHA256           \
-                    +RSA:+AES-256-CBC:+SHA256               \
-                    +DHE-RSA:+AES-256-CBC:+SHA256           \
-                    +ECDHE-RSA:+AES-128-CBC:+SHA256         \
-                    +ECDHE-RSA:+AES-256-CBC:+SHA384         \
-                    +RSA:+AES-128-GCM:+AEAD                 \
-                    +RSA:+AES-256-GCM:+AEAD                 \
-                    +DHE-RSA:+AES-128-GCM:+AEAD             \
-                    +DHE-RSA:+AES-256-GCM:+AEAD             \
-                    +ECDHE-RSA:+AES-128-GCM:+AEAD           \
-                    +ECDHE-RSA:+AES-256-GCM:+AEAD           \
-                    "
-                O_CIPHERS="$O_CIPHERS           \
-                    NULL-SHA256                 \
-                    AES128-SHA256               \
-                    DHE-RSA-AES128-SHA256       \
-                    AES256-SHA256               \
-                    DHE-RSA-AES256-SHA256       \
-                    ECDHE-RSA-AES128-SHA256     \
-                    ECDHE-RSA-AES256-SHA384     \
-                    AES128-GCM-SHA256           \
-                    DHE-RSA-AES128-GCM-SHA256   \
-                    AES256-GCM-SHA384           \
-                    DHE-RSA-AES256-GCM-SHA384   \
-                    ECDHE-RSA-AES128-GCM-SHA256 \
-                    ECDHE-RSA-AES256-GCM-SHA384 \
+                    TLS-RSA-WITH-NULL-SHA256                \
                     "
             fi
             ;;
 
         "PSK")
-            M_CIPHERS="$M_CIPHERS                       \
+            CIPHERS="$CIPHERS                           \
                 TLS-PSK-WITH-3DES-EDE-CBC-SHA           \
                 TLS-PSK-WITH-AES-128-CBC-SHA            \
                 TLS-PSK-WITH-AES-256-CBC-SHA            \
                 "
-            G_CIPHERS="$G_CIPHERS                       \
-                +PSK:+3DES-CBC:+SHA1                    \
-                +PSK:+AES-128-CBC:+SHA1                 \
-                +PSK:+AES-256-CBC:+SHA1                 \
-                "
-            O_CIPHERS="$O_CIPHERS               \
-                PSK-3DES-EDE-CBC-SHA            \
-                PSK-AES128-CBC-SHA              \
-                PSK-AES256-CBC-SHA              \
-                "
             ;;
     esac
+
+    M_CIPHERS="$M_CIPHERS $CIPHERS"
+
+    T=$(./scripts/translate_ciphers.py g $CIPHERS)
+    check_translation $? "$T"
+    G_CIPHERS="$G_CIPHERS $T"
+
+    T=$(./scripts/translate_ciphers.py o $CIPHERS)
+    check_translation $? "$T"
+    O_CIPHERS="$O_CIPHERS $T"
 }
 
 # Ciphersuites usable only with Mbed TLS and OpenSSL
-# Each ciphersuite should appear two times, once with its OpenSSL name, once
-# with its Mbed TLS name.
+# A list of ciphersuites in the Mbed TLS convention is compiled and
+# appended to the list of Mbed TLS ciphersuites $M_CIPHERS. The same list
+# is translated to the OpenSSL naming convention and appended to the list of
+# OpenSSL ciphersuites $O_CIPHERS.
 #
 # NOTE: for some reason RSA-PSK doesn't work with OpenSSL,
 # so RSA-PSK ciphersuites need to go in other sections, see
@@ -432,28 +352,23 @@
 # GnuTLS in 3.5.0 and the CI only has 3.4.x so far.
 add_openssl_ciphersuites()
 {
+    CIPHERS=""
     case $TYPE in
 
         "ECDSA")
             if [ `minor_ver "$MODE"` -gt 0 ]
             then
-                M_CIPHERS="$M_CIPHERS                       \
-                    TLS-ECDH-ECDSA-WITH-NULL-SHA            \
+                CIPHERS="$CIPHERS                           \
+                    TLS-ECDH-ECDSA-WITH-NULL-SHA       \
                     TLS-ECDH-ECDSA-WITH-3DES-EDE-CBC-SHA    \
                     TLS-ECDH-ECDSA-WITH-AES-128-CBC-SHA     \
                     TLS-ECDH-ECDSA-WITH-AES-256-CBC-SHA     \
                     "
-                O_CIPHERS="$O_CIPHERS               \
-                    ECDH-ECDSA-NULL-SHA             \
-                    ECDH-ECDSA-DES-CBC3-SHA         \
-                    ECDH-ECDSA-AES128-SHA           \
-                    ECDH-ECDSA-AES256-SHA           \
-                    "
             fi
             if [ `minor_ver "$MODE"` -ge 3 ]
             then
-                M_CIPHERS="$M_CIPHERS                               \
-                    TLS-ECDH-ECDSA-WITH-AES-128-CBC-SHA256          \
+                CIPHERS="$CIPHERS                           \
+                    TLS-ECDH-ECDSA-WITH-AES-128-CBC-SHA256     \
                     TLS-ECDH-ECDSA-WITH-AES-256-CBC-SHA384          \
                     TLS-ECDH-ECDSA-WITH-AES-128-GCM-SHA256          \
                     TLS-ECDH-ECDSA-WITH-AES-256-GCM-SHA384          \
@@ -461,31 +376,18 @@
                     TLS-ECDHE-ECDSA-WITH-ARIA-128-GCM-SHA256        \
                     TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256   \
                     "
-                O_CIPHERS="$O_CIPHERS               \
-                    ECDH-ECDSA-AES128-SHA256        \
-                    ECDH-ECDSA-AES256-SHA384        \
-                    ECDH-ECDSA-AES128-GCM-SHA256    \
-                    ECDH-ECDSA-AES256-GCM-SHA384    \
-                    ECDHE-ECDSA-ARIA256-GCM-SHA384  \
-                    ECDHE-ECDSA-ARIA128-GCM-SHA256  \
-                    ECDHE-ECDSA-CHACHA20-POLY1305   \
-                    "
             fi
             ;;
 
         "RSA")
-            M_CIPHERS="$M_CIPHERS                       \
-                TLS-RSA-WITH-DES-CBC-SHA                \
+            CIPHERS="$CIPHERS                           \
+                TLS-RSA-WITH-DES-CBC-SHA           \
                 TLS-DHE-RSA-WITH-DES-CBC-SHA            \
                 "
-            O_CIPHERS="$O_CIPHERS               \
-                DES-CBC-SHA                     \
-                EDH-RSA-DES-CBC-SHA             \
-                "
             if [ `minor_ver "$MODE"` -ge 3 ]
             then
-                M_CIPHERS="$M_CIPHERS                               \
-                    TLS-ECDHE-RSA-WITH-ARIA-256-GCM-SHA384          \
+                CIPHERS="$CIPHERS                           \
+                    TLS-ECDHE-RSA-WITH-ARIA-256-GCM-SHA384     \
                     TLS-DHE-RSA-WITH-ARIA-256-GCM-SHA384            \
                     TLS-RSA-WITH-ARIA-256-GCM-SHA384                \
                     TLS-ECDHE-RSA-WITH-ARIA-128-GCM-SHA256          \
@@ -494,24 +396,14 @@
                     TLS-DHE-RSA-WITH-CHACHA20-POLY1305-SHA256       \
                     TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256     \
                     "
-                O_CIPHERS="$O_CIPHERS               \
-                    ECDHE-ARIA256-GCM-SHA384        \
-                    DHE-RSA-ARIA256-GCM-SHA384      \
-                    ARIA256-GCM-SHA384              \
-                    ECDHE-ARIA128-GCM-SHA256        \
-                    DHE-RSA-ARIA128-GCM-SHA256      \
-                    ARIA128-GCM-SHA256              \
-                    DHE-RSA-CHACHA20-POLY1305       \
-                    ECDHE-RSA-CHACHA20-POLY1305     \
-                    "
             fi
             ;;
 
         "PSK")
             if [ `minor_ver "$MODE"` -ge 3 ]
             then
-                M_CIPHERS="$M_CIPHERS                               \
-                    TLS-DHE-PSK-WITH-ARIA-256-GCM-SHA384            \
+                CIPHERS="$CIPHERS                           \
+                    TLS-DHE-PSK-WITH-ARIA-256-GCM-SHA384       \
                     TLS-DHE-PSK-WITH-ARIA-128-GCM-SHA256            \
                     TLS-PSK-WITH-ARIA-256-GCM-SHA384                \
                     TLS-PSK-WITH-ARIA-128-GCM-SHA256                \
@@ -519,66 +411,47 @@
                     TLS-ECDHE-PSK-WITH-CHACHA20-POLY1305-SHA256     \
                     TLS-DHE-PSK-WITH-CHACHA20-POLY1305-SHA256       \
                     "
-                O_CIPHERS="$O_CIPHERS               \
-                    DHE-PSK-ARIA256-GCM-SHA384      \
-                    DHE-PSK-ARIA128-GCM-SHA256      \
-                    PSK-ARIA256-GCM-SHA384          \
-                    PSK-ARIA128-GCM-SHA256          \
-                    DHE-PSK-CHACHA20-POLY1305       \
-                    ECDHE-PSK-CHACHA20-POLY1305     \
-                    PSK-CHACHA20-POLY1305           \
-                    "
             fi
             ;;
     esac
+
+    M_CIPHERS="$M_CIPHERS $CIPHERS"
+
+    T=$(./scripts/translate_ciphers.py o $CIPHERS)
+    check_translation $? "$T"
+    O_CIPHERS="$O_CIPHERS $T"
 }
 
 # Ciphersuites usable only with Mbed TLS and GnuTLS
-# Each ciphersuite should appear two times, once with its GnuTLS name, once
-# with its Mbed TLS name.
+# A list of ciphersuites in the Mbed TLS convention is compiled and
+# appended to the list of Mbed TLS ciphersuites $M_CIPHERS. The same list
+# is translated to the GnuTLS naming convention and appended to the list of
+# GnuTLS ciphersuites $G_CIPHERS.
 add_gnutls_ciphersuites()
 {
+    CIPHERS=""
     case $TYPE in
 
         "ECDSA")
             if [ `minor_ver "$MODE"` -ge 3 ]
             then
-                M_CIPHERS="$M_CIPHERS                               \
-                    TLS-ECDHE-ECDSA-WITH-CAMELLIA-128-CBC-SHA256    \
-                    TLS-ECDHE-ECDSA-WITH-CAMELLIA-256-CBC-SHA384    \
-                    TLS-ECDHE-ECDSA-WITH-CAMELLIA-128-GCM-SHA256    \
-                    TLS-ECDHE-ECDSA-WITH-CAMELLIA-256-GCM-SHA384    \
-                    TLS-ECDHE-ECDSA-WITH-AES-128-CCM                \
-                    TLS-ECDHE-ECDSA-WITH-AES-256-CCM                \
-                    TLS-ECDHE-ECDSA-WITH-AES-128-CCM-8              \
-                    TLS-ECDHE-ECDSA-WITH-AES-256-CCM-8              \
-                   "
-                G_CIPHERS="$G_CIPHERS                               \
-                    +ECDHE-ECDSA:+CAMELLIA-128-CBC:+SHA256          \
-                    +ECDHE-ECDSA:+CAMELLIA-256-CBC:+SHA384          \
-                    +ECDHE-ECDSA:+CAMELLIA-128-GCM:+AEAD            \
-                    +ECDHE-ECDSA:+CAMELLIA-256-GCM:+AEAD            \
-                    +ECDHE-ECDSA:+AES-128-CCM:+AEAD                 \
-                    +ECDHE-ECDSA:+AES-256-CCM:+AEAD                 \
-                    +ECDHE-ECDSA:+AES-128-CCM-8:+AEAD               \
-                    +ECDHE-ECDSA:+AES-256-CCM-8:+AEAD               \
+                CIPHERS="$CIPHERS                           \
+                    TLS-ECDHE-ECDSA-WITH-CAMELLIA-128-CBC-SHA256   \
+                    TLS-ECDHE-ECDSA-WITH-CAMELLIA-256-CBC-SHA384        \
+                    TLS-ECDHE-ECDSA-WITH-CAMELLIA-128-GCM-SHA256        \
+                    TLS-ECDHE-ECDSA-WITH-CAMELLIA-256-GCM-SHA384        \
+                    TLS-ECDHE-ECDSA-WITH-AES-128-CCM                    \
+                    TLS-ECDHE-ECDSA-WITH-AES-256-CCM                    \
+                    TLS-ECDHE-ECDSA-WITH-AES-128-CCM-8                  \
+                    TLS-ECDHE-ECDSA-WITH-AES-256-CCM-8                  \
                    "
             fi
             ;;
 
         "RSA")
-            if [ `minor_ver "$MODE"` -gt 0 ]
-            then
-                M_CIPHERS="$M_CIPHERS                           \
-                    TLS-RSA-WITH-NULL-SHA256                    \
-                    "
-                G_CIPHERS="$G_CIPHERS                           \
-                    +RSA:+NULL:+SHA256                          \
-                    "
-            fi
             if [ `minor_ver "$MODE"` -ge 3 ]
             then
-                M_CIPHERS="$M_CIPHERS                           \
+                CIPHERS="$CIPHERS                               \
                     TLS-ECDHE-RSA-WITH-CAMELLIA-128-CBC-SHA256  \
                     TLS-ECDHE-RSA-WITH-CAMELLIA-256-CBC-SHA384  \
                     TLS-RSA-WITH-CAMELLIA-128-CBC-SHA256        \
@@ -600,45 +473,18 @@
                     TLS-DHE-RSA-WITH-AES-128-CCM-8              \
                     TLS-DHE-RSA-WITH-AES-256-CCM-8              \
                     "
-                G_CIPHERS="$G_CIPHERS                           \
-                    +ECDHE-RSA:+CAMELLIA-128-CBC:+SHA256        \
-                    +ECDHE-RSA:+CAMELLIA-256-CBC:+SHA384        \
-                    +RSA:+CAMELLIA-128-CBC:+SHA256              \
-                    +RSA:+CAMELLIA-256-CBC:+SHA256              \
-                    +DHE-RSA:+CAMELLIA-128-CBC:+SHA256          \
-                    +DHE-RSA:+CAMELLIA-256-CBC:+SHA256          \
-                    +ECDHE-RSA:+CAMELLIA-128-GCM:+AEAD          \
-                    +ECDHE-RSA:+CAMELLIA-256-GCM:+AEAD          \
-                    +DHE-RSA:+CAMELLIA-128-GCM:+AEAD            \
-                    +DHE-RSA:+CAMELLIA-256-GCM:+AEAD            \
-                    +RSA:+CAMELLIA-128-GCM:+AEAD                \
-                    +RSA:+CAMELLIA-256-GCM:+AEAD                \
-                    +RSA:+AES-128-CCM:+AEAD                     \
-                    +RSA:+AES-256-CCM:+AEAD                     \
-                    +RSA:+AES-128-CCM-8:+AEAD                   \
-                    +RSA:+AES-256-CCM-8:+AEAD                   \
-                    +DHE-RSA:+AES-128-CCM:+AEAD                 \
-                    +DHE-RSA:+AES-256-CCM:+AEAD                 \
-                    +DHE-RSA:+AES-128-CCM-8:+AEAD               \
-                    +DHE-RSA:+AES-256-CCM-8:+AEAD               \
-                    "
             fi
             ;;
 
         "PSK")
-            M_CIPHERS="$M_CIPHERS                               \
-                TLS-DHE-PSK-WITH-3DES-EDE-CBC-SHA               \
-                TLS-DHE-PSK-WITH-AES-128-CBC-SHA                \
-                TLS-DHE-PSK-WITH-AES-256-CBC-SHA                \
-                "
-            G_CIPHERS="$G_CIPHERS                               \
-                +DHE-PSK:+3DES-CBC:+SHA1                        \
-                +DHE-PSK:+AES-128-CBC:+SHA1                     \
-                +DHE-PSK:+AES-256-CBC:+SHA1                     \
+            CIPHERS="$CIPHERS                                 \
+                TLS-DHE-PSK-WITH-3DES-EDE-CBC-SHA             \
+                TLS-DHE-PSK-WITH-AES-128-CBC-SHA              \
+                TLS-DHE-PSK-WITH-AES-256-CBC-SHA              \
                 "
             if [ `minor_ver "$MODE"` -gt 0 ]
             then
-                M_CIPHERS="$M_CIPHERS                           \
+                CIPHERS="$CIPHERS                               \
                     TLS-ECDHE-PSK-WITH-AES-256-CBC-SHA          \
                     TLS-ECDHE-PSK-WITH-AES-128-CBC-SHA          \
                     TLS-ECDHE-PSK-WITH-3DES-EDE-CBC-SHA         \
@@ -646,18 +492,10 @@
                     TLS-RSA-PSK-WITH-AES-256-CBC-SHA            \
                     TLS-RSA-PSK-WITH-AES-128-CBC-SHA            \
                     "
-                G_CIPHERS="$G_CIPHERS                           \
-                    +ECDHE-PSK:+3DES-CBC:+SHA1                  \
-                    +ECDHE-PSK:+AES-128-CBC:+SHA1               \
-                    +ECDHE-PSK:+AES-256-CBC:+SHA1               \
-                    +RSA-PSK:+3DES-CBC:+SHA1                    \
-                    +RSA-PSK:+AES-256-CBC:+SHA1                 \
-                    +RSA-PSK:+AES-128-CBC:+SHA1                 \
-                    "
             fi
             if [ `minor_ver "$MODE"` -ge 3 ]
             then
-                M_CIPHERS="$M_CIPHERS                           \
+                CIPHERS="$CIPHERS                               \
                     TLS-ECDHE-PSK-WITH-AES-256-CBC-SHA384       \
                     TLS-ECDHE-PSK-WITH-CAMELLIA-256-CBC-SHA384  \
                     TLS-ECDHE-PSK-WITH-AES-128-CBC-SHA256       \
@@ -703,55 +541,15 @@
                     TLS-RSA-PSK-WITH-AES-256-GCM-SHA384         \
                     TLS-RSA-PSK-WITH-AES-128-GCM-SHA256         \
                     "
-                G_CIPHERS="$G_CIPHERS                           \
-                    +ECDHE-PSK:+AES-256-CBC:+SHA384             \
-                    +ECDHE-PSK:+CAMELLIA-256-CBC:+SHA384        \
-                    +ECDHE-PSK:+AES-128-CBC:+SHA256             \
-                    +ECDHE-PSK:+CAMELLIA-128-CBC:+SHA256        \
-                    +PSK:+AES-128-CBC:+SHA256                   \
-                    +PSK:+AES-256-CBC:+SHA384                   \
-                    +DHE-PSK:+AES-128-CBC:+SHA256               \
-                    +DHE-PSK:+AES-256-CBC:+SHA384               \
-                    +RSA-PSK:+AES-256-CBC:+SHA384               \
-                    +RSA-PSK:+AES-128-CBC:+SHA256               \
-                    +DHE-PSK:+CAMELLIA-128-CBC:+SHA256          \
-                    +DHE-PSK:+CAMELLIA-256-CBC:+SHA384          \
-                    +PSK:+CAMELLIA-128-CBC:+SHA256              \
-                    +PSK:+CAMELLIA-256-CBC:+SHA384              \
-                    +RSA-PSK:+CAMELLIA-256-CBC:+SHA384          \
-                    +RSA-PSK:+CAMELLIA-128-CBC:+SHA256          \
-                    +PSK:+AES-128-GCM:+AEAD                     \
-                    +PSK:+AES-256-GCM:+AEAD                     \
-                    +DHE-PSK:+AES-128-GCM:+AEAD                 \
-                    +DHE-PSK:+AES-256-GCM:+AEAD                 \
-                    +PSK:+AES-128-CCM:+AEAD                     \
-                    +PSK:+AES-256-CCM:+AEAD                     \
-                    +DHE-PSK:+AES-128-CCM:+AEAD                 \
-                    +DHE-PSK:+AES-256-CCM:+AEAD                 \
-                    +PSK:+AES-128-CCM-8:+AEAD                   \
-                    +PSK:+AES-256-CCM-8:+AEAD                   \
-                    +DHE-PSK:+AES-128-CCM-8:+AEAD               \
-                    +DHE-PSK:+AES-256-CCM-8:+AEAD               \
-                    +RSA-PSK:+CAMELLIA-128-GCM:+AEAD            \
-                    +RSA-PSK:+CAMELLIA-256-GCM:+AEAD            \
-                    +PSK:+CAMELLIA-128-GCM:+AEAD                \
-                    +PSK:+CAMELLIA-256-GCM:+AEAD                \
-                    +DHE-PSK:+CAMELLIA-128-GCM:+AEAD            \
-                    +DHE-PSK:+CAMELLIA-256-GCM:+AEAD            \
-                    +RSA-PSK:+AES-256-GCM:+AEAD                 \
-                    +RSA-PSK:+AES-128-GCM:+AEAD                 \
-                    +ECDHE-PSK:+NULL:+SHA384                    \
-                    +ECDHE-PSK:+NULL:+SHA256                    \
-                    +PSK:+NULL:+SHA256                          \
-                    +PSK:+NULL:+SHA384                          \
-                    +DHE-PSK:+NULL:+SHA256                      \
-                    +DHE-PSK:+NULL:+SHA384                      \
-                    +RSA-PSK:+NULL:+SHA256                      \
-                    +RSA-PSK:+NULL:+SHA384                      \
-                    "
             fi
             ;;
     esac
+
+    M_CIPHERS="$M_CIPHERS $CIPHERS"
+
+    T=$(./scripts/translate_ciphers.py g $CIPHERS)
+    check_translation $? "$T"
+    G_CIPHERS="$G_CIPHERS $T"
 }
 
 # Ciphersuites usable only with Mbed TLS (not currently supported by another
diff --git a/tests/scripts/all.sh b/tests/scripts/all.sh
index 9944a85..f30795c 100755
--- a/tests/scripts/all.sh
+++ b/tests/scripts/all.sh
@@ -841,7 +841,7 @@
 
 component_check_names () {
     msg "Check: declared and exported names (builds the library)" # < 3s
-    tests/scripts/check-names.sh -v
+    tests/scripts/check_names.py -v
 }
 
 component_check_test_cases () {
@@ -2565,6 +2565,8 @@
     make
     msg "test: default config with MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL enabled, without padding"
     make test
+    msg "ssl-opt.sh (TLS 1.3 experimental)"
+    if_build_succeeded tests/ssl-opt.sh
 }
 
 component_test_tls13_experimental_with_padding () {
@@ -2579,6 +2581,31 @@
     if_build_succeeded tests/ssl-opt.sh
 }
 
+component_test_tls13_experimental_with_ecp_restartable () {
+    msg "build: default config with MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL enabled, with ecp_restartable"
+    scripts/config.py set MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL
+    scripts/config.py set MBEDTLS_ECP_RESTARTABLE
+    CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
+    make
+    msg "test: default config with MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL enabled, with ecp_restartable"
+    make test
+    msg "ssl-opt.sh (TLS 1.3 experimental)"
+    if_build_succeeded tests/ssl-opt.sh
+}
+
+component_test_tls13_experimental_with_everest () {
+    msg "build: default config with MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL enabled, with Everest"
+    scripts/config.py set MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL
+    scripts/config.py set MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED
+    scripts/config.py unset MBEDTLS_ECP_RESTARTABLE
+    CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
+    make
+    msg "test: default config with MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL enabled, with Everest"
+    make test
+    msg "ssl-opt.sh (TLS 1.3 experimental)"
+    if_build_succeeded tests/ssl-opt.sh
+}
+
 component_build_mingw () {
     msg "build: Windows cross build - mingw64, make (Link Library)" # ~ 30s
     make CC=i686-w64-mingw32-gcc AR=i686-w64-mingw32-ar LD=i686-w64-minggw32-ld CFLAGS='-Werror -Wall -Wextra' WINDOWS_BUILD=1 lib programs
@@ -2743,12 +2770,15 @@
     tests/scripts/check-python-files.sh
 }
 
-component_check_generate_test_code () {
-    msg "uint test: generate_test_code.py"
+component_check_test_helpers () {
+    msg "unit test: generate_test_code.py"
     # unittest writes out mundane stuff like number or tests run on stderr.
     # Our convention is to reserve stderr for actual errors, and write
     # harmless info on stdout so it can be suppress with --quiet.
     ./tests/scripts/test_generate_test_code.py 2>&1
+
+    msg "unit test: translate_ciphers.py"
+    python3 -m unittest tests/scripts/translate_ciphers.py 2>&1
 }
 
 ################################################################
diff --git a/tests/scripts/check-names.sh b/tests/scripts/check-names.sh
deleted file mode 100755
index 2a06adc..0000000
--- a/tests/scripts/check-names.sh
+++ /dev/null
@@ -1,129 +0,0 @@
-#!/bin/sh
-#
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -eu
-
-if [ $# -ne 0 ] && [ "$1" = "--help" ]; then
-    cat <<EOF
-$0 [-v]
-This script confirms that the naming of all symbols and identifiers in mbed
-TLS are consistent with the house style and are also self-consistent.
-
-  -v    If the script fails unexpectedly, print a command trace.
-EOF
-    exit
-fi
-
-trace=
-if [ $# -ne 0 ] && [ "$1" = "-v" ]; then
-  shift
-  trace='-x'
-  exec 2>check-names.err
-  trap 'echo "FAILED UNEXPECTEDLY, status=$?";
-        cat check-names.err' EXIT
-  set -x
-fi
-
-printf "Analysing source code...\n"
-
-sh $trace tests/scripts/list-macros.sh
-tests/scripts/list-enum-consts.pl
-sh $trace tests/scripts/list-identifiers.sh
-sh $trace tests/scripts/list-symbols.sh
-
-FAIL=0
-
-printf "\nExported symbols declared in header: "
-UNDECLARED=$( diff exported-symbols identifiers | sed -n -e 's/^< //p' )
-if [ "x$UNDECLARED" = "x" ]; then
-    echo "PASS"
-else
-    echo "FAIL"
-    echo "$UNDECLARED"
-    FAIL=1
-fi
-
-diff macros identifiers | sed -n -e 's/< //p' > actual-macros
-
-for THING in actual-macros enum-consts; do
-    printf 'Names of %s: ' "$THING"
-    test -r $THING
-    BAD=$( grep -E -v '^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$' $THING || true )
-    UNDERSCORES=$( grep -E '.*__.*' $THING || true )
-
-    if [ "x$BAD" = "x" ] && [ "x$UNDERSCORES" = "x" ]; then
-        echo "PASS"
-    else
-        echo "FAIL"
-        echo "$BAD"
-        echo "$UNDERSCORES"
-        FAIL=1
-    fi
-done
-
-for THING in identifiers; do
-    printf 'Names of %s: ' "$THING"
-    test -r $THING
-    BAD=$( grep -E -v '^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$' $THING || true )
-    if [ "x$BAD" = "x" ]; then
-        echo "PASS"
-    else
-        echo "FAIL"
-        echo "$BAD"
-        FAIL=1
-    fi
-done
-
-printf "Likely typos: "
-sort -u actual-macros enum-consts > _caps
-HEADERS=$( ls include/mbedtls/*.h include/psa/*.h | egrep -v 'compat-2\.x\.h' )
-HEADERS="$HEADERS library/*.h"
-HEADERS="$HEADERS 3rdparty/everest/include/everest/everest.h 3rdparty/everest/include/everest/x25519.h"
-LIBRARY="$( ls library/*.c )"
-LIBRARY="$LIBRARY 3rdparty/everest/library/everest.c 3rdparty/everest/library/x25519.c"
-NL='
-'
-cat $HEADERS $LIBRARY \
-    | grep -v -e '//no-check-names' -e '#error' \
-    | sed -n 's/MBED..._[A-Z0-9_]*/\'"$NL"'&\'"$NL"/gp \
-    | grep MBEDTLS | sort -u > _MBEDTLS_XXX
-TYPOS=$( diff _caps _MBEDTLS_XXX | sed -n 's/^> //p' \
-            | egrep -v 'XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$' || true )
-rm _MBEDTLS_XXX _caps
-if [ "x$TYPOS" = "x" ]; then
-    echo "PASS"
-else
-    echo "FAIL"
-    echo "$TYPOS"
-    FAIL=1
-fi
-
-if [ -n "$trace" ]; then
-  set +x
-  trap - EXIT
-  rm check-names.err
-fi
-
-printf "\nOverall: "
-if [ "$FAIL" -eq 0 ]; then
-    rm macros actual-macros enum-consts identifiers exported-symbols
-    echo "PASSED"
-    exit 0
-else
-    echo "FAILED"
-    exit 1
-fi
diff --git a/tests/scripts/check_names.py b/tests/scripts/check_names.py
new file mode 100755
index 0000000..a9aa118
--- /dev/null
+++ b/tests/scripts/check_names.py
@@ -0,0 +1,865 @@
+#!/usr/bin/env python3
+#
+# Copyright The Mbed TLS Contributors
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+This script confirms that the naming of all symbols and identifiers in Mbed TLS
+are consistent with the house style and are also self-consistent. It only runs
+on Linux and macOS since it depends on nm.
+
+It contains two major Python classes, CodeParser and NameChecker. They both have
+a comprehensive "run-all" function (comprehensive_parse() and perform_checks())
+but the individual functions can also be used for specific needs.
+
+CodeParser makes heavy use of regular expressions to parse the code, and is
+dependent on the current code formatting. Many Python C parser libraries require
+preprocessed C code, which means no macro parsing. Compiler tools are also not
+very helpful when we want the exact location in the original source (which
+becomes impossible when e.g. comments are stripped).
+
+NameChecker performs the following checks:
+
+- All exported and available symbols in the library object files, are explicitly
+  declared in the header files. This uses the nm command.
+- All macros, constants, and identifiers (function names, struct names, etc)
+  follow the required regex pattern.
+- Typo checking: All words that begin with MBED exist as macros or constants.
+
+The script returns 0 on success, 1 on test failure, and 2 if there is a script
+error. It must be run from Mbed TLS root.
+"""
+
+import abc
+import argparse
+import glob
+import textwrap
+import os
+import sys
+import traceback
+import re
+import enum
+import shutil
+import subprocess
+import logging
+
+# Naming patterns to check against. These are defined outside the NameCheck
+# class for ease of modification.
+MACRO_PATTERN = r"^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$"
+CONSTANTS_PATTERN = MACRO_PATTERN
+IDENTIFIER_PATTERN = r"^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$"
+
+class Match(): # pylint: disable=too-few-public-methods
+    """
+    A class representing a match, together with its found position.
+
+    Fields:
+    * filename: the file that the match was in.
+    * line: the full line containing the match.
+    * line_no: the line number.
+    * pos: a tuple of (start, end) positions on the line where the match is.
+    * name: the match itself.
+    """
+    def __init__(self, filename, line, line_no, pos, name):
+        # pylint: disable=too-many-arguments
+        self.filename = filename
+        self.line = line
+        self.line_no = line_no
+        self.pos = pos
+        self.name = name
+
+    def __str__(self):
+        """
+        Return a formatted code listing representation of the erroneous line.
+        """
+        gutter = format(self.line_no, "4d")
+        underline = self.pos[0] * " " + (self.pos[1] - self.pos[0]) * "^"
+
+        return (
+            " {0} |\n".format(" " * len(gutter)) +
+            " {0} | {1}".format(gutter, self.line) +
+            " {0} | {1}\n".format(" " * len(gutter), underline)
+        )
+
+class Problem(abc.ABC): # pylint: disable=too-few-public-methods
+    """
+    An abstract parent class representing a form of static analysis error.
+    It extends an Abstract Base Class, which means it is not instantiable, and
+    it also mandates certain abstract methods to be implemented in subclasses.
+    """
+    # Class variable to control the quietness of all problems
+    quiet = False
+    def __init__(self):
+        self.textwrapper = textwrap.TextWrapper()
+        self.textwrapper.width = 80
+        self.textwrapper.initial_indent = "    > "
+        self.textwrapper.subsequent_indent = "      "
+
+    def __str__(self):
+        """
+        Unified string representation method for all Problems.
+        """
+        if self.__class__.quiet:
+            return self.quiet_output()
+        return self.verbose_output()
+
+    @abc.abstractmethod
+    def quiet_output(self):
+        """
+        The output when --quiet is enabled.
+        """
+        pass
+
+    @abc.abstractmethod
+    def verbose_output(self):
+        """
+        The default output with explanation and code snippet if appropriate.
+        """
+        pass
+
+class SymbolNotInHeader(Problem): # pylint: disable=too-few-public-methods
+    """
+    A problem that occurs when an exported/available symbol in the object file
+    is not explicitly declared in header files. Created with
+    NameCheck.check_symbols_declared_in_header()
+
+    Fields:
+    * symbol_name: the name of the symbol.
+    """
+    def __init__(self, symbol_name):
+        self.symbol_name = symbol_name
+        Problem.__init__(self)
+
+    def quiet_output(self):
+        return "{0}".format(self.symbol_name)
+
+    def verbose_output(self):
+        return self.textwrapper.fill(
+            "'{0}' was found as an available symbol in the output of nm, "
+            "however it was not declared in any header files."
+            .format(self.symbol_name))
+
+class PatternMismatch(Problem): # pylint: disable=too-few-public-methods
+    """
+    A problem that occurs when something doesn't match the expected pattern.
+    Created with NameCheck.check_match_pattern()
+
+    Fields:
+    * pattern: the expected regex pattern
+    * match: the Match object in question
+    """
+    def __init__(self, pattern, match):
+        self.pattern = pattern
+        self.match = match
+        Problem.__init__(self)
+
+
+    def quiet_output(self):
+        return (
+            "{0}:{1}:{2}"
+            .format(self.match.filename, self.match.line_no, self.match.name)
+        )
+
+    def verbose_output(self):
+        return self.textwrapper.fill(
+            "{0}:{1}: '{2}' does not match the required pattern '{3}'."
+            .format(
+                self.match.filename,
+                self.match.line_no,
+                self.match.name,
+                self.pattern
+            )
+        ) + "\n" + str(self.match)
+
+class Typo(Problem): # pylint: disable=too-few-public-methods
+    """
+    A problem that occurs when a word using MBED doesn't appear to be defined as
+    constants nor enum values. Created with NameCheck.check_for_typos()
+
+    Fields:
+    * match: the Match object of the MBED name in question.
+    """
+    def __init__(self, match):
+        self.match = match
+        Problem.__init__(self)
+
+    def quiet_output(self):
+        return (
+            "{0}:{1}:{2}"
+            .format(self.match.filename, self.match.line_no, self.match.name)
+        )
+
+    def verbose_output(self):
+        return self.textwrapper.fill(
+            "{0}:{1}: '{2}' looks like a typo. It was not found in any "
+            "macros or any enums. If this is not a typo, put "
+            "//no-check-names after it."
+            .format(self.match.filename, self.match.line_no, self.match.name)
+        ) + "\n" + str(self.match)
+
+class CodeParser():
+    """
+    Class for retrieving files and parsing the code. This can be used
+    independently of the checks that NameChecker performs, for example for
+    list_internal_identifiers.py.
+    """
+    def __init__(self, log):
+        self.log = log
+        self.check_repo_path()
+
+        # Memo for storing "glob expression": set(filepaths)
+        self.files = {}
+
+        # Globally excluded filenames
+        self.excluded_files = ["**/bn_mul", "**/compat-2.x.h"]
+
+    @staticmethod
+    def check_repo_path():
+        """
+        Check that the current working directory is the project root, and throw
+        an exception if not.
+        """
+        if not all(os.path.isdir(d) for d in ["include", "library", "tests"]):
+            raise Exception("This script must be run from Mbed TLS root")
+
+    def comprehensive_parse(self):
+        """
+        Comprehensive ("default") function to call each parsing function and
+        retrieve various elements of the code, together with the source location.
+
+        Returns a dict of parsed item key to the corresponding List of Matches.
+        """
+        self.log.info("Parsing source code...")
+        self.log.debug(
+            "The following files are excluded from the search: {}"
+            .format(str(self.excluded_files))
+        )
+
+        all_macros = self.parse_macros([
+            "include/mbedtls/*.h",
+            "include/psa/*.h",
+            "library/*.h",
+            "tests/include/test/drivers/*.h",
+            "3rdparty/everest/include/everest/everest.h",
+            "3rdparty/everest/include/everest/x25519.h"
+        ])
+        enum_consts = self.parse_enum_consts([
+            "include/mbedtls/*.h",
+            "library/*.h",
+            "3rdparty/everest/include/everest/everest.h",
+            "3rdparty/everest/include/everest/x25519.h"
+        ])
+        identifiers = self.parse_identifiers([
+            "include/mbedtls/*.h",
+            "include/psa/*.h",
+            "library/*.h",
+            "3rdparty/everest/include/everest/everest.h",
+            "3rdparty/everest/include/everest/x25519.h"
+        ])
+        mbed_words = self.parse_mbed_words([
+            "include/mbedtls/*.h",
+            "include/psa/*.h",
+            "library/*.h",
+            "3rdparty/everest/include/everest/everest.h",
+            "3rdparty/everest/include/everest/x25519.h",
+            "library/*.c",
+            "3rdparty/everest/library/everest.c",
+            "3rdparty/everest/library/x25519.c"
+        ])
+        symbols = self.parse_symbols()
+
+        # Remove identifier macros like mbedtls_printf or mbedtls_calloc
+        identifiers_justname = [x.name for x in identifiers]
+        actual_macros = []
+        for macro in all_macros:
+            if macro.name not in identifiers_justname:
+                actual_macros.append(macro)
+
+        self.log.debug("Found:")
+        # Aligns the counts on the assumption that none exceeds 4 digits
+        self.log.debug("  {:4} Total Macros".format(len(all_macros)))
+        self.log.debug("  {:4} Non-identifier Macros".format(len(actual_macros)))
+        self.log.debug("  {:4} Enum Constants".format(len(enum_consts)))
+        self.log.debug("  {:4} Identifiers".format(len(identifiers)))
+        self.log.debug("  {:4} Exported Symbols".format(len(symbols)))
+        return {
+            "macros": actual_macros,
+            "enum_consts": enum_consts,
+            "identifiers": identifiers,
+            "symbols": symbols,
+            "mbed_words": mbed_words
+        }
+
+    def get_files(self, include_wildcards, exclude_wildcards):
+        """
+        Get all files that match any of the UNIX-style wildcards. While the
+        check_names script is designed only for use on UNIX/macOS (due to nm),
+        this function alone would work fine on Windows even with forward slashes
+        in the wildcard.
+
+        Args:
+        * include_wildcards: a List of shell-style wildcards to match filepaths.
+        * exclude_wildcards: a List of shell-style wildcards to exclude.
+
+        Returns a List of relative filepaths.
+        """
+        accumulator = set()
+
+        # exclude_wildcards may be None. Also, consider the global exclusions.
+        exclude_wildcards = (exclude_wildcards or []) + self.excluded_files
+
+        # Internal function to hit the memoisation cache or add to it the result
+        # of a glob operation. Used both for inclusion and exclusion since the
+        # only difference between them is whether they perform set union or
+        # difference on the return value of this function.
+        def hit_cache(wildcard):
+            if wildcard not in self.files:
+                self.files[wildcard] = set(glob.glob(wildcard, recursive=True))
+            return self.files[wildcard]
+
+        for include_wildcard in include_wildcards:
+            accumulator = accumulator.union(hit_cache(include_wildcard))
+
+        for exclude_wildcard in exclude_wildcards:
+            accumulator = accumulator.difference(hit_cache(exclude_wildcard))
+
+        return list(accumulator)
+
+    def parse_macros(self, include, exclude=None):
+        """
+        Parse all macros defined by #define preprocessor directives.
+
+        Args:
+        * include: A List of glob expressions to look for files through.
+        * exclude: A List of glob expressions for excluding files.
+
+        Returns a List of Match objects for the found macros.
+        """
+        macro_regex = re.compile(r"# *define +(?P<macro>\w+)")
+        exclusions = (
+            "asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
+        )
+
+        files = self.get_files(include, exclude)
+        self.log.debug("Looking for macros in {} files".format(len(files)))
+
+        macros = []
+        for header_file in files:
+            with open(header_file, "r", encoding="utf-8") as header:
+                for line_no, line in enumerate(header):
+                    for macro in macro_regex.finditer(line):
+                        if macro.group("macro").startswith(exclusions):
+                            continue
+
+                        macros.append(Match(
+                            header_file,
+                            line,
+                            line_no,
+                            macro.span("macro"),
+                            macro.group("macro")))
+
+        return macros
+
+    def parse_mbed_words(self, include, exclude=None):
+        """
+        Parse all words in the file that begin with MBED, in and out of macros,
+        comments, anything.
+
+        Args:
+        * include: A List of glob expressions to look for files through.
+        * exclude: A List of glob expressions for excluding files.
+
+        Returns a List of Match objects for words beginning with MBED.
+        """
+        # Typos of TLS are common, hence the broader check below than MBEDTLS.
+        mbed_regex = re.compile(r"\bMBED.+?_[A-Z0-9_]*")
+        exclusions = re.compile(r"// *no-check-names|#error")
+
+        files = self.get_files(include, exclude)
+        self.log.debug("Looking for MBED words in {} files".format(len(files)))
+
+        mbed_words = []
+        for filename in files:
+            with open(filename, "r", encoding="utf-8") as fp:
+                for line_no, line in enumerate(fp):
+                    if exclusions.search(line):
+                        continue
+
+                    for name in mbed_regex.finditer(line):
+                        mbed_words.append(Match(
+                            filename,
+                            line,
+                            line_no,
+                            name.span(0),
+                            name.group(0)))
+
+        return mbed_words
+
+    def parse_enum_consts(self, include, exclude=None):
+        """
+        Parse all enum value constants that are declared.
+
+        Args:
+        * include: A List of glob expressions to look for files through.
+        * exclude: A List of glob expressions for excluding files.
+
+        Returns a List of Match objects for the findings.
+        """
+        files = self.get_files(include, exclude)
+        self.log.debug("Looking for enum consts in {} files".format(len(files)))
+
+        # Emulate a finite state machine to parse enum declarations.
+        # OUTSIDE_KEYWORD = outside the enum keyword
+        # IN_BRACES = inside enum opening braces
+        # IN_BETWEEN = between enum keyword and opening braces
+        states = enum.Enum("FSM", ["OUTSIDE_KEYWORD", "IN_BRACES", "IN_BETWEEN"])
+        enum_consts = []
+        for header_file in files:
+            state = states.OUTSIDE_KEYWORD
+            with open(header_file, "r", encoding="utf-8") as header:
+                for line_no, line in enumerate(header):
+                    # Match typedefs and brackets only when they are at the
+                    # beginning of the line -- if they are indented, they might
+                    # be sub-structures within structs, etc.
+                    if (state == states.OUTSIDE_KEYWORD and
+                            re.search(r"^(typedef +)?enum +{", line)):
+                        state = states.IN_BRACES
+                    elif (state == states.OUTSIDE_KEYWORD and
+                          re.search(r"^(typedef +)?enum", line)):
+                        state = states.IN_BETWEEN
+                    elif (state == states.IN_BETWEEN and
+                          re.search(r"^{", line)):
+                        state = states.IN_BRACES
+                    elif (state == states.IN_BRACES and
+                          re.search(r"^}", line)):
+                        state = states.OUTSIDE_KEYWORD
+                    elif (state == states.IN_BRACES and
+                          not re.search(r"^ *#", line)):
+                        enum_const = re.search(r"^ *(?P<enum_const>\w+)", line)
+                        if not enum_const:
+                            continue
+
+                        enum_consts.append(Match(
+                            header_file,
+                            line,
+                            line_no,
+                            enum_const.span("enum_const"),
+                            enum_const.group("enum_const")))
+
+        return enum_consts
+
+    def parse_identifiers(self, include, exclude=None):
+        """
+        Parse all lines of a header where a function/enum/struct/union/typedef
+        identifier is declared, based on some regex and heuristics. Highly
+        dependent on formatting style.
+
+        Args:
+        * include: A List of glob expressions to look for files through.
+        * exclude: A List of glob expressions for excluding files.
+
+        Returns a List of Match objects with identifiers.
+        """
+        identifier_regex = re.compile(
+            # Match " something(a" or " *something(a". Functions.
+            # Assumptions:
+            # - function definition from return type to one of its arguments is
+            #   all on one line
+            # - function definition line only contains alphanumeric, asterisk,
+            #   underscore, and open bracket
+            r".* \**(\w+) *\( *\w|"
+            # Match "(*something)(".
+            r".*\( *\* *(\w+) *\) *\(|"
+            # Match names of named data structures.
+            r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$|"
+            # Match names of typedef instances, after closing bracket.
+            r"}? *(\w+)[;[].*"
+        )
+        # The regex below is indented for clarity.
+        exclusion_lines = re.compile(
+            r"^("
+                r"extern +\"C\"|" # pylint: disable=bad-continuation
+                r"(typedef +)?(struct|union|enum)( *{)?$|"
+                r"} *;?$|"
+                r"$|"
+                r"//|"
+                r"#"
+            r")"
+        )
+
+        files = self.get_files(include, exclude)
+        self.log.debug("Looking for identifiers in {} files".format(len(files)))
+
+        identifiers = []
+        for header_file in files:
+            with open(header_file, "r", encoding="utf-8") as header:
+                in_block_comment = False
+                # The previous line variable is used for concatenating lines
+                # when identifiers are formatted and spread across multiple
+                # lines.
+                previous_line = ""
+
+                for line_no, line in enumerate(header):
+                    # Skip parsing this line if a block comment ends on it,
+                    # but don't skip if it has just started -- there is a chance
+                    # it ends on the same line.
+                    if re.search(r"/\*", line):
+                        in_block_comment = not in_block_comment
+                    if re.search(r"\*/", line):
+                        in_block_comment = not in_block_comment
+                        continue
+
+                    if in_block_comment:
+                        previous_line = ""
+                        continue
+
+                    if exclusion_lines.search(line):
+                        previous_line = ""
+                        continue
+
+                    # If the line contains only space-separated alphanumeric
+                    # characters (or underscore, asterisk, or, open bracket),
+                    # and nothing else, high chance it's a declaration that
+                    # continues on the next line
+                    if re.search(r"^([\w\*\(]+\s+)+$", line):
+                        previous_line += line
+                        continue
+
+                    # If previous line seemed to start an unfinished declaration
+                    # (as above), concat and treat them as one.
+                    if previous_line:
+                        line = previous_line.strip() + " " + line.strip() + "\n"
+                        previous_line = ""
+
+                    # Skip parsing if line has a space in front = heuristic to
+                    # skip function argument lines (highly subject to formatting
+                    # changes)
+                    if line[0] == " ":
+                        continue
+
+                    identifier = identifier_regex.search(line)
+
+                    if not identifier:
+                        continue
+
+                    # Find the group that matched, and append it
+                    for group in identifier.groups():
+                        if not group:
+                            continue
+
+                        identifiers.append(Match(
+                            header_file,
+                            line,
+                            line_no,
+                            identifier.span(),
+                            group))
+
+        return identifiers
+
+    def parse_symbols(self):
+        """
+        Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
+        object files using nm to retrieve the list of referenced symbols.
+        Exceptions thrown here are rethrown because they would be critical
+        errors that void several tests, and thus needs to halt the program. This
+        is explicitly done for clarity.
+
+        Returns a List of unique symbols defined and used in the libraries.
+        """
+        self.log.info("Compiling...")
+        symbols = []
+
+        # Back up the config and atomically compile with the full configratuion.
+        shutil.copy(
+            "include/mbedtls/mbedtls_config.h",
+            "include/mbedtls/mbedtls_config.h.bak"
+        )
+        try:
+            # Use check=True in all subprocess calls so that failures are raised
+            # as exceptions and logged.
+            subprocess.run(
+                ["python3", "scripts/config.py", "full"],
+                universal_newlines=True,
+                check=True
+            )
+            my_environment = os.environ.copy()
+            my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
+            # Run make clean separately to lib to prevent unwanted behavior when
+            # make is invoked with parallelism.
+            subprocess.run(
+                ["make", "clean"],
+                universal_newlines=True,
+                check=True
+            )
+            subprocess.run(
+                ["make", "lib"],
+                env=my_environment,
+                universal_newlines=True,
+                stdout=subprocess.PIPE,
+                stderr=subprocess.STDOUT,
+                check=True
+            )
+
+            # Perform object file analysis using nm
+            symbols = self.parse_symbols_from_nm([
+                "library/libmbedcrypto.a",
+                "library/libmbedtls.a",
+                "library/libmbedx509.a"
+            ])
+
+            subprocess.run(
+                ["make", "clean"],
+                universal_newlines=True,
+                check=True
+            )
+        except subprocess.CalledProcessError as error:
+            self.log.debug(error.output)
+            raise error
+        finally:
+            # Put back the original config regardless of there being errors.
+            # Works also for keyboard interrupts.
+            shutil.move(
+                "include/mbedtls/mbedtls_config.h.bak",
+                "include/mbedtls/mbedtls_config.h"
+            )
+
+        return symbols
+
+    def parse_symbols_from_nm(self, object_files):
+        """
+        Run nm to retrieve the list of referenced symbols in each object file.
+        Does not return the position data since it is of no use.
+
+        Args:
+        * object_files: a List of compiled object filepaths to search through.
+
+        Returns a List of unique symbols defined and used in any of the object
+        files.
+        """
+        nm_undefined_regex = re.compile(r"^\S+: +U |^$|^\S+:$")
+        nm_valid_regex = re.compile(r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)")
+        exclusions = ("FStar", "Hacl")
+
+        symbols = []
+
+        # Gather all outputs of nm
+        nm_output = ""
+        for lib in object_files:
+            nm_output += subprocess.run(
+                ["nm", "-og", lib],
+                universal_newlines=True,
+                stdout=subprocess.PIPE,
+                stderr=subprocess.STDOUT,
+                check=True
+            ).stdout
+
+        for line in nm_output.splitlines():
+            if not nm_undefined_regex.search(line):
+                symbol = nm_valid_regex.search(line)
+                if (symbol and not symbol.group("symbol").startswith(exclusions)):
+                    symbols.append(symbol.group("symbol"))
+                else:
+                    self.log.error(line)
+
+        return symbols
+
+class NameChecker():
+    """
+    Representation of the core name checking operation performed by this script.
+    """
+    def __init__(self, parse_result, log):
+        self.parse_result = parse_result
+        self.log = log
+
+    def perform_checks(self, quiet=False):
+        """
+        A comprehensive checker that performs each check in order, and outputs
+        a final verdict.
+
+        Args:
+        * quiet: whether to hide detailed problem explanation.
+        """
+        self.log.info("=============")
+        Problem.quiet = quiet
+        problems = 0
+        problems += self.check_symbols_declared_in_header()
+
+        pattern_checks = [
+            ("macros", MACRO_PATTERN),
+            ("enum_consts", CONSTANTS_PATTERN),
+            ("identifiers", IDENTIFIER_PATTERN)
+        ]
+        for group, check_pattern in pattern_checks:
+            problems += self.check_match_pattern(group, check_pattern)
+
+        problems += self.check_for_typos()
+
+        self.log.info("=============")
+        if problems > 0:
+            self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
+            if quiet:
+                self.log.info("Remove --quiet to see explanations.")
+            else:
+                self.log.info("Use --quiet for minimal output.")
+            return 1
+        else:
+            self.log.info("PASS")
+            return 0
+
+    def check_symbols_declared_in_header(self):
+        """
+        Perform a check that all detected symbols in the library object files
+        are properly declared in headers.
+        Assumes parse_names_in_source() was called before this.
+
+        Returns the number of problems that need fixing.
+        """
+        problems = []
+
+        for symbol in self.parse_result["symbols"]:
+            found_symbol_declared = False
+            for identifier_match in self.parse_result["identifiers"]:
+                if symbol == identifier_match.name:
+                    found_symbol_declared = True
+                    break
+
+            if not found_symbol_declared:
+                problems.append(SymbolNotInHeader(symbol))
+
+        self.output_check_result("All symbols in header", problems)
+        return len(problems)
+
+    def check_match_pattern(self, group_to_check, check_pattern):
+        """
+        Perform a check that all items of a group conform to a regex pattern.
+        Assumes parse_names_in_source() was called before this.
+
+        Args:
+        * group_to_check: string key to index into self.parse_result.
+        * check_pattern: the regex to check against.
+
+        Returns the number of problems that need fixing.
+        """
+        problems = []
+
+        for item_match in self.parse_result[group_to_check]:
+            if not re.search(check_pattern, item_match.name):
+                problems.append(PatternMismatch(check_pattern, item_match))
+            # Double underscore should not be used for names
+            if re.search(r".*__.*", item_match.name):
+                problems.append(
+                    PatternMismatch("no double underscore allowed", item_match))
+
+        self.output_check_result(
+            "Naming patterns of {}".format(group_to_check),
+            problems)
+        return len(problems)
+
+    def check_for_typos(self):
+        """
+        Perform a check that all words in the soure code beginning with MBED are
+        either defined as macros, or as enum constants.
+        Assumes parse_names_in_source() was called before this.
+
+        Returns the number of problems that need fixing.
+        """
+        problems = []
+
+        # Set comprehension, equivalent to a list comprehension wrapped by set()
+        all_caps_names = {
+            match.name
+            for match
+            in self.parse_result["macros"] + self.parse_result["enum_consts"]}
+        typo_exclusion = re.compile(r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$")
+
+        for name_match in self.parse_result["mbed_words"]:
+            found = name_match.name in all_caps_names
+
+            # Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
+            # PSA driver, they will not exist as macros. However, they
+            # should still be checked for typos using the equivalent
+            # BUILTINs that exist.
+            if "MBEDTLS_PSA_ACCEL_" in name_match.name:
+                found = name_match.name.replace(
+                    "MBEDTLS_PSA_ACCEL_",
+                    "MBEDTLS_PSA_BUILTIN_") in all_caps_names
+
+            if not found and not typo_exclusion.search(name_match.name):
+                problems.append(Typo(name_match))
+
+        self.output_check_result("Likely typos", problems)
+        return len(problems)
+
+    def output_check_result(self, name, problems):
+        """
+        Write out the PASS/FAIL status of a performed check depending on whether
+        there were problems.
+
+        Args:
+        * name: the name of the test
+        * problems: a List of encountered Problems
+        """
+        if problems:
+            self.log.info("{}: FAIL\n".format(name))
+            for problem in problems:
+                self.log.warning(str(problem))
+        else:
+            self.log.info("{}: PASS".format(name))
+
+def main():
+    """
+    Perform argument parsing, and create an instance of CodeParser and
+    NameChecker to begin the core operation.
+    """
+    parser = argparse.ArgumentParser(
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description=(
+            "This script confirms that the naming of all symbols and identifiers "
+            "in Mbed TLS are consistent with the house style and are also "
+            "self-consistent.\n\n"
+            "Expected to be run from the MbedTLS root directory.")
+    )
+    parser.add_argument(
+        "-v", "--verbose",
+        action="store_true",
+        help="show parse results"
+    )
+    parser.add_argument(
+        "-q", "--quiet",
+        action="store_true",
+        help="hide unnecessary text, explanations, and highlighs"
+    )
+
+    args = parser.parse_args()
+
+    # Configure the global logger, which is then passed to the classes below
+    log = logging.getLogger()
+    log.setLevel(logging.DEBUG if args.verbose else logging.INFO)
+    log.addHandler(logging.StreamHandler())
+
+    try:
+        code_parser = CodeParser(log)
+        parse_result = code_parser.comprehensive_parse()
+    except Exception: # pylint: disable=broad-except
+        traceback.print_exc()
+        sys.exit(2)
+
+    name_checker = NameChecker(parse_result, log)
+    return_code = name_checker.perform_checks(quiet=args.quiet)
+
+    sys.exit(return_code)
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/scripts/list-enum-consts.pl b/tests/scripts/list-enum-consts.pl
deleted file mode 100755
index 6d93693..0000000
--- a/tests/scripts/list-enum-consts.pl
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env perl
-#
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-use warnings;
-use strict;
-
-use utf8;
-use open qw(:std utf8);
-
--d 'include/mbedtls' or die "$0: must be run from root\n";
-
-@ARGV = grep { ! /compat-2\.x\.h/ } <include/mbedtls/*.h>;
-push @ARGV, "3rdparty/everest/include/everest/everest.h";
-push @ARGV, "3rdparty/everest/include/everest/x25519.h";
-push @ARGV, glob("library/*.h");
-
-my @consts;
-my $state = 'out';
-while (<>)
-{
-    if( $state eq 'out' and /^(typedef )?enum \{/ ) {
-        $state = 'in';
-    } elsif( $state eq 'out' and /^(typedef )?enum/ ) {
-        $state = 'start';
-    } elsif( $state eq 'start' and /{/ ) {
-        $state = 'in';
-    } elsif( $state eq 'in' and /}/ ) {
-        $state = 'out';
-    } elsif( $state eq 'in' and not /^#/) {
-        s/=.*//; s!/\*.*!!; s/,.*//; s/\s+//g; chomp;
-        push @consts, $_ if $_;
-    }
-}
-
-open my $fh, '>', 'enum-consts' or die;
-print $fh "$_\n" for sort @consts;
-close $fh or die;
-
-printf "%8d enum-consts\n", scalar @consts;
diff --git a/tests/scripts/list-identifiers.sh b/tests/scripts/list-identifiers.sh
index 9698fc8..9b93080 100755
--- a/tests/scripts/list-identifiers.sh
+++ b/tests/scripts/list-identifiers.sh
@@ -1,8 +1,11 @@
 #!/bin/bash
 #
 # Create a file named identifiers containing identifiers from internal header
-# files or all header files, based on --internal flag.
+# files, based on the --internal flag.
 # Outputs the line count of the file to stdout.
+# A very thin wrapper around list_internal_identifiers.py for backwards
+# compatibility.
+# Must be run from Mbed TLS root.
 #
 # Usage: list-identifiers.sh [ -i | --internal ]
 #
@@ -24,7 +27,7 @@
 set -eu
 
 if [ -d include/mbedtls ]; then :; else
-    echo "$0: must be run from root" >&2
+    echo "$0: Must be run from Mbed TLS root" >&2
     exit 1
 fi
 
@@ -47,32 +50,17 @@
 
 if [ $INTERNAL ]
 then
-    HEADERS=$( ls include/mbedtls/*_internal.h library/*.h | egrep -v 'compat-2\.x\.h' )
+    tests/scripts/list_internal_identifiers.py
+    wc -l identifiers
 else
-    HEADERS=$( ls include/mbedtls/*.h include/psa/*.h library/*.h | egrep -v 'compat-2\.x\.h' )
-    HEADERS="$HEADERS 3rdparty/everest/include/everest/everest.h 3rdparty/everest/include/everest/x25519.h"
+    cat <<EOF
+Sorry, this script has to be called with --internal.
+
+This script exists solely for backwards compatibility with the previous
+iteration of list-identifiers.sh, of which only the --internal option remains in
+use. It is a thin wrapper around list_internal_identifiers.py.
+
+check-names.sh, which used to depend on this script, has been replaced with
+check_names.py and is now self-complete.
+EOF
 fi
-
-rm -f identifiers
-
-grep '^[^ /#{]' $HEADERS | \
-    sed -e 's/^[^:]*://' | \
-    egrep -v '^(extern "C"|(typedef )?(struct|union|enum)( {)?$|};?$)' \
-    > _decls
-
-if true; then
-sed -n -e 's/.* \**\([a-zA-Z_][a-zA-Z0-9_]*\)(.*/\1/p' \
-       -e 's/.*(\*\(.*\))(.*/\1/p' _decls
-grep -v '(' _decls | sed -e 's/\([a-zA-Z0-9_]*\)[;[].*/\1/' -e 's/.* \**//'
-fi > _identifiers
-
-if [ $( wc -l < _identifiers ) -eq $( wc -l < _decls ) ]; then
-    rm _decls
-    egrep -v '^(u?int(16|32|64)_t)$' _identifiers | sort > identifiers
-    rm _identifiers
-else
-    echo "$0: oops, lost some identifiers" 2>&1
-    exit 1
-fi
-
-wc -l identifiers
diff --git a/tests/scripts/list-macros.sh b/tests/scripts/list-macros.sh
deleted file mode 100755
index 2e62359..0000000
--- a/tests/scripts/list-macros.sh
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/sh
-#
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -eu
-
-if [ -d include/mbedtls ]; then :; else
-    echo "$0: must be run from root" >&2
-    exit 1
-fi
-
-HEADERS=$( ls include/mbedtls/*.h include/psa/*.h tests/include/test/drivers/*.h | egrep -v 'compat-2\.x\.h' )
-HEADERS="$HEADERS library/*.h"
-HEADERS="$HEADERS 3rdparty/everest/include/everest/everest.h 3rdparty/everest/include/everest/x25519.h"
-
-sed -n -e 's/.*#define \([a-zA-Z0-9_]*\).*/\1/p' $HEADERS \
-    | egrep -v '^(asm|inline|EMIT|_CRT_SECURE_NO_DEPRECATE)$|^MULADDC_' \
-    | sort -u > macros
-
-# For include/mbedtls/config_psa.h need to ignore the MBEDTLS_xxx define
-# in that file since they may not be defined in include/psa/crypto_config.h
-# This line renames the potentially missing defines to ones that should
-# be present.
-sed -ne 's/^MBEDTLS_PSA_BUILTIN_/MBEDTLS_PSA_ACCEL_/p' <macros >>macros
-
-wc -l macros
diff --git a/tests/scripts/list-symbols.sh b/tests/scripts/list-symbols.sh
deleted file mode 100755
index 4a5d035..0000000
--- a/tests/scripts/list-symbols.sh
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/bin/sh
-#
-# Copyright The Mbed TLS Contributors
-# SPDX-License-Identifier: Apache-2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -eu
-
-if [ -d include/mbedtls ]; then :; else
-    echo "$0: must be run from root" >&2
-    exit 1
-fi
-
-if grep -i cmake Makefile >/dev/null; then
-    echo "$0: not compatible with cmake" >&2
-    exit 1
-fi
-
-cp include/mbedtls/mbedtls_config.h include/mbedtls/mbedtls_config.h.bak
-scripts/config.py full
-make clean
-make_ret=
-CFLAGS=-fno-asynchronous-unwind-tables make lib \
-      >list-symbols.make.log 2>&1 ||
-  {
-    make_ret=$?
-    echo "Build failure: CFLAGS=-fno-asynchronous-unwind-tables make lib"
-    cat list-symbols.make.log >&2
-  }
-rm list-symbols.make.log
-mv include/mbedtls/mbedtls_config.h.bak include/mbedtls/mbedtls_config.h
-if [ -n "$make_ret" ]; then
-    exit "$make_ret"
-fi
-
-if uname | grep -F Darwin >/dev/null; then
-    nm -gUj library/libmbed*.a 2>/dev/null | sed -n -e 's/^_//p' | grep -v -e ^FStar -e ^Hacl
-elif uname | grep -F Linux >/dev/null; then
-    nm -og library/libmbed*.a | grep -v '^[^ ]*: *U \|^$\|^[^ ]*:$' | sed 's/^[^ ]* . //' | grep -v -e ^FStar -e ^Hacl
-fi | sort > exported-symbols
-make clean
-
-wc -l exported-symbols
diff --git a/tests/scripts/list_internal_identifiers.py b/tests/scripts/list_internal_identifiers.py
new file mode 100755
index 0000000..779a16f
--- /dev/null
+++ b/tests/scripts/list_internal_identifiers.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+#
+# Copyright The Mbed TLS Contributors
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+This script generates a file called identifiers that contains all Mbed TLS
+identifiers found on internal headers. This is the equivalent of what was
+previously `list-identifiers.sh --internal`, and is useful for generating an
+exclusion file list for ABI/API checking, since we do not promise compatibility
+for them.
+
+It uses the CodeParser class from check_names.py to perform the parsing.
+
+The script returns 0 on success, 1 if there is a script error.
+Must be run from Mbed TLS root.
+"""
+
+import argparse
+import logging
+from check_names import CodeParser
+
+def main():
+    parser = argparse.ArgumentParser(
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description=(
+            "This script writes a list of parsed identifiers in internal "
+            "headers to \"identifiers\". This is useful for generating a list "
+            "of names to exclude from API/ABI compatibility checking. "))
+
+    parser.parse_args()
+
+    name_check = CodeParser(logging.getLogger())
+    result = name_check.parse_identifiers([
+        "include/mbedtls/*_internal.h",
+        "library/*.h"
+    ])
+    result.sort(key=lambda x: x.name)
+
+    identifiers = ["{}\n".format(match.name) for match in result]
+    with open("identifiers", "w", encoding="utf-8") as f:
+        f.writelines(identifiers)
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/scripts/translate_ciphers.py b/tests/scripts/translate_ciphers.py
new file mode 100755
index 0000000..d5f847f
--- /dev/null
+++ b/tests/scripts/translate_ciphers.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+
+# translate_ciphers.py
+#
+# Copyright The Mbed TLS Contributors
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Translate ciphersuite names in Mbed TLS format to OpenSSL and GNUTLS
+standards.
+
+To test the translation functions run:
+python3 -m unittest translate_cipher.py
+"""
+
+import re
+import argparse
+import unittest
+
+class TestTranslateCiphers(unittest.TestCase):
+    """
+    Ensure translate_ciphers.py translates and formats ciphersuite names
+    correctly
+    """
+    def test_translate_all_cipher_names(self):
+        """
+        Translate MbedTLS ciphersuite names to their OpenSSL and
+        GnuTLS counterpart. Use only a small subset of ciphers
+        that exercise each step of the translate functions
+        """
+        ciphers = [
+            ("TLS-ECDHE-ECDSA-WITH-NULL-SHA",
+             "+ECDHE-ECDSA:+NULL:+SHA1",
+             "ECDHE-ECDSA-NULL-SHA"),
+            ("TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256",
+             "+ECDHE-ECDSA:+AES-128-GCM:+AEAD",
+             "ECDHE-ECDSA-AES128-GCM-SHA256"),
+            ("TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA",
+             "+DHE-RSA:+3DES-CBC:+SHA1",
+             "EDH-RSA-DES-CBC3-SHA"),
+            ("TLS-RSA-WITH-AES-256-CBC-SHA",
+             "+RSA:+AES-256-CBC:+SHA1",
+             "AES256-SHA"),
+            ("TLS-PSK-WITH-3DES-EDE-CBC-SHA",
+             "+PSK:+3DES-CBC:+SHA1",
+             "PSK-3DES-EDE-CBC-SHA"),
+            ("TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256",
+             None,
+             "ECDHE-ECDSA-CHACHA20-POLY1305"),
+            ("TLS-ECDHE-ECDSA-WITH-AES-128-CCM",
+             "+ECDHE-ECDSA:+AES-128-CCM:+AEAD",
+             None),
+            ("TLS-ECDHE-RSA-WITH-ARIA-256-GCM-SHA384",
+             None,
+             "ECDHE-ARIA256-GCM-SHA384"),
+        ]
+
+        for m, g_exp, o_exp in ciphers:
+
+            if g_exp is not None:
+                g = translate_gnutls(m)
+                self.assertEqual(g, g_exp)
+
+            if o_exp is not None:
+                o = translate_ossl(m)
+                self.assertEqual(o, o_exp)
+
+def translate_gnutls(m_cipher):
+    """
+    Translate m_cipher from Mbed TLS ciphersuite naming convention
+    and return the GnuTLS naming convention
+    """
+
+    m_cipher = re.sub(r'\ATLS-', '+', m_cipher)
+    m_cipher = m_cipher.replace("-WITH-", ":+")
+    m_cipher = m_cipher.replace("-EDE", "")
+
+    # SHA in Mbed TLS == SHA1 GnuTLS,
+    # if the last 3 chars are SHA append 1
+    if m_cipher[-3:] == "SHA":
+        m_cipher = m_cipher+"1"
+
+    # CCM or CCM-8 should be followed by ":+AEAD"
+    # Replace "GCM:+SHAxyz" with "GCM:+AEAD"
+    if "CCM" in m_cipher or "GCM" in m_cipher:
+        m_cipher = re.sub(r"GCM-SHA\d\d\d", "GCM", m_cipher)
+        m_cipher = m_cipher+":+AEAD"
+
+    # Replace the last "-" with ":+"
+    else:
+        index = m_cipher.rindex("-")
+        m_cipher = m_cipher[:index] + ":+" + m_cipher[index+1:]
+
+    return m_cipher
+
+def translate_ossl(m_cipher):
+    """
+    Translate m_cipher from Mbed TLS ciphersuite naming convention
+    and return the OpenSSL naming convention
+    """
+
+    m_cipher = re.sub(r'^TLS-', '', m_cipher)
+    m_cipher = m_cipher.replace("-WITH", "")
+
+    # Remove the "-" from "ABC-xyz"
+    m_cipher = m_cipher.replace("AES-", "AES")
+    m_cipher = m_cipher.replace("CAMELLIA-", "CAMELLIA")
+    m_cipher = m_cipher.replace("ARIA-", "ARIA")
+
+    # Remove "RSA" if it is at the beginning
+    m_cipher = re.sub(r'^RSA-', r'', m_cipher)
+
+    # For all circumstances outside of PSK
+    if "PSK" not in m_cipher:
+        m_cipher = m_cipher.replace("-EDE", "")
+        m_cipher = m_cipher.replace("3DES-CBC", "DES-CBC3")
+
+        # Remove "CBC" if it is not prefixed by DES
+        m_cipher = re.sub(r'(?<!DES-)CBC-', r'', m_cipher)
+
+    # ECDHE-RSA-ARIA does not exist in OpenSSL
+    m_cipher = m_cipher.replace("ECDHE-RSA-ARIA", "ECDHE-ARIA")
+
+    # POLY1305 should not be followed by anything
+    if "POLY1305" in m_cipher:
+        index = m_cipher.rindex("POLY1305")
+        m_cipher = m_cipher[:index+8]
+
+    # If DES is being used, Replace DHE with EDH
+    if "DES" in m_cipher and "DHE" in m_cipher and "ECDHE" not in m_cipher:
+        m_cipher = m_cipher.replace("DHE", "EDH")
+
+    return m_cipher
+
+def format_ciphersuite_names(mode, names):
+    t = {"g": translate_gnutls, "o": translate_ossl}[mode]
+    return " ".join(t(c) for c in names)
+
+def main(target, names):
+    print(format_ciphersuite_names(target, names))
+
+if __name__ == "__main__":
+    PARSER = argparse.ArgumentParser()
+    PARSER.add_argument('target', metavar='TARGET', choices=['o', 'g'])
+    PARSER.add_argument('names', metavar='NAMES', nargs='+')
+    ARGS = PARSER.parse_args()
+    main(ARGS.target, ARGS.names)
diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh
index 3e199e2..39499d4 100755
--- a/tests/ssl-opt.sh
+++ b/tests/ssl-opt.sh
@@ -1322,6 +1322,11 @@
     O_LEGACY_CLI="$O_LEGACY_CLI -connect localhost:+SRV_PORT"
 fi
 
+if [ -n "${OPENSSL_NEXT:-}" ]; then
+    O_NEXT_SRV="$O_NEXT_SRV -accept $SRV_PORT"
+    O_NEXT_CLI="$O_NEXT_CLI -connect localhost:+SRV_PORT"
+fi
+
 if [ -n "${GNUTLS_NEXT_SERV:-}" ]; then
     G_NEXT_SRV="$G_NEXT_SRV -p $SRV_PORT"
 fi
@@ -8661,6 +8666,24 @@
             -s "SSL - The requested feature is not available" \
             -c "SSL - The requested feature is not available"
 
+requires_openssl_tls1_3
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL
+run_test    "TLS1.3: Test client hello msg work - openssl" \
+            "$O_NEXT_SRV -tls1_3 -msg" \
+            "$P_CLI min_version=tls1_3 max_version=tls1_3" \
+            1 \
+            -c "SSL - The requested feature is not available" \
+            -s "ServerHello"
+
+requires_gnutls_tls1_3
+requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL
+run_test    "TLS1.3: Test client hello msg work - gnutls" \
+            "$G_NEXT_SRV --priority=NORMAL:-VERS-ALL:+VERS-TLS1.3 --debug=4" \
+            "$P_CLI min_version=tls1_3 max_version=tls1_3" \
+            1 \
+            -c "SSL - The requested feature is not available" \
+            -s "SERVER HELLO was queued"
+
 # Test heap memory usage after handshake
 requires_config_enabled MBEDTLS_MEMORY_DEBUG
 requires_config_enabled MBEDTLS_MEMORY_BUFFER_ALLOC_C
diff --git a/tests/suites/test_suite_net.function b/tests/suites/test_suite_net.function
index f429fc9..513b723 100644
--- a/tests/suites/test_suite_net.function
+++ b/tests/suites/test_suite_net.function
@@ -9,11 +9,11 @@
 #endif
 
 #if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE)
-#include <sys/fcntl.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/types.h>
+#include <fcntl.h>
 #include <unistd.h>
 #endif