security_framework/
passwords_options.rs

1//! Support for password options, to be used with the passwords module
2
3// NB: re-export these types in the `passwords` module!
4
5use crate::access_control::SecAccessControl;
6use core_foundation::base::{CFOptionFlags, CFType, TCFType};
7#[allow(unused_imports)]
8use core_foundation::boolean::CFBoolean;
9use core_foundation::dictionary::CFDictionary;
10use core_foundation::number::CFNumber;
11use core_foundation::string::{CFString, CFStringRef};
12use security_framework_sys::access_control::*;
13use security_framework_sys::item::{
14    kSecAttrAccessControl, kSecAttrAccessGroup, kSecAttrAccount, kSecAttrAuthenticationType,
15    kSecAttrComment, kSecAttrDescription, kSecAttrLabel,
16    kSecAttrPath, kSecAttrPort, kSecAttrProtocol, kSecAttrSecurityDomain, kSecAttrServer,
17    kSecAttrService, kSecClass, kSecClassGenericPassword, kSecClassInternetPassword,
18};
19#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
20use security_framework_sys::item::kSecAttrSynchronizable;
21#[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
22use security_framework_sys::item::kSecAttrSynchronizableAny;
23#[cfg(any(feature = "OSX_10_15", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
24use security_framework_sys::item::kSecUseDataProtectionKeychain;
25use security_framework_sys::keychain::{SecAuthenticationType, SecProtocolType};
26
27/// `PasswordOptions` constructor
28pub struct PasswordOptions {
29    /// query built for the keychain request
30    #[deprecated(note = "This field should have been private. Please use setters that don't expose CFType")]
31    pub query: Vec<(CFString, CFType)>,
32}
33
34bitflags::bitflags! {
35    /// The option flags used to configure the evaluation of a `SecAccessControl`.
36    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37    pub struct AccessControlOptions: CFOptionFlags {
38        /** Constraint to access an item with either biometry or passcode. */
39        const USER_PRESENCE = kSecAccessControlUserPresence;
40        #[cfg(feature = "OSX_10_13")]
41        /** Constraint to access an item with Touch ID for any enrolled fingers, or Face ID. */
42        const BIOMETRY_ANY = kSecAccessControlBiometryAny;
43        #[cfg(feature = "OSX_10_13")]
44        /** Constraint to access an item with Touch ID for currently enrolled fingers, or from Face ID with the currently enrolled user. */
45        const BIOMETRY_CURRENT_SET = kSecAccessControlBiometryCurrentSet;
46        /** Constraint to access an item with a passcode. */
47        const DEVICE_PASSCODE = kSecAccessControlDevicePasscode;
48        #[cfg(feature = "OSX_10_15")]
49        /** Constraint to access an item with a watch. */
50        const WATCH = kSecAccessControlWatch;
51        /** Indicates that at least one constraint must be satisfied. */
52        const OR = kSecAccessControlOr;
53        /** Indicates that all constraints must be satisfied. */
54        const AND = kSecAccessControlAnd;
55        /** Enable a private key to be used in signing a block of data or verifying a signed block. */
56        const PRIVATE_KEY_USAGE = kSecAccessControlPrivateKeyUsage;
57        /** Option to use an application-provided password for data encryption key generation. */
58        const APPLICATION_PASSWORD = kSecAccessControlApplicationPassword;
59    }
60}
61
62impl PasswordOptions {
63    /// Create a new generic password options
64    /// Generic passwords are identified by service and account.  They have other
65    /// attributes, but this interface doesn't allow specifying them.
66    #[must_use]
67    pub fn new_generic_password(service: &str, account: &str) -> Self {
68        #[allow(deprecated)]
69        Self { query: vec![
70            (
71                unsafe { CFString::wrap_under_get_rule(kSecClass) },
72                unsafe { CFString::wrap_under_get_rule(kSecClassGenericPassword).into_CFType() },
73            ),
74            (unsafe { CFString::wrap_under_get_rule(kSecAttrService) }, CFString::from(service).into_CFType()),
75            (unsafe { CFString::wrap_under_get_rule(kSecAttrAccount) }, CFString::from(account).into_CFType()),
76        ] }
77    }
78
79    /// Create a new internet password options
80    /// Internet passwords are identified by a number of attributes.
81    /// They can have others, but this interface doesn't allow specifying them.
82    #[must_use]
83    pub fn new_internet_password(
84        server: &str,
85        security_domain: Option<&str>,
86        account: &str,
87        path: &str,
88        port: Option<u16>,
89        protocol: SecProtocolType,
90        authentication_type: SecAuthenticationType,
91    ) -> Self {
92        #[allow(deprecated)]
93        let mut this = Self { query: vec![
94            (
95                unsafe { CFString::wrap_under_get_rule(kSecClass) },
96                unsafe { CFString::wrap_under_get_rule(kSecClassInternetPassword) }.into_CFType(),
97            ),
98            (unsafe { CFString::wrap_under_get_rule(kSecAttrServer) }, CFString::from(server).into_CFType()),
99            (unsafe { CFString::wrap_under_get_rule(kSecAttrPath) }, CFString::from(path).into_CFType()),
100            (unsafe { CFString::wrap_under_get_rule(kSecAttrAccount) }, CFString::from(account).into_CFType()),
101            (unsafe { CFString::wrap_under_get_rule(kSecAttrProtocol) }, CFNumber::from(protocol as i32).into_CFType()),
102            (
103                unsafe { CFString::wrap_under_get_rule(kSecAttrAuthenticationType) },
104                CFNumber::from(authentication_type as i32).into_CFType(),
105            ),
106        ] };
107        if let Some(domain) = security_domain {
108            unsafe {
109                this.push_query(kSecAttrSecurityDomain, CFString::from(domain));
110            }
111        }
112        if let Some(port) = port {
113            unsafe {
114                this.push_query(kSecAttrPort, CFNumber::from(i32::from(port)));
115            }
116        }
117        this
118    }
119
120    /// Add access control to the password
121    pub fn set_access_control_options(&mut self, options: AccessControlOptions) {
122        unsafe {
123            self.push_query(kSecAttrAccessControl, SecAccessControl::create_with_flags(options.bits()).unwrap());
124        }
125    }
126
127    /// Add access control to the password
128    pub fn set_access_control(&mut self, access_control: SecAccessControl) {
129        unsafe {
130            self.push_query(kSecAttrAccessControl, access_control);
131        }
132    }
133
134    /// Add access group to the password
135    pub fn set_access_group(&mut self, group: &str) {
136        unsafe {
137            self.push_query(kSecAttrAccessGroup, CFString::from(group));
138        }
139    }
140
141    #[cfg(any(feature = "OSX_10_12", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
142    /// Specify whether password is cloud-synchronized, not cloud-synchronized, or either (`None`).
143    ///
144    /// Note: cloud-synchronized and not-cloud-synchronized passwords are kept
145    /// in completely different stores, so they are uniquely identified not just
146    /// by their `service` and `account` but also their cloud-synchronized option.
147    ///
148    /// If you specify a non-`None` value for this option, any operation you
149    /// perform - whether set, get, or delete - will only affect the store matching
150    /// the value: Some(`true`) will only affect the cloud-synchronized store and
151    /// Some(`false`) will only affect the not-cloud-synchronized store.
152    ///
153    /// If you specify `None` for this option, the effect depends on your operation:
154    ///
155    /// - Performing a delete will delete from both stores.
156    /// - Performing a get will return values from both stores, but since get only
157    ///   returns one value you can't be sure which store that value was in.
158    /// - Performing a set will update existing values in both stores. _But_, before
159    ///   doing any updates, set will first try to create a new value in the
160    ///   not-cloud-synchronized store (interpreting `None` as `false`). If
161    ///   that creation attempt succeeds, no update will be done of any existing
162    ///   value in the cloud-synchronized store. Thus, only if there is an existing
163    ///   value in the not-cloud-synchronized store will set update the
164    ///   cloud-synchronized store.
165    pub fn set_access_synchronized(&mut self, synchronized: Option<bool>) {
166        unsafe {
167            if let Some(synchronizable) = synchronized {
168                self.push_query(kSecAttrSynchronizable, CFBoolean::from(synchronizable));
169            } else {
170                let either = CFString::wrap_under_get_rule(kSecAttrSynchronizableAny);
171                self.push_query(kSecAttrSynchronizable, either);
172            }
173        }
174    }
175
176    /// Set the comment on the password
177    pub fn set_comment(&mut self, comment: &str) {
178        unsafe {
179            self.push_query(kSecAttrComment, CFString::from(comment));
180        }
181    }
182
183    /// Add a description to the password
184    pub fn set_description(&mut self, description: &str) {
185        unsafe {
186            self.push_query(kSecAttrDescription, CFString::from(description));
187        }
188    }
189
190    /// Add a label to the password
191    pub fn set_label(&mut self, label: &str) {
192        unsafe {
193            self.push_query(kSecAttrLabel, CFString::from(label));
194        }
195    }
196
197    #[cfg(any(feature = "OSX_10_15", target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "visionos"))]
198    /// Use the data protection keychain (always true except on macOS)
199    pub fn use_protected_keychain(&mut self) {
200        unsafe {
201            self.push_query(kSecUseDataProtectionKeychain, CFBoolean::from(true));
202        }
203    }
204
205    /// The key must be a `kSec*` constant.
206    /// Value is any owned ObjC object, like `CFString`.
207    pub(crate) unsafe fn push_query(&mut self, static_key_constant: CFStringRef, value: impl TCFType) {
208        #[allow(deprecated)]
209        self.query.push((
210            unsafe { CFString::wrap_under_get_rule(static_key_constant) },
211            value.into_CFType(),
212        ));
213    }
214
215    pub(crate) fn to_dictionary(&self) -> CFDictionary<CFString, CFType> {
216        #[allow(deprecated)]
217        CFDictionary::from_CFType_pairs(&self.query[..])
218    }
219}