keeper_secrets_manager_core/utils.rs
1// -*- coding: utf-8 -*-
2// _ __
3// | |/ /___ ___ _ __ ___ _ _ (R)
4// | ' </ -_) -_) '_ \/ -_) '_|
5// |_|\_\___\___| .__/\___|_|
6// |_|
7//
8// Keeper Secrets Manager
9// Copyright 2024 Keeper Security Inc.
10// Contact: sm@keepersecurity.com
11//
12
13use crate::{
14 crypto::{unpad_data, CryptoUtils},
15 custom_error::KSMRError,
16};
17use base64::{
18 engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD},
19 prelude::BASE64_URL_SAFE,
20 Engine as _,
21};
22use chrono::Utc;
23use core::str;
24use data_encoding::BASE32;
25use hmac::{Hmac, Mac};
26use log::warn;
27use num_bigint::BigUint;
28use rand::{
29 seq::{IteratorRandom, SliceRandom},
30 thread_rng,
31};
32use serde::Serialize;
33use serde_json::Value;
34use sha1::Sha1;
35use sha2::{Sha256, Sha512};
36use std::process::Output;
37use std::{collections::HashMap, option::Option};
38use std::{env, io};
39use url::{form_urlencoded::parse, Url};
40
41#[cfg(unix)]
42use std::os::unix::fs::PermissionsExt;
43
44#[cfg(target_os = "windows")]
45use log::debug;
46use std::fs::File;
47#[cfg(target_os = "windows")]
48use std::process::Command;
49
50#[cfg(unix)]
51use std::fs;
52
53/// Allowed Windows configuration administrators.
54pub const ALLOWED_WINDOWS_CONFIG_ADMINS: [&[u8]; 2] = [b"Administrators", b"SYSTEM"];
55
56/// Encoding format.
57pub const ENCODING: &str = "UTF-8";
58
59/// Special characters used in passwords or other contexts.
60pub const SPECIAL_CHARACTERS: &str = r#"""!@#$%()+;<>=?[]{}^.,"""#;
61
62/// Default password length.
63pub const DEFAULT_PASSWORD_LENGTH: usize = 32;
64
65/// Converts a string representation of truth to a boolean value.
66///
67/// The function accepts string values that represent true or false:
68/// - True values: "y", "yes", "t", "true", "on", "1"
69/// - False values: "n", "no", "f", "false", "off", "0"
70///
71/// # Errors
72///
73/// Returns an error if the input string does not match any of the valid
74/// truth values.
75///
76/// # Examples
77///
78/// ```
79/// use keeper_secrets_manager_core::utils::str_to_bool;
80/// let true_value = str_to_bool("yes").unwrap();
81/// assert_eq!(true_value, true);
82///
83/// let false_value = str_to_bool("no").unwrap();
84/// assert_eq!(false_value, false);
85///
86/// let invalid_value = str_to_bool("maybe");
87/// assert!(invalid_value.is_err());
88/// ```
89pub fn str_to_bool(val: &str) -> Result<bool, String> {
90 let val = val.to_lowercase();
91 match val.as_str() {
92 "y" | "yes" | "t" | "true" | "on" | "1" => Ok(true),
93 "n" | "no" | "f" | "false" | "off" | "0" => Ok(false),
94 _ => Err(format!("invalid truth value {:?}", val)),
95 }
96}
97
98/// Gets the name of the operating system.
99///
100/// This function returns a string slice that indicates the current
101/// operating system. The possible return values are:
102///
103/// - `"linux"` for Linux operating systems.
104/// - `"macOS"` for macOS.
105/// - The value of `std::env::consts::OS` for any other operating systems.
106///
107/// # Examples
108///
109/// ```
110/// use keeper_secrets_manager_core::utils::get_os;
111/// let os_name = get_os();
112/// println!("Operating System: {}", os_name);
113/// ```
114pub fn get_os() -> &'static str {
115 determine_os(env::consts::OS)
116}
117
118// Helper function for testability
119pub(crate) fn determine_os(os: &str) -> &str {
120 match os {
121 "linux" => "linux",
122 "macos" => "macOS",
123 "windows" => {
124 if cfg!(target_os = "windows") {
125 "win32"
126 } else {
127 "win64"
128 }
129 }
130 _ => os,
131 }
132}
133
134/// Converts a byte slice to a String using the specified encoding.
135///
136/// # Arguments
137///
138/// * `b` - A byte slice (`&[u8]`) that needs to be converted to a String.
139///
140/// # Returns
141///
142/// A `Result<String, std::str::Utf8Error>` where:
143/// - `Ok(String)` contains the decoded string if successful.
144/// - `Err(std::str::Utf8Error)` if the byte slice is not valid UTF-8.
145///
146/// # Examples
147///
148/// ```
149/// use keeper_secrets_manager_core::utils::bytes_to_string;
150/// let bytes = b"Hello, world!";
151/// let result = bytes_to_string(bytes);
152/// assert_eq!(result.unwrap(), "Hello, world!");
153/// ```
154pub fn bytes_to_string(b: &[u8]) -> Result<String, KSMRError> {
155 // let bytes_sorted = unpad_data(b)?;
156 let string_of_bytes: String = str::from_utf8(b)
157 .map_err(|e| KSMRError::DecodeError(e.to_string()))?
158 .to_string();
159 Ok(string_of_bytes)
160}
161
162pub fn bytes_to_string_unpad(b: &[u8]) -> Result<String, KSMRError> {
163 let bytes_sorted = unpad_data(b)?;
164 let string_of_bytes: String = str::from_utf8(&bytes_sorted)
165 .map_err(|e| KSMRError::DecodeError(e.to_string()))?
166 .to_string();
167 Ok(string_of_bytes)
168}
169
170/// Converts a byte slice to an integer using big-endian byte order.
171///
172/// # Arguments
173///
174/// * `b` - A byte slice (`&[u8]`) that needs to be converted to an integer.
175///
176/// # Returns
177///
178/// An `Option<u64>` where:
179/// - `Some(u64)` contains the converted integer if successful.
180/// - `None` if the byte slice is empty or too long to fit in a u64.
181///
182/// # Examples
183///
184/// ```
185/// use keeper_secrets_manager_core::utils::bytes_to_int;
186/// use num_bigint::BigUint;
187/// let bytes = [0x00, 0x01, 0x02, 0x03];
188/// let result = bytes_to_int(&bytes);
189/// assert_eq!(result.unwrap(), BigUint::from(66051u64));
190/// ```
191pub fn bytes_to_int(b: &[u8]) -> Result<BigUint, KSMRError> {
192 Ok(BigUint::from_bytes_be(b))
193}
194
195/// Converts a byte slice to a Base64-encoded string.
196///
197/// # Arguments
198///
199/// * `b` - A byte slice (`&[u8]`) that needs to be converted to a Base64 string.
200///
201/// # Returns
202///
203/// A `String` containing the Base64-encoded representation of the input byte slice.
204///
205/// # Examples
206///
207/// ```
208/// use keeper_secrets_manager_core::utils::bytes_to_base64;
209/// let bytes = b"Hello, world!";
210/// let result = bytes_to_base64(bytes);
211/// assert_eq!(result, "SGVsbG8sIHdvcmxkIQ==");
212/// ```
213pub fn bytes_to_base64(b: &[u8]) -> String {
214 STANDARD.encode(b)
215}
216
217/// Converts a Base64-encoded string to a byte vector.
218///
219/// # Arguments
220///
221/// * `s` - A string slice (`&str`) that contains the Base64-encoded data.
222///
223/// # Returns
224///
225/// A `Result<Vec<u8>, base64::DecodeError>` where:
226/// - `Ok(Vec<u8>)` contains the decoded byte vector if successful.
227/// - `Err(base64::DecodeError)` if the input string is not valid Base64.
228///
229/// # Examples
230///
231/// ```
232/// use keeper_secrets_manager_core::utils::base64_to_bytes;
233/// let encoded = "SGVsbG8sIHdvcmxkIQ==";
234/// let result = base64_to_bytes(encoded);
235/// assert_eq!(result.unwrap(), b"Hello, world!");
236/// ```
237pub fn base64_to_bytes(s: &str) -> Result<Vec<u8>, KSMRError> {
238 let decode_confed_str = s.to_string().replace("+", "-").replace("/", "_");
239
240 let decoded_bytes = BASE64_URL_SAFE
241 .decode(decode_confed_str)
242 .map_err(|e| KSMRError::DecodeError(e.to_string()))?;
243
244 Ok(decoded_bytes)
245}
246
247/// Converts a Base64-encoded string to a UTF-8 string.
248///
249/// # Arguments
250///
251/// * `b64s` - A string slice (`&str`) containing the Base64-encoded data.
252///
253/// # Returns
254///
255/// A `Result<String, KSMRError>` where:
256/// - `Ok(String)` contains the decoded UTF-8 string if successful.
257/// - `Err(KSMRError)` if there is an error during decoding.
258///
259/// # Examples
260///
261/// ```
262/// use keeper_secrets_manager_core::utils::base64_to_string;
263/// use base64::{ engine::general_purpose::STANDARD, Engine as _};
264/// let encoded = STANDARD.encode("hello");
265/// let result = base64_to_string(&encoded.to_string());
266/// assert_eq!(result.unwrap(), "hello");
267/// ```
268pub fn base64_to_string(b64s: &str) -> Result<String, KSMRError> {
269 let decoded_bytes = STANDARD
270 .decode(b64s)
271 .map_err(|_| KSMRError::DecodeError("Failed to decode Base64 string".to_string()))?;
272
273 let decoded_string = String::from_utf8(decoded_bytes)
274 .map_err(|_| KSMRError::DecodeError("Failed to convert bytes to UTF-8".to_string()))?;
275
276 Ok(decoded_string)
277}
278
279pub fn base64_to_string_lossy(b64s: &str) -> Result<String, KSMRError> {
280 let decoded_bytes = STANDARD
281 .decode(b64s)
282 .map_err(|_| KSMRError::DecodeError("Failed to decode Base64 string".to_string()))?;
283
284 let decoded_string = String::from_utf8_lossy(&decoded_bytes).to_string();
285
286 Ok(decoded_string)
287}
288
289/// Converts a string to a byte vector using UTF-8 encoding.
290///
291/// # Arguments
292///
293/// * `s` - A string slice (`&str`) to be converted to bytes.
294///
295/// # Returns
296///
297/// A `Vec<u8>` containing the UTF-8 encoded bytes of the input string.
298///
299/// # Examples
300///
301/// ```
302/// use keeper_secrets_manager_core::utils::string_to_bytes;
303/// let input = "Hello, world!";
304/// let bytes = string_to_bytes(input);
305/// assert_eq!(bytes, b"Hello, world!");
306/// ```
307pub fn string_to_bytes(s: &str) -> Vec<u8> {
308 s.as_bytes().to_vec() // Convert the string slice to a byte vector
309}
310
311/// Converts a URL-safe Base64-encoded string to a byte vector.
312///
313/// # Arguments
314///
315/// * `s` - A string slice (`&str`) containing the URL-safe Base64-encoded data.
316///
317/// # Returns
318///
319/// A `Result<Vec<u8>, base64::DecodeError>` where:
320/// - `Ok(Vec<u8>)` contains the decoded byte vector if successful.
321/// - `Err(base64::DecodeError)` if the input string is not valid Base64.
322///
323/// # Examples
324///
325/// ```
326/// use keeper_secrets_manager_core::utils::url_safe_str_to_bytes;
327/// let encoded = "SGVsbG8sIFdvcmxkIQ"; // URL-safe Base64 string
328/// let result = url_safe_str_to_bytes(encoded);
329/// assert_eq!(result.unwrap(), b"Hello, World!");
330/// ```
331pub fn url_safe_str_to_bytes(s: &str) -> Result<Vec<u8>, crate::custom_error::KSMRError> {
332 // // Add padding manually if necessary
333 // let padded_str = if s.len() % 4 != 0 {
334 // format!("{}{}", s, "=".repeat(4 - s.len() % 4))
335 // } else {
336 // s.to_string()
337 // };
338 URL_SAFE_NO_PAD
339 .decode(s)
340 .map_err(|e| KSMRError::DecodeError(e.to_string()))
341}
342
343/// Converts a URL-safe Base64-encoded string to a u64 integer.
344///
345/// # Arguments
346///
347/// * `s` - A string slice (`&str`) containing the URL-safe Base64-encoded data.
348///
349/// # Returns
350///
351/// A `Result<u64, String>` where:
352/// - `Ok(u64)` contains the converted integer if successful.
353/// - `Err(String)` if the input string is not valid Base64 or if the conversion fails.
354/// # Examples
355///
356/// ```
357/// use keeper_secrets_manager_core::utils::url_safe_str_to_int;
358/// use num_bigint::BigUint;
359/// let encoded = "4oCU"; // URL-safe Base64 string
360/// let decoded = url_safe_str_to_int(encoded).unwrap();
361/// assert_eq!(decoded, BigUint::from(14844052u64));
362pub fn url_safe_str_to_int(s: &str) -> Result<BigUint, KSMRError> {
363 let bytes_of_str = url_safe_str_to_bytes(s)?;
364 bytes_to_int(bytes_of_str.as_slice()).map_err(|e| KSMRError::DecodeError(e.to_string()))
365}
366
367/// Generates a vector of random bytes.
368///
369/// # Arguments
370///
371/// * `length` - The number of random bytes to generate.
372///
373/// # Returns
374///
375/// A `Vec<u8>` containing the generated random bytes.
376///
377/// # Examples
378///
379/// ```
380/// use keeper_secrets_manager_core::utils::generate_random_bytes;
381/// let random_bytes = generate_random_bytes(16);
382/// assert_eq!(random_bytes.len(), 16); // Should be 16 bytes long
383/// ```
384pub fn generate_random_bytes(length: usize) -> Vec<u8> {
385 CryptoUtils::generate_random_bytes(length)
386}
387
388/// Generates UID bytes with specific bit conditions.
389///
390/// # Returns
391///
392/// A `Vec<u8>` containing the generated UID bytes.
393///
394/// # Examples
395///
396/// ```
397/// use keeper_secrets_manager_core::utils::generate_uid_bytes;
398/// let uid = generate_uid_bytes();
399/// assert_eq!(uid.len(), 16); // Should generate a UID of 16 bytes
400/// // Further checks can be added based on expected UID properties
401/// ```
402pub fn generate_uid_bytes() -> Vec<u8> {
403 let dash = [0xf8, 0x7f]; // Represents [11111000, 01111111]
404 let mut uid_bytes: Vec<u8> = Vec::new();
405
406 for _ in 0..8 {
407 uid_bytes = generate_random_bytes(16);
408 if dash[0] & uid_bytes[0] != dash[0] {
409 break;
410 }
411 }
412
413 if dash[0] & uid_bytes[0] == dash[0] {
414 uid_bytes[0] &= dash[1];
415 }
416
417 uid_bytes
418}
419
420pub fn generate_uid() -> String {
421 let uid_bytes = generate_uid_bytes();
422 CryptoUtils::bytes_to_url_safe_str(&uid_bytes)
423}
424
425/// Converts a dictionary to a JSON string with pretty formatting.
426///
427/// # Arguments
428///
429/// * `dictionary` - A reference to a serializable object that can be converted to JSON.
430///
431/// # Returns
432///
433/// A `Result<String, serde_json::Error>` containing the formatted JSON string if successful,
434/// or an error if serialization fails.
435///
436/// # Examples
437///
438/// ```
439/// use keeper_secrets_manager_core::utils::dict_to_json;
440/// let dictionary = [("key1", "value1"), ("key2", "value2")].iter().cloned().collect::<std::collections::HashMap<_, _>>();
441/// let json = dict_to_json(&dictionary).unwrap();
442/// println!("{}", json); // Outputs the JSON representation of the dictionary
443/// ```
444pub fn dict_to_json<T: Serialize>(dictionary: &T) -> serde_json::Result<String> {
445 serde_json::to_string_pretty(dictionary)
446}
447
448/// Converts a JSON string to a dictionary (HashMap).
449///
450/// # Arguments
451///
452/// * `json_str` - A string slice containing the JSON data.
453///
454/// # Returns
455///
456/// An `Option<Value>` which will be `Some(Value)` containing the parsed JSON
457/// if successful, or `None` if parsing fails.
458///
459/// # Examples
460///
461/// ```
462/// use keeper_secrets_manager_core::utils::json_to_dict;
463/// let json_str = r#"{"key1": "value1", "key2": "value2"}"#;
464/// let dict = json_to_dict(json_str);
465/// assert!(dict.is_some());
466/// ```
467pub fn json_to_dict(json_str: &str) -> Option<HashMap<String, Value>> {
468 let return_value = serde_json::from_str(json_str).map_err(|err| {
469 warn!("JSON decode error: {}", err);
470 });
471 // return Some(return_value);
472 match return_value {
473 Ok(map) => Some(map),
474 Err(err) => {
475 warn!("JSON decode error: {:?}", err);
476 None
477 }
478 }
479}
480
481/// Returns the current time in milliseconds since the Unix epoch.
482///
483/// This function retrieves the current UTC time and converts it into
484/// milliseconds since January 1, 1970 (the Unix epoch).
485///
486/// # Returns
487///
488/// An `i64` representing the current time in milliseconds.
489///
490/// # Examples
491///
492/// ```
493/// use keeper_secrets_manager_core::utils::now_milliseconds;
494/// let millis = now_milliseconds();
495/// println!("Current time in milliseconds: {}", millis);
496/// ```
497///
498/// In the example above, the function returns the current time in milliseconds,
499/// which can be used for timestamping events or measuring time intervals.
500pub fn now_milliseconds() -> i64 {
501 Utc::now().timestamp_millis()
502}
503/// Represents a TOTP code along with its time left and period.
504#[derive(Debug, Clone)]
505pub struct TotpCode {
506 code: String,
507 time_left: u64, // Assuming time_left is in seconds
508 period: u64, // Assuming period is also in seconds
509}
510
511impl TotpCode {
512 /// Creates a new `TotpCode`.
513 ///
514 /// # Arguments
515 ///
516 /// * `code` - A string representing the TOTP code.
517 /// * `time_left` - The time left until the code expires, in seconds.
518 /// * `period` - The period for which the TOTP code is valid, in seconds.
519 ///
520 /// # Returns
521 ///
522 /// A new instance of `TotpCode`.
523 pub fn new(code: String, time_left: u64, period: u64) -> Self {
524 TotpCode {
525 code,
526 time_left,
527 period,
528 }
529 }
530
531 /// Returns the TOTP code.
532 pub fn get_code(&self) -> &str {
533 &self.code
534 }
535
536 /// Returns the time left.
537 pub fn get_time_left(&self) -> u64 {
538 self.time_left
539 }
540
541 /// Returns the period.\
542 pub fn get_period(&self) -> u64 {
543 self.period
544 }
545}
546
547/// Generates a TOTP code from a given otp auth URL.
548///
549/// # Arguments
550///
551/// * `url` - A string slice containing the otp auth URL.
552///
553/// # Returns
554///
555/// A `Result<TotpCode, String>` which contains the generated TOTP code
556/// if successful, or an error message if parsing or generation fails.
557///
558/// # Examples
559///
560/// ```
561/// use keeper_secrets_manager_core::utils::get_totp_code;
562/// let url = "otpauth://totp/Example?secret=JBSWY3DPEHPK3PXP&issuer=Example";
563/// match get_totp_code(url) {
564/// Ok(totp_code) => println!("Generated TOTP code: {:?}", totp_code),
565/// Err(e) => println!("Error: {}", e),
566/// }
567/// ```
568pub fn get_totp_code(url: &str) -> Result<TotpCode, KSMRError> {
569 let comp = Url::parse(url).map_err(|_| KSMRError::TOTPError("Invalid URL".to_string()))?;
570 if comp.scheme() != "otpauth" {
571 return Err(KSMRError::TOTPError("Not an otpauth URI".to_string()));
572 }
573
574 let mut secret = None;
575 let mut algorithm = "SHA1".to_string();
576 let mut digits = 6;
577 let mut period = 30;
578 let mut counter = 0;
579
580 // Parse URL query string
581 let query_pairs = parse(comp.query().unwrap_or("").as_bytes());
582 for (key, value) in query_pairs {
583 match key.as_ref() {
584 "secret" => secret = Some(value.into_owned()),
585 "algorithm" => algorithm = value.into_owned().to_uppercase(),
586 "digits" => {
587 if let Ok(num) = value.parse::<u32>() {
588 if num > 0 && num < 10 {
589 digits = num;
590 } else {
591 return Err(KSMRError::TOTPError(
592 "TOTP Digits may only be 6, 7, or 8".to_string(),
593 ));
594 }
595 }
596 }
597 "period" => {
598 if let Ok(num) = value.parse::<u32>() {
599 if num > 0 {
600 period = num;
601 }
602 }
603 }
604 "counter" => {
605 if let Ok(num) = value.parse::<u32>() {
606 if num > 0 {
607 counter = num;
608 }
609 }
610 }
611 _ => {}
612 }
613 }
614
615 // Validate parameters
616 let secret = secret
617 .ok_or(KSMRError::TOTPError(
618 "TOTP secret not found in URI".to_string(),
619 ))?
620 .to_ascii_uppercase();
621 let decoded_key_option = BASE32.decode(secret.as_bytes());
622 let key = match decoded_key_option {
623 Ok(decoded_key) => decoded_key,
624 Err(err) => Err(KSMRError::DecodeError(format!(
625 "Invalid TOTP secret: {}",
626 err
627 )))?,
628 };
629
630 let tm_base = if counter > 0 {
631 counter
632 } else {
633 Utc::now().timestamp() as u32
634 };
635 let tm = tm_base / period;
636 let msg = (tm as u64).to_be_bytes();
637
638 let digest: Vec<u8> = match algorithm.as_str() {
639 "SHA1" => {
640 let mut hmac = Hmac::<Sha1>::new_from_slice(&key)
641 .map_err(|_| KSMRError::TOTPError("Failed to create HMAC".to_string()))?;
642 hmac.update(&msg);
643 hmac.finalize().into_bytes().to_vec()
644 }
645 "SHA256" => {
646 let mut hmac = Hmac::<Sha256>::new_from_slice(&key)
647 .map_err(|_| KSMRError::TOTPError("Failed to create HMAC".to_string()))?;
648 hmac.update(&msg);
649 hmac.finalize().into_bytes().to_vec()
650 }
651 "SHA512" => {
652 let mut hmac = Hmac::<Sha512>::new_from_slice(&key)
653 .map_err(|_| KSMRError::TOTPError("Failed to create HMAC".to_string()))?;
654 hmac.update(&msg);
655 hmac.finalize().into_bytes().to_vec()
656 }
657 _ => {
658 return Err(KSMRError::TOTPError(format!(
659 "Invalid algorithm: {}",
660 algorithm
661 )))
662 }
663 };
664
665 let offset = (digest.last().unwrap() & 0x0f) as usize;
666 let base = &digest[offset..offset + 4];
667 let code_int = ((base[0] & 0x7f) as u32) << 24
668 | (base[1] as u32) << 16
669 | (base[2] as u32) << 8
670 | (base[3] as u32);
671 let code = format!(
672 "{:0width$}",
673 code_int % 10u32.pow(digits),
674 width = digits as usize
675 );
676
677 let elapsed = tm_base % period; // time elapsed in current period in seconds
678 let ttl = period - elapsed; // time to live in seconds
679
680 Ok(TotpCode::new(code, ttl as u64, period as u64))
681}
682
683pub fn get_otp_url_from_value_obj(val: serde_json::Value) -> Result<String, KSMRError> {
684 let otp_value = match val.is_array() {
685 true => val.as_array().unwrap()[0][0].clone(),
686 false => {
687 return Err(KSMRError::RecordDataError(
688 "otpCode or otp field is not an array".to_string(),
689 ))
690 }
691 };
692
693 let url_retrieved = match otp_value.is_string() {
694 true => otp_value.as_str().unwrap().to_string(),
695 false => {
696 return Err(KSMRError::RecordDataError(
697 "otpCode or otp field is not a string".to_string(),
698 ))
699 }
700 };
701
702 Ok(url_retrieved)
703}
704/// Generates a random sample of characters from a given string.
705///
706/// # Parameters
707/// - `sample_length`: The number of characters to sample.
708/// - `sample_string`: The string from which to sample characters.
709///
710/// # Returns
711/// A `String` containing the sampled characters.
712///
713/// # Errors
714/// Returns an error if `sample_length` is negative or if `sample_string` is empty.
715///
716/// # Example
717/// ```
718/// use keeper_secrets_manager_core::utils::random_sample;
719/// let result = random_sample(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789").unwrap();
720/// println!("Random Sample: {}", result);
721/// ```
722pub fn random_sample(
723 sample_length: usize,
724 sample_string: &str,
725) -> Result<String, Box<dyn std::error::Error>> {
726 // Validate inputs
727 if sample_length == 0 {
728 return Ok(String::new());
729 }
730
731 if sample_string.is_empty() {
732 return Err("sample_string must not be empty".into());
733 }
734
735 let mut rng = rand::thread_rng();
736 let mut sample = String::new();
737
738 for _ in 0..sample_length {
739 let char = sample_string
740 .chars()
741 .choose(&mut rng)
742 .ok_or("Failed to choose a character")?;
743 sample.push(char);
744 }
745
746 Ok(sample)
747}
748
749/// Gets the current Windows user SID and name.
750///
751/// # Returns
752/// A tuple containing the SID and username, or (None, None) if there was an error.
753///
754/// # Example
755/// ```
756/// use std::process::{Command, Output};
757/// use std::option::Option;
758/// use keeper_secrets_manager_core::utils::get_windows_user_sid_and_name;
759/// let (sid, username) = get_windows_user_sid_and_name::<fn() -> Output>(None);
760/// println!("SID: {:?}, Username: {:?}", sid, username);
761/// ```
762#[cfg(target_os = "windows")]
763pub fn get_windows_user_sid_and_name<F>(command: Option<F>) -> (Option<String>, Option<String>)
764where
765 F: Fn() -> Output,
766{
767 let output = match command {
768 Some(comm) => comm(),
769 None => _default_command(),
770 };
771
772 if output.status.success() {
773 let stdout = String::from_utf8_lossy(&output.stdout);
774 let lines: Vec<&str> = stdout.lines().collect();
775 if let Some(last_line) = lines.last() {
776 let parts: Vec<&str> = last_line.split('\\').collect();
777 if let Some(username_sid) = parts.last() {
778 let username_sid_string = username_sid.to_string();
779 let split_parts: Vec<&str> = username_sid_string.split(" ").collect();
780 // let mut parts_split = username_sid.split_ascii_whitespace();
781 // let username = parts_split.next().unwrap_or("");
782 // let sid = parts_split.next().unwrap_or("");
783 let username = split_parts[0].to_string();
784 let sid = split_parts[1].to_string();
785 return (Some(sid), Some(username));
786 }
787 }
788 } else {
789 eprintln!("Failed to execute 'whoami.exe'");
790 }
791
792 (None, None)
793}
794
795#[cfg(target_os = "windows")]
796fn _default_command() -> Output {
797 Command::new("whoami.exe")
798 .arg("/user")
799 .output()
800 .expect("Failed to execute whoami.exe")
801}
802
803#[cfg(not(target_os = "windows"))]
804fn _default_command() -> Output {
805 // This can be a dummy output or an error for non-Windows platforms.
806
807 use std::os::unix::process::ExitStatusExt;
808 Output {
809 status: std::process::ExitStatus::from_raw(1),
810 stdout: Vec::new(),
811 stderr: Vec::new(),
812 }
813}
814
815/**
816Sets the configuration mode for the specified file, adjusting its permissions
817according to the operating system's conventions. On Windows, it uses `icacls`
818to set file permissions, while on Linux/MacOS, it sets the permissions to 0600.
819
820# Arguments
821
822* `file` - A string slice that holds the path to the configuration file.
823
824# Returns
825
826This function returns `Ok(())` if the mode is set successfully, or an `io::Error`
827if an error occurs.
828
829*/
830pub fn set_config_mode(
831 file: &str,
832) -> Result<(), io::Error> {
833 // Check if we should skip setting the mode
834 if let Ok(skip_mode) = env::var("KSM_CONFIG_SKIP_MODE") {
835 if skip_mode.to_lowercase() == "true" {
836 return Ok(());
837 }
838 }
839
840 // For Windows, use icacls commands
841 #[cfg(target_os = "windows")]
842 {
843 let sid = match get_windows_user_sid_and_name::<fn() -> Output>(None) {
844 (Some(sid), _) => sid,
845 _ => {
846 return Err(io::Error::new(
847 io::ErrorKind::Other,
848 "Failed to get user SID",
849 ))
850 }
851 };
852
853 // Commands to set the file permissions
854 let commands = vec![
855 format!(r#"icacls "{}" /reset"#, file),
856 format!(r#"icacls "{}" /inheritance:r"#, file),
857 format!(r#"icacls "{}" /remove:g Everyone:F"#, file),
858 format!(r#"icacls "{}" /grant:r Administrators:F"#, file),
859 format!(r#"icacls "{}" /grant:r "{}:F""#, file, sid),
860 ];
861
862 for command in commands {
863
864 let output = Command::new("cmd").args(&["/C", &command]).output()?;
865
866 match output.status.code() {
867 Some(2) => {
868 return Err(io::Error::new(
869 io::ErrorKind::NotFound,
870 format!("Cannot find configuration file {}", file),
871 ))
872 }
873 Some(5) => {
874 return Err(io::Error::new(
875 io::ErrorKind::PermissionDenied,
876 format!("Access denied to configuration file {}", file),
877 ))
878 }
879 Some(1332) => {
880 debug!("{} {}", "Failed to set some ACL permissions: {}", command);
881 continue; // Skip localized group/user names error
882 }
883 Some(_) if !output.status.success() => {
884 let message = format!(
885 "Could not change the ACL for file '{}'. Set the environmental variable 'KSM_CONFIG_SKIP_MODE' to 'TRUE' to skip setting the ACL mode.",
886 file
887 );
888 let stderr = String::from_utf8_lossy(&output.stderr);
889 let full_message = if !stderr.is_empty() {
890 format!("{}: {}", message, stderr.trim())
891 } else {
892 format!("{}.", message)
893 };
894 return Err(io::Error::new(
895 io::ErrorKind::PermissionDenied,
896 full_message,
897 ));
898 }
899 _ => {}
900 }
901 }
902 }
903 #[cfg(not(target_os = "windows"))]
904 {
905 // On Linux/MacOS, set file permissions to 0600
906 let permissions = fs::metadata(file)?.permissions();
907 let mut new_permissions = permissions;
908 new_permissions.set_mode(0o600);
909 fs::set_permissions(file, new_permissions)?;
910 }
911
912 Ok(())
913}
914
915//This function runs only on windows and this unit test runs only on windows
916/// Retrieves localized account names for known administrative accounts on Windows.
917///
918/// This function uses the Win32 API to fetch account names for the local system
919/// and built-in administrators. It returns a vector of strings containing the
920/// localized names.
921///
922/// # Errors
923///
924/// This function returns an error if it fails to create a well-known SID,
925/// look up the account names, or execute the command to convert the names
926/// to the console's code page.
927///
928/// # Example
929///
930/// ```ignore
931/// fn main() -> Result<(), u32> {
932/// match _populate_windows_localized_admin_names_win32api() {
933/// Ok(localized_admins) => {
934/// for admin in localized_admins {
935/// println!("Localized Admin: {}", admin);
936/// }
937/// },
938/// Err(err) => eprintln!("Error retrieving localized admin names: {}", err),
939/// }
940/// Ok(())
941/// }
942/// ```
943///
944/// # Test
945///
946/// This test will run if the target operating system is Windows and the
947/// function is expected to return a non-empty list of localized admin names.
948///
949/// ```ignore
950/// #[cfg(test)]
951/// mod tests {
952/// use super::*; // Ensure the test module has access to the function
953///
954/// #[test]
955/// #[cfg(target_os = "windows")] // Only run this test on Windows
956/// fn test_populate_windows_localized_admin_names_win32api() {
957/// let result = _populate_windows_localized_admin_names_win32api();
958/// assert!(result.is_ok(), "Function should return Ok on success");
959/// let localized_admins = result.unwrap();
960/// assert!(!localized_admins.is_empty(), "Should return at least one localized admin name");
961/// }
962/// }
963/// ```
964#[cfg(target_os = "windows")]
965fn _populate_windows_localized_admin_names_win32api() -> Result<Vec<String>, u32> {
966 use std::ffi::OsString; // Make sure to import OsString
967 use std::os::windows::ffi::OsStringExt; // Import the OsStringExt trait
968 use std::ptr;
969 use winapi::shared::winerror::{ERROR_INSUFFICIENT_BUFFER, ERROR_INVALID_PARAMETER};
970 use winapi::um::errhandlingapi::GetLastError;
971 use winapi::um::securitybaseapi::CreateWellKnownSid;
972 use winapi::um::winbase::LookupAccountSidW;
973
974 // Define WellKnownSidType manually
975 #[allow(non_camel_case_types)]
976 #[repr(u32)]
977 #[derive(Clone)]
978 pub enum WellKnownSidType {
979 WinLocalSystemSid = 22,
980 WinBuiltinAdministratorsSid = 26,
981 }
982
983 // Helper function to convert a wide string (UTF-16) to a Rust String
984 fn wide_to_string(wide: &[u16]) -> String {
985 let os_string = OsString::from_wide(wide);
986 os_string.to_string_lossy().into_owned()
987 }
988
989 // Function to get account name for a specific SID type
990 fn get_account_name(sid_type: WellKnownSidType) -> Result<(String, String), u32> {
991 let mut sid_size = 256;
992 let mut sid = vec![0u8; sid_size as usize];
993
994 unsafe {
995 // CreateWellKnownSid
996 if CreateWellKnownSid(
997 sid_type.clone() as u32,
998 ptr::null_mut(),
999 sid.as_mut_ptr() as *mut _,
1000 &mut sid_size,
1001 ) == 0
1002 {
1003 let error = GetLastError();
1004 if error == ERROR_INSUFFICIENT_BUFFER || error == ERROR_INVALID_PARAMETER {
1005 sid = vec![0u8; sid_size as usize];
1006 if CreateWellKnownSid(
1007 sid_type as u32,
1008 ptr::null_mut(),
1009 sid.as_mut_ptr() as *mut _,
1010 &mut sid_size,
1011 ) == 0
1012 {
1013 return Err(GetLastError());
1014 }
1015 } else {
1016 return Err(error);
1017 }
1018 }
1019
1020 // LookupAccountSidW to get the size needed for the name and domain
1021 let mut name_size = 0;
1022 let mut domain_size = 0;
1023 let mut sid_name_use = 0;
1024 LookupAccountSidW(
1025 ptr::null(),
1026 sid.as_mut_ptr() as *mut _,
1027 ptr::null_mut(),
1028 &mut name_size,
1029 ptr::null_mut(),
1030 &mut domain_size,
1031 &mut sid_name_use,
1032 );
1033
1034 let error = GetLastError();
1035 if error != ERROR_INSUFFICIENT_BUFFER {
1036 return Err(error);
1037 }
1038
1039 // Allocate buffers for the account name and domain name
1040 let mut name = vec![0u16; name_size as usize];
1041 let mut domain = vec![0u16; domain_size as usize];
1042
1043 // LookupAccountSidW to actually get the name and domain
1044 if LookupAccountSidW(
1045 ptr::null(),
1046 sid.as_mut_ptr() as *mut _,
1047 name.as_mut_ptr(),
1048 &mut name_size,
1049 domain.as_mut_ptr(),
1050 &mut domain_size,
1051 &mut sid_name_use,
1052 ) == 0
1053 {
1054 return Err(GetLastError());
1055 }
1056
1057 // Convert wide strings to Rust Strings
1058 let domain_str = wide_to_string(&domain);
1059 let name_str = wide_to_string(&name);
1060
1061 Ok((domain_str, name_str))
1062 }
1063 }
1064
1065 let mut localized_admins = Vec::new();
1066 let mut admins = Vec::new();
1067
1068 // Retrieve account names for specific well-known SIDs
1069 if let Ok((_, name)) = get_account_name(WellKnownSidType::WinLocalSystemSid) {
1070 admins.push(name);
1071 }
1072
1073 if let Ok((_, name)) = get_account_name(WellKnownSidType::WinBuiltinAdministratorsSid) {
1074 admins.push(name);
1075 }
1076
1077 // Convert WMI names (admins) to the console's code page using a shell command (like "cmd /c")
1078 if !admins.is_empty() {
1079 let mut cmd = String::from("echo.");
1080 for admin in &admins {
1081 cmd.push_str(&format!(" & echo {}", admin));
1082 }
1083
1084 // Execute the command in the shell
1085 let output = std::process::Command::new("cmd")
1086 .args(&["/C", &cmd])
1087 .output()
1088 .expect("Failed to execute command");
1089
1090 if output.status.success() {
1091 let output_lines = output.stdout.split(|&b| b == b'\n');
1092 for line in output_lines {
1093 if !line.is_empty() {
1094 localized_admins.push(String::from_utf8_lossy(line).trim().to_string());
1095 }
1096 }
1097 }
1098 }
1099
1100 Ok(localized_admins)
1101}
1102
1103#[derive(Debug)]
1104pub enum ConfigError {
1105 PermissionDenied(String),
1106 FileNotFound(String),
1107 GeneralError(String),
1108}
1109
1110/// This function checks the permissions of a given configuration file.
1111/// On Windows, it uses the `icacls` command to verify permissions.
1112/// On Unix-like systems (Linux, macOS), it checks the file mode and ensures that
1113/// only the owner has access.
1114///
1115/// The function will skip permission checking if the `KSM_CONFIG_SKIP_MODE` environment
1116/// variable is set to `TRUE`.
1117///
1118/// On Windows, if access is denied or the file is missing, specific errors are returned.
1119/// The function also checks for warnings about overly permissive access modes.
1120///
1121/// # Errors
1122///
1123/// Returns:
1124/// - `ConfigError::PermissionDenied` if the file is accessible by users other than the owner.
1125/// - `ConfigError::FileNotFound` if the file does not exist.
1126/// - `ConfigError::GeneralError` if there are other issues, such as executing the `icacls` command.
1127///
1128/// # Example (Unix-like systems)
1129/// ```ignore
1130/// use std::fs;
1131/// #[cfg(unix)]
1132/// use std::os::unix::fs::PermissionsExt;
1133/// use keeper_secrets_manager_core::utils::check_config_mode;
1134/// use keeper_secrets_manager_core::utils::ConfigError;
1135///
1136/// // Create a file and set restrictive permissions (only owner can access).
1137/// let file_path = "client-config.json";
1138/// fs::File::create(file_path).unwrap();
1139/// let mut perms = fs::metadata(file_path).unwrap().permissions();
1140/// perms.set_mode(0o600); // Owner can read/write
1141/// fs::set_permissions(file_path, perms).unwrap();
1142///
1143/// // Run the function
1144/// match check_config_mode(file_path) {
1145/// Ok(true) => println!("Permissions are correctly set."),
1146/// Ok(false) => println!("Permissions are too open."),
1147/// Err(ConfigError::PermissionDenied(err)) => eprintln!("Permission denied: {}", err),
1148/// Err(ConfigError::FileNotFound(err)) => eprintln!("File not found: {}", err),
1149/// Err(ConfigError::GeneralError(err)) => eprintln!("General error: {}", err),
1150/// _ => eprintln!("Unknown error."),
1151/// }
1152/// ```
1153///
1154/// # Example (Windows)
1155/// ```rust,ignore
1156/// use keeper_secrets_manager_core::utils::check_config_mode;
1157/// use keeper_secrets_manager_core::utils::ConfigError;
1158///
1159/// let file_path = "client-config.json";
1160///
1161/// // Run the function
1162/// match check_config_mode(file_path) {
1163/// Ok(true) => println!("Permissions are correctly set."),
1164/// Ok(false) => println!("Permissions are too open."),
1165/// Err(ConfigError::PermissionDenied(err)) => eprintln!("Permission denied: {}", err),
1166/// Err(ConfigError::FileNotFound(err)) => eprintln!("File not found: {}", err),
1167/// Err(ConfigError::GeneralError(err)) => eprintln!("General error: {}", err),
1168/// _ => eprintln!("Unknown error."),
1169/// }
1170/// ```
1171pub fn check_config_mode(file: &str) -> Result<bool, ConfigError> {
1172 let skip_mode_check = env::var("KSM_CONFIG_SKIP_MODE")
1173 .unwrap_or("FALSE".to_string())
1174 .eq_ignore_ascii_case("TRUE");
1175
1176 if skip_mode_check {
1177 return Ok(true);
1178 }
1179
1180 #[cfg(target_os = "windows")]
1181 return check_windows_permissions(file);
1182
1183 #[cfg(not(target_os = "windows"))]
1184 return check_unix_permissions(file);
1185}
1186
1187#[cfg(target_os = "windows")]
1188fn check_windows_permissions(file: &str) -> Result<bool, ConfigError> {
1189 use std::process::Command;
1190
1191 // Execute the `icacls` command to check file permissions
1192 let output = Command::new("icacls")
1193 .arg(file)
1194 .output()
1195 .map_err(|e| ConfigError::GeneralError(format!("Error executing icacls: {}", e)))?;
1196
1197 if !output.status.success() {
1198 return match output.status.code() {
1199 Some(2) => Err(ConfigError::FileNotFound(file.to_string())),
1200 Some(5) => Err(ConfigError::PermissionDenied(file.to_string())),
1201 _ => Err(ConfigError::GeneralError(
1202 "Unknown error in icacls".to_string(),
1203 )),
1204 };
1205 }
1206
1207 // Additional checks for user permissions
1208 if !is_file_accessible(file) {
1209 return Err(ConfigError::PermissionDenied(format!(
1210 "Access denied to {}",
1211 file
1212 )));
1213 }
1214
1215 Ok(true)
1216}
1217
1218#[cfg(not(target_os = "windows"))]
1219fn check_unix_permissions(file: &str) -> Result<bool, ConfigError> {
1220 // Check if the file exists first
1221
1222 use std::path::Path;
1223 let file_path = Path::new(file);
1224 if !file_path.exists() {
1225 return Err(ConfigError::FileNotFound(file.to_string()));
1226 }
1227
1228 // Attempt to open the file to verify access permissions
1229 let metadata =
1230 fs::metadata(file_path).map_err(|_| ConfigError::FileNotFound(file.to_string()))?;
1231 if !is_file_accessible(file) {
1232 return Err(ConfigError::PermissionDenied(file.to_string()));
1233 }
1234 // Retrieve file mode and permissions for validation
1235 let permissions = metadata.permissions().mode();
1236 if permissions & 0o077 != 0 {
1237 eprintln!(
1238 "Warning: File permissions for {} are too open ({:o}). Consider setting to 0600.",
1239 file, permissions
1240 );
1241 return Err(ConfigError::PermissionDenied(format!(
1242 "File permissions too open for {}",
1243 file
1244 )));
1245 }
1246
1247 Ok(true)
1248}
1249
1250// Check if file is accessible
1251fn is_file_accessible(file: &str) -> bool {
1252 File::open(file).is_ok()
1253}
1254
1255#[derive(Debug)]
1256pub struct PasswordOptions {
1257 length: usize,
1258 lowercase: Option<i32>,
1259 uppercase: Option<i32>,
1260 digits: Option<i32>,
1261 special_characters: Option<i32>,
1262 special_characterset: String,
1263}
1264
1265impl PasswordOptions {
1266 /// Creates a new PasswordOptions with default values.
1267 pub fn new() -> Self {
1268 PasswordOptions {
1269 length: DEFAULT_PASSWORD_LENGTH,
1270 lowercase: None,
1271 uppercase: None,
1272 digits: None,
1273 special_characters: None,
1274 special_characterset: String::from(SPECIAL_CHARACTERS),
1275 }
1276 }
1277
1278 /// Set the password length.
1279 pub fn length(mut self, length: usize) -> Self {
1280 if length > 0 {
1281 self.length = length;
1282 } else {
1283 self.length = 32
1284 }
1285 self
1286 }
1287
1288 /// Set the minimum number of lowercase characters.
1289 pub fn lowercase(mut self, count: i32) -> Self {
1290 self.lowercase = Some(count);
1291 self
1292 }
1293
1294 /// Set the minimum number of uppercase characters.
1295 pub fn uppercase(mut self, count: i32) -> Self {
1296 self.uppercase = Some(count);
1297 self
1298 }
1299
1300 /// Set the minimum number of digits.
1301 pub fn digits(mut self, count: i32) -> Self {
1302 self.digits = Some(count);
1303 self
1304 }
1305
1306 /// Set the minimum number of special characters.
1307 pub fn special_characters(mut self, count: i32) -> Self {
1308 self.special_characters = Some(count);
1309 self
1310 }
1311
1312 /// Set the custom set of special characters.
1313 pub fn special_characterset(mut self, charset: String) -> Self {
1314 self.special_characterset = charset;
1315 self
1316 }
1317}
1318
1319impl Default for PasswordOptions {
1320 fn default() -> Self {
1321 Self::new()
1322 }
1323}
1324
1325/// Generates a new password based on the specified options.
1326///
1327/// The generated password will adhere to the constraints set by the
1328/// provided `PasswordOptions`. If the specified character counts exceed
1329/// the total desired password length, an error will be returned.
1330///
1331/// # Parameters
1332///
1333/// - `options`: An instance of `PasswordOptions` that defines the desired
1334/// characteristics of the password, such as length and minimum character
1335/// counts for lowercase, uppercase, digits, and special characters.
1336///
1337/// # Returns
1338///
1339/// - `Ok(String)`: A randomly generated password that meets the specified
1340/// criteria.
1341/// - `Err(String)`: An error message if the specified character counts
1342/// exceed the total password length or if there are any issues during
1343/// password generation.
1344///
1345/// # Example
1346///
1347/// ```rust
1348/// use keeper_secrets_manager_core::utils::generate_password_with_options;
1349/// use keeper_secrets_manager_core::utils::PasswordOptions;
1350///
1351/// let options = PasswordOptions::new()
1352/// .length(16)
1353/// .lowercase(4)
1354/// .uppercase(4)
1355/// .digits(4);
1356///
1357/// match generate_password_with_options(options) {
1358/// Ok(password) => println!("Generated Password: {}", password),
1359/// Err(err) => eprintln!("Error: {}", err),
1360/// }
1361/// ```
1362///
1363/// # Panics
1364///
1365/// This function does not panic, but will return an error if constraints are not met.
1366///
1367/// # Errors
1368///
1369/// - If the specified lowercase, uppercase, digits, and special characters
1370/// exceed the total length of the password, an error will be returned.
1371pub fn generate_password_with_options(options: PasswordOptions) -> Result<String, KSMRError> {
1372 let mut rng = thread_rng();
1373
1374 // Collect the counts for each character type
1375 let lowercase_count = options.lowercase.unwrap_or(0).max(0);
1376 let uppercase_count = options.uppercase.unwrap_or(0).max(0);
1377 let digits_count = options.digits.unwrap_or(0).max(0);
1378 let special_count = options.special_characters.unwrap_or(0).max(0);
1379
1380 // Calculate the total number of specified characters
1381 let total_specified = lowercase_count + uppercase_count + digits_count + special_count;
1382
1383 // Check if specified characters exceed the total length
1384 if total_specified > options.length as i32 {
1385 return Err(KSMRError::PasswordCreationError(format!(
1386 "The specified character counts ({}) exceed the total password length ({})!",
1387 total_specified, options.length
1388 )));
1389 }
1390
1391 let extra_count = options.length as i32 - total_specified;
1392
1393 // Build the extra character pool based on available options
1394 let mut extra_chars = String::new();
1395 if options.lowercase.is_some() || lowercase_count > 0 {
1396 extra_chars.push_str("abcdefghijklmnopqrstuvwxyz");
1397 }
1398 if options.uppercase.is_some() || uppercase_count > 0 {
1399 extra_chars.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
1400 }
1401 if options.digits.is_some() || digits_count > 0 {
1402 extra_chars.push_str("0123456789");
1403 }
1404 if options.special_characters.is_some() || special_count > 0 {
1405 extra_chars.push_str(&options.special_characterset);
1406 }
1407 if extra_chars.is_empty() {
1408 extra_chars.push_str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
1409 extra_chars.push_str(SPECIAL_CHARACTERS);
1410 }
1411
1412 // Initialize the category map
1413 let category_map = vec![
1414 (lowercase_count as usize, "abcdefghijklmnopqrstuvwxyz"),
1415 (uppercase_count as usize, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
1416 (digits_count as usize, "0123456789"),
1417 (special_count as usize, &options.special_characterset),
1418 (extra_count.max(0) as usize, &extra_chars),
1419 ];
1420
1421 let mut password_list = Vec::new();
1422 for (count, chars) in category_map {
1423 let char_slice: Vec<char> = chars.chars().collect();
1424 let mut repeated_chars = char_slice.iter().cycle(); // Infinite repetition
1425 for _ in 0..count {
1426 if let Some(&sample) = repeated_chars.next() {
1427 password_list.push(sample);
1428 }
1429 }
1430 }
1431
1432 let mut remaining_length = options.length - password_list.len();
1433
1434 while remaining_length > 0 {
1435 // Randomly select additional characters from the extra characters
1436 let extra_char_slice: Vec<char> = extra_chars.chars().collect();
1437 let additional_samples: Vec<char> = extra_char_slice
1438 .choose_multiple(&mut rng, remaining_length)
1439 .cloned()
1440 .collect();
1441
1442 password_list.extend(additional_samples);
1443 remaining_length = options.length - password_list.len()
1444 }
1445 password_list.shuffle(&mut rng);
1446
1447 Ok(password_list.into_iter().collect())
1448}
1449
1450pub fn generate_password() -> Result<String, KSMRError> {
1451 let password_options_default = PasswordOptions::new();
1452 generate_password_with_options(password_options_default)
1453}