diff --git a/src/cli/pubkey.cpp b/src/cli/pubkey.cpp index ba788d360d..52b5463451 100644 --- a/src/cli/pubkey.cpp +++ b/src/cli/pubkey.cpp @@ -103,11 +103,12 @@ Botan::PK_Signature_Options sig_options( return sig_options(key, "PSS", hash, use_der, provider); } - return Botan::PK_Signature_Options() + return Botan::PK_Signature_Options_Builder() .with_hash(hash) .with_padding(padding) .with_der_encoded_signature(use_der) - .with_provider(provider); + .with_provider(provider) + .commit(); } } // namespace diff --git a/src/examples/ecdsa.cpp b/src/examples/ecdsa.cpp index 801720b725..5ee752965a 100644 --- a/src/examples/ecdsa.cpp +++ b/src/examples/ecdsa.cpp @@ -15,13 +15,13 @@ int main() { const std::string message("This is a tasty burger!"); // sign data - Botan::PK_Signer signer(key, rng, Botan::PK_Signature_Options().with_hash("SHA-256")); + Botan::PK_Signer signer(key, rng, Botan::PK_Signature_Options_Builder().with_hash("SHA-256").commit()); signer.update(message); std::vector signature = signer.signature(rng); std::cout << "Signature:\n" << Botan::hex_encode(signature); // now verify the signature - Botan::PK_Verifier verifier(key, Botan::PK_Signature_Options().with_hash("SHA-256")); + Botan::PK_Verifier verifier(key, Botan::PK_Signature_Options_Builder().with_hash("SHA-256").commit()); verifier.update(message); std::cout << "\nis " << (verifier.check_signature(signature) ? "valid" : "invalid"); return 0; diff --git a/src/examples/pkcs11_ecdsa.cpp b/src/examples/pkcs11_ecdsa.cpp index cbbb3118ff..64ab01a6d9 100644 --- a/src/examples/pkcs11_ecdsa.cpp +++ b/src/examples/pkcs11_ecdsa.cpp @@ -96,10 +96,10 @@ int main() { std::vector plaintext(20, 0x01); - Botan::PK_Signer signer(key_pair.second, rng, Botan::PK_Signature_Options().with_hash("Raw")); + Botan::PK_Signer signer(key_pair.second, rng, Botan::PK_Signature_Options_Builder().with_hash("Raw").commit()); auto signature = signer.sign_message(plaintext, rng); - Botan::PK_Verifier token_verifier(key_pair.first, Botan::PK_Signature_Options().with_hash("Raw")); + Botan::PK_Verifier token_verifier(key_pair.first, Botan::PK_Signature_Options_Builder().with_hash("Raw").commit()); bool ecdsa_ok = token_verifier.verify_message(plaintext, signature); return ecdsa_ok ? 0 : 1; diff --git a/src/examples/pkcs11_rsa.cpp b/src/examples/pkcs11_rsa.cpp index 04c7a6dcdd..163601a7b4 100644 --- a/src/examples/pkcs11_rsa.cpp +++ b/src/examples/pkcs11_rsa.cpp @@ -93,13 +93,13 @@ int main() { /************ RSA sign *************/ Botan::PK_Signer signer( - rsa_keypair.second, rng, Botan::PK_Signature_Options().with_hash("SHA-256").with_padding("PSS")); + rsa_keypair.second, rng, Botan::PK_Signature_Options_Builder().with_hash("SHA-256").with_padding("PSS").commit()); auto signature = signer.sign_message(plaintext, rng); /************ RSA verify *************/ Botan::PK_Verifier verifier(rsa_keypair.first, - Botan::PK_Signature_Options().with_hash("SHA-256").with_padding("PSS")); + Botan::PK_Signature_Options_Builder().with_hash("SHA-256").with_padding("PSS").commit()); auto ok = verifier.verify_message(plaintext, signature); return ok ? 0 : 1; diff --git a/src/lib/pubkey/gost_3410/gost_3410.cpp b/src/lib/pubkey/gost_3410/gost_3410.cpp index 3acfad77a9..3af605bb0a 100644 --- a/src/lib/pubkey/gost_3410/gost_3410.cpp +++ b/src/lib/pubkey/gost_3410/gost_3410.cpp @@ -173,21 +173,25 @@ PK_Signature_Options gost_hash_from_algid(const AlgorithmIdentifier& alg_id) { throw Decoding_Error("Unexpected non-empty AlgorithmIdentifier parameters for GOST 34.10 signature"); } - const std::string oid_str = alg_id.oid().to_formatted_string(); - if(oid_str == "GOST-34.10/GOST-R-34.11-94") { - return PK_Signature_Options("GOST-R-34.11-94"); - } - if(oid_str == "GOST-34.10-2012-256/Streebog-256") { - return PK_Signature_Options("Streebog-256"); - } - if(oid_str == "GOST-34.10-2012-512/Streebog-512") { - return PK_Signature_Options("Streebog-512"); - } - if(oid_str == "GOST-34.10-2012-256/SHA-256") { - return PK_Signature_Options("SHA-256"); + const auto hash = [&](std::string_view oid_str) -> std::optional { + if(oid_str == "GOST-34.10/GOST-R-34.11-94") { + return "GOST-R-34.11-94"; + } else if(oid_str == "GOST-34.10-2012-256/Streebog-256") { + return "Streebog-256"; + } else if(oid_str == "GOST-34.10-2012-512/Streebog-512") { + return "Streebog-512"; + } else if(oid_str == "GOST-34.10-2012-256/SHA-256") { + return "SHA-256"; + } else { + return std::nullopt; + } + }(alg_id.oid().to_formatted_string()); + + if(!hash.has_value()) { + throw Decoding_Error(fmt("Unknown OID ({}) for GOST 34.10 signatures", alg_id.oid())); } - throw Decoding_Error(fmt("Unknown OID ({}) for GOST 34.10 signatures", alg_id.oid())); + return PK_Signature_Options_Builder().with_hash(hash.value()).commit(); } /** diff --git a/src/lib/pubkey/pk_keys.cpp b/src/lib/pubkey/pk_keys.cpp index e46adb62c6..0d513f17bb 100644 --- a/src/lib/pubkey/pk_keys.cpp +++ b/src/lib/pubkey/pk_keys.cpp @@ -141,14 +141,14 @@ std::unique_ptr Private_Key::create_key_agreement_op(Rand std::unique_ptr Public_Key::create_verification_op(std::string_view params, std::string_view provider) const { - PK_Signature_Options opts(algo_name(), params, provider); + auto opts = PK_Signature_Options_Builder(algo_name(), params, provider).commit(); return this->_create_verification_op(opts); } std::unique_ptr Private_Key::create_signature_op(RandomNumberGenerator& rng, std::string_view params, std::string_view provider) const { - PK_Signature_Options opts(algo_name(), params, provider); + auto opts = PK_Signature_Options_Builder(algo_name(), params, provider).commit(); return this->_create_signature_op(rng, opts); } diff --git a/src/lib/pubkey/pk_options.cpp b/src/lib/pubkey/pk_options.cpp index 525391fd7b..93d0c3d9b2 100644 --- a/src/lib/pubkey/pk_options.cpp +++ b/src/lib/pubkey/pk_options.cpp @@ -12,7 +12,9 @@ namespace Botan { -PK_Signature_Options::PK_Signature_Options(std::string_view algo, std::string_view params, std::string_view provider) { +PK_Signature_Options_Builder::PK_Signature_Options_Builder(std::string_view algo, + std::string_view params, + std::string_view provider) { /* * This is a convoluted mess because we must handle dispatch for every algorithm * specific detail of how padding strings were formatted in versions prior to 3.6. @@ -21,8 +23,6 @@ PK_Signature_Options::PK_Signature_Options(std::string_view algo, std::string_vi * are removed in Botan4. */ - auto options = PK_Signature_Options(); - if(!provider.empty() && provider != "base") { with_provider(provider); } diff --git a/src/lib/pubkey/pk_options.h b/src/lib/pubkey/pk_options.h index 2941a843d0..7f5665c6e4 100644 --- a/src/lib/pubkey/pk_options.h +++ b/src/lib/pubkey/pk_options.h @@ -12,36 +12,110 @@ #include #include #include -#include #include #include #include namespace Botan { +namespace detail { + +struct PK_Signature_Options_Container { + // NOLINTBEGIN(misc-non-private-member-variables-in-classes) + + Option<"hash", std::string> hash_fn; + Option<"prehash", std::optional> prehash; + Option<"padding", std::string> padding; + Option<"context", std::vector> context; + Option<"provider", std::string> provider; + Option<"salt size", size_t> salt_size; + Option<"use DER", bool> use_der; + Option<"deterministic", bool> deterministic_sig; + Option<"explicit trailer field", bool> explicit_trailer_field; + + // NOLINTEND(misc-non-private-member-variables-in-classes) + + auto all_options() const { + return std::tie(hash_fn, + prehash, + padding, + context, + provider, + salt_size, + use_der, + deterministic_sig, + explicit_trailer_field); + } +}; + +} // namespace detail + +class BOTAN_UNSTABLE_API PK_Signature_Options : public Options { + public: + using Options::Options; + + public: + [[nodiscard]] auto hash_function() { return take(options().hash_fn); } + + [[nodiscard]] auto prehash() { return take(options().prehash); } + + [[nodiscard]] auto padding() { return take(options().padding); } + + [[nodiscard]] auto context() { return take(options().context); } + + [[nodiscard]] auto provider() { return take(options().provider); } + + [[nodiscard]] auto salt_size() { return take(options().salt_size); } + + [[nodiscard]] bool using_der_encoded_signature() { return take(options().use_der).or_default(false); } + + [[nodiscard]] bool using_deterministic_signature() { return take(options().deterministic_sig).or_default(false); } + + [[nodiscard]] bool using_explicit_trailer_field() { + return take(options().explicit_trailer_field).or_default(false); + } + + public: + /// It may be acceptable to provide a hash function, for hash-based + /// signatures (like SLH-DSA or LMS), but it is not required. + /// @throws Invalid_Argument if the provided hash is not acceptable + void validate_for_hash_based_signature_algorithm(std::string_view algo_name, + std::optional acceptable_hash = std::nullopt); + + /// This is a convenience helper for algorithms that do not support + /// specifying a provider. + /// @throws Provider_Not_Found if a provider is set + void exclude_provider_for_algorithm(std::string_view algo_name) { + if(auto p = provider().optional()) { + throw Provider_Not_Found(algo_name, p.value()); + }; + } +}; + /** * Signature generation/verification options * * The normal usage of this is in a builder style, eg * -* PK_Signature_Options() -* .with_hash("SHA-256") -* .with_der_encoded_signature() -* .with_context("Foo") +* auto opts = PK_Signature_Options_Builder() +* .with_hash("SHA-256") +* .with_der_encoded_signature() +* .with_context("Foo") +* .commit(); */ -class BOTAN_PUBLIC_API(3, 6) PK_Signature_Options : public Builder { +class BOTAN_PUBLIC_API(3, 6) PK_Signature_Options_Builder : public OptionsBuilder { public: - /// Create a PK_Signature_Options with no options set + /// Create a PK_Signature_Options_Builder with no options set /// /// This can be further parameterized by calling with_xxx functions - PK_Signature_Options() = default; + PK_Signature_Options_Builder() = default; - /// Create a PK_Signature_Options specifying the hash to use + /// Create a PK_Signature_Options_Builder specifying the hash to use /// /// Most but not all signature schemes require specifying the hash /// /// This can be further parameterized by calling with_xxx functions - PK_Signature_Options(std::string_view hash_fn) { with_hash(hash_fn); } + PK_Signature_Options_Builder(std::string_view hash_fn) { with_hash(hash_fn); } /// Parse the legacy set of parameters that used to be passed to /// PK_Signer/PK_Verifier. This should not be used by new code. @@ -49,20 +123,20 @@ class BOTAN_PUBLIC_API(3, 6) PK_Signature_Options : public Builder prehash = std::nullopt) & { - set_or_throw(m_prehash, std::move(prehash)); + PK_Signature_Options_Builder& with_prehash(std::optional prehash = std::nullopt) & { + set_or_throw(options().prehash, std::move(prehash)); return *this; } @@ -114,7 +190,7 @@ class BOTAN_PUBLIC_API(3, 6) PK_Signature_Options : public Builder prehash = std::nullopt) && { + PK_Signature_Options_Builder with_prehash(std::optional prehash = std::nullopt) && { return std::move(with_prehash(std::move(prehash))); } @@ -127,8 +203,8 @@ class BOTAN_PUBLIC_API(3, 6) PK_Signature_Options : public Builder context) & { - set_or_throw(m_context, std::vector(context.begin(), context.end())); + PK_Signature_Options_Builder& with_context(std::span context) & { + set_or_throw(options().context, std::vector(context.begin(), context.end())); return *this; } @@ -141,7 +217,7 @@ class BOTAN_PUBLIC_API(3, 6) PK_Signature_Options : public Builder context) && { + PK_Signature_Options_Builder with_context(std::span context) && { return std::move(with_context(context)); } @@ -154,7 +230,7 @@ class BOTAN_PUBLIC_API(3, 6) PK_Signature_Options : public Builder acceptable_hash = std::nullopt); - - [[nodiscard]] auto prehash() { return take(m_prehash); } - - [[nodiscard]] auto padding() { return take(m_padding); } - - [[nodiscard]] auto context() { return take(m_context); } - - [[nodiscard]] auto provider() { return take(m_provider); } - - /// This is a convenience helper for algorithms that do not support - /// specifying a provider. - /// @throws Provider_Not_Found if a provider is set - void exclude_provider_for_algorithm(std::string_view algo_name) { - if(auto p = provider().optional()) { - throw Provider_Not_Found(algo_name, p.value()); - }; + PK_Signature_Options_Builder with_provider(std::string_view provider) && { + return std::move(with_provider(provider)); } - - [[nodiscard]] auto salt_size() { return take(m_salt_size); } - - [[nodiscard]] bool using_der_encoded_signature() { return take(m_use_der).or_default(false); } - - [[nodiscard]] bool using_deterministic_signature() { return take(m_deterministic_sig).or_default(false); } - - [[nodiscard]] bool using_explicit_trailer_field() { return take(m_explicit_trailer_field).or_default(false); } - - private: - friend class Builder; - - auto all_options() const { - return std::tie(m_hash_fn, - m_prehash, - m_padding, - m_context, - m_provider, - m_salt_size, - m_use_der, - m_deterministic_sig, - m_explicit_trailer_field); - } - - private: - detail::Option<"hash", std::string> m_hash_fn; - detail::Option<"prehash", std::optional> m_prehash; - detail::Option<"padding", std::string> m_padding; - detail::Option<"context", std::vector> m_context; - detail::Option<"provider", std::string> m_provider; - detail::Option<"salt size", size_t> m_salt_size; - detail::Option<"use DER", bool> m_use_der; - detail::Option<"deterministic", bool> m_deterministic_sig; - detail::Option<"explicit trailer field", bool> m_explicit_trailer_field; }; } // namespace Botan diff --git a/src/lib/pubkey/pubkey.cpp b/src/lib/pubkey/pubkey.cpp index ab599b8d41..421bda0337 100644 --- a/src/lib/pubkey/pubkey.cpp +++ b/src/lib/pubkey/pubkey.cpp @@ -247,8 +247,9 @@ PK_Signer::PK_Signer(const Private_Key& key, std::string_view provider) : PK_Signer(key, rng, - PK_Signature_Options(key.algo_name(), padding, provider) - .with_der_encoded_signature(format == Signature_Format::DerSequence)) {} + PK_Signature_Options_Builder(key.algo_name(), padding, provider) + .with_der_encoded_signature(format == Signature_Format::DerSequence) + .commit()) {} PK_Signer::PK_Signer(const Private_Key& key, RandomNumberGenerator& rng, PK_Signature_Options& options) { m_op = key._create_signature_op(rng, options); @@ -332,8 +333,9 @@ PK_Verifier::PK_Verifier(const Public_Key& pub_key, Signature_Format format, std::string_view provider) : PK_Verifier(pub_key, - PK_Signature_Options(pub_key.algo_name(), padding, provider) - .with_der_encoded_signature(format == Signature_Format::DerSequence)) {} + PK_Signature_Options_Builder(pub_key.algo_name(), padding, provider) + .with_der_encoded_signature(format == Signature_Format::DerSequence) + .commit()) {} PK_Verifier::PK_Verifier(const Public_Key& key, PK_Signature_Options& options) { m_op = key._create_verification_op(options); diff --git a/src/lib/pubkey/rsa/rsa.cpp b/src/lib/pubkey/rsa/rsa.cpp index ecee632865..c11888b6e9 100644 --- a/src/lib/pubkey/rsa/rsa.cpp +++ b/src/lib/pubkey/rsa/rsa.cpp @@ -751,6 +751,8 @@ PK_Signature_Options parse_rsa_signature_algorithm(const AlgorithmIdentifier& al const std::string& padding = sig_info[1]; + PK_Signature_Options_Builder opts; + if(padding == "EMSA4") { // "MUST contain RSASSA-PSS-params" if(alg_id.parameters().empty()) { @@ -782,7 +784,7 @@ PK_Signature_Options parse_rsa_signature_algorithm(const AlgorithmIdentifier& al throw Decoding_Error("Unacceptable trailer field for PSS signatures"); } - return PK_Signature_Options().with_padding("PSS").with_hash(hash_algo).with_salt_size(pss_params.salt_length()); + opts.with_padding("PSS").with_hash(hash_algo).with_salt_size(pss_params.salt_length()); } else { SCAN_Name scan(padding); @@ -790,8 +792,10 @@ PK_Signature_Options parse_rsa_signature_algorithm(const AlgorithmIdentifier& al throw Decoding_Error("Unexpected OID for RSA signatures"); } - return PK_Signature_Options().with_padding("PKCS1v15").with_hash(scan.arg(0)); + opts.with_padding("PKCS1v15").with_hash(scan.arg(0)); } + + return opts.commit(); } } // namespace diff --git a/src/lib/utils/options_builder.h b/src/lib/utils/options_builder.h index 21ea790055..9e08be80d4 100644 --- a/src/lib/utils/options_builder.h +++ b/src/lib/utils/options_builder.h @@ -24,8 +24,14 @@ namespace Botan { class HashFunction; class MessageAuthenticationCode; -template -class Builder; +template +class OptionsBuilder; + +template +class Options; + +template +class Option; namespace detail { @@ -58,32 +64,6 @@ std::string to_string(const std::unique_ptr& value); } // namespace BuilderOptionHelper -/** - * Wraps a builder option value and provides a way to convert it to a string - * for debugging and error messages. - */ -template -class Option { - public: - constexpr static std::string_view name = option_name.value; - using value_type = T; - - std::string to_string() const { - if(!value.has_value()) { - return ""; - } else { - return BuilderOptionHelper::to_string(*value); - } - } - - private: - // Only the abstract builder may access the wrapped value - template - friend class Botan::Builder; - - std::optional value; -}; - /** * Return wrapper of an option value that allows for different ways to consume * the value. Downstream code can choose to either require the option to be set @@ -144,100 +124,116 @@ template struct is_builder_option : std::false_type {}; template -struct is_builder_option> : std::true_type {}; +struct is_builder_option> : std::true_type {}; template concept BuilderOption = is_builder_option::value; -template -consteval bool all_builder_options(std::tuple) { - return (BuilderOption && ... && true); -} +template +concept AllBuilderOptionsTuple = + requires(const T t) { std::apply([](const OptionTs&...) {}, t); }; + +template +concept OptionsContainer = std::is_default_constructible_v && std::is_move_assignable_v && + std::is_move_constructible_v && requires(const T opts) { + { opts.all_options() } -> AllBuilderOptionsTuple; + }; + +template +concept ConcreteOptions = OptionsContainer && + requires(typename T::Container&& container, std::string_view name) { T(container, name); }; + +static constexpr auto unknown_product = "Unknown"; } // namespace detail /** - * Base class for all builder helper classes - * - * Concrete implementations of builders should derive from this class, wrap all - * its options in `Option` instances and implement the `all_options` method. - * - * Below is an example that sets up a hypothetical key derivation function. - * Note that the `with_*` methods are overloaded for lvalue and rvalue refs, to - * allow for properly chaining the calls. - * - * TODO: C++23: Using "deducing-this" we will be able to remove the CRTP and - * remove the overloads for lvalue and rvalue refs. - * - * class KDF_Builder : public Builder { - * private: - * detail::Option<"context", std::string> m_context; - * detail::Option<"label", std::string> m_label; - * detail::Option<"hash", std::unique_ptr> m_hash; - * - * friend class Builder; - * - * /// Returns a tuple of all options (needed for the base implementation) - * auto all_options() const { return std::tie(m_context, m_hash); } - * - * public: - * KDF_Builder& with_context(std::string_view ctx) & { - * set_or_throw(m_context, std::string(ctx)); - * return *this; - * } - * - * KDF_Builder with_context(std::string_view ctx) && { - * return std::move(with_context(ctx)); - * } - * - * KDF_Builder& with_label(std::string_view label) & { - * set_or_throw(m_context, std::string(label)); - * return *this; - * } - * - * KDF_Builder with_label(std::string_view label) && { - * return std::move(with_label(label)); - * } - * - * KDF_Builder& with_hash(std::string_view hash) & { - * set_or_throw(m_hash, Botan::HashFunction::create_or_throw(hash)); - * return *this; - * } - * - * KDF_Builder with_hash(std::string_view hash) && { - * return std::move(with_hash(hash)); - * } - * - * KDF_Builder& with_hash(std::unique_ptr hash) & { - * set_or_throw(m_hash, std::move(hash)); - * return *this; - * } - * - * KDF_Builder with_hash(std::unique_ptr hash) && { - * return std::move(with_hash(std::move(hash))); - * } - * - * /// Creates a new KDF instance with the current options and validates - * /// that all options have been consumed by the new KDF instance. - * KDF create() { - * KDF kdf(*this); - * validate_option_consumption(); - * return kdf; - * } + * Wraps a builder option value and provides a way to convert it to a string + * for debugging and error messages. + */ +template +class Option { + public: + constexpr static std::string_view name = option_name.value; + using value_type = T; + + std::string to_string() const { + if(!value.has_value()) { + return ""; + } else { + return detail::BuilderOptionHelper::to_string(*value); + } + } + + private: + template + friend class Botan::OptionsBuilder; + + template + friend class Options; + + std::optional value; +}; + +/** + * Base class for all options builder helper classes. * - * /// Gets the context value or std::nullopt if it wasn't set - * std::optional context() { return take(context); } + * Concrete implementations of builders should derive from this class and + * pass their concrete implementation of the options consumer as the template, + * parameter. All available options must be wrapped in a default-constructible + * struct of `Option<>` instances that implements the `all_options` method. * - * /// Gets the label value or a default if it wasn't set - * std::string label() { return take(label).value_or("default"); } + * See the example at the end of this file for a full picture. + */ +template +class OptionsBuilder { + public: + static_assert(detail::ConcreteOptions); + using Container = typename OptionsT::Container; + + public: + OptionsT commit() { + return OptionsT(std::exchange(m_options, {}), std::exchange(m_product_name, detail::unknown_product)); + } + + protected: + Container& options() { return m_options; } + + template ValueT> + void set_or_throw(OptionT& option, ValueT&& value) { + if(option.value.has_value()) { + throw Invalid_State("'" + m_product_name + "' already set the '" + std::string(OptionT::name) + "' option"); + } + option.value.emplace(std::forward(value)); + } + + void with_product_name(std::string name) { m_product_name = std::move(name); } + + private: + Container m_options; + std::string m_product_name = detail::unknown_product; +}; + +/** + * Base class for all options consumer classes. * - * /// Gets the hash function or throws if it wasn't set - * std::unique_ptr hash() { return require(hash); } - * }; + * Concrete implementations of options consumers should derive from this class + * and pass their concrete implementation of the options container as the + * template parameter. The options container must implement the `all_options` + * method that returns a tuple of all available options. */ -template -class Builder { +template +class Options { public: + static_assert(detail::OptionsContainer); + using Container = OptionsContainerT; + + public: + Options() = default; + + Options(Container options, std::string_view product_name) : + m_options(std::move(options)), m_product_name(product_name) {} + [[nodiscard]] std::string to_string() const { std::ostringstream oss; foreach_option([&](const OptionT& option) { @@ -247,27 +243,17 @@ class Builder { } protected: - void set_product_name(std::string_view name) { m_product_name = std::string(name); } + Container& options() { return m_options; } template [[nodiscard]] auto take(OptionT& o) noexcept { return detail::OptionValue(std::exchange(o.value, {}), OptionT::name, m_product_name); } - template ValueT> - void set_or_throw(OptionT& option, ValueT&& value) { - if(option.value.has_value()) { - throw Invalid_State("'" + m_product_name + "' already set the '" + std::string(OptionT::name) + "' option"); - } - option.value.emplace(std::forward(value)); - } - template void foreach_option(FnT&& fn) const { - // TODO: C++23: using deducing-this we can remove the CRTP and simply - // deduce the DerivedT from the explicit object parameter. std::apply([&](const OptionTs&... options) { (fn(options), ...); }, - static_cast(*this).all_options()); + m_options.all_options()); } void validate_option_consumption() { @@ -292,9 +278,91 @@ class Builder { } private: - std::string m_product_name = "Unknown"; + Container m_options; + std::string m_product_name = detail::unknown_product; }; } // namespace Botan +/** + * Below is an example that sets up options for a hypothetical KDF. + * Note that the `with_*` methods are overloaded for lvalue and rvalue refs, to + * allow for properly chaining the calls. + * + * struct KDF_OptionsContainer { + * Option<"context", std::string> context; + * Option<"label", std::string> label; + * Option<"hash", std::unique_ptr> hash; + * + * auto all_options() { return std::tie(context, label, hash); } + * }; + * + * class KDF_Options : public Options { + * public: + * using Options::Options; + * + * public: + * /// Gets the context value or std::nullopt if it wasn't set + * [[nodiscard]] auto context() { return take(options().context); } + * + * /// Gets the label value or a default if it wasn't set + * [[nodiscard]] auto label() { return take(options().label); } + * + * /// Gets the hash function or throws if it wasn't set + * [[nodiscard]] auto hash() { return take(options().hash); } + * }; + * + * // TODO: C++23: Using "deducing-this" we will be able to remove the CRTP and + * // remove the overloads for lvalue and rvalue refs. + * + * class KDF_Builder : public OptionsBuilder { + * public: + * KDF_Builder& with_context(std::string_view ctx) & { + * set_or_throw(options().context, std::string(ctx)); + * return *this; + * } + * + * KDF_Builder with_context(std::string_view ctx) && { + * return std::move(with_context(ctx)); + * } + * + * KDF_Builder& with_label(std::string_view label) & { + * set_or_throw(options().label, std::string(label)); + * return *this; + * } + * + * KDF_Builder with_label(std::string_view label) && { + * return std::move(with_label(label)); + * } + * + * KDF_Builder& with_hash(std::string_view hash) & { + * set_or_throw(options().hash, Botan::HashFunction::create_or_throw(hash)); + * return *this; + * } + * + * KDF_Builder with_hash(std::string_view hash) && { + * return std::move(with_hash(hash)); + * } + * + * KDF_Builder& with_hash(std::unique_ptr hash) & { + * set_or_throw(options().hash, std::move(hash)); + * return *this; + * } + * + * KDF_Builder with_hash(std::unique_ptr hash) && { + * return std::move(with_hash(std::move(hash))); + * } + * + * /// Creates a new KDF instance with the current options and validates + * /// that all options have been consumed by the new KDF instance. + * KDF create() { + * with_product_name("KDF"); + * auto opts = this->commit(); + * KDF kdf(opts); + * opts.validate_option_consumption(); + * return kdf; + * } + * }; + */ + #endif diff --git a/src/tests/test_ecdsa.cpp b/src/tests/test_ecdsa.cpp index bcba547d6e..adcbda3ce1 100644 --- a/src/tests/test_ecdsa.cpp +++ b/src/tests/test_ecdsa.cpp @@ -258,8 +258,8 @@ class ECDSA_AllGroups_Test : public Test { } try { - Botan::PK_Signer signer(priv, rng(), Botan::PK_Signature_Options().with_hash(hash)); - Botan::PK_Verifier verifier(*pub, Botan::PK_Signature_Options().with_hash(hash)); + Botan::PK_Signer signer(priv, rng(), Botan::PK_Signature_Options_Builder().with_hash(hash).commit()); + Botan::PK_Verifier verifier(*pub, Botan::PK_Signature_Options_Builder().with_hash(hash).commit()); for(size_t i = 0; i != 16; ++i) { auto message = rng().random_vec(rng().next_byte()); diff --git a/src/tests/test_rsa.cpp b/src/tests/test_rsa.cpp index 41a97d33b1..ba54e97c5c 100644 --- a/src/tests/test_rsa.cpp +++ b/src/tests/test_rsa.cpp @@ -204,8 +204,8 @@ class RSA_Blinding_Tests final : public Test { */ // don't try this at home - Botan::PK_Signer signer(rsa, this->rng(), Botan::PK_Signature_Options().with_hash("Raw")); - Botan::PK_Verifier verifier(rsa, Botan::PK_Signature_Options().with_hash("Raw")); + Botan::PK_Signer signer(rsa, this->rng(), Botan::PK_Signature_Options_Builder().with_hash("Raw").commit()); + Botan::PK_Verifier verifier(rsa, Botan::PK_Signature_Options_Builder().with_hash("Raw").commit()); for(size_t i = 1; i <= BOTAN_BLINDING_REINIT_INTERVAL * 6; ++i) { std::vector input(16); diff --git a/src/tests/unit_ecdsa.cpp b/src/tests/unit_ecdsa.cpp index 73de46bf97..4c8ee7b9cc 100644 --- a/src/tests/unit_ecdsa.cpp +++ b/src/tests/unit_ecdsa.cpp @@ -139,8 +139,8 @@ Test::Result test_read_pkcs8() { result.confirm("EC_Group is marked as explicit encoding", ecdsa_nodp->domain().used_explicit_encoding()); - Botan::PK_Signer signer(*ecdsa_nodp, *rng, Botan::PK_Signature_Options().with_hash("SHA-256")); - Botan::PK_Verifier verifier(*ecdsa_nodp, Botan::PK_Signature_Options().with_hash("SHA-256")); + Botan::PK_Signer signer(*ecdsa_nodp, *rng, Botan::PK_Signature_Options_Builder().with_hash("SHA-256").commit()); + Botan::PK_Verifier verifier(*ecdsa_nodp, Botan::PK_Signature_Options_Builder().with_hash("SHA-256").commit()); const auto msg = rng->random_vec(48);