1use std::cell::Cell;
17
18use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
19use bherror::traits::{ErrorContext as _, ForeignError as _, PropagateError as _};
20
21use crate::{
22 openssl_ec_pub_key_to_jwk, CryptoError, JwkPublic, SignatureVerifier, Signer, SigningAlgorithm,
23};
24
25pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
27
28pub fn construct_jws_payload(header: &str, claims: &str) -> String {
35 format!("{header}.{claims}")
36}
37
38pub fn base64_url_encode<T: AsRef<[u8]>>(input: T) -> String {
40 URL_SAFE_NO_PAD.encode(input)
41}
42
43pub(crate) fn sign_jwt<UnsignedJwt, SignedJwt, S>(
46 unsigned_jwt: UnsignedJwt,
47 signer: &S,
48) -> Result<SignedJwt, BoxError>
49where
50 UnsignedJwt: jwt::SignWithKey<SignedJwt>,
51 S: Signer + ?Sized,
52{
53 let signer_wrapper = ErrorHolder::new(signer);
54 unsigned_jwt
55 .sign_with_key(&signer_wrapper)
56 .map_err(signer_wrapper.combine_error())
57}
58
59impl<T: Signer + ?Sized> jwt::SigningAlgorithm for ErrorHolder<&'_ T> {
60 fn algorithm_type(&self) -> jwt::AlgorithmType {
61 self.inner.algorithm().into()
62 }
63
64 fn sign(&self, header: &str, claims: &str) -> Result<String, jwt::Error> {
65 let message = construct_jws_payload(header, claims);
66
67 match self.inner.sign(message.as_bytes()) {
68 Ok(signature_bytes) => Ok(base64_url_encode(signature_bytes)),
69 Err(error) => Err(self.store_error(error)),
70 }
71 }
72}
73
74pub(crate) fn verify_jwt_signature<UnverifiedJwt, VerifiedJwt, V>(
78 unverified_jwt: UnverifiedJwt,
79 verifier: &V,
80 public_key: &JwkPublic,
81) -> Result<VerifiedJwt, BoxError>
82where
83 UnverifiedJwt: jwt::VerifyWithKey<VerifiedJwt>,
84 V: SignatureVerifier + ?Sized,
85{
86 let verifier_wrapper = ErrorHolder::new(VerifierWrapper {
87 verifier,
88 public_key,
89 });
90 unverified_jwt
91 .verify_with_key(&verifier_wrapper)
92 .map_err(verifier_wrapper.combine_error())
93}
94
95struct VerifierWrapper<'a, T: SignatureVerifier + ?Sized> {
97 verifier: &'a T,
98 public_key: &'a JwkPublic,
99}
100
101impl<T: SignatureVerifier + ?Sized> jwt::VerifyingAlgorithm
102 for ErrorHolder<VerifierWrapper<'_, T>>
103{
104 fn algorithm_type(&self) -> jwt::AlgorithmType {
105 self.inner.verifier.algorithm().into()
106 }
107
108 fn verify_bytes(
109 &self,
110 header: &str,
111 claims: &str,
112 signature: &[u8],
113 ) -> Result<bool, jwt::Error> {
114 let message = construct_jws_payload(header, claims);
115
116 self.inner
117 .verifier
118 .verify(message.as_bytes(), signature, self.inner.public_key)
119 .map_err(|error| self.store_error(error))
120 }
121}
122
123struct ErrorHolder<T> {
126 inner: T,
127 error: Cell<Option<BoxError>>,
130}
131
132impl<T> ErrorHolder<T> {
133 fn new(inner: T) -> Self {
134 Self {
135 inner,
136 error: Cell::new(None),
137 }
138 }
139
140 fn store_error(&self, error: BoxError) -> jwt::Error {
141 let previous = self.error.replace(Some(error));
142 debug_assert!(previous.is_none());
143
144 jwt::Error::InvalidSignature
147 }
148
149 fn combine_error(self) -> impl FnOnce(jwt::Error) -> BoxError {
156 |jwt_error| {
157 if let Some(underlying_error) = self.error.into_inner() {
158 debug_assert!(matches!(jwt_error, jwt::Error::InvalidSignature));
159 underlying_error
160 } else {
161 Box::new(jwt_error)
162 }
163 }
164 }
165}
166
167pub fn public_jwk_from_x5chain_leaf(
171 x5chain: &bhx5chain::X5Chain,
172 alg: &SigningAlgorithm,
173 kid: Option<&str>,
174) -> bherror::Result<JwkPublic, CryptoError> {
175 let pkey = x5chain
176 .leaf_certificate_key()
177 .with_err(|| CryptoError::InvalidX5Chain)
178 .ctx(|| "invalid public key from certificate")?;
179
180 match (alg, pkey.id()) {
181 (SigningAlgorithm::Es256, openssl::pkey::Id::EC) => {
182 let ec_key = pkey
183 .ec_key()
184 .foreign_err(|| CryptoError::CryptoBackend)
185 .ctx(|| "invalid EC key")?;
186
187 openssl_ec_pub_key_to_jwk(&ec_key, kid).ctx(|| "unable to construct JWK")
188 }
189 _ => Err(bherror::Error::root(CryptoError::Unsupported(
190 "only Es256 is currently supported".to_string(),
191 ))),
192 }
193}