Skip to main content

A Python implementation of CBOR Web Token (CWT) and CBOR Object Signing and Encryption (COSE).

Project description

Python CWT

PyPI version PyPI - Python Version Documentation Status Github CI codecov

A Python implementation of CBOR Web Token (CWT) and CBOR Object Signing and Encryption (COSE).

See Document for details:

Installing

Install with pip:

pip install cwt

Usase

Python CWT is an easy-to-use CWT/COSE library a little bit inspired by PyJWT. If you already know about JSON Web Token (JWT), little knowledge of CBOR, COSE and CWT is required to use this library.

>>> import cwt
>>> from cwt import claims, cose_key
>>> key = cose_key.from_symmetric_key(alg="HS256")
>>> token = cwt.encode({"iss": "coaps://as.example", "sub": "dajiaji", "cti": "123"}, key)
>>> token.hex()
'd18443a10105a05835a60172636f6170733a2f2f61732e6578616d706c65026764616a69616a69'
'0743313233041a609097b7051a609089a7061a609089a758201fad9b0a76803194bd11ca9b9b3c'
'bbf1028005e15321665a768994f38c7127f7'
>>> decoded = cwt.decode(token, key)
>>> decoded
{1: 'coaps://as.example', 2: 'dajiaji', 7: b'123',
 4: 1620088759, 5: 1620085159, 6: 1620085159}
>>> readable = claims.from_dict(decoded)
>>> readable.iss
'coaps://as.example'
>>> readable.sub
'dajiaji'
>>> readable.exp
1620088759

Followings are typical and basic examples which create CWT, verify and decode it:

See Usage Examples for details.

MACed CWT

Create a MACed CWT with HS256, verify and decode it as follows:

import cwt
from cwt import cose_key

key = cose_key.from_symmetric_key(alg="HS256")
token = cwt.encode({"iss": "coaps://as.example", "sub": "dajiaji", "cti": "123"}, key)
decoded = cwt.decode(token, key)

CBOR-like structure (Dict[int, Any]) can also be used as follows:

import cwt
from cwt import cose_key

key = cose_key.from_symmetric_key(alg="HS256")
token = cwt.encode({1: "coaps://as.example", 2: "dajiaji", 7: b"123"}, key)
decoded = cwt.decode(token, key)

MAC algorithms other than HS256 are listed in Supported COSE Algorithms.

Signed CWT

Create an Ed25519 (Ed25519 for use w/ EdDSA only) key pair:

$ openssl genpkey -algorithm ed25519 -out private_key.pem
$ openssl pkey -in private_key.pem -pubout -out public_key.pem

Create a Signed CWT with Ed25519, verify and decode it with the key pair as follows:

import cwt
from cwt import cose_key

with open("./private_key.pem") as key_file:
    private_key = cose_key.from_pem(key_file.read(), kid="01")
with open("./public_key.pem") as key_file:
    public_key = cose_key.from_pem(key_file.read(), kid="01")


token = cwt.encode(
    {"iss": "coaps://as.example", "sub": "dajiaji", "cti": "123"}, private_key
)

decoded = cwt.decode(token, public_key)

JWKs can also be used instead of the PEM-formatted keys as follows:

import cwt
from cwt import cose_key

private_key = cose_key.from_jwk({
    "kty": "OKP",
    "d": "L8JS08VsFZoZxGa9JvzYmCWOwg7zaKcei3KZmYsj7dc",
    "use": "sig",
    "crv": "Ed25519",
    "kid": "01",
    "x": "2E6dX83gqD_D0eAmqnaHe1TC1xuld6iAKXfw2OVATr0",
    "alg": "EdDSA",
})
public_key = cose_key.from_jwk({
    "kty": "OKP",
    "use": "sig",
    "crv": "Ed25519",
    "kid": "01",
    "x": "2E6dX83gqD_D0eAmqnaHe1TC1xuld6iAKXfw2OVATr0",
})

token = cwt.encode(
    {"iss": "coaps://as.example", "sub": "dajiaji", "cti": "123"}, private_key
)
decoded = cwt.decode(token, public_key)

Algorithms other than Ed25519 are listed in Supported COSE Algorithms.

Encrypted CWT

Create an encrypted CWT with ChaCha20/Poly1305 (ChaCha20/Poly1305 w/ 256-bit key, 128-bit tag), and decrypt it as follows:

import cwt
from cwt import cose_key

enc_key = cose_key.from_symmetric_key(alg="ChaCha20/Poly1305")
token = cwt.encode({"iss": "coaps://as.example", "sub": "dajiaji", "cti": "123"}, enc_key)
decoded = cwt.decode(token, enc_key)

Algorithms other than ChaCha20/Poly1305 are listed in Supported COSE Algorithms.

Nested CWT

Create a signed CWT and encrypt it, and then decrypt and verify the nested CWT as follows.

import cwt
from cwt import cose_key

with open("./private_key.pem") as key_file:
    private_key = cose_key.from_pem(key_file.read(), kid="01")
with open("./public_key.pem") as key_file:
    public_key = cose_key.from_pem(key_file.read(), kid="01")

# Creates a CWT with ES256 signing.
token = cwt.encode(
    {"iss": "coaps://as.example", "sub": "dajiaji", "cti": "123"}, private_key
)

# Encrypts the signed CWT.
enc_key = cose_key.from_symmetric_key(alg="ChaCha20/Poly1305")
nested = cwt.encode(token, enc_key)

# Decrypts and verifies the nested CWT.
decoded = cwt.decode(nested, [enc_key, public_key])

CWT with User-Defined Claims

You can use your own claims as follows:

Note that such user-defined claim's key should be less than -65536.

import cwt
from cwt import cose_key

with open("./private_key.pem") as key_file:
    private_key = cose_key.from_pem(key_file.read(), kid="01")
with open("./public_key.pem") as key_file:
    public_key = cose_key.from_pem(key_file.read(), kid="01")
token = cwt.encode(
    {
        1: "coaps://as.example",  # iss
        2: "dajiaji",  # sub
        7: b"123",  # cti
        -70001: "foo",
        -70002: ["bar"],
        -70003: {"baz": "qux"},
        -70004: 123,
    },
    private_key,
)
raw = cwt.decode(token, public_key)
# raw[-70001] == "foo"
# raw[-70002][0] == "bar"
# raw[-70003]["baz"] == "qux"
# raw[-70004] == 123
readable = claims.from_dict(raw)
# readable.get(-70001) == "foo"
# readable.get(-70002)[0] == "bar"
# readable.get(-70003)["baz"] == "qux"
# readable.get(-70004) == 123

User-defined claims can also be used with JSON-based claims as follows:

import cwt
from cwt import claims, cose_key

with open("./private_key.pem") as key_file:
    private_key = cose_key.from_pem(key_file.read(), kid="01")
with open("./public_key.pem") as key_file:
    public_key = cose_key.from_pem(key_file.read(), kid="01")

cwt.set_private_claim_names(
    {
        "ext_1": -70001,
        "ext_2": -70002,
        "ext_3": -70003,
        "ext_4": -70004,
    }
)
token = cwt.encode(
    {
        "iss": "coaps://as.example",
        "sub": "dajiaji",
        "cti": b"123",
        "ext_1": "foo",
        "ext_2": ["bar"],
        "ext_3": {"baz": "qux"},
        "ext_4": 123,
    },
    private_key,
)
claims.set_private_claim_names(
    {
        "ext_1": -70001,
        "ext_2": -70002,
        "ext_3": -70003,
        "ext_4": -70004,
    }
)
raw = cwt.decode(token, public_key)
readable = claims.from_dict(raw)
# readable.get("ext_1") == "foo"
# readable.get("ext_2")[0] == "bar"
# readable.get("ext_3")["baz"] == "qux"
# readable.get("ext_4") == 123

CWT with PoP key

This library supports Proof-of-Possession Key Semantics for CBOR Web Tokens (CWTs). A CWT can include a PoP key as follows:

On the issuer side:

import cwt
from cwt import cose_key

# Prepares a signing key for CWT in advance.
with open("./private_key_of_issuer.pem") as key_file:
    private_key = cose_key.from_pem(key_file.read(), kid="issuer-01")

# Sets the PoP key to a CWT for the presenter.
token = cwt.encode(
    {
        "iss": "coaps://as.example",
        "sub": "dajiaji",
        "cti": "123",
        "cnf": {
            "jwk": {  # Provided by the CWT presenter.
                "kty": "OKP",
                "use": "sig",
                "crv": "Ed25519",
                "kid": "01",
                "x": "2E6dX83gqD_D0eAmqnaHe1TC1xuld6iAKXfw2OVATr0",
                "alg": "EdDSA",
            },
        },
    },
    private_key,
)

# Issues the token to the presenter.

On the CWT presenter side:

import cwt
from cwt import cose_key

# Prepares a private PoP key in advance.
with open("./private_pop_key.pem") as key_file:
    pop_key_private = cose_key.from_pem(key_file.read(), kid="01")

# Receives a message (e.g., nonce)  from the recipient.
msg = b"could-you-sign-this-message?"  # Provided by recipient.

# Signs the message with the private PoP key.
sig = pop_key_private.sign(msg)

# Sends the msg and the sig with the CWT to the recipient.

On the CWT recipient side:

import cwt
from cwt import claims, cose_key

# Prepares the public key of the issuer in advance.
with open("./public_key_of_issuer.pem") as key_file:
    public_key = cose_key.from_pem(key_file.read(), kid="issuer-01")

# Verifies and decodes the CWT received from the presenter.
raw = cwt.decode(token, public_key)
decoded = claims.from_dict(raw)

# Extracts the PoP key from the CWT.
extracted_pop_key = cose_key.from_dict(decoded.cnf)  # = raw[8][1]

# Then, verifies the message sent by the presenter
# with the signature which is also sent by the presenter as follows:
extracted_pop_key.verify(msg, sig)

Usage Examples shows other examples which use other confirmation methods for PoP keys.

Tests

You can run tests from the project root after cloning with:

$ tox

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cwt-0.6.0.tar.gz (68.8 kB view hashes)

Uploaded Source

Built Distribution

cwt-0.6.0-py3-none-any.whl (30.6 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page