Merge remote-tracking branch 'origin/pr/2821' into development

* origin/pr/2821:
  Update notification e-mail address
  Remove blocked branches
  Update Coverity secure token
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fb1de81..4350d4e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -51,19 +51,19 @@
                          "${CTR_DRBG_128_BIT_KEY_WARN_L3}"
                          "${WARNING_BORDER}")
 
-find_package(PythonInterp)
-find_package(Perl)
-if(PERL_FOUND)
+# Python 3 is only needed here to check for configuration warnings.
+find_package(PythonInterp 3)
+if(PYTHONINTERP_FOUND)
 
     # If 128-bit keys are configured for CTR_DRBG, display an appropriate warning
-    execute_process(COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/config.pl -f ${CMAKE_CURRENT_SOURCE_DIR}/include/mbedtls/config.h get MBEDTLS_CTR_DRBG_USE_128_BIT_KEY
+    execute_process(COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/config.py -f ${CMAKE_CURRENT_SOURCE_DIR}/include/mbedtls/config.h get MBEDTLS_CTR_DRBG_USE_128_BIT_KEY
                         RESULT_VARIABLE result)
     if(${result} EQUAL 0)
         message(WARNING ${CTR_DRBG_128_BIT_KEY_WARNING})
     endif()
 
     # If NULL Entropy is configured, display an appropriate warning
-    execute_process(COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/config.pl -f ${CMAKE_CURRENT_SOURCE_DIR}/include/mbedtls/config.h get MBEDTLS_TEST_NULL_ENTROPY
+    execute_process(COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/config.py -f ${CMAKE_CURRENT_SOURCE_DIR}/include/mbedtls/config.h get MBEDTLS_TEST_NULL_ENTROPY
                         RESULT_VARIABLE result)
     if(${result} EQUAL 0)
         message(WARNING ${NULL_ENTROPY_WARNING})
diff --git a/ChangeLog b/ChangeLog
index f16c97e..973f213 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,16 @@
 mbed TLS ChangeLog (Sorted per branch, date)
 
+= mbed TLS 2.19.1 branch released 2019-09-16
+
+Features
+   * Add nss_keylog to ssl_client2 and ssl_server2, enabling easier analysis of
+     TLS sessions with tools like Wireshark.
+
+API Changes
+   * Make client_random and server_random const in
+     mbedtls_ssl_export_keys_ext_t, so that the key exporter is discouraged
+     from modifying the client/server hello.
+
 = mbed TLS 2.19.0 branch released 2019-09-06
 
 Security
diff --git a/Makefile b/Makefile
index e898975..4f4e437 100644
--- a/Makefile
+++ b/Makefile
@@ -80,11 +80,11 @@
 ifndef WINDOWS
 
 	# If 128-bit keys are configured for CTR_DRBG, display an appropriate warning
-	-scripts/config.pl get MBEDTLS_CTR_DRBG_USE_128_BIT_KEY && ([ $$? -eq 0 ]) && \
+	-scripts/config.py get MBEDTLS_CTR_DRBG_USE_128_BIT_KEY && ([ $$? -eq 0 ]) && \
 	    echo '$(CTR_DRBG_128_BIT_KEY_WARNING)'
 
 	# If NULL Entropy is configured, display an appropriate warning
-	-scripts/config.pl get MBEDTLS_TEST_NULL_ENTROPY && ([ $$? -eq 0 ]) && \
+	-scripts/config.py get MBEDTLS_TEST_NULL_ENTROPY && ([ $$? -eq 0 ]) && \
 	    echo '$(NULL_ENTROPY_WARNING)'
 endif
 
diff --git a/README.md b/README.md
index 83b3234..43065b1 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 Configuration
 -------------
 
-Mbed TLS should build out of the box on most systems. Some platform specific options are available in the fully documented configuration file `include/mbedtls/config.h`, which is also the place where features can be selected. This file can be edited manually, or in a more programmatic way using the Perl script `scripts/config.pl` (use `--help` for usage instructions).
+Mbed TLS should build out of the box on most systems. Some platform specific options are available in the fully documented configuration file `include/mbedtls/config.h`, which is also the place where features can be selected. This file can be edited manually, or in a more programmatic way using the Perl script `scripts/config.py` (use `--help` for usage instructions).
 
 Compiler options can be set using conventional environment variables such as `CC` and `CFLAGS` when using the Make and CMake build system (see below).
 
diff --git a/crypto b/crypto
index 92348d1..3f20efc 160000
--- a/crypto
+++ b/crypto
@@ -1 +1 @@
-Subproject commit 92348d1c4931f8c33c2d092928afca556f672c42
+Subproject commit 3f20efc03016b38f2677dadd476b21229c627c80
diff --git a/doxygen/input/doc_mainpage.h b/doxygen/input/doc_mainpage.h
index 1661a6f..d5ead37 100644
--- a/doxygen/input/doc_mainpage.h
+++ b/doxygen/input/doc_mainpage.h
@@ -24,7 +24,7 @@
  */
 
 /**
- * @mainpage mbed TLS v2.19.0 source code documentation
+ * @mainpage mbed TLS v2.19.1 source code documentation
  *
  * This documentation describes the internal structure of mbed TLS.  It was
  * automatically generated from specially formatted comment blocks in
diff --git a/doxygen/mbedtls.doxyfile b/doxygen/mbedtls.doxyfile
index 7604c11..eb2d96e 100644
--- a/doxygen/mbedtls.doxyfile
+++ b/doxygen/mbedtls.doxyfile
@@ -28,7 +28,7 @@
 # identify the project. Note that if you do not use Doxywizard you need
 # to put quotes around the project name if it contains spaces.
 
-PROJECT_NAME           = "mbed TLS v2.19.0"
+PROJECT_NAME           = "mbed TLS v2.19.1"
 
 # The PROJECT_NUMBER tag can be used to enter a project or revision number.
 # This could be handy for archiving the generated documentation or
diff --git a/include/mbedtls/check_config.h b/include/mbedtls/check_config.h
index f500bf6..6ebe6b7 100644
--- a/include/mbedtls/check_config.h
+++ b/include/mbedtls/check_config.h
@@ -45,7 +45,7 @@
 #endif
 
 /* Fix the config here. Not convenient to put an #ifdef _WIN32 in config.h as
- * it would confuse config.pl. */
+ * it would confuse config.py. */
 #if !defined(MBEDTLS_PLATFORM_SNPRINTF_ALT) && \
     !defined(MBEDTLS_PLATFORM_SNPRINTF_MACRO)
 #define MBEDTLS_PLATFORM_SNPRINTF_ALT
@@ -305,6 +305,14 @@
 #error "MBEDTLS_MEMORY_BUFFER_ALLOC_C defined, but not all prerequisites"
 #endif
 
+#if defined(MBEDTLS_MEMORY_BACKTRACE) && !defined(MBEDTLS_MEMORY_BUFFER_ALLOC_C)
+#error "MBEDTLS_MEMORY_BACKTRACE defined, but not all prerequesites"
+#endif
+
+#if defined(MBEDTLS_MEMORY_DEBUG) && !defined(MBEDTLS_MEMORY_BUFFER_ALLOC_C)
+#error "MBEDTLS_MEMORY_DEBUG defined, but not all prerequesites"
+#endif
+
 #if defined(MBEDTLS_PADLOCK_C) && !defined(MBEDTLS_HAVE_ASM)
 #error "MBEDTLS_PADLOCK_C defined, but not all prerequisites"
 #endif
diff --git a/include/mbedtls/config.h b/include/mbedtls/config.h
index 6348735..058969b 100644
--- a/include/mbedtls/config.h
+++ b/include/mbedtls/config.h
@@ -3518,7 +3518,7 @@
  *            on it, and considering stronger message digests instead.
  *
  */
-// #define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_CERTIFICATES
+//#define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_CERTIFICATES
 
 /**
  * Allow SHA-1 in the default TLS configuration for TLS 1.2 handshake
diff --git a/include/mbedtls/ssl.h b/include/mbedtls/ssl.h
index 458857f..655f59d 100644
--- a/include/mbedtls/ssl.h
+++ b/include/mbedtls/ssl.h
@@ -970,7 +970,8 @@
      *  tls_prf and random bytes. Should replace f_export_keys    */
     int (*f_export_keys_ext)( void *, const unsigned char *,
                 const unsigned char *, size_t, size_t, size_t,
-                unsigned char[32], unsigned char[32], mbedtls_tls_prf_types );
+                const unsigned char[32], const unsigned char[32],
+                mbedtls_tls_prf_types );
     void *p_export_keys;            /*!< context for key export callback    */
 #endif
 
@@ -1925,8 +1926,8 @@
                                            size_t maclen,
                                            size_t keylen,
                                            size_t ivlen,
-                                           unsigned char client_random[32],
-                                           unsigned char server_random[32],
+                                           const unsigned char client_random[32],
+                                           const unsigned char server_random[32],
                                            mbedtls_tls_prf_types tls_prf_type );
 #endif /* MBEDTLS_SSL_EXPORT_KEYS */
 
diff --git a/include/mbedtls/version.h b/include/mbedtls/version.h
index f78e40a..ae694ee 100644
--- a/include/mbedtls/version.h
+++ b/include/mbedtls/version.h
@@ -40,16 +40,16 @@
  */
 #define MBEDTLS_VERSION_MAJOR  2
 #define MBEDTLS_VERSION_MINOR  19
-#define MBEDTLS_VERSION_PATCH  0
+#define MBEDTLS_VERSION_PATCH  1
 
 /**
  * The single version number has the following structure:
  *    MMNNPP00
  *    Major version | Minor version | Patch version
  */
-#define MBEDTLS_VERSION_NUMBER         0x02130000
-#define MBEDTLS_VERSION_STRING         "2.19.0"
-#define MBEDTLS_VERSION_STRING_FULL    "mbed TLS 2.19.0"
+#define MBEDTLS_VERSION_NUMBER         0x02130100
+#define MBEDTLS_VERSION_STRING         "2.19.1"
+#define MBEDTLS_VERSION_STRING_FULL    "mbed TLS 2.19.1"
 
 #if defined(MBEDTLS_VERSION_C)
 
diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt
index 774ef7d..5e36a5b 100644
--- a/library/CMakeLists.txt
+++ b/library/CMakeLists.txt
@@ -174,14 +174,14 @@
 if(USE_SHARED_MBEDTLS_LIBRARY)
 
     add_library(mbedx509 SHARED ${src_x509})
-    set_target_properties(mbedx509 PROPERTIES VERSION 2.19.0 SOVERSION 1)
+    set_target_properties(mbedx509 PROPERTIES VERSION 2.19.1 SOVERSION 1)
     target_link_libraries(mbedx509 ${libs} mbedcrypto)
     target_include_directories(mbedx509
         PUBLIC ${MBEDTLS_DIR}/include/
         PUBLIC ${MBEDTLS_DIR}/crypto/include/)
 
     add_library(mbedtls SHARED ${src_tls})
-    set_target_properties(mbedtls PROPERTIES VERSION 2.19.0 SOVERSION 13)
+    set_target_properties(mbedtls PROPERTIES VERSION 2.19.1 SOVERSION 13)
     target_link_libraries(mbedtls ${libs} mbedx509)
     target_include_directories(mbedtls
         PUBLIC ${MBEDTLS_DIR}/include/
diff --git a/library/ssl_tls.c b/library/ssl_tls.c
index f4bca87..a7facb8 100644
--- a/library/ssl_tls.c
+++ b/library/ssl_tls.c
@@ -1427,9 +1427,8 @@
                                       master, keyblk,
                                       mac_key_len, keylen,
                                       iv_copy_len,
-                                      /* work around bug in exporter type */
-                                      (unsigned char *) randbytes + 32,
-                                      (unsigned char *) randbytes,
+                                      randbytes + 32,
+                                      randbytes,
                                       tls_prf_get_type( tls_prf ) );
     }
 #endif
diff --git a/programs/fuzz/README.md b/programs/fuzz/README.md
index 8b24908..b6a4333 100644
--- a/programs/fuzz/README.md
+++ b/programs/fuzz/README.md
@@ -26,7 +26,7 @@
 To run the fuzz targets without oss-fuzz, you first need to install one libFuzzingEngine (libFuzzer for instance).
 Then you need to compile the code with the compiler flags of the wished sanitizer.
 ```
-perl scripts/config.pl set MBEDTLS_PLATFORM_TIME_ALT
+perl scripts/config.py set MBEDTLS_PLATFORM_TIME_ALT
 mkdir build
 cd build
 cmake ..
diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c
index 61b88d1..558fa28 100644
--- a/programs/ssl/ssl_client2.c
+++ b/programs/ssl/ssl_client2.c
@@ -135,6 +135,8 @@
 #define DFL_CA_CALLBACK         0
 #define DFL_EAP_TLS             0
 #define DFL_REPRODUCIBLE        0
+#define DFL_NSS_KEYLOG          0
+#define DFL_NSS_KEYLOG_FILE     NULL
 
 #define GET_REQUEST "GET %s HTTP/1.0\r\nExtra-header: "
 #define GET_REQUEST_END "\r\n\r\n"
@@ -231,8 +233,15 @@
 #if defined(MBEDTLS_SSL_EXPORT_KEYS)
 #define USAGE_EAP_TLS                                       \
     "    eap_tls=%%d          default: 0 (disabled)\n"
+#define USAGE_NSS_KEYLOG                                    \
+    "    nss_keylog=%%d          default: 0 (disabled)\n"               \
+    "                             This cannot be used with eap_tls=1\n"
+#define USAGE_NSS_KEYLOG_FILE                               \
+    "    nss_keylog_file=%%s\n"
 #else
 #define USAGE_EAP_TLS ""
+#define USAGE_NSS_KEYLOG ""
+#define USAGE_NSS_KEYLOG_FILE ""
 #endif /* MBEDTLS_SSL_EXPORT_KEYS */
 
 #if defined(MBEDTLS_SSL_TRUNCATED_HMAC)
@@ -489,6 +498,8 @@
     int etm;                    /* negotiate encrypt then mac?              */
     int context_crt_cb;         /* use context-specific CRT verify callback */
     int eap_tls;                /* derive EAP-TLS keying material?          */
+    int nss_keylog;             /* export NSS key log material              */
+    const char *nss_keylog_file; /* NSS key log file                        */
     int cid_enabled;            /* whether to use the CID extension or not  */
     int cid_enabled_renego;     /* whether to use the CID extension or not
                                  * during renegotiation                     */
@@ -515,8 +526,8 @@
                                     size_t maclen,
                                     size_t keylen,
                                     size_t ivlen,
-                                    unsigned char client_random[32],
-                                    unsigned char server_random[32],
+                                    const unsigned char client_random[32],
+                                    const unsigned char server_random[32],
                                     mbedtls_tls_prf_types tls_prf_type )
 {
     eap_tls_keys *keys = (eap_tls_keys *)p_expkey;
@@ -535,6 +546,81 @@
     }
     return( 0 );
 }
+
+static int nss_keylog_export( void *p_expkey,
+                              const unsigned char *ms,
+                              const unsigned char *kb,
+                              size_t maclen,
+                              size_t keylen,
+                              size_t ivlen,
+                              const unsigned char client_random[32],
+                              const unsigned char server_random[32],
+                              mbedtls_tls_prf_types tls_prf_type )
+{
+    char nss_keylog_line[ 200 ];
+    size_t const client_random_len = 32;
+    size_t const master_secret_len = 48;
+    size_t len = 0;
+    size_t j;
+    int ret = 0;
+
+    ((void) p_expkey);
+    ((void) kb);
+    ((void) maclen);
+    ((void) keylen);
+    ((void) ivlen);
+    ((void) server_random);
+    ((void) tls_prf_type);
+
+    len += sprintf( nss_keylog_line + len,
+                    "%s", "CLIENT_RANDOM " );
+
+    for( j = 0; j < client_random_len; j++ )
+    {
+        len += sprintf( nss_keylog_line + len,
+                        "%02x", client_random[j] );
+    }
+
+    len += sprintf( nss_keylog_line + len, " " );
+
+    for( j = 0; j < master_secret_len; j++ )
+    {
+        len += sprintf( nss_keylog_line + len,
+                        "%02x", ms[j] );
+    }
+
+    len += sprintf( nss_keylog_line + len, "\n" );
+    nss_keylog_line[ len ] = '\0';
+
+    mbedtls_printf( "\n" );
+    mbedtls_printf( "---------------- NSS KEYLOG -----------------\n" );
+    mbedtls_printf( "%s", nss_keylog_line );
+    mbedtls_printf( "---------------------------------------------\n" );
+
+    if( opt.nss_keylog_file != NULL )
+    {
+        FILE *f;
+
+        if( ( f = fopen( opt.nss_keylog_file, "a" ) ) == NULL )
+        {
+            ret = -1;
+            goto exit;
+        }
+
+        if( fwrite( nss_keylog_line, 1, len, f ) != len )
+        {
+            ret = -1;
+            goto exit;
+        }
+
+        fclose( f );
+    }
+
+exit:
+    mbedtls_platform_zeroize( nss_keylog_line,
+                              sizeof( nss_keylog_line ) );
+    return( ret );
+}
 #endif
 
 static void my_debug( void *ctx, int level,
@@ -1204,6 +1290,8 @@
     opt.serialize           = DFL_SERIALIZE;
     opt.eap_tls             = DFL_EAP_TLS;
     opt.reproducible        = DFL_REPRODUCIBLE;
+    opt.nss_keylog          = DFL_NSS_KEYLOG;
+    opt.nss_keylog_file     = DFL_NSS_KEYLOG_FILE;
 
     for( i = 1; i < argc; i++ )
     {
@@ -1606,10 +1694,26 @@
         {
             opt.reproducible = 1;
         }
+        else if( strcmp( p, "nss_keylog" ) == 0 )
+        {
+            opt.nss_keylog = atoi( q );
+            if( opt.nss_keylog < 0 || opt.nss_keylog > 1 )
+                goto usage;
+        }
+        else if( strcmp( p, "nss_keylog_file" ) == 0 )
+        {
+            opt.nss_keylog_file = q;
+        }
         else
             goto usage;
     }
 
+    if( opt.nss_keylog != 0 && opt.eap_tls != 0 )
+    {
+        mbedtls_printf( "Error: eap_tls and nss_keylog options cannot be used together.\n" );
+        goto usage;
+    }
+
     /* Event-driven IO is incompatible with the above custom
      * receive and send functions, as the polling builds on
      * refers to the underlying net_context. */
@@ -2145,8 +2249,16 @@
 
 #if defined(MBEDTLS_SSL_EXPORT_KEYS)
     if( opt.eap_tls != 0 )
+    {
         mbedtls_ssl_conf_export_keys_ext_cb( &conf, eap_tls_key_derivation,
                                              &eap_tls_keying );
+    }
+    else if( opt.nss_keylog != 0 )
+    {
+        mbedtls_ssl_conf_export_keys_ext_cb( &conf,
+                                             nss_keylog_export,
+                                             NULL );
+    }
 #endif
 
 #if defined(MBEDTLS_SSL_CBC_RECORD_SPLITTING)
diff --git a/programs/ssl/ssl_server2.c b/programs/ssl/ssl_server2.c
index 102951b..e27bbc6 100644
--- a/programs/ssl/ssl_server2.c
+++ b/programs/ssl/ssl_server2.c
@@ -176,6 +176,8 @@
 #define DFL_CA_CALLBACK         0
 #define DFL_EAP_TLS             0
 #define DFL_REPRODUCIBLE        0
+#define DFL_NSS_KEYLOG          0
+#define DFL_NSS_KEYLOG_FILE     NULL
 
 #define LONG_RESPONSE "<p>01-blah-blah-blah-blah-blah-blah-blah-blah-blah\r\n" \
     "02-blah-blah-blah-blah-blah-blah-blah-blah-blah-blah-blah-blah-blah\r\n"  \
@@ -308,8 +310,15 @@
 #if defined(MBEDTLS_SSL_EXPORT_KEYS)
 #define USAGE_EAP_TLS                                       \
     "    eap_tls=%%d          default: 0 (disabled)\n"
+#define USAGE_NSS_KEYLOG                                    \
+    "    nss_keylog=%%d          default: 0 (disabled)\n"   \
+    "                             This cannot be used with eap_tls=1\n"
+#define USAGE_NSS_KEYLOG_FILE                               \
+    "    nss_keylog_file=%%s\n"
 #else
 #define USAGE_EAP_TLS ""
+#define USAGE_NSS_KEYLOG ""
+#define USAGE_NSS_KEYLOG_FILE ""
 #endif /* MBEDTLS_SSL_EXPORT_KEYS */
 
 #if defined(MBEDTLS_SSL_CACHE_C)
@@ -487,6 +496,8 @@
     USAGE_TICKETS                                           \
     USAGE_EAP_TLS                                           \
     USAGE_REPRODUCIBLE                                      \
+    USAGE_NSS_KEYLOG                                        \
+    USAGE_NSS_KEYLOG_FILE                                   \
     USAGE_CACHE                                             \
     USAGE_MAX_FRAG_LEN                                      \
     USAGE_TRUNC_HMAC                                        \
@@ -598,6 +609,8 @@
     int dgram_packing;          /* allow/forbid datagram packing            */
     int badmac_limit;           /* Limit of records with bad MAC            */
     int eap_tls;                /* derive EAP-TLS keying material?          */
+    int nss_keylog;             /* export NSS key log material              */
+    const char *nss_keylog_file; /* NSS key log file                        */
     int cid_enabled;            /* whether to use the CID extension or not  */
     int cid_enabled_renego;     /* whether to use the CID extension or not
                                  * during renegotiation                     */
@@ -624,8 +637,8 @@
                                     size_t maclen,
                                     size_t keylen,
                                     size_t ivlen,
-                                    unsigned char client_random[32],
-                                    unsigned char server_random[32],
+                                    const unsigned char client_random[32],
+                                    const unsigned char server_random[32],
                                     mbedtls_tls_prf_types tls_prf_type )
 {
     eap_tls_keys *keys = (eap_tls_keys *)p_expkey;
@@ -644,6 +657,82 @@
     }
     return( 0 );
 }
+
+static int nss_keylog_export( void *p_expkey,
+                              const unsigned char *ms,
+                              const unsigned char *kb,
+                              size_t maclen,
+                              size_t keylen,
+                              size_t ivlen,
+                              const unsigned char client_random[32],
+                              const unsigned char server_random[32],
+                              mbedtls_tls_prf_types tls_prf_type )
+{
+    char nss_keylog_line[ 200 ];
+    size_t const client_random_len = 32;
+    size_t const master_secret_len = 48;
+    size_t len = 0;
+    size_t j;
+    int ret = 0;
+
+    ((void) p_expkey);
+    ((void) kb);
+    ((void) maclen);
+    ((void) keylen);
+    ((void) ivlen);
+    ((void) server_random);
+    ((void) tls_prf_type);
+
+    len += sprintf( nss_keylog_line + len,
+                    "%s", "CLIENT_RANDOM " );
+
+    for( j = 0; j < client_random_len; j++ )
+    {
+        len += sprintf( nss_keylog_line + len,
+                        "%02x", client_random[j] );
+    }
+
+    len += sprintf( nss_keylog_line + len, " " );
+
+    for( j = 0; j < master_secret_len; j++ )
+    {
+        len += sprintf( nss_keylog_line + len,
+                        "%02x", ms[j] );
+    }
+
+    len += sprintf( nss_keylog_line + len, "\n" );
+    nss_keylog_line[ len ] = '\0';
+
+    mbedtls_printf( "\n" );
+    mbedtls_printf( "---------------- NSS KEYLOG -----------------\n" );
+    mbedtls_printf( "%s", nss_keylog_line );
+    mbedtls_printf( "---------------------------------------------\n" );
+
+    if( opt.nss_keylog_file != NULL )
+    {
+        FILE *f;
+
+        if( ( f = fopen( opt.nss_keylog_file, "a" ) ) == NULL )
+        {
+            ret = -1;
+            goto exit;
+        }
+
+        if( fwrite( nss_keylog_line, 1, len, f ) != len )
+        {
+            ret = -1;
+            goto exit;
+        }
+
+        fclose( f );
+    }
+
+exit:
+    mbedtls_platform_zeroize( nss_keylog_line,
+                              sizeof( nss_keylog_line ) );
+    return( ret );
+}
+
 #endif
 
 static void my_debug( void *ctx, int level,
@@ -1892,6 +1981,8 @@
     opt.serialize           = DFL_SERIALIZE;
     opt.eap_tls             = DFL_EAP_TLS;
     opt.reproducible        = DFL_REPRODUCIBLE;
+    opt.nss_keylog          = DFL_NSS_KEYLOG;
+    opt.nss_keylog_file     = DFL_NSS_KEYLOG_FILE;
 
     for( i = 1; i < argc; i++ )
     {
@@ -2320,10 +2411,26 @@
         {
             opt.reproducible = 1;
         }
+        else if( strcmp( p, "nss_keylog" ) == 0 )
+        {
+            opt.nss_keylog = atoi( q );
+            if( opt.nss_keylog < 0 || opt.nss_keylog > 1 )
+                goto usage;
+        }
+        else if( strcmp( p, "nss_keylog_file" ) == 0 )
+        {
+            opt.nss_keylog_file = q;
+        }
         else
             goto usage;
     }
 
+    if( opt.nss_keylog != 0 && opt.eap_tls != 0 )
+    {
+        mbedtls_printf( "Error: eap_tls and nss_keylog options cannot be used together.\n" );
+        goto usage;
+    }
+
     /* Event-driven IO is incompatible with the above custom
      * receive and send functions, as the polling builds on
      * refers to the underlying net_context. */
@@ -2960,8 +3067,16 @@
 
 #if defined(MBEDTLS_SSL_EXPORT_KEYS)
     if( opt.eap_tls != 0 )
+    {
         mbedtls_ssl_conf_export_keys_ext_cb( &conf, eap_tls_key_derivation,
                                              &eap_tls_keying );
+    }
+    else if( opt.nss_keylog != 0 )
+    {
+        mbedtls_ssl_conf_export_keys_ext_cb( &conf,
+                                             nss_keylog_export,
+                                             NULL );
+    }
 #endif
 
 #if defined(MBEDTLS_SSL_ALPN)
diff --git a/scripts/apidoc_full.sh b/scripts/apidoc_full.sh
index bebab10..dfe1177 100755
--- a/scripts/apidoc_full.sh
+++ b/scripts/apidoc_full.sh
@@ -19,7 +19,7 @@
 CONFIG_BAK=${CONFIG_H}.bak
 cp -p $CONFIG_H $CONFIG_BAK
 
-scripts/config.pl realfull
+scripts/config.py realfull
 make apidoc
 
 mv $CONFIG_BAK $CONFIG_H
diff --git a/scripts/config.pl b/scripts/config.pl
index 3942584..95e3191 100755
--- a/scripts/config.pl
+++ b/scripts/config.pl
@@ -1,308 +1,27 @@
 #!/usr/bin/env perl
-#
-# This file is part of mbed TLS (https://tls.mbed.org)
-#
-# Copyright (c) 2014-2016, ARM Limited, All Rights Reserved
-#
-# Purpose
-#
-# Comments and uncomments #define lines in the given header file and optionally
-# sets their value or can get the value. This is to provide scripting control of
-# what preprocessor symbols, and therefore what build time configuration flags
-# are set in the 'config.h' file.
-#
-# Usage: config.pl [-f <file> | --file <file>] [-o | --force]
-#                   [set <symbol> <value> | unset <symbol> | get <symbol> |
-#                       full | realfull]
-#
-# Full usage description provided below.
-#
-# The following options are disabled instead of enabled with "full".
-#
-#   MBEDTLS_TEST_NULL_ENTROPY
-#   MBEDTLS_DEPRECATED_REMOVED
-#   MBEDTLS_HAVE_SSE2
-#   MBEDTLS_PLATFORM_NO_STD_FUNCTIONS
-#   MBEDTLS_ECP_DP_M221_ENABLED
-#   MBEDTLS_ECP_DP_M383_ENABLED
-#   MBEDTLS_ECP_DP_M511_ENABLED
-#   MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES
-#   MBEDTLS_NO_PLATFORM_ENTROPY
-#   MBEDTLS_REMOVE_ARC4_CIPHERSUITES
-#   MBEDTLS_REMOVE_3DES_CIPHERSUITES
-#   MBEDTLS_SSL_HW_RECORD_ACCEL
-#   MBEDTLS_RSA_NO_CRT
-#   MBEDTLS_X509_ALLOW_EXTENSIONS_NON_V3
-#   MBEDTLS_X509_ALLOW_UNSUPPORTED_CRITICAL_EXTENSION
-#       - this could be enabled if the respective tests were adapted
-#   MBEDTLS_ZLIB_SUPPORT
-#   MBEDTLS_PKCS11_C
-#   MBEDTLS_PSA_CRYPTO_SPM
-#   MBEDTLS_PSA_INJECT_ENTROPY
-#   MBEDTLS_ECP_RESTARTABLE
-#   and any symbol beginning _ALT
-#
+# Backward compatibility redirection
 
-use warnings;
-use strict;
+## Copyright (C) 2019, ARM Limited, All Rights Reserved
+## 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 file is part of Mbed TLS (https://tls.mbed.org)
 
-my $config_file = "include/mbedtls/config.h";
-my $usage = <<EOU;
-$0 [-f <file> | --file <file>] [-o | --force]
-                   [set <symbol> <value> | unset <symbol> | get <symbol> |
-                        full | realfull | baremetal]
-
-Commands
-    set <symbol> [<value>]  - Uncomments or adds a #define for the <symbol> to
-                              the configuration file, and optionally making it
-                              of <value>.
-                              If the symbol isn't present in the file an error
-                              is returned.
-    unset <symbol>          - Comments out the #define for the given symbol if
-                              present in the configuration file.
-    get <symbol>            - Finds the #define for the given symbol, returning
-                              an exitcode of 0 if the symbol is found, and 1 if
-                              not. The value of the symbol is output if one is
-                              specified in the configuration file.
-    full                    - Uncomments all #define's in the configuration file
-                              excluding some reserved symbols, until the
-                              'Module configuration options' section
-    realfull                - Uncomments all #define's with no exclusions
-    baremetal               - Sets full configuration suitable for baremetal build.
-
-Options
-    -f | --file <filename>  - The file or file path for the configuration file
-                              to edit. When omitted, the following default is
-                              used:
-                                $config_file
-    -o | --force            - If the symbol isn't present in the configuration
-                              file when setting its value, a #define is
-                              appended to the end of the file.
-
-EOU
-
-my @excluded = qw(
-MBEDTLS_TEST_NULL_ENTROPY
-MBEDTLS_DEPRECATED_REMOVED
-MBEDTLS_HAVE_SSE2
-MBEDTLS_PLATFORM_NO_STD_FUNCTIONS
-MBEDTLS_ECP_DP_M221_ENABLED
-MBEDTLS_ECP_DP_M383_ENABLED
-MBEDTLS_ECP_DP_M511_ENABLED
-MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES
-MBEDTLS_NO_PLATFORM_ENTROPY
-MBEDTLS_RSA_NO_CRT
-MBEDTLS_REMOVE_ARC4_CIPHERSUITES
-MBEDTLS_REMOVE_3DES_CIPHERSUITES
-MBEDTLS_SSL_HW_RECORD_ACCEL
-MBEDTLS_X509_ALLOW_EXTENSIONS_NON_V3
-MBEDTLS_X509_ALLOW_UNSUPPORTED_CRITICAL_EXTENSION
-MBEDTLS_ZLIB_SUPPORT
-MBEDTLS_PKCS11_C
-MBEDTLS_NO_UDBL_DIVISION
-MBEDTLS_NO_64BIT_MULTIPLICATION
-MBEDTLS_PSA_CRYPTO_SPM
-MBEDTLS_PSA_INJECT_ENTROPY
-MBEDTLS_ECP_RESTARTABLE
-MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED
-_ALT\s*$
-);
-
-# Things that should be disabled in "baremetal"
-my @excluded_baremetal = qw(
-MBEDTLS_NET_C
-MBEDTLS_TIMING_C
-MBEDTLS_FS_IO
-MBEDTLS_ENTROPY_NV_SEED
-MBEDTLS_HAVE_TIME
-MBEDTLS_HAVE_TIME_DATE
-MBEDTLS_DEPRECATED_WARNING
-MBEDTLS_HAVEGE_C
-MBEDTLS_THREADING_C
-MBEDTLS_THREADING_PTHREAD
-MBEDTLS_MEMORY_BACKTRACE
-MBEDTLS_MEMORY_BUFFER_ALLOC_C
-MBEDTLS_PLATFORM_TIME_ALT
-MBEDTLS_PLATFORM_FPRINTF_ALT
-MBEDTLS_PSA_ITS_FILE_C
-MBEDTLS_PSA_CRYPTO_STORAGE_C
-);
-
-# Things that should be enabled in "full" even if they match @excluded
-my @non_excluded = qw(
-PLATFORM_[A-Z0-9]+_ALT
-);
-
-# Things that should be enabled in "baremetal"
-my @non_excluded_baremetal = qw(
-MBEDTLS_NO_PLATFORM_ENTROPY
-);
-
-# Process the command line arguments
-
-my $force_option = 0;
-
-my ($arg, $name, $value, $action);
-
-while ($arg = shift) {
-
-    # Check if the argument is an option
-    if ($arg eq "-f" || $arg eq "--file") {
-        $config_file = shift;
-
-        -f $config_file or die "No such file: $config_file\n";
-
-    }
-    elsif ($arg eq "-o" || $arg eq "--force") {
-        $force_option = 1;
-
-    }
-    else
-    {
-        # ...else assume it's a command
-        $action = $arg;
-
-        if ($action eq "full" || $action eq "realfull" || $action eq "baremetal" ) {
-            # No additional parameters
-            die $usage if @ARGV;
-
-        }
-        elsif ($action eq "unset" || $action eq "get") {
-            die $usage unless @ARGV;
-            $name = shift;
-
-        }
-        elsif ($action eq "set") {
-            die $usage unless @ARGV;
-            $name = shift;
-            $value = shift if @ARGV;
-
-        }
-        else {
-            die "Command '$action' not recognised.\n\n".$usage;
-        }
-    }
-}
-
-# If no command was specified, exit...
-if ( not defined($action) ){ die $usage; }
-
-# Check the config file is present
-if (! -f $config_file)  {
-
-    chdir '..' or die;
-
-    # Confirm this is the project root directory and try again
-    if ( !(-d 'scripts' && -d 'include' && -d 'library' && -f $config_file) ) {
-        die "If no file specified, must be run from the project root or scripts directory.\n";
-    }
-}
-
-
-# Now read the file and process the contents
-
-open my $config_read, '<', $config_file or die "read $config_file: $!\n";
-my @config_lines = <$config_read>;
-close $config_read;
-
-# Add required baremetal symbols to the list that is included.
-if ( $action eq "baremetal" ) {
-    @non_excluded = ( @non_excluded, @non_excluded_baremetal );
-}
-
-my ($exclude_re, $no_exclude_re, $exclude_baremetal_re);
-if ($action eq "realfull") {
-    $exclude_re = qr/^$/;
-    $no_exclude_re = qr/./;
-} else {
-    $exclude_re = join '|', @excluded;
-    $no_exclude_re = join '|', @non_excluded;
-}
-if ( $action eq "baremetal" ) {
-    $exclude_baremetal_re = join '|', @excluded_baremetal;
-}
-
-my $config_write = undef;
-if ($action ne "get") {
-    open $config_write, '>', $config_file or die "write $config_file: $!\n";
-}
-
-my $done;
-for my $line (@config_lines) {
-    if ($action eq "full" || $action eq "realfull" || $action eq "baremetal" ) {
-        if ($line =~ /name SECTION: Module configuration options/) {
-            $done = 1;
-        }
-
-        if (!$done && $line =~ m!^//\s?#define! &&
-                ( $line !~ /$exclude_re/ || $line =~ /$no_exclude_re/ ) &&
-                ( $action ne "baremetal" || ( $line !~ /$exclude_baremetal_re/ ) ) ) {
-            $line =~ s!^//\s?!!;
-        }
-        if (!$done && $line =~ m!^\s?#define! &&
-                ! ( ( $line !~ /$exclude_re/ || $line =~ /$no_exclude_re/ ) &&
-                    ( $action ne "baremetal" || ( $line !~ /$exclude_baremetal_re/ ) ) ) ) {
-            $line =~ s!^!//!;
-        }
-    } elsif ($action eq "unset") {
-        if (!$done && $line =~ /^\s*#define\s*$name\b/) {
-            $line = '//' . $line;
-            $done = 1;
-        }
-    } elsif (!$done && $action eq "set") {
-        if ($line =~ m!^(?://)?\s*#define\s*$name\b!) {
-            $line = "#define $name";
-            $line .= " $value" if defined $value && $value ne "";
-            $line .= "\n";
-            $done = 1;
-        }
-    } elsif (!$done && $action eq "get") {
-        if ($line =~ /^\s*#define\s*$name(?:\s+(.*?))\s*(?:$|\/\*|\/\/)/) {
-            $value = $1;
-            $done = 1;
-        }
-    }
-
-    if (defined $config_write) {
-        print $config_write $line or die "write $config_file: $!\n";
-    }
-}
-
-# Did the set command work?
-if ($action eq "set" && $force_option && !$done) {
-
-    # If the force option was set, append the symbol to the end of the file
-    my $line = "#define $name";
-    $line .= " $value" if defined $value && $value ne "";
-    $line .= "\n";
-    $done = 1;
-
-    print $config_write $line or die "write $config_file: $!\n";
-}
-
-if (defined $config_write) {
-    close $config_write or die "close $config_file: $!\n";
-}
-
-if ($action eq "get") {
-    if ($done) {
-        if ($value ne '') {
-            print "$value\n";
-        }
-        exit 0;
-    } else {
-        # If the symbol was not found, return an error
-        exit 1;
-    }
-}
-
-if ($action eq "full" && !$done) {
-    die "Configuration section was not found in $config_file\n";
-
-}
-
-if ($action ne "full" && $action ne "unset" && !$done) {
-    die "A #define for the symbol $name was not found in $config_file\n";
-}
-
-__END__
+my $py = $0;
+$py =~ s/\.pl$/.py/;
+exec 'python3', $py, @ARGV;
+print STDERR "$0: python3: $!\n";
+exec 'python', $py, @ARGV;
+print STDERR "$0: python: $!\n";
+exit 127;
diff --git a/scripts/config.py b/scripts/config.py
new file mode 100755
index 0000000..e01b9d5
--- /dev/null
+++ b/scripts/config.py
@@ -0,0 +1,426 @@
+#!/usr/bin/env python3
+
+"""Mbed TLS configuration file manipulation library and tool
+
+Basic usage, to read the Mbed TLS or Mbed Crypto configuration:
+    config = ConfigFile()
+    if 'MBEDTLS_RSA_C' in config: print('RSA is enabled')
+"""
+
+## Copyright (C) 2019, ARM Limited, All Rights Reserved
+## 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 file is part of Mbed TLS (https://tls.mbed.org)
+
+import os
+import re
+
+class Setting:
+    """Representation of one Mbed TLS config.h setting.
+
+    Fields:
+    * name: the symbol name ('MBEDTLS_xxx').
+    * value: the value of the macro. The empty string for a plain #define
+      with no value.
+    * active: True if name is defined, False if a #define for name is
+      present in config.h but commented out.
+    * section: the name of the section that contains this symbol.
+    """
+    # pylint: disable=too-few-public-methods
+    def __init__(self, active, name, value='', section=None):
+        self.active = active
+        self.name = name
+        self.value = value
+        self.section = section
+
+class Config:
+    """Representation of the Mbed TLS configuration.
+
+    In the documentation of this class, a symbol is said to be *active*
+    if there is a #define for it that is not commented out, and *known*
+    if there is a #define for it whether commented out or not.
+
+    This class supports the following protocols:
+    * `name in config` is `True` if the symbol `name` is active, `False`
+      otherwise (whether `name` is inactive or not known).
+    * `config[name]` is the value of the macro `name`. If `name` is inactive,
+      raise `KeyError` (even if `name` is known).
+    * `config[name] = value` sets the value associated to `name`. `name`
+      must be known, but does not need to be set. This does not cause
+      name to become set.
+    """
+
+    def __init__(self):
+        self.settings = {}
+
+    def __contains__(self, name):
+        """True if the given symbol is active (i.e. set).
+
+        False if the given symbol is not set, even if a definition
+        is present but commented out.
+        """
+        return name in self.settings and self.settings[name].active
+
+    def all(self, *names):
+        """True if all the elements of names are active (i.e. set)."""
+        return all(self.__contains__(name) for name in names)
+
+    def any(self, *names):
+        """True if at least one symbol in names are active (i.e. set)."""
+        return any(self.__contains__(name) for name in names)
+
+    def known(self, name):
+        """True if a #define for name is present, whether it's commented out or not."""
+        return name in self.settings
+
+    def __getitem__(self, name):
+        """Get the value of name, i.e. what the preprocessor symbol expands to.
+
+        If name is not known, raise KeyError. name does not need to be active.
+        """
+        return self.settings[name].value
+
+    def get(self, name, default=None):
+        """Get the value of name. If name is inactive (not set), return default.
+
+        If a #define for name is present and not commented out, return
+        its expansion, even if this is the empty string.
+
+        If a #define for name is present but commented out, return default.
+        """
+        if name in self.settings:
+            return self.settings[name].value
+        else:
+            return default
+
+    def __setitem__(self, name, value):
+        """If name is known, set its value.
+
+        If name is not known, raise KeyError.
+        """
+        self.settings[name].value = value
+
+    def set(self, name, value=None):
+        """Set name to the given value and make it active.
+
+        If value is None and name is already known, don't change its value.
+        If value is None and name is not known, set its value to the empty
+        string.
+        """
+        if name in self.settings:
+            if value is not None:
+                self.settings[name].value = value
+            self.settings[name].active = True
+        else:
+            self.settings[name] = Setting(True, name, value=value)
+
+    def unset(self, name):
+        """Make name unset (inactive).
+
+        name remains known if it was known before.
+        """
+        if name not in self.settings:
+            return
+        self.settings[name].active = False
+
+    def adapt(self, adapter):
+        """Run adapter on each known symbol and (de)activate it accordingly.
+
+        `adapter` must be a function that returns a boolean. It is called as
+        `adapter(name, active, section)` for each setting, where `active` is
+        `True` if `name` is set and `False` if `name` is known but unset,
+        and `section` is the name of the section containing `name`. If
+        `adapter` returns `True`, then set `name` (i.e. make it active),
+        otherwise unset `name` (i.e. make it known but inactive).
+        """
+        for setting in self.settings.values():
+            setting.active = adapter(setting.name, setting.active,
+                                     setting.section)
+
+def is_full_section(section):
+    """Is this section affected by "config.py full" and friends?"""
+    return section.endswith('support') or section.endswith('modules')
+
+def realfull_adapter(_name, active, section):
+    """Activate all symbols found in the system and feature sections."""
+    if not is_full_section(section):
+        return active
+    return True
+
+def include_in_full(name):
+    """Rules for symbols in the "full" configuration."""
+    if re.search(r'PLATFORM_[A-Z0-9]+_ALT', name):
+        return True
+    if name in [
+            'MBEDTLS_DEPRECATED_REMOVED',
+            'MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED',
+            'MBEDTLS_ECP_RESTARTABLE',
+            'MBEDTLS_HAVE_SSE2',
+            'MBEDTLS_MEMORY_BACKTRACE',
+            'MBEDTLS_MEMORY_BUFFER_ALLOC_C',
+            'MBEDTLS_MEMORY_DEBUG',
+            'MBEDTLS_NO_64BIT_MULTIPLICATION',
+            'MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES',
+            'MBEDTLS_NO_PLATFORM_ENTROPY',
+            'MBEDTLS_NO_UDBL_DIVISION',
+            'MBEDTLS_PKCS11_C',
+            'MBEDTLS_PLATFORM_NO_STD_FUNCTIONS',
+            'MBEDTLS_PSA_CRYPTO_SPM',
+            'MBEDTLS_PSA_INJECT_ENTROPY',
+            'MBEDTLS_REMOVE_3DES_CIPHERSUITES',
+            'MBEDTLS_REMOVE_ARC4_CIPHERSUITES',
+            'MBEDTLS_RSA_NO_CRT',
+            'MBEDTLS_SSL_HW_RECORD_ACCEL',
+            'MBEDTLS_TEST_NULL_ENTROPY',
+            'MBEDTLS_X509_ALLOW_EXTENSIONS_NON_V3',
+            'MBEDTLS_X509_ALLOW_UNSUPPORTED_CRITICAL_EXTENSION',
+            'MBEDTLS_ZLIB_SUPPORT',
+    ]:
+        return False
+    if name.endswith('_ALT'):
+        return False
+    return True
+
+def full_adapter(name, active, section):
+    """Config adapter for "full"."""
+    if not is_full_section(section):
+        return active
+    return include_in_full(name)
+
+def keep_in_baremetal(name):
+    """Rules for symbols in the "baremetal" configuration."""
+    if name in [
+            'MBEDTLS_DEPRECATED_WARNING',
+            'MBEDTLS_ENTROPY_NV_SEED',
+            'MBEDTLS_FS_IO',
+            'MBEDTLS_HAVEGE_C',
+            'MBEDTLS_HAVE_TIME',
+            'MBEDTLS_HAVE_TIME_DATE',
+            'MBEDTLS_MEMORY_BACKTRACE',
+            'MBEDTLS_MEMORY_BUFFER_ALLOC_C',
+            'MBEDTLS_NET_C',
+            'MBEDTLS_PLATFORM_FPRINTF_ALT',
+            'MBEDTLS_PLATFORM_TIME_ALT',
+            'MBEDTLS_PSA_CRYPTO_STORAGE_C',
+            'MBEDTLS_PSA_ITS_FILE_C',
+            'MBEDTLS_THREADING_C',
+            'MBEDTLS_THREADING_PTHREAD',
+            'MBEDTLS_TIMING_C',
+    ]:
+        return False
+    return True
+
+def baremetal_adapter(name, active, section):
+    """Config adapter for "baremetal"."""
+    if not is_full_section(section):
+        return active
+    if name == 'MBEDTLS_NO_PLATFORM_ENTROPY':
+        return True
+    return include_in_full(name) and keep_in_baremetal(name)
+
+class ConfigFile(Config):
+    """Representation of the Mbed TLS configuration read for a file.
+
+    See the documentation of the `Config` class for methods to query
+    and modify the configuration.
+    """
+
+    _path_in_tree = 'include/mbedtls/config.h'
+    default_path = [_path_in_tree,
+                    os.path.join(os.path.dirname(__file__),
+                                 os.pardir,
+                                 _path_in_tree),
+                    os.path.join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))),
+                                 _path_in_tree)]
+
+    def __init__(self, filename=None):
+        """Read the Mbed TLS configuration file."""
+        if filename is None:
+            for filename in self.default_path:
+                if os.path.lexists(filename):
+                    break
+        super().__init__()
+        self.filename = filename
+        self.current_section = 'header'
+        with open(filename, 'r', encoding='utf-8') as file:
+            self.templates = [self._parse_line(line) for line in file]
+        self.current_section = None
+
+    def set(self, name, value=None):
+        if name not in self.settings:
+            self.templates.append((name, '', '#define ' + name + ' '))
+        super().set(name, value)
+
+    _define_line_regexp = (r'(?P<indentation>\s*)' +
+                           r'(?P<commented_out>(//\s*)?)' +
+                           r'(?P<define>#\s*define\s+)' +
+                           r'(?P<name>\w+)' +
+                           r'(?P<arguments>(?:\((?:\w|\s|,)*\))?)' +
+                           r'(?P<separator>\s*)' +
+                           r'(?P<value>.*)')
+    _section_line_regexp = (r'\s*/?\*+\s*[\\@]name\s+SECTION:\s*' +
+                            r'(?P<section>.*)[ */]*')
+    _config_line_regexp = re.compile(r'|'.join([_define_line_regexp,
+                                                _section_line_regexp]))
+    def _parse_line(self, line):
+        """Parse a line in config.h and return the corresponding template."""
+        line = line.rstrip('\r\n')
+        m = re.match(self._config_line_regexp, line)
+        if m is None:
+            return line
+        elif m.group('section'):
+            self.current_section = m.group('section')
+            return line
+        else:
+            active = not m.group('commented_out')
+            name = m.group('name')
+            value = m.group('value')
+            template = (name,
+                        m.group('indentation'),
+                        m.group('define') + name +
+                        m.group('arguments') + m.group('separator'))
+            self.settings[name] = Setting(active, name, value,
+                                          self.current_section)
+            return template
+
+    def _format_template(self, name, indent, middle):
+        """Build a line for config.h for the given setting.
+
+        The line has the form "<indent>#define <name> <value>"
+        where <middle> is "#define <name> ".
+        """
+        setting = self.settings[name]
+        value = setting.value
+        if value is None:
+            value = ''
+        # Normally the whitespace to separte the symbol name from the
+        # value is part of middle, and there's no whitespace for a symbol
+        # with no value. But if a symbol has been changed from having a
+        # value to not having one, the whitespace is wrong, so fix it.
+        if value:
+            if middle[-1] not in '\t ':
+                middle += ' '
+        else:
+            middle = middle.rstrip()
+        return ''.join([indent,
+                        '' if setting.active else '//',
+                        middle,
+                        value]).rstrip()
+
+    def write_to_stream(self, output):
+        """Write the whole configuration to output."""
+        for template in self.templates:
+            if isinstance(template, str):
+                line = template
+            else:
+                line = self._format_template(*template)
+            output.write(line + '\n')
+
+    def write(self, filename=None):
+        """Write the whole configuration to the file it was read from.
+
+        If filename is specified, write to this file instead.
+        """
+        if filename is None:
+            filename = self.filename
+        with open(filename, 'w', encoding='utf-8') as output:
+            self.write_to_stream(output)
+
+if __name__ == '__main__':
+    def main():
+        """Command line config.h manipulation tool."""
+        parser = argparse.ArgumentParser(description="""
+        Mbed TLS and Mbed Crypto configuration file manipulation tool.
+        """)
+        parser.add_argument('--file', '-f',
+                            help="""File to read (and modify if requested).
+                            Default: {}.
+                            """.format(ConfigFile.default_path))
+        parser.add_argument('--force', '-o',
+                            action='store_true',
+                            help="""For the set command, if SYMBOL is not
+                            present, add a definition for it.""")
+        parser.add_argument('--write', '-w', metavar='FILE',
+                            help="""File to write to instead of the input file.""")
+        subparsers = parser.add_subparsers(dest='command',
+                                           title='Commands')
+        parser_get = subparsers.add_parser('get',
+                                           help="""Find the value of SYMBOL
+                                           and print it. Exit with
+                                           status 0 if a #define for SYMBOL is
+                                           found, 1 otherwise.
+                                           """)
+        parser_get.add_argument('symbol', metavar='SYMBOL')
+        parser_set = subparsers.add_parser('set',
+                                           help="""Set SYMBOL to VALUE.
+                                           If VALUE is omitted, just uncomment
+                                           the #define for SYMBOL.
+                                           Error out of a line defining
+                                           SYMBOL (commented or not) is not
+                                           found, unless --force is passed.
+                                           """)
+        parser_set.add_argument('symbol', metavar='SYMBOL')
+        parser_set.add_argument('value', metavar='VALUE', nargs='?',
+                                default='')
+        parser_unset = subparsers.add_parser('unset',
+                                             help="""Comment out the #define
+                                             for SYMBOL. Do nothing if none
+                                             is present.""")
+        parser_unset.add_argument('symbol', metavar='SYMBOL')
+
+        def add_adapter(name, function, description):
+            subparser = subparsers.add_parser(name, help=description)
+            subparser.set_defaults(adapter=function)
+        add_adapter('baremetal', baremetal_adapter,
+                    """Like full, but exclude features that require platform
+                    features such as file input-output.""")
+        add_adapter('full', full_adapter,
+                    """Uncomment most features.
+                    Exclude alternative implementations and platform support
+                    options, as well as some options that are awkward to test.
+                    """)
+        add_adapter('realfull', realfull_adapter,
+                    """Uncomment all boolean #defines.
+                    Suitable for generating documentation, but not for building.""")
+
+        args = parser.parse_args()
+        config = ConfigFile(args.file)
+        if args.command is None:
+            parser.print_help()
+            return 1
+        elif args.command == 'get':
+            if args.symbol in config:
+                value = config[args.symbol]
+                if value:
+                    sys.stdout.write(value + '\n')
+            return args.symbol not in config
+        elif args.command == 'set':
+            if not args.force and args.symbol not in config.settings:
+                sys.stderr.write("A #define for the symbol {} "
+                                 "was not found in {}\n"
+                                 .format(args.symbol, config.filename))
+                return 1
+            config.set(args.symbol, value=args.value)
+        elif args.command == 'unset':
+            config.unset(args.symbol)
+        else:
+            config.adapt(args.adapter)
+        config.write(args.write)
+
+    # Import modules only used by main only if main is defined and called.
+    # pylint: disable=wrong-import-position
+    import argparse
+    import sys
+    sys.exit(main())
diff --git a/scripts/ecc-heap.sh b/scripts/ecc-heap.sh
index 94a04cf..69777a6 100755
--- a/scripts/ecc-heap.sh
+++ b/scripts/ecc-heap.sh
@@ -59,8 +59,8 @@
 
 for F in 0 1; do
     for W in 2 3 4 5 6; do
-        scripts/config.pl set MBEDTLS_ECP_WINDOW_SIZE $W
-        scripts/config.pl set MBEDTLS_ECP_FIXED_POINT_OPTIM $F
+        scripts/config.py set MBEDTLS_ECP_WINDOW_SIZE $W
+        scripts/config.py set MBEDTLS_ECP_FIXED_POINT_OPTIM $F
         make benchmark >/dev/null 2>&1
         echo "fixed point optim = $F, max window size = $W"
         echo "--------------------------------------------"
diff --git a/scripts/footprint.sh b/scripts/footprint.sh
index c08ef1c..961a0d6 100755
--- a/scripts/footprint.sh
+++ b/scripts/footprint.sh
@@ -62,10 +62,10 @@
     fi
 
     {
-        scripts/config.pl unset MBEDTLS_NET_C || true
-        scripts/config.pl unset MBEDTLS_TIMING_C || true
-        scripts/config.pl unset MBEDTLS_FS_IO || true
-        scripts/config.pl --force set MBEDTLS_NO_PLATFORM_ENTROPY || true
+        scripts/config.py unset MBEDTLS_NET_C || true
+        scripts/config.py unset MBEDTLS_TIMING_C || true
+        scripts/config.py unset MBEDTLS_FS_IO || true
+        scripts/config.py --force set MBEDTLS_NO_PLATFORM_ENTROPY || true
     } >/dev/null 2>&1
 
     make clean >/dev/null
diff --git a/scripts/memory.sh b/scripts/memory.sh
index 3dad289..c415f92 100755
--- a/scripts/memory.sh
+++ b/scripts/memory.sh
@@ -46,10 +46,10 @@
     echo ""
     echo "config-$NAME:"
     cp configs/config-$NAME.h $CONFIG_H
-    scripts/config.pl unset MBEDTLS_SSL_SRV_C
+    scripts/config.py unset MBEDTLS_SSL_SRV_C
 
     for FLAG in $UNSET_LIST; do
-        scripts/config.pl unset $FLAG
+        scripts/config.py unset $FLAG
     done
 
     grep -F SSL_MAX_CONTENT_LEN $CONFIG_H || echo 'SSL_MAX_CONTENT_LEN=16384'
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index ecf6f34..d3d487a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -10,9 +10,9 @@
     set(libs ${libs} ${ZLIB_LIBRARIES})
 endif(ENABLE_ZLIB_SUPPORT)
 
-find_package(Perl)
-if(NOT PERL_FOUND)
-    message(FATAL_ERROR "Cannot build test suites without Perl")
+find_package(PythonInterp)
+if(NOT PYTHONINTERP_FOUND)
+    message(FATAL_ERROR "Cannot build test suites without Python 2 or 3")
 endif()
 
 # Enable definition of various functions used throughout the testsuite
diff --git a/tests/scripts/all.sh b/tests/scripts/all.sh
index ef96e2d..7f7c4bd 100755
--- a/tests/scripts/all.sh
+++ b/tests/scripts/all.sh
@@ -622,7 +622,7 @@
     SMALL_MPI_MAX_SIZE=136 # Small enough to interfere with the EC signatures
 
     msg "build: cmake + MBEDTLS_MPI_MAX_SIZE=${SMALL_MPI_MAX_SIZE}, gcc, ASan" # ~ 1 min 50s
-    scripts/config.pl set MBEDTLS_MPI_MAX_SIZE $SMALL_MPI_MAX_SIZE
+    scripts/config.py set MBEDTLS_MPI_MAX_SIZE $SMALL_MPI_MAX_SIZE
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -660,6 +660,22 @@
     if_build_succeeded tests/compat.sh
 }
 
+component_test_full_cmake_gcc_asan () {
+    msg "build: full config, cmake, gcc, ASan"
+    scripts/config.py full
+    CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
+    make
+
+    msg "test: main suites (inc. selftests) (full config, ASan build)"
+    make test
+
+    msg "test: ssl-opt.sh (full config, ASan build)"
+    if_build_succeeded tests/ssl-opt.sh
+
+    msg "test: compat.sh (full config, ASan build)"
+    if_build_succeeded tests/compat.sh
+}
+
 component_test_ref_configs () {
     msg "test/build: ref-configs (ASan build)" # ~ 6 min 20s
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
@@ -668,7 +684,7 @@
 
 component_test_sslv3 () {
     msg "build: Default + SSLv3 (ASan build)" # ~ 6 min
-    scripts/config.pl set MBEDTLS_SSL_PROTO_SSL3
+    scripts/config.py set MBEDTLS_SSL_PROTO_SSL3
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -685,7 +701,7 @@
 
 component_test_no_renegotiation () {
     msg "build: Default + !MBEDTLS_SSL_RENEGOTIATION (ASan build)" # ~ 6 min
-    scripts/config.pl unset MBEDTLS_SSL_RENEGOTIATION
+    scripts/config.py unset MBEDTLS_SSL_RENEGOTIATION
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -698,8 +714,8 @@
 
 component_test_no_pem_no_fs () {
     msg "build: Default + !MBEDTLS_PEM_PARSE_C + !MBEDTLS_FS_IO (ASan build)"
-    scripts/config.pl unset MBEDTLS_PEM_PARSE_C
-    scripts/config.pl unset MBEDTLS_FS_IO
+    scripts/config.py unset MBEDTLS_PEM_PARSE_C
+    scripts/config.py unset MBEDTLS_FS_IO
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -712,7 +728,7 @@
 
 component_test_rsa_no_crt () {
     msg "build: Default + RSA_NO_CRT (ASan build)" # ~ 6 min
-    scripts/config.pl set MBEDTLS_RSA_NO_CRT
+    scripts/config.py set MBEDTLS_RSA_NO_CRT
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -728,8 +744,8 @@
 
 component_test_everest () {
     msg "build: Everest ECDH context (ASan build)" # ~ 6 min
-    scripts/config.pl unset MBEDTLS_ECDH_LEGACY_CONTEXT
-    scripts/config.pl set MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED
+    scripts/config.py unset MBEDTLS_ECDH_LEGACY_CONTEXT
+    scripts/config.py set MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED
     CC=clang cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -746,8 +762,8 @@
 
 component_test_small_ssl_out_content_len () {
     msg "build: small SSL_OUT_CONTENT_LEN (ASan build)"
-    scripts/config.pl set MBEDTLS_SSL_IN_CONTENT_LEN 16384
-    scripts/config.pl set MBEDTLS_SSL_OUT_CONTENT_LEN 4096
+    scripts/config.py set MBEDTLS_SSL_IN_CONTENT_LEN 16384
+    scripts/config.py set MBEDTLS_SSL_OUT_CONTENT_LEN 4096
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -757,8 +773,8 @@
 
 component_test_small_ssl_in_content_len () {
     msg "build: small SSL_IN_CONTENT_LEN (ASan build)"
-    scripts/config.pl set MBEDTLS_SSL_IN_CONTENT_LEN 4096
-    scripts/config.pl set MBEDTLS_SSL_OUT_CONTENT_LEN 16384
+    scripts/config.py set MBEDTLS_SSL_IN_CONTENT_LEN 4096
+    scripts/config.py set MBEDTLS_SSL_OUT_CONTENT_LEN 16384
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -768,7 +784,7 @@
 
 component_test_small_ssl_dtls_max_buffering () {
     msg "build: small MBEDTLS_SSL_DTLS_MAX_BUFFERING #0"
-    scripts/config.pl set MBEDTLS_SSL_DTLS_MAX_BUFFERING 1000
+    scripts/config.py set MBEDTLS_SSL_DTLS_MAX_BUFFERING 1000
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -778,7 +794,7 @@
 
 component_test_small_mbedtls_ssl_dtls_max_buffering () {
     msg "build: small MBEDTLS_SSL_DTLS_MAX_BUFFERING #1"
-    scripts/config.pl set MBEDTLS_SSL_DTLS_MAX_BUFFERING 190
+    scripts/config.py set MBEDTLS_SSL_DTLS_MAX_BUFFERING 190
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -788,8 +804,7 @@
 
 component_test_full_cmake_clang () {
     msg "build: cmake, full config, clang" # ~ 50s
-    scripts/config.pl full
-    scripts/config.pl unset MBEDTLS_MEMORY_BACKTRACE # too slow for tests
+    scripts/config.py full
     CC=clang cmake -D CMAKE_BUILD_TYPE:String=Check -D ENABLE_TESTING=On .
     make
 
@@ -808,8 +823,8 @@
 
 component_build_deprecated () {
     msg "build: make, full config + DEPRECATED_WARNING, gcc -O" # ~ 30s
-    scripts/config.pl full
-    scripts/config.pl set MBEDTLS_DEPRECATED_WARNING
+    scripts/config.py full
+    scripts/config.py set MBEDTLS_DEPRECATED_WARNING
     # Build with -O -Wextra to catch a maximum of issues.
     make CC=gcc CFLAGS='-O -Werror -Wall -Wextra' lib programs
     make CC=gcc CFLAGS='-O -Werror -Wall -Wextra -Wno-unused-function' tests
@@ -817,8 +832,8 @@
     msg "build: make, full config + DEPRECATED_REMOVED, clang -O" # ~ 30s
     # No cleanup, just tweak the configuration and rebuild
     make clean
-    scripts/config.pl unset MBEDTLS_DEPRECATED_WARNING
-    scripts/config.pl set MBEDTLS_DEPRECATED_REMOVED
+    scripts/config.py unset MBEDTLS_DEPRECATED_WARNING
+    scripts/config.py set MBEDTLS_DEPRECATED_REMOVED
     # Build with -O -Wextra to catch a maximum of issues.
     make CC=clang CFLAGS='-O -Werror -Wall -Wextra' lib programs
     make CC=clang CFLAGS='-O -Werror -Wall -Wextra -Wno-unused-function' tests
@@ -858,13 +873,12 @@
 component_test_no_use_psa_crypto_full_cmake_asan() {
     # full minus MBEDTLS_USE_PSA_CRYPTO: run the same set of tests as basic-build-test.sh
     msg "build: cmake, full config minus MBEDTLS_USE_PSA_CRYPTO, ASan"
-    scripts/config.pl full
-    scripts/config.pl unset MBEDTLS_MEMORY_BUFFER_ALLOC_C # slow and makes ASan mostly ineffective
-    scripts/config.pl set MBEDTLS_ECP_RESTARTABLE  # not using PSA, so enable restartable ECC
-    scripts/config.pl unset MBEDTLS_PSA_CRYPTO_C
-    scripts/config.pl unset MBEDTLS_USE_PSA_CRYPTO
-    scripts/config.pl unset MBEDTLS_PSA_ITS_FILE_C
-    scripts/config.pl unset MBEDTLS_PSA_CRYPTO_STORAGE_C
+    scripts/config.py full
+    scripts/config.py set MBEDTLS_ECP_RESTARTABLE  # not using PSA, so enable restartable ECC
+    scripts/config.py unset MBEDTLS_PSA_CRYPTO_C
+    scripts/config.py unset MBEDTLS_USE_PSA_CRYPTO
+    scripts/config.py unset MBEDTLS_PSA_ITS_FILE_C
+    scripts/config.py unset MBEDTLS_PSA_CRYPTO_STORAGE_C
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -889,10 +903,9 @@
 
 component_test_check_params_functionality () {
     msg "build+test: MBEDTLS_CHECK_PARAMS functionality"
-    scripts/config.pl full # includes CHECK_PARAMS
+    scripts/config.py full # includes CHECK_PARAMS
     # Make MBEDTLS_PARAM_FAILED call mbedtls_param_failed().
-    scripts/config.pl unset MBEDTLS_CHECK_PARAMS_ASSERT
-    scripts/config.pl unset MBEDTLS_MEMORY_BUFFER_ALLOC_C
+    scripts/config.py unset MBEDTLS_CHECK_PARAMS_ASSERT
     # Only build and run tests. Do not build sample programs, because
     # they don't have a mbedtls_param_failed() function.
     make CC=gcc CFLAGS='-Werror -O1' lib test
@@ -900,24 +913,22 @@
 
 component_test_check_params_without_platform () {
     msg "build+test: MBEDTLS_CHECK_PARAMS without MBEDTLS_PLATFORM_C"
-    scripts/config.pl full # includes CHECK_PARAMS
+    scripts/config.py full # includes CHECK_PARAMS
     # Keep MBEDTLS_PARAM_FAILED as assert.
-    scripts/config.pl unset MBEDTLS_MEMORY_BUFFER_ALLOC_C
-    scripts/config.pl unset MBEDTLS_PLATFORM_EXIT_ALT
-    scripts/config.pl unset MBEDTLS_PLATFORM_TIME_ALT
-    scripts/config.pl unset MBEDTLS_PLATFORM_FPRINTF_ALT
-    scripts/config.pl unset MBEDTLS_PLATFORM_MEMORY
-    scripts/config.pl unset MBEDTLS_PLATFORM_PRINTF_ALT
-    scripts/config.pl unset MBEDTLS_PLATFORM_SNPRINTF_ALT
-    scripts/config.pl unset MBEDTLS_ENTROPY_NV_SEED
-    scripts/config.pl unset MBEDTLS_PLATFORM_C
+    scripts/config.py unset MBEDTLS_PLATFORM_EXIT_ALT
+    scripts/config.py unset MBEDTLS_PLATFORM_TIME_ALT
+    scripts/config.py unset MBEDTLS_PLATFORM_FPRINTF_ALT
+    scripts/config.py unset MBEDTLS_PLATFORM_MEMORY
+    scripts/config.py unset MBEDTLS_PLATFORM_PRINTF_ALT
+    scripts/config.py unset MBEDTLS_PLATFORM_SNPRINTF_ALT
+    scripts/config.py unset MBEDTLS_ENTROPY_NV_SEED
+    scripts/config.py unset MBEDTLS_PLATFORM_C
     make CC=gcc CFLAGS='-Werror -O1' all test
 }
 
 component_test_check_params_silent () {
     msg "build+test: MBEDTLS_CHECK_PARAMS with alternative MBEDTLS_PARAM_FAILED()"
-    scripts/config.pl full # includes CHECK_PARAMS
-    scripts/config.pl unset MBEDTLS_MEMORY_BACKTRACE # too slow for tests
+    scripts/config.py full # includes CHECK_PARAMS
     # Set MBEDTLS_PARAM_FAILED to nothing.
     sed -i 's/.*\(#define MBEDTLS_PARAM_FAILED( cond )\).*/\1/' "$CONFIG_H"
     make CC=gcc CFLAGS='-Werror -O1' all test
@@ -928,20 +939,19 @@
     # This should catch missing mbedtls_printf definitions, and by disabling file
     # IO, it should catch missing '#include <stdio.h>'
     msg "build: full config except platform/fsio/net, make, gcc, C99" # ~ 30s
-    scripts/config.pl full
-    scripts/config.pl unset MBEDTLS_PLATFORM_C
-    scripts/config.pl unset MBEDTLS_NET_C
-    scripts/config.pl unset MBEDTLS_PLATFORM_MEMORY
-    scripts/config.pl unset MBEDTLS_PLATFORM_PRINTF_ALT
-    scripts/config.pl unset MBEDTLS_PLATFORM_FPRINTF_ALT
-    scripts/config.pl unset MBEDTLS_PLATFORM_SNPRINTF_ALT
-    scripts/config.pl unset MBEDTLS_PLATFORM_TIME_ALT
-    scripts/config.pl unset MBEDTLS_PLATFORM_EXIT_ALT
-    scripts/config.pl unset MBEDTLS_ENTROPY_NV_SEED
-    scripts/config.pl unset MBEDTLS_MEMORY_BUFFER_ALLOC_C
-    scripts/config.pl unset MBEDTLS_FS_IO
-    scripts/config.pl unset MBEDTLS_PSA_CRYPTO_STORAGE_C
-    scripts/config.pl unset MBEDTLS_PSA_ITS_FILE_C
+    scripts/config.py full
+    scripts/config.py unset MBEDTLS_PLATFORM_C
+    scripts/config.py unset MBEDTLS_NET_C
+    scripts/config.py unset MBEDTLS_PLATFORM_MEMORY
+    scripts/config.py unset MBEDTLS_PLATFORM_PRINTF_ALT
+    scripts/config.py unset MBEDTLS_PLATFORM_FPRINTF_ALT
+    scripts/config.py unset MBEDTLS_PLATFORM_SNPRINTF_ALT
+    scripts/config.py unset MBEDTLS_PLATFORM_TIME_ALT
+    scripts/config.py unset MBEDTLS_PLATFORM_EXIT_ALT
+    scripts/config.py unset MBEDTLS_ENTROPY_NV_SEED
+    scripts/config.py unset MBEDTLS_FS_IO
+    scripts/config.py unset MBEDTLS_PSA_CRYPTO_STORAGE_C
+    scripts/config.py unset MBEDTLS_PSA_ITS_FILE_C
     # Note, _DEFAULT_SOURCE needs to be defined for platforms using glibc version >2.19,
     # to re-enable platform integration features otherwise disabled in C99 builds
     make CC=gcc CFLAGS='-Werror -Wall -Wextra -std=c99 -pedantic -O0 -D_DEFAULT_SOURCE' lib programs
@@ -951,23 +961,23 @@
 component_build_no_std_function () {
     # catch compile bugs in _uninit functions
     msg "build: full config with NO_STD_FUNCTION, make, gcc" # ~ 30s
-    scripts/config.pl full
-    scripts/config.pl set MBEDTLS_PLATFORM_NO_STD_FUNCTIONS
-    scripts/config.pl unset MBEDTLS_ENTROPY_NV_SEED
+    scripts/config.py full
+    scripts/config.py set MBEDTLS_PLATFORM_NO_STD_FUNCTIONS
+    scripts/config.py unset MBEDTLS_ENTROPY_NV_SEED
     make CC=gcc CFLAGS='-Werror -Wall -Wextra -O0'
 }
 
 component_build_no_ssl_srv () {
     msg "build: full config except ssl_srv.c, make, gcc" # ~ 30s
-    scripts/config.pl full
-    scripts/config.pl unset MBEDTLS_SSL_SRV_C
+    scripts/config.py full
+    scripts/config.py unset MBEDTLS_SSL_SRV_C
     make CC=gcc CFLAGS='-Werror -Wall -Wextra -O0'
 }
 
 component_build_no_ssl_cli () {
     msg "build: full config except ssl_cli.c, make, gcc" # ~ 30s
-    scripts/config.pl full
-    scripts/config.pl unset MBEDTLS_SSL_CLI_C
+    scripts/config.py full
+    scripts/config.py unset MBEDTLS_SSL_CLI_C
     make CC=gcc CFLAGS='-Werror -Wall -Wextra -O0'
 }
 
@@ -975,16 +985,44 @@
     # Note, C99 compliance can also be tested with the sockets support disabled,
     # as that requires a POSIX platform (which isn't the same as C99).
     msg "build: full config except net_sockets.c, make, gcc -std=c99 -pedantic" # ~ 30s
-    scripts/config.pl full
-    scripts/config.pl unset MBEDTLS_NET_C # getaddrinfo() undeclared, etc.
-    scripts/config.pl set MBEDTLS_NO_PLATFORM_ENTROPY # uses syscall() on GNU/Linux
+    scripts/config.py full
+    scripts/config.py unset MBEDTLS_NET_C # getaddrinfo() undeclared, etc.
+    scripts/config.py set MBEDTLS_NO_PLATFORM_ENTROPY # uses syscall() on GNU/Linux
     make CC=gcc CFLAGS='-Werror -Wall -Wextra -O0 -std=c99 -pedantic' lib
 }
 
+component_test_memory_buffer_allocator_backtrace () {
+    msg "build: default config with memory buffer allocator and backtrace enabled"
+    scripts/config.py set MBEDTLS_MEMORY_BUFFER_ALLOC_C
+    scripts/config.py set MBEDTLS_PLATFORM_MEMORY
+    scripts/config.py set MBEDTLS_MEMORY_BACKTRACE
+    scripts/config.py set MBEDTLS_MEMORY_DEBUG
+    CC=gcc cmake .
+    make
+
+    msg "test: MBEDTLS_MEMORY_BUFFER_ALLOC_C and MBEDTLS_MEMORY_BACKTRACE"
+    make test
+}
+
+component_test_memory_buffer_allocator () {
+    msg "build: default config with memory buffer allocator"
+    scripts/config.py set MBEDTLS_MEMORY_BUFFER_ALLOC_C
+    scripts/config.py set MBEDTLS_PLATFORM_MEMORY
+    CC=gcc cmake .
+    make
+
+    msg "test: MBEDTLS_MEMORY_BUFFER_ALLOC_C"
+    make test
+
+    msg "test: ssl-opt.sh, MBEDTLS_MEMORY_BUFFER_ALLOC_C"
+    # MBEDTLS_MEMORY_BUFFER_ALLOC is slow. Skip tests that tend to time out.
+    if_build_succeeded tests/ssl-opt.sh -e '^DTLS proxy'
+}
+
 component_test_no_max_fragment_length () {
     # Run max fragment length tests with MFL disabled
     msg "build: default config except MFL extension (ASan build)" # ~ 30s
-    scripts/config.pl unset MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
+    scripts/config.py unset MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -994,7 +1032,7 @@
 
 component_test_asan_remove_peer_certificate () {
     msg "build: default config with MBEDTLS_SSL_KEEP_PEER_CERTIFICATE disabled (ASan build)"
-    scripts/config.pl unset MBEDTLS_SSL_KEEP_PEER_CERTIFICATE
+    scripts/config.py unset MBEDTLS_SSL_KEEP_PEER_CERTIFICATE
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -1010,9 +1048,9 @@
 
 component_test_no_max_fragment_length_small_ssl_out_content_len () {
     msg "build: no MFL extension, small SSL_OUT_CONTENT_LEN (ASan build)"
-    scripts/config.pl unset MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
-    scripts/config.pl set MBEDTLS_SSL_IN_CONTENT_LEN 16384
-    scripts/config.pl set MBEDTLS_SSL_OUT_CONTENT_LEN 4096
+    scripts/config.py unset MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
+    scripts/config.py set MBEDTLS_SSL_IN_CONTENT_LEN 16384
+    scripts/config.py set MBEDTLS_SSL_OUT_CONTENT_LEN 4096
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -1022,9 +1060,9 @@
 
 component_test_when_no_ciphersuites_have_mac () {
     msg "build: when no ciphersuites have MAC"
-    scripts/config.pl unset MBEDTLS_CIPHER_NULL_CIPHER
-    scripts/config.pl unset MBEDTLS_ARC4_C
-    scripts/config.pl unset MBEDTLS_CIPHER_MODE_CBC
+    scripts/config.py unset MBEDTLS_CIPHER_NULL_CIPHER
+    scripts/config.py unset MBEDTLS_ARC4_C
+    scripts/config.py unset MBEDTLS_CIPHER_MODE_CBC
     make
 
     msg "test: !MBEDTLS_SSL_SOME_MODES_USE_MAC"
@@ -1036,12 +1074,12 @@
 
 component_test_null_entropy () {
     msg "build: default config with  MBEDTLS_TEST_NULL_ENTROPY (ASan build)"
-    scripts/config.pl set MBEDTLS_TEST_NULL_ENTROPY
-    scripts/config.pl set MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES
-    scripts/config.pl set MBEDTLS_ENTROPY_C
-    scripts/config.pl unset MBEDTLS_ENTROPY_NV_SEED
-    scripts/config.pl unset MBEDTLS_ENTROPY_HARDWARE_ALT
-    scripts/config.pl unset MBEDTLS_HAVEGE_C
+    scripts/config.py set MBEDTLS_TEST_NULL_ENTROPY
+    scripts/config.py set MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES
+    scripts/config.py set MBEDTLS_ENTROPY_C
+    scripts/config.py unset MBEDTLS_ENTROPY_NV_SEED
+    scripts/config.py unset MBEDTLS_ENTROPY_HARDWARE_ALT
+    scripts/config.py unset MBEDTLS_HAVEGE_C
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan -D UNSAFE_BUILD=ON .
     make
 
@@ -1051,9 +1089,9 @@
 
 component_test_platform_calloc_macro () {
     msg "build: MBEDTLS_PLATFORM_{CALLOC/FREE}_MACRO enabled (ASan build)"
-    scripts/config.pl set MBEDTLS_PLATFORM_MEMORY
-    scripts/config.pl set MBEDTLS_PLATFORM_CALLOC_MACRO calloc
-    scripts/config.pl set MBEDTLS_PLATFORM_FREE_MACRO   free
+    scripts/config.py set MBEDTLS_PLATFORM_MEMORY
+    scripts/config.py set MBEDTLS_PLATFORM_CALLOC_MACRO calloc
+    scripts/config.py set MBEDTLS_PLATFORM_FREE_MACRO   free
     CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
     make
 
@@ -1079,7 +1117,7 @@
     msg "build: make with MBEDTLS_CONFIG_FILE" # ~40s
     # Use the full config so as to catch a maximum of places where
     # the check of MBEDTLS_CONFIG_FILE might be missing.
-    scripts/config.pl full
+    scripts/config.py full
     sed 's!"check_config.h"!"mbedtls/check_config.h"!' <"$CONFIG_H" >full_config.h
     echo '#error "MBEDTLS_CONFIG_FILE is not working"' >"$CONFIG_H"
     make CFLAGS="-I '$PWD' -DMBEDTLS_CONFIG_FILE='\"full_config.h\"'"
@@ -1089,8 +1127,7 @@
 component_test_m32_o0 () {
     # Build once with -O0, to compile out the i386 specific inline assembly
     msg "build: i386, make, gcc -O0 (ASan build)" # ~ 30s
-    scripts/config.pl full
-    scripts/config.pl unset MBEDTLS_MEMORY_BUFFER_ALLOC_C # slow and makes ASan mostly ineffective
+    scripts/config.py full
     make CC=gcc CFLAGS='-O0 -Werror -Wall -Wextra -m32 -fsanitize=address' LDFLAGS='-m32 -fsanitize=address'
 
     msg "test: i386, make, gcc -O0 (ASan build)"
@@ -1106,8 +1143,7 @@
 component_test_m32_o1 () {
     # Build again with -O1, to compile in the i386 specific inline assembly
     msg "build: i386, make, gcc -O1 (ASan build)" # ~ 30s
-    scripts/config.pl full
-    scripts/config.pl unset MBEDTLS_MEMORY_BUFFER_ALLOC_C # slow and makes ASan mostly ineffective
+    scripts/config.py full
     make CC=gcc CFLAGS='-O1 -Werror -Wall -Wextra -m32 -fsanitize=address' LDFLAGS='-m32 -fsanitize=address'
 
     msg "test: i386, make, gcc -O1 (ASan build)"
@@ -1122,8 +1158,8 @@
 
 component_test_m32_everest () {
     msg "build: i386, Everest ECDH context (ASan build)" # ~ 6 min
-    scripts/config.pl unset MBEDTLS_ECDH_LEGACY_CONTEXT
-    scripts/config.pl set MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED
+    scripts/config.py unset MBEDTLS_ECDH_LEGACY_CONTEXT
+    scripts/config.py set MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED
     make CC=gcc CFLAGS='-O2 -Werror -Wall -Wextra -m32 -fsanitize=address' LDFLAGS='-m32 -fsanitize=address'
 
     msg "test: i386, Everest ECDH context - main suites (inc. selftests) (ASan build)" # ~ 50s
@@ -1142,7 +1178,7 @@
 
 component_test_mx32 () {
     msg "build: 64-bit ILP32, make, gcc" # ~ 30s
-    scripts/config.pl full
+    scripts/config.py full
     make CC=gcc CFLAGS='-Werror -Wall -Wextra -mx32' LDFLAGS='-mx32'
 
     msg "test: 64-bit ILP32, make, gcc"
@@ -1157,13 +1193,13 @@
 
 component_build_arm_none_eabi_gcc () {
     msg "build: arm-none-eabi-gcc, make" # ~ 10s
-    scripts/config.pl baremetal
+    scripts/config.py baremetal
     make CC=arm-none-eabi-gcc AR=arm-none-eabi-ar LD=arm-none-eabi-ld CFLAGS='-Werror -Wall -Wextra' lib
 }
 
 component_build_arm_none_eabi_gcc_arm5vte () {
     msg "build: arm-none-eabi-gcc -march=arm5vte, make" # ~ 10s
-    scripts/config.pl baremetal
+    scripts/config.py baremetal
     # Build for a target platform that's close to what Debian uses
     # for its "armel" distribution (https://wiki.debian.org/ArmEabiPort).
     # See https://github.com/ARMmbed/mbedtls/pull/2169 and comments.
@@ -1174,8 +1210,8 @@
 
 component_build_arm_none_eabi_gcc_no_udbl_division () {
     msg "build: arm-none-eabi-gcc -DMBEDTLS_NO_UDBL_DIVISION, make" # ~ 10s
-    scripts/config.pl baremetal
-    scripts/config.pl set MBEDTLS_NO_UDBL_DIVISION
+    scripts/config.py baremetal
+    scripts/config.py set MBEDTLS_NO_UDBL_DIVISION
     make CC=arm-none-eabi-gcc AR=arm-none-eabi-ar LD=arm-none-eabi-ld CFLAGS='-Werror -Wall -Wextra' lib
     echo "Checking that software 64-bit division is not required"
     if_build_succeeded not grep __aeabi_uldiv library/*.o
@@ -1183,8 +1219,8 @@
 
 component_build_arm_none_eabi_gcc_no_64bit_multiplication () {
     msg "build: arm-none-eabi-gcc MBEDTLS_NO_64BIT_MULTIPLICATION, make" # ~ 10s
-    scripts/config.pl baremetal
-    scripts/config.pl set MBEDTLS_NO_64BIT_MULTIPLICATION
+    scripts/config.py baremetal
+    scripts/config.py set MBEDTLS_NO_64BIT_MULTIPLICATION
     make CC=arm-none-eabi-gcc AR=arm-none-eabi-ar LD=arm-none-eabi-ld CFLAGS='-Werror -O1 -march=armv6-m -mthumb' lib
     echo "Checking that software 64-bit multiplication is not required"
     if_build_succeeded not grep __aeabi_lmul library/*.o
@@ -1192,7 +1228,7 @@
 
 component_build_armcc () {
     msg "build: ARM Compiler 5, make"
-    scripts/config.pl baremetal
+    scripts/config.py baremetal
 
     make CC="$ARMC5_CC" AR="$ARMC5_AR" WARNING_CFLAGS='--strict --c99' lib
     make clean
@@ -1215,7 +1251,7 @@
 
 component_test_allow_sha1 () {
     msg "build: allow SHA1 in certificates by default"
-    scripts/config.pl set MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_CERTIFICATES
+    scripts/config.py set MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_CERTIFICATES
     make CFLAGS='-Werror -Wall -Wextra'
     msg "test: allow SHA1 in certificates by default"
     make test
@@ -1244,7 +1280,7 @@
 
 component_test_memsan () {
     msg "build: MSan (clang)" # ~ 1 min 20s
-    scripts/config.pl unset MBEDTLS_AESNI_C # memsan doesn't grok asm
+    scripts/config.py unset MBEDTLS_AESNI_C # memsan doesn't grok asm
     CC=clang cmake -D CMAKE_BUILD_TYPE:String=MemSan .
     make
 
diff --git a/tests/scripts/basic-build-test.sh b/tests/scripts/basic-build-test.sh
index 4b71ff6..6419f05 100755
--- a/tests/scripts/basic-build-test.sh
+++ b/tests/scripts/basic-build-test.sh
@@ -67,8 +67,8 @@
 export LDFLAGS=' --coverage'
 make clean
 cp "$CONFIG_H" "$CONFIG_BAK"
-scripts/config.pl full
-scripts/config.pl unset MBEDTLS_MEMORY_BACKTRACE
+scripts/config.py full
+scripts/config.py unset MBEDTLS_MEMORY_BACKTRACE
 make -j
 
 
diff --git a/tests/scripts/curves.pl b/tests/scripts/curves.pl
index 4791d55..3e22552 100755
--- a/tests/scripts/curves.pl
+++ b/tests/scripts/curves.pl
@@ -46,13 +46,13 @@
     system( "make clean" ) and die;
 
     # depends on a specific curve. Also, ignore error if it wasn't enabled
-    system( "scripts/config.pl unset MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED" );
+    system( "scripts/config.py unset MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED" );
 
     print "\n******************************************\n";
     print "* Testing without curve: $curve\n";
     print "******************************************\n";
 
-    system( "scripts/config.pl unset $curve" )
+    system( "scripts/config.py unset $curve" )
         and abort "Failed to disable $curve\n";
 
     system( "CFLAGS='-Werror -Wall -Wextra' make lib" )
diff --git a/tests/scripts/depends-hashes.pl b/tests/scripts/depends-hashes.pl
index f57e7ed..92bcceb 100755
--- a/tests/scripts/depends-hashes.pl
+++ b/tests/scripts/depends-hashes.pl
@@ -58,11 +58,11 @@
     print "* Testing without hash: $hash\n";
     print "******************************************\n";
 
-    system( "scripts/config.pl unset $hash" )
+    system( "scripts/config.py unset $hash" )
         and abort "Failed to disable $hash\n";
 
     for my $opt (@ssl) {
-        system( "scripts/config.pl unset $opt" )
+        system( "scripts/config.py unset $opt" )
             and abort "Failed to disable $opt\n";
     }
 
diff --git a/tests/scripts/depends-pkalgs.pl b/tests/scripts/depends-pkalgs.pl
index 97a43e8..e3eac00 100755
--- a/tests/scripts/depends-pkalgs.pl
+++ b/tests/scripts/depends-pkalgs.pl
@@ -73,10 +73,10 @@
     print "* Testing without alg: $alg\n";
     print "******************************************\n";
 
-    system( "scripts/config.pl unset $alg" )
+    system( "scripts/config.py unset $alg" )
         and abort "Failed to disable $alg\n";
     for my $opt (@$extras) {
-        system( "scripts/config.pl unset $opt" )
+        system( "scripts/config.py unset $opt" )
             and abort "Failed to disable $opt\n";
     }
 
diff --git a/tests/scripts/key-exchanges.pl b/tests/scripts/key-exchanges.pl
index 3bf7ae3..be029c7 100755
--- a/tests/scripts/key-exchanges.pl
+++ b/tests/scripts/key-exchanges.pl
@@ -47,10 +47,10 @@
     print "******************************************\n";
 
     # full config with all key exchanges disabled except one
-    system( "scripts/config.pl full" ) and abort "Failed config full\n";
+    system( "scripts/config.py full" ) and abort "Failed config full\n";
     for my $k (@kexes) {
         next if $k eq $kex;
-        system( "scripts/config.pl unset $k" )
+        system( "scripts/config.py unset $k" )
             and abort "Failed to disable $k\n";
     }
 
diff --git a/tests/scripts/list-symbols.sh b/tests/scripts/list-symbols.sh
index 6ecc199..1c348a7 100755
--- a/tests/scripts/list-symbols.sh
+++ b/tests/scripts/list-symbols.sh
@@ -13,7 +13,7 @@
 fi
 
 cp include/mbedtls/config.h include/mbedtls/config.h.bak
-scripts/config.pl full
+scripts/config.py full
 make clean
 make_ret=
 CFLAGS=-fno-asynchronous-unwind-tables make lib \
diff --git a/tests/scripts/test_config_script.py b/tests/scripts/test_config_script.py
new file mode 100755
index 0000000..40ed9fd
--- /dev/null
+++ b/tests/scripts/test_config_script.py
@@ -0,0 +1,187 @@
+#!/usr/bin/env python3
+
+"""Test helper for the Mbed TLS configuration file tool
+
+Run config.py with various parameters and write the results to files.
+
+This is a harness to help regression testing, not a functional tester.
+Sample usage:
+
+    test_config_script.py -d old
+    ## Modify config.py and/or config.h ##
+    test_config_script.py -d new
+    diff -ru old new
+"""
+
+## Copyright (C) 2019, ARM Limited, All Rights Reserved
+## 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 file is part of Mbed TLS (https://tls.mbed.org)
+
+import argparse
+import glob
+import os
+import re
+import shutil
+import subprocess
+
+OUTPUT_FILE_PREFIX = 'config-'
+
+def output_file_name(directory, stem, extension):
+    return os.path.join(directory,
+                        '{}{}.{}'.format(OUTPUT_FILE_PREFIX,
+                                         stem, extension))
+
+def cleanup_directory(directory):
+    """Remove old output files."""
+    for extension in []:
+        pattern = output_file_name(directory, '*', extension)
+        filenames = glob.glob(pattern)
+        for filename in filenames:
+            os.remove(filename)
+
+def prepare_directory(directory):
+    """Create the output directory if it doesn't exist yet.
+
+    If there are old output files, remove them.
+    """
+    if os.path.exists(directory):
+        cleanup_directory(directory)
+    else:
+        os.makedirs(directory)
+
+def guess_presets_from_help(help_text):
+    """Figure out what presets the script supports.
+
+    help_text should be the output from running the script with --help.
+    """
+    # Try the output format from config.py
+    hits = re.findall(r'\{([-\w,]+)\}', help_text)
+    for hit in hits:
+        words = set(hit.split(','))
+        if 'get' in words and 'set' in words and 'unset' in words:
+            words.remove('get')
+            words.remove('set')
+            words.remove('unset')
+            return words
+    # Try the output format from config.pl
+    hits = re.findall(r'\n +([-\w]+) +- ', help_text)
+    if hits:
+        return hits
+    raise Exception("Unable to figure out supported presets. Pass the '-p' option.")
+
+def list_presets(options):
+    """Return the list of presets to test.
+
+    The list is taken from the command line if present, otherwise it is
+    extracted from running the config script with --help.
+    """
+    if options.presets:
+        return re.split(r'[ ,]+', options.presets)
+    else:
+        help_text = subprocess.run([options.script, '--help'],
+                                   stdout=subprocess.PIPE,
+                                   stderr=subprocess.STDOUT).stdout
+        return guess_presets_from_help(help_text.decode('ascii'))
+
+def run_one(options, args, stem_prefix='', input_file=None):
+    """Run the config script with the given arguments.
+
+    Take the original content from input_file if specified, defaulting
+    to options.input_file if input_file is None.
+
+    Write the following files, where xxx contains stem_prefix followed by
+    a filename-friendly encoding of args:
+    * config-xxx.h: modified file.
+    * config-xxx.out: standard output.
+    * config-xxx.err: standard output.
+    * config-xxx.status: exit code.
+
+    Return ("xxx+", "path/to/config-xxx.h") which can be used as
+    stem_prefix and input_file to call this function again with new args.
+    """
+    if input_file is None:
+        input_file = options.input_file
+    stem = stem_prefix + '-'.join(args)
+    data_filename = output_file_name(options.output_directory, stem, 'h')
+    stdout_filename = output_file_name(options.output_directory, stem, 'out')
+    stderr_filename = output_file_name(options.output_directory, stem, 'err')
+    status_filename = output_file_name(options.output_directory, stem, 'status')
+    shutil.copy(input_file, data_filename)
+    # Pass only the file basename, not the full path, to avoid getting the
+    # directory name in error messages, which would make comparisons
+    # between output directories more difficult.
+    cmd = [os.path.abspath(options.script),
+           '-f', os.path.basename(data_filename)]
+    with open(stdout_filename, 'wb') as out:
+        with open(stderr_filename, 'wb') as err:
+            status = subprocess.call(cmd + args,
+                                     cwd=options.output_directory,
+                                     stdin=subprocess.DEVNULL,
+                                     stdout=out, stderr=err)
+    with open(status_filename, 'w') as status_file:
+        status_file.write('{}\n'.format(status))
+    return stem + "+", data_filename
+
+### A list of symbols to test with.
+### This script currently tests what happens when you change a symbol from
+### having a value to not having a value or vice versa. This is not
+### necessarily useful behavior, and we may not consider it a bug if
+### config.py stops handling that case correctly.
+TEST_SYMBOLS = [
+    'CUSTOM_SYMBOL', # does not exist
+    'MBEDTLS_AES_C', # set, no value
+    'MBEDTLS_MPI_MAX_SIZE', # unset, has a value
+    'MBEDTLS_NO_UDBL_DIVISION', # unset, in "System support"
+    'MBEDTLS_PLATFORM_ZEROIZE_ALT', # unset, in "Customisation configuration options"
+]
+
+def run_all(options):
+    """Run all the command lines to test."""
+    presets = list_presets(options)
+    for preset in presets:
+        run_one(options, [preset])
+    for symbol in TEST_SYMBOLS:
+        run_one(options, ['get', symbol])
+        (stem, filename) = run_one(options, ['set', symbol])
+        run_one(options, ['get', symbol], stem_prefix=stem, input_file=filename)
+        run_one(options, ['--force', 'set', symbol])
+        (stem, filename) = run_one(options, ['set', symbol, 'value'])
+        run_one(options, ['get', symbol], stem_prefix=stem, input_file=filename)
+        run_one(options, ['--force', 'set', symbol, 'value'])
+        run_one(options, ['unset', symbol])
+
+def main():
+    """Command line entry point."""
+    parser = argparse.ArgumentParser(description=__doc__,
+                                     formatter_class=argparse.RawDescriptionHelpFormatter)
+    parser.add_argument('-d', metavar='DIR',
+                        dest='output_directory', required=True,
+                        help="""Output directory.""")
+    parser.add_argument('-f', metavar='FILE',
+                        dest='input_file', default='include/mbedtls/config.h',
+                        help="""Config file (default: %(default)s).""")
+    parser.add_argument('-p', metavar='PRESET,...',
+                        dest='presets',
+                        help="""Presets to test (default: guessed from --help).""")
+    parser.add_argument('-s', metavar='FILE',
+                        dest='script', default='scripts/config.py',
+                        help="""Configuration script (default: %(default)s).""")
+    options = parser.parse_args()
+    prepare_directory(options.output_directory)
+    run_all(options)
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh
index 47b6b80..91fceea 100755
--- a/tests/ssl-opt.sh
+++ b/tests/ssl-opt.sh
@@ -300,9 +300,9 @@
 }
 
 # Calculate the input & output maximum content lengths set in the config
-MAX_CONTENT_LEN=$( ../scripts/config.pl get MBEDTLS_SSL_MAX_CONTENT_LEN || echo "16384")
-MAX_IN_LEN=$( ../scripts/config.pl get MBEDTLS_SSL_IN_CONTENT_LEN || echo "$MAX_CONTENT_LEN")
-MAX_OUT_LEN=$( ../scripts/config.pl get MBEDTLS_SSL_OUT_CONTENT_LEN || echo "$MAX_CONTENT_LEN")
+MAX_CONTENT_LEN=$( ../scripts/config.py get MBEDTLS_SSL_MAX_CONTENT_LEN || echo "16384")
+MAX_IN_LEN=$( ../scripts/config.py get MBEDTLS_SSL_IN_CONTENT_LEN || echo "$MAX_CONTENT_LEN")
+MAX_OUT_LEN=$( ../scripts/config.py get MBEDTLS_SSL_OUT_CONTENT_LEN || echo "$MAX_CONTENT_LEN")
 
 if [ "$MAX_IN_LEN" -lt "$MAX_CONTENT_LEN" ]; then
     MAX_CONTENT_LEN="$MAX_IN_LEN"
@@ -3718,7 +3718,7 @@
 # default value (8)
 
 MAX_IM_CA='8'
-MAX_IM_CA_CONFIG=$( ../scripts/config.pl get MBEDTLS_X509_MAX_INTERMEDIATE_CA)
+MAX_IM_CA_CONFIG=$( ../scripts/config.py get MBEDTLS_X509_MAX_INTERMEDIATE_CA)
 
 if [ -n "$MAX_IM_CA_CONFIG" ] && [ "$MAX_IM_CA_CONFIG" -ne "$MAX_IM_CA" ]; then
     printf "The ${CONFIG_H} file contains a value for the configuration of\n"
diff --git a/tests/suites/test_suite_version.data b/tests/suites/test_suite_version.data
index 8e85ad1..b6dca23 100644
--- a/tests/suites/test_suite_version.data
+++ b/tests/suites/test_suite_version.data
@@ -1,8 +1,8 @@
 Check compiletime library version
-check_compiletime_version:"2.19.0"
+check_compiletime_version:"2.19.1"
 
 Check runtime library version
-check_runtime_version:"2.19.0"
+check_runtime_version:"2.19.1"
 
 Check for MBEDTLS_VERSION_C
 check_feature:"MBEDTLS_VERSION_C":0
diff --git a/tests/suites/test_suite_x509write.function b/tests/suites/test_suite_x509write.function
index e15802f..7b369bb 100644
--- a/tests/suites/test_suite_x509write.function
+++ b/tests/suites/test_suite_x509write.function
@@ -39,25 +39,36 @@
     unsigned char hash[MBEDTLS_MD_MAX_SIZE];
     const mbedtls_md_info_t *md_info;
     mbedtls_x509_csr csr;
+    int ret = 0;
+
+    mbedtls_x509_csr_init( &csr );
 
     if( mbedtls_x509_csr_parse( &csr, buf, buflen ) != 0 )
-        return( MBEDTLS_ERR_X509_BAD_INPUT_DATA );
+    {
+        ret = MBEDTLS_ERR_X509_BAD_INPUT_DATA;
+        goto cleanup;
+    }
 
     md_info = mbedtls_md_info_from_type( csr.sig_md );
     if( mbedtls_md( md_info, csr.cri.p, csr.cri.len, hash ) != 0 )
     {
         /* Note: this can't happen except after an internal error */
-        return( MBEDTLS_ERR_X509_BAD_INPUT_DATA );
+        ret = MBEDTLS_ERR_X509_BAD_INPUT_DATA;
+        goto cleanup;
     }
 
     if( mbedtls_pk_verify_ext( csr.sig_pk, csr.sig_opts, &csr.pk,
                        csr.sig_md, hash, mbedtls_md_get_size( md_info ),
                        csr.sig.p, csr.sig.len ) != 0 )
     {
-        return( MBEDTLS_ERR_X509_CERT_VERIFY_FAILED );
+        ret = MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
+        goto cleanup;
     }
 
-    return( 0 );
+cleanup:
+
+    mbedtls_x509_csr_free( &csr );
+    return( ret );
 }
 #endif /* MBEDTLS_USE_PSA_CRYPTO */
 
diff --git a/visualc/VS2010/mbedTLS.vcxproj b/visualc/VS2010/mbedTLS.vcxproj
index 41906f5..45ae103 100644
--- a/visualc/VS2010/mbedTLS.vcxproj
+++ b/visualc/VS2010/mbedTLS.vcxproj
@@ -222,7 +222,6 @@
     <ClCompile Include="..\..\crypto\library\md2.c" />

     <ClCompile Include="..\..\crypto\library\md4.c" />

     <ClCompile Include="..\..\crypto\library\md5.c" />

-    <ClCompile Include="..\..\crypto\library\md_wrap.c" />

     <ClCompile Include="..\..\crypto\library\memory_buffer_alloc.c" />

     <ClCompile Include="..\..\crypto\library\nist_kw.c" />

     <ClCompile Include="..\..\crypto\library\oid.c" />