From b6a111602b2a7c870681a2672c7dac64e37f13c7 Mon Sep 17 00:00:00 2001 From: John Turner Date: Fri, 5 Sep 2025 15:04:10 -0400 Subject: init --- .dir-locals.el | 10 +++++++ .flake8 | 2 ++ .mypy.ini | 2 ++ bozo4/__init__.py | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ bozo4/py.typed | 0 5 files changed, 102 insertions(+) create mode 100644 .dir-locals.el create mode 100644 .flake8 create mode 100644 .mypy.ini create mode 100644 bozo4/__init__.py create mode 100644 bozo4/py.typed diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..042b2ea --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,10 @@ +((python-ts-mode . ((eval . (flycheck-mode 1)) + (eval . (flycheck-select-checker 'python-mypy)) + (eval . (flycheck-add-next-checker 'python-mypy (cons t 'python-flake8))) + (eval . (add-hook 'before-save-hook 'fmt-current-buffer nil t)) + (eval . (setq-local fmt-executable "black" + fmt-args '("-"))))) + (meson-mode + . ((fmt-executable . "meson") + (fmt-args . ("format" "-")) + (eval . (add-hook 'before-save-hook 'fmt-current-buffer nil t))))) diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..79a16af --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 120 \ No newline at end of file diff --git a/.mypy.ini b/.mypy.ini new file mode 100644 index 0000000..1d781df --- /dev/null +++ b/.mypy.ini @@ -0,0 +1,2 @@ +[mypy] +strict = true \ No newline at end of file diff --git a/bozo4/__init__.py b/bozo4/__init__.py new file mode 100644 index 0000000..0f2e2fa --- /dev/null +++ b/bozo4/__init__.py @@ -0,0 +1,88 @@ +import hmac +from urllib.parse import quote as quote_url +from hashlib import sha256 +from datetime import datetime +from typing import Dict + + +def _s3v4_sign(msg: str, key: bytes) -> bytes: + return hmac.new(key, msg.encode(), sha256).digest() + + +def s3v4_datetime_string(dt: datetime) -> str: + return dt.strftime("%Y%m%dT%H%M%SZ") + + +def s3v4_sign_request( + endpoint: str, + region: str, + access_key: str, + secret_key: str, + request_method: str, + date: datetime, + payload_hash: str, + uri: str, + parameters: Dict[str, str], + headers: Dict[str, str], + service: str, +) -> str: + algo = "AWS4-HMAC-SHA256" + datestamp = date.strftime("%Y%m%d") + amzdate = s3v4_datetime_string(date) + + canon_uri = quote_url(uri) + + signed_headers = ";".join( + key for key in sorted(key.lower() for key in headers.keys()) + ) + + quoted_parameters = { + quote_url(key): quote_url(value) for key, value in parameters.items() + } + + canon_parameters = "&".join( + f"{key}={val}" + for key, val in sorted(quoted_parameters.items(), key=lambda p: p[0]) + ) + + def fixup_header_value(val: str) -> str: + return " ".join(w for w in val.strip().split()) + + canon_headers = "".join( + f"{key.lower()}:{fixup_header_value(val)}\n" + for key, val in sorted(headers.items(), key=lambda h: h[0]) + ) + + canon_request = "\n".join( + x + for x in [ + request_method, + canon_uri, + canon_parameters, + canon_headers, + signed_headers, + payload_hash, + ] + ) + + hashed_request = sha256(canon_request.encode()).hexdigest().lower() + + credential_scope = f"{datestamp}/{region}/{service}/aws4_request" + + s = f"{algo}\n{amzdate}\n{credential_scope}\n{hashed_request}" + + k = f"AWS4{secret_key}".encode() + k = _s3v4_sign(msg=datestamp, key=k) + k = _s3v4_sign(msg=region, key=k) + k = _s3v4_sign(msg=service, key=k) + k = _s3v4_sign(msg="aws4_request", key=k) + sig = _s3v4_sign(msg=s, key=k).hex().lower() + + return ", ".join( + x + for x in [ + f"{algo} Credential={access_key}/{credential_scope}", + f"SignedHeaders={signed_headers}", + f"Signature={sig}", + ] + ) diff --git a/bozo4/py.typed b/bozo4/py.typed new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3