1#[doc(inline)]
7pub use crate::passwords_options::{AccessControlOptions, PasswordOptions};
8
9use crate::base::Result;
10use crate::{cvt, Error};
11use core_foundation::base::TCFType;
12use core_foundation::boolean::CFBoolean;
13use core_foundation::data::CFData;
14use core_foundation::dictionary::CFDictionary;
15use core_foundation_sys::base::{CFGetTypeID, CFRelease, CFTypeRef};
16use core_foundation_sys::data::CFDataRef;
17use security_framework_sys::base::{errSecDuplicateItem, errSecParam};
18use security_framework_sys::item::{kSecReturnData, kSecValueData};
19use security_framework_sys::keychain::{SecAuthenticationType, SecProtocolType};
20use security_framework_sys::keychain_item::{
21 SecItemAdd, SecItemCopyMatching, SecItemDelete, SecItemUpdate,
22};
23
24pub fn set_generic_password(service: &str, account: &str, password: &[u8]) -> Result<()> {
27 let mut options = PasswordOptions::new_generic_password(service, account);
28 set_password_internal(&mut options, password)
29}
30
31pub fn set_generic_password_options(password: &[u8], mut options: PasswordOptions) -> Result<()> {
34 set_password_internal(&mut options, password)
35}
36
37#[doc(hidden)]
40pub fn get_generic_password(service: &str, account: &str) -> Result<Vec<u8>> {
41 generic_password(PasswordOptions::new_generic_password(service, account))
42}
43
44pub fn generic_password(mut options: PasswordOptions) -> Result<Vec<u8>> {
54 unsafe { options.push_query(kSecReturnData, CFBoolean::from(true)); }
55 let params = options.to_dictionary();
56 let mut ret: CFTypeRef = std::ptr::null();
57 cvt(unsafe { SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret) })?;
58 get_password_and_release(ret)
59}
60
61pub fn delete_generic_password(service: &str, account: &str) -> Result<()> {
64 let options = PasswordOptions::new_generic_password(service, account);
65 delete_generic_password_options(options)
66}
67
68pub fn delete_generic_password_options(options: PasswordOptions) -> Result<()> {
78 let params = options.to_dictionary();
79 cvt(unsafe { SecItemDelete(params.as_concrete_TypeRef()) })
80}
81
82#[allow(clippy::too_many_arguments)]
85pub fn set_internet_password(
86 server: &str,
87 security_domain: Option<&str>,
88 account: &str,
89 path: &str,
90 port: Option<u16>,
91 protocol: SecProtocolType,
92 authentication_type: SecAuthenticationType,
93 password: &[u8],
94) -> Result<()> {
95 let mut options = PasswordOptions::new_internet_password(
96 server,
97 security_domain,
98 account,
99 path,
100 port,
101 protocol,
102 authentication_type,
103 );
104 set_password_internal(&mut options, password)
105}
106
107pub fn get_internet_password(
110 server: &str,
111 security_domain: Option<&str>,
112 account: &str,
113 path: &str,
114 port: Option<u16>,
115 protocol: SecProtocolType,
116 authentication_type: SecAuthenticationType,
117) -> Result<Vec<u8>> {
118 let mut options = PasswordOptions::new_internet_password(
119 server,
120 security_domain,
121 account,
122 path,
123 port,
124 protocol,
125 authentication_type,
126 );
127 unsafe { options.push_query(kSecReturnData, CFBoolean::from(true)); }
128 let params = options.to_dictionary();
129 let mut ret: CFTypeRef = std::ptr::null();
130 cvt(unsafe { SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret) })?;
131 get_password_and_release(ret)
132}
133
134pub fn delete_internet_password(
137 server: &str,
138 security_domain: Option<&str>,
139 account: &str,
140 path: &str,
141 port: Option<u16>,
142 protocol: SecProtocolType,
143 authentication_type: SecAuthenticationType,
144) -> Result<()> {
145 let options = PasswordOptions::new_internet_password(
146 server,
147 security_domain,
148 account,
149 path,
150 port,
151 protocol,
152 authentication_type,
153 );
154 let params = options.to_dictionary();
155 cvt(unsafe { SecItemDelete(params.as_concrete_TypeRef()) })
156}
157
158fn set_password_internal(options: &mut PasswordOptions, password: &[u8]) -> Result<()> {
161 #[allow(deprecated)]
162 let query_without_password = options.query.len();
163 unsafe { options.push_query(kSecValueData, CFData::from_buffer(password)); }
164
165 let params = options.to_dictionary();
166 let mut ret = std::ptr::null();
167 let status = unsafe { SecItemAdd(params.as_concrete_TypeRef(), &mut ret) };
168 if status == errSecDuplicateItem {
169 #[allow(deprecated)]
170 let (query, pass) = options.query.split_at(query_without_password);
171 let params = CFDictionary::from_CFType_pairs(query);
172 let update = CFDictionary::from_CFType_pairs(pass);
173 cvt(unsafe { SecItemUpdate(params.as_concrete_TypeRef(), update.as_concrete_TypeRef()) })
174 } else {
175 cvt(status)
176 }
177}
178
179fn get_password_and_release(data: CFTypeRef) -> Result<Vec<u8>> {
185 if !data.is_null() {
186 let type_id = unsafe { CFGetTypeID(data) };
187 if type_id == CFData::type_id() {
188 let val = unsafe { CFData::wrap_under_create_rule(data as CFDataRef) };
189 let mut vec = Vec::new();
190 if !val.is_empty() {
191 vec.extend_from_slice(val.bytes());
192 }
193 return Ok(vec);
194 }
195 unsafe { CFRelease(data) };
199 }
200 Err(Error::from_code(errSecParam))
201}
202
203#[cfg(test)]
204mod test {
205 use super::*;
206 use security_framework_sys::base::errSecItemNotFound;
207
208 #[test]
209 fn missing_generic() {
210 let name = "a string not likely to already be in the keychain as service or account";
211 let result = delete_generic_password(name, name);
212 match result {
213 Ok(()) => (), Err(err) if err.code() == errSecItemNotFound => (),
215 Err(err) => panic!("missing_generic: delete failed with status: {}", err.code()),
216 }
217 let result = get_generic_password(name, name);
218 match result {
219 Ok(bytes) => panic!("missing_generic: get returned {bytes:?}"),
220 Err(err) if err.code() == errSecItemNotFound => (),
221 Err(err) => panic!("missing_generic: get failed with status: {}", err.code()),
222 }
223 let result = delete_generic_password(name, name);
224 match result {
225 Ok(()) => panic!("missing_generic: second delete found a password"),
226 Err(err) if err.code() == errSecItemNotFound => (),
227 Err(err) => panic!("missing_generic: delete failed with status: {}", err.code()),
228 }
229 }
230
231 #[test]
232 fn roundtrip_generic() {
233 let name = "roundtrip_generic";
234 set_generic_password(name, name, name.as_bytes()).expect("set_generic_password");
235 let pass = get_generic_password(name, name).expect("get_generic_password");
236 assert_eq!(name.as_bytes(), pass);
237 delete_generic_password(name, name).expect("delete_generic_password");
238 }
239
240 #[test]
241 #[cfg(feature = "OSX_10_12")]
242 fn update_generic() {
243 let name = "update_generic";
244 set_generic_password(name, name, name.as_bytes()).expect("set_generic_password");
245 let alternate = "update_generic_alternate";
246 set_generic_password(name, name, alternate.as_bytes()).expect("set_generic_password");
247 let pass = get_generic_password(name, name).expect("get_generic_password");
248 assert_eq!(pass, alternate.as_bytes());
249 delete_generic_password(name, name).expect("delete_generic_password");
250 }
251
252 #[test]
253 fn missing_internet() {
254 let name = "a string not likely to already be in the keychain as service or account";
255 let (server, domain, account, path, port, protocol, auth) =
256 (name, None, name, "/", Some(8080u16), SecProtocolType::HTTP, SecAuthenticationType::Any);
257 let result = delete_internet_password(server, domain, account, path, port, protocol, auth);
258 match result {
259 Ok(()) => (), Err(err) if err.code() == errSecItemNotFound => (),
261 Err(err) => panic!("missing_internet: delete failed with status: {}", err.code()),
262 }
263 let result = get_internet_password(server, domain, account, path, port, protocol, auth);
264 match result {
265 Ok(bytes) => panic!("missing_internet: get returned {bytes:?}"),
266 Err(err) if err.code() == errSecItemNotFound => (),
267 Err(err) => panic!("missing_internet: get failed with status: {}", err.code()),
268 }
269 let result = delete_internet_password(server, domain, account, path, port, protocol, auth);
270 match result {
271 Ok(()) => panic!("missing_internet: second delete found a password"),
272 Err(err) if err.code() == errSecItemNotFound => (),
273 Err(err) => panic!("missing_internet: delete failed with status: {}", err.code()),
274 }
275 }
276
277 #[test]
278 fn roundtrip_internet() {
279 let name = "roundtrip_internet";
280 let (server, domain, account, path, port, protocol, auth) =
281 (name, None, name, "/", Some(8080u16), SecProtocolType::HTTP, SecAuthenticationType::Any);
282 set_internet_password(server, domain, account, path, port, protocol, auth, name.as_bytes())
283 .expect("set_internet_password");
284 let pass =
285 get_internet_password(server, domain, account, path, port, protocol, auth).expect("get_internet_password");
286 assert_eq!(name.as_bytes(), pass);
287 delete_internet_password(server, domain, account, path, port, protocol, auth)
288 .expect("delete_internet_password");
289 }
290
291 #[test]
292 fn update_internet() {
293 let name = "update_internet";
294 let (server, domain, account, path, port, protocol, auth) =
295 (name, None, name, "/", Some(8080u16), SecProtocolType::HTTP, SecAuthenticationType::Any);
296
297 let _ = delete_internet_password(server, domain, account, path, port, protocol, auth);
299
300 set_internet_password(server, domain, account, path, port, protocol, auth, name.as_bytes())
301 .expect("set_internet_password");
302 let alternate = "alternate_internet_password";
303 set_internet_password(server, domain, account, path, port, protocol, auth, alternate.as_bytes())
304 .expect("set_internet_password");
305 let pass =
306 get_internet_password(server, domain, account, path, port, protocol, auth).expect("get_internet_password");
307 assert_eq!(pass, alternate.as_bytes());
308 delete_internet_password(server, domain, account, path, port, protocol, auth)
309 .expect("delete_internet_password");
310 }
311}