Skip to content

Commit

Permalink
[1+CRYPTO] add docstrings and NOT!! doctests to crypto.rs
Browse files Browse the repository at this point in the history
- why not doctests? because we need a workaround for module visibility in doctests.
  - [UNSTABLE] doctests will come after we add the unstable_internal cargo feature
- [KDF] link the HASH_OUTPUT_SIZE of a kdf signature to a the signature length declaration from
  crypto.rs
  • Loading branch information
cosmicexplorer committed Jun 27, 2022
1 parent 622389b commit 9ed10e4
Showing 1 changed file with 84 additions and 16 deletions.
100 changes: 84 additions & 16 deletions rust/protocol/src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
//
// Copyright 2020 Signal Messenger, LLC.
// Copyright 2020-2022 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//

use std::convert::TryInto;
use std::result::Result;
//! Application of cryptographic primitives, including [HMAC] and [AES].
//!
//! [HMAC]: https://en.wikipedia.org/wiki/HMAC
//! [AES]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard

#![warn(missing_docs)]

use aes::cipher::{NewCipher, StreamCipher};
use aes::{Aes256, Aes256Ctr};
Expand All @@ -14,14 +18,16 @@ use hmac::{Hmac, Mac, NewMac};
use sha2::Sha256;
use subtle::ConstantTimeEq;

use std::convert::TryInto;

#[derive(Debug)]
pub(crate) enum EncryptionError {
pub enum EncryptionError {
/// The key or IV is the wrong length.
BadKeyOrIv,
}

#[derive(Debug)]
pub(crate) enum DecryptionError {
pub enum DecryptionError {
/// The key or IV is the wrong length.
BadKeyOrIv,
/// Either the input is malformed, or the MAC doesn't match on decryption.
Expand All @@ -30,24 +36,54 @@ pub(crate) enum DecryptionError {
BadCiphertext(&'static str),
}

fn aes_256_ctr_encrypt(ptext: &[u8], key: &[u8]) -> Result<Vec<u8>, EncryptionError> {
let key: [u8; 32] = key.try_into().map_err(|_| EncryptionError::BadKeyOrIv)?;

let zero_nonce = [0u8; 16];
/// TODO: Could be nice to have a type-safe library for manipulating units of bytes safely.
const BITS_PER_BYTE: usize = std::mem::size_of::<u8>() * 8;

/// The length of the key we use for [AES] encryption in this crate.
///
/// Used in [aes_256_ctr_encrypt] and [aes_256_ctr_decrypt].
///
/// [AES]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
pub const AES_256_KEY_SIZE: usize = 256 / BITS_PER_BYTE;

/// The size of the generated nonce we use for [AES] encryption in this crate.
///
/// Used in [aes_256_ctr_encrypt] and [aes_256_ctr_decrypt].
///
/// [AES]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
pub const AES_NONCE_SIZE: usize = 128 / BITS_PER_BYTE;

/// Encrypt plaintext `ptext` using key `key` with [AES]-256 in [CTR] mode.
///
/// [AES]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
/// [CTR]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CTR
pub fn aes_256_ctr_encrypt(ptext: &[u8], key: &[u8]) -> Result<Vec<u8>, EncryptionError> {
let key: [u8; AES_256_KEY_SIZE] = key.try_into().map_err(|_| EncryptionError::BadKeyOrIv)?;

let zero_nonce = [0u8; AES_NONCE_SIZE];
let mut cipher = Aes256Ctr::new(key[..].into(), zero_nonce[..].into());

let mut ctext = ptext.to_vec();
cipher.apply_keystream(&mut ctext);
Ok(ctext)
}

fn aes_256_ctr_decrypt(ctext: &[u8], key: &[u8]) -> Result<Vec<u8>, DecryptionError> {
/// Decrypt ciphertext `ctext` using key `key` with [AES]-256 in [CTR] mode.
///
/// [AES]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
/// [CTR]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CTR
pub fn aes_256_ctr_decrypt(ctext: &[u8], key: &[u8]) -> Result<Vec<u8>, DecryptionError> {
aes_256_ctr_encrypt(ctext, key).map_err(|e| match e {
EncryptionError::BadKeyOrIv => DecryptionError::BadKeyOrIv,
})
}

pub(crate) fn aes_256_cbc_encrypt(
/// Encrypt plaintext `ptext` using key `key` and initialization vector `iv` with [AES]-256 in
/// [CBC] mode.
///
/// [AES]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
/// [CBC]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC
pub fn aes_256_cbc_encrypt(
ptext: &[u8],
key: &[u8],
iv: &[u8],
Expand All @@ -58,7 +94,12 @@ pub(crate) fn aes_256_cbc_encrypt(
}
}

pub(crate) fn aes_256_cbc_decrypt(
/// Decrypt ciphertext `ctext` using key `key` and initialization vector `iv` with [AES]-256 in
/// [CBC] mode.
///
/// [AES]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
/// [CBC]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC
pub fn aes_256_cbc_decrypt(
ctext: &[u8],
key: &[u8],
iv: &[u8],
Expand All @@ -75,25 +116,52 @@ pub(crate) fn aes_256_cbc_decrypt(
.map_err(|_| DecryptionError::BadCiphertext("failed to decrypt"))
}

pub(crate) fn hmac_sha256(key: &[u8], input: &[u8]) -> [u8; 32] {
/// The statically-known size of the output of [hmac_sha256].
pub const HMAC_OUTPUT_SIZE: usize = 256 / BITS_PER_BYTE;

/// Calculate the [HMAC]-SHA256 code over `input` using `key`.
///
/// [HMAC]: https://en.wikipedia.org/wiki/HMAC
pub fn hmac_sha256(key: &[u8], input: &[u8]) -> [u8; HMAC_OUTPUT_SIZE] {
let mut hmac =
Hmac::<Sha256>::new_from_slice(key).expect("HMAC-SHA256 should accept any size key");
hmac.update(input);
hmac.finalize().into_bytes().into()
}

pub(crate) fn aes256_ctr_hmacsha256_encrypt(
/// Length in bytes of the [HMAC] key used for [aes256_ctr_hmacsha256_encrypt] and
/// [aes256_ctr_hmacsha256_decrypt].
///
/// [HMAC]: https://en.wikipedia.org/wiki/HMAC
pub const MAC_KEY_LENGTH: usize = 80 / BITS_PER_BYTE;

/// Encrypt plaintext `msg` with [AES]-256 and embed a computed [HMAC] into the returned bytes.
///
/// *Implementation note: within the body of this method, only the first [MAC_KEY_LENGTH] bytes of
/// the computed MAC are used.*
///
/// [AES]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
/// [HMAC]: https://en.wikipedia.org/wiki/HMAC
pub fn aes256_ctr_hmacsha256_encrypt(
msg: &[u8],
cipher_key: &[u8],
mac_key: &[u8],
) -> Result<Vec<u8>, EncryptionError> {
let mut ctext = aes_256_ctr_encrypt(msg, cipher_key)?;
let mac = hmac_sha256(mac_key, &ctext);
ctext.extend_from_slice(&mac[..10]);
ctext.extend_from_slice(&mac[..MAC_KEY_LENGTH]);
Ok(ctext)
}

pub(crate) fn aes256_ctr_hmacsha256_decrypt(
/// Validate the [HMAC] `mac_key` against the ciphertext `ctext`, then decrypt `ctext` using
/// [AES]-256 with `cipher_key` and [aes_256_ctr_decrypt].
///
/// *Implementation note: the last [MAC_KEY_LENGTH] bytes of the `ctext` slice represent the
/// truncated [HMAC] of the rest of the message, as generated by [aes256_ctr_hmacsha256_encrypt].*
///
/// [AES]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
/// [HMAC]: https://en.wikipedia.org/wiki/HMAC
pub fn aes256_ctr_hmacsha256_decrypt(
ctext: &[u8],
cipher_key: &[u8],
mac_key: &[u8],
Expand Down

0 comments on commit 9ed10e4

Please sign in to comment.