Skip to content

Commit d8587e8

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 d8587e8

File tree

6 files changed

+230
-15
lines changed

6 files changed

+230
-15
lines changed

.github/workflows/aes.yml

Lines changed: 6 additions & 2 deletions
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: cargo test --release --target ${{ matrix.target }} --features force-soft
151+
- run: cargo test --release --target ${{ matrix.target }} --all-features

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

aes/Cargo.toml

Lines changed: 5 additions & 1 deletion
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]
2730
compact = [] # Reduce code size at the cost of 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

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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+
pub(super) enum Inner {
134+
Ni(crate::ni::$name),
135+
Soft(crate::soft::$name),
136+
}
137+
}
138+
139+
impl FromBlockCipher for $name {
140+
type BlockCipher = $cipher;
141+
type NonceSize = <$cipher as BlockCipher>::BlockSize;
142+
143+
fn from_block_cipher(
144+
cipher: $cipher,
145+
nonce: &GenericArray<u8, Self::NonceSize>,
146+
) -> Self {
147+
let (_, aesni_present) = aes_ssse3_cpuid::init_get();
148+
149+
let inner = if aesni_present {
150+
$module::Inner::Ni(
151+
crate::ni::$name::from_block_cipher(
152+
unsafe { cipher.inner.ni },
153+
nonce
154+
)
155+
)
156+
} else {
157+
$module::Inner::Soft(
158+
crate::soft::$name::from_block_cipher(
159+
unsafe { cipher.inner.soft },
160+
nonce
161+
)
162+
)
163+
};
164+
165+
Self { inner }
166+
}
167+
}
168+
169+
impl SyncStreamCipher for $name {
170+
#[inline]
171+
fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> {
172+
match &mut self.inner {
173+
$module::Inner::Ni(aes) => aes.try_apply_keystream(data),
174+
$module::Inner::Soft(aes) => aes.try_apply_keystream(data)
175+
}
176+
}
177+
}
178+
179+
impl SyncStreamCipherSeek for $name {
180+
#[inline]
181+
fn try_current_pos<T: SeekNum>(&self) -> Result<T, OverflowError> {
182+
match &self.inner {
183+
$module::Inner::Ni(aes) => aes.try_current_pos(),
184+
$module::Inner::Soft(aes) => aes.try_current_pos()
185+
}
186+
}
187+
188+
#[inline]
189+
fn try_seek<T: SeekNum>(&mut self, pos: T) -> Result<(), LoopError> {
190+
match &mut self.inner {
191+
$module::Inner::Ni(aes) => aes.try_seek(pos),
192+
$module::Inner::Soft(aes) => aes.try_seek(pos)
193+
}
194+
}
195+
}
196+
197+
opaque_debug::implement!($name);
198+
}
199+
}
200+
201+
define_aes_ctr_impl!(Aes128Ctr, Aes128, aes128ctr, "AES-128 in CTR mode");
202+
define_aes_ctr_impl!(Aes192Ctr, Aes192, aes192ctr, "AES-192 in CTR mode");
203+
define_aes_ctr_impl!(Aes256Ctr, Aes256, aes256ctr, "AES-256 in CTR mode");
204+
}

aes/src/lib.rs

Lines changed: 7 additions & 11 deletions
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/soft/impls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ macro_rules! define_aes_impl {
2020
$doc:expr
2121
) => {
2222
#[doc=$doc]
23-
#[derive(Clone)]
23+
#[derive(Copy, Clone)]
2424
pub struct $name {
2525
keys: $fixslice_keys,
2626
}

0 commit comments

Comments
 (0)