cloud_sdk/authentication/signing/
context.rs1use core::fmt;
2
3use crate::transport::EndpointIdentity;
4use crate::{ProviderId, ServiceId};
5
6use super::super::ScopeValue;
7
8pub const MAX_SIGNING_KEY_ID_BYTES: usize = 256;
10pub const MAX_SIGNING_DIGEST_ALGORITHM_BYTES: usize = 128;
12pub const MAX_SIGNING_ALGORITHM_BYTES: usize = 128;
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum SigningContextValueError {
18 Empty,
20 TooLong,
22 InvalidByte,
24}
25
26impl_static_error!(SigningContextValueError,
27 Self::Empty => "signing context value is empty",
28 Self::TooLong => "signing context value exceeds the length limit",
29 Self::InvalidByte => "signing context value contains an invalid byte",
30);
31
32macro_rules! define_context_value {
33 ($name:ident, $maximum:ident, $label:literal) => {
34 #[doc = $label]
35 #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
36 pub struct $name<'a>(&'a str);
37
38 impl<'a> $name<'a> {
39 pub fn new(value: &'a str) -> Result<Self, SigningContextValueError> {
41 validate_context_value(value, $maximum)?;
42 Ok(Self(value))
43 }
44
45 #[must_use]
47 pub const fn as_str(self) -> &'a str {
48 self.0
49 }
50 }
51
52 impl fmt::Debug for $name<'_> {
53 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
54 formatter.write_str(concat!(stringify!($name), "([redacted])"))
55 }
56 }
57 };
58}
59
60define_context_value!(
61 SigningKeyId,
62 MAX_SIGNING_KEY_ID_BYTES,
63 "Borrowed provider-owned signing key identifier."
64);
65define_context_value!(
66 SigningDigestAlgorithm,
67 MAX_SIGNING_DIGEST_ALGORITHM_BYTES,
68 "Borrowed provider-owned request-body digest algorithm identifier."
69);
70define_context_value!(
71 SigningAlgorithm,
72 MAX_SIGNING_ALGORITHM_BYTES,
73 "Borrowed provider-owned signature algorithm identifier."
74);
75
76#[derive(Clone, Copy)]
78pub struct SigningContext<'a> {
79 provider: ProviderId,
80 service: ServiceId,
81 endpoint: EndpointIdentity<'a>,
82 audience: Option<ScopeValue<'a>>,
83 account: Option<ScopeValue<'a>>,
84 tenant: Option<ScopeValue<'a>>,
85 key_id: SigningKeyId<'a>,
86 digest_algorithm: SigningDigestAlgorithm<'a>,
87 signature_algorithm: SigningAlgorithm<'a>,
88}
89
90impl<'a> SigningContext<'a> {
91 #[must_use]
93 pub const fn new(
94 provider: ProviderId,
95 service: ServiceId,
96 endpoint: EndpointIdentity<'a>,
97 key_id: SigningKeyId<'a>,
98 digest_algorithm: SigningDigestAlgorithm<'a>,
99 signature_algorithm: SigningAlgorithm<'a>,
100 ) -> Self {
101 Self {
102 provider,
103 service,
104 endpoint,
105 audience: None,
106 account: None,
107 tenant: None,
108 key_id,
109 digest_algorithm,
110 signature_algorithm,
111 }
112 }
113
114 #[must_use]
116 pub const fn with_audience(mut self, value: ScopeValue<'a>) -> Self {
117 self.audience = Some(value);
118 self
119 }
120
121 #[must_use]
123 pub const fn with_account(mut self, value: ScopeValue<'a>) -> Self {
124 self.account = Some(value);
125 self
126 }
127
128 #[must_use]
130 pub const fn with_tenant(mut self, value: ScopeValue<'a>) -> Self {
131 self.tenant = Some(value);
132 self
133 }
134
135 #[must_use]
137 pub const fn provider(self) -> ProviderId {
138 self.provider
139 }
140
141 #[must_use]
143 pub const fn service(self) -> ServiceId {
144 self.service
145 }
146
147 #[must_use]
149 pub const fn endpoint(self) -> EndpointIdentity<'a> {
150 self.endpoint
151 }
152
153 #[must_use]
155 pub const fn audience(self) -> Option<ScopeValue<'a>> {
156 self.audience
157 }
158
159 #[must_use]
161 pub const fn account(self) -> Option<ScopeValue<'a>> {
162 self.account
163 }
164
165 #[must_use]
167 pub const fn tenant(self) -> Option<ScopeValue<'a>> {
168 self.tenant
169 }
170
171 #[must_use]
173 pub const fn key_id(self) -> SigningKeyId<'a> {
174 self.key_id
175 }
176
177 #[must_use]
179 pub const fn digest_algorithm(self) -> SigningDigestAlgorithm<'a> {
180 self.digest_algorithm
181 }
182
183 #[must_use]
185 pub const fn signature_algorithm(self) -> SigningAlgorithm<'a> {
186 self.signature_algorithm
187 }
188}
189
190impl fmt::Debug for SigningContext<'_> {
191 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
192 formatter
193 .debug_struct("SigningContext")
194 .field("provider", &self.provider)
195 .field("service", &self.service)
196 .field("endpoint", &"[redacted]")
197 .field("audience", &self.audience.map(|_| "[redacted]"))
198 .field("account", &self.account.map(|_| "[redacted]"))
199 .field("tenant", &self.tenant.map(|_| "[redacted]"))
200 .field("key_id", &"[redacted]")
201 .field("digest_algorithm", &"[redacted]")
202 .field("signature_algorithm", &"[redacted]")
203 .finish()
204 }
205}
206
207fn validate_context_value(value: &str, maximum: usize) -> Result<(), SigningContextValueError> {
208 if value.is_empty() {
209 return Err(SigningContextValueError::Empty);
210 }
211 if value.len() > maximum {
212 return Err(SigningContextValueError::TooLong);
213 }
214 if !value
215 .bytes()
216 .all(|byte| byte.is_ascii_graphic() && byte != b'\\')
217 {
218 return Err(SigningContextValueError::InvalidByte);
219 }
220 Ok(())
221}