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 support for setting the nonce type and digest on a PKEY_CTX #2144

Merged
merged 7 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
5 changes: 5 additions & 0 deletions openssl-sys/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## [Unreleased]

### Added

* Added `OSSL_PARAM`, `OSSL_PARAM_construct_uint` , `OSSL_PARAM_construct_end`.
* Added `EVP_PKEY_CTX_set_params` and `EVP_PKEY_CTX_get_params`.

## [v0.9.99] - 2024-01-19

### Added
Expand Down
6 changes: 6 additions & 0 deletions openssl-sys/src/handwritten/evp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,12 @@ extern "C" {
#[cfg(ossl300)]
pub fn EVP_PKEY_CTX_set_signature_md(ctx: *mut EVP_PKEY_CTX, md: *const EVP_MD) -> c_int;

#[cfg(ossl300)]
pub fn EVP_PKEY_CTX_set_params(ctx: *mut EVP_PKEY_CTX, params: *const OSSL_PARAM) -> c_int;

#[cfg(ossl300)]
pub fn EVP_PKEY_CTX_get_params(ctx: *mut EVP_PKEY_CTX, params: *mut OSSL_PARAM) -> c_int;

pub fn EVP_PKEY_new_mac_key(
type_: c_int,
e: *mut ENGINE,
Expand Down
2 changes: 2 additions & 0 deletions openssl-sys/src/handwritten/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub use self::hmac::*;
pub use self::kdf::*;
pub use self::object::*;
pub use self::ocsp::*;
pub use self::params::*;
pub use self::pem::*;
pub use self::pkcs12::*;
pub use self::pkcs7::*;
Expand Down Expand Up @@ -51,6 +52,7 @@ mod hmac;
mod kdf;
mod object;
mod ocsp;
mod params;
mod pem;
mod pkcs12;
mod pkcs7;
Expand Down
9 changes: 9 additions & 0 deletions openssl-sys/src/handwritten/params.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use super::super::*;
use libc::*;

extern "C" {
#[cfg(ossl300)]
pub fn OSSL_PARAM_construct_uint(key: *const c_char, buf: *mut c_uint) -> OSSL_PARAM;
#[cfg(ossl300)]
pub fn OSSL_PARAM_construct_end() -> OSSL_PARAM;
}
10 changes: 10 additions & 0 deletions openssl-sys/src/handwritten/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1093,3 +1093,13 @@ pub enum OSSL_PROVIDER {}

#[cfg(ossl300)]
pub enum OSSL_LIB_CTX {}

#[cfg(ossl300)]
#[repr(C)]
pub struct OSSL_PARAM {
key: *const c_char,
data_type: c_uchar,
data: *mut c_void,
data_size: size_t,
return_size: size_t,
}
4 changes: 4 additions & 0 deletions openssl/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

* Added `PkeyCtxRef::{nonce_type, set_nonce_type}`.

## [v0.10.63] - 2024-01-19

### Added
Expand Down
108 changes: 108 additions & 0 deletions openssl/src/pkey_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ use crate::{cvt, cvt_p};
use foreign_types::{ForeignType, ForeignTypeRef};
#[cfg(not(boringssl))]
use libc::c_int;
#[cfg(ossl320)]
use libc::c_uint;
use openssl_macros::corresponds;
use std::convert::TryFrom;
#[cfg(ossl320)]
use std::ffi::CStr;
use std::ptr;

/// HKDF modes of operation.
Expand Down Expand Up @@ -105,6 +109,21 @@ impl HkdfMode {
pub const EXPAND_ONLY: Self = HkdfMode(ffi::EVP_PKEY_HKDEF_MODE_EXPAND_ONLY);
}

/// Nonce type for ECDSA and DSA.
#[cfg(ossl320)]
#[derive(Debug, PartialEq)]
pub struct NonceType(c_uint);

#[cfg(ossl320)]
impl NonceType {
/// This is the default mode. It uses a random value for the nonce k as defined in FIPS 186-4 Section 6.3
/// “Secret Number Generation”.
pub const RANDOM_K: Self = NonceType(0);
alex marked this conversation as resolved.
Show resolved Hide resolved

/// Uses a deterministic value for the nonce k as defined in RFC #6979 (See Section 3.2 “Generation of k”).
pub const DETERMINISTIC_K: Self = NonceType(1);
}

generic_foreign_type_and_impl_send_sync! {
type CType = ffi::EVP_PKEY_CTX;
fn drop = ffi::EVP_PKEY_CTX_free;
Expand Down Expand Up @@ -714,6 +733,53 @@ impl<T> PkeyCtxRef<T> {
Ok(PKey::from_ptr(key))
}
}

/// Sets the nonce type for a private key context.
///
/// The nonce for DSA and ECDSA can be either random (the default) or deterministic (as defined by RFC 6979).
///
/// This is only useful for DSA and ECDSA.
/// Requires OpenSSL 3.2.0 or newer.
#[cfg(ossl320)]
#[corresponds(EVP_PKEY_CTX_set_params)]
Copy link
Contributor

Choose a reason for hiding this comment

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

These corresponds probably should be dropped since this uses the function, but it is a generic func that can be used in many places. That said, I'd defer to @sfackler for his opinion.

Copy link
Contributor Author

@facutuesca facutuesca Feb 6, 2024

Choose a reason for hiding this comment

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

I thought the same at first, but there's also a #corresponds(EVP_PKEY_CTX_ctrl)] (also a generic func) for set_keygen_cipher and set_keygen_mac_key, so I went with it anyway. I can remove it if it should not be there.

pub fn set_nonce_type(&mut self, nonce_type: NonceType) -> Result<(), ErrorStack> {
let nonce_field_name = CStr::from_bytes_with_nul("nonce-type\0".as_bytes()).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
let nonce_field_name = CStr::from_bytes_with_nul("nonce-type\0".as_bytes()).unwrap();
let nonce_field_name = CStr::from_bytes_with_nul(b"nonce-type\0").unwrap();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed!

let mut nonce_type = nonce_type.0;
unsafe {
let param_nonce =
ffi::OSSL_PARAM_construct_uint(nonce_field_name.as_ptr(), &mut nonce_type);
let param_end = ffi::OSSL_PARAM_construct_end();

let params = [param_nonce, param_end];
cvt(ffi::EVP_PKEY_CTX_set_params(self.as_ptr(), params.as_ptr()))?;
}
Ok(())
}

/// Gets the nonce type for a private key context.
///
/// The nonce for DSA and ECDSA can be either random (the default) or deterministic (as defined by RFC 6979).
///
/// This is only useful for DSA and ECDSA.
/// Requires OpenSSL 3.2.0 or newer.
#[cfg(ossl320)]
#[corresponds(EVP_PKEY_CTX_get_params)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Same corresponds statement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(same comment as above)

pub fn nonce_type(&mut self) -> Result<NonceType, ErrorStack> {
let nonce_field_name = CStr::from_bytes_with_nul("nonce-type\0".as_bytes()).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
let nonce_field_name = CStr::from_bytes_with_nul("nonce-type\0".as_bytes()).unwrap();
let nonce_field_name = CStr::from_bytes_with_nul(b"nonce-type\0").unwrap();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed!

let mut nonce_type: c_uint = 0;
unsafe {
let param_nonce =
ffi::OSSL_PARAM_construct_uint(nonce_field_name.as_ptr(), &mut nonce_type);
let param_end = ffi::OSSL_PARAM_construct_end();

let mut params = [param_nonce, param_end];
cvt(ffi::EVP_PKEY_CTX_get_params(
self.as_ptr(),
params.as_mut_ptr(),
))?;
}
Ok(NonceType(nonce_type))
}
}

#[cfg(test)]
Expand Down Expand Up @@ -999,4 +1065,46 @@ mod test {
// The digest is the end of the DigestInfo structure.
assert_eq!(result_buf[length - digest.len()..length], digest);
}

#[test]
Copy link
Contributor

Choose a reason for hiding this comment

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

We should add a test that confirms setting noncetype + digest works as expected. You can get a test vector from: https://github.com/openssl/openssl/blob/master/test/recipes/30-test_evp_data/evppkey_ecdsa_rfc6979.txt

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea, fixed!

#[cfg(ossl320)]
fn set_nonce_type() {
let key1 =
EcKey::generate(&EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap()).unwrap();
let key1 = PKey::from_ec_key(key1).unwrap();

let mut ctx = PkeyCtx::new(&key1).unwrap();
ctx.sign_init().unwrap();
ctx.set_nonce_type(NonceType::DETERMINISTIC_K).unwrap();
let nonce_type = ctx.nonce_type().unwrap();
assert_eq!(nonce_type, NonceType::DETERMINISTIC_K);
assert!(ErrorStack::get().errors().is_empty());
}

// Test vector from
// https://github.com/openssl/openssl/blob/openssl-3.2.0/test/recipes/30-test_evp_data/evppkey_ecdsa_rfc6979.txt
#[test]
#[cfg(ossl320)]
fn ecdsa_deterministic_signature() {
let private_key_pem = "-----BEGIN PRIVATE KEY-----
MDkCAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQEEHzAdAgEBBBhvqwNJNOTA/Jrmf1tWWanX0f79GH7g
n9Q=
-----END PRIVATE KEY-----";

let key1 = EcKey::private_key_from_pem(private_key_pem.as_bytes()).unwrap();
let key1 = PKey::from_ec_key(key1).unwrap();
let input = "sample";
let expected_output = hex::decode("303502190098C6BD12B23EAF5E2A2045132086BE3EB8EBD62ABF6698FF021857A22B07DEA9530F8DE9471B1DC6624472E8E2844BC25B64").unwrap();

let hashed_input = hash(MessageDigest::sha1(), input.as_bytes()).unwrap();
let mut ctx = PkeyCtx::new(&key1).unwrap();
ctx.sign_init().unwrap();
ctx.set_signature_md(Md::sha1()).unwrap();
ctx.set_nonce_type(NonceType::DETERMINISTIC_K).unwrap();

let mut output = vec![];
ctx.sign_to_vec(&hashed_input, &mut output).unwrap();
assert_eq!(output, expected_output);
assert!(ErrorStack::get().errors().is_empty());
}
}
Loading