summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.dir-locals.el10
-rw-r--r--.flake82
-rw-r--r--.mypy.ini2
-rw-r--r--bozo4/__init__.py88
-rw-r--r--bozo4/py.typed0
5 files changed, 102 insertions, 0 deletions
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
--- /dev/null
+++ b/bozo4/py.typed