-
Notifications
You must be signed in to change notification settings - Fork 175
Add support for the AES256-GCM AEAD construction #418
Conversation
Pull Request Test Coverage Report for Build 1383
💛 - Coveralls |
bfc5b2d
to
eebccb6
Compare
Hi, checking in on this. Is there any reason to not merge? |
Which means I don't have access to aes256-gcm even if I have aes&pclmulqdq |
Rust won't use specialized instructions in test profile builds unless you either target a specific CPU or test a release profile. Those instructions exist in Westmere and beyond (~2009) so support on x86 is near universal. |
It would be useful to add these env vars into CI to build your code. Also we can add rustflags to package metadata(https://docs.rs/about) so that docs.rs could render aes module in the docs instead of Also I am curious whether we should add docs into README how to turn on aes. Should we point that this module is turned on only on AES-NI ISA. |
Cargo.toml
Outdated
|
||
[package.metadata.docs.rs] | ||
default-target = "x86_64-unknown-linux-gnu" | ||
rustc-args = ["-C target-feature=+aes", "-C target-feature=+pclmulqdq"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't -C target-cpu=native be enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To your earlier point doing it this way ensures that the docs will always be “expanded” regardless of what platform they’re built on. I could arbitrarily pick a target with AES/CMUL support but doing it this way documents intent. There’s an open discussion about how rust docs should handle conditional compilation (feature detection or otherwise) but this seems like the lesser of evils for now.
LGTM. Does anybody want to review this PR? |
README.md
Outdated
The AES AEAD variant `crypto_aead_aes256gcm` requires hardware support for the | ||
`AES` and `CLMUL` instruction set extensions to x86; you can read why that's the | ||
case | ||
[here](https://libsodium.gitbook.io/doc/secret-key_cryptography/aead/aes-256-gcm#limitations). These instruction set extensions were first made |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe prefer the link that includes the navigation for context, e.g. https://doc.libsodium.org/secret-key_cryptography/aead/aes-256-gcm#limitations
README.md
Outdated
`RUSTFLAGS` set to include one of the following option sets: | ||
|
||
* `-C target-cpu=native` if you're building on a box with `AES` and `CLMUL` | ||
* `-C target-cpu=[target]` where `[target]` is `rustup` target with `AES` and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this adds up, a rustup
target, i.e. something listed by rustup target list
, is a host tuple that is passed via --target
to rustc whereas the possible options for the target CPU are given by rustc --print target-cpus
. (It is also not a target in itself but just an alias for a collection of target features AFAIK.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, I didn't document that correctly. The new PR clarifies.
src/crypto/aead/aes256gcm.rs
Outdated
//! instruction (Westmere and beyond). | ||
|
||
#[cfg(any( | ||
doc, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need the any(doc, ...)
arm here now that the flags for docs.rs
are adjusted? (Running cargo doc
should produce whatever your compiler flags will produce for the build, shouldn't it?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately we do still need it until https://github.com/rust-lang/cargo/issues/7677 is resolved.
The docs won't build without it.
README.md
Outdated
x86 target does not. | ||
|
||
If you need to use `crypto_aead_aes256gcm` you must build/test sodiumoxide with | ||
`RUSTFLAGS` set to include one of the following option sets: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest to avoid advocating use of the RUSTFLAGS
environment variable here as in a production setting this should often be done via Cargo configuration instead. Maybe talk about rustc
flags in a generic manner?
@klittlepage One thing I do not fully understand is the interaction of rustc target features with how libsodium is compiled/does implementation selection here, i.e. doesn't libsodium do CPU feature detection at runtime, c.f. https://github.com/jedisct1/libsodium/blob/master/src/libsodium/sodium/runtime.c, and hence could have a hardware-backed AES implementation available even if the Rust code was not compiled to take advantage of these instructions? Meaning that I am not really convinced that hiding that API based on the compile-time target features is helpful as they have nothing to do with whether these instructions will be available at runtime and these bindings should probably be as flexible as the underlying implementation of libsodium is. |
README.md
Outdated
on suitable hardware; attempting to run it on a processor that | ||
*does not support* `AES` and `CLMUL` will result in a runtime `SIGILL` | ||
(illegal instruction). You can test for hardware support at runtime using | ||
`aead::aes256gcm::is_available` (make sure that the rustoxide `init` method is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is "rustoxide" maybe meant to be "sodiumoxide"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops...
Yes, that's true. How the rust side of things is compiled has no impact on the compilation of libsodium and the libsodium API exports AES unconditionally.
Yes, and if the goal is to mirror libsodium that makes sense. However, there are plenty of examples where sodiumoxide improves on the safety of the libsodium API e.g. the use of strongly typed keys, nonces, and tags in AEAD. These approaches aren't as "flexible" as the underlying implementation of libsodium - and that's a good thing. Putting AES functionality behind a compile time feature check limits the possibility of a foot gun (using AES on an architecture that doesn't support it) so I'm in favor of making the behavior opt-in. I added a feature flag "aes" for users who have read the caveats and want to force an unconditional build. |
515a2af
to
d1e95fd
Compare
I don't think testing cpu features at compile time is a good idea, instead this should be done at runtime. Binary distribution is very common and the cpu of the build system is almost guaranteed to be different from the system that's actually going to run it. A binary compiled on a cpu with support for aes instructions should be able to run on cpus without this instruction and vice versa. I've accidentally shipped broken updates to users in the past because the build system was feature-tested and the final binary assumed the instructions would be present without further runtime checks, causing SIGILL for some users. Not testing this at compile-time is also important for reproducible builds, which relies on the build to be deterministic to allow independent verification of the binary on a system that isn't 100% identical. I'm surprised libsodium doesn't have a fallback, does anybody know why? @jedisct1 |
The assumption that a runtime check resolves that issue hinges on correct API usage. Libsodium doesn't gate the internals of AES with a runtime feature check - it relies on the caller calling Here are the compile and run scenarios for conditional compilation:
Without conditional compilation 3) turns into 4) and is much more likely to happen on accident. At worst 2) forces the user to read the docs. That being viewed as a bad thing really doesn't bode well for correct use of
That's why - as @adamreichold noted - using rust's configuration system is the way to go. Reproducibility is always modulo the target/config. Said differently it's about purity - maintaining a consistent mapping of inputs to outputs. It's not about creating the same binary irrespective of the inputs. Your point that you still want to allow for verification of a binary on a non-identical system is valid and completely compatible with this model. Rust has a strong story around reproducibility specifically because the configuration system works to capture inputs that might otherwise be implicit or forgotten; we might as well use that system to build safer APIs.
AES in GCM is notoriously prone to timing side-channels in the computation of the Galois hash unless you have access to hardware primitives for constant time carry-less multiplication. There are software mitigants that are slower and more complex; the latter point is the real concern. |
Compile-time checks don't solve this. I'd argue there's (unfortunately) no way to expose Your argument dismisses the fact that binary distributions like Debian and Arch Linux exist, "the user" might not be the person writing the code, and the person compiling it might be yet another person. In the opensource world the binaries are deployed on a much broader set of cpus than in controlled, proprietary deployments, there is no concept of a "target" more specific than amd64/x86_64. It's also going to cause reproducible build issues for real packages that depend on sodiumoxide today and are currently considered reproducible according to reproducible-builds.org standards, the build machine cpu is not considered a build input. My best guess is having |
I am very much on board with these bindings improving the safety beyond what libsodium itself offers, I just do not really see the target features helping here as:
Considering that the person building the software is often not the same person that is running the software, the outcome of an incorrect assumption embedded in the build config and an incorrect/missing runtime check is the same: the program will crash.
Did we actually measure the overhead of e.g. using |
@klittlepage I think we can have our cake and it eat it too: Instead of exposing free functions as an API, we could add a unit struct like Of course this could imply avoiding the EDIT: Following the signature of
should work. |
Minor nit on the trait impls of the unit struct, otherwise this looks good to me now! |
If folks are open to AES having a slightly different API, this is by far the best way to do it. My earlier commit reflected an assumption that the API needed to remain consistent. I've updated the implementation and docs. @kpcyrd to your earlier point that "Compile-time checks don't solve this" - just to clarify - I never said that they did. The argument for using them was simply that it requires someone (either the person building the package) or consuming the API to explicitly opt in to the behavior having read the docs. Your point that the value of that is lessened when different people build/package/consume the library is completely true, so the AES specific API really does seem like the best solution. I ran a Criterion test across all of the AEAD primitives (violin plot attached). AES enjoys a slight performance lead at every payload size and a more significant reduction in variance; there's not a compelling performance reason to favor it over the stream ciphers. |
@kpcyrd is anything else needed on my end? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work!
bors: r+ |
418: Add support for the AES256-GCM AEAD construction r=kpp a=klittlepage This commit adds support for AES256-GCM. NB: libsodium limits its support of AES256-GCM to x86/x86-64 processors with the AES-NI instruction set. Compile time feature flags are used to hide GCM functionality for targets lacking the requisite support. In the interest of maintaining parity with the libsodium API an `is_available` function mapping to `crypto_aead_aes256gcm_is_available` is unconditionally exported but shouldn't be required given the compile-time check. Signed-off-by: Kelly Littlepage <kelly@onechronos.com> Co-authored-by: Kelly Littlepage <kelly@onechronos.com> Co-authored-by: Kelly Littlepage <klittlepage@users.noreply.github.com>
@klittlepage rebase plz |
c7c0bf6
to
a0fee78
Compare
This commit adds support for AES256-GCM. NB: libsodium limits its support of AES256-GCM to x86/x86-64 processors with the AES-NI instruction set. Compile time feature flags are used to hide GCM functionality for targets lacking the requisite support. In the interest of maintaining parity with the libsodium API an `is_available` function mapping to `crypto_aead_aes256gcm_is_available` is unconditionally exported but shouldn't be required given the compile-time check. Signed-off-by: Kelly Littlepage <kelly@onechronos.com>
a0fee78
to
3a15541
Compare
@kpp done - thank you! |
bors: r+ |
This commit adds support for AES256-GCM. NB: libsodium limits its support
of AES256-GCM to x86/x86-64 processors with the AES-NI instruction set.
Compile time feature flags are used to hide GCM functionality for targets
lacking the requisite support. In the interest of maintaining parity with
the libsodium API an
is_available
function mapping tocrypto_aead_aes256gcm_is_available
is unconditionally exported butshouldn't be required given the compile-time check.
Signed-off-by: Kelly Littlepage kelly@onechronos.com