diff --git a/hiboo/captcha/fields.py b/hiboo/captcha/fields.py index 35ee8d1f6b025a426cb44cdafbdbfa534be55285..efa0de02c33188e7ff1cbd9cefc1cd6116ce700a 100644 --- a/hiboo/captcha/fields.py +++ b/hiboo/captcha/fields.py @@ -1,36 +1,49 @@ from wtforms import validators, fields, widgets, utils -from jwcrypto import jwe, common +from joserfc import jwk, jwt +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.hkdf import HKDF import flask import random +import datetime class ContextTokenField(fields.Field): """ Field that has a token hidden field that holds a stateless context. """ - # TODO: implement context expiration - # TODO: implement context validity based on request attributes - @classmethod def encode(cls, context): - """ JOSE authenticating encryption - """ - jose = jwe.JWE( - common.json_encode(context), - common.json_encode({"alg": "PBES2-HS512+A256KW", "enc": "A256GCM"}) - ) - jose.add_recipient(flask.current_app.config["SECRET_KEY"]) - return jose.serialize(True) + """JOSE authenticating encryption""" + kdf = HKDF(hashes.SHA512(), length=32, salt=None, info=None) + dkey = kdf.derive(flask.current_app.config["SECRET_KEY"].encode()) + key = jwk.OctKey.import_key(dkey) + header = {"alg": "A256KW", "enc": "A256GCM"} + expired = datetime.datetime.now() + datetime.timedelta(minutes=3) + claims = { + "exp": int(expired.timestamp()), + "aud": flask.url_for('account.signup'), + "context": context + } + + return jwt.encode(header, claims, key) @classmethod - def decode(cls, serialized): - """ JOSE decryption - """ - jose = jwe.JWE() - jose.deserialize(serialized) - jose.decrypt(flask.current_app.config["SECRET_KEY"]) - return common.json_decode(jose.payload) + def decode(cls, token): + """JOSE decryption""" + kdf = HKDF(hashes.SHA512(), length=32, salt=None, info=None) + dkey = kdf.derive(flask.current_app.config["SECRET_KEY"].encode()) + key = jwk.OctKey.import_key(dkey) + decoded_token = jwt.decode(token, key) + claims_requests = jwt.JWTClaimsRegistry( + now=int(datetime.datetime.now().timestamp()), + aud={'essential': True, 'value': flask.url_for('account.signup')}, + exp={'essential': True}, + context={'essential': True} + ) + claims_requests.validate(decoded_token.claims) + + return decoded_token.claims["context"] def __init__(self, *args, **kwargs): self.received_context = {} diff --git a/poetry.lock b/poetry.lock index 380120ffe417821fb777f1c280dc4596fd911d9b..3bd72dce5cd21297645a6889db07f0e4c5908fb5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -843,18 +843,21 @@ MarkupSafe = ">=2.0" i18n = ["Babel (>=2.7)"] [[package]] -name = "jwcrypto" -version = "1.5.1" -description = "Implementation of JOSE Web standards" +name = "joserfc" +version = "0.9.0" +description = "The ultimate Python library for JOSE RFCs, including JWS, JWE, JWK, JWA, JWT" optional = false -python-versions = ">= 3.6" +python-versions = ">=3.8" files = [ - {file = "jwcrypto-1.5.1.tar.gz", hash = "sha256:48bb9bf433777136253579e52b75ffe0f9a4a721d133d01f45a0b91ed5f4f1ae"}, + {file = "joserfc-0.9.0-py3-none-any.whl", hash = "sha256:4026bdbe2c196cd40574e916fa1e28874d99649412edaab0e373dec3077153fb"}, + {file = "joserfc-0.9.0.tar.gz", hash = "sha256:eebca7f587b1761ce43a98ffd5327f2b600b9aa5bb0a77b947687f503ad43bc0"}, ] [package.dependencies] -cryptography = ">=3.4" -deprecated = "*" +cryptography = "*" + +[package.extras] +drafts = ["pycryptodome"] [[package]] name = "limits" @@ -1550,7 +1553,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2060,4 +2062,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "54e33e527f9f597c2775263a0801e4e74b4297353c402dff1ab67f4963a383bc" +content-hash = "fd6a5238f1d7b0eb915b347c4f066a5ffc48a84457a51204a58869cf41607aa9" diff --git a/pyproject.toml b/pyproject.toml index c3736ba71919f66bf5edb243c9e4db08e4cbe76e..d21724de10582bb1890618a93f6f8e9ea212c5af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,8 +50,8 @@ Werkzeug = "^3.0.1" email-validator = "^2.1.0.post1" pyotp = "^2.9.0" qrcode = "^7.4.2" -jwcrypto = "^1.5.1" Pillow = "^10.2.0" +joserfc = "^0.9.0" bootstrap-flask = "^2.3.3" [tool.poetry.group.dev]