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