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)
|