From 2798504fa2663364573cf2d1043d8d7fef389499 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Padilla?= <jpadilla@users.noreply.github.com>
Date: Thu, 10 Sep 2026 08:33:25 -0400
Subject: [PATCH] fix: reject DER public keys as HMAC secrets

---
 jwt/algorithms.py        | 27 ++++++++++++++++++++++++++-
 tests/test_algorithms.py | 39 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+), 1 deletion(-)

Index: pyjwt-2.12.1/jwt/algorithms.py
===================================================================
--- pyjwt-2.12.1.orig/jwt/algorithms.py
+++ pyjwt-2.12.1/jwt/algorithms.py
@@ -33,6 +33,7 @@ from .utils import (
 )
 
 try:
+    from cryptography import x509
     from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
     from cryptography.hazmat.backends import default_backend
     from cryptography.hazmat.primitives import hashes
@@ -72,6 +73,7 @@ try:
         NoEncryption,
         PrivateFormat,
         PublicFormat,
+        load_der_public_key,
         load_pem_private_key,
         load_pem_public_key,
         load_ssh_public_key,
@@ -322,13 +324,36 @@ class HMACAlgorithm(Algorithm):
     def __init__(self, hash_alg: HashlibHash) -> None:
         self.hash_alg = hash_alg
 
+    @staticmethod
+    def _is_der_key(key_bytes: bytes) -> bool:
+        if not has_crypto:
+            return False
+
+        try:
+            load_der_public_key(key_bytes)
+        except (TypeError, ValueError, UnsupportedAlgorithm):
+            pass
+        else:
+            return True
+
+        try:
+            x509.load_der_x509_certificate(key_bytes)
+        except (TypeError, ValueError, UnsupportedAlgorithm):
+            return False
+        else:
+            return True
+
     def prepare_key(self, key: str | bytes) -> bytes:
         key_bytes = force_bytes(key)
 
         if len(key_bytes) == 0:
             raise InvalidKeyError("HMAC key must not be empty.")
 
-        if is_pem_format(key_bytes) or is_ssh_key(key_bytes):
+        if (
+            is_pem_format(key_bytes)
+            or is_ssh_key(key_bytes)
+            or self._is_der_key(key_bytes)
+        ):
             raise InvalidKeyError(
                 "The specified key is an asymmetric key or x509 certificate and"
                 " should not be used as an HMAC secret."
Index: pyjwt-2.12.1/tests/test_algorithms.py
===================================================================
--- pyjwt-2.12.1.orig/tests/test_algorithms.py
+++ pyjwt-2.12.1/tests/test_algorithms.py
@@ -12,6 +12,7 @@ from .keys import load_ec_pub_key_p_521,
 from .utils import crypto_required, key_path
 
 if has_crypto:
+    from cryptography import x509
     from cryptography.hazmat.primitives.asymmetric.ec import (
         EllipticCurvePrivateKey,
         EllipticCurvePublicKey,
@@ -28,6 +29,10 @@ if has_crypto:
         RSAPrivateKey,
         RSAPublicKey,
     )
+    from cryptography.hazmat.primitives.serialization import (
+        Encoding,
+        PublicFormat,
+    )
 
     from jwt.algorithms import ECAlgorithm, OKPAlgorithm, RSAAlgorithm, RSAPSSAlgorithm
 
@@ -268,6 +273,40 @@ class TestAlgorithms:
             assert algo.verify(b"Hello World!", pub_key, signature)
 
     @crypto_required
+    @pytest.mark.parametrize(
+        "key_format_name",
+        ("SubjectPublicKeyInfo", "PKCS1"),
+    )
+    def test_hmac_prepare_key_rejects_der_public_key(
+        self, key_format_name: str
+    ) -> None:
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+        public_key = cast(RSAPublicKey, load_rsa_pub_key())
+        der_key = public_key.public_bytes(
+            Encoding.DER, getattr(PublicFormat, key_format_name)
+        )
+
+        with pytest.raises(InvalidKeyError, match="asymmetric key"):
+            algo.prepare_key(der_key)
+
+    @crypto_required
+    def test_hmac_prepare_key_rejects_der_certificate(self) -> None:
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+        with open(key_path("testkey_rsa.cer"), "rb") as certificate_file:
+            certificate = x509.load_pem_x509_certificate(certificate_file.read())
+        der_certificate = certificate.public_bytes(Encoding.DER)
+
+        with pytest.raises(InvalidKeyError, match="x509 certificate"):
+            algo.prepare_key(der_certificate)
+
+    @crypto_required
+    def test_hmac_prepare_key_accepts_non_key_binary_secret(self) -> None:
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+        secret = b"\x30\x82not-a-der-key"
+
+        assert algo.prepare_key(secret) == secret
+
+    @crypto_required
     def test_ec_jwk_fails_on_invalid_json(self) -> None:
         algo = ECAlgorithm(ECAlgorithm.SHA512)
 
