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