-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Support for ECDSA deterministic signing (RFC 6979) #10369
Conversation
441b799
to
c4f3bd9
Compare
rust-openssl has had a release. Also can you please make a seperate PR adding the test vectors? |
c4f3bd9
to
01388ce
Compare
01388ce
to
bd354d1
Compare
I rebased now that the test vectors are merged |
src/rust/src/backend/ec.rs
Outdated
@@ -274,14 +274,35 @@ impl ECPrivateKey { | |||
)); | |||
} | |||
|
|||
let (data, _) = utils::calculate_digest_and_algorithm( | |||
let (data, _algo) = utils::calculate_digest_and_algorithm( |
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.
if this is used, the name shouldn't have a leading prefix
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.
it's only used when OpenSSL >=3.2.0, so without it it complains when compiling with older OpenSSLs (or libre/boring). I could do something like this instead?:
cfg_if::cfg_if! {
if #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)]{
let (data, _algo) = utils::calculate_digest_and_algorithm(
py,
data.as_bytes(),
signature_algorithm.getattr(pyo3::intern!(py, "algorithm"))?,
)?;
} else {
let (data, _) = utils::calculate_digest_and_algorithm(
py,
data.as_bytes(),
signature_algorithm.getattr(pyo3::intern!(py, "algorithm"))?,
)?;
}
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.
You can do let _ = algo;
in the other branch.
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.
oh, that is a lot better! fixed
src/rust/src/backend/ec.rs
Outdated
.extract()?; | ||
cfg_if::cfg_if! { | ||
if #[cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)]{ | ||
match deterministic { |
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.
This can be an if
, no need for a match
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.
fixed
src/rust/src/backend/ec.rs
Outdated
let hash_function_name = _algo.getattr(pyo3::intern!(py, "name"))?.extract::<&str>()?; | ||
let hash_function = openssl::md::Md::fetch(None, hash_function_name, None)?; |
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.
There's a function in the hashes module that you can use for this.
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 tried, but I'm having trouble constructing the appropriate type. The hashes
function I tried to use returns a openssl::hash::MessageDigest
:
pub(crate) fn message_digest_from_algorithm(
py: pyo3::Python<'_>,
algorithm: &pyo3::PyAny,
) -> CryptographyResult<openssl::hash::MessageDigest> {
The function I'm calling that uses the hash function takes an openssl::md::MdRef
:
pub fn set_signature_md(&self, md: &MdRef) -> Result<(), ErrorStack>
I can convert one to the other by adding unsafe code:
let hash_function = hashes::message_digest_from_algorithm(py, _algo)?;
unsafe {
signer.set_signature_md(&openssl::md::Md::from_ptr(hash_function.as_ptr().cast_mut()))?;
}
But not sure if that's what we want (as opposed to directly using openssl::md::Md::fetch(), which already encapsulates the unsafe use)
tests/utils.py
Outdated
elif not line: | ||
if data: | ||
vectors.append(data) | ||
data = dict() |
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.
data = dict() | |
data = {} |
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.
fixed
@@ -47,6 +47,16 @@ Elliptic Curve Signature Algorithms | |||
:param algorithm: An instance of | |||
:class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. | |||
|
|||
:param bool deterministic_signing: A boolean flag defaulting to ``False`` | |||
that specifies whether the signing procedure should be deterministic | |||
or not, as defined in :rfc:`6979`. |
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.
Docs should say that this has no impact on verification.
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.
Fixed. The new docstring now says:
:param bool deterministic_signing: A boolean flag defaulting to ``False``
that specifies whether the signing procedure should be deterministic
or not, as defined in :rfc:`6979`. This only impacts the signing
process, verification is not affected (the verification process
is the same for both deterministic and non-deterministic signed
messages).
bd354d1
to
b8dfa6c
Compare
Merge, please! |
CHANGELOG.rst
Outdated
@@ -26,6 +26,7 @@ Changelog | |||
and :class:`~cryptography.hazmat.primitives.ciphers.algorithms.ARC4` into | |||
:doc:`/hazmat/decrepit/index` and deprecated them in the ``cipher`` module. | |||
They will be removed from the ``cipher`` module in 48.0.0. | |||
* Added support for deterministic ECDSA (:rfc:`6979`) |
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.
Please add a link to the relevant code -- making ECDSA a link to the class should be enough.
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.
fixed
src/rust/src/backend/ec.rs
Outdated
@@ -274,14 +274,35 @@ impl ECPrivateKey { | |||
)); | |||
} | |||
|
|||
let (data, _) = utils::calculate_digest_and_algorithm( | |||
let (data, _algo) = utils::calculate_digest_and_algorithm( |
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.
You can do let _ = algo;
in the other branch.
tests/hazmat/primitives/test_ec.py
Outdated
@@ -508,6 +513,70 @@ def test_signature_failures(self, backend, subtests): | |||
signature, vector["message"], ec.ECDSA(hash_type()) | |||
) | |||
|
|||
def test_unsupported_deterministic_nonce(self, backend, subtests): |
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.
def test_unsupported_deterministic_nonce(self, backend, subtests): | |
def test_unsupported_deterministic_nonce(self, backend): |
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.
fixed
tests/hazmat/primitives/test_ec.py
Outdated
key = bytes("\n".join(vector["key"]), "utf-8") | ||
if "digest_sign" in vector: | ||
algorithm = vector["digest_sign"] | ||
assert algorithm in supported_hash_algorithms |
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.
assert algorithm in supported_hash_algorithms |
Not needed, in tests it's fine for the next line to raise KeyError
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.
removed
tests/hazmat/primitives/test_ec.py
Outdated
f" backend {backend}" | ||
) | ||
|
||
supported_hash_algorithms: typing.Dict[ |
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.
It's simpler if you just make the values here instance of the hash algorithm, rather than instantiating them below.
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.
fixed
6801f81
to
0342369
Compare
0342369
to
dd28add
Compare
Rebased now that #10486 is merged |
@facutuesca |
src/rust/src/backend/ec.rs
Outdated
} | ||
} else { | ||
let _ = algo; | ||
assert!(!deterministic); |
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.
So this assert won't trigger unless you do something dumb (instantiate ECDSA then set obj._deterministic = True
yourself on non-OpenSSL 3.2.0) but I'd rather it wasn't possible to cause program termination even if you do something truly stupid.
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.
Makes sense, I removed the assert
and didn't add any code (therefore completely ignoring deterministic
's value) as per @alex's suggestion.
0685c47
to
087f948
Compare
087f948
to
dd6d63d
Compare
090da44
to
ca593a9
Compare
Part of #9795. Now that the Rust bindings needed are in place (sfackler/rust-openssl#2144), this PR adds support for ECDSA deterministic signing (RFC 6979), a new feature in
OpenSSL >= 3.2.0
.The way to enable it is by passing a new boolean parameter
deterministic_signing
toec.ECDSA()
:UnsupportedAlgorithm
exception. It will also raise if FIPS is enabled.deterministic_signing
parameter defaults toFalse
, so that old code that does not specify it maintains the same behavior.