Skip to content

Commit

Permalink
AES Key Wrap Algorithm Support
Browse files Browse the repository at this point in the history
  • Loading branch information
skmcgrail committed Nov 16, 2023
1 parent a30dec2 commit 9dd7466
Show file tree
Hide file tree
Showing 6 changed files with 1,045 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ deps/aws-lc-sys/src/bindings.rs

**/target
/Cargo.lock

lcov.info
2 changes: 1 addition & 1 deletion aws-lc-rs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ asan-fips:
RUST_BACKTRACE=1 ASAN_OPTIONS=detect_leaks=1 RUSTFLAGS=-Zsanitizer=address RUSTDOCFLAGS=-Zsanitizer=address cargo +nightly test --lib --bins --tests --examples --target `rustc -vV | sed -n 's|host: ||p'` --no-default-features --features fips,asan

coverage:
cargo llvm-cov --no-fail-fast --fail-under-lines 95 --ignore-filename-regex "aws-lc-sys/*"
cargo llvm-cov --no-fail-fast --fail-under-lines 95 --ignore-filename-regex "aws-lc-sys/*" --lcov --output-path lcov.info

test:
cargo test --all-targets --features ring-benchmarks
Expand Down
354 changes: 354 additions & 0 deletions aws-lc-rs/src/key_wrap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

//! Key Wrap Algorithm [NIST SP 800-38F](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38F.pdf)
//!
//! # Examples
//! ```rust
//! # use std::error::Error;
//! # fn main() -> Result<(), Box<dyn Error>> {
//! use aws_lc_rs::key_wrap::{KeyEncryptionKey, WrappingMode, AES_128};
//!
//! const KEY: &[u8] = &[
//! 0xa8, 0xe0, 0x6d, 0xa6, 0x25, 0xa6, 0x5b, 0x25, 0xcf, 0x50, 0x30, 0x82, 0x68, 0x30, 0xb6,
//! 0x61,
//! ];
//! const PLAINTEXT: &[u8] = &[0x43, 0xac, 0xff, 0x29, 0x31, 0x20, 0xdd, 0x5d];
//!
//! let kek = KeyEncryptionKey::new(&AES_128, KEY, WrappingMode::Padded)?;
//!
//! let mut output = vec![0u8; PLAINTEXT.len() + 15];
//!
//! let ciphertext = kek.wrap(PLAINTEXT, &mut output)?;
//!
//! let kek = KeyEncryptionKey::new(&AES_128, KEY, WrappingMode::Padded)?;
//!
//! let mut output = vec![0u8; ciphertext.len()];
//!
//! let plaintext = kek.unwrap(&*ciphertext, &mut output)?;
//!
//! assert_eq!(PLAINTEXT, plaintext);
//!
//! Ok(())
//! # }
//! ```
use std::{fmt::Debug, mem::MaybeUninit, ptr::null};

use aws_lc::{
AES_set_decrypt_key, AES_set_encrypt_key, AES_unwrap_key, AES_unwrap_key_padded, AES_wrap_key,
AES_wrap_key_padded, AES_KEY,
};

use crate::{error::Unspecified, fips::indicator_check};

mod tests;

/// The Key Wrapping Algorithm
pub struct Algorithm {
id: AlgorithmId,
key_len: usize,
}

impl Algorithm {
/// Returns the algorithm identifier.
#[inline]
#[must_use]
pub fn id(&self) -> AlgorithmId {
self.id
}

/// Returns the algorithm key length.
#[inline]
#[must_use]
fn key_len(&self) -> usize {
self.key_len
}
}

impl Debug for Algorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.id, f)
}
}

/// The Key Wrapping Algorithm Identifier
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum AlgorithmId {
/// AES-128 Key Wrap
Aes128,

/// AES-256 Key Wrap
Aes256,
}

/// AES-128 Key Wrapping
pub const AES_128: Algorithm = Algorithm {
id: AlgorithmId::Aes128,
key_len: 16,
};

/// AES-256 Key Wrapping
pub const AES_256: Algorithm = Algorithm {
id: AlgorithmId::Aes256,
key_len: 32,
};

/// The mode of operation for the wrapping algorithm.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum WrappingMode {
/// Key Wrap with Padding
Padded,

/// Key Wrap
Unpadded,
}

impl WrappingMode {
fn wrap<'output>(
self,
key: &[u8],
input: &[u8],
output: &'output mut [u8],
) -> Result<&'output mut [u8], Unspecified> {
// For most checks we can count on AWS-LC, but in this instance
// it expects that output is sufficiently sized. So we must check it here.
if self == WrappingMode::Unpadded && output.len() < input.len() + 8 {
return Err(Unspecified);
}

let mut aes_key = MaybeUninit::<AES_KEY>::uninit();

let key_bits: u32 = (key.len() * 8).try_into().map_err(|_| Unspecified)?;

if 0 != unsafe { AES_set_encrypt_key(key.as_ptr(), key_bits, aes_key.as_mut_ptr()) } {
return Err(Unspecified);
}

let aes_key = unsafe { aes_key.assume_init() };

let out_len = match self {
WrappingMode::Padded => {
let mut out_len: usize = 0;

// AWS-LC validates the following:
// * in_len != 0
// * in_len <= INT_MAX
// * max_out >= required_padding + 8
if 1 != indicator_check!(unsafe {
AES_wrap_key_padded(
&aes_key,
output.as_mut_ptr(),
&mut out_len,
output.len(),
input.as_ptr(),
input.len(),
)
}) {
return Err(Unspecified);
}

out_len
}
WrappingMode::Unpadded => {
// AWS-LC validates the following:
// * in_len <= INT_MAX - 8
// * in_len >= 16
// * in_len % 8 == 0
let out_len = indicator_check!(unsafe {
AES_wrap_key(
&aes_key,
null(),
output.as_mut_ptr(),
input.as_ptr(),
input.len(),
)
});

if out_len == -1 {
return Err(Unspecified);
}

let out_len: usize = out_len.try_into().map_err(|_| Unspecified)?;

debug_assert_eq!(out_len, input.len() + 8);

out_len
}
};

Ok(&mut output[..out_len])
}

fn unwrap<'output>(
self,
key: &[u8],
input: &[u8],
output: &'output mut [u8],
) -> Result<&'output mut [u8], Unspecified> {
if self == WrappingMode::Unpadded && output.len() < input.len() - 8 {
return Err(Unspecified);
}

let mut aes_key = MaybeUninit::<AES_KEY>::uninit();

if 0 != unsafe {
AES_set_decrypt_key(
key.as_ptr(),
(key.len() * 8).try_into().map_err(|_| Unspecified)?,
aes_key.as_mut_ptr(),
)
} {
return Err(Unspecified);
}

let aes_key = unsafe { aes_key.assume_init() };

let out_len = match self {
WrappingMode::Padded => {
let mut out_len: usize = 0;

// AWS-LC validates the following:
// * in_len >= AES_BLOCK_SIZE
// * max_out >= in_len - 8
if 1 != indicator_check!(unsafe {
AES_unwrap_key_padded(
&aes_key,
output.as_mut_ptr(),
&mut out_len,
output.len(),
input.as_ptr(),
input.len(),
)
}) {
return Err(Unspecified);
};

out_len
}
WrappingMode::Unpadded => {
// AWS-LC validates the following:
// * in_len < INT_MAX
// * in_len > 24
// * in_len % 8 == 0
let out_len = indicator_check!(unsafe {
AES_unwrap_key(
&aes_key,
null(),
output.as_mut_ptr(),
input.as_ptr(),
input.len(),
)
});

if out_len == -1 {
return Err(Unspecified);
}

let out_len: usize = out_len.try_into().map_err(|_| Unspecified)?;

debug_assert_eq!(out_len, input.len() - 8);

out_len
}
};

Ok(&mut output[..out_len])
}
}

/// The key-encryption key used with the selected cipher algorithn to wrap or unwrap a key.
pub struct KeyEncryptionKey {
algorithm: &'static Algorithm,
key: Box<[u8]>,
mode: WrappingMode,
}

impl KeyEncryptionKey {
/// Creates a new `KeyEncryptionKey` using the provider cipher algorithm, key bytes, and wrapping mode.
///
/// # Errors
/// * [`Unspecified`]: Returned if `key_bytes.len()` does not match the size expected for the provided algorithm.
pub fn new(
algorithm: &'static Algorithm,
key_bytes: &[u8],
mode: WrappingMode,
) -> Result<Self, Unspecified> {
if algorithm.key_len() != key_bytes.len() {
return Err(Unspecified);
}

let key = Vec::from(key_bytes).into_boxed_slice();

Ok(Self {
algorithm,
key,
mode,
})
}

/// Peforms the key wrap encryption algorithm using `KeyEncryptionKey`'s configured cipher algorithm
/// and wrapping operation mode. It wraps the provided `input` plaintext and writes the
/// ciphertext to `output`.
///
/// If `WrappingMode::Unpadded` is the configured mode, then `input.len()` must be a multiple of 8 and non-zero.
///
/// # Sizing `output`
/// `output` must be sized appropriately depending on the configured [`WrappingMode`].
/// * [`WrappingMode::Padded`]: `output.len() >= (input.len() + 15)`
/// * [`WrappingMode::Unpadded`]: `output.len() >= (input.len() + 8)`
///
/// # Errors
/// * [`Unspecified`]: An error occurred either due to `output` being insufficiently sized, `input` exceeding
/// the allowed input size, or for other unspecified reasons.
pub fn wrap<'output>(
self,
input: &[u8],
output: &'output mut [u8],
) -> Result<&'output mut [u8], Unspecified> {
self.mode.wrap(&self.key, input, output)
}

/// Peforms the key wrap decryption algorithm using `KeyEncryptionKey`'s configured cipher algorithm
/// and wrapping operation mode. It unwraps the provided `input` ciphertext and writes the
/// plaintext to `output`.
///
/// If `WrappingMode::Unpadded` is the configured mode, then `input.len()` must be a multiple of 8.
///
/// # Sizing `output`
/// `output` must be sized appropriately depending on the configured [`WrappingMode`].
/// * [`WrappingMode::Padded`]: `output.len() >= input.len()`
/// * [`WrappingMode::Unpadded`]: `output.len() >= (input.len() - 8)`
///
/// # Errors
/// * [`Unspecified`]: An error occurred either due to `output` being insufficiently sized, `input` exceeding
/// the allowed input size, or for other unspecified reasons.
pub fn unwrap<'output>(
self,
input: &[u8],
output: &'output mut [u8],
) -> Result<&'output mut [u8], Unspecified> {
self.mode.unwrap(&self.key, input, output)
}

/// Returns the configured `Algorithm`.
#[must_use]
pub fn algorithm(&self) -> &'static Algorithm {
self.algorithm
}

/// Returns the configured `WrappingMode`.
#[must_use]
pub fn wrapping_mode(&self) -> WrappingMode {
self.mode
}
}

#[allow(clippy::missing_fields_in_debug)]
impl Debug for KeyEncryptionKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("KeyEncryptionKey")
.field("algorithm", &self.algorithm)
.field("mode", &self.mode)
.finish()
}
}
Loading

0 comments on commit 9dd7466

Please sign in to comment.