Skip to content

Commit 2462a4b

Browse files
committed
You may want to add extra claims to the JWT header.
1 parent 3b49d3f commit 2462a4b

File tree

5 files changed

+84
-14
lines changed

5 files changed

+84
-14
lines changed

doc/jws.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ JSON Web Signature (JWS)
66
JSON Web Signature (JWS) represents content secured with digital signatures
77
or Message Authentication Codes (MACs) using JSON-based data structures.
88

9-
It's assumed that you know all you need to know about key handling
9+
It's assumed that you know all you need to know about key handling if not
10+
please spend some time reading keyhandling_ .
11+

doc/keyhandling.rst

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ Let us start with you not having any key at all and you want to create a
2929
signed JSON Web Token (JWS_).
3030
What to do ?
3131

32-
Well if you know what kind of key you want, if it is a asymmetric key you can
33-
use one of the provided factory methods.
32+
Well if you know what kind of key you want, and if it is a asymmetric key you
33+
want, you can use one of the provided factory methods.
3434

3535
RSA
3636
:py:func:`cryptojwt.jwk.rsa.new_rsa_key`
@@ -86,7 +86,7 @@ and::
8686
>>> ec_key.has_private_key()
8787
True
8888

89-
When it comes to exporting keys a :py:class:`cryptojwt.jwk.JWK` instance
89+
When it comes to exporting keys, a :py:class:`cryptojwt.jwk.JWK` instance
9090
only know how to serialize into the format described in JWK_.
9191

9292
>>> from cryptojwt.jwk.rsa import new_rsa_key
@@ -160,7 +160,7 @@ Key bundle
160160
As mentioned above a key bundle is used to manage keys that have a common
161161
origin.
162162

163-
You can initiate a key bundle in serveral ways. You can use all the
163+
You can initiate a key bundle in several ways. You can use all the
164164
import variants we described above and then add the resulting key to a key
165165
bundle::
166166

@@ -204,14 +204,14 @@ bundle::
204204
]
205205
}
206206

207-
**Note** that you will get a JWKS representing the public keys unless you
208-
specify that you want a representation of the private keys.
207+
**Note** that this will get you a JWKS representing the public keys.
209208

210209
As an example of the special functionality of
211210
:py:class:`cryptojwt.key_bundle.KeyBundle` assume you have imported a file
212211
containing a JWKS with one key into a key bundle and then some time later
213212
another key is added to the file.
214-
This is how key bundle deals with that::
213+
214+
First import the file with one key::
215215

216216
>>> from cryptojwt.key_bundle import KeyBundle
217217
>>> kb = KeyBundle(source="file://{}".format(fname), fileformat='jwks')
@@ -225,10 +225,13 @@ keys in the key bundle::
225225
>>> len(_keys)
226226
2
227227

228-
It turns out the it contains the 2 keys that are in the file.
228+
It turns out the key bundle now contains 2 keys. Both the keys that are in the
229+
file.
230+
229231
If the change is that one key is removed then something else happens.
230232
Assume we add one key and remove one of the ones that was there before.
231-
The file now should contain 2 keys::
233+
The file now contain 2 keys, and you might expect the key bundle to do the
234+
same::
232235

233236
>>> _keys = kb.keys()
234237
>>> len(_keys)
@@ -264,7 +267,7 @@ Creating a key jar with your own newly minted keys you would do:
264267

265268
**Note* that the default issuer ID is the empty string ''.
266269
267-
To import a JWKS you would do::
270+
To import a JWKS you could do it by first creating a key bundle::
268271

269272
>>> from cryptojwt.key_bundle import KeyBundle
270273
>>> from cryptojwt.key_jar import KeyJar
@@ -291,6 +294,41 @@ The last line can also be expressed as::
291294
**Note** both variants, adds a key bundle to the list of key bundles that
292295
belongs to '' it does not overwrite anything that was already there.
293296

297+
Adding a JWKS is such a common thing that there is a simpler way to do it::
298+
299+
>>> from cryptojwt.key_jar import KeyJar
300+
>>> JWKS = {
301+
"keys": [
302+
{
303+
"kty": "RSA",
304+
"e": "AQAB",
305+
"kid": "abc",
306+
"n":
307+
"wf-wiusGhA-gleZYQAOPQlNUIucPiqXdPVyieDqQbXXOPBe3nuggtVzeq7
308+
pVFH1dZz4dY2Q2LA5DaegvP8kRvoSB_87ds3dy3Rfym_GUSc5B0l1TgEob
309+
cyaep8jguRoHto6GWHfCfKqoUYZq4N8vh4LLMQwLR6zi6Jtu82nB5k8"
310+
}
311+
]}
312+
>>> key_jar = KeyJar()
313+
>>> key_jar.import_jwks(JWKS)
314+
315+
The end result is the same as when you first created a key bundle and then
316+
added it to the key jar.
317+
318+
When dealing with signed and/or encrypted JSON Web Tokens
319+
:py:class:`cryptojwt.key_jar.KeyJar` has these nice methods.
320+
321+
get_jwt_verify_keys
322+
:py:func:`cryptojwt.key_jar.KeyJar.get_jwt_verify_keys` takes an
323+
signed JWT as input and returns a set of keys that
324+
can be used to verify the signature. The set you get back is a best
325+
estimate and might not contain **the** key. How good the estimate is
326+
depends on the information present in the JWS.
327+
328+
get_jwt_decrypt_keys
329+
:py:func:`cryptojwt.key_jar.KeyJar.get_jwt_decrypt_keys` does the
330+
same thing but returns keys that can be used to decrypt a message.
331+
294332

295333
.. _cryptography: https://cryptography.io/en/latest/
296334
.. _JWK: https://tools.ietf.org/html/rfc7517

src/cryptojwt/jws/jws.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,21 +98,26 @@ def alg_keys(self, keys, use, protected=None):
9898

9999
return key, xargs, _alg
100100

101-
def sign_compact(self, keys=None, protected=None):
101+
def sign_compact(self, keys=None, protected=None, **kwargs):
102102
"""
103103
Produce a JWS using the JWS Compact Serialization
104104
105105
:param keys: A dictionary of keys
106106
:param protected: The protected headers (a dictionary)
107+
:param kwargs: claims you want to add to the standard headers
107108
:return: A signed JSON Web Token
108109
"""
109110

111+
_headers = self._header
112+
_headers.update(kwargs)
113+
110114
key, xargs, _alg = self.alg_keys(keys, 'sig', protected)
111115

112116
if "typ" in self:
113117
xargs["typ"] = self["typ"]
114118

115-
jwt = JWSig(**xargs)
119+
_headers.update(xargs)
120+
jwt = JWSig(**_headers)
116121
if _alg == "none":
117122
return jwt.pack(parts=[self.msg, ""])
118123

@@ -384,6 +389,9 @@ def _is_compact_jws(self, jws):
384389
def alg2keytype(self, alg):
385390
return alg2keytype(alg)
386391

392+
def set_header_claim(self, key, value):
393+
self._header[key] = value
394+
387395

388396
def factory(token):
389397
_jw = JWS()

src/cryptojwt/jwx.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def __init__(self, msg=None, with_digest=False, httpc=None, **kwargs):
5757
self.jwt = None
5858
self._jwk = None
5959
self._jwks = None
60+
self._header = {}
6061

6162
if kwargs:
6263
for key in self.args:
@@ -111,7 +112,7 @@ def keys(self):
111112

112113
def headers(self, extra=None):
113114
_extra = extra or {}
114-
_header = {}
115+
_header = self._header.copy()
115116
for param in self.args:
116117
try:
117118
_header[param] = _extra[param]

tests/test_06_jws.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,3 +762,24 @@ def test_parse_rsa_algorithm_ps512():
762762
assert hash
763763
assert hash.name == 'sha512'
764764
assert padding.name == 'EMSA-PSS'
765+
766+
767+
def test_extra_headers_1():
768+
pkey = import_private_rsa_key_from_file(full_path("./size2048.key"))
769+
payload = "Please take a moment to register today"
770+
keys = [RSAKey(priv_key=pkey)]
771+
_jws = JWS(payload, alg='RS256')
772+
sjwt = _jws.sign_compact(keys, foo='bar')
773+
_jwt = factory(sjwt)
774+
assert set(_jwt.jwt.headers.keys()) == {'alg', 'foo'}
775+
776+
777+
def test_extra_headers_2():
778+
pkey = import_private_rsa_key_from_file(full_path("./size2048.key"))
779+
payload = "Please take a moment to register today"
780+
keys = [RSAKey(priv_key=pkey)]
781+
_jws = JWS(payload, alg='RS256')
782+
_jws.set_header_claim('foo', 'bar')
783+
sjwt = _jws.sign_compact(keys)
784+
_jwt = factory(sjwt)
785+
assert set(_jwt.jwt.headers.keys()) == {'alg', 'foo'}

0 commit comments

Comments
 (0)