Skip to content

Commit 7582d0f

Browse files
committed
aes: autodetection support for AES-NI
On i686/x86_64 platforms, uses the `cpuid-bool` crate to detect at runtime whether AES-NI is available. This eliminates the need to specify `target_feature=+aes` when compiling the crate in order to take advantage of AES-NI.
1 parent 319a426 commit 7582d0f

File tree

10 files changed

+384
-210
lines changed

10 files changed

+384
-210
lines changed

.github/workflows/aes.yml

+6-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ jobs:
3939
- run: cargo build --release --target ${{ matrix.target }}
4040
- run: cargo build --release --target ${{ matrix.target }} --features compact
4141
- run: cargo build --release --target ${{ matrix.target }} --features ctr
42-
- run: cargo build --release --target ${{ matrix.target }} --features compact,ctr
42+
- run: cargo build --release --target ${{ matrix.target }} --features force-soft
43+
- run: cargo build --release --target ${{ matrix.target }} --all-features
4344

4445
# Tests for the portable software backend
4546
soft:
@@ -73,6 +74,7 @@ jobs:
7374
- run: cargo test --release --target ${{ matrix.target }}
7475
- run: cargo test --release --target ${{ matrix.target }} --features compact
7576
- run: cargo test --release --target ${{ matrix.target }} --features ctr
77+
- run: cargo test --release --target ${{ matrix.target }} --features force-soft
7678
- run: cargo test --release --target ${{ matrix.target }} --all-features
7779

7880
# Tests for the AES-NI backend
@@ -111,6 +113,7 @@ jobs:
111113
- run: cargo test --release --target ${{ matrix.target }}
112114
- run: cargo test --release --target ${{ matrix.target }} --features compact
113115
- run: cargo test --release --target ${{ matrix.target }} --features ctr
116+
- run: cargo test --release --target ${{ matrix.target }} --features force-soft
114117
- run: cargo test --release --target ${{ matrix.target }} --all-features
115118

116119
# Cross-compiled tests
@@ -144,4 +147,5 @@ jobs:
144147
- run: cross test --release --target ${{ matrix.target }}
145148
- run: cross test --release --target ${{ matrix.target }} --features compact
146149
- run: cross test --release --target ${{ matrix.target }} --features ctr
147-
- run: cross test --release --target ${{ matrix.target }} --features compact,ctr
150+
- run: cross test --release --target ${{ matrix.target }} --features force-soft
151+
- run: cross test --release --target ${{ matrix.target }} --all-features

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

aes/Cargo.toml

+6-2
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@ opaque-debug = "0.3"
2323
[dev-dependencies]
2424
cipher = { version = "=0.3.0-pre", features = ["dev"] }
2525

26+
[target.'cfg(any(target_arch = "x86_64", target_arch = "x86"))'.dependencies]
27+
cpuid-bool = "0.2"
28+
2629
[features]
27-
compact = [] # Reduce code size at the cost of performance
30+
compact = [] # Reduce code size at the cost of slower performance
31+
force-soft = [] # Disable support for AES hardware intrinsics
2832

2933
[package.metadata.docs.rs]
30-
all-features = true
34+
features = ["ctr"]
3135
rustdoc-args = ["--cfg", "docsrs"]

aes/src/autodetect.rs

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
//! Autodetection support for hardware accelerated AES backends with fallback
2+
//! to the fixsliced "soft" implementation.
3+
4+
use crate::{Block, ParBlocks};
5+
use cipher::{
6+
consts::{U16, U24, U32, U8},
7+
generic_array::GenericArray,
8+
BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher,
9+
};
10+
11+
cpuid_bool::new!(aes_cpuid, "aes");
12+
13+
macro_rules! define_aes_impl {
14+
(
15+
$name:tt,
16+
$module:tt,
17+
$key_size:ty,
18+
$doc:expr
19+
) => {
20+
#[doc=$doc]
21+
#[derive(Clone)]
22+
pub struct $name {
23+
inner: $module::Inner,
24+
token: aes_cpuid::InitToken
25+
}
26+
27+
mod $module {
28+
#[derive(Copy, Clone)]
29+
pub(super) union Inner {
30+
pub(super) ni: crate::ni::$name,
31+
pub(super) soft: crate::soft::$name,
32+
}
33+
}
34+
35+
impl NewBlockCipher for $name {
36+
type KeySize = $key_size;
37+
38+
#[inline]
39+
fn new(key: &GenericArray<u8, $key_size>) -> Self {
40+
let (token, aesni_present) = aes_cpuid::init_get();
41+
42+
let inner = if aesni_present {
43+
$module::Inner { ni: crate::ni::$name::new(key) }
44+
} else {
45+
$module::Inner { soft: crate::soft::$name::new(key) }
46+
};
47+
48+
Self { inner, token }
49+
}
50+
}
51+
52+
impl BlockCipher for $name {
53+
type BlockSize = U16;
54+
type ParBlocks = U8;
55+
}
56+
57+
impl BlockEncrypt for $name {
58+
#[inline]
59+
fn encrypt_block(&self, block: &mut Block) {
60+
if self.token.get() {
61+
unsafe { self.inner.ni.encrypt_block(block) }
62+
} else {
63+
unsafe { self.inner.soft.encrypt_block(block) }
64+
}
65+
}
66+
67+
#[inline]
68+
fn encrypt_par_blocks(&self, blocks: &mut ParBlocks) {
69+
if self.token.get() {
70+
unsafe { self.inner.ni.encrypt_par_blocks(blocks) }
71+
} else {
72+
unsafe { self.inner.soft.encrypt_par_blocks(blocks) }
73+
}
74+
}
75+
}
76+
77+
impl BlockDecrypt for $name {
78+
#[inline]
79+
fn decrypt_block(&self, block: &mut Block) {
80+
if self.token.get() {
81+
unsafe { self.inner.ni.decrypt_block(block) }
82+
} else {
83+
unsafe { self.inner.soft.decrypt_block(block) }
84+
}
85+
}
86+
87+
#[inline]
88+
fn decrypt_par_blocks(&self, blocks: &mut ParBlocks) {
89+
if self.token.get() {
90+
unsafe { self.inner.ni.decrypt_par_blocks(blocks) }
91+
} else {
92+
unsafe { self.inner.soft.decrypt_par_blocks(blocks) }
93+
}
94+
}
95+
}
96+
97+
opaque_debug::implement!($name);
98+
}
99+
}
100+
101+
define_aes_impl!(Aes128, aes128, U16, "AES-128 block cipher instance");
102+
define_aes_impl!(Aes192, aes192, U24, "AES-192 block cipher instance");
103+
define_aes_impl!(Aes256, aes256, U32, "AES-256 block cipher instance");
104+
105+
#[cfg(feature = "ctr")]
106+
pub(crate) mod ctr {
107+
use super::{Aes128, Aes192, Aes256};
108+
use cipher::{
109+
block::BlockCipher,
110+
generic_array::GenericArray,
111+
stream::{
112+
FromBlockCipher, LoopError, OverflowError, SeekNum, SyncStreamCipher,
113+
SyncStreamCipherSeek,
114+
},
115+
};
116+
117+
cpuid_bool::new!(aes_ssse3_cpuid, "aes", "ssse3");
118+
119+
macro_rules! define_aes_ctr_impl {
120+
(
121+
$name:tt,
122+
$cipher:ident,
123+
$module:tt,
124+
$doc:expr
125+
) => {
126+
#[doc=$doc]
127+
#[cfg_attr(docsrs, doc(cfg(feature = "ctr")))]
128+
pub struct $name {
129+
inner: $module::Inner,
130+
}
131+
132+
mod $module {
133+
#[allow(clippy::large_enum_variant)]
134+
pub(super) enum Inner {
135+
Ni(crate::ni::$name),
136+
Soft(crate::soft::$name),
137+
}
138+
}
139+
140+
impl FromBlockCipher for $name {
141+
type BlockCipher = $cipher;
142+
type NonceSize = <$cipher as BlockCipher>::BlockSize;
143+
144+
fn from_block_cipher(
145+
cipher: $cipher,
146+
nonce: &GenericArray<u8, Self::NonceSize>,
147+
) -> Self {
148+
let (_, aesni_present) = aes_ssse3_cpuid::init_get();
149+
150+
let inner = if aesni_present {
151+
$module::Inner::Ni(
152+
crate::ni::$name::from_block_cipher(
153+
unsafe { cipher.inner.ni },
154+
nonce
155+
)
156+
)
157+
} else {
158+
$module::Inner::Soft(
159+
crate::soft::$name::from_block_cipher(
160+
unsafe { cipher.inner.soft },
161+
nonce
162+
)
163+
)
164+
};
165+
166+
Self { inner }
167+
}
168+
}
169+
170+
impl SyncStreamCipher for $name {
171+
#[inline]
172+
fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> {
173+
match &mut self.inner {
174+
$module::Inner::Ni(aes) => aes.try_apply_keystream(data),
175+
$module::Inner::Soft(aes) => aes.try_apply_keystream(data)
176+
}
177+
}
178+
}
179+
180+
impl SyncStreamCipherSeek for $name {
181+
#[inline]
182+
fn try_current_pos<T: SeekNum>(&self) -> Result<T, OverflowError> {
183+
match &self.inner {
184+
$module::Inner::Ni(aes) => aes.try_current_pos(),
185+
$module::Inner::Soft(aes) => aes.try_current_pos()
186+
}
187+
}
188+
189+
#[inline]
190+
fn try_seek<T: SeekNum>(&mut self, pos: T) -> Result<(), LoopError> {
191+
match &mut self.inner {
192+
$module::Inner::Ni(aes) => aes.try_seek(pos),
193+
$module::Inner::Soft(aes) => aes.try_seek(pos)
194+
}
195+
}
196+
}
197+
198+
opaque_debug::implement!($name);
199+
}
200+
}
201+
202+
define_aes_ctr_impl!(Aes128Ctr, Aes128, aes128ctr, "AES-128 in CTR mode");
203+
define_aes_ctr_impl!(Aes192Ctr, Aes192, aes192ctr, "AES-192 in CTR mode");
204+
define_aes_ctr_impl!(Aes256Ctr, Aes256, aes256ctr, "AES-256 in CTR mode");
205+
}

aes/src/lib.rs

+7-11
Original file line numberDiff line numberDiff line change
@@ -63,21 +63,17 @@ use cfg_if::cfg_if;
6363

6464
cfg_if! {
6565
if #[cfg(all(
66-
target_feature = "aes",
67-
target_feature = "sse2",
6866
any(target_arch = "x86_64", target_arch = "x86"),
67+
not(feature = "force-soft")
6968
))] {
69+
mod autodetect;
7070
mod ni;
71-
pub use ni::{Aes128, Aes192, Aes256};
71+
mod soft;
72+
73+
pub use autodetect::{Aes128, Aes192, Aes256};
7274

7375
#[cfg(feature = "ctr")]
74-
cfg_if! {
75-
if #[cfg(target_feature = "ssse3")] {
76-
pub use ni::{Aes128Ctr, Aes192Ctr, Aes256Ctr};
77-
} else {
78-
compile_error!("Please enable the +ssse3 target feature to use `ctr` with AES-NI")
79-
}
80-
}
76+
pub use autodetect::ctr::{Aes128Ctr, Aes192Ctr, Aes256Ctr};
8177
} else {
8278
mod soft;
8379
pub use soft::{Aes128, Aes192, Aes256};
@@ -87,7 +83,7 @@ cfg_if! {
8783
}
8884
}
8985

90-
pub use cipher::{self, BlockCipher, NewBlockCipher};
86+
pub use cipher::{self, BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher};
9187

9288
/// 128-bit AES block
9389
pub type Block = cipher::generic_array::GenericArray<u8, cipher::consts::U16>;

aes/src/ni/aes128.rs

+4
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ impl BlockDecrypt for Aes128 {
112112
// Safety: `loadu` and `storeu` support unaligned access
113113
#[allow(clippy::cast_ptr_alignment)]
114114
let mut b = _mm_loadu_si128(block.as_ptr() as *const __m128i);
115+
115116
b = _mm_xor_si128(b, keys[10]);
116117
b = _mm_aesdec_si128(b, keys[9]);
117118
b = _mm_aesdec_si128(b, keys[8]);
@@ -123,6 +124,9 @@ impl BlockDecrypt for Aes128 {
123124
b = _mm_aesdec_si128(b, keys[2]);
124125
b = _mm_aesdec_si128(b, keys[1]);
125126
b = _mm_aesdeclast_si128(b, keys[0]);
127+
128+
// Safety: `loadu` and `storeu` support unaligned access
129+
#[allow(clippy::cast_ptr_alignment)]
126130
_mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, b);
127131
}
128132

aes/src/ni/aes192.rs

+4
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ impl BlockDecrypt for Aes192 {
114114
// Safety: `loadu` and `storeu` support unaligned access
115115
#[allow(clippy::cast_ptr_alignment)]
116116
let mut b = _mm_loadu_si128(block.as_ptr() as *const __m128i);
117+
117118
b = _mm_xor_si128(b, keys[12]);
118119
b = _mm_aesdec_si128(b, keys[11]);
119120
b = _mm_aesdec_si128(b, keys[10]);
@@ -127,6 +128,9 @@ impl BlockDecrypt for Aes192 {
127128
b = _mm_aesdec_si128(b, keys[2]);
128129
b = _mm_aesdec_si128(b, keys[1]);
129130
b = _mm_aesdeclast_si128(b, keys[0]);
131+
132+
// Safety: `loadu` and `storeu` support unaligned access
133+
#[allow(clippy::cast_ptr_alignment)]
130134
_mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, b);
131135
}
132136

aes/src/ni/aes256.rs

+4
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ impl BlockDecrypt for Aes256 {
118118
// Safety: `loadu` and `storeu` support unaligned access
119119
#[allow(clippy::cast_ptr_alignment)]
120120
let mut b = _mm_loadu_si128(block.as_ptr() as *const __m128i);
121+
121122
b = _mm_xor_si128(b, keys[14]);
122123
b = _mm_aesdec_si128(b, keys[13]);
123124
b = _mm_aesdec_si128(b, keys[12]);
@@ -133,6 +134,9 @@ impl BlockDecrypt for Aes256 {
133134
b = _mm_aesdec_si128(b, keys[2]);
134135
b = _mm_aesdec_si128(b, keys[1]);
135136
b = _mm_aesdeclast_si128(b, keys[0]);
137+
138+
// Safety: `loadu` and `storeu` support unaligned access
139+
#[allow(clippy::cast_ptr_alignment)]
136140
_mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, b);
137141
}
138142

0 commit comments

Comments
 (0)