summaryrefslogtreecommitdiffstats
path: root/vendor/security-framework/src/passwords.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/security-framework/src/passwords.rs')
-rw-r--r--vendor/security-framework/src/passwords.rs332
1 files changed, 332 insertions, 0 deletions
diff --git a/vendor/security-framework/src/passwords.rs b/vendor/security-framework/src/passwords.rs
new file mode 100644
index 000000000..83dad6d28
--- /dev/null
+++ b/vendor/security-framework/src/passwords.rs
@@ -0,0 +1,332 @@
+//! Support for password entries in the keychain. Works on both iOS and macOS.
+//!
+//! If you want the extended keychain facilities only available on macOS, use the
+//! version of these functions in the macOS extensions module.
+
+use crate::base::Result;
+use crate::passwords_options::PasswordOptions;
+use crate::{cvt, Error};
+use core_foundation::base::TCFType;
+use core_foundation::boolean::CFBoolean;
+use core_foundation::data::CFData;
+use core_foundation::dictionary::CFDictionary;
+use core_foundation::string::CFString;
+use core_foundation_sys::base::{CFGetTypeID, CFRelease, CFTypeRef};
+use core_foundation_sys::data::CFDataRef;
+use security_framework_sys::base::{errSecDuplicateItem, errSecParam};
+use security_framework_sys::item::{kSecReturnData, kSecValueData};
+use security_framework_sys::keychain::{SecAuthenticationType, SecProtocolType};
+use security_framework_sys::keychain_item::{
+ SecItemAdd, SecItemCopyMatching, SecItemDelete, SecItemUpdate,
+};
+
+/// Set a generic password for the given service and account.
+/// Creates or updates a keychain entry.
+pub fn set_generic_password(service: &str, account: &str, password: &[u8]) -> Result<()> {
+ let mut options = PasswordOptions::new_generic_password(service, account);
+ set_password_internal(&mut options, password)
+}
+
+/// Get the generic password for the given service and account. If no matching
+/// keychain entry exists, fails with error code `errSecItemNotFound`.
+pub fn get_generic_password(service: &str, account: &str) -> Result<Vec<u8>> {
+ let mut options = PasswordOptions::new_generic_password(service, account);
+ options.query.push((
+ unsafe { CFString::wrap_under_get_rule(kSecReturnData) },
+ CFBoolean::from(true).into_CFType(),
+ ));
+ let params = CFDictionary::from_CFType_pairs(&options.query);
+ let mut ret: CFTypeRef = std::ptr::null();
+ cvt(unsafe { SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret) })?;
+ get_password_and_release(ret)
+}
+
+/// Delete the generic password keychain entry for the given service and account.
+/// If none exists, fails with error code `errSecItemNotFound`.
+pub fn delete_generic_password(service: &str, account: &str) -> Result<()> {
+ let options = PasswordOptions::new_generic_password(service, account);
+ let params = CFDictionary::from_CFType_pairs(&options.query);
+ cvt(unsafe { SecItemDelete(params.as_concrete_TypeRef()) })
+}
+
+/// Set an internet password for the given endpoint parameters.
+/// Creates or updates a keychain entry.
+#[allow(clippy::too_many_arguments)]
+pub fn set_internet_password(
+ server: &str,
+ security_domain: Option<&str>,
+ account: &str,
+ path: &str,
+ port: Option<u16>,
+ protocol: SecProtocolType,
+ authentication_type: SecAuthenticationType,
+ password: &[u8],
+) -> Result<()> {
+ let mut options = PasswordOptions::new_internet_password(
+ server,
+ security_domain,
+ account,
+ path,
+ port,
+ protocol,
+ authentication_type,
+ );
+ set_password_internal(&mut options, password)
+}
+
+/// Get the internet password for the given endpoint parameters. If no matching
+/// keychain entry exists, fails with error code `errSecItemNotFound`.
+pub fn get_internet_password(
+ server: &str,
+ security_domain: Option<&str>,
+ account: &str,
+ path: &str,
+ port: Option<u16>,
+ protocol: SecProtocolType,
+ authentication_type: SecAuthenticationType,
+) -> Result<Vec<u8>> {
+ let mut options = PasswordOptions::new_internet_password(
+ server,
+ security_domain,
+ account,
+ path,
+ port,
+ protocol,
+ authentication_type,
+ );
+ options.query.push((
+ unsafe { CFString::wrap_under_get_rule(kSecReturnData) },
+ CFBoolean::from(true).into_CFType(),
+ ));
+ let params = CFDictionary::from_CFType_pairs(&options.query);
+ let mut ret: CFTypeRef = std::ptr::null();
+ cvt(unsafe { SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret) })?;
+ get_password_and_release(ret)
+}
+
+/// Delete the internet password for the given endpoint parameters.
+/// If none exists, fails with error code `errSecItemNotFound`.
+pub fn delete_internet_password(
+ server: &str,
+ security_domain: Option<&str>,
+ account: &str,
+ path: &str,
+ port: Option<u16>,
+ protocol: SecProtocolType,
+ authentication_type: SecAuthenticationType,
+) -> Result<()> {
+ let options = PasswordOptions::new_internet_password(
+ server,
+ security_domain,
+ account,
+ path,
+ port,
+ protocol,
+ authentication_type,
+ );
+ let params = CFDictionary::from_CFType_pairs(&options.query);
+ cvt(unsafe { SecItemDelete(params.as_concrete_TypeRef()) })
+}
+
+// This starts by trying to create the password with the given query params.
+// If the creation attempt reveals that one exists, its password is updated.
+fn set_password_internal(options: &mut PasswordOptions, password: &[u8]) -> Result<()> {
+ let query_len = options.query.len();
+ options.query.push((
+ unsafe { CFString::wrap_under_get_rule(kSecValueData) },
+ CFData::from_buffer(password).into_CFType(),
+ ));
+
+ let params = CFDictionary::from_CFType_pairs(&options.query);
+ let mut ret = std::ptr::null();
+ let status = unsafe { SecItemAdd(params.as_concrete_TypeRef(), &mut ret) };
+ if status == errSecDuplicateItem {
+ let params = CFDictionary::from_CFType_pairs(&options.query[0..query_len]);
+ let update = CFDictionary::from_CFType_pairs(&options.query[query_len..]);
+ cvt(unsafe { SecItemUpdate(params.as_concrete_TypeRef(), update.as_concrete_TypeRef()) })
+ } else {
+ cvt(status)
+ }
+}
+
+// Having retrieved a password entry, this copies and returns the password.
+//
+// # Safety
+// The data element passed in is assumed to have been returned from a Copy
+// call, so it's released after we are done with it.
+fn get_password_and_release(data: CFTypeRef) -> Result<Vec<u8>> {
+ if !data.is_null() {
+ let type_id = unsafe { CFGetTypeID(data) };
+ if type_id == CFData::type_id() {
+ let val = unsafe { CFData::wrap_under_create_rule(data as CFDataRef) };
+ let mut vec = Vec::new();
+ vec.extend_from_slice(val.bytes());
+ return Ok(vec);
+ } else {
+ // unexpected: we got a reference to some other type.
+ // Release it to make sure there's no leak, but
+ // we can't return the password in this case.
+ unsafe { CFRelease(data) };
+ }
+ }
+ Err(Error::from_code(errSecParam))
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use security_framework_sys::base::errSecItemNotFound;
+
+ #[test]
+ fn missing_generic() {
+ let name = "a string not likely to already be in the keychain as service or account";
+ let result = delete_generic_password(name, name);
+ match result {
+ Ok(()) => (), // this is ok because the name _might_ be in the keychain
+ Err(err) if err.code() == errSecItemNotFound => (),
+ Err(err) => panic!("missing_generic: delete failed with status: {}", err.code()),
+ };
+ let result = get_generic_password(name, name);
+ match result {
+ Ok(bytes) => panic!("missing_generic: get returned {:?}", bytes),
+ Err(err) if err.code() == errSecItemNotFound => (),
+ Err(err) => panic!("missing_generic: get failed with status: {}", err.code()),
+ };
+ let result = delete_generic_password(name, name);
+ match result {
+ Ok(()) => panic!("missing_generic: second delete found a password"),
+ Err(err) if err.code() == errSecItemNotFound => (),
+ Err(err) => panic!("missing_generic: delete failed with status: {}", err.code()),
+ };
+ }
+
+ #[test]
+ fn roundtrip_generic() {
+ let name = "roundtrip_generic";
+ set_generic_password(name, name, name.as_bytes()).expect("set_generic_password");
+ let pass = get_generic_password(name, name).expect("get_generic_password");
+ assert_eq!(name.as_bytes(), pass);
+ delete_generic_password(name, name).expect("delete_generic_password")
+ }
+
+ #[test]
+ fn update_generic() {
+ let name = "update_generic";
+ set_generic_password(name, name, name.as_bytes()).expect("set_generic_password");
+ let alternate = "update_generic_alternate";
+ set_generic_password(name, name, alternate.as_bytes()).expect("set_generic_password");
+ let pass = get_generic_password(name, name).expect("get_generic_password");
+ assert_eq!(pass, alternate.as_bytes());
+ delete_generic_password(name, name).expect("delete_generic_password")
+ }
+
+ #[test]
+ fn missing_internet() {
+ let name = "a string not likely to already be in the keychain as service or account";
+ let (server, domain, account, path, port, protocol, auth) = (
+ name,
+ None,
+ name,
+ "/",
+ Some(8080u16),
+ SecProtocolType::HTTP,
+ SecAuthenticationType::Any,
+ );
+ let result = delete_internet_password(server, domain, account, path, port, protocol, auth);
+ match result {
+ Ok(()) => (), // this is ok because the name _might_ be in the keychain
+ Err(err) if err.code() == errSecItemNotFound => (),
+ Err(err) => panic!(
+ "missing_internet: delete failed with status: {}",
+ err.code()
+ ),
+ };
+ let result = get_internet_password(server, domain, account, path, port, protocol, auth);
+ match result {
+ Ok(bytes) => panic!("missing_internet: get returned {:?}", bytes),
+ Err(err) if err.code() == errSecItemNotFound => (),
+ Err(err) => panic!("missing_internet: get failed with status: {}", err.code()),
+ };
+ let result = delete_internet_password(server, domain, account, path, port, protocol, auth);
+ match result {
+ Ok(()) => panic!("missing_internet: second delete found a password"),
+ Err(err) if err.code() == errSecItemNotFound => (),
+ Err(err) => panic!(
+ "missing_internet: delete failed with status: {}",
+ err.code()
+ ),
+ };
+ }
+
+ #[test]
+ fn roundtrip_internet() {
+ let name = "roundtrip_internet";
+ let (server, domain, account, path, port, protocol, auth) = (
+ name,
+ None,
+ name,
+ "/",
+ Some(8080u16),
+ SecProtocolType::HTTP,
+ SecAuthenticationType::Any,
+ );
+ set_internet_password(
+ server,
+ domain,
+ account,
+ path,
+ port,
+ protocol,
+ auth,
+ name.as_bytes(),
+ )
+ .expect("set_internet_password");
+ let pass = get_internet_password(server, domain, account, path, port, protocol, auth)
+ .expect("get_internet_password");
+ assert_eq!(name.as_bytes(), pass);
+ delete_internet_password(server, domain, account, path, port, protocol, auth)
+ .expect("delete_internet_password");
+ }
+
+ #[test]
+ fn update_internet() {
+ let name = "update_internet";
+ let (server, domain, account, path, port, protocol, auth) = (
+ name,
+ None,
+ name,
+ "/",
+ Some(8080u16),
+ SecProtocolType::HTTP,
+ SecAuthenticationType::Any,
+ );
+ set_internet_password(
+ server,
+ domain,
+ account,
+ path,
+ port,
+ protocol,
+ auth,
+ name.as_bytes(),
+ )
+ .expect("set_internet_password");
+ let alternate = "alternate_internet_password";
+ set_internet_password(
+ server,
+ domain,
+ account,
+ path,
+ port,
+ protocol,
+ auth,
+ alternate.as_bytes(),
+ )
+ .expect("set_internet_password");
+ let pass = get_internet_password(server, domain, account, path, port, protocol, auth)
+ .expect("get_internet_password");
+ assert_eq!(pass, alternate.as_bytes());
+ delete_internet_password(server, domain, account, path, port, protocol, auth)
+ .expect("delete_internet_password");
+ }
+}