-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ssh-cipher: refactor
ChaCha20Poly1305
to use aead
API
Changes the `ChaCha20Poly1305` type to impl the standard AEAD APIs exported by the `aead` crate.
- Loading branch information
Showing
5 changed files
with
112 additions
and
39 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,21 @@ | ||
//! OpenSSH variant of ChaCha20Poly1305. | ||
|
||
use crate::{Error, Nonce, Result, Tag}; | ||
use chacha20::{ChaCha20, Key}; | ||
use cipher::{KeyInit, KeyIvInit, StreamCipher, StreamCipherSeek}; | ||
use crate::Tag; | ||
use aead::{ | ||
array::typenum::{U0, U16, U32, U8}, | ||
AeadCore, AeadInPlace, Error, KeyInit, KeySizeUser, Result, | ||
}; | ||
use chacha20::ChaCha20Legacy as ChaCha20; | ||
use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek}; | ||
use poly1305::Poly1305; | ||
use subtle::ConstantTimeEq; | ||
|
||
/// Key for `[email protected]`. | ||
pub type ChaChaKey = chacha20::Key; | ||
|
||
/// Nonce for `[email protected]`. | ||
pub type ChaChaNonce = chacha20::LegacyNonce; | ||
|
||
/// OpenSSH variant of ChaCha20Poly1305: `[email protected]` | ||
/// as described in [PROTOCOL.chacha20poly1305]. | ||
/// | ||
|
@@ -16,19 +26,60 @@ use subtle::ConstantTimeEq; | |
/// | ||
/// [PROTOCOL.chacha20poly1305]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD | ||
/// [RFC8439]: https://datatracker.ietf.org/doc/html/rfc8439 | ||
#[derive(Clone)] | ||
pub struct ChaCha20Poly1305 { | ||
// TODO(tarcieri): zeroize on drop | ||
key: ChaChaKey, | ||
} | ||
|
||
impl KeySizeUser for ChaCha20Poly1305 { | ||
type KeySize = U32; | ||
} | ||
|
||
impl KeyInit for ChaCha20Poly1305 { | ||
#[inline] | ||
fn new(key: &ChaChaKey) -> Self { | ||
Self { key: *key } | ||
} | ||
} | ||
|
||
impl AeadCore for ChaCha20Poly1305 { | ||
type NonceSize = U8; | ||
type TagSize = U16; | ||
type CiphertextOverhead = U0; | ||
} | ||
|
||
impl AeadInPlace for ChaCha20Poly1305 { | ||
fn encrypt_in_place_detached( | ||
&self, | ||
nonce: &ChaChaNonce, | ||
associated_data: &[u8], | ||
buffer: &mut [u8], | ||
) -> Result<Tag> { | ||
Cipher::new(&self.key, nonce).encrypt(associated_data, buffer) | ||
} | ||
|
||
fn decrypt_in_place_detached( | ||
&self, | ||
nonce: &ChaChaNonce, | ||
associated_data: &[u8], | ||
buffer: &mut [u8], | ||
tag: &Tag, | ||
) -> Result<()> { | ||
Cipher::new(&self.key, nonce).decrypt(associated_data, buffer, *tag) | ||
} | ||
} | ||
|
||
/// Internal type representing a cipher instance. | ||
struct Cipher { | ||
cipher: ChaCha20, | ||
mac: Poly1305, | ||
} | ||
|
||
impl ChaCha20Poly1305 { | ||
/// Create a new [`ChaCha20Poly1305`] instance with a 32-byte key. | ||
/// | ||
/// [PROTOCOL.chacha20poly1305]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD | ||
pub fn new(key: &[u8], nonce: &[u8]) -> Result<Self> { | ||
let key = Key::try_from(key).map_err(|_| Error::KeySize)?; | ||
let nonce = Nonce::try_from(nonce).map_err(|_| Error::IvSize)?; | ||
let mut cipher = ChaCha20::new(&key, &nonce.into()); | ||
impl Cipher { | ||
/// Create a new cipher instance. | ||
pub fn new(key: &ChaChaKey, nonce: &ChaChaNonce) -> Self { | ||
let mut cipher = ChaCha20::new(&key, nonce.into()); | ||
let mut poly1305_key = poly1305::Key::default(); | ||
cipher.apply_keystream(&mut poly1305_key); | ||
|
||
|
@@ -37,14 +88,19 @@ impl ChaCha20Poly1305 { | |
// Seek to block 1 | ||
cipher.seek(64); | ||
|
||
Ok(Self { cipher, mac }) | ||
Self { cipher, mac } | ||
} | ||
|
||
/// Encrypt the provided `buffer` in-place, returning the Poly1305 authentication tag. | ||
#[inline] | ||
pub fn encrypt(mut self, buffer: &mut [u8]) -> Tag { | ||
pub fn encrypt(mut self, associated_data: &[u8], buffer: &mut [u8]) -> Result<Tag> { | ||
// TODO(tarcieri): support associated data (RustCrypto/SSH#279) | ||
if !associated_data.is_empty() { | ||
return Err(Error); | ||
} | ||
|
||
self.cipher.apply_keystream(buffer); | ||
self.mac.compute_unpadded(buffer).into() | ||
Ok(self.mac.compute_unpadded(buffer).into()) | ||
} | ||
|
||
/// Decrypt the provided `buffer` in-place, verifying it against the provided Poly1305 | ||
|
@@ -55,14 +111,19 @@ impl ChaCha20Poly1305 { | |
/// | ||
/// Upon success, `Ok(())` is returned and `buffer` is rewritten with the decrypted plaintext. | ||
#[inline] | ||
pub fn decrypt(mut self, buffer: &mut [u8], tag: Tag) -> Result<()> { | ||
pub fn decrypt(mut self, associated_data: &[u8], buffer: &mut [u8], tag: Tag) -> Result<()> { | ||
// TODO(tarcieri): support associated data (RustCrypto/SSH#279) | ||
if !associated_data.is_empty() { | ||
return Err(Error); | ||
} | ||
|
||
let expected_tag = self.mac.compute_unpadded(buffer); | ||
|
||
if expected_tag.ct_eq(&tag).into() { | ||
self.cipher.apply_keystream(buffer); | ||
Ok(()) | ||
} else { | ||
Err(Error::Crypto) | ||
Err(Error) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,20 +35,24 @@ mod encryptor; | |
|
||
pub use crate::error::{Error, Result}; | ||
|
||
#[cfg(feature = "chacha20poly1305")] | ||
pub use crate::chacha20poly1305::ChaCha20Poly1305; | ||
|
||
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))] | ||
pub use crate::{decryptor::Decryptor, encryptor::Encryptor}; | ||
|
||
#[cfg(feature = "chacha20poly1305")] | ||
pub use crate::chacha20poly1305::{ChaCha20Poly1305, ChaChaKey, ChaChaNonce}; | ||
|
||
use cipher::array::{typenum::U16, Array}; | ||
use core::{fmt, str}; | ||
use encoding::{Label, LabelError}; | ||
|
||
#[cfg(feature = "aes-gcm")] | ||
use aes_gcm::{aead::AeadInPlace, Aes128Gcm, Aes256Gcm}; | ||
use { | ||
aead::array::typenum::U12, | ||
aes_gcm::{Aes128Gcm, Aes256Gcm}, | ||
}; | ||
|
||
#[cfg(feature = "aes-gcm")] | ||
use cipher::KeyInit; | ||
#[cfg(any(feature = "aes-gcm", feature = "chacha20poly1305"))] | ||
use aead::{AeadInPlace, KeyInit}; | ||
|
||
/// AES-128 in block chaining (CBC) mode | ||
const AES128_CBC: &str = "aes128-cbc"; | ||
|
@@ -80,17 +84,15 @@ const CHACHA20_POLY1305: &str = "[email protected]"; | |
/// Triple-DES in block chaining (CBC) mode | ||
const TDES_CBC: &str = "3des-cbc"; | ||
|
||
/// Nonce for AEAD modes. | ||
/// | ||
/// This is used by e.g. `[email protected]`/`[email protected]` and | ||
/// `[email protected]`. | ||
pub type Nonce = [u8; 12]; | ||
/// Nonce for `[email protected]`/`[email protected]`. | ||
#[cfg(feature = "aes-gcm")] | ||
pub type AesGcmNonce = Array<u8, U12>; | ||
|
||
/// Authentication tag for ciphertext data. | ||
/// | ||
/// This is used by e.g. `[email protected]`/`[email protected]` and | ||
/// `[email protected]`. | ||
pub type Tag = [u8; 16]; | ||
pub type Tag = Array<u8, U16>; | ||
|
||
/// Counter mode with a 128-bit big endian counter. | ||
#[cfg(feature = "aes-ctr")] | ||
|
@@ -172,7 +174,7 @@ impl Cipher { | |
Self::Aes256Ctr => Some((32, 16)), | ||
Self::Aes128Gcm => Some((16, 12)), | ||
Self::Aes256Gcm => Some((32, 12)), | ||
Self::ChaCha20Poly1305 => Some((32, 12)), | ||
Self::ChaCha20Poly1305 => Some((32, 8)), | ||
Self::TDesCbc => Some((24, 8)), | ||
} | ||
} | ||
|
@@ -233,7 +235,7 @@ impl Cipher { | |
#[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 nonce = AesGcmNonce::try_from(iv).map_err(|_| Error::IvSize)?; | ||
let tag = tag.ok_or(Error::TagSize)?; | ||
cipher | ||
.decrypt_in_place_detached(&nonce.into(), &[], buffer, &tag.into()) | ||
|
@@ -244,7 +246,7 @@ impl Cipher { | |
#[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 nonce = AesGcmNonce::try_from(iv).map_err(|_| Error::IvSize)?; | ||
let tag = tag.ok_or(Error::TagSize)?; | ||
cipher | ||
.decrypt_in_place_detached(&nonce.into(), &[], buffer, &tag.into()) | ||
|
@@ -254,8 +256,12 @@ impl Cipher { | |
} | ||
#[cfg(feature = "chacha20poly1305")] | ||
Self::ChaCha20Poly1305 => { | ||
let key = key.try_into().map_err(|_| Error::KeySize)?; | ||
let nonce = iv.try_into().map_err(|_| Error::IvSize)?; | ||
let tag = tag.ok_or(Error::TagSize)?; | ||
ChaCha20Poly1305::new(key, iv)?.decrypt(buffer, tag) | ||
ChaCha20Poly1305::new(key) | ||
.decrypt_in_place_detached(nonce, b"", buffer, &tag) | ||
.map_err(|_| Error::Crypto) | ||
} | ||
// Use `Decryptor` for non-AEAD modes | ||
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))] | ||
|
@@ -294,7 +300,7 @@ impl Cipher { | |
#[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 nonce = AesGcmNonce::try_from(iv).map_err(|_| Error::IvSize)?; | ||
let tag = cipher | ||
.encrypt_in_place_detached(&nonce.into(), &[], buffer) | ||
.map_err(|_| Error::Crypto)?; | ||
|
@@ -304,7 +310,7 @@ impl Cipher { | |
#[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 nonce = AesGcmNonce::try_from(iv).map_err(|_| Error::IvSize)?; | ||
let tag = cipher | ||
.encrypt_in_place_detached(&nonce.into(), &[], buffer) | ||
.map_err(|_| Error::Crypto)?; | ||
|
@@ -313,7 +319,11 @@ impl Cipher { | |
} | ||
#[cfg(feature = "chacha20poly1305")] | ||
Self::ChaCha20Poly1305 => { | ||
let tag = ChaCha20Poly1305::new(key, iv)?.encrypt(buffer); | ||
let key = key.try_into().map_err(|_| Error::KeySize)?; | ||
let nonce = iv.try_into().map_err(|_| Error::IvSize)?; | ||
let tag = ChaCha20Poly1305::new(key) | ||
.encrypt_in_place_detached(nonce, b"", buffer) | ||
.map_err(|_| Error::Crypto)?; | ||
Ok(Some(tag)) | ||
} | ||
// Use `Encryptor` for non-AEAD modes | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters