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