JakeDern commented on issue #413:
URL:
https://github.com/apache/arrow-rs-object-store/issues/413#issuecomment-3141808890
I had also started poking around with an implementation. Mine is a little
different from yours @faysou in that I was trying to avoid mutually exclusive
features because my understanding is that those can cause problems if two
different dependencies use them differently. The idea being the crate uses ring
by default, but if some feature like `open-ssl` is enabled then it uses the
`openssl` crate instead.
I also pulled everything out to a crypto module and tried to encapsulate it
behind a trait. Here's as far as I got:
```rust
use std::ops::Deref;
/// Error type for crypto operations
/// TODO: Figure out a better pattern here
#[derive(Debug)]
pub(crate) enum CryptoError {
#[cfg(feature = "open-ssl")]
OpenSsl(openssl::error::ErrorStack),
}
impl std::fmt::Display for CryptoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(feature = "open-ssl")]
CryptoError::OpenSsl(e) => write!(f, "OpenSSL error: {}", e),
}
}
}
impl std::error::Error for CryptoError {}
#[cfg(feature = "open-ssl")]
impl From<openssl::error::ErrorStack> for CryptoError {
fn from(e: openssl::error::ErrorStack) -> Self {
CryptoError::OpenSsl(e)
}
}
/// Wrapper type for digests that abstracts over different crypto providers'
types, some of which don't allocate
pub(crate) struct Digest<T> {
inner: T,
}
impl<T> Digest<T> {
fn new(inner: T) -> Self {
Self { inner }
}
}
impl<T: AsRef<[u8]>> Deref for Digest<T> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.inner.as_ref()
}
}
/// Wrapper type for HMAC tags that abstracts over different crypto
providers' types, some of which don't allocate
pub(crate) struct Tag<T> {
inner: T,
}
impl<T> Tag<T> {
fn new(inner: T) -> Self {
Self { inner }
}
}
impl<T: AsRef<[u8]>> Deref for Tag<T> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.inner.as_ref()
}
}
pub trait Crypto {
type DigestType: AsRef<[u8]>;
type TagType: AsRef<[u8]>;
fn digest_sha256(bytes: &[u8]) -> Result<Digest<Self::DigestType>,
CryptoError>;
fn hmac_sha256(secret: &[u8], bytes: &[u8]) ->
Result<Tag<Self::TagType>, CryptoError>;
}
// Type alias to the current crypto provider based on feature flags
#[cfg(feature = "open-ssl")]
type CryptoProvider = openssl_crypto::OpenSslCrypto;
#[cfg(not(feature = "open-ssl"))]
type CryptoProvider = ring_crypto::RingCrypto;
// Type aliases for the associated types of the current provider
// Note: This seems to be necessary or we get ambiguous type errors on the
public functions
type CurrentDigest = <CryptoProvider as Crypto>::DigestType;
type CurrentTag = <CryptoProvider as Crypto>::TagType;
pub(crate) fn digest_sha256(bytes: &[u8]) -> Result<Digest<CurrentDigest>,
CryptoError> {
CryptoProvider::digest_sha256(bytes)
}
pub(crate) fn hmac_sha256(secret: &[u8], bytes: &[u8]) ->
Result<Tag<CurrentTag>, CryptoError> {
CryptoProvider::hmac_sha256(secret, bytes)
}
pub(crate) fn hex_digest(bytes: &[u8]) -> Result<String, CryptoError> {
let sha_256_hash = digest_sha256(bytes)?;
Ok(hex_encode(&sha_256_hash))
}
pub(crate) fn hex_encode(bytes: &[u8]) -> String {
use std::fmt::Write;
let mut out = String::with_capacity(bytes.len() * 2);
for byte in bytes {
// String writing is infallible
let _ = write!(out, "{byte:02x}");
}
out
}
#[cfg(feature = "open-ssl")]
mod openssl_crypto {
use openssl::hash::{DigestBytes, MessageDigest};
use openssl::pkey::PKey;
use openssl::sign::Signer;
use super::{Crypto, CryptoError, Digest, Tag};
pub struct OpenSslCrypto;
impl Crypto for OpenSslCrypto {
type DigestType = DigestBytes;
type TagType = Vec<u8>;
fn digest_sha256(bytes: &[u8]) -> Result<Digest<Self::DigestType>,
CryptoError> {
let digest = openssl::hash::hash(MessageDigest::sha256(),
bytes)?;
Ok(Digest::new(digest))
}
fn hmac_sha256(secret: &[u8], bytes: &[u8]) ->
Result<Tag<Self::TagType>, CryptoError> {
let key = PKey::hmac(secret)?;
let mut signer = Signer::new(MessageDigest::sha256(), &key)?;
signer.update(bytes)?;
let hmac = signer.sign_to_vec()?;
Ok(Tag::new(hmac))
}
}
}
#[cfg(not(feature = "open-ssl"))]
mod ring_crypto {
use super::{Crypto, CryptoError, Digest, Tag};
pub struct RingCrypto;
impl Crypto for RingCrypto {
type DigestType = ring::digest::Digest;
type TagType = ring::hmac::Tag;
fn digest_sha256(bytes: &[u8]) -> Result<Digest<Self::DigestType>,
CryptoError> {
let digest = ring::digest::digest(&ring::digest::SHA256, bytes);
Ok(Digest::new(digest))
}
fn hmac_sha256(secret: &[u8], bytes: &[u8]) ->
Result<Tag<Self::TagType>, CryptoError> {
let key = ring::hmac::Key::new(ring::hmac::HMAC_SHA256, secret);
let tag = ring::hmac::sign(&key, bytes);
Ok(Tag::new(tag))
}
}
}
```
I'm happy to clean this up as well, I just need to write some tests and also
move the code in the crate to use this instead.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]