Merge pull request #45 from h2o/master

Align to ESNI-03
diff --git a/include/picotls.h b/include/picotls.h
index 09bda7f..1054879 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -84,7 +84,10 @@
 #define PTLS_SIGNATURE_RSA_PSS_RSAE_SHA512 0x0806
 
 /* ESNI */
-#define PTLS_ESNI_VERSION_DRAFT02 0xff01
+#define PTLS_ESNI_VERSION_DRAFT03 0xff02
+
+#define PTLS_ESNI_RESPONSE_TYPE_ACCEPT 0
+#define PTLS_ESNI_RESPONSE_TYPE_RETRY_REQUEST 1
 
 /* error classes and macros */
 #define PTLS_ERROR_CLASS_SELF_ALERT 0
@@ -132,6 +135,7 @@
 #define PTLS_ERROR_STATELESS_RETRY (PTLS_ERROR_CLASS_INTERNAL + 6)
 #define PTLS_ERROR_NOT_AVAILABLE (PTLS_ERROR_CLASS_INTERNAL + 7)
 #define PTLS_ERROR_COMPRESSION_FAILURE (PTLS_ERROR_CLASS_INTERNAL + 8)
+#define PTLS_ERROR_ESNI_RETRY (PTLS_ERROR_CLASS_INTERNAL + 8)
 
 #define PTLS_ERROR_INCORRECT_BASE64 (PTLS_ERROR_CLASS_INTERNAL + 50)
 #define PTLS_ERROR_PEM_LABEL_NOT_FOUND (PTLS_ERROR_CLASS_INTERNAL + 51)
@@ -346,7 +350,7 @@
     /**
      * returns the digest and performs necessary operation specified by mode
      */
-    void (* final)(struct st_ptls_hash_context_t *ctx, void *md, ptls_hash_final_mode_t mode);
+    void (*final)(struct st_ptls_hash_context_t *ctx, void *md, ptls_hash_final_mode_t mode);
     /**
      * creates a copy of the hash context
      */
@@ -403,8 +407,31 @@
     uint16_t padded_length;
     uint64_t not_before;
     uint64_t not_after;
+    uint16_t version;
 } ptls_esni_context_t;
 
+/**
+ * holds the ESNI secret, as exchanged during the handshake
+ */
+
+#define PTLS_ESNI_NONCE_SIZE 16
+
+typedef struct st_ptls_esni_secret_t {
+    ptls_iovec_t secret;
+    uint8_t nonce[PTLS_ESNI_NONCE_SIZE];
+    uint8_t esni_contents_hash[PTLS_MAX_DIGEST_SIZE];
+    union {
+        struct {
+            ptls_key_exchange_algorithm_t *key_share;
+            ptls_cipher_suite_t *cipher;
+            ptls_iovec_t pubkey;
+            uint8_t record_digest[PTLS_MAX_DIGEST_SIZE];
+            uint16_t padded_length;
+        } client;
+    };
+    uint16_t version;
+} ptls_esni_secret_t;
+
 #define PTLS_CALLBACK_TYPE0(ret, name)                                                                                             \
     typedef struct st_ptls_##name##_t {                                                                                            \
         ret (*cb)(struct st_ptls_##name##_t * self);                                                                               \
@@ -489,7 +516,8 @@
  * event logging (incl. secret logging)
  */
 typedef struct st_ptls_log_event_t {
-    void (*cb)(struct st_ptls_log_event_t *self, ptls_t *tls, const char *type, const char *fmt, ...) __attribute__((format(printf, 4, 5)));
+    void (*cb)(struct st_ptls_log_event_t *self, ptls_t *tls, const char *type, const char *fmt, ...)
+        __attribute__((format(printf, 4, 5)));
 } ptls_log_event_t;
 /**
  * reference counting
@@ -1154,6 +1182,12 @@
  *
  */
 void ptls_esni_dispose_context(ptls_esni_context_t *esni);
+
+/**
+ * Obtain the ESNI secrets negotiated during the handshake.
+ */
+ptls_esni_secret_t *ptls_get_esni_secret(ptls_t *ctx);
+
 /**
  *
  */
diff --git a/lib/openssl.c b/lib/openssl.c
index 6d503b5..5ce6e85 100644
--- a/lib/openssl.c
+++ b/lib/openssl.c
@@ -44,6 +44,10 @@
 #include "picotls/openssl.h"
 
 #ifdef _WINDOWS
+#ifndef _CRT_SECURE_NO_WARNINGS
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#pragma warning(disable : 4996)
 #include <ms\applink.c>
 #endif
 
@@ -237,7 +241,7 @@
 
 static int x9_62_create_key_exchange(ptls_key_exchange_algorithm_t *algo, ptls_key_exchange_context_t **_ctx)
 {
-    EC_GROUP* group = NULL;
+    EC_GROUP *group = NULL;
     struct st_x9_62_keyex_context_t *ctx = NULL;
     int ret;
 
@@ -600,7 +604,6 @@
 
     default:
         return PTLS_ERROR_INCOMPATIBLE_KEY;
-
     }
 }
 
@@ -1217,7 +1220,8 @@
     return ret;
 }
 
-static void cleanup_cipher_ctx(EVP_CIPHER_CTX *ctx) {
+static void cleanup_cipher_ctx(EVP_CIPHER_CTX *ctx)
+{
     if (!EVP_CIPHER_CTX_cleanup(ctx)) {
         fprintf(stderr, "EVP_CIPHER_CTX_cleanup() failed\n");
         abort();
diff --git a/lib/picotls.c b/lib/picotls.c
index b4a5705..a479bb8 100644
--- a/lib/picotls.c
+++ b/lib/picotls.c
@@ -38,8 +38,6 @@
 #define PTLS_RECORD_VERSION_MAJOR 3
 #define PTLS_RECORD_VERSION_MINOR 3
 
-#define PTLS_ESNI_NONCE_SIZE 16
-
 #define PTLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC 20
 #define PTLS_CONTENT_TYPE_ALERT 21
 #define PTLS_CONTENT_TYPE_HANDSHAKE 22
@@ -127,21 +125,6 @@
     struct st_ptls_signature_algorithms_t signature_algorithms;
 };
 
-struct st_ptls_esni_secret_t {
-    ptls_iovec_t secret;
-    uint8_t nonce[PTLS_ESNI_NONCE_SIZE];
-    uint8_t esni_contents_hash[PTLS_MAX_DIGEST_SIZE];
-    union {
-        struct {
-            ptls_key_exchange_algorithm_t *key_share;
-            ptls_cipher_suite_t *cipher;
-            ptls_iovec_t pubkey;
-            uint8_t record_digest[PTLS_MAX_DIGEST_SIZE];
-            uint16_t padded_length;
-        } client;
-    };
-};
-
 struct st_ptls_t {
     /**
      * the context
@@ -211,7 +194,7 @@
     /**
      * esni
      */
-    struct st_ptls_esni_secret_t *esni;
+    ptls_esni_secret_t *esni;
     /**
      * exporter master secret (either 0rtt or 1rtt)
      */
@@ -458,26 +441,35 @@
 #undef EXT
 }
 
+#ifndef ntoh16
 static uint16_t ntoh16(const uint8_t *src)
 {
     return (uint16_t)src[0] << 8 | src[1];
 }
+#endif
 
+
+#ifndef ntoh24
 static uint32_t ntoh24(const uint8_t *src)
 {
     return (uint32_t)src[0] << 16 | (uint32_t)src[1] << 8 | src[2];
 }
+#endif
 
+#ifndef ntoh32
 static uint32_t ntoh32(const uint8_t *src)
 {
     return (uint32_t)src[0] << 24 | (uint32_t)src[1] << 16 | (uint32_t)src[2] << 8 | src[3];
 }
+#endif
 
+#ifndef ntoh64
 static uint64_t ntoh64(const uint8_t *src)
 {
     return (uint64_t)src[0] << 56 | (uint64_t)src[1] << 48 | (uint64_t)src[2] << 40 | (uint64_t)src[3] << 32 |
            (uint64_t)src[4] << 24 | (uint64_t)src[5] << 16 | (uint64_t)src[6] << 8 | src[7];
 }
+#endif
 
 void ptls_buffer__release_memory(ptls_buffer_t *buf)
 {
@@ -1583,9 +1575,9 @@
     return ret;
 }
 
-static int parse_esni_keys(ptls_context_t *ctx, ptls_key_exchange_algorithm_t **selected_key_share,
+static int parse_esni_keys(ptls_context_t *ctx, uint16_t *esni_version, ptls_key_exchange_algorithm_t **selected_key_share,
                            ptls_cipher_suite_t **selected_cipher, ptls_iovec_t *peer_key, uint16_t *padded_length,
-                           ptls_iovec_t input)
+                           char **published_sni, ptls_iovec_t input)
 {
     const uint8_t *src = input.base, *const end = input.base + input.len;
     uint16_t version;
@@ -1595,7 +1587,7 @@
     /* version */
     if ((ret = ptls_decode16(&version, &src, end)) != 0)
         goto Exit;
-    if (version != PTLS_ESNI_VERSION_DRAFT02) {
+    if (version != PTLS_ESNI_VERSION_DRAFT03) {
         ret = PTLS_ALERT_DECODE_ERROR;
         goto Exit;
     }
@@ -1621,6 +1613,21 @@
         }
         src += 4;
     }
+    *esni_version = version;
+    /* published sni */
+    ptls_decode_open_block(src, end, 2, {
+        size_t len = end - src;
+        *published_sni = malloc(len + 1);
+        if (*published_sni == NULL) {
+            ret = PTLS_ERROR_NO_MEMORY;
+            goto Exit;
+        }
+        if (len > 0) {
+            memcpy(*published_sni, src, len);
+        }
+        (*published_sni)[len] = 0;
+        src = end;
+    });
     /* key-shares */
     ptls_decode_open_block(src, end, 2, {
         if ((ret = select_key_share(selected_key_share, peer_key, ctx->key_exchanges, &src, end, 0)) != 0)
@@ -1706,7 +1713,7 @@
     return ret;
 }
 
-static void free_esni_secret(struct st_ptls_esni_secret_t **esni, int is_server)
+static void free_esni_secret(ptls_esni_secret_t **esni, int is_server)
 {
     assert(*esni != NULL);
     if ((*esni)->secret.base != NULL) {
@@ -1720,7 +1727,7 @@
     *esni = NULL;
 }
 
-static int client_setup_esni(ptls_context_t *ctx, struct st_ptls_esni_secret_t **esni, ptls_iovec_t esni_keys,
+static int client_setup_esni(ptls_context_t *ctx, ptls_esni_secret_t **esni, ptls_iovec_t esni_keys, char **published_sni,
                              const uint8_t *client_random)
 {
     ptls_iovec_t peer_key;
@@ -1731,8 +1738,8 @@
     memset(*esni, 0, sizeof(**esni));
 
     /* parse ESNI_Keys (and return success while keeping *esni NULL) */
-    if (parse_esni_keys(ctx, &(*esni)->client.key_share, &(*esni)->client.cipher, &peer_key, &(*esni)->client.padded_length,
-                        esni_keys) != 0) {
+    if (parse_esni_keys(ctx, &(*esni)->version, &(*esni)->client.key_share, &(*esni)->client.cipher, &peer_key,
+                        &(*esni)->client.padded_length, published_sni, esni_keys) != 0) {
         free(*esni);
         *esni = NULL;
         return 0;
@@ -1759,7 +1766,7 @@
     return ret;
 }
 
-static int emit_esni_extension(struct st_ptls_esni_secret_t *esni, ptls_buffer_t *buf, ptls_iovec_t esni_keys,
+static int emit_esni_extension(ptls_esni_secret_t *esni, ptls_buffer_t *buf, ptls_iovec_t esni_keys,
                                const char *server_name, size_t key_share_ch_off, size_t key_share_ch_len)
 {
     ptls_aead_context_t *aead = NULL;
@@ -1810,6 +1817,7 @@
                              ptls_iovec_t *cookie)
 {
     ptls_iovec_t resumption_secret = {NULL}, resumption_ticket;
+    char *published_sni = NULL;
     uint32_t obfuscated_ticket_age = 0;
     size_t msghash_off;
     uint8_t binder_key[PTLS_MAX_DIGEST_SIZE];
@@ -1819,8 +1827,10 @@
     if (properties != NULL) {
         /* try to use ESNI */
         if (!is_second_flight && send_sni && properties->client.esni_keys.base != NULL) {
-            if ((ret = client_setup_esni(tls->ctx, &tls->esni, properties->client.esni_keys, tls->client_random)) != 0)
+            if ((ret = client_setup_esni(tls->ctx, &tls->esni, properties->client.esni_keys, &published_sni, tls->client_random)) !=
+                0) {
                 goto Exit;
+            }
             if (tls->ctx->update_esni_key != NULL) {
                 if ((ret = tls->ctx->update_esni_key->cb(tls->ctx->update_esni_key, tls, tls->esni->secret,
                                                          tls->esni->client.cipher->hash, tls->esni->esni_contents_hash)) != 0)
@@ -1905,6 +1915,12 @@
             });
             if (send_sni) {
                 if (tls->esni != NULL) {
+                    if (published_sni != NULL) {
+                        buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, {
+                            if ((ret = emit_server_name_extension(sendbuf, published_sni)) != 0)
+                                goto Exit;
+                        });
+                    }
                     buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_SERVER_NAME, {
                         if ((ret = emit_esni_extension(tls->esni, sendbuf, properties->client.esni_keys, tls->server_name,
                                                        key_share_client_hello.off, key_share_client_hello.len)) != 0)
@@ -2024,6 +2040,9 @@
     ret = PTLS_ERROR_IN_PROGRESS;
 
 Exit:
+    if (published_sni != NULL) {
+        free(published_sni);
+    }
     ptls_clear_memory(binder_key, sizeof(binder_key));
     return ret;
 }
@@ -2183,8 +2202,8 @@
         tls->client.key_share_ctx = NULL;
     }
     if (tls->client.using_early_data) {
-        /* release traffic encryption key so that 2nd CH goes out in cleartext, but keep the epoch at 1 since we've already called
-         * derive-secret */
+        /* release traffic encryption key so that 2nd CH goes out in cleartext, but keep the epoch at 1 since we've already
+         * called derive-secret */
         if (tls->ctx->update_traffic_key == NULL) {
             assert(tls->traffic_protection.enc.aead != NULL);
             ptls_aead_free(tls->traffic_protection.enc.aead);
@@ -2284,7 +2303,6 @@
 static int handle_unknown_extension(ptls_t *tls, ptls_handshake_properties_t *properties, uint16_t type, const uint8_t *src,
                                     const uint8_t *const end, ptls_raw_extension_t *slots)
 {
-
     if (properties != NULL && properties->collect_extension != NULL && properties->collect_extension(tls, properties, type)) {
         size_t i;
         for (i = 0; slots[i].type != UINT16_MAX; ++i) {
@@ -2313,7 +2331,7 @@
 
 static int client_handle_encrypted_extensions(ptls_t *tls, ptls_iovec_t message, ptls_handshake_properties_t *properties)
 {
-    const uint8_t *src = message.base + PTLS_HANDSHAKE_HEADER_SIZE, *const end = message.base + message.len, *esni = NULL;
+    const uint8_t *src = message.base + PTLS_HANDSHAKE_HEADER_SIZE, *const end = message.base + message.len, *esni_nonce = NULL;
     uint16_t type;
     ptls_raw_extension_t unknown_extensions[MAX_UNKNOWN_EXTENSIONS + 1];
     int ret, skip_early_data = 1;
@@ -2337,11 +2355,17 @@
             }
             break;
         case PTLS_EXTENSION_TYPE_ENCRYPTED_SERVER_NAME:
-            if (end - src != PTLS_ESNI_NONCE_SIZE) {
-                ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+            if (*src == PTLS_ESNI_RESPONSE_TYPE_ACCEPT) {
+                if (end - src != PTLS_ESNI_NONCE_SIZE + 1) {
+                    ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+                    goto Exit;
+                }
+                esni_nonce = src + 1;
+            } else {
+                /* TODO: provide API to parse the RETRY REQUEST response */
+                ret = PTLS_ERROR_ESNI_RETRY;
                 goto Exit;
             }
-            esni = src;
             break;
         case PTLS_EXTENSION_TYPE_ALPN:
             ptls_decode_block(src, end, 2, {
@@ -2375,13 +2399,13 @@
     });
 
     if (tls->esni != NULL) {
-        if (esni == NULL || !ptls_mem_equal(esni, tls->esni->nonce, PTLS_ESNI_NONCE_SIZE)) {
+        if (esni_nonce == NULL || !ptls_mem_equal(esni_nonce, tls->esni->nonce, PTLS_ESNI_NONCE_SIZE)) {
             ret = PTLS_ALERT_ILLEGAL_PARAMETER;
             goto Exit;
         }
         free_esni_secret(&tls->esni, 0);
     } else {
-        if (esni != NULL) {
+        if (esni_nonce != NULL) {
             ret = PTLS_ALERT_ILLEGAL_PARAMETER;
             goto Exit;
         }
@@ -2889,7 +2913,7 @@
     return ret;
 }
 
-static int client_hello_decrypt_esni(ptls_context_t *ctx, ptls_iovec_t *server_name, struct st_ptls_esni_secret_t **secret,
+static int client_hello_decrypt_esni(ptls_context_t *ctx, ptls_iovec_t *server_name, ptls_esni_secret_t **secret,
                                      struct st_ptls_client_hello_t *ch)
 {
     ptls_esni_context_t **esni;
@@ -2914,8 +2938,10 @@
             ret = PTLS_ALERT_ILLEGAL_PARAMETER;
             goto Exit;
         }
-        if (memcmp((*esni)->cipher_suites[i].record_digest, ch->esni.record_digest, ch->esni.cipher->hash->digest_size) == 0)
+        if (memcmp((*esni)->cipher_suites[i].record_digest, ch->esni.record_digest, ch->esni.cipher->hash->digest_size) == 0) {
+            (*secret)->version = (*esni)->version;
             break;
+        }
     }
     if (*esni == NULL) {
         ret = PTLS_ALERT_ILLEGAL_PARAMETER;
@@ -3505,8 +3531,8 @@
                               additional_extensions                                                                                \
                           } while (0);                                                                                             \
                       })
-    struct st_ptls_client_hello_t ch = {NULL,   {NULL}, {NULL},     0,     {NULL},   {NULL}, {NULL},        {{0}},
-                                        {NULL}, {NULL}, {{{NULL}}}, {{0}}, {{0}}, {{NULL}}, {NULL}, {{UINT16_MAX}}};
+    struct st_ptls_client_hello_t ch = {NULL,   {NULL}, {NULL},     0,     {NULL}, {NULL},   {NULL}, {{0}},
+                                        {NULL}, {NULL}, {{{NULL}}}, {{0}}, {{0}},  {{NULL}}, {NULL}, {{UINT16_MAX}}};
     struct {
         ptls_key_exchange_algorithm_t *algorithm;
         ptls_iovec_t peer_key;
@@ -3561,14 +3587,14 @@
             ret = PTLS_ALERT_DECODE_ERROR;
             goto Exit;
         }
-        /* the following check is necessary so that we would be able to track the connection in SSLKEYLOGFILE, even though it might
-         * not be for the safety of the protocol */
+        /* the following check is necessary so that we would be able to track the connection in SSLKEYLOGFILE, even though it
+         * might not be for the safety of the protocol */
         if (!ptls_mem_equal(tls->client_random, ch.random_bytes, sizeof(tls->client_random))) {
             ret = PTLS_ALERT_HANDSHAKE_FAILURE;
             goto Exit;
         }
-        /* We compare SNI only when the value is saved by the on_client_hello callback. This should be OK because we are ignoring
-         * the value unless the callback saves the server-name. */
+        /* We compare SNI only when the value is saved by the on_client_hello callback. This should be OK because we are
+         * ignoring the value unless the callback saves the server-name. */
         if (tls->server_name != NULL) {
             size_t l = strlen(tls->server_name);
             if (!(ch.server_name.len == l && memcmp(ch.server_name.base, tls->server_name, l) == 0)) {
@@ -3642,7 +3668,8 @@
             ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
             assert(tls->key_schedule->generation == 0);
             if (properties != NULL && properties->server.retry_uses_cookie) {
-                /* emit HRR with cookie (note: we MUST omit KeyShare if the client has specified the correct one; see 46554f0) */
+                /* emit HRR with cookie (note: we MUST omit KeyShare if the client has specified the correct one; see 46554f0)
+                 */
                 EMIT_HELLO_RETRY_REQUEST(NULL, key_share.algorithm != NULL ? NULL : negotiated_group, {
                     ptls_buffer_t *sendbuf = emitter->buf;
                     buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COOKIE, {
@@ -3805,14 +3832,17 @@
         ptls_buffer_t *sendbuf = emitter->buf;
         ptls_buffer_push_block(sendbuf, 2, {
             if (tls->esni != NULL) {
-                /* the extension is sent even if the application does not handle server name, because otherwise the handshake would
-                 * fail (FIXME ch.esni.nonce will be zero on HRR) */
-                buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_SERVER_NAME,
-                                      { ptls_buffer_pushv(sendbuf, tls->esni->nonce, PTLS_ESNI_NONCE_SIZE); });
+                /* the extension is sent even if the application does not handle server name, because otherwise the handshake
+                 * would fail (FIXME ch.esni.nonce will be zero on HRR) */
+                buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_SERVER_NAME, {
+                    uint8_t response_type = PTLS_ESNI_RESPONSE_TYPE_ACCEPT;
+                    ptls_buffer_pushv(sendbuf, &response_type, 1);
+                    ptls_buffer_pushv(sendbuf, tls->esni->nonce, PTLS_ESNI_NONCE_SIZE);
+                });
                 free_esni_secret(&tls->esni, 1);
             } else if (tls->server_name != NULL) {
-                /* In this event, the server SHALL include an extension of type "server_name" in the (extended) server hello. The
-                 * "extension_data" field of this extension SHALL be empty. (RFC 6066 section 3) */
+                /* In this event, the server SHALL include an extension of type "server_name" in the (extended) server hello.
+                 * The "extension_data" field of this extension SHALL be empty. (RFC 6066 section 3) */
                 buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, {});
             }
             if (tls->negotiated_protocol != NULL) {
@@ -4860,7 +4890,8 @@
 int ptls_hkdf_expand_label(ptls_hash_algorithm_t *algo, void *output, size_t outlen, ptls_iovec_t secret, const char *label,
                            ptls_iovec_t hash_value, const char *label_prefix)
 {
-    /* the handshake layer should call hkdf_expand_label directly, always setting key_schedule->hkdf_label_prefix as the argument */
+    /* the handshake layer should call hkdf_expand_label directly, always setting key_schedule->hkdf_label_prefix as the
+     * argument */
     if (label_prefix == NULL)
         label_prefix = PTLS_HKDF_EXPAND_LABEL_PREFIX;
     return hkdf_expand_label(algo, output, outlen, secret, label, hash_value, label_prefix);
@@ -5088,11 +5119,18 @@
     memcpy(esni->key_exchanges, key_exchanges, sizeof(*esni->key_exchanges) * (num_key_exchanges + 1));
 
     /* ESNIKeys */
-    if (end - src < 6) {
+    if ((ret = ptls_decode16(&esni->version, &src, end)) != 0)
+        goto Exit;
+    /* Skip checksum fields */
+    if (end - src < 4) {
         ret = PTLS_ALERT_DECRYPT_ERROR;
         goto Exit;
     }
-    src += 6;
+    src += 4;
+    /* Published SNI field */
+    ptls_decode_open_block(src, end, 2, { src = end; });
+
+    /* Process the list of KeyShareEntries, verify for each of them that the ciphersuite is supported. */
     ptls_decode_open_block(src, end, 2, {
         do {
             /* parse */
@@ -5111,6 +5149,7 @@
             }
         } while (src != end);
     });
+    /* Process the list of cipher_suites. If they are supported, store in esni context  */
     ptls_decode_open_block(src, end, 2, {
         void *newp;
         do {
@@ -5137,12 +5176,14 @@
         esni->cipher_suites = newp;
         esni->cipher_suites[num_cipher_suites].cipher_suite = NULL;
     });
+    /* Parse the padded length, not before, not after parameters */
     if ((ret = ptls_decode16(&esni->padded_length, &src, end)) != 0)
         goto Exit;
     if ((ret = ptls_decode64(&esni->not_before, &src, end)) != 0)
         goto Exit;
     if ((ret = ptls_decode64(&esni->not_after, &src, end)) != 0)
         goto Exit;
+    /* Skip the extension fields */
     ptls_decode_block(src, end, 2, {
         while (src != end) {
             uint16_t ext_type;
@@ -5181,6 +5222,14 @@
 }
 
 /**
+ * Obtain the ESNI secrets negotiated during the handshake.
+ */
+ptls_esni_secret_t *ptls_get_esni_secret(ptls_t *ctx)
+{
+    return ctx->esni;
+}
+
+/**
  * checks if given name looks like an IP address
  */
 int ptls_server_name_is_ipaddr(const char *name)
diff --git a/picotlsvs/picotls-esni/getopt.c b/picotlsvs/picotls-esni/getopt.c
new file mode 100644
index 0000000..21cf6ca
--- /dev/null
+++ b/picotlsvs/picotls-esni/getopt.c
@@ -0,0 +1,103 @@
+#include "getopt.h"
+
+/*
+* Copyright (c) 1987, 1993, 1994
+*      The Regents of the University of California.  All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in the
+*    documentation and/or other materials provided with the distribution.
+* 3. All advertising materials mentioning features or use of this software
+*    must display the following acknowledgement:
+*      This product includes software developed by the University of
+*      California, Berkeley and its contributors.
+* 4. Neither the name of the University nor the names of its contributors
+*    may be used to endorse or promote products derived from this software
+*    without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+* SUCH DAMAGE.
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+int opterr = 1, /* if error message should be printed */
+    optind = 1, /* index into parent argv vector */
+    optopt, /* character checked for validity */
+    optreset; /* reset getopt */
+const char* optarg; /* argument associated with option */
+
+#define BADCH (int)'?'
+#define BADARG (int)':'
+#define EMSG ""
+
+/*
+ * getopt --
+ *      Parse argc/argv argument vector.
+ */
+int getopt(int nargc, char* const nargv[], const char* ostr)
+{
+    static const char* place = EMSG; /* option letter processing */
+    const char* oli; /* option letter list index */
+
+    if (optreset || !*place) { /* update scanning pointer */
+        optreset = 0;
+        if (optind >= nargc || *(place = nargv[optind]) != '-') {
+            place = EMSG;
+            return (-1);
+        }
+        if (place[1] && *++place == '-') { /* found "--" */
+            ++optind;
+            place = EMSG;
+            return (-1);
+        }
+    } /* option letter okay? */
+    if ((optopt = (int)*place++) == (int)':' || !(oli = strchr(ostr, optopt))) {
+        /*
+      * if the user didn't specify '-' as an option,
+      * assume it means -1.
+      */
+        if (optopt == (int)'-')
+            return (-1);
+        if (!*place)
+            ++optind;
+        if (opterr && *ostr != ':')
+            (void)printf("illegal option -- %c\n", optopt);
+        return (BADCH);
+    }
+    if (*++oli != ':') { /* don't need argument */
+        optarg = NULL;
+        if (!*place)
+            ++optind;
+    } else { /* need an argument */
+        if (*place) /* no white space */
+            optarg = place;
+        else if (nargc <= ++optind) { /* no arg */
+            place = EMSG;
+            if (*ostr == ':')
+                return (BADARG);
+            if (opterr)
+                (void)printf("option requires an argument -- %c\n", optopt);
+            return (BADCH);
+        } else /* white space */
+            optarg = nargv[optind];
+        place = EMSG;
+        ++optind;
+    }
+    return (optopt); /* dump back option letter */
+}
diff --git a/picotlsvs/picotls-esni/getopt.h b/picotlsvs/picotls-esni/getopt.h
new file mode 100644
index 0000000..1ea538e
--- /dev/null
+++ b/picotlsvs/picotls-esni/getopt.h
@@ -0,0 +1,19 @@
+#ifndef GETOPT_H
+#ifndef __APPLE__
+
+#define GETOPT_H
+
+#ifndef _GETOPT_H
+#define _GETOPT_H
+#endif
+
+extern int opterr; /* if error message should be printed */
+extern int optind; /* index into parent argv vector */
+extern int optopt; /* character checked for validity */
+extern int optreset; /* reset getopt  */
+extern const char* optarg; /* argument associated with option */
+
+int getopt(int nargc, char* const nargv[], const char* ostr);
+
+#endif
+#endif
diff --git a/picotlsvs/picotls-esni/picotls-esni.vcxproj b/picotlsvs/picotls-esni/picotls-esni.vcxproj
new file mode 100644
index 0000000..9e4678f
--- /dev/null
+++ b/picotlsvs/picotls-esni/picotls-esni.vcxproj
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\src\esni.c" />
+    <ClCompile Include="getopt.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="getopt.h" />
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <VCProjectVersion>15.0</VCProjectVersion>
+    <ProjectGuid>{592127C5-DD8C-47ED-8EBA-026B5848C374}</ProjectGuid>
+    <Keyword>Win32Proj</Keyword>
+    <RootNamespace>picotlsesni</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v141</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v141</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v141</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v141</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <LinkIncremental>true</LinkIncremental>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <LinkIncremental>true</LinkIncremental>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <LinkIncremental>false</LinkIncremental>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <LinkIncremental>false</LinkIncremental>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>_DEBUG;_CONSOLE;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <AdditionalIncludeDirectories>$(ProjectDir)..\..\include;$(OPENSSL64DIR)\include;$(ProjectDir)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>$(OPENSSL64DIR);$(OPENSSL64DIR)\lib;$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>picotls.lib;libcrypto.lib;libssl.lib;microecc.lib;cifra.lib;bcrypt.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <AdditionalIncludeDirectories>$(OPENSSLDIR)\include;$(ProjectDir)..\..\include;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>$(OPENSSLDIR);$(OPENSSLDIR)\lib;$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>picotls.lib;libcrypto.lib;libssl.lib;microecc.lib;cifra.lib;bcrypt.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <AdditionalIncludeDirectories>$(OPENSSLDIR)\include;$(ProjectDir)..\..\include;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>$(OPENSSLDIR);$(OPENSSLDIR)\lib;$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>picotls.lib;libcrypto.lib;libssl.lib;microecc.lib;cifra.lib;bcrypt.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <SDLCheck>true</SDLCheck>
+      <PreprocessorDefinitions>NDEBUG;_CONSOLE;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ConformanceMode>true</ConformanceMode>
+      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <AdditionalIncludeDirectories>$(ProjectDir)..\..\include;$(OPENSSL64DIR)\include;$(ProjectDir)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>$(OPENSSL64DIR);$(OPENSSL64DIR)\lib;$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>picotls.lib;libcrypto.lib;libssl.lib;microecc.lib;cifra.lib;bcrypt.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/picotlsvs/picotls-esni/picotls-esni.vcxproj.filters b/picotlsvs/picotls-esni/picotls-esni.vcxproj.filters
new file mode 100644
index 0000000..2ad14fc
--- /dev/null
+++ b/picotlsvs/picotls-esni/picotls-esni.vcxproj.filters
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
+    </Filter>
+    <Filter Include="Resource Files">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\src\esni.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="getopt.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="getopt.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/picotlsvs/picotls-esni/picotls-esni.vcxproj.user b/picotlsvs/picotls-esni/picotls-esni.vcxproj.user
new file mode 100644
index 0000000..1bac74a
--- /dev/null
+++ b/picotlsvs/picotls-esni/picotls-esni.vcxproj.user
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <LocalDebuggerCommandArguments>-K ..\..\..\..\picoquic\certs\esni-secp256r1.key -a rr.txt &gt; rr.bin</LocalDebuggerCommandArguments>
+    <LocalDebuggerWorkingDirectory>$(OutDir)</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/picotlsvs/picotls/wincompat.h b/picotlsvs/picotls/wincompat.h
index 4a7b2f5..8c9c084 100644
--- a/picotlsvs/picotls/wincompat.h
+++ b/picotlsvs/picotls/wincompat.h
@@ -14,24 +14,24 @@
 #define __attribute__(X)
 #endif
 
-#ifdef  __cplusplus
+#ifdef __cplusplus
 extern "C" {
 #endif
-	struct timezone {
-		int tz_minuteswest;     /* minutes west of Greenwich */
-		int tz_dsttime;         /* type of DST correction */
-	};
+struct timezone {
+    int tz_minuteswest; /* minutes west of Greenwich */
+    int tz_dsttime;     /* type of DST correction */
+};
 
-    int wintimeofday(struct timeval* tv, struct timezone* tz);
+int wintimeofday(struct timeval *tv, struct timezone *tz);
 
-#ifdef  __cplusplus
+#ifndef strcasecmp
+#define strcasecmp _stricmp
+#endif
+
+#ifdef __cplusplus
 } /* extern "C" */
 #endif
 
-
-
-
 #endif
 
-
 #endif /* WINCOMPAT_H */
\ No newline at end of file
diff --git a/picotlsvs/picotlsvs.sln b/picotlsvs/picotlsvs.sln
index e812b84..39a4171 100644
--- a/picotlsvs/picotlsvs.sln
+++ b/picotlsvs/picotlsvs.sln
@@ -30,6 +30,13 @@
 		{3440FDEA-84D2-4424-BB19-B4B26A6ADC8A} = {3440FDEA-84D2-4424-BB19-B4B26A6ADC8A}
 	EndProjectSection
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "picotls-esni", "picotls-esni\picotls-esni.vcxproj", "{592127C5-DD8C-47ED-8EBA-026B5848C374}"
+	ProjectSection(ProjectDependencies) = postProject
+		{5D4DA3A3-7851-4CAE-AE4F-C421A2C8C440} = {5D4DA3A3-7851-4CAE-AE4F-C421A2C8C440}
+		{46E6D6E9-7A30-4058-9661-DF70CC07E821} = {46E6D6E9-7A30-4058-9661-DF70CC07E821}
+		{3440FDEA-84D2-4424-BB19-B4B26A6ADC8A} = {3440FDEA-84D2-4424-BB19-B4B26A6ADC8A}
+	EndProjectSection
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|x64 = Debug|x64
@@ -86,6 +93,14 @@
 		{15D7D32F-3B62-4B10-A06A-BA1ADD591D7F}.Release|x64.Build.0 = Release|x64
 		{15D7D32F-3B62-4B10-A06A-BA1ADD591D7F}.Release|x86.ActiveCfg = Release|Win32
 		{15D7D32F-3B62-4B10-A06A-BA1ADD591D7F}.Release|x86.Build.0 = Release|Win32
+		{592127C5-DD8C-47ED-8EBA-026B5848C374}.Debug|x64.ActiveCfg = Debug|x64
+		{592127C5-DD8C-47ED-8EBA-026B5848C374}.Debug|x64.Build.0 = Debug|x64
+		{592127C5-DD8C-47ED-8EBA-026B5848C374}.Debug|x86.ActiveCfg = Debug|Win32
+		{592127C5-DD8C-47ED-8EBA-026B5848C374}.Debug|x86.Build.0 = Debug|Win32
+		{592127C5-DD8C-47ED-8EBA-026B5848C374}.Release|x64.ActiveCfg = Release|x64
+		{592127C5-DD8C-47ED-8EBA-026B5848C374}.Release|x64.Build.0 = Release|x64
+		{592127C5-DD8C-47ED-8EBA-026B5848C374}.Release|x86.ActiveCfg = Release|Win32
+		{592127C5-DD8C-47ED-8EBA-026B5848C374}.Release|x86.Build.0 = Release|Win32
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/src/esni.c b/src/esni.c
index 76f47f4..5c5c047 100644
--- a/src/esni.c
+++ b/src/esni.c
@@ -24,7 +24,15 @@
 #include <inttypes.h>
 #include <stdio.h>
 #include <string.h>
+#ifdef _WINDOWS
+#include "..\picotls\wincompat.h"
+#ifndef _CRT_SECURE_NO_WARNINGS
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#pragma warning(disable : 4996)
+#else
 #include <strings.h>
+#endif
 #include <time.h>
 #include <openssl/err.h>
 #include <openssl/engine.h>
@@ -34,7 +42,7 @@
 #include "picotls/openssl.h"
 
 static int emit_esni(ptls_key_exchange_context_t **key_exchanges, ptls_cipher_suite_t **cipher_suites, uint16_t padded_length,
-                     uint64_t not_before, uint64_t lifetime)
+                     uint64_t not_before, uint64_t lifetime, char const *published_sni, char const *file_output)
 {
     ptls_buffer_t buf;
     ptls_key_exchange_context_t *ctx[256] = {NULL};
@@ -42,8 +50,13 @@
 
     ptls_buffer_init(&buf, "", 0);
 
-    ptls_buffer_push16(&buf, PTLS_ESNI_VERSION_DRAFT02);
+    ptls_buffer_push16(&buf, PTLS_ESNI_VERSION_DRAFT03);
     ptls_buffer_push(&buf, 0, 0, 0, 0); /* checksum, filled later */
+    if (published_sni != NULL) {
+        ptls_buffer_push_block(&buf, 2, { ptls_buffer_pushv(&buf, published_sni, strlen(published_sni)); });
+    } else {
+        ptls_buffer_push16(&buf, 0);
+    }
     ptls_buffer_push_block(&buf, 2, {
         size_t i;
         for (i = 0; key_exchanges[i] != NULL; ++i) {
@@ -67,9 +80,20 @@
         memcpy(buf.base + 2, d, 4);
     }
 
-    /* emit the structure to stdout */
-    fwrite(buf.base, 1, buf.off, stdout);
-    fflush(stdout);
+    if (file_output != NULL) {
+        FILE *fo = fopen(file_output, "wb");
+        if (fo == NULL) {
+            fprintf(stderr, "failed to open file:%s:%s\n", optarg, strerror(errno));
+            goto Exit;
+        } else {
+            fwrite(buf.base, 1, buf.off, fo);
+            fclose(fo);
+        }
+    } else {
+        /* emit the structure to stdout */
+        fwrite(buf.base, 1, buf.off, stdout);
+        fflush(stdout);
+    }
 
     ret = 0;
 Exit : {
@@ -87,11 +111,14 @@
            "\n"
            "Usage: %s [options]\n"
            "Options:\n"
+           "  -n <published-sni>  published sni value\n"
            "  -K <key-file>       private key files (repeat the option to include multiple\n"
            "                      keys)\n"
            "  -c <cipher-suite>   aes128-gcm, chacha20-poly1305, ...\n"
            "  -d <days>           number of days until expiration (default: 90)\n"
            "  -p <padded-length>  padded length (default: 260)\n"
+           "  -o <output-file>    write output to specified file instead of stdout\n"
+           "                      (use on Windows as stdout is not binary there)\n"
            "  -h                  prints this help\n"
            "\n"
            "-c and -x can be used multiple times.\n"
@@ -102,6 +129,8 @@
 
 int main(int argc, char **argv)
 {
+    char const *published_sni = NULL;
+    char const *file_output = NULL;
     ERR_load_crypto_strings();
     OpenSSL_add_all_algorithms();
 #if !defined(OPENSSL_NO_ENGINE)
@@ -123,15 +152,21 @@
     uint64_t lifetime = 90 * 86400;
 
     int ch;
-    while ((ch = getopt(argc, argv, "K:c:d:p:h")) != -1) {
+
+    while ((ch = getopt(argc, argv, "n:K:c:d:p:o:h")) != -1) {
         switch (ch) {
+        case 'n':
+            published_sni = optarg;
+            break;
         case 'K': {
             FILE *fp;
             EVP_PKEY *pkey;
+
             if ((fp = fopen(optarg, "rt")) == NULL) {
                 fprintf(stderr, "failed to open file:%s:%s\n", optarg, strerror(errno));
                 exit(1);
             }
+
             if ((pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL)) == NULL) {
                 fprintf(stderr, "failed to read private key from file:%s:%s\n", optarg, strerror(errno));
                 exit(1);
@@ -162,10 +197,20 @@
             lifetime *= 86400; /* convert to seconds */
             break;
         case 'p':
+#ifdef _WINDOWS
+            if (sscanf_s(optarg, "%" SCNu16, &padded_length) != 1 || padded_length == 0) {
+                fprintf(stderr, "padded length must be a positive integer\n");
+                exit(1);
+            }
+#else
             if (sscanf(optarg, "%" SCNu16, &padded_length) != 1 || padded_length == 0) {
                 fprintf(stderr, "padded length must be a positive integer\n");
                 exit(1);
             }
+#endif
+            break;
+        case 'o':
+            file_output = optarg;
             break;
         case 'h':
             usage(argv[0], 0);
@@ -185,7 +230,8 @@
     argc -= optind;
     argv += optind;
 
-    if (emit_esni(key_exchanges.elements, cipher_suites.elements, padded_length, time(NULL), lifetime) != 0) {
+    if (emit_esni(key_exchanges.elements, cipher_suites.elements, padded_length, time(NULL), lifetime, published_sni,
+                  file_output) != 0) {
         fprintf(stderr, "failed to generate ESNI private structure.\n");
         exit(1);
     }
diff --git a/t/test.h b/t/test.h
index f1d95ba..0d35832 100644
--- a/t/test.h
+++ b/t/test.h
@@ -53,10 +53,12 @@
 
 /* secp256r1 key that lasts until 2028 */
 #define ESNIKEYS                                                                                                                   \
-    "\xff\x01\xba\xd5\xad\xa2\x00\x45\x00\x17\x00\x41\x04\x3e\xee\xf7\x10\xe3\x75\x07\xa8\xfb\x3e\xfc\x62\x50\x24\x95\xa0\x61\x6e" \
-    "\xff\x6b\x63\x0f\xa3\xfd\xcc\x33\x36\xd0\xb1\x2d\x55\xba\xb0\x06\xbd\xb4\x29\x82\xc6\xd9\xee\x66\x84\xa9\x63\x94\x44\xbe\x04" \
-    "\xe7\xee\xcf\xab\xc2\xc9\xdd\x40\xe6\xc8\x89\x88\xed\x94\x86\x00\x04\x13\x01\x13\x03\x01\x04\x00\x00\x00\x00\x5c\x13\x5e\xd2" \
-    "\x00\x00\x00\x00\x6e\xdf\x61\xd1\x00\x00"
+    "\xff\x02\xcf\x27\xde\x17\x00\x0b\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d\x00\x45"                                         \
+    "\x00\x17\x00\x41\x04\x3e\xee\xf7\x10\xe3\x75\x07\xa8\xfb\x3e\xfc\x62\x50\x24\x95\xa0"                                         \
+    "\x61\x6e\xff\x6b\x63\x0f\xa3\xfd\xcc\x33\x36\xd0\xb1\x2d\x55\xba\xb0\x06\xbd\xb4\x29"                                         \
+    "\x82\xc6\xd9\xee\x66\x84\xa9\x63\x94\x44\xbe\x04\xe7\xee\xcf\xab\xc2\xc9\xdd\x40\xe6"                                         \
+    "\xc8\x89\x88\xed\x94\x86\x00\x02\x13\x01\x01\x04\x00\x00\x00\x00\x5d\x1c\xc0\x63\x00"                                         \
+    "\x00\x4e\x94\xee\x6b\xc0\x62\x00\x00"
 #define ESNI_SECP256R1KEY                                                                                                          \
     "-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE "                         \
     "KEY-----\nMHcCAQEEIGrRVTfTXuOVewLt/g+Ugvg9XW/g4lGXrkZ8fdYaYuJCoAoGCCqGSM49\nAwEHoUQDQgAEPu73EON1B6j7PvxiUCSVoGFu/"            \