1#![forbid(unsafe_code)]
2
3#![cfg_attr(feature = "x509", doc = "```")]
15#![cfg_attr(not(feature = "x509"), doc = "```ignore")]
16use tonic::transport::{Certificate, ClientTlsConfig, Identity, ServerTlsConfig};
29
30#[cfg(feature = "x509")]
32pub trait TonicIdentityExt {
33 fn identity_tonic(&self) -> Identity;
35}
36
37#[cfg(feature = "x509")]
38impl TonicIdentityExt for uselesskey_x509::X509Cert {
39 fn identity_tonic(&self) -> Identity {
40 Identity::from_pem(self.cert_pem(), self.private_key_pkcs8_pem())
41 }
42}
43
44#[cfg(feature = "x509")]
45impl TonicIdentityExt for uselesskey_x509::X509Chain {
46 fn identity_tonic(&self) -> Identity {
47 Identity::from_pem(self.chain_pem(), self.leaf_private_key_pkcs8_pem())
48 }
49}
50
51#[cfg(feature = "x509")]
53pub trait TonicServerTlsExt {
54 fn server_tls_config_tonic(&self) -> ServerTlsConfig;
56}
57
58#[cfg(feature = "x509")]
59impl TonicServerTlsExt for uselesskey_x509::X509Cert {
60 fn server_tls_config_tonic(&self) -> ServerTlsConfig {
61 ServerTlsConfig::new().identity(self.identity_tonic())
62 }
63}
64
65#[cfg(feature = "x509")]
66impl TonicServerTlsExt for uselesskey_x509::X509Chain {
67 fn server_tls_config_tonic(&self) -> ServerTlsConfig {
68 ServerTlsConfig::new().identity(self.identity_tonic())
69 }
70}
71
72#[cfg(feature = "x509")]
74pub trait TonicClientTlsExt {
75 fn client_tls_config_tonic(&self, domain_name: impl Into<String>) -> ClientTlsConfig;
77}
78
79#[cfg(feature = "x509")]
80impl TonicClientTlsExt for uselesskey_x509::X509Cert {
81 fn client_tls_config_tonic(&self, domain_name: impl Into<String>) -> ClientTlsConfig {
82 ClientTlsConfig::new()
83 .domain_name(domain_name)
84 .ca_certificate(Certificate::from_pem(self.cert_pem()))
85 }
86}
87
88#[cfg(feature = "x509")]
89impl TonicClientTlsExt for uselesskey_x509::X509Chain {
90 fn client_tls_config_tonic(&self, domain_name: impl Into<String>) -> ClientTlsConfig {
91 ClientTlsConfig::new()
92 .domain_name(domain_name)
93 .ca_certificate(Certificate::from_pem(self.root_cert_pem()))
94 }
95}
96
97#[cfg(feature = "x509")]
99pub trait TonicMtlsExt {
100 fn server_tls_config_mtls_tonic(&self) -> ServerTlsConfig;
102
103 fn client_tls_config_mtls_tonic(&self, domain_name: impl Into<String>) -> ClientTlsConfig;
105}
106
107#[cfg(feature = "x509")]
108impl TonicMtlsExt for uselesskey_x509::X509Chain {
109 fn server_tls_config_mtls_tonic(&self) -> ServerTlsConfig {
110 ServerTlsConfig::new()
111 .identity(self.identity_tonic())
112 .client_ca_root(Certificate::from_pem(self.root_cert_pem()))
113 }
114
115 fn client_tls_config_mtls_tonic(&self, domain_name: impl Into<String>) -> ClientTlsConfig {
116 self.client_tls_config_tonic(domain_name)
117 .identity(self.identity_tonic())
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::{TonicClientTlsExt, TonicIdentityExt, TonicMtlsExt, TonicServerTlsExt};
124 use std::sync::OnceLock;
125 use uselesskey_core::{Factory, Seed};
126 use uselesskey_x509::{ChainSpec, X509FactoryExt, X509Spec};
127
128 static FX: OnceLock<Factory> = OnceLock::new();
129
130 fn fx() -> Factory {
131 FX.get_or_init(|| {
132 let seed = Seed::from_env_value("uselesskey-tonic-inline-test-seed-v1")
133 .expect("test seed should always parse");
134 Factory::deterministic(seed)
135 })
136 .clone()
137 }
138
139 #[test]
140 fn self_signed_identity_and_tls_configs_build() {
141 let fx = fx();
142 let cert = fx.x509_self_signed("grpc-self-signed", X509Spec::self_signed("localhost"));
143
144 let _identity = cert.identity_tonic();
145 let _server = cert.server_tls_config_tonic();
146 let _client = cert.client_tls_config_tonic("localhost");
147 }
148
149 #[test]
150 fn chain_identity_and_tls_configs_build() {
151 let fx = fx();
152 let chain = fx.x509_chain("grpc-chain", ChainSpec::new("test.example.com"));
153
154 let _identity = chain.identity_tonic();
155 let _server = chain.server_tls_config_tonic();
156 let _client = chain.client_tls_config_tonic("test.example.com");
157 }
158
159 #[test]
160 fn chain_mtls_configs_build() {
161 let fx = fx();
162 let chain = fx.x509_chain("grpc-mtls", ChainSpec::new("test.example.com"));
163
164 let _server = chain.server_tls_config_mtls_tonic();
165 let _client = chain.client_tls_config_mtls_tonic("test.example.com");
166 }
167
168 #[test]
169 fn deterministic_chain_material_stays_stable() {
170 let seed = Seed::from_env_value("grpc-tonic-stability").expect("seed");
171 let fx = Factory::deterministic(seed);
172
173 let a = fx.x509_chain("stable", ChainSpec::new("det.example.com"));
174 fx.clear_cache();
175 let b = fx.x509_chain("stable", ChainSpec::new("det.example.com"));
176
177 assert_eq!(a.chain_pem(), b.chain_pem());
178 assert_eq!(a.root_cert_pem(), b.root_cert_pem());
179 assert_eq!(
180 a.leaf_private_key_pkcs8_pem(),
181 b.leaf_private_key_pkcs8_pem()
182 );
183 }
184}