diff --git a/Cargo.lock b/Cargo.lock index d6ce4d0a6824f189d235de3dcc85e77eb4041263..6b87192615ebc40417f6ce7ade2c515238489f79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,54 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bcrypt-pbkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2" +dependencies = [ + "blowfish", + "pbkdf2", + "sha2", +] + [[package]] name = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cbitset" version = "0.2.0" @@ -32,6 +74,31 @@ dependencies = [ "libc", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crt0" version = "0.1.0" @@ -44,10 +111,41 @@ version = "0.1.0" name = "crtn" version = "0.1.0" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "dlmalloc" version = "0.2.4" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "goblin" version = "0.7.1" @@ -59,6 +157,24 @@ dependencies = [ "scroll", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "ld_so" version = "0.1.0" @@ -75,6 +191,16 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.1" @@ -99,6 +225,28 @@ dependencies = [ "autocfg", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", + "sha2", +] + [[package]] name = "plain" version = "0.2.3" @@ -140,8 +288,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.5.1", "rand_hc", + "rand_pcg", ] [[package]] @@ -151,7 +300,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.5.1", ] [[package]] @@ -160,13 +309,19 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core", + "rand_core 0.5.1", ] [[package]] @@ -176,17 +331,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a404fd88e0f817fc1c3351b9ba2207ffa65038cdde464405308a5f5d254835fe" dependencies = [ "libc", - "rand_core", + "rand_core 0.5.1", "winapi", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rand_xorshift" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" dependencies = [ - "rand_core", + "rand_core 0.5.1", ] [[package]] @@ -216,14 +380,18 @@ dependencies = [ name = "relibc" version = "0.2.5" dependencies = [ + "base64ct", + "bcrypt-pbkdf", "bitflags", "cbitset", "cc", "dlmalloc", "goblin", "libc", + "md-5", "memchr", "memoffset", + "pbkdf2", "plain", "posix-regex", "rand", @@ -233,9 +401,21 @@ dependencies = [ "redox-path", "redox_syscall", "sc", + "scrypt", + "sha-crypt", + "sha2", "unicode-width", ] +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "sc" version = "0.2.7" @@ -262,6 +442,45 @@ dependencies = [ "syn", ] +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "sha-crypt" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88e79009728d8311d42d754f2f319a975f9e38f156fd5e422d2451486c78b286" +dependencies = [ + "base64ct", + "sha2", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "2.0.48" @@ -273,6 +492,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -285,6 +510,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 3e8e98e16c9309e16349669c1a71b9ee5760645f..4e029d5d457f29066980ffab790f5780d19f7e02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ memoffset = "0.9" posix-regex = { path = "posix-regex", features = ["no_std"] } # TODO: For some reason, rand_jitter hasn't been updated to use the latest rand_core -rand = { version = "0.7", default-features = false } +rand = { version = "0.7", default-features = false, features = ["small_rng"] } rand_xorshift = "0.2" rand_jitter = "0.3" @@ -36,6 +36,13 @@ memchr = { version = "2.2.0", default-features = false } plain = "0.2" unicode-width = "0.1" __libc_only_for_layout_checks = { package = "libc", version = "0.2.149", optional = true } +md5-crypto = { package = "md-5", version = "0.10.6", default-features = false} +sha-crypt = { version = "0.5", default-features = false } +base64ct = { version = "1.6", default-features = false, features = ["alloc"] } +bcrypt-pbkdf = { version = "0.10", default-features = false, features = ["alloc"] } +scrypt = { version = "0.11", default-features = false, features = ["simple"]} +pbkdf2 = { version = "0.12", features = ["sha2"]} +sha2 = { version = "0.10", default-features = false } [dependencies.goblin] version = "0.7" diff --git a/src/header/crypt/blowfish.rs b/src/header/crypt/blowfish.rs new file mode 100644 index 0000000000000000000000000000000000000000..3d24325d90882a7d642fc68d2c20554594f549db --- /dev/null +++ b/src/header/crypt/blowfish.rs @@ -0,0 +1,90 @@ +use crate::platform::types::{c_uchar, c_uint}; +use alloc::{string::String, vec::Vec}; +use base64ct::{Base64Bcrypt, Encoding}; +use bcrypt_pbkdf::bcrypt_pbkdf; +use core::str; + +const MIN_COST: u32 = 4; +const MAX_COST: u32 = 31; +const BHASH_WORDS: usize = 8; +const BHASH_OUTPUT_SIZE: usize = BHASH_WORDS * 4; + +/// Inspired by https://github.com/Keats/rust-bcrypt/blob/87fc59e917bcb6cf3f3752fc7f2b4c659d415597/src/lib.rs#L135 +fn split_with_prefix(hash: &str) -> Option<(&str, &str, c_uint)> { + let valid_prefixes = ["2y", "2b", "2a", "2x"]; + + // Should be [prefix, cost, hash] + let raw_parts: Vec<_> = hash.split('$').skip(1).collect(); + if raw_parts.len() != 3 { + return None; + } + + let prefix = raw_parts[0]; + let setting = raw_parts[2]; + + if !valid_prefixes.contains(&prefix) { + return None; + } + + raw_parts[1] + .parse::<c_uint>() + .ok() + .map(|cost| (prefix, setting, cost)) +} + +/// Performs Blowfish key derivation on a given password with a specific setting. +/// +/// # Parameters +/// * `passw`: The password to be hashed. It must be a string slice (`&str`). +/// * `setting`: The settings for the Blowfish key derivation. It must be a string slice (`&str`) +/// and should follow the format `$<prefix>$<cost>$<setting>`, where `<prefix>` is a string that +/// indicates the type of the hash (e.g., "$2a$"), `<cost>` is a decimal number representing +/// the cost factor for the Blowfish operation, and `<setting>` is a base64-encoded string +/// representing the salt to be used for the Blowfish function. +/// +/// # Returns +/// * `Option<String>`: Returns `Some(String)` if the Blowfish operation was successful, where the +/// returned string is the result of the Blowfish operation formatted according to the Modular +/// Crypt Format (MCF). If the Blowfish operation failed, it returns `None`. +/// +/// # Errors +/// * If the cost factor is outside the range `[MIN_COST, MAX_COST]`. +/// +/// # Example +/// ``` +/// let password = "correctbatteryhorsestapler"; +/// let setting = "$2y$12$L6Bc/AlTQHyd9liGgGEZyO"; +/// let result = crypt_blowfish(password, setting); +/// assert!(result.is_some()); +///``` +/// +/// # Note +/// The `crypt_blowfish` function uses the Blowfish block cipher for hashing. +/// The output of the Blowfish operation is base64-encoded using the BCrypt variant of base64. +pub fn crypt_blowfish(passw: &str, setting: &str) -> Option<String> { + if let Some((prefix, setting, cost)) = split_with_prefix(setting) { + if !(MIN_COST..=MAX_COST).contains(&cost) { + return None; + } + // Passwords need to be null terminated + let mut vec = Vec::with_capacity(passw.len() + 1); + vec.extend_from_slice(passw.as_bytes()); + vec.push(0); + + // We only consider the first 72 chars; truncate if necessary. + let passw_t = if vec.len() > 72 { &vec[..72] } else { &vec }; + let passw: &[c_uchar] = &passw_t; + let setting = setting.as_bytes(); + let mut output = vec![0; BHASH_OUTPUT_SIZE + 1]; + + bcrypt_pbkdf(passw, setting, cost, &mut output).ok()?; + Some(format!( + "${}${}${}", + prefix, + cost, + Base64Bcrypt::encode_string(&output), + )) + } else { + None + } +} diff --git a/src/header/crypt/cbindgen.toml b/src/header/crypt/cbindgen.toml new file mode 100644 index 0000000000000000000000000000000000000000..bdcf5ebda142d88bf5b02188808d43672ed74ef1 --- /dev/null +++ b/src/header/crypt/cbindgen.toml @@ -0,0 +1,9 @@ +include_guard = "_RELIBC_CRYPT_H" +language = "C" +style = "Type" +no_includes = true +cpp_compat = true + + +[enum] +prefix_with_name = true \ No newline at end of file diff --git a/src/header/crypt/md5.rs b/src/header/crypt/md5.rs new file mode 100644 index 0000000000000000000000000000000000000000..25cfacff37c2f5f1bcfd059bd7add81b0628bd23 --- /dev/null +++ b/src/header/crypt/md5.rs @@ -0,0 +1,146 @@ +use crate::platform::types::c_uchar; +use alloc::string::String; +use base64ct::{Base64ShaCrypt, Encoding}; +use core::str; +use md5_crypto::{Digest, Md5}; + +// Block size for MD5 +const BLOCK_SIZE: usize = 16; +// PWD part length of the password string +const PW_SIZE_MD5: usize = 22; +// Maximum length of a setting +const SALT_MAX: usize = 8; +// Inverse encoding map for MD5. +const MAP_MD5: [c_uchar; BLOCK_SIZE] = [12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11]; + +const KEY_MAX: usize = 30000; + +fn encode_md5(source: &[c_uchar]) -> Option<[c_uchar; PW_SIZE_MD5]> { + let mut transposed = [0; BLOCK_SIZE]; + for (i, &ti) in MAP_MD5.iter().enumerate() { + transposed[i] = source[ti as usize]; + } + let mut buf = [0; PW_SIZE_MD5]; + Base64ShaCrypt::encode(&transposed, &mut buf).ok()?; + Some(buf) +} + +/// Function taken from PR: https://github.com/RustCrypto/password-hashes/pull/351 +/// This won't be needed once the PR is merged +fn inner_md5(passw: &str, setting: &str) -> Option<String> { + let mut digest_b = Md5::default(); + digest_b.update(passw); + digest_b.update(setting); + digest_b.update(passw); + let hash_b = digest_b.finalize(); + + let mut digest_a = Md5::default(); + digest_a.update(passw); + digest_a.update("$1$"); + digest_a.update(setting); + + let mut pw_len = passw.len(); + let rounds = pw_len / BLOCK_SIZE; + for _ in 0..rounds { + digest_a.update(hash_b); + } + + // leftover passw + digest_a.update(&hash_b[..(pw_len - rounds * BLOCK_SIZE)]); + + while pw_len > 0 { + match pw_len & 1 { + 0 => digest_a.update(&passw[..1]), + 1 => digest_a.update([0u8]), + _ => unreachable!(), + } + pw_len >>= 1; + } + + let mut hash_a = digest_a.finalize(); + + // Repeatedly run the collected hash value through MD5 to burn + // CPU cycles + for i in 0..1000_usize { + // new hasher + let mut hasher = Md5::default(); + + // Add key or last result + if (i & 1) != 0 { + hasher.update(passw); + } else { + hasher.update(hash_a); + } + + // Add setting for numbers not divisible by 3 + if i % 3 != 0 { + hasher.update(setting); + } + + // Add key for numbers not divisible by 7 + if i % 7 != 0 { + hasher.update(passw); + } + + // Add key or last result + if (i & 1) != 0 { + hasher.update(hash_a); + } else { + hasher.update(passw); + } + + // digest_c.clone_from_slice(&hasher.finalize()); + hash_a = hasher.finalize(); + } + encode_md5(hash_a.as_slice()) + .map(|encstr| format!("$1${}${}", setting, str::from_utf8(&encstr).unwrap())) +} + +/// Performs MD5 hashing on a given password with a specific setting. +/// +/// # Parameters +/// * `passw`: The password to be hashed. It must be a string slice (`&str`). +/// * `setting`: The settings for the MD5 hashing. It must be a string slice (`&str`) +/// and should start with "$1$". The rest of the string should represent the salt +/// to be used for the MD5 hashing. +/// +/// # Returns +/// * `Option<String>`: Returns `Some(String)` if the MD5 operation was successful, where the +/// returned string is the result of the MD5 operation formatted according to the Modular +/// Crypt Format (MCF). If the MD5 operation failed, it returns `None`. +/// +/// # Errors +/// * If the `passw` length exceeds `KEY_MAX`. +/// * If the `setting` does not start with "$1$". +/// +/// # Example +/// ``` +/// let password = "my_password"; +/// let setting = "$1$saltstring"; +/// let result = crypt_md5(password, setting); +/// assert!(result.is_some()); +/// ``` +/// +/// # Note +/// The `crypt_md5` function uses the MD5 hashing algorithm for hashing. +/// The output of the MD5 operation is base64-encoded using the BCrypt variant of base64. +pub fn crypt_md5(passw: &str, setting: &str) -> Option<String> { + /* reject large keys */ + if passw.len() > KEY_MAX { + return None; + } + + if &setting[0..3] != "$1$" { + return None; + } + + let cursor = 3; + let slen = cursor + + setting[cursor..cursor + SALT_MAX] + .chars() + .take_while(|c| *c != '$') + .count(); + let setting = &setting[cursor..slen]; + + inner_md5(passw, setting) +} diff --git a/src/header/crypt/mod.rs b/src/header/crypt/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..aa5cfac0cdbccb64334f43c7c4501603aaa9e1fb --- /dev/null +++ b/src/header/crypt/mod.rs @@ -0,0 +1,101 @@ +use ::scrypt::password_hash::{Salt, SaltString}; +use alloc::{ + ffi::CString, + string::{String, ToString}, +}; +use core::ptr; +use rand::{rngs::SmallRng, RngCore, SeedableRng}; + +use crate::{ + c_str::CStr, + header::{errno::EINVAL, stdlib::rand}, + platform::{self, types::*}, +}; + +mod blowfish; +mod md5; +mod pbkdf2; +mod scrypt; +mod sha; + +use self::{ + blowfish::crypt_blowfish, + md5::crypt_md5, + pbkdf2::crypt_pbkdf2, + scrypt::crypt_scrypt, + sha::{crypt_sha, ShaType::*}, +}; + +#[repr(C)] +pub struct crypt_data { + initialized: c_int, + buff: [c_char; 256], +} + +impl crypt_data { + pub fn new() -> Self { + crypt_data { + initialized: 1, + buff: [0; 256], + } + } +} + +fn gen_salt() -> Option<String> { + let mut rng = SmallRng::seed_from_u64(unsafe { rand() as u64 }); + let mut bytes = [0u8; Salt::RECOMMENDED_LENGTH]; + rng.fill_bytes(&mut bytes); + Some(SaltString::encode_b64(&bytes).ok()?.as_str().to_string()) +} + +#[no_mangle] +pub unsafe extern "C" fn crypt_r( + key: *const c_char, + setting: *const c_char, + data: *mut crypt_data, +) -> *mut c_char { + if (*data).initialized == 0 { + *data = crypt_data::new(); + } + + let key = CStr::from_ptr(key).to_str().expect("key must be utf-8"); + let setting = CStr::from_ptr(setting) + .to_str() + .expect("setting must be utf-8"); + let setting_bytes = setting.as_bytes(); + + let encoded = if setting_bytes[0] == b'$' && setting_bytes[1] != 0 && setting_bytes[2] != 0 { + if setting_bytes[1] == b'1' && setting_bytes[2] == b'$' { + crypt_md5(key, setting) + } else if setting_bytes[1] == b'2' && setting_bytes[3] == b'$' { + crypt_blowfish(key, setting) + } else if setting_bytes[1] == b'5' && setting_bytes[2] == b'$' { + crypt_sha(key, setting, Sha256) + } else if setting_bytes[1] == b'6' && setting_bytes[2] == b'$' { + crypt_sha(key, setting, Sha512) + } else if setting_bytes[1] == b'7' && setting_bytes[2] == b'$' { + crypt_scrypt(key, setting) + } else if setting_bytes[1] == b'8' && setting_bytes[2] == b'$' { + crypt_pbkdf2(key, setting) + } else { + platform::errno = EINVAL; + return ptr::null_mut(); + } + } else { + None + }; + + if let Some(inner) = encoded { + let len = inner.len(); + if let Ok(ret) = CString::new(inner) { + let ret_ptr = ret.into_raw(); + let dst = (*data).buff.as_mut_ptr(); + ptr::copy_nonoverlapping(ret_ptr, dst.cast(), len); + ret_ptr.cast() + } else { + ptr::null_mut() + } + } else { + ptr::null_mut() + } +} diff --git a/src/header/crypt/pbkdf2.rs b/src/header/crypt/pbkdf2.rs new file mode 100644 index 0000000000000000000000000000000000000000..43e0e24d536e02c9a301ef570a1ead4e3badf88c --- /dev/null +++ b/src/header/crypt/pbkdf2.rs @@ -0,0 +1,64 @@ +use super::gen_salt; +use alloc::string::{String, ToString}; +use base64ct::{Base64Bcrypt, Encoding}; +use core::{str, u32}; +use pbkdf2::pbkdf2_hmac; +use sha2::Sha256; + +/// Performs PBKDF2 key derivation on a given password with a specific setting. +/// +/// # Parameters +/// * `passw`: The password to be hashed. It must be a string slice (`&str`). +/// * `setting`: The settings for the PBKDF2 key derivation. It must be a string slice (`&str`) +/// and should follow the format `$<iter>$<salt>`. The `<iter>` part should be a hexadecimal +/// number representing the iteration count for the PBKDF2 function. The `<salt>` part should +/// be a base64-encoded string representing the salt to be used for the PBKDF2 function. +/// +/// # Returns +/// * `Option<String>`: Returns `Some(String)` if the PBKDF2 operation was successful, where the +/// returned string is the result of the PBKDF2 operation formatted according to the Modular +/// Crypt Format (MCF). If the PBKDF2 operation failed, it returns `None`. +/// +/// # Errors +/// * If the `setting` does not contain a '$' character. +/// * If the `setting` contains another '$' character after the first one. +/// * If the `<salt>` part of the `setting` is empty. +/// * If the `<iter>` part of the `setting` cannot be converted into a `u32` integer. +/// +/// # Example +/// ``` +/// let password = "my_password"; +/// let setting = "$8$3e8$salt"; +/// let result = crypt_pbkdf2(password, setting); +/// assert!(result.is_some()); +/// ``` +/// +/// # Note +/// The `crypt_pbkdf2` function uses the SHA256 hashing algorithm for the PBKDF2 operation. +/// The output of the PBKDF2 operation is base64-encoded using the BCrypt variant of base64. +pub fn crypt_pbkdf2(passw: &str, setting: &str) -> Option<String> { + if let Some((iter_str, salt)) = &setting[3..].split_once('$') { + if salt.contains('$') { + return None; + } + + let actual_salt = if salt.len() > 0 { + salt.to_string() + } else { + gen_salt()? + }; + + let iter = u32::from_str_radix(iter_str, 16).ok()?; + let mut buffer = [0u8; 32]; + pbkdf2_hmac::<Sha256>(passw.as_bytes(), actual_salt.as_bytes(), iter, &mut buffer); + + Some(format!( + "$8${}${}${}", + iter_str, + salt, + Base64Bcrypt::encode_string(&buffer) + )) + } else { + None + } +} diff --git a/src/header/crypt/scrypt.rs b/src/header/crypt/scrypt.rs new file mode 100644 index 0000000000000000000000000000000000000000..e18df8a23416b71a5ecb55625519e6b5dd95c239 --- /dev/null +++ b/src/header/crypt/scrypt.rs @@ -0,0 +1,115 @@ +use super::gen_salt; +use crate::platform::types::*; +use alloc::string::{String, ToString}; +use base64ct::{Base64Bcrypt, Encoding}; +use core::{str, u32}; +use scrypt::{scrypt, Params}; + +/// Map for encoding and decoding +#[inline(always)] +fn to_digit(c: char, radix: u32) -> Option<u32> { + match c { + '.' => Some(0), + '/' => Some(1), + _ => c.to_digit(radix).map(|d| d + 2), + } +} + +/// Decodes a 5 character lengt str value to c_uint +/// +/// # Arguments +/// +/// * `value` - A string slice that represents a u32 value in base64 +/// +/// # Returns +/// +/// * `Option<c_uint>` - Returns the decoded c_uint value if successful, otherwise None +fn dencode_uint(value: &str) -> Option<c_uint> { + if value.len() != 5 { + return None; + } + + let result = value + .chars() + .enumerate() + .try_fold(0 as c_uint, |acc, (i, c)| { + acc.checked_add((to_digit(c, 30)? as c_uint) << (i * 6)) + }); + + Some(result?) +} + +/// Reads settings for password encryption +/// +/// # Arguments +/// +/// * `setting` - A string slice that represents the settings +/// +/// # Returns +/// +/// * `Option<(c_uchar, c_uint, c_uint, String)>` - Returns a tuple containing the settings if successful, otherwise None +fn read_setting(setting: &str) -> Option<(c_uchar, c_uint, c_uint, String)> { + let nlog2 = to_digit(setting.chars().next()?, 30)? as c_uchar; + let r = dencode_uint(&setting[1..6])?; + let p = dencode_uint(&setting[6..11])?; + + let salt = &setting[11..]; + let actual_salt = if salt.len() > 0 { + salt.to_string() + } else { + gen_salt()? + }; + + Some((nlog2, r, p, actual_salt)) +} + +/// Performs Scrypt key derivation on a given password with a specific setting. +/// +/// # Parameters +/// * `passw`: The password to be hashed. It must be a string slice (`&str`). +/// * `setting`: The settings for the Scrypt key derivation. It must be a string slice (`&str`) +/// and should follow the format `$<Nlog2>$<r>$<p>$<salt>`. The `<Nlog2>` part should be a decimal +/// number representing the logarithm base 2 of the CPU/memory cost factor N for Scrypt. The `<r>` +/// part should be a decimal number representing the block size r. The `<p>` part should be a decimal +/// number representing the parallelization factor p. The `<salt>` part should be a base64-encoded +/// string representing the salt to be used for the Scrypt function. +/// +/// # Returns +/// * `Option<String>`: Returns `Some(String)` if the Scrypt operation was successful, where the +/// returned string is the result of the Scrypt operation formatted according to the Modular +/// Crypt Format (MCF). If the Scrypt operation failed, it returns `None`. +/// +/// # Errors +/// * If the `setting` length is less than 14 characters. +/// * If the `scrypt` function fails to perform the Scrypt operation. +/// +/// # Example +/// ``` +/// let password = "my_password"; +/// let setting = "$7$C6..../....SodiumChloride"; +/// let result = crypt_scrypt(password, setting); +/// assert!(result.is_some()); +/// ``` +/// +/// # Note +/// The `crypt_scrypt` function uses the Scrypt key derivation function for hashing. +/// The output of the Scrypt operation is base64-encoded using the BCrypt variant of base64. +pub fn crypt_scrypt(passw: &str, setting: &str) -> Option<String> { + if setting.len() < 14 { + return None; + } + + let (nlog2, r, p, salt) = read_setting(&setting[3..])?; + + let params = Params::new(nlog2, r, p, 32).ok()?; + let mut output = [0u8; 32]; + + scrypt(passw.as_bytes(), salt.as_bytes(), ¶ms, &mut output).ok()?; + + Some(format!( + "$7${}${}${}", + &setting[3..14], + salt, + Base64Bcrypt::encode_string(&output) + )) +} diff --git a/src/header/crypt/sha.rs b/src/header/crypt/sha.rs new file mode 100644 index 0000000000000000000000000000000000000000..366ccc87fc35e413c32ab7e5269f199a57607698 --- /dev/null +++ b/src/header/crypt/sha.rs @@ -0,0 +1,141 @@ +use alloc::string::{String, ToString}; + +use sha_crypt::{ + sha256_crypt_b64, sha512_crypt_b64, Sha256Params, Sha512Params, ROUNDS_DEFAULT, ROUNDS_MAX, + ROUNDS_MIN, +}; + +use crate::platform::types::*; + +// key limit is not part of the original design, added for DoS protection. +// rounds limit has been lowered (versus the reference/spec), also for DoS +// protection. runtime is O(klen^2 + klen*rounds) +const KEY_MAX: usize = 256; +const SALT_MAX: usize = 16; +const RSTRING: &str = "rounds="; + +pub enum ShaType { + Sha256, + Sha512, +} + +/// Performs SHA hashing on a given password with a specific setting. +/// +/// # Parameters +/// * `passw`: The password to be hashed. It must be a string slice (`&str`). +/// * `setting`: The settings for the SHA hashing. It must be a string slice (`&str`) +/// and should start with "$5$" for SHA256 or "$6$" for SHA512. The rest of the string should represent the salt +/// to be used for the SHA hashing. +/// * `cipher`: The type of SHA algorithm to use. It should be either `ShaType::Sha256` or `ShaType::Sha512`. +/// +/// # Returns +/// * `Option<String>`: Returns `Some(String)` if the SHA operation was successful, where the +/// returned string is the result of the SHA operation formatted according to the Modular +/// Crypt Format (MCF). If the SHA operation failed, it returns `None`. +/// +/// # Errors +/// * If the `passw` length exceeds `KEY_MAX`. +/// * If the `setting` does not start with "$5$" or "$6$". +/// * If the `setting` does not contain a '$' character. +/// * If the `setting` contains another '$' character after the first one. +/// * If the `setting` contains invalid characters. +/// * If the `setting` contains an invalid number of rounds. +/// * If the `sha256_crypt_b64` or `sha512_crypt_b64` function fails to hash the password. +/// +/// # Example +/// ``` +/// let password = "my_password"; +/// let setting = "$5$rounds=1400$anotherlongsaltstringg"; +/// let result = crypt_sha(password, setting, ShaType::Sha256); +/// assert!(result.is_some()); +/// ``` +/// +/// # Note +/// The `crypt_sha` function uses the SHA256 or SHA512 hashing algorithm for hashing. +/// The output of the SHA operation is base64-encoded using the BCrypt variant of base64. +pub fn crypt_sha(passw: &str, setting: &str, cipher: ShaType) -> Option<String> { + let mut cursor = 3; + let rounds; + + /* reject large keys */ + if passw.len() > KEY_MAX { + return None; + } + + // SHA256 + // setting: $5$rounds=n$setting$ (rounds=n$ and closing $ are optional) + // SHA512 + // setting: $6$rounds=n$setting$ (rounds=n$ and closing $ are optional) + let param = match cipher { + ShaType::Sha256 => "$5$", + ShaType::Sha512 => "$6$", + }; + + if &setting[0..3] != param { + return None; + } + + let has_round; + // 7 is len("rounds=") + if &setting[cursor..cursor + 7] == RSTRING { + cursor += 7; + has_round = true; + if let Some(c_end) = setting[cursor..].chars().position(|r| r == '$') { + if let Ok(u) = setting[cursor..cursor + c_end].parse::<c_ulong>() { + cursor += c_end + 1; + rounds = u.min(ROUNDS_MAX as c_ulong).max(ROUNDS_MIN as c_ulong); + } else { + return None; + } + } else { + return None; + } + } else { + has_round = false; + rounds = ROUNDS_DEFAULT as c_ulong; + } + + let mut slen = cursor; + + for i in 0..SALT_MAX.min(setting.len() - cursor) { + let idx = cursor + i; + + if &setting[idx..idx + 1] == "$" { + break; + } + + // reject characters that interfere with /etc/shadow parsing + if &setting[idx..idx + 1] == "\n" || &setting[idx..idx + 1] == ":" { + return None; + } + slen += 1; + } + + let setting = &setting[cursor..slen]; + + if let Ok(enc) = match cipher { + ShaType::Sha256 => { + let params = Sha256Params::new(rounds as usize) + .unwrap_or(Sha256Params::new(ROUNDS_DEFAULT).unwrap()); + sha256_crypt_b64(passw.as_bytes(), setting.as_bytes(), ¶ms) + } + ShaType::Sha512 => { + let params = Sha512Params::new(rounds as usize) + .unwrap_or(Sha512Params::new(ROUNDS_DEFAULT).unwrap()); + sha512_crypt_b64(passw.as_bytes(), setting.as_bytes(), ¶ms) + } + } { + let (r_slice, rn_slice) = if has_round { + (RSTRING, rounds.to_string() + "$") + } else { + ("", String::new()) + }; + + Some(format!( + "{}{}{}{}${}", + param, r_slice, rn_slice, setting, enc + )) + } else { + None + } +} diff --git a/src/header/mod.rs b/src/header/mod.rs index 14ff2f56b9b4eb13dc4babe197424e70dbad0b07..c820e41a4843b648d20b4e607285b911dad0e749 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -4,6 +4,7 @@ pub mod arpa_inet; pub mod assert; pub mod bits_pthread; pub mod bits_sched; +pub mod crypt; pub mod ctype; pub mod dirent; #[path = "dl-tls/mod.rs"] diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs index 1cb6236a3488af76b60b1a4c9cea5270c12c9a84..06ec98966471a98092ac99aa8b1dc363a7d77d76 100644 --- a/src/header/unistd/mod.rs +++ b/src/header/unistd/mod.rs @@ -10,8 +10,11 @@ use core::{ use crate::{ c_str::CStr, header::{ - errno, fcntl, limits, stdlib::getenv, sys_ioctl, sys_resource, sys_time, sys_utsname, - termios, time::timespec, + crypt::{crypt_data, crypt_r}, + errno, fcntl, limits, + stdlib::getenv, + sys_ioctl, sys_resource, sys_time, sys_utsname, termios, + time::timespec, }, platform::{self, types::*, Pal, Sys}, }; @@ -119,9 +122,10 @@ pub extern "C" fn confstr(name: c_int, buf: *mut c_char, len: size_t) -> size_t unimplemented!(); } -// #[no_mangle] -pub extern "C" fn crypt(key: *const c_char, salt: *const c_char) -> *mut c_char { - unimplemented!(); +#[no_mangle] +pub unsafe extern "C" fn crypt(key: *const c_char, salt: *const c_char) -> *mut c_char { + let mut data = crypt_data::new(); + crypt_r(key, salt, &mut data as *mut _) } #[no_mangle] diff --git a/tests/Makefile b/tests/Makefile index 3f6c43b305ba99165043520f9e9de50f5f3ac6bd..21ed06bbb11de3861031383c2cf685487f7eb00c 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -6,6 +6,12 @@ EXPECT_NAMES=\ assert \ constructor \ ctype \ + crypt/blowfish \ + crypt/md5 \ + crypt/pbkdf2 \ + crypt/scrypt \ + crypt/sha256 \ + crypt/sha512 \ destructor \ dirent/scandir \ errno \ diff --git a/tests/crypt/blowfish.c b/tests/crypt/blowfish.c new file mode 100644 index 0000000000000000000000000000000000000000..0a5b62e34b8bcaa6411b024458bc114c184f067b --- /dev/null +++ b/tests/crypt/blowfish.c @@ -0,0 +1,26 @@ +#include <assert.h> +#include <crypt.h> +#include <string.h> +#include <unistd.h> + +int main() +{ + char *expected_output = "$2y$12$CJr4uRfziaGp4CWIBk0fB.I2tCOHYe3pomaWbC53/92p"; + char *result = crypt("correctbatteryhorsestapler", "$2y$12$L6Bc/AlTQHyd9liGgGEZyO"); + assert(strcmp(result, expected_output) == 0); + + // Invalid salt for Blowfish + result = crypt("correctbatteryhorsestapler", "$2t$12$L6Bc/AlTQHyd9liGgGEZyO"); + assert(result == NULL); + + expected_output = "$2a$4$IAwt7hxuME3DekssMMTWU.xnJub2Xn45xK/oP.wWt3UC"; + result = crypt("password", "$2a$04$UuTkLRZZ6QofpDOlMz32Mu"); + assert(strcmp(result, expected_output) == 0); + + // Invalid salt for Blowfish + result = "$2b$10$testtesttesttes"; + result = crypt("correctbatteryhorsestapler", "$2y$12$L6Bc/AlTQHyd9liGgGEZyO"); + assert(result != NULL); + + return 0; +} diff --git a/tests/crypt/md5.c b/tests/crypt/md5.c new file mode 100644 index 0000000000000000000000000000000000000000..4251d70f9dbde7b574fc5b6f89aa0a9f7e69c5ff --- /dev/null +++ b/tests/crypt/md5.c @@ -0,0 +1,38 @@ +/* Copyright (C) 1991-2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <crypt.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +int main () { + const char salt[] = "$1$saltstring"; + char *cp; + int result = 0; + + cp = crypt ("Hello world!", salt); + + /* MD5 is disabled in FIPS mode. */ + if (cp) { + result |= strcmp ("$1$saltstri$YMyguxXMBpd2TEZ.vS/3q1", cp); + if (!result) + printf("Success!\n"); + } + + return result; +} \ No newline at end of file diff --git a/tests/crypt/pbkdf2.c b/tests/crypt/pbkdf2.c new file mode 100644 index 0000000000000000000000000000000000000000..f3e9a3cfbc1a9125c10d3b4a35464c9854635cce --- /dev/null +++ b/tests/crypt/pbkdf2.c @@ -0,0 +1,25 @@ +#include <assert.h> +#include <crypt.h> +#include <string.h> +#include <unistd.h> + +int main() +{ + char *expected_output = "$8$3e8$salt$ZyTZgT5Pyp4CKdom6q6zHg0QrrAQO4ptWYCpWz4gu16"; + char *result = crypt("pleaseletmein", "$8$3e8$salt"); + assert(strcmp(result, expected_output) == 0); + + // No salt + result = crypt("pleaseletmein", "$8$3e8$"); + assert(result != NULL); + + // Invalid encoded number for rounds + result = crypt("pleaseletmein", "$8$$salt"); + assert(result == NULL); + + // Invalid encoded number for rounds + result = crypt("pleaseletmein", "$8$.$salt"); + assert(result == NULL); + + return 0; +} diff --git a/tests/crypt/scrypt.c b/tests/crypt/scrypt.c new file mode 100644 index 0000000000000000000000000000000000000000..d6f70778ce1be3d4520148f2fcc3a2da8616aa29 --- /dev/null +++ b/tests/crypt/scrypt.c @@ -0,0 +1,25 @@ +#include <assert.h> +#include <crypt.h> +#include <string.h> +#include <unistd.h> + +int main() +{ + char *expected_output = "$7$C6..../....$SodiumChloride$aAM7wxp7ayfEF.ZLedy2490m85oOR228oZTB7jPbmdG"; + char *result = crypt("pleaseletmein", "$7$C6..../....SodiumChloride"); + assert(strcmp(result, expected_output) == 0); + + // No salt + result = crypt("pleaseletmein", "$7$C6..../...."); + assert(result != NULL); + + // Invalid encoded number for r + result = crypt("pleaseletmein", "$7$C6.../....SodiumChloride"); + assert(result == NULL); + + // Invalid encoded number for p + result = crypt("pleaseletmein", "$7$C6..../...SodiumChloride"); + assert(result == NULL); + + return 0; +} diff --git a/tests/crypt/sha256.c b/tests/crypt/sha256.c new file mode 100644 index 0000000000000000000000000000000000000000..6e9c3ffa472bd9be6644cf0e5dd107b7ee5ea131 --- /dev/null +++ b/tests/crypt/sha256.c @@ -0,0 +1,67 @@ +/* Copyright (C) 1991-2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <crypt.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +static const struct +{ + const char *salt; + const char *input; + const char *expected; +} tests[] = +{ + { "$5$saltstring", "Hello world!", + "$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5" }, + { "$5$rounds=10000$saltstringsaltstring", "Hello world!", + "$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2." + "opqey6IcA" }, + { "$5$rounds=5000$toolongsaltstring", "This is just a test", + "$5$rounds=5000$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPvOW8" + "mGRcvxa5" }, + { "$5$rounds=1400$anotherlongsaltstring", + "a very much longer text to encrypt. This one even stretches over more" + "than one line.", + "$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyxf12" + "oP84Bnq1" }, + { "$5$rounds=77777$short", + "we have a short salt string but not a short password", + "$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/" }, + { "$5$rounds=123456$asaltof16chars..", "a short string", + "$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPyzV/" + "cZKmF/wJvD" }, + { "$5$rounds=10$roundstoolow", "the minimum number is still observed", + "$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/gL97" + "2bIC" }, +}; + +const int ntests = sizeof(tests) / sizeof(tests[0]); + +int main (void) { + int result = 0; + + for (int i = 0; i < ntests; ++i){ + char *cp = crypt (tests[i].input, tests[i].salt); + if (strcmp (cp, tests[i].expected) != 0) { + printf ("test %d: expected \"%s\", got \"%s\"\n", i, tests[i].expected, cp); + result = 1; + } + } + return result; +} \ No newline at end of file diff --git a/tests/crypt/sha512.c b/tests/crypt/sha512.c new file mode 100644 index 0000000000000000000000000000000000000000..efab1e316221621cbcc7364b65eaaaf2d2742c4a --- /dev/null +++ b/tests/crypt/sha512.c @@ -0,0 +1,72 @@ +/* Copyright (C) 1991-2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <crypt.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +static const struct +{ + const char *salt; + const char *input; + const char *expected; +} tests[] = +{ + { "$6$saltstring", "Hello world!", + "$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJu" + "esI68u4OTLiBFdcbYEdFCoEOfaS35inz1" }, + { "$6$rounds=10000$saltstringsaltstring", "Hello world!", + "$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sb" + "HbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v." }, + { "$6$rounds=5000$toolongsaltstring", "This is just a test", + "$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQ" + "zQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0" }, + { "$6$rounds=1400$anotherlongsaltstring", + "a very much longer text to encrypt. This one even stretches over more" + "than one line.", + "$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wP" + "vMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1" }, + { "$6$rounds=77777$short", + "we have a short salt string but not a short password", + "$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0g" + "ge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0" }, + { "$6$rounds=123456$asaltof16chars..", "a short string", + "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwc" + "elCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1" }, + { "$6$rounds=10$roundstoolow", "the minimum number is still observed", + "$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1x" + "hLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX." }, +}; + +const int ntests = sizeof(tests) / sizeof(tests[0]); + +int main(void) { + int result = 0; + int i; + + for (i = 0; i < ntests; ++i) { + char * cp = crypt(tests[i].input, tests[i].salt); + + if (strcmp(cp, tests[i].expected) != 0) { + printf("test %d: expected \"%s\", got \"%s\"\n", i, tests[i].expected, cp); + result = 1; + } + } + + return result; +} \ No newline at end of file diff --git a/tests/expected/bins_static/crypt/blowfish.stderr b/tests/expected/bins_static/crypt/blowfish.stderr new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/expected/bins_static/crypt/blowfish.stdout b/tests/expected/bins_static/crypt/blowfish.stdout new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/expected/bins_static/crypt/md5.stderr b/tests/expected/bins_static/crypt/md5.stderr new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/expected/bins_static/crypt/md5.stdout b/tests/expected/bins_static/crypt/md5.stdout new file mode 100644 index 0000000000000000000000000000000000000000..f985b46aff02b2ec96fac19c214629e5a3dc7468 --- /dev/null +++ b/tests/expected/bins_static/crypt/md5.stdout @@ -0,0 +1 @@ +Success! diff --git a/tests/expected/bins_static/crypt/pbkdf2.stderr b/tests/expected/bins_static/crypt/pbkdf2.stderr new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/expected/bins_static/crypt/pbkdf2.stdout b/tests/expected/bins_static/crypt/pbkdf2.stdout new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/expected/bins_static/crypt/scrypt.stderr b/tests/expected/bins_static/crypt/scrypt.stderr new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/expected/bins_static/crypt/scrypt.stdout b/tests/expected/bins_static/crypt/scrypt.stdout new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/expected/bins_static/crypt/sha256.stderr b/tests/expected/bins_static/crypt/sha256.stderr new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/expected/bins_static/crypt/sha256.stdout b/tests/expected/bins_static/crypt/sha256.stdout new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/expected/bins_static/crypt/sha512.stderr b/tests/expected/bins_static/crypt/sha512.stderr new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/expected/bins_static/crypt/sha512.stdout b/tests/expected/bins_static/crypt/sha512.stdout new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391