JOSE protocol implementation in Python with support for JSON Web Algorithms, Keys, and Signatures
73
JOSE-compliant base64 encoding/decoding and JSON serialization utilities with support for certificates, CSRs, and typed field handling. Provides the foundation for secure data encoding and structured JSON object management.
URL-safe base64 encoding with padding stripped according to JOSE specifications.
def b64encode(data: bytes) -> bytes:
"""
JOSE Base64 encode.
Parameters:
- data: Data to be encoded
Returns:
bytes: JOSE Base64 string (URL-safe, no padding)
Raises:
TypeError: If data is not bytes
"""
def b64decode(data: Union[bytes, str]) -> bytes:
"""
JOSE Base64 decode.
Parameters:
- data: Base64 string to decode (str or bytes)
Returns:
bytes: Decoded data
Raises:
TypeError: If input is not str or bytes
ValueError: If input contains non-ASCII characters (str input)
"""from josepy import b64encode, b64decode
# Encode data to JOSE base64
data = b"Hello, JOSE Base64!"
encoded = b64encode(data)
print(f"Encoded: {encoded}") # URL-safe, no padding
# Decode back to original
decoded = b64decode(encoded)
print(f"Decoded: {decoded}")
# Decode from string
encoded_str = encoded.decode('ascii')
decoded_from_str = b64decode(encoded_str)
assert decoded == decoded_from_str
# JOSE base64 vs standard base64
import base64
standard_b64 = base64.b64encode(data)
jose_b64 = b64encode(data)
print(f"Standard: {standard_b64}") # May have padding and +/
print(f"JOSE: {jose_b64}") # URL-safe, no paddingAdditional encoding/decoding functions for JOSE-specific data handling.
def encode_b64jose(data: bytes) -> str:
"""
Encode bytes to JOSE base64 string.
Parameters:
- data: Data to encode
Returns:
str: JOSE base64 encoded string
"""
def decode_b64jose(data: str, size: Optional[int] = None, minimum: bool = False) -> bytes:
"""
Decode JOSE base64 string to bytes with size validation.
Parameters:
- data: JOSE base64 encoded string
- size: Expected output size in bytes (optional)
- minimum: If True, size is minimum required (default: exact match)
Returns:
bytes: Decoded data
Raises:
josepy.errors.DeserializationError: If size validation fails
"""
def encode_hex16(value: bytes) -> str:
"""
Encode bytes to hex string.
Parameters:
- value: Bytes to encode
Returns:
str: Hex encoded string
"""
def decode_hex16(value: str, size: Optional[int] = None, minimum: bool = False) -> bytes:
"""
Decode hex string to bytes with size validation.
Parameters:
- value: Hex encoded string
- size: Expected output size in bytes (optional)
- minimum: If True, size is minimum required
Returns:
bytes: Decoded bytes
"""Utilities for encoding and decoding X.509 certificates and Certificate Signing Requests (CSRs).
def encode_cert(cert: x509.Certificate) -> str:
"""
Encode X.509 certificate to base64 DER string.
Parameters:
- cert: X.509 certificate object from cryptography
Returns:
str: Base64 DER encoded certificate
"""
def decode_cert(b64der: str) -> x509.Certificate:
"""
Decode base64 DER string to X.509 certificate.
Parameters:
- b64der: Base64 DER encoded certificate string
Returns:
x509.Certificate: Certificate object
Raises:
josepy.errors.DeserializationError: If decoding fails
"""
def encode_csr(csr: x509.CertificateSigningRequest) -> str:
"""
Encode X.509 CSR to base64 DER string.
Parameters:
- csr: X.509 CSR object from cryptography
Returns:
str: Base64 DER encoded CSR
"""
def decode_csr(b64der: str) -> x509.CertificateSigningRequest:
"""
Decode base64 DER string to X.509 CSR.
Parameters:
- b64der: Base64 DER encoded CSR string
Returns:
x509.CertificateSigningRequest: CSR object
Raises:
josepy.errors.DeserializationError: If decoding fails
"""from josepy import encode_cert, decode_cert, encode_csr, decode_csr
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
# Assuming you have a certificate
# cert = x509.load_pem_x509_certificate(cert_pem_data, default_backend())
# Encode certificate to base64 DER
cert_b64 = encode_cert(cert)
print(f"Certificate B64: {cert_b64[:60]}...")
# Decode back to certificate object
decoded_cert = decode_cert(cert_b64)
assert cert == decoded_cert
# Same process for CSRs
# csr = x509.load_pem_x509_csr(csr_pem_data, default_backend())
csr_b64 = encode_csr(csr)
decoded_csr = decode_csr(csr_b64)Framework for creating JSON-serializable objects with typed fields and validation.
def field(json_name: str, default: Any = None, omitempty: bool = False, decoder: Optional[Callable] = None, encoder: Optional[Callable] = None) -> Any:
"""
Declare a JSON field with type annotations and custom encoding/decoding.
Parameters:
- json_name: JSON property name
- default: Default value if omitted
- omitempty: Skip field if value equals default
- decoder: Function to decode from JSON value
- encoder: Function to encode to JSON value
Returns:
Field descriptor for use in class definitions
"""
class Field:
"""Field descriptor for JSON object properties"""
def __init__(self, json_name: str, default: Any = None, omitempty: bool = False, decoder: Optional[Callable] = None, encoder: Optional[Callable] = None): ...
def decode(self, value: Any) -> Any:
"""Decode JSON value to Python object"""
def encode(self, value: Any) -> Any:
"""Encode Python object to JSON value"""
class JSONObjectWithFields:
"""Base class for JSON objects with field definitions"""
@classmethod
def from_json(cls, jobj: Any): ...
def to_partial_json(self) -> Any: ...
def json_dumps(self, **kwargs) -> str: ...
@classmethod
def json_loads(cls, json_string: str): ...
class TypedJSONObjectWithFields(JSONObjectWithFields):
"""Typed variant supporting automatic type-based deserialization"""
type_field_name: str = "typ" # JSON field containing type information
TYPES: Dict[str, Type] = {} # Registry of types for deserializationfrom josepy import field, JSONObjectWithFields
from josepy.json_util import TypedJSONObjectWithFields
# Define a custom JSON object with fields
class Person(JSONObjectWithFields):
name: str = field('name')
age: int = field('age', default=0)
email: str = field('email', omitempty=True)
# Create and serialize
person = Person(name="Alice", age=30, email="alice@example.com")
person_json = person.json_dumps()
print(f"Person JSON: {person_json}")
# Deserialize
loaded_person = Person.json_loads(person_json)
print(f"Loaded: {loaded_person.name}, age {loaded_person.age}")
# Typed objects with automatic type detection
class Message(TypedJSONObjectWithFields):
type_field_name = "type"
content: str = field('content')
class ErrorMessage(Message):
error_code: int = field('error_code')
class InfoMessage(Message):
info_level: str = field('info_level')
# Register types
Message.TYPES['error'] = ErrorMessage
Message.TYPES['info'] = InfoMessage
# Automatic deserialization based on type field
error_json = '{"type": "error", "content": "Failed", "error_code": 500}'
msg = Message.from_json(json.loads(error_json))
print(f"Message type: {type(msg).__name__}") # ErrorMessageAdditional utility classes for key handling and immutable data structures.
class ComparableKey:
"""Comparable wrapper for cryptography keys"""
def __init__(self, wrapped): ...
def public_key(self) -> 'ComparableKey': ...
def __eq__(self, other) -> bool: ...
def __hash__(self) -> int: ...
class ComparableRSAKey(ComparableKey):
"""Comparable wrapper for RSA keys"""
class ComparableECKey(ComparableKey):
"""Comparable wrapper for EC keys"""
class ImmutableMap:
"""Immutable key-to-value mapping with attribute access"""
def __init__(self, **kwargs): ...
def update(self, **kwargs) -> 'ImmutableMap': ...
def __getitem__(self, key: str) -> Any: ...
def __iter__(self): ...
def __len__(self) -> int: ...
def __hash__(self) -> int: ...from josepy import ComparableRSAKey, ImmutableMap
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
# Comparable keys enable equality comparison
key1 = rsa.generate_private_key(65537, 2048, default_backend())
key2 = rsa.generate_private_key(65537, 2048, default_backend())
comp_key1 = ComparableRSAKey(key1)
comp_key2 = ComparableRSAKey(key2)
comp_key1_copy = ComparableRSAKey(key1)
print(f"Keys equal: {comp_key1 == comp_key1_copy}") # True
print(f"Different keys: {comp_key1 == comp_key2}") # False
# Keys can be used in sets and dictionaries
key_set = {comp_key1, comp_key2, comp_key1_copy}
print(f"Unique keys: {len(key_set)}") # 2
# Immutable maps
config = ImmutableMap(host="localhost", port=8080, ssl=True)
print(f"Host: {config.host}, Port: {config.port}")
# Update creates new instance
new_config = config.update(port=9090, debug=True)
print(f"New port: {new_config.port}, Debug: {new_config.debug}")
print(f"Original port: {config.port}") # Unchanged
# Can be used as dict keys (hashable)
configs = {config: "production", new_config: "development"}All utilities may raise specific exceptions for validation and encoding failures:
from josepy.errors import DeserializationError
try:
# Invalid base64
decoded = b64decode("invalid base64!")
except ValueError as e:
print(f"Base64 decode error: {e}")
try:
# Size validation failure
data = decode_b64jose("dGVzdA", size=10) # "test" is only 4 bytes
except DeserializationError as e:
print(f"Size validation error: {e}")
try:
# Invalid certificate data
cert = decode_cert("not a certificate")
except DeserializationError as e:
print(f"Certificate decode error: {e}")Install 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