-
Notifications
You must be signed in to change notification settings - Fork 623
Add PK_Signature_Options #5021
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
base: master
Are you sure you want to change the base?
Add PK_Signature_Options #5021
Conversation
750f8fb to
9601274
Compare
This allows controlling all details of how signatures are created, without having to stuff values into the single parameters string which was previously available.
9601274 to
0729830
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing I'd like to raise for a final time would be support for move-vs-copy. (Feel free to just give a thumbs down reaction on this comment for a hard veto.) 😅
I know that most of the expected std::string contents in this instance are probably falling within short-string-optimization (which seems to be ~12 chars for 32bit machines [source]). However, setting a "context" buffer would result in unnecessary allocations/copies of arbitrarily large data. Especially when users set it as the first option of a chain of with_... calls. Other builders might behave worse here. E.g. the CertificateParametersBuilder (#4996) will likely hold plenty of vectors and DNs that would be copied around a lot when implemented in the same way as this PR.
Making the builder smarter in this regard comes with a bit of boilerplate, but to me it seems small enough to consider (here's the example below with some explanation in Compiler Explorer). The essential idea would be to overload each setter for an rvalue-this and a const-ref-this. The latter is pure boilerplate and just calls the former. The rvalue-this implementation can be put into an implementation file and does not need to create an explicit next copy anymore.
class Options {
private:
Options myself() const& { return *this; }
Options myself() && { return std::move(*this); }
public:
// setters...
Options with_hash(std::string_view hash_fn) const& { return myself().with_hash(hash_fn); }
Options with_hash(std::string_view hash_fn) && {
m_hash_fn = hash_fn;
return myself();
}
Options with_der_encoding(bool der = true) const& { return myself().with_der_encoding(der); }
Options with_der_encoding(bool der = true) && {
m_der_encoding = der;
return myself();
}
// getters...
const auto& hash() const { return m_hash_fn; }
bool der() const { return m_der_encoding; }
private:
std::optional<std::string> m_hash_fn;
bool m_der_encoding = false;
};| /// Specify producing or expecting a DER encoded signature | ||
| /// | ||
| /// This is mostly used with ECDSA | ||
| /// | ||
| /// For schemes that do not support such formatting (such as RSA | ||
| /// or post-quantum schemes), an exception will be thrown when the | ||
| /// PK_Signer or PK_Verifier is created. | ||
| PK_Signature_Options with_der_encoded_signature(bool der = true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idiom of passing a defaulted bool value to be able to disable the option explicitly. I would suggest to apply this to with_deterministic_signature() and with_explicit_trailer_field() as well.
| /// Specify the hash function to use for signing/verification | ||
| /// | ||
| /// Most, but not all, schemes require specifying a hash function. | ||
| PK_Signature_Options with_hash(std::string_view hash); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps not in the first iteration, but it would be awesome if we were able to pass a user-defined hash function here. We discussed that already in #4318, for instance to let users offload the hash calculation into some (obscure) hardware.
Holding a std::unique_ptr<HashFunction> as a member will break the current design of relying on the copy-constructor, though. So we would have to keep the hash object in a shared pointer, which seems like the right thing to do but could cause trouble downstream when we have to pass a unique pointer inside some API, I don't know. Or we would have to make the PK_Signature_Options move-only making it harder to use as a policy-like object across multiple signatures and forcing us to re-introduce lvalue& and rvalue& overloads for every setter.
|
|
||
| const std::optional<std::string>& hash_function() const { return m_hash_fn; } | ||
|
|
||
| const std::optional<std::string>& prehash_fn() const { return m_prehash; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consistency-nit: The ordinary hash_function is spelled out, let's not abbreviate this one for consistency:
| const std::optional<std::string>& prehash_fn() const { return m_prehash; } | |
| const std::optional<std::string>& prehash_function() const { return m_prehash; } |
| next.m_using_prehash = true; | ||
| next.m_prehash = std::move(prehash_fn); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| next.m_using_prehash = true; | |
| next.m_prehash = std::move(prehash_fn); | |
| // Calling this with a std::nullopt enables prehashing with an algorithm- | |
| // specific hash function that is not user-defined. Hence the bool flag. | |
| next.m_using_prehash = true; | |
| next.m_prehash = std::move(prehash_fn); |
... I needed a moment to understand why m_prehash.has_value() wasn't enough to decide whether prehash was enabled or not.
| PK_Signature_Options PK_Signature_Options::with_context(std::span<const uint8_t> context) { | ||
| BOTAN_STATE_CHECK_MSG(!using_context(), "PK_Signature_Options::with_context cannot specify context twice"); | ||
| auto next = (*this); | ||
| next.m_context = std::vector<uint8_t>(context.begin(), context.end()); | ||
| return next; | ||
| } | ||
|
|
||
| PK_Signature_Options PK_Signature_Options::with_context(std::string_view context) { | ||
| BOTAN_STATE_CHECK_MSG(!using_context(), "PK_Signature_Options::with_context cannot specify context twice"); | ||
| auto next = (*this); | ||
| auto contextb = as_span_of_bytes(context); | ||
| next.m_context = std::vector<uint8_t>(contextb.begin(), contextb.end()); | ||
| return next; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nits:
std::optional::emplaceinstead of explicit assignment ofstd::vector- implement
string_viewoverload in terms ofspanversion
| PK_Signature_Options PK_Signature_Options::with_context(std::span<const uint8_t> context) { | |
| BOTAN_STATE_CHECK_MSG(!using_context(), "PK_Signature_Options::with_context cannot specify context twice"); | |
| auto next = (*this); | |
| next.m_context = std::vector<uint8_t>(context.begin(), context.end()); | |
| return next; | |
| } | |
| PK_Signature_Options PK_Signature_Options::with_context(std::string_view context) { | |
| BOTAN_STATE_CHECK_MSG(!using_context(), "PK_Signature_Options::with_context cannot specify context twice"); | |
| auto next = (*this); | |
| auto contextb = as_span_of_bytes(context); | |
| next.m_context = std::vector<uint8_t>(contextb.begin(), contextb.end()); | |
| return next; | |
| } | |
| PK_Signature_Options PK_Signature_Options::with_context(std::span<const uint8_t> context) { | |
| BOTAN_STATE_CHECK_MSG(!using_context(), "PK_Signature_Options::with_context cannot specify context twice"); | |
| auto next = (*this); | |
| next.m_context.emplace(context.begin(), context.end()); | |
| return next; | |
| } | |
| PK_Signature_Options PK_Signature_Options::with_context(std::string_view context) { | |
| return with_context(as_span_of_bytes(context)); | |
| } |
Continuation / rebase of #4318
This allows controlling all details of how signatures are created, without having to stuff values into the single parameters string which was previously available.