Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PK_Signature_Options #4318

Draft
wants to merge 42 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
98f9b00
Add PK_Signature_Options
randombit Aug 17, 2024
2c633fc
Fix TPM
randombit Aug 17, 2024
aacc762
Fix include
randombit Aug 17, 2024
932f615
Add Asymmetric_Key::supports_context_data
randombit Aug 17, 2024
35e8dc6
Cleanup provider handling
randombit Aug 17, 2024
212351f
Improve validation
randombit Aug 17, 2024
3a15aa8
fmt
randombit Aug 17, 2024
ab61a27
Missing add
randombit Aug 17, 2024
c284e9c
Attempt workaround for MSVC bug
randombit Aug 18, 2024
88d31d2
Fix bogus include
randombit Aug 18, 2024
dbf1d07
Fix XMSS cli test
randombit Aug 18, 2024
337b924
Update verification
randombit Aug 18, 2024
f26e63e
Apply suggestions from code review
randombit Aug 19, 2024
72e3a81
Fix typo
randombit Aug 19, 2024
454b42f
More stuff
randombit Aug 19, 2024
92b5dc5
Gut EMSA parsing logic
randombit Aug 20, 2024
455f9ef
Temporarily disable two tests that need fixing
randombit Aug 20, 2024
c2512b0
fixes
randombit Aug 20, 2024
d4e7bce
value_or
randombit Aug 20, 2024
0e93767
fixes
randombit Aug 20, 2024
b025b41
Handle explicit trailer fields
randombit Aug 20, 2024
6f75c8e
Fixes
randombit Aug 20, 2024
5163ff3
f
randombit Aug 20, 2024
c04b241
Update some cli, examples, tests
randombit Aug 20, 2024
c1be281
fmt
randombit Aug 20, 2024
90d854b
Move
randombit Aug 20, 2024
ddad2c7
shim
randombit Aug 20, 2024
6a71101
f
randombit Aug 20, 2024
a4b80e2
Introduce a base builder as discussed here: https://github.com/random…
reneme Sep 10, 2024
88af001
Consumers can specify expectations of value availability
reneme Sep 11, 2024
efd60ad
Split Options and Builder into two classes
reneme Sep 11, 2024
589524a
Disentangle pk_keys.h and pubkey.h with fwd declares
reneme Sep 11, 2024
04fa277
Go all-in on the builder pattern
reneme Sep 11, 2024
c82da41
remove half-baked c'tors of PK_Signer/Verifier
reneme Sep 11, 2024
e07e48c
.with_provider() filters out legacy 'base' provider
reneme Sep 12, 2024
d2d4e63
PK_Signature_Options::from_legacy()
reneme Sep 12, 2024
89a8964
Code cleanup in base classes
reneme Sep 12, 2024
a4ef95a
Cleanups
reneme Sep 12, 2024
aa04f2a
Dilithium can't deal with context/prehash yet
reneme Sep 12, 2024
9c79af7
Options<>::to_string() can render uint8_t buffers
reneme Sep 12, 2024
d3dbcc2
Test: OptionsBuilder
reneme Sep 12, 2024
9d8cee0
Fix after rebase
reneme Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 18 additions & 39 deletions src/cli/pubkey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,25 +97,11 @@ BOTAN_REGISTER_COMMAND("keygen", PK_Keygen);

namespace {

std::string choose_sig_padding(const std::string& key, const std::string& padding, const std::string& hash) {
if(key == "RSA") {
std::ostringstream oss;
if(padding.empty()) {
oss << "PSS";
} else {
oss << padding;
}

oss << "(" << hash << ")";
return oss.str();
} else if(padding.empty()) {
return hash;
} else if(hash.empty()) {
return padding;
std::string_view normalize_padding(const std::string& algo, const std::string& requested_padding) {
if(algo == "RSA" && requested_padding.empty()) {
return "PSS";
} else {
std::ostringstream oss;
oss << padding << "(" << hash << ")";
return oss.str();
return requested_padding;
}
}

Expand Down Expand Up @@ -196,21 +182,17 @@ class PK_Sign final : public Command {
throw CLI_Error_Unsupported("hashing", hash_fn);
}

const std::string sig_padding = choose_sig_padding(key->algo_name(), get_arg("padding"), hash_fn);
auto signer_builder = key->signer()
.with_rng(rng())
.with_hash(hash_fn)
.with_der_encoded_signature(flag_set("der-format"))
.with_provider(get_arg("provider"));

auto format = Botan::Signature_Format::Standard;

if(flag_set("der-format")) {
if(key->message_parts() == 1) {
throw CLI_Usage_Error("Key type " + key->algo_name() +
" does not support DER formatting for signatures");
}
format = Botan::Signature_Format::DerSequence;
if(const auto padding = normalize_padding(key->algo_name(), get_arg("padding")); !padding.empty()) {
signer_builder.with_padding(padding);
}

const std::string provider = get_arg("provider");

Botan::PK_Signer signer(*key, rng(), sig_padding, format, provider);
auto signer = signer_builder.create();

auto onData = [&signer](const uint8_t b[], size_t l) { signer.update(b, l); };
Command::read_file(get_arg("file"), onData);
Expand Down Expand Up @@ -254,18 +236,15 @@ class PK_Verify final : public Command {
throw CLI_Error_Unsupported("hashing", hash_fn);
}

const std::string sig_padding = choose_sig_padding(key->algo_name(), get_arg("padding"), hash_fn);
auto verifier_builder =
key->signature_verifier().with_hash(hash_fn).with_der_encoded_signature(flag_set("der-format"));

auto format = Botan::Signature_Format::Standard;
if(flag_set("der-format")) {
if(key->message_parts() == 1) {
throw CLI_Usage_Error("Key type " + key->algo_name() +
" does not support DER formatting for signatures");
}
format = Botan::Signature_Format::DerSequence;
if(const auto padding = normalize_padding(key->algo_name(), get_arg("padding")); !padding.empty()) {
verifier_builder.with_padding(padding);
}

Botan::PK_Verifier verifier(*key, sig_padding, format);
auto verifier = verifier_builder.create();

auto onData = [&verifier](const uint8_t b[], size_t l) { verifier.update(b, l); };
Command::read_file(get_arg("file"), onData);

Expand Down
4 changes: 2 additions & 2 deletions src/examples/ecdsa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ int main() {
const std::string message("This is a tasty burger!");

// sign data
Botan::PK_Signer signer(key, rng, "SHA-256");
auto signer = key.signer().with_hash("SHA-256").with_rng(rng).create();
signer.update(message);
std::vector<uint8_t> signature = signer.signature(rng);
std::cout << "Signature:\n" << Botan::hex_encode(signature);

// now verify the signature
Botan::PK_Verifier verifier(key, "SHA-256");
auto verifier = key.signature_verifier().with_hash("SHA-256").create();
verifier.update(message);
std::cout << "\nis " << (verifier.check_signature(signature) ? "valid" : "invalid");
return 0;
Expand Down
4 changes: 2 additions & 2 deletions src/examples/pkcs11_ecdsa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ int main() {

std::vector<uint8_t> plaintext(20, 0x01);

Botan::PK_Signer signer(key_pair.second, rng, "Raw", Botan::Signature_Format::Standard, "pkcs11");
auto signer = key_pair.second.signer().with_rng(rng).with_hash("Raw").create();
auto signature = signer.sign_message(plaintext, rng);

Botan::PK_Verifier token_verifier(key_pair.first, "Raw", Botan::Signature_Format::Standard, "pkcs11");
auto token_verifier = key_pair.first.signature_verifier().with_hash("Raw").create();
bool ecdsa_ok = token_verifier.verify_message(plaintext, signature);

return ecdsa_ok ? 0 : 1;
Expand Down
4 changes: 2 additions & 2 deletions src/examples/pkcs11_rsa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ int main() {

/************ RSA sign *************/

Botan::PK_Signer signer(rsa_keypair.second, rng, "EMSA4(SHA-256)", Botan::Signature_Format::Standard);
auto signer = rsa_keypair.second.signer().with_rng(rng).with_hash("SHA-256").with_padding("PSS").create();
auto signature = signer.sign_message(plaintext, rng);

/************ RSA verify *************/

Botan::PK_Verifier verifier(rsa_keypair.first, "EMSA4(SHA-256)", Botan::Signature_Format::Standard);
auto verifier = rsa_keypair.first.signature_verifier().with_hash("SHA-256").with_padding("PSS").create();
auto ok = verifier.verify_message(plaintext, signature);

return ok ? 0 : 1;
Expand Down
10 changes: 5 additions & 5 deletions src/examples/xmss.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ int main() {
const Botan::XMSS_PublicKey& public_key(private_key);

// create Public Key Signer using the private key.
Botan::PK_Signer signer(private_key, rng, "");
auto signer = private_key.signer().with_rng(rng).create();

// create and sign a message using the Public Key Signer.
Botan::secure_vector<uint8_t> msg{0x01, 0x02, 0x03, 0x04};
signer.update(msg.data(), msg.size());
signer.update(msg);
std::vector<uint8_t> sig = signer.signature(rng);

// create Public Key Verifier using the public key
Botan::PK_Verifier verifier(public_key, "");
auto verifier = public_key.signature_verifier().create();

// verify the signature for the previously generated message.
verifier.update(msg.data(), msg.size());
if(verifier.check_signature(sig.data(), sig.size())) {
verifier.update(msg);
if(verifier.check_signature(sig)) {
std::cout << "Success.\n";
return 0;
} else {
Expand Down
1 change: 1 addition & 0 deletions src/lib/math/pcurves/pcurves_impl/pcurves_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#define BOTAN_PCURVES_IMPL_H_

#include <botan/rng.h>
#include <botan/template_utils.h>
#include <botan/internal/ct_utils.h>
#include <botan/internal/loadstor.h>
#include <botan/internal/pcurves_util.h>
Expand Down
132 changes: 50 additions & 82 deletions src/lib/pk_pad/emsa.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (C) 2015 Jack Lloyd
* (C) 2015,2024 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
Expand All @@ -8,7 +8,7 @@

#include <botan/exceptn.h>
#include <botan/hash.h>
#include <botan/internal/scan_name.h>
#include <botan/pk_options.h>

#if defined(BOTAN_HAS_EMSA_X931)
#include <botan/internal/emsa_x931.h>
Expand All @@ -32,108 +32,76 @@

namespace Botan {

std::unique_ptr<EMSA> EMSA::create(std::string_view algo_spec) {
SCAN_Name req(algo_spec);
std::unique_ptr<EMSA> EMSA::create_or_throw(PK_Signature_Options& options) {
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";
Comment on lines +35 to +39
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From an end-user's perspective this makes it quite easy to create an RSA signature without a padding. Like so:

auto sk = create_private_key("RSA", rng);
auto signer = sk->signer().with_rng(rng).create();
signer.update("Hello, world");
const auto signature = signer.signature(rng);

... this is quite the footgun and we should look into making this harder.

For instance, we could throw if neither hash nor padding are defined instead of defaulting to "Raw". Note, that users may still explicitly request with_padding("Raw") if they really want to.


#if defined(BOTAN_HAS_EMSA_PKCS1)
if(req.algo_name() == "EMSA_PKCS1" || req.algo_name() == "PKCS1v15" || req.algo_name() == "EMSA-PKCS1-v1_5" ||
req.algo_name() == "EMSA3") {
if(req.arg_count() == 2 && req.arg(0) == "Raw") {
return std::make_unique<EMSA_PKCS1v15_Raw>(req.arg(1));
} else if(req.arg_count() == 1) {
if(req.arg(0) == "Raw") {
return std::make_unique<EMSA_PKCS1v15_Raw>();
} else {
if(auto hash = HashFunction::create(req.arg(0))) {
return std::make_unique<EMSA_PKCS1v15>(std::move(hash));
if(is_raw_padding) {
// Only valid possibility for empty padding is no hash / "Raw" hash

#if defined(BOTAN_HAS_EMSA_RAW)
if(is_raw_hash) {
if(auto prehash = options.prehash().optional(); prehash.has_value() && prehash->has_value()) {
if(auto prehash_fn = HashFunction::create(prehash->value())) {
return std::make_unique<EMSA_Raw>(prehash_fn->output_length());
}
} else {
return std::make_unique<EMSA_Raw>();
}
}
}
#endif
} else {
// null if raw_hash
auto hash_fn = [&]() -> std::unique_ptr<HashFunction> {
if(is_raw_hash) {
return nullptr;
} else {
return HashFunction::create(hash.value());
}
}();

#if defined(BOTAN_HAS_EMSA_PSSR)
if(req.algo_name() == "PSS_Raw" || req.algo_name() == "PSSR_Raw") {
if(req.arg_count_between(1, 3) && req.arg(1, "MGF1") == "MGF1") {
if(auto hash = HashFunction::create(req.arg(0))) {
if(req.arg_count() == 3) {
const size_t salt_size = req.arg_as_integer(2, 0);
return std::make_unique<PSSR_Raw>(std::move(hash), salt_size);
} else {
return std::make_unique<PSSR_Raw>(std::move(hash));
}
#if defined(BOTAN_HAS_EMSA_PKCS1)
if(padding == "PKCS1v15") {
if(is_raw_hash) {
return std::make_unique<EMSA_PKCS1v15_Raw>(options.prehash().or_default(std::nullopt));
} else if(hash_fn) {
return std::make_unique<EMSA_PKCS1v15>(std::move(hash_fn));
}
}
}
#endif

if(req.algo_name() == "PSS" || req.algo_name() == "PSSR" || req.algo_name() == "EMSA-PSS" ||
req.algo_name() == "PSS-MGF1" || req.algo_name() == "EMSA4") {
if(req.arg_count_between(1, 3) && req.arg(1, "MGF1") == "MGF1") {
if(auto hash = HashFunction::create(req.arg(0))) {
if(req.arg_count() == 3) {
const size_t salt_size = req.arg_as_integer(2, 0);
return std::make_unique<PSSR>(std::move(hash), salt_size);
} else {
return std::make_unique<PSSR>(std::move(hash));
}
}
#if defined(BOTAN_HAS_EMSA_PSSR)
if(padding == "PSS_Raw" && hash_fn) {
return std::make_unique<PSSR_Raw>(std::move(hash_fn), options.salt_size().optional());
}

if(padding == "PSS" && hash_fn) {
return std::make_unique<PSSR>(std::move(hash_fn), options.salt_size().optional());
}
}
#endif

#if defined(BOTAN_HAS_ISO_9796)
if(req.algo_name() == "ISO_9796_DS2") {
if(req.arg_count_between(1, 3)) {
if(auto hash = HashFunction::create(req.arg(0))) {
const size_t salt_size = req.arg_as_integer(2, hash->output_length());
const bool implicit = req.arg(1, "exp") == "imp";
return std::make_unique<ISO_9796_DS2>(std::move(hash), implicit, salt_size);
}
if(padding == "ISO_9796_DS2" && hash_fn) {
return std::make_unique<ISO_9796_DS2>(
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
if(req.algo_name() == "ISO_9796_DS3") {
if(req.arg_count_between(1, 2)) {
if(auto hash = HashFunction::create(req.arg(0))) {
const bool implicit = req.arg(1, "exp") == "imp";
return std::make_unique<ISO_9796_DS3>(std::move(hash), implicit);
}

//ISO-9796-2 DS 3 is deterministic and DS2 without a salt
if(padding == "ISO_9796_DS3" && hash_fn) {
return std::make_unique<ISO_9796_DS3>(std::move(hash_fn), !options.using_explicit_trailer_field());
}
}
#endif

#if defined(BOTAN_HAS_EMSA_X931)
if(req.algo_name() == "EMSA_X931" || req.algo_name() == "EMSA2" || req.algo_name() == "X9.31") {
if(req.arg_count() == 1) {
if(auto hash = HashFunction::create(req.arg(0))) {
return std::make_unique<EMSA_X931>(std::move(hash));
}
if(padding == "X9.31" && hash_fn) {
return std::make_unique<EMSA_X931>(std::move(hash_fn));
}
}
#endif

#if defined(BOTAN_HAS_EMSA_RAW)
if(req.algo_name() == "Raw") {
if(req.arg_count() == 0) {
return std::make_unique<EMSA_Raw>();
} else {
auto hash = HashFunction::create(req.arg(0));
if(hash) {
return std::make_unique<EMSA_Raw>(hash->output_length());
}
}
}
#endif

return nullptr;
}

std::unique_ptr<EMSA> EMSA::create_or_throw(std::string_view algo_spec) {
auto emsa = EMSA::create(algo_spec);
if(emsa) {
return emsa;
}
throw Algorithm_Not_Found(algo_spec);
throw Lookup_Error("Invalid or unavailable signature padding scheme\n" + options.to_string());
}

} // namespace Botan
13 changes: 3 additions & 10 deletions src/lib/pk_pad/emsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Botan {

class RandomNumberGenerator;
class PK_Signature_Options;

/**
* EMSA, from IEEE 1363s Encoding Method for Signatures, Appendix
Expand All @@ -27,18 +28,10 @@ class BOTAN_TEST_API EMSA {
/**
* Factory method for EMSA (message-encoding methods for signatures
* with appendix) objects
* @param algo_spec the name of the EMSA to create
* @return pointer to newly allocated object of that type, or nullptr
*/
static std::unique_ptr<EMSA> create(std::string_view algo_spec);

/**
* Factory method for EMSA (message-encoding methods for signatures
* with appendix) objects
* @param algo_spec the name of the EMSA to create
* @param options the algorithm parameters
* @return pointer to newly allocated object of that type, or throws
*/
static std::unique_ptr<EMSA> create_or_throw(std::string_view algo_spec);
static std::unique_ptr<EMSA> create_or_throw(PK_Signature_Options& options);

/**
* Add more data to the signature computation
Expand Down
20 changes: 10 additions & 10 deletions src/lib/pk_pad/emsa_pkcs1/emsa_pkcs1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,16 @@ EMSA_PKCS1v15::EMSA_PKCS1v15(std::unique_ptr<HashFunction> hash) : m_hash(std::m
m_hash_id = pkcs_hash_id(m_hash->name());
}

EMSA_PKCS1v15_Raw::EMSA_PKCS1v15_Raw() {
m_hash_output_len = 0;
// m_hash_id, m_hash_name left empty
}

EMSA_PKCS1v15_Raw::EMSA_PKCS1v15_Raw(std::string_view hash_algo) {
std::unique_ptr<HashFunction> hash(HashFunction::create_or_throw(hash_algo));
m_hash_id = pkcs_hash_id(hash_algo);
m_hash_name = hash->name();
m_hash_output_len = hash->output_length();
EMSA_PKCS1v15_Raw::EMSA_PKCS1v15_Raw(const std::optional<std::string>& hash_algo) {
if(hash_algo) {
std::unique_ptr<HashFunction> hash(HashFunction::create_or_throw(hash_algo.value()));
m_hash_id = pkcs_hash_id(hash->name());
m_hash_name = hash->name();
m_hash_output_len = hash->output_length();
} else {
m_hash_output_len = 0;
// m_hash_id, m_hash_name left empty
}
}

void EMSA_PKCS1v15_Raw::update(const uint8_t input[], size_t length) {
Expand Down
Loading
Loading