Fix CVE-2024-23342: Replace python-jose with jwcrypto (#13012)

Co-authored-by: OpenHands CVE Fix Bot <openhands@all-hands.dev>
This commit is contained in:
aivong-openhands
2026-02-27 16:15:23 -06:00
committed by GitHub
parent 402fa47422
commit baaec8473a
4 changed files with 142 additions and 79 deletions

View File

@@ -6,8 +6,8 @@ from typing import Any, AsyncGenerator
import jwt
from fastapi import Request
from jose import jwe
from jose.constants import ALGORITHMS
from jwcrypto import jwe as jwcrypto_jwe
from jwcrypto import jwk
from pydantic import BaseModel, PrivateAttr
from openhands.agent_server.utils import utc_now
@@ -168,21 +168,21 @@ class JwtService:
# Derive a 256-bit key using SHA256
key_256 = hashlib.sha256(key_bytes).digest()
# Encrypt the payload (convert to JSON string first)
payload_json = json.dumps(jwt_payload)
encrypted_token = jwe.encrypt(
payload_json,
key_256,
algorithm=ALGORITHMS.DIR,
encryption=ALGORITHMS.A256GCM,
kid=key_id,
)
# Ensure we return a string
return (
encrypted_token.decode('utf-8')
if isinstance(encrypted_token, bytes)
else encrypted_token
# Create JWK from symmetric key for jwcrypto
symmetric_key = jwk.JWK(kty='oct', k=jwk.base64url_encode(key_256))
# Create JWE token with jwcrypto
protected_header = {
'alg': 'dir',
'enc': 'A256GCM',
'kid': key_id,
}
jwe_token = jwcrypto_jwe.JWE(
json.dumps(jwt_payload).encode('utf-8'),
recipient=symmetric_key,
protected=protected_header,
)
return jwe_token.serialize(compact=True)
def decrypt_jwe_token(
self, token: str, key_id: str | None = None
@@ -201,15 +201,31 @@ class JwtService:
ValueError: If token is invalid or key_id is not found
Exception: If token decryption fails
"""
# Deserialize once and reuse for both header extraction and decryption
try:
jwe_obj = jwcrypto_jwe.JWE()
jwe_obj.deserialize(token)
except Exception:
raise ValueError('Invalid JWE token format')
# Extract and validate the protected header
try:
protected_header = json.loads(jwe_obj.objects['protected'])
except (KeyError, json.JSONDecodeError) as e:
raise ValueError(f'Invalid JWE token format: {type(e).__name__}')
# Verify algorithms to prevent cryptographic agility attacks
if (
protected_header.get('alg') != 'dir'
or protected_header.get('enc') != 'A256GCM'
):
raise ValueError('Unsupported or unexpected JWE algorithm')
if key_id is None:
# Try to extract key_id from the token's header
try:
header = jwe.get_unverified_header(token)
key_id = header.get('kid')
if not key_id:
raise ValueError("Token does not contain 'kid' header with key ID")
except Exception:
raise ValueError('Invalid JWE token format')
# Extract key_id from the token's header
key_id = protected_header.get('kid')
if not key_id:
raise ValueError("Token does not contain 'kid' header with key ID")
if key_id not in self._keys:
raise ValueError(f"Key ID '{key_id}' not found")
@@ -221,10 +237,14 @@ class JwtService:
key_256 = hashlib.sha256(key_bytes).digest()
try:
payload_json = jwe.decrypt(token, key_256)
assert payload_json is not None
# Create JWK from symmetric key for jwcrypto
symmetric_key = jwk.JWK(kty='oct', k=jwk.base64url_encode(key_256))
# Decrypt the JWE token (reusing already deserialized jwe_obj)
jwe_obj.decrypt(symmetric_key)
# Parse the JSON string back to dictionary
payload = json.loads(payload_json)
payload = json.loads(jwe_obj.payload.decode('utf-8'))
return payload
except Exception as e:
raise Exception(f'Token decryption failed: {str(e)}')