Skip to main content

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