diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 6d596400b..84ba02008 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -321,6 +321,16 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "buffered-reader" version = "1.3.1" @@ -2465,12 +2475,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "powerfmt" version = "0.2.0" @@ -2580,6 +2584,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rc4" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f1256e23efe6097f27aa82d6ca6889361c001586ae0f6917cbad072f05eb275" +dependencies = [ + "cipher", +] + [[package]] name = "redis" version = "0.22.3" @@ -2893,6 +2906,7 @@ dependencies = [ "anyhow", "async-trait", "base64 0.21.7", + "blowfish", "cbc", "ccm", "chacha20", @@ -2943,6 +2957,7 @@ dependencies = [ "quick-xml", "rand", "rayon", + "rc4", "redis", "regex", "ripemd", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index a209e898a..21b86bcfc 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -87,6 +87,8 @@ libssh-rs = {version = "~0.2", features = ["vendored-openssl", "vendored"], opti nasl-function-proc-macro = { path = "crates/nasl-function-proc-macro" } nasl-c-lib = { path = "crates/nasl-c-lib", optional = true } openssl = { version = "0.10.66", features = ["vendored"] } +blowfish = "0.9.1" +rc4 = "0.1.0" [workspace] resolver = "2" diff --git a/rust/src/nasl/builtin/cryptographic/bf_cbc.rs b/rust/src/nasl/builtin/cryptographic/bf_cbc.rs new file mode 100644 index 000000000..8a6fe0ff5 --- /dev/null +++ b/rust/src/nasl/builtin/cryptographic/bf_cbc.rs @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2023 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception + +use blowfish::{ + cipher::{ + block_padding::{NoPadding, ZeroPadding}, + BlockCipher, BlockDecrypt, BlockDecryptMut, BlockEncrypt, BlockEncryptMut, KeyInit, + KeyIvInit, + }, + Blowfish, +}; +use cbc::{Decryptor, Encryptor}; + +use crate::function_set; +use crate::nasl::syntax::NaslValue; +use crate::nasl::utils::error::FunctionErrorKind; +use crate::nasl::utils::{Context, Register}; + +use super::{get_data, get_iv, get_key, get_len, Crypt}; + +/// Base function for en- and decrypting Cipher Block Chaining (CBC) mode +fn cbc(register: &Register, crypt: Crypt) -> Result +where + D: BlockCipher + BlockEncrypt + BlockDecrypt + KeyInit, +{ + // Get Arguments + let key = get_key(register)?; + let data = get_data(register)?; + let iv = get_iv(register)?; + + // Mode Encrypt or Decrypt + match crypt { + Crypt::Encrypt => { + let res = Encryptor::::new_from_slices(key, iv); + match res { + Ok(encryptor) => Ok(encryptor.encrypt_padded_vec_mut::(data).into()), + Err(e) => Err(FunctionErrorKind::WrongArgument(e.to_string())), + } + } + Crypt::Decrypt => { + // length for encrypted data + let len = match get_len(register)? { + Some(x) => x, + None => data.len(), + }; + + // len should not be more than the length of the data + if len > data.len() { + return Err(FunctionErrorKind::wrong_argument( + "len", + format!("<={:?}", data.len()).as_str(), + len.to_string().as_str(), + )); + } + let res = Decryptor::::new_from_slices(key, iv); + match res { + Ok(decryptor) => Ok(decryptor + .decrypt_padded_vec_mut::(data) + .map_err(|e| FunctionErrorKind::WrongArgument(e.to_string()))?[..len] + .to_vec() + .into()), + Err(e) => Err(FunctionErrorKind::WrongArgument(e.to_string())), + } + } + } +} + +/// NASL function to encrypt data with blowfish cbc. +/// +/// Encrypt the plaintext data using the blowfish algorithm in CBC mode +/// with the key key and the initialization vector iv. The key must be +/// 16 bytes long. The iv must be at least 8 bytes long. Data must be a +/// multiple of 8 bytes long. +/// +/// The return value is an array a with a[0] being the encrypted data and +/// a[1] the new initialization vector to use for the next part of the +/// data. + +fn bf_cbc_encrypt(register: &Register, _: &Context) -> Result { + cbc::(register, Crypt::Encrypt) +} + +/// NASL function to decrypt data with blowfish cbc. +/// +/// Decrypt the cipher text data using the blowfish algorithm in CBC mode +/// with the key key and the initialization vector iv. The key must be +/// 16 bytes long. The iv must be at least 8 bytes long. data must be a +/// multiple of 8 bytes long. +/// +/// The return value is an array a with a[0] being the plaintext data +/// and a[1] the new initialization vector to use for the next part of +/// the data. +fn bf_cbc_decrypt(register: &Register, _: &Context) -> Result { + cbc::(register, Crypt::Decrypt) +} + +pub struct BfCbc; + +function_set! { + BfCbc, + sync_stateless, + ( + bf_cbc_encrypt, + bf_cbc_decrypt, + ) +} diff --git a/rust/src/nasl/builtin/cryptographic/mod.rs b/rust/src/nasl/builtin/cryptographic/mod.rs index 66f92ca9f..06cfad2aa 100644 --- a/rust/src/nasl/builtin/cryptographic/mod.rs +++ b/rust/src/nasl/builtin/cryptographic/mod.rs @@ -14,9 +14,11 @@ pub mod aes_cmac; pub mod aes_ctr; pub mod aes_gcm; pub mod aes_gmac; +pub mod bf_cbc; pub mod des; pub mod hash; pub mod hmac; +pub mod rc4; pub mod rsa; #[cfg(test)] @@ -120,6 +122,7 @@ impl IntoFunctionSet for Cryptographic { set.add_set(hash::Hash); set.add_set(des::Des); set.add_set(rsa::Rsa); + set.add_set(bf_cbc::BfCbc); set } } diff --git a/rust/src/nasl/builtin/cryptographic/rc4.rs b/rust/src/nasl/builtin/cryptographic/rc4.rs new file mode 100644 index 000000000..03dcce2b8 --- /dev/null +++ b/rust/src/nasl/builtin/cryptographic/rc4.rs @@ -0,0 +1,296 @@ +// SPDX-FileCopyrightText: 2023 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception + +use rc4::Rc4; +use rc4::{consts::*, KeyInit, StreamCipher}; +use std::sync::{Arc, Mutex, MutexGuard}; + +use crate::nasl::prelude::*; +use crate::nasl::syntax::NaslValue; +use crate::nasl::utils::error::FunctionErrorKind; +use crate::nasl::utils::{Context, Register}; + +use super::{get_data, get_key}; + +/// Structure to hold a Cipher Handler +pub struct CipherHandler { + /// Handler ID + pub id: i32, + /// Handler + pub handler: Rc4Key, +} + +fn lock_handlers( + handlers: &Arc>>, +) -> Result>, FunctionErrorKind> { + // we actually need to panic as a lock error is fatal + // alternatively we need to add a poison error on FunctionErrorKind + Ok(Arc::as_ref(handlers).lock().unwrap()) +} + +fn get_new_cipher_id(handlers: &MutexGuard>) -> i32 { + let mut new_val: i32 = 5000; + if handlers.is_empty() { + return new_val; + } + + let mut list = handlers.iter().map(|x| x.id).collect::>(); + list.sort(); + + for (i, v) in list.iter().enumerate() { + if i == list.len() - 1 { + new_val = v + 1; + break; + } + if new_val != list[i] { + break; + } + + new_val += 1; + } + new_val +} + +#[derive(Default)] +pub struct CipherHandlers { + cipher_handlers: Arc>>, +} + +impl CipherHandlers { + /// Closes a stream cipher. + pub fn close_stream_cipher( + &self, + register: &Register, + _: &Context, + ) -> Result { + let hd = match register.named("hd") { + Some(ContextType::Value(NaslValue::Number(x))) => *x as i32, + _ => { + return Err(FunctionErrorKind::Diagnostic( + "Handler ID not found".to_string(), + Some(NaslValue::Null), + )) + } + }; + + let mut handlers = lock_handlers(&self.cipher_handlers)?; + match handlers.iter_mut().enumerate().find(|(_i, h)| h.id == hd) { + Some((i, _h)) => { + handlers.remove(i); + Ok(NaslValue::Number(0)) + } + _ => Err(FunctionErrorKind::Diagnostic( + format!("Handler ID {} not found", hd), + Some(NaslValue::Null), + )), + } + } + + /// Open RC4 cipher to encrypt a stream of data. The handler can be used to encrypt stream data. + /// Opened cipher must be closed with (close_stream_cipher)[close_stream_cipher.md] when it is not used anymore. + /// -iv: the initival vector + /// -key: the key used for encryption + /// + /// Returns the id of the encrypted data cipher handler on success. + pub fn open_rc4_cipher( + &self, + register: &Register, + _: &Context, + ) -> Result { + // Get Arguments + + let key = match get_key(register) { + Ok(k) if !k.is_empty() => k.to_vec(), + _ => { + return Err(FunctionErrorKind::Diagnostic( + "Missing Key argument".to_string(), + Some(NaslValue::Null), + )) + } + }; + + let rc_handler = Rc4Key::build_handler_from_key(key.to_vec())?; + let mut handlers = lock_handlers(&self.cipher_handlers)?; + let id = get_new_cipher_id(&handlers); + //let rc_handler = Rc4::::new_from_slice(key).unwrap(); // new_from_slice(key).unwrap(); + + let hd = CipherHandler { + id, + handler: rc_handler, + }; + handlers.push(hd); + Ok(NaslValue::Number(id as i64)) + } + + /// Encrypt data with a RC4 cipher. + /// If a perviously opened (RC4 handler) exist the hd parameter should be set it will use + /// the handler for encryption. + /// If there is no open handler than the key and iv parameter must be set. + /// -data: string Data to decrypt + /// -hd: the handler index. (mandatory if not key and iv is given) + /// -iv: string Initialization vector (mandatory if no handler is given). + /// -key: string key (mandatory if no handler is given). + pub fn rc4_encrypt( + &self, + register: &Register, + _: &Context, + ) -> Result { + let data = match get_data(register) { + Ok(d) if !d.is_empty() => d.to_vec(), + _ => { + return Err(FunctionErrorKind::Diagnostic( + "Missing data argument".to_string(), + Some(NaslValue::Null), + )) + } + }; + + let hd = match register.named("hd") { + Some(ContextType::Value(NaslValue::Number(x))) => *x as i32, + _ => 0, + }; + + let mut handlers = lock_handlers(&self.cipher_handlers)?; + + if hd > 0 { + if let Some((_i, h)) = handlers.iter_mut().enumerate().find(|(_i, h)| h.id == hd) { + let d = h.handler.encode(data); + return Ok(NaslValue::Data(d)); + }; + }; + + let key = match get_key(register) { + Ok(k) if !k.is_empty() => k.to_vec(), + _ => { + return Err(FunctionErrorKind::Diagnostic( + "Missing Key argument".to_string(), + Some(NaslValue::Null), + )) + } + }; + + let mut rc_handler = Rc4Key::build_handler_from_key(key.to_vec())?; + let d = rc_handler.encode(data); + Ok(NaslValue::Data(d)) + } +} + +macro_rules! build_rc4key_enum { + ($(($i:ident, $ty: ty, $l:literal),)*) => { + pub enum Rc4Key { + $( + $i( Rc4<$ty>), + )* + } + + impl Rc4Key { + fn build_handler_from_key(bl: Vec) -> Result { + match bl.len() { + $($l => Ok(Self::$i(Rc4::new_from_slice(bl.as_slice()).unwrap())),)* + _ => {return Err(FunctionErrorKind::Diagnostic("RC4 Key size not supported".into(), Some(NaslValue::Null)))} + } + } + + fn encode (&mut self, data: Vec) -> Vec { + match self { + $(Rc4Key::$i(e) =>{ + let mut d = data.clone(); + e.apply_keystream(&mut d); + d + })* + } + } + } + }; +} + +build_rc4key_enum! { + (U1, U1, 1), + (U2, U2, 2), + (U3, U3, 3), + (U4, U4, 4), + (U5, U5, 5), + (U6, U6, 6), + (U7, U7, 7), + (U8, U8, 8), + (U9, U9, 9), + (U10, U10, 10), + (U11, U11, 11), + (U12, U12, 12), + (U13, U13, 13), + (U14, U14, 14), + (U15, U15, 15), + (U16, U16, 16), + (U17, U17, 17), + (U18, U18, 18), + (U19, U19, 19), + (U20, U20, 20), + (U21, U21, 21), + (U22, U22, 22), + (U23, U23, 23), + (U24, U24, 24), + (U25, U25, 25), + (U26, U26, 26), + (U27, U27, 27), + (U28, U28, 28), + (U29, U29, 29), + (U30, U30, 30), + (U31, U31, 31), + (U32, U32, 32), + (U33, U33, 33), + (U34, U34, 34), + (U35, U35, 35), + (U36, U36, 36), + (U37, U37, 37), + (U38, U38, 38), + (U39, U39, 39), + (U40, U40, 40), + (U41, U41, 41), + (U42, U42, 42), + (U43, U43, 43), + (U44, U44, 44), + (U45, U45, 45), + (U46, U46, 46), + (U47, U47, 47), + (U48, U48, 48), + (U49, U49, 49), + (U50, U50, 50), + (U51, U51, 51), + (U52, U52, 52), + (U53, U53, 53), + (U54, U54, 54), + (U55, U55, 55), + (U56, U56, 56), + (U57, U57, 57), + (U58, U58, 58), + (U59, U59, 59), + (U60, U60, 60), + (U61, U61, 61), + (U62, U62, 62), + (U63, U63, 63), + (U64, U64, 64), + (U70, U70, 70), + (U80, U80, 80), + (U90, U90, 90), + (U100, U100, 100), + (U200, U200, 200), + (U300, U300, 300), + (U400, U400, 400), + (U500, U500, 500), + (U128, U128, 128), + (U256, U256, 256), + (U512, U512, 512), + (U1000, U1000, 1000), + (U1024, U1024, 1024), +} + +function_set! { + CipherHandlers, + sync_stateful, + ( + (CipherHandlers::close_stream_cipher, "close_stream_cipher"), + (CipherHandlers::open_rc4_cipher, "open_rc4_cipher"), + (CipherHandlers::rc4_encrypt, "rc4_encrypt") + ) +} diff --git a/rust/src/nasl/builtin/cryptographic/tests/bf_cbc.rs b/rust/src/nasl/builtin/cryptographic/tests/bf_cbc.rs new file mode 100644 index 000000000..934642e7f --- /dev/null +++ b/rust/src/nasl/builtin/cryptographic/tests/bf_cbc.rs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception + +use crate::nasl::test_prelude::*; + +use super::helper::decode_hex; + +#[test] +fn bf_cbc_crypt() { + let mut t = TestBuilder::default(); + t.run(r#"key = raw_string(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0xF);"#); + t.run(r#"iv = raw_string(0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10);"#); + t.run(r#"data = raw_string(0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);"#); + t.run(r#"crypt = bf_cbc_encrypt(key: key, iv: iv, data: data);"#); + t.ok( + r#"crypt = bf_cbc_encrypt(key: key, iv: iv, data: data);"#, + decode_hex("56f4c9607998aa4a").unwrap(), + ); + t.ok( + r#"bf_cbc_decrypt(key: key, data: crypt, iv: iv);"#, + decode_hex("8000000000000000").unwrap(), + ); +} diff --git a/rust/src/nasl/builtin/cryptographic/tests/mod.rs b/rust/src/nasl/builtin/cryptographic/tests/mod.rs index 40cfb5f4d..e413d6ec0 100644 --- a/rust/src/nasl/builtin/cryptographic/tests/mod.rs +++ b/rust/src/nasl/builtin/cryptographic/tests/mod.rs @@ -3,8 +3,11 @@ mod aes_ccm; mod aes_cmac; mod aes_ctr; mod aes_gcm; +mod bf_cbc; mod des; mod hash; mod helper; mod hmac; +mod rc4; mod rsa; + diff --git a/rust/src/nasl/builtin/cryptographic/tests/rc4.rs b/rust/src/nasl/builtin/cryptographic/tests/rc4.rs new file mode 100644 index 000000000..d3ca342c7 --- /dev/null +++ b/rust/src/nasl/builtin/cryptographic/tests/rc4.rs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception + +use crate::nasl::test_prelude::*; + +use super::helper::decode_hex; + +#[test] +fn rc4_encrypt() { + let mut t = TestBuilder::default(); + t.run(r#"key = raw_string(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0xF);"#); + t.run(r#"data = raw_string(0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);"#); + t.run(r#"crypt = rc4_encrypt(key: key, data: data);"#); + t.ok( + r#"crypt = rc4_encrypt(key: key, data: data);"#, + decode_hex("699c40f947e219cc").unwrap(), + ); +} + +#[test] +fn rc4_open_encrypt() { + let mut t = TestBuilder::default(); + t.run(r#"key = raw_string(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0xF);"#); + t.run(r#"data = raw_string(0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);"#); + t.run(r#"hd = open_rc4_cipher(key: key);"#); + t.ok( + r#"crypt = rc4_encrypt(hd: hd, data: data);"#, + decode_hex("699c40f947e219cc").unwrap(), + ); + t.ok(r#"hd;"#, 5000); + t.ok(r#"close_stream_cipher(hd: hd);"#, 0); +} diff --git a/rust/src/nasl/builtin/mod.rs b/rust/src/nasl/builtin/mod.rs index 1c1fcaacb..f3a47c25e 100644 --- a/rust/src/nasl/builtin/mod.rs +++ b/rust/src/nasl/builtin/mod.rs @@ -49,7 +49,8 @@ pub fn nasl_std_functions() -> Executor { .add_set(regex::RegularExpressions) .add_set(cryptographic::Cryptographic) .add_set(description::Description) - .add_set(isotime::NaslIsotime); + .add_set(isotime::NaslIsotime) + .add_set(cryptographic::rc4::CipherHandlers::default()); #[cfg(feature = "nasl-builtin-ssh")] executor.add_set(ssh::Ssh::default());