hidekatsu-izuno / josekit-rs

JOSE (Javascript Object Signing and Encryption) library for Rust (based on OpenSSL).
Apache License 2.0
69 stars 30 forks source link

JWS with x5c header parameter #19

Closed backan10 closed 1 year ago

backan10 commented 1 year ago

Hi,

thanks for this great lib. Helps a lot for a project using JWS.

Have a question though, cannot figure it out, and that is how to use the x5c header parameter with JWS.

As far as I can see I get no help from the lib to parse out the header so that I can get a hold of the certificate(s) in the x5c parameter.

Now I do something like this:

pub fn deserialize(jws: &str) -> String {
    // Only support for rs256
    let alg = Rs256;

    //"Manually" decode header. Is there a way to do this in josekit?
    let header = decode_header(jws).unwrap();

    let header = JwsHeader::from_bytes(&header).unwrap();

    // Get signing certificate (first certificate)
    let cert = &header.x509_certificate_chain().unwrap()[0];

    // Get public key
    let key = get_public_key_from_der(cert);

    // Verify
    let verifier = alg.verifier_from_der(&key).unwrap();
    let (dst_payload, _dst_header) = jws::deserialize_compact(&jws, &verifier).unwrap();

    from_utf8(&dst_payload).unwrap().to_string()
}

But if I could use the deserialize_compact_with_selector function (https://docs.rs/josekit/0.8.1/josekit/jws/fn.deserialize_compact_with_selector.html) and use the closure to create the verifier, since in the closer I have the header, it would make life easier. This is not possible now though right? Since the verifier in the closer returns a reference to a JwsVerifier, it need to be owned somewhere before. Could it be modified (or a new function added) that returns a box?

Or is there a better way that I'm missing?

Thanks!

hidekatsu-izuno commented 1 year ago

Hi. Sorry for the late reply.

"Manually" decode header. Is there a way to do this in josekit?

Yes. You can use a jwt::decode_header function.

Since the verifier in the closer returns a reference to a JwsVerifier, it need to be owned somewhere before. Could it be modified (or a new function added) that returns a box?

How about using OnceCell, for example? The following code seems to work.

let cell: OnceCell<Box<dyn JwsVerifier>> = OnceCell::new();
let (dst_payload, dst_header) = jws::deserialize_compact_with_selector(&jwt, |header| {
    let public_keys = header.x509_certificate_chain().unwrap();
    let verifier = alg.verifier_from_pem(&public_keys[0])?;
    Ok(Some(cell.get_or_init(|| Box::new(verifier)).as_ref()))
})?;
backan10 commented 1 year ago

Hi, thanks for your reply.

Yes, the jwt::decode_header function works for decoding the compact serialization. Unfortunately I need to support general JWS JSON Serialization as well.

The "OnceCell solution" works though! But for this I need to enable the nightly channel right?

One more thing, seems that the x509_certificate_chain functions for JwsHeaderSet uses URL_SAFE_NO_PAD, this should be STANDARD_NO_PAD according to the JWS spec right? I see that this has been corrected for the JwsHeader struct.

Thanks for you support.

hidekatsu-izuno commented 1 year ago

I need to support general JWS JSON Serialization as well.

I got it. I will consider supporting it.

The "OnceCell solution" works though! But for this I need to enable the nightly channel right?

You can use OnceCell crate. This crate is not a core module and it is used in josekit too.

One more thing, seems that the x509_certificate_chain functions for JwsHeaderSet uses URL_SAFE_NO_PAD, this should be STANDARD_NO_PAD according to the JWS spec right?

That method uses STANDARD_NO_PAD. So There does not seem to be such a problem.

    pub fn set_x509_certificate_chain(&mut self, values: &Vec<impl AsRef<[u8]>>) {
        let key = "x5c";
        let mut vec = Vec::with_capacity(values.len());
        for val in values {
            vec.push(Value::String(base64::encode_config(
                val.as_ref(),
                base64::STANDARD_NO_PAD,
            )));
        }
        self.claims.insert(key.to_string(), Value::Array(vec));
    }
backan10 commented 1 year ago

Right. The OnceCell crate works. Thanks!

Yes, set_x509_certificate_chain in JwsHeader uses STANDARD_NO_PAD. But the x509_certificate_chain in JwsHeaderSet does not:

/// Return values for a X.509 certificate chain header claim (x5c).
    pub fn x509_certificate_chain(&self) -> Option<Vec<Vec<u8>>> {
        match self.claim("x5c") {
            Some(Value::Array(vals)) => {
                let mut vec = Vec::with_capacity(vals.len());
                for val in vals {
                    match val {
                        Value::String(val2) => {
                            match base64::decode_config(val2, base64::URL_SAFE_NO_PAD) {
                                Ok(val3) => vec.push(val3.clone()),
                                Err(_) => return None,
                            }
                        }
                        _ => return None,
                    }
                }
                Some(vec)
            }
            _ => None,
        }
    }
hidekatsu-izuno commented 1 year ago

I understand now. It's a bug and x509_certificate_chain must be encoded in STANDARD, not URL_SAFE_NO_PAD or STANDARD_NOPAD.

https://www.rfc-editor.org/rfc/rfc7515#page-54

Thank you for reporting. I will fix in a few days.

hidekatsu-izuno commented 1 year ago

I released v0.8.2 which fixed a x509_certificate_chain problem. Try it out.

I need to support general JWS JSON Serialization as well.

I decided not to support this feature. Because there may be more than one header in the JWS JSON Serialization.

backan10 commented 1 year ago

Thanks for your quick support!