diff --git a/openssl-sys/src/handwritten/ssl.rs b/openssl-sys/src/handwritten/ssl.rs index cdcdea5881..eab821955b 100644 --- a/openssl-sys/src/handwritten/ssl.rs +++ b/openssl-sys/src/handwritten/ssl.rs @@ -951,3 +951,8 @@ extern "C" { #[cfg(any(ossl110, libressl360))] pub fn SSL_get_security_level(s: *const SSL) -> c_int; } + +extern "C" { + #[cfg(ossl300)] + pub fn SSL_group_to_name(ssl: *mut SSL, id: c_int) -> *const c_char; +} diff --git a/openssl-sys/src/ssl.rs b/openssl-sys/src/ssl.rs index 52ea5b2135..e3c2abf682 100644 --- a/openssl-sys/src/ssl.rs +++ b/openssl-sys/src/ssl.rs @@ -363,6 +363,8 @@ pub const SSL_CTRL_GET_MIN_PROTO_VERSION: c_int = 130; pub const SSL_CTRL_GET_MAX_PROTO_VERSION: c_int = 131; #[cfg(ossl300)] pub const SSL_CTRL_GET_TMP_KEY: c_int = 133; +#[cfg(ossl300)] +pub const SSL_CTRL_GET_NEGOTIATED_GROUP: c_int = 134; pub unsafe fn SSL_CTX_set_tmp_dh(ctx: *mut SSL_CTX, dh: *mut DH) -> c_long { SSL_CTX_ctrl(ctx, SSL_CTRL_SET_TMP_DH, 0, dh as *mut c_void) @@ -519,6 +521,10 @@ cfg_if! { pub unsafe fn SSL_get_tmp_key(ssl: *mut SSL, key: *mut *mut EVP_PKEY) -> c_long { SSL_ctrl(ssl, SSL_CTRL_GET_TMP_KEY, 0, key as *mut c_void) } + + pub unsafe fn SSL_get_negotiated_group(ssl: *mut SSL) -> c_int { + SSL_ctrl(ssl, SSL_CTRL_GET_NEGOTIATED_GROUP, 0, ptr::null_mut()) as c_int + } } } diff --git a/openssl/src/nid.rs b/openssl/src/nid.rs index e50feb0683..6fcc59b0e7 100644 --- a/openssl/src/nid.rs +++ b/openssl/src/nid.rs @@ -1096,6 +1096,76 @@ impl Nid { pub const CHACHA20_POLY1305: Nid = Nid(ffi::NID_chacha20_poly1305); } +/// An abstract type wrapping a numerical identifier for a negotiated group. +/// +/// This is most similar to a `Nid` (when it represents a known group) +/// but can also represent a specific "unknown shared group". +/// If the NID for the shared group is unknown then the value is set to the bitwise OR of TLSEXT_nid_unknown (0x1000000) and the id of the group. +/// +/// # Examples +/// +/// To view the integer representation of a `NegotiatedGroup`: +/// +/// ``` +/// use openssl::nid::Nid; +/// use openssl::nid::NegotiatedGroup; +/// +/// assert!(Nid::AES_256_GCM.as_raw() == 901); +/// assert!(NegotiatedGroup::from_raw(901).nid() == Some(Nid::AES_256_GCM)); +/// assert!(NegotiatedGroup::from_raw(901).unknown_group_id() == None); +/// +/// let bogus_id: i32 = 0x6399; +/// let raw = bogus_id | NegotiatedGroup::TLSEXT_nid_unknown; +/// assert!(NegotiatedGroup::from_raw(raw).unknown_group_id() == Some(bogus_id)); +/// assert!(NegotiatedGroup::from_raw(raw).nid() == None); +/// ``` +/// +/// # External Documentation +/// +/// The following documentation provides context about returned numeric identifiers +/// for negotiated groups in OpenSSL. +/// +/// - [SSL_get_negotiated_group](https://www.openssl.org/docs/manmaster/man3/SSL_get_negotiated_group.html) +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg(ossl300)] +pub struct NegotiatedGroup(c_int); + +#[cfg(ossl300)] +impl NegotiatedGroup { + #[allow(non_upper_case_globals)] + pub const TLSEXT_nid_unknown: i32 = 0x1000000; + + /// Create a `NegotiatedGroup` from an integer representation. + pub const fn from_raw(raw: c_int) -> Self { + Self(raw) + } + + /// Return the integer representation of a `NegotiatedGroup`. + #[allow(clippy::trivially_copy_pass_by_ref)] + pub const fn as_raw(&self) -> c_int { + self.0 + } + + /// Return a `Nid` for the known negotiated group or `None``. + pub fn nid(self) -> Option { + if (self.0 & Self::TLSEXT_nid_unknown) == 0 { + Some(Nid::from_raw(self.0)) + } else { + None + } + } + + /// Return a `i32` id for the unknown negotiated group or `None`. + pub fn unknown_group_id(self) -> Option { + if (self.0 & Self::TLSEXT_nid_unknown) != 0 { + let id = self.0 ^ Self::TLSEXT_nid_unknown; + Some(id) + } else { + None + } + } +} + #[cfg(test)] mod test { use super::Nid; diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index 2ff9dac1fd..8e87fb2a59 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -59,6 +59,8 @@ //! ``` #[cfg(ossl300)] use crate::cvt_long; +#[cfg(ossl300)] +use crate::cvt_p_const; use crate::dh::{Dh, DhRef}; #[cfg(all(ossl101, not(ossl110)))] use crate::ec::EcKey; @@ -67,6 +69,8 @@ use crate::error::ErrorStack; use crate::ex_data::Index; #[cfg(ossl111)] use crate::hash::MessageDigest; +#[cfg(ossl300)] +use crate::nid::NegotiatedGroup; #[cfg(any(ossl110, libressl270))] use crate::nid::Nid; use crate::pkey::{HasPrivate, PKeyRef, Params, Private}; @@ -3484,6 +3488,51 @@ impl SslRef { } } } + + /// Returns the NID of the negotiated group used for the handshake key + /// exchange process. + /// For TLSv1.3 connections this typically reflects the state of the + /// current connection, though in the case of PSK-only resumption, the + /// returned value will be from a previous connection. + /// For earlier TLS versions, when a session has been resumed, it always + /// reflects the group used for key exchange during the initial handshake + /// (otherwise it is from the current, non-resumption, connection). + /// This can be called by either client or server. + /// + /// If the NID for the shared group is unknown then the value is set to the + /// bitwise OR of TLSEXT_nid_unknown (0x1000000) and the id of the group. + #[corresponds(SSL_get_negotiated_group)] + #[cfg(ossl300)] + pub fn negotiated_group(&self) -> Result { + use crate::nid::NegotiatedGroup; + + let raw = unsafe { cvt(ffi::SSL_get_negotiated_group(self.as_ptr())) }; + raw.map(NegotiatedGroup::from_raw) + } + + /// Return the TLS group name associated with a given TLS group ID, as + /// registered via built-in or external providers and as returned by a call + /// to SSL_get1_groups() or SSL_get_shared_group(). + /// + /// If non-NULL, SSL_group_to_name() returns the TLS group name + /// corresponding to the given id as a NUL-terminated string. + /// If SSL_group_to_name() returns NULL, an error occurred; possibly no + /// corresponding tlsname was registered during provider initialisation. + /// + /// Note that the return value is valid only during the lifetime of the + /// SSL object ssl. + #[corresponds(SSL_group_to_name)] + #[cfg(ossl300)] + pub fn group_to_name(&self, id: c_int) -> Result<&str, ErrorStack> { + unsafe { + match cvt_p_const(ffi::SSL_group_to_name(self.as_ptr(), id)) { + Ok(constp) => Ok(CStr::from_ptr(constp) + .to_str() + .expect("Invalid UTF8 in input")), + Err(e) => Err(e), + } + } + } } /// An SSL stream midway through the handshake process.