1
//! Certificate validator support for dynamic modules.
2
//!
3
//! This module provides traits and types for implementing custom TLS certificate validators
4
//! as dynamic modules. Certificate validators are used during TLS handshakes to verify
5
//! the peer's certificate chain.
6
//!
7
//! # Example
8
//!
9
//! ```ignore
10
//! use envoy_proxy_dynamic_modules_rust_sdk::*;
11
//! use envoy_proxy_dynamic_modules_rust_sdk::cert_validator::*;
12
//!
13
//! fn program_init() -> bool {
14
//!   true
15
//! }
16
//!
17
//! fn new_cert_validator_config(name: &str, config: &[u8]) -> Option<Box<dyn CertValidatorConfig>> {
18
//!   Some(Box::new(MyCertValidatorConfig {}))
19
//! }
20
//!
21
//! declare_cert_validator_init_functions!(program_init, new_cert_validator_config);
22
//!
23
//! struct MyCertValidatorConfig {}
24
//!
25
//! impl CertValidatorConfig for MyCertValidatorConfig {
26
//!   fn do_verify_cert_chain(
27
//!     &self,
28
//!     _envoy_cert_validator: &EnvoyCertValidator,
29
//!     certs: &[&[u8]],
30
//!     host_name: &str,
31
//!     is_server: bool,
32
//!   ) -> ValidationResult {
33
//!     ValidationResult::successful()
34
//!   }
35
//!
36
//!   fn get_ssl_verify_mode(&self, handshaker_provides_certificates: bool) -> i32 {
37
//!     0x03 // SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT
38
//!   }
39
//!
40
//!   fn update_digest(&self) -> &[u8] {
41
//!     b"my_cert_validator"
42
//!   }
43
//! }
44
//! ```
45

            
46
use crate::{abi, bytes_to_module_buffer, EnvoyBuffer};
47

            
48
/// Wrapper around the Envoy cert validator config pointer, providing access to
49
/// Envoy-side operations such as filter state during certificate validation.
50
///
51
/// This is passed to [`CertValidatorConfig::do_verify_cert_chain`] and is only valid for
52
/// the duration of that call.
53
pub struct EnvoyCertValidator {
54
  raw: abi::envoy_dynamic_module_type_cert_validator_config_envoy_ptr,
55
}
56

            
57
impl EnvoyCertValidator {
58
  /// Create a new `EnvoyCertValidator` from the raw Envoy pointer.
59
  pub(crate) fn new(raw: abi::envoy_dynamic_module_type_cert_validator_config_envoy_ptr) -> Self {
60
    Self { raw }
61
  }
62

            
63
  /// Set a string value in the connection's filter state with Connection life span.
64
  ///
65
  /// Returns true if the operation was successful, false otherwise (e.g. no connection
66
  /// context available or the key already exists and is read-only).
67
  pub fn set_filter_state(&self, key: &[u8], value: &[u8]) -> bool {
68
    unsafe {
69
      abi::envoy_dynamic_module_callback_cert_validator_set_filter_state(
70
        self.raw,
71
        bytes_to_module_buffer(key),
72
        bytes_to_module_buffer(value),
73
      )
74
    }
75
  }
76

            
77
  /// Get a string value from the connection's filter state.
78
  ///
79
  /// Returns `None` if the key is not found or no connection context is available.
80
  pub fn get_filter_state<'a>(&'a self, key: &[u8]) -> Option<EnvoyBuffer<'a>> {
81
    let mut result = abi::envoy_dynamic_module_type_envoy_buffer {
82
      ptr: std::ptr::null(),
83
      length: 0,
84
    };
85
    let success = unsafe {
86
      abi::envoy_dynamic_module_callback_cert_validator_get_filter_state(
87
        self.raw,
88
        bytes_to_module_buffer(key),
89
        &mut result as *mut _ as *mut _,
90
      )
91
    };
92
    if success && !result.ptr.is_null() && result.length > 0 {
93
      Some(unsafe { EnvoyBuffer::new_from_raw(result.ptr as *const _, result.length) })
94
    } else {
95
      None
96
    }
97
  }
98
}
99

            
100
/// The result of a certificate chain validation.
101
pub struct ValidationResult {
102
  /// The overall validation status.
103
  pub status: ValidationStatus,
104
  /// The detailed client validation status.
105
  pub detailed_status: ClientValidationStatus,
106
  /// Optional TLS alert code to send on failure.
107
  pub tls_alert: Option<u8>,
108
  /// Optional error details string. If set, the SDK will pass it to Envoy via a callback so
109
  /// that the module does not need to manage the string's lifetime across the FFI boundary.
110
  pub error_details: Option<String>,
111
}
112

            
113
impl ValidationResult {
114
  /// Create a successful validation result.
115
  pub fn successful() -> Self {
116
    Self {
117
      status: ValidationStatus::Successful,
118
      detailed_status: ClientValidationStatus::Validated,
119
      tls_alert: None,
120
      error_details: None,
121
    }
122
  }
123

            
124
  /// Create a failed validation result.
125
  pub fn failed(
126
    detailed_status: ClientValidationStatus,
127
    tls_alert: Option<u8>,
128
    error_details: Option<String>,
129
  ) -> Self {
130
    Self {
131
      status: ValidationStatus::Failed,
132
      detailed_status,
133
      tls_alert,
134
      error_details,
135
    }
136
  }
137
}
138

            
139
impl From<&ValidationResult> for abi::envoy_dynamic_module_type_cert_validator_validation_result {
140
  fn from(result: &ValidationResult) -> Self {
141
    let status = match result.status {
142
      ValidationStatus::Successful => {
143
        abi::envoy_dynamic_module_type_cert_validator_validation_status::Successful
144
      },
145
      ValidationStatus::Failed => {
146
        abi::envoy_dynamic_module_type_cert_validator_validation_status::Failed
147
      },
148
    };
149

            
150
    let detailed_status = match result.detailed_status {
151
      ClientValidationStatus::NotValidated => {
152
        abi::envoy_dynamic_module_type_cert_validator_client_validation_status::NotValidated
153
      },
154
      ClientValidationStatus::NoClientCertificate => {
155
        abi::envoy_dynamic_module_type_cert_validator_client_validation_status::NoClientCertificate
156
      },
157
      ClientValidationStatus::Validated => {
158
        abi::envoy_dynamic_module_type_cert_validator_client_validation_status::Validated
159
      },
160
      ClientValidationStatus::Failed => {
161
        abi::envoy_dynamic_module_type_cert_validator_client_validation_status::Failed
162
      },
163
    };
164

            
165
    let (has_tls_alert, tls_alert) = match result.tls_alert {
166
      Some(alert) => (true, alert),
167
      None => (false, 0),
168
    };
169

            
170
    abi::envoy_dynamic_module_type_cert_validator_validation_result {
171
      status,
172
      detailed_status,
173
      tls_alert,
174
      has_tls_alert,
175
    }
176
  }
177
}
178

            
179
/// The overall validation status.
180
pub enum ValidationStatus {
181
  /// The certificate chain is valid.
182
  Successful,
183
  /// The certificate chain is invalid.
184
  Failed,
185
}
186

            
187
/// Detailed client validation status.
188
pub enum ClientValidationStatus {
189
  /// Client certificate was not validated.
190
  NotValidated,
191
  /// No client certificate was provided.
192
  NoClientCertificate,
193
  /// Client certificate was successfully validated.
194
  Validated,
195
  /// Client certificate validation failed.
196
  Failed,
197
}
198

            
199
/// Trait for implementing a certificate validator configuration.
200
///
201
/// An implementation of this trait is created once per validator configuration and shared
202
/// across TLS handshakes. All methods must be thread-safe.
203
pub trait CertValidatorConfig: Send + Sync {
204
  /// Verify a certificate chain.
205
  ///
206
  /// Called during a TLS handshake to validate the peer's certificate chain.
207
  /// Each certificate in `certs` is DER-encoded, with the first entry being the leaf certificate.
208
  ///
209
  /// The `envoy_cert_validator` provides access to Envoy-side operations such as reading and
210
  /// writing filter state on the connection. It is only valid for the duration of this call.
211
  ///
212
  /// # Arguments
213
  /// * `envoy_cert_validator` - The Envoy cert validator handle for accessing filter state.
214
  /// * `certs` - Slice of DER-encoded certificates. The first entry is the leaf certificate.
215
  /// * `host_name` - The SNI host name for validation.
216
  /// * `is_server` - True if validating client certificates on the server side.
217
  fn do_verify_cert_chain(
218
    &self,
219
    envoy_cert_validator: &EnvoyCertValidator,
220
    certs: &[&[u8]],
221
    host_name: &str,
222
    is_server: bool,
223
  ) -> ValidationResult;
224

            
225
  /// Get the SSL verify mode flags.
226
  ///
227
  /// Called during SSL context initialization. The return value should be a combination of
228
  /// SSL_VERIFY_* flags. For example, `0x03` for `SSL_VERIFY_PEER |
229
  /// SSL_VERIFY_FAIL_IF_NO_PEER_CERT`.
230
  fn get_ssl_verify_mode(&self, handshaker_provides_certificates: bool) -> i32;
231

            
232
  /// Get bytes to contribute to the session context hash.
233
  ///
234
  /// Returns bytes that uniquely identify this validation configuration so that configuration
235
  /// changes invalidate existing TLS sessions. The returned slice must remain valid for the
236
  /// lifetime of the config.
237
  fn update_digest(&self) -> &[u8];
238
}
239

            
240
// =============================================================================
241
// FFI trampolines
242
// =============================================================================
243

            
244
use crate::{drop_wrapped_c_void_ptr, wrap_into_c_void_ptr, NEW_CERT_VALIDATOR_CONFIG_FUNCTION};
245

            
246
/// # Safety
247
///
248
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
249
/// by the Envoy dynamic module ABI.
250
#[no_mangle]
251
pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_config_new(
252
  _config_envoy_ptr: abi::envoy_dynamic_module_type_cert_validator_config_envoy_ptr,
253
  name: abi::envoy_dynamic_module_type_envoy_buffer,
254
  config: abi::envoy_dynamic_module_type_envoy_buffer,
255
) -> abi::envoy_dynamic_module_type_cert_validator_config_module_ptr {
256
  let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
257
    name.ptr as *const _,
258
    name.length,
259
  ));
260
  let config_slice = std::slice::from_raw_parts(config.ptr as *const _, config.length);
261
  let new_config_fn = NEW_CERT_VALIDATOR_CONFIG_FUNCTION
262
    .get()
263
    .expect("NEW_CERT_VALIDATOR_CONFIG_FUNCTION must be set");
264
  match new_config_fn(name_str, config_slice) {
265
    Some(config) => wrap_into_c_void_ptr!(config),
266
    None => std::ptr::null(),
267
  }
268
}
269

            
270
/// # Safety
271
///
272
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
273
/// by the Envoy dynamic module ABI.
274
#[no_mangle]
275
pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_config_destroy(
276
  config_ptr: abi::envoy_dynamic_module_type_cert_validator_config_module_ptr,
277
) {
278
  drop_wrapped_c_void_ptr!(config_ptr, CertValidatorConfig);
279
}
280

            
281
/// # Safety
282
///
283
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
284
/// by the Envoy dynamic module ABI.
285
#[no_mangle]
286
pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_do_verify_cert_chain(
287
  config_envoy_ptr: abi::envoy_dynamic_module_type_cert_validator_config_envoy_ptr,
288
  config_module_ptr: abi::envoy_dynamic_module_type_cert_validator_config_module_ptr,
289
  certs: *mut abi::envoy_dynamic_module_type_envoy_buffer,
290
  certs_count: usize,
291
  host_name: abi::envoy_dynamic_module_type_envoy_buffer,
292
  is_server: bool,
293
) -> abi::envoy_dynamic_module_type_cert_validator_validation_result {
294
  let config = {
295
    let raw = config_module_ptr as *const *const dyn CertValidatorConfig;
296
    &**raw
297
  };
298

            
299
  let envoy_cert_validator = EnvoyCertValidator::new(config_envoy_ptr);
300

            
301
  let cert_buffers = std::slice::from_raw_parts(certs, certs_count);
302
  let cert_slices: Vec<&[u8]> = cert_buffers
303
    .iter()
304
    .map(|buf| std::slice::from_raw_parts(buf.ptr as *const u8, buf.length))
305
    .collect();
306

            
307
  let host_name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
308
    host_name.ptr as *const _,
309
    host_name.length,
310
  ));
311

            
312
  let result = config.do_verify_cert_chain(
313
    &envoy_cert_validator,
314
    &cert_slices,
315
    host_name_str,
316
    is_server,
317
  );
318

            
319
  // If the module provided error details, pass them to Envoy via the callback.
320
  // Envoy copies the buffer immediately, so the string only needs to live until the call returns.
321
  if let Some(ref error) = result.error_details {
322
    let error_buf = abi::envoy_dynamic_module_type_module_buffer {
323
      ptr: error.as_ptr() as *const _,
324
      length: error.len(),
325
    };
326
    abi::envoy_dynamic_module_callback_cert_validator_set_error_details(
327
      config_envoy_ptr,
328
      error_buf,
329
    );
330
  }
331

            
332
  abi::envoy_dynamic_module_type_cert_validator_validation_result::from(&result)
333
}
334

            
335
/// # Safety
336
///
337
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
338
/// by the Envoy dynamic module ABI.
339
#[no_mangle]
340
pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_get_ssl_verify_mode(
341
  config_module_ptr: abi::envoy_dynamic_module_type_cert_validator_config_module_ptr,
342
  handshaker_provides_certificates: bool,
343
) -> std::os::raw::c_int {
344
  let config = {
345
    let raw = config_module_ptr as *const *const dyn CertValidatorConfig;
346
    &**raw
347
  };
348
  config.get_ssl_verify_mode(handshaker_provides_certificates)
349
}
350

            
351
/// # Safety
352
///
353
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
354
/// by the Envoy dynamic module ABI.
355
#[no_mangle]
356
pub unsafe extern "C" fn envoy_dynamic_module_on_cert_validator_update_digest(
357
  config_module_ptr: abi::envoy_dynamic_module_type_cert_validator_config_module_ptr,
358
  out_data: *mut abi::envoy_dynamic_module_type_module_buffer,
359
) {
360
  let config = {
361
    let raw = config_module_ptr as *const *const dyn CertValidatorConfig;
362
    &**raw
363
  };
364
  let digest = config.update_digest();
365
  (*out_data).ptr = digest.as_ptr() as *const _;
366
  (*out_data).length = digest.len();
367
}