0
# Base64 and JSON Utilities
1
2
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.
3
4
## Capabilities
5
6
### JOSE Base64 Encoding
7
8
URL-safe base64 encoding with padding stripped according to JOSE specifications.
9
10
```python { .api }
11
def b64encode(data: bytes) -> bytes:
12
"""
13
JOSE Base64 encode.
14
15
Parameters:
16
- data: Data to be encoded
17
18
Returns:
19
bytes: JOSE Base64 string (URL-safe, no padding)
20
21
Raises:
22
TypeError: If data is not bytes
23
"""
24
25
def b64decode(data: Union[bytes, str]) -> bytes:
26
"""
27
JOSE Base64 decode.
28
29
Parameters:
30
- data: Base64 string to decode (str or bytes)
31
32
Returns:
33
bytes: Decoded data
34
35
Raises:
36
TypeError: If input is not str or bytes
37
ValueError: If input contains non-ASCII characters (str input)
38
"""
39
```
40
41
#### Usage Examples
42
43
```python
44
from josepy import b64encode, b64decode
45
46
# Encode data to JOSE base64
47
data = b"Hello, JOSE Base64!"
48
encoded = b64encode(data)
49
print(f"Encoded: {encoded}") # URL-safe, no padding
50
51
# Decode back to original
52
decoded = b64decode(encoded)
53
print(f"Decoded: {decoded}")
54
55
# Decode from string
56
encoded_str = encoded.decode('ascii')
57
decoded_from_str = b64decode(encoded_str)
58
assert decoded == decoded_from_str
59
60
# JOSE base64 vs standard base64
61
import base64
62
standard_b64 = base64.b64encode(data)
63
jose_b64 = b64encode(data)
64
print(f"Standard: {standard_b64}") # May have padding and +/
65
print(f"JOSE: {jose_b64}") # URL-safe, no padding
66
```
67
68
### Extended JOSE Base64 Utilities
69
70
Additional encoding/decoding functions for JOSE-specific data handling.
71
72
```python { .api }
73
def encode_b64jose(data: bytes) -> str:
74
"""
75
Encode bytes to JOSE base64 string.
76
77
Parameters:
78
- data: Data to encode
79
80
Returns:
81
str: JOSE base64 encoded string
82
"""
83
84
def decode_b64jose(data: str, size: Optional[int] = None, minimum: bool = False) -> bytes:
85
"""
86
Decode JOSE base64 string to bytes with size validation.
87
88
Parameters:
89
- data: JOSE base64 encoded string
90
- size: Expected output size in bytes (optional)
91
- minimum: If True, size is minimum required (default: exact match)
92
93
Returns:
94
bytes: Decoded data
95
96
Raises:
97
josepy.errors.DeserializationError: If size validation fails
98
"""
99
100
def encode_hex16(value: bytes) -> str:
101
"""
102
Encode bytes to hex string.
103
104
Parameters:
105
- value: Bytes to encode
106
107
Returns:
108
str: Hex encoded string
109
"""
110
111
def decode_hex16(value: str, size: Optional[int] = None, minimum: bool = False) -> bytes:
112
"""
113
Decode hex string to bytes with size validation.
114
115
Parameters:
116
- value: Hex encoded string
117
- size: Expected output size in bytes (optional)
118
- minimum: If True, size is minimum required
119
120
Returns:
121
bytes: Decoded bytes
122
"""
123
```
124
125
### X.509 Certificate Utilities
126
127
Utilities for encoding and decoding X.509 certificates and Certificate Signing Requests (CSRs).
128
129
```python { .api }
130
def encode_cert(cert: x509.Certificate) -> str:
131
"""
132
Encode X.509 certificate to base64 DER string.
133
134
Parameters:
135
- cert: X.509 certificate object from cryptography
136
137
Returns:
138
str: Base64 DER encoded certificate
139
"""
140
141
def decode_cert(b64der: str) -> x509.Certificate:
142
"""
143
Decode base64 DER string to X.509 certificate.
144
145
Parameters:
146
- b64der: Base64 DER encoded certificate string
147
148
Returns:
149
x509.Certificate: Certificate object
150
151
Raises:
152
josepy.errors.DeserializationError: If decoding fails
153
"""
154
155
def encode_csr(csr: x509.CertificateSigningRequest) -> str:
156
"""
157
Encode X.509 CSR to base64 DER string.
158
159
Parameters:
160
- csr: X.509 CSR object from cryptography
161
162
Returns:
163
str: Base64 DER encoded CSR
164
"""
165
166
def decode_csr(b64der: str) -> x509.CertificateSigningRequest:
167
"""
168
Decode base64 DER string to X.509 CSR.
169
170
Parameters:
171
- b64der: Base64 DER encoded CSR string
172
173
Returns:
174
x509.CertificateSigningRequest: CSR object
175
176
Raises:
177
josepy.errors.DeserializationError: If decoding fails
178
"""
179
```
180
181
#### Usage Examples
182
183
```python
184
from josepy import encode_cert, decode_cert, encode_csr, decode_csr
185
from cryptography import x509
186
from cryptography.x509.oid import NameOID
187
from cryptography.hazmat.primitives import hashes
188
189
# Assuming you have a certificate
190
# cert = x509.load_pem_x509_certificate(cert_pem_data, default_backend())
191
192
# Encode certificate to base64 DER
193
cert_b64 = encode_cert(cert)
194
print(f"Certificate B64: {cert_b64[:60]}...")
195
196
# Decode back to certificate object
197
decoded_cert = decode_cert(cert_b64)
198
assert cert == decoded_cert
199
200
# Same process for CSRs
201
# csr = x509.load_pem_x509_csr(csr_pem_data, default_backend())
202
csr_b64 = encode_csr(csr)
203
decoded_csr = decode_csr(csr_b64)
204
```
205
206
### JSON Object Framework
207
208
Framework for creating JSON-serializable objects with typed fields and validation.
209
210
```python { .api }
211
def field(json_name: str, default: Any = None, omitempty: bool = False, decoder: Optional[Callable] = None, encoder: Optional[Callable] = None) -> Any:
212
"""
213
Declare a JSON field with type annotations and custom encoding/decoding.
214
215
Parameters:
216
- json_name: JSON property name
217
- default: Default value if omitted
218
- omitempty: Skip field if value equals default
219
- decoder: Function to decode from JSON value
220
- encoder: Function to encode to JSON value
221
222
Returns:
223
Field descriptor for use in class definitions
224
"""
225
226
class Field:
227
"""Field descriptor for JSON object properties"""
228
229
def __init__(self, json_name: str, default: Any = None, omitempty: bool = False, decoder: Optional[Callable] = None, encoder: Optional[Callable] = None): ...
230
231
def decode(self, value: Any) -> Any:
232
"""Decode JSON value to Python object"""
233
234
def encode(self, value: Any) -> Any:
235
"""Encode Python object to JSON value"""
236
237
class JSONObjectWithFields:
238
"""Base class for JSON objects with field definitions"""
239
240
@classmethod
241
def from_json(cls, jobj: Any): ...
242
243
def to_partial_json(self) -> Any: ...
244
245
def json_dumps(self, **kwargs) -> str: ...
246
247
@classmethod
248
def json_loads(cls, json_string: str): ...
249
250
class TypedJSONObjectWithFields(JSONObjectWithFields):
251
"""Typed variant supporting automatic type-based deserialization"""
252
253
type_field_name: str = "typ" # JSON field containing type information
254
TYPES: Dict[str, Type] = {} # Registry of types for deserialization
255
```
256
257
#### Usage Examples
258
259
```python
260
from josepy import field, JSONObjectWithFields
261
from josepy.json_util import TypedJSONObjectWithFields
262
263
# Define a custom JSON object with fields
264
class Person(JSONObjectWithFields):
265
name: str = field('name')
266
age: int = field('age', default=0)
267
email: str = field('email', omitempty=True)
268
269
# Create and serialize
270
person = Person(name="Alice", age=30, email="alice@example.com")
271
person_json = person.json_dumps()
272
print(f"Person JSON: {person_json}")
273
274
# Deserialize
275
loaded_person = Person.json_loads(person_json)
276
print(f"Loaded: {loaded_person.name}, age {loaded_person.age}")
277
278
# Typed objects with automatic type detection
279
class Message(TypedJSONObjectWithFields):
280
type_field_name = "type"
281
282
content: str = field('content')
283
284
class ErrorMessage(Message):
285
error_code: int = field('error_code')
286
287
class InfoMessage(Message):
288
info_level: str = field('info_level')
289
290
# Register types
291
Message.TYPES['error'] = ErrorMessage
292
Message.TYPES['info'] = InfoMessage
293
294
# Automatic deserialization based on type field
295
error_json = '{"type": "error", "content": "Failed", "error_code": 500}'
296
msg = Message.from_json(json.loads(error_json))
297
print(f"Message type: {type(msg).__name__}") # ErrorMessage
298
```
299
300
### Utility Classes
301
302
Additional utility classes for key handling and immutable data structures.
303
304
```python { .api }
305
class ComparableKey:
306
"""Comparable wrapper for cryptography keys"""
307
308
def __init__(self, wrapped): ...
309
def public_key(self) -> 'ComparableKey': ...
310
def __eq__(self, other) -> bool: ...
311
def __hash__(self) -> int: ...
312
313
class ComparableRSAKey(ComparableKey):
314
"""Comparable wrapper for RSA keys"""
315
316
class ComparableECKey(ComparableKey):
317
"""Comparable wrapper for EC keys"""
318
319
class ImmutableMap:
320
"""Immutable key-to-value mapping with attribute access"""
321
322
def __init__(self, **kwargs): ...
323
def update(self, **kwargs) -> 'ImmutableMap': ...
324
def __getitem__(self, key: str) -> Any: ...
325
def __iter__(self): ...
326
def __len__(self) -> int: ...
327
def __hash__(self) -> int: ...
328
```
329
330
#### Usage Examples
331
332
```python
333
from josepy import ComparableRSAKey, ImmutableMap
334
from cryptography.hazmat.primitives.asymmetric import rsa
335
from cryptography.hazmat.backends import default_backend
336
337
# Comparable keys enable equality comparison
338
key1 = rsa.generate_private_key(65537, 2048, default_backend())
339
key2 = rsa.generate_private_key(65537, 2048, default_backend())
340
341
comp_key1 = ComparableRSAKey(key1)
342
comp_key2 = ComparableRSAKey(key2)
343
comp_key1_copy = ComparableRSAKey(key1)
344
345
print(f"Keys equal: {comp_key1 == comp_key1_copy}") # True
346
print(f"Different keys: {comp_key1 == comp_key2}") # False
347
348
# Keys can be used in sets and dictionaries
349
key_set = {comp_key1, comp_key2, comp_key1_copy}
350
print(f"Unique keys: {len(key_set)}") # 2
351
352
# Immutable maps
353
config = ImmutableMap(host="localhost", port=8080, ssl=True)
354
print(f"Host: {config.host}, Port: {config.port}")
355
356
# Update creates new instance
357
new_config = config.update(port=9090, debug=True)
358
print(f"New port: {new_config.port}, Debug: {new_config.debug}")
359
print(f"Original port: {config.port}") # Unchanged
360
361
# Can be used as dict keys (hashable)
362
configs = {config: "production", new_config: "development"}
363
```
364
365
## Error Handling
366
367
All utilities may raise specific exceptions for validation and encoding failures:
368
369
```python
370
from josepy.errors import DeserializationError
371
372
try:
373
# Invalid base64
374
decoded = b64decode("invalid base64!")
375
except ValueError as e:
376
print(f"Base64 decode error: {e}")
377
378
try:
379
# Size validation failure
380
data = decode_b64jose("dGVzdA", size=10) # "test" is only 4 bytes
381
except DeserializationError as e:
382
print(f"Size validation error: {e}")
383
384
try:
385
# Invalid certificate data
386
cert = decode_cert("not a certificate")
387
except DeserializationError as e:
388
print(f"Certificate decode error: {e}")
389
```