JOSE protocol implementation in Python with support for JSON Web Algorithms, Keys, and Signatures
73
Complete signing and verification workflow implementing the JSON Web Signature specification (RFC 7515). Provides comprehensive support for creating, serializing, and verifying digitally signed JSON messages with customizable headers and multiple serialization formats.
Main class for creating and managing JSON Web Signatures with support for signing, verification, and serialization.
class JWS:
"""JSON Web Signature implementation"""
@classmethod
def sign(cls, payload: bytes, **kwargs) -> 'JWS':
"""
Create a new JWS by signing payload. The key and algorithm are passed via kwargs.
Parameters:
- payload: Message bytes to sign
- **kwargs: Signing parameters including 'key' (JWK), 'alg' (JWASignature),
and other header fields
Returns:
JWS: New JWS instance with signature
"""
def verify(self, key: Optional[JWK] = None) -> bool:
"""
Verify JWS signature.
Parameters:
- key: JWK instance containing verification key (optional if key in header)
Returns:
bool: True if signature is valid, False otherwise
Raises:
josepy.errors.Error: If signature verification fails
"""
def json_dumps(self, **kwargs) -> str:
"""Serialize JWS to JSON string"""
@classmethod
def json_loads(cls, json_string: str) -> 'JWS':
"""Deserialize JWS from JSON string"""
def to_compact(self) -> bytes:
"""
Serialize JWS to compact format.
Returns:
bytes: Compact JWS serialization (header.payload.signature)
Raises:
AssertionError: If JWS doesn't have exactly one signature or algorithm not in protected header
"""
@classmethod
def from_compact(cls, compact: bytes) -> 'JWS':
"""
Deserialize JWS from compact format.
Parameters:
- compact: Compact JWS serialization bytes
Returns:
JWS: Deserialized JWS instance
Raises:
josepy.errors.DeserializationError: If compact format is invalid
"""
@property
def signature(self) -> 'Signature':
"""
Get singleton signature component.
Returns:
Signature: The single signature component
Raises:
AssertionError: If JWS doesn't have exactly one signature
"""
# JWS components
payload: bytes # Message payload
signatures: List['Signature'] # List of signature componentsfrom josepy import JWS, Header, JWKRSA, RS256
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
# Generate RSA key pair
private_key = rsa.generate_private_key(65537, 2048, default_backend())
jwk = JWKRSA(key=private_key)
public_jwk = jwk.public_key()
# Create and sign JWS
payload = b'{"user": "alice", "scope": "read:profile"}'
jws = JWS.sign(payload, key=jwk, alg=RS256)
# Serialize to JSON
jws_json = jws.json_dumps()
print(f"JWS JSON: {jws_json}")
# Deserialize and verify
loaded_jws = JWS.json_loads(jws_json)
verified_payload = loaded_jws.verify(public_jwk)
print(f"Verified payload: {verified_payload.decode()}")
# Sign with custom headers
custom_jws = JWS.sign(
payload,
key=jwk,
alg=RS256,
kid="key-1", # Key ID
typ="JWT" # Token type
)JOSE header management supporting registered header parameters with type-safe field handling.
class Header:
"""JOSE Header for JWS"""
def __init__(self, alg=None, **kwargs):
"""
Initialize JOSE header.
Parameters:
- alg: Signature algorithm (JWASignature instance)
- **kwargs: Additional header parameters
"""
def not_omitted(self) -> Dict[str, Any]:
"""Get header fields that would not be omitted in JSON"""
@classmethod
def from_json(cls, jobj: Any) -> 'Header':
"""Deserialize header from JSON object"""
def to_partial_json(self) -> Any:
"""Serialize header to JSON-compatible dictionary"""
# Standard header fields
alg: Optional[JWASignature] # Algorithm
jku: Optional[bytes] # JWK Set URL
jwk: Optional[JWK] # JSON Web Key
kid: Optional[str] # Key ID
x5u: Optional[bytes] # X.509 URL
x5c: Tuple[x509.Certificate, ...] # X.509 Certificate Chain
x5t: Optional[bytes] # X.509 Certificate SHA-1 Thumbprint
x5tS256: Optional[bytes] # X.509 Certificate SHA-256 Thumbprint
typ: Optional[MediaType] # Type (Media Type)
cty: Optional[MediaType] # Content Type
crit: Tuple[Any, ...] # Critical extensionsfrom josepy import Header, RS256, JWKRSA
from cryptography import x509
# Basic header with algorithm
header = Header(alg=RS256)
# Header with key ID
header_with_kid = Header(alg=RS256, kid="rsa-key-1")
# Header with embedded JWK
jwk = JWKRSA(key=private_key)
header_with_jwk = Header(alg=RS256, jwk=jwk.public_key())
# Header with X.509 certificate chain
# (assuming you have certificates)
header_with_x5c = Header(
alg=RS256,
x5c=(cert1, cert2), # x509.Certificate objects
x5t=cert_thumbprint # SHA-1 thumbprint bytes
)
# Custom header fields
header_custom = Header(
alg=RS256,
typ="JWT",
custom_field="custom_value" # Non-standard fields supported
)
# Serialize header
header_json = header.json_dumps()
print(f"Header JSON: {header_json}")Individual signature component supporting JWS signature operations.
class Signature:
"""JWS Signature component"""
def __init__(self, signature: bytes, header: Header):
"""
Initialize signature component.
Parameters:
- signature: Raw signature bytes
- header: JOSE header for this signature
"""
@classmethod
def from_json(cls, jobj: Any) -> 'Signature':
"""Deserialize signature from JSON object"""
def to_partial_json(self) -> Any:
"""Serialize signature to JSON-compatible dictionary"""
signature: bytes # Raw signature bytes
header: Header # JOSE headerUtility class for handling JOSE media type encoding and decoding.
class MediaType:
"""Media Type field encoder/decoder"""
PREFIX = "application/" # MIME prefix
@classmethod
def decode(cls, value: str) -> str:
"""
Decode media type from JOSE format.
Adds 'application/' prefix if not present.
"""
@classmethod
def encode(cls, value: str) -> str:
"""
Encode media type to JOSE format.
Removes 'application/' prefix if present.
"""from josepy import JWS, Header, JWKRSA, RS256
import json
import time
# Create JWT payload
payload_dict = {
"sub": "1234567890",
"name": "John Doe",
"iat": int(time.time()),
"exp": int(time.time()) + 3600 # 1 hour expiration
}
payload_json = json.dumps(payload_dict).encode()
# Sign with RS256
private_key = rsa.generate_private_key(65537, 2048, default_backend())
jwk = JWKRSA(key=private_key)
jws = JWS.sign(
payload_json,
key=jwk,
alg=RS256,
typ="JWT",
kid="key-1"
)
# The result is a complete JWS that can be transmitted
token = jws.json_dumps()# Receive JWS token (e.g., from HTTP request)
received_token = '{"header": {...}, "payload": "...", "signature": "..."}'
try:
# Parse JWS
jws = JWS.json_loads(received_token)
# Extract key ID from header
key_id = jws.header.kid
# Look up verification key (application-specific)
verification_jwk = get_public_key_by_id(key_id) # Your function
# Verify signature and get payload
verified_payload = jws.verify(verification_jwk)
# Parse payload as needed
payload_dict = json.loads(verified_payload.decode())
# Additional validation (expiration, audience, etc.)
if payload_dict.get('exp', 0) < time.time():
raise ValueError("Token expired")
print(f"Valid token for user: {payload_dict.get('sub')}")
except Exception as e:
print(f"Token verification failed: {e}")from josepy import RS256, ES256, HS256, JWKRSA, JWKEC, JWKOct
# Create keys for different algorithms
rsa_key = rsa.generate_private_key(65537, 2048, default_backend())
ec_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
hmac_key = os.urandom(32)
# Create JWKs
rsa_jwk = JWKRSA(key=rsa_key)
ec_jwk = JWKEC(key=ec_key)
hmac_jwk = JWKOct(key=hmac_key)
payload = b'{"message": "Hello, multi-algorithm world!"}'
# Sign with different algorithms
rsa_jws = JWS.sign(payload, key=rsa_jwk, alg=RS256)
ec_jws = JWS.sign(payload, key=ec_jwk, alg=ES256)
hmac_jws = JWS.sign(payload, key=hmac_jwk, alg=HS256)
# Verify with corresponding keys
rsa_verified = rsa_jws.verify(rsa_jwk.public_key())
ec_verified = ec_jws.verify(ec_jwk.public_key())
hmac_verified = hmac_jws.verify(hmac_jwk) # Symmetric key
print("All signatures verified successfully!")JOSEPY provides a comprehensive command-line tool for JWS operations through the jws command:
class CLI:
"""JWS Command Line Interface"""
@classmethod
def sign(cls, args: argparse.Namespace) -> None:
"""Execute JWS signing operation from command line arguments"""
@classmethod
def verify(cls, args: argparse.Namespace) -> bool:
"""Execute JWS verification operation from command line arguments"""
@classmethod
def run(cls, args: Optional[List[str]] = None) -> Optional[bool]:
"""Parse command line arguments and execute sign/verify operations"""Basic Signing:
# Sign payload from stdin with RSA key
echo "Hello, World!" | jws sign --key private_key.pem --alg RS256
# Sign with compact serialization
echo "Hello, World!" | jws --compact sign --key private_key.pem --alg RS256
# Sign with protected headers
echo "Hello, World!" | jws sign --key private_key.pem --alg RS256 --protect alg --protect kidVerification:
# Verify JWS token from stdin using key
echo '{"header": {...}, "payload": "...", "signature": "..."}' | jws verify --key public_key.pem --kty RSA
# Verify compact JWS
echo "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...." | jws --compact verify --key public_key.pem --kty RSA
# Verify with embedded key (no --key needed)
echo '{"header": {"jwk": {...}}, ...}' | jws verifyAvailable Options:
--compact: Use compact serialization format--key: Path to key file (PEM/DER format)--alg: Signature algorithm (RS256, ES256, HS256, etc.)--protect: Header parameters to include in protected header--kty: Key type for verification (RSA, EC, oct)from josepy.errors import Error, DeserializationError
try:
# Invalid JWS structure
jws = JWS.json_loads('{"invalid": "structure"}')
except DeserializationError as e:
print(f"JWS parsing failed: {e}")
try:
# Signature verification failure
payload = jws.verify(wrong_key)
except Error as e:
print(f"Signature verification failed: {e}")
# Check header for critical extensions
if jws.header.crit:
print("Warning: JWS contains critical extensions")
# Handle or reject based on your security policyInstall with Tessl CLI
npx tessl i tessl/pypi-josepyevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10