From 1be9081107aac69621ff571275766b9db223f81e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Wed, 11 Sep 2024 11:53:22 +0200 Subject: [PATCH] Consumers can specify expectations of value availability As discussed: https://github.com/randombit/botan/pull/4318\#issuecomment-2340834399 --- src/lib/pk_pad/emsa.cpp | 18 ++--- src/lib/prov/pkcs11/p11_ecdsa.cpp | 4 +- src/lib/prov/pkcs11/p11_mechanism.cpp | 4 +- src/lib/pubkey/curve448/ed448/ed448.cpp | 8 +-- .../dilithium/dilithium_common/dilithium.cpp | 5 +- src/lib/pubkey/eckcdsa/eckcdsa.cpp | 3 +- src/lib/pubkey/ed25519/ed25519_key.cpp | 8 +-- src/lib/pubkey/pk_ops.cpp | 9 ++- src/lib/pubkey/pk_options.cpp | 2 +- src/lib/pubkey/pk_options.h | 30 +++----- src/lib/pubkey/sm2/sm2.cpp | 8 +-- .../sphincsplus_common/sphincsplus.cpp | 2 + src/lib/utils/info.txt | 2 +- .../{base_builder.cpp => options_builder.cpp} | 2 +- .../{base_builder.h => options_builder.h} | 70 ++++++++++++++++--- 15 files changed, 110 insertions(+), 65 deletions(-) rename src/lib/utils/{base_builder.cpp => options_builder.cpp} (96%) rename src/lib/utils/{base_builder.h => options_builder.h} (78%) diff --git a/src/lib/pk_pad/emsa.cpp b/src/lib/pk_pad/emsa.cpp index f4649c2181..dacfb9bd9f 100644 --- a/src/lib/pk_pad/emsa.cpp +++ b/src/lib/pk_pad/emsa.cpp @@ -33,8 +33,8 @@ namespace Botan { std::unique_ptr EMSA::create_or_throw(PK_Signature_Options& options) { - const auto hash = options.maybe_hash_function(); - const auto padding = options.padding(); + const auto hash = options.hash_function().optional(); + const auto padding = options.padding().optional(); const bool is_raw_hash = !hash.has_value() || hash.value() == "Raw"; const bool is_raw_padding = !padding.has_value() || padding.value() == "Raw"; @@ -43,9 +43,9 @@ std::unique_ptr EMSA::create_or_throw(PK_Signature_Options& options) { #if defined(BOTAN_HAS_EMSA_RAW) if(is_raw_hash) { - if(auto [using_prehash, prehash_fn] = options.prehash(); using_prehash && prehash_fn.has_value()) { - if(auto prehash = HashFunction::create(prehash_fn.value())) { - return std::make_unique(prehash->output_length()); + if(auto prehash = options.prehash().optional(); prehash.has_value() && prehash->has_value()) { + if(auto prehash_fn = HashFunction::create(prehash->value())) { + return std::make_unique(prehash_fn->output_length()); } } else { return std::make_unique(); @@ -65,7 +65,7 @@ std::unique_ptr EMSA::create_or_throw(PK_Signature_Options& options) { #if defined(BOTAN_HAS_EMSA_PKCS1) if(padding == "PKCS1v15") { if(is_raw_hash) { - return std::make_unique(options.prehash().second); + return std::make_unique(options.prehash().or_default(std::nullopt)); } else if(hash_fn) { return std::make_unique(std::move(hash_fn)); } @@ -74,18 +74,18 @@ std::unique_ptr EMSA::create_or_throw(PK_Signature_Options& options) { #if defined(BOTAN_HAS_EMSA_PSSR) if(padding == "PSS_Raw" && hash_fn) { - return std::make_unique(std::move(hash_fn), options.salt_size()); + return std::make_unique(std::move(hash_fn), options.salt_size().optional()); } if(padding == "PSS" && hash_fn) { - return std::make_unique(std::move(hash_fn), options.salt_size()); + return std::make_unique(std::move(hash_fn), options.salt_size().optional()); } #endif #if defined(BOTAN_HAS_ISO_9796) if(padding == "ISO_9796_DS2" && hash_fn) { return std::make_unique( - std::move(hash_fn), !options.using_explicit_trailer_field(), options.salt_size()); + std::move(hash_fn), !options.using_explicit_trailer_field(), options.salt_size().optional()); } //ISO-9796-2 DS 3 is deterministic and DS2 without a salt diff --git a/src/lib/prov/pkcs11/p11_ecdsa.cpp b/src/lib/prov/pkcs11/p11_ecdsa.cpp index b8d1c401bf..72eeee7225 100644 --- a/src/lib/prov/pkcs11/p11_ecdsa.cpp +++ b/src/lib/prov/pkcs11/p11_ecdsa.cpp @@ -63,7 +63,7 @@ class PKCS11_ECDSA_Signature_Operation final : public PK_Ops::Signature { public: PKCS11_ECDSA_Signature_Operation(const PKCS11_ECDSA_PrivateKey& key, PK_Signature_Options& options) : - PKCS11_ECDSA_Signature_Operation(key, options.hash_function()) {} + PKCS11_ECDSA_Signature_Operation(key, options.hash_function().required()) {} void update(std::span input) override { if(!m_initialized) { @@ -128,7 +128,7 @@ class PKCS11_ECDSA_Verification_Operation final : public PK_Ops::Verification { public: PKCS11_ECDSA_Verification_Operation(const PKCS11_ECDSA_PublicKey& key, PK_Signature_Options& options) : - PKCS11_ECDSA_Verification_Operation(key, options.hash_function()) {} + PKCS11_ECDSA_Verification_Operation(key, options.hash_function().required()) {} void update(std::span input) override { if(!m_initialized) { diff --git a/src/lib/prov/pkcs11/p11_mechanism.cpp b/src/lib/prov/pkcs11/p11_mechanism.cpp index d8f765581e..6fe67f8f62 100644 --- a/src/lib/prov/pkcs11/p11_mechanism.cpp +++ b/src/lib/prov/pkcs11/p11_mechanism.cpp @@ -192,8 +192,8 @@ MechanismWrapper MechanismWrapper::create_rsa_crypt_mechanism(std::string_view p MechanismWrapper MechanismWrapper::create_rsa_sign_mechanism(PK_Signature_Options& options) { const std::string padding = [&]() { - const auto hash = options.maybe_hash_function(); - const auto padding = options.padding(); + const auto hash = options.hash_function().optional(); + const auto padding = options.padding().optional(); if(hash && padding) { return fmt("{}({})", padding.value(), hash.value()); diff --git a/src/lib/pubkey/curve448/ed448/ed448.cpp b/src/lib/pubkey/curve448/ed448/ed448.cpp index f8041f5b1b..3a0c909b75 100644 --- a/src/lib/pubkey/curve448/ed448/ed448.cpp +++ b/src/lib/pubkey/curve448/ed448/ed448.cpp @@ -218,8 +218,8 @@ AlgorithmIdentifier Ed448_Sign_Operation::algorithm_identifier() const { std::unique_ptr Ed448_PublicKey::_create_verification_op(PK_Signature_Options& options) const { options.exclude_provider_for_algorithm(algo_name()); - if(const auto [uses_prehash, prehash_fn] = options.prehash(); uses_prehash) { - return std::make_unique(*this, prehash_fn.value_or("SHAKE-256(512)")); + if(auto prehash = options.prehash().optional()) { + return std::make_unique(*this, prehash->value_or("SHAKE-256(512)")); } else { return std::make_unique(*this); } @@ -242,8 +242,8 @@ std::unique_ptr Ed448_PrivateKey::_create_signature_op(Random BOTAN_UNUSED(rng); options.exclude_provider_for_algorithm(algo_name()); - if(const auto [uses_prehash, prehash_fn] = options.prehash(); uses_prehash) { - return std::make_unique(*this, prehash_fn.value_or("SHAKE-256(512)")); + if(auto prehash = options.prehash().optional()) { + return std::make_unique(*this, prehash->value_or("SHAKE-256(512)")); } else { return std::make_unique(*this); } diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp b/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp index 21abb663e8..5ba057331e 100644 --- a/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp +++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp @@ -194,7 +194,10 @@ class Dilithium_Signature_Operation final : public PK_Ops::Signature { m_s1(ntt(m_priv_key->s1().clone())), m_s2(ntt(m_priv_key->s2().clone())), m_t0(ntt(m_priv_key->t0().clone())), - m_A(Dilithium_Algos::expand_A(m_priv_key->rho(), m_priv_key->mode())) {} + m_A(Dilithium_Algos::expand_A(m_priv_key->rho(), m_priv_key->mode())) { + options.context().not_implemented("will come in Botan 3.7.0"); + options.prehash().not_implemented("will come in Botan 3.7.0"); + } void update(std::span input) override { m_h.update(input); } diff --git a/src/lib/pubkey/eckcdsa/eckcdsa.cpp b/src/lib/pubkey/eckcdsa/eckcdsa.cpp index 948aabd335..7fc12164a9 100644 --- a/src/lib/pubkey/eckcdsa/eckcdsa.cpp +++ b/src/lib/pubkey/eckcdsa/eckcdsa.cpp @@ -40,11 +40,12 @@ namespace { std::unique_ptr eckcdsa_signature_hash(PK_Signature_Options& options) { // TODO: We could support prehashing, but it's not standard + options.prehash().not_implemented("non-standard prehashing is not supported"); // intentionally not supporting Raw for ECKCDSA, since we need to know // the length in advance which complicates the logic for Raw - return HashFunction::create_or_throw(options.hash_function()); + return HashFunction::create_or_throw(options.hash_function().required()); } std::unique_ptr eckcdsa_signature_hash(const AlgorithmIdentifier& alg_id) { diff --git a/src/lib/pubkey/ed25519/ed25519_key.cpp b/src/lib/pubkey/ed25519/ed25519_key.cpp index c1d514c32a..2f6c515c79 100644 --- a/src/lib/pubkey/ed25519/ed25519_key.cpp +++ b/src/lib/pubkey/ed25519/ed25519_key.cpp @@ -276,9 +276,9 @@ class Ed25519_Hashed_Sign_Operation final : public PK_Ops::Signature { std::unique_ptr Ed25519_PublicKey::_create_verification_op(PK_Signature_Options& options) const { options.exclude_provider_for_algorithm(algo_name()); - if(auto [uses_prehash, prehash_fn] = options.prehash(); uses_prehash) { + if(auto prehash = options.prehash().optional()) { return std::make_unique( - *this, prehash_fn.value_or("SHA-512"), !prehash_fn.has_value()); + *this, prehash->value_or("SHA-512"), !prehash->has_value()); } else { return std::make_unique(*this); } @@ -301,9 +301,9 @@ std::unique_ptr Ed25519_PrivateKey::_create_signature_op(Rand BOTAN_UNUSED(rng); options.exclude_provider_for_algorithm(algo_name()); - if(auto [uses_prehash, prehash_fn] = options.prehash(); uses_prehash) { + if(auto prehash = options.prehash().optional()) { return std::make_unique( - *this, prehash_fn.value_or("SHA-512"), !prehash_fn.has_value()); + *this, prehash->value_or("SHA-512"), !prehash->has_value()); } else { return std::make_unique(*this); } diff --git a/src/lib/pubkey/pk_ops.cpp b/src/lib/pubkey/pk_ops.cpp index b7a5a0842d..5ae4b844ce 100644 --- a/src/lib/pubkey/pk_ops.cpp +++ b/src/lib/pubkey/pk_ops.cpp @@ -85,16 +85,15 @@ secure_vector PK_Ops::Key_Agreement_with_KDF::agree(size_t key_len, namespace { std::unique_ptr validate_options_returning_hash(PK_Signature_Options& options) { - const auto hash = options.hash_function(); + const auto hash = options.hash_function().required(); /* * In a sense ECDSA/DSA are *always* in prehashing mode, so we accept the case * where prehashing is requested as long as the prehash hash matches the signature hash. */ - if(auto [uses_prehash, prehash_fn] = options.prehash(); uses_prehash) { - if(prehash_fn.has_value() && prehash_fn.value() != hash) { - throw Invalid_Argument("This algorithm does not support prehashing with a different hash"); - } + if(auto prehash = options.prehash().optional(); + prehash.has_value() && prehash->has_value() && prehash->value() != hash) { + throw Invalid_Argument("This algorithm does not support prehashing with a different hash"); } #if defined(BOTAN_HAS_RAW_HASH_FN) diff --git a/src/lib/pubkey/pk_options.cpp b/src/lib/pubkey/pk_options.cpp index 9bacc38f0a..525391fd7b 100644 --- a/src/lib/pubkey/pk_options.cpp +++ b/src/lib/pubkey/pk_options.cpp @@ -185,7 +185,7 @@ PK_Signature_Options::PK_Signature_Options(std::string_view algo, std::string_vi void PK_Signature_Options::validate_for_hash_based_signature_algorithm( std::string_view algo_name, std::optional acceptable_hash) { - if(auto hash = take(m_hash_fn)) { + if(auto hash = hash_function().optional()) { if(!acceptable_hash.has_value()) { throw Invalid_Argument(fmt("This {} key does not support explicit hash function choice", algo_name)); } diff --git a/src/lib/pubkey/pk_options.h b/src/lib/pubkey/pk_options.h index 04234e0e7a..2941a843d0 100644 --- a/src/lib/pubkey/pk_options.h +++ b/src/lib/pubkey/pk_options.h @@ -8,8 +8,8 @@ #ifndef BOTAN_PK_OPTIONS_H_ #define BOTAN_PK_OPTIONS_H_ -#include #include +#include #include #include #include @@ -286,9 +286,7 @@ class BOTAN_PUBLIC_API(3, 6) PK_Signature_Options : public Builder maybe_hash_function() { return take(m_hash_fn); } + [[nodiscard]] auto hash_function() { return take(m_hash_fn); } /// It may be acceptable to provide a hash function, for hash-based /// signatures (like SLH-DSA or LMS), but it is not required. @@ -296,36 +294,30 @@ class BOTAN_PUBLIC_API(3, 6) PK_Signature_Options : public Builder acceptable_hash = std::nullopt); - [[nodiscard]] std::pair> prehash() { - if(auto prehash = take(m_prehash)) { - return {true, std::move(prehash.value())}; - } else { - return {false, std::nullopt}; - } - } + [[nodiscard]] auto prehash() { return take(m_prehash); } - [[nodiscard]] std::optional padding() { return take(m_padding); } + [[nodiscard]] auto padding() { return take(m_padding); } - [[nodiscard]] std::optional> context() { return take(m_context); } + [[nodiscard]] auto context() { return take(m_context); } - [[nodiscard]] std::optional provider() { return take(m_provider); } + [[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()) { + if(auto p = provider().optional()) { throw Provider_Not_Found(algo_name, p.value()); }; } - [[nodiscard]] std::optional salt_size() { return take(m_salt_size); } + [[nodiscard]] auto salt_size() { return take(m_salt_size); } - [[nodiscard]] bool using_der_encoded_signature() { return take(m_use_der).value_or(false); } + [[nodiscard]] bool using_der_encoded_signature() { return take(m_use_der).or_default(false); } - [[nodiscard]] bool using_deterministic_signature() { return take(m_deterministic_sig).value_or(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).value_or(false); } + [[nodiscard]] bool using_explicit_trailer_field() { return take(m_explicit_trailer_field).or_default(false); } private: friend class Builder; diff --git a/src/lib/pubkey/sm2/sm2.cpp b/src/lib/pubkey/sm2/sm2.cpp index 56c21da0da..85527dc2f3 100644 --- a/src/lib/pubkey/sm2/sm2.cpp +++ b/src/lib/pubkey/sm2/sm2.cpp @@ -104,11 +104,11 @@ class SM2_Signature_Operation final : public PK_Ops::Signature { public: SM2_Signature_Operation(const SM2_PrivateKey& sm2, PK_Signature_Options& options) : m_group(sm2.domain()), m_x(sm2._private_key()), m_da_inv(sm2._get_da_inv()) { - const auto hash = options.hash_function(); + const auto hash = options.hash_function().required(); if(hash == "Raw") { // m_hash is null, m_za is empty } else { - auto context = options.context().value_or(sm2_default_userid); + auto context = options.context().or_default(sm2_default_userid); m_hash = HashFunction::create_or_throw(hash); // ZA=H256(ENTLA || IDA || a || b || xG || yG || xA || yA) @@ -171,11 +171,11 @@ class SM2_Verification_Operation final : public PK_Ops::Verification { public: SM2_Verification_Operation(const SM2_PublicKey& sm2, PK_Signature_Options& options) : m_group(sm2.domain()), m_gy_mul(sm2._public_key()) { - const auto hash = options.hash_function(); + const auto hash = options.hash_function().required(); if(hash == "Raw") { // m_hash is null, m_za is empty } else { - auto context = options.context().value_or(sm2_default_userid); + auto context = options.context().or_default(sm2_default_userid); m_hash = HashFunction::create_or_throw(hash); // ZA=H256(ENTLA || IDA || a || b || xG || yG || xA || yA) diff --git a/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp b/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp index 733c2ecfef..a2fb30dceb 100644 --- a/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp +++ b/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp @@ -352,6 +352,8 @@ std::unique_ptr SphincsPlus_PrivateKey::_create_signature_op( PK_Signature_Options& options) const { BOTAN_UNUSED(rng); options.exclude_provider_for_algorithm(algo_name()); + options.context().not_implemented("will come in Botan 3.7.0"); + options.prehash().not_implemented("will come in Botan 3.7.0"); options.validate_for_hash_based_signature_algorithm(algo_name(), m_public->parameters().hash_name()); return std::make_unique( m_private, m_public, options.using_deterministic_signature()); diff --git a/src/lib/utils/info.txt b/src/lib/utils/info.txt index 38a1cb74ad..7f4e62524b 100644 --- a/src/lib/utils/info.txt +++ b/src/lib/utils/info.txt @@ -12,7 +12,6 @@ load_on always assert.h allocator.h -base_builder.h compiler.h concepts.h data_src.h @@ -20,6 +19,7 @@ database.h exceptn.h mem_ops.h mutex.h +options_builder.h template_utils.h types.h strong_type.h diff --git a/src/lib/utils/base_builder.cpp b/src/lib/utils/options_builder.cpp similarity index 96% rename from src/lib/utils/base_builder.cpp rename to src/lib/utils/options_builder.cpp index 3baf5ce28e..1a015aff37 100644 --- a/src/lib/utils/base_builder.cpp +++ b/src/lib/utils/options_builder.cpp @@ -5,7 +5,7 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include #if defined(BOTAN_HAS_HASH) #include diff --git a/src/lib/utils/base_builder.h b/src/lib/utils/options_builder.h similarity index 78% rename from src/lib/utils/base_builder.h rename to src/lib/utils/options_builder.h index d035dba47b..21ea790055 100644 --- a/src/lib/utils/base_builder.h +++ b/src/lib/utils/options_builder.h @@ -5,8 +5,8 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#ifndef BOTAN_BASE_BUILDER_H_ -#define BOTAN_BASE_BUILDER_H_ +#ifndef BOTAN_OPTIONS_BUILDER_H_ +#define BOTAN_OPTIONS_BUILDER_H_ #include #include @@ -84,6 +84,61 @@ class Option { 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 + * or to handle it as an optional value. + */ +template +class OptionValue { + private: + std::optional take() noexcept { return std::exchange(m_value, {}); } + + public: + OptionValue(std::optional option, std::string_view option_name, std::string_view product_name) : + m_value(std::move(option)), m_option_name(option_name), m_product_name(product_name) {} + + /** + * @returns the option value or std::nullopt if it wasn't set. + */ + [[nodiscard]] std::optional optional() && noexcept { return take(); } + + /** + * @throws Invalid_Argument if the option wasn't set. + * @returns the option value or throws if it wasn't set. + */ + [[nodiscard]] T required() && { + if(!m_value.has_value()) { + throw Invalid_Argument("'" + m_product_name + "' requires the '" + std::string(m_option_name) + "' option"); + } + return take().value(); + } + + /** + * @returns the option value or the given @p default_value if it wasn't set. + */ + template U> + [[nodiscard]] T or_default(U&& default_value) && noexcept { + return take().value_or(std::forward(default_value)); + } + + /** + * Consumes the option value and throws if it was set. + * @throws Not_Implemented if the option was set, with given @p message. + */ + void not_implemented(std::string_view message) { + if(take().has_value()) { + throw Not_Implemented("'" + m_product_name + "' currently does not implement the '" + + std::string(m_option_name) + "' option: " + std::string(message)); + } + } + + private: + std::optional m_value; + std::string_view m_option_name; + std::string m_product_name; +}; + /// Concept to check whether T is a BuilderOption template struct is_builder_option : std::false_type {}; @@ -194,16 +249,9 @@ class Builder { protected: void set_product_name(std::string_view name) { m_product_name = std::string(name); } - [[nodiscard]] static auto take(detail::BuilderOption auto& o) noexcept { - return std::exchange(o.value, std::nullopt); - } - template - [[nodiscard]] auto require(OptionT& o) { - if(!o.value.has_value()) { - throw Invalid_Argument("'" + m_product_name + "' requires the '" + std::string(OptionT::name) + "' option"); - } - return take(o).value(); + [[nodiscard]] auto take(OptionT& o) noexcept { + return detail::OptionValue(std::exchange(o.value, {}), OptionT::name, m_product_name); } template ValueT>