magnetar_proto/auth/
tls.rs1use std::fs;
15use std::path::Path;
16
17use bytes::Bytes;
18
19use super::{AuthError, AuthProvider};
20
21#[derive(Clone)]
30pub struct TlsAuth {
31 cert_chain_pem: Bytes,
32 private_key_pem: Bytes,
33}
34
35impl std::fmt::Debug for TlsAuth {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.debug_struct("TlsAuth")
38 .field("cert_chain_pem_len", &self.cert_chain_pem.len())
39 .field("private_key_pem", &"<redacted>")
40 .finish()
41 }
42}
43
44impl TlsAuth {
45 #[must_use]
47 pub fn from_pem_bytes(cert_chain_pem: Bytes, private_key_pem: Bytes) -> Self {
48 Self {
49 cert_chain_pem,
50 private_key_pem,
51 }
52 }
53
54 pub fn from_pem_files(
56 cert_path: impl AsRef<Path>,
57 key_path: impl AsRef<Path>,
58 ) -> Result<Self, AuthError> {
59 let cert = fs::read(cert_path.as_ref()).map_err(|err| {
60 AuthError::Io(format!(
61 "reading cert file {}: {err}",
62 cert_path.as_ref().display()
63 ))
64 })?;
65 let key = fs::read(key_path.as_ref()).map_err(|err| {
66 AuthError::Io(format!(
67 "reading key file {}: {err}",
68 key_path.as_ref().display()
69 ))
70 })?;
71 Ok(Self::from_pem_bytes(Bytes::from(cert), Bytes::from(key)))
72 }
73
74 #[must_use]
76 pub fn cert_chain_pem(&self) -> &Bytes {
77 &self.cert_chain_pem
78 }
79
80 #[must_use]
82 pub fn private_key_pem(&self) -> &Bytes {
83 &self.private_key_pem
84 }
85}
86
87impl AuthProvider for TlsAuth {
88 #[allow(clippy::unnecessary_literal_bound)]
89 fn method(&self) -> &str {
90 "tls"
91 }
92
93 fn initial(&self) -> Result<Bytes, AuthError> {
94 Ok(Bytes::new())
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use bytes::Bytes;
102
103 use super::{AuthProvider, TlsAuth};
104
105 #[test]
106 fn round_trip_holds_bytes_and_method_is_tls() {
107 let cert =
108 Bytes::from_static(b"-----BEGIN CERTIFICATE-----\nfake\n-----END CERTIFICATE-----\n");
109 let key =
110 Bytes::from_static(b"-----BEGIN PRIVATE KEY-----\nfake\n-----END PRIVATE KEY-----\n");
111 let p = TlsAuth::from_pem_bytes(cert.clone(), key.clone());
112 assert_eq!(p.method(), "tls");
113 assert_eq!(p.cert_chain_pem(), &cert);
114 assert_eq!(p.private_key_pem(), &key);
115 assert!(p.initial().expect("initial").is_empty());
116 }
117
118 #[test]
119 fn from_pem_files_missing_paths() {
120 let err = TlsAuth::from_pem_files(
121 "/this/path/does/not/exist/cert.pem",
122 "/this/path/does/not/exist/key.pem",
123 )
124 .unwrap_err();
125 let msg = err.to_string();
126 assert!(msg.contains("cert file"), "msg={msg}");
127 }
128
129 #[test]
133 fn debug_redacts_private_key() {
134 let cert = Bytes::from_static(
135 b"-----BEGIN CERTIFICATE-----\npublic-cert-bytes\n-----END CERTIFICATE-----\n",
136 );
137 let key = Bytes::from_static(
138 b"-----BEGIN PRIVATE KEY-----\nSECRET-KEY-MATERIAL-1234\n-----END PRIVATE KEY-----\n",
139 );
140 let p = TlsAuth::from_pem_bytes(cert, key);
141 let rendered = format!("{p:?}");
142 assert!(
143 !rendered.contains("SECRET-KEY-MATERIAL"),
144 "private key leaked through Debug: {rendered}",
145 );
146 assert!(
147 !rendered.contains("BEGIN PRIVATE KEY"),
148 "PEM header for key leaked through Debug: {rendered}",
149 );
150 assert!(
151 rendered.contains("<redacted>"),
152 "Debug should mark redaction explicitly: {rendered}",
153 );
154 }
155}