|
| 1 | +#![warn(clippy::undocumented_unsafe_blocks)] |
| 2 | + |
| 3 | +use core::ptr; |
| 4 | +use std::os::raw::c_int; |
| 5 | +use std::slice; |
| 6 | + |
| 7 | +use openssl_sys::{ |
| 8 | + OPENSSL_free, OPENSSL_sk_num, OPENSSL_sk_value, X509, X509_STORE_CTX, |
| 9 | + X509_STORE_CTX_get_error_depth, X509_STORE_CTX_get0_chain, X509_STORE_CTX_set_error, |
| 10 | + X509_V_ERR_APPLICATION_VERIFICATION, X509_V_ERR_CERT_REVOKED, i2d_X509, stack_st_X509, |
| 11 | +}; |
| 12 | +use rustls_pki_types::CertificateDer; |
| 13 | +use upki::revocation::{Index, RevocationCheckInput, RevocationStatus}; |
| 14 | +use upki::{Config, ConfigPath, Error}; |
| 15 | + |
| 16 | +/// This is a function matching OpenSSL's `SSL_verify_cb` type which does |
| 17 | +/// revocation checking using upki. |
| 18 | +/// |
| 19 | +/// The configuration file and data location is found automatically. |
| 20 | +/// |
| 21 | +/// # Errors |
| 22 | +/// This function returns 0 if called with 0 for the `preverify_ok` parameter. |
| 23 | +/// As a result, it never allows a verification to pass if the previous verification |
| 24 | +/// step has failed. |
| 25 | +/// |
| 26 | +/// If the certificate chain obtained from `x509_ctx` is revoked, this function returns 0 |
| 27 | +/// and sets the `X509_V_ERR_CERT_REVOKED` error on `x509_ctx` (using |
| 28 | +/// `X509_STORE_CTX_set_error(3SSL)`). |
| 29 | +/// |
| 30 | +/// If the certificate chain obtained from `x509_ctx` is not included in the revocation data, |
| 31 | +/// this function returns `preverify_ok`. |
| 32 | +/// |
| 33 | +/// If the revocation status cannot be determined, this function returns 0 and sets |
| 34 | +/// the `X509_V_ERR_APPLICATION_VERIFICATION` error on `x509_ctx` (using |
| 35 | +/// `X509_STORE_CTX_set_error(3SSL)`). |
| 36 | +/// |
| 37 | +/// On unexpected/unrecoverable errors, this function returns 0. |
| 38 | +/// |
| 39 | +/// # Safety |
| 40 | +/// This function is called by OpenSSL typically, and its correct operation |
| 41 | +/// hinges almost entirely on being called properly. For example, that |
| 42 | +/// `x509_ctx` is a valid pointer, or NULL. |
| 43 | +#[unsafe(no_mangle)] |
| 44 | +pub unsafe extern "C" fn upki_openssl_verify_callback( |
| 45 | + mut preverify_ok: c_int, |
| 46 | + x509_ctx: *mut X509_STORE_CTX, |
| 47 | +) -> c_int { |
| 48 | + // Revocation checking never improves the situation if the verification has failed. |
| 49 | + if preverify_ok == 0 { |
| 50 | + return preverify_ok; |
| 51 | + } |
| 52 | + |
| 53 | + // SAFETY: via essential and established principles of the C type system, we rely on |
| 54 | + // OpenSSL to call this function with a `x509_ctx` that points to a valid value, or |
| 55 | + // exceptionally is NULL. |
| 56 | + let Some(mut x509_ctx) = (unsafe { BorrowedX509StoreCtx::from_ptr(x509_ctx) }) else { |
| 57 | + return 0; |
| 58 | + }; |
| 59 | + |
| 60 | + // This callback is called once per certificate, with the final call being for the |
| 61 | + // leaf certificate denoted by error_depth = 0. We only process the chain as a whole; |
| 62 | + // do this at the leaf certificate level. |
| 63 | + if x509_ctx.error_depth() != 0 { |
| 64 | + return preverify_ok; |
| 65 | + } |
| 66 | + |
| 67 | + let Some(chain) = x509_ctx.chain() else { |
| 68 | + return 0; |
| 69 | + }; |
| 70 | + |
| 71 | + let Some(certs) = chain.copy_certs() else { |
| 72 | + return 0; |
| 73 | + }; |
| 74 | + |
| 75 | + match revocation_check(&certs) { |
| 76 | + Ok(RevocationStatus::CertainlyRevoked) => { |
| 77 | + x509_ctx.set_error(X509_V_ERR_CERT_REVOKED); |
| 78 | + preverify_ok = 0; |
| 79 | + } |
| 80 | + Ok(RevocationStatus::NotCoveredByRevocationData | RevocationStatus::NotRevoked) => {} |
| 81 | + Err(_e) => { |
| 82 | + x509_ctx.set_error(X509_V_ERR_APPLICATION_VERIFICATION); |
| 83 | + preverify_ok = 0; |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + preverify_ok |
| 88 | +} |
| 89 | + |
| 90 | +fn revocation_check(certs: &[CertificateDer<'_>]) -> Result<RevocationStatus, Error> { |
| 91 | + let path = ConfigPath::new(None)?; |
| 92 | + let config = Config::from_file_or_user_default(&path)?; |
| 93 | + let mut index = Index::from_cache(&config)?; |
| 94 | + let input = RevocationCheckInput::from_certificates(certs)?; |
| 95 | + match index.check(&input) { |
| 96 | + Ok(st) => Ok(st), |
| 97 | + Err(e) => Err(Error::Revocation(e)), |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +struct BorrowedX509StoreCtx<'a>(&'a mut X509_STORE_CTX); |
| 102 | + |
| 103 | +impl<'a> BorrowedX509StoreCtx<'a> { |
| 104 | + unsafe fn from_ptr(ptr: *mut X509_STORE_CTX) -> Option<Self> { |
| 105 | + // SAFETY: we pass up the requirements of `ptr::as_mut()` to our caller |
| 106 | + unsafe { ptr.as_mut() }.map(Self) |
| 107 | + } |
| 108 | + |
| 109 | + fn error_depth(&self) -> c_int { |
| 110 | + // SAFETY: the input pointer is valid, because it comes from our reference. |
| 111 | + unsafe { X509_STORE_CTX_get_error_depth(ptr::from_ref(self.0)) } |
| 112 | + } |
| 113 | + |
| 114 | + fn chain(&self) -> Option<BorrowedX509Stack<'a>> { |
| 115 | + // SAFETY: X509_STORE_CTX_get0_chain has no published documentation saying when it is |
| 116 | + // safe to call. This type guarantees that the pointer is of the correct type, alignment, etc, |
| 117 | + // and is non-NULL. |
| 118 | + let chain = unsafe { X509_STORE_CTX_get0_chain(ptr::from_ref(self.0)) }; |
| 119 | + |
| 120 | + // SAFETY: we require that openssl correctly returns a valid pointer, or NULL. |
| 121 | + unsafe { chain.as_ref() }.map(BorrowedX509Stack) |
| 122 | + } |
| 123 | + |
| 124 | + fn set_error(&mut self, err: i32) { |
| 125 | + // SAFETY: the input pointer is valid, because it comes from our reference. |
| 126 | + // OpenSSL does not document any other preconditions. |
| 127 | + unsafe { X509_STORE_CTX_set_error(ptr::from_mut(self.0), err) }; |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +struct BorrowedX509Stack<'a>(&'a stack_st_X509); |
| 132 | + |
| 133 | +impl<'a> BorrowedX509Stack<'a> { |
| 134 | + fn copy_certs(&self) -> Option<Vec<CertificateDer<'static>>> { |
| 135 | + // SAFETY: the stack pointer is valid, thanks to it being from a reference. |
| 136 | + let count = unsafe { OPENSSL_sk_num(ptr::from_ref(self.0).cast()) }; |
| 137 | + if count < 0 { |
| 138 | + return None; |
| 139 | + } |
| 140 | + |
| 141 | + let mut certs = vec![]; |
| 142 | + for i in 0..count { |
| 143 | + // SAFETY: the stack pointer is valid, thanks to it being from a reference. `OPENSSL_sk_value` returns |
| 144 | + // a valid pointer to the item or NULL. |
| 145 | + let x509: *const X509 = |
| 146 | + unsafe { OPENSSL_sk_value(ptr::from_ref(self.0).cast(), i).cast() }; |
| 147 | + |
| 148 | + // SAFETY: we require OpenSSL only fills the stack with valid pointers to X509 objects (or NULL) |
| 149 | + let x509 = unsafe { x509.as_ref() }?; |
| 150 | + certs.push(x509_to_certificate_der(x509)?); |
| 151 | + } |
| 152 | + |
| 153 | + Some(certs) |
| 154 | + } |
| 155 | +} |
| 156 | + |
| 157 | +fn x509_to_certificate_der(x509: &'_ X509) -> Option<CertificateDer<'static>> { |
| 158 | + // SAFETY: the x509 pointer is valid, thanks to it coming from a reference. |
| 159 | + let (ptr, len) = unsafe { |
| 160 | + let mut ptr = ptr::null_mut(); |
| 161 | + let len = i2d_X509(ptr::from_ref(x509), &mut ptr); |
| 162 | + (ptr, len) |
| 163 | + }; |
| 164 | + |
| 165 | + if len <= 0 || ptr.is_null() { |
| 166 | + return None; |
| 167 | + } |
| 168 | + let len = len as usize; |
| 169 | + |
| 170 | + let mut v = Vec::with_capacity(len); |
| 171 | + // SAFETY: we rely on i2d_X509 allocating `ptr` correctly and signalling an error via negative `len` if not. |
| 172 | + // `ptr` must be an allocated pointer. |
| 173 | + unsafe { |
| 174 | + v.extend_from_slice(slice::from_raw_parts(ptr, len)); |
| 175 | + OPENSSL_free(ptr as *mut _); |
| 176 | + } |
| 177 | + Some(v.into()) |
| 178 | +} |
| 179 | + |
| 180 | +#[cfg(test)] |
| 181 | +mod test; |
0 commit comments