Skip to content

Commit

Permalink
feat(gitoid): Add BoringSSL backend support for SHA-1 and SHA-256 in …
Browse files Browse the repository at this point in the history
…gitoid (omnibor#182)

* feat(gitoid): Add BoringSSL backend support for SHA-1 and SHA-256 in gitoid

This commit introduces support for the BoringSSL backend for SHA-1 and
SHA-256 in the gitoid Rust package. The SHA-1CD algorithm is not
supported with this backend. To enable BoringSSL, use the "boring"
feature in Cargo.

Additionally, benchmarks have been added to measure the performance
of the implemented cryptographic algorithms.

Signed-off-by: Frederick F. Kautz IV <[email protected]>

* chore(gitoid): removed some commented unnecessary code

Signed-off-by: Frederick F. Kautz IV <[email protected]>

* chore(gitoid): Refactor feature setup and conditional compilation in gitoid

- Remove conditional compilation annotations within `boring_sha.rs` since the
  entire module is conditionally compiled.
- Introduce separate features for RustCrypto and BoringSSL backends.
- Adjust `Cargo.toml` to include `rustcrypto` and `boringssl` features.
- Ensure `boringssl` always activates `sha1` and `sha256` since it provides
  both.
- Modify logic to prevent `sha1cd` from being used with only the BoringSSL
  backend.
- Update hash algorithm implementations in `hash_algorithm.rs` to respect the
  new feature setup.
- Add compilation errors for invalid feature combinations to `lib.rs`.
- Update tests in `tests.rs` to conditionally compile based on feature setup.

Signed-off-by: Frederick F. Kautz IV <[email protected]>

* chore(gh-actions): Update CI workflow to include BoringSSL feature

- Modify `.github/workflows/ci.yml` to build and test with the `boring` feature enabled.

Signed-off-by: Frederick F. Kautz IV <[email protected]>

* feat: Make "boringssl" and "rustcrypto" features non-exclusive.

This commit makes it so both the "boringssl" and "rustcrypto" features
can be active at the same time, meaning users of the `gitoid` crate will
be able to select at runtime which of the algorithms they want to use,
rather than solely deciding at compile time.

Signed-off-by: Andrew Lilley Brinker <[email protected]>

* chore: Hide internal types for "boringssl" backend

This commit hides the `BoringSha1` and `BoringSha256` types from the
`boringssl` backend. They technically have to be public because the
associated type of a public trait must be public, but they only exist to
wrap the types from the `boring` crate and implement the relevant traits
from the `digest` crate so those types can fit into the interface we
require for a cryptography backend. We don't _really_ want downstream
users to use these types, so we make them public but `#[doc(hidden)]`.

Signed-off-by: Andrew Lilley Brinker <[email protected]>

* fix(ci): correct build feature from 'boring' to 'boringssl'

Signed-off-by: Frederick F. Kautz IV <[email protected]>

---------

Signed-off-by: Frederick F. Kautz IV <[email protected]>
Signed-off-by: Andrew Lilley Brinker <[email protected]>
Co-authored-by: Andrew Lilley Brinker <[email protected]>
  • Loading branch information
fkautz and alilleybrinker authored Jun 24, 2024
1 parent 54d0b76 commit 178f7c4
Show file tree
Hide file tree
Showing 10 changed files with 404 additions and 64 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Building
run: cargo build --verbose
run: cargo build --verbose --features=boringssl
- name: Testing (Rust)
run: cargo test --verbose
run: cargo test --verbose --features=boringssl
- name: Linting
run: cargo clippy --verbose
run: cargo clippy --verbose --features=boringssl

conventional-commits:
name: Conventional Commits
Expand All @@ -35,4 +35,4 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: webiny/[email protected]


44 changes: 40 additions & 4 deletions gitoid/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ edition.workspace = true
# so we know we always get the crate.
digest = { version = "0.10.7" }
sha1 = { version = "0.10.6", default-features = false, optional = true }
sha1collisiondetection = { version = "0.3.3", default-features = false, features = ["digest-trait"], optional = true }
sha1collisiondetection = { version = "0.3.3", default-features = false, features = [
"digest-trait",
], optional = true }
sha2 = { version = "0.10.8", default-features = false, optional = true }

# std-requiring dependencies.
Expand All @@ -33,12 +35,19 @@ hex = { version = "0.4.3", optional = true }
serde = { version = "1.0.197", optional = true }
tokio = { version = "1.36.0", features = ["io-util"], optional = true }
url = { version = "2.4.1", optional = true }
boring = { version = "4.6.0", optional = true }

[dev-dependencies]

# Need "rt" and "fs" additionally for tests.
tokio = { version = "1.36.0", features = ["io-util", "fs", "rt", "rt-multi-thread"] }
tokio = { version = "1.36.0", features = [
"io-util",
"fs",
"rt",
"rt-multi-thread",
] }
serde_test = "1.0.176"
criterion = { version = "0.5.1" }

[features]

Expand All @@ -49,7 +58,18 @@ serde_test = "1.0.176"
# - Hex: ability to print a GitOid with a hexadecimal hash representation.
# - Url: ability to convert a GitOid to and from a gitoid-scheme URL.
# - Serde: ability to serialize and deserialize a GitOid to and from a URL.
default = ["async", "hex", "serde", "sha1", "sha1cd", "sha256", "std", "url"]
# - Rustcrypto: use the RustCrypto crates as the cryptography backend.
default = [
"async",
"hex",
"serde",
"std",
"url",
"rustcrypto",
"sha1",
"sha1cd",
"sha256",
]

# Async support is optional. That said, it's currently _only_ with Tokio,
# meaning you'd need to handle integrating with any other async runtime
Expand Down Expand Up @@ -85,11 +105,27 @@ std = [
"sha1?/std",
"sha1collisiondetection?/std",
"sha2?/std",
"dep:format-bytes"
"dep:format-bytes",
]

# Get the ability to construct and get out URLs.
#
# This relies on `std` as the `url` crate isn't `no_std`-compatible.
# This also relies on `hex` as the URL includes the hex-encoded hash.
url = ["dep:url", "hex", "std"]

# Enable using RustCrypto as a cryptography backend.
rustcrypto = []

# Enable using BoringSLL as a cryptography backend.
#
# NOTE: This unconditionally turns on the "sha1" and "sha256" features,
# because the `boring` crate which provides the BoringSSL cryptography
# implementations does not permit conditionally compiling those
# implementations out. Since they're _always_ present, we might as well
# use them unconditionally.
boringssl = ["dep:boring", "sha1", "sha256"]

[[bench]]
name = "benchmark"
harness = false
20 changes: 20 additions & 0 deletions gitoid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,26 @@ meaningful ways.
the `sha1cd` algorithm. This is reflected in the `gitoid`-scheme URLs
generated when using the `GitOid` type.
## Boring Feature
The `gitoid` crate supports using the BoringSSL cryptographic library for SHA-1
and SHA-256 hashing through the `boring` feature. This can be useful for
environments where BoringSSL is preferred or required for compliance reasons.
### Enabling the Boring Feature
To enable the `boring` feature, add the following to your `Cargo.toml`:
```toml
[dependencies]
gitoid = { version = "0.7.1", features = ["boring"] }
```

When the `boring` feature is enabled, the crate will use BoringSSL's
implementations of SHA-1 and SHA-256 instead of the default RustCrypto
implementations. Note that `sha1cd` is not supported by the `boring` feature
and will fall back to using the RustCrypto implementation.

## Minimum Supported Rust Version (MSRV)

This crate does not maintain a Minimum Supported Rust Version, and generally
Expand Down
136 changes: 136 additions & 0 deletions gitoid/benches/benchmark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use criterion::black_box;
use criterion::criterion_group;
use criterion::criterion_main;
use criterion::Criterion;
#[cfg(feature = "boringssl")]
use gitoid::boringssl::Sha1 as BoringSha1;
#[cfg(feature = "boringssl")]
use gitoid::boringssl::Sha256 as BoringSha256;
#[cfg(all(feature = "rustcrypto", feature = "sha1"))]
use gitoid::rustcrypto::Sha1 as RustSha1;
#[cfg(all(feature = "rustcrypto", feature = "sha256"))]
use gitoid::rustcrypto::Sha256 as RustSha256;
use gitoid::Blob;
use gitoid::GitOid;

#[cfg(not(any(feature = "rustcrypto", feature = "boringssl",)))]
compile_error!(
r#"At least one cryptography backend must be active: "rustcrypto" and/or "boringssl""#
);

#[cfg(feature = "rustcrypto")]
fn bench_rustcrypto_sha1_small(c: &mut Criterion) {
let name = "GitOid RustCrypto SHA-1 11B";
let input = b"hello world";
c.bench_function(name, |b| {
b.iter(|| {
let _ = GitOid::<RustSha1, Blob>::id_bytes(black_box(input));
})
});
}

#[cfg(feature = "boringssl")]
fn bench_boring_sha1_small(c: &mut Criterion) {
let name = "GitOid BoringSSL SHA-1 11B";
let input = b"hello world";
c.bench_function(name, |b| {
b.iter(|| {
let _ = GitOid::<BoringSha1, Blob>::id_bytes(black_box(input));
})
});
}

#[cfg(feature = "rustcrypto")]
fn bench_rustcrypto_sha256_small(c: &mut Criterion) {
let name = "GitOid RustCrypto SHA-256 11B";
let input = b"hello world";
c.bench_function(name, |b| {
b.iter(|| {
let _ = GitOid::<RustSha256, Blob>::id_bytes(black_box(input));
})
});
}

#[cfg(feature = "boringssl")]
fn bench_boring_sha256_small(c: &mut Criterion) {
let name = "GitOid BoringSSL SHA-256 11B";
let input = b"hello world";
c.bench_function(name, |b| {
b.iter(|| {
let _ = GitOid::<BoringSha256, Blob>::id_bytes(black_box(input));
})
});
}

#[cfg(feature = "rustcrypto")]
fn bench_rustcrypto_sha1_large(c: &mut Criterion) {
let name = "GitOid RustCrypto SHA-1 100MB";
let input = &[0; 1024 * 1024 * 100]; // 100 MB
c.bench_function(name, |b| {
b.iter(|| {
let _ = GitOid::<RustSha1, Blob>::id_bytes(black_box(input));
})
});
}

#[cfg(feature = "boringssl")]
fn bench_boring_sha1_large(c: &mut Criterion) {
let name = "GitOid BoringSSL SHA-1 100MB";
let input = &[0; 1024 * 1024 * 100]; // 100 MB
c.bench_function(name, |b| {
b.iter(|| {
let _ = GitOid::<BoringSha1, Blob>::id_bytes(black_box(input));
})
});
}

#[cfg(feature = "rustcrypto")]
fn bench_rustcrypto_sha256_large(c: &mut Criterion) {
let name = "GitOid RustCrypto SHA-256 100MB";
let input = &[0; 1024 * 1024 * 100]; // 100 MB
c.bench_function(name, |b| {
b.iter(|| {
let _ = GitOid::<RustSha256, Blob>::id_bytes(black_box(input));
})
});
}

#[cfg(feature = "boringssl")]
fn bench_boring_sha256_large(c: &mut Criterion) {
let name = "GitOid BoringSSL SHA-256 100MB";
let input = &[0; 1024 * 1024 * 100]; // 100 MB
c.bench_function(name, |b| {
b.iter(|| {
let _ = GitOid::<BoringSha256, Blob>::id_bytes(black_box(input));
})
});
}

#[cfg(feature = "rustcrypto")]
criterion_group!(
name = rustcrypto_benches;
config = Criterion::default();
targets = bench_rustcrypto_sha1_small,
bench_rustcrypto_sha256_small,
bench_rustcrypto_sha1_large,
bench_rustcrypto_sha256_large
);

#[cfg(feature = "boringssl")]
criterion_group!(
name = boringssl_benches;
config = Criterion::default();
targets = bench_boring_sha1_small,
bench_boring_sha256_small,
bench_boring_sha1_large,
bench_boring_sha256_large
);

#[cfg(all(feature = "rustcrypto", feature = "boringssl"))]
criterion_main!(rustcrypto_benches, boringssl_benches);

#[cfg(all(feature = "rustcrypto", not(feature = "boringssl")))]
criterion_main!(rustcrypto_benches);

#[cfg(all(not(feature = "rustcrypto"), feature = "boringssl"))]
criterion_main!(boringssl_benches);
111 changes: 111 additions & 0 deletions gitoid/src/backend/boringssl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! BoringSSL-based cryptography backend.
use crate::impl_hash_algorithm;
use crate::sealed::Sealed;
use crate::HashAlgorithm;
use boring::sha;
use digest::consts::U20;
use digest::consts::U32;
use digest::generic_array::GenericArray;
use digest::Digest;
use digest::FixedOutput;
use digest::HashMarker;
use digest::Output;
use digest::OutputSizeUser;
use digest::Update;

#[cfg(feature = "sha1")]
/// SHA-1 algorithm
pub struct Sha256 {
#[doc(hidden)]
_private: (),
}

/// Boring SHA-256 implementation.
#[doc(hidden)]
pub struct BoringSha256 {
hash: sha::Sha256,
}

#[cfg(all(feature = "sha256", feature = "boringssl"))]
impl_hash_algorithm!(Sha256, BoringSha256, "sha256");

impl Update for BoringSha256 {
fn update(&mut self, data: &[u8]) {
self.hash.update(data);
}
}

impl OutputSizeUser for BoringSha256 {
type OutputSize = U32;
}

impl FixedOutput for BoringSha256 {
fn finalize_into(self, out: &mut Output<Self>) {
out.copy_from_slice(self.hash.finish().as_slice());
}

fn finalize_fixed(self) -> Output<Self> {
let mut out = Output::<Self>::default();
out.copy_from_slice(self.hash.finish().as_slice());
out
}
}

impl HashMarker for BoringSha256 {}

impl Default for BoringSha256 {
fn default() -> Self {
Self {
hash: sha::Sha256::new(),
}
}
}

#[cfg(feature = "sha1")]
/// SHA-1 algorithm
pub struct Sha1 {
#[doc(hidden)]
_private: (),
}

/// Boring SHA-1 implementation.
#[doc(hidden)]
pub struct BoringSha1 {
hash: sha::Sha1,
}

#[cfg(all(feature = "sha1", feature = "boringssl"))]
impl_hash_algorithm!(Sha1, BoringSha1, "sha1");

impl Update for BoringSha1 {
fn update(&mut self, data: &[u8]) {
self.hash.update(data);
}
}

impl OutputSizeUser for BoringSha1 {
type OutputSize = U20;
}

impl FixedOutput for BoringSha1 {
fn finalize_into(self, out: &mut Output<Self>) {
out.copy_from_slice(self.hash.finish().as_slice());
}

fn finalize_fixed(self) -> Output<Self> {
let mut out = Output::<Self>::default();
out.copy_from_slice(self.hash.finish().as_slice());
out
}
}

impl HashMarker for BoringSha1 {}

impl Default for BoringSha1 {
fn default() -> Self {
Self {
hash: sha::Sha1::new(),
}
}
}
7 changes: 7 additions & 0 deletions gitoid/src/backend/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Cryptography backends, providing hash function implementations.
#[cfg(feature = "boringssl")]
pub mod boringssl;

#[cfg(feature = "rustcrypto")]
pub mod rustcrypto;
Loading

0 comments on commit 178f7c4

Please sign in to comment.