use alloc::{string::String, vec::Vec};
use core::convert::Infallible;
use hashbrown::HashSet;
use super::{serde_impl::Base64DerCertificate, JsonWebKey, JsonWebKeyType, KeyOperation, KeyUsage};
use crate::{
jwa::JsonWebAlgorithm,
policy::{Checkable, Checked, Policy},
Uri,
};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum JsonWebKeyBuildError<P> {
#[error("the `key_type` and `algorithm` are not compatible")]
IncompatibleKeyType,
#[error(transparent)]
PolicyCheckFailed(P),
}
#[derive(Debug, Clone)]
pub struct JsonWebKeyBuilder<A> {
pub(super) key_type: JsonWebKeyType,
pub(super) key_use: Option<KeyUsage>,
pub(super) key_operations: Option<HashSet<KeyOperation>>,
pub(super) algorithm: Option<JsonWebAlgorithm>,
pub(super) kid: Option<String>,
pub(super) x509_url: Option<Uri>,
pub(super) x509_certificate_chain: Vec<Base64DerCertificate>,
pub(super) x509_certificate_sha1_thumbprint: Option<[u8; 20]>,
pub(super) x509_certificate_sha256_thumbprint: Option<[u8; 32]>,
pub(super) additional: A,
}
macro_rules! gen_builder_methods {
($($field:ident: $T:ty,)*) => {
$(#[doc = concat!("Override the `", stringify!($field), "` for this JWK.")]
#[inline]
pub fn $field(mut self, $field: Option<impl Into<$T>>) -> Self {
self.$field = $field.map(Into::into);
self
})*
};
}
impl JsonWebKeyBuilder<()> {
pub fn new(key_type: impl Into<JsonWebKeyType>) -> Self {
Self {
key_type: key_type.into(),
key_use: None,
key_operations: None,
algorithm: None,
kid: None,
x509_url: None,
x509_certificate_chain: alloc::vec![],
x509_certificate_sha1_thumbprint: None,
x509_certificate_sha256_thumbprint: None,
additional: (),
}
}
}
impl<A> JsonWebKeyBuilder<A> {
pub fn build(self) -> Result<JsonWebKey<A>, JsonWebKeyBuildError<Infallible>> {
let Self {
key_type,
key_use,
key_operations,
algorithm,
kid,
x509_url,
x509_certificate_chain,
x509_certificate_sha1_thumbprint,
x509_certificate_sha256_thumbprint,
additional,
} = self;
if let Some(ref algorithm) = algorithm {
if !key_type.compatible_with(algorithm) {
return Err(JsonWebKeyBuildError::IncompatibleKeyType);
}
}
Ok(JsonWebKey {
key_type,
key_use,
key_operations,
algorithm,
kid,
x509_url,
x509_certificate_chain,
x509_certificate_sha1_thumbprint,
x509_certificate_sha256_thumbprint,
additional,
})
}
#[allow(clippy::type_complexity, clippy::result_large_err)]
pub fn build_and_check<P: Policy>(
self,
policy: P,
) -> Result<Checked<JsonWebKey<A>, P>, JsonWebKeyBuildError<P::Error>>
where
A: Checkable,
{
self.build()
.map_err(|e| match e {
JsonWebKeyBuildError::IncompatibleKeyType => {
JsonWebKeyBuildError::IncompatibleKeyType
}
JsonWebKeyBuildError::PolicyCheckFailed(x) => match x {},
})?
.check(policy)
.map_err(|(_, e)| JsonWebKeyBuildError::PolicyCheckFailed(e))
}
}
impl<A> JsonWebKeyBuilder<A> {
gen_builder_methods! {
key_use: KeyUsage,
key_operations: HashSet<KeyOperation>,
algorithm: JsonWebAlgorithm,
kid: String,
x509_url: Uri,
x509_certificate_sha1_thumbprint: [u8; 20],
x509_certificate_sha256_thumbprint: [u8; 32],
}
#[inline]
pub fn key_type(mut self, key_type: JsonWebKeyType) -> Self {
self.key_type = key_type;
self
}
#[inline]
pub fn x509_certificate_chain(mut self, x509_certificate_chain: Vec<Vec<u8>>) -> Self {
self.x509_certificate_chain = x509_certificate_chain
.into_iter()
.map(Base64DerCertificate)
.collect();
self
}
#[inline]
pub fn additional<N>(self, additional: N) -> JsonWebKeyBuilder<N> {
JsonWebKeyBuilder {
key_type: self.key_type,
key_use: self.key_use,
key_operations: self.key_operations,
algorithm: self.algorithm,
kid: self.kid,
x509_url: self.x509_url,
x509_certificate_chain: self.x509_certificate_chain,
x509_certificate_sha1_thumbprint: self.x509_certificate_sha1_thumbprint,
x509_certificate_sha256_thumbprint: self.x509_certificate_sha256_thumbprint,
additional,
}
}
}