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

KeyProvider-style API for signature schemes (HMAC) #673

Open
5 tasks done
arlyon opened this issue Oct 6, 2023 · 2 comments
Open
5 tasks done

KeyProvider-style API for signature schemes (HMAC) #673

arlyon opened this issue Oct 6, 2023 · 2 comments
Labels
feature request A feature has been asked for or suggested by the community

Comments

@arlyon
Copy link

arlyon commented Oct 6, 2023

Checklist

  • I have looked into the Readme and Examples, and have not found a suitable solution or answer.
  • I have looked into the API documentation and have not found a suitable solution or answer.
  • I have searched the issues and have not found a suitable solution or answer.
  • I have searched the Auth0 Community forums and have not found a suitable solution or answer.
  • I agree to the terms within the Auth0 Code of Conduct.

Describe the problem you'd like to have solved

I am working on a project just for fun to learn a little more about JWTs

There is a handy KeyProvider API for asymmetric encryption schemes that allow identifying keys by a KID and validating a claims from a set of potential keys. A similar API for HMAC (which has no public key) is not available.

KeyIDs are handy when setting up secret key rotation, so an equivalent API for HMAC would be handy.

Describe the ideal solution

A KeyProvider-style API specifically for HMAC which does away with the public key part aspect and solely focuses on keys with only a private portion (could also cover symmetric keys).

Alternatives and current workarounds

I considered extending HMACAlgorithm to do this, but all the relevant classes are locked down.

Additional context

None

@arlyon arlyon added the feature request A feature has been asked for or suggested by the community label Oct 6, 2023
@jimmyjames
Copy link
Contributor

👋 hi @arlyon, thanks for raising the request. Could you provide some additional info such as some pseudo-code of what the ideal solution would look like from a consumer's usage? Thanks!

@arlyon
Copy link
Author

arlyon commented Dec 1, 2023

I ended up writing my own implementation of Algorithm that reimplements HMACAlgorithm but with my custom key provider added. I would call this low priority since you can manually implement HMAC but I'd rather roll as little of my own crypto as possible :)

import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.SignatureGenerationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

/**
 * Subclass representing an Hash-based MAC signing algorithm
 *
 * <p>This class is thread-safe.
 */
public class KeyProvidedHMACAlgorithm extends Algorithm {

  private final CryptoHelper crypto;
  private final HMACKeyProvider keyProvider;

  // Visible for testing
  KeyProvidedHMACAlgorithm(
      CryptoHelper crypto, String id, String algorithm, HMACKeyProvider provider)
      throws IllegalArgumentException {
    super(id, algorithm);
    this.crypto = crypto;
    this.keyProvider = provider;
  }

  KeyProvidedHMACAlgorithm(String id, String algorithm, HMACKeyProvider provider)
      throws IllegalArgumentException {
    this(new CryptoHelper(), id, algorithm, provider);
  }

  @Override
  public void verify(DecodedJWT jwt) throws SignatureVerificationException {
    try {
      byte[] signatureBytes = Base64.getUrlDecoder().decode(jwt.getSignature());
      var secret = keyProvider.getKeyById(jwt.getKeyId());
      boolean valid =
          crypto.verifySignatureFor(
              toString(), secret, jwt.getHeader(), jwt.getPayload(), signatureBytes);
      if (!valid) {
        throw new SignatureVerificationException(this);
      }
    } catch (IllegalStateException
        | InvalidKeyException
        | NoSuchAlgorithmException
        | IllegalArgumentException e) {
      throw new SignatureVerificationException(this, e);
    }
  }

  @Override
  public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException {
    try {
      var secret = keyProvider.getKeyById(keyProvider.getKeyId());
      return crypto.createSignatureFor(toString(), secret, headerBytes, payloadBytes);
    } catch (NoSuchAlgorithmException | InvalidKeyException e) {
      throw new SignatureGenerationException(this, e);
    }
  }

  @Override
  public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
    try {
      var secret = keyProvider.getKeyById(keyProvider.getKeyId());
      return crypto.createSignatureFor(toString(), secret, contentBytes);
    } catch (NoSuchAlgorithmException | InvalidKeyException e) {
      throw new SignatureGenerationException(this, e);
    }
  }
}
import java.util.HashMap;
import java.util.List;

class HMACKeyProvider {

  HashMap<Long, byte[]> keyMap = new HashMap<Long, byte[]>();

  HMACKeyProvider(List<Secret> secrets) {
    for (Secret secret : secrets) {
      keyMap.put(secret.getKid(), secret.getSecret().getBytes());
    }
  }

  byte[] getKeyById(Long keyId) {
    if (keyId == null) {
      keyId = 1L;
    }
    return keyMap.get(keyId);
  }

  byte[] getKeyById(String keyId) throws NumberFormatException {
    return getKeyById(keyId == null ? null : Long.parseLong(keyId));
  }

  Long getKeyId() {
    return keyMap.keySet().stream().max(Long::compare).get();
  }
}

The request is an api similar to ECDSA and RSA that supports a generic HMACKeyProvider interface rather than hardcoding the key.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request A feature has been asked for or suggested by the community
Projects
None yet
Development

No branches or pull requests

2 participants