Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QUESTION] how to properly use decode_with_verifier_in_jwk_set #34

Open
alexisgaziello opened this issue Mar 31, 2024 · 4 comments
Open

Comments

@alexisgaziello
Copy link

Hey,

I am new to rust and I am having trouble using the library. Could you help me?

I have a jwk_set, and I want to use it to decode a token. I am unsure what to put in the arg selector.

I am trying to use decode_with_verifier_in_jwk_set but having little success. ChatGpt suggested something like:


    let (payload, header) = decode_with_verifier_in_jwk_set(
        token,
        jwk_set,
        |jwk| -> Result<Option<&dyn JwsVerifier>, JoseError> {

            if jwk.algorithm().map_or(false, |alg| alg == "RS256") {

                let verifier = RS256.verifier_from_jwk(jwk)?;
                // Box the verifier to create a trait object and then convert the Box into a reference.
                let boxed: Box<dyn JwsVerifier> = Box::new(verifier);

                // Convert the Box into a trait object reference.
                let trait_object_ref: &dyn JwsVerifier = &*boxed;
                // Leak the Box to avoid it being dropped at the end of this scope.
                // This is safe because the trait object is essentially 'static within the context of this closure's execution.
                // However, it does mean the memory will not be reclaimed until the end of the program.
                let leaked: &dyn JwsVerifier = Box::leak(boxed);
                return Ok(Some(leaked));
            }

            Ok(None)

        }
    )
        .map_err(|e| e.to_string())?;

But seems weird and overly complex. I would appreciate any help.

Thank you

@wellcaffeinated
Copy link

I was stuck on this for hours until I saw this code.

I think likely the library should have the selectors just return Box<dyn JwsVerifier>... but they don't for some reason.

What I did was:

fn get_jws_verifier(jwk: &Jwk, header: &JwsHeader) -> Result<Option<Box<dyn JwsVerifier>>, JoseError> {
  match header.algorithm() {
    Some("RS256") => Ok(Some(Box::new(RS256.verifier_from_jwk(jwk)?))),
    Some("RS384") => Ok(Some(Box::new(RS384.verifier_from_jwk(jwk)?))),
    Some("RS512") => Ok(Some(Box::new(RS512.verifier_from_jwk(jwk)?))),
    Some("ES256") => Ok(Some(Box::new(ES256.verifier_from_jwk(jwk)?))),
    Some("ES256K") => Ok(Some(Box::new(ES256K.verifier_from_jwk(jwk)?))),
    Some("ES384") => Ok(Some(Box::new(ES384.verifier_from_jwk(jwk)?))),
    Some("ES512") => Ok(Some(Box::new(ES512.verifier_from_jwk(jwk)?))),
    Some("EdDSA") => Ok(Some(Box::new(EdDSA.verifier_from_jwk(jwk)?))),
    _ => Ok(None),
  }
}

let selector = |header: &JwsHeader| -> Result<Option<&dyn JwsVerifier>, JoseError> {
  get_jws_verifier(jwk, header).map(|v| v.map(|v| {
    let leaked: &dyn JwsVerifier = Box::leak(v);
    leaked
  }))
};

@alexisgaziello
Copy link
Author

feels like the function get_jws_verifier should be part of the library right?

Also, do you understand why we have to use Box::leak? isn't that leaking memory?

I am running this on every authenticated request so if I leak every request that doesn't sound good. Probably my understanding of this is wrong

@wellcaffeinated
Copy link

Ok I think i figured out a better option:

use anyhow::anyhow;
use josekit::{jwk::Jwk, jws::{JwsSigner, *}, JoseError};

use crate::signer::SigningError;

fn get_jws_signer(jwk: &Jwk, header: &JwsHeader) -> Result<Box<dyn JwsSigner>, JoseError> {
  match header.algorithm() {
    Some("RS256") => Ok(Box::new(RS256.signer_from_jwk(jwk)?)),
    Some("RS384") => Ok(Box::new(RS384.signer_from_jwk(jwk)?)),
    Some("RS512") => Ok(Box::new(RS512.signer_from_jwk(jwk)?)),
    Some("ES256") => Ok(Box::new(ES256.signer_from_jwk(jwk)?)),
    Some("ES256K") => Ok(Box::new(ES256K.signer_from_jwk(jwk)?)),
    Some("ES384") => Ok(Box::new(ES384.signer_from_jwk(jwk)?)),
    Some("ES512") => Ok(Box::new(ES512.signer_from_jwk(jwk)?)),
    Some("EdDSA") => Ok(Box::new(EdDSA.signer_from_jwk(jwk)?)),
    _ => return Err(JoseError::UnsupportedSignatureAlgorithm(anyhow!("Unsupported algorithm {}", header.algorithm().unwrap_or("none")))),
  }
}

fn get_header(jwk: &Jwk) -> JwsHeader {
  let alg = match jwk.key_type() {
    "RSA" => {
      match jwk.curve() {
        Some("P-256") => "RS256",
        Some("P-384") => "RS384",
        Some("P-521") => "RS512",
        _ => "RS256",
      }
    },
    "EC" => {
      match jwk.curve() {
        Some("P-256") => "ES256",
        Some("P-384") => "ES384",
        Some("P-521") => "ES512",
        _ => "ES256",
      }
    },
    "OKP" => {
      match jwk.curve() {
        Some("Ed25519") => "EdDSA",
        _ => "EdDSA",
      }
    },
    _ => panic!("Unsupported key type")
  };
  let mut h = JwsHeader::new();
  h.set_algorithm(alg);
  h
}

struct SignerSelector<'a> {
  jwk: &'a Jwk,
  signer: Option<Box<dyn JwsSigner>>,
}

impl<'a> SignerSelector<'a> {
  pub fn new(jwk: &'a Jwk) -> Self {
    Self {
      jwk,
      signer: None,
    }
  }

  pub fn select(&mut self, header: &JwsHeader) -> Result<&dyn JwsSigner, JoseError> {
    self.signer = Some(get_jws_signer(self.jwk, header)?);
    Ok(self.signer.as_deref().map(|v| v).unwrap())
  }
}

pub fn sign<P: AsRef<[u8]>>(jwk: &Jwk, payload: P) -> Result<String, SigningError> {
  let mut selector = SignerSelector::new(jwk);
  let header = get_header(jwk);
  let signer = selector.select(&header)
    .map_err(|e| SigningError(e.to_string()))?;
  serialize_compact(payload.as_ref(), &header, signer)
    .map_err(|e| SigningError(format!("Could not sign: {}", e)))
}

@wellcaffeinated
Copy link

Here's what I'm using for verification:

use base64::Engine;
use josekit::{jwk::Jwk, jws::{JwsVerifier, *}, JoseError};
use crate::errors::VerificationError;

fn get_jws_verifier(jwk: &Jwk, header: &JwsHeader) -> Result<Option<Box<dyn JwsVerifier>>, JoseError> {
  match header.algorithm() {
    Some("RS256") => Ok(Some(Box::new(RS256.verifier_from_jwk(jwk)?))),
    Some("RS384") => Ok(Some(Box::new(RS384.verifier_from_jwk(jwk)?))),
    Some("RS512") => Ok(Some(Box::new(RS512.verifier_from_jwk(jwk)?))),
    Some("ES256") => Ok(Some(Box::new(ES256.verifier_from_jwk(jwk)?))),
    Some("ES256K") => Ok(Some(Box::new(ES256K.verifier_from_jwk(jwk)?))),
    Some("ES384") => Ok(Some(Box::new(ES384.verifier_from_jwk(jwk)?))),
    Some("ES512") => Ok(Some(Box::new(ES512.verifier_from_jwk(jwk)?))),
    Some("EdDSA") => Ok(Some(Box::new(EdDSA.verifier_from_jwk(jwk)?))),
    _ => Ok(None),
  }
}

struct VerifierSelector<'a> {
  jwk: &'a Jwk,
  verifier: Option<Box<dyn JwsVerifier>>,
}

impl<'a> VerifierSelector<'a> {
  pub fn new(jwk: &'a Jwk) -> Self {
    Self {
      jwk,
      verifier: None,
    }
  }
}

impl<'a> VerifierSelector<'a> {
  pub fn select(&mut self, header: &JwsHeader) -> Result<&dyn JwsVerifier, JoseError> {
    self.verifier = get_jws_verifier(self.jwk, header)?;
    Ok(self.verifier.as_deref().map(|v| v).unwrap())
  }
}

fn get_header(input: impl AsRef<[u8]>) -> Result<JwsHeader, JoseError> {
  let input = input.as_ref();
  let indexies: Vec<usize> = input
      .iter()
      .enumerate()
      .filter(|(_, b)| **b == b'.' as u8)
      .map(|(pos, _)| pos)
      .collect();
  if indexies.len() != 2 {
    JoseError::InvalidJwsFormat(
      anyhow::anyhow!("The compact serialization form of JWS must be three parts separated by colon.")
    );
  }

  let header = &input[0..indexies[0]];
  // let payload = &input[(indexies[0] + 1)..(indexies[1])];
  // let signature = &input[(indexies[1] + 1)..];

  use josekit::{Map, Value};
  let header = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(header)
    .map_err(|e| JoseError::InvalidJwsFormat(anyhow::anyhow!(e.to_string())))?;
  let header: Map<String, Value> = serde_json::from_slice(&header)
    .map_err(|e| JoseError::InvalidJwsFormat(anyhow::anyhow!(e.to_string())))?;
  let header = JwsHeader::from_map(header)?;
  Ok(header)
}

pub fn verify_signature<S: AsRef<str>, P: AsRef<[u8]>>(jwk: &Jwk, signature: S, expected_payload: P) -> Result<(), VerificationError> {
  let mut selector = VerifierSelector::new(jwk);
  let header = get_header(signature.as_ref())
    .map_err(|e| VerificationError::InvalidFormat(e.to_string()))?;
  let verifier = selector.select(&header)
    .map_err(|e| VerificationError::InvalidFormat(e.to_string()))?;
  // this checks sig
  let (payload, _) = deserialize_compact(signature.as_ref(), verifier).map_err(|e| {
    match e {
      JoseError::InvalidSignature(e) => VerificationError::BadSignature(e.to_string()),
      _ => VerificationError::InvalidFormat("Signature was not formatted as compact JWS".to_string()),
    }
  })?;
  // check the content hash
  if expected_payload.as_ref() != payload {
    return Err(VerificationError::BadSignature("Payload does not match signature".to_string()));
  }
  Ok(())
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants