whois: show negotiated TLS version/group/cipher in 671; sslgroup crate reads the KEX group
This commit is contained in:
parent
85eb8218f0
commit
009bea734a
13 changed files with 103 additions and 7 deletions
|
|
@ -35,6 +35,10 @@ memchr = "2"
|
||||||
# ring provider (no aws-lc-rs); its `unsafe` stays internal like every other crate.
|
# ring provider (no aws-lc-rs); its `unsafe` stays internal like every other crate.
|
||||||
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12", "logging"] }
|
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12", "logging"] }
|
||||||
rustls-pemfile = "2"
|
rustls-pemfile = "2"
|
||||||
|
# Reads the negotiated TLS group name (SSL_get0_group_name) for the WHOIS 671 line;
|
||||||
|
# isolates the one unsafe FFI call the safe openssl crate lacks, so the daemon stays
|
||||||
|
# `#![forbid(unsafe_code)]`.
|
||||||
|
sslgroup = { path = "sslgroup" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# integration tests spawn the built binary and act as a TLS client against it
|
# integration tests spawn the built binary and act as a TLS client against it
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,7 @@ impl Command for Whois {
|
||||||
last_active: u64,
|
last_active: u64,
|
||||||
signon: u64,
|
signon: u64,
|
||||||
certfp: Option<String>,
|
certfp: Option<String>,
|
||||||
|
tls_info: Option<String>,
|
||||||
swhois: Option<String>,
|
swhois: Option<String>,
|
||||||
showwhois: bool,
|
showwhois: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -219,6 +220,7 @@ impl Command for Whois {
|
||||||
last_active,
|
last_active,
|
||||||
signon,
|
signon,
|
||||||
certfp,
|
certfp,
|
||||||
|
tls_info,
|
||||||
swhois,
|
swhois,
|
||||||
showwhois,
|
showwhois,
|
||||||
} = {
|
} = {
|
||||||
|
|
@ -239,6 +241,7 @@ impl Command for Whois {
|
||||||
last_active: u.last_active,
|
last_active: u.last_active,
|
||||||
signon: u.signon,
|
signon: u.signon,
|
||||||
certfp: u.certfp.clone(),
|
certfp: u.certfp.clone(),
|
||||||
|
tls_info: u.tls_info.clone(),
|
||||||
swhois: u
|
swhois: u
|
||||||
.ext
|
.ext
|
||||||
.get::<crate::coremods::core_oper::Swhois>()
|
.get::<crate::coremods::core_oper::Swhois>()
|
||||||
|
|
@ -399,12 +402,18 @@ impl Command for Whois {
|
||||||
&format!("{nick} {acct} :is logged in as"),
|
&format!("{nick} {acct} :is logged in as"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// sslinfo: advertise a secure (TLS) connection
|
// sslinfo: advertise a secure (TLS) connection, with the negotiated
|
||||||
|
// version/group/cipher (e.g. TLSv1.3/X25519MLKEM768/TLS_CHACHA20_POLY1305_SHA256)
|
||||||
|
// when the backend could report it.
|
||||||
if secure && !(hide && crate::modules::hidewhois::hide_secure(s)) {
|
if secure && !(hide && crate::modules::hidewhois::hide_secure(s)) {
|
||||||
|
let detail = match &tls_info {
|
||||||
|
Some(t) if !t.is_empty() => format!(" [{t}]"),
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
s.numeric(
|
s.numeric(
|
||||||
uid,
|
uid,
|
||||||
RPL_WHOISSECURE,
|
RPL_WHOISSECURE,
|
||||||
&format!("{nick} :is using a secure connection"),
|
&format!("{nick} :is using a secure connection{detail}"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// client-cert fingerprint (CertFP) — shown to the user themselves and opers
|
// client-cert fingerprint (CertFP) — shown to the user themselves and opers
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ pub enum Event {
|
||||||
sock: Option<TcpStream>,
|
sock: Option<TcpStream>,
|
||||||
secure: bool,
|
secure: bool,
|
||||||
certfp: Option<String>, // TLS client-cert fingerprint (clients only)
|
certfp: Option<String>, // TLS client-cert fingerprint (clients only)
|
||||||
|
tls_info: Option<String>, // negotiated TLS version/group/cipher (WHOIS 671)
|
||||||
local_port: u16, // the listener port the client connected to
|
local_port: u16, // the listener port the client connected to
|
||||||
link: bool, // a server-to-server connection, not a client
|
link: bool, // a server-to-server connection, not a client
|
||||||
outbound: bool, // (link) we dialed them
|
outbound: bool, // (link) we dialed them
|
||||||
|
|
@ -189,6 +190,7 @@ impl Ircd {
|
||||||
sock,
|
sock,
|
||||||
secure,
|
secure,
|
||||||
certfp,
|
certfp,
|
||||||
|
tls_info,
|
||||||
local_port,
|
local_port,
|
||||||
link,
|
link,
|
||||||
outbound,
|
outbound,
|
||||||
|
|
@ -198,7 +200,7 @@ impl Ircd {
|
||||||
self.server.add_link(uid, addr, out, sock, outbound);
|
self.server.add_link(uid, addr, out, sock, outbound);
|
||||||
} else {
|
} else {
|
||||||
self.server
|
self.server
|
||||||
.add_conn(uid, addr, out, sock, secure, certfp, local_port);
|
.add_conn(uid, addr, out, sock, secure, certfp, tls_info, local_port);
|
||||||
if websocket {
|
if websocket {
|
||||||
if let Some(u) = self.server.users.get_mut(&uid) {
|
if let Some(u) = self.server.users.get_mut(&uid) {
|
||||||
u.flags.via_websocket = true;
|
u.flags.via_websocket = true;
|
||||||
|
|
|
||||||
|
|
@ -2827,6 +2827,7 @@ mod tests {
|
||||||
vhost: None,
|
vhost: None,
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
|
tls_info: None,
|
||||||
account: None,
|
account: None,
|
||||||
signon: 0,
|
signon: 0,
|
||||||
nick_ts: 0,
|
nick_ts: 0,
|
||||||
|
|
@ -2916,6 +2917,7 @@ mod tests {
|
||||||
vhost: None,
|
vhost: None,
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
|
tls_info: None,
|
||||||
account: None,
|
account: None,
|
||||||
signon: 0,
|
signon: 0,
|
||||||
nick_ts: 0,
|
nick_ts: 0,
|
||||||
|
|
|
||||||
|
|
@ -374,6 +374,7 @@ mod tests {
|
||||||
vhost: None,
|
vhost: None,
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
|
tls_info: None,
|
||||||
account: Some("reverse".into()),
|
account: Some("reverse".into()),
|
||||||
signon: 0,
|
signon: 0,
|
||||||
nick_ts: 0,
|
nick_ts: 0,
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ impl Node {
|
||||||
vhost: None,
|
vhost: None,
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
|
tls_info: None,
|
||||||
account: None,
|
account: None,
|
||||||
signon: 0,
|
signon: 0,
|
||||||
nick_ts: 0,
|
nick_ts: 0,
|
||||||
|
|
|
||||||
|
|
@ -387,6 +387,7 @@ impl Server {
|
||||||
sock: Option<TcpStream>,
|
sock: Option<TcpStream>,
|
||||||
secure: bool,
|
secure: bool,
|
||||||
certfp: Option<String>,
|
certfp: Option<String>,
|
||||||
|
tls_info: Option<String>,
|
||||||
local_port: u16,
|
local_port: u16,
|
||||||
) {
|
) {
|
||||||
let uuid = self.next_uuid();
|
let uuid = self.next_uuid();
|
||||||
|
|
@ -405,6 +406,7 @@ impl Server {
|
||||||
vhost: None,
|
vhost: None,
|
||||||
secure,
|
secure,
|
||||||
certfp,
|
certfp,
|
||||||
|
tls_info,
|
||||||
account: None,
|
account: None,
|
||||||
signon: now(),
|
signon: now(),
|
||||||
nick_ts: now(),
|
nick_ts: now(),
|
||||||
|
|
@ -1544,6 +1546,7 @@ mod tests {
|
||||||
vhost: None,
|
vhost: None,
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
|
tls_info: None,
|
||||||
account: None,
|
account: None,
|
||||||
signon: 0,
|
signon: 0,
|
||||||
nick_ts: 0,
|
nick_ts: 0,
|
||||||
|
|
|
||||||
|
|
@ -656,6 +656,7 @@ fn reactor_loop(
|
||||||
sock: None,
|
sock: None,
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
|
tls_info: None,
|
||||||
local_port: a.local_port,
|
local_port: a.local_port,
|
||||||
link: false,
|
link: false,
|
||||||
outbound: false,
|
outbound: false,
|
||||||
|
|
@ -788,7 +789,8 @@ fn try_handshake(
|
||||||
core: &Sender<Event>,
|
core: &Sender<Event>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut close = false;
|
let mut close = false;
|
||||||
let mut connect: Option<(Uid, SocketAddr, u16, Option<String>, OutSink)> = None;
|
let mut connect: Option<(Uid, SocketAddr, u16, Option<String>, Option<String>, OutSink)> =
|
||||||
|
None;
|
||||||
if let Some(c) = conns.get_mut(&t) {
|
if let Some(c) = conns.get_mut(&t) {
|
||||||
if !c.handshaking {
|
if !c.handshaking {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -798,10 +800,11 @@ fn try_handshake(
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
c.handshaking = false;
|
c.handshaking = false;
|
||||||
let certfp = sess.peer_cert_fp();
|
let certfp = sess.peer_cert_fp();
|
||||||
|
let tls_info = sess.tls_info();
|
||||||
connect = c
|
connect = c
|
||||||
.pending_out
|
.pending_out
|
||||||
.take()
|
.take()
|
||||||
.map(|out| (c.uid, c.addr, c.local_port, certfp, out));
|
.map(|out| (c.uid, c.addr, c.local_port, certfp, tls_info, out));
|
||||||
set_interest(poll, c, t); // handshake done: drop the extra WRITABLE
|
set_interest(poll, c, t); // handshake done: drop the extra WRITABLE
|
||||||
}
|
}
|
||||||
Ok(false) => return false, // still negotiating
|
Ok(false) => return false, // still negotiating
|
||||||
|
|
@ -813,7 +816,7 @@ fn try_handshake(
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if let Some((uid, addr, local_port, certfp, out)) = connect {
|
if let Some((uid, addr, local_port, certfp, tls_info, out)) = connect {
|
||||||
let _ = core.send(Event::Connect {
|
let _ = core.send(Event::Connect {
|
||||||
uid,
|
uid,
|
||||||
addr,
|
addr,
|
||||||
|
|
@ -821,6 +824,7 @@ fn try_handshake(
|
||||||
sock: None,
|
sock: None,
|
||||||
secure: true,
|
secure: true,
|
||||||
certfp,
|
certfp,
|
||||||
|
tls_info,
|
||||||
local_port,
|
local_port,
|
||||||
link: false,
|
link: false,
|
||||||
outbound: false,
|
outbound: false,
|
||||||
|
|
@ -943,6 +947,7 @@ fn read_conn(poll: &mut Poll, conns: &mut HashMap<usize, Conn>, t: usize, core:
|
||||||
sock: None,
|
sock: None,
|
||||||
secure,
|
secure,
|
||||||
certfp,
|
certfp,
|
||||||
|
tls_info: None,
|
||||||
local_port,
|
local_port,
|
||||||
link: false,
|
link: false,
|
||||||
outbound: false,
|
outbound: false,
|
||||||
|
|
@ -1096,6 +1101,7 @@ pub fn accept_loop(
|
||||||
sock: Some(shutdown),
|
sock: Some(shutdown),
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
|
tls_info: None,
|
||||||
local_port,
|
local_port,
|
||||||
link,
|
link,
|
||||||
outbound: false,
|
outbound: false,
|
||||||
|
|
@ -1180,6 +1186,7 @@ pub fn connect_link(addr: &str, core: Sender<Event>, counter: Arc<AtomicU64>, ma
|
||||||
sock: Some(shutdown),
|
sock: Some(shutdown),
|
||||||
secure: false,
|
secure: false,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
|
tls_info: None,
|
||||||
local_port: 0,
|
local_port: 0,
|
||||||
link: true,
|
link: true,
|
||||||
outbound: true,
|
outbound: true,
|
||||||
|
|
@ -1287,6 +1294,7 @@ fn tls_conn(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let certfp = conn.peer_cert_fp();
|
let certfp = conn.peer_cert_fp();
|
||||||
|
let tls_info = conn.tls_info();
|
||||||
let (out_tx, out_rx) = mpsc::channel::<String>();
|
let (out_tx, out_rx) = mpsc::channel::<String>();
|
||||||
if core
|
if core
|
||||||
.send(Event::Connect {
|
.send(Event::Connect {
|
||||||
|
|
@ -1296,6 +1304,7 @@ fn tls_conn(
|
||||||
sock: Some(shutdown),
|
sock: Some(shutdown),
|
||||||
secure: true,
|
secure: true,
|
||||||
certfp,
|
certfp,
|
||||||
|
tls_info,
|
||||||
local_port,
|
local_port,
|
||||||
link,
|
link,
|
||||||
outbound: false,
|
outbound: false,
|
||||||
|
|
|
||||||
30
src/tls.rs
30
src/tls.rs
|
|
@ -15,7 +15,7 @@ use mio::net::TcpStream as MioStream;
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::MessageDigest;
|
||||||
use openssl::ssl::{
|
use openssl::ssl::{
|
||||||
ErrorCode, NameType, SniError, Ssl, SslAcceptor, SslAcceptorBuilder, SslContext, SslFiletype,
|
ErrorCode, NameType, SniError, Ssl, SslAcceptor, SslAcceptorBuilder, SslContext, SslFiletype,
|
||||||
SslMethod, SslMode, SslStream, SslVerifyMode,
|
SslMethod, SslMode, SslRef, SslStream, SslVerifyMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A hot-reloadable TLS certificate source (implemented by the openssl backend);
|
/// A hot-reloadable TLS certificate source (implemented by the openssl backend);
|
||||||
|
|
@ -41,6 +41,11 @@ pub trait TlsConn: Send {
|
||||||
/// SHA-256 fingerprint (lowercase hex) of the peer's certificate, if it sent
|
/// SHA-256 fingerprint (lowercase hex) of the peer's certificate, if it sent
|
||||||
/// one. Drives SASL EXTERNAL / CertFP.
|
/// one. Drives SASL EXTERNAL / CertFP.
|
||||||
fn peer_cert_fp(&self) -> Option<String>;
|
fn peer_cert_fp(&self) -> Option<String>;
|
||||||
|
/// `<version>/<group>/<cipher>` summary of the session for the WHOIS 671
|
||||||
|
/// sslinfo line, if the backend can report it.
|
||||||
|
fn tls_info(&self) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A non-blocking TLS session the reactor drives itself over a mio socket. The
|
/// A non-blocking TLS session the reactor drives itself over a mio socket. The
|
||||||
|
|
@ -70,6 +75,10 @@ pub trait TlsSession: Send {
|
||||||
fn source(&mut self) -> &mut MioStream;
|
fn source(&mut self) -> &mut MioStream;
|
||||||
/// SHA-256 fingerprint of the peer certificate (CertFP / SASL EXTERNAL), if any.
|
/// SHA-256 fingerprint of the peer certificate (CertFP / SASL EXTERNAL), if any.
|
||||||
fn peer_cert_fp(&self) -> Option<String>;
|
fn peer_cert_fp(&self) -> Option<String>;
|
||||||
|
/// `<version>/<group>/<cipher>` summary of the session for WHOIS 671, if any.
|
||||||
|
fn tls_info(&self) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
fn shutdown(&mut self);
|
fn shutdown(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,6 +213,19 @@ fn ssl_io_err(e: openssl::ssl::Error) -> io::Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `<version>/<group>/<cipher>` for the WHOIS 671 sslinfo line, e.g.
|
||||||
|
/// `TLSv1.3/X25519MLKEM768/TLS_CHACHA20_POLY1305_SHA256`. The key-exchange group
|
||||||
|
/// comes from `sslgroup` (SSL_get0_group_name); it's omitted when OpenSSL can't
|
||||||
|
/// report it (TLS 1.2, or before the handshake completes).
|
||||||
|
fn openssl_tls_info(ssl: &SslRef) -> Option<String> {
|
||||||
|
let cipher = ssl.current_cipher()?.name();
|
||||||
|
let ver = ssl.version_str();
|
||||||
|
match sslgroup::group_name(ssl) {
|
||||||
|
Some(g) if !g.is_empty() => Some(format!("{ver}/{g}/{cipher}")),
|
||||||
|
_ => Some(format!("{ver}/{cipher}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TlsSession for OpensslSession {
|
impl TlsSession for OpensslSession {
|
||||||
fn accept(&mut self) -> io::Result<bool> {
|
fn accept(&mut self) -> io::Result<bool> {
|
||||||
match self.0.accept() {
|
match self.0.accept() {
|
||||||
|
|
@ -233,6 +255,9 @@ impl TlsSession for OpensslSession {
|
||||||
let digest = cert.digest(MessageDigest::sha256()).ok()?;
|
let digest = cert.digest(MessageDigest::sha256()).ok()?;
|
||||||
Some(digest.iter().map(|b| format!("{b:02x}")).collect())
|
Some(digest.iter().map(|b| format!("{b:02x}")).collect())
|
||||||
}
|
}
|
||||||
|
fn tls_info(&self) -> Option<String> {
|
||||||
|
openssl_tls_info(self.0.ssl())
|
||||||
|
}
|
||||||
fn shutdown(&mut self) {
|
fn shutdown(&mut self) {
|
||||||
// best-effort TLS close_notify, then close the socket. Non-blocking, so a
|
// best-effort TLS close_notify, then close the socket. Non-blocking, so a
|
||||||
// WouldBlock just means the alert is queued — we don't wait for the peer's.
|
// WouldBlock just means the alert is queued — we don't wait for the peer's.
|
||||||
|
|
@ -264,4 +289,7 @@ impl TlsConn for OpensslConn {
|
||||||
let digest = cert.digest(MessageDigest::sha256()).ok()?;
|
let digest = cert.digest(MessageDigest::sha256()).ok()?;
|
||||||
Some(digest.iter().map(|b| format!("{b:02x}")).collect())
|
Some(digest.iter().map(|b| format!("{b:02x}")).collect())
|
||||||
}
|
}
|
||||||
|
fn tls_info(&self) -> Option<String> {
|
||||||
|
openssl_tls_info(self.0.ssl())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -230,6 +230,7 @@ pub struct User {
|
||||||
pub vhost: Option<String>, // displayed-host override (CHGHOST/SETHOST vhost)
|
pub vhost: Option<String>, // displayed-host override (CHGHOST/SETHOST vhost)
|
||||||
pub secure: bool, // connected over TLS (drives WHOIS 671 / sslinfo)
|
pub secure: bool, // connected over TLS (drives WHOIS 671 / sslinfo)
|
||||||
pub certfp: Option<String>, // TLS client-cert fingerprint (SASL EXTERNAL / CertFP)
|
pub certfp: Option<String>, // TLS client-cert fingerprint (SASL EXTERNAL / CertFP)
|
||||||
|
pub tls_info: Option<String>, // negotiated TLS version/group/cipher (WHOIS 671)
|
||||||
pub account: Option<String>, // logged-in account name (set by services)
|
pub account: Option<String>, // logged-in account name (set by services)
|
||||||
pub signon: u64, // unix secs at registration (WHOIS 317)
|
pub signon: u64, // unix secs at registration (WHOIS 317)
|
||||||
pub nick_ts: u64, // unix secs the current nick was taken (nick-collision arbitration)
|
pub nick_ts: u64, // unix secs the current nick was taken (nick-collision arbitration)
|
||||||
|
|
|
||||||
|
|
@ -278,6 +278,7 @@ fn ws_session<S: WsStream>(
|
||||||
sock: Some(shutdown),
|
sock: Some(shutdown),
|
||||||
secure,
|
secure,
|
||||||
certfp: None,
|
certfp: None,
|
||||||
|
tls_info: None,
|
||||||
local_port,
|
local_port,
|
||||||
link: false,
|
link: false,
|
||||||
outbound: false,
|
outbound: false,
|
||||||
|
|
|
||||||
12
sslgroup/Cargo.toml
Normal file
12
sslgroup/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "sslgroup"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Read the negotiated TLS key-exchange group name via SSL_get0_group_name (not exposed by the safe openssl crate)."
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Same versions echoircd resolves, so the SslRef / SSL pointer types match.
|
||||||
|
openssl = "0.10"
|
||||||
|
openssl-sys = "0.9"
|
||||||
|
foreign-types = "0.3"
|
||||||
23
sslgroup/src/lib.rs
Normal file
23
sslgroup/src/lib.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
//! Reads the negotiated TLS key-exchange group name (e.g. `X25519MLKEM768`) via
|
||||||
|
//! OpenSSL's `SSL_get0_group_name`, which the safe `openssl` crate does not expose.
|
||||||
|
//! Isolated in its own crate so the daemon can stay `#![forbid(unsafe_code)]` — the
|
||||||
|
//! single `unsafe` FFI call lives here, exactly like the FFI inside `openssl`/`ring`.
|
||||||
|
|
||||||
|
use foreign_types::ForeignTypeRef;
|
||||||
|
use openssl::ssl::SslRef;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
|
||||||
|
/// The TLS key-exchange group name for an accepted session (OpenSSL 3.2+), or
|
||||||
|
/// `None` if unavailable (before the handshake, or an OpenSSL without the API).
|
||||||
|
pub fn group_name(ssl: &SslRef) -> Option<String> {
|
||||||
|
// SAFETY: `ssl` is a live, accepted `SSL`. `SSL_get0_group_name` returns a
|
||||||
|
// NUL-terminated string OpenSSL owns for the session's lifetime; we copy it out
|
||||||
|
// here, so the borrow does not escape.
|
||||||
|
unsafe {
|
||||||
|
let name = openssl_sys::SSL_get0_group_name(ssl.as_ptr());
|
||||||
|
if name.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
CStr::from_ptr(name).to_str().ok().map(String::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue