use core::fmt;
use crate::transport::EndpointIdentity;
use crate::{ProviderId, ServiceId};
use super::super::ScopeValue;
pub const MAX_SIGNING_KEY_ID_BYTES: usize = 256;
pub const MAX_SIGNING_DIGEST_ALGORITHM_BYTES: usize = 128;
pub const MAX_SIGNING_ALGORITHM_BYTES: usize = 128;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SigningContextValueError {
Empty,
TooLong,
InvalidByte,
}
impl_static_error!(SigningContextValueError,
Self::Empty => "signing context value is empty",
Self::TooLong => "signing context value exceeds the length limit",
Self::InvalidByte => "signing context value contains an invalid byte",
);
macro_rules! define_context_value {
($name:ident, $maximum:ident, $label:literal) => {
#[doc = $label]
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct $name<'a>(&'a str);
impl<'a> $name<'a> {
pub fn new(value: &'a str) -> Result<Self, SigningContextValueError> {
validate_context_value(value, $maximum)?;
Ok(Self(value))
}
#[must_use]
pub const fn as_str(self) -> &'a str {
self.0
}
}
impl fmt::Debug for $name<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(concat!(stringify!($name), "([redacted])"))
}
}
};
}
define_context_value!(
SigningKeyId,
MAX_SIGNING_KEY_ID_BYTES,
"Borrowed provider-owned signing key identifier."
);
define_context_value!(
SigningDigestAlgorithm,
MAX_SIGNING_DIGEST_ALGORITHM_BYTES,
"Borrowed provider-owned request-body digest algorithm identifier."
);
define_context_value!(
SigningAlgorithm,
MAX_SIGNING_ALGORITHM_BYTES,
"Borrowed provider-owned signature algorithm identifier."
);
#[derive(Clone, Copy)]
pub struct SigningContext<'a> {
provider: ProviderId,
service: ServiceId,
endpoint: EndpointIdentity<'a>,
audience: Option<ScopeValue<'a>>,
account: Option<ScopeValue<'a>>,
tenant: Option<ScopeValue<'a>>,
key_id: SigningKeyId<'a>,
digest_algorithm: SigningDigestAlgorithm<'a>,
signature_algorithm: SigningAlgorithm<'a>,
}
impl<'a> SigningContext<'a> {
#[must_use]
pub const fn new(
provider: ProviderId,
service: ServiceId,
endpoint: EndpointIdentity<'a>,
key_id: SigningKeyId<'a>,
digest_algorithm: SigningDigestAlgorithm<'a>,
signature_algorithm: SigningAlgorithm<'a>,
) -> Self {
Self {
provider,
service,
endpoint,
audience: None,
account: None,
tenant: None,
key_id,
digest_algorithm,
signature_algorithm,
}
}
#[must_use]
pub const fn with_audience(mut self, value: ScopeValue<'a>) -> Self {
self.audience = Some(value);
self
}
#[must_use]
pub const fn with_account(mut self, value: ScopeValue<'a>) -> Self {
self.account = Some(value);
self
}
#[must_use]
pub const fn with_tenant(mut self, value: ScopeValue<'a>) -> Self {
self.tenant = Some(value);
self
}
#[must_use]
pub const fn provider(self) -> ProviderId {
self.provider
}
#[must_use]
pub const fn service(self) -> ServiceId {
self.service
}
#[must_use]
pub const fn endpoint(self) -> EndpointIdentity<'a> {
self.endpoint
}
#[must_use]
pub const fn audience(self) -> Option<ScopeValue<'a>> {
self.audience
}
#[must_use]
pub const fn account(self) -> Option<ScopeValue<'a>> {
self.account
}
#[must_use]
pub const fn tenant(self) -> Option<ScopeValue<'a>> {
self.tenant
}
#[must_use]
pub const fn key_id(self) -> SigningKeyId<'a> {
self.key_id
}
#[must_use]
pub const fn digest_algorithm(self) -> SigningDigestAlgorithm<'a> {
self.digest_algorithm
}
#[must_use]
pub const fn signature_algorithm(self) -> SigningAlgorithm<'a> {
self.signature_algorithm
}
}
impl fmt::Debug for SigningContext<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SigningContext")
.field("provider", &self.provider)
.field("service", &self.service)
.field("endpoint", &"[redacted]")
.field("audience", &self.audience.map(|_| "[redacted]"))
.field("account", &self.account.map(|_| "[redacted]"))
.field("tenant", &self.tenant.map(|_| "[redacted]"))
.field("key_id", &"[redacted]")
.field("digest_algorithm", &"[redacted]")
.field("signature_algorithm", &"[redacted]")
.finish()
}
}
fn validate_context_value(value: &str, maximum: usize) -> Result<(), SigningContextValueError> {
if value.is_empty() {
return Err(SigningContextValueError::Empty);
}
if value.len() > maximum {
return Err(SigningContextValueError::TooLong);
}
if !value
.bytes()
.all(|byte| byte.is_ascii_graphic() && byte != b'\\')
{
return Err(SigningContextValueError::InvalidByte);
}
Ok(())
}