Skip to content

Latest commit

 

History

History
332 lines (244 loc) · 11.8 KB

File metadata and controls

332 lines (244 loc) · 11.8 KB
description:How to serialize and deserialize JWS in Compact, General JSON, and Flattened JSON Serialization.

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)

Compact Signature

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

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

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

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
cGxlLmNvbS9pc19yb290Ijp0cnVlfQ.
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Serialization

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

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.

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:

{
  "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:

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:

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:

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.

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:

{
  "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
  "protected": "eyJhbGciOiJFUzI1NiJ9",
  "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
  "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
}
{
  "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:

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:

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

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:

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:

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)