send ECH_REQUIRED alert if rejected, saving retry_configs correctly
diff --git a/include/picotls.h b/include/picotls.h
index 14a3b46..1382499 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -210,6 +210,7 @@
 #define PTLS_ALERT_UNRECOGNIZED_NAME 112
 #define PTLS_ALERT_CERTIFICATE_REQUIRED 116
 #define PTLS_ALERT_NO_APPLICATION_PROTOCOL 120
+#define PTLS_ALERT_ECH_REQUIRED 121
 
 /* TLS 1.2 */
 #define PTLS_TLS12_MASTER_SECRET_SIZE 48
diff --git a/lib/picotls.c b/lib/picotls.c
index 25159f5..2d2ab7d 100644
--- a/lib/picotls.c
+++ b/lib/picotls.c
@@ -2812,7 +2812,8 @@
                     ret = PTLS_ERROR_NO_MEMORY;
                     goto Exit;
                 }
-                *properties->client.ech.retry_configs = ptls_iovec_init(src, end - src);
+                memcpy(properties->client.ech.retry_configs->base, src, end - src);
+                properties->client.ech.retry_configs->len = end - src;
             }
             src = end;
         } break;
@@ -3224,6 +3225,12 @@
         goto Exit;
     ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
 
+    /* if ECH was rejected, close the connection with ECH_REQUIRED alert after verifying messages up to Finished */
+    if (tls->client.first_ech.base != NULL && !ptls_is_ech_handshake(tls)) {
+        ret = PTLS_ALERT_ECH_REQUIRED;
+        goto Exit;
+    }
+
     /* update traffic keys by using messages upto ServerFinished, but commission them after sending ClientFinished */
     if ((ret = key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0))) != 0)
         goto Exit;
diff --git a/t/openssl.c b/t/openssl.c
index 97e3dcf..da3c733 100644
--- a/t/openssl.c
+++ b/t/openssl.c
@@ -390,7 +390,10 @@
                                   .cipher_suites = ptls_openssl_cipher_suites,
                                   .tls12_cipher_suites = ptls_openssl_tls12_cipher_suites,
                                   .certificates = {&cert, 1},
-                                  .ech = {ptls_openssl_hpke_cipher_suites, ptls_openssl_hpke_kems, &ech_create_opener},
+                                  .ech = {.ciphers = ptls_openssl_hpke_cipher_suites,
+                                          .kems = ptls_openssl_hpke_kems,
+                                          .create_opener = &ech_create_opener,
+                                          .retry_configs = {(uint8_t *)ECH_CONFIG_LIST, sizeof(ECH_CONFIG_LIST) - 1}},
                                   .sign_certificate = &openssl_sign_certificate.super};
     assert(openssl_ctx.cipher_suites[0]->hash->digest_size == 48); /* sha384 */
     ptls_context_t openssl_ctx_sha256only = openssl_ctx;
diff --git a/t/picotls.c b/t/picotls.c
index 835f9ed..4b04657 100644
--- a/t/picotls.c
+++ b/t/picotls.c
@@ -1261,6 +1261,53 @@
     ptls_buffer_dispose(&sbuf);
 }
 
+static void test_ech_config_mismatch(void)
+{
+    ptls_t *client, *server;
+    ptls_buffer_t cbuf, sbuf;
+    size_t consumed;
+    int ret;
+    ptls_iovec_t retry_configs = {NULL};
+    ptls_handshake_properties_t client_hs_prop = {
+        .client.ech = {
+            .configs = ptls_iovec_init((void *)ECH_ALTERNATIVE_CONFIG_LIST, sizeof(ECH_ALTERNATIVE_CONFIG_LIST) - 1),
+            .retry_configs = &retry_configs,
+        }};
+
+    client = ptls_new(ctx, 0);
+    server = ptls_new(ctx_peer, 1);
+    ptls_buffer_init(&cbuf, "", 0);
+    ptls_buffer_init(&sbuf, "", 0);
+
+    ret = ptls_handshake(client, &cbuf, NULL, NULL, &client_hs_prop);
+    ok(ret == PTLS_ERROR_IN_PROGRESS);
+
+    consumed = cbuf.off;
+    ret = ptls_handshake(server, &sbuf, cbuf.base, &consumed, NULL);
+    ok(ret == 0);
+    ok(cbuf.off == consumed);
+    cbuf.off = 0;
+
+    consumed = sbuf.off;
+    ret = ptls_handshake(client, &cbuf, sbuf.base, &consumed, &client_hs_prop);
+    ok(ret == PTLS_ALERT_ECH_REQUIRED);
+    ok(sbuf.off == consumed);
+    ok(retry_configs.len == sizeof(ECH_CONFIG_LIST) - 1);
+    ok(memcmp(retry_configs.base, ECH_CONFIG_LIST, retry_configs.len) == 0);
+    sbuf.off = 0;
+
+    consumed = cbuf.off;
+    ret = ptls_handshake(server, &sbuf, cbuf.base, &consumed, NULL);
+    ok(ret == PTLS_ALERT_TO_PEER_ERROR(PTLS_ALERT_ECH_REQUIRED));
+    ok(cbuf.off == consumed);
+
+    ptls_free(client);
+    ptls_free(server);
+    ptls_buffer_dispose(&cbuf);
+    ptls_buffer_dispose(&sbuf);
+    free(retry_configs.base);
+}
+
 typedef uint8_t traffic_secrets_t[2 /* is_enc */][4 /* epoch */][PTLS_MAX_DIGEST_SIZE /* octets */];
 
 static int on_update_traffic_key(ptls_update_traffic_key_t *self, ptls_t *tls, int is_enc, size_t epoch, const void *secret)
@@ -1596,10 +1643,8 @@
             ctx->ech.ciphers = NULL;
             subtest("ech (server-only)", test_all_handshakes_core);
             ctx->ech.ciphers = orig_ech_ciphers.client;
-            ctx_peer->ech.ciphers = NULL;
-            subtest("ech (client-only)", test_all_handshakes_core);
-            ctx_peer->ech.ciphers = orig_ech_ciphers.server;
         }
+        subtest("ech-config-match", test_ech_config_mismatch);
     }
 
     ctx_peer->sign_certificate = sc_orig;
diff --git a/t/test.h b/t/test.h
index 66667ef..5513ce4 100644
--- a/t/test.h
+++ b/t/test.h
@@ -51,12 +51,17 @@
     "\x1d\x99\x42\xe0\xa2\xb7\x75\xbb\x14\x03\x79\x9a\xf6\x07\xd8\xa5\xab\x2b\x3a\x70\x8b\x77\x85\x70\x8a\x98\x38\x9b\x35\x09\xf6" \
     "\x62\x6b\x29\x4a\xa7\xa7\xf9\x3b\xde\xd8\xc8\x90\x57\xf2\x76\x2a\x23\x0b\x01\x68\xc6\x9a\xf2"
 
-/* test vector from RFC 9180 A.3 */
+/* test vector using RFC 9180 A.3 */
 #define ECH_CONFIG_LIST                                                                                                            \
     "\x00\x63\xfe\x0d\x00\x5f\x12\x00\x10\x00\x41\x04\xfe\x8c\x19\xce\x09\x05\x19\x1e\xbc\x29\x8a\x92\x45\x79\x25\x31\xf2\x6f\x0c" \
     "\xec\xe2\x46\x06\x39\xe8\xbc\x39\xcb\x7f\x70\x6a\x82\x6a\x77\x9b\x4c\xf9\x69\xb8\xa0\xe5\x39\xc7\xf6\x2f\xb3\xd3\x0a\xd6\xaa" \
     "\x8f\x80\xe3\x0f\x1d\x12\x8a\xaf\xd6\x8a\x2c\xe7\x2e\xa0\x00\x08\x00\x02\x00\x02\x00\x01\x00\x01\x40\x0b\x65\x78\x61\x6d\x70" \
     "\x6c\x65\x2e\x63\x6f\x6d\x00\x00"
+/* key from a third party (that we do not know the corresponding private key) */
+#define ECH_ALTERNATIVE_CONFIG_LIST                                                                                                \
+    "\x00\x46\xfe\x0d\x00\x42\xf4\x00\x20\x00\x20\x53\x85\xed\xda\xc8\x63\xe8\x02\x00\x05\x12\xcf\xc0\x45\xda\xda\x26\x37\x93\x17" \
+    "\xe5\xfe\xe8\x9f\x0e\x14\xb5\xad\x78\x4b\x99\x78\x00\x04\x00\x01\x00\x01\x00\x13\x63\x6c\x6f\x75\x64\x66\x6c\x61\x72\x65\x2d" \
+    "\x65\x73\x6e\x69\x2e\x63\x6f\x6d\x00\x00"
 #define ECH_PRIVATE_KEY                                                                                                            \
     "-----BEGIN PRIVATE KEY-----\n"                                                                                                \
     "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg885/2uV+GjENh/Hr\n"                                                           \