This is an automated email from the ASF dual-hosted git repository.

alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs-object-store.git


The following commit(s) were added to refs/heads/main by this push:
     new 996e084  Pluggable Crypto / Update reqwest 0.13 (#707)
996e084 is described below

commit 996e084600a4adc7e14cf34d332d3cb0972547b4
Author: Geoffry Song <[email protected]>
AuthorDate: Wed Jun 17 12:42:15 2026 -0700

    Pluggable Crypto / Update reqwest 0.13 (#707)
    
    * Pluggable Crypto
    
    * Add CI
    
    * Upgrade reqwest to 0.13
    
    * Certificates
    
    * Switch to aws-lc-rs
    
    * Fix CI
    
    * Fix doc
    
    * Remove reqwest/rustls-no-provider feature dependency
    
    * Compile with reqwest/rustls feature in CI when not selecting a crypto 
backend
    
    * Hack around WASIp1 CI by downgrading reqwest
    
    * Update docs
    
    * Improve
    
    * Document how to use flags
    
    * fixes
    
    * Updates
    
    * typo
    
    * docs
    
    * Clarify base feature crypto and HTTP requirements
    
    * revert non fips
    
    * Improve comments, and fix clippy
    
    * Use pluggable crypto API for Azure CPK key digest instead of ring
    
    The merged CPK support (#742) computed the encryption key SHA-256 via
    ring::digest directly, which fails to compile on this branch since
    azure-base no longer depends on ring. Route it through the CryptoProvider
    abstraction (DigestAlgorithm::Sha256) instead.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
    
    ---------
    
    Co-authored-by: Raphael Taylor-Davies <[email protected]>
    Co-authored-by: Adam Gutglick <[email protected]>
    Co-authored-by: Andrew Lamb <[email protected]>
    Co-authored-by: Kevin Liu <[email protected]>
    Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
 .github/workflows/ci.yml | 103 ++++++++++--
 Cargo.toml               |  39 +++--
 src/aws/builder.rs       |  12 +-
 src/aws/client.rs        | 112 ++++++++-----
 src/aws/credential.rs    | 141 +++++++++++-----
 src/aws/mod.rs           |   8 +-
 src/azure/builder.rs     |  19 ++-
 src/azure/client.rs      |  71 +++++---
 src/azure/credential.rs  | 120 ++++++++++++--
 src/azure/mod.rs         |   8 +-
 src/client/crypto.rs     | 410 +++++++++++++++++++++++++++++++++++++++++++++++
 src/client/mod.rs        |  61 ++++++-
 src/gcp/builder.rs       |  27 +++-
 src/gcp/client.rs        |   4 +-
 src/gcp/credential.rs    | 273 ++++++++++++++++++++++---------
 src/gcp/mod.rs           |   5 +-
 src/lib.rs               | 124 +++++++++++---
 src/util.rs              |  16 +-
 18 files changed, 1272 insertions(+), 281 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c14df42..60be27e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -39,6 +39,55 @@ jobs:
       - uses: actions/checkout@v6
       - name: Setup Clippy
         run: rustup component add clippy
+      - name: Verify base features do not enable TLS/signing crypto crates
+        # `--prefix none -f '{p}'` prints one `name vX.Y.Z` per line, so the
+        # crate name can be anchored with `^`. Avoid `\b`, which is not
+        # portable (e.g. BSD grep) and would also substring-match crates like
+        # `iri-string`.
+        run: |
+          found=$(cargo tree --no-default-features --features 
aws-base,azure-base,gcp-base,http-base --edges normal --prefix none -f '{p}' \
+            | grep -E '^(aws-lc-rs|aws-lc-sys|native-tls|openssl|ring|rustls) 
v' | sort -u || true)
+          if [ -n "$found" ]; then
+            echo "❌ disallowed crate(s) found:"
+            echo "$found"
+            exit 1
+          fi
+          echo "✅ no TLS/signing crypto crates"
+      - name: Verify base features do not enable reqwest
+        run: |
+          found=$(cargo tree --no-default-features --features 
aws-base,azure-base,gcp-base,http-base --edges normal --prefix none -f '{p}' \
+            | grep -E '^reqwest v' | sort -u || true)
+          if [ -n "$found" ]; then
+            echo "❌ reqwest must not be enabled by *-base features:"
+            echo "$found"
+            exit 1
+          fi
+          echo "✅ *-base features do not enable reqwest"
+      - name: Check ring build does not pull aws-lc-rs
+        # A `ring` build (with a non-aws-lc-rs reqwest TLS backend) must be 
free
+        # of aws-lc-rs entirely, so users can opt out of it on every target.
+        run: |
+          found=$(cargo tree --no-default-features --features 
aws-base,azure-base,gcp-base,http-base,reqwest,reqwest/native-tls,ring --edges 
normal --prefix none -f '{p}' \
+            | grep -E '^(aws-lc-rs|aws-lc-sys) v' | sort -u || true)
+          if [ -n "$found" ]; then
+            echo "❌ aws-lc-rs pulled into a ring-only build:"
+            echo "$found"
+            exit 1
+          fi
+          echo "✅ ring build is free of aws-lc-rs"
+      - name: Check batteries-included features do not pull ring
+        # The batteries-included features use reqwest/rustls, which uses 
aws-lc-rs
+        # for TLS by default. Keep object_store's default bundled crypto 
provider
+        # aligned with that choice by ensuring these features don't pull ring.
+        run: |
+          found=$(cargo tree --no-default-features --features 
aws,azure,gcp,http --edges normal --prefix none -f '{p}' \
+            | grep -E '^ring v' | sort -u || true)
+          if [ -n "$found" ]; then
+            echo "❌ batteries-included features pulled ring:"
+            echo "$found"
+            exit 1
+          fi
+          echo "✅ batteries-included features are free of ring"
       # Run different tests for the library on its own as well as
       # all targets to ensure that it still works in the absence of
       # features that might be enabled by dev-dependencies of other
@@ -65,18 +114,23 @@ jobs:
         run: cargo clippy --no-default-features --features gcp-base -- -D 
warnings
       - name: Run clippy with http-base feature
         run: cargo clippy --no-default-features --features http-base -- -D 
warnings
-      - name: Verify base features do not enable reqwest
-        run: |
-          if cargo tree \
-            --no-default-features \
-            --features aws-base,azure-base,gcp-base,http-base \
-            --edges normal \
-            --prefix none \
-            -f '{p}' \
-            | grep -E '^reqwest v'; then
-            echo "reqwest must not be enabled by *-base features"
-            exit 1
-          fi
+      # Testing matrix of HTTP and crypto providers
+      #
+      # Improvements tracked in  
https://githubcom/apache/arrow-rs-object-store/issues/759
+      #
+      # Notes:
+      # Direct `reqwest` needs a TLS backend. Use `rustls-no-provider` so TLS
+      # does not add AWS-LC to ring/custom CryptoProvider checks.
+      - name: Run clippy with base features, reqwest and ring CryptoProvider
+        run: cargo clippy --no-default-features --features 
aws-base,azure-base,gcp-base,http-base,reqwest,reqwest/rustls-no-provider,ring 
-- -D warnings
+      - name: Run clippy with base features, reqwest and no bundled 
CryptoProvider
+        run: cargo clippy --no-default-features --features 
aws-base,azure-base,gcp-base,http-base,reqwest,reqwest/rustls-no-provider -- -D 
warnings
+      - name: Run clippy with base features, custom HTTP and aws-lc-rs 
CryptoProvider
+        run: cargo clippy --no-default-features --features 
aws-base,azure-base,gcp-base,http-base,aws-lc-rs,aws-lc-rs/aws-lc-sys -- -D 
warnings
+      - name: Run clippy with base features, custom HTTP and ring 
CryptoProvider
+        run: cargo clippy --no-default-features --features 
aws-base,azure-base,gcp-base,http-base,ring -- -D warnings
+      - name: Run clippy with base features, custom HTTP and no bundled 
CryptoProvider
+        run: cargo clippy --no-default-features --features 
aws-base,azure-base,gcp-base,http-base -- -D warnings
       - name: Run clippy with integration feature
         run: cargo clippy --no-default-features --features integration -- -D 
warnings
       - name: Run clippy with all features
@@ -159,7 +213,7 @@ jobs:
       - name: Configure Azurite (Azure emulation)
         # the magical connection string is from
         # 
https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio#http-connection-strings
-        # We skip the API version check to prevent breaks related to 
differences between Azurite, Azure and the azure-cli, 
+        # We skip the API version check to prevent breaks related to 
differences between Azurite, Azure and the azure-cli,
         # see https://github.com/Azure/Azurite/issues/2623
         run: |
           echo "AZURITE_CONTAINER=$(docker run -d -p 10000:10000 -p 
10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite azurite -l 
/data --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 
--skipApiVersionCheck)" >> $GITHUB_ENV
@@ -180,6 +234,17 @@ jobs:
           AWS_CONDITIONAL_PUT: etag
           AWS_COPY_IF_NOT_EXISTS: multipart
 
+      # Exercise the `ring` crypto provider at runtime (the other jobs only
+      # compile it)
+      #
+      # Notes:
+      # * Use `reqwest/native-tls` to avoid aws-lc-rs anywhere in the tree
+      #
+      # * use `env -u TEST_INTEGRATION` to run only the (crypto) unit tests
+      #   as the integration tests already ran above.
+      - name: Run object_store tests with ring crypto provider
+        run: env -u TEST_INTEGRATION cargo test --lib --tests 
--no-default-features --features 
fs,aws-base,azure-base,gcp-base,http-base,reqwest,reqwest/native-tls,ring
+
       - name: GCS Output
         if: ${{ !cancelled() }}
         run: docker logs $GCS_CONTAINER
@@ -212,10 +277,18 @@ jobs:
         run: rustup target add wasm32-unknown-unknown
       - name: Build wasm32-unknown-unknown
         run: cargo build --target wasm32-unknown-unknown
+      # Note: we don't `cargo build` the cloud features for 
wasm32-unknown-unknown.
+      # They pull in `rand` -> `getrandom`, which on this target only compiles 
with
+      # its `wasm_js` feature enabled. We can't enable that via `--features` 
because
+      # `getrandom` is a transitive dependency (pulled in by `rand`), not 
direct
       - name: Install wasm32-wasip1
         run: rustup target add wasm32-wasip1
+      - name: Use reqwest 0.13.2 for wasm32-wasip1
+        # TODO: reqwest 0.13.3+ doesn't compile against wasm32-wasip1
+        run: cargo update -p reqwest --precise 0.13.2
       - name: Build wasm32-wasip1
-        run: cargo build --all-features --target wasm32-wasip1
+        # aws-lc-rs does not build for wasm32-wasip1, so use the ring crypto 
provider here
+        run: cargo build --no-default-features --features 
aws-base,gcp-base,azure-base,http-base,reqwest,ring --target wasm32-wasip1
       - name: Build wasm32-wasip1 without reqwest
         run: cargo build --no-default-features --features aws-base --target 
wasm32-wasip1
       - name: Install wasm-pack
@@ -224,7 +297,7 @@ jobs:
         with:
           node-version: 20
       - name: Run wasm32-unknown-unknown tests (via Node)
-        run: wasm-pack test --node --features http --no-default-features
+        run: wasm-pack test --node --features http-base,reqwest,ring 
--no-default-features
 
   windows:
     name: cargo test LocalFileSystem (win64)
diff --git a/Cargo.toml b/Cargo.toml
index e115cae..0b8d7f6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -55,8 +55,9 @@ hyper = { version = "1.2", default-features = false, optional 
= true }
 md-5 = { version = "0.11.0", default-features = false, optional = true }
 quick-xml = { version = "0.40.1", features = ["serialize", 
"overlapped-lists"], optional = true }
 rand = { version = "0.10", default-features = false, features = ["std", 
"std_rng", "thread_rng"], optional = true }
-reqwest = { version = "0.12", default-features = false, features = 
["rustls-tls-native-roots", "http2"], optional = true }
+reqwest = { version = "0.13", default-features = false, features = ["http2"], 
optional = true }
 ring = { version = "0.17", default-features = false, features = ["std"], 
optional = true }
+aws-lc-rs = { version = "1.15", default-features = false, optional = true }
 rustls-pki-types = { version = "1.9", default-features = false, features = 
["std"], optional = true }
 serde = { version = "1.0", default-features = false, features = ["derive"], 
optional = true }
 serde_json = { version = "1.0", default-features = false, features = ["std"], 
optional = true }
@@ -84,29 +85,36 @@ futures-channel = {version = "0.3", features = ["sink"]}
 default = ["fs"]
 
 # Shared cloud/provider implementation dependencies.
-# This intentionally does NOT include reqwest.
-cloud-base = ["serde", "serde_json", "quick-xml", "hyper", "chrono/serde", 
"base64", "rand", "ring", "http-body-util", "form_urlencoded", 
"serde_urlencoded", "tokio"]
+# This intentionally does NOT include reqwest or a bundled crypto provider.
+cloud-base = ["serde", "serde_json", "quick-xml", "hyper", "chrono/serde", 
"base64", "rand", "http-body-util", "form_urlencoded", "serde_urlencoded", 
"tokio"]
 
 # Built-in reqwest-based HTTP transport.
 reqwest = ["dep:reqwest", "reqwest/stream"]
 
-# Provider base features.
-# These compile provider logic without forcing reqwest.
+# Bundled crypto providers.
+# Selecting one provides the default `CryptoProvider`; otherwise a custom
+# provider must be supplied at runtime.
+aws-lc-rs = ["dep:aws-lc-rs", "rustls-pki-types"]
+ring = ["dep:ring", "rustls-pki-types"]
+
+# Implementation base features.
+# These compile provider logic without forcing reqwest or a crypto provider.
 azure-base = ["cloud-base", "httparse"]
-gcp-base = ["cloud-base", "rustls-pki-types"]
+gcp-base = ["cloud-base"]
 aws-base = ["cloud-base", "crc-fast", "md-5"]
 http-base = ["cloud-base"]
 
-# Batteries-included provider features.
-# These preserve existing behavior: enabling aws/azure/gcp/http gives
-# the default reqwest transport.
-azure = ["azure-base", "reqwest"]
-gcp = ["gcp-base", "reqwest"]
-aws = ["aws-base", "reqwest"]
-http = ["http-base", "reqwest"]
+# "Batteries-included" implementation features.
+# Each enables the default `reqwest` transport (with rustls) and `aws-lc-rs`.
+# This keeps object_store's bundled crypto provider aligned with 
reqwest/rustls,
+# which uses aws-lc-rs for TLS by default.
+azure = ["azure-base", "reqwest", "reqwest/rustls", "aws-lc-rs"]
+gcp = ["gcp-base", "reqwest", "reqwest/rustls", "aws-lc-rs"]
+aws = ["aws-base", "reqwest", "reqwest/rustls", "aws-lc-rs"]
+# HTTP/WebDAV doesn't sign object_store requests, but reqwest/rustls uses 
aws-lc-rs by default.
+http = ["http-base", "reqwest", "reqwest/rustls", "aws-lc-rs"]
 
 fs = ["walkdir", "tokio", "nix", "windows-sys"]
-tls-webpki-roots = ["reqwest?/rustls-tls-webpki-roots"]
 integration = ["rand", "tokio"]
 tokio = ["dep:tokio", "dep:tracing"]
 
@@ -117,8 +125,9 @@ hyper-util = "0.1"
 rand = "0.10"
 tempfile = "3.1.0"
 regex = "1.11.1"
+webpki-root-certs = "1"
 # The "gzip" feature for reqwest is enabled for an integration test.
-reqwest = { version = "0.12", default-features = false, features = ["gzip"] }
+reqwest = { version = "0.13", default-features = false, features = ["gzip"] }
 
 [target.'cfg(all(target_arch = "wasm32", target_os = 
"unknown"))'.dev-dependencies]
 wasm-bindgen-test = "0.3.50"
diff --git a/src/aws/builder.rs b/src/aws/builder.rs
index c9fd6e3..b660a6c 100644
--- a/src/aws/builder.rs
+++ b/src/aws/builder.rs
@@ -24,7 +24,7 @@ use crate::aws::{
     AmazonS3, AwsCredential, AwsCredentialProvider, Checksum, 
S3ConditionalPut, S3CopyIfNotExists,
     STORE,
 };
-use crate::client::{HttpConnector, TokenCredentialProvider, http_connector};
+use crate::client::{CryptoProvider, HttpConnector, TokenCredentialProvider, 
http_connector};
 use crate::config::ConfigValue;
 use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, 
StaticCredentialProvider};
 use base64::Engine;
@@ -173,6 +173,8 @@ pub struct AmazonS3Builder {
     client_options: ClientOptions,
     /// Credentials
     credentials: Option<AwsCredentialProvider>,
+    /// The [`CryptoProvider`] to use
+    crypto: Option<Arc<dyn CryptoProvider>>,
     /// Skip signing requests
     skip_signature: ConfigValue<bool>,
     /// Copy if not exists
@@ -894,6 +896,12 @@ impl AmazonS3Builder {
         self
     }
 
+    /// The [`CryptoProvider`] to use
+    pub fn with_crypto_provider(mut self, provider: Arc<dyn CryptoProvider>) 
-> Self {
+        self.crypto = Some(provider);
+        self
+    }
+
     /// Sets what protocol is allowed.
     ///
     /// If `allow_http` is :
@@ -1226,6 +1234,7 @@ impl AmazonS3Builder {
                             endpoint: endpoint.clone(),
                             region: region.clone(),
                             credentials: Arc::clone(&credentials),
+                            crypto: self.crypto.clone(),
                         },
                         http.connect(&self.client_options)?,
                         self.retry_config.clone(),
@@ -1269,6 +1278,7 @@ impl AmazonS3Builder {
             bucket,
             bucket_endpoint,
             credentials,
+            crypto: self.crypto,
             session_provider,
             retry_config: self.retry_config,
             client_options: self.client_options,
diff --git a/src/aws/client.rs b/src/aws/client.rs
index f72155f..4ea3b88 100644
--- a/src/aws/client.rs
+++ b/src/aws/client.rs
@@ -32,7 +32,10 @@ use crate::client::s3::{
     CompleteMultipartUpload, CompleteMultipartUploadResult, CopyPartResult,
     InitiateMultipartUploadResult, ListResponse, PartMetadata,
 };
-use crate::client::{GetOptionsExt, HttpClient, HttpError, HttpResponse};
+use crate::client::{
+    CryptoProvider, DigestAlgorithm, GetOptionsExt, HttpClient, HttpError, 
HttpResponse,
+    crypto_provider,
+};
 use crate::list::{PaginatedListOptions, PaginatedListResult};
 use crate::multipart::PartId;
 use crate::{
@@ -52,8 +55,6 @@ use itertools::Itertools;
 use md5::{Digest, Md5};
 use percent_encoding::{PercentEncode, utf8_percent_encode};
 use quick_xml::events::{self as xml_events};
-use ring::digest;
-use ring::digest::Context;
 use serde::{Deserialize, Serialize};
 use std::sync::Arc;
 
@@ -199,6 +200,7 @@ pub(crate) struct S3Config {
     pub bucket: String,
     pub bucket_endpoint: String,
     pub credentials: AwsCredentialProvider,
+    pub crypto: Option<Arc<dyn CryptoProvider>>,
     pub session_provider: Option<AwsCredentialProvider>,
     pub retry_config: RetryConfig,
     pub client_options: ClientOptions,
@@ -218,19 +220,18 @@ impl S3Config {
         format!("{}/{}", self.bucket_endpoint, encode_path(path))
     }
 
-    async fn get_session_credential(&self) -> Result<SessionCredential<'_>> {
-        let credential = match self.skip_signature {
+    async fn get_session_credential(&self) -> 
Result<Option<SessionCredential<'_>>> {
+        Ok(match self.skip_signature {
             false => {
                 let provider = 
self.session_provider.as_ref().unwrap_or(&self.credentials);
-                Some(provider.get_credential().await?)
+                let credential = provider.get_credential().await?;
+                Some(SessionCredential {
+                    credential,
+                    session_token: self.session_provider.is_some(),
+                    config: self,
+                })
             }
             true => None,
-        };
-
-        Ok(SessionCredential {
-            credential,
-            session_token: self.session_provider.is_some(),
-            config: self,
         })
     }
 
@@ -245,27 +246,32 @@ impl S3Config {
     pub(crate) fn is_s3_express(&self) -> bool {
         self.session_provider.is_some()
     }
+
+    pub(crate) fn crypto(&self) -> Result<&dyn CryptoProvider> {
+        crypto_provider(self.crypto.as_deref())
+    }
 }
 
 struct SessionCredential<'a> {
-    credential: Option<Arc<AwsCredential>>,
+    credential: Arc<AwsCredential>,
     session_token: bool,
     config: &'a S3Config,
 }
 
 impl SessionCredential<'_> {
-    fn authorizer(&self) -> Option<AwsAuthorizer<'_>> {
+    fn authorizer(&self) -> Result<AwsAuthorizer<'_>> {
         let mut authorizer =
-            AwsAuthorizer::new(self.credential.as_deref()?, "s3", 
&self.config.region)
+            AwsAuthorizer::new(self.credential.as_ref(), "s3", 
&self.config.region)
                 .with_sign_payload(self.config.sign_payload)
-                .with_request_payer(self.config.request_payer);
+                .with_request_payer(self.config.request_payer)
+                .with_crypto(self.config.crypto()?);
 
         if self.session_token {
             let token = HeaderName::from_static("x-amz-s3session-token");
             authorizer = authorizer.with_token_header(token)
         }
 
-        Some(authorizer)
+        Ok(authorizer)
     }
 }
 
@@ -293,12 +299,12 @@ impl From<RequestError> for crate::Error {
     }
 }
 
-/// A builder for a request allowing customisation of the headers and query 
string
+/// A builder for a request allowing customization of the headers and query 
string
 pub(crate) struct Request<'a> {
     path: &'a Path,
     config: &'a S3Config,
     builder: HttpRequestBuilder,
-    payload_sha256: Option<digest::Digest>,
+    payload_sha256: Option<[u8; 32]>,
     payload: Option<PutPayload>,
     use_session_creds: bool,
     idempotent: bool,
@@ -399,26 +405,30 @@ impl Request<'_> {
         Self { builder, ..self }
     }
 
-    pub(crate) fn with_payload(mut self, payload: PutPayload) -> Self {
-        use std::cell::LazyCell;
-
-        let sha256_digest = LazyCell::new(|| {
-            let mut sha256 = Context::new(&digest::SHA256);
+    pub(crate) fn with_payload(mut self, payload: PutPayload) -> Result<Self> {
+        let mut cached_digest: Option<[u8; 32]> = None;
+        let mut sha256_digest = || -> Result<[u8; 32]> {
+            if let Some(digest) = cached_digest {
+                return Ok(digest);
+            }
+            let mut ctx = 
self.config.crypto()?.digest(DigestAlgorithm::Sha256)?;
             for part in &payload {
-                sha256.update(part);
+                ctx.update(part);
             }
-            sha256.finish()
-        });
+            let digest = ctx.finish()?.try_into().unwrap();
+            cached_digest = Some(digest);
+            Ok(digest)
+        };
 
         if !self.config.skip_signature && self.config.sign_payload {
-            self.payload_sha256 = Some(*sha256_digest);
+            self.payload_sha256 = Some(sha256_digest()?);
         }
 
         match self.config.checksum {
             Some(Checksum::SHA256) => {
                 self.builder = self
                     .builder
-                    .header(SHA256_CHECKSUM, 
BASE64_STANDARD.encode(*sha256_digest));
+                    .header(SHA256_CHECKSUM, 
BASE64_STANDARD.encode(sha256_digest()?));
             }
             Some(Checksum::CRC64NVME) => {
                 let crc_algo = crc_fast::CrcAlgorithm::Crc64Nvme;
@@ -437,24 +447,28 @@ impl Request<'_> {
         let content_length = payload.content_length();
         self.builder = self.builder.header(CONTENT_LENGTH, content_length);
         self.payload = Some(payload);
-        self
+        Ok(self)
     }
 
     pub(crate) async fn send(self) -> Result<HttpResponse, RequestError> {
         let credential = match self.use_session_creds {
             true => self.config.get_session_credential().await?,
-            false => SessionCredential {
-                credential: self.config.get_credential().await?,
-                session_token: false,
-                config: self.config,
-            },
+            false => {
+                let credential = self.config.get_credential().await?;
+                credential.map(|credential| SessionCredential {
+                    credential,
+                    session_token: false,
+                    config: self.config,
+                })
+            }
         };
+        let authorizer = credential.as_ref().map(|x| 
x.authorizer()).transpose()?;
 
         let sha = self.payload_sha256.as_ref().map(|x| x.as_ref());
 
         let path = self.path.as_ref();
         self.builder
-            .with_aws_sigv4(credential.authorizer(), sha)
+            .with_aws_sigv4(authorizer, sha)?
             .retryable(&self.config.retry_config)
             .retry_on_conflict(self.retry_on_conflict)
             .idempotent(self.idempotent)
@@ -520,6 +534,7 @@ impl S3Client {
         }
 
         let credential = self.config.get_session_credential().await?;
+        let authorizer = credential.as_ref().map(|x| 
x.authorizer()).transpose()?;
         let url = format!("{}?delete", self.config.bucket_endpoint);
 
         let mut buffer = Vec::new();
@@ -566,7 +581,11 @@ impl S3Client {
             builder = builder.headers(headers.clone());
         }
 
-        let digest = digest::digest(&digest::SHA256, &body);
+        let crypto = self.config.crypto()?;
+        let mut ctx = crypto.digest(DigestAlgorithm::Sha256)?;
+        ctx.update(body.as_ref());
+        let digest = ctx.finish()?;
+
         builder = builder.header(SHA256_CHECKSUM, 
BASE64_STANDARD.encode(digest));
 
         // S3 *requires* DeleteObjects to include a Content-MD5 header:
@@ -580,7 +599,7 @@ impl S3Client {
         let response = builder
             .header(CONTENT_TYPE, "application/xml")
             .body(body)
-            .with_aws_sigv4(credential.authorizer(), Some(digest.as_ref()))
+            .with_aws_sigv4(authorizer, Some(digest))?
             .retryable(&self.config.retry_config)
             .retry_error_body(true)
             .send()
@@ -735,7 +754,7 @@ impl S3Client {
             .idempotent(true);
 
         request = match data {
-            PutPartPayload::Part(payload) => request.with_payload(payload),
+            PutPartPayload::Part(payload) => request.with_payload(payload)?,
             PutPartPayload::Copy(path) => request.header(
                 "x-amz-copy-source",
                 &format!("{}/{}", self.config.bucket, encode_path(path)),
@@ -831,6 +850,7 @@ impl S3Client {
         let body = quick_xml::se::to_string(&request).unwrap();
 
         let credential = self.config.get_session_credential().await?;
+        let authorizer = credential.as_ref().map(|x| 
x.authorizer()).transpose()?;
         let url = self.config.path_url(location);
 
         let mut builder = self.client.post(url);
@@ -841,7 +861,7 @@ impl S3Client {
         let request = builder
             .query(&[("uploadId", upload_id)])
             .body(body)
-            .with_aws_sigv4(credential.authorizer(), None);
+            .with_aws_sigv4(authorizer, None)?;
 
         let request = match mode {
             CompleteMultipartMode::Overwrite => request,
@@ -882,11 +902,12 @@ impl S3Client {
     #[cfg(test)]
     pub(crate) async fn get_object_tagging(&self, path: &Path) -> 
Result<HttpResponse> {
         let credential = self.config.get_session_credential().await?;
+        let authorizer = credential.as_ref().map(|x| 
x.authorizer()).transpose()?;
         let url = format!("{}?tagging", self.config.path_url(path));
         let response = self
             .client
             .request(Method::GET, url)
-            .with_aws_sigv4(credential.authorizer(), None)
+            .with_aws_sigv4(authorizer, None)?
             .send_retry(&self.config.retry_config)
             .await
             .map_err(|e| e.error(STORE, path.to_string()))?;
@@ -917,6 +938,7 @@ impl GetClient for S3Client {
         options: GetOptions,
     ) -> Result<HttpResponse> {
         let credential = self.config.get_session_credential().await?;
+        let authorizer = credential.as_ref().map(|x| 
x.authorizer()).transpose()?;
         let url = self.config.path_url(path);
         let method = match options.head {
             true => Method::HEAD,
@@ -942,7 +964,7 @@ impl GetClient for S3Client {
 
         let response = builder
             .with_get_options(options)
-            .with_aws_sigv4(credential.authorizer(), None)
+            .with_aws_sigv4(authorizer, None)?
             .retryable_request()
             .send(ctx)
             .await
@@ -961,6 +983,7 @@ impl ListClient for Arc<S3Client> {
         opts: PaginatedListOptions,
     ) -> Result<PaginatedListResult> {
         let credential = self.config.get_session_credential().await?;
+        let authorizer = credential.as_ref().map(|x| 
x.authorizer()).transpose()?;
         let url = self.config.bucket_endpoint.clone();
 
         let mut query = Vec::with_capacity(4);
@@ -994,7 +1017,7 @@ impl ListClient for Arc<S3Client> {
             .request(Method::GET, &url)
             .extensions(opts.extensions)
             .query(&query)
-            .with_aws_sigv4(credential.authorizer(), None)
+            .with_aws_sigv4(authorizer, None)?
             .send_retry(&self.config.retry_config)
             .await
             .map_err(|source| Error::ListRequest { source })?;
@@ -1080,6 +1103,7 @@ mod tests {
             conditional_put: Default::default(),
             encryption_headers: Default::default(),
             request_payer: false,
+            crypto: None,
         };
 
         let client = S3Client::new(config, 
HttpClient::new(reqwest::Client::new()));
@@ -1125,6 +1149,7 @@ mod tests {
             client_options: ClientOptions::new()
                 .with_allow_http(true)
                 .with_default_headers(default_headers),
+            crypto: None,
             skip_signature: false,
             session_provider: None,
             retry_config: Default::default(),
@@ -1157,6 +1182,7 @@ mod tests {
         let result = client
             .request(Method::PUT, &Path::from("test"))
             .with_payload(PutPayload::default())
+            .unwrap()
             .do_put()
             .await;
 
diff --git a/src/aws/credential.rs b/src/aws/credential.rs
index df2ea67..c16330a 100644
--- a/src/aws/credential.rs
+++ b/src/aws/credential.rs
@@ -19,8 +19,11 @@ use crate::aws::{AwsCredentialProvider, STORE, 
STRICT_ENCODE_SET, STRICT_PATH_EN
 use crate::client::builder::HttpRequestBuilder;
 use crate::client::retry::RetryExt;
 use crate::client::token::{TemporaryToken, TokenCache};
-use crate::client::{HttpClient, HttpError, HttpRequest, TokenProvider};
-use crate::util::{hex_digest, hex_encode, hmac_sha256};
+use crate::client::{
+    CryptoProvider, DigestAlgorithm, HttpClient, HttpError, HttpRequest, 
TokenProvider,
+    crypto_provider,
+};
+use crate::util::{hex_digest, hex_encode};
 use crate::{CredentialProvider, Result, RetryConfig};
 use async_trait::async_trait;
 use bytes::Buf;
@@ -91,13 +94,33 @@ impl AwsCredential {
     /// Signs a string
     ///
     /// 
<https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html>
-    fn sign(&self, to_sign: &str, date: DateTime<Utc>, region: &str, service: 
&str) -> String {
+    fn sign(
+        &self,
+        crypto: &dyn CryptoProvider,
+        to_sign: &str,
+        date: DateTime<Utc>,
+        region: &str,
+        service: &str,
+    ) -> Result<String> {
         let date_string = date.format("%Y%m%d").to_string();
-        let date_hmac = hmac_sha256(format!("AWS4{}", self.secret_key), 
date_string);
-        let region_hmac = hmac_sha256(date_hmac, region);
-        let service_hmac = hmac_sha256(region_hmac, service);
-        let signing_hmac = hmac_sha256(service_hmac, b"aws4_request");
-        hex_encode(hmac_sha256(signing_hmac, to_sign).as_ref())
+        let secret_key = format!("AWS4{}", self.secret_key);
+
+        let mut ctx = crypto.hmac(DigestAlgorithm::Sha256, 
secret_key.as_bytes())?;
+        ctx.update(date_string.as_bytes());
+
+        let mut ctx = crypto.hmac(DigestAlgorithm::Sha256, ctx.finish()?)?;
+        ctx.update(region.as_bytes());
+
+        let mut ctx = crypto.hmac(DigestAlgorithm::Sha256, ctx.finish()?)?;
+        ctx.update(service.as_bytes());
+
+        let mut ctx = crypto.hmac(DigestAlgorithm::Sha256, ctx.finish()?)?;
+        ctx.update(b"aws4_request");
+
+        let mut ctx = crypto.hmac(DigestAlgorithm::Sha256, ctx.finish()?)?;
+        ctx.update(to_sign.as_bytes());
+
+        Ok(hex_encode(ctx.finish()?))
     }
 }
 
@@ -108,6 +131,7 @@ impl AwsCredential {
 pub struct AwsAuthorizer<'a> {
     date: Option<DateTime<Utc>>,
     credential: &'a AwsCredential,
+    crypto: Option<&'a dyn CryptoProvider>,
     service: &'a str,
     region: &'a str,
     token_header: Option<HeaderName>,
@@ -129,6 +153,7 @@ impl<'a> AwsAuthorizer<'a> {
             credential,
             service,
             region,
+            crypto: None,
             date: None,
             sign_payload: true,
             token_header: None,
@@ -157,6 +182,24 @@ impl<'a> AwsAuthorizer<'a> {
         self
     }
 
+    /// Specify the crypto provider
+    pub fn with_crypto(mut self, crypto: &'a dyn CryptoProvider) -> Self {
+        self.crypto = Some(crypto);
+        self
+    }
+
+    /// Authorize `request` with an optional pre-calculated SHA256 digest by 
attaching
+    /// the relevant [AWS SigV4] headers
+    ///
+    /// # Panics
+    ///
+    /// Panics on cryptography error
+    ///
+    #[deprecated(note = "use AwsAuthorized::try_authorize")]
+    pub fn authorize(&self, request: &mut HttpRequest, pre_calculated_digest: 
Option<&[u8]>) {
+        self.try_authorize(request, pre_calculated_digest).unwrap()
+    }
+
     /// Authorize `request` with an optional pre-calculated SHA256 digest by 
attaching
     /// the relevant [AWS SigV4] headers
     ///
@@ -170,7 +213,12 @@ impl<'a> AwsAuthorizer<'a> {
     /// * Otherwise it is set to the hex encoded SHA256 of the request body
     ///
     /// [AWS SigV4]: 
https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html
-    pub fn authorize(&self, request: &mut HttpRequest, pre_calculated_digest: 
Option<&[u8]>) {
+    pub fn try_authorize(
+        &self,
+        request: &mut HttpRequest,
+        pre_calculated_digest: Option<&[u8]>,
+    ) -> Result<()> {
+        let crypto = crypto_provider(self.crypto)?;
         let url = Url::parse(&request.uri().to_string()).unwrap();
 
         if let Some(ref token) = self.credential.token {
@@ -195,7 +243,7 @@ impl<'a> AwsAuthorizer<'a> {
                 None => match request.body().is_empty() {
                     true => EMPTY_SHA256_HASH.to_string(),
                     false => match request.body().as_bytes() {
-                        Some(bytes) => hex_digest(bytes),
+                        Some(bytes) => hex_digest(crypto, bytes)?,
                         None => STREAMING_PAYLOAD.to_string(),
                     },
                 },
@@ -219,6 +267,7 @@ impl<'a> AwsAuthorizer<'a> {
         let scope = self.scope(date);
 
         let string_to_sign = self.string_to_sign(
+            crypto,
             date,
             &scope,
             request.method(),
@@ -226,12 +275,12 @@ impl<'a> AwsAuthorizer<'a> {
             &canonical_headers,
             &signed_headers,
             &digest,
-        );
+        )?;
 
         // sign the string
-        let signature = self
-            .credential
-            .sign(&string_to_sign, date, self.region, self.service);
+        let signature =
+            self.credential
+                .sign(crypto, &string_to_sign, date, self.region, 
self.service)?;
 
         // build the actual auth header
         let authorisation = format!(
@@ -243,9 +292,12 @@ impl<'a> AwsAuthorizer<'a> {
         request
             .headers_mut()
             .insert(&AUTHORIZATION, authorization_val);
+        Ok(())
     }
 
-    pub(crate) fn sign(&self, method: Method, url: &mut Url, expires_in: 
Duration) {
+    pub(crate) fn sign(&self, method: Method, url: &mut Url, expires_in: 
Duration) -> Result<()> {
+        let crypto = crypto_provider(self.crypto)?;
+
         let date = self.date.unwrap_or_else(Utc::now);
         let scope = self.scope(date);
 
@@ -285,6 +337,7 @@ impl<'a> AwsAuthorizer<'a> {
         let (signed_headers, canonical_headers) = 
canonicalize_headers(&headers);
 
         let string_to_sign = self.string_to_sign(
+            crypto,
             date,
             &scope,
             &method,
@@ -292,19 +345,21 @@ impl<'a> AwsAuthorizer<'a> {
             &canonical_headers,
             &signed_headers,
             digest,
-        );
+        )?;
 
-        let signature = self
-            .credential
-            .sign(&string_to_sign, date, self.region, self.service);
+        let signature =
+            self.credential
+                .sign(crypto, &string_to_sign, date, self.region, 
self.service)?;
 
         url.query_pairs_mut()
             .append_pair("X-Amz-Signature", &signature);
+        Ok(())
     }
 
     #[allow(clippy::too_many_arguments)]
     fn string_to_sign(
         &self,
+        crypto: &dyn CryptoProvider,
         date: DateTime<Utc>,
         scope: &str,
         request_method: &Method,
@@ -312,7 +367,7 @@ impl<'a> AwsAuthorizer<'a> {
         canonical_headers: &str,
         signed_headers: &str,
         digest: &str,
-    ) -> String {
+    ) -> Result<String> {
         // Each path segment must be URI-encoded twice (except for Amazon S3 
which only gets
         // URI-encoded once).
         // see 
https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
@@ -334,15 +389,15 @@ impl<'a> AwsAuthorizer<'a> {
             digest
         );
 
-        let hashed_canonical_request = 
hex_digest(canonical_request.as_bytes());
+        let hashed_canonical_request = hex_digest(crypto, 
canonical_request.as_bytes())?;
 
-        format!(
+        Ok(format!(
             "{}\n{}\n{}\n{}",
             ALGORITHM,
             date.format("%Y%m%dT%H%M%SZ"),
             scope,
             hashed_canonical_request
-        )
+        ))
     }
 
     fn scope(&self, date: DateTime<Utc>) -> String {
@@ -355,13 +410,13 @@ impl<'a> AwsAuthorizer<'a> {
     }
 }
 
-pub(crate) trait CredentialExt {
+pub(crate) trait CredentialExt: Sized {
     /// Sign a request 
<https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html>
     fn with_aws_sigv4(
         self,
         authorizer: Option<AwsAuthorizer<'_>>,
         payload_sha256: Option<&[u8]>,
-    ) -> Self;
+    ) -> Result<Self>;
 }
 
 impl CredentialExt for HttpRequestBuilder {
@@ -369,16 +424,16 @@ impl CredentialExt for HttpRequestBuilder {
         self,
         authorizer: Option<AwsAuthorizer<'_>>,
         payload_sha256: Option<&[u8]>,
-    ) -> Self {
+    ) -> Result<Self> {
         match authorizer {
             Some(authorizer) => {
                 let (client, request) = self.into_parts();
                 let mut request = request.expect("request valid");
-                authorizer.authorize(&mut request, payload_sha256);
+                authorizer.try_authorize(&mut request, payload_sha256)?;
 
-                Self::from_parts(client, request)
+                Ok(Self::from_parts(client, request))
             }
-            None => self,
+            None => Ok(self),
         }
     }
 }
@@ -816,6 +871,7 @@ pub(crate) struct SessionProvider {
     pub endpoint: String,
     pub region: String,
     pub credentials: AwsCredentialProvider,
+    pub crypto: Option<Arc<dyn CryptoProvider>>,
 }
 
 #[async_trait]
@@ -827,12 +883,13 @@ impl TokenProvider for SessionProvider {
         client: &HttpClient,
         retry: &RetryConfig,
     ) -> Result<TemporaryToken<Arc<Self::Credential>>> {
+        let crypto = crypto_provider(self.crypto.as_deref())?;
         let creds = self.credentials.get_credential().await?;
-        let authorizer = AwsAuthorizer::new(&creds, "s3", &self.region);
+        let authorizer = AwsAuthorizer::new(&creds, "s3", 
&self.region).with_crypto(crypto);
 
         let bytes = client
             .get(format!("{}?session", self.endpoint))
-            .with_aws_sigv4(Some(authorizer), None)
+            .with_aws_sigv4(Some(authorizer), None)?
             .send_retry(retry)
             .await
             .map_err(|source| Error::CreateSessionRequest { source })?
@@ -901,6 +958,7 @@ mod tests {
 
         let signer = AwsAuthorizer {
             date: Some(date),
+            crypto: None,
             credential: &credential,
             service: "ec2",
             region: "us-east-1",
@@ -909,7 +967,7 @@ mod tests {
             request_payer: false,
         };
 
-        signer.authorize(&mut request, None);
+        signer.try_authorize(&mut request, None).unwrap();
         assert_eq!(
             request.headers().get(&AUTHORIZATION).unwrap(),
             "AWS4-HMAC-SHA256 
Credential=AKIAIOSFODNN7EXAMPLE/20220806/us-east-1/ec2/aws4_request, 
SignedHeaders=host;x-amz-content-sha256;x-amz-date, 
Signature=a3c787a7ed37f7fdfbfd2d7056a3d7c9d85e6d52a2bfbec73793c0be6e7862d4"
@@ -946,6 +1004,7 @@ mod tests {
 
         let signer = AwsAuthorizer {
             date: Some(date),
+            crypto: None,
             credential: &credential,
             service: "ec2",
             region: "us-east-1",
@@ -954,7 +1013,7 @@ mod tests {
             request_payer: true,
         };
 
-        signer.authorize(&mut request, None);
+        signer.try_authorize(&mut request, None).unwrap();
         assert_eq!(
             request.headers().get(&AUTHORIZATION).unwrap(),
             "AWS4-HMAC-SHA256 
Credential=AKIAIOSFODNN7EXAMPLE/20220806/us-east-1/ec2/aws4_request, 
SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-request-payer, 
Signature=7030625a9e9b57ed2a40e63d749f4a4b7714b6e15004cab026152f870dd8565d"
@@ -991,6 +1050,7 @@ mod tests {
 
         let authorizer = AwsAuthorizer {
             date: Some(date),
+            crypto: None,
             credential: &credential,
             service: "ec2",
             region: "us-east-1",
@@ -999,7 +1059,7 @@ mod tests {
             request_payer: false,
         };
 
-        authorizer.authorize(&mut request, None);
+        authorizer.try_authorize(&mut request, None).unwrap();
         assert_eq!(
             request.headers().get(&AUTHORIZATION).unwrap(),
             "AWS4-HMAC-SHA256 
Credential=AKIAIOSFODNN7EXAMPLE/20220806/us-east-1/ec2/aws4_request, 
SignedHeaders=host;x-amz-content-sha256;x-amz-date, 
Signature=653c3d8ea261fd826207df58bc2bb69fbb5003e9eb3c0ef06e4a51f2a81d8699"
@@ -1021,6 +1081,7 @@ mod tests {
 
         let authorizer = AwsAuthorizer {
             date: Some(date),
+            crypto: None,
             credential: &credential,
             service: "s3",
             region: "us-east-1",
@@ -1030,7 +1091,9 @@ mod tests {
         };
 
         let mut url = 
Url::parse("https://examplebucket.s3.amazonaws.com/test.txt";).unwrap();
-        authorizer.sign(Method::GET, &mut url, Duration::from_secs(86400));
+        authorizer
+            .sign(Method::GET, &mut url, Duration::from_secs(86400))
+            .unwrap();
 
         assert_eq!(
             url,
@@ -1062,6 +1125,7 @@ mod tests {
 
         let authorizer = AwsAuthorizer {
             date: Some(date),
+            crypto: None,
             credential: &credential,
             service: "s3",
             region: "us-east-1",
@@ -1071,7 +1135,9 @@ mod tests {
         };
 
         let mut url = 
Url::parse("https://examplebucket.s3.amazonaws.com/test.txt";).unwrap();
-        authorizer.sign(Method::GET, &mut url, Duration::from_secs(86400));
+        authorizer
+            .sign(Method::GET, &mut url, Duration::from_secs(86400))
+            .unwrap();
 
         assert_eq!(
             url,
@@ -1118,6 +1184,7 @@ mod tests {
 
         let authorizer = AwsAuthorizer {
             date: Some(date),
+            crypto: None,
             credential: &credential,
             service: "s3",
             region: "us-east-1",
@@ -1126,7 +1193,7 @@ mod tests {
             request_payer: false,
         };
 
-        authorizer.authorize(&mut request, None);
+        authorizer.try_authorize(&mut request, None).unwrap();
         assert_eq!(
             request.headers().get(&AUTHORIZATION).unwrap(),
             "AWS4-HMAC-SHA256 
Credential=H20ABqCkLZID4rLe/20220809/us-east-1/s3/aws4_request, 
SignedHeaders=host;x-amz-content-sha256;x-amz-date, 
Signature=9ebf2f92872066c99ac94e573b4e1b80f4dbb8a32b1e8e23178318746e7d1b4d"
diff --git a/src/aws/mod.rs b/src/aws/mod.rs
index c4d8063..149d9e2 100644
--- a/src/aws/mod.rs
+++ b/src/aws/mod.rs
@@ -141,9 +141,11 @@ impl Signer for AmazonS3 {
     /// # }
     /// ```
     async fn signed_url(&self, method: Method, path: &Path, expires_in: 
Duration) -> Result<Url> {
+        let crypto = self.client.config.crypto()?;
         let credential = self.credentials().get_credential().await?;
         let authorizer = AwsAuthorizer::new(&credential, "s3", 
&self.client.config.region)
-            .with_request_payer(self.client.config.request_payer);
+            .with_request_payer(self.client.config.request_payer)
+            .with_crypto(crypto);
 
         let path_url = self.path_url(path);
         let mut url = path_url.parse().map_err(|e| Error::Generic {
@@ -151,7 +153,7 @@ impl Signer for AmazonS3 {
             source: format!("Unable to parse url {path_url}: {e}").into(),
         })?;
 
-        authorizer.sign(method, &mut url, expires_in);
+        authorizer.sign(method, &mut url, expires_in)?;
 
         Ok(url)
     }
@@ -175,7 +177,7 @@ impl ObjectStore for AmazonS3 {
         let request = self
             .client
             .request(Method::PUT, location)
-            .with_payload(payload)
+            .with_payload(payload)?
             .with_attributes(attributes)
             .with_tags(tags)
             .with_extensions(extensions)
diff --git a/src/azure/builder.rs b/src/azure/builder.rs
index 64a44b8..c1f0d72 100644
--- a/src/azure/builder.rs
+++ b/src/azure/builder.rs
@@ -21,7 +21,7 @@ use crate::azure::credential::{
     ImdsManagedIdentityProvider, WorkloadIdentityOAuthProvider,
 };
 use crate::azure::{AzureCredential, AzureCredentialProvider, MicrosoftAzure, 
STORE};
-use crate::client::{HttpConnector, TokenCredentialProvider, http_connector};
+use crate::client::{CryptoProvider, HttpConnector, TokenCredentialProvider, 
http_connector};
 use crate::config::ConfigValue;
 use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, 
StaticCredentialProvider};
 use percent_encoding::percent_decode_str;
@@ -167,6 +167,8 @@ pub struct MicrosoftAzureBuilder {
     client_options: ClientOptions,
     /// Credentials
     credentials: Option<AzureCredentialProvider>,
+    /// The [`CryptoProvider`] to use
+    crypto: Option<Arc<dyn CryptoProvider>>,
     /// Skip signing requests
     skip_signature: ConfigValue<bool>,
     /// When set to true, fabric url scheme will be used
@@ -840,6 +842,12 @@ impl MicrosoftAzureBuilder {
         self
     }
 
+    /// The [`CryptoProvider`] to use
+    pub fn with_crypto_provider(mut self, provider: Arc<dyn CryptoProvider>) 
-> Self {
+        self.crypto = Some(provider);
+        self
+    }
+
     /// Set if the Azure emulator should be used (defaults to false)
     pub fn with_use_emulator(mut self, use_emulator: bool) -> Self {
         self.use_emulator = use_emulator.into();
@@ -1117,14 +1125,14 @@ impl MicrosoftAzureBuilder {
         };
 
         let encryption_headers =
-            
AzureEncryptionHeaders::try_new(self.encryption_key).map_err(|source| {
-                Error::InvalidEncryptionKey {
+            AzureEncryptionHeaders::try_new(self.crypto.as_deref(), 
self.encryption_key).map_err(
+                |source| Error::InvalidEncryptionKey {
                     source: match source {
                         crate::Error::Generic { source, .. } => source,
                         other => Box::new(other),
                     },
-                }
-            })?;
+                },
+            )?;
 
         let config = AzureConfig {
             account,
@@ -1136,6 +1144,7 @@ impl MicrosoftAzureBuilder {
             client_options: self.client_options,
             service: storage_url,
             credentials: auth,
+            crypto: self.crypto,
             encryption_headers,
         };
 
diff --git a/src/azure/client.rs b/src/azure/client.rs
index 3c76b1c..1c21fa9 100644
--- a/src/azure/client.rs
+++ b/src/azure/client.rs
@@ -23,7 +23,10 @@ use crate::client::get::GetClient;
 use crate::client::header::{HeaderConfig, get_put_result};
 use crate::client::list::ListClient;
 use crate::client::retry::{RetryContext, RetryExt};
-use crate::client::{GetOptionsExt, HttpClient, HttpError, HttpRequest, 
HttpResponse};
+use crate::client::{
+    CryptoProvider, DigestAlgorithm, GetOptionsExt, HttpClient, HttpError, 
HttpRequest,
+    HttpResponse, crypto_provider,
+};
 use crate::list::{PaginatedListOptions, PaginatedListResult};
 use crate::multipart::PartId;
 use crate::util::{GetRange, deserialize_rfc1123};
@@ -41,7 +44,6 @@ use http::{
     header::{CONTENT_LENGTH, CONTENT_TYPE, HeaderMap, HeaderValue, IF_MATCH, 
IF_NONE_MATCH},
 };
 use rand::RngExt;
-use ring::digest;
 use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 use std::sync::Arc;
@@ -180,6 +182,7 @@ impl From<Error> for crate::Error {
 pub(crate) struct AzureConfig {
     pub account: String,
     pub container: String,
+    pub crypto: Option<Arc<dyn CryptoProvider>>,
     pub credentials: AzureCredentialProvider,
     pub retry_config: RetryConfig,
     pub service: Url,
@@ -250,7 +253,10 @@ impl std::fmt::Debug for AzureEncryptionHeaders {
 }
 
 impl AzureEncryptionHeaders {
-    pub(crate) fn try_new(encryption_key: Option<String>) -> Result<Self> {
+    pub(crate) fn try_new(
+        crypto: Option<&dyn CryptoProvider>,
+        encryption_key: Option<String>,
+    ) -> Result<Self> {
         let Some(encryption_key) = encryption_key else {
             return Ok(Self::default());
         };
@@ -275,8 +281,10 @@ impl AzureEncryptionHeaders {
             });
         }
 
-        let encryption_key_sha256 =
-            BASE64_STANDARD.encode(digest::digest(&digest::SHA256, 
&decoded_key));
+        let crypto = crypto_provider(crypto)?;
+        let mut ctx = crypto.digest(DigestAlgorithm::Sha256)?;
+        ctx.update(&decoded_key);
+        let encryption_key_sha256 = BASE64_STANDARD.encode(ctx.finish()?);
 
         Ok(Self {
             encryption_key: Some(encryption_key),
@@ -416,11 +424,12 @@ impl PutRequest<'_> {
     async fn send(self) -> Result<HttpResponse> {
         let credential = self.config.get_credential().await?;
         let sensitive = self.config.is_sensitive(&credential);
+        let crypto = self.config.crypto.as_deref();
         let response = self
             .builder
             .with_azure_encryption_headers(&self.config.encryption_headers)
             .header(CONTENT_LENGTH, self.payload.content_length())
-            .with_azure_authorization(&credential, &self.config.account)
+            .with_azure_authorization(crypto, &credential, 
&self.config.account)?
             .retryable(&self.config.retry_config)
             .sensitive(sensitive)
             .idempotent(self.idempotent)
@@ -674,6 +683,10 @@ impl AzureClient {
         self.config.get_credential().await
     }
 
+    pub(crate) fn crypto(&self) -> Option<&dyn CryptoProvider> {
+        self.config.crypto.as_deref()
+    }
+
     fn put_request<'a>(&'a self, path: &'a Path, payload: PutPayload) -> 
PutRequest<'a> {
         let url = self.config.path_url(path);
         let builder = self.client.request(Method::PUT, url.as_str());
@@ -783,7 +796,8 @@ impl AzureClient {
         boundary: &str,
         paths: &[Path],
         credential: &Option<Arc<AzureCredential>>,
-    ) -> Vec<u8> {
+    ) -> Result<Vec<u8>> {
+        let crypto = self.crypto();
         let mut body_bytes = Vec::with_capacity(paths.len() * 2048);
 
         for (idx, path) in paths.iter().enumerate() {
@@ -799,7 +813,7 @@ impl AzureClient {
                 // Each subrequest must be authorized individually [1] and we 
use
                 // the CredentialExt for this.
                 // [1]: 
https://learn.microsoft.com/en-us/rest/api/storageservices/blob-batch?tabs=microsoft-entra-id#request-body
-                .with_azure_authorization(credential, &self.config.account)
+                .with_azure_authorization(crypto, credential, 
&self.config.account)?
                 .into_parts()
                 .1
                 .unwrap();
@@ -817,7 +831,7 @@ impl AzureClient {
         extend(&mut body_bytes, boundary.as_bytes());
         extend(&mut body_bytes, b"--");
         extend(&mut body_bytes, b"\r\n");
-        body_bytes
+        Ok(body_bytes)
     }
 
     pub(crate) async fn bulk_delete_request(&self, paths: Vec<Path>) -> 
Result<Vec<Result<Path>>> {
@@ -831,7 +845,7 @@ impl AzureClient {
         let random_bytes = rand::random::<[u8; 16]>(); // 128 bits
         let boundary = format!("batch_{}", 
BASE64_STANDARD_NO_PAD.encode(random_bytes));
 
-        let body_bytes = self.build_bulk_delete_body(&boundary, &paths, 
&credential);
+        let body_bytes = self.build_bulk_delete_body(&boundary, &paths, 
&credential)?;
 
         // Send multipart request
         let url = self.config.path_url(&Path::from("/"));
@@ -847,7 +861,7 @@ impl AzureClient {
             )
             .header(CONTENT_LENGTH, HeaderValue::from(body_bytes.len()))
             .body(body_bytes)
-            .with_azure_authorization(&credential, &self.config.account)
+            .with_azure_authorization(self.crypto(), &credential, 
&self.config.account)?
             .retryable(&self.config.retry_config)
             .sensitive(sensitive)
             .send()
@@ -901,7 +915,11 @@ impl AzureClient {
                         signed_expiry,
                         None,
                     )
-                    .sign(&Method::GET, &mut source)?;
+                    .sign(
+                        crypto_provider(self.crypto())?,
+                        &Method::GET,
+                        &mut source,
+                    )?;
                 }
                 Some(AzureCredential::BearerToken(token)) => {
                     source_authorization = Some(format!("Bearer {token}"));
@@ -935,7 +953,7 @@ impl AzureClient {
 
         let sensitive = self.config.is_sensitive(&credential);
         builder
-            .with_azure_authorization(&credential, &self.config.account)
+            .with_azure_authorization(self.crypto(), &credential, 
&self.config.account)?
             .retryable(&self.config.retry_config)
             .sensitive(sensitive)
             .idempotent(overwrite)
@@ -973,7 +991,7 @@ impl AzureClient {
             .post(url.as_str())
             .body(body)
             .query(&[("restype", "service"), ("comp", "userdelegationkey")])
-            .with_azure_authorization(&credential, &self.config.account)
+            .with_azure_authorization(self.crypto(), &credential, 
&self.config.account)?
             .retryable(&self.config.retry_config)
             .sensitive(sensitive)
             .idempotent(true)
@@ -1036,7 +1054,7 @@ impl AzureClient {
             .client
             .get(url.as_str())
             .query(&[("comp", "tags")])
-            .with_azure_authorization(&credential, &self.config.account)
+            .with_azure_authorization(self.crypto(), &credential, 
&self.config.account)?
             .retryable(&self.config.retry_config)
             .sensitive(sensitive)
             .send()
@@ -1105,7 +1123,7 @@ impl GetClient for AzureClient {
 
         let response = builder
             .with_get_options(options)
-            .with_azure_authorization(&credential, &self.config.account)
+            .with_azure_authorization(self.crypto(), &credential, 
&self.config.account)?
             .retryable_request()
             .sensitive(sensitive)
             .send(ctx)
@@ -1173,7 +1191,7 @@ impl ListClient for Arc<AzureClient> {
             .get(url.as_str())
             .extensions(opts.extensions)
             .query(&query)
-            .with_azure_authorization(&credential, &self.config.account)
+            .with_azure_authorization(self.crypto(), &credential, 
&self.config.account)?
             .retryable(&self.config.retry_config)
             .sensitive(sensitive)
             .send()
@@ -1468,8 +1486,7 @@ mod tests {
     <NextMarker />
 </EnumerationResults>";
 
-        let mut _list_blobs_response_internal: ListResultInternal =
-            quick_xml::de::from_str(S).unwrap();
+        let _list_blobs_response_internal: ListResultInternal = 
quick_xml::de::from_str(S).unwrap();
     }
 
     #[test]
@@ -1588,15 +1605,17 @@ mod tests {
             account: "testaccount".to_string(),
             container: "testcontainer".to_string(),
             credentials: credential_provider,
+            crypto: None,
             service: "http://example.com".try_into().unwrap(),
             retry_config: Default::default(),
             is_emulator: false,
             skip_signature: false,
             disable_tagging: false,
             client_options: Default::default(),
-            encryption_headers: AzureEncryptionHeaders::try_new(Some(
-                BASE64_STANDARD.encode([7_u8; 32]),
-            ))
+            encryption_headers: AzureEncryptionHeaders::try_new(
+                None,
+                Some(BASE64_STANDARD.encode([7_u8; 32])),
+            )
             .unwrap(),
         };
 
@@ -1607,7 +1626,9 @@ mod tests {
 
         let boundary = "batch_statictestboundary".to_string();
 
-        let body_bytes = client.build_bulk_delete_body(&boundary, paths, 
&credential);
+        let body_bytes = client
+            .build_bulk_delete_body(&boundary, paths, &credential)
+            .unwrap();
 
         // Replace Date header value with a static date
         let re = Regex::new("Date:[^\r]+").unwrap();
@@ -1660,7 +1681,7 @@ Authorization: Bearer static-token\r
     #[test]
     fn test_azure_encryption_headers_debug_redacts_key() {
         let encryption_key = BASE64_STANDARD.encode([7_u8; 32]);
-        let headers = 
AzureEncryptionHeaders::try_new(Some(encryption_key.clone())).unwrap();
+        let headers = AzureEncryptionHeaders::try_new(None, 
Some(encryption_key.clone())).unwrap();
         let encryption_key_sha256 = 
headers.encryption_key_sha256.clone().unwrap();
 
         let debug = format!("{headers:?}");
@@ -1674,7 +1695,7 @@ Authorization: Bearer static-token\r
     #[test]
     fn test_azure_sensitive_headers_redact_client_request_debug() {
         let encryption_key = BASE64_STANDARD.encode([7_u8; 32]);
-        let headers = 
AzureEncryptionHeaders::try_new(Some(encryption_key.clone())).unwrap();
+        let headers = AzureEncryptionHeaders::try_new(None, 
Some(encryption_key.clone())).unwrap();
         let encryption_key_sha256 = 
headers.encryption_key_sha256.clone().unwrap();
         let copy_source = 
"http://example.com/source.txt?sig=secret-source-sas";;
         let source_authorization = "Bearer static-token";
diff --git a/src/azure/credential.rs b/src/azure/credential.rs
index 111c908..f4c2989 100644
--- a/src/azure/credential.rs
+++ b/src/azure/credential.rs
@@ -21,8 +21,10 @@ use crate::azure::STORE;
 use crate::client::builder::{HttpRequestBuilder, add_query_pairs};
 use crate::client::retry::RetryExt;
 use crate::client::token::{TemporaryToken, TokenCache};
-use crate::client::{CredentialProvider, HttpClient, HttpError, HttpRequest, 
TokenProvider};
-use crate::util::hmac_sha256;
+use crate::client::{
+    CredentialProvider, CryptoProvider, DigestAlgorithm, HttpClient, 
HttpError, HttpRequest,
+    TokenProvider, crypto_provider,
+};
 use async_trait::async_trait;
 use base64::Engine;
 use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD};
@@ -187,7 +189,12 @@ impl AzureSigner {
         }
     }
 
-    pub(crate) fn sign(&self, method: &Method, url: &mut Url) -> Result<()> {
+    pub(crate) fn sign(
+        &self,
+        crypto: &dyn CryptoProvider,
+        method: &Method,
+        url: &mut Url,
+    ) -> crate::Result<()> {
         let (str_to_sign, query_pairs) = match &self.delegation_key {
             Some(delegation_key) => string_to_sign_user_delegation_sas(
                 url,
@@ -199,7 +206,10 @@ impl AzureSigner {
             ),
             None => string_to_sign_service_sas(url, method, &self.account, 
&self.start, &self.end),
         };
-        let auth = hmac_sha256(&self.signing_key.0, str_to_sign);
+        let mut ctx = crypto.hmac(DigestAlgorithm::Sha256, 
&self.signing_key.0)?;
+        ctx.update(str_to_sign.as_bytes());
+        let auth = ctx.finish()?;
+
         url.query_pairs_mut().extend_pairs(query_pairs);
         url.query_pairs_mut()
             .append_pair("sig", BASE64_STANDARD.encode(auth).as_str());
@@ -227,6 +237,7 @@ fn add_date_and_version_headers(request: &mut HttpRequest) {
 #[derive(Debug)]
 pub struct AzureAuthorizer<'a> {
     credential: &'a AzureCredential,
+    crypto: Option<&'a dyn CryptoProvider>,
     account: &'a str,
 }
 
@@ -236,23 +247,46 @@ impl<'a> AzureAuthorizer<'a> {
         AzureAuthorizer {
             credential,
             account,
+            crypto: None,
         }
     }
 
+    /// Specify the crypto provider
+    pub fn with_crypto(mut self, crypto: &'a dyn CryptoProvider) -> Self {
+        self.crypto = Some(crypto);
+        self
+    }
+
+    fn crypto(&self) -> crate::Result<&dyn CryptoProvider> {
+        crypto_provider(self.crypto)
+    }
+
     /// Authorize `request`
+    ///
+    /// # Panics
+    ///
+    /// Panics on cryptography error
+    #[deprecated(note = "use AzureAuthorizer::try_authorize")]
     pub fn authorize(&self, request: &mut HttpRequest) {
+        self.try_authorize(request).unwrap()
+    }
+
+    /// Authorize `request`
+    pub fn try_authorize(&self, request: &mut HttpRequest) -> 
crate::Result<()> {
         add_date_and_version_headers(request);
 
         match self.credential {
             AzureCredential::AccessKey(key) => {
+                let crypto = self.crypto()?;
                 let url = Url::parse(&request.uri().to_string()).unwrap();
                 let signature = generate_authorization(
+                    crypto,
                     request.headers(),
                     &url,
                     request.method(),
                     self.account,
                     key,
-                );
+                )?;
 
                 // "signature" is a base 64 encoded string so it should never
                 // contain illegal characters
@@ -271,53 +305,67 @@ impl<'a> AzureAuthorizer<'a> {
                 add_query_pairs(request.uri_mut(), query_pairs);
             }
         }
+        Ok(())
     }
 }
 
-pub(crate) trait CredentialExt {
+pub(crate) trait CredentialExt: Sized {
     /// Apply authorization to requests against azure storage accounts
     /// 
<https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-requests-to-azure-storage>
     fn with_azure_authorization(
         self,
+        crypto: Option<&dyn CryptoProvider>,
         credential: &Option<impl Deref<Target = AzureCredential>>,
         account: &str,
-    ) -> Self;
+    ) -> crate::Result<Self>;
 }
 
 impl CredentialExt for HttpRequestBuilder {
     fn with_azure_authorization(
         self,
+        crypto: Option<&dyn CryptoProvider>,
         credential: &Option<impl Deref<Target = AzureCredential>>,
         account: &str,
-    ) -> Self {
+    ) -> crate::Result<Self> {
         let (client, request) = self.into_parts();
         let mut request = request.expect("request valid");
 
         match credential.as_deref() {
             Some(credential) => {
-                AzureAuthorizer::new(credential, account).authorize(&mut 
request);
+                let mut authorizer = AzureAuthorizer::new(credential, account);
+                if let Some(crypto) = crypto {
+                    authorizer = authorizer.with_crypto(crypto);
+                }
+                authorizer.try_authorize(&mut request)?;
             }
             None => {
                 add_date_and_version_headers(&mut request);
             }
         }
 
-        Self::from_parts(client, request)
+        Ok(Self::from_parts(client, request))
     }
 }
 
 /// Generate signed key for authorization via access keys
 /// 
<https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key>
 fn generate_authorization(
+    crypto: &dyn CryptoProvider,
     h: &HeaderMap,
     u: &Url,
     method: &Method,
     account: &str,
     key: &AzureAccessKey,
-) -> String {
+) -> crate::Result<String> {
     let str_to_sign = string_to_sign(h, u, method, account);
-    let auth = hmac_sha256(&key.0, str_to_sign);
-    format!("SharedKey {}:{}", account, BASE64_STANDARD.encode(auth))
+    let mut ctx = crypto.hmac(DigestAlgorithm::Sha256, &key.0)?;
+    ctx.update(str_to_sign.as_bytes());
+    let auth = ctx.finish()?;
+    Ok(format!(
+        "SharedKey {}:{}",
+        account,
+        BASE64_STANDARD.encode(auth)
+    ))
 }
 
 fn add_if_exists<'a>(h: &'a HeaderMap, key: &HeaderName) -> &'a str {
@@ -1077,6 +1125,7 @@ mod tests {
     use super::*;
     use crate::azure::MicrosoftAzureBuilder;
     use crate::azure::client::RequestVersionExt;
+    use crate::client::HttpRequestBody;
     use crate::client::mock_server::MockServer;
     use crate::{ObjectStoreExt, Path};
 
@@ -1226,6 +1275,48 @@ mod tests {
         }
     }
 
+    #[test]
+    fn bearer_authorization_does_not_require_crypto_provider() {
+        let credential = 
Some(Arc::new(AzureCredential::BearerToken("TOKEN".into())));
+        let builder = HttpRequestBuilder::new(HttpClient::new(Client::new()))
+            .method(Method::GET)
+            .uri("https://account.blob.core.windows.net/container/file.txt";)
+            .body(HttpRequestBody::empty())
+            .with_azure_authorization(None, &credential, "account")
+            .unwrap();
+
+        let (_, request) = builder.into_parts();
+        let request = request.unwrap();
+        assert_eq!(
+            request.headers().get(AUTHORIZATION).unwrap(),
+            "Bearer TOKEN"
+        );
+        assert!(request.headers().contains_key(&DATE));
+        assert!(request.headers().contains_key(&VERSION));
+    }
+
+    #[test]
+    fn sas_authorization_does_not_require_crypto_provider() {
+        let credential = Some(Arc::new(AzureCredential::SASToken(vec![
+            ("sig".into(), "signature".into()),
+            ("se".into(), "expiry".into()),
+        ])));
+        let builder = HttpRequestBuilder::new(HttpClient::new(Client::new()))
+            .method(Method::GET)
+            .uri("https://account.blob.core.windows.net/container/file.txt";)
+            .body(HttpRequestBody::empty())
+            .with_azure_authorization(None, &credential, "account")
+            .unwrap();
+
+        let (_, request) = builder.into_parts();
+        let request = request.unwrap();
+        let query = request.uri().query().unwrap();
+        assert!(query.contains("sig=signature"));
+        assert!(query.contains("se=expiry"));
+        assert!(request.headers().contains_key(&DATE));
+        assert!(request.headers().contains_key(&VERSION));
+    }
+
     #[cfg(feature = "reqwest")]
     #[tokio::test]
     async fn test_fabric_refresh_expired_token() {
@@ -1295,7 +1386,8 @@ mod tests {
         let request = client
             .request(Method::GET, "http://example.com/container/blob";)
             .with_azure_version("2026-02-06")
-            .with_azure_authorization(&credential, "account")
+            .with_azure_authorization(None, &credential, "account")
+            .unwrap()
             .into_parts()
             .1
             .unwrap();
diff --git a/src/azure/mod.rs b/src/azure/mod.rs
index 2e802e3..b75798f 100644
--- a/src/azure/mod.rs
+++ b/src/azure/mod.rs
@@ -42,9 +42,9 @@ use std::sync::Arc;
 use std::time::Duration;
 use url::Url;
 
-use crate::client::CredentialProvider;
 use crate::client::get::GetClientExt;
 use crate::client::list::{ListClient, ListClientExt};
+use crate::client::{CredentialProvider, crypto_provider};
 pub use credential::{AzureAccessKey, AzureAuthorizer, authority_hosts};
 
 mod builder;
@@ -224,9 +224,10 @@ impl Signer for MicrosoftAzure {
             });
         }
 
+        let crypto = crypto_provider(self.client.crypto())?;
         let mut url = self.path_url(path);
         let signer = self.client.signer(expires_in).await?;
-        signer.sign(&method, &mut url)?;
+        signer.sign(crypto, &method, &mut url)?;
         Ok(url)
     }
 
@@ -242,11 +243,12 @@ impl Signer for MicrosoftAzure {
             });
         }
 
+        let crypto = crypto_provider(self.client.crypto())?;
         let mut urls = Vec::with_capacity(paths.len());
         let signer = self.client.signer(expires_in).await?;
         for path in paths {
             let mut url = self.path_url(path);
-            signer.sign(&method, &mut url)?;
+            signer.sign(crypto, &method, &mut url)?;
             urls.push(url);
         }
         Ok(urls)
diff --git a/src/client/crypto.rs b/src/client/crypto.rs
new file mode 100644
index 0000000..41e3ef5
--- /dev/null
+++ b/src/client/crypto.rs
@@ -0,0 +1,410 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use crate::Result;
+
+/// Algorithm for computing digests
+#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum DigestAlgorithm {
+    /// SHA-256
+    Sha256,
+}
+
+/// Algorithm for signing payloads
+#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum SigningAlgorithm {
+    /// RSASSA-PKCS1-v1_5 using SHA-256
+    RS256,
+}
+
+/// Provides cryptographic primitives
+pub trait CryptoProvider: std::fmt::Debug + Send + Sync {
+    /// Compute a digest
+    fn digest(&self, algorithm: DigestAlgorithm) -> Result<Box<dyn 
DigestContext>>;
+
+    /// Compute an HMAC with the provided `secret`
+    fn hmac(&self, algorithm: DigestAlgorithm, secret: &[u8]) -> 
Result<Box<dyn HmacContext>>;
+
+    /// Sign a payload with the provided PEM-encoded secret
+    fn sign(&self, algorithm: SigningAlgorithm, pem: &[u8]) -> Result<Box<dyn 
Signer>>;
+}
+
+/// Incrementally compute a digest, see [`CryptoProvider::digest`]
+pub trait DigestContext: Send {
+    /// Updates the digest with all the data in data.
+    ///
+    /// It is implementation-defined behaviour to call this after calling 
[`Self::finish`]
+    fn update(&mut self, data: &[u8]);
+
+    /// Finalizes the digest calculation and returns the digest value.
+    ///
+    /// It is implementation-defined behaviour to call this after calling 
[`Self::finish`]
+    fn finish(&mut self) -> Result<&[u8]>;
+}
+
+/// Incrementally compute a HMAC, see [`CryptoProvider::hmac`]
+pub trait HmacContext: Send {
+    /// Updates the HMAC with all the data in data.
+    ///
+    /// It is implementation-defined behaviour to call this after calling 
[`Self::finish`]
+    fn update(&mut self, data: &[u8]);
+
+    /// Finalizes the HMAC calculation and returns the HMAC value.
+    ///
+    /// It is implementation-defined behaviour to call this after calling 
[`Self::finish`]
+    fn finish(&mut self) -> Result<&[u8]>;
+}
+
+/// Sign a payload, see [`CryptoProvider::sign`]
+pub trait Signer: Send + Sync {
+    /// Sign the provided payload
+    fn sign(&self, string_to_sign: &[u8]) -> Result<Vec<u8>>;
+}
+
+/// Attempts to find a [`CryptoProvider`]
+///
+/// If `custom` is `Some(v)` returns `v` otherwise returns the compile-time 
default
+///
+/// If both `ring` and `aws-lc-rs` are enabled, the `aws-lc-rs` provider is 
used.
+pub(crate) fn crypto_provider(custom: Option<&dyn CryptoProvider>) -> 
Result<&dyn CryptoProvider> {
+    if let Some(x) = custom {
+        return Ok(x);
+    }
+
+    #[cfg(feature = "aws-lc-rs")]
+    {
+        Ok(&aws_lc_rs::PROVIDER)
+    }
+
+    #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))]
+    {
+        Ok(&ring::PROVIDER)
+    }
+
+    #[cfg(not(any(feature = "ring", feature = "aws-lc-rs")))]
+    {
+        Err(crate::Error::NotSupported {
+            source: "Must enable aws-lc-rs, ring, or specify custom 
CryptoProvider"
+                .to_string()
+                .into(),
+        })
+    }
+}
+
+#[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))]
+pub(crate) mod ring {
+    use super::*;
+    use ::ring::{digest, hmac, rand, signature};
+    use thiserror::Error;
+
+    #[derive(Debug, Error)]
+    pub(crate) enum RingError {
+        #[error("No RSA key found in pem file")]
+        MissingKey,
+
+        #[error("Invalid RSA key: {}", source)]
+        InvalidKey {
+            #[from]
+            source: ::ring::error::KeyRejected,
+        },
+
+        #[error("Error reading pem file: {}", source)]
+        ReadPem {
+            source: rustls_pki_types::pem::Error,
+        },
+
+        #[error("Error signing: {}", source)]
+        Sign { source: ::ring::error::Unspecified },
+    }
+
+    impl From<RingError> for crate::Error {
+        fn from(value: RingError) -> Self {
+            Self::Generic {
+                store: "RingCryptoProvider",
+                source: Box::new(value),
+            }
+        }
+    }
+
+    pub(crate) const PROVIDER: RingCryptoProvider = RingCryptoProvider { 
_private: () };
+
+    #[derive(Debug, Default)]
+    pub(crate) struct RingCryptoProvider {
+        _private: (),
+    }
+
+    impl CryptoProvider for RingCryptoProvider {
+        fn digest(&self, algorithm: DigestAlgorithm) -> Result<Box<dyn 
DigestContext>> {
+            let algorithm = match algorithm {
+                DigestAlgorithm::Sha256 => &digest::SHA256,
+            };
+            let ctx = digest::Context::new(algorithm);
+            Ok(Box::new(RingDigestContext {
+                ctx: Some(ctx),
+                out: None,
+            }))
+        }
+
+        fn hmac(&self, algorithm: DigestAlgorithm, secret: &[u8]) -> 
Result<Box<dyn HmacContext>> {
+            let algorithm = match algorithm {
+                DigestAlgorithm::Sha256 => hmac::HMAC_SHA256,
+            };
+            let ctx = hmac::Context::with_key(&hmac::Key::new(algorithm, 
secret));
+            Ok(Box::new(RingHmacContext {
+                ctx: Some(ctx),
+                out: None,
+            }))
+        }
+
+        fn sign(&self, algorithm: SigningAlgorithm, pem: &[u8]) -> 
Result<Box<dyn Signer>> {
+            match algorithm {
+                SigningAlgorithm::RS256 => 
Ok(Box::new(RsaKeyPair::from_pem(pem)?)),
+            }
+        }
+    }
+
+    struct RingDigestContext {
+        ctx: Option<digest::Context>,
+        out: Option<digest::Digest>,
+    }
+
+    impl DigestContext for RingDigestContext {
+        fn update(&mut self, data: &[u8]) {
+            self.ctx.as_mut().unwrap().update(data);
+        }
+
+        fn finish(&mut self) -> Result<&[u8]> {
+            let digest = self.ctx.take().unwrap().finish();
+            Ok(digest::Digest::as_ref(self.out.insert(digest)))
+        }
+    }
+
+    struct RingHmacContext {
+        ctx: Option<hmac::Context>,
+        out: Option<hmac::Tag>,
+    }
+
+    impl HmacContext for RingHmacContext {
+        fn update(&mut self, data: &[u8]) {
+            self.ctx.as_mut().unwrap().update(data);
+        }
+
+        fn finish(&mut self) -> Result<&[u8]> {
+            let tag = self.ctx.take().unwrap().sign();
+            Ok(hmac::Tag::as_ref(self.out.insert(tag)))
+        }
+    }
+
+    /// A private RSA key for a service account
+    #[derive(Debug)]
+    pub(crate) struct RsaKeyPair(signature::RsaKeyPair);
+
+    impl RsaKeyPair {
+        /// Parses a pem-encoded RSA key
+        pub(crate) fn from_pem(encoded: &[u8]) -> Result<Self, RingError> {
+            use rustls_pki_types::PrivateKeyDer;
+            use rustls_pki_types::pem::PemObject;
+
+            match PrivateKeyDer::from_pem_slice(encoded) {
+                Ok(PrivateKeyDer::Pkcs8(key)) => 
Self::from_pkcs8(key.secret_pkcs8_der()),
+                Ok(PrivateKeyDer::Pkcs1(key)) => 
Self::from_der(key.secret_pkcs1_der()),
+                Ok(_) => Err(RingError::MissingKey),
+                Err(source) => Err(RingError::ReadPem { source }),
+            }
+        }
+
+        /// Parses an unencrypted PKCS#8-encoded RSA private key.
+        pub(crate) fn from_pkcs8(key: &[u8]) -> Result<Self, RingError> {
+            Ok(Self(signature::RsaKeyPair::from_pkcs8(key)?))
+        }
+
+        /// Parses an unencrypted PKCS#8-encoded RSA private key.
+        pub(crate) fn from_der(key: &[u8]) -> Result<Self, RingError> {
+            Ok(Self(signature::RsaKeyPair::from_der(key)?))
+        }
+    }
+
+    impl Signer for RsaKeyPair {
+        fn sign(&self, string_to_sign: &[u8]) -> Result<Vec<u8>> {
+            let mut signature = vec![0; self.0.public().modulus_len()];
+            self.0
+                .sign(
+                    &signature::RSA_PKCS1_SHA256,
+                    &rand::SystemRandom::new(),
+                    string_to_sign,
+                    &mut signature,
+                )
+                .map_err(|source| RingError::Sign { source })?;
+
+            Ok(signature)
+        }
+    }
+}
+
+#[cfg(feature = "aws-lc-rs")]
+pub(crate) mod aws_lc_rs {
+    use super::*;
+    use ::aws_lc_rs::{digest, hmac, rand, signature};
+    use thiserror::Error;
+
+    #[derive(Debug, Error)]
+    pub(crate) enum AwsLcError {
+        #[error("No RSA key found in pem file")]
+        MissingKey,
+
+        #[error("Invalid RSA key: {}", source)]
+        InvalidKey {
+            #[from]
+            source: ::aws_lc_rs::error::KeyRejected,
+        },
+
+        #[error("Error reading pem file: {}", source)]
+        ReadPem {
+            source: rustls_pki_types::pem::Error,
+        },
+
+        #[error("Error signing: {}", source)]
+        Sign {
+            source: ::aws_lc_rs::error::Unspecified,
+        },
+    }
+
+    impl From<AwsLcError> for crate::Error {
+        fn from(value: AwsLcError) -> Self {
+            Self::Generic {
+                store: "AwsLcCryptoProvider",
+                source: Box::new(value),
+            }
+        }
+    }
+
+    pub(crate) const PROVIDER: AwsLcCryptoProvider = AwsLcCryptoProvider { 
_private: () };
+
+    #[derive(Debug, Default)]
+    pub(crate) struct AwsLcCryptoProvider {
+        _private: (),
+    }
+
+    impl CryptoProvider for AwsLcCryptoProvider {
+        fn digest(&self, algorithm: DigestAlgorithm) -> Result<Box<dyn 
DigestContext>> {
+            let algorithm = match algorithm {
+                DigestAlgorithm::Sha256 => &digest::SHA256,
+            };
+            let ctx = digest::Context::new(algorithm);
+            Ok(Box::new(AwsLcDigestContext {
+                ctx: Some(ctx),
+                out: None,
+            }))
+        }
+
+        fn hmac(&self, algorithm: DigestAlgorithm, secret: &[u8]) -> 
Result<Box<dyn HmacContext>> {
+            let algorithm = match algorithm {
+                DigestAlgorithm::Sha256 => hmac::HMAC_SHA256,
+            };
+            let ctx = hmac::Context::with_key(&hmac::Key::new(algorithm, 
secret));
+            Ok(Box::new(AwsLcHmacContext {
+                ctx: Some(ctx),
+                out: None,
+            }))
+        }
+
+        fn sign(&self, algorithm: SigningAlgorithm, pem: &[u8]) -> 
Result<Box<dyn Signer>> {
+            match algorithm {
+                SigningAlgorithm::RS256 => 
Ok(Box::new(RsaKeyPair::from_pem(pem)?)),
+            }
+        }
+    }
+
+    struct AwsLcDigestContext {
+        ctx: Option<digest::Context>,
+        out: Option<digest::Digest>,
+    }
+
+    impl DigestContext for AwsLcDigestContext {
+        fn update(&mut self, data: &[u8]) {
+            self.ctx.as_mut().unwrap().update(data);
+        }
+
+        fn finish(&mut self) -> Result<&[u8]> {
+            let digest = self.ctx.take().unwrap().finish();
+            Ok(digest::Digest::as_ref(self.out.insert(digest)))
+        }
+    }
+
+    struct AwsLcHmacContext {
+        ctx: Option<hmac::Context>,
+        out: Option<hmac::Tag>,
+    }
+
+    impl HmacContext for AwsLcHmacContext {
+        fn update(&mut self, data: &[u8]) {
+            self.ctx.as_mut().unwrap().update(data);
+        }
+
+        fn finish(&mut self) -> Result<&[u8]> {
+            let tag = self.ctx.take().unwrap().sign();
+            Ok(hmac::Tag::as_ref(self.out.insert(tag)))
+        }
+    }
+
+    /// A private RSA key for a service account
+    #[derive(Debug)]
+    pub(crate) struct RsaKeyPair(signature::RsaKeyPair);
+
+    impl RsaKeyPair {
+        /// Parses a pem-encoded RSA key
+        pub(crate) fn from_pem(encoded: &[u8]) -> Result<Self, AwsLcError> {
+            use rustls_pki_types::PrivateKeyDer;
+            use rustls_pki_types::pem::PemObject;
+
+            match PrivateKeyDer::from_pem_slice(encoded) {
+                Ok(PrivateKeyDer::Pkcs8(key)) => 
Self::from_pkcs8(key.secret_pkcs8_der()),
+                Ok(PrivateKeyDer::Pkcs1(key)) => 
Self::from_der(key.secret_pkcs1_der()),
+                Ok(_) => Err(AwsLcError::MissingKey),
+                Err(source) => Err(AwsLcError::ReadPem { source }),
+            }
+        }
+
+        /// Parses an unencrypted PKCS#8-encoded RSA private key.
+        pub(crate) fn from_pkcs8(key: &[u8]) -> Result<Self, AwsLcError> {
+            Ok(Self(signature::RsaKeyPair::from_pkcs8(key)?))
+        }
+
+        /// Parses an unencrypted PKCS#8-encoded RSA private key.
+        pub(crate) fn from_der(key: &[u8]) -> Result<Self, AwsLcError> {
+            Ok(Self(signature::RsaKeyPair::from_der(key)?))
+        }
+    }
+
+    impl Signer for RsaKeyPair {
+        fn sign(&self, string_to_sign: &[u8]) -> Result<Vec<u8>> {
+            let mut signature = vec![0; self.0.public_modulus_len()];
+            self.0
+                .sign(
+                    &signature::RSA_PKCS1_SHA256,
+                    &rand::SystemRandom::new(),
+                    string_to_sign,
+                    &mut signature,
+                )
+                .map_err(|source| AwsLcError::Sign { source })?;
+
+            Ok(signature)
+        }
+    }
+}
diff --git a/src/client/mod.rs b/src/client/mod.rs
index 896fcb2..a8d2ae5 100644
--- a/src/client/mod.rs
+++ b/src/client/mod.rs
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-//! Generic utilities for [`reqwest`] based [`ObjectStore`] implementations
+//! Generic utilities for network based [`ObjectStore`] implementations
 //!
 //! [`ObjectStore`]: crate::ObjectStore
 
@@ -53,6 +53,12 @@ mod http;
 pub(crate) mod parts;
 pub use http::*;
 
+#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
+mod crypto;
+
+#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
+pub use crypto::*;
+
 use ::http::header::{HeaderMap, HeaderValue};
 use async_trait::async_trait;
 use serde::{Deserialize, Serialize};
@@ -105,6 +111,14 @@ pub enum ClientConfigKey {
     /// Supported keys:
     /// - `allow_invalid_certificates`
     AllowInvalidCertificates,
+    /// Disable certificate validation using the operating system's 
certificate facilities.
+    ///
+    /// See [`ClientOptions::with_no_system_certificates`]
+    ///
+    /// Supported keys:
+    ///
+    /// - `disable_system_certificates`
+    NoSystemCertificates,
     /// Timeout for only the connect phase of a Client
     ///
     /// Supported keys:
@@ -216,6 +230,7 @@ impl AsRef<str> for ClientConfigKey {
         match self {
             Self::AllowHttp => "allow_http",
             Self::AllowInvalidCertificates => "allow_invalid_certificates",
+            Self::NoSystemCertificates => "disable_system_certificates",
             Self::ConnectTimeout => "connect_timeout",
             Self::DefaultContentType => "default_content_type",
             Self::Http1Only => "http1_only",
@@ -244,6 +259,7 @@ impl FromStr for ClientConfigKey {
         match s {
             "allow_http" => Ok(Self::AllowHttp),
             "allow_invalid_certificates" => Ok(Self::AllowInvalidCertificates),
+            "disable_system_certificates" => Ok(Self::NoSystemCertificates),
             "connect_timeout" => Ok(Self::ConnectTimeout),
             "default_content_type" => Ok(Self::DefaultContentType),
             "http1_only" => Ok(Self::Http1Only),
@@ -326,6 +342,7 @@ pub struct ClientOptions {
     user_agent: Option<ConfigValue<HeaderValue>>,
     #[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
     root_certificates: Vec<Certificate>,
+    no_system_certificates: ConfigValue<bool>,
     content_type_map: HashMap<String, String>,
     default_content_type: Option<String>,
     default_headers: Option<HeaderMap>,
@@ -361,6 +378,7 @@ impl Default for ClientOptions {
             user_agent: None,
             #[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
             root_certificates: Default::default(),
+            no_system_certificates: false.into(),
             content_type_map: Default::default(),
             default_content_type: None,
             default_headers: None,
@@ -401,6 +419,7 @@ impl ClientOptions {
             ClientConfigKey::AllowInvalidCertificates => {
                 self.allow_invalid_certificates.parse(value)
             }
+            ClientConfigKey::NoSystemCertificates => 
self.no_system_certificates.parse(value),
             ClientConfigKey::ConnectTimeout => {
                 self.connect_timeout = 
Some(ConfigValue::Deferred(value.into()))
             }
@@ -449,6 +468,7 @@ impl ClientOptions {
             ClientConfigKey::AllowInvalidCertificates => {
                 Some(self.allow_invalid_certificates.to_string())
             }
+            ClientConfigKey::NoSystemCertificates => 
Some(self.no_system_certificates.to_string()),
             ClientConfigKey::ConnectTimeout => 
self.connect_timeout.as_ref().map(fmt_duration),
             ClientConfigKey::ReadTimeout => 
self.read_timeout.as_ref().map(fmt_duration),
             ClientConfigKey::DefaultContentType => 
self.default_content_type.clone(),
@@ -532,6 +552,7 @@ impl ClientOptions {
         self.allow_http = allow_http.into();
         self
     }
+
     /// Allows connections to invalid SSL certificates
     ///
     /// If `allow_invalid_certificates` is :
@@ -554,6 +575,20 @@ impl ClientOptions {
         self
     }
 
+    /// Disable certificates provided by the system
+    ///
+    /// By default TLS certificates are validated using 
[`rustls-platform-verifier`],
+    /// which makes use of the system's trust store, in addition to any 
certificates
+    /// registered using [`Self::with_root_certificate`]. If disabled, instead 
[`rustls-webpki`]
+    /// is used with only the certificates registered using 
[`Self::with_root_certificate`].
+    ///
+    /// [`rustls-platform-verifier`]: 
https://crates.io/crates/rustls-platform-verifier
+    /// [`rustls-webpki`]: https://crates.io/crates/rustls-webpki
+    pub fn with_no_system_certificates(mut self, no_certs: bool) -> Self {
+        self.no_system_certificates = no_certs.into();
+        self
+    }
+
     /// Only use HTTP/1 connections (default)
     ///
     /// # See Also
@@ -816,7 +851,7 @@ impl ClientOptions {
                 let certificate = 
reqwest::tls::Certificate::from_pem(certificate.as_bytes())
                     .map_err(map_client_error)?;
 
-                builder = builder.add_root_certificate(certificate);
+                builder = 
builder.tls_certs_merge(std::iter::once(certificate));
             }
 
             if let Some(proxy_excludes) = &self.proxy_excludes {
@@ -828,8 +863,15 @@ impl ClientOptions {
             builder = builder.proxy(proxy);
         }
 
-        for certificate in &self.root_certificates {
-            builder = builder.add_root_certificate(certificate.0.clone());
+        let certs = self
+            .root_certificates
+            .iter()
+            .map(|certificate| certificate.0.clone());
+
+        if self.no_system_certificates.get()? {
+            builder = builder.tls_certs_only(certs);
+        } else {
+            builder = builder.tls_certs_merge(certs);
         }
 
         if let Some(timeout) = &self.timeout {
@@ -1073,6 +1115,7 @@ mod tests {
         let http2_keep_alive_timeout = "91 seconds".to_string();
         let http2_keep_alive_while_idle = "92 seconds".to_string();
         let http2_max_frame_size = "1337".to_string();
+        let no_system_certificates = "true".to_string();
         let pool_idle_timeout = "93 seconds".to_string();
         let pool_max_idle_per_host = "94".to_string();
         let proxy_url = "https://fake_proxy_url".to_string();
@@ -1100,6 +1143,10 @@ mod tests {
                 http2_keep_alive_while_idle.clone(),
             ),
             ("http2_max_frame_size", http2_max_frame_size.clone()),
+            (
+                "disable_system_certificates",
+                no_system_certificates.clone(),
+            ),
             ("pool_idle_timeout", pool_idle_timeout.clone()),
             ("pool_max_idle_per_host", pool_max_idle_per_host.clone()),
             ("proxy_url", proxy_url.clone()),
@@ -1174,6 +1221,12 @@ mod tests {
                 .unwrap(),
             http2_max_frame_size
         );
+        assert_eq!(
+            builder
+                .get_config_value(&ClientConfigKey::NoSystemCertificates)
+                .unwrap(),
+            no_system_certificates
+        );
 
         assert_eq!(
             builder
diff --git a/src/gcp/builder.rs b/src/gcp/builder.rs
index 55cfe63..7487ad6 100644
--- a/src/gcp/builder.rs
+++ b/src/gcp/builder.rs
@@ -15,7 +15,9 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use crate::client::{HttpConnector, TokenCredentialProvider, http_connector};
+use crate::client::{
+    CryptoProvider, HttpConnector, TokenCredentialProvider, crypto_provider, 
http_connector,
+};
 use crate::config::ConfigValue;
 use crate::gcp::client::{GoogleCloudStorageClient, GoogleCloudStorageConfig};
 use crate::gcp::credential::{
@@ -114,6 +116,8 @@ pub struct GoogleCloudStorageBuilder {
     credentials: Option<GcpCredentialProvider>,
     /// Explicit bearer token, if configured
     bearer_token: Option<String>,
+    /// The [`CryptoProvider`] to use
+    crypto: Option<Arc<dyn CryptoProvider>>,
     /// Skip signing requests
     skip_signature: ConfigValue<bool>,
     /// Credentials for sign url
@@ -254,6 +258,7 @@ impl Default for GoogleCloudStorageBuilder {
             base_url: None,
             credentials: None,
             bearer_token: None,
+            crypto: None,
             skip_signature: Default::default(),
             signing_credentials: None,
             http_connector: None,
@@ -494,6 +499,12 @@ impl GoogleCloudStorageBuilder {
         self
     }
 
+    /// The [`CryptoProvider`] to use
+    pub fn with_crypto_provider(mut self, provider: Arc<dyn CryptoProvider>) 
-> Self {
+        self.crypto = Some(provider);
+        self
+    }
+
     /// Set the retry configuration
     pub fn with_retry(mut self, retry_config: RetryConfig) -> Self {
         self.retry_config = retry_config;
@@ -542,7 +553,6 @@ impl GoogleCloudStorageBuilder {
         }
 
         let bucket_name = self.bucket_name.ok_or(Error::MissingBucketName {})?;
-
         let http = http_connector(self.http_connector)?;
 
         // First try to initialize from the service account information.
@@ -592,8 +602,9 @@ impl GoogleCloudStorageBuilder {
                 bearer: "".to_string(),
             })) as _
         } else if let Some(credentials) = service_account_credentials.clone() {
+            let crypto = crypto_provider(self.crypto.as_deref())?;
             Arc::new(TokenCredentialProvider::new(
-                credentials.token_provider()?,
+                credentials.token_provider(crypto)?,
                 http.connect(&self.client_options)?,
                 self.retry_config.clone(),
             )) as _
@@ -608,8 +619,9 @@ impl GoogleCloudStorageBuilder {
                     .with_min_ttl(TOKEN_MIN_TTL),
                 ) as _,
                 ApplicationDefaultCredentials::ServiceAccount(token) => {
+                    let crypto = crypto_provider(self.crypto.as_deref())?;
                     Arc::new(TokenCredentialProvider::new(
-                        token.token_provider()?,
+                        token.token_provider(crypto)?,
                         http.connect(&self.client_options)?,
                         self.retry_config.clone(),
                     )) as _
@@ -634,7 +646,8 @@ impl GoogleCloudStorageBuilder {
                 private_key: None,
             })) as _
         } else if let Some(credentials) = service_account_credentials.clone() {
-            credentials.signing_credentials()?
+            let crypto = crypto_provider(self.crypto.as_deref())?;
+            credentials.signing_credentials(crypto)?
         } else if let Some(credentials) = 
application_default_credentials.clone() {
             match credentials {
                 ApplicationDefaultCredentials::AuthorizedUser(token) => {
@@ -645,7 +658,8 @@ impl GoogleCloudStorageBuilder {
                     )) as _
                 }
                 ApplicationDefaultCredentials::ServiceAccount(token) => {
-                    token.signing_credentials()?
+                    let crypto = crypto_provider(self.crypto.as_deref())?;
+                    token.signing_credentials(crypto)?
                 }
             }
         } else {
@@ -661,6 +675,7 @@ impl GoogleCloudStorageBuilder {
             credentials,
             signing_credentials,
             bucket_name,
+            crypto: self.crypto,
             retry_config: self.retry_config,
             client_options: self.client_options,
             skip_signature: self.skip_signature.get()?,
diff --git a/src/gcp/client.rs b/src/gcp/client.rs
index 5356cc4..f41171d 100644
--- a/src/gcp/client.rs
+++ b/src/gcp/client.rs
@@ -24,7 +24,7 @@ use crate::client::s3::{
     CompleteMultipartUpload, CompleteMultipartUploadResult, 
InitiateMultipartUploadResult,
     ListResponse,
 };
-use crate::client::{GetOptionsExt, HttpClient, HttpError, HttpResponse};
+use crate::client::{CryptoProvider, GetOptionsExt, HttpClient, HttpError, 
HttpResponse};
 use crate::gcp::credential::CredentialExt;
 use crate::gcp::{GcpCredential, GcpCredentialProvider, 
GcpSigningCredentialProvider, STORE};
 use crate::list::{PaginatedListOptions, PaginatedListResult};
@@ -142,6 +142,8 @@ pub(crate) struct GoogleCloudStorageConfig {
 
     pub signing_credentials: GcpSigningCredentialProvider,
 
+    pub crypto: Option<Arc<dyn CryptoProvider>>,
+
     pub bucket_name: String,
 
     pub retry_config: RetryConfig,
diff --git a/src/gcp/credential.rs b/src/gcp/credential.rs
index dfba358..4a43929 100644
--- a/src/gcp/credential.rs
+++ b/src/gcp/credential.rs
@@ -19,7 +19,9 @@ use super::client::GoogleCloudStorageClient;
 use crate::client::builder::HttpRequestBuilder;
 use crate::client::retry::RetryExt;
 use crate::client::token::TemporaryToken;
-use crate::client::{HttpClient, HttpError, TokenProvider};
+use crate::client::{
+    CryptoProvider, HttpClient, HttpError, Signer, SigningAlgorithm, 
TokenProvider,
+};
 use crate::gcp::{GcpSigningCredentialProvider, STORE};
 use crate::util::{STRICT_ENCODE_SET, hex_digest, hex_encode};
 use crate::{RetryConfig, StaticCredentialProvider};
@@ -31,7 +33,6 @@ use futures_util::TryFutureExt;
 use http::{HeaderMap, Method};
 use itertools::Itertools;
 use percent_encoding::utf8_percent_encode;
-use ring::signature::RsaKeyPair;
 use serde::Deserialize;
 use std::collections::BTreeMap;
 use std::env;
@@ -54,7 +55,7 @@ const DEFAULT_METADATA_HOST: &str = 
"metadata.google.internal";
 const DEFAULT_METADATA_IP: &str = "169.254.169.254";
 
 #[derive(Debug, thiserror::Error)]
-pub enum Error {
+pub(super) enum Error {
     #[error("Unable to open service account file from {}: {}", path.display(), 
source)]
     OpenCredentials {
         source: std::io::Error,
@@ -64,24 +65,9 @@ pub enum Error {
     #[error("Unable to decode service account file: {}", source)]
     DecodeCredentials { source: serde_json::Error },
 
-    #[error("No RSA key found in pem file")]
-    MissingKey,
-
-    #[error("Invalid RSA key: {}", source)]
-    InvalidKey {
-        #[from]
-        source: ring::error::KeyRejected,
-    },
-
-    #[error("Error signing: {}", source)]
-    Sign { source: ring::error::Unspecified },
-
     #[error("Error encoding jwt payload: {}", source)]
     Encode { source: serde_json::Error },
 
-    #[error("Unsupported key encoding: {}", encoding)]
-    UnsupportedKey { encoding: String },
-
     #[error("Error performing token request: {}", source)]
     TokenRequest {
         source: crate::client::retry::RetryError,
@@ -89,11 +75,6 @@ pub enum Error {
 
     #[error("Error getting token response body: {}", source)]
     TokenResponseBody { source: HttpError },
-
-    #[error("Error reading pem file: {}", source)]
-    ReadPem {
-        source: rustls_pki_types::pem::Error,
-    },
 }
 
 impl From<Error> for crate::Error {
@@ -123,45 +104,64 @@ pub struct GcpSigningCredential {
 }
 
 /// A private RSA key for a service account
-#[derive(Debug)]
-pub struct ServiceAccountKey(RsaKeyPair);
+pub struct ServiceAccountKey(Box<dyn Signer>);
+
+impl std::fmt::Debug for ServiceAccountKey {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_tuple("ServiceAccountKey").finish_non_exhaustive()
+    }
+}
 
 impl ServiceAccountKey {
+    /// Creates a [`ServiceAccountKey`] from the provided [`Signer`]
+    pub fn new(signer: Box<dyn Signer>) -> Self {
+        Self(signer)
+    }
+
     /// Parses a pem-encoded RSA key
-    pub fn from_pem(encoded: &[u8]) -> Result<Self> {
-        use rustls_pki_types::PrivateKeyDer;
-        use rustls_pki_types::pem::PemObject;
-
-        match PrivateKeyDer::from_pem_slice(encoded) {
-            Ok(PrivateKeyDer::Pkcs8(key)) => 
Self::from_pkcs8(key.secret_pkcs8_der()),
-            Ok(PrivateKeyDer::Pkcs1(key)) => 
Self::from_der(key.secret_pkcs1_der()),
-            Ok(_) => Err(Error::MissingKey),
-            Err(source) => Err(Error::ReadPem { source }),
-        }
+    #[cfg(feature = "aws-lc-rs")]
+    pub fn from_pem(encoded: &[u8]) -> crate::Result<Self> {
+        let key = crate::client::aws_lc_rs::RsaKeyPair::from_pem(encoded)?;
+        Ok(Self::new(Box::new(key)))
     }
 
     /// Parses an unencrypted PKCS#8-encoded RSA private key.
-    pub fn from_pkcs8(key: &[u8]) -> Result<Self> {
-        Ok(Self(RsaKeyPair::from_pkcs8(key)?))
+    #[cfg(feature = "aws-lc-rs")]
+    pub fn from_pkcs8(key: &[u8]) -> crate::Result<Self> {
+        let key = crate::client::aws_lc_rs::RsaKeyPair::from_pkcs8(key)?;
+        Ok(Self::new(Box::new(key)))
     }
 
     /// Parses an unencrypted PKCS#8-encoded RSA private key.
-    pub fn from_der(key: &[u8]) -> Result<Self> {
-        Ok(Self(RsaKeyPair::from_der(key)?))
-    }
-
-    fn sign(&self, string_to_sign: &str) -> Result<String> {
-        let mut signature = vec![0; self.0.public().modulus_len()];
-        self.0
-            .sign(
-                &ring::signature::RSA_PKCS1_SHA256,
-                &ring::rand::SystemRandom::new(),
-                string_to_sign.as_bytes(),
-                &mut signature,
-            )
-            .map_err(|source| Error::Sign { source })?;
+    #[cfg(feature = "aws-lc-rs")]
+    pub fn from_der(key: &[u8]) -> crate::Result<Self> {
+        let key = crate::client::aws_lc_rs::RsaKeyPair::from_der(key)?;
+        Ok(Self::new(Box::new(key)))
+    }
 
-        Ok(hex_encode(&signature))
+    /// Parses a pem-encoded RSA key
+    #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))]
+    pub fn from_pem(encoded: &[u8]) -> crate::Result<Self> {
+        let key = crate::client::ring::RsaKeyPair::from_pem(encoded)?;
+        Ok(Self::new(Box::new(key)))
+    }
+
+    /// Parses an unencrypted PKCS#8-encoded RSA private key.
+    #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))]
+    pub fn from_pkcs8(key: &[u8]) -> crate::Result<Self> {
+        let key = crate::client::ring::RsaKeyPair::from_pkcs8(key)?;
+        Ok(Self::new(Box::new(key)))
+    }
+
+    /// Parses an unencrypted PKCS#8-encoded RSA private key.
+    #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))]
+    pub fn from_der(key: &[u8]) -> crate::Result<Self> {
+        let key = crate::client::ring::RsaKeyPair::from_der(key)?;
+        Ok(Self::new(Box::new(key)))
+    }
+
+    fn sign(&self, string_to_sign: &[u8]) -> crate::Result<Vec<u8>> {
+        self.0.sign(string_to_sign)
     }
 }
 
@@ -287,17 +287,7 @@ impl TokenProvider for SelfSignedJwt {
 
         let claim_str = b64_encode_obj(&claims)?;
         let message = [jwt_header.as_ref(), claim_str.as_ref()].join(".");
-        let mut sig_bytes = vec![0; self.private_key.0.public().modulus_len()];
-        self.private_key
-            .0
-            .sign(
-                &ring::signature::RSA_PKCS1_SHA256,
-                &ring::rand::SystemRandom::new(),
-                message.as_bytes(),
-                &mut sig_bytes,
-            )
-            .map_err(|source| Error::Sign { source })?;
-
+        let sig_bytes = self.private_key.sign(message.as_bytes())?;
         let signature = BASE64_URL_SAFE_NO_PAD.encode(sig_bytes);
         let bearer = [message, signature].join(".");
 
@@ -360,20 +350,28 @@ impl ServiceAccountCredentials {
     /// # References
     /// - 
<https://stackoverflow.com/questions/63222450/service-account-authorization-without-oauth-can-we-get-file-from-google-cloud/71834557#71834557>
     /// - 
<https://www.codejam.info/2022/05/google-cloud-service-account-authorization-without-oauth.html>
-    pub(crate) fn token_provider(self) -> crate::Result<SelfSignedJwt> {
+    pub(crate) fn token_provider(
+        self,
+        crypto: &dyn CryptoProvider,
+    ) -> crate::Result<SelfSignedJwt> {
+        let key = crypto.sign(SigningAlgorithm::RS256, 
self.private_key.as_bytes())?;
         Ok(SelfSignedJwt::new(
             self.private_key_id,
             self.client_email,
-            ServiceAccountKey::from_pem(self.private_key.as_bytes())?,
+            ServiceAccountKey::new(key),
             DEFAULT_SCOPE.to_string(),
         )?)
     }
 
-    pub(crate) fn signing_credentials(self) -> 
crate::Result<GcpSigningCredentialProvider> {
+    pub(crate) fn signing_credentials(
+        self,
+        crypto: &dyn CryptoProvider,
+    ) -> crate::Result<GcpSigningCredentialProvider> {
+        let key = crypto.sign(SigningAlgorithm::RS256, 
self.private_key.as_bytes())?;
         Ok(Arc::new(StaticCredentialProvider::new(
             GcpSigningCredential {
                 email: self.client_email,
-                private_key: 
Some(ServiceAccountKey::from_pem(self.private_key.as_bytes())?),
+                private_key: Some(ServiceAccountKey::new(key)),
             },
         )))
     }
@@ -763,6 +761,7 @@ impl GCSAuthorizer {
 
     pub(crate) async fn sign(
         &self,
+        crypto: &dyn CryptoProvider,
         method: Method,
         url: &mut Url,
         expires_in: Duration,
@@ -785,9 +784,9 @@ impl GCSAuthorizer {
             .append_pair("X-Goog-Expires", &expires_in.as_secs().to_string())
             .append_pair("X-Goog-SignedHeaders", &signed_headers);
 
-        let string_to_sign = self.string_to_sign(date, &method, url, &headers);
+        let string_to_sign = self.string_to_sign(crypto, date, &method, url, 
&headers)?;
         let signature = match &self.credential.private_key {
-            Some(key) => key.sign(&string_to_sign)?,
+            Some(key) => hex_encode(&key.sign(string_to_sign.as_bytes())?),
             None => client.sign_blob(&string_to_sign, email).await?,
         };
 
@@ -885,22 +884,23 @@ impl GCSAuthorizer {
     /// 
<https://cloud.google.com/storage/docs/authentication/signatures#string-to-sign>
     pub(crate) fn string_to_sign(
         &self,
+        crypto: &dyn CryptoProvider,
         date: DateTime<Utc>,
         request_method: &Method,
         url: &Url,
         headers: &HeaderMap,
-    ) -> String {
+    ) -> crate::Result<String> {
         let canonical_request = Self::canonicalize_request(url, 
request_method, headers);
-        let hashed_canonical_req = hex_digest(canonical_request.as_bytes());
+        let hashed_canonical_req = hex_digest(crypto, 
canonical_request.as_bytes())?;
         let scope = self.scope(date);
 
-        format!(
+        Ok(format!(
             "{}\n{}\n{}\n{}",
             "GOOG4-RSA-SHA256",
             date.format("%Y%m%dT%H%M%SZ"),
             scope,
             hashed_canonical_req
-        )
+        ))
     }
 }
 
@@ -927,6 +927,133 @@ impl CredentialExt for HttpRequestBuilder {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::client::{
+        ClientOptions, DigestAlgorithm, DigestContext, HmacContext, 
StaticCredentialProvider,
+    };
+    use crate::gcp::client::{GoogleCloudStorageClient, 
GoogleCloudStorageConfig};
+
+    const SIGNATURE_BYTES: &[u8] = &[0x00, 0x01, 0x02, 0xab, 0xcd];
+
+    struct FixedSigner;
+
+    impl Signer for FixedSigner {
+        fn sign(&self, _string_to_sign: &[u8]) -> crate::Result<Vec<u8>> {
+            Ok(SIGNATURE_BYTES.to_vec())
+        }
+    }
+
+    #[derive(Debug)]
+    struct FixedCryptoProvider;
+
+    impl CryptoProvider for FixedCryptoProvider {
+        fn digest(&self, _algorithm: DigestAlgorithm) -> crate::Result<Box<dyn 
DigestContext>> {
+            Ok(Box::new(FixedDigestContext))
+        }
+
+        fn hmac(
+            &self,
+            _algorithm: DigestAlgorithm,
+            _secret: &[u8],
+        ) -> crate::Result<Box<dyn HmacContext>> {
+            panic!("GCS signed URL should not use HMAC")
+        }
+
+        fn sign(
+            &self,
+            _algorithm: SigningAlgorithm,
+            _pem: &[u8],
+        ) -> crate::Result<Box<dyn Signer>> {
+            Ok(Box::new(FixedSigner))
+        }
+    }
+
+    struct FixedDigestContext;
+
+    impl DigestContext for FixedDigestContext {
+        fn update(&mut self, _data: &[u8]) {}
+
+        fn finish(&mut self) -> crate::Result<&[u8]> {
+            Ok(&[0x12, 0x34])
+        }
+    }
+
+    #[derive(Debug)]
+    struct UnusedHttpService;
+
+    #[async_trait::async_trait]
+    impl crate::client::HttpService for UnusedHttpService {
+        async fn call(
+            &self,
+            _req: crate::client::HttpRequest,
+        ) -> std::result::Result<crate::client::HttpResponse, HttpError> {
+            panic!("SelfSignedJwt should not make HTTP requests")
+        }
+    }
+
+    #[test]
+    fn self_signed_jwt_base64url_encodes_raw_signature_bytes() {
+        let jwt = SelfSignedJwt::new(
+            "key-id".into(),
+            "[email protected]".into(),
+            ServiceAccountKey::new(Box::new(FixedSigner)),
+            DEFAULT_SCOPE.to_string(),
+        )
+        .unwrap();
+        let client = HttpClient::new(UnusedHttpService);
+        let token = futures_executor::block_on(jwt.fetch_token(&client, 
&RetryConfig::default()))
+            .unwrap()
+            .token;
+        let signature = token.bearer.rsplit('.').next().unwrap();
+
+        assert_eq!(signature, BASE64_URL_SAFE_NO_PAD.encode(SIGNATURE_BYTES));
+        assert_ne!(
+            signature,
+            BASE64_URL_SAFE_NO_PAD.encode(hex_encode(SIGNATURE_BYTES))
+        );
+    }
+
+    #[test]
+    fn signed_url_hex_encodes_local_signature_bytes() {
+        let signing_credential = Arc::new(GcpSigningCredential {
+            email: "[email protected]".into(),
+            private_key: Some(ServiceAccountKey::new(Box::new(FixedSigner))),
+        });
+        let authorizer = GCSAuthorizer::new(Arc::clone(&signing_credential));
+        let config = GoogleCloudStorageConfig {
+            base_url: DEFAULT_GCS_BASE_URL.into(),
+            credentials: Arc::new(StaticCredentialProvider::new(GcpCredential {
+                bearer: "bearer".into(),
+            })),
+            signing_credentials: 
Arc::new(StaticCredentialProvider::new(GcpSigningCredential {
+                email: "[email protected]".into(),
+                private_key: None,
+            })),
+            crypto: None,
+            bucket_name: "bucket".into(),
+            retry_config: RetryConfig::default(),
+            client_options: ClientOptions::default(),
+            skip_signature: false,
+        };
+        let client =
+            GoogleCloudStorageClient::new(config, 
HttpClient::new(UnusedHttpService)).unwrap();
+        let mut url = 
Url::parse("https://storage.googleapis.com/bucket/object";).unwrap();
+
+        futures_executor::block_on(authorizer.sign(
+            &FixedCryptoProvider,
+            Method::GET,
+            &mut url,
+            Duration::from_secs(60),
+            &client,
+        ))
+        .unwrap();
+
+        let signature = url
+            .query_pairs()
+            .find(|(key, _)| key == "X-Goog-Signature")
+            .unwrap()
+            .1;
+        assert_eq!(signature, hex_encode(SIGNATURE_BYTES));
+    }
 
     #[test]
     fn test_canonicalize_headers() {
diff --git a/src/gcp/mod.rs b/src/gcp/mod.rs
index 7c663b8..fe9b290 100644
--- a/src/gcp/mod.rs
+++ b/src/gcp/mod.rs
@@ -41,7 +41,7 @@ use std::sync::Arc;
 use std::time::Duration;
 
 use crate::CopyOptions;
-use crate::client::CredentialProvider;
+use crate::client::{CredentialProvider, crypto_provider};
 use crate::gcp::credential::GCSAuthorizer;
 use crate::signer::Signer;
 use crate::{
@@ -280,8 +280,9 @@ impl Signer for GoogleCloudStorage {
         let signing_credentials = 
self.signing_credentials().get_credential().await?;
         let authorizer = GCSAuthorizer::new(signing_credentials);
 
+        let crypto = crypto_provider(self.client.config().crypto.as_deref())?;
         authorizer
-            .sign(method, &mut url, expires_in, &self.client)
+            .sign(crypto, method, &mut url, expires_in, &self.client)
             .await?;
 
         Ok(url)
diff --git a/src/lib.rs b/src/lib.rs
index 48bf994..115770d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -521,39 +521,43 @@
 //!
 //! # Feature Flags
 //!
-//! The feature set is layered so that you can pick a provider independently
-//! of its HTTP transport:
+//! The feature set is layered so that you can pick an object store
+//! implementation, its HTTP transport, and its cryptography provider
+//! independently:
 //!
-//! * `cloud-base` holds the shared provider implementation (XML/JSON parsing,
+//! * `cloud-base` shared cloud implementation (XML/JSON parsing,
 //!   credentials, retry, etc.) and intentionally does *not* depend on
-//!   `reqwest`.
+//!   `reqwest` or a cryptography provider.
 //! * `reqwest` enables the built-in [`reqwest`]-based [`HttpConnector`].
+//! * `aws-lc-rs` and `ring` each provide a bundled [`client::CryptoProvider`].
 //! * `<provider>-base` (`aws-base`, `azure-base`, `gcp-base`, `http-base`)
-//!   adds the per-provider logic on top of `cloud-base` without pulling in
-//!   `reqwest`.
+//!   adds the implementation specific logic on top of `cloud-base` without 
pulling in
+//!   `reqwest` or a cryptography provider.
 //! * `<provider>` (`aws`, `azure`, `gcp`, `http`) is the batteries-included
-//!   alias for `<provider>-base` + `reqwest` and is the typical choice.
+//!   feature for `<provider>-base` + `reqwest` (with `rustls`) + the default
+//!   `aws-lc-rs` cryptography provider, and is the typical choice.
 //!
-//! ## Provider features
+//! ## Implementation specific features
 //!
 //! | Feature | Enables | Notes |
 //! | --- | --- | --- |
-//! | `aws` | `aws-base` + `reqwest` | Amazon S3 with the built-in HTTP 
transport. |
-//! | `azure` | `azure-base` + `reqwest` | Azure Blob Storage with the 
built-in HTTP transport. |
-//! | `gcp` | `gcp-base` + `reqwest` | Google Cloud Storage with the built-in 
HTTP transport. |
-//! | `http` | `http-base` + `reqwest` | HTTP/WebDAV with the built-in HTTP 
transport. |
-//! | `aws-base` | provider only | S3 provider without `reqwest`; supply your 
own [`HttpConnector`]. |
-//! | `azure-base` | provider only | Azure provider without `reqwest`; supply 
your own [`HttpConnector`]. |
-//! | `gcp-base` | provider only | GCS provider without `reqwest`; supply your 
own [`HttpConnector`]. |
-//! | `http-base` | provider only | HTTP/WebDAV provider without `reqwest`; 
supply your own [`HttpConnector`]. |
+//! | `aws` | `aws-base` + `reqwest` + `aws-lc-rs` | Amazon S3 with the 
built-in HTTP transport. |
+//! | `azure` | `azure-base` + `reqwest` + `aws-lc-rs` | Azure Blob Storage 
with the built-in HTTP transport. |
+//! | `gcp` | `gcp-base` + `reqwest` + `aws-lc-rs` | Google Cloud Storage with 
the built-in HTTP transport. |
+//! | `http` | `http-base` + `reqwest` + `aws-lc-rs` | HTTP/WebDAV with the 
built-in HTTP transport. |
+//! | `aws-base` |  | S3 without `reqwest` or crypto; supply your own 
[`HttpConnector`] and [`client::CryptoProvider`]. |
+//! | `azure-base` |  | Azure without `reqwest` or crypto; supply your own 
[`HttpConnector`] and [`client::CryptoProvider`]. |
+//! | `gcp-base` |  | GCS without `reqwest` or crypto; supply your own 
[`HttpConnector`] and [`client::CryptoProvider`]. |
+//! | `http-base` |  | HTTP/WebDAV without `reqwest`; supply your own 
[`HttpConnector`]. |
 //!
-//! ## Transport and shared features
+//! ## Transport and crypto features
 //!
 //! | Feature | Description |
 //! | --- | --- |
-//! | `reqwest` | Enables the default [`reqwest`]-based [`HttpConnector`]. 
Pulled in automatically by `aws`, `azure`, `gcp`, and `http`. |
-//! | `tls-webpki-roots` | When `reqwest` is enabled, also bundle Mozilla's 
[`webpki-roots`] CA certificates. See [TLS Certificates](#tls-certificates). |
-//! | `cloud-base` | Shared cloud-provider implementation. Pulled in 
automatically by every `*-base` feature; usually not enabled directly. |
+//! | `reqwest` | Enables the [`reqwest`]-based [`HttpConnector`]. Enabled 
automatically by `aws`, `azure`, `gcp`, and `http`. |
+//! | `aws-lc-rs` | Bundled [`aws-lc-rs`]-based [`client::CryptoProvider`]. 
The default for the batteries-included provider features. |
+//! | `ring` | Bundled [`ring`]-based [`client::CryptoProvider`], e.g. for 
WASM targets. |
+//! | `cloud-base` | Shared cloud-provider implementation. Enabled 
automatically by `*-base` features; usually not enabled directly. |
 //!
 //! ## Other features
 //!
@@ -563,15 +567,85 @@
 //! | `tokio` | Enables Tokio-based utilities such as 
[`BufReader`](buffered::BufReader) and [`BufWriter`](buffered::BufWriter). 
Pulled in automatically by `fs` and the `*-base` features. |
 //! | `integration` | Exposes the [`integration`] module, a reusable test 
suite for verifying custom [`ObjectStore`] implementations. Not API-stable. |
 //!
+//! ## Selecting a `reqwest` TLS backend
+//!
+//! `reqwest` needs a TLS backend to compile, so whenever you enable the 
`reqwest` feature directly
+//! you must also enable one of `reqwest`'s TLS features:
+//!
+//! | reqwest feature | TLS stack | Notes |
+//! | --- | --- | --- |
+//! | `reqwest/rustls` | [rustls] with [`aws-lc-rs`] | enables `aws-lc-rs`. 
This is what `aws`/`azure`/`gcp`/`http` enable. |
+//! | `reqwest/native-tls` | the platform's native TLS (OpenSSL / SChannel / 
Secure Transport) | enables neither `rustls` nor `aws-lc-rs`. |
+//! | `reqwest/rustls-no-provider` | [rustls] with no bundled provider | 
enables neither provider; you must install one at runtime, e.g. 
`rustls::crypto::ring::default_provider().install_default()`. |
+//!
+//! ## Feature examples
+//!
+//! S3 implementation only; user provides the HTTP connector and crypto 
provider:
+//! ```toml
+//! object_store = { default-features = false, features = ["aws-base"] }
+//! ```
+//!
+//! S3 implementation + `reqwest` + `aws-lc-rs` signing (equivalent to the 
`aws` feature):
+//! ```toml
+//! object_store = { default-features = false, features = ["aws-base", 
"reqwest", "reqwest/rustls", "aws-lc-rs"] }
+//! ```
+//!
+//! S3 implementation + `reqwest` with native TLS + `ring` signing (no 
`aws-lc-rs` in the dependency tree):
+//! ```toml
+//! object_store = { default-features = false, features = ["aws-base", 
"reqwest", "reqwest/native-tls", "ring"] }
+//! ```
+//!
+//! [rustls]: https://crates.io/crates/rustls/
+//!
+//! # Cryptography
+//!
+//! Request signing (e.g. AWS SigV4 or GCP service-account signing) requires a
+//! [`client::CryptoProvider`]. The `aws`, `gcp`, and `azure` features
+//! use [`aws-lc-rs`], matching `reqwest`'s default so that applications do 
not end up with
+//! two crypto stacks.
+//!
+//! If you wish to use [`ring`] (e.g. to support WASM targets), use the
+//! `*-base` feature flags, e.g. `aws-base`, and then enable the `ring` 
feature.
+//!
+//! If both `ring` and `aws-lc-rs` are enabled, `aws-lc-rs` is used by default.
+//!
+//! You can also implement a custom [`client::CryptoProvider`] to use your own 
cryptographic library.
+//!
+//! This signing provider is independent of the TLS crypto provider used by the
+//! built-in `reqwest` transport — see
+//! [Selecting a `reqwest` TLS backend](#selecting-a-reqwest-tls-backend). The
+//! only combination that needs the provider registered manually (e.g.
+//! `rustls::crypto::ring::default_provider().install_default()` in your 
`main`)
+//! is `reqwest/rustls-no-provider`; `reqwest/rustls` and `reqwest/native-tls`
+//! configure their TLS stack automatically.
+//!
+//! [`aws-lc-rs`]: https://crates.io/crates/aws-lc-rs/
+//! [`ring`]: https://crates.io/crates/ring/
+//!
 //! # TLS Certificates
 //!
-//! Stores that use HTTPS/TLS (this is true for most cloud stores) can choose 
the source of their [CA]
-//! certificates. By default the system-bundled certificates are used (see
-//! [`rustls-native-certs`]). The `tls-webpki-roots` feature switch can be 
used to also bundle Mozilla's
-//! root certificates with the library/application (see [`webpki-roots`]).
+//! Stores that use HTTPS/TLS (this is true for most cloud stores) can choose 
how certificates are validated.
+//!
+//! By default [`rustls-platform-verifier`] is used to verify certificates 
using the system's certificate
+//! facilities. Alternatively, this functionality can be disabled using
+//! [`ClientOptions::with_no_system_certificates`] and certificates manually 
registered using
+//! [`ClientOptions::with_root_certificate`].
+//!
+//! These could be a custom CA chain, or alternatively an alternative trust 
store, e.g. [`webpki-roots`].
+//!
+//! ```ignore-wasm32
+//! # #[cfg(feature = "aws")] {
+//! use object_store::{ClientOptions, Certificate};
+//!
+//! let mut options = 
ClientOptions::default().with_no_system_certificates(true);
+//! for root_cert in webpki_root_certs::TLS_SERVER_ROOT_CERTS {
+//!     options = 
options.with_root_certificate(Certificate::from_der(root_cert.as_ref()).unwrap());
+//! }
+//! # }
+//! ```
 //!
 //! [CA]: https://en.wikipedia.org/wiki/Certificate_authority
-//! [`rustls-native-certs`]: https://crates.io/crates/rustls-native-certs/
+//! [`rustls-platform-verifier`]: 
https://crates.io/crates/rustls-platform-verifier/
 //! [`webpki-roots`]: https://crates.io/crates/webpki-roots
 //!
 //! # Customizing HTTP Clients
diff --git a/src/util.rs b/src/util.rs
index 21c9f7a..0dd6745 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -42,12 +42,6 @@ where
     Ok(chrono::TimeZone::from_utc_datetime(&chrono::Utc, &naive))
 }
 
-#[cfg(any(feature = "aws-base", feature = "azure-base"))]
-pub(crate) fn hmac_sha256(secret: impl AsRef<[u8]>, bytes: impl AsRef<[u8]>) 
-> ring::hmac::Tag {
-    let key = ring::hmac::Key::new(ring::hmac::HMAC_SHA256, secret.as_ref());
-    ring::hmac::sign(&key, bytes.as_ref())
-}
-
 /// Collect a stream into [`Bytes`] avoiding copying in the event of a single 
chunk
 pub async fn collect_bytes<S, E>(mut stream: S, size_hint: Option<u64>) -> 
Result<Bytes, E>
 where
@@ -309,9 +303,13 @@ pub(crate) const STRICT_ENCODE_SET: 
percent_encoding::AsciiSet = percent_encodin
 
 /// Computes the SHA256 digest of `body` returned as a hex encoded string
 #[cfg(any(feature = "aws-base", feature = "gcp-base"))]
-pub(crate) fn hex_digest(bytes: &[u8]) -> String {
-    let digest = ring::digest::digest(&ring::digest::SHA256, bytes);
-    hex_encode(digest.as_ref())
+pub(crate) fn hex_digest(
+    crypto: &dyn crate::client::CryptoProvider,
+    bytes: &[u8],
+) -> Result<String> {
+    let mut ctx = crypto.digest(crate::client::DigestAlgorithm::Sha256)?;
+    ctx.update(bytes);
+    Ok(hex_encode(ctx.finish()?))
 }
 
 /// Returns `bytes` as a lower-case hex encoded string

Reply via email to