Skip to main content

object_store/client/
crypto.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::Result;
19
20/// Algorithm for computing digests
21#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
22#[non_exhaustive]
23pub enum DigestAlgorithm {
24    /// SHA-256
25    Sha256,
26}
27
28/// Algorithm for signing payloads
29#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
30#[non_exhaustive]
31pub enum SigningAlgorithm {
32    /// RSASSA-PKCS1-v1_5 using SHA-256
33    RS256,
34}
35
36/// Provides cryptographic primitives
37pub trait CryptoProvider: std::fmt::Debug + Send + Sync {
38    /// Compute a digest
39    fn digest(&self, algorithm: DigestAlgorithm) -> Result<Box<dyn DigestContext>>;
40
41    /// Compute an HMAC with the provided `secret`
42    fn hmac(&self, algorithm: DigestAlgorithm, secret: &[u8]) -> Result<Box<dyn HmacContext>>;
43
44    /// Sign a payload with the provided PEM-encoded secret
45    fn sign(&self, algorithm: SigningAlgorithm, pem: &[u8]) -> Result<Box<dyn Signer>>;
46}
47
48/// Incrementally compute a digest, see [`CryptoProvider::digest`]
49pub trait DigestContext: Send {
50    /// Updates the digest with all the data in data.
51    ///
52    /// It is implementation-defined behaviour to call this after calling [`Self::finish`]
53    fn update(&mut self, data: &[u8]);
54
55    /// Finalizes the digest calculation and returns the digest value.
56    ///
57    /// It is implementation-defined behaviour to call this after calling [`Self::finish`]
58    fn finish(&mut self) -> Result<&[u8]>;
59}
60
61/// Incrementally compute a HMAC, see [`CryptoProvider::hmac`]
62pub trait HmacContext: Send {
63    /// Updates the HMAC with all the data in data.
64    ///
65    /// It is implementation-defined behaviour to call this after calling [`Self::finish`]
66    fn update(&mut self, data: &[u8]);
67
68    /// Finalizes the HMAC calculation and returns the HMAC value.
69    ///
70    /// It is implementation-defined behaviour to call this after calling [`Self::finish`]
71    fn finish(&mut self) -> Result<&[u8]>;
72}
73
74/// Sign a payload, see [`CryptoProvider::sign`]
75pub trait Signer: Send + Sync {
76    /// Sign the provided payload
77    fn sign(&self, string_to_sign: &[u8]) -> Result<Vec<u8>>;
78}
79
80/// Attempts to find a [`CryptoProvider`]
81///
82/// If `custom` is `Some(v)` returns `v` otherwise returns the compile-time default
83///
84/// If both `ring` and `aws-lc-rs` are enabled, the `aws-lc-rs` provider is used.
85pub(crate) fn crypto_provider(custom: Option<&dyn CryptoProvider>) -> Result<&dyn CryptoProvider> {
86    if let Some(x) = custom {
87        return Ok(x);
88    }
89
90    #[cfg(feature = "aws-lc-rs")]
91    {
92        Ok(&aws_lc_rs::PROVIDER)
93    }
94
95    #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))]
96    {
97        Ok(&ring::PROVIDER)
98    }
99
100    #[cfg(not(any(feature = "ring", feature = "aws-lc-rs")))]
101    {
102        Err(crate::Error::NotSupported {
103            source: "Must enable aws-lc-rs, ring, or specify custom CryptoProvider"
104                .to_string()
105                .into(),
106        })
107    }
108}
109
110#[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))]
111pub(crate) mod ring {
112    use super::*;
113    use ::ring::{digest, hmac, rand, signature};
114    use thiserror::Error;
115
116    #[derive(Debug, Error)]
117    pub(crate) enum RingError {
118        #[error("No RSA key found in pem file")]
119        MissingKey,
120
121        #[error("Invalid RSA key: {}", source)]
122        InvalidKey {
123            #[from]
124            source: ::ring::error::KeyRejected,
125        },
126
127        #[error("Error reading pem file: {}", source)]
128        ReadPem {
129            source: rustls_pki_types::pem::Error,
130        },
131
132        #[error("Error signing: {}", source)]
133        Sign { source: ::ring::error::Unspecified },
134    }
135
136    impl From<RingError> for crate::Error {
137        fn from(value: RingError) -> Self {
138            Self::Generic {
139                store: "RingCryptoProvider",
140                source: Box::new(value),
141            }
142        }
143    }
144
145    pub(crate) const PROVIDER: RingCryptoProvider = RingCryptoProvider { _private: () };
146
147    #[derive(Debug, Default)]
148    pub(crate) struct RingCryptoProvider {
149        _private: (),
150    }
151
152    impl CryptoProvider for RingCryptoProvider {
153        fn digest(&self, algorithm: DigestAlgorithm) -> Result<Box<dyn DigestContext>> {
154            let algorithm = match algorithm {
155                DigestAlgorithm::Sha256 => &digest::SHA256,
156            };
157            let ctx = digest::Context::new(algorithm);
158            Ok(Box::new(RingDigestContext {
159                ctx: Some(ctx),
160                out: None,
161            }))
162        }
163
164        fn hmac(&self, algorithm: DigestAlgorithm, secret: &[u8]) -> Result<Box<dyn HmacContext>> {
165            let algorithm = match algorithm {
166                DigestAlgorithm::Sha256 => hmac::HMAC_SHA256,
167            };
168            let ctx = hmac::Context::with_key(&hmac::Key::new(algorithm, secret));
169            Ok(Box::new(RingHmacContext {
170                ctx: Some(ctx),
171                out: None,
172            }))
173        }
174
175        fn sign(&self, algorithm: SigningAlgorithm, pem: &[u8]) -> Result<Box<dyn Signer>> {
176            match algorithm {
177                SigningAlgorithm::RS256 => Ok(Box::new(RsaKeyPair::from_pem(pem)?)),
178            }
179        }
180    }
181
182    struct RingDigestContext {
183        ctx: Option<digest::Context>,
184        out: Option<digest::Digest>,
185    }
186
187    impl DigestContext for RingDigestContext {
188        fn update(&mut self, data: &[u8]) {
189            self.ctx.as_mut().unwrap().update(data);
190        }
191
192        fn finish(&mut self) -> Result<&[u8]> {
193            let digest = self.ctx.take().unwrap().finish();
194            Ok(digest::Digest::as_ref(self.out.insert(digest)))
195        }
196    }
197
198    struct RingHmacContext {
199        ctx: Option<hmac::Context>,
200        out: Option<hmac::Tag>,
201    }
202
203    impl HmacContext for RingHmacContext {
204        fn update(&mut self, data: &[u8]) {
205            self.ctx.as_mut().unwrap().update(data);
206        }
207
208        fn finish(&mut self) -> Result<&[u8]> {
209            let tag = self.ctx.take().unwrap().sign();
210            Ok(hmac::Tag::as_ref(self.out.insert(tag)))
211        }
212    }
213
214    /// A private RSA key for a service account
215    #[derive(Debug)]
216    pub(crate) struct RsaKeyPair(signature::RsaKeyPair);
217
218    impl RsaKeyPair {
219        /// Parses a pem-encoded RSA key
220        pub(crate) fn from_pem(encoded: &[u8]) -> Result<Self, RingError> {
221            use rustls_pki_types::PrivateKeyDer;
222            use rustls_pki_types::pem::PemObject;
223
224            match PrivateKeyDer::from_pem_slice(encoded) {
225                Ok(PrivateKeyDer::Pkcs8(key)) => Self::from_pkcs8(key.secret_pkcs8_der()),
226                Ok(PrivateKeyDer::Pkcs1(key)) => Self::from_der(key.secret_pkcs1_der()),
227                Ok(_) => Err(RingError::MissingKey),
228                Err(source) => Err(RingError::ReadPem { source }),
229            }
230        }
231
232        /// Parses an unencrypted PKCS#8-encoded RSA private key.
233        pub(crate) fn from_pkcs8(key: &[u8]) -> Result<Self, RingError> {
234            Ok(Self(signature::RsaKeyPair::from_pkcs8(key)?))
235        }
236
237        /// Parses an unencrypted PKCS#8-encoded RSA private key.
238        pub(crate) fn from_der(key: &[u8]) -> Result<Self, RingError> {
239            Ok(Self(signature::RsaKeyPair::from_der(key)?))
240        }
241    }
242
243    impl Signer for RsaKeyPair {
244        fn sign(&self, string_to_sign: &[u8]) -> Result<Vec<u8>> {
245            let mut signature = vec![0; self.0.public().modulus_len()];
246            self.0
247                .sign(
248                    &signature::RSA_PKCS1_SHA256,
249                    &rand::SystemRandom::new(),
250                    string_to_sign,
251                    &mut signature,
252                )
253                .map_err(|source| RingError::Sign { source })?;
254
255            Ok(signature)
256        }
257    }
258}
259
260#[cfg(feature = "aws-lc-rs")]
261pub(crate) mod aws_lc_rs {
262    use super::*;
263    use ::aws_lc_rs::{digest, hmac, rand, signature};
264    use thiserror::Error;
265
266    #[derive(Debug, Error)]
267    pub(crate) enum AwsLcError {
268        #[error("No RSA key found in pem file")]
269        MissingKey,
270
271        #[error("Invalid RSA key: {}", source)]
272        InvalidKey {
273            #[from]
274            source: ::aws_lc_rs::error::KeyRejected,
275        },
276
277        #[error("Error reading pem file: {}", source)]
278        ReadPem {
279            source: rustls_pki_types::pem::Error,
280        },
281
282        #[error("Error signing: {}", source)]
283        Sign {
284            source: ::aws_lc_rs::error::Unspecified,
285        },
286    }
287
288    impl From<AwsLcError> for crate::Error {
289        fn from(value: AwsLcError) -> Self {
290            Self::Generic {
291                store: "AwsLcCryptoProvider",
292                source: Box::new(value),
293            }
294        }
295    }
296
297    pub(crate) const PROVIDER: AwsLcCryptoProvider = AwsLcCryptoProvider { _private: () };
298
299    #[derive(Debug, Default)]
300    pub(crate) struct AwsLcCryptoProvider {
301        _private: (),
302    }
303
304    impl CryptoProvider for AwsLcCryptoProvider {
305        fn digest(&self, algorithm: DigestAlgorithm) -> Result<Box<dyn DigestContext>> {
306            let algorithm = match algorithm {
307                DigestAlgorithm::Sha256 => &digest::SHA256,
308            };
309            let ctx = digest::Context::new(algorithm);
310            Ok(Box::new(AwsLcDigestContext {
311                ctx: Some(ctx),
312                out: None,
313            }))
314        }
315
316        fn hmac(&self, algorithm: DigestAlgorithm, secret: &[u8]) -> Result<Box<dyn HmacContext>> {
317            let algorithm = match algorithm {
318                DigestAlgorithm::Sha256 => hmac::HMAC_SHA256,
319            };
320            let ctx = hmac::Context::with_key(&hmac::Key::new(algorithm, secret));
321            Ok(Box::new(AwsLcHmacContext {
322                ctx: Some(ctx),
323                out: None,
324            }))
325        }
326
327        fn sign(&self, algorithm: SigningAlgorithm, pem: &[u8]) -> Result<Box<dyn Signer>> {
328            match algorithm {
329                SigningAlgorithm::RS256 => Ok(Box::new(RsaKeyPair::from_pem(pem)?)),
330            }
331        }
332    }
333
334    struct AwsLcDigestContext {
335        ctx: Option<digest::Context>,
336        out: Option<digest::Digest>,
337    }
338
339    impl DigestContext for AwsLcDigestContext {
340        fn update(&mut self, data: &[u8]) {
341            self.ctx.as_mut().unwrap().update(data);
342        }
343
344        fn finish(&mut self) -> Result<&[u8]> {
345            let digest = self.ctx.take().unwrap().finish();
346            Ok(digest::Digest::as_ref(self.out.insert(digest)))
347        }
348    }
349
350    struct AwsLcHmacContext {
351        ctx: Option<hmac::Context>,
352        out: Option<hmac::Tag>,
353    }
354
355    impl HmacContext for AwsLcHmacContext {
356        fn update(&mut self, data: &[u8]) {
357            self.ctx.as_mut().unwrap().update(data);
358        }
359
360        fn finish(&mut self) -> Result<&[u8]> {
361            let tag = self.ctx.take().unwrap().sign();
362            Ok(hmac::Tag::as_ref(self.out.insert(tag)))
363        }
364    }
365
366    /// A private RSA key for a service account
367    #[derive(Debug)]
368    pub(crate) struct RsaKeyPair(signature::RsaKeyPair);
369
370    impl RsaKeyPair {
371        /// Parses a pem-encoded RSA key
372        pub(crate) fn from_pem(encoded: &[u8]) -> Result<Self, AwsLcError> {
373            use rustls_pki_types::PrivateKeyDer;
374            use rustls_pki_types::pem::PemObject;
375
376            match PrivateKeyDer::from_pem_slice(encoded) {
377                Ok(PrivateKeyDer::Pkcs8(key)) => Self::from_pkcs8(key.secret_pkcs8_der()),
378                Ok(PrivateKeyDer::Pkcs1(key)) => Self::from_der(key.secret_pkcs1_der()),
379                Ok(_) => Err(AwsLcError::MissingKey),
380                Err(source) => Err(AwsLcError::ReadPem { source }),
381            }
382        }
383
384        /// Parses an unencrypted PKCS#8-encoded RSA private key.
385        pub(crate) fn from_pkcs8(key: &[u8]) -> Result<Self, AwsLcError> {
386            Ok(Self(signature::RsaKeyPair::from_pkcs8(key)?))
387        }
388
389        /// Parses an unencrypted PKCS#8-encoded RSA private key.
390        pub(crate) fn from_der(key: &[u8]) -> Result<Self, AwsLcError> {
391            Ok(Self(signature::RsaKeyPair::from_der(key)?))
392        }
393    }
394
395    impl Signer for RsaKeyPair {
396        fn sign(&self, string_to_sign: &[u8]) -> Result<Vec<u8>> {
397            let mut signature = vec![0; self.0.public_modulus_len()];
398            self.0
399                .sign(
400                    &signature::RSA_PKCS1_SHA256,
401                    &rand::SystemRandom::new(),
402                    string_to_sign,
403                    &mut signature,
404                )
405                .map_err(|source| AwsLcError::Sign { source })?;
406
407            Ok(signature)
408        }
409    }
410}