Skip to content

Commit

Permalink
aes-kw: code refactoring (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
newpavlov authored Nov 8, 2024
1 parent 3cfe59f commit 28330dc
Show file tree
Hide file tree
Showing 11 changed files with 748 additions and 948 deletions.
18 changes: 18 additions & 0 deletions aes-kw/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## UNRELEASED
### Added
- `AssociatedOid` implementations ([#35])

### Changed
- Bump `aes` dependency to v0.9 ([#34])
- `Kek` type is split into separate `AesKw` and `AesKwp` types ([#40])
- `wrap` and `unwrap` methods now return resulting slice ([#40])

### Removed
- `Kek::new` inherent method in favor of implementing `InnerInit` ([#40])
- `From`/`Into` impls from key into key-wrapper types ([#40])
- `IV`, `KWP_IV_PREFIX`, and `KWP_MAX_LEN` constants ([#40])

[#34]: https://github.com/RustCrypto/key-wraps/pull/34
[#35]: https://github.com/RustCrypto/key-wraps/pull/35
[#40]: https://github.com/RustCrypto/key-wraps/pull/40

## 0.2.1 (2022-04-20)
### Changed
- Use `encrypt_with_backend`/`decrypt_with_backend` methods ([#19])
Expand Down
4 changes: 1 addition & 3 deletions aes-kw/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ const-oid = { version = "0.10.0-rc.3", optional = true }
hex-literal = "0.3"

[features]
default = ["std", "oid"]
alloc = []
std = ["alloc"]
default = ["oid"]
oid = ["dep:const-oid"]

[package.metadata.docs.rs]
Expand Down
63 changes: 20 additions & 43 deletions aes-kw/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
[![Build Status][build-image]][build-link]

Pure Rust implementation of the [NIST AES-KW Key Wrap] and
[NIST AES-KWP Key Wrap with Padding] modes also described in [RFC3394]
and [RFC5649].
[NIST AES-KWP Key Wrap with Padding] modes also described in [RFC 3394]
and [RFC 5649].

## About

RFC3394 § 2 describes AES-KW as follows:
RFC 3394 § 2 describes AES-KW as follows:

> The AES key wrap algorithm is designed to wrap or encrypt key data.
> The key wrap operates on blocks of 64 bits. Before being wrapped,
Expand Down Expand Up @@ -54,54 +54,31 @@ RFC5649 § 1 describes AES-KWP as follows:
> octets. Most systems will have other factors that limit the
> practical size of key data to much less than 2^32 octets.
# Usage

The most common way to use AES-KW is as follows: you provide the Key Wrapping Key and the key-to-be-wrapped, then wrap it, or provide a wrapped-key and unwrap it.
## Examples

```rust
# fn main() -> Result<(), Box<dyn std::error::Error>> {
# #[cfg(feature = "std")]
# {
use aes_kw::Kek;
use hex_literal::hex;
use aes_kw::{KwAes128, KeyInit};

let kek = Kek::from(hex!("000102030405060708090A0B0C0D0E0F"));
let input_key = hex!("00112233445566778899AABBCCDDEEFF");

let wrapped_key = kek.wrap_vec(&input_key)?;
assert_eq!(wrapped_key, hex!("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5"));

let unwrapped_key = kek.unwrap_vec(&wrapped_key)?;
assert_eq!(unwrapped_key, input_key);
# }
# Ok(())
# }
```

Alternatively, AES-KWP can be used to wrap keys which are not a multiple of 8 bytes long.
// Key which is used to perform wrapping
let kw_key: [u8; 16] = hex!("000102030405060708090A0B0C0D0E0F");
// Key which will be wrapped
let key: [u8; 16] = hex!("00112233445566778899AABBCCDDEEFF");
// Wrapped key
let wkey: [u8; 24] = hex!("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5");

```rust
# fn main() -> Result<(), Box<dyn std::error::Error>> {
# #[cfg(feature = "std")]
# {
use aes_kw::Kek;
use hex_literal::hex;

let kek = Kek::from(hex!("5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8"));
let input_key = hex!("c37b7e6492584340bed12207808941155068f738");
let kw = KwAes128::new(&kw_key.into());

let wrapped_key = kek.wrap_with_padding_vec(&input_key)?;
assert_eq!(wrapped_key, hex!("138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a"));
let mut buf = [0u8; 24];
kw.wrap(&key, &mut buf).unwrap();
assert_eq!(buf, wkey);

let unwrapped_key = kek.unwrap_with_padding_vec(&wrapped_key)?;
assert_eq!(unwrapped_key, input_key);
# }
# Ok(())
# }
let mut buf = [0u8; 16];
kw.unwrap(&wkey, &mut buf).unwrap();
assert_eq!(buf, key);
```

Implemented for 128/192/256bit keys.

## Minimum Supported Rust Version

This crate requires **Rust 1.81** at a minimum.
Expand Down Expand Up @@ -139,5 +116,5 @@ dual licensed as above, without any additional terms or conditions.

[NIST AES-KW Key Wrap]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38F.pdf
[NIST AES-KWP Key Wrap with Padding]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38F.pdf
[RFC3394]: https://datatracker.ietf.org/doc/html/rfc3394
[RFC5649]: https://datatracker.ietf.org/doc/html/rfc5649
[RFC 3394]: https://www.rfc-editor.org/rfc/rfc3394.txt
[RFC 5649]: https://www.rfc-editor.org/rfc/rfc5649.txt
67 changes: 67 additions & 0 deletions aes-kw/src/ctx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use crate::{IV_LEN, SEMIBLOCK_SIZE};
use aes::cipher::{
typenum::U16, Block, BlockCipherDecBackend, BlockCipherDecClosure, BlockCipherEncBackend,
BlockCipherEncClosure, BlockSizeUser,
};

pub(crate) struct Ctx<'a> {
pub(crate) blocks_len: usize,
pub(crate) block: &'a mut Block<Self>,
pub(crate) buf: &'a mut [u8],
}

impl BlockSizeUser for Ctx<'_> {
type BlockSize = U16;
}

/// Very similar to the W(S) function defined by NIST in SP 800-38F, Section 6.1
impl BlockCipherEncClosure for Ctx<'_> {
#[inline(always)]
fn call<B: BlockCipherEncBackend<BlockSize = U16>>(self, backend: &B) {
for j in 0..=5 {
for (i, chunk) in self.buf.chunks_mut(SEMIBLOCK_SIZE).skip(1).enumerate() {
// A | R[i]
self.block[IV_LEN..].copy_from_slice(chunk);
// B = AES(K, ..)
backend.encrypt_block(self.block.into());

// A = MSB(64, B) ^ t
let t = (self.blocks_len * j + (i + 1)) as u64;
for (ai, ti) in self.block[..IV_LEN].iter_mut().zip(&t.to_be_bytes()) {
*ai ^= ti;
}

// R[i] = LSB(64, B)
chunk.copy_from_slice(&self.block[IV_LEN..]);
}
}
}
}

/// Very similar to the W^-1(S) function defined by NIST in SP 800-38F, Section 6.1
impl BlockCipherDecClosure for Ctx<'_> {
#[inline(always)]
fn call<B: BlockCipherDecBackend<BlockSize = U16>>(self, backend: &B) {
for j in (0..=5).rev() {
for (i, chunk) in self.buf.chunks_mut(SEMIBLOCK_SIZE).enumerate().rev() {
// A ^ t
let t = (self.blocks_len * j + (i + 1)) as u64;
for (ai, ti) in self.block[..IV_LEN].iter_mut().zip(&t.to_be_bytes()) {
*ai ^= ti;
}

// (A ^ t) | R[i]
self.block[IV_LEN..].copy_from_slice(chunk);

// B = AES-1(K, ..)
backend.decrypt_block(self.block.into());

// A = MSB(64, B)
// already set

// R[i] = LSB(64, B)
chunk.copy_from_slice(&self.block[IV_LEN..]);
}
}
}
}
21 changes: 4 additions & 17 deletions aes-kw/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
use core::fmt;

/// Result type with the `aes-kw` crate's [`Error`].
pub type Result<T> = core::result::Result<T, Error>;

/// Errors emitted from the wrap and unwrap operations.
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
/// Input data length invalid.
InvalidDataSize,

/// Invalid KEK size.
InvalidKekSize {
/// KEK size provided in bytes (expected 8, 12, or 24).
size: usize,
},

/// Output buffer size invalid.
InvalidOutputSize {
/// Expected size in bytes.
expected: usize,
expected_len: usize,
},

/// Integrity check did not pass.
Expand All @@ -29,10 +20,7 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidDataSize => write!(f, "data must be a multiple of 64 bits for AES-KW and less than 2^32 bytes for AES-KWP"),
Error::InvalidKekSize { size } => {
write!(f, "invalid AES KEK size: {}", size)
}
Error::InvalidOutputSize { expected } => {
Error::InvalidOutputSize { expected_len: expected } => {
write!(f, "invalid output buffer size: expected {}", expected)
}
Error::IntegrityCheckFailed => {
Expand All @@ -42,5 +30,4 @@ impl fmt::Display for Error {
}
}

#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl core::error::Error for Error {}
138 changes: 138 additions & 0 deletions aes-kw/src/kw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use crate::{ctx::Ctx, Error, IV_LEN, SEMIBLOCK_SIZE};
use aes::cipher::{
crypto_common::{InnerInit, InnerUser},
typenum::U16,
Block, BlockCipherDecrypt, BlockCipherEncrypt,
};

/// Default Initial Value for AES-KW as defined in RFC3394 § 2.2.3.1.
///
/// <https://datatracker.ietf.org/doc/html/rfc3394#section-2.2.3.1>
///
/// ```text
/// The default initial value (IV) is defined to be the hexadecimal
/// constant:
///
/// A[0] = IV = A6A6A6A6A6A6A6A6
///
/// The use of a constant as the IV supports a strong integrity check on
/// the key data during the period that it is wrapped. If unwrapping
/// produces A[0] = A6A6A6A6A6A6A6A6, then the chance that the key data
/// is corrupt is 2^-64. If unwrapping produces A[0] any other value,
/// then the unwrap must return an error and not return any key data.
/// ```
const IV: [u8; IV_LEN] = [0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6];

/// AES Key Wrapper (KW), as defined in [RFC 3394].
///
/// [RFC 3394]: https://www.rfc-editor.org/rfc/rfc3394.txt
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AesKw<C> {
cipher: C,
}

impl<C> InnerUser for AesKw<C> {
type Inner = C;
}

impl<C> InnerInit for AesKw<C> {
#[inline]
fn inner_init(cipher: Self::Inner) -> Self {
AesKw { cipher }
}
}

impl<C: BlockCipherEncrypt<BlockSize = U16>> AesKw<C> {
/// Wrap `data` and write result to `buf`.
///
/// Returns slice which points to `buf` and contains wrapped data.
///
/// Length of `data` must be multiple of [`SEMIBLOCK_SIZE`] and bigger than zero.
/// Length of `buf` must be bigger or equal to `data.len() + IV_LEN`.
#[inline]
pub fn wrap<'a>(&self, data: &[u8], buf: &'a mut [u8]) -> Result<&'a [u8], Error> {
let blocks_len = data.len() / SEMIBLOCK_SIZE;
let blocks_rem = data.len() % SEMIBLOCK_SIZE;
if blocks_rem != 0 {
return Err(Error::InvalidDataSize);
}

let expected_len = data.len() + IV_LEN;
let buf = buf
.get_mut(..expected_len)
.ok_or(Error::InvalidOutputSize { expected_len })?;

// 1) Initialize variables

// Set A to the IV
let block = &mut Block::<C>::default();
block[..IV_LEN].copy_from_slice(&IV);

// 2) Calculate intermediate values
buf[IV_LEN..].copy_from_slice(data);

self.cipher.encrypt_with_backend(Ctx {
blocks_len,
block,
buf,
});

// 3) Output the results
buf[..IV_LEN].copy_from_slice(&block[..IV_LEN]);

Ok(buf)
}
}

impl<C: BlockCipherDecrypt<BlockSize = U16>> AesKw<C> {
/// Unwrap `data` and write result to `buf`.
///
/// Returns slice which points to `buf` and contains unwrapped data.
///
/// Length of `data` must be multiple of [`SEMIBLOCK_SIZE`] and bigger than zero.
/// Length of `buf` must be bigger or equal to `data.len()`.
#[inline]
pub fn unwrap<'a>(&self, data: &[u8], buf: &'a mut [u8]) -> Result<&'a [u8], Error> {
let blocks_len = data.len() / SEMIBLOCK_SIZE;
let blocks_rem = data.len() % SEMIBLOCK_SIZE;
if blocks_rem != 0 || blocks_len < 1 {
return Err(Error::InvalidDataSize);
}

// 0) Prepare inputs

let blocks_len = blocks_len - 1;

let expected_len = blocks_len * SEMIBLOCK_SIZE;
let buf = buf
.get_mut(..expected_len)
.ok_or(Error::InvalidOutputSize { expected_len })?;

// 1) Initialize variables

let block = &mut Block::<C>::default();
block[..IV_LEN].copy_from_slice(&data[..IV_LEN]);

// for i = 1 to n: R[i] = C[i]
buf.copy_from_slice(&data[IV_LEN..]);

// 2) Calculate intermediate values

self.cipher.decrypt_with_backend(Ctx {
blocks_len,
block,
buf,
});

// 3) Output the results

let expected_iv = u64::from_ne_bytes(IV);
let calc_iv = u64::from_ne_bytes(block[..IV_LEN].try_into().unwrap());
if calc_iv == expected_iv {
Ok(buf)
} else {
buf.fill(0);
Err(Error::IntegrityCheckFailed)
}
}
}
Loading

0 comments on commit 28330dc

Please sign in to comment.