CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-josepy

JOSE protocol implementation in Python with support for JSON Web Algorithms, Keys, and Signatures

73

1.15x
Overview
Eval results
Files

jws.mddocs/

JSON Web Signature (JWS)

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.

Capabilities

JWS Class

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 components

Usage Examples

from 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
)

Header Class

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 extensions

Usage Examples

from 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}")

Signature Class

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 header

Media Type Handling

Utility 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.
        """

Complete Workflow Examples

JWT-style Token Creation

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()

Multi-Step Verification Process

# 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}")

Multiple Algorithm Support

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!")

Command Line Interface

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"""

CLI Usage Examples

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 kid

Verification:

# 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 verify

Available 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)

Error Handling

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 policy

Install with Tessl CLI

npx tessl i tessl/pypi-josepy

docs

errors.md

index.md

interfaces.md

jwa.md

jwk.md

jws.md

utilities.md

tile.json