rustdtp 0.9.0

Cross-platform networking interfaces for Rust.
Documentation
//! Crypto utilities.

use crate::error::Result;
use aes_gcm::aead::{Aead, KeyInit, OsRng};
use aes_gcm::{AeadCore, Aes256Gcm, Nonce};
use sha2::{Digest, Sha256};
use std::io;
use std::sync::Arc;
use x25519_dalek::{EphemeralSecret, PublicKey};

/// The number of bytes to use for an X25519 public key.
pub const PUBLIC_KEY_SIZE: usize = 32;

/// The number of bytes to use for an AES key.
pub const AES_KEY_SIZE: usize = 32;

/// The number of bytes to use for an AES nonce.
pub const AES_NONCE_SIZE: usize = 12;

/// Generates a new X25519 key pair.
pub async fn dh_key_pair() -> (PublicKey, EphemeralSecret) {
    tokio::task::spawn_blocking(move || {
        let secret_key = EphemeralSecret::random_from_rng(OsRng);
        let public_key = PublicKey::from(&secret_key);

        (public_key, secret_key)
    })
    .await
    .unwrap()
}

/// Performs the math for establishing a shared key via the Diffie-Hellman key
/// exchange.
pub async fn dh_shared_key(
    this_secret_key: EphemeralSecret,
    other_public_key: impl Into<PublicKey> + Send,
) -> [u8; AES_KEY_SIZE] {
    let other_public_key = other_public_key.into();

    tokio::task::spawn_blocking(move || {
        let shared_secret = this_secret_key.diffie_hellman(&other_public_key);
        let hashed_key = Sha256::digest(shared_secret);
        hashed_key.as_slice().try_into().unwrap()
    })
    .await
    .unwrap()
}

/// Encrypt some data with AES.
///
/// - `key`: the AES key.
/// - `plaintext`: the data to encrypt.
///
/// Returns a result containing the encrypted data with the nonce prepended, or
/// the error variant if an error occurred while encrypting.
pub async fn aes_encrypt(key: [u8; AES_KEY_SIZE], plaintext: Arc<[u8]>) -> Result<Vec<u8>> {
    tokio::task::spawn_blocking(move || {
        let cipher = Aes256Gcm::new_from_slice(&key).unwrap();
        let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
        let ciphertext = cipher.encrypt(&nonce, plaintext.as_ref())?;

        let mut ciphertext_with_nonce = nonce.to_vec();
        ciphertext_with_nonce.extend(ciphertext);

        Ok(ciphertext_with_nonce)
    })
    .await
    .unwrap()
}

/// Decrypt some data with AES.
///
/// - `key`: the AES key.
/// - `ciphertext_with_nonce`: the data to decrypt, containing the prepended
///   nonce.
///
/// Returns a result containing the decrypted data, or the error variant if an
/// error occurred while decrypting.
pub async fn aes_decrypt(
    key: [u8; AES_KEY_SIZE],
    ciphertext_with_nonce: Arc<[u8]>,
) -> Result<Vec<u8>> {
    tokio::task::spawn_blocking(move || {
        let cipher = Aes256Gcm::new_from_slice(&key).unwrap();
        let (nonce_slice, ciphertext) = ciphertext_with_nonce.split_at(AES_NONCE_SIZE);
        let nonce_slice_sized: [u8; AES_NONCE_SIZE] = nonce_slice
            .try_into()
            .map_err(|_| io::Error::new(io::ErrorKind::UnexpectedEof, "incorrect nonce length"))?;

        let nonce = Nonce::from(nonce_slice_sized);
        let plaintext = cipher.decrypt(&nonce, ciphertext.as_ref())?;

        Ok(plaintext)
    })
    .await
    .unwrap()
}