Skip to content

Commit fcc494a

Browse files
committed
Pluggable Crypto
1 parent eedbf3d commit fcc494a

25 files changed

+729
-264
lines changed

Cargo.toml

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ hyper = { version = "1.2", default-features = false, optional = true }
5454
md-5 = { version = "0.10.6", default-features = false, optional = true }
5555
quick-xml = { version = "0.38.0", features = ["serialize", "overlapped-lists"], optional = true }
5656
rand = { version = "0.9", default-features = false, features = ["std", "std_rng", "thread_rng"], optional = true }
57-
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2"], optional = true }
57+
reqwest = { version = "0.12", default-features = false, features = ["http2", "rustls-tls-no-provider"], optional = true }
5858
ring = { version = "0.17", default-features = false, features = ["std"], optional = true }
5959
rustls-pki-types = { version = "1.9", default-features = false, features = ["std"], optional = true }
6060
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
@@ -71,12 +71,24 @@ wasm-bindgen-futures = "0.4.18"
7171

7272
[features]
7373
default = ["fs"]
74-
cloud = ["serde", "serde_json", "quick-xml", "hyper", "reqwest", "reqwest/stream", "chrono/serde", "base64", "rand", "ring", "http-body-util", "form_urlencoded", "serde_urlencoded"]
75-
azure = ["cloud", "httparse"]
74+
cloud-no-crypto = ["serde", "serde_json", "quick-xml", "hyper", "reqwest", "reqwest/stream", "chrono/serde", "base64", "rand","http-body-util", "form_urlencoded", "serde_urlencoded"]
75+
cloud = ["ring", "rustls-pki-types", "cloud-no-crypto", "reqwest?/rustls-tls-native-roots"]
76+
77+
78+
azure-no-crypto = ["cloud-no-crypto", "httparse"]
79+
azure = ["cloud", "azure-no-crypto"]
80+
7681
fs = ["walkdir"]
77-
gcp = ["cloud", "rustls-pki-types"]
78-
aws = ["cloud", "md-5"]
79-
http = ["cloud"]
82+
83+
gcp-no-crypto = ["cloud-no-crypto"]
84+
gcp = ["cloud", "gcp-no-crypto"]
85+
86+
aws-no-crypto = ["cloud-no-crypto", "md-5"]
87+
aws = ["cloud", "aws-no-crypto"]
88+
89+
http-no-crypto = ["cloud-no-crypto"]
90+
http = ["cloud", "http-no-crypto"]
91+
8092
tls-webpki-roots = ["reqwest?/rustls-tls-webpki-roots"]
8193
integration = ["rand"]
8294

@@ -105,4 +117,4 @@ features = ["js"]
105117
[[test]]
106118
name = "get_range_file"
107119
path = "tests/get_range_file.rs"
108-
required-features = ["fs"]
120+
required-features = ["fs"]

src/aws/builder.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::aws::{
2424
AmazonS3, AwsCredential, AwsCredentialProvider, Checksum, S3ConditionalPut, S3CopyIfNotExists,
2525
STORE,
2626
};
27-
use crate::client::{HttpConnector, TokenCredentialProvider, http_connector};
27+
use crate::client::{CryptoProvider, HttpConnector, TokenCredentialProvider, http_connector};
2828
use crate::config::ConfigValue;
2929
use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, StaticCredentialProvider};
3030
use base64::Engine;
@@ -171,6 +171,8 @@ pub struct AmazonS3Builder {
171171
client_options: ClientOptions,
172172
/// Credentials
173173
credentials: Option<AwsCredentialProvider>,
174+
/// The [`CryptoProvider`] to use
175+
crypto: Option<Arc<dyn CryptoProvider>>,
174176
/// Skip signing requests
175177
skip_signature: ConfigValue<bool>,
176178
/// Copy if not exists
@@ -843,6 +845,12 @@ impl AmazonS3Builder {
843845
self
844846
}
845847

848+
/// The [`CryptoProvider`] to use
849+
pub fn with_crypto_provider(mut self, provider: Arc<dyn CryptoProvider>) -> Self {
850+
self.crypto = Some(provider);
851+
self
852+
}
853+
846854
/// Sets what protocol is allowed. If `allow_http` is :
847855
/// * false (default): Only HTTPS are allowed
848856
/// * true: HTTP and HTTPS are allowed
@@ -1150,6 +1158,7 @@ impl AmazonS3Builder {
11501158
endpoint: endpoint.clone(),
11511159
region: region.clone(),
11521160
credentials: Arc::clone(&credentials),
1161+
crypto: self.crypto.clone(),
11531162
},
11541163
http.connect(&self.client_options)?,
11551164
self.retry_config.clone(),
@@ -1190,6 +1199,7 @@ impl AmazonS3Builder {
11901199
bucket,
11911200
bucket_endpoint,
11921201
credentials,
1202+
crypto: self.crypto,
11931203
session_provider,
11941204
retry_config: self.retry_config,
11951205
client_options: self.client_options,

src/aws/client.rs

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ use crate::client::s3::{
3232
CompleteMultipartUpload, CompleteMultipartUploadResult, CopyPartResult,
3333
InitiateMultipartUploadResult, ListResponse, PartMetadata,
3434
};
35-
use crate::client::{GetOptionsExt, HttpClient, HttpError, HttpResponse};
35+
use crate::client::{
36+
CryptoProvider, DigestAlgorithm, GetOptionsExt, HttpClient, HttpError, HttpResponse,
37+
crypto_provider,
38+
};
3639
use crate::list::{PaginatedListOptions, PaginatedListResult};
3740
use crate::multipart::PartId;
3841
use crate::{
@@ -52,8 +55,6 @@ use itertools::Itertools;
5255
use md5::{Digest, Md5};
5356
use percent_encoding::{PercentEncode, utf8_percent_encode};
5457
use quick_xml::events::{self as xml_events};
55-
use ring::digest;
56-
use ring::digest::Context;
5758
use serde::{Deserialize, Serialize};
5859
use std::sync::Arc;
5960

@@ -198,6 +199,7 @@ pub(crate) struct S3Config {
198199
pub bucket: String,
199200
pub bucket_endpoint: String,
200201
pub credentials: AwsCredentialProvider,
202+
pub crypto: Option<Arc<dyn CryptoProvider>>,
201203
pub session_provider: Option<AwsCredentialProvider>,
202204
pub retry_config: RetryConfig,
203205
pub client_options: ClientOptions,
@@ -216,19 +218,18 @@ impl S3Config {
216218
format!("{}/{}", self.bucket_endpoint, encode_path(path))
217219
}
218220

219-
async fn get_session_credential(&self) -> Result<SessionCredential<'_>> {
220-
let credential = match self.skip_signature {
221+
async fn get_session_credential(&self) -> Result<Option<SessionCredential<'_>>> {
222+
Ok(match self.skip_signature {
221223
false => {
222224
let provider = self.session_provider.as_ref().unwrap_or(&self.credentials);
223-
Some(provider.get_credential().await?)
225+
let credential = provider.get_credential().await?;
226+
Some(SessionCredential {
227+
credential,
228+
session_token: self.session_provider.is_some(),
229+
config: self,
230+
})
224231
}
225232
true => None,
226-
};
227-
228-
Ok(SessionCredential {
229-
credential,
230-
session_token: self.session_provider.is_some(),
231-
config: self,
232233
})
233234
}
234235

@@ -243,27 +244,32 @@ impl S3Config {
243244
pub(crate) fn is_s3_express(&self) -> bool {
244245
self.session_provider.is_some()
245246
}
247+
248+
pub(crate) fn crypto(&self) -> Result<&dyn CryptoProvider> {
249+
crypto_provider(self.crypto.as_deref())
250+
}
246251
}
247252

248253
struct SessionCredential<'a> {
249-
credential: Option<Arc<AwsCredential>>,
254+
credential: Arc<AwsCredential>,
250255
session_token: bool,
251256
config: &'a S3Config,
252257
}
253258

254259
impl SessionCredential<'_> {
255-
fn authorizer(&self) -> Option<AwsAuthorizer<'_>> {
260+
fn authorizer(&self) -> Result<AwsAuthorizer<'_>> {
256261
let mut authorizer =
257-
AwsAuthorizer::new(self.credential.as_deref()?, "s3", &self.config.region)
262+
AwsAuthorizer::new(self.credential.as_ref(), "s3", &self.config.region)
258263
.with_sign_payload(self.config.sign_payload)
259-
.with_request_payer(self.config.request_payer);
264+
.with_request_payer(self.config.request_payer)
265+
.with_crypto(self.config.crypto()?);
260266

261267
if self.session_token {
262268
let token = HeaderName::from_static("x-amz-s3session-token");
263269
authorizer = authorizer.with_token_header(token)
264270
}
265271

266-
Some(authorizer)
272+
Ok(authorizer)
267273
}
268274
}
269275

@@ -296,7 +302,7 @@ pub(crate) struct Request<'a> {
296302
path: &'a Path,
297303
config: &'a S3Config,
298304
builder: HttpRequestBuilder,
299-
payload_sha256: Option<digest::Digest>,
305+
payload_sha256: Option<[u8; 32]>,
300306
payload: Option<PutPayload>,
301307
use_session_creds: bool,
302308
idempotent: bool,
@@ -397,13 +403,13 @@ impl Request<'_> {
397403
Self { builder, ..self }
398404
}
399405

400-
pub(crate) fn with_payload(mut self, payload: PutPayload) -> Self {
406+
pub(crate) fn with_payload(mut self, payload: PutPayload) -> Result<Self> {
401407
if (!self.config.skip_signature && self.config.sign_payload)
402408
|| self.config.checksum.is_some()
403409
{
404-
let mut sha256 = Context::new(&digest::SHA256);
405-
payload.iter().for_each(|x| sha256.update(x));
406-
let payload_sha256 = sha256.finish();
410+
let mut ctx = self.config.crypto()?.digest(DigestAlgorithm::Sha256)?;
411+
payload.iter().for_each(|x| ctx.update(x));
412+
let payload_sha256 = ctx.finish()?.try_into().unwrap();
407413

408414
if let Some(Checksum::SHA256) = self.config.checksum {
409415
self.builder = self
@@ -416,24 +422,28 @@ impl Request<'_> {
416422
let content_length = payload.content_length();
417423
self.builder = self.builder.header(CONTENT_LENGTH, content_length);
418424
self.payload = Some(payload);
419-
self
425+
Ok(self)
420426
}
421427

422428
pub(crate) async fn send(self) -> Result<HttpResponse, RequestError> {
423429
let credential = match self.use_session_creds {
424430
true => self.config.get_session_credential().await?,
425-
false => SessionCredential {
426-
credential: self.config.get_credential().await?,
427-
session_token: false,
428-
config: self.config,
429-
},
431+
false => {
432+
let credential = self.config.get_credential().await?;
433+
credential.map(|credential| SessionCredential {
434+
credential,
435+
session_token: false,
436+
config: self.config,
437+
})
438+
}
430439
};
440+
let authorizer = credential.as_ref().map(|x| x.authorizer()).transpose()?;
431441

432442
let sha = self.payload_sha256.as_ref().map(|x| x.as_ref());
433443

434444
let path = self.path.as_ref();
435445
self.builder
436-
.with_aws_sigv4(credential.authorizer(), sha)
446+
.with_aws_sigv4(authorizer, sha)?
437447
.retryable(&self.config.retry_config)
438448
.retry_on_conflict(self.retry_on_conflict)
439449
.idempotent(self.idempotent)
@@ -493,6 +503,7 @@ impl S3Client {
493503
}
494504

495505
let credential = self.config.get_session_credential().await?;
506+
let authorizer = credential.as_ref().map(|x| x.authorizer()).transpose()?;
496507
let url = format!("{}?delete", self.config.bucket_endpoint);
497508

498509
let mut buffer = Vec::new();
@@ -536,7 +547,11 @@ impl S3Client {
536547

537548
let mut builder = self.client.request(Method::POST, url);
538549

539-
let digest = digest::digest(&digest::SHA256, &body);
550+
let crypto = self.config.crypto()?;
551+
let mut ctx = crypto.digest(DigestAlgorithm::Sha256)?;
552+
ctx.update(body.as_ref());
553+
let digest = ctx.finish()?;
554+
540555
builder = builder.header(SHA256_CHECKSUM, BASE64_STANDARD.encode(digest));
541556

542557
// S3 *requires* DeleteObjects to include a Content-MD5 header:
@@ -550,7 +565,7 @@ impl S3Client {
550565
let response = builder
551566
.header(CONTENT_TYPE, "application/xml")
552567
.body(body)
553-
.with_aws_sigv4(credential.authorizer(), Some(digest.as_ref()))
568+
.with_aws_sigv4(authorizer, Some(digest))?
554569
.send_retry(&self.config.retry_config)
555570
.await
556571
.map_err(|source| Error::DeleteObjectsRequest {
@@ -690,7 +705,7 @@ impl S3Client {
690705
.idempotent(true);
691706

692707
request = match data {
693-
PutPartPayload::Part(payload) => request.with_payload(payload),
708+
PutPartPayload::Part(payload) => request.with_payload(payload)?,
694709
PutPartPayload::Copy(path) => request.header(
695710
"x-amz-copy-source",
696711
&format!("{}/{}", self.config.bucket, encode_path(path)),
@@ -775,14 +790,15 @@ impl S3Client {
775790
let body = quick_xml::se::to_string(&request).unwrap();
776791

777792
let credential = self.config.get_session_credential().await?;
793+
let authorizer = credential.as_ref().map(|x| x.authorizer()).transpose()?;
778794
let url = self.config.path_url(location);
779795

780796
let request = self
781797
.client
782798
.post(url)
783799
.query(&[("uploadId", upload_id)])
784800
.body(body)
785-
.with_aws_sigv4(credential.authorizer(), None);
801+
.with_aws_sigv4(authorizer, None)?;
786802

787803
let request = match mode {
788804
CompleteMultipartMode::Overwrite => request,
@@ -821,11 +837,12 @@ impl S3Client {
821837
#[cfg(test)]
822838
pub(crate) async fn get_object_tagging(&self, path: &Path) -> Result<HttpResponse> {
823839
let credential = self.config.get_session_credential().await?;
840+
let authorizer = credential.as_ref().map(|x| x.authorizer()).transpose()?;
824841
let url = format!("{}?tagging", self.config.path_url(path));
825842
let response = self
826843
.client
827844
.request(Method::GET, url)
828-
.with_aws_sigv4(credential.authorizer(), None)
845+
.with_aws_sigv4(authorizer, None)?
829846
.send_retry(&self.config.retry_config)
830847
.await
831848
.map_err(|e| e.error(STORE, path.to_string()))?;
@@ -856,6 +873,7 @@ impl GetClient for S3Client {
856873
options: GetOptions,
857874
) -> Result<HttpResponse> {
858875
let credential = self.config.get_session_credential().await?;
876+
let authorizer = credential.as_ref().map(|x| x.authorizer()).transpose()?;
859877
let url = self.config.path_url(path);
860878
let method = match options.head {
861879
true => Method::HEAD,
@@ -878,7 +896,7 @@ impl GetClient for S3Client {
878896

879897
let response = builder
880898
.with_get_options(options)
881-
.with_aws_sigv4(credential.authorizer(), None)
899+
.with_aws_sigv4(authorizer, None)?
882900
.retryable_request()
883901
.send(ctx)
884902
.await
@@ -897,6 +915,7 @@ impl ListClient for Arc<S3Client> {
897915
opts: PaginatedListOptions,
898916
) -> Result<PaginatedListResult> {
899917
let credential = self.config.get_session_credential().await?;
918+
let authorizer = credential.as_ref().map(|x| x.authorizer()).transpose()?;
900919
let url = self.config.bucket_endpoint.clone();
901920

902921
let mut query = Vec::with_capacity(4);
@@ -930,7 +949,7 @@ impl ListClient for Arc<S3Client> {
930949
.request(Method::GET, &url)
931950
.extensions(opts.extensions)
932951
.query(&query)
933-
.with_aws_sigv4(credential.authorizer(), None)
952+
.with_aws_sigv4(authorizer, None)?
934953
.send_retry(&self.config.retry_config)
935954
.await
936955
.map_err(|source| Error::ListRequest { source })?
@@ -1000,6 +1019,7 @@ mod tests {
10001019
conditional_put: Default::default(),
10011020
encryption_headers: Default::default(),
10021021
request_payer: false,
1022+
crypto: None,
10031023
};
10041024

10051025
let client = S3Client::new(config, HttpClient::new(reqwest::Client::new()));

0 commit comments

Comments
 (0)