From 40331c131e1552e41876a9096eb4821eddffe5ac Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Tue, 26 May 2020 13:39:41 +0900
Subject: [PATCH] Implement NEW_TOKEN

---
 examples/client.cc           |  64 +++++++-
 examples/client.h            |   3 +
 examples/server.cc           | 300 ++++++++++++++++++++++++++++++-----
 examples/server.h            |  12 +-
 lib/includes/ngtcp2/ngtcp2.h |  55 ++++++-
 lib/ngtcp2_conn.c            |  76 +++++++--
 lib/ngtcp2_conn.h            |   2 -
 lib/ngtcp2_log.c             |  11 +-
 lib/ngtcp2_pkt.c             |  16 +-
 lib/ngtcp2_pkt.h             |   3 +-
 lib/ngtcp2_qlog.c            |  12 +-
 tests/main.c                 |   2 +
 tests/ngtcp2_conn_test.c     |  47 ++++++
 tests/ngtcp2_conn_test.h     |   1 +
 tests/ngtcp2_pkt_test.c      |  10 +-
 15 files changed, 520 insertions(+), 94 deletions(-)

diff --git a/examples/client.cc b/examples/client.cc
index 27ed356a..caf6f3e0 100644
--- a/examples/client.cc
+++ b/examples/client.cc
@@ -736,6 +736,22 @@ int Client::extend_max_stream_data(int64_t stream_id, uint64_t max_data) {
   return 0;
 }
 
+namespace {
+int recv_new_token(ngtcp2_conn *conn, const ngtcp2_vec *token,
+                   void *user_data) {
+  auto f = BIO_new_file(config.token_file.data(), "w");
+  if (f == nullptr) {
+    std::cerr << "Could not write token in " << config.token_file << std::endl;
+    return 0;
+  }
+
+  PEM_write_bio(f, "QUIC TOKEN", "", token->base, token->len);
+  BIO_free(f);
+
+  return 0;
+}
+} // namespace
+
 int Client::init_ssl() {
   if (ssl_) {
     SSL_free(ssl_);
@@ -853,6 +869,9 @@ int Client::init(int fd, const Address &local_addr, const Address &remote_addr,
       nullptr, // extend_max_remote_streams_bidi,
       nullptr, // extend_max_remote_streams_uni,
       ::extend_max_stream_data,
+      nullptr, // dcid_status
+      nullptr, // handshake_confirmed
+      ::recv_new_token,
   };
 
   auto dis = std::uniform_int_distribution<uint8_t>(
@@ -896,6 +915,32 @@ int Client::init(int fd, const Address &local_addr, const Address &remote_addr,
   settings.cc_algo =
       config.cc == "cubic" ? NGTCP2_CC_ALGO_CUBIC : NGTCP2_CC_ALGO_RENO;
   settings.initial_ts = util::timestamp(loop_);
+
+  if (!config.token_file.empty()) {
+    std::cerr << "Reading token file " << config.token_file << std::endl;
+    auto f = BIO_new_file(config.token_file.data(), "r");
+    if (f == nullptr) {
+      std::cerr << "Could not open token file " << config.token_file
+                << std::endl;
+    } else {
+      char *name, *header;
+      unsigned char *data;
+      long datalen;
+      if (PEM_read_bio(f, &name, &header, &data, &datalen) != 1) {
+        std::cerr << "Could not read token file " << config.token_file
+                  << std::endl;
+      } else {
+        settings.token.base = data;
+        settings.token.len = datalen;
+
+        OPENSSL_free(name);
+        OPENSSL_free(header);
+      }
+
+      BIO_free(f);
+    }
+  }
+
   auto &params = settings.transport_params;
   params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local;
   params.initial_max_stream_data_bidi_remote =
@@ -912,9 +957,14 @@ int Client::init(int fd, const Address &local_addr, const Address &remote_addr,
                            reinterpret_cast<const uint8_t *>(&local_addr.su))},
       {remote_addr.len, const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(
                             &remote_addr.su))}};
-  if (auto rv = ngtcp2_conn_client_new(&conn_, &dcid, &scid, &path, version,
-                                       &callbacks, &settings, nullptr, this);
-      rv != 0) {
+  auto rv = ngtcp2_conn_client_new(&conn_, &dcid, &scid, &path, version,
+                                   &callbacks, &settings, nullptr, this);
+
+  if (settings.token.base) {
+    OPENSSL_free(settings.token.base);
+  }
+
+  if (rv != 0) {
     std::cerr << "ngtcp2_conn_client_new: " << ngtcp2_strerror(rv) << std::endl;
     return -1;
   }
@@ -2422,6 +2472,9 @@ Options:
               Disable early data.
   --cc=(<cubic>|<reno>)
               The name of congestion controller algorithm.
+  --token-file=<PATH>
+              Read/write token from/to <PATH>.  Token is obtained from
+              NEW_TOKEN frame from server.
   -h, --help  Display this help and exit.
 
 ---
@@ -2483,6 +2536,7 @@ int main(int argc, char **argv) {
         {"qlog-dir", required_argument, &flag, 26},
         {"cc", required_argument, &flag, 27},
         {"exit-on-all-streams-close", no_argument, &flag, 28},
+        {"token-file", required_argument, &flag, 29},
         {nullptr, 0, nullptr, 0},
     };
 
@@ -2700,6 +2754,10 @@ int main(int argc, char **argv) {
         // --exit-on-all-streams-close
         config.exit_on_all_streams_close = true;
         break;
+      case 29:
+        // --token-file
+        config.token_file = optarg;
+        break;
       }
       break;
     default:
diff --git a/examples/client.h b/examples/client.h
index f86bd585..e84434ad 100644
--- a/examples/client.h
+++ b/examples/client.h
@@ -151,6 +151,9 @@ struct Config {
   std::array<uint8_t, 32> static_secret;
   // cc is the congestion controller algorithm.
   std::string_view cc;
+  // token_file is a path to file to read or write token from
+  // NEW_TOKEN frame.
+  std::string_view token_file;
 };
 
 struct Buffer {
diff --git a/examples/server.cc b/examples/server.cc
index a101ca0c..e1539a07 100644
--- a/examples/server.cc
+++ b/examples/server.cc
@@ -82,6 +82,21 @@ namespace {
 auto randgen = util::make_mt19937();
 } // namespace
 
+namespace {
+// RETRY_TOKEN_MAGIC is the magic byte of Retry token.  Sent in
+// plaintext.
+constexpr uint8_t RETRY_TOKEN_MAGIC = 0xb6;
+constexpr size_t MAX_RETRY_TOKENLEN =
+    /* magic */ 1 + sizeof(uint64_t) + NGTCP2_MAX_CIDLEN +
+    /* aead tag */ 16 + TOKEN_RAND_DATALEN;
+
+// TOKEN_MAGIC is the magic byte of token which is sent in NEW_TOKEN
+// frame.  Sent in plaintext.
+constexpr uint8_t TOKEN_MAGIC = 0x36;
+constexpr size_t MAX_TOKENLEN =
+    /* magic */ 1 + sizeof(uint64_t) + /* aead tag */ 16 + TOKEN_RAND_DATALEN;
+} // namespace
+
 namespace {
 Config config{};
 } // namespace
@@ -807,6 +822,27 @@ int Handler::handshake_completed() {
     }
   }
 
+  std::array<uint8_t, MAX_TOKENLEN> tokenbuf;
+  size_t tokenlen = tokenbuf.size();
+
+  if (server_->generate_token(tokenbuf.data(), tokenlen, &remote_addr_.su.sa,
+                              remote_addr_.len) != 0) {
+    if (!config.quiet) {
+      std::cerr << "Unable to generate token" << std::endl;
+    }
+    return 0;
+  }
+
+  ngtcp2_vec token{tokenbuf.data(), tokenlen};
+
+  if (auto rv = ngtcp2_conn_submit_new_token(conn_, &token); rv != 0) {
+    if (!config.quiet) {
+      std::cerr << "ngtcp2_conn_submit_new_token: " << ngtcp2_strerror(rv)
+                << std::endl;
+    }
+    return -1;
+  }
+
   return 0;
 }
 
@@ -2353,11 +2389,39 @@ int Server::on_read(Endpoint &ep) {
               send_retry(&hd, ep, &su.sa, addrlen);
               continue;
             }
-            if (verify_token(&ocid, &hd, &su.sa, addrlen) != 0) {
-              send_stateless_connection_close(&hd, ep, &su.sa, addrlen);
-              continue;
+
+            switch (hd.token[0]) {
+            case RETRY_TOKEN_MAGIC:
+              if (verify_retry_token(&ocid, &hd, &su.sa, addrlen) != 0) {
+                send_stateless_connection_close(&hd, ep, &su.sa, addrlen);
+                continue;
+              }
+              pocid = &ocid;
+              break;
+            case TOKEN_MAGIC:
+              if (verify_token(&hd, &su.sa, addrlen) != 0) {
+                if (config.validate_addr) {
+                  send_retry(&hd, ep, &su.sa, addrlen);
+                  continue;
+                }
+
+                hd.token = nullptr;
+                hd.tokenlen = 0;
+              }
+              break;
+            default:
+              if (!config.quiet) {
+                std::cerr << "Ignore unrecognized token" << std::endl;
+              }
+              if (config.validate_addr) {
+                send_retry(&hd, ep, &su.sa, addrlen);
+                continue;
+              }
+
+              hd.token = nullptr;
+              hd.tokenlen = 0;
+              break;
             }
-            pocid = &ocid;
           }
           break;
         case NGTCP2_PKT_0RTT:
@@ -2520,11 +2584,11 @@ int Server::send_retry(const ngtcp2_pkt_hd *chd, Endpoint &ep,
   std::generate(scid.data, scid.data + scid.datalen,
                 [&dis]() { return dis(randgen); });
 
-  std::array<uint8_t, 256> token;
+  std::array<uint8_t, MAX_RETRY_TOKENLEN> token;
   size_t tokenlen = token.size();
 
-  if (generate_token(token.data(), tokenlen, sa, salen, &scid, &chd->dcid) !=
-      0) {
+  if (generate_retry_token(token.data(), tokenlen, sa, salen, &scid,
+                           &chd->dcid) != 0) {
     return -1;
   }
 
@@ -2611,13 +2675,10 @@ void Server::generate_rand_data(uint8_t *buf, size_t len) {
   std::generate_n(buf, len, [&dis]() { return dis(randgen); });
 }
 
-// RETRY_TOKEN_MAGIC is the magic byte of Retry token.  Sent in
-// plaintext.
-constexpr uint8_t RETRY_TOKEN_MAGIC = 0xb6;
-
 namespace {
-size_t generate_token_aad(uint8_t *dest, size_t destlen, const sockaddr *sa,
-                          socklen_t salen, const ngtcp2_cid *retry_scid) {
+size_t generate_retry_token_aad(uint8_t *dest, size_t destlen,
+                                const sockaddr *sa, socklen_t salen,
+                                const ngtcp2_cid *retry_scid) {
   assert(destlen >= salen + retry_scid->datalen);
 
   auto p = std::copy_n(reinterpret_cast<const uint8_t *>(sa), salen, dest);
@@ -2627,9 +2688,10 @@ size_t generate_token_aad(uint8_t *dest, size_t destlen, const sockaddr *sa,
 }
 } // namespace
 
-int Server::generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
-                           socklen_t salen, const ngtcp2_cid *retry_scid,
-                           const ngtcp2_cid *ocid) {
+int Server::generate_retry_token(uint8_t *token, size_t &tokenlen,
+                                 const sockaddr *sa, socklen_t salen,
+                                 const ngtcp2_cid *retry_scid,
+                                 const ngtcp2_cid *ocid) {
   std::array<uint8_t, 4096> plaintext;
 
   uint64_t t = std::chrono::duration_cast<std::chrono::nanoseconds>(
@@ -2637,7 +2699,6 @@ int Server::generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
                    .count();
 
   auto p = std::begin(plaintext);
-  p = std::copy_n(reinterpret_cast<const uint8_t *>(sa), salen, p);
   // Host byte order
   p = std::copy_n(reinterpret_cast<uint8_t *>(&t), sizeof(t), p);
   p = std::copy_n(ocid->data, ocid->datalen, p);
@@ -2657,7 +2718,7 @@ int Server::generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
 
   std::array<uint8_t, 256> aad;
   auto aadlen =
-      generate_token_aad(aad.data(), aad.size(), sa, salen, retry_scid);
+      generate_retry_token_aad(aad.data(), aad.size(), sa, salen, retry_scid);
 
   token[0] = RETRY_TOKEN_MAGIC;
   if (ngtcp2_crypto_encrypt(token + 1, &token_aead_, plaintext.data(),
@@ -2674,8 +2735,8 @@ int Server::generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
   return 0;
 }
 
-int Server::verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
-                         const sockaddr *sa, socklen_t salen) {
+int Server::verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
+                               const sockaddr *sa, socklen_t salen) {
   std::array<char, NI_MAXHOST> host;
   std::array<char, NI_MAXSERV> port;
 
@@ -2687,12 +2748,8 @@ int Server::verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
   }
 
   if (!config.quiet) {
-    std::cerr << "Verifying token from [" << host.data() << "]:" << port.data()
-              << std::endl;
-  }
-
-  if (!config.quiet) {
-    std::cerr << "Received address validation token:" << std::endl;
+    std::cerr << "Verifying Retry token from [" << host.data()
+              << "]:" << port.data() << std::endl;
     util::hexdump(stderr, hd->token, hd->tokenlen);
   }
 
@@ -2703,14 +2760,15 @@ int Server::verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
     }
     return -1;
   }
-
-  if (hd->token[0] != RETRY_TOKEN_MAGIC) {
+  if (hd->tokenlen > MAX_RETRY_TOKENLEN) {
     if (!config.quiet) {
-      std::cerr << "Token has invalid magic" << std::endl;
+      std::cerr << "Token is too long" << std::endl;
     }
     return -1;
   }
 
+  assert(hd->token[0] == RETRY_TOKEN_MAGIC);
+
   auto rand_data = hd->token + hd->tokenlen - TOKEN_RAND_DATALEN;
   auto ciphertext = hd->token + 1;
   auto ciphertextlen = hd->tokenlen - TOKEN_RAND_DATALEN - 1;
@@ -2726,9 +2784,9 @@ int Server::verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
 
   std::array<uint8_t, 256> aad;
   auto aadlen =
-      generate_token_aad(aad.data(), aad.size(), sa, salen, &hd->dcid);
+      generate_retry_token_aad(aad.data(), aad.size(), sa, salen, &hd->dcid);
 
-  std::array<uint8_t, 4096> plaintext;
+  std::array<uint8_t, MAX_RETRY_TOKENLEN> plaintext;
 
   if (ngtcp2_crypto_decrypt(plaintext.data(), &token_aead_, ciphertext,
                             ciphertextlen, key.data(), iv.data(), ivlen,
@@ -2742,14 +2800,14 @@ int Server::verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
   assert(ciphertextlen >= ngtcp2_crypto_aead_taglen(&token_aead_));
 
   auto plaintextlen = ciphertextlen - ngtcp2_crypto_aead_taglen(&token_aead_);
-  if (plaintextlen < salen + sizeof(uint64_t)) {
+  if (plaintextlen < sizeof(uint64_t)) {
     if (!config.quiet) {
       std::cerr << "Bad token construction" << std::endl;
     }
     return -1;
   }
 
-  auto cil = plaintextlen - salen - sizeof(uint64_t);
+  auto cil = plaintextlen - sizeof(uint64_t);
   if (cil != 0 && (cil < NGTCP2_MIN_CIDLEN || cil > NGTCP2_MAX_CIDLEN)) {
     if (!config.quiet) {
       std::cerr << "Bad token construction" << std::endl;
@@ -2757,29 +2815,193 @@ int Server::verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
     return -1;
   }
 
-  if (memcmp(plaintext.data(), sa, salen) != 0) {
+  uint64_t t;
+  memcpy(&t, plaintext.data(), sizeof(uint64_t));
+
+  uint64_t now = std::chrono::duration_cast<std::chrono::nanoseconds>(
+                     std::chrono::system_clock::now().time_since_epoch())
+                     .count();
+
+  // Allow 10 seconds window
+  if (t + 10ULL * NGTCP2_SECONDS < now) {
+    if (!config.quiet) {
+      std::cerr << "Token has been expired" << std::endl;
+    }
+    return -1;
+  }
+
+  ngtcp2_cid_init(ocid, plaintext.data() + sizeof(uint64_t), cil);
+
+  if (!config.quiet) {
+    std::cerr << "Token was successfully validated" << std::endl;
+  }
+
+  return 0;
+}
+
+namespace {
+size_t generate_token_aad(uint8_t *dest, size_t destlen, const sockaddr *sa,
+                          socklen_t salen) {
+  const uint8_t *addr;
+  size_t addrlen;
+
+  switch (sa->sa_family) {
+  case AF_INET:
+    addr = reinterpret_cast<const uint8_t *>(
+        &reinterpret_cast<const sockaddr_in *>(sa)->sin_addr);
+    addrlen = sizeof(reinterpret_cast<const sockaddr_in *>(sa)->sin_addr);
+    break;
+  case AF_INET6:
+    addr = reinterpret_cast<const uint8_t *>(
+        &reinterpret_cast<const sockaddr_in6 *>(sa)->sin6_addr);
+    addrlen = sizeof(reinterpret_cast<const sockaddr_in6 *>(sa)->sin6_addr);
+    break;
+  default:
+    if (!config.quiet) {
+      std::cerr << "Unknown address family 0x" << std::hex << sa->sa_family
+                << std::dec << std::endl;
+    }
+    return -1;
+  }
+
+  assert(destlen >= addrlen);
+
+  return std::copy_n(addr, addrlen, dest) - dest;
+}
+} // namespace
+
+int Server::generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
+                           socklen_t salen) {
+  std::array<uint8_t, 8> plaintext;
+
+  uint64_t t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+                   std::chrono::system_clock::now().time_since_epoch())
+                   .count();
+
+  std::array<uint8_t, 256> aad;
+  auto aadlen = generate_token_aad(aad.data(), aad.size(), sa, salen);
+
+  auto p = std::begin(plaintext);
+  // Host byte order
+  p = std::copy_n(reinterpret_cast<uint8_t *>(&t), sizeof(t), p);
+
+  std::array<uint8_t, TOKEN_RAND_DATALEN> rand_data;
+  std::array<uint8_t, 32> key, iv;
+  auto keylen = key.size();
+  auto ivlen = iv.size();
+
+  generate_rand_data(rand_data.data(), rand_data.size());
+  if (derive_token_key(key.data(), keylen, iv.data(), ivlen, rand_data.data(),
+                       rand_data.size()) != 0) {
+    return -1;
+  }
+
+  auto plaintextlen = std::distance(std::begin(plaintext), p);
+
+  token[0] = TOKEN_MAGIC;
+  if (ngtcp2_crypto_encrypt(token + 1, &token_aead_, plaintext.data(),
+                            plaintextlen, key.data(), iv.data(), ivlen,
+                            aad.data(), aadlen) != 0) {
+    return -1;
+  }
+
+  /* 1 for magic byte */
+  tokenlen = 1 + plaintextlen + ngtcp2_crypto_aead_taglen(&token_aead_);
+  memcpy(token + tokenlen, rand_data.data(), rand_data.size());
+  tokenlen += rand_data.size();
+
+  return 0;
+}
+
+int Server::verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa,
+                         socklen_t salen) {
+  std::array<char, NI_MAXHOST> host;
+  std::array<char, NI_MAXSERV> port;
+
+  if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
+                            port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+      rv != 0) {
+    std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+    return -1;
+  }
+
+  if (!config.quiet) {
+    std::cerr << "Verifying token from [" << host.data() << "]:" << port.data()
+              << std::endl;
+    util::hexdump(stderr, hd->token, hd->tokenlen);
+  }
+
+  /* 1 for TOKEN_MAGIC */
+  if (hd->tokenlen < TOKEN_RAND_DATALEN + 1) {
+    if (!config.quiet) {
+      std::cerr << "Token is too short" << std::endl;
+    }
+    return -1;
+  }
+  if (hd->tokenlen > MAX_TOKENLEN) {
+    if (!config.quiet) {
+      std::cerr << "Token is too long" << std::endl;
+    }
+    return -1;
+  }
+
+  assert(hd->token[0] == TOKEN_MAGIC);
+
+  std::array<uint8_t, 256> aad;
+  auto aadlen = generate_token_aad(aad.data(), aad.size(), sa, salen);
+
+  auto rand_data = hd->token + hd->tokenlen - TOKEN_RAND_DATALEN;
+  auto ciphertext = hd->token + 1;
+  auto ciphertextlen = hd->tokenlen - TOKEN_RAND_DATALEN - 1;
+
+  std::array<uint8_t, 32> key, iv;
+  auto keylen = key.size();
+  auto ivlen = iv.size();
+
+  if (derive_token_key(key.data(), keylen, iv.data(), ivlen, rand_data,
+                       TOKEN_RAND_DATALEN) != 0) {
+    return -1;
+  }
+
+  std::array<uint8_t, MAX_TOKENLEN> plaintext;
+
+  if (ngtcp2_crypto_decrypt(plaintext.data(), &token_aead_, ciphertext,
+                            ciphertextlen, key.data(), iv.data(), ivlen,
+                            aad.data(), aadlen) != 0) {
     if (!config.quiet) {
-      std::cerr << "Client address does not match" << std::endl;
+      std::cerr << "Could not decrypt token" << std::endl;
+    }
+    return -1;
+  }
+
+  assert(ciphertextlen >= ngtcp2_crypto_aead_taglen(&token_aead_));
+
+  auto plaintextlen = ciphertextlen - ngtcp2_crypto_aead_taglen(&token_aead_);
+  if (plaintextlen != sizeof(uint64_t)) {
+    if (!config.quiet) {
+      std::cerr << "Bad token construction" << std::endl;
     }
     return -1;
   }
 
   uint64_t t;
-  memcpy(&t, plaintext.data() + salen, sizeof(uint64_t));
+  memcpy(&t, plaintext.data(), sizeof(uint64_t));
 
   uint64_t now = std::chrono::duration_cast<std::chrono::nanoseconds>(
                      std::chrono::system_clock::now().time_since_epoch())
                      .count();
 
-  // Allow 10 seconds window
-  if (t + 10ULL * NGTCP2_SECONDS < now) {
+  // Allow 1 hour window
+  if (t + 3600ULL * NGTCP2_SECONDS < now) {
     if (!config.quiet) {
       std::cerr << "Token has been expired" << std::endl;
     }
     return -1;
   }
 
-  ngtcp2_cid_init(ocid, plaintext.data() + salen + sizeof(uint64_t), cil);
+  if (!config.quiet) {
+    std::cerr << "Token was successfully validated" << std::endl;
+  }
 
   return 0;
 }
diff --git a/examples/server.h b/examples/server.h
index 922ab1b5..710d3401 100644
--- a/examples/server.h
+++ b/examples/server.h
@@ -342,11 +342,15 @@ public:
                  socklen_t salen);
   int send_stateless_connection_close(const ngtcp2_pkt_hd *chd, Endpoint &ep,
                                       const sockaddr *sa, socklen_t salen);
+  int generate_retry_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
+                           socklen_t salen, const ngtcp2_cid *scid,
+                           const ngtcp2_cid *ocid);
+  int verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
+                         const sockaddr *sa, socklen_t salen);
   int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
-                     socklen_t salen, const ngtcp2_cid *scid,
-                     const ngtcp2_cid *ocid);
-  int verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
-                   const sockaddr *sa, socklen_t salen);
+                     socklen_t salen);
+  int verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa,
+                   socklen_t salen);
   int send_packet(Endpoint &ep, const Address &remote_addr, const uint8_t *data,
                   size_t datalen, size_t gso_size, ev_io *wev = nullptr);
   void remove(const Handler *h);
diff --git a/lib/includes/ngtcp2/ngtcp2.h b/lib/includes/ngtcp2/ngtcp2.h
index 4034aa80..c9e29822 100644
--- a/lib/includes/ngtcp2/ngtcp2.h
+++ b/lib/includes/ngtcp2/ngtcp2.h
@@ -713,10 +713,18 @@ typedef struct ngtcp2_settings {
      controller to compute congestion window.  If it is set to 0, it
      defaults to NGTCP2_DEFAULT_MAX_PKTLEN. */
   size_t max_udp_payload_size;
-  /* token is a token received in Client Initial packet and
-     successfully validated.  Only server application may specify this
-     field.  Server then verifies that all Client Initial packets have
-     this token.  `ngtcp2_conn_server_new` makes a copy of token. */
+  /**
+   * token is a token from Retry packet or NEW_TOKEN frame.
+   *
+   * Server sets this field if it received the token in Client Initial
+   * packet and successfully validated.
+   *
+   * Client sets this field if it intends to send token in its Initial
+   * packet.
+   *
+   * `ngtcp2_conn_server_new` and `ngtcp2_conn_client_new` make a copy
+   * of token.
+   */
   ngtcp2_vec token;
 } ngtcp2_settings;
 
@@ -1563,6 +1571,21 @@ typedef int (*ngtcp2_connection_id_status)(ngtcp2_conn *conn, int type,
                                            const uint8_t *token,
                                            void *user_data);
 
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_recv_new_token` is a callback function which is
+ * called when new token is received from server.
+ *
+ * |token| is the received token.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :enum:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_recv_new_token)(ngtcp2_conn *conn, const ngtcp2_vec *token,
+                                     void *user_data);
+
 typedef struct ngtcp2_conn_callbacks {
   /**
    * client_initial is a callback function which is invoked when
@@ -1744,6 +1767,11 @@ typedef struct ngtcp2_conn_callbacks {
    * handshake confirmation for server.
    */
   ngtcp2_handshake_confirmed handshake_confirmed;
+  /**
+   * recv_new_token is a callback function which is invoked when new
+   * token is received from server.  This field is ignored by server.
+   */
+  ngtcp2_recv_new_token recv_new_token;
 } ngtcp2_conn_callbacks;
 
 /**
@@ -2760,6 +2788,25 @@ ngtcp2_conn_submit_crypto_data(ngtcp2_conn *conn,
                                ngtcp2_crypto_level crypto_level,
                                const uint8_t *data, const size_t datalen);
 
+/**
+ * @function
+ *
+ * `ngtcp2_conn_submit_new_token` submits address validation token.
+ * It is sent in NEW_TOKEN frame.  Only server can call this function.
+ * |token| must not be empty.
+ *
+ * This function makes a copy of the buffer pointed by |token|->base
+ * of length |token|->len.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_submit_new_token(ngtcp2_conn *conn,
+                                               const ngtcp2_vec *token);
+
 /**
  * @function
  *
diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c
index 9479580c..2ab1d35f 100644
--- a/lib/ngtcp2_conn.c
+++ b/lib/ngtcp2_conn.c
@@ -659,7 +659,7 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid,
 
   (*pconn)->local.settings = *settings;
 
-  if (server && settings->token.len) {
+  if (settings->token.len) {
     buf = ngtcp2_mem_malloc(mem, settings->token.len);
     if (buf == NULL) {
       goto fail_token;
@@ -924,7 +924,6 @@ void ngtcp2_conn_del(ngtcp2_conn *conn) {
 
   ngtcp2_qlog_end(&conn->qlog);
 
-  ngtcp2_mem_free(conn->mem, conn->token.begin);
   ngtcp2_mem_free(conn->mem, conn->crypto.decrypt_buf.base);
   ngtcp2_mem_free(conn->mem, conn->local.settings.token.base);
 
@@ -1735,9 +1734,10 @@ static ngtcp2_ssize conn_write_handshake_pkt(ngtcp2_conn *conn, uint8_t *dest,
                      pktns->tx.last_pkt_num + 1, pktns_select_pkt_numlen(pktns),
                      conn->version, 0);
 
-  if (type == NGTCP2_PKT_INITIAL && ngtcp2_buf_len(&conn->token)) {
-    hd.token = conn->token.pos;
-    hd.tokenlen = ngtcp2_buf_len(&conn->token);
+  if (!conn->server && type == NGTCP2_PKT_INITIAL &&
+      conn->local.settings.token.len) {
+    hd.token = conn->local.settings.token.base;
+    hd.tokenlen = conn->local.settings.token.len;
   }
 
   ngtcp2_ppe_init(&ppe, dest, destlen, &cc);
@@ -3611,12 +3611,12 @@ static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
                          size_t hdpktlen, const uint8_t *pkt, size_t pktlen) {
   int rv;
   ngtcp2_pkt_retry retry;
-  uint8_t *p;
   ngtcp2_pktns *in_pktns = conn->in_pktns;
   ngtcp2_rtb *rtb = &conn->pktns.rtb;
   ngtcp2_rtb *in_rtb;
   uint8_t cidbuf[sizeof(retry.odcid.data) * 2 + 1];
   ngtcp2_frame_chain *frc = NULL;
+  ngtcp2_vec *token;
 
   if (!in_pktns || conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) {
     return 0;
@@ -3688,17 +3688,19 @@ static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
     return rv;
   }
 
-  assert(conn->token.begin == NULL);
+  token = &conn->local.settings.token;
+
+  ngtcp2_mem_free(conn->mem, token->base);
+  token->base = NULL;
+  token->len = 0;
 
-  p = ngtcp2_mem_malloc(conn->mem, retry.tokenlen);
-  if (p == NULL) {
+  token->base = ngtcp2_mem_malloc(conn->mem, retry.tokenlen);
+  if (token->base == NULL) {
     return NGTCP2_ERR_NOMEM;
   }
-  ngtcp2_buf_init(&conn->token, p, retry.tokenlen);
+  token->len = retry.tokenlen;
 
-  ngtcp2_cpymem(conn->token.begin, retry.token, retry.tokenlen);
-  conn->token.pos = conn->token.begin;
-  conn->token.last = conn->token.pos + retry.tokenlen;
+  ngtcp2_cpymem(token->base, retry.token, retry.tokenlen);
 
   conn_reset_congestion_state(conn);
 
@@ -5995,16 +5997,29 @@ static int conn_recv_retire_connection_id(ngtcp2_conn *conn,
  * This function returns 0 if it succeeds, or one of the following
  * negative error codes:
  *
- * NGTCP2_ERR_FRAME_ENCODING:
+ * NGTCP2_ERR_FRAME_ENCODING
  *     Token is empty
+ * NGTCP2_ERR_PROTO:
+ *     Server received NEW_TOKEN.
  */
 static int conn_recv_new_token(ngtcp2_conn *conn, const ngtcp2_new_token *fr) {
-  (void)conn;
+  int rv;
+
+  if (conn->server) {
+    return NGTCP2_ERR_PROTO;
+  }
 
-  if (fr->tokenlen == 0) {
+  if (fr->token.len == 0) {
     return NGTCP2_ERR_FRAME_ENCODING;
   }
-  /* TODO Not implemented yet*/
+
+  if (conn->callbacks.recv_new_token) {
+    rv = conn->callbacks.recv_new_token(conn, &fr->token, conn->user_data);
+    if (rv != 0) {
+      return NGTCP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+
   return 0;
 }
 
@@ -9237,6 +9252,33 @@ int ngtcp2_conn_submit_crypto_data(ngtcp2_conn *conn,
   return 0;
 }
 
+int ngtcp2_conn_submit_new_token(ngtcp2_conn *conn, const ngtcp2_vec *token) {
+  int rv;
+  ngtcp2_frame_chain *nfrc;
+  uint8_t *p;
+
+  assert(conn->server);
+  assert(token->base);
+  assert(token->len);
+
+  rv = ngtcp2_frame_chain_extralen_new(&nfrc, token->len, conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  nfrc->fr.type = NGTCP2_FRAME_NEW_TOKEN;
+
+  p = (uint8_t *)nfrc + sizeof(*nfrc);
+  memcpy(p, token->base, token->len);
+
+  ngtcp2_vec_init(&nfrc->fr.new_token.token, p, token->len);
+
+  nfrc->next = conn->pktns.tx.frq;
+  conn->pktns.tx.frq = nfrc;
+
+  return 0;
+}
+
 ngtcp2_strm *ngtcp2_conn_tx_strmq_top(ngtcp2_conn *conn) {
   assert(!ngtcp2_pq_empty(&conn->tx.strmq));
   return ngtcp2_struct_of(ngtcp2_pq_top(&conn->tx.strmq), ngtcp2_strm, pe);
diff --git a/lib/ngtcp2_conn.h b/lib/ngtcp2_conn.h
index b8083853..720579bf 100644
--- a/lib/ngtcp2_conn.h
+++ b/lib/ngtcp2_conn.h
@@ -460,8 +460,6 @@ struct ngtcp2_conn {
   ngtcp2_rst rst;
   ngtcp2_cc_algo cc_algo;
   ngtcp2_cc cc;
-  /* token is an address validation token received from server. */
-  ngtcp2_buf token;
   const ngtcp2_mem *mem;
   /* idle_ts is the time instant when idle timer started. */
   ngtcp2_tstamp idle_ts;
diff --git a/lib/ngtcp2_log.c b/lib/ngtcp2_log.c
index 1b480f6f..8846d594 100644
--- a/lib/ngtcp2_log.c
+++ b/lib/ngtcp2_log.c
@@ -408,17 +408,16 @@ static void log_fr_new_token(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
   uint8_t buf[128 + 1 + 1];
   uint8_t *p;
 
-  if (fr->tokenlen > 64) {
-    p = ngtcp2_encode_hex(buf, fr->token, 64);
+  if (fr->token.len > 64) {
+    p = ngtcp2_encode_hex(buf, fr->token.base, 64);
     p[128] = '*';
     p[129] = '\0';
   } else {
-    p = ngtcp2_encode_hex(buf, fr->token, fr->tokenlen);
+    p = ngtcp2_encode_hex(buf, fr->token.base, fr->token.len);
   }
   log->log_printf(
-      log->user_data,
-      (NGTCP2_LOG_PKT " NEW_TOKEN(0x%02x) token=0x%s token_len=%zu"),
-      NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, (const char *)p, fr->tokenlen);
+      log->user_data, (NGTCP2_LOG_PKT " NEW_TOKEN(0x%02x) token=0x%s len=%zu"),
+      NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, (const char *)p, fr->token.len);
 }
 
 static void log_fr_retire_connection_id(ngtcp2_log *log,
diff --git a/lib/ngtcp2_pkt.c b/lib/ngtcp2_pkt.c
index 1a500a61..00d2d885 100644
--- a/lib/ngtcp2_pkt.c
+++ b/lib/ngtcp2_pkt.c
@@ -1324,10 +1324,10 @@ ngtcp2_ssize ngtcp2_pkt_decode_new_token_frame(ngtcp2_new_token *dest,
   len += datalen;
 
   dest->type = NGTCP2_FRAME_NEW_TOKEN;
-  dest->tokenlen = datalen;
+  dest->token.len = datalen;
   p += n;
-  dest->token = p;
-  p += dest->tokenlen;
+  dest->token.base = (uint8_t *)p;
+  p += dest->token.len;
 
   assert((size_t)(p - payload) == len);
 
@@ -1856,9 +1856,11 @@ ngtcp2_ssize ngtcp2_pkt_encode_crypto_frame(uint8_t *out, size_t outlen,
 
 ngtcp2_ssize ngtcp2_pkt_encode_new_token_frame(uint8_t *out, size_t outlen,
                                                const ngtcp2_new_token *fr) {
-  size_t len = 1 + ngtcp2_put_varint_len(fr->tokenlen) + fr->tokenlen;
+  size_t len = 1 + ngtcp2_put_varint_len(fr->token.len) + fr->token.len;
   uint8_t *p;
 
+  assert(fr->token.len);
+
   if (outlen < len) {
     return NGTCP2_ERR_NOBUF;
   }
@@ -1867,10 +1869,8 @@ ngtcp2_ssize ngtcp2_pkt_encode_new_token_frame(uint8_t *out, size_t outlen,
 
   *p++ = NGTCP2_FRAME_NEW_TOKEN;
 
-  p = ngtcp2_put_varint(p, fr->tokenlen);
-  if (fr->tokenlen) {
-    p = ngtcp2_cpymem(p, fr->token, fr->tokenlen);
-  }
+  p = ngtcp2_put_varint(p, fr->token.len);
+  p = ngtcp2_cpymem(p, fr->token.base, fr->token.len);
 
   assert((size_t)(p - out) == len);
 
diff --git a/lib/ngtcp2_pkt.h b/lib/ngtcp2_pkt.h
index 651c8630..6e9249c5 100644
--- a/lib/ngtcp2_pkt.h
+++ b/lib/ngtcp2_pkt.h
@@ -262,8 +262,7 @@ typedef struct {
 
 typedef struct {
   uint8_t type;
-  size_t tokenlen;
-  const uint8_t *token;
+  ngtcp2_vec token;
 } ngtcp2_new_token;
 
 typedef struct {
diff --git a/lib/ngtcp2_qlog.c b/lib/ngtcp2_qlog.c
index 07ceff74..f0abc8db 100644
--- a/lib/ngtcp2_qlog.c
+++ b/lib/ngtcp2_qlog.c
@@ -447,14 +447,17 @@ static uint8_t *write_new_token_frame(uint8_t *p, const ngtcp2_new_token *fr) {
   (void)fr;
 
   /*
-   * {"frame_type":"new_token"}
+   * {"frame_type":"new_token","length":0000000000000000000,"token":""}
    */
-#define NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD 26
+#define NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD 66
 
   *p++ = '{';
   p = write_pair(p, ngtcp2_vec_lit(&name, "frame_type"),
                  ngtcp2_vec_lit(&value, "new_token"));
-  /* TODO Write token here */
+  *p++ = ',';
+  p = write_pair_number(p, ngtcp2_vec_lit(&name, "length"), fr->token.len);
+  *p++ = ',';
+  p = write_pair_hex(p, ngtcp2_vec_lit(&name, "token"), &fr->token);
   *p++ = '}';
 
   return p;
@@ -870,7 +873,8 @@ void ngtcp2_qlog_write_frame(ngtcp2_qlog *qlog, const ngtcp2_frame *fr) {
     p = write_crypto_frame(p, &fr->crypto);
     break;
   case NGTCP2_FRAME_NEW_TOKEN:
-    if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD + 1 +
+    if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD +
+                                          fr->new_token.token.len * 2 + 1 +
                                           NGTCP2_QLOG_PKT_WRITE_END_OVERHEAD) {
       return;
     }
diff --git a/tests/main.c b/tests/main.c
index 1470a475..3cf5bfbc 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -246,6 +246,8 @@ int main() {
                    test_ngtcp2_conn_get_active_dcid) ||
       !CU_add_test(pSuite, "conn_recv_version_negotiation",
                    test_ngtcp2_conn_recv_version_negotiation) ||
+      !CU_add_test(pSuite, "conn_send_initial_token",
+                   test_ngtcp2_conn_send_initial_token) ||
       !CU_add_test(pSuite, "pkt_write_connection_close",
                    test_ngtcp2_pkt_write_connection_close) ||
       !CU_add_test(pSuite, "map", test_ngtcp2_map) ||
diff --git a/tests/ngtcp2_conn_test.c b/tests/ngtcp2_conn_test.c
index 04388fd8..b704af1a 100644
--- a/tests/ngtcp2_conn_test.c
+++ b/tests/ngtcp2_conn_test.c
@@ -5082,6 +5082,53 @@ void test_ngtcp2_conn_recv_version_negotiation(void) {
   ngtcp2_conn_del(conn);
 }
 
+void test_ngtcp2_conn_send_initial_token(void) {
+  ngtcp2_conn *conn;
+  uint8_t buf[2048];
+  ngtcp2_conn_callbacks cb;
+  ngtcp2_settings settings;
+  ngtcp2_cid rcid, scid;
+  ngtcp2_crypto_aead retry_aead = {0};
+  uint8_t token[] = "this is token";
+  ngtcp2_ssize spktlen, shdlen;
+  ngtcp2_tstamp t = 0;
+  ngtcp2_pkt_hd hd;
+
+  rcid_init(&rcid);
+  scid_init(&scid);
+
+  memset(&cb, 0, sizeof(cb));
+  cb.client_initial = client_initial;
+  cb.recv_crypto_data = recv_crypto_data;
+  cb.decrypt = null_decrypt;
+  cb.encrypt = null_encrypt;
+  cb.hp_mask = null_hp_mask;
+  cb.get_new_connection_id = get_new_connection_id;
+  client_default_settings(&settings);
+
+  settings.token.base = token;
+  settings.token.len = sizeof(token);
+
+  ngtcp2_conn_client_new(&conn, &rcid, &scid, &null_path, NGTCP2_PROTO_VER_MAX,
+                         &cb, &settings, /* mem = */ NULL, NULL);
+  ngtcp2_conn_install_initial_key(conn, null_key, null_iv, null_hp_key,
+                                  null_key, null_iv, null_hp_key,
+                                  sizeof(null_key), sizeof(null_iv));
+  ngtcp2_conn_set_retry_aead(conn, &retry_aead);
+
+  spktlen = ngtcp2_conn_write_pkt(conn, NULL, buf, sizeof(buf), ++t);
+
+  CU_ASSERT(spktlen > 0);
+
+  shdlen = ngtcp2_pkt_decode_hd_long(&hd, buf, (size_t)spktlen);
+
+  CU_ASSERT(shdlen > 0);
+  CU_ASSERT(sizeof(token) == hd.tokenlen);
+  CU_ASSERT(0 == memcmp(token, hd.token, sizeof(token)));
+
+  ngtcp2_conn_del(conn);
+}
+
 void test_ngtcp2_pkt_write_connection_close(void) {
   ngtcp2_ssize spktlen;
   uint8_t buf[1200];
diff --git a/tests/ngtcp2_conn_test.h b/tests/ngtcp2_conn_test.h
index 1bd088f5..cc992245 100644
--- a/tests/ngtcp2_conn_test.h
+++ b/tests/ngtcp2_conn_test.h
@@ -70,6 +70,7 @@ void test_ngtcp2_conn_recv_client_initial_retry(void);
 void test_ngtcp2_conn_recv_client_initial_token(void);
 void test_ngtcp2_conn_get_active_dcid(void);
 void test_ngtcp2_conn_recv_version_negotiation(void);
+void test_ngtcp2_conn_send_initial_token(void);
 void test_ngtcp2_pkt_write_connection_close(void);
 
 #endif /* NGTCP2_CONN_TEST_H */
diff --git a/tests/ngtcp2_pkt_test.c b/tests/ngtcp2_pkt_test.c
index 5aa2de7a..8946d09c 100644
--- a/tests/ngtcp2_pkt_test.c
+++ b/tests/ngtcp2_pkt_test.c
@@ -31,6 +31,7 @@
 #include "ngtcp2_conv.h"
 #include "ngtcp2_cid.h"
 #include "ngtcp2_str.h"
+#include "ngtcp2_vec.h"
 
 static int null_retry_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
                               const uint8_t *plaintext, size_t plaintextlen,
@@ -1089,8 +1090,7 @@ void test_ngtcp2_pkt_encode_new_token_frame(void) {
   size_t framelen;
 
   fr.type = NGTCP2_FRAME_NEW_TOKEN;
-  fr.new_token.tokenlen = strsize(token);
-  fr.new_token.token = token;
+  ngtcp2_vec_init(&fr.new_token.token, token, strsize(token));
 
   framelen = 1 + 1 + strsize(token);
 
@@ -1102,9 +1102,9 @@ void test_ngtcp2_pkt_encode_new_token_frame(void) {
 
   CU_ASSERT((ngtcp2_ssize)framelen == rv);
   CU_ASSERT(fr.type == nfr.type);
-  CU_ASSERT(fr.new_token.tokenlen == nfr.new_token.tokenlen);
-  CU_ASSERT(0 == memcmp(fr.new_token.token, nfr.new_token.token,
-                        fr.new_token.tokenlen));
+  CU_ASSERT(fr.new_token.token.len == nfr.new_token.token.len);
+  CU_ASSERT(0 == memcmp(fr.new_token.token.base, nfr.new_token.token.base,
+                        fr.new_token.token.len));
 }
 
 void test_ngtcp2_pkt_encode_retire_connection_id_frame(void) {
-- 
GitLab