Understanding Complex Trait Implementation Compiler Issues

I need some help with a demo project I am working on to showcase Google OAuth login using rocket and jwt. I'm implementing jwt::Store for a custom tuple type that I have created which wraps a HashMap<String, PKeyWithDigest>.

The implementation of jwt::Store looks like this:

/// Response from www.googleapis.com/oauth2/v1/certs containing key IDs to PEM-encoded certificates.
#[derive(Deserialize)]
struct GoogleCertsResponse(HashMap<String, String>);

/// Key-store for Google JWT signing keys.
struct JwtKeystore(HashMap<String, PKeyWithDigest<Public>>);

impl TryFrom<GoogleCertsResponse> for JwtKeystore {
    type Error = openssl::error::ErrorStack;

    fn try_from(value: GoogleCertsResponse) -> Result<Self, Self::Error> {
        let mut result = HashMap::with_capacity(value.0.len());

        for (k, v) in value.0.into_iter() {
            result.insert(
                k,
                PKeyWithDigest {
                    key: X509::from_pem(v.as_bytes())?.public_key()?,
                    digest: MessageDigest::sha256(),
                },
            );
        }

        Ok(Self { 0: result })
    }
}

impl Store for JwtKeystore {
    type Algorithm = PKeyWithDigest<Public>;

    fn get(&self, key_id: &str) -> Option<&Self::Algorithm> {
        self.0.get(key_id)
    }
}

I'm then attempting to use this code like so:

let token: Result<Token<JwtHeader, JwtClaims, Verified>, jwt::Error> =
        form.credential.verify_with_store(keystore.inner());

keystore is a JwtKeystore and the error message is:

error[E0277]: the trait bound `&str: VerifyWithStore<Token<serde_json::Value, serde_json::Value, Verified>>` is not satisfied
   --> src/lib.rs:131:25
    |
131 |         form.credential.verify_with_store(keystore.inner());
    |                         ^^^^^^^^^^^^^^^^^ the trait `VerifyWithStore<Token<serde_json::Value, serde_json::Value, Verified>>` is not implemented for `&str`
    |
    = help: the following implementations were found:
              <&'a str as VerifyWithStore<C>>
              <&'a str as VerifyWithStore<Token<H, C, Verified>>>

I'm having trouble understanding this issue. The pull request is naftulikay/rocket-oauth-jwt-demo#1. This code was previously working:

let token: Token<JwtHeader, JwtClaims, Unverified> =
        Token::parse_unverified(form.credential).unwrap();

How can I parse this error to read the docs to understand what is expected and how to change my code? I'd like to be more confident in diagnosing and fixing these issues on my own.

As someone completely ignorant of the code base and most libraries involved, here's how I stumbled through it.

In the VerifyWithStore trait documentation, we can see that

  • If you want a Result over T, you need an implementation of VerifyWithStore<T>

And you're passing a &str and trying to get a Token, so we need an implementation of VerifyWithStore<Token<...>> for &str. Token is also part of the jwt crate, and str is in the standard library, so (due to the orphan rules), the only place such an implementation could be defined is in the jwt crate.

And we can see that there is a blanket implementation for &str involving Token in the crate:

impl<'a, H, C> VerifyWithStore<Token<H, C, Verified>> for &'a str
where
    H: FromBase64 + JoseHeader,
    C: FromBase64,

For this implementation to apply, you're going to need

  • JwtHeader to implement FromBase64 + JoseHeader
  • JwtClaims to implement FromBase64

The traits in question are jwt crate traits.

Both of the types are just your local aliases for serde_json::Value. FromBase64 has a very generic blanket implementation, but JoseHeader is much more specific. The jwt crate only defines it for two specific jwt types: Header and PrecomputedAlgorithmOnlyHeader.

At this point, my guess is that you actually want to use one of these rather than JwtHeader. The alternative would be to create your own type that implements JoseHeader.


Looking a little further, if we check the source for the VerifyWithStore implementation, we can see that it calls Token::parse_unverified(self) (which you say worked before), and then unverified.verify_with_store(store). unverified is a Token, so this time the implementer of VerifyWithStore is a Token, not a &str. If we look at the implementation for Tokens, we can see how it uses the header implementation

  • to pull out the key_id from the header
  • to pull the key from the store
  • to verify the key

PrecomputedAlgorithmOnlyHeader is a minimal struct that uses the default implementation for most methods, including key_id; the default implementation for key_id just returns None. So you're not going to get an Ok using that.

Thus, you probably want to use Header.

1 Like