Skip to content

Feature request: Support arbitrary crypto backends #409

Open
@sidrubs

Description

@sidrubs

Hi,

I see there has already been a bit of activity surrounding crypto backend support. We have a use-case where we would like to provide an arbitrary "signer" and "verifier" (e.g. a call out to an HSM). I would, personally, prefer to get this into the jsonwebtoken crate, rather than maintaining a fork, as I don't like unnecessary fragmentation of the ecosystem.

I am more than happy to make the PR (making sure that we don't break backwards compatibility). Is this something that the jsonwebtoken crate would consider including?

I did a very rough POC making use of RustCrypto's signature::Signer and signature::Verifier traits.

Setting up signing and verifying traits:

use signature::{Signer, Verifier};

use crate::Algorithm;

/// Provide the type of algorithm implemented by the signing module.
///
/// This is used to populate and verify the header of the JWT.
pub trait AlgorithmType {
    /// Return the [`Algorithm`] corresponding to the signing module.
    fn algorithm_type(&self) -> Algorithm;
}

/// Provides the functionality required to sign a JWT.
///
/// This would usually be implemented to provide custom signing mechanisms (e.g. HSM).
pub trait SigningModule<S>: AlgorithmType + Signer<S> {}

/// Provides the functionality required to verify a JWT.
///
/// This would usually be implemented to provide custom verifying mechanisms (e.g. HSM).
pub trait VerifyingModule<S>: AlgorithmType + Verifier<S> {}

Performing the encoding:

use serde::Serialize;

use crate::{errors::Error, serialization::b64_encode_part, Header};

use super::SigningModule;

/// Encodes the claims into a JWT.
///
/// The header is generated from the `signing_module.algorithm_type`.
pub fn encode<T: Serialize, M: SigningModule<String>>(
    claims: &T,
    signing_module: M,
) -> Result<String, Error> {
    let header = Header::new(signing_module.algorithm_type());

    let encoded_header = b64_encode_part(&header)?;
    let encoded_claims = b64_encode_part(claims)?;
    let message = [encoded_header, encoded_claims].join(".");

    let signature = signing_module.sign(message.as_bytes());

    Ok([message, signature].join("."))
}

One could implement default signing and verifying modules using ring (or RustCrypto, or aws-lc-rs) and could put all the "complexity" of the creating the signing and verifying modules behind the current public facing encode and decode functions so that this does not introduce breaking changes.

Is there anything else I would need to consider?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions