diff --git a/fizz/cmake/FizzSources.cmake b/fizz/cmake/FizzSources.cmake index fb29ba04e1d..012013e14a4 100644 --- a/fizz/cmake/FizzSources.cmake +++ b/fizz/cmake/FizzSources.cmake @@ -1,4 +1,4 @@ -# @generated SignedSource<> +# @generated SignedSource<<6739c8027c4f0f51ab7bf5dcb36c5d96>> # # This file is generated file from `fizz/facebook/boilerplate.sh`. # All manual changes will be lost. @@ -79,6 +79,7 @@ set( protocol/clock/SystemClock.cpp protocol/ech/Decrypter.cpp protocol/ech/Encryption.cpp + protocol/ech/GreaseECH.cpp record/BufAndPaddingPolicy.cpp record/EncryptedRecordLayer.cpp record/PlaintextRecordLayer.cpp @@ -225,6 +226,8 @@ set( protocol/ech/ECHExtensions-inl.h protocol/ech/ECHExtensions.h protocol/ech/Encryption.h + protocol/ech/GreaseECH.h + protocol/ech/GreaseECHSetting.h protocol/ech/Types-inl.h protocol/ech/Types.h record/BufAndPaddingPolicy.h diff --git a/fizz/protocol/ech/BUCK b/fizz/protocol/ech/BUCK index 9993aff9aea..d4c56056d76 100644 --- a/fizz/protocol/ech/BUCK +++ b/fizz/protocol/ech/BUCK @@ -52,3 +52,31 @@ cpp_library( "//fizz/protocol:factory", ], ) + +cpp_library( + name = "grease_ech_setting", + headers = [ + "GreaseECHSetting.h", + ], + exported_deps = [ + "//fizz/crypto/hpke:hpke", + ], +) + +cpp_library( + name = "grease_ech", + srcs = [ + "GreaseECH.cpp", + ], + headers = [ + "GreaseECH.h", + ], + deps = [ + "//fizz/crypto/hpke:utils", + ], + exported_deps = [ + ":encrypted_client_hello", + ":grease_ech_setting", + "//fizz/protocol:factory", + ], +) diff --git a/fizz/protocol/ech/GreaseECH.cpp b/fizz/protocol/ech/GreaseECH.cpp new file mode 100644 index 00000000000..82b624db808 --- /dev/null +++ b/fizz/protocol/ech/GreaseECH.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include + +#include + +namespace fizz { +namespace ech { +namespace { + +/** + * A random number generator adaptor based on the Factory. + * Current usage is limited to type size_t. + */ +class RandomNumberGenerator { + public: + typedef size_t result_type; + + static constexpr size_t min() { + return 0; + } + + static constexpr size_t max() { + return std::numeric_limits::max(); + } + + explicit RandomNumberGenerator(const Factory& factory) : factory_{factory} {} + + size_t operator()() const { + size_t number = 0; + factory_.makeRandomBytes( + reinterpret_cast(&number), sizeof(number)); + return number; + } + + private: + const Factory& factory_; +}; + +class RandomSelector { + public: + explicit RandomSelector(RandomNumberGenerator&& generator) + : generator_{std::move(generator)} {} + + size_t genNumber(size_t min, size_t max) const { + std::uniform_int_distribution distribution(min, max); + return distribution(generator_); + } + + template + T select(const std::vector& elems) const { + return elems[genNumber(0, elems.size() - 1)]; + } + + private: + const RandomNumberGenerator generator_; +}; +} // namespace + +OuterECHClientHello generateGreaseECH( + const GreaseECHSetting& setting, + const Factory& factory, + size_t encodedChloSize) { + RandomSelector selector{RandomNumberGenerator{factory}}; + OuterECHClientHello echExtension; + echExtension.cipher_suite = HpkeSymmetricCipherSuite{ + selector.select(setting.kdfs), selector.select(setting.aeads)}; + echExtension.config_id = + selector.genNumber(setting.minConfigId, setting.maxConfigId); + echExtension.enc = factory.makeRandomIOBuf(selector.select(setting.keySizes)); + size_t payloadSize = + selector.genNumber(setting.minPayloadSize, setting.maxPayloadSize); + if (setting.payloadStrategy == PayloadGenerationStrategy::Computed) { + payloadSize += encodedChloSize + + hpke::getCipherOverhead(echExtension.cipher_suite.aead_id); + } + echExtension.payload = factory.makeRandomIOBuf(payloadSize); + return echExtension; +} +} // namespace ech +} // namespace fizz diff --git a/fizz/protocol/ech/GreaseECH.h b/fizz/protocol/ech/GreaseECH.h new file mode 100644 index 00000000000..d3c0e7925e3 --- /dev/null +++ b/fizz/protocol/ech/GreaseECH.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace fizz { +namespace ech { +OuterECHClientHello generateGreaseECH( + const GreaseECHSetting& setting, + const Factory& factory, + size_t encodedChloSize); +} // namespace ech +} // namespace fizz diff --git a/fizz/protocol/ech/GreaseECHSetting.h b/fizz/protocol/ech/GreaseECHSetting.h new file mode 100644 index 00000000000..536ece179a7 --- /dev/null +++ b/fizz/protocol/ech/GreaseECHSetting.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +namespace fizz { +namespace ech { + +enum class PayloadGenerationStrategy : uint8_t { + /** + * Generates a payload with a size P where P is in the range + * [minPayloadSize, maxPayloadSize]. + */ + UniformRandom = 0, + + /** + * Generates a payload with size L + C + P bytes where L is the size of the + * encoded inner client hello, C is the ciphertext expansion of the selected + * AEAD schema, and P is the expected padding within the range + * [minPayloadSize, maxPayloadSize]. + */ + Computed +}; + +struct GreaseECHSetting { + uint8_t minConfigId{0}; + uint8_t maxConfigId{std::numeric_limits::max()}; + PayloadGenerationStrategy payloadStrategy{ + PayloadGenerationStrategy::UniformRandom}; + size_t minPayloadSize{0}; + size_t maxPayloadSize{0}; + std::vector keySizes{32, 48, 64}; + std::vector kdfs{ + hpke::KDFId::Sha256, + hpke::KDFId::Sha384, + hpke::KDFId::Sha512}; + std::vector aeads{ + hpke::AeadId::TLS_AES_128_GCM_SHA256, + hpke::AeadId::TLS_AES_256_GCM_SHA384, + hpke::AeadId::TLS_CHACHA20_POLY1305_SHA256}; +}; +} // namespace ech +} // namespace fizz diff --git a/fizz/protocol/ech/test/BUCK b/fizz/protocol/ech/test/BUCK index de43b7d8099..f6a360a180a 100644 --- a/fizz/protocol/ech/test/BUCK +++ b/fizz/protocol/ech/test/BUCK @@ -74,3 +74,16 @@ cpp_unittest( "//folly/portability:gtest", ], ) + +cpp_unittest( + name = "grease_ech_test", + srcs = [ + "GreaseECHTest.cpp", + ], + deps = [ + "//fizz/crypto/hpke:utils", + "//fizz/protocol/ech:grease_ech", + "//fizz/protocol/test:mocks", + "//fizz/protocol/test:test_util", + ], +) diff --git a/fizz/protocol/ech/test/GreaseECHTest.cpp b/fizz/protocol/ech/test/GreaseECHTest.cpp new file mode 100644 index 00000000000..184c5ed6838 --- /dev/null +++ b/fizz/protocol/ech/test/GreaseECHTest.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include + +#include +#include +#include + +using namespace fizz::test; + +namespace fizz { +namespace ech { +namespace test { + +TEST(GreaseECHTest, TestGenerateRandomGreaseECH) { + MockFactory factory; + factory.setDefaults(); + + auto chlo = TestMessages::clientHelloPsk(); + auto sni = getExtension(chlo.extensions); + + GreaseECHSetting setting{}; + setting.maxPayloadSize = 100; + auto greaseEch = generateGreaseECH(setting, factory, 0); + + std::array kdfs{ + hpke::KDFId::Sha256, hpke::KDFId::Sha384, hpke::KDFId::Sha512}; + std::array aeads{ + hpke::AeadId::TLS_AES_128_GCM_SHA256, + hpke::AeadId::TLS_AES_256_GCM_SHA384, + hpke::AeadId::TLS_CHACHA20_POLY1305_SHA256}; + + EXPECT_NE( + kdfs.end(), + std::find(kdfs.begin(), kdfs.end(), greaseEch.cipher_suite.kdf_id)); + EXPECT_NE( + aeads.end(), + std::find(aeads.begin(), aeads.end(), greaseEch.cipher_suite.aead_id)); + + std::array keyLengths{32, 48, 64}; + EXPECT_NE( + keyLengths.end(), + std::find( + keyLengths.begin(), + keyLengths.end(), + greaseEch.enc->computeChainDataLength())); + + EXPECT_LE(greaseEch.payload->computeChainDataLength(), 100); +} + +TEST(GreaseECHTest, TestGenerateComputedGreaseECH) { + MockFactory factory; + factory.setDefaults(); + + auto chlo = TestMessages::clientHelloPsk(); + auto sni = getExtension(chlo.extensions); + auto encodedChlo = encode(chlo); + + size_t encodedChloSize = encodedChlo->computeChainDataLength(); + GreaseECHSetting setting{ + /* minConfigId = */ 0, + /* maxConfigId = */ 0, + PayloadGenerationStrategy::Computed, + /* minPayloadSize = */ 100, + /* maxPayloadSize = */ 100, + /* keySizes = */ {32}, + /* kdfs = */ {hpke::KDFId::Sha256}, + /* aeads = */ {hpke::AeadId::TLS_AES_128_GCM_SHA256}}; + auto greaseEch = generateGreaseECH(setting, factory, encodedChloSize); + + EXPECT_EQ(hpke::KDFId::Sha256, greaseEch.cipher_suite.kdf_id); + EXPECT_EQ( + hpke::AeadId::TLS_AES_128_GCM_SHA256, greaseEch.cipher_suite.aead_id); + EXPECT_EQ(32, greaseEch.enc->computeChainDataLength()); + + size_t expectedPayloadSize = encodedChloSize + + hpke::getCipherOverhead(greaseEch.cipher_suite.aead_id) + 100; + EXPECT_EQ(expectedPayloadSize, greaseEch.payload->computeChainDataLength()); +} +} // namespace test +} // namespace ech +} // namespace fizz