google_cloud_auth/
signer.rs

1// Copyright 2025 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Abstraction for signing arbitrary bytes using Google Cloud [Credentials][crate::credentials::Credentials].
16//!
17//! A [`Signer`] is used to sign data, typically for authentication or
18//! authorization purposes. One primary use case in the Google Cloud
19//! ecosystem is generating [Signed URLs] for Google Cloud Storage.
20//!
21//! The main type in this module is [Signer]. This is an opaque type
22//! that implements the [SigningProvider] trait and can be used to
23//! sign content. Use [crate::credentials::Builder::build_signer]
24//! to create a `Signer` from loaded credentials.
25//!
26//! ## Example: Creating a Signer using [Application Default Credentials] (ADC)
27//!
28//! This is the recommended way for most applications. It automatically finds
29//! credentials from the environment. See how [Application Default Credentials]
30//! works.
31//!
32//! ```
33//! use google_cloud_auth::credentials::Builder;
34//! use google_cloud_auth::signer::Signer;
35//!
36//! # tokio_test::block_on(async {
37//! let signer: Signer = Builder::default().build_signer()?;
38//! # Ok::<(), anyhow::Error>(())
39//! # });
40//! ```
41//!
42//! ## Example: Creating a Signer using a Service Account Key File
43//!
44//! This is useful when you have a specific service account key file (JSON)
45//! and want to use it directly. Service account based signers work by local
46//! signing and do not make network requests, which can be useful in
47//! environments where network access is restricted and performance is
48//! critical.
49//!
50//! <div class="warning">
51//!     <strong>Caution:</strong> Service account keys are a security risk if not managed correctly.
52//!     See <a href="https://docs.cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys">
53//!     Best practices for managing service account keys</a> for more information.
54//! </div>
55//!
56//! ```
57//! use google_cloud_auth::credentials::service_account::Builder;
58//! use google_cloud_auth::signer::Signer;
59//!
60//! # tokio_test::block_on(async {
61//! let service_account_key = serde_json::json!({ /* add details here */ });
62//!
63//! let signer: Signer = Builder::new(service_account_key).build_signer()?;
64//! # Ok::<(), anyhow::Error>(())
65//! # });
66//! ```
67//!
68//! [Application Default Credentials]: https://docs.cloud.google.com/docs/authentication/application-default-credentials
69//! [Signed URLs]: https://cloud.google.com/storage/docs/access-control/signed-urls
70
71use std::sync::Arc;
72
73pub(crate) mod iam;
74pub(crate) mod mds;
75pub(crate) mod service_account;
76
77pub type Result<T> = std::result::Result<T, SigningError>;
78
79/// An implementation of [crate::signer::SigningProvider] that wraps a
80/// dynamic provider.
81///
82/// This struct is the primary entry point for signing operations.
83/// The most common way to create an instance of `Signer`
84/// is via [crate::credentials::Builder::build_signer].
85///
86/// # Example
87///
88/// ```
89/// use google_cloud_auth::credentials::Builder;
90/// use google_cloud_auth::signer::Signer;
91///
92/// # tokio_test::block_on(async {
93/// let signer: Signer = Builder::default().build_signer()?;
94/// # Ok::<(), anyhow::Error>(())
95/// # });
96/// ```
97#[derive(Clone, Debug)]
98pub struct Signer {
99    pub(crate) inner: Arc<dyn dynamic::SigningProvider>,
100}
101
102impl<T> std::convert::From<T> for Signer
103where
104    T: SigningProvider + Send + Sync + 'static,
105{
106    fn from(value: T) -> Self {
107        Self {
108            inner: Arc::new(value),
109        }
110    }
111}
112
113impl Signer {
114    /// Returns the email address of the client performing the signing.
115    ///
116    /// This is typically the service account email.
117    pub async fn client_email(&self) -> Result<String> {
118        self.inner.client_email().await
119    }
120
121    /// Signs the provided content using the underlying provider.
122    ///
123    /// The content is typically a string-to-sign generated by the caller.
124    pub async fn sign<T>(&self, content: T) -> Result<bytes::Bytes>
125    where
126        T: AsRef<[u8]> + Send + Sync,
127    {
128        self.inner.sign(content.as_ref()).await
129    }
130}
131
132/// A trait for types that can sign content.
133pub trait SigningProvider: std::fmt::Debug {
134    /// Returns the email address of the authorizer.
135    ///
136    /// It is typically the Google service account client email address
137    /// from the Google Developers Console in the form of
138    /// "xxx@developer.gserviceaccount.com".
139    fn client_email(&self) -> impl Future<Output = Result<String>> + Send;
140
141    /// Signs the content.
142    ///
143    /// Returns the signature.
144    fn sign(&self, content: &[u8]) -> impl Future<Output = Result<bytes::Bytes>> + Send;
145}
146
147pub(crate) mod dynamic {
148    use super::Result;
149
150    /// A dyn-compatible, crate-private version of `SigningProvider`.
151    #[async_trait::async_trait]
152    pub trait SigningProvider: Send + Sync + std::fmt::Debug {
153        async fn client_email(&self) -> Result<String>;
154        async fn sign(&self, content: &[u8]) -> Result<bytes::Bytes>;
155    }
156
157    /// The public CredentialsProvider implements the dyn-compatible CredentialsProvider.
158    #[async_trait::async_trait]
159    impl<T> SigningProvider for T
160    where
161        T: super::SigningProvider + Send + Sync,
162    {
163        async fn client_email(&self) -> Result<String> {
164            T::client_email(self).await
165        }
166
167        async fn sign(&self, content: &[u8]) -> Result<bytes::Bytes> {
168            T::sign(self, content).await
169        }
170    }
171}
172
173type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
174
175/// Represents an error occurred during a signing operation.
176///
177/// This error can occur when using a [Signer] to sign content. It may represent
178/// a transport error when using a remote API (like the IAM signBlob API) or a
179/// parsing/signing error when performing local signing with a service account key.
180///
181/// Applications rarely need to create instances of this error type. The
182/// exception might be when testing application code, where the application is
183/// mocking a [Signer] behavior.
184///
185/// # Example
186///
187/// ```
188/// # use google_cloud_auth::signer::{SigningError, Signer};
189/// # async fn handle_signer(signer: Signer) {
190/// let content = b"content to sign";
191/// match signer.sign(content).await {
192///     Ok(signature) => println!("Signature: {:?}", signature),
193///     Err(e) if e.is_transport() => println!("Transport error: {}", e),
194///     Err(e) => println!("Other error: {}", e),
195/// }
196/// # }
197/// ```
198#[derive(thiserror::Error, Debug)]
199#[error(transparent)]
200pub struct SigningError(SigningErrorKind);
201
202impl SigningError {
203    /// Returns true if the error was caused by a transport problem.
204    ///
205    /// This typically happens when using a remote signer (like the IAM signBlob API)
206    /// and the network request fails or the service returns a transient error.
207    pub fn is_transport(&self) -> bool {
208        matches!(self.0, SigningErrorKind::Transport(_))
209    }
210
211    /// Returns true if the error was caused by a parsing problem.
212    ///
213    /// This typically happens when the signer is using a private key that
214    /// cannot be parsed or is invalid.
215    pub fn is_parsing(&self) -> bool {
216        matches!(self.0, SigningErrorKind::Parsing(_))
217    }
218
219    /// Returns true if the error was caused by a problem during the signing operation.
220    ///
221    /// This can happen during local signing if the cryptographic operation fails,
222    /// or if the remote API returns an error that is not a transport error.
223    pub fn is_sign(&self) -> bool {
224        matches!(self.0, SigningErrorKind::Sign(_))
225    }
226
227    /// Creates a [SigningError] representing a parsing error.
228    ///
229    /// This function is primarily intended for use in the client libraries
230    /// implementation or in testing mocks.
231    pub(crate) fn parsing<T>(source: T) -> SigningError
232    where
233        T: Into<BoxError>,
234    {
235        SigningError(SigningErrorKind::Parsing(source.into()))
236    }
237
238    /// Creates a [SigningError] representing a transport error.
239    ///
240    /// This function is primarily intended for use in the client libraries
241    /// implementation or in testing mocks.
242    pub(crate) fn transport<T>(source: T) -> SigningError
243    where
244        T: Into<BoxError>,
245    {
246        SigningError(SigningErrorKind::Transport(source.into()))
247    }
248
249    /// Creates a [SigningError] representing a signing error.
250    ///
251    /// This function is primarily intended for use in the client libraries
252    /// implementation or in testing mocks.
253    pub(crate) fn sign<T>(source: T) -> SigningError
254    where
255        T: Into<BoxError>,
256    {
257        SigningError(SigningErrorKind::Sign(source.into()))
258    }
259
260    /// Creates a new `SigningError` from a message.
261    ///
262    /// This function is only intended for use in the client libraries
263    /// implementation. Application may use this in mocks, though we do not
264    /// recommend that you write tests for specific error cases.
265    ///
266    /// # Parameters
267    /// * `message` - The underlying error that caused the signing failure.
268    #[doc(hidden)]
269    pub fn from_msg<T>(message: T) -> SigningError
270    where
271        T: Into<BoxError>,
272    {
273        SigningError(SigningErrorKind::Sign(message.into()))
274    }
275}
276
277#[derive(thiserror::Error, Debug)]
278enum SigningErrorKind {
279    #[error("failed to generate signature via IAM API: {0}")]
280    Transport(#[source] BoxError),
281    #[error("failed to parse private key: {0}")]
282    Parsing(#[source] BoxError),
283    #[error("failed to sign content: {0}")]
284    Sign(#[source] BoxError),
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290
291    type TestResult = anyhow::Result<()>;
292
293    mockall::mock! {
294        #[derive(Debug)]
295        Signer{}
296
297        impl SigningProvider for Signer {
298            async fn client_email(&self) -> Result<String>;
299            async fn sign(&self, content: &[u8]) -> Result<bytes::Bytes>;
300        }
301    }
302
303    #[tokio::test]
304    async fn test_signer_success() -> TestResult {
305        let mut mock = MockSigner::new();
306        mock.expect_client_email()
307            .returning(|| Ok("test".to_string()));
308        mock.expect_sign()
309            .returning(|_| Ok(bytes::Bytes::from("test")));
310        let signer = Signer::from(mock);
311
312        let result = signer.client_email().await?;
313        assert_eq!(result, "test");
314        let result = signer.sign("test").await?;
315        assert_eq!(result, "test");
316
317        Ok(())
318    }
319
320    #[tokio::test]
321    async fn test_signer_error() -> TestResult {
322        let mut mock = MockSigner::new();
323        mock.expect_client_email()
324            .returning(|| Err(SigningError::transport("test")));
325        mock.expect_sign()
326            .returning(|_| Err(SigningError::sign("test")));
327        let signer = Signer::from(mock);
328
329        let result = signer.client_email().await;
330        assert!(result.is_err());
331        assert!(result.unwrap_err().is_transport());
332        let result = signer.sign("test").await;
333        assert!(result.is_err());
334        assert!(result.unwrap_err().is_sign());
335
336        Ok(())
337    }
338}