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/>.
//

//! Functionality for handling cipher algorithms.

use std::collections::linked_list::LinkedList;

use crate::{
    action::{
        CryptographicAction,
        Direction,
    },
    ui::application::{
        ApplicationError,
        OPTION_CIPHER,
    },
    cipher::{
        Cipher,
        shift_cipher::{
            CaesarContext,
            VigenereContext,
        },
    },
};

/// The command line argument for the `Caesar` cipher.
#[cfg(feature = "caesar")]
const ALGORITHM_CAESAR: &str = "CAESAR";
/// The command line argument for the `Vigenère` cipher.
#[cfg(feature = "vigenere")]
const ALGORITHM_VIGENERE: &str = "VIGENERE";
/// The command line argument for enabling decrypting. Can not be used together with the
/// [encrypt](OPTION_ENCRYPT) argument for the same cipher argument.
const OPTION_DECRYPT: &str = "decrypt";
/// The command line argument for enabling encrypting. Can not be used together with the
/// [decrypt](OPTION_DECRYPT) argument for the same cipher argument.
const OPTION_ENCRYPT: &str = "encrypt";
/// The command line argument for key parsing. The argument that is coming after this argument
/// (separated by a comma) is the actual key.
const OPTION_KEY: &str = "key";
/// The command line argument for enabling lowercase output. Can not be used together with the
/// [uppercase](OPTION_UPPERCASE) argument for the same cipher argument.
const OPTION_LOWERCASE: &str = "lowercase";
/// The command line argument for enabling uppercase output. Can not be used together with the
/// [lowercase](OPTION_LOWERCASE) argument for the same cipher argument.
const OPTION_UPPERCASE: &str = "uppercase";



/// Helper function for creating new [`cipher`](crate::cipher) contexts.
///
/// Every new cipher that gets added to the crate must be added here to be accessible via the
/// command line argument.
#[inline]
fn create_new_cipher_context(
    arguments: Option<&String>
) -> Result<(Box<dyn Cipher>, String), ApplicationError> {
    let arguments = if let Some(arg) = arguments {
        arg
    } else {
        let mut description = "Missing argument for ".to_string();
        description.push_str(OPTION_CIPHER);
        description.push_str("!");
        return Err(ApplicationError::MissingArgument(
            description, "create_new_cipher_context()".to_string(),
        ));
    };

    let arguments = arguments
        .split(":")
        .collect::<Vec<&str>>();

    if arguments.len() == 1 {
        let mut description = "For algorithm class ".to_string();
        description.push_str(OPTION_CIPHER);
        description.push_str("!");
        Err(ApplicationError::MissingArgument(
            description, "create_new_cipher_context()".to_string(),
        ))
    } else if arguments.len() > 2 {
        let mut description = "For algorithm class ".to_string();
        description.push_str(OPTION_CIPHER);
        description.push_str("!");
        Err(ApplicationError::TooManyArguments(
            description, "create_new_cipher_context()".to_string(),
        ))
    } else {
        let ctx: Box<dyn Cipher> = match arguments[0] {
            #[cfg(feature = "caesar")]
            algorithm if algorithm == ALGORITHM_CAESAR => {
                Box::from(CaesarContext::new())
            }
            #[cfg(any(feature = "vigenere"))]
            algorithm if algorithm == ALGORITHM_VIGENERE => {
                Box::from(VigenereContext::new())
            }
            algorithm => {
                let mut description = "Algorithm ".to_string();
                description.push_str(algorithm);
                description.push_str(" unknown!");
                return Err(ApplicationError::WrongCommandLineArgument(
                    description,
                    "create_new_cipher_context()".to_string(),
                ));
            }
        };
        Ok((ctx, arguments[1].to_string()))
    }
}

/// Helper function for handling all command line arguments for ciphers.
///
/// If new arguments are added for ciphers they must be added here to be accessible for all ciphers
/// that have been attached to the command line interface.
pub(crate) fn handle_cipher_option_arguments(
    arguments: Option<&String>,
    current_action: &mut Option<CryptographicAction>,
) -> Result<(), ApplicationError> {
    let (mut ctx, arguments) = match create_new_cipher_context(
        arguments
    ) {
        Ok(ctx) => { ctx }
        Err(error) => { return Err(error); }
    };

    let mut arguments = split_cipher_algorithm_arguments(&arguments);
    let mut direction = None;
    let mut maybe_arg = arguments.pop_front();
    let mut case_argument_encountered = false;
    while maybe_arg != None {
        let code_arg = maybe_arg.unwrap();
        match code_arg {
            arg if arg == OPTION_DECRYPT || arg == OPTION_ENCRYPT => {
                if direction.is_none() {
                    direction = Some(
                        if arg == OPTION_DECRYPT {
                            Direction::ToPlain
                        } else {
                            Direction::ToCipher
                        }
                    );
                } else {
                    return Err(ApplicationError::TooManyArguments(
                        "Only exactly one of \"encrypt\" and \"decrypt\" may be \
                               supplied!".to_string(),
                        "handle_cipher_option_arguments()".to_string(),
                    ));
                }
            }
            arg if arg == OPTION_KEY => {
                let key = if let Some(value) = arguments.pop_front() {
                    Vec::from(value)
                } else {
                    let mut description = "Missing value for ".to_string();
                    description.push_str(ctx.get_atom_type().to_string().as_str());
                    description.push_str(" key!");
                    return Err(ApplicationError::MissingArgument(
                        description,
                        "handle_cipher_option_arguments()".to_string(),
                    ));
                };
                if let Err(error) = ctx.set_key(&key) {
                    let mut description = "The following CryptoError has occurred while \
                                                  setting the key for a ".to_string();
                    description.push_str(ctx.get_atom_type().to_string().as_str());
                    description.push_str(" cipher!");
                    return Err(ApplicationError::MalformedKey(
                        description,
                        "handle_cipher_option_arguments()".to_string(),
                        error,
                    ));
                }
            }
            arg if arg == OPTION_LOWERCASE || arg == OPTION_UPPERCASE => {
                if case_argument_encountered {
                    return Err(ApplicationError::TooManyArguments(
                        "Only exactly one of \"uppercase\" and \"lowercase\" may be \
                               supplied!".to_string(),
                        "handle_cipher_option_arguments()".to_string(),
                    ));
                } else {
                    case_argument_encountered = true;
                }
                ctx.set_ciphertext_capitalization(arg == OPTION_UPPERCASE);
            }
            arg => {
                let mut description = "For algorithm ".to_string();
                description.push_str(ctx.get_atom_type().to_string().as_str());
                description.push_str("! \"");
                description.push_str(arg);
                description.push_str("\" is unknown!");
                return Err(ApplicationError::WrongCommandLineArgument(
                    description,
                    "handle_cipher_option_arguments()".to_string(),
                ));
            }
        }
        maybe_arg = arguments.pop_front();
    }

    if let Some(direction) = direction {
        *current_action = Some(CryptographicAction::new_cipher_action(
            ctx, direction,
        ));
        Ok(())
    } else {
        Err(ApplicationError::MissingArgument(
            "Neither \"encrypt\" nor \"decrypt\" argument has been supplied!".to_string(),
            "handle_cipher_option_arguments()".to_string(),
        ))
    }
}

/// Splits up the cipher algorithm arguments.
#[inline]
fn split_cipher_algorithm_arguments(
    arguments: &String,
) -> LinkedList<&str> {
    arguments
        .as_str()
        .split(",")
        .collect::<LinkedList<&str>>()
}