diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1266ccc7..b290170c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -148,6 +148,7 @@ if(ENABLE_APP) shrpx_http3_upstream.cc http3.cc quic.cc + siphash.cc ) endif() add_library(nghttpx_static STATIC ${NGHTTPX_SRCS}) @@ -190,6 +191,9 @@ if(ENABLE_APP) base64_test.cc ${CMAKE_SOURCE_DIR}/tests/munit/munit.c ) + if(ENABLE_HTTP3) + list(APPEND NGHTTPX_UNITTEST_SOURCES siphash_test.cc) + endif() add_executable(nghttpx-unittest EXCLUDE_FROM_ALL ${NGHTTPX_UNITTEST_SOURCES} $ diff --git a/src/Makefile.am b/src/Makefile.am index 2cb67d90..97ee184d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -189,7 +189,8 @@ NGHTTPX_SRCS += \ shrpx_quic_connection_handler.cc shrpx_quic_connection_handler.h \ shrpx_http3_upstream.cc shrpx_http3_upstream.h \ http3.cc http3.h \ - quic.cc quic.h + quic.cc quic.h \ + siphash.cc siphash.h endif # ENABLE_HTTP3 noinst_LIBRARIES = libnghttpx.a @@ -229,6 +230,9 @@ nghttpx_unittest_SOURCES = shrpx-unittest.cc \ base64_test.cc base64_test.h \ $(top_srcdir)/tests/munit/munit.c $(top_srcdir)/tests/munit/munit.h \ $(top_srcdir)/tests/munit/munitxx.h +if ENABLE_HTTP3 +nghttpx_unittest_SOURCES += siphash_test.cc siphash_test.h +endif # ENABLE_HTTP3 nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS} \ -I$(top_srcdir)/tests/munit \ -DNGHTTP2_SRC_DIR=\"$(top_srcdir)/src\" diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index 00147710..929aa3f3 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -45,18 +45,31 @@ #include "tls.h" #include "shrpx_router_test.h" #include "shrpx_log.h" +#ifdef ENABLE_HTTP3 +# include "siphash_test.h" +#endif // ENABLE_HTTP3 int main(int argc, char *argv[]) { shrpx::create_config(); const MunitSuite suites[] = { - shrpx::tls_suite, shrpx::downstream_suite, - shrpx::config_suite, shrpx::worker_suite, - shrpx::http_suite, shrpx::router_suite, - shrpx::http2_suite, shrpx::util_suite, - gzip_suite, buffer_suite, - memchunk_suite, template_suite, - base64_suite, {}, + shrpx::tls_suite, + shrpx::downstream_suite, + shrpx::config_suite, + shrpx::worker_suite, + shrpx::http_suite, + shrpx::router_suite, + shrpx::http2_suite, + shrpx::util_suite, + gzip_suite, + buffer_suite, + memchunk_suite, + template_suite, + base64_suite, +#ifdef ENABLE_HTTP3 + siphash_suite, +#endif // ENABLE_HTTP3 + {}, }; const MunitSuite suite = { "", nullptr, suites, 1, MUNIT_SUITE_OPTION_NONE, diff --git a/src/shrpx_quic.h b/src/shrpx_quic.h index cfddea8d..aa3c80a0 100644 --- a/src/shrpx_quic.h +++ b/src/shrpx_quic.h @@ -38,31 +38,32 @@ #ifdef NGHTTP2_OPENSSL_IS_WOLFSSL # include # include +# include #else // !NGHTTP2_OPENSSL_IS_WOLFSSL # include +# include #endif // !NGHTTP2_OPENSSL_IS_WOLFSSL #include +#include "siphash.h" +#include "template.h" #include "network.h" using namespace nghttp2; namespace std { template <> struct hash { - std::size_t operator()(const ngtcp2_cid &cid) const noexcept { - // FNV-1a 64bits variant - constexpr uint64_t basis = 0xCBF29CE484222325ULL; - const uint8_t *p = cid.data, *end = cid.data + cid.datalen; - uint64_t h = basis; - - for (; p != end;) { - h ^= *p++; - h *= basis; - } - - return static_cast(h); + hash() { + auto s = as_writable_uint8_span(std::span{key}); + assert(RAND_bytes(s.data(), s.size()) == 1); } + + std::size_t operator()(const ngtcp2_cid &cid) const noexcept { + return static_cast(siphash24(key, {cid.data, cid.datalen})); + } + + std::array key; }; } // namespace std diff --git a/src/siphash.cc b/src/siphash.cc new file mode 100644 index 00000000..6ab58ad2 --- /dev/null +++ b/src/siphash.cc @@ -0,0 +1,114 @@ +/* Copyright 2019 The BoringSSL Authors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2025 nghttp2 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 +#include + +#include "siphash.h" + +namespace { +auto CRYPTO_load_u64_le(std::span in) { + uint64_t v; + + memcpy(&v, in.data(), sizeof(v)); + + if constexpr (std::endian::native == std::endian::big) { + return byteswap(v); + } + + return v; +} +} // namespace + +namespace { +constexpr void siphash_round(uint64_t v[4]) { + v[0] += v[1]; + v[2] += v[3]; + v[1] = std::rotl(v[1], 13); + v[3] = std::rotl(v[3], 16); + v[1] ^= v[0]; + v[3] ^= v[2]; + v[0] = std::rotl(v[0], 32); + v[2] += v[1]; + v[0] += v[3]; + v[1] = std::rotl(v[1], 17); + v[3] = std::rotl(v[3], 21); + v[1] ^= v[2]; + v[3] ^= v[0]; + v[2] = std::rotl(v[2], 32); +} +} // namespace + +uint64_t siphash24(std::span key, + std::span input) { + const auto orig_input_len = input.size(); + uint64_t v[]{ + key[0] ^ UINT64_C(0x736f6d6570736575), + key[1] ^ UINT64_C(0x646f72616e646f6d), + key[0] ^ UINT64_C(0x6c7967656e657261), + key[1] ^ UINT64_C(0x7465646279746573), + }; + + while (input.size() >= sizeof(uint64_t)) { + auto m = CRYPTO_load_u64_le(input.first()); + v[3] ^= m; + siphash_round(v); + siphash_round(v); + v[0] ^= m; + + input = input.subspan(sizeof(uint64_t)); + } + + std::array last_block{}; + std::ranges::copy(input, std::begin(last_block)); + last_block.back() = orig_input_len & 0xff; + + auto last_block_word = CRYPTO_load_u64_le(last_block); + v[3] ^= last_block_word; + siphash_round(v); + siphash_round(v); + v[0] ^= last_block_word; + + v[2] ^= 0xff; + siphash_round(v); + siphash_round(v); + siphash_round(v); + siphash_round(v); + + return v[0] ^ v[1] ^ v[2] ^ v[3]; +} diff --git a/src/siphash.h b/src/siphash.h new file mode 100644 index 00000000..07c538ef --- /dev/null +++ b/src/siphash.h @@ -0,0 +1,61 @@ +/* Copyright 2019 The BoringSSL Authors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2025 nghttp2 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 SIPHASH_H +#define SIPHASH_H + +#include +#include +#include +#include + +// SipHash is a fast, secure PRF that is often used for hash tables. + +// siphash24 implements SipHash-2-4. See +// https://131002.net/siphash/siphash.pdf +uint64_t siphash24(std::span key, + std::span input); + +// Define here to be usable in tests. +template T byteswap(T v) { + auto c = std::bit_cast>(v); + std::ranges::reverse(c); + return std::bit_cast(c); +} + +#endif // SIPHASH_H diff --git a/src/siphash_test.cc b/src/siphash_test.cc new file mode 100644 index 00000000..57d8b439 --- /dev/null +++ b/src/siphash_test.cc @@ -0,0 +1,68 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2025 nghttp2 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 "siphash_test.h" + +#include +#include +#include + +#include "munitxx.h" + +#include + +#include "siphash.h" + +namespace nghttp2 { + +namespace { +const MunitTest tests[]{ + munit_void_test(test_siphash), + munit_test_end(), +}; +} // namespace + +const MunitSuite siphash_suite{ + "/siphash", tests, nullptr, 1, MUNIT_SUITE_OPTION_NONE, +}; + +void test_siphash(void) { + std::array key_bytes; + std::iota(std::begin(key_bytes), std::end(key_bytes), 0); + + std::array key; + memcpy(key.data(), key_bytes.data(), key_bytes.size()); + + if constexpr (std::endian::native == std::endian::big) { + key[0] = byteswap(key[0]); + key[1] = byteswap(key[1]); + } + + std::array input; + std::iota(std::begin(input), std::end(input), 0); + + assert_uint64(0xa129ca6149be45e5ull, ==, siphash24(key, input)); +} + +} // namespace nghttp2 diff --git a/src/siphash_test.h b/src/siphash_test.h new file mode 100644 index 00000000..4dd57e53 --- /dev/null +++ b/src/siphash_test.h @@ -0,0 +1,44 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2025 nghttp2 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 SIPHASH_TEST_H +#define SIPHASH_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +#define MUNIT_ENABLE_ASSERT_ALIASES + +#include "munit.h" + +namespace nghttp2 { + +extern const MunitSuite siphash_suite; + +munit_void_test_decl(test_siphash) + +} // namespace nghttp2 + +#endif // SIPHASH_TEST_H diff --git a/src/template.h b/src/template.h index 48e2b785..d3a2ca3a 100644 --- a/src/template.h +++ b/src/template.h @@ -482,6 +482,17 @@ as_uint8_span(std::span s) noexcept { {reinterpret_cast(s.data()), s.size_bytes()}; } +template +[[nodiscard]] std::span +as_writable_uint8_span(std::span s) noexcept { + return std::span < uint8_t, + N == std::dynamic_extent + ? std::dynamic_extent + : N * sizeof(T) > + {reinterpret_cast(s.data()), s.size_bytes()}; +} + inline int run_app(std::function app, int argc, char **argv) { try {