initial setup for bssl crate with hmac and sha2 bindings - update rust folder to split into `bssl` and `bssl-sys` - add initial bindings for hmac and a subset of sha2 Change-Id: I09e0e778c1590de6818a49e19529ceb011e4d9f6 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/57285 Reviewed-by: Bob Beck <bbe@google.com> Commit-Queue: Bob Beck <bbe@google.com> Reviewed-by: Adam Langley <agl@google.com>
diff --git a/.gitignore b/.gitignore index 68dca6c..ac0ede8 100644 --- a/.gitignore +++ b/.gitignore
@@ -26,3 +26,4 @@ util/bot/sde-win32 util/bot/sde-win32.tar.xz util/bot/win_toolchain.json +target/
diff --git a/rust/CMakeLists.txt b/rust/CMakeLists.txt index 422905f..fcdf3db 100644 --- a/rust/CMakeLists.txt +++ b/rust/CMakeLists.txt
@@ -1,39 +1 @@ -# Additional interop for things like macros and inlined functions. -add_library(rust_wrapper STATIC rust_wrapper.c) -target_link_libraries(rust_wrapper crypto) - -# Generate architecture-specific wrappers. -set(WRAPPER_TARGET ${CMAKE_CURRENT_BINARY_DIR}/src/wrapper_${RUST_BINDINGS}.rs) -set(COMMAND ${BINDGEN_EXECUTABLE} "wrapper.h" - -o ${WRAPPER_TARGET} - --no-derive-default - --enable-function-attribute-detection - --use-core - --size_t-is-usize - --default-macro-constant-type="signed" - --rustified-enum="point_conversion_form_t" - --allowlist-file=".*/include/openssl/.*\\.h" - --allowlist-file=".*/rust_wrapper\\.h" - -- # these are LLVM arg passthroughs - -I../include - # https://doc.rust-lang.org/nightly/rustc/platform-support.html - --target=${RUST_BINDINGS}) - -set(INCLUDES "include!(\"wrapper_${RUST_BINDINGS}.rs\");\n") - -add_custom_target( - bindgen_rust_${RUST_BINDINGS} - ALL - ${COMMAND} - BYPRODUCTS ${WRAPPER_TARGET} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} -) - -# move files into build directory -configure_file("src/lib.rs" "src/lib.rs") - -if(NOT BUILD_SHARED_LIBS) - configure_file("build.rs" "build.rs" COPYONLY) -endif() - -configure_file("Cargo.toml" "Cargo.toml" COPYONLY) +add_subdirectory(bssl-sys) \ No newline at end of file
diff --git a/rust/bssl-crypto/Cargo.lock b/rust/bssl-crypto/Cargo.lock new file mode 100644 index 0000000..ed7fe34 --- /dev/null +++ b/rust/bssl-crypto/Cargo.lock
@@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bssl-crypto" +version = "0.1.0" +dependencies = [ + "bssl-sys", +] + +[[package]] +name = "bssl-sys" +version = "0.1.0"
diff --git a/rust/bssl-crypto/Cargo.toml b/rust/bssl-crypto/Cargo.toml new file mode 100644 index 0000000..57a6440 --- /dev/null +++ b/rust/bssl-crypto/Cargo.toml
@@ -0,0 +1,11 @@ +[package] +name = "bssl-crypto" +version = "0.1.0" +edition = "2021" +publish = false +license = "MIT" + +[dependencies] +# the crate will need to be generated at this path by running this command at root +# `mkdir build && cd build && cmake -G Ninja .. -DRUST_BINDINGS="$(gcc -dumpmachine)" && ninja` +bssl-sys = {path = "../../build/rust/bssl-sys"} \ No newline at end of file
diff --git a/rust/bssl-crypto/README.md b/rust/bssl-crypto/README.md new file mode 100644 index 0000000..9e10fad --- /dev/null +++ b/rust/bssl-crypto/README.md
@@ -0,0 +1,14 @@ +bssl-crypto +============ + +rust bindings to boringssl which wrap bssl-sys, a low level autogenerated binding + +Before using this crate, first generate the bssl-sys bindings by running this command from the root of the repo: +``` +mkdir build && cd build && cmake -G Ninja .. -DRUST_BINDINGS="$(gcc -dumpmachine)" && ninja +``` + +Then to run all tests: +``` +cd rust/bssl-crypto && cargo clippy && cargo deny check && cargo test +``` \ No newline at end of file
diff --git a/rust/bssl-crypto/deny.toml b/rust/bssl-crypto/deny.toml new file mode 100644 index 0000000..d398485 --- /dev/null +++ b/rust/bssl-crypto/deny.toml
@@ -0,0 +1,212 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #{ triple = "x86_64-unknown-linux-musl" }, + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory database is cloned/fetched into +db-path = "~/.cargo/advisory-db" +# The url(s) of the advisory databases to use +db-urls = ["https://github.com/rustsec/advisory-db"] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", +] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# The lint level for crates which do not have a detectable license +unlicensed = "deny" +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "Unicode-DFS-2016", +] +# List of explicitly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +deny = [ + #"Nokia", +] +# Lint level for licenses considered copyleft +copyleft = "warn" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "neither" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The name of the crate the clarification applies to +#name = "ring" +# The optional version constraint for the crate +#version = "*" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + #{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, + # + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, +] +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite +skip-tree = [ + #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# 1 or more github.com organizations to allow git sources for +#github = [""] +# 1 or more gitlab.com organizations to allow git sources for +#gitlab = [""] +# 1 or more bitbucket.org organizations to allow git sources for +#bitbucket = [""]
diff --git a/rust/bssl-crypto/src/digest.rs b/rust/bssl-crypto/src/digest.rs new file mode 100644 index 0000000..cecdfdf --- /dev/null +++ b/rust/bssl-crypto/src/digest.rs
@@ -0,0 +1,80 @@ +/* Copyright (c) 2023, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +use crate::ForeignTypeRef; + +/// The BoringSSL implemented SHA-256 digest algorithm. +#[derive(Clone)] +pub struct Sha256 {} + +/// The BoringSSL implemented SHA-512 digest algorithm. +#[derive(Clone)] +pub struct Sha512 {} + +/// A reference to an [`Md`], which abstracts the details of a specific hash function allowing code +/// to deal with the concept of a "hash function" without needing to know exactly which hash function +/// it is. +pub(crate) struct MdRef; + +unsafe impl ForeignTypeRef for MdRef { + type CType = bssl_sys::EVP_MD; +} + +/// Used internally to get a BoringSSL internal MD +pub(crate) trait Md { + /// gets a reference to a message digest algorithm to be used by the hkdf implementation + fn get_md() -> &'static MdRef; +} + +impl Md for Sha256 { + fn get_md() -> &'static MdRef { + // Safety: + // - this always returns a valid pointer to an EVP_MD + unsafe { MdRef::from_ptr(bssl_sys::EVP_sha256() as *mut _) } + } +} + +impl Md for Sha512 { + fn get_md() -> &'static MdRef { + // Safety: + // - this always returns a valid pointer to an EVP_MD + unsafe { MdRef::from_ptr(bssl_sys::EVP_sha512() as *mut _) } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_sha256_c_type() { + unsafe { + assert_eq!( + MdRef::from_ptr(bssl_sys::EVP_sha256() as *mut _).as_ptr(), + bssl_sys::EVP_sha256() as *mut _ + ) + } + } + + #[test] + fn test_sha512_c_type() { + unsafe { + assert_eq!( + MdRef::from_ptr(bssl_sys::EVP_sha512() as *mut _).as_ptr(), + bssl_sys::EVP_sha512() as *mut _ + ) + } + } +}
diff --git a/rust/bssl-crypto/src/hmac.rs b/rust/bssl-crypto/src/hmac.rs new file mode 100644 index 0000000..829816c --- /dev/null +++ b/rust/bssl-crypto/src/hmac.rs
@@ -0,0 +1,391 @@ +/* Copyright (c) 2023, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +use crate::{ + digest::{Md, Sha256, Sha512}, + CSlice, ForeignTypeRef as _, PanicResultHandler, +}; +use core::{ + ffi::{c_uint, c_void}, + marker::PhantomData, + ptr, +}; + +/// Computes the HMAC-SHA-256 of `data` as a one-shot operation. +/// +/// Calculates the HMAC of data, using the given `key` and returns the result. +/// It returns the computed hmac or `InvalidLength` of the input key size is too large. +/// Can panic if memory allocation fails in the underlying BoringSSL code. +pub fn hmac_sha_256(key: &[u8], data: &[u8]) -> Result<[u8; 32], InvalidLength> { + hmac::<32, Sha256>(key, data) +} + +/// Computes the HMAC-SHA-512 of `data` as a one-shot operation. +/// +/// Calculates the HMAC of data, using the given `key` and returns the result. +/// It returns the computed hmac or `InvalidLength` of the input key size is too large. +/// Can panic if memory allocation fails in the underlying BoringSSL code. +pub fn hmac_sha_512(key: &[u8], data: &[u8]) -> Result<[u8; 64], InvalidLength> { + hmac::<64, Sha512>(key, data) +} + +/// The BoringSSL HMAC-SHA-256 implementation. The operations may panic if memory allocation fails +/// in BoringSSL. +pub struct HmacSha256(Hmac<32, Sha256>); + +impl HmacSha256 { + /// Create a new hmac from a fixed size key. + pub fn new(key: [u8; 32]) -> Self { + Self(Hmac::new(key)) + } + + /// Create new hmac value from variable size key. + pub fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> { + Hmac::new_from_slice(key).map(Self) + } + + /// Update state using the provided data. + pub fn update(&mut self, data: &[u8]) { + self.0.update(data) + } + + /// Obtain the hmac computation consuming the hmac instance. + pub fn finalize(self) -> [u8; 32] { + self.0.finalize() + } + + /// Check that the tag value is correct for the processed input. + pub fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> { + self.0.verify_slice(tag) + } + + /// Check that the tag value is correct for the processed input. + pub fn verify(self, tag: [u8; 32]) -> Result<(), MacError> { + self.0.verify(tag) + } + + /// Check truncated tag correctness using left side bytes of the calculated tag. + pub fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> { + self.0.verify_truncated_left(tag) + } +} + +/// The BoringSSL HMAC-SHA-512 implementation. The operations may panic if memory allocation fails +/// in BoringSSL. +pub struct HmacSha512(Hmac<64, Sha512>); + +impl HmacSha512 { + /// Create a new hmac from a fixed size key. + pub fn new(key: [u8; 64]) -> Self { + Self(Hmac::new(key)) + } + + /// Create new hmac value from variable size key. + pub fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> { + Hmac::new_from_slice(key).map(Self) + } + + /// Update state using the provided data. + pub fn update(&mut self, data: &[u8]) { + self.0.update(data) + } + + /// Obtain the hmac computation consuming the hmac instance. + pub fn finalize(self) -> [u8; 64] { + self.0.finalize() + } + + /// Check that the tag value is correct for the processed input. + pub fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> { + self.0.verify_slice(tag) + } + + /// Check that the tag value is correct for the processed input. + pub fn verify(self, tag: [u8; 64]) -> Result<(), MacError> { + self.0.verify(tag) + } + + /// Check truncated tag correctness using left side bytes of the calculated tag. + pub fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> { + self.0.verify_truncated_left(tag) + } +} + +/// Error type for when the provided key material length is invalid. +#[derive(Debug)] +pub struct InvalidLength; + +/// Error type for when the output of the hmac operation is not equal to the expected value. +#[derive(Debug)] +pub struct MacError; + +/// Private generically implemented function for computing hmac as a oneshot operation. +/// This should only be exposed publicly by types with the correct output size `N` which corresponds +/// to the output size of the provided generic hash function. Ideally `N` would just come from `M`, +/// but this is not possible until the Rust language can support the `min_const_generics` feature. +/// Until then we will have to pass both separately: https://github.com/rust-lang/rust/issues/60551 +#[inline] +fn hmac<const N: usize, M: Md>(key: &[u8], data: &[u8]) -> Result<[u8; N], InvalidLength> { + let mut out = [0_u8; N]; + let mut size: c_uint = 0; + + // Safety: + // - buf always contains N bytes of space + // - If NULL is returned on error we panic immediately + unsafe { + bssl_sys::HMAC( + M::get_md().as_ptr(), + CSlice::from(key).as_ptr(), + key.len(), + CSlice::from(data).as_ptr(), + data.len(), + out.as_mut_ptr(), + &mut size as *mut c_uint, + ) + } + .panic_if_error(); + + Ok(out) +} + +/// Private generically implemented hmac instance given a generic hash function and a length `N`, +/// where `N` is the output size of the hash function. This should only be exposed publicly by +/// wrapper types with the correct output size `N` which corresponds to the output size of the +/// provided generic hash function. Ideally `N` would just come from `M`, but this is not possible +/// until the Rust language can support the `min_const_generics` feature. Until then we will have to +/// pass both separately: https://github.com/rust-lang/rust/issues/60551 +struct Hmac<const N: usize, M: Md> { + ctx: *mut bssl_sys::HMAC_CTX, + _marker: PhantomData<M>, +} + +impl<const N: usize, M: Md> Hmac<N, M> { + /// Infallible HMAC creation from a fixed length key. + fn new(key: [u8; N]) -> Self { + #[allow(clippy::expect_used)] + Self::new_from_slice(&key).expect("output length of hash is always a valid hmac key size") + } + + /// Create new hmac value from variable size key. Panics on allocation failure + /// returns InvalidLength if the key length is greater than the max message digest block size. + fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> { + (validate_key_len(key.len())) + .then(|| { + // Safety: + // - HMAC_CTX_new panics if allocation fails + let ctx = unsafe { bssl_sys::HMAC_CTX_new() }; + ctx.panic_if_error(); + + // Safety: + // - HMAC_Init_ex must be called with a context previously created with HMAC_CTX_new, + // which is the line above. + // - HMAC_Init_ex may return an error if key is null but the md is different from + // before. This is avoided here since key is guaranteed to be non-null. + // - HMAC_Init_ex returns 0 on allocation failure in which case we panic + unsafe { + bssl_sys::HMAC_Init_ex( + ctx, + CSlice::from(key).as_ptr() as *const c_void, + key.len(), + M::get_md().as_ptr(), + ptr::null_mut(), + ) + } + .panic_if_error(); + + Self { + ctx, + _marker: Default::default(), + } + }) + .ok_or(InvalidLength) + } + + /// Update state using the provided data, can be called repeatedly. + fn update(&mut self, data: &[u8]) { + unsafe { + // Safety: HMAC_Update will always return 1, in case it doesnt we panic + bssl_sys::HMAC_Update(self.ctx, data.as_ptr(), data.len()) + } + .panic_if_error() + } + + /// Obtain the hmac computation consuming the hmac instance. + fn finalize(self) -> [u8; N] { + let mut buf = [0_u8; N]; + let mut size: c_uint = 0; + // Safety: + // - hmac has a fixed size output of N which will never exceed the length of an N + // length array + // - on allocation failure we panic + unsafe { bssl_sys::HMAC_Final(self.ctx, buf.as_mut_ptr(), &mut size as *mut c_uint) } + .panic_if_error(); + buf + } + + /// Check that the tag value is correct for the processed input. + fn verify(self, tag: [u8; N]) -> Result<(), MacError> { + self.verify_slice(&tag) + } + + /// Check truncated tag correctness using all bytes + /// of calculated tag. + /// + /// Returns `Error` if `tag` is not valid or not equal in length + /// to MAC's output. + fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> { + tag.len().eq(&N).then_some(()).ok_or(MacError)?; + self.verify_truncated_left(tag) + } + + /// Check truncated tag correctness using left side bytes + /// (i.e. `tag[..n]`) of calculated tag. + /// + /// Returns `Error` if `tag` is not valid or empty. + fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> { + let len = tag.len(); + if len == 0 || len > N { + return Err(MacError); + } + + let result = &self.finalize()[..len]; + + // Safety: + // - if a != b is undefined, it simply returns a non-zero result + unsafe { + bssl_sys::CRYPTO_memcmp( + CSlice::from(result).as_ptr() as *const c_void, + CSlice::from(tag).as_ptr() as *const c_void, + result.len(), + ) + } + .eq(&0) + .then_some(()) + .ok_or(MacError) + } +} + +impl<const N: usize, M: Md> Drop for Hmac<N, M> { + fn drop(&mut self) { + unsafe { bssl_sys::HMAC_CTX_free(self.ctx) } + } +} + +// make sure key len is within a valid range +fn validate_key_len(len: usize) -> bool { + if len > bssl_sys::EVP_MAX_MD_BLOCK_SIZE as usize { + return false; + } + true +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hmac_sha256_test() { + let expected_hmac = [ + 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, + 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, + 0x2e, 0x32, 0xcf, 0xf7, + ]; + + let key: [u8; 20] = [0x0b; 20]; + let data = b"Hi There"; + + let mut hmac = HmacSha256::new_from_slice(&key).expect("length is valid"); + hmac.update(data); + let hmac_result: [u8; 32] = hmac.finalize(); + + // let hmac_result = + // hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac"); + assert_eq!(&hmac_result, &expected_hmac); + } + + #[test] + fn hmac_sha256_fixed_size_key_test() { + let expected_hmac = [ + 0x19, 0x8a, 0x60, 0x7e, 0xb4, 0x4b, 0xfb, 0xc6, 0x99, 0x3, 0xa0, 0xf1, 0xcf, 0x2b, + 0xbd, 0xc5, 0xba, 0xa, 0xa3, 0xf3, 0xd9, 0xae, 0x3c, 0x1c, 0x7a, 0x3b, 0x16, 0x96, + 0xa0, 0xb6, 0x8c, 0xf7, + ]; + + let key: [u8; 32] = [0x0b; 32]; + let data = b"Hi There"; + + let mut hmac = HmacSha256::new(key); + hmac.update(data); + let hmac_result: [u8; 32] = hmac.finalize(); + assert_eq!(&hmac_result, &expected_hmac); + } + + #[test] + fn hmac_sha256_update_test() { + let expected_hmac = [ + 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, + 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, + 0x2e, 0x32, 0xcf, 0xf7, + ]; + let key: [u8; 20] = [0x0b; 20]; + let data = b"Hi There"; + let mut hmac: HmacSha256 = HmacSha256::new_from_slice(&key).expect(""); + hmac.update(data); + let result = hmac.finalize(); + assert_eq!(&result, &expected_hmac); + assert_eq!(result.len(), 32); + } + + #[test] + fn hmac_sha256_test_big_buffer() { + let expected_hmac = [ + 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, + 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, + 0x2e, 0x32, 0xcf, 0xf7, + ]; + let key: [u8; 20] = [0x0b; 20]; + let data = b"Hi There"; + let hmac_result = hmac_sha_256(&key, data).expect("Couldn't calculate sha256 hmac"); + assert_eq!(&hmac_result, &expected_hmac); + } + + #[test] + fn hmac_sha256_update_chunks_test() { + let expected_hmac = [ + 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, + 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, + 0x2e, 0x32, 0xcf, 0xf7, + ]; + let key: [u8; 20] = [0x0b; 20]; + let mut hmac = HmacSha256::new_from_slice(&key).expect("key is valid length"); + hmac.update(b"Hi"); + hmac.update(b" There"); + let result = hmac.finalize(); + assert_eq!(&result, &expected_hmac); + } + + #[test] + fn hmac_sha256_verify_test() { + let expected_hmac = [ + 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, + 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, + 0x2e, 0x32, 0xcf, 0xf7, + ]; + let key: [u8; 20] = [0x0b; 20]; + let data = b"Hi There"; + let mut hmac: HmacSha256 = HmacSha256::new_from_slice(&key).expect(""); + hmac.update(data); + assert!(hmac.verify(expected_hmac).is_ok()) + } +}
diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs new file mode 100644 index 0000000..166f999 --- /dev/null +++ b/rust/bssl-crypto/src/lib.rs
@@ -0,0 +1,118 @@ +/* Copyright (c) 2023, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#![deny( + missing_docs, + clippy::indexing_slicing, + clippy::unwrap_used, + clippy::panic, + clippy::expect_used +)] + +//! Rust boringssl binding + +extern crate core; +use core::ops::Not; + +/// BoringSSL implemented hmac operations. +pub mod hmac; + +/// BoringSSL implemented hash functions. +pub mod digest; + +/// Used for handling result types from C APIs. +trait PanicResultHandler { + /// Panics if a C api returns an invalid result + /// Used for APIs which return error codes for allocation failures. + fn panic_if_error(&self); +} + +impl PanicResultHandler for i32 { + /// BoringSSL APIs return 1 on success or 0 on allocation failure. + #[allow(clippy::expect_used)] + fn panic_if_error(&self) { + self.gt(&0).then_some(()).expect("allocation failed!") + } +} + +impl<T> PanicResultHandler for *mut T { + /// Boringssl APIs return NULL on allocation failure for APIs that return a CTX. + #[allow(clippy::expect_used)] + fn panic_if_error(&self) { + self.is_null() + .not() + .then_some(()) + .expect("allocation failed!") + } +} + +struct CSlice<'a>(&'a [u8]); + +impl CSlice<'_> { + pub fn as_ptr<T>(&self) -> *const T { + if self.0.is_empty() { + std::ptr::null() + } else { + self.0.as_ptr() as *const T + } + } +} + +impl<'a> From<&'a [u8]> for CSlice<'a> { + fn from(value: &'a [u8]) -> Self { + Self(value) + } +} + +/// A helper trait implemented by types which reference borrowed foreign types. +/// +/// # Safety +/// +/// Implementations of `ForeignTypeRef` must guarantee the following: +/// +/// - `Self::from_ptr(x).as_ptr() == x` +/// - `Self::from_mut_ptr(x).as_ptr() == x` +unsafe trait ForeignTypeRef: Sized { + /// The raw C type. + type CType; + + /// Constructs a shared instance of this type from its raw type. + /// + /// # Safety + /// + /// `ptr` must be a valid, immutable, instance of the type for the `'a` lifetime. + #[inline] + unsafe fn from_ptr<'a>(ptr: *mut Self::CType) -> &'a Self { + debug_assert!(!ptr.is_null()); + &*(ptr as *mut _) + } + + /// Constructs a mutable reference of this type from its raw type. + /// + /// # Safety + /// + /// `ptr` must be a valid, unique, instance of the type for the `'a` lifetime. + #[inline] + unsafe fn from_ptr_mut<'a>(ptr: *mut Self::CType) -> &'a mut Self { + debug_assert!(!ptr.is_null()); + &mut *(ptr as *mut _) + } + + /// Returns a raw pointer to the wrapped value. + #[inline] + fn as_ptr(&self) -> *mut Self::CType { + self as *const _ as *mut _ + } +}
diff --git a/rust/bssl-sys/CMakeLists.txt b/rust/bssl-sys/CMakeLists.txt new file mode 100644 index 0000000..9b5c841 --- /dev/null +++ b/rust/bssl-sys/CMakeLists.txt
@@ -0,0 +1,40 @@ +# Additional interop for things like macros and inlined functions. +add_library(rust_wrapper STATIC rust_wrapper.c) +target_link_libraries(rust_wrapper crypto) + + +# Generate architecture-specific wrappers. +set(WRAPPER_TARGET ${CMAKE_BINARY_DIR}/rust/bssl-sys/src/wrapper_${RUST_BINDINGS}.rs) +set(COMMAND ${BINDGEN_EXECUTABLE} "wrapper.h" + -o ${WRAPPER_TARGET} + --no-derive-default + --enable-function-attribute-detection + --use-core + --size_t-is-usize + --default-macro-constant-type="signed" + --rustified-enum="point_conversion_form_t" + --allowlist-file=".*/include/openssl/.*\\.h" + --allowlist-file=".*/rust_wrapper\\.h" + -- # these are LLVM arg passthroughs + -I../../include + # https://doc.rust-lang.org/nightly/rustc/platform-support.html + --target=${RUST_BINDINGS}) + +set(INCLUDES "include!(\"wrapper_${RUST_BINDINGS}.rs\");\n") + +add_custom_target( + bindgen_rust_${RUST_BINDINGS} + ALL + ${COMMAND} + BYPRODUCTS ${WRAPPER_TARGET} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + +# move files into build directory +configure_file("src/lib.rs" "src/lib.rs") + +if(NOT BUILD_SHARED_LIBS) + configure_file("build.rs" "build.rs" COPYONLY) +endif() + +configure_file("Cargo.toml" "Cargo.toml" COPYONLY) \ No newline at end of file
diff --git a/rust/Cargo.toml b/rust/bssl-sys/Cargo.toml similarity index 81% rename from rust/Cargo.toml rename to rust/bssl-sys/Cargo.toml index 6a2bb40..c5e61fd 100644 --- a/rust/Cargo.toml +++ b/rust/bssl-sys/Cargo.toml
@@ -3,6 +3,8 @@ version = "0.1.0" authors = ["Benjamin Brittain <bwb@google.com>"] edition = "2018" +publish = false +license = "MIT" [dependencies] libc = "0.2"
diff --git a/rust/README.md b/rust/bssl-sys/README.md similarity index 100% rename from rust/README.md rename to rust/bssl-sys/README.md
diff --git a/rust/build.rs b/rust/bssl-sys/build.rs similarity index 77% rename from rust/build.rs rename to rust/bssl-sys/build.rs index b029223..955eae3 100644 --- a/rust/build.rs +++ b/rust/bssl-sys/build.rs
@@ -19,18 +19,21 @@ fn main() { let dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let crate_path = Path::new(&dir); - let parent_path = crate_path.parent().unwrap(); + + // building bssl-sys with: `mkdir build && cd build && cmake -G Ninja .. -DRUST_BINDINGS="$(gcc -dumpmachine)" && ninja` + // outputs this crate to /build/rust/bssl-sys/ so need to go up 3 levels to the root of the repo + let repo_root = crate_path.parent().unwrap().parent().unwrap(); // Statically link libraries. println!( "cargo:rustc-link-search=native={}", - parent_path.join("crypto").display() + repo_root.join("crypto").display() ); println!("cargo:rustc-link-lib=static=crypto"); println!( "cargo:rustc-link-search=native={}", - parent_path.join("ssl").display() + repo_root.join("ssl").display() ); println!("cargo:rustc-link-lib=static=ssl");
diff --git a/rust/rust_wrapper.c b/rust/bssl-sys/rust_wrapper.c similarity index 100% rename from rust/rust_wrapper.c rename to rust/bssl-sys/rust_wrapper.c
diff --git a/rust/rust_wrapper.h b/rust/bssl-sys/rust_wrapper.h similarity index 100% rename from rust/rust_wrapper.h rename to rust/bssl-sys/rust_wrapper.h
diff --git a/rust/src/lib.rs b/rust/bssl-sys/src/lib.rs similarity index 100% rename from rust/src/lib.rs rename to rust/bssl-sys/src/lib.rs
diff --git a/rust/bssl-sys/wrapper.h b/rust/bssl-sys/wrapper.h new file mode 100644 index 0000000..101b455 --- /dev/null +++ b/rust/bssl-sys/wrapper.h
@@ -0,0 +1,79 @@ +#include "../../include/openssl/aes.h" +#include "../../include/openssl/asn1.h" +#include "../../include/openssl/asn1_mac.h" +#include "../../include/openssl/asn1t.h" +#include "../../include/openssl/base.h" +#include "../../include/openssl/base64.h" +#include "../../include/openssl/bio.h" +#include "../../include/openssl/blake2.h" +#include "../../include/openssl/blowfish.h" +#include "../../include/openssl/bn.h" +#include "../../include/openssl/buf.h" +#include "../../include/openssl/buffer.h" +#include "../../include/openssl/bytestring.h" +#include "../../include/openssl/cast.h" +#include "../../include/openssl/chacha.h" +#include "../../include/openssl/cipher.h" +#include "../../include/openssl/cmac.h" +#include "../../include/openssl/conf.h" +#include "../../include/openssl/cpu.h" +#include "../../include/openssl/crypto.h" +#include "../../include/openssl/ctrdrbg.h" +#include "../../include/openssl/curve25519.h" +#include "../../include/openssl/des.h" +#include "../../include/openssl/dh.h" +#include "../../include/openssl/digest.h" +#include "../../include/openssl/dsa.h" +#include "../../include/openssl/dtls1.h" +#include "../../include/openssl/e_os2.h" +#include "../../include/openssl/ec.h" +#include "../../include/openssl/ec_key.h" +#include "../../include/openssl/ecdh.h" +#include "../../include/openssl/ecdsa.h" +#include "../../include/openssl/engine.h" +#include "../../include/openssl/err.h" +#include "../../include/openssl/evp.h" +#include "../../include/openssl/evp_errors.h" +#include "../../include/openssl/ex_data.h" +#include "../../include/openssl/hkdf.h" +#include "../../include/openssl/hmac.h" +#include "../../include/openssl/hpke.h" +#include "../../include/openssl/hrss.h" +#include "../../include/openssl/is_boringssl.h" +#include "../../include/openssl/kdf.h" +#include "../../include/openssl/lhash.h" +#include "../../include/openssl/md4.h" +#include "../../include/openssl/md5.h" +#include "../../include/openssl/mem.h" +#include "../../include/openssl/obj.h" +#include "../../include/openssl/obj_mac.h" +#include "../../include/openssl/objects.h" +#include "../../include/openssl/opensslconf.h" +#include "../../include/openssl/opensslv.h" +#include "../../include/openssl/ossl_typ.h" +#include "../../include/openssl/pem.h" +#include "../../include/openssl/pkcs12.h" +#include "../../include/openssl/pkcs7.h" +#include "../../include/openssl/pkcs8.h" +#include "../../include/openssl/poly1305.h" +#include "../../include/openssl/pool.h" +#include "../../include/openssl/rand.h" +#include "../../include/openssl/rc4.h" +#include "../../include/openssl/ripemd.h" +#include "../../include/openssl/rsa.h" +#include "../../include/openssl/safestack.h" +#include "../../include/openssl/sha.h" +#include "../../include/openssl/siphash.h" +#include "../../include/openssl/span.h" +#include "../../include/openssl/srtp.h" +#include "../../include/openssl/ssl.h" +#include "../../include/openssl/ssl3.h" +#include "../../include/openssl/stack.h" +#include "../../include/openssl/thread.h" +#include "../../include/openssl/tls1.h" +#include "../../include/openssl/trust_token.h" +#include "../../include/openssl/x509.h" +#include "../../include/openssl/x509_vfy.h" +#include "../../include/openssl/x509v3.h" + +#include "rust_wrapper.h"
diff --git a/rust/wrapper.h b/rust/wrapper.h deleted file mode 100644 index 1d65c5a..0000000 --- a/rust/wrapper.h +++ /dev/null
@@ -1,79 +0,0 @@ -#include "../include/openssl/aes.h" -#include "../include/openssl/asn1.h" -#include "../include/openssl/asn1_mac.h" -#include "../include/openssl/asn1t.h" -#include "../include/openssl/base.h" -#include "../include/openssl/base64.h" -#include "../include/openssl/bio.h" -#include "../include/openssl/blake2.h" -#include "../include/openssl/blowfish.h" -#include "../include/openssl/bn.h" -#include "../include/openssl/buf.h" -#include "../include/openssl/buffer.h" -#include "../include/openssl/bytestring.h" -#include "../include/openssl/cast.h" -#include "../include/openssl/chacha.h" -#include "../include/openssl/cipher.h" -#include "../include/openssl/cmac.h" -#include "../include/openssl/conf.h" -#include "../include/openssl/cpu.h" -#include "../include/openssl/crypto.h" -#include "../include/openssl/ctrdrbg.h" -#include "../include/openssl/curve25519.h" -#include "../include/openssl/des.h" -#include "../include/openssl/dh.h" -#include "../include/openssl/digest.h" -#include "../include/openssl/dsa.h" -#include "../include/openssl/dtls1.h" -#include "../include/openssl/e_os2.h" -#include "../include/openssl/ec.h" -#include "../include/openssl/ec_key.h" -#include "../include/openssl/ecdh.h" -#include "../include/openssl/ecdsa.h" -#include "../include/openssl/engine.h" -#include "../include/openssl/err.h" -#include "../include/openssl/evp.h" -#include "../include/openssl/evp_errors.h" -#include "../include/openssl/ex_data.h" -#include "../include/openssl/hkdf.h" -#include "../include/openssl/hmac.h" -#include "../include/openssl/hpke.h" -#include "../include/openssl/hrss.h" -#include "../include/openssl/is_boringssl.h" -#include "../include/openssl/kdf.h" -#include "../include/openssl/lhash.h" -#include "../include/openssl/md4.h" -#include "../include/openssl/md5.h" -#include "../include/openssl/mem.h" -#include "../include/openssl/obj.h" -#include "../include/openssl/obj_mac.h" -#include "../include/openssl/objects.h" -#include "../include/openssl/opensslconf.h" -#include "../include/openssl/opensslv.h" -#include "../include/openssl/ossl_typ.h" -#include "../include/openssl/pem.h" -#include "../include/openssl/pkcs12.h" -#include "../include/openssl/pkcs7.h" -#include "../include/openssl/pkcs8.h" -#include "../include/openssl/poly1305.h" -#include "../include/openssl/pool.h" -#include "../include/openssl/rand.h" -#include "../include/openssl/rc4.h" -#include "../include/openssl/ripemd.h" -#include "../include/openssl/rsa.h" -#include "../include/openssl/safestack.h" -#include "../include/openssl/sha.h" -#include "../include/openssl/siphash.h" -#include "../include/openssl/span.h" -#include "../include/openssl/srtp.h" -#include "../include/openssl/ssl.h" -#include "../include/openssl/ssl3.h" -#include "../include/openssl/stack.h" -#include "../include/openssl/thread.h" -#include "../include/openssl/tls1.h" -#include "../include/openssl/trust_token.h" -#include "../include/openssl/x509.h" -#include "../include/openssl/x509_vfy.h" -#include "../include/openssl/x509v3.h" - -#include "rust_wrapper.h"