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