461 lines
14 KiB
Rust
461 lines
14 KiB
Rust
#![no_std]
|
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
#![doc = include_str!("../README.md")]
|
|
#![doc(
|
|
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
|
|
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
|
|
)]
|
|
#![forbid(unsafe_code)]
|
|
#![warn(
|
|
clippy::arithmetic_side_effects,
|
|
clippy::panic,
|
|
clippy::panic_in_result_fn,
|
|
clippy::unwrap_used,
|
|
missing_docs,
|
|
rust_2018_idioms,
|
|
unused_lifetimes,
|
|
unused_qualifications
|
|
)]
|
|
|
|
#[cfg(feature = "std")]
|
|
extern crate std;
|
|
|
|
mod error;
|
|
|
|
#[cfg(feature = "chacha20poly1305")]
|
|
mod chacha20poly1305;
|
|
|
|
pub use crate::error::{Error, Result};
|
|
|
|
use core::{fmt, str};
|
|
use encoding::{Label, LabelError};
|
|
|
|
#[cfg(feature = "aes-ctr")]
|
|
use cipher::StreamCipherCore;
|
|
|
|
#[cfg(feature = "aes-gcm")]
|
|
use aes_gcm::{aead::AeadInPlace, Aes128Gcm, Aes256Gcm};
|
|
|
|
#[cfg(feature = "chacha20poly1305")]
|
|
use crate::chacha20poly1305::ChaCha20Poly1305;
|
|
|
|
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
|
|
use aes::{Aes128, Aes192, Aes256};
|
|
|
|
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
|
|
use {
|
|
cbc::{Decryptor, Encryptor},
|
|
cipher::{block_padding::NoPadding, BlockCipher, BlockDecryptMut, BlockEncryptMut},
|
|
};
|
|
|
|
#[cfg(any(feature = "aes-cbc", feature = "aes-gcm", feature = "tdes"))]
|
|
use cipher::KeyInit;
|
|
|
|
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
|
|
use cipher::KeyIvInit;
|
|
|
|
#[cfg(feature = "tdes")]
|
|
use des::TdesEde3;
|
|
|
|
/// AES-128 in block chaining (CBC) mode
|
|
const AES128_CBC: &str = "aes128-cbc";
|
|
|
|
/// AES-192 in block chaining (CBC) mode
|
|
const AES192_CBC: &str = "aes192-cbc";
|
|
|
|
/// AES-256 in block chaining (CBC) mode
|
|
const AES256_CBC: &str = "aes256-cbc";
|
|
|
|
/// AES-128 in counter (CTR) mode
|
|
const AES128_CTR: &str = "aes128-ctr";
|
|
|
|
/// AES-192 in counter (CTR) mode
|
|
const AES192_CTR: &str = "aes192-ctr";
|
|
|
|
/// AES-256 in counter (CTR) mode
|
|
const AES256_CTR: &str = "aes256-ctr";
|
|
|
|
/// AES-128 in Galois/Counter Mode (GCM).
|
|
const AES128_GCM: &str = "aes128-gcm@openssh.com";
|
|
|
|
/// AES-256 in Galois/Counter Mode (GCM).
|
|
const AES256_GCM: &str = "aes256-gcm@openssh.com";
|
|
|
|
/// ChaCha20-Poly1305
|
|
const CHACHA20_POLY1305: &str = "chacha20-poly1305@openssh.com";
|
|
|
|
/// Triple-DES in block chaining (CBC) mode
|
|
const TDES_CBC: &str = "3des-cbc";
|
|
|
|
/// Nonce for AEAD modes.
|
|
///
|
|
/// This is used by e.g. `aes128-gcm@openssh.com`/`aes256-gcm@openssh.com` and
|
|
/// `chacha20-poly1305@openssh.com`.
|
|
pub type Nonce = [u8; 12];
|
|
|
|
/// Authentication tag for ciphertext data.
|
|
///
|
|
/// This is used by e.g. `aes128-gcm@openssh.com`/`aes256-gcm@openssh.com` and
|
|
/// `chacha20-poly1305@openssh.com`.
|
|
pub type Tag = [u8; 16];
|
|
|
|
/// Counter mode with a 32-bit big endian counter.
|
|
#[cfg(feature = "aes-ctr")]
|
|
type Ctr128BE<Cipher> = ctr::CtrCore<Cipher, ctr::flavors::Ctr128BE>;
|
|
|
|
/// Cipher algorithms.
|
|
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
|
#[non_exhaustive]
|
|
pub enum Cipher {
|
|
/// No cipher.
|
|
None,
|
|
|
|
/// AES-128 in cipher block chaining (CBC) mode.
|
|
Aes128Cbc,
|
|
|
|
/// AES-192 in cipher block chaining (CBC) mode.
|
|
Aes192Cbc,
|
|
|
|
/// AES-256 in cipher block chaining (CBC) mode.
|
|
Aes256Cbc,
|
|
|
|
/// AES-128 in counter (CTR) mode.
|
|
Aes128Ctr,
|
|
|
|
/// AES-192 in counter (CTR) mode.
|
|
Aes192Ctr,
|
|
|
|
/// AES-256 in counter (CTR) mode.
|
|
Aes256Ctr,
|
|
|
|
/// AES-128 in Galois/Counter Mode (GCM).
|
|
Aes128Gcm,
|
|
|
|
/// AES-256 in Galois/Counter Mode (GCM).
|
|
Aes256Gcm,
|
|
|
|
/// ChaCha20-Poly1305
|
|
ChaCha20Poly1305,
|
|
|
|
/// TripleDES in block chaining (CBC) mode
|
|
TDesCbc,
|
|
}
|
|
|
|
impl Cipher {
|
|
/// Decode cipher algorithm from the given `ciphername`.
|
|
///
|
|
/// # Supported cipher names
|
|
/// - `aes256-ctr`
|
|
pub fn new(ciphername: &str) -> core::result::Result<Self, LabelError> {
|
|
ciphername.parse()
|
|
}
|
|
|
|
/// Get the string identifier which corresponds to this algorithm.
|
|
pub fn as_str(self) -> &'static str {
|
|
match self {
|
|
Self::None => "none",
|
|
Self::Aes128Cbc => AES128_CBC,
|
|
Self::Aes192Cbc => AES192_CBC,
|
|
Self::Aes256Cbc => AES256_CBC,
|
|
Self::Aes128Ctr => AES128_CTR,
|
|
Self::Aes192Ctr => AES192_CTR,
|
|
Self::Aes256Ctr => AES256_CTR,
|
|
Self::Aes128Gcm => AES128_GCM,
|
|
Self::Aes256Gcm => AES256_GCM,
|
|
Self::ChaCha20Poly1305 => CHACHA20_POLY1305,
|
|
Self::TDesCbc => TDES_CBC,
|
|
}
|
|
}
|
|
|
|
/// Get the key and IV size for this cipher in bytes.
|
|
pub fn key_and_iv_size(self) -> Option<(usize, usize)> {
|
|
match self {
|
|
Self::None => None,
|
|
Self::Aes128Cbc => Some((16, 16)),
|
|
Self::Aes192Cbc => Some((24, 16)),
|
|
Self::Aes256Cbc => Some((32, 16)),
|
|
Self::Aes128Ctr => Some((16, 16)),
|
|
Self::Aes192Ctr => Some((24, 16)),
|
|
Self::Aes256Ctr => Some((32, 16)),
|
|
Self::Aes128Gcm => Some((16, 12)),
|
|
Self::Aes256Gcm => Some((32, 12)),
|
|
Self::ChaCha20Poly1305 => Some((64, 0)),
|
|
Self::TDesCbc => Some((24, 8)),
|
|
}
|
|
}
|
|
|
|
/// Get the block size for this cipher in bytes.
|
|
pub fn block_size(self) -> usize {
|
|
match self {
|
|
Self::None | Self::ChaCha20Poly1305 | Self::TDesCbc => 8,
|
|
Self::Aes128Cbc
|
|
| Self::Aes192Cbc
|
|
| Self::Aes256Cbc
|
|
| Self::Aes128Ctr
|
|
| Self::Aes192Ctr
|
|
| Self::Aes256Ctr
|
|
| Self::Aes128Gcm
|
|
| Self::Aes256Gcm => 16,
|
|
}
|
|
}
|
|
|
|
/// Compute the length of padding necessary to pad the given input to
|
|
/// the block size.
|
|
#[allow(clippy::arithmetic_side_effects)]
|
|
pub fn padding_len(self, input_size: usize) -> usize {
|
|
match input_size % self.block_size() {
|
|
0 => 0,
|
|
input_rem => self.block_size() - input_rem,
|
|
}
|
|
}
|
|
|
|
/// Does this cipher have an authentication tag? (i.e. is it an AEAD mode?)
|
|
pub fn has_tag(self) -> bool {
|
|
matches!(
|
|
self,
|
|
Self::Aes128Gcm | Self::Aes256Gcm | Self::ChaCha20Poly1305
|
|
)
|
|
}
|
|
|
|
/// Is this cipher `none`?
|
|
pub fn is_none(self) -> bool {
|
|
self == Self::None
|
|
}
|
|
|
|
/// Is the cipher anything other than `none`?
|
|
pub fn is_some(self) -> bool {
|
|
!self.is_none()
|
|
}
|
|
|
|
/// Decrypt the ciphertext in the `buffer` in-place using this cipher.
|
|
pub fn decrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8], tag: Option<Tag>) -> Result<()> {
|
|
match self {
|
|
#[cfg(feature = "aes-cbc")]
|
|
Self::Aes128Cbc => {
|
|
if tag.is_some() {
|
|
return Err(Error::TagSize);
|
|
}
|
|
cbc_decrypt::<Aes128>(key, iv, buffer)
|
|
}
|
|
#[cfg(feature = "aes-cbc")]
|
|
Self::Aes192Cbc => {
|
|
if tag.is_some() {
|
|
return Err(Error::TagSize);
|
|
}
|
|
cbc_decrypt::<Aes192>(key, iv, buffer)
|
|
}
|
|
#[cfg(feature = "aes-cbc")]
|
|
Self::Aes256Cbc => {
|
|
if tag.is_some() {
|
|
return Err(Error::TagSize);
|
|
}
|
|
cbc_decrypt::<Aes256>(key, iv, buffer)
|
|
}
|
|
#[cfg(feature = "aes-ctr")]
|
|
Self::Aes128Ctr | Self::Aes192Ctr | Self::Aes256Ctr => {
|
|
if tag.is_some() {
|
|
return Err(Error::TagSize);
|
|
}
|
|
|
|
// Counter mode encryption and decryption are the same operation
|
|
self.encrypt(key, iv, buffer)?;
|
|
Ok(())
|
|
}
|
|
#[cfg(feature = "aes-gcm")]
|
|
Self::Aes128Gcm => {
|
|
let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
|
|
let nonce = Nonce::try_from(iv).map_err(|_| Error::IvSize)?;
|
|
let tag = tag.ok_or(Error::TagSize)?;
|
|
cipher
|
|
.decrypt_in_place_detached(&nonce.into(), &[], buffer, &tag.into())
|
|
.map_err(|_| Error::Crypto)?;
|
|
|
|
Ok(())
|
|
}
|
|
#[cfg(feature = "aes-gcm")]
|
|
Self::Aes256Gcm => {
|
|
let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
|
|
let nonce = Nonce::try_from(iv).map_err(|_| Error::IvSize)?;
|
|
let tag = tag.ok_or(Error::TagSize)?;
|
|
cipher
|
|
.decrypt_in_place_detached(&nonce.into(), &[], buffer, &tag.into())
|
|
.map_err(|_| Error::Crypto)?;
|
|
|
|
Ok(())
|
|
}
|
|
#[cfg(feature = "chacha20poly1305")]
|
|
Self::ChaCha20Poly1305 => {
|
|
let tag = tag.ok_or(Error::TagSize)?;
|
|
ChaCha20Poly1305::new(key, iv)?.decrypt(buffer, tag)
|
|
}
|
|
#[cfg(feature = "tdes")]
|
|
Self::TDesCbc => {
|
|
if tag.is_some() {
|
|
return Err(Error::TagSize);
|
|
}
|
|
cbc_decrypt::<TdesEde3>(key, iv, buffer)
|
|
}
|
|
_ => {
|
|
// Suppress unused variable warnings.
|
|
let (_, _, _, _) = (key, iv, buffer, tag);
|
|
Err(self.unsupported())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Encrypt the ciphertext in the `buffer` in-place using this cipher.
|
|
pub fn encrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<Option<Tag>> {
|
|
match self {
|
|
#[cfg(feature = "aes-cbc")]
|
|
Self::Aes128Cbc => {
|
|
cbc_encrypt::<Aes128>(key, iv, buffer)?;
|
|
Ok(None)
|
|
}
|
|
#[cfg(feature = "aes-cbc")]
|
|
Self::Aes192Cbc => {
|
|
cbc_encrypt::<Aes192>(key, iv, buffer)?;
|
|
Ok(None)
|
|
}
|
|
#[cfg(feature = "aes-cbc")]
|
|
Self::Aes256Cbc => {
|
|
cbc_encrypt::<Aes256>(key, iv, buffer)?;
|
|
Ok(None)
|
|
}
|
|
#[cfg(feature = "aes-ctr")]
|
|
Self::Aes128Ctr => {
|
|
ctr_encrypt::<Ctr128BE<Aes128>>(key, iv, buffer)?;
|
|
Ok(None)
|
|
}
|
|
#[cfg(feature = "aes-ctr")]
|
|
Self::Aes192Ctr => {
|
|
ctr_encrypt::<Ctr128BE<Aes192>>(key, iv, buffer)?;
|
|
Ok(None)
|
|
}
|
|
#[cfg(feature = "aes-ctr")]
|
|
Self::Aes256Ctr => {
|
|
ctr_encrypt::<Ctr128BE<Aes256>>(key, iv, buffer)?;
|
|
Ok(None)
|
|
}
|
|
#[cfg(feature = "aes-gcm")]
|
|
Self::Aes128Gcm => {
|
|
let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
|
|
let nonce = Nonce::try_from(iv).map_err(|_| Error::IvSize)?;
|
|
let tag = cipher
|
|
.encrypt_in_place_detached(&nonce.into(), &[], buffer)
|
|
.map_err(|_| Error::Crypto)?;
|
|
|
|
Ok(Some(tag.into()))
|
|
}
|
|
#[cfg(feature = "aes-gcm")]
|
|
Self::Aes256Gcm => {
|
|
let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?;
|
|
let nonce = Nonce::try_from(iv).map_err(|_| Error::IvSize)?;
|
|
let tag = cipher
|
|
.encrypt_in_place_detached(&nonce.into(), &[], buffer)
|
|
.map_err(|_| Error::Crypto)?;
|
|
|
|
Ok(Some(tag.into()))
|
|
}
|
|
#[cfg(feature = "chacha20poly1305")]
|
|
Self::ChaCha20Poly1305 => {
|
|
let tag = ChaCha20Poly1305::new(key, iv)?.encrypt(buffer);
|
|
Ok(Some(tag))
|
|
}
|
|
#[cfg(feature = "tdes")]
|
|
Self::TDesCbc => {
|
|
cbc_encrypt::<TdesEde3>(key, iv, buffer)?;
|
|
Ok(None)
|
|
}
|
|
_ => {
|
|
// Suppress unused variable warnings.
|
|
let (_, _, _) = (key, iv, buffer);
|
|
Err(self.unsupported())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create an unsupported cipher error.
|
|
fn unsupported(self) -> Error {
|
|
Error::UnsupportedCipher(self)
|
|
}
|
|
}
|
|
|
|
impl AsRef<str> for Cipher {
|
|
fn as_ref(&self) -> &str {
|
|
self.as_str()
|
|
}
|
|
}
|
|
|
|
impl Label for Cipher {}
|
|
|
|
impl fmt::Display for Cipher {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.write_str(self.as_str())
|
|
}
|
|
}
|
|
|
|
impl str::FromStr for Cipher {
|
|
type Err = LabelError;
|
|
|
|
fn from_str(ciphername: &str) -> core::result::Result<Self, LabelError> {
|
|
match ciphername {
|
|
"none" => Ok(Self::None),
|
|
AES128_CBC => Ok(Self::Aes128Cbc),
|
|
AES192_CBC => Ok(Self::Aes192Cbc),
|
|
AES256_CBC => Ok(Self::Aes256Cbc),
|
|
AES128_CTR => Ok(Self::Aes128Ctr),
|
|
AES192_CTR => Ok(Self::Aes192Ctr),
|
|
AES256_CTR => Ok(Self::Aes256Ctr),
|
|
AES128_GCM => Ok(Self::Aes128Gcm),
|
|
AES256_GCM => Ok(Self::Aes256Gcm),
|
|
CHACHA20_POLY1305 => Ok(Self::ChaCha20Poly1305),
|
|
TDES_CBC => Ok(Self::TDesCbc),
|
|
_ => Err(LabelError::new(ciphername)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
|
|
fn cbc_encrypt<C>(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()>
|
|
where
|
|
C: BlockEncryptMut + BlockCipher + KeyInit,
|
|
{
|
|
let cipher = Encryptor::<C>::new_from_slices(key, iv).map_err(|_| Error::KeySize)?;
|
|
|
|
// Since the passed in buffer is already padded, using NoPadding here
|
|
cipher
|
|
.encrypt_padded_mut::<NoPadding>(buffer, buffer.len())
|
|
.map_err(|_| Error::Crypto)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
|
|
fn cbc_decrypt<C>(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()>
|
|
where
|
|
C: BlockDecryptMut + BlockCipher + KeyInit,
|
|
{
|
|
let cipher = Decryptor::<C>::new_from_slices(key, iv).map_err(|_| Error::KeySize)?;
|
|
|
|
// Since the passed in buffer is already padded, using NoPadding here
|
|
cipher
|
|
.decrypt_padded_mut::<NoPadding>(buffer)
|
|
.map_err(|_| Error::Crypto)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "aes-ctr")]
|
|
fn ctr_encrypt<C>(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()>
|
|
where
|
|
C: StreamCipherCore + KeyIvInit,
|
|
{
|
|
let cipher = C::new_from_slices(key, iv).map_err(|_| Error::KeySize)?;
|
|
|
|
cipher
|
|
.try_apply_keystream_partial(buffer.into())
|
|
.map_err(|_| Error::Crypto)?;
|
|
|
|
Ok(())
|
|
}
|