Skip to main content

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