encodex 0.2.0

Implementation of and cryptanalysis tool for legacy and modern codes, ciphers and hashes.
Documentation
// Copyright (C) 2023  Fabian Moos
// This file is part of encodex.
//
// encodex is free software: you can redistribute it and/or modify it under the terms of the GNU
// General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// encodex 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with encodex. If not,
// see <https://www.gnu.org/licenses/>.
//

//! Action for any [`CryptographicAtom`]s.

use crate::{
    AtomClass,
    AtomType,
    CryptoError,
};
#[cfg(any(
feature = "base16",
feature = "base32",
feature = "base32hex",
feature = "caesar",
feature = "vigenere",
feature = "hex"
))]
use crate::CaseInsensitiveCiphertext;

#[cfg(feature = "cipher")]
use crate::cipher::{
    Cipher,
    Key,
};
#[cfg(feature = "caesar")]
use crate::cipher::shift_cipher::CaesarContext;
#[cfg(feature = "vigenere")]
use crate::cipher::shift_cipher::VigenereContext;
#[cfg(any(
feature = "base16",
feature = "hex"
))]
use crate::code::base_encoding::Base16Context;
#[cfg(feature = "base32")]
use crate::code::base_encoding::Base32Context;
#[cfg(feature = "base32hex")]
use crate::code::base_encoding::Base32HexContext;
#[cfg(feature = "base64")]
use crate::code::base_encoding::Base64Context;
#[cfg(feature = "base64url")]
use crate::code::base_encoding::Base64UrlContext;
#[cfg(feature = "code")]
use crate::code::Code;
#[cfg(feature = "digest")]
use crate::digest::Digest;



/// The direction of a [`CryptographicAction`].
#[cfg(any(
feature = "cipher",
feature = "code"
))]
#[derive(Clone, Copy, PartialEq)]
pub enum Direction {
    /// Encoding or encrypting a plaintext to a ciphertext.
    ToCipher,
    /// Decoding or decrypting a ciphertext to a plaintext.
    ToPlain,
}



/// Represents a cryptographic action.
///
/// This action can be any [type](AtomType).
#[derive(Clone)]
pub struct CryptographicAction {
    /// The [class](AtomClass) of this action.
    ///
    /// This field has been added to make it easier to identify the context that is saved in this
    /// action.
    crypto_class: AtomClass,
    /// The direction of the action.
    ///
    /// This is either [`ToCipher`](Direction::ToCipher) or [`ToPlain`](Direction::ToPlain) for
    /// `Cipher`s or `Code`s or [`None`] for `Digest`s.
    #[cfg(any(
    feature = "cipher",
    feature = "code"
    ))]
    direction: Option<Direction>,
    /// A [`Cipher`] context.
    ///
    /// If this is a [`Some`] value all other context fields will be [`None`] values.
    #[cfg(feature = "cipher")]
    cipher: Option<Box<dyn Cipher>>,
    /// A [`Code`] context.
    ///
    /// If this is a [`Some`] value all other context fields will be [`None`] values.
    #[cfg(feature = "code")]
    code: Option<Box<dyn Code>>,
    /// A [`Digest`] context.
    ///
    /// If this is a [`Some`] value all other context fields will be [`None`] values.
    #[cfg(feature = "digest")]
    digest: Option<Box<dyn Digest>>,
}

#[cfg(any(feature = "cipher", feature = "code", feature = "digest"))]
impl CryptographicAction {
    /// Executes the action with the given input.
    pub fn execute(
        &mut self,
        input: &Vec<u8>,
    ) -> Result<Vec<u8>, CryptoError> {
        match self.crypto_class {
            #[cfg(feature = "cipher")]
            AtomClass::Cipher => {
                if self.direction == Some(Direction::ToCipher) {
                    self.cipher.as_mut().unwrap().encrypt(input)
                } else {
                    self.cipher.as_mut().unwrap().decrypt(input)
                }
            }
            #[cfg(feature = "code")]
            AtomClass::Code => {
                if self.direction == Some(Direction::ToCipher) {
                    self.code.as_mut().unwrap().encode(input)
                } else {
                    self.code.as_mut().unwrap().decode(input)
                }
            }
            #[cfg(feature = "digest")]
            AtomClass::Digest => {
                todo!["Action::execute(&mut CryptographicAction, input: Vec<u8>) -> ... for digest"];
            }
        }
    }

    /// Creates a new [`Cipher`] action.
    #[cfg(feature = "cipher")]
    pub fn new_cipher_action(
        algorithm: Box<dyn Cipher>,
        direction: Direction,
    ) -> CryptographicAction {
        CryptographicAction {
            crypto_class: algorithm.get_atom_class(),
            direction: Some(direction),
            cipher: Some(algorithm),
            #[cfg(feature = "code")]
            code: None,
            #[cfg(feature = "digest")]
            digest: None,
        }
    }

    /// Creates a new [`Code`] action.
    #[cfg(feature = "code")]
    pub fn new_code_action(
        algorithm: Box<dyn Code>,
        direction: Direction,
    ) -> CryptographicAction {
        CryptographicAction {
            crypto_class: algorithm.get_atom_class(),
            direction: Some(direction),
            #[cfg(feature = "cipher")]
            cipher: None,
            code: Some(algorithm),
            #[cfg(feature = "digest")]
            digest: None,
        }
    }
}

impl PartialEq for CryptographicAction {
    /// Compares [`CryptographicAction`]s.
    fn eq(
        &self,
        other: &Self,
    ) -> bool {
        let mut is_equal = self.direction == other.direction &&
            self.crypto_class == other.crypto_class;
        #[cfg(feature = "cipher")] {
            is_equal = is_equal && ((self.cipher.is_none() && other.cipher.is_none()) ||
                (self.cipher.is_some() && other.cipher.is_some() &&
                    self.cipher.as_ref().unwrap().get_atom_class()
                        == other.cipher.as_ref().unwrap().get_atom_class() &&
                    self.cipher.as_ref().unwrap().get_atom_type()
                        == other.cipher.as_ref().unwrap().get_atom_type() &&
                    self.cipher.as_ref().unwrap().ciphertext_is_capitalized()
                        == other.cipher.as_ref().unwrap().ciphertext_is_capitalized() &&
                    self.cipher.as_ref().unwrap().get_key()
                        == other.cipher.as_ref().unwrap().get_key()));
        }
        #[cfg(feature = "code")] {
            is_equal = is_equal && ((self.code.is_none() && other.code.is_none()) ||
                (self.code.is_some() && other.code.is_some() &&
                    self.code.as_ref().unwrap().get_atom_class()
                        == other.code.as_ref().unwrap().get_atom_class() &&
                    self.code.as_ref().unwrap().get_atom_type()
                        == other.code.as_ref().unwrap().get_atom_type() &&
                    self.code.as_ref().unwrap().ciphertext_is_capitalized()
                        == other.code.as_ref().unwrap().ciphertext_is_capitalized()));
        }
        #[cfg(feature = "digest")] {
            is_equal = is_equal && ((self.digest.is_none() && other.digest.is_none())
                || (self.digest.is_some() && other.digest.is_some()
                // Compare atom class
                && self.digest.unwrap().get_atom_class() == self.digest.unwrap().get_atom_class()
                // Compare atom type
                && self.digest.unwrap().get_atom_type() == self.digest.unwrap().get_atom_type()
                // Compare capitalization state
                && self.digest.unwrap().ciphertext_is_capitalized()
                == self.digest.unwrap().ciphertext_is_capitalized()))
        }
        is_equal
    }
}

#[cfg(feature = "cipher")]
impl Clone for Box<dyn Cipher> {
    /// Clones any [`Cipher`] [`Box`](Box).
    fn clone(
        &self
    ) -> Self {
        match self.get_atom_type() {
            #[cfg(feature = "caesar")]
            AtomType::Caesar => {
                let mut ctx = CaesarContext::new();
                ctx.set_ciphertext_capitalization(self.ciphertext_is_capitalized().unwrap());
                if let Some(key) = self.get_key() {
                    let _ = ctx.set_key(key);
                }
                Box::from(ctx)
            }
            #[cfg(feature = "vigenere")]
            AtomType::Vigenere => {
                let mut ctx = VigenereContext::new();
                ctx.set_ciphertext_capitalization(self.ciphertext_is_capitalized().unwrap());
                if let Some(key) = self.get_key()
                {
                    let _ = ctx.set_key(key);
                }
                Box::from(ctx)
            }
            #[cfg(any(feature = "code", feature = "digest"))]
            _ => { panic!["Inappropriate AtomType during cloning of Box<dyn Cipher>!"] }
        }
    }
}

#[cfg(feature = "code")]
impl Clone for Box<dyn Code> {
    /// Clones any [`Code`] [`Box`](Box).
    fn clone(
        &self
    ) -> Self {
        match self.get_atom_type() {
            #[cfg(feature = "base64")]
            AtomType::Base64 => {
                let ctx = Base64Context::new();
                Box::from(ctx)
            }
            #[cfg(feature = "base64url")]
            AtomType::Base64Url => {
                let ctx = Base64UrlContext::new();
                Box::from(ctx)
            }
            #[cfg(feature = "base32")]
            AtomType::Base32 => {
                let mut ctx = Base32Context::new();
                ctx.set_ciphertext_capitalization(self.ciphertext_is_capitalized().unwrap());
                Box::from(ctx)
            }
            #[cfg(feature = "base32hex")]
            AtomType::Base32Hex => {
                let mut ctx = Base32HexContext::new();
                ctx.set_ciphertext_capitalization(self.ciphertext_is_capitalized().unwrap());
                Box::from(ctx)
            }
            #[cfg(any(feature = "base16", feature = "hex"))]
            AtomType::Base16 => {
                let mut ctx = Base16Context::new();
                ctx.set_ciphertext_capitalization(self.ciphertext_is_capitalized().unwrap());
                Box::from(ctx)
            }
            #[cfg(any(feature = "cipher", feature = "digest"))]
            _ => { panic!["Inappropriate AtomType during cloning of Box<dyn Code>!"]; }
        }
    }
}

#[cfg(feature = "digest")]
impl Clone for Box<dyn Digest> {
    /// Clones any [`Digest`] [`Box`](Box).
    fn clone(
        &self
    ) -> Self {
        todo!["Clone::clone(&Box<dyn Digest>) -> Box<dyn Digest>"];
    }
}