1 //! Authorization Services support. 2 3 /// # Potential improvements 4 /// 5 /// * When generic specialization stabilizes prevent copying from CString 6 /// arguments. 7 /// * AuthorizationCopyRightsAsync 8 /// * Provide constants for well known item names 9 use crate::base::{Error, Result}; 10 use core_foundation::base::{CFTypeRef, TCFType}; 11 use core_foundation::bundle::CFBundleRef; 12 use core_foundation::dictionary::{CFDictionary, CFDictionaryRef}; 13 use core_foundation::string::{CFString, CFStringRef}; 14 use security_framework_sys::authorization as sys; 15 use security_framework_sys::base::errSecConversionError; 16 use std::mem::MaybeUninit; 17 use std::os::raw::c_void; 18 use std::{ 19 convert::TryFrom, 20 ffi::{CStr, CString}, 21 }; 22 use std::{convert::TryInto, marker::PhantomData}; 23 use sys::AuthorizationExternalForm; 24 25 macro_rules! optional_str_to_cfref { 26 ($string:ident) => {{ 27 $string 28 .map(CFString::new) 29 .map_or(std::ptr::null(), |cfs| cfs.as_concrete_TypeRef()) 30 }}; 31 } 32 33 macro_rules! cstring_or_err { 34 ($x:expr) => {{ 35 CString::new($x).map_err(|_| Error::from_code(errSecConversionError)) 36 }}; 37 } 38 39 bitflags::bitflags! { 40 /// The flags used to specify authorization options. 41 pub struct Flags: sys::AuthorizationFlags { 42 /// An empty flag set that you use as a placeholder when you don't want 43 /// any of the other flags. 44 const DEFAULTS = sys::kAuthorizationFlagDefaults; 45 46 /// A flag that permits user interaction as needed. 47 const INTERACTION_ALLOWED = sys::kAuthorizationFlagInteractionAllowed; 48 49 /// A flag that permits the Security Server to attempt to grant the 50 /// rights requested. 51 const EXTEND_RIGHTS = sys::kAuthorizationFlagExtendRights; 52 53 /// A flag that permits the Security Server to grant rights on an 54 /// individual basis. 55 const PARTIAL_RIGHTS = sys::kAuthorizationFlagPartialRights; 56 57 /// A flag that instructs the Security Server to revoke authorization. 58 const DESTROY_RIGHTS = sys::kAuthorizationFlagDestroyRights; 59 60 /// A flag that instructs the Security Server to preauthorize the rights 61 /// requested. 62 const PREAUTHORIZE = sys::kAuthorizationFlagPreAuthorize; 63 } 64 } 65 66 impl Default for Flags { 67 #[inline(always)] default() -> Flags68 fn default() -> Flags { 69 Flags::DEFAULTS 70 } 71 } 72 73 /// Information about an authorization right or the environment. 74 #[repr(C)] 75 pub struct AuthorizationItem(sys::AuthorizationItem); 76 77 impl AuthorizationItem { 78 /// The required name of the authorization right or environment data. 79 /// 80 /// If `name` isn't convertable to a `CString` it will return 81 /// Err(errSecConversionError). name(&self) -> &str82 pub fn name(&self) -> &str { 83 unsafe { 84 CStr::from_ptr(self.0.name) 85 .to_str() 86 .expect("AuthorizationItem::name failed to convert &str to CStr") 87 } 88 } 89 90 /// The information pertaining to the name field. Do not rely on NULL 91 /// termination of string data. value(&self) -> Option<&[u8]>92 pub fn value(&self) -> Option<&[u8]> { 93 if self.0.value.is_null() { 94 return None; 95 } 96 97 let value = 98 unsafe { std::slice::from_raw_parts(self.0.value as *const u8, self.0.valueLength) }; 99 100 Some(value) 101 } 102 } 103 104 /// A set of authorization items returned and owned by the Security Server. 105 #[derive(Debug)] 106 #[repr(C)] 107 pub struct AuthorizationItemSet<'a> { 108 inner: *const sys::AuthorizationItemSet, 109 phantom: PhantomData<&'a sys::AuthorizationItemSet>, 110 } 111 112 impl<'a> Drop for AuthorizationItemSet<'a> { 113 #[inline] drop(&mut self)114 fn drop(&mut self) { 115 unsafe { 116 sys::AuthorizationFreeItemSet(self.inner as *mut sys::AuthorizationItemSet); 117 } 118 } 119 } 120 121 /// Used by `AuthorizationItemSetBuilder` to store data pointed to by 122 /// `sys::AuthorizationItemSet`. 123 #[derive(Debug)] 124 pub struct AuthorizationItemSetStorage { 125 /// The layout of this is a little awkward because of the requirements of 126 /// Apple's APIs. `items` contains pointers to data owned by `names` and 127 /// `values`, so we must not modify them once `items` has been set up. 128 names: Vec<CString>, 129 values: Vec<Option<Vec<u8>>>, 130 items: Vec<sys::AuthorizationItem>, 131 132 /// Must not be given to APIs which would attempt to modify it. 133 /// 134 /// See `AuthorizationItemSet` for sets owned by the Security Server which 135 /// are writable. 136 pub set: sys::AuthorizationItemSet, 137 } 138 139 impl Default for AuthorizationItemSetStorage { 140 #[inline] default() -> Self141 fn default() -> Self { 142 AuthorizationItemSetStorage { 143 names: Vec::new(), 144 values: Vec::new(), 145 items: Vec::new(), 146 set: sys::AuthorizationItemSet { 147 count: 0, 148 items: std::ptr::null_mut(), 149 }, 150 } 151 } 152 } 153 154 /// A convenience `AuthorizationItemSetBuilder` builder which enabled you to use 155 /// rust types. All names and values passed in will be copied. 156 #[derive(Debug, Default)] 157 pub struct AuthorizationItemSetBuilder { 158 storage: AuthorizationItemSetStorage, 159 } 160 161 // Stores AuthorizationItems contiguously, and their items separately 162 impl AuthorizationItemSetBuilder { 163 /// Creates a new `AuthorizationItemSetStore`, which simplifies creating 164 /// owned vectors of `AuthorizationItem`s. 165 #[inline(always)] new() -> AuthorizationItemSetBuilder166 pub fn new() -> AuthorizationItemSetBuilder { 167 Default::default() 168 } 169 170 /// Adds an AuthorizationItem with the name set to a right and an empty 171 /// value. 172 /// 173 /// If `name` isn't convertable to a `CString` it will return 174 /// Err(errSecConversionError). add_right<N: Into<Vec<u8>>>(mut self, name: N) -> Result<Self>175 pub fn add_right<N: Into<Vec<u8>>>(mut self, name: N) -> Result<Self> { 176 self.storage.names.push(cstring_or_err!(name)?); 177 self.storage.values.push(None); 178 Ok(self) 179 } 180 181 /// Adds an AuthorizationItem with arbitrary data. 182 /// 183 /// If `name` isn't convertable to a `CString` it will return 184 /// Err(errSecConversionError). add_data<N, V>(mut self, name: N, value: V) -> Result<Self> where N: Into<Vec<u8>>, V: Into<Vec<u8>>,185 pub fn add_data<N, V>(mut self, name: N, value: V) -> Result<Self> 186 where 187 N: Into<Vec<u8>>, 188 V: Into<Vec<u8>>, 189 { 190 self.storage.names.push(cstring_or_err!(name)?); 191 self.storage.values.push(Some(value.into())); 192 Ok(self) 193 } 194 195 /// Adds an AuthorizationItem with NULL terminated string data. 196 /// 197 /// If `name` or `value` isn't convertable to a `CString` it will return 198 /// Err(errSecConversionError). add_string<N, V>(mut self, name: N, value: V) -> Result<Self> where N: Into<Vec<u8>>, V: Into<Vec<u8>>,199 pub fn add_string<N, V>(mut self, name: N, value: V) -> Result<Self> 200 where 201 N: Into<Vec<u8>>, 202 V: Into<Vec<u8>>, 203 { 204 self.storage.names.push(cstring_or_err!(name)?); 205 self.storage 206 .values 207 .push(Some(cstring_or_err!(value)?.to_bytes().to_vec())); 208 Ok(self) 209 } 210 211 /// Creates the `sys::AuthorizationItemSet`, and gives you ownership of the 212 /// data it points to. build(mut self) -> AuthorizationItemSetStorage213 pub fn build(mut self) -> AuthorizationItemSetStorage { 214 self.storage.items = self 215 .storage 216 .names 217 .iter() 218 .zip(self.storage.values.iter()) 219 .map(|(n, v)| sys::AuthorizationItem { 220 name: n.as_ptr(), 221 value: v 222 .as_ref() 223 .map_or(std::ptr::null_mut(), |v| v.as_ptr() as *mut c_void), 224 valueLength: v.as_ref().map_or(0, |v| v.len()), 225 flags: 0, 226 }) 227 .collect(); 228 229 self.storage.set = sys::AuthorizationItemSet { 230 count: self.storage.items.len() as u32, 231 items: self.storage.items.as_ptr() as *mut sys::AuthorizationItem, 232 }; 233 234 self.storage 235 } 236 } 237 238 /// Used by `Authorization::set_item` to define the rules of he right. 239 pub enum RightDefinition<'a> { 240 /// The dictionary will contain the keys and values that define the rules. 241 FromDictionary(&'a CFDictionary<CFStringRef, CFTypeRef>), 242 243 /// The specified right's rules will be duplicated. 244 FromExistingRight(&'a str), 245 } 246 247 /// A wrapper around AuthorizationCreate and functions which operate on an 248 /// AuthorizationRef. 249 #[derive(Debug)] 250 pub struct Authorization { 251 handle: sys::AuthorizationRef, 252 free_flags: Flags, 253 } 254 255 impl TryFrom<AuthorizationExternalForm> for Authorization { 256 type Error = Error; 257 258 /// Internalizes the external representation of an authorization reference. 259 #[cold] try_from(external_form: AuthorizationExternalForm) -> Result<Self>260 fn try_from(external_form: AuthorizationExternalForm) -> Result<Self> { 261 let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit(); 262 263 let status = unsafe { 264 sys::AuthorizationCreateFromExternalForm(&external_form, handle.as_mut_ptr()) 265 }; 266 267 if status != sys::errAuthorizationSuccess { 268 return Err(Error::from_code(status)); 269 } 270 271 let auth = Authorization { 272 handle: unsafe { handle.assume_init() }, 273 free_flags: Default::default(), 274 }; 275 276 Ok(auth) 277 } 278 } 279 280 impl<'a> Authorization { 281 /// Creates an authorization object which has no environment or associated 282 /// rights. 283 #[inline] default() -> Result<Self>284 pub fn default() -> Result<Self> { 285 Self::new(None, None, Default::default()) 286 } 287 288 /// Creates an authorization reference and provides an option to authorize 289 /// or preauthorize rights. 290 /// 291 /// `rights` should be the names of the rights you want to create. 292 /// 293 /// `environment` is used when authorizing or preauthorizing rights. Not 294 /// used in OS X v10.2 and earlier. In macOS 10.3 and later, you can pass 295 /// icon or prompt data to be used in the authentication dialog box. In 296 /// macOS 10.4 and later, you can also pass a user name and password in 297 /// order to authorize a user without user interaction. new( rights: Option<AuthorizationItemSetStorage>, environment: Option<AuthorizationItemSetStorage>, flags: Flags, ) -> Result<Self>298 pub fn new( 299 rights: Option<AuthorizationItemSetStorage>, 300 environment: Option<AuthorizationItemSetStorage>, 301 flags: Flags, 302 ) -> Result<Self> { 303 let rights_ptr = rights.as_ref().map_or(std::ptr::null(), |r| { 304 &r.set as *const sys::AuthorizationItemSet 305 }); 306 307 let env_ptr = environment.as_ref().map_or(std::ptr::null(), |e| { 308 &e.set as *const sys::AuthorizationItemSet 309 }); 310 311 let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit(); 312 313 let status = unsafe { 314 sys::AuthorizationCreate(rights_ptr, env_ptr, flags.bits(), handle.as_mut_ptr()) 315 }; 316 317 if status != sys::errAuthorizationSuccess { 318 return Err(Error::from_code(status)); 319 } 320 321 Ok(Authorization { 322 handle: unsafe { handle.assume_init() }, 323 free_flags: Default::default(), 324 }) 325 } 326 327 /// Internalizes the external representation of an authorization reference. 328 #[deprecated(since = "2.0.1", note = "Please use the TryFrom trait instead")] from_external_form(external_form: sys::AuthorizationExternalForm) -> Result<Self>329 pub fn from_external_form(external_form: sys::AuthorizationExternalForm) -> Result<Self> { 330 external_form.try_into() 331 } 332 333 /// By default the rights acquired will be retained by the Security Server. 334 /// Use this to ensure they are destroyed and to prevent shared rights' 335 /// continued used by other processes. 336 #[inline(always)] destroy_rights(mut self)337 pub fn destroy_rights(mut self) { 338 self.free_flags = Flags::DESTROY_RIGHTS; 339 } 340 341 /// Retrieve's the right's definition as a dictionary. Use `right_exists` 342 /// if you want to avoid retrieving the dictionary. 343 /// 344 /// `name` can be a wildcard right name. 345 /// 346 /// If `name` isn't convertable to a `CString` it will return 347 /// Err(errSecConversionError). get_right<T: Into<Vec<u8>>>(name: T) -> Result<CFDictionary<CFString, CFTypeRef>>348 pub fn get_right<T: Into<Vec<u8>>>(name: T) -> Result<CFDictionary<CFString, CFTypeRef>> { 349 let name = cstring_or_err!(name)?; 350 let mut dict = MaybeUninit::<CFDictionaryRef>::uninit(); 351 352 let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), dict.as_mut_ptr()) }; 353 354 if status != sys::errAuthorizationSuccess { 355 return Err(Error::from_code(status)); 356 } 357 358 let dict = unsafe { CFDictionary::wrap_under_create_rule(dict.assume_init()) }; 359 360 Ok(dict) 361 } 362 363 /// Checks if a right exists within the policy database. This is the same as 364 /// `get_right`, but avoids a dictionary allocation. 365 /// 366 /// If `name` isn't convertable to a `CString` it will return 367 /// Err(errSecConversionError). right_exists<T: Into<Vec<u8>>>(name: T) -> Result<bool>368 pub fn right_exists<T: Into<Vec<u8>>>(name: T) -> Result<bool> { 369 let name = cstring_or_err!(name)?; 370 371 let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), std::ptr::null_mut()) }; 372 373 Ok(status == sys::errAuthorizationSuccess) 374 } 375 376 /// Removes a right from the policy database. 377 /// 378 /// `name` cannot be a wildcard right name. 379 /// 380 /// If `name` isn't convertable to a `CString` it will return 381 /// Err(errSecConversionError). remove_right<T: Into<Vec<u8>>>(&self, name: T) -> Result<()>382 pub fn remove_right<T: Into<Vec<u8>>>(&self, name: T) -> Result<()> { 383 let name = cstring_or_err!(name)?; 384 385 let status = unsafe { sys::AuthorizationRightRemove(self.handle, name.as_ptr()) }; 386 387 if status != sys::errAuthorizationSuccess { 388 return Err(Error::from_code(status)); 389 } 390 391 Ok(()) 392 } 393 394 /// Creates or updates a right entry in the policy database. Your process 395 /// must have a code signature in order to be able to add rights to the 396 /// authorization database. 397 /// 398 /// `name` cannot be a wildcard right. 399 /// 400 /// `definition` can be either a `CFDictionaryRef` containing keys defining 401 /// the rules or a `CFStringRef` representing the name of another right 402 /// whose rules you wish to duplicaate. 403 /// 404 /// `description` is a key which can be used to look up localized 405 /// descriptions. 406 /// 407 /// `bundle` will be used to get localizations from if not the main bundle. 408 /// 409 /// `localeTableName` will be used to get localizations if provided. 410 /// 411 /// If `name` isn't convertable to a `CString` it will return 412 /// Err(errSecConversionError). set_right<T: Into<Vec<u8>>>( &self, name: T, definition: RightDefinition<'_>, description: Option<&str>, bundle: Option<CFBundleRef>, locale: Option<&str>, ) -> Result<()>413 pub fn set_right<T: Into<Vec<u8>>>( 414 &self, 415 name: T, 416 definition: RightDefinition<'_>, 417 description: Option<&str>, 418 bundle: Option<CFBundleRef>, 419 locale: Option<&str>, 420 ) -> Result<()> { 421 let name = cstring_or_err!(name)?; 422 423 let definition_cfstring: CFString; 424 let definition_ref = match definition { 425 RightDefinition::FromDictionary(def) => def.as_CFTypeRef(), 426 RightDefinition::FromExistingRight(def) => { 427 definition_cfstring = CFString::new(def); 428 definition_cfstring.as_CFTypeRef() 429 } 430 }; 431 432 let status = unsafe { 433 sys::AuthorizationRightSet( 434 self.handle, 435 name.as_ptr(), 436 definition_ref, 437 optional_str_to_cfref!(description), 438 bundle.unwrap_or(std::ptr::null_mut()), 439 optional_str_to_cfref!(locale), 440 ) 441 }; 442 443 if status != sys::errAuthorizationSuccess { 444 return Err(Error::from_code(status)); 445 } 446 447 Ok(()) 448 } 449 450 /// An authorization plugin can store the results of an authentication 451 /// operation by calling the `SetContextValue` function. You can then 452 /// retrieve this supporting data, such as the user name. 453 /// 454 /// `tag` should specify the type of data the Security Server should return. 455 /// If `None`, all available information is retreieved. 456 /// 457 /// If `tag` isn't convertable to a `CString` it will return 458 /// Err(errSecConversionError). copy_info<T: Into<Vec<u8>>>(&self, tag: Option<T>) -> Result<AuthorizationItemSet<'_>>459 pub fn copy_info<T: Into<Vec<u8>>>(&self, tag: Option<T>) -> Result<AuthorizationItemSet<'_>> { 460 let tag_with_nul: CString; 461 462 let tag_ptr = match tag { 463 Some(tag) => { 464 tag_with_nul = cstring_or_err!(tag)?; 465 tag_with_nul.as_ptr() 466 } 467 None => std::ptr::null(), 468 }; 469 470 let mut inner = MaybeUninit::<*mut sys::AuthorizationItemSet>::uninit(); 471 472 let status = 473 unsafe { sys::AuthorizationCopyInfo(self.handle, tag_ptr, inner.as_mut_ptr()) }; 474 475 if status != sys::errAuthorizationSuccess { 476 return Err(Error::from(status)); 477 } 478 479 let set = AuthorizationItemSet { 480 inner: unsafe { inner.assume_init() }, 481 phantom: PhantomData, 482 }; 483 484 Ok(set) 485 } 486 487 /// Creates an external representation of an authorization reference so that 488 /// you can transmit it between processes. make_external_form(&self) -> Result<sys::AuthorizationExternalForm>489 pub fn make_external_form(&self) -> Result<sys::AuthorizationExternalForm> { 490 let mut external_form = MaybeUninit::<sys::AuthorizationExternalForm>::uninit(); 491 492 let status = 493 unsafe { sys::AuthorizationMakeExternalForm(self.handle, external_form.as_mut_ptr()) }; 494 495 if status != sys::errAuthorizationSuccess { 496 return Err(Error::from(status)); 497 } 498 499 Ok(unsafe { external_form.assume_init() }) 500 } 501 } 502 503 impl Drop for Authorization { 504 #[inline] drop(&mut self)505 fn drop(&mut self) { 506 unsafe { 507 sys::AuthorizationFree(self.handle, self.free_flags.bits()); 508 } 509 } 510 } 511 512 #[cfg(test)] 513 mod tests { 514 use super::*; 515 use core_foundation::string::CFString; 516 517 #[test] test_create_default_authorization()518 fn test_create_default_authorization() { 519 Authorization::default().unwrap(); 520 } 521 522 #[test] test_create_allowed_authorization() -> Result<()>523 fn test_create_allowed_authorization() -> Result<()> { 524 let rights = AuthorizationItemSetBuilder::new() 525 .add_right("system.hdd.smart")? 526 .add_right("system.login.done")? 527 .build(); 528 529 Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap(); 530 531 Ok(()) 532 } 533 534 #[test] test_create_then_destroy_allowed_authorization() -> Result<()>535 fn test_create_then_destroy_allowed_authorization() -> Result<()> { 536 let rights = AuthorizationItemSetBuilder::new() 537 .add_right("system.hdd.smart")? 538 .add_right("system.login.done")? 539 .build(); 540 541 let auth = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap(); 542 auth.destroy_rights(); 543 544 Ok(()) 545 } 546 547 #[test] test_create_authorization_requiring_interaction() -> Result<()>548 fn test_create_authorization_requiring_interaction() -> Result<()> { 549 let rights = AuthorizationItemSetBuilder::new() 550 .add_right("system.privilege.admin")? 551 .build(); 552 553 let error = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap_err(); 554 555 assert_eq!(error.code(), sys::errAuthorizationInteractionNotAllowed); 556 557 Ok(()) 558 } 559 create_credentials_env() -> Result<AuthorizationItemSetStorage>560 fn create_credentials_env() -> Result<AuthorizationItemSetStorage> { 561 let set = AuthorizationItemSetBuilder::new() 562 .add_string( 563 "username", 564 option_env!("USER").expect("You must set the USER environment variable"), 565 )? 566 .add_string( 567 "password", 568 option_env!("PASSWORD").expect("You must set the PASSWORD environment varible"), 569 )? 570 .build(); 571 572 Ok(set) 573 } 574 575 #[test] test_create_authorization_with_bad_credentials() -> Result<()>576 fn test_create_authorization_with_bad_credentials() -> Result<()> { 577 let rights = AuthorizationItemSetBuilder::new() 578 .add_right("system.privilege.admin")? 579 .build(); 580 581 let env = AuthorizationItemSetBuilder::new() 582 .add_string("username", "Tim Apple")? 583 .add_string("password", "butterfly")? 584 .build(); 585 586 let error = 587 Authorization::new(Some(rights), Some(env), Flags::INTERACTION_ALLOWED).unwrap_err(); 588 589 assert_eq!(error.code(), sys::errAuthorizationDenied); 590 591 Ok(()) 592 } 593 594 #[test] 595 #[ignore] test_create_authorization_with_credentials() -> Result<()>596 fn test_create_authorization_with_credentials() -> Result<()> { 597 let rights = AuthorizationItemSetBuilder::new() 598 .add_right("system.privilege.admin")? 599 .build(); 600 601 let env = create_credentials_env()?; 602 603 Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap(); 604 605 Ok(()) 606 } 607 608 #[test] test_query_authorization_database() -> Result<()>609 fn test_query_authorization_database() -> Result<()> { 610 assert!(Authorization::right_exists("system.hdd.smart")?); 611 assert!(!Authorization::right_exists("EMPTY")?); 612 613 let dict = Authorization::get_right("system.hdd.smart").unwrap(); 614 615 let key = CFString::from_static_string("class"); 616 assert!(dict.contains_key(&key)); 617 618 let invalid_key = CFString::from_static_string("EMPTY"); 619 assert!(!dict.contains_key(&invalid_key)); 620 621 Ok(()) 622 } 623 624 /// This test will only pass if its process has a valid code signature. 625 #[test] 626 #[ignore] test_modify_authorization_database() -> Result<()>627 fn test_modify_authorization_database() -> Result<()> { 628 let rights = AuthorizationItemSetBuilder::new() 629 .add_right("config.modify.")? 630 .build(); 631 632 let env = create_credentials_env()?; 633 634 let auth = Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap(); 635 636 assert!(!Authorization::right_exists("TEST_RIGHT")?); 637 638 auth.set_right( 639 "TEST_RIGHT", 640 RightDefinition::FromExistingRight("system.hdd.smart"), 641 None, 642 None, 643 None, 644 ) 645 .unwrap(); 646 647 assert!(Authorization::right_exists("TEST_RIGHT")?); 648 649 auth.remove_right("TEST_RIGHT").unwrap(); 650 651 assert!(!Authorization::right_exists("TEST_RIGHT")?); 652 653 Ok(()) 654 } 655 } 656