From 9c91b39a02ffcc6bcaa7adb1c90ad5caf8f9649f Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Mon, 26 Jun 2017 01:14:48 +0900
Subject: [PATCH] Send CONNECTION_CLOSE in encrypted QUIC packet

---
 examples/Makefile.am         |   6 +-
 examples/client.cc           | 179 ++++++++++++++++++++---
 examples/client.h            |  15 ++
 examples/crypto.cc           | 213 +++++++++++++++++++++++++++
 examples/crypto.h            | 104 ++++++++++++++
 examples/debug.cc            |  26 +++-
 examples/server.cc           | 137 +++++++++++++++++-
 examples/server.h            |  14 ++
 examples/template.h          |   4 +
 lib/Makefile.am              |   6 +-
 lib/includes/ngtcp2/ngtcp2.h |  71 ++++-----
 lib/ngtcp2_conn.c            | 272 ++++++++++++++++++++++++++++++++++-
 lib/ngtcp2_conn.h            |   7 +-
 lib/ngtcp2_conv.c            |  11 --
 lib/ngtcp2_conv.h            |  11 ++
 lib/ngtcp2_crypto.c          |  75 ++++++++++
 lib/ngtcp2_crypto.h          |  60 ++++++++
 lib/ngtcp2_pkt.c             |  67 ++++++++-
 lib/ngtcp2_pkt.h             |  14 ++
 lib/ngtcp2_ppe.c             | 124 ++++++++++++++++
 lib/ngtcp2_ppe.h             |  63 ++++++++
 tests/main.c                 |   2 +
 tests/ngtcp2_pkt_test.c      |  62 ++++++++
 tests/ngtcp2_pkt_test.h      |   1 +
 24 files changed, 1465 insertions(+), 79 deletions(-)
 create mode 100644 examples/crypto.cc
 create mode 100644 examples/crypto.h
 create mode 100644 lib/ngtcp2_crypto.c
 create mode 100644 lib/ngtcp2_crypto.h
 create mode 100644 lib/ngtcp2_ppe.c
 create mode 100644 lib/ngtcp2_ppe.h

diff --git a/examples/Makefile.am b/examples/Makefile.am
index 76ed7691..d39a8eec 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -37,9 +37,11 @@ noinst_PROGRAMS = client server
 client_SOURCES = client.cc client.h \
 	template.h \
 	debug.cc debug.h \
-	util.cc util.h
+	util.cc util.h \
+	crypto.cc crypto.h
 
 server_SOURCES = server.cc server.h \
 	template.h \
 	debug.cc debug.h \
-	util.cc util.h
+	util.cc util.h \
+	crypto.cc crypto.h
diff --git a/examples/client.cc b/examples/client.cc
index 204f567b..86708152 100644
--- a/examples/client.cc
+++ b/examples/client.cc
@@ -40,6 +40,7 @@
 #include "network.h"
 #include "debug.h"
 #include "util.h"
+#include "crypto.h"
 
 using namespace ngtcp2;
 
@@ -142,6 +143,17 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) {
 }
 } // namespace
 
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto c = static_cast<Client *>(w->data);
+
+  debug::print_timestamp();
+  std::cerr << "Timeout" << std::endl;
+
+  c->disconnect();
+}
+} // namespace
+
 Client::Client(struct ev_loop *loop, SSL_CTX *ssl_ctx)
     : remote_addr_{},
       max_pktlen_(0),
@@ -151,17 +163,22 @@ Client::Client(struct ev_loop *loop, SSL_CTX *ssl_ctx)
       fd_(-1),
       ncread_(0),
       nsread_(0),
-      conn_(nullptr) {
+      conn_(nullptr),
+      prf_(nullptr),
+      aead_(nullptr),
+      secretlen_(0) {
   ev_io_init(&wev_, writecb, 0, EV_WRITE);
   ev_io_init(&rev_, readcb, 0, EV_READ);
   wev_.data = this;
   rev_.data = this;
+  ev_timer_init(&timer_, timeoutcb, 5., 0.);
+  timer_.data = this;
 }
 
 Client::~Client() { disconnect(); }
 
 void Client::disconnect() {
-  std::cerr << "disconnecting" << std::endl;
+  ev_timer_stop(loop_, &timer_);
 
   ev_io_stop(loop_, &rev_);
   ev_io_stop(loop_, &wev_);
@@ -231,6 +248,56 @@ int recv_handshake_data(ngtcp2_conn *conn, const uint8_t *data, size_t datalen,
 }
 } // namespace
 
+namespace {
+int handshake_completed(ngtcp2_conn *conn, void *user_data) {
+  auto c = static_cast<Client *>(user_data);
+
+  debug::handshake_completed(conn, user_data);
+
+  if (c->setup_crypto_context() != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+} // namespace
+
+namespace {
+ssize_t do_encrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
+                   const uint8_t *plaintext, size_t plaintextlen,
+                   const uint8_t *key, size_t keylen, const uint8_t *nonce,
+                   size_t noncelen, const uint8_t *ad, size_t adlen,
+                   void *user_data) {
+  auto c = static_cast<Client *>(user_data);
+
+  auto nwrite = c->encrypt_data(dest, destlen, plaintext, plaintextlen, key,
+                                keylen, nonce, noncelen, ad, adlen);
+  if (nwrite < 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return nwrite;
+}
+} // namespace
+
+namespace {
+ssize_t do_decrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
+                   const uint8_t *ciphertext, size_t ciphertextlen,
+                   const uint8_t *key, size_t keylen, const uint8_t *nonce,
+                   size_t noncelen, const uint8_t *ad, size_t adlen,
+                   void *user_data) {
+  auto c = static_cast<Client *>(user_data);
+
+  auto nwrite = c->decrypt_data(dest, destlen, ciphertext, ciphertextlen, key,
+                                keylen, nonce, noncelen, ad, adlen);
+  if (nwrite < 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return nwrite;
+}
+} // namespace
+
 int Client::init(int fd, const Address &remote_addr) {
   int rv;
 
@@ -264,8 +331,10 @@ int Client::init(int fd, const Address &remote_addr) {
       debug::send_frame,
       debug::recv_pkt,
       debug::recv_frame,
-      debug::handshake_completed,
+      handshake_completed,
       debug::recv_version_negotiation,
+      do_encrypt,
+      do_decrypt,
   };
 
   auto conn_id = std::uniform_int_distribution<uint64_t>(
@@ -282,6 +351,7 @@ int Client::init(int fd, const Address &remote_addr) {
   ev_io_set(&rev_, fd_, EV_READ);
 
   ev_io_start(loop_, &rev_);
+  ev_timer_start(loop_, &timer_);
 
   return 0;
 }
@@ -348,21 +418,23 @@ int Client::on_read() {
 int Client::on_write() {
   std::array<uint8_t, NGTCP2_MAX_PKTLEN_IPV4> buf;
   assert(buf.size() >= max_pktlen_);
-  auto n = ngtcp2_conn_send(conn_, buf.data(), max_pktlen_, util::timestamp());
-  if (n < 0) {
-    return -1;
-  }
-  if (n == 0) {
-    return 0;
-  }
 
-  auto nwrite = write(fd_, buf.data(), n);
-  if (nwrite == -1) {
-    std::cerr << "write: " << strerror(errno) << std::endl;
-    return -1;
-  }
+  for (;;) {
+    auto n =
+        ngtcp2_conn_send(conn_, buf.data(), max_pktlen_, util::timestamp());
+    if (n < 0) {
+      return -1;
+    }
+    if (n == 0) {
+      return 0;
+    }
 
-  return 0;
+    auto nwrite = write(fd_, buf.data(), n);
+    if (nwrite == -1) {
+      std::cerr << "write: " << strerror(errno) << std::endl;
+      return -1;
+    }
+  }
 }
 
 void Client::write_client_handshake(const uint8_t *data, size_t datalen) {
@@ -387,6 +459,81 @@ void Client::write_server_handshake(const uint8_t *data, size_t datalen) {
   std::copy_n(data, datalen, std::back_inserter(shandshake_));
 }
 
+int Client::setup_crypto_context() {
+  int rv;
+
+  prf_ = crypto::get_negotiated_prf(ssl_);
+  assert(prf_);
+  aead_ = crypto::get_negotiated_aead(ssl_);
+  assert(aead_);
+
+  auto length = EVP_MD_size(prf_);
+
+  secretlen_ = length;
+
+  rv = crypto::export_client_secret(tx_secret_.data(), secretlen_, ssl_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  std::array<uint8_t, 64> key{}, iv{};
+
+  auto keylen = crypto::derive_packet_protection_key(
+      key.data(), key.size(), tx_secret_.data(), secretlen_, aead_, prf_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  auto ivlen = crypto::derive_packet_protection_iv(
+      iv.data(), iv.size(), tx_secret_.data(), secretlen_, aead_, prf_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  ngtcp2_conn_update_tx_keys(conn_, key.data(), keylen, iv.data(), ivlen);
+
+  rv = crypto::export_server_secret(rx_secret_.data(), secretlen_, ssl_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  keylen = crypto::derive_packet_protection_key(
+      key.data(), key.size(), rx_secret_.data(), secretlen_, aead_, prf_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  ivlen = crypto::derive_packet_protection_iv(
+      iv.data(), iv.size(), rx_secret_.data(), secretlen_, aead_, prf_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  ngtcp2_conn_update_rx_keys(conn_, key.data(), keylen, iv.data(), ivlen);
+
+  ngtcp2_conn_set_aead_overhead(conn_, EVP_AEAD_max_overhead(aead_));
+
+  return 0;
+}
+
+ssize_t Client::encrypt_data(uint8_t *dest, size_t destlen,
+                             const uint8_t *plaintext, size_t plaintextlen,
+                             const uint8_t *key, size_t keylen,
+                             const uint8_t *nonce, size_t noncelen,
+                             const uint8_t *ad, size_t adlen) {
+  return crypto::encrypt(dest, destlen, plaintext, plaintextlen, aead_, key,
+                         keylen, nonce, noncelen, ad, adlen);
+}
+
+ssize_t Client::decrypt_data(uint8_t *dest, size_t destlen,
+                             const uint8_t *ciphertext, size_t ciphertextlen,
+                             const uint8_t *key, size_t keylen,
+                             const uint8_t *nonce, size_t noncelen,
+                             const uint8_t *ad, size_t adlen) {
+  return crypto::decrypt(dest, destlen, ciphertext, ciphertextlen, aead_, key,
+                         keylen, nonce, noncelen, ad, adlen);
+}
+
 namespace {
 SSL_CTX *create_ssl_ctx() {
   auto ssl_ctx = SSL_CTX_new(TLS_method());
diff --git a/examples/client.h b/examples/client.h
index d2dae9ad..7d6748c1 100644
--- a/examples/client.h
+++ b/examples/client.h
@@ -60,11 +60,22 @@ public:
   size_t read_server_handshake(uint8_t *buf, size_t buflen);
   void write_server_handshake(const uint8_t *data, size_t datalen);
 
+  int setup_crypto_context();
+  ssize_t encrypt_data(uint8_t *dest, size_t destlen, const uint8_t *plaintext,
+                       size_t plaintextlen, const uint8_t *key, size_t keylen,
+                       const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
+                       size_t adlen);
+  ssize_t decrypt_data(uint8_t *dest, size_t destlen, const uint8_t *ciphertext,
+                       size_t ciphertextlen, const uint8_t *key, size_t keylen,
+                       const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
+                       size_t adlen);
+
 private:
   Address remote_addr_;
   size_t max_pktlen_;
   ev_io wev_;
   ev_io rev_;
+  ev_timer timer_;
   struct ev_loop *loop_;
   SSL_CTX *ssl_ctx_;
   SSL *ssl_;
@@ -74,6 +85,10 @@ private:
   std::vector<uint8_t> shandshake_;
   size_t nsread_;
   ngtcp2_conn *conn_;
+  const EVP_MD *prf_;
+  const EVP_AEAD *aead_;
+  std::array<uint8_t, 64> tx_secret_, rx_secret_;
+  size_t secretlen_;
 };
 
 #endif // CLIENT_H
diff --git a/examples/crypto.cc b/examples/crypto.cc
new file mode 100644
index 00000000..f44f44d5
--- /dev/null
+++ b/examples/crypto.cc
@@ -0,0 +1,213 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "crypto.h"
+
+#include <cassert>
+#include <algorithm>
+
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif /* HAVE_ARPA_INET_H */
+
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/aead.h>
+
+#include "template.h"
+
+#ifdef WORDS_BIGENDIAN
+#define bwap64(N) (N)
+#else /* !WORDS_BIGENDIAN */
+#define bswap64(N)                                                             \
+  (((uint64_t)(ntohl(((uint32_t)(N)) & 0xffffffffu))) << 32 | ntohl((N) >> 32))
+#endif /* !WORDS_BIGENDIAN */
+
+namespace ngtcp2 {
+
+namespace crypto {
+
+const EVP_MD *get_negotiated_prf(SSL *ssl) {
+  switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
+  case 0x03001301u: // TLS_AES_128_GCM_SHA256
+  case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256
+    return EVP_sha256();
+  case 0x03001302u: // TLS_AES_256_GCM_SHA384
+    return EVP_sha384();
+  default:
+    return NULL;
+  }
+}
+
+const EVP_AEAD *get_negotiated_aead(SSL *ssl) {
+  switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
+  case 0x03001301u: // TLS_AES_128_GCM_SHA256
+    return EVP_aead_aes_128_gcm();
+  case 0x03001302u: // TLS_AES_256_GCM_SHA384
+    return EVP_aead_aes_256_gcm();
+  case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256
+    return EVP_aead_chacha20_poly1305();
+  default:
+    return NULL;
+  }
+}
+
+int export_secret(uint8_t *dest, size_t destlen, SSL *ssl, const uint8_t *label,
+                  size_t labellen) {
+  int rv;
+
+  rv = SSL_export_keying_material(ssl, dest, destlen,
+                                  reinterpret_cast<const char *>(label),
+                                  labellen, nullptr, 0, 1);
+  if (rv != 1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int export_client_secret(uint8_t *dest, size_t destlen, SSL *ssl) {
+  constexpr uint8_t label[] = "EXPORTER-QUIC client 1-RTT Secret";
+  return export_secret(dest, destlen, ssl, label, str_size(label));
+}
+
+int export_server_secret(uint8_t *dest, size_t destlen, SSL *ssl) {
+  constexpr uint8_t label[] = "EXPORTER-QUIC server 1-RTT Secret";
+  return export_secret(dest, destlen, ssl, label, str_size(label));
+}
+
+int hkdf_expand_label(uint8_t *dest, size_t destlen, const uint8_t *secret,
+                      size_t secretlen, const uint8_t *qlabel, size_t qlabellen,
+                      const EVP_MD *prf) {
+  std::array<uint8_t, 256> info;
+  int rv;
+  constexpr const uint8_t LABEL[] = "TLS 1.3, ";
+
+  auto p = std::begin(info);
+  *p++ = destlen / 256;
+  *p++ = destlen % 256;
+  *p++ = str_size(LABEL) + qlabellen;
+  p = std::copy_n(LABEL, str_size(LABEL), p);
+  p = std::copy_n(qlabel, qlabellen, p);
+  *p++ = 0;
+
+  rv = HKDF(dest, destlen, prf, secret, secretlen, nullptr, 0, info.data(),
+            p - std::begin(info));
+
+  if (rv != 1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+ssize_t derive_packet_protection_key(uint8_t *dest, size_t destlen,
+                                     const uint8_t *secret, size_t secretlen,
+                                     const EVP_AEAD *aead, const EVP_MD *prf) {
+  int rv;
+  constexpr uint8_t LABEL_KEY[] = "key";
+
+  auto keylen = EVP_AEAD_key_length(aead);
+  if (keylen > destlen) {
+    return -1;
+  }
+
+  rv = crypto::hkdf_expand_label(dest, keylen, secret, secretlen, LABEL_KEY,
+                                 str_size(LABEL_KEY), prf);
+  if (rv != 0) {
+    return -1;
+  }
+
+  return keylen;
+}
+
+ssize_t derive_packet_protection_iv(uint8_t *dest, size_t destlen,
+                                    const uint8_t *secret, size_t secretlen,
+                                    const EVP_AEAD *aead, const EVP_MD *prf) {
+  int rv;
+  constexpr uint8_t LABEL_IV[] = "iv";
+
+  auto ivlen = std::max(static_cast<size_t>(8), EVP_AEAD_nonce_length(aead));
+  if (ivlen > destlen) {
+    return -1;
+  }
+
+  rv = crypto::hkdf_expand_label(dest, ivlen, secret, secretlen, LABEL_IV,
+                                 str_size(LABEL_IV), prf);
+  if (rv != 0) {
+    return -1;
+  }
+
+  return ivlen;
+}
+
+ssize_t encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext,
+                size_t plaintextlen, const EVP_AEAD *aead, const uint8_t *key,
+                size_t keylen, const uint8_t *nonce, size_t noncelen,
+                const uint8_t *ad, size_t adlen) {
+  int rv;
+
+  auto actx = EVP_AEAD_CTX_new(aead, key, keylen, 0);
+
+  assert(actx);
+
+  auto actx_d = defer(EVP_AEAD_CTX_free, actx);
+
+  size_t outlen;
+
+  rv = EVP_AEAD_CTX_seal(actx, dest, &outlen, destlen, nonce, noncelen,
+                         plaintext, plaintextlen, ad, adlen);
+  if (rv != 1) {
+    return -1;
+  }
+
+  return outlen;
+}
+
+ssize_t decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext,
+                size_t ciphertextlen, const EVP_AEAD *aead, const uint8_t *key,
+                size_t keylen, const uint8_t *nonce, size_t noncelen,
+                const uint8_t *ad, size_t adlen) {
+  int rv;
+
+  auto actx = EVP_AEAD_CTX_new(aead, key, keylen, 0);
+
+  assert(actx);
+
+  auto actx_d = defer(EVP_AEAD_CTX_free, actx);
+
+  size_t outlen;
+
+  rv = EVP_AEAD_CTX_open(actx, dest, &outlen, destlen, nonce, noncelen,
+                         ciphertext, ciphertextlen, ad, adlen);
+  if (rv != 1) {
+    return -1;
+  }
+
+  return outlen;
+}
+
+} // namespace crypto
+
+} // namespace ngtcp2
diff --git a/examples/crypto.h b/examples/crypto.h
new file mode 100644
index 00000000..1ab412e9
--- /dev/null
+++ b/examples/crypto.h
@@ -0,0 +1,104 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef CRYPTO_H
+#define CRYPTO_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <ngtcp2/ngtcp2.h>
+
+#include <openssl/ssl.h>
+
+namespace ngtcp2 {
+
+namespace crypto {
+
+// get_negotiated_prf returns the negotiated PRF by TLS.
+const EVP_MD *get_negotiated_prf(SSL *ssl);
+
+// get_negotiated_aead returns the negotiated AEAD by TLS.
+const EVP_AEAD *get_negotiated_aead(SSL *ssl);
+
+// export_secret exports secret with given label.  It returns 0 if it
+// succeeds, or -1.
+int export_secret(uint8_t *dest, size_t destlen, SSL *ssl, const uint8_t *label,
+                  size_t labellen);
+
+// export_client_secret exports secret, client_pp_secret_0, from |ssl|
+// for client.  It returns 0 if it succeeds, or -1.
+int export_client_secret(uint8_t *dest, size_t destlen, SSL *ssl);
+
+// export_server_secret exports secret, server_pp_secret_0, from |ssl|
+// for server.  It returns 0 if it succeeds, or -1.
+int export_server_secret(uint8_t *dest, size_t destlen, SSL *ssl);
+
+// hkdf_expand_label derives secret using HDKF-Expand-Label.  It
+// returns 0 if it succeeds, or -1.
+int hkdf_expand_label(uint8_t *dest, size_t destlen, const uint8_t *secret,
+                      size_t secretlen, const uint8_t *qlabel, size_t qlabellen,
+                      const EVP_MD *prf);
+
+// derive_packet_protection_key derives and stores the packet
+// protection key in the buffer pointed by |dest| of length |destlen|,
+// and the key size is returned.  This function returns the key length
+// if it succeeds, or -1.
+ssize_t derive_packet_protection_key(uint8_t *dest, size_t destlen,
+                                     const uint8_t *secret, size_t secretlen,
+                                     const EVP_AEAD *aead, const EVP_MD *prf);
+
+// derive_packet_protection_iv derives and stores the packet
+// protection IV in the buffer pointed by |dest| of length |destlen|.
+// This function returns the length of IV if it succeeds, or -1.
+ssize_t derive_packet_protection_iv(uint8_t *dest, size_t destlen,
+                                    const uint8_t *secret, size_t secretlen,
+                                    const EVP_AEAD *aead, const EVP_MD *prf);
+
+// encrypt encrypts |plaintext| of length |plaintextlen| and writes
+// the encrypted data in the buffer pointed by |dest| of length
+// |destlen|.  This function can encrypt data in-place.  In other
+// words, |dest| == |plaintext| is allowed.  This function returns the
+// number of bytes written if it succeeds, or -1.
+ssize_t encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext,
+                size_t plaintextlen, const EVP_AEAD *aead, const uint8_t *key,
+                size_t keylen, const uint8_t *nonce, size_t noncelen,
+                const uint8_t *ad, size_t adlen);
+
+// decrypt decrypts |ciphertext| of length |ciphertextlen| and writes
+// the decrypted data in the buffer pointed by |dest| of length
+// |destlen|.  This function can decrypt data in-place.  In other
+// words, |dest| == |ciphertext| is allowed.  This function returns
+// the number of bytes written if it succeeds, or -1.
+ssize_t decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext,
+                size_t ciphertextlen, const EVP_AEAD *aead, const uint8_t *key,
+                size_t keylen, const uint8_t *nonce, size_t noncelen,
+                const uint8_t *ad, size_t adlen);
+
+} // namespace crypto
+
+} // namespace ngtcp2
+
+#endif // CRYPTO_H
diff --git a/examples/debug.cc b/examples/debug.cc
index d72cf730..85f58912 100644
--- a/examples/debug.cc
+++ b/examples/debug.cc
@@ -90,6 +90,21 @@ std::string strpkttype_long(uint8_t type) {
 }
 } // namespace
 
+namespace {
+std::string strpkttype_short(uint8_t type) {
+  switch (type) {
+  case NGTCP2_PKT_01:
+    return "Short 01";
+  case NGTCP2_PKT_02:
+    return "Short 02";
+  case NGTCP2_PKT_03:
+    return "Short 03";
+  default:
+    return "UNKNOWN(0x" + util::format_hex(type) + ")";
+  }
+}
+} // namespace
+
 namespace {
 std::string strframetype(uint8_t type) {
   switch (type) {
@@ -162,7 +177,11 @@ void print_pkt_long(ngtcp2_dir dir, const ngtcp2_pkt_hd *hd) {
 
 namespace {
 void print_pkt_short(ngtcp2_dir dir, const ngtcp2_pkt_hd *hd) {
-  fprintf(outfile, "short pkt\n");
+  fprintf(outfile, "%s%s%s packet\n", pkt_ansi_esc(dir),
+          strpkttype_short(hd->type).c_str(), ansi_escend());
+  print_indent();
+  fprintf(outfile, "<conn_id=0x%016lx, pkt_num=%lu>\n", hd->conn_id,
+          hd->pkt_num);
 }
 } // namespace
 
@@ -207,6 +226,11 @@ void print_frame(ngtcp2_dir dir, const ngtcp2_frame *fr) {
               blk->blklen);
     }
     break;
+  case NGTCP2_FRAME_CONNECTION_CLOSE:
+    print_indent();
+    fprintf(outfile, "<error_code=0x%08x, reason_length=%zu>\n",
+            fr->connection_close.error_code, fr->connection_close.reasonlen);
+    break;
   }
 }
 } // namespace
diff --git a/examples/server.cc b/examples/server.cc
index 277c6b1e..59ead8b4 100644
--- a/examples/server.cc
+++ b/examples/server.cc
@@ -32,7 +32,6 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netdb.h>
-#include <stdio.h>
 
 #include <openssl/bio.h>
 
@@ -41,6 +40,7 @@
 #include "network.h"
 #include "debug.h"
 #include "util.h"
+#include "crypto.h"
 
 using namespace ngtcp2;
 
@@ -169,7 +169,10 @@ Handler::Handler(struct ev_loop *loop, SSL_CTX *ssl_ctx)
       fd_(-1),
       ncread_(0),
       nsread_(0),
-      conn_(nullptr) {
+      conn_(nullptr),
+      prf_(nullptr),
+      aead_(nullptr),
+      secretlen_(0) {
   ev_io_init(&wev_, hwritecb, 0, EV_WRITE);
   ev_io_init(&rev_, hreadcb, 0, EV_READ);
   wev_.data = this;
@@ -227,6 +230,56 @@ ssize_t send_server_cleartext(ngtcp2_conn *conn, uint32_t flags,
 }
 } // namespace
 
+namespace {
+int handshake_completed(ngtcp2_conn *conn, void *user_data) {
+  auto h = static_cast<Handler *>(user_data);
+
+  debug::handshake_completed(conn, user_data);
+
+  if (h->setup_crypto_context() != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+} // namespace
+
+namespace {
+ssize_t do_encrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
+                   const uint8_t *plaintext, size_t plaintextlen,
+                   const uint8_t *key, size_t keylen, const uint8_t *nonce,
+                   size_t noncelen, const uint8_t *ad, size_t adlen,
+                   void *user_data) {
+  auto h = static_cast<Handler *>(user_data);
+
+  auto nwrite = h->encrypt_data(dest, destlen, plaintext, plaintextlen, key,
+                                keylen, nonce, noncelen, ad, adlen);
+  if (nwrite < 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return nwrite;
+}
+} // namespace
+
+namespace {
+ssize_t do_decrypt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
+                   const uint8_t *ciphertext, size_t ciphertextlen,
+                   const uint8_t *key, size_t keylen, const uint8_t *nonce,
+                   size_t noncelen, const uint8_t *ad, size_t adlen,
+                   void *user_data) {
+  auto h = static_cast<Handler *>(user_data);
+
+  auto nwrite = h->decrypt_data(dest, destlen, ciphertext, ciphertextlen, key,
+                                keylen, nonce, noncelen, ad, adlen);
+  if (nwrite < 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return nwrite;
+}
+} // namespace
+
 namespace {
 int recv_handshake_data(ngtcp2_conn *conn, const uint8_t *data, size_t datalen,
                         void *user_data) {
@@ -276,7 +329,10 @@ int Handler::init(int fd, const sockaddr *sa, socklen_t salen) {
       debug::send_frame,
       debug::recv_pkt,
       debug::recv_frame,
-      debug::handshake_completed,
+      handshake_completed,
+      nullptr,
+      do_encrypt,
+      do_decrypt,
   };
 
   auto conn_id = std::uniform_int_distribution<uint64_t>(
@@ -353,6 +409,81 @@ void Handler::write_client_handshake(const uint8_t *data, size_t datalen) {
   std::copy_n(data, datalen, std::back_inserter(shandshake_));
 }
 
+int Handler::setup_crypto_context() {
+  int rv;
+
+  prf_ = crypto::get_negotiated_prf(ssl_);
+  assert(prf_);
+  aead_ = crypto::get_negotiated_aead(ssl_);
+  assert(aead_);
+
+  auto length = EVP_MD_size(prf_);
+
+  secretlen_ = length;
+
+  rv = crypto::export_server_secret(tx_secret_.data(), secretlen_, ssl_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  std::array<uint8_t, 64> key{}, iv{};
+
+  auto keylen = crypto::derive_packet_protection_key(
+      key.data(), key.size(), tx_secret_.data(), secretlen_, aead_, prf_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  auto ivlen = crypto::derive_packet_protection_iv(
+      iv.data(), iv.size(), tx_secret_.data(), secretlen_, aead_, prf_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  ngtcp2_conn_update_tx_keys(conn_, key.data(), keylen, iv.data(), ivlen);
+
+  rv = crypto::export_client_secret(rx_secret_.data(), secretlen_, ssl_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  keylen = crypto::derive_packet_protection_key(
+      key.data(), key.size(), rx_secret_.data(), secretlen_, aead_, prf_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  ivlen = crypto::derive_packet_protection_iv(
+      iv.data(), iv.size(), rx_secret_.data(), secretlen_, aead_, prf_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  ngtcp2_conn_update_rx_keys(conn_, key.data(), keylen, iv.data(), ivlen);
+
+  ngtcp2_conn_set_aead_overhead(conn_, EVP_AEAD_max_overhead(aead_));
+
+  return 0;
+}
+
+ssize_t Handler::encrypt_data(uint8_t *dest, size_t destlen,
+                              const uint8_t *plaintext, size_t plaintextlen,
+                              const uint8_t *key, size_t keylen,
+                              const uint8_t *nonce, size_t noncelen,
+                              const uint8_t *ad, size_t adlen) {
+  return crypto::encrypt(dest, destlen, plaintext, plaintextlen, aead_, key,
+                         keylen, nonce, noncelen, ad, adlen);
+}
+
+ssize_t Handler::decrypt_data(uint8_t *dest, size_t destlen,
+                              const uint8_t *ciphertext, size_t ciphertextlen,
+                              const uint8_t *key, size_t keylen,
+                              const uint8_t *nonce, size_t noncelen,
+                              const uint8_t *ad, size_t adlen) {
+  return crypto::decrypt(dest, destlen, ciphertext, ciphertextlen, aead_, key,
+                         keylen, nonce, noncelen, ad, adlen);
+}
+
 int Handler::feed_data(const uint8_t *data, size_t datalen) {
   int rv;
 
diff --git a/examples/server.h b/examples/server.h
index fa76deac..176b69b0 100644
--- a/examples/server.h
+++ b/examples/server.h
@@ -58,6 +58,16 @@ public:
   size_t read_client_handshake(uint8_t *buf, size_t buflen);
   void write_client_handshake(const uint8_t *data, size_t datalen);
 
+  int setup_crypto_context();
+  ssize_t encrypt_data(uint8_t *dest, size_t destlen, const uint8_t *plaintext,
+                       size_t plaintextlen, const uint8_t *key, size_t keylen,
+                       const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
+                       size_t adlen);
+  ssize_t decrypt_data(uint8_t *dest, size_t destlen, const uint8_t *ciphertext,
+                       size_t ciphertextlen, const uint8_t *key, size_t keylen,
+                       const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
+                       size_t adlen);
+
 private:
   Address remote_addr_;
   size_t max_pktlen_;
@@ -73,6 +83,10 @@ private:
   std::vector<uint8_t> shandshake_;
   size_t nsread_;
   ngtcp2_conn *conn_;
+  const EVP_MD *prf_;
+  const EVP_AEAD *aead_;
+  std::array<uint8_t, 64> tx_secret_, rx_secret_;
+  size_t secretlen_;
 };
 
 class Server {
diff --git a/examples/template.h b/examples/template.h
index be60c4a7..2e0bf937 100644
--- a/examples/template.h
+++ b/examples/template.h
@@ -51,4 +51,8 @@ template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) {
   return N;
 }
 
+template <typename T, size_t N> constexpr size_t str_size(T (&)[N]) {
+  return N - 1;
+}
+
 #endif // TEMPLATE_H
diff --git a/lib/Makefile.am b/lib/Makefile.am
index d955dca0..7da50fba 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -40,7 +40,9 @@ OBJECTS = \
 	ngtcp2_conn.c \
 	ngtcp2_mem.c \
 	ngtcp2_pq.c \
-	ngtcp2_rob.c
+	ngtcp2_rob.c \
+	ngtcp2_ppe.c \
+	ngtcp2_crypto.c
 
 HFILES = \
 	ngtcp2_pkt.h \
@@ -52,6 +54,8 @@ HFILES = \
 	ngtcp2_mem.h \
 	ngtcp2_pq.h \
 	ngtcp2_rob.h \
+	ngtcp2_ppe.h \
+	ngtcp2_crypto.h \
 	ngtcp2_macro.h
 
 libngtcp2_la_SOURCES = $(HFILES) $(OBJECTS)
diff --git a/lib/includes/ngtcp2/ngtcp2.h b/lib/includes/ngtcp2/ngtcp2.h
index 1e77be4f..e59e8c99 100644
--- a/lib/includes/ngtcp2/ngtcp2.h
+++ b/lib/includes/ngtcp2/ngtcp2.h
@@ -172,7 +172,7 @@ typedef enum {
   NGTCP2_ERR_NOMEM = -501,
   NGTCP2_ERR_CALLBACK_FAILURE = -502,
   NGTCP2_ERR_INTERNAL_ERROR = -503
-} ngtcp2_error;
+} ngtcp2_lib_error;
 
 typedef enum {
   NGTCP2_PKT_FLAG_NONE = 0,
@@ -213,6 +213,8 @@ typedef enum {
   NGTCP2_FRAME_STREAM = 0xc0
 } ngtcp2_frame_type;
 
+typedef enum { NGTCP2_QUIC_INTERNAL_ERROR = 0x80000001u } ngtcp2_error;
+
 /*
  * ngtcp2_tstamp is a timestamp with microsecond resolution.
  */
@@ -270,7 +272,12 @@ typedef struct {
 
 typedef struct { uint8_t type; } ngtcp2_rst_stream;
 
-typedef struct { uint8_t type; } ngtcp2_connection_close;
+typedef struct {
+  uint8_t type;
+  uint32_t error_code;
+  size_t reasonlen;
+  uint8_t *reason;
+} ngtcp2_connection_close;
 
 typedef struct { uint8_t type; } ngtcp2_goaway;
 
@@ -349,21 +356,6 @@ NGTCP2_EXTERN ssize_t ngtcp2_pkt_decode_frame(ngtcp2_frame *dest,
 NGTCP2_EXTERN ssize_t ngtcp2_pkt_encode_frame(uint8_t *out, size_t outlen,
                                               const ngtcp2_frame *fr);
 
-/* Protected Packet Encoder: ppe */
-struct ngtcp2_ppe;
-typedef struct ngtcp2_ppe ngtcp2_ppe;
-
-struct ngtcp2_crypto_ctx;
-typedef struct ngtcp2_crypto_ctx ngtcp2_crypto_ctx;
-
-NGTCP2_EXTERN int ngtcp2_ppe_init(ngtcp2_ppe *ppe, ngtcp2_crypto_ctx *cctx,
-                                  uint8_t *out, size_t outlen);
-NGTCP2_EXTERN ssize_t ngtcp2_ppe_encode_hd(ngtcp2_ppe *ppe,
-                                           const ngtcp2_pkt_hd *hd);
-NGTCP2_EXTERN ssize_t ngtcp2_ppe_encode_frame(ngtcp2_ppe *ppe,
-                                              const ngtcp2_frame *fr);
-NGTCP2_EXTERN ssize_t ngtcp2_ppe_final(ngtcp2_ppe *ppe);
-
 /* Unprotected Packet Encoder: upe */
 struct ngtcp2_upe;
 typedef struct ngtcp2_upe ngtcp2_upe;
@@ -472,24 +464,6 @@ NGTCP2_EXTERN size_t ngtcp2_upe_left(ngtcp2_upe *upe);
  */
 NGTCP2_EXTERN int ngtcp2_pkt_verify(const uint8_t *pkt, size_t pktlen);
 
-/**
- * @function
- *
- * `ngtcp2_crypto_ctx_decrypt` performs decryption of QUIC payload
- * included in QUIC packet |pkg| of length |pktlen|.  The result of
- * decryption is written to the memory pointed by |dest|.  The valid
- * length of |dest| is given in |destlen|.
- *
- * This function returns the number of bytes written to |dest| if it
- * succeeds, or one of the following negative error codes:
- *
- * TBD
- */
-NGTCP2_EXTERN ssize_t ngtcp2_crypto_ctx_decrypt(ngtcp2_crypto_ctx *cctx,
-                                                uint8_t *dest, size_t destlen,
-                                                const uint8_t *pkt,
-                                                size_t pktlen);
-
 typedef enum { NGTCP2_CONN_FLAG_NONE } ngtcp2_conn_flag;
 
 struct ngtcp2_conn;
@@ -535,6 +509,20 @@ typedef int (*ngtcp2_recv_version_negotiation)(ngtcp2_conn *conn,
                                                const uint32_t *sv, size_t nsv,
                                                void *user_data);
 
+typedef ssize_t (*ngtcp2_encrypt)(ngtcp2_conn *conn, uint8_t *dest,
+                                  size_t destlen, const uint8_t *plaintext,
+                                  size_t plaintextlen, const uint8_t *key,
+                                  size_t keylen, const uint8_t *nonce,
+                                  size_t noncelen, const uint8_t *ad,
+                                  size_t adlen, void *user_data);
+
+typedef ssize_t (*ngtcp2_decrypt)(ngtcp2_conn *conn, uint8_t *dest,
+                                  size_t destlen, const uint8_t *ciphertext,
+                                  size_t ciphertextlen, const uint8_t *key,
+                                  size_t keylen, const uint8_t *nonce,
+                                  size_t noncelen, const uint8_t *ad,
+                                  size_t adlen, void *user_data);
+
 typedef struct {
   ngtcp2_send_client_initial send_client_initial;
   ngtcp2_send_client_cleartext send_client_cleartext;
@@ -546,6 +534,8 @@ typedef struct {
   ngtcp2_recv_frame recv_frame;
   ngtcp2_handshake_completed handshake_completed;
   ngtcp2_recv_version_negotiation recv_version_negotiation;
+  ngtcp2_encrypt encrypt;
+  ngtcp2_decrypt decrypt;
 } ngtcp2_conn_callbacks;
 
 /*
@@ -596,6 +586,17 @@ NGTCP2_EXTERN ssize_t ngtcp2_conn_send(ngtcp2_conn *conn, uint8_t *dest,
  */
 NGTCP2_EXTERN int ngtcp2_conn_handshake_completed(ngtcp2_conn *conn);
 
+NGTCP2_EXTERN void ngtcp2_conn_set_aead_overhead(ngtcp2_conn *conn,
+                                                 size_t aead_overhead);
+
+NGTCP2_EXTERN int ngtcp2_conn_update_tx_keys(ngtcp2_conn *conn,
+                                             const uint8_t *key, size_t keylen,
+                                             const uint8_t *iv, size_t ivlen);
+
+NGTCP2_EXTERN int ngtcp2_conn_update_rx_keys(ngtcp2_conn *conn,
+                                             const uint8_t *key, size_t keylen,
+                                             const uint8_t *iv, size_t ivlen);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c
index 8666a8f3..fcf3842f 100644
--- a/lib/ngtcp2_conn.c
+++ b/lib/ngtcp2_conn.c
@@ -28,6 +28,7 @@
 #include <assert.h>
 
 #include "ngtcp2_upe.h"
+#include "ngtcp2_ppe.h"
 #include "ngtcp2_pkt.h"
 #include "ngtcp2_macro.h"
 
@@ -206,6 +207,9 @@ void ngtcp2_conn_del(ngtcp2_conn *conn) {
     return;
   }
 
+  ngtcp2_crypto_km_del(conn->rx_ckm, conn->mem);
+  ngtcp2_crypto_km_del(conn->tx_ckm, conn->mem);
+
   ngtcp2_strm_free(&conn->strm0);
 
   ngtcp2_pq_each(&conn->ackq, ackq_rx_pkt_free, conn);
@@ -479,6 +483,95 @@ static ssize_t ngtcp2_conn_send_server_cleartext(ngtcp2_conn *conn,
                                    ackfr.type == 0 ? NULL : &ackfr, tx_buf);
 }
 
+static ssize_t conn_send_connection_close(ngtcp2_conn *conn, uint8_t *dest,
+                                          size_t destlen, ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_ppe ppe;
+  ngtcp2_pkt_hd hd;
+  ngtcp2_frame fr;
+  ngtcp2_frame ackfr;
+  ssize_t nwrite;
+  ngtcp2_crypto_ctx ctx;
+
+  ackfr.type = 0;
+  rv = conn_create_ack_frame(conn, &ackfr.ack, ts);
+  if (rv != 0) {
+    return rv;
+  }
+
+  /* TODO Choose appropriate packet number size */
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_CONN_ID, NGTCP2_PKT_03, conn->conn_id,
+                     conn->next_tx_pkt_num, conn->version);
+
+  ctx.ckm = conn->tx_ckm;
+  ctx.aead_overhead = conn->aead_overhead;
+  ctx.encrypt = conn->callbacks.encrypt;
+  ctx.user_data = conn;
+
+  rv = ngtcp2_ppe_init(&ppe, dest, destlen, &ctx, conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  rv = ngtcp2_ppe_encode_hd(&ppe, &hd);
+  if (rv != 0) {
+    nwrite = rv;
+    goto fin;
+  }
+
+  rv = conn_call_send_pkt(conn, &hd);
+  if (rv != 0) {
+    nwrite = rv;
+    goto fin;
+  }
+
+  if (ackfr.type) {
+    rv = ngtcp2_ppe_encode_frame(&ppe, &ackfr);
+    if (rv != 0) {
+      nwrite = rv;
+      goto fin;
+    }
+
+    rv = conn_call_send_frame(conn, &hd, &ackfr);
+    if (rv != 0) {
+      nwrite = rv;
+      goto fin;
+    }
+  }
+
+  /* TODO CONNECTION_CLOSE cannot be sent if ACK frame is too
+     large. */
+
+  fr.type = NGTCP2_FRAME_CONNECTION_CLOSE;
+  fr.connection_close.error_code = NGTCP2_QUIC_INTERNAL_ERROR;
+  fr.connection_close.reasonlen = 0;
+  fr.connection_close.reason = NULL;
+
+  rv = ngtcp2_ppe_encode_frame(&ppe, &fr);
+  if (rv != 0) {
+    nwrite = rv;
+    goto fin;
+  }
+
+  rv = conn_call_send_frame(conn, &hd, &fr);
+  if (rv != 0) {
+    nwrite = rv;
+    goto fin;
+  }
+
+  nwrite = ngtcp2_ppe_final(&ppe, NULL);
+  if (nwrite < 0) {
+    goto fin;
+  }
+
+  ++conn->next_tx_pkt_num;
+
+fin:
+  ngtcp2_ppe_free(&ppe);
+
+  return nwrite;
+}
+
 ssize_t ngtcp2_conn_send(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
                          ngtcp2_tstamp ts) {
   ssize_t nwrite = 0;
@@ -502,7 +595,7 @@ ssize_t ngtcp2_conn_send(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
       if (rv != 0) {
         return rv;
       }
-      conn->state = NGTCP2_CS_HANDSHAKE_COMPLETED;
+      conn->state = NGTCP2_CS_POST_HANDSHAKE;
     }
     break;
   case NGTCP2_CS_SERVER_CI_RECVED:
@@ -518,6 +611,13 @@ ssize_t ngtcp2_conn_send(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
       break;
     }
     break;
+  case NGTCP2_CS_POST_HANDSHAKE:
+    nwrite = conn_send_connection_close(conn, dest, destlen, ts);
+    if (nwrite < 0) {
+      break;
+    }
+    conn->state = NGTCP2_CS_CLOSE_WAIT;
+    break;
   }
 
   return nwrite;
@@ -630,9 +730,11 @@ static int ngtcp2_conn_recv_cleartext(ngtcp2_conn *conn, uint8_t exptype,
       return rv;
     }
 
-    /* We don't ack packet which contains ACK frames only. */
+    /* We don't ack packet which contains ACK and CONNECTION_CLOSE
+       only. */
     /* TODO What about packet with PADDING frames only? */
-    require_ack |= fr.type != NGTCP2_FRAME_ACK;
+    require_ack |=
+        fr.type != NGTCP2_FRAME_ACK && fr.type != NGTCP2_FRAME_CONNECTION_CLOSE;
 
     if (fr.type != NGTCP2_FRAME_STREAM || fr.stream.stream_id != 0 ||
         conn->strm0.rx_offset >= fr.stream.offset + fr.stream.datalen) {
@@ -660,6 +762,8 @@ static int ngtcp2_conn_recv_cleartext(ngtcp2_conn *conn, uint8_t exptype,
     }
   }
 
+  conn->max_rx_pkt_num = ngtcp2_max(conn->max_rx_pkt_num, hd.pkt_num);
+
   if (require_ack) {
     rv = ngtcp2_conn_sched_ack(conn, hd.pkt_num, ts);
     if (rv != 0) {
@@ -670,6 +774,137 @@ static int ngtcp2_conn_recv_cleartext(ngtcp2_conn *conn, uint8_t exptype,
   return 0;
 }
 
+static ssize_t conn_decrypt_packet(ngtcp2_conn *conn, uint8_t *dest,
+                                   size_t destlen, const uint8_t *pkt,
+                                   size_t pktlen, const uint8_t *ad,
+                                   size_t adlen, uint64_t pkt_num) {
+  uint8_t nonce[64];
+  ngtcp2_crypto_km *ckm = conn->rx_ckm;
+  ssize_t nwrite;
+
+  assert(sizeof(nonce) >= ckm->ivlen);
+
+  ngtcp2_crypto_create_nonce(nonce, ckm->iv, ckm->ivlen, pkt_num);
+
+  nwrite = conn->callbacks.decrypt(conn, dest, destlen, pkt, pktlen, ckm->key,
+                                   ckm->keylen, nonce, ckm->ivlen, ad, adlen,
+                                   conn->user_data);
+
+  if (nwrite < 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return nwrite;
+}
+
+static int conn_recv_packet(ngtcp2_conn *conn, const uint8_t *pkt,
+                            size_t pktlen, ngtcp2_tstamp ts) {
+  ngtcp2_pkt_hd hd;
+  size_t pkt_num_bits;
+  int encrypted = 0;
+  int rv = 0;
+  const uint8_t *hdpkt = pkt;
+  uint8_t *plaintext = NULL;
+  ssize_t nread, nwrite;
+  ngtcp2_frame fr;
+  int require_ack = 0;
+
+  if (pkt[0] & NGTCP2_HEADER_FORM_BIT) {
+    nread = ngtcp2_pkt_decode_hd_long(&hd, pkt, pktlen);
+  } else {
+    nread = ngtcp2_pkt_decode_hd_short(&hd, pkt, pktlen);
+  }
+  if (nread < 0) {
+    return (int)nread;
+  }
+
+  pkt += nread;
+  pktlen -= (size_t)nread;
+
+  if (hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) {
+    pkt_num_bits = 32;
+    if (hd.type == NGTCP2_PKT_1RTT_PROTECTED_K0) {
+      encrypted = 1;
+    }
+  } else {
+    switch (hd.type) {
+    case NGTCP2_PKT_01:
+      pkt_num_bits = 8;
+      break;
+    case NGTCP2_PKT_02:
+      pkt_num_bits = 16;
+      break;
+    case NGTCP2_PKT_03:
+      pkt_num_bits = 32;
+      break;
+    default:
+      assert(0);
+    }
+    if (!(hd.flags & NGTCP2_PKT_FLAG_KEY_PHASE)) {
+      encrypted = 1;
+    }
+  }
+
+  hd.pkt_num =
+      ngtcp2_pkt_adjust_pkt_num(conn->max_rx_pkt_num, hd.pkt_num, pkt_num_bits);
+
+  rv = conn_call_recv_pkt(conn, &hd);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (encrypted) {
+    plaintext = ngtcp2_mem_malloc(conn->mem, pktlen);
+    if (plaintext == NULL) {
+      return NGTCP2_ERR_NOMEM;
+    }
+    nwrite = conn_decrypt_packet(conn, plaintext, pktlen, pkt, pktlen, hdpkt,
+                                 (size_t)nread, hd.pkt_num);
+    if (nwrite < 0) {
+      rv = (int)nwrite;
+      goto fin;
+    }
+    pkt = plaintext;
+    pktlen = (size_t)nwrite;
+  }
+
+  for (; pktlen;) {
+    nread = ngtcp2_pkt_decode_frame(&fr, pkt, pktlen, conn->max_rx_pkt_num);
+    if (nread < 0) {
+      rv = (int)nread;
+      goto fin;
+    }
+
+    pkt += nread;
+    pktlen -= (size_t)nread;
+
+    rv = conn_call_recv_frame(conn, &hd, &fr);
+    if (rv != 0) {
+      goto fin;
+    }
+
+    /* We don't ack packet which contains ACK and CONNECTION_CLOSE
+       only. */
+    /* TODO What about packet with PADDING frames only? */
+    require_ack |=
+        fr.type != NGTCP2_FRAME_ACK && fr.type != NGTCP2_FRAME_CONNECTION_CLOSE;
+  }
+
+  conn->max_rx_pkt_num = ngtcp2_max(conn->max_rx_pkt_num, hd.pkt_num);
+
+  if (require_ack) {
+    rv = ngtcp2_conn_sched_ack(conn, hd.pkt_num, ts);
+    if (rv != 0) {
+      goto fin;
+    }
+  }
+
+fin:
+  ngtcp2_mem_free(conn->mem, plaintext);
+
+  return rv;
+}
+
 int ngtcp2_conn_recv(ngtcp2_conn *conn, const uint8_t *pkt, size_t pktlen,
                      ngtcp2_tstamp ts) {
   int rv = 0;
@@ -721,7 +956,14 @@ int ngtcp2_conn_recv(ngtcp2_conn *conn, const uint8_t *pkt, size_t pktlen,
       if (rv != 0) {
         return rv;
       }
-      conn->state = NGTCP2_CS_HANDSHAKE_COMPLETED;
+      conn->state = NGTCP2_CS_POST_HANDSHAKE;
+    }
+    break;
+  case NGTCP2_CS_POST_HANDSHAKE:
+  case NGTCP2_CS_CLOSE_WAIT:
+    rv = conn_recv_packet(conn, pkt, pktlen, ts);
+    if (rv < 0) {
+      break;
     }
     break;
   }
@@ -857,3 +1099,25 @@ int ngtcp2_accept(ngtcp2_pkt_hd *dest, const uint8_t *pkt, size_t pktlen) {
 
   return 0;
 }
+
+void ngtcp2_conn_set_aead_overhead(ngtcp2_conn *conn, size_t aead_overhead) {
+  conn->aead_overhead = aead_overhead;
+}
+
+int ngtcp2_conn_update_tx_keys(ngtcp2_conn *conn, const uint8_t *key,
+                               size_t keylen, const uint8_t *iv, size_t ivlen) {
+  if (conn->tx_ckm) {
+    return NGTCP2_ERR_INVALID_STATE;
+  }
+
+  return ngtcp2_crypto_km_new(&conn->tx_ckm, key, keylen, iv, ivlen, conn->mem);
+}
+
+int ngtcp2_conn_update_rx_keys(ngtcp2_conn *conn, const uint8_t *key,
+                               size_t keylen, const uint8_t *iv, size_t ivlen) {
+  if (conn->rx_ckm) {
+    return NGTCP2_ERR_INVALID_STATE;
+  }
+
+  return ngtcp2_crypto_km_new(&conn->rx_ckm, key, keylen, iv, ivlen, conn->mem);
+}
diff --git a/lib/ngtcp2_conn.h b/lib/ngtcp2_conn.h
index ebbb3969..6f55a54f 100644
--- a/lib/ngtcp2_conn.h
+++ b/lib/ngtcp2_conn.h
@@ -34,6 +34,7 @@
 #include "ngtcp2_mem.h"
 #include "ngtcp2_buf.h"
 #include "ngtcp2_rob.h"
+#include "ngtcp2_crypto.h"
 
 typedef enum {
   /* Client specific handshake states */
@@ -50,7 +51,8 @@ typedef enum {
   NGTCP2_CS_SERVER_SC_ACKED,
   NGTCP2_CS_SERVER_CC_RECVED,
   /* Shared by both client and server */
-  NGTCP2_CS_HANDSHAKE_COMPLETED,
+  NGTCP2_CS_POST_HANDSHAKE,
+  NGTCP2_CS_CLOSE_WAIT,
 } ngtcp2_conn_state;
 
 typedef struct {
@@ -102,6 +104,9 @@ struct ngtcp2_conn {
   uint32_t version;
   int handshake_completed;
   int server;
+  ngtcp2_crypto_km *tx_ckm;
+  ngtcp2_crypto_km *rx_ckm;
+  size_t aead_overhead;
 };
 
 /*
diff --git a/lib/ngtcp2_conv.c b/lib/ngtcp2_conv.c
index 0a090941..1c8a9e42 100644
--- a/lib/ngtcp2_conv.c
+++ b/lib/ngtcp2_conv.c
@@ -26,19 +26,8 @@
 
 #include <string.h>
 
-#ifdef HAVE_ARPA_INET_H
-#include <arpa/inet.h>
-#endif /* HAVE_ARPA_INET_H */
-
 #include "ngtcp2_str.h"
 
-#ifdef WORDS_BIGENDIAN
-#define bwap64(N) (N)
-#else /* !WORDS_BIGENDIAN */
-#define bswap64(N)                                                             \
-  (((uint64_t)(ntohl(((uint32_t)(N)) & 0xffffffffu))) << 32 | ntohl((N) >> 32))
-#endif /* !WORDS_BIGENDIAN */
-
 uint64_t ngtcp2_get_uint64(const uint8_t *p) {
   uint64_t n;
   memcpy(&n, p, 8);
diff --git a/lib/ngtcp2_conv.h b/lib/ngtcp2_conv.h
index 456ea9fd..841be523 100644
--- a/lib/ngtcp2_conv.h
+++ b/lib/ngtcp2_conv.h
@@ -29,8 +29,19 @@
 #include <config.h>
 #endif /* HAVE_CONFIG_H */
 
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif /* HAVE_ARPA_INET_H */
+
 #include <ngtcp2/ngtcp2.h>
 
+#ifdef WORDS_BIGENDIAN
+#define bwap64(N) (N)
+#else /* !WORDS_BIGENDIAN */
+#define bswap64(N)                                                             \
+  (((uint64_t)(ntohl(((uint32_t)(N)) & 0xffffffffu))) << 32 | ntohl((N) >> 32))
+#endif /* !WORDS_BIGENDIAN */
+
 /*
  * ngtcp2_get_uint64 reads 8 bytes from |p| as 64 bits unsigned
  * integer encoded as network byte order, and returns it in host byte
diff --git a/lib/ngtcp2_crypto.c b/lib/ngtcp2_crypto.c
new file mode 100644
index 00000000..88eaa12c
--- /dev/null
+++ b/lib/ngtcp2_crypto.c
@@ -0,0 +1,75 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_crypto.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_str.h"
+#include "ngtcp2_conv.h"
+
+int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *key,
+                         size_t keylen, const uint8_t *iv, size_t ivlen,
+                         ngtcp2_mem *mem) {
+  size_t len;
+  uint8_t *p;
+
+  len = sizeof(ngtcp2_crypto_km) + keylen + ivlen;
+
+  *pckm = ngtcp2_mem_malloc(mem, len);
+  if (*pckm == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  p = (uint8_t *)(*pckm) + sizeof(ngtcp2_crypto_km);
+  (*pckm)->key = p;
+  (*pckm)->keylen = keylen;
+  p = ngtcp2_cpymem(p, key, keylen);
+  (*pckm)->iv = p;
+  (*pckm)->ivlen = ivlen;
+  p = ngtcp2_cpymem(p, iv, ivlen);
+
+  return 0;
+}
+
+void ngtcp2_crypto_km_del(ngtcp2_crypto_km *ckm, ngtcp2_mem *mem) {
+  if (ckm == NULL) {
+    return;
+  }
+
+  ngtcp2_mem_free(mem, ckm);
+}
+
+void ngtcp2_crypto_create_nonce(uint8_t *dest, const uint8_t *iv, size_t ivlen,
+                                uint64_t pkt_num) {
+  size_t i;
+
+  memcpy(dest, iv, ivlen);
+  pkt_num = bswap64(pkt_num);
+
+  for (i = 0; i < 8; ++i) {
+    dest[ivlen - 8 + i] ^= ((uint8_t *)&pkt_num)[i];
+  }
+}
diff --git a/lib/ngtcp2_crypto.h b/lib/ngtcp2_crypto.h
new file mode 100644
index 00000000..ff187702
--- /dev/null
+++ b/lib/ngtcp2_crypto.h
@@ -0,0 +1,60 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CRYPTO_H
+#define NGTCP2_CRYPTO_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_mem.h"
+
+typedef struct {
+  const uint8_t *key;
+  size_t keylen;
+  const uint8_t *iv;
+  size_t ivlen;
+} ngtcp2_crypto_km;
+
+int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *key,
+                         size_t keylen, const uint8_t *iv, size_t ivlen,
+                         ngtcp2_mem *mem);
+
+void ngtcp2_crypto_km_del(ngtcp2_crypto_km *ckm, ngtcp2_mem *mem);
+
+typedef struct {
+  const ngtcp2_crypto_km *ckm;
+  size_t aead_overhead;
+  ngtcp2_encrypt encrypt;
+  ngtcp2_decrypt decrypt;
+  void *user_data;
+} ngtcp2_crypto_ctx;
+
+void ngtcp2_crypto_create_nonce(uint8_t *dest, const uint8_t *iv, size_t ivlen,
+                                size_t pkt_num);
+
+#endif /* NGTCP2_CRYPTO_H */
diff --git a/lib/ngtcp2_pkt.c b/lib/ngtcp2_pkt.c
index e42d669a..5ddb0461 100644
--- a/lib/ngtcp2_pkt.c
+++ b/lib/ngtcp2_pkt.c
@@ -133,6 +133,8 @@ ssize_t ngtcp2_pkt_decode_hd_short(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
 
   ++p;
 
+  dest->type = type;
+
   if (flags & NGTCP2_PKT_FLAG_CONN_ID) {
     dest->conn_id = ngtcp2_get_uint64(p);
     p += 8;
@@ -588,11 +590,39 @@ ssize_t ngtcp2_pkt_decode_rst_stream_frame(ngtcp2_rst_stream *dest,
 
 ssize_t ngtcp2_pkt_decode_connection_close_frame(ngtcp2_connection_close *dest,
                                                  const uint8_t *payload,
-                                                 size_t len) {
-  (void)dest;
-  (void)payload;
-  (void)len;
-  return -1;
+                                                 size_t payloadlen) {
+  size_t len = 1 + 4 + 2;
+  const uint8_t *p;
+  size_t reasonlen;
+
+  if (payloadlen < len || payload[0] != NGTCP2_FRAME_CONNECTION_CLOSE) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  reasonlen = ngtcp2_get_uint16(payload + 1 + 4);
+  len += reasonlen;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  p = payload + 1;
+
+  dest->type = NGTCP2_FRAME_CONNECTION_CLOSE;
+  dest->error_code = ngtcp2_get_uint32(p);
+  p += 4;
+  dest->reasonlen = reasonlen;
+  p += 2;
+  if (reasonlen == 0) {
+    dest->reason = NULL;
+  } else {
+    dest->reason = (uint8_t *)p;
+    p += reasonlen;
+  }
+
+  assert((size_t)(p - payload) == len);
+
+  return (ssize_t)len;
 }
 
 ssize_t ngtcp2_pkt_decode_goaway_frame(ngtcp2_goaway *dest,
@@ -681,6 +711,9 @@ ssize_t ngtcp2_pkt_encode_frame(uint8_t *out, size_t outlen,
     return ngtcp2_pkt_encode_ack_frame(out, outlen, &fr->ack);
   case NGTCP2_FRAME_PADDING:
     return ngtcp2_pkt_encode_padding_frame(out, outlen, &fr->padding);
+  case NGTCP2_FRAME_CONNECTION_CLOSE:
+    return ngtcp2_pkt_encode_connection_close_frame(out, outlen,
+                                                    &fr->connection_close);
   default:
     return NGTCP2_ERR_INVALID_ARGUMENT;
   }
@@ -828,6 +861,30 @@ ssize_t ngtcp2_pkt_encode_padding_frame(uint8_t *out, size_t outlen,
   return (ssize_t)fr->len;
 }
 
+ssize_t
+ngtcp2_pkt_encode_connection_close_frame(uint8_t *out, size_t outlen,
+                                         const ngtcp2_connection_close *fr) {
+  size_t len = 1 + 4 + 2 + fr->reasonlen;
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_CONNECTION_CLOSE;
+  p = ngtcp2_put_uint32be(p, fr->error_code);
+  p = ngtcp2_put_uint16be(p, (uint16_t)fr->reasonlen);
+  if (fr->reasonlen) {
+    p = ngtcp2_cpymem(p, fr->reason, fr->reasonlen);
+  }
+
+  assert((size_t)(p - out) == len);
+
+  return (ssize_t)len;
+}
+
 int ngtcp2_pkt_verify(const uint8_t *pkt, size_t pktlen) {
   uint64_t a, b;
 
diff --git a/lib/ngtcp2_pkt.h b/lib/ngtcp2_pkt.h
index 930f369e..e5f7a69f 100644
--- a/lib/ngtcp2_pkt.h
+++ b/lib/ngtcp2_pkt.h
@@ -261,6 +261,20 @@ ssize_t ngtcp2_pkt_encode_ack_frame(uint8_t *out, size_t outlen,
 ssize_t ngtcp2_pkt_encode_padding_frame(uint8_t *out, size_t outlen,
                                         const ngtcp2_padding *fr);
 
+/**
+ * ngtcp2_pkt_encode_connection_close_frame encodes CONNECTION_CLOSE
+ * frame |fr| into the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ssize_t
+ngtcp2_pkt_encode_connection_close_frame(uint8_t *out, size_t outlen,
+                                         const ngtcp2_connection_close *fr);
+
 /**
  * ngtcp2_pkt_adjust_pkt_num find the full 64 bits packet number for
  * |pkt_num|, which is expected to be least significant |n| bits.  The
diff --git a/lib/ngtcp2_ppe.c b/lib/ngtcp2_ppe.c
new file mode 100644
index 00000000..c1baa0ce
--- /dev/null
+++ b/lib/ngtcp2_ppe.c
@@ -0,0 +1,124 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_ppe.h"
+#include "ngtcp2_pkt.h"
+#include "ngtcp2_str.h"
+#include "ngtcp2_conv.h"
+#include "ngtcp2_mem.h"
+#include "ngtcp2_conn.h"
+
+int ngtcp2_ppe_init(ngtcp2_ppe *ppe, uint8_t *out, size_t outlen,
+                    ngtcp2_crypto_ctx *cctx, ngtcp2_mem *mem) {
+  uint8_t *nonce;
+  size_t plen;
+
+  if (cctx->aead_overhead > outlen) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  plen = outlen - cctx->aead_overhead;
+  nonce = ngtcp2_mem_malloc(mem, cctx->ckm->ivlen + plen);
+  if (nonce == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  ngtcp2_buf_init(&ppe->pbuf, nonce + cctx->ckm->ivlen, plen);
+  ngtcp2_buf_init(&ppe->cbuf, out, outlen);
+
+  ppe->nonce = nonce;
+  ppe->ctx = cctx;
+  ppe->mem = mem;
+
+  return 0;
+}
+
+void ngtcp2_ppe_free(ngtcp2_ppe *ppe) {
+  if (ppe == NULL) {
+    return;
+  }
+
+  ngtcp2_mem_free(ppe->mem, ppe->nonce);
+}
+
+int ngtcp2_ppe_encode_hd(ngtcp2_ppe *ppe, const ngtcp2_pkt_hd *hd) {
+  ssize_t rv;
+  ngtcp2_buf *buf = &ppe->cbuf;
+
+  if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) {
+    rv = ngtcp2_pkt_encode_hd_long(buf->last, ngtcp2_buf_left(buf), hd);
+  } else {
+    rv = ngtcp2_pkt_encode_hd_short(buf->last, ngtcp2_buf_left(buf), hd);
+  }
+  if (rv < 0) {
+    return (int)rv;
+  }
+
+  buf->last += rv;
+
+  ppe->pkt_num = hd->pkt_num;
+
+  return 0;
+}
+
+int ngtcp2_ppe_encode_frame(ngtcp2_ppe *ppe, const ngtcp2_frame *fr) {
+  ssize_t rv;
+  ngtcp2_buf *buf = &ppe->pbuf;
+
+  rv = ngtcp2_pkt_encode_frame(buf->last, ngtcp2_buf_left(buf), fr);
+  if (rv < 0) {
+    return (int)rv;
+  }
+
+  buf->last += rv;
+
+  return 0;
+}
+
+ssize_t ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt) {
+  ssize_t rv;
+  ngtcp2_buf *cbuf = &ppe->cbuf;
+  ngtcp2_buf *pbuf = &ppe->pbuf;
+  ngtcp2_crypto_ctx *ctx = ppe->ctx;
+  ngtcp2_conn *conn = ctx->user_data;
+
+  ngtcp2_crypto_create_nonce(ppe->nonce, ctx->ckm->iv, ctx->ckm->ivlen,
+                             ppe->pkt_num);
+
+  rv = ppe->ctx->encrypt(conn, cbuf->last, ngtcp2_buf_left(cbuf), pbuf->pos,
+                         ngtcp2_buf_len(pbuf), ctx->ckm->key, ctx->ckm->keylen,
+                         ppe->nonce, ctx->ckm->ivlen, cbuf->begin,
+                         ngtcp2_buf_len(cbuf), conn->user_data);
+  if (rv < 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  cbuf->last += rv;
+
+  if (ppkt != NULL) {
+    *ppkt = cbuf->begin;
+  }
+
+  return (ssize_t)ngtcp2_buf_len(cbuf);
+}
diff --git a/lib/ngtcp2_ppe.h b/lib/ngtcp2_ppe.h
new file mode 100644
index 00000000..836c6ea1
--- /dev/null
+++ b/lib/ngtcp2_ppe.h
@@ -0,0 +1,63 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_PPE_H
+#define NGTCP2_PPE_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_buf.h"
+#include "ngtcp2_crypto.h"
+
+/*
+ * ngtcp2_ppe is the Protected Packet Encoder.
+ */
+typedef struct {
+  uint8_t *nonce;
+  ngtcp2_buf cbuf;
+  ngtcp2_buf pbuf;
+  ngtcp2_crypto_ctx *ctx;
+  uint64_t pkt_num;
+  ngtcp2_mem *mem;
+} ngtcp2_ppe;
+
+/*
+ * ngtcp2_ppe_init initializes |ppe| with the given buffer.
+ */
+int ngtcp2_ppe_init(ngtcp2_ppe *ppe, uint8_t *out, size_t outlen,
+                    ngtcp2_crypto_ctx *cctx, ngtcp2_mem *mem);
+
+void ngtcp2_ppe_free(ngtcp2_ppe *ppe);
+
+int ngtcp2_ppe_encode_hd(ngtcp2_ppe *ppe, const ngtcp2_pkt_hd *hd);
+
+int ngtcp2_ppe_encode_frame(ngtcp2_ppe *ppe, const ngtcp2_frame *fr);
+
+ssize_t ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt);
+
+#endif /* NGTCP2_PPE_H */
diff --git a/tests/main.c b/tests/main.c
index 0698f61c..5d4477f4 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -68,6 +68,8 @@ int main() {
                    test_ngtcp2_pkt_encode_stream_frame) ||
       !CU_add_test(pSuite, "pkt_encode_ack_frame",
                    test_ngtcp2_pkt_encode_ack_frame) ||
+      !CU_add_test(pSuite, "pkt_encode_connection_close_frame",
+                   test_ngtcp2_pkt_encode_connection_close_frame) ||
       !CU_add_test(pSuite, "pkt_adjust_pkt_num",
                    test_ngtcp2_pkt_adjust_pkt_num) ||
       !CU_add_test(pSuite, "upe_encode", test_ngtcp2_upe_encode) ||
diff --git a/tests/ngtcp2_pkt_test.c b/tests/ngtcp2_pkt_test.c
index eea3cf0b..6d320172 100644
--- a/tests/ngtcp2_pkt_test.c
+++ b/tests/ngtcp2_pkt_test.c
@@ -73,6 +73,7 @@ void test_ngtcp2_pkt_decode_hd_short(void) {
 
   CU_ASSERT((ssize_t)expectedlen == rv);
   CU_ASSERT(hd.flags == nhd.flags);
+  CU_ASSERT(NGTCP2_PKT_03 == nhd.type);
   CU_ASSERT(0 == nhd.conn_id);
   CU_ASSERT(hd.pkt_num == nhd.pkt_num);
   CU_ASSERT(0 == nhd.version);
@@ -91,6 +92,7 @@ void test_ngtcp2_pkt_decode_hd_short(void) {
 
   CU_ASSERT((ssize_t)expectedlen == rv);
   CU_ASSERT(hd.flags == nhd.flags);
+  CU_ASSERT(NGTCP2_PKT_02 == nhd.type);
   CU_ASSERT(0 == nhd.conn_id);
   CU_ASSERT((hd.pkt_num & 0xffff) == nhd.pkt_num);
   CU_ASSERT(0 == nhd.version);
@@ -109,6 +111,7 @@ void test_ngtcp2_pkt_decode_hd_short(void) {
 
   CU_ASSERT((ssize_t)expectedlen == rv);
   CU_ASSERT(hd.flags == nhd.flags);
+  CU_ASSERT(NGTCP2_PKT_01 == nhd.type);
   CU_ASSERT(0 == nhd.conn_id);
   CU_ASSERT((hd.pkt_num & 0xff) == nhd.pkt_num);
   CU_ASSERT(0 == nhd.version);
@@ -128,6 +131,7 @@ void test_ngtcp2_pkt_decode_hd_short(void) {
 
   CU_ASSERT((ssize_t)expectedlen == rv);
   CU_ASSERT(hd.flags == nhd.flags);
+  CU_ASSERT(NGTCP2_PKT_03 == nhd.type);
   CU_ASSERT(hd.conn_id == nhd.conn_id);
   CU_ASSERT(hd.pkt_num == nhd.pkt_num);
   CU_ASSERT(0 == nhd.version);
@@ -516,6 +520,64 @@ void test_ngtcp2_pkt_encode_ack_frame(void) {
   memset(&nfr, 0, sizeof(nfr));
 }
 
+void test_ngtcp2_pkt_encode_connection_close_frame(void) {
+  uint8_t buf[2048];
+  ngtcp2_frame fr, nfr;
+  ssize_t rv;
+  size_t framelen;
+  uint8_t reason[1024];
+
+  memset(reason, 0xfa, sizeof(reason));
+
+  /* no Reason Phrase */
+  fr.type = NGTCP2_FRAME_CONNECTION_CLOSE;
+  fr.connection_close.error_code = 0xf1f2f3f4u;
+  fr.connection_close.reasonlen = 0;
+  fr.connection_close.reason = NULL;
+
+  framelen = 1 + 4 + 2;
+
+  rv = ngtcp2_pkt_encode_connection_close_frame(buf, sizeof(buf),
+                                                &fr.connection_close);
+
+  CU_ASSERT((ssize_t)framelen == rv);
+
+  rv = ngtcp2_pkt_decode_connection_close_frame(&nfr.connection_close, buf,
+                                                framelen);
+
+  CU_ASSERT((ssize_t)framelen == rv);
+  CU_ASSERT(fr.type == nfr.type);
+  CU_ASSERT(fr.connection_close.error_code == nfr.connection_close.error_code);
+  CU_ASSERT(fr.connection_close.reasonlen == nfr.connection_close.reasonlen);
+  CU_ASSERT(fr.connection_close.reason == nfr.connection_close.reason);
+
+  memset(&nfr, 0, sizeof(nfr));
+
+  /* 1024 bytes Reason Phrase */
+  fr.type = NGTCP2_FRAME_CONNECTION_CLOSE;
+  fr.connection_close.error_code = 0xf1f2f3f4u;
+  fr.connection_close.reasonlen = sizeof(reason);
+  fr.connection_close.reason = reason;
+
+  framelen = 1 + 4 + 2 + sizeof(reason);
+
+  rv = ngtcp2_pkt_encode_connection_close_frame(buf, sizeof(buf),
+                                                &fr.connection_close);
+
+  CU_ASSERT((ssize_t)framelen == rv);
+
+  rv = ngtcp2_pkt_decode_connection_close_frame(&nfr.connection_close, buf,
+                                                framelen);
+
+  CU_ASSERT((ssize_t)framelen == rv);
+  CU_ASSERT(fr.type == nfr.type);
+  CU_ASSERT(fr.connection_close.error_code == nfr.connection_close.error_code);
+  CU_ASSERT(fr.connection_close.reasonlen == nfr.connection_close.reasonlen);
+  CU_ASSERT(0 == memcmp(reason, nfr.connection_close.reason, sizeof(reason)));
+
+  memset(&nfr, 0, sizeof(nfr));
+}
+
 void test_ngtcp2_pkt_adjust_pkt_num(void) {
   CU_ASSERT(0xaa831f94llu ==
             ngtcp2_pkt_adjust_pkt_num(0xaa82f30ellu, 0x1f94, 16));
diff --git a/tests/ngtcp2_pkt_test.h b/tests/ngtcp2_pkt_test.h
index 83f5d968..6cb5fbe2 100644
--- a/tests/ngtcp2_pkt_test.h
+++ b/tests/ngtcp2_pkt_test.h
@@ -36,6 +36,7 @@ void test_ngtcp2_pkt_decode_ack_frame(void);
 void test_ngtcp2_pkt_decode_padding_frame(void);
 void test_ngtcp2_pkt_encode_stream_frame(void);
 void test_ngtcp2_pkt_encode_ack_frame(void);
+void test_ngtcp2_pkt_encode_connection_close_frame(void);
 void test_ngtcp2_pkt_adjust_pkt_num(void);
 
 #endif /* NGTCP2_PKT_TEST_H */
-- 
GitLab