Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 1 | // Copyright 2020, The Android Open Source Project |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | //! This crate implements a safe wrapper around the ConfirmationUI HIDL spec, which |
| 16 | //! is the backend for Android Protected Confirmation (APC). |
| 17 | //! |
| 18 | //! It provides a safe wrapper around a C++ implementation of ConfirmationUI |
| 19 | //! client. |
| 20 | |
| 21 | use keystore2_apc_compat_bindgen::{ |
Jeff Vander Stoep | 76c0f28 | 2022-12-07 11:39:27 +0100 | [diff] [blame] | 22 | abortUserConfirmation, closeUserConfirmationService, promptUserConfirmation, |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 23 | tryGetUserConfirmationService, ApcCompatCallback, ApcCompatServiceHandle, |
| 24 | }; |
| 25 | pub use keystore2_apc_compat_bindgen::{ |
| 26 | ApcCompatUiOptions, APC_COMPAT_ERROR_ABORTED, APC_COMPAT_ERROR_CANCELLED, |
| 27 | APC_COMPAT_ERROR_IGNORED, APC_COMPAT_ERROR_OK, APC_COMPAT_ERROR_OPERATION_PENDING, |
| 28 | APC_COMPAT_ERROR_SYSTEM_ERROR, INVALID_SERVICE_HANDLE, |
| 29 | }; |
| 30 | use std::{ffi::CString, slice}; |
| 31 | |
| 32 | /// Safe wrapper around the ConfirmationUI HIDL spec. |
| 33 | /// |
| 34 | /// # Example |
| 35 | /// ``` |
| 36 | /// struct Cb(); |
| 37 | /// impl ApcHalCallback for Cb { |
| 38 | /// fn result( |
| 39 | /// &self, |
| 40 | /// rc: u32, |
| 41 | /// message: Option<&[u8]>, |
| 42 | /// token: Option<&[u8]>, |
| 43 | /// ) { |
| 44 | /// println!("Callback called with rc: {}, message: {}, token: {}", rc, message, token); |
| 45 | /// } |
| 46 | /// }; |
| 47 | /// |
| 48 | /// fn prompt() -> Result<(), u32> { |
| 49 | /// let hal = ApcHal::try_get_service()?; |
| 50 | /// hal.prompt_user_confirmation(Box::new(Cb()), "Do you agree?", b"extra data", "en", 0)?; |
| 51 | /// } |
| 52 | /// |
| 53 | /// ``` |
| 54 | pub struct ApcHal(ApcCompatServiceHandle); |
| 55 | |
| 56 | unsafe impl Send for ApcHal {} |
| 57 | unsafe impl Sync for ApcHal {} |
| 58 | |
| 59 | impl Drop for ApcHal { |
| 60 | fn drop(&mut self) { |
| 61 | // # Safety: |
| 62 | // This ends the life cycle of the contained `ApcCompatServiceHandle` owned by this |
| 63 | // `ApcHal` object. |
| 64 | // |
| 65 | // `ApcHal` objects are only created if a valid handle was acquired so self.0 is |
| 66 | // always valid when dropped. |
| 67 | unsafe { |
| 68 | closeUserConfirmationService(self.0); |
| 69 | } |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | type Callback = dyn FnOnce(u32, Option<&[u8]>, Option<&[u8]>); |
| 74 | |
| 75 | extern "C" fn confirmation_result_callback( |
| 76 | handle: *mut ::std::os::raw::c_void, |
| 77 | rc: u32, |
| 78 | tbs_message: *const u8, |
Jeff Vander Stoep | 76c0f28 | 2022-12-07 11:39:27 +0100 | [diff] [blame] | 79 | tbs_message_size: usize, |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 80 | confirmation_token: *const u8, |
Jeff Vander Stoep | 76c0f28 | 2022-12-07 11:39:27 +0100 | [diff] [blame] | 81 | confirmation_token_size: usize, |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 82 | ) { |
| 83 | // # Safety: |
| 84 | // The C/C++ implementation must pass to us the handle that was created |
| 85 | // and assigned to the `ApcCompatCallback::data` field in |
| 86 | // `ApcHal::prompt_user_confirmation` below. Also we consume the handle, |
| 87 | // by letting `hal_cb` go out of scope with this function call. So |
| 88 | // the C/C++ implementation must assure that each `ApcCompatCallback` is only used once. |
| 89 | let hal_cb: Box<Box<Callback>> = unsafe { Box::from_raw(handle as *mut Box<Callback>) }; |
| 90 | let tbs_message = match (tbs_message.is_null(), tbs_message_size) { |
| 91 | (true, _) | (_, 0) => None, |
| 92 | (false, s) => Some( |
| 93 | // # Safety: |
| 94 | // If the pointer and size is not nullptr and not 0 respectively, the C/C++ |
| 95 | // implementation must pass a valid pointer to an allocation of at least size bytes, |
| 96 | // and the pointer must be valid until this function returns. |
| 97 | unsafe { slice::from_raw_parts(tbs_message, s as usize) }, |
| 98 | ), |
| 99 | }; |
| 100 | let confirmation_token = match (confirmation_token.is_null(), confirmation_token_size) { |
| 101 | (true, _) | (_, 0) => None, |
| 102 | (false, s) => Some( |
| 103 | // # Safety: |
| 104 | // If the pointer and size is not nullptr and not 0 respectively, the C/C++ |
| 105 | // implementation must pass a valid pointer to an allocation of at least size bytes, |
| 106 | // and the pointer must be valid until this function returns. |
| 107 | unsafe { slice::from_raw_parts(confirmation_token, s as usize) }, |
| 108 | ), |
| 109 | }; |
| 110 | hal_cb(rc, tbs_message, confirmation_token) |
| 111 | } |
| 112 | |
| 113 | impl ApcHal { |
| 114 | /// Attempts to connect to the APC (confirmationui) backend. On success, it returns an |
| 115 | /// initialized `ApcHal` object. |
| 116 | pub fn try_get_service() -> Option<Self> { |
| 117 | // # Safety: |
| 118 | // `tryGetUserConfirmationService` returns a valid handle or INVALID_SERVICE_HANDLE. |
| 119 | // On success, `ApcHal` takes ownership of this handle and frees it with |
| 120 | // `closeUserConfirmationService` when dropped. |
| 121 | let handle = unsafe { tryGetUserConfirmationService() }; |
| 122 | match handle { |
| 123 | h if h == unsafe { INVALID_SERVICE_HANDLE } => None, |
| 124 | h => Some(Self(h)), |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | /// Attempts to start a confirmation prompt. The given callback is consumed, and it is |
| 129 | /// guaranteed to be called eventually IFF this function returns `APC_COMPAT_ERROR_OK`. |
| 130 | /// |
| 131 | /// The callback has the following arguments: |
| 132 | /// rc: u32 - The reason for the termination which takes one of the values. |
| 133 | /// * `APC_COMPAT_ERROR_OK` - The user confirmed the prompted message. |
| 134 | /// * `APC_COMPAT_ERROR_CANCELLED` - The user rejected the prompted message. |
| 135 | /// * `APC_COMPAT_ERROR_ABORTED` - The prompt was aborted either because the client |
| 136 | /// aborted. the session or an asynchronous system event occurred that ended the |
| 137 | /// prompt prematurely. |
| 138 | /// * `APC_COMPAT_ERROR_SYSTEMERROR` - An unspecified system error occurred. Logs may |
| 139 | /// have more information. |
| 140 | /// |
| 141 | /// data_confirmed: Option<&[u8]> and |
| 142 | /// confirmation_token: Option<&[u8]> hold the confirmed message and the confirmation token |
| 143 | /// respectively. They must be `Some()` if `rc == APC_COMPAT_ERROR_OK` and `None` otherwise. |
Janis Danisevskis | b1673db | 2021-02-08 18:11:57 -0800 | [diff] [blame] | 144 | /// |
| 145 | /// `cb` does not get called if this function returns an error. |
| 146 | /// (Thus the allow(unused_must_use)) |
| 147 | #[allow(unused_must_use)] |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 148 | pub fn prompt_user_confirmation<F>( |
| 149 | &self, |
| 150 | prompt_text: &str, |
| 151 | extra_data: &[u8], |
| 152 | locale: &str, |
| 153 | ui_opts: ApcCompatUiOptions, |
| 154 | cb: F, |
| 155 | ) -> Result<(), u32> |
| 156 | where |
Janis Danisevskis | b1673db | 2021-02-08 18:11:57 -0800 | [diff] [blame] | 157 | F: FnOnce(u32, Option<&[u8]>, Option<&[u8]>) + 'static, |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 158 | { |
Janis Danisevskis | b1673db | 2021-02-08 18:11:57 -0800 | [diff] [blame] | 159 | let cb_data_ptr = Box::into_raw(Box::new(Box::new(cb) as Box<Callback>)); |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 160 | let cb = ApcCompatCallback { |
| 161 | data: cb_data_ptr as *mut std::ffi::c_void, |
| 162 | result: Some(confirmation_result_callback), |
| 163 | }; |
| 164 | let prompt_text = CString::new(prompt_text).unwrap(); |
| 165 | let locale = CString::new(locale).unwrap(); |
| 166 | // # Safety: |
| 167 | // The `ApcCompatCallback` object (`cb`) is passed to the callee by value, and with it |
| 168 | // ownership of the `data` field pointer. The data pointer is guaranteed to be valid |
| 169 | // until the C/C++ implementation calls the callback. Calling the callback consumes |
| 170 | // the data pointer. The C/C++ implementation must not access it after calling the |
| 171 | // callback and it must not call the callback a second time. |
| 172 | // |
| 173 | // The C/C++ must make no assumptions about the life time of the other parameters after |
| 174 | // the function returns. |
| 175 | let rc = unsafe { |
| 176 | promptUserConfirmation( |
| 177 | self.0, |
| 178 | cb, |
| 179 | prompt_text.as_ptr(), |
| 180 | extra_data.as_ptr(), |
Jeff Vander Stoep | 76c0f28 | 2022-12-07 11:39:27 +0100 | [diff] [blame] | 181 | extra_data.len() as usize, |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 182 | locale.as_ptr(), |
| 183 | ui_opts, |
| 184 | ) |
| 185 | }; |
| 186 | match rc { |
| 187 | APC_COMPAT_ERROR_OK => Ok(()), |
| 188 | rc => { |
| 189 | // # Safety: |
| 190 | // If promptUserConfirmation does not succeed, it must not take ownership of the |
| 191 | // callback, so we must destroy it. |
| 192 | unsafe { Box::from_raw(cb_data_ptr) }; |
| 193 | Err(rc) |
| 194 | } |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | /// Aborts a running confirmation session, or no-op if none is running. |
| 199 | pub fn abort(&self) { |
| 200 | // # Safety: |
| 201 | // It is always safe to call `abortUserConfirmation`, because spurious calls are ignored. |
| 202 | // The handle argument must be valid, but this is an invariant of `ApcHal`. |
| 203 | unsafe { abortUserConfirmation(self.0) } |
| 204 | } |
| 205 | } |