encodex 0.2.0

Implementation of and cryptanalysis tool for legacy and modern codes, ciphers and hashes.
Documentation
// Copyright (C) 2022,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/>.
//

//! This crate provides functionality for handling plaintexts and ciphertext for any of the
//! following codes, ciphers or digests.
//!
//! Additionally a cryptanalysis tool is provided to perform cryptanalysis on any of the
//! [`CryptographicAtom`]s implemented in this crate.
//!
//! ---
//!
//! # Features
//!
//! To enable any [`CryptographicAtom`], the corresponding feature must be enabled. Though building
//! the crate without any feature is possible, it is not useful, because then the crate does not
//! supply any functionality.
//!
//! | feature     | enables          ||| feature     | enables          |
//! |-------------|------------------|||-------------|------------------|
//! | base64      | Base64Context    ||| base16, hex | Base16Context    |
//! | base64_url  | Base64UrlContext ||| caesar      | CaesarContext    |
//! | base32      | Base32Context    ||| ui          | Ui elements      |
//! | base32_hex  | Base32HexContext ||| vigenere    | VigenereContext  |
//!
//! By enabling any of these features the respective modules will be compiled. Additional
//! information for every [`CryptographicAtom`] can be found in their respective module.
//!
//! ---
//!
//! # Nomenclature
//!
//! This part contains explanations for some words that are frequently used throughout the crate.
//! The meaning of most of the words might be obvious, but since some of them are used in cases
//! where they are not normally used, an explanation is included in this crate.
//!
//! ## Ciphertext
//!
//! A byte vector after an `encrypt` or `encode` operation or before a `decrypt` or `decode`
//! operation has been performed on the vector.
//!
//! ## Plaintext
//!
//! A byte vector after a `decrypt` or `decode` operation or before a `encrypt` or `encode`
//! operation has been performed on the vector.
//!
//! ---
//!
//! # Please note
//!
//! I develop this library/cli-tool mostly for lectures at university and understanding how all the
//! codes, ciphers and digest-algorithms work internally. It is not intended for use in production
//! code.
//!
//! If you still decide to use this library in production code or to play around with it, every
//! constructive feedback is appreciated.
//!
//! ---

#[cfg(any(
feature = "ui",
feature = "cipher",
feature = "code",
feature = "digest"
))]
pub mod action;
#[cfg(feature = "cipher")]
pub mod cipher;
#[cfg(feature = "ui")]
pub mod ui;
#[cfg(feature = "code")]
pub mod code;
#[cfg(feature = "digest")]
pub mod digest;
#[cfg(feature = "cryptanalysis")]
pub mod cryptanalysis;
mod util;

use core::fmt;



/// Creates a [`HashMap`](std::collections::hash_map::HashMap).
///
/// The map is created from an array of `m` `n-tuples`. The first element of each tuple is the key,
/// the second element is the value. Handing a `1-tuple` to the macro is an error and will deny
/// compilation. If more than two elements are supplied as a tuple, every tuple-element with an
/// index greater than 1 will be ignored.
///
/// # Usage Example
///
/// ```
/// use std::collections::HashMap;
/// use encodex::map;
///
/// let map = map![("first", 3), ("second", 1), ("third", 0)];
///
/// assert_eq!(map.get("first"), Some(&3));
/// assert_eq!(map.get("second"), Some(&1));
/// assert_eq!(map.get("third"), Some(&0));
/// ```
#[macro_export]
macro_rules! map {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_map = HashMap::new();
            $(
                temp_map.insert($x.0, $x.1);
            )*
            temp_map
        }
    };
}



/// Trait for case insensitive [`CryptographicAtom`]s.
///
/// Some ciphertexts of some cryptographic atoms can have either upper- or lowercase ciphertexts. If
/// a cipher supports both, upper- and lowercase ciphertexts, without changing the meaning of the
/// ciphertext, it is called case insensitive and should implement this trait in a way, that
/// [`ciphertext_is_capitalized`](CaseInsensitiveCiphertext::ciphertext_is_capitalized) always
/// returns a [`Some`] value.
///
/// Ciphers that do not support case insensitive ciphertexts should always return [`None`].
pub trait CaseInsensitiveCiphertext {
    /// Returns the capitalization state of this cryptographic atom.
    ///
    /// Not every atom needs this function. Atoms that do not need this function should always
    /// return [`None`].
    fn ciphertext_is_capitalized(
        &self
    ) -> Option<bool>;
    /// Sets the capitalization state of this cryptographic atom.
    ///
    /// Some atoms can output capitalized or small case letters ciphertexts that are equivalent to
    /// each other, e.g. the `Base32Hex` ciphertext `CPN MUO J1E 8== === =` can also be
    /// `cpn muo j1e 8== === =` and will still be decoded to the same plaintext. This method should
    /// update the capitalization state for such an atom.
    fn set_ciphertext_capitalization(
        &mut self,
        capitalized: bool,
    );
}

/// Trait for cryptographic atoms.
///
/// A cryptographic atom is any one `code`, `cipher` or `digest`. This trait defines all methods
/// that apply to every cryptographic algorithm implemented in this crate. This assures that every
/// cryptographic atom in this crate can be handled in a generic way.
///
/// Every struct in this crate that resembles a cryptographic algorithm implements this trait.
pub trait CryptographicAtom: CaseInsensitiveCiphertext {
    /// Returns the [class](AtomClass) of this cryptographic atom.
    fn get_atom_class(
        &self
    ) -> AtomClass;
    /// Returns the [type](AtomType) of this cryptographic atom.
    ///
    /// This function makes it possible to identify the type of an instance of any crypto item.
    fn get_atom_type(
        &self
    ) -> AtomType;
}



/// Defines the three classes of [`CryptographicAtom`]s that can be encountered in this crate.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AtomClass {
    /// The class of all [`cipher`] contexts.
    #[cfg(feature = "cipher")]
    Cipher,
    /// The class of all [`code`] contexts.
    #[cfg(feature = "code")]
    Code,
    /// The class of all [`digest`] contexts.
    #[cfg(feature = "digest")]
    Digest,
}



/// Defines all types of [`CryptographicAtom`]s.
///
/// There is a value for every atom that is implemented in this crate.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AtomType {
    /// The instance type of the [`Base16Context`](code::base_encoding::Base16Context)
    /// [`CryptographicAtom`].
    #[cfg(any(feature = "base16", feature = "hex"))]
    Base16,
    /// The instance type of the [`Base32Context`](code::base_encoding::Base32Context)
    /// [`CryptographicAtom`].
    #[cfg(feature = "base32")]
    Base32,
    /// The instance type of the [`Base32HexContext`](code::base_encoding::Base32HexContext)
    /// [`CryptographicAtom`].
    #[cfg(feature = "base32hex")]
    Base32Hex,
    /// The instance type of the [`Base64Context`](code::base_encoding::Base64Context)
    /// [`CryptographicAtom`].
    #[cfg(feature = "base64")]
    Base64,
    /// The instance type of the [`Base64UrlContext`](code::base_encoding::Base64UrlContext)
    /// [`CryptographicAtom`].
    #[cfg(feature = "base64url")]
    Base64Url,
    /// The instance type of the [`Caesar`](cipher::shift_cipher::CaesarContext)
    /// [`CryptographicAtom`].
    #[cfg(feature = "caesar")]
    Caesar,
    /// The instances type of the [`VigenereContext`](cipher::shift_cipher::VigenereContext)
    /// [`CryptographicAtom`].
    #[cfg(feature = "vigenere")]
    Vigenere,
}

impl fmt::Display for AtomType {
    /// Prints the string representation of atom types.
    ///
    /// This is really just the identifier defined by the [`AtomType`] enum as a [string](std::str).
    fn fmt(
        &self,
        f: &mut fmt::Formatter<'_>
    ) -> fmt::Result {
        match self {
            #[cfg(any(
            feature = "base16",
            feature = "hex"
            ))]
            AtomType::Base16 => { write![f, "Base16" ] }
            #[cfg(feature = "base32")]
            AtomType::Base32 => { write![f, "Base32" ] }
            #[cfg(feature = "base32hex")]
            AtomType::Base32Hex => { write![f, "Base32Hex" ]}
            #[cfg(feature = "base64")]
            AtomType::Base64 => { write![f, "Base64"] }
            #[cfg(feature = "base64url")]
            AtomType::Base64Url => { write![f, "Base64Url"] }
            #[cfg(feature = "caesar")]
            AtomType::Caesar => { write![f, "Caesar"] }
            #[cfg(feature = "sha256")]
            AtomType::Sha256 => { write![f, "Sha256"] }
            #[cfg(feature = "vigenere")]
            AtomType::Vigenere => { write![f, "Vigenere"] }
        }
    }
}

/// The error used by all [`CryptographicAtom`]s.
///
/// Not all errors are required by all crypto atoms. The `InvalidKey` error for example is not
/// necessary for any `code`, because `code`s as defined by this crate are just a remapping of bit
/// representations.
///
/// Only the errors required by the currently activated features are compiled into the crate.
#[derive(Debug, PartialEq)]
pub enum CryptoError {
    /// Is build when a byte is encountered at a position, where it can not be interpreted by a
    /// [`CryptographicAtom`].
    ///
    /// The first argument is a [`String`] that describes the error in more detail. The second
    /// argument is the index where the illegal character has been encountered. The last argument is
    /// the byte stream that has caused the error.
    ///
    /// TODO: Change last argument: Implement a struct, that copies only a portion of the malformed
    ///     byte vector and saves it (e.g. the first 100 bytes before and behind the illegal
    ///     character). Additionally it saves the index of the original byte stream where the
    ///     illegal character has been encountered.
    IllegalCharacter(String, usize, Vec<u8>),
    /// Is build when a byte stream does not match the residue class (`a + mℤ`), that is required
    /// for a specific plain- or ciphertext.
    ///
    /// The first argument is a [`String`] that describes the error in more detail. The second
    /// argument is the `a` value of the residue class. The third argument is the `m` value of the
    /// residue class.
    #[cfg(any(
    feature = "base16",
    feature = "base32",
    feature = "base32hex",
    feature = "base64",
    feature = "base64url",
    feature = "hex"
    ))]
    IllegalResidueClass(String, u8, u8),
    /// Is build when a [`cipher`] tries to set an invalid key.
    ///
    /// The first argument is a [`String`] that describes the error in more detail. The second
    /// argument is the invalid key.
    #[cfg(feature = "cipher")]
    InvalidKey(String, Vec<u8>),
    /// Is build when a [`CryptographicAtom`] that requires some kind of padding encounters a
    /// malformed variant of the required padding.
    ///
    /// The first argument is a [`String`] that describes the error in more detail. The second
    /// argument is the byte stream with the malformed padding.
    ///
    /// TODO: Replace the last argument here with the same struct as for the IllegalCharacter error.
    #[cfg(any(
    feature = "base32",
    feature = "base32hex",
    feature = "base64",
    feature = "base64url"
    ))]
    MalformedPadding(String, Vec<u8>),
    /// Is build when a [`cipher`] tries to perform a [`Decrypt`](cipher::Decrypt) or
    /// [`Encrypt`](cipher::Encrypt) operation without any key set.
    ///
    /// The first argument is a [`String`] that describes the error in more detail.
    #[cfg(feature = "cipher")]
    MissingKey(String),
}

impl fmt::Display for CryptoError {
    /// Formats every error variant so that it can be nicely printed to the terminal.
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Err(error) = write![f, ">>> Runtime Error:\n"] {
            return Err(error);
        }
        match self {
            CryptoError::IllegalCharacter(description, index, origin) => {
                write![f, "...   Class: Illegal Character\n\
                           ...   Description: {}\n\
                           ...   Origin: {:#?}\n\
                           ...   Index: {}\n",
                       description, origin, index]
            }
            #[cfg(any(
            feature = "base16",
            feature = "base32",
            feature = "base32hex",
            feature = "base64",
            feature = "base64url",
            feature = "hex"
            ))]
            CryptoError::IllegalResidueClass(description, a, m) => {
                write![f, "...   Class: Illegal Residue Class\n\
                           ...   Description: {}\n\
                           ...   Residue Class: {} + {}ℤ\n",
                       description, a, m]
            }
            #[cfg(feature = "cipher")]
            CryptoError::InvalidKey(description, key) => {
                write![f, "...   Class: Invalid Key\n\
                           ...   Description: {}\n\
                           ...   Invalid Key: {:#?}\n",
                       description, key]
            }
            #[cfg(any(feature = "base32", feature = "base32hex", feature = "base64",
            feature = "base64url"))]
            CryptoError::MalformedPadding(description, origin) => {
                write![f, "...   Class: Malformed Padding\n\
                           ...   Description: {}\n\
                           ...   Origin: {:#?}\n",
                       description, origin]
            }
            #[cfg(feature = "cipher")]
            CryptoError::MissingKey(description) => {
                write![f, "...   Class: Missing Key\n\
                           ...   Description: {}\n",
                       description]
            }
        }
    }
}