0
# JSON Web Signature (JWS)
1
2
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.
3
4
## Capabilities
5
6
### JWS Class
7
8
Main class for creating and managing JSON Web Signatures with support for signing, verification, and serialization.
9
10
```python { .api }
11
class JWS:
12
"""JSON Web Signature implementation"""
13
14
@classmethod
15
def sign(cls, payload: bytes, **kwargs) -> 'JWS':
16
"""
17
Create a new JWS by signing payload. The key and algorithm are passed via kwargs.
18
19
Parameters:
20
- payload: Message bytes to sign
21
- **kwargs: Signing parameters including 'key' (JWK), 'alg' (JWASignature),
22
and other header fields
23
24
Returns:
25
JWS: New JWS instance with signature
26
"""
27
28
def verify(self, key: Optional[JWK] = None) -> bool:
29
"""
30
Verify JWS signature.
31
32
Parameters:
33
- key: JWK instance containing verification key (optional if key in header)
34
35
Returns:
36
bool: True if signature is valid, False otherwise
37
38
Raises:
39
josepy.errors.Error: If signature verification fails
40
"""
41
42
def json_dumps(self, **kwargs) -> str:
43
"""Serialize JWS to JSON string"""
44
45
@classmethod
46
def json_loads(cls, json_string: str) -> 'JWS':
47
"""Deserialize JWS from JSON string"""
48
49
def to_compact(self) -> bytes:
50
"""
51
Serialize JWS to compact format.
52
53
Returns:
54
bytes: Compact JWS serialization (header.payload.signature)
55
56
Raises:
57
AssertionError: If JWS doesn't have exactly one signature or algorithm not in protected header
58
"""
59
60
@classmethod
61
def from_compact(cls, compact: bytes) -> 'JWS':
62
"""
63
Deserialize JWS from compact format.
64
65
Parameters:
66
- compact: Compact JWS serialization bytes
67
68
Returns:
69
JWS: Deserialized JWS instance
70
71
Raises:
72
josepy.errors.DeserializationError: If compact format is invalid
73
"""
74
75
@property
76
def signature(self) -> 'Signature':
77
"""
78
Get singleton signature component.
79
80
Returns:
81
Signature: The single signature component
82
83
Raises:
84
AssertionError: If JWS doesn't have exactly one signature
85
"""
86
87
# JWS components
88
payload: bytes # Message payload
89
signatures: List['Signature'] # List of signature components
90
```
91
92
#### Usage Examples
93
94
```python
95
from josepy import JWS, Header, JWKRSA, RS256
96
from cryptography.hazmat.primitives.asymmetric import rsa
97
from cryptography.hazmat.backends import default_backend
98
99
# Generate RSA key pair
100
private_key = rsa.generate_private_key(65537, 2048, default_backend())
101
jwk = JWKRSA(key=private_key)
102
public_jwk = jwk.public_key()
103
104
# Create and sign JWS
105
payload = b'{"user": "alice", "scope": "read:profile"}'
106
jws = JWS.sign(payload, key=jwk, alg=RS256)
107
108
# Serialize to JSON
109
jws_json = jws.json_dumps()
110
print(f"JWS JSON: {jws_json}")
111
112
# Deserialize and verify
113
loaded_jws = JWS.json_loads(jws_json)
114
verified_payload = loaded_jws.verify(public_jwk)
115
print(f"Verified payload: {verified_payload.decode()}")
116
117
# Sign with custom headers
118
custom_jws = JWS.sign(
119
payload,
120
key=jwk,
121
alg=RS256,
122
kid="key-1", # Key ID
123
typ="JWT" # Token type
124
)
125
```
126
127
### Header Class
128
129
JOSE header management supporting registered header parameters with type-safe field handling.
130
131
```python { .api }
132
class Header:
133
"""JOSE Header for JWS"""
134
135
def __init__(self, alg=None, **kwargs):
136
"""
137
Initialize JOSE header.
138
139
Parameters:
140
- alg: Signature algorithm (JWASignature instance)
141
- **kwargs: Additional header parameters
142
"""
143
144
def not_omitted(self) -> Dict[str, Any]:
145
"""Get header fields that would not be omitted in JSON"""
146
147
@classmethod
148
def from_json(cls, jobj: Any) -> 'Header':
149
"""Deserialize header from JSON object"""
150
151
def to_partial_json(self) -> Any:
152
"""Serialize header to JSON-compatible dictionary"""
153
154
# Standard header fields
155
alg: Optional[JWASignature] # Algorithm
156
jku: Optional[bytes] # JWK Set URL
157
jwk: Optional[JWK] # JSON Web Key
158
kid: Optional[str] # Key ID
159
x5u: Optional[bytes] # X.509 URL
160
x5c: Tuple[x509.Certificate, ...] # X.509 Certificate Chain
161
x5t: Optional[bytes] # X.509 Certificate SHA-1 Thumbprint
162
x5tS256: Optional[bytes] # X.509 Certificate SHA-256 Thumbprint
163
typ: Optional[MediaType] # Type (Media Type)
164
cty: Optional[MediaType] # Content Type
165
crit: Tuple[Any, ...] # Critical extensions
166
```
167
168
#### Usage Examples
169
170
```python
171
from josepy import Header, RS256, JWKRSA
172
from cryptography import x509
173
174
# Basic header with algorithm
175
header = Header(alg=RS256)
176
177
# Header with key ID
178
header_with_kid = Header(alg=RS256, kid="rsa-key-1")
179
180
# Header with embedded JWK
181
jwk = JWKRSA(key=private_key)
182
header_with_jwk = Header(alg=RS256, jwk=jwk.public_key())
183
184
# Header with X.509 certificate chain
185
# (assuming you have certificates)
186
header_with_x5c = Header(
187
alg=RS256,
188
x5c=(cert1, cert2), # x509.Certificate objects
189
x5t=cert_thumbprint # SHA-1 thumbprint bytes
190
)
191
192
# Custom header fields
193
header_custom = Header(
194
alg=RS256,
195
typ="JWT",
196
custom_field="custom_value" # Non-standard fields supported
197
)
198
199
# Serialize header
200
header_json = header.json_dumps()
201
print(f"Header JSON: {header_json}")
202
```
203
204
### Signature Class
205
206
Individual signature component supporting JWS signature operations.
207
208
```python { .api }
209
class Signature:
210
"""JWS Signature component"""
211
212
def __init__(self, signature: bytes, header: Header):
213
"""
214
Initialize signature component.
215
216
Parameters:
217
- signature: Raw signature bytes
218
- header: JOSE header for this signature
219
"""
220
221
@classmethod
222
def from_json(cls, jobj: Any) -> 'Signature':
223
"""Deserialize signature from JSON object"""
224
225
def to_partial_json(self) -> Any:
226
"""Serialize signature to JSON-compatible dictionary"""
227
228
signature: bytes # Raw signature bytes
229
header: Header # JOSE header
230
```
231
232
### Media Type Handling
233
234
Utility class for handling JOSE media type encoding and decoding.
235
236
```python { .api }
237
class MediaType:
238
"""Media Type field encoder/decoder"""
239
240
PREFIX = "application/" # MIME prefix
241
242
@classmethod
243
def decode(cls, value: str) -> str:
244
"""
245
Decode media type from JOSE format.
246
Adds 'application/' prefix if not present.
247
"""
248
249
@classmethod
250
def encode(cls, value: str) -> str:
251
"""
252
Encode media type to JOSE format.
253
Removes 'application/' prefix if present.
254
"""
255
```
256
257
## Complete Workflow Examples
258
259
### JWT-style Token Creation
260
261
```python
262
from josepy import JWS, Header, JWKRSA, RS256
263
import json
264
import time
265
266
# Create JWT payload
267
payload_dict = {
268
"sub": "1234567890",
269
"name": "John Doe",
270
"iat": int(time.time()),
271
"exp": int(time.time()) + 3600 # 1 hour expiration
272
}
273
payload_json = json.dumps(payload_dict).encode()
274
275
# Sign with RS256
276
private_key = rsa.generate_private_key(65537, 2048, default_backend())
277
jwk = JWKRSA(key=private_key)
278
279
jws = JWS.sign(
280
payload_json,
281
key=jwk,
282
alg=RS256,
283
typ="JWT",
284
kid="key-1"
285
)
286
287
# The result is a complete JWS that can be transmitted
288
token = jws.json_dumps()
289
```
290
291
### Multi-Step Verification Process
292
293
```python
294
# Receive JWS token (e.g., from HTTP request)
295
received_token = '{"header": {...}, "payload": "...", "signature": "..."}'
296
297
try:
298
# Parse JWS
299
jws = JWS.json_loads(received_token)
300
301
# Extract key ID from header
302
key_id = jws.header.kid
303
304
# Look up verification key (application-specific)
305
verification_jwk = get_public_key_by_id(key_id) # Your function
306
307
# Verify signature and get payload
308
verified_payload = jws.verify(verification_jwk)
309
310
# Parse payload as needed
311
payload_dict = json.loads(verified_payload.decode())
312
313
# Additional validation (expiration, audience, etc.)
314
if payload_dict.get('exp', 0) < time.time():
315
raise ValueError("Token expired")
316
317
print(f"Valid token for user: {payload_dict.get('sub')}")
318
319
except Exception as e:
320
print(f"Token verification failed: {e}")
321
```
322
323
### Multiple Algorithm Support
324
325
```python
326
from josepy import RS256, ES256, HS256, JWKRSA, JWKEC, JWKOct
327
328
# Create keys for different algorithms
329
rsa_key = rsa.generate_private_key(65537, 2048, default_backend())
330
ec_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
331
hmac_key = os.urandom(32)
332
333
# Create JWKs
334
rsa_jwk = JWKRSA(key=rsa_key)
335
ec_jwk = JWKEC(key=ec_key)
336
hmac_jwk = JWKOct(key=hmac_key)
337
338
payload = b'{"message": "Hello, multi-algorithm world!"}'
339
340
# Sign with different algorithms
341
rsa_jws = JWS.sign(payload, key=rsa_jwk, alg=RS256)
342
ec_jws = JWS.sign(payload, key=ec_jwk, alg=ES256)
343
hmac_jws = JWS.sign(payload, key=hmac_jwk, alg=HS256)
344
345
# Verify with corresponding keys
346
rsa_verified = rsa_jws.verify(rsa_jwk.public_key())
347
ec_verified = ec_jws.verify(ec_jwk.public_key())
348
hmac_verified = hmac_jws.verify(hmac_jwk) # Symmetric key
349
350
print("All signatures verified successfully!")
351
```
352
353
## Command Line Interface
354
355
JOSEPY provides a comprehensive command-line tool for JWS operations through the `jws` command:
356
357
```python { .api }
358
class CLI:
359
"""JWS Command Line Interface"""
360
361
@classmethod
362
def sign(cls, args: argparse.Namespace) -> None:
363
"""Execute JWS signing operation from command line arguments"""
364
365
@classmethod
366
def verify(cls, args: argparse.Namespace) -> bool:
367
"""Execute JWS verification operation from command line arguments"""
368
369
@classmethod
370
def run(cls, args: Optional[List[str]] = None) -> Optional[bool]:
371
"""Parse command line arguments and execute sign/verify operations"""
372
```
373
374
### CLI Usage Examples
375
376
**Basic Signing:**
377
```bash
378
# Sign payload from stdin with RSA key
379
echo "Hello, World!" | jws sign --key private_key.pem --alg RS256
380
381
# Sign with compact serialization
382
echo "Hello, World!" | jws --compact sign --key private_key.pem --alg RS256
383
384
# Sign with protected headers
385
echo "Hello, World!" | jws sign --key private_key.pem --alg RS256 --protect alg --protect kid
386
```
387
388
**Verification:**
389
```bash
390
# Verify JWS token from stdin using key
391
echo '{"header": {...}, "payload": "...", "signature": "..."}' | jws verify --key public_key.pem --kty RSA
392
393
# Verify compact JWS
394
echo "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...." | jws --compact verify --key public_key.pem --kty RSA
395
396
# Verify with embedded key (no --key needed)
397
echo '{"header": {"jwk": {...}}, ...}' | jws verify
398
```
399
400
**Available Options:**
401
- `--compact`: Use compact serialization format
402
- `--key`: Path to key file (PEM/DER format)
403
- `--alg`: Signature algorithm (RS256, ES256, HS256, etc.)
404
- `--protect`: Header parameters to include in protected header
405
- `--kty`: Key type for verification (RSA, EC, oct)
406
407
## Error Handling
408
409
```python
410
from josepy.errors import Error, DeserializationError
411
412
try:
413
# Invalid JWS structure
414
jws = JWS.json_loads('{"invalid": "structure"}')
415
except DeserializationError as e:
416
print(f"JWS parsing failed: {e}")
417
418
try:
419
# Signature verification failure
420
payload = jws.verify(wrong_key)
421
except Error as e:
422
print(f"Signature verification failed: {e}")
423
424
# Check header for critical extensions
425
if jws.header.crit:
426
print("Warning: JWS contains critical extensions")
427
# Handle or reject based on your security policy
428
```