google-cloud-auth 1.10.0

Google Cloud Client Libraries for Rust - Authentication
Documentation
// Copyright 2025 Google LLC
//
// Licensed 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
//
//     https://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.

//! Abstraction for signing arbitrary bytes using Google Cloud [Credentials][crate::credentials::Credentials].
//!
//! A [`Signer`] is used to sign data, typically for authentication or
//! authorization purposes. One primary use case in the Google Cloud
//! ecosystem is generating [Signed URLs] for Google Cloud Storage.
//!
//! The main type in this module is [Signer]. This is an opaque type
//! that implements the [SigningProvider] trait and can be used to
//! sign content. Use [crate::credentials::Builder::build_signer]
//! to create a `Signer` from loaded credentials.
//!
//! ## Example: Creating a Signer using [Application Default Credentials] (ADC)
//!
//! This is the recommended way for most applications. It automatically finds
//! credentials from the environment. See how [Application Default Credentials]
//! works.
//!
//! ```
//! use google_cloud_auth::credentials::Builder;
//! use google_cloud_auth::signer::Signer;
//!
//! # fn sample() -> anyhow::Result<()> {
//! let signer: Signer = Builder::default().build_signer()?;
//! # Ok(()) }
//! ```
//!
//! ## Example: Creating a Signer using a Service Account Key File
//!
//! This is useful when you have a specific service account key file (JSON)
//! and want to use it directly. Service account based signers work by local
//! signing and do not make network requests, which can be useful in
//! environments where network access is restricted and performance is
//! critical.
//!
//! <div class="warning">
//!     <strong>Caution:</strong> Service account keys are a security risk if not managed correctly.
//!     See <a href="https://docs.cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys">
//!     Best practices for managing service account keys</a> for more information.
//! </div>
//!
//! ```
//! use google_cloud_auth::credentials::service_account::Builder;
//! use google_cloud_auth::signer::Signer;
//!
//! # fn sample() -> anyhow::Result<()> {
//! let service_account_key = serde_json::json!({ /* add details here */ });
//!
//! let signer: Signer = Builder::new(service_account_key).build_signer()?;
//! # Ok(()) }
//! ```
//!
//! [Application Default Credentials]: https://docs.cloud.google.com/docs/authentication/application-default-credentials
//! [Signed URLs]: https://cloud.google.com/storage/docs/access-control/signed-urls

use std::sync::Arc;

pub(crate) mod iam;
pub(crate) mod mds;
pub(crate) mod service_account;

/// A specialized `Result` type for signing operations.
pub type Result<T> = std::result::Result<T, SigningError>;

/// An implementation of [crate::signer::SigningProvider] that wraps a
/// dynamic provider.
///
/// This struct is the primary entry point for signing operations.
/// The most common way to create an instance of `Signer`
/// is via [crate::credentials::Builder::build_signer].
///
/// # Example
///
/// ```
/// use google_cloud_auth::credentials::Builder;
/// use google_cloud_auth::signer::Signer;
///
/// # fn sample() -> anyhow::Result<()> {
/// let signer: Signer = Builder::default().build_signer()?;
/// # Ok(()) }
/// ```
#[derive(Clone, Debug)]
pub struct Signer {
    pub(crate) inner: Arc<dyn dynamic::SigningProvider>,
}

impl<T> std::convert::From<T> for Signer
where
    T: SigningProvider + Send + Sync + 'static,
{
    fn from(value: T) -> Self {
        Self {
            inner: Arc::new(value),
        }
    }
}

impl Signer {
    /// Returns the email address of the client performing the signing.
    ///
    /// This is typically the service account email.
    pub async fn client_email(&self) -> Result<String> {
        self.inner.client_email().await
    }

    /// Signs the provided content using the underlying provider.
    ///
    /// The content is typically a string-to-sign generated by the caller.
    pub async fn sign<T>(&self, content: T) -> Result<bytes::Bytes>
    where
        T: AsRef<[u8]> + Send + Sync,
    {
        self.inner.sign(content.as_ref()).await
    }
}

/// A trait for types that can sign content.
pub trait SigningProvider: std::fmt::Debug {
    /// Returns the email address of the authorizer.
    ///
    /// It is typically the Google service account client email address
    /// from the Google Developers Console in the form of
    /// "xxx@developer.gserviceaccount.com".
    fn client_email(&self) -> impl Future<Output = Result<String>> + Send;

    /// Signs the content.
    ///
    /// Returns the signature.
    fn sign(&self, content: &[u8]) -> impl Future<Output = Result<bytes::Bytes>> + Send;
}

pub(crate) mod dynamic {
    use super::Result;

    /// A dyn-compatible, crate-private version of `SigningProvider`.
    #[async_trait::async_trait]
    pub trait SigningProvider: Send + Sync + std::fmt::Debug {
        async fn client_email(&self) -> Result<String>;
        async fn sign(&self, content: &[u8]) -> Result<bytes::Bytes>;
    }

    /// The public CredentialsProvider implements the dyn-compatible CredentialsProvider.
    #[async_trait::async_trait]
    impl<T> SigningProvider for T
    where
        T: super::SigningProvider + Send + Sync,
    {
        async fn client_email(&self) -> Result<String> {
            T::client_email(self).await
        }

        async fn sign(&self, content: &[u8]) -> Result<bytes::Bytes> {
            T::sign(self, content).await
        }
    }
}

type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;

/// Represents an error occurred during a signing operation.
///
/// This error can occur when using a [Signer] to sign content. It may represent
/// a transport error when using a remote API (like the IAM signBlob API) or a
/// parsing/signing error when performing local signing with a service account key.
///
/// Applications rarely need to create instances of this error type. The
/// exception might be when testing application code, where the application is
/// mocking a [Signer] behavior.
///
/// # Example
///
/// ```
/// # use google_cloud_auth::signer::{SigningError, Signer};
/// # async fn handle_signer(signer: Signer) {
/// let content = b"content to sign";
/// match signer.sign(content).await {
///     Ok(signature) => println!("Signature: {:?}", signature),
///     Err(e) if e.is_transport() => println!("Transport error: {}", e),
///     Err(e) => println!("Other error: {}", e),
/// }
/// # }
/// ```
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub struct SigningError(SigningErrorKind);

impl SigningError {
    /// Returns true if the error was caused by a transport problem.
    ///
    /// This typically happens when using a remote signer (like the IAM signBlob API)
    /// and the network request fails or the service returns a transient error.
    pub fn is_transport(&self) -> bool {
        matches!(self.0, SigningErrorKind::Transport(_))
    }

    /// Returns true if the error was caused by a parsing problem.
    ///
    /// This typically happens when the signer is using a private key that
    /// cannot be parsed or is invalid.
    pub fn is_parsing(&self) -> bool {
        matches!(self.0, SigningErrorKind::Parsing(_))
    }

    /// Returns true if the error was caused by a problem during the signing operation.
    ///
    /// This can happen during local signing if the cryptographic operation fails,
    /// or if the remote API returns an error that is not a transport error.
    pub fn is_sign(&self) -> bool {
        matches!(self.0, SigningErrorKind::Sign(_))
    }

    /// Creates a [SigningError] representing a parsing error.
    ///
    /// This function is primarily intended for use in the client libraries
    /// implementation or in testing mocks.
    pub(crate) fn parsing<T>(source: T) -> SigningError
    where
        T: Into<BoxError>,
    {
        SigningError(SigningErrorKind::Parsing(source.into()))
    }

    /// Creates a [SigningError] representing a transport error.
    ///
    /// This function is primarily intended for use in the client libraries
    /// implementation or in testing mocks.
    pub(crate) fn transport<T>(source: T) -> SigningError
    where
        T: Into<BoxError>,
    {
        SigningError(SigningErrorKind::Transport(source.into()))
    }

    /// Creates a [SigningError] representing a signing error.
    ///
    /// This function is primarily intended for use in the client libraries
    /// implementation or in testing mocks.
    pub(crate) fn sign<T>(source: T) -> SigningError
    where
        T: Into<BoxError>,
    {
        SigningError(SigningErrorKind::Sign(source.into()))
    }

    /// Creates a new `SigningError` from a message.
    ///
    /// This function is only intended for use in the client libraries
    /// implementation. Application may use this in mocks, though we do not
    /// recommend that you write tests for specific error cases.
    ///
    /// # Parameters
    /// * `message` - The underlying error that caused the signing failure.
    #[doc(hidden)]
    pub fn from_msg<T>(message: T) -> SigningError
    where
        T: Into<BoxError>,
    {
        SigningError(SigningErrorKind::Sign(message.into()))
    }
}

#[derive(thiserror::Error, Debug)]
enum SigningErrorKind {
    #[error("failed to generate signature via IAM API: {0}")]
    Transport(#[source] BoxError),
    #[error("failed to parse private key: {0}")]
    Parsing(#[source] BoxError),
    #[error("failed to sign content: {0}")]
    Sign(#[source] BoxError),
}

#[cfg(test)]
mod tests {
    use super::*;

    type TestResult = anyhow::Result<()>;

    mockall::mock! {
        #[derive(Debug)]
        Signer{}

        impl SigningProvider for Signer {
            async fn client_email(&self) -> Result<String>;
            async fn sign(&self, content: &[u8]) -> Result<bytes::Bytes>;
        }
    }

    #[tokio::test]
    async fn test_signer_success() -> TestResult {
        let mut mock = MockSigner::new();
        mock.expect_client_email()
            .returning(|| Ok("test".to_string()));
        mock.expect_sign()
            .returning(|_| Ok(bytes::Bytes::from("test")));
        let signer = Signer::from(mock);

        let result = signer.client_email().await?;
        assert_eq!(result, "test");
        let result = signer.sign("test").await?;
        assert_eq!(result, "test");

        Ok(())
    }

    #[tokio::test]
    async fn test_signer_error() -> TestResult {
        let mut mock = MockSigner::new();
        mock.expect_client_email()
            .returning(|| Err(SigningError::transport("test")));
        mock.expect_sign()
            .returning(|_| Err(SigningError::sign("test")));
        let signer = Signer::from(mock);

        let result = signer.client_email().await;
        assert!(result.is_err(), "{result:?}");
        assert!(result.unwrap_err().is_transport());
        let result = signer.sign("test").await;
        assert!(result.is_err(), "{result:?}");
        assert!(result.unwrap_err().is_sign());

        Ok(())
    }
}