From df7c06cff0f4c27c102c3928a7ea977232d95db3 Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Sun, 1 Mar 2026 23:33:10 +0530 Subject: [PATCH 1/9] Add call credentials --- grpc/src/credentials/call.rs | 278 +++++++++++++++++++++++++++++++ grpc/src/credentials/client.rs | 64 +++++++ grpc/src/credentials/insecure.rs | 26 +-- grpc/src/credentials/mod.rs | 2 + 4 files changed, 360 insertions(+), 10 deletions(-) create mode 100644 grpc/src/credentials/call.rs diff --git a/grpc/src/credentials/call.rs b/grpc/src/credentials/call.rs new file mode 100644 index 000000000..860a659d8 --- /dev/null +++ b/grpc/src/credentials/call.rs @@ -0,0 +1,278 @@ +/* + * + * Copyright 2026 gRPC authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +use std::fmt::Debug; +use std::sync::Arc; + +use tonic::async_trait; +use tonic::metadata::MetadataMap; +use tonic::Status; + +use crate::attributes::Attributes; +use crate::credentials::common::SecurityLevel; + +/// Details regarding the RPC call. +/// +/// The fully qualified method name is constructed as: +/// `service_url` + "/" + `method_name` +pub struct CallDetails { + service_url: String, + + /// The method name suffix (e.g., `Method` or `package.Service/Method`). + method_name: String, +} + +impl CallDetails { + pub(crate) fn new(service_url: String, method_name: String) -> Self { + Self { + service_url, + method_name, + } + } + + pub fn service_url(&self) -> &str { + &self.service_url + } + + /// The method name suffix (e.g., `Method` or `package.Service/Method`). + pub fn method_name(&self) -> &str { + &self.method_name + } +} + +pub struct ChannelSecurityInfo { + security_protocol: &'static str, + security_level: SecurityLevel, + /// Stores extra data derived from the underlying protocol. + attributes: Attributes, +} + +impl ChannelSecurityInfo { + pub fn new( + security_protocol: &'static str, + security_level: SecurityLevel, + attributes: Attributes, + ) -> Self { + Self { + security_protocol, + security_level, + attributes, + } + } + + pub fn security_protocol(&self) -> &'static str { + self.security_protocol + } + + pub fn security_level(&self) -> SecurityLevel { + self.security_level + } + + pub fn attributes(&self) -> &Attributes { + &self.attributes + } +} + +/// A thread-safe handle for credentials that are attached to individual RPC +/// calls. +/// +/// `CallCredentials` wrap a [`CallCredentialsProvider`]. They can be combined +/// using [`CompositeCallCredentials`]. +#[derive(Debug, Clone)] +pub struct CallCredentials { + inner: Arc, +} + +impl CallCredentials { + pub(crate) async fn get_metadata( + &self, + call_details: &CallDetails, + auth_info: &ChannelSecurityInfo, + metadata: &mut MetadataMap, + ) -> Result<(), Status> { + self.inner + .get_metadata(call_details, auth_info, metadata) + .await + } + + pub(crate) fn minimum_channel_security_level(&self) -> SecurityLevel { + self.inner.minimum_channel_security_level() + } +} + +impl From for CallCredentials { + fn from(provider: T) -> Self { + Self { + inner: Arc::new(provider), + } + } +} + +/// Defines the interface for credentials that need to attach security +/// information to every individual RPC (e.g., OAuth2 tokens, JWTs). +#[async_trait] +pub trait CallCredentialsProvider: Send + Sync + Debug { + /// Generates the authentication metadata for a specific RPC call. + /// + /// This method is called by the transport layer on each request. + /// Implementations should populate the provided `metadata` map with the + /// necessary authorization headers (e.g., `authorization: Bearer `). + /// + /// If this returns an `Err`, the RPC will fail immediately with a status + /// derived from the error if the status code is in the range defined in + /// gRFC A54. Otherwise, the RPC is failed with an internal status. + async fn get_metadata( + &self, + call_details: &CallDetails, + auth_info: &ChannelSecurityInfo, + metadata: &mut MetadataMap, + ) -> Result<(), Status>; + + /// Indicates the minimum transport security level required to send + /// these credentials. + fn minimum_channel_security_level(&self) -> SecurityLevel { + SecurityLevel::PrivacyAndIntegrity + } +} + +/// A composite implementation of [`CallCredentialsProvider`] that combines +/// multiple credentials. +#[derive(Debug)] +pub struct CompositeCallCredentials { + creds: Vec, +} + +impl CompositeCallCredentials { + /// Creates a new [`CompositeCallCredentials`] with the first two credentials. + pub fn new(first: impl Into, second: impl Into) -> Self { + Self { + creds: vec![first.into(), second.into()], + } + } + + /// Adds an additional [`CallCredentials`] to the composite. + pub fn with_call_credentials(mut self, creds: impl Into) -> Self { + self.creds.push(creds.into()); + self + } +} + +#[async_trait] +impl CallCredentialsProvider for CompositeCallCredentials { + async fn get_metadata( + &self, + call_details: &CallDetails, + auth_info: &ChannelSecurityInfo, + metadata: &mut MetadataMap, + ) -> Result<(), Status> { + for cred in &self.creds { + cred.get_metadata(call_details, auth_info, metadata).await?; + } + Ok(()) + } + + fn minimum_channel_security_level(&self) -> SecurityLevel { + self.creds + .iter() + .map(|c| c.minimum_channel_security_level()) + .max_by_key(|&l| match l { + SecurityLevel::NoSecurity => 0, + SecurityLevel::IntegrityOnly => 1, + SecurityLevel::PrivacyAndIntegrity => 2, + }) + .unwrap_or(SecurityLevel::NoSecurity) + } +} + +#[cfg(test)] +mod tests { + use tonic::metadata::MetadataValue; + + use super::*; + + #[derive(Debug)] + struct MockCallCredentials { + key: String, + value: String, + security_level: SecurityLevel, + } + + #[async_trait] + impl CallCredentialsProvider for MockCallCredentials { + async fn get_metadata( + &self, + _call_details: &CallDetails, + _auth_info: &ChannelSecurityInfo, + metadata: &mut MetadataMap, + ) -> Result<(), Status> { + metadata.insert( + self.key + .parse::>() + .unwrap(), + MetadataValue::try_from(&self.value).unwrap(), + ); + Ok(()) + } + + fn minimum_channel_security_level(&self) -> SecurityLevel { + self.security_level + } + } + + #[tokio::test] + async fn test_composite_call_credentials() { + let cred1 = MockCallCredentials { + key: "key1".to_string(), + value: "value1".to_string(), + security_level: SecurityLevel::IntegrityOnly, + }; + let cred2 = MockCallCredentials { + key: "key2".to_string(), + value: "value2".to_string(), + security_level: SecurityLevel::PrivacyAndIntegrity, + }; + + let composite = CompositeCallCredentials::new(cred1, cred2); + + let call_details = CallDetails { + service_url: "url".to_string(), + method_name: "method".to_string(), + }; + let auth_info = + ChannelSecurityInfo::new("test", SecurityLevel::PrivacyAndIntegrity, Attributes); + let mut metadata = MetadataMap::new(); + + composite + .get_metadata(&call_details, &auth_info, &mut metadata) + .await + .unwrap(); + + assert_eq!(metadata.get("key1").unwrap(), "value1"); + assert_eq!(metadata.get("key2").unwrap(), "value2"); + assert_eq!( + composite.minimum_channel_security_level(), + SecurityLevel::PrivacyAndIntegrity + ); + } +} diff --git a/grpc/src/credentials/client.rs b/grpc/src/credentials/client.rs index aa7977df3..8fc21e35c 100644 --- a/grpc/src/credentials/client.rs +++ b/grpc/src/credentials/client.rs @@ -23,8 +23,13 @@ */ use crate::attributes::Attributes; +use crate::credentials::call::CallCredentials; +use crate::credentials::call::CompositeCallCredentials; use crate::credentials::common::Authority; use crate::credentials::common::SecurityLevel; +use crate::credentials::insecure; +use crate::credentials::ChannelCredentials; +use crate::credentials::ProtocolInfo; use crate::rt::GrpcEndpoint; use crate::rt::GrpcRuntime; @@ -53,6 +58,9 @@ pub trait ChannelCredsInternal { info: ClientHandshakeInfo, runtime: GrpcRuntime, ) -> Result, Self::ContextType>, String>; + + /// Returns call credentials to be used for all RPCs made on a connection. + fn get_call_credentials(&self) -> Option<&CallCredentials>; } pub struct HandshakeOutput { @@ -160,3 +168,59 @@ impl ClientHandshakeInfo { &self.attributes } } + +/// A credential that combines [`ChannelCredentials`] with [`CallCredentials`]. +/// +/// This is used to attach per-call authentication (like OAuth2 tokens) to a +/// secure channel (like TLS). +pub struct CompositeChannelCredentials { + channel_creds: T, + call_creds: CallCredentials, +} + +impl CompositeChannelCredentials { + pub fn new(channel_creds: T, call_creds: CallCredentials) -> Result { + if channel_creds.info().security_protocol() == insecure::PROTOCOL_NAME { + return Err("using tokens on an insecure credentials is disallowed".to_string()); + } + + let combined_call_creds = if let Some(existing) = channel_creds.get_call_credentials() { + let composite_creds = CompositeCallCredentials::new(existing.clone(), call_creds); + composite_creds.into() + } else { + call_creds + }; + + Ok(Self { + channel_creds, + call_creds: combined_call_creds, + }) + } +} + +impl ChannelCredsInternal for CompositeChannelCredentials { + type ContextType = T::ContextType; + type Output = T::Output; + + async fn connect( + &self, + authority: &Authority, + source: Input, + info: ClientHandshakeInfo, + runtime: GrpcRuntime, + ) -> Result, Self::ContextType>, String> { + self.channel_creds + .connect(authority, source, info, runtime) + .await + } + + fn get_call_credentials(&self) -> Option<&CallCredentials> { + Some(&self.call_creds) + } +} + +impl ChannelCredentials for CompositeChannelCredentials { + fn info(&self) -> &ProtocolInfo { + self.channel_creds.info() + } +} diff --git a/grpc/src/credentials/insecure.rs b/grpc/src/credentials/insecure.rs index 3fa452714..b05664c1f 100644 --- a/grpc/src/credentials/insecure.rs +++ b/grpc/src/credentials/insecure.rs @@ -23,6 +23,7 @@ */ use crate::attributes::Attributes; +use crate::credentials::call::CallCredentials; use crate::credentials::client::ClientConnectionSecurityContext; use crate::credentials::client::ClientConnectionSecurityInfo; use crate::credentials::client::ClientHandshakeInfo; @@ -47,6 +48,8 @@ pub struct InsecureChannelCredentials { _private: (), } +pub const PROTOCOL_NAME: &str = "insecure"; + impl InsecureChannelCredentials { /// Creates a new instance of `InsecureChannelCredentials`. pub fn new() -> Self { @@ -78,18 +81,22 @@ impl client::ChannelCredsInternal for InsecureChannelCredentials { Ok(HandshakeOutput { endpoint: source, security: ClientConnectionSecurityInfo::new( - "insecure", + PROTOCOL_NAME, SecurityLevel::NoSecurity, InsecureConnectionSecurityContext, Attributes, ), }) } + + fn get_call_credentials(&self) -> Option<&CallCredentials> { + None + } } impl ChannelCredentials for InsecureChannelCredentials { fn info(&self) -> &ProtocolInfo { - static INFO: ProtocolInfo = ProtocolInfo::new("insecure"); + static INFO: ProtocolInfo = ProtocolInfo::new(PROTOCOL_NAME); &INFO } } @@ -117,7 +124,7 @@ impl server::ServerCredsInternal for InsecureServerCredentials { Ok(server::HandshakeOutput { endpoint: source, security: ServerConnectionSecurityInfo::new( - "insecure", + PROTOCOL_NAME, SecurityLevel::NoSecurity, Attributes, ), @@ -127,7 +134,7 @@ impl server::ServerCredsInternal for InsecureServerCredentials { impl ServerCredentials for InsecureServerCredentials { fn info(&self) -> &ProtocolInfo { - static INFO: ProtocolInfo = ProtocolInfo::new("insecure"); + static INFO: ProtocolInfo = ProtocolInfo::new(PROTOCOL_NAME); &INFO } } @@ -139,6 +146,7 @@ mod test { use tokio::net::TcpListener; use tokio::net::TcpStream; + use super::*; use crate::credentials::client::ChannelCredsInternal as ClientSealed; use crate::credentials::client::ClientConnectionSecurityContext; use crate::credentials::client::ClientHandshakeInfo; @@ -146,8 +154,6 @@ mod test { use crate::credentials::common::SecurityLevel; use crate::credentials::server::ServerCredsInternal; use crate::credentials::ChannelCredentials; - use crate::credentials::InsecureChannelCredentials; - use crate::credentials::InsecureServerCredentials; use crate::credentials::ServerCredentials; use crate::rt::GrpcEndpoint; use crate::rt::TcpOptions; @@ -158,7 +164,7 @@ mod test { let creds = InsecureChannelCredentials::new(); let info = creds.info(); - assert_eq!(info.security_protocol(), "insecure"); + assert_eq!(info.security_protocol(), PROTOCOL_NAME); let addr = "127.0.0.1:0"; let listener = TcpListener::bind(addr).await.unwrap(); @@ -181,7 +187,7 @@ mod test { let security_info = output.security; // Verify security info. - assert_eq!(security_info.security_protocol(), "insecure"); + assert_eq!(security_info.security_protocol(), PROTOCOL_NAME); assert_eq!(security_info.security_level(), SecurityLevel::NoSecurity); // Verify data transfer. @@ -208,7 +214,7 @@ mod test { let creds = InsecureServerCredentials::new(); let info = creds.info(); - assert_eq!(info.security_protocol, "insecure"); + assert_eq!(info.security_protocol, PROTOCOL_NAME); let addr = "127.0.0.1:0"; let runtime = rt::default_runtime(); @@ -234,7 +240,7 @@ mod test { let mut endpoint = output.endpoint; let security_info = output.security; - assert_eq!(security_info.security_protocol(), "insecure"); + assert_eq!(security_info.security_protocol(), PROTOCOL_NAME); assert_eq!(security_info.security_level(), SecurityLevel::NoSecurity); let mut buf = vec![0u8; 10]; diff --git a/grpc/src/credentials/mod.rs b/grpc/src/credentials/mod.rs index cfa533393..951ac1145 100644 --- a/grpc/src/credentials/mod.rs +++ b/grpc/src/credentials/mod.rs @@ -22,11 +22,13 @@ * */ +mod call; pub(crate) mod client; pub(crate) mod dyn_wrapper; mod insecure; pub(crate) mod server; +pub use client::CompositeChannelCredentials; pub use insecure::InsecureChannelCredentials; pub use insecure::InsecureServerCredentials; From 91faec452f351010fa628102041e283a4981050c Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Sun, 1 Mar 2026 23:52:32 +0530 Subject: [PATCH 2/9] tests --- grpc/src/credentials/call.rs | 11 ++-- grpc/src/credentials/client.rs | 104 +++++++++++++++++++++++++++++++++ grpc/src/credentials/local.rs | 5 ++ 3 files changed, 116 insertions(+), 4 deletions(-) diff --git a/grpc/src/credentials/call.rs b/grpc/src/credentials/call.rs index 860a659d8..514385cbe 100644 --- a/grpc/src/credentials/call.rs +++ b/grpc/src/credentials/call.rs @@ -25,9 +25,9 @@ use std::fmt::Debug; use std::sync::Arc; +use tonic::Status; use tonic::async_trait; use tonic::metadata::MetadataMap; -use tonic::Status; use crate::attributes::Attributes; use crate::credentials::common::SecurityLevel; @@ -69,7 +69,7 @@ pub struct ChannelSecurityInfo { } impl ChannelSecurityInfo { - pub fn new( + pub(crate) fn new( security_protocol: &'static str, security_level: SecurityLevel, attributes: Attributes, @@ -259,8 +259,11 @@ mod tests { service_url: "url".to_string(), method_name: "method".to_string(), }; - let auth_info = - ChannelSecurityInfo::new("test", SecurityLevel::PrivacyAndIntegrity, Attributes); + let auth_info = ChannelSecurityInfo::new( + "test", + SecurityLevel::PrivacyAndIntegrity, + Attributes::new(), + ); let mut metadata = MetadataMap::new(); composite diff --git a/grpc/src/credentials/client.rs b/grpc/src/credentials/client.rs index 8fc21e35c..b6b4a4295 100644 --- a/grpc/src/credentials/client.rs +++ b/grpc/src/credentials/client.rs @@ -224,3 +224,107 @@ impl ChannelCredentials for CompositeChannelCredentials Result<(), Status> { + metadata.insert( + self.key.parse::>().unwrap(), + MetadataValue::try_from(self.value).unwrap(), + ); + Ok(()) + } + + fn minimum_channel_security_level(&self) -> SecurityLevel { + self.min_security_level + } + } + + #[tokio::test] + async fn test_multiple_composition() { + let channel_creds = LocalChannelCredentials::new(); + let call_creds1 = CallCredentials::from(MockCallCredentials { + key: "auth1", + value: "val1", + min_security_level: SecurityLevel::IntegrityOnly, + }); + let call_creds2 = CallCredentials::from(MockCallCredentials { + key: "auth2", + value: "val2", + min_security_level: SecurityLevel::PrivacyAndIntegrity, + }); + + // First composition. + let composite1 = CompositeChannelCredentials::new(channel_creds, call_creds1).unwrap(); + + // Second composition (using the first composite as base). + let composite2 = CompositeChannelCredentials::new(composite1, call_creds2).unwrap(); + + // Verify call credentials + let combined_call_creds = composite2.get_call_credentials().unwrap(); + let call_details = CallDetails::new("service".to_string(), "method".to_string()); + let auth_info = ChannelSecurityInfo::new("local", SecurityLevel::NoSecurity, Attributes::new()); + let mut metadata = MetadataMap::new(); + + combined_call_creds.get_metadata(&call_details, &auth_info, &mut metadata).await.unwrap(); + + assert_eq!(metadata.get("auth1").unwrap(), "val1"); + assert_eq!(metadata.get("auth2").unwrap(), "val2"); + + // Verify min security level is the max of both. + assert_eq!( + combined_call_creds.minimum_channel_security_level(), + SecurityLevel::PrivacyAndIntegrity + ); + + // Verify security level + let addr = "127.0.0.1:0"; + let listener = TcpListener::bind(addr).await.unwrap(); + let server_addr = listener.local_addr().unwrap(); + let authority = Authority::new("localhost".to_string(), Some(server_addr.port())); + let runtime = rt::default_runtime(); + let endpoint = runtime.tcp_stream(server_addr, TcpOptions::default()).await.unwrap(); + + let output = composite2.connect(&authority, endpoint, ClientHandshakeInfo::default(), runtime).await.unwrap(); + assert_eq!(output.security.security_level(), SecurityLevel::NoSecurity); + assert_eq!(output.security.security_protocol(), "local"); + } + + #[test] + fn test_composite_channel_credentials_insecure() { + let channel_creds = InsecureChannelCredentials::new(); + let call_creds = CallCredentials::from(MockCallCredentials { + key: "auth", + value: "val", + min_security_level: SecurityLevel::NoSecurity, + }); + let result = CompositeChannelCredentials::new(channel_creds, call_creds); + assert!(result.is_err()); + } +} diff --git a/grpc/src/credentials/local.rs b/grpc/src/credentials/local.rs index 13d444ff9..b403c71ee 100644 --- a/grpc/src/credentials/local.rs +++ b/grpc/src/credentials/local.rs @@ -30,6 +30,7 @@ use crate::client::name_resolution::TCP_IP_NETWORK_TYPE; use crate::credentials::ChannelCredentials; use crate::credentials::ProtocolInfo; use crate::credentials::ServerCredentials; +use crate::credentials::call::CallCredentials; use crate::credentials::client; use crate::credentials::client::ClientConnectionSecurityContext; use crate::credentials::client::ClientConnectionSecurityInfo; @@ -117,6 +118,10 @@ impl client::ChannelCredsInternal for LocalChannelCredentials { ), }) } + + fn get_call_credentials(&self) -> Option<&CallCredentials> { + None + } } impl ChannelCredentials for LocalChannelCredentials { From b0ca952406d32cfd5e7cce6f2a7f83ae2ce82566 Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Mon, 2 Mar 2026 10:03:45 +0530 Subject: [PATCH 3/9] post-merge fixes --- grpc/src/credentials/call.rs | 3 +- grpc/src/credentials/client.rs | 39 ++++++++++++++++------- grpc/src/credentials/rustls/client/mod.rs | 5 +++ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/grpc/src/credentials/call.rs b/grpc/src/credentials/call.rs index 514385cbe..b97a6b02a 100644 --- a/grpc/src/credentials/call.rs +++ b/grpc/src/credentials/call.rs @@ -38,8 +38,6 @@ use crate::credentials::common::SecurityLevel; /// `service_url` + "/" + `method_name` pub struct CallDetails { service_url: String, - - /// The method name suffix (e.g., `Method` or `package.Service/Method`). method_name: String, } @@ -51,6 +49,7 @@ impl CallDetails { } } + /// Returns the base URL of the service for this RPC call. pub fn service_url(&self) -> &str { &self.service_url } diff --git a/grpc/src/credentials/client.rs b/grpc/src/credentials/client.rs index b6b4a4295..b7369ad12 100644 --- a/grpc/src/credentials/client.rs +++ b/grpc/src/credentials/client.rs @@ -23,13 +23,13 @@ */ use crate::attributes::Attributes; +use crate::credentials::ChannelCredentials; +use crate::credentials::ProtocolInfo; use crate::credentials::call::CallCredentials; use crate::credentials::call::CompositeCallCredentials; use crate::credentials::common::Authority; use crate::credentials::common::SecurityLevel; use crate::credentials::insecure; -use crate::credentials::ChannelCredentials; -use crate::credentials::ProtocolInfo; use crate::rt::GrpcEndpoint; use crate::rt::GrpcRuntime; @@ -233,11 +233,11 @@ mod tests { use crate::credentials::local::LocalChannelCredentials; use crate::rt; use crate::rt::TcpOptions; + use tokio::net::TcpListener; + use tonic::Status; + use tonic::async_trait; use tonic::metadata::MetadataMap; use tonic::metadata::MetadataValue; - use tonic::async_trait; - use tonic::Status; - use tokio::net::TcpListener; #[derive(Debug)] struct MockCallCredentials { @@ -255,7 +255,9 @@ mod tests { metadata: &mut MetadataMap, ) -> Result<(), Status> { metadata.insert( - self.key.parse::>().unwrap(), + self.key + .parse::>() + .unwrap(), MetadataValue::try_from(self.value).unwrap(), ); Ok(()) @@ -289,10 +291,14 @@ mod tests { // Verify call credentials let combined_call_creds = composite2.get_call_credentials().unwrap(); let call_details = CallDetails::new("service".to_string(), "method".to_string()); - let auth_info = ChannelSecurityInfo::new("local", SecurityLevel::NoSecurity, Attributes::new()); + let auth_info = + ChannelSecurityInfo::new("local", SecurityLevel::NoSecurity, Attributes::new()); let mut metadata = MetadataMap::new(); - combined_call_creds.get_metadata(&call_details, &auth_info, &mut metadata).await.unwrap(); + combined_call_creds + .get_metadata(&call_details, &auth_info, &mut metadata) + .await + .unwrap(); assert_eq!(metadata.get("auth1").unwrap(), "val1"); assert_eq!(metadata.get("auth2").unwrap(), "val2"); @@ -309,9 +315,20 @@ mod tests { let server_addr = listener.local_addr().unwrap(); let authority = Authority::new("localhost".to_string(), Some(server_addr.port())); let runtime = rt::default_runtime(); - let endpoint = runtime.tcp_stream(server_addr, TcpOptions::default()).await.unwrap(); - - let output = composite2.connect(&authority, endpoint, ClientHandshakeInfo::default(), runtime).await.unwrap(); + let endpoint = runtime + .tcp_stream(server_addr, TcpOptions::default()) + .await + .unwrap(); + + let output = composite2 + .connect( + &authority, + endpoint, + ClientHandshakeInfo::default(), + runtime, + ) + .await + .unwrap(); assert_eq!(output.security.security_level(), SecurityLevel::NoSecurity); assert_eq!(output.security.security_protocol(), "local"); } diff --git a/grpc/src/credentials/rustls/client/mod.rs b/grpc/src/credentials/rustls/client/mod.rs index 659918b8c..5b7b8b7ed 100644 --- a/grpc/src/credentials/rustls/client/mod.rs +++ b/grpc/src/credentials/rustls/client/mod.rs @@ -36,6 +36,7 @@ use tokio_rustls::TlsStream as RustlsStream; use crate::attributes::Attributes; use crate::credentials::ChannelCredentials; use crate::credentials::ProtocolInfo; +use crate::credentials::call::CallCredentials; use crate::credentials::client; use crate::credentials::client::ClientConnectionSecurityContext; use crate::credentials::client::ClientConnectionSecurityInfo; @@ -261,6 +262,10 @@ impl client::ChannelCredsInternal for RustlsClientTlsCredendials { security: cs_info, }) } + + fn get_call_credentials(&self) -> Option<&CallCredentials> { + None + } } impl ChannelCredentials for RustlsClientTlsCredendials { From 7704181879549777ee197a626fbb5006820b1e60 Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Mon, 2 Mar 2026 12:06:14 +0530 Subject: [PATCH 4/9] fix docs --- grpc/src/credentials/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grpc/src/credentials/mod.rs b/grpc/src/credentials/mod.rs index e3b90c909..9a51a8137 100644 --- a/grpc/src/credentials/mod.rs +++ b/grpc/src/credentials/mod.rs @@ -22,7 +22,7 @@ * */ -mod call; +pub mod call; pub(crate) mod client; pub(crate) mod dyn_wrapper; mod insecure; From 164bdb93a8c26851343555904c419c531d663cbc Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Wed, 4 Mar 2026 19:14:21 +0530 Subject: [PATCH 5/9] remove wrapper type, expose Arc --- grpc/src/credentials/call.rs | 59 +++++------------------ grpc/src/credentials/client.rs | 37 ++++++++------ grpc/src/credentials/insecure.rs | 4 +- grpc/src/credentials/local.rs | 3 +- grpc/src/credentials/rustls/client/mod.rs | 2 +- 5 files changed, 39 insertions(+), 66 deletions(-) diff --git a/grpc/src/credentials/call.rs b/grpc/src/credentials/call.rs index b97a6b02a..d9c89304a 100644 --- a/grpc/src/credentials/call.rs +++ b/grpc/src/credentials/call.rs @@ -93,45 +93,10 @@ impl ChannelSecurityInfo { } } -/// A thread-safe handle for credentials that are attached to individual RPC -/// calls. -/// -/// `CallCredentials` wrap a [`CallCredentialsProvider`]. They can be combined -/// using [`CompositeCallCredentials`]. -#[derive(Debug, Clone)] -pub struct CallCredentials { - inner: Arc, -} - -impl CallCredentials { - pub(crate) async fn get_metadata( - &self, - call_details: &CallDetails, - auth_info: &ChannelSecurityInfo, - metadata: &mut MetadataMap, - ) -> Result<(), Status> { - self.inner - .get_metadata(call_details, auth_info, metadata) - .await - } - - pub(crate) fn minimum_channel_security_level(&self) -> SecurityLevel { - self.inner.minimum_channel_security_level() - } -} - -impl From for CallCredentials { - fn from(provider: T) -> Self { - Self { - inner: Arc::new(provider), - } - } -} - /// Defines the interface for credentials that need to attach security /// information to every individual RPC (e.g., OAuth2 tokens, JWTs). #[async_trait] -pub trait CallCredentialsProvider: Send + Sync + Debug { +pub trait CallCredentials: Send + Sync + Debug { /// Generates the authentication metadata for a specific RPC call. /// /// This method is called by the transport layer on each request. @@ -159,26 +124,26 @@ pub trait CallCredentialsProvider: Send + Sync + Debug { /// multiple credentials. #[derive(Debug)] pub struct CompositeCallCredentials { - creds: Vec, + creds: Vec>, } impl CompositeCallCredentials { /// Creates a new [`CompositeCallCredentials`] with the first two credentials. - pub fn new(first: impl Into, second: impl Into) -> Self { + pub fn new(first: Arc, second: Arc) -> Self { Self { - creds: vec![first.into(), second.into()], + creds: vec![first, second], } } /// Adds an additional [`CallCredentials`] to the composite. - pub fn with_call_credentials(mut self, creds: impl Into) -> Self { - self.creds.push(creds.into()); + pub fn with_call_credentials(mut self, creds: Arc) -> Self { + self.creds.push(creds); self } } #[async_trait] -impl CallCredentialsProvider for CompositeCallCredentials { +impl CallCredentials for CompositeCallCredentials { async fn get_metadata( &self, call_details: &CallDetails, @@ -218,7 +183,7 @@ mod tests { } #[async_trait] - impl CallCredentialsProvider for MockCallCredentials { + impl CallCredentials for MockCallCredentials { async fn get_metadata( &self, _call_details: &CallDetails, @@ -241,16 +206,16 @@ mod tests { #[tokio::test] async fn test_composite_call_credentials() { - let cred1 = MockCallCredentials { + let cred1 = Arc::new(MockCallCredentials { key: "key1".to_string(), value: "value1".to_string(), security_level: SecurityLevel::IntegrityOnly, - }; - let cred2 = MockCallCredentials { + }); + let cred2 = Arc::new(MockCallCredentials { key: "key2".to_string(), value: "value2".to_string(), security_level: SecurityLevel::PrivacyAndIntegrity, - }; + }); let composite = CompositeCallCredentials::new(cred1, cred2); diff --git a/grpc/src/credentials/client.rs b/grpc/src/credentials/client.rs index b7369ad12..289fc0391 100644 --- a/grpc/src/credentials/client.rs +++ b/grpc/src/credentials/client.rs @@ -22,6 +22,8 @@ * */ +use std::sync::Arc; + use crate::attributes::Attributes; use crate::credentials::ChannelCredentials; use crate::credentials::ProtocolInfo; @@ -60,7 +62,7 @@ pub trait ChannelCredsInternal { ) -> Result, Self::ContextType>, String>; /// Returns call credentials to be used for all RPCs made on a connection. - fn get_call_credentials(&self) -> Option<&CallCredentials>; + fn get_call_credentials(&self) -> Option>; } pub struct HandshakeOutput { @@ -175,18 +177,18 @@ impl ClientHandshakeInfo { /// secure channel (like TLS). pub struct CompositeChannelCredentials { channel_creds: T, - call_creds: CallCredentials, + call_creds: Arc, } impl CompositeChannelCredentials { - pub fn new(channel_creds: T, call_creds: CallCredentials) -> Result { + pub fn new(channel_creds: T, call_creds: Arc) -> Result { if channel_creds.info().security_protocol() == insecure::PROTOCOL_NAME { return Err("using tokens on an insecure credentials is disallowed".to_string()); } let combined_call_creds = if let Some(existing) = channel_creds.get_call_credentials() { let composite_creds = CompositeCallCredentials::new(existing.clone(), call_creds); - composite_creds.into() + Arc::new(composite_creds) } else { call_creds }; @@ -214,8 +216,8 @@ impl ChannelCredsInternal for CompositeChannelCredentials .await } - fn get_call_credentials(&self) -> Option<&CallCredentials> { - Some(&self.call_creds) + fn get_call_credentials(&self) -> Option> { + Some(self.call_creds.clone()) } } @@ -227,18 +229,21 @@ impl ChannelCredentials for CompositeChannelCredentials Option<&CallCredentials> { + fn get_call_credentials(&self) -> Option> { None } } diff --git a/grpc/src/credentials/local.rs b/grpc/src/credentials/local.rs index b403c71ee..6baa4cf12 100644 --- a/grpc/src/credentials/local.rs +++ b/grpc/src/credentials/local.rs @@ -24,6 +24,7 @@ use std::net::SocketAddr; use std::str::FromStr; +use std::sync::Arc; use crate::attributes::Attributes; use crate::client::name_resolution::TCP_IP_NETWORK_TYPE; @@ -119,7 +120,7 @@ impl client::ChannelCredsInternal for LocalChannelCredentials { }) } - fn get_call_credentials(&self) -> Option<&CallCredentials> { + fn get_call_credentials(&self) -> Option> { None } } diff --git a/grpc/src/credentials/rustls/client/mod.rs b/grpc/src/credentials/rustls/client/mod.rs index 5b7b8b7ed..47b1c61b4 100644 --- a/grpc/src/credentials/rustls/client/mod.rs +++ b/grpc/src/credentials/rustls/client/mod.rs @@ -263,7 +263,7 @@ impl client::ChannelCredsInternal for RustlsClientTlsCredendials { }) } - fn get_call_credentials(&self) -> Option<&CallCredentials> { + fn get_call_credentials(&self) -> Option> { None } } From 1607845c8f9c4cdc2bca014205121ee1c8947a4d Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Wed, 4 Mar 2026 20:35:46 +0530 Subject: [PATCH 6/9] address review --- grpc/src/credentials/call.rs | 23 +++++++++++++---------- grpc/src/credentials/mod.rs | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/grpc/src/credentials/call.rs b/grpc/src/credentials/call.rs index d9c89304a..682aee7ef 100644 --- a/grpc/src/credentials/call.rs +++ b/grpc/src/credentials/call.rs @@ -32,7 +32,7 @@ use tonic::metadata::MetadataMap; use crate::attributes::Attributes; use crate::credentials::common::SecurityLevel; -/// Details regarding the RPC call. +/// Details regarding the call. /// /// The fully qualified method name is constructed as: /// `service_url` + "/" + `method_name` @@ -49,12 +49,12 @@ impl CallDetails { } } - /// Returns the base URL of the service for this RPC call. + /// Returns the base URL of the service for this call. pub fn service_url(&self) -> &str { &self.service_url } - /// The method name suffix (e.g., `Method` or `package.Service/Method`). + /// The method name suffix (e.g., `Method` in `package.Service/Method`). pub fn method_name(&self) -> &str { &self.method_name } @@ -97,7 +97,7 @@ impl ChannelSecurityInfo { /// information to every individual RPC (e.g., OAuth2 tokens, JWTs). #[async_trait] pub trait CallCredentials: Send + Sync + Debug { - /// Generates the authentication metadata for a specific RPC call. + /// Generates the authentication metadata for a specific call. /// /// This method is called by the transport layer on each request. /// Implementations should populate the provided `metadata` map with the @@ -106,6 +106,11 @@ pub trait CallCredentials: Send + Sync + Debug { /// If this returns an `Err`, the RPC will fail immediately with a status /// derived from the error if the status code is in the range defined in /// gRFC A54. Otherwise, the RPC is failed with an internal status. + /// + /// # Cancellation Safety + /// + /// Implementations of this method must be cancel safe as the future may be + /// dropped due to RPC timeouts. async fn get_metadata( &self, call_details: &CallDetails, @@ -122,6 +127,8 @@ pub trait CallCredentials: Send + Sync + Debug { /// A composite implementation of [`CallCredentialsProvider`] that combines /// multiple credentials. +/// +/// The inner credentials are invoked sequentially during metadata retrieval. #[derive(Debug)] pub struct CompositeCallCredentials { creds: Vec>, @@ -160,12 +167,8 @@ impl CallCredentials for CompositeCallCredentials { self.creds .iter() .map(|c| c.minimum_channel_security_level()) - .max_by_key(|&l| match l { - SecurityLevel::NoSecurity => 0, - SecurityLevel::IntegrityOnly => 1, - SecurityLevel::PrivacyAndIntegrity => 2, - }) - .unwrap_or(SecurityLevel::NoSecurity) + .max() + .expect("CompositeCallCredentials must hold at least two children.") } } diff --git a/grpc/src/credentials/mod.rs b/grpc/src/credentials/mod.rs index 9a51a8137..acfe22e2c 100644 --- a/grpc/src/credentials/mod.rs +++ b/grpc/src/credentials/mod.rs @@ -51,7 +51,7 @@ pub trait ServerCredentials: server::ServerCredsInternal + Sync + 'static { pub(crate) mod common { /// Defines the level of protection provided by an established connection. - #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[non_exhaustive] pub enum SecurityLevel { /// The connection is insecure; no protection is applied. From 2f90e0380e3f4c5f5e6b624e9d793ce93ef4a2dc Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Wed, 4 Mar 2026 20:42:20 +0530 Subject: [PATCH 7/9] fix docs --- grpc/src/credentials/call.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grpc/src/credentials/call.rs b/grpc/src/credentials/call.rs index 682aee7ef..171cf32eb 100644 --- a/grpc/src/credentials/call.rs +++ b/grpc/src/credentials/call.rs @@ -125,7 +125,7 @@ pub trait CallCredentials: Send + Sync + Debug { } } -/// A composite implementation of [`CallCredentialsProvider`] that combines +/// A composite implementation of [`CallCredentials`] that combines /// multiple credentials. /// /// The inner credentials are invoked sequentially during metadata retrieval. From 7f707d55ad2aea8df438b5e99c229345e012679d Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Fri, 6 Mar 2026 13:33:29 +0530 Subject: [PATCH 8/9] return arc ref and document default level --- grpc/src/credentials/call.rs | 1 + grpc/src/credentials/client.rs | 6 +++--- grpc/src/credentials/insecure.rs | 2 +- grpc/src/credentials/local.rs | 2 +- grpc/src/credentials/rustls/client/mod.rs | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/grpc/src/credentials/call.rs b/grpc/src/credentials/call.rs index 171cf32eb..1a1c48499 100644 --- a/grpc/src/credentials/call.rs +++ b/grpc/src/credentials/call.rs @@ -120,6 +120,7 @@ pub trait CallCredentials: Send + Sync + Debug { /// Indicates the minimum transport security level required to send /// these credentials. + /// **Default:** Returns [`SecurityLevel::PrivacyAndIntegrity`]. fn minimum_channel_security_level(&self) -> SecurityLevel { SecurityLevel::PrivacyAndIntegrity } diff --git a/grpc/src/credentials/client.rs b/grpc/src/credentials/client.rs index 289fc0391..0755fa41e 100644 --- a/grpc/src/credentials/client.rs +++ b/grpc/src/credentials/client.rs @@ -62,7 +62,7 @@ pub trait ChannelCredsInternal { ) -> Result, Self::ContextType>, String>; /// Returns call credentials to be used for all RPCs made on a connection. - fn get_call_credentials(&self) -> Option>; + fn get_call_credentials(&self) -> Option<&Arc>; } pub struct HandshakeOutput { @@ -216,8 +216,8 @@ impl ChannelCredsInternal for CompositeChannelCredentials .await } - fn get_call_credentials(&self) -> Option> { - Some(self.call_creds.clone()) + fn get_call_credentials(&self) -> Option<&Arc> { + Some(&self.call_creds) } } diff --git a/grpc/src/credentials/insecure.rs b/grpc/src/credentials/insecure.rs index 3a38e74af..250bdafb2 100644 --- a/grpc/src/credentials/insecure.rs +++ b/grpc/src/credentials/insecure.rs @@ -91,7 +91,7 @@ impl client::ChannelCredsInternal for InsecureChannelCredentials { }) } - fn get_call_credentials(&self) -> Option> { + fn get_call_credentials(&self) -> Option<&Arc> { None } } diff --git a/grpc/src/credentials/local.rs b/grpc/src/credentials/local.rs index 6baa4cf12..1a7bcf936 100644 --- a/grpc/src/credentials/local.rs +++ b/grpc/src/credentials/local.rs @@ -120,7 +120,7 @@ impl client::ChannelCredsInternal for LocalChannelCredentials { }) } - fn get_call_credentials(&self) -> Option> { + fn get_call_credentials(&self) -> Option<&Arc> { None } } diff --git a/grpc/src/credentials/rustls/client/mod.rs b/grpc/src/credentials/rustls/client/mod.rs index 47b1c61b4..752a5f8e1 100644 --- a/grpc/src/credentials/rustls/client/mod.rs +++ b/grpc/src/credentials/rustls/client/mod.rs @@ -263,7 +263,7 @@ impl client::ChannelCredsInternal for RustlsClientTlsCredendials { }) } - fn get_call_credentials(&self) -> Option> { + fn get_call_credentials(&self) -> Option<&Arc> { None } } From 390ce3e35a412ce950c8c852a489b613cb6b0740 Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Fri, 6 Mar 2026 13:41:36 +0530 Subject: [PATCH 9/9] export security level --- grpc/src/credentials/call.rs | 2 +- grpc/src/credentials/client.rs | 2 +- grpc/src/credentials/dyn_wrapper.rs | 9 +++--- grpc/src/credentials/insecure.rs | 4 +-- grpc/src/credentials/local.rs | 4 +-- grpc/src/credentials/mod.rs | 38 +++++++++++------------ grpc/src/credentials/rustls/client/mod.rs | 2 +- grpc/src/credentials/rustls/server/mod.rs | 2 +- grpc/src/credentials/server.rs | 2 +- 9 files changed, 33 insertions(+), 32 deletions(-) diff --git a/grpc/src/credentials/call.rs b/grpc/src/credentials/call.rs index 1a1c48499..77c8ad249 100644 --- a/grpc/src/credentials/call.rs +++ b/grpc/src/credentials/call.rs @@ -30,7 +30,7 @@ use tonic::async_trait; use tonic::metadata::MetadataMap; use crate::attributes::Attributes; -use crate::credentials::common::SecurityLevel; +use crate::credentials::SecurityLevel; /// Details regarding the call. /// diff --git a/grpc/src/credentials/client.rs b/grpc/src/credentials/client.rs index 0755fa41e..0c7cf69f5 100644 --- a/grpc/src/credentials/client.rs +++ b/grpc/src/credentials/client.rs @@ -27,10 +27,10 @@ use std::sync::Arc; use crate::attributes::Attributes; use crate::credentials::ChannelCredentials; use crate::credentials::ProtocolInfo; +use crate::credentials::SecurityLevel; use crate::credentials::call::CallCredentials; use crate::credentials::call::CompositeCallCredentials; use crate::credentials::common::Authority; -use crate::credentials::common::SecurityLevel; use crate::credentials::insecure; use crate::rt::GrpcEndpoint; use crate::rt::GrpcRuntime; diff --git a/grpc/src/credentials/dyn_wrapper.rs b/grpc/src/credentials/dyn_wrapper.rs index eceb43b05..3b1f62ac7 100644 --- a/grpc/src/credentials/dyn_wrapper.rs +++ b/grpc/src/credentials/dyn_wrapper.rs @@ -122,17 +122,18 @@ where #[cfg(test)] mod tests { + use tokio::io::AsyncReadExt; + use tokio::io::AsyncWriteExt; + use tokio::net::TcpListener; + use super::*; use crate::credentials::InsecureServerCredentials; + use crate::credentials::SecurityLevel; use crate::credentials::client::ClientHandshakeInfo; use crate::credentials::common::Authority; - use crate::credentials::common::SecurityLevel; use crate::credentials::insecure::InsecureChannelCredentials; use crate::rt::TcpOptions; use crate::rt::{self}; - use tokio::io::AsyncReadExt; - use tokio::io::AsyncWriteExt; - use tokio::net::TcpListener; #[tokio::test] async fn test_dyn_client_credential_dispatch() { diff --git a/grpc/src/credentials/insecure.rs b/grpc/src/credentials/insecure.rs index 250bdafb2..75224ea23 100644 --- a/grpc/src/credentials/insecure.rs +++ b/grpc/src/credentials/insecure.rs @@ -27,6 +27,7 @@ use std::sync::Arc; use crate::attributes::Attributes; use crate::credentials::ChannelCredentials; use crate::credentials::ProtocolInfo; +use crate::credentials::SecurityLevel; use crate::credentials::ServerCredentials; use crate::credentials::call::CallCredentials; use crate::credentials::client::ClientConnectionSecurityContext; @@ -35,7 +36,6 @@ use crate::credentials::client::ClientHandshakeInfo; use crate::credentials::client::HandshakeOutput; use crate::credentials::client::{self}; use crate::credentials::common::Authority; -use crate::credentials::common::SecurityLevel; use crate::credentials::server::ServerConnectionSecurityInfo; use crate::credentials::server::{self}; use crate::rt::GrpcEndpoint; @@ -152,12 +152,12 @@ mod test { use crate::credentials::ChannelCredentials; use crate::credentials::InsecureChannelCredentials; use crate::credentials::InsecureServerCredentials; + use crate::credentials::SecurityLevel; use crate::credentials::ServerCredentials; use crate::credentials::client::ChannelCredsInternal as ClientSealed; use crate::credentials::client::ClientConnectionSecurityContext; use crate::credentials::client::ClientHandshakeInfo; use crate::credentials::common::Authority; - use crate::credentials::common::SecurityLevel; use crate::credentials::server::ServerCredsInternal; use crate::rt::GrpcEndpoint; use crate::rt::TcpOptions; diff --git a/grpc/src/credentials/local.rs b/grpc/src/credentials/local.rs index 1a7bcf936..f8ae2342c 100644 --- a/grpc/src/credentials/local.rs +++ b/grpc/src/credentials/local.rs @@ -30,6 +30,7 @@ use crate::attributes::Attributes; use crate::client::name_resolution::TCP_IP_NETWORK_TYPE; use crate::credentials::ChannelCredentials; use crate::credentials::ProtocolInfo; +use crate::credentials::SecurityLevel; use crate::credentials::ServerCredentials; use crate::credentials::call::CallCredentials; use crate::credentials::client; @@ -38,7 +39,6 @@ use crate::credentials::client::ClientConnectionSecurityInfo; use crate::credentials::client::ClientHandshakeInfo; use crate::credentials::client::HandshakeOutput; use crate::credentials::common::Authority; -use crate::credentials::common::SecurityLevel; use crate::credentials::server; use crate::credentials::server::ServerConnectionSecurityInfo; use crate::rt::GrpcEndpoint; @@ -181,12 +181,12 @@ mod test { use super::*; use crate::credentials::ChannelCredentials; + use crate::credentials::SecurityLevel; use crate::credentials::ServerCredentials; use crate::credentials::client::ChannelCredsInternal as ClientSealed; use crate::credentials::client::ClientConnectionSecurityContext; use crate::credentials::client::ClientHandshakeInfo; use crate::credentials::common::Authority; - use crate::credentials::common::SecurityLevel; use crate::credentials::server::ServerCredsInternal; use crate::rt; use crate::rt::GrpcEndpoint; diff --git a/grpc/src/credentials/mod.rs b/grpc/src/credentials/mod.rs index acfe22e2c..919d729bd 100644 --- a/grpc/src/credentials/mod.rs +++ b/grpc/src/credentials/mod.rs @@ -49,26 +49,26 @@ pub trait ServerCredentials: server::ServerCredsInternal + Sync + 'static { fn info(&self) -> &ProtocolInfo; } -pub(crate) mod common { - /// Defines the level of protection provided by an established connection. - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - #[non_exhaustive] - pub enum SecurityLevel { - /// The connection is insecure; no protection is applied. - NoSecurity, - /// The connection guarantees data integrity (tamper-proofing) but not - /// privacy. - /// - /// Payloads are visible to observers but cannot be modified without - /// detection. - IntegrityOnly, - /// The connection guarantees both privacy (confidentiality) and data - /// integrity. - /// - /// This is the standard level for secure transports like TLS. - PrivacyAndIntegrity, - } +/// Defines the level of protection provided by an established connection. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum SecurityLevel { + /// The connection is insecure; no protection is applied. + NoSecurity, + /// The connection guarantees data integrity (tamper-proofing) but not + /// privacy. + /// + /// Payloads are visible to observers but cannot be modified without + /// detection. + IntegrityOnly, + /// The connection guarantees both privacy (confidentiality) and data + /// integrity. + /// + /// This is the standard level for secure transports like TLS. + PrivacyAndIntegrity, +} +pub(crate) mod common { /// Represents the value passed as the `:authority` pseudo-header, typically /// in the form `host:port`. pub struct Authority { diff --git a/grpc/src/credentials/rustls/client/mod.rs b/grpc/src/credentials/rustls/client/mod.rs index 752a5f8e1..b0ab83988 100644 --- a/grpc/src/credentials/rustls/client/mod.rs +++ b/grpc/src/credentials/rustls/client/mod.rs @@ -36,6 +36,7 @@ use tokio_rustls::TlsStream as RustlsStream; use crate::attributes::Attributes; use crate::credentials::ChannelCredentials; use crate::credentials::ProtocolInfo; +use crate::credentials::SecurityLevel; use crate::credentials::call::CallCredentials; use crate::credentials::client; use crate::credentials::client::ClientConnectionSecurityContext; @@ -43,7 +44,6 @@ use crate::credentials::client::ClientConnectionSecurityInfo; use crate::credentials::client::ClientHandshakeInfo; use crate::credentials::client::HandshakeOutput; use crate::credentials::common::Authority; -use crate::credentials::common::SecurityLevel; use crate::credentials::rustls::ALPN_PROTO_STR_H2; use crate::credentials::rustls::Identity; use crate::credentials::rustls::Provider; diff --git a/grpc/src/credentials/rustls/server/mod.rs b/grpc/src/credentials/rustls/server/mod.rs index d12e025a8..4cff5ede8 100644 --- a/grpc/src/credentials/rustls/server/mod.rs +++ b/grpc/src/credentials/rustls/server/mod.rs @@ -37,8 +37,8 @@ use webpki::EndEntityCert; use crate::attributes::Attributes; use crate::credentials::ProtocolInfo; +use crate::credentials::SecurityLevel; use crate::credentials::ServerCredentials; -use crate::credentials::common::SecurityLevel; use crate::credentials::rustls::ALPN_PROTO_STR_H2; use crate::credentials::rustls::IdentityList; use crate::credentials::rustls::Provider; diff --git a/grpc/src/credentials/server.rs b/grpc/src/credentials/server.rs index 05dbc0b6b..0cc5c1530 100644 --- a/grpc/src/credentials/server.rs +++ b/grpc/src/credentials/server.rs @@ -23,7 +23,7 @@ */ use crate::attributes::Attributes; -use crate::credentials::common::SecurityLevel; +use crate::credentials::SecurityLevel; use crate::rt::GrpcEndpoint; use crate::rt::GrpcRuntime;