File: jws.rst

package info (click to toggle)
joserfc 1.6.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,480 kB
  • sloc: python: 8,096; makefile: 18
file content (332 lines) | stat: -rw-r--r-- 12,125 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
:description: How to serialize and deserialize JWS in Compact, General JSON, and Flattened JSON Serialization.

.. _jws:

JSON Web Signature
==================

.. module:: joserfc
    :noindex:

JSON Web Signature (JWS) represents content secured with digital
signatures or Message Authentication Codes (MACs) using JSON-based
data structures. (via RFC7515_)

.. _RFC7515: https://www.rfc-editor.org/rfc/rfc7515

Compact Signature
-----------------

The JWS Compact Serialization represents digitally signed or MACed
content as a compact, URL-safe string. This string is:

.. code-block:: text

    BASE64URL(UTF8(JWS Protected Header)) || '.' ||
    BASE64URL(JWS Payload) || '.' ||
    BASE64URL(JWS Signature)

An example of a compact serialization (line breaks for display purposes only):

.. code-block:: text

    eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.
    eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
    cGxlLmNvbS9pc19yb290Ijp0cnVlfQ.
    dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Serialization
~~~~~~~~~~~~~

You can call :meth:`jws.serialize_compact` to construct a compact JWS serialization:

.. code-block:: python

    from joserfc import jws
    from joserfc.jwk import OctKey

    key = OctKey.import_key("your-secret-key")
    protected = {"alg": "HS256"}
    jws.serialize_compact(protected, "hello", key)
    # => 'eyJhbGciOiJIUzI1NiJ9.aGVsbG8.i_RrfHeMxwHrhk5Xi3J_bU9B9O-gjkMaQtagtqiCndM'

A compact JWS is constructed by protected header, payload and a private key. In the above
example, ``protected`` is the "protected header" part, `"hello"` is the payload part, and
`"secret"` is a plain private key.

Deserialization
~~~~~~~~~~~~~~~

Calling :meth:`jws.deserialize_compact` to extract and verify the compact
serialization with a public key.

.. code-block:: python

    from joserfc import jws
    from joserfc.jwk import OctKey

    text = "eyJhbGciOiJIUzI1NiJ9.aGVsbG8.i_RrfHeMxwHrhk5Xi3J_bU9B9O-gjkMaQtagtqiCndM"
    key = OctKey.import_key("your-secret-key")
    obj = jws.deserialize_compact(text, key)
    # obj.protected => {"alg": "HS256"}
    # obj.payload => b"hello"

JSON Signature
--------------

The JWS JSON Serialization represents digitally signed or MACed
content as a JSON object.  This representation is neither optimized
for compactness nor URL-safe.

An example of a JSON serialization:

.. code-block:: json

    {
      "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
      "signatures": [
        {
          "protected": "eyJhbGciOiJSUzI1NiJ9",
          "header": {"kid":"2010-12-29"},
          "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"
        },
        {
          "protected": "eyJhbGciOiJFUzI1NiJ9",
          "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
          "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
        }
      ]
    }

Serialization
~~~~~~~~~~~~~

You can call :meth:`jws.serialize_json` to construct a JSON JWS serialization:

.. code-block:: python

    import json
    from joserfc import jws
    from joserfc.jwk import KeySet

    members = [
        {
            "protected": {"alg": "RS256"},
            "header": {"kid": "2010-12-29"},
        },
        {
            "protected": {"alg": "ES256"},
            "header": {"kid": "e9bc097a-ce51-4036-9562-d2ade882db0d"},
        },
    ]
    payload = b'{"iss":"joe",\r\n "exp":1300819380,\r\n "http://example.com/is_root":true}'

    with open("your-private-jwks.json") as f:
        data = json.load(f)
        # this key set SHOULD contains kid of "2010-12-29"
        # and "e9bc097a-ce51-4036-9562-d2ade882db0d"
        private_key_set = KeySet.import_key_set(data)

    value = jws.serialize_json(members, payload, private_key_set)
    #: this ``value`` is a dict which looks like the example above

The JSON JWS serialization is constructed by members, payload and private key. A **member**
is a combination of protected header and public header:

.. code-block:: python

    member = {
        "protected": {"alg": "RS256"},
        "header": {"kid": "2010-12-29"},
    }

The ``protected`` header will be base64 encoded in the JSON serialization, together with
the payload to sign a signature for the member:

.. code-block:: none

    SIGNATURE INPUT =
        BASE64URL(UTF8(JWS Protected Header)) || '.' ||
        BASE64URL(JWS Payload)

    SIGNATURE =
        BASE64URL(SignMethod(SIGNATURE INPUT, Private Key))

In the above example, we passed a :class:`jwk.KeySet` as the private key parameter, the
:meth:`jws.serialize_json` will find the correct key in the key set by ``kid``.

Deserialization
~~~~~~~~~~~~~~~

Calling :meth:`jws.deserialize_json` to extract and verify the JSON
serialization with a public key.

.. code-block:: python

    with open("your-public-jwks.json") as f:
        data = json.load(f)
        # the public pair of your previous private key set
        public_key_set = KeySet.import_key_set(data)

    # value is the generated by above code
    obj = jws.deserialize_json(value, public_key_set)
    # => assert obj.payload == payload

General and Flattened
~~~~~~~~~~~~~~~~~~~~~

There are two types of JSON JWS serializations, "general" and "flattened".
The above example is a General JSON Serialization. A Flattened JSON Serialization
contains only one member. Compare the below examples:

.. code-block:: json
    :caption: Flattened JSON Serialization

    {
      "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
      "protected": "eyJhbGciOiJFUzI1NiJ9",
      "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
      "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
    }

.. code-block:: json
    :caption: General JSON Serialization

    {
      "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
      "signatures": [
        {
          "protected": "eyJhbGciOiJFUzI1NiJ9",
          "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
          "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
        }
      ]
    }

You can pass a member dict to construct a flattened serialization; and
a list of members to construct a general serialization:

.. code-block:: python

    member = {
        "protected": {"alg": "ES256"},
        "header": {"kid": "e9bc097a-ce51-4036-9562-d2ade882db0d"},
    }

    # flattened
    jws.serialize_json(member, payload, private_key)

    # general
    jws.serialize_json([member], payload, private_key)

The returned value from ``deserialize_json`` is an object of
:class:`jws.GeneralJSONSignature` or :class:`jws.FlattenedJSONSignature`,
you can tell if the signature is flattened or general with ``obj.flattened``:

.. code-block:: python

    obj = jws.deserialize_json(data, public_key)
    if obj.flattened:
        print("Flattened JSON Serialization")
    else:
        print("General JSON Serialization")

Algorithms
----------

``joserfc.jws`` module supports algorithms from RFC7518, RFC8037, RFC8812,
and RFC9864. Here lists all the algorithms ``joserfc.jws`` supporting:

============== ================================================ =========================
Algorithm name              Description                            Requirements
============== ================================================ =========================
none           No digital signature or MAC performed            :bdg-danger:`Deprecated`
HS256          HMAC using SHA-256                               :bdg-success:`Recommended`
HS384          HMAC using SHA-384                               :bdg-muted:`Optional`
HS512          HMAC using SHA-512                               :bdg-muted:`Optional`
RS256          RSASSA-PKCS1-v1_5 using SHA-256                  :bdg-success:`Recommended`
RS384          RSASSA-PKCS1-v1_5 using SHA-384                  :bdg-muted:`Optional`
RS512          RSASSA-PKCS1-v1_5 using SHA-512                  :bdg-muted:`Optional`
ES256          ECDSA using P-256 and SHA-256                    :bdg-success:`Recommended`
ES384          ECDSA using P-384 and SHA-384                    :bdg-muted:`Optional`
ES512          ECDSA using P-521 and SHA-512                    :bdg-muted:`Optional`
PS256          RSASSA-PSS using SHA-256 and MGF1 with SHA-256   :bdg-muted:`Optional`
PS384          RSASSA-PSS using SHA-384 and MGF1 with SHA-384   :bdg-muted:`Optional`
PS512          RSASSA-PSS using SHA-512 and MGF1 with SHA-512   :bdg-muted:`Optional`
EdDSA          Edwards-curve Digital Signature                  :bdg-danger:`Deprecated`
ES256K         ECDSA using secp256k1 curve and SHA-256          :bdg-muted:`Optional`
Ed25519        EdDSA using the Ed25519 parameter set            :bdg-muted:`Optional`
Ed448          EdDSA using the Ed448 parameter set              :bdg-muted:`Optional`
============== ================================================ =========================

.. versionadded:: 1.5.0

    ``Ed25519`` and ``Ed448`` are added since 1.5.0 per RFC9864.

The serialization and deserialization methods on ``joserfc.jws`` module accept
an ``algorithms`` parameter for specifying the allowed algorithms. By default,
those ``serialize`` and ``deserialize`` methods will ONLY allow recommended
algorithms defined by RFCs. With non recommended algorithms, you may encounter
an :ref:`UnsupportedAlgorithmError` error.

.. _unencoded_payload:

Unencoded Payload Option
------------------------

The unencoded payload option, defined in RFC7797, allows the payload of a
JWS (JSON Web Signature) to remain unencoded, without using base64 encoding.

To enable this option, you need to set the ``b64`` header parameter to ``false``
in the JWS header.

Here are examples demonstrating the usage of the ``b64`` option:

.. code-block:: python

    from joserfc.jws import serialize_compact, deserialize_compact
    from joserfc.jwk import OctKey

    key = OctKey.import_key("your-secret-key")
    protected = {"alg": "HS256", "b64": False, "crit": ["b64"]}
    value = serialize_compact(protected, "hello", key)
    # => 'eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19.hello.mdPbZLtc3tqQ6NCV1pKF-qfEx-3jtR6rv109phKAc4I'
    deserialize_compact(value, key)

.. note::

    The ``crit`` MUST be present with ``"b64"`` in its value set when
    ``b64`` is in the header.

Since the payload is not base64 encoded, if the payload contains non urlsafe
characters, the compact serialization will detach the payload:

.. code-block:: python

    from joserfc.jws import serialize_compact, deserialize_compact
    from joserfc.jwk import OctKey

    key = OctKey.import_key("your-secret-key")
    protected = {"alg": "HS256", "b64": False, "crit": ["b64"]}
    value = serialize_compact(protected, "$.02", key)
    # => 'eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..6iWgAK_TZLgwzk1mhtxs6Imw-dJM1cRstsOKVQ5MjFQ'
    # since the payload is detached, you need to specify the
    # payload when calling deserialize_compact
    deserialize_compact(value, key, payload="$.02")

You can also use ``b64`` header for JSON serialization: ``serialize_json`` and
``deserialize_json``.

Guess Algorithms via Key
------------------------

If you are unsure which algorithm to use but already have a key, you can call the
:meth:`jws.JWSRegistry.guess_algorithm` method to determine a suitable algorithm:

.. code-block:: python

    from joserfc.jws import JWSRegistry, serialize_compact

    alg = JWSRegistry.guess_algorithm(key, JWSRegistry.Strategy.RECOMMENDED)
    protected = {"alg": alg.name}
    serialize_compact(protected, b"payload", key)