From c6ac0d60430053c25db341816d63cf17e38e0978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?c=C4=83t=C4=83lin?= Date: Tue, 9 May 2023 09:11:36 +0200 Subject: [PATCH] feat: add multiple, remote recipients support --- .pre-commit-config.yaml | 2 +- README.md | 23 ++++-- halig/__version__.py | 2 +- halig/commands/edit.py | 2 +- halig/encryption.py | 36 +++++----- halig/settings.py | 53 ++++++++++++-- halig/utils.py | 2 +- pdm.lock | 147 ++++++++++++++++++++++++++++----------- pyproject.toml | 2 + tests/test_encryption.py | 37 ++++++---- 10 files changed, 218 insertions(+), 88 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0449a3..db80a5b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: args: [ --fix=lf ] - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.260 + rev: v0.0.265 hooks: - id: ruff args: diff --git a/README.md b/README.md index d849823..3b0adc2 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,21 @@ it encrypts the new contents into an [age](https://github.com/FiloSottile/age) file that you can store, _relatively_ safe, anywhere. + +## Features + +- Simple notebooks management with paths autocompletion +- Passphrase-less, fully-encrypted notes, compatible with existing SSH keys +- No external `age` binary needed +- Almost all `age` advantages, like having multiple keys for encryption and decryption +- Remote (HTTP) public keys import: e.g: github.com/.keys + ## Install ```shell pipx install halig # or pip ``` - ## Setup TLDR ```shell @@ -26,9 +34,13 @@ ssh-keygen -t ed25519 mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/halig" cat << EOF > "${XDG_CONFIG_HOME:-$HOME/.config}/halig/halig.yml" --- -notebooks_root_path: /home/$(id -un)/Documents/Notebooks -identity_path: /home/$(id -un)/.ssh/id_ed25519 -recipient_path: /home/$(id -un)/.ssh/id_ed25519.pub +notebooks_root_path: ~/Documents/Notebooks +identity_paths: + - ~/.ssh/id_ed25519 +recipient_path: + - ~/.ssh/id_ed25519.pub + - https://github.com/.keys + - https://gitlab.com/.keys EOF ``` @@ -38,4 +50,5 @@ EOF halig edit some_notebook # edit today's note relative to /some_notebook halig edit some_notebook/foo # edit /some_notebook/foo.age halig notebooks # list current notebooks -``` \ No newline at end of file +``` + diff --git a/halig/__version__.py b/halig/__version__.py index d3ec452..3ced358 100644 --- a/halig/__version__.py +++ b/halig/__version__.py @@ -1 +1 @@ -__version__ = "0.2.0" +__version__ = "0.2.1" diff --git a/halig/commands/edit.py b/halig/commands/edit.py index 8b1650b..1b44e76 100644 --- a/halig/commands/edit.py +++ b/halig/commands/edit.py @@ -58,7 +58,7 @@ class EditCommand(BaseCommand): temp_path = Path(tf.name) editor = os.environ.get("EDITOR", "vim") - subprocess.call([editor, temp_path]) + subprocess.call([editor, temp_path]) # noqa: S603 with temp_path.open("rb") as f: new_contents = f.read() diff --git a/halig/encryption.py b/halig/encryption.py index 72b22a6..a5faa73 100644 --- a/halig/encryption.py +++ b/halig/encryption.py @@ -1,6 +1,6 @@ -from pyrage import decrypt as rage_decrypt -from pyrage import encrypt as rage_encrypt -from pyrage import ssh, x25519 +from pyrage import decrypt as rage_decrypt # pyright: ignore [reportGeneralTypeIssues] +from pyrage import encrypt as rage_encrypt # pyright: ignore [reportGeneralTypeIssues] +from pyrage import ssh, x25519 # pyright: ignore [reportGeneralTypeIssues] from halig.settings import Settings @@ -14,27 +14,25 @@ class Encryptor: def __init__(self, settings: Settings): self.settings = settings + self.identities = [] + self.recipients = [] - # load identity - with settings.identity_path.open("r") as f: - identity_contents = f.read() - if identity_contents.startswith("-----BEGIN OPENSSH PRIVATE KEY-----"): - self.identity = ssh.Identity.from_buffer(identity_contents.encode()) - else: - self.identity = x25519.Identity.from_str(identity_contents) + for key in settings.load_private_keys(): + if key.startswith("-----BEGIN OPENSSH PRIVATE KEY-----"): + self.identities.append(ssh.Identity.from_buffer(key.encode())) + else: + self.identities.append(x25519.Identity.from_str(key)) - # load recipient - with settings.recipient_path.open("r") as f: - recipient_contents = f.read() - if recipient_contents.startswith("ssh-ed25519"): - self.recipient = ssh.Recipient.from_str(recipient_contents) - else: - self.recipient = x25519.Recipient.from_str(recipient_contents) + for key in settings.load_public_keys(): + if key.startswith("ssh-ed25519"): + self.recipients.append(ssh.Recipient.from_str(key)) + else: + self.recipients.append(x25519.Recipient.from_str(key)) def encrypt(self, data: str | bytes) -> bytes: if isinstance(data, str): data = data.encode() - return rage_encrypt(data, [self.recipient]) # type: ignore[no-any-return] + return rage_encrypt(data, self.recipients) # type: ignore[no-any-return] def decrypt(self, data: bytes) -> bytes: - return rage_decrypt(data, [self.identity]) # type: ignore[no-any-return] + return rage_decrypt(data, self.identities) # type: ignore[no-any-return] diff --git a/halig/settings.py b/halig/settings.py index 37f4bef..062493c 100644 --- a/halig/settings.py +++ b/halig/settings.py @@ -2,8 +2,9 @@ import os from functools import lru_cache from pathlib import Path +import httpx import yaml -from pydantic import BaseSettings, DirectoryPath, FilePath +from pydantic import BaseSettings, DirectoryPath, FilePath, HttpUrl, validator class Settings(BaseSettings): @@ -14,15 +15,53 @@ class Settings(BaseSettings): Attributes: notebooks_root_path (DirectoryPath): a *valid* path to a directory that may contain notes or other notebooks - identity_path (FilePath): a *valid* path to an identity file, which usually - is understood as a private key. Defaults to `~/.ssh/id_ed25519` - recipient_path (FilePath): a *valid* path to a recipient file, which usually - is understood as a public key. Defaults to `~/.ssh/id_ed25519.pub` + identity_paths (list[FilePath]): a list of *valid* paths of private keys. + Defaults to `[~/.ssh/id_ed25519]` + recipient_paths (list[FilePath|HttpUrl]): a list *valid* paths of public keys, + which usually is understood as a public key. Defaults to + `[~/.ssh/id_ed25519.pub]` """ notebooks_root_path: DirectoryPath - identity_path: FilePath = Path("~/.ssh/id_ed25519").expanduser() - recipient_path: FilePath = Path("~/.ssh/id_ed25519.pub").expanduser() + identity_paths: list[FilePath] = [Path("~/.ssh/id_ed25519").expanduser()] + recipient_paths: list[FilePath | HttpUrl] = [ + Path("~/.ssh/id_ed25519.pub").expanduser(), + ] + + @validator("identity_paths", "recipient_paths", pre=True) + def validate_paths(cls, v: list[str]): # noqa: N805 + new_v = [] + for path in v: + if isinstance(path, str): + if not path.startswith("http"): + new_v.append(Path(path).expanduser()) + elif isinstance(path, Path): + new_v.append(path.expanduser()) + else: + new_v.append(path) + + return v + + def load_private_keys(self) -> set[str]: + keys = set() + for path in self.identity_paths: + with path.open("r") as f: + keys.add(f.read()) + return keys + + def load_public_keys(self) -> set[str]: + keys = set() + for path in self.recipient_paths: + if isinstance(path, HttpUrl): + response = httpx.get(str(path)) + if response.status_code == httpx.codes.OK: + for line in response.content.decode().split("\n"): + if line: + keys.add(line) + elif isinstance(path, Path): + with path.open("r") as f: + keys.add(f.read()) + return keys class Config: env_prefix = "halig_" diff --git a/halig/utils.py b/halig/utils.py index c28608f..534eed3 100644 --- a/halig/utils.py +++ b/halig/utils.py @@ -24,7 +24,7 @@ def capture(fn: Callable): print(f"[red]{exc}") sys.exit(1) except Exception as exc: # noqa: BLE001 - print(f"[bold red] Unexpected error: {exc}") + print(f"[beld red] Unexpected error: {exc}") sys.exit(2) return wrapper diff --git a/pdm.lock b/pdm.lock index d21ad49..56b017e 100644 --- a/pdm.lock +++ b/pdm.lock @@ -1,6 +1,16 @@ # This file is @generated by PDM. # It is not intended for manual editing. +[[package]] +name = "anyio" +version = "3.6.2" +requires_python = ">=3.6.2" +summary = "High level compatibility layer for multiple asynchronous event loop implementations" +dependencies = [ + "idna>=2.8", + "sniffio>=1.1", +] + [[package]] name = "black" version = "23.3.0" @@ -87,6 +97,36 @@ dependencies = [ "colorama>=0.4", ] +[[package]] +name = "h11" +version = "0.14.0" +requires_python = ">=3.7" +summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" + +[[package]] +name = "httpcore" +version = "0.17.0" +requires_python = ">=3.7" +summary = "A minimal low-level HTTP client." +dependencies = [ + "anyio<5.0,>=3.0", + "certifi", + "h11<0.15,>=0.13", + "sniffio==1.*", +] + +[[package]] +name = "httpx" +version = "0.24.0" +requires_python = ">=3.7" +summary = "The next generation HTTP client." +dependencies = [ + "certifi", + "httpcore<0.18.0,>=0.15.0", + "idna", + "sniffio", +] + [[package]] name = "idna" version = "3.4" @@ -171,7 +211,7 @@ dependencies = [ [[package]] name = "mkdocs-material" -version = "9.1.7" +version = "9.1.11" requires_python = ">=3.7" summary = "Documentation that simply works" dependencies = [ @@ -342,7 +382,7 @@ summary = "Python bindings for rage (age in Rust)" [[package]] name = "pyright" -version = "1.1.304" +version = "1.1.306" requires_python = ">=3.7" summary = "Command line wrapper for pyright" dependencies = [ @@ -416,7 +456,7 @@ dependencies = [ [[package]] name = "pytest-reportlog" -version = "0.2.1" +version = "0.3.0" requires_python = ">=3.7" summary = "Replacement for the --resultlog option, focused in simplicity and extensibility" dependencies = [ @@ -473,7 +513,7 @@ dependencies = [ [[package]] name = "rich" -version = "13.3.4" +version = "13.3.5" requires_python = ">=3.7.0" summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" dependencies = [ @@ -483,7 +523,7 @@ dependencies = [ [[package]] name = "ruff" -version = "0.0.262" +version = "0.0.265" requires_python = ">=3.7" summary = "An extremely fast Python linter, written in Rust." @@ -499,6 +539,12 @@ version = "1.16.0" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" summary = "Python 2 and 3 compatibility utilities" +[[package]] +name = "sniffio" +version = "1.3.0" +requires_python = ">=3.7" +summary = "Sniff out which async library your code is running under" + [[package]] name = "tomli" version = "2.0.1" @@ -507,11 +553,12 @@ summary = "A lil' TOML parser" [[package]] name = "typer" -version = "0.7.0" +version = "0.9.0" requires_python = ">=3.6" summary = "Typer, build great CLIs. Easy to code. Based on Python type hints." dependencies = [ "click<9.0.0,>=7.1.1", + "typing-extensions>=3.7.4.3", ] [[package]] @@ -540,9 +587,13 @@ summary = "Filesystem events monitoring" [metadata] lock_version = "4.2" groups = ["default", "docs", "linting", "testing"] -content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d25092287d1" +content_hash = "sha256:07ceae75e97c0c1a4e995e1e9c638fb6c30697ec83f437e2e14df6a7ad4441c2" [metadata.files] +"anyio 3.6.2" = [ + {url = "https://files.pythonhosted.org/packages/77/2b/b4c0b7a3f3d61adb1a1e0b78f90a94e2b6162a043880704b7437ef297cad/anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, + {url = "https://files.pythonhosted.org/packages/8b/94/6928d4345f2bc1beecbff03325cad43d320717f51ab74ab5a571324f4f5a/anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, +] "black 23.3.0" = [ {url = "https://files.pythonhosted.org/packages/06/1e/273d610249f0335afb1ddb03664a03223f4826e3d1a95170a0142cb19fb4/black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, {url = "https://files.pythonhosted.org/packages/12/4b/99c71d1cf1353edd5aff2700b8960f92e9b805c9dab72639b67dbb449d3a/black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, @@ -728,6 +779,18 @@ content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d250 {url = "https://files.pythonhosted.org/packages/07/38/d1c37c3021c2f81c4734d96c41c9d263ed92071559c4f417e96647ad2da9/griffe-0.26.0.tar.gz", hash = "sha256:08675ffe8c17139e7769e950dd21f8e98a2e76205cbbd2911d5dec26d2cbf1be"}, {url = "https://files.pythonhosted.org/packages/57/bf/a2e8f1e3b3139c84d2d488beacca56a6b6c2f70e7aefaadda053fe1e87de/griffe-0.26.0-py3-none-any.whl", hash = "sha256:38f3f6bbe834501cc199a07b7b7e0e2550aaf19a9d1ee27acf879027e47c9b9e"}, ] +"h11 0.14.0" = [ + {url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] +"httpcore 0.17.0" = [ + {url = "https://files.pythonhosted.org/packages/41/16/c809655d32fd93e688b9e2a1aaba1008118369d1eda00818f6f65eb509f8/httpcore-0.17.0.tar.gz", hash = "sha256:cc045a3241afbf60ce056202301b4d8b6af08845e3294055eb26b09913ef903c"}, + {url = "https://files.pythonhosted.org/packages/6c/39/05ebe30333ec66bba849d3c25c85d759b94c43bb03b2222de051c50d4390/httpcore-0.17.0-py3-none-any.whl", hash = "sha256:0fdfea45e94f0c9fd96eab9286077f9ff788dd186635ae61b312693e4d943599"}, +] +"httpx 0.24.0" = [ + {url = "https://files.pythonhosted.org/packages/4e/c1/692013f1e6115a061a14f6c7d05947515a1eb7b85ef6e9bf0ffbf0e92738/httpx-0.24.0-py3-none-any.whl", hash = "sha256:447556b50c1921c351ea54b4fe79d91b724ed2b027462ab9a329465d147d5a4e"}, + {url = "https://files.pythonhosted.org/packages/ae/23/f7beaf11a8b95fc173b8979c4bfd23ea7711c5ebd458d657d24a59df7e9f/httpx-0.24.0.tar.gz", hash = "sha256:507d676fc3e26110d41df7d35ebd8b3b8585052450f4097401c9be59d928c63e"}, +] "idna 3.4" = [ {url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, {url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, @@ -816,9 +879,9 @@ content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d250 {url = "https://files.pythonhosted.org/packages/3b/3f/9531888bc92bafb1bffddca5d9240a7bae9a479d465528883b61808ef9d6/mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, {url = "https://files.pythonhosted.org/packages/fb/5c/6594400290df38f99bf8d9ef249387b56f4ad962667836266f6fe7da8597/mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, ] -"mkdocs-material 9.1.7" = [ - {url = "https://files.pythonhosted.org/packages/1a/a7/096d75a6751184c068ea3eb4ecff9786b06f2bf01446dd71f7e65551fac8/mkdocs_material-9.1.7-py3-none-any.whl", hash = "sha256:4ab0e2f378bb811ca44a001a63d687403a2725f0485c41d6a2a7aed3aa34cd31"}, - {url = "https://files.pythonhosted.org/packages/fe/58/befd75349c7f417a7cc17af1a93ea1ecbad22d166612e8ab816f0c744d96/mkdocs_material-9.1.7.tar.gz", hash = "sha256:244f85d13e3aa773aaa66c74b95bb4b00d89a3a917f6f9d33704a5655bf0c423"}, +"mkdocs-material 9.1.11" = [ + {url = "https://files.pythonhosted.org/packages/36/2d/e42f392cc7dd582da579da75180b0ad8f7d27155a5803ba1f79861b97c5d/mkdocs_material-9.1.11.tar.gz", hash = "sha256:f5d473eb79d6640a5e668d4b2ab5b9de5e76ae0a0e2d864112df0cfe9016dc1d"}, + {url = "https://files.pythonhosted.org/packages/cf/73/18d407acaaa03bdc46027b6696bbdeef42858b3f45708937ec8e3441e5b8/mkdocs_material-9.1.11-py3-none-any.whl", hash = "sha256:fbc86d50ec2cf34d40d5c4365780f290ceedde23f1a0704323b34e7f16b0c0dd"}, ] "mkdocs-material-extensions 1.1.1" = [ {url = "https://files.pythonhosted.org/packages/cd/3f/e5e3c9bfbb42e4cb661f71bcec787ae6bdf4a161b8c4bb68fd7d991c436c/mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, @@ -973,9 +1036,9 @@ content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d250 {url = "https://files.pythonhosted.org/packages/d9/a4/785af8296a882132b63de046c54d9a45c8d1d634e22c5d40463accd8eefd/pyrage-1.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c554d7669539c0a7a870464faf55f0121c117339b1b0f57d6ca5eb85079c107"}, {url = "https://files.pythonhosted.org/packages/fb/9c/54a5c8aad8442ba26845c325c8b4eaa90cc3b470d0e57465d82ca54ad843/pyrage-1.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:32b63a06f138a1c231285c61f61a1800b7b894851b06cb763b7b35b18ccd2b11"}, ] -"pyright 1.1.304" = [ - {url = "https://files.pythonhosted.org/packages/40/dc/1cb8b90aae25a597fd5c17f29b49e3eadc751b353706234ba026a22793a1/pyright-1.1.304.tar.gz", hash = "sha256:87adec38081904c939e3657ab23d5fc40b7ccc22709be0af1859fc785ae4ea61"}, - {url = "https://files.pythonhosted.org/packages/8d/68/84f64dff33655a42dec9097a33f45eed8fd389a348c5070f60c30a192891/pyright-1.1.304-py3-none-any.whl", hash = "sha256:70021bbae07fc28ed16e435f5efa65cd71e06a1888d9ca998798c283d4b3d010"}, +"pyright 1.1.306" = [ + {url = "https://files.pythonhosted.org/packages/91/0a/8e5bfa6079f8240da3bcb02780583f808e431e3e79627c2748b96919ee11/pyright-1.1.306-py3-none-any.whl", hash = "sha256:008eb2a29584ae274a154d749cf81476a3073fb562a462eac8d43a753378b9db"}, + {url = "https://files.pythonhosted.org/packages/9c/65/e12aee503b6093a22e1e01254a37bf5115bce8f62d67a6bd27fb81196c7f/pyright-1.1.306.tar.gz", hash = "sha256:16d5d198be64de497d5f9002000a271176c381e21b977ca5566cf779b643c9ed"}, ] "pytest 7.3.1" = [ {url = "https://files.pythonhosted.org/packages/1b/d1/72df649a705af1e3a09ffe14b0c7d3be1fd730da6b98beb4a2ed26b8a023/pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, @@ -1000,9 +1063,9 @@ content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d250 {url = "https://files.pythonhosted.org/packages/a5/18/30ad0408295f3157f7a4913f0eaa51a0a377ebad0ffa51ff239e833c6c72/pytest_pretty-1.2.0.tar.gz", hash = "sha256:105a355f128e392860ad2c478ae173ff96d2f03044692f9818ff3d49205d3a60"}, {url = "https://files.pythonhosted.org/packages/bf/fe/d44d391312c1b8abee2af58ee70fabb1c00b6577ac4e0bdf25b70c1caffb/pytest_pretty-1.2.0-py3-none-any.whl", hash = "sha256:6f79122bf53864ae2951b6c9e94d7a06a87ef753476acd4588aeac018f062036"}, ] -"pytest-reportlog 0.2.1" = [ - {url = "https://files.pythonhosted.org/packages/41/9a/6898175d1079b888994f25388afe7ffa0704a36d5549dd0e1143b01bfba2/pytest-reportlog-0.2.1.tar.gz", hash = "sha256:df59f7f1fcd9a0388e39b30e5aa264a609e64953e116f3ea6eb3aab22e3658e6"}, - {url = "https://files.pythonhosted.org/packages/69/d9/636a707bfb67df041dac191abdb5412db4a25167c057a9d5bdcc60f56669/pytest_reportlog-0.2.1-py3-none-any.whl", hash = "sha256:65ac38cb5af90470df3dde6c03a6dd88090913d16765ee54d135279b5579c113"}, +"pytest-reportlog 0.3.0" = [ + {url = "https://files.pythonhosted.org/packages/58/20/8d62051ff42965ad21e641e3f69d6418732ff4e0d62fd1f1cb2a350c7bd9/pytest_reportlog-0.3.0-py3-none-any.whl", hash = "sha256:30561aee147b512fcde13cabbd609d275ff8db43e1a348fda5b43ec280b41dcf"}, + {url = "https://files.pythonhosted.org/packages/79/33/2e1e5c5c64282be6f90843e6609942787d254131cc8f0cd1b7efe1ba6cb7/pytest-reportlog-0.3.0.tar.gz", hash = "sha256:7b1644039babcb9cbbce31f4d9407d7131b10b1fc49e1e7cbfa27c2918b95b1a"}, ] "python-dateutil 2.8.2" = [ {url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, @@ -1124,28 +1187,28 @@ content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d250 {url = "https://files.pythonhosted.org/packages/9d/ee/391076f5937f0a8cdf5e53b701ffc91753e87b07d66bae4a09aa671897bf/requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, {url = "https://files.pythonhosted.org/packages/d2/f4/274d1dbe96b41cf4e0efb70cbced278ffd61b5c7bb70338b62af94ccb25b/requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, ] -"rich 13.3.4" = [ - {url = "https://files.pythonhosted.org/packages/31/3b/2360352760b436f822258396e66ffb6d42585518a9cde2f93f142e64c5eb/rich-13.3.4.tar.gz", hash = "sha256:b5d573e13605423ec80bdd0cd5f8541f7844a0e71a13f74cf454ccb2f490708b"}, - {url = "https://files.pythonhosted.org/packages/9d/1a/28117ae737aec7c004ed5067034a8949adab43730420b50312821f466c3f/rich-13.3.4-py3-none-any.whl", hash = "sha256:22b74cae0278fd5086ff44144d3813be1cedc9115bdfabbfefd86400cb88b20a"}, +"rich 13.3.5" = [ + {url = "https://files.pythonhosted.org/packages/39/03/6de23bdd88f5ee7f8b03f94f6e88108f5d7ffe6d207e95cdb06d9aa4cd57/rich-13.3.5-py3-none-any.whl", hash = "sha256:69cdf53799e63f38b95b9bf9c875f8c90e78dd62b2f00c13a911c7a3b9fa4704"}, + {url = "https://files.pythonhosted.org/packages/3d/0b/8dd34d20929c4b5e474db2e64426175469c2b7fea5ba71c6d4b3397a9729/rich-13.3.5.tar.gz", hash = "sha256:2d11b9b8dd03868f09b4fffadc84a6a8cda574e40dc90821bd845720ebb8e89c"}, ] -"ruff 0.0.262" = [ - {url = "https://files.pythonhosted.org/packages/00/0c/9f2741560bdf0f922b9d7714d942d46913433ea0b88f4b5c9944efa68dc1/ruff-0.0.262-py3-none-win_amd64.whl", hash = "sha256:973ac29193f718349cf5746b7d86dfeaf7d40e9651ed97790a9b9327305888b9"}, - {url = "https://files.pythonhosted.org/packages/05/69/e5ba7d18ca8ad24f6a7fd2229f30ff52c7e1528929202194dca4f356aa50/ruff-0.0.262-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ca04348372efc59f6ee808d903d35e0d352cf2c78e487757cd48b65104b83e"}, - {url = "https://files.pythonhosted.org/packages/06/01/8227365f0dfef05a14efea32a49019223ac4f1f29c87ad8743481b78da87/ruff-0.0.262-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cca35e2aeddff72bb4379a1dabc134e0c0d25ebc754a2cb733a1f8d4dbbb5e0"}, - {url = "https://files.pythonhosted.org/packages/1d/dd/55dcc53ec4c650e0052916ee7788c9844edf98ed6da67d0807bcdab9aa66/ruff-0.0.262-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15bf5533ce169aebbafa00017987f673e879f60a625d932b464b8cdaf32a4fce"}, - {url = "https://files.pythonhosted.org/packages/26/d9/e7b66a2050bfda62f4e731d129a5223ffa4021ba8adf9d841b9dc8eca79e/ruff-0.0.262-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:c26c1abd420d041592d05d63aee8c6a18feb24aed4deb6e91129e9f2c7b4914a"}, - {url = "https://files.pythonhosted.org/packages/27/81/a01281da437ab6d59d28f6077de9ca16feb708da3a694ea75e98de85e822/ruff-0.0.262-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0baff3c9a22227358ea109c165efe62dbdd0f2b9fd5256567dda8682b444fe23"}, - {url = "https://files.pythonhosted.org/packages/2e/70/dca9a145a60def1f7891933b42c547ebf5d591e7a0679162fa013df08379/ruff-0.0.262-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:b379e9765afa679316e52288a942df085e590862f8945088936a7bce3116d8f3"}, - {url = "https://files.pythonhosted.org/packages/50/94/d550ce79dc02684121fcd4728ce8db89837a370ae980a92245dc1fd23883/ruff-0.0.262-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b7e0ca6821aafbd2b059df3119fcd5881250721ca8e825789fd2c471f7c59be"}, - {url = "https://files.pythonhosted.org/packages/64/32/425fd707ccb3584199a943885f50d0738083722f2a7c80ce7a529c70b554/ruff-0.0.262-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:083bac6e238d8b7d5ac3618666ea63b7ac661cf94c5da160070a58e190082831"}, - {url = "https://files.pythonhosted.org/packages/68/e3/1eb5c08c68380068d6827ae73cd08f23827adae27e4d34b8c328424a0795/ruff-0.0.262-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3909e249d984c4517194005a1c30eaa0c3a6d906c789d9fc0c9c7e007fb3e759"}, - {url = "https://files.pythonhosted.org/packages/6a/83/0e55b57dd9773f39c3bcad5a9ed87e62a248c721bc1a084fcde32d466bad/ruff-0.0.262-py3-none-win32.whl", hash = "sha256:15bbfa2d15c137717627e0d56b0e535ae297b734551e34e03fcc25d7642cf43a"}, - {url = "https://files.pythonhosted.org/packages/7c/f9/2200fe67292c7925edd2d219172265ba7a4aabca45dca6946c34f4e24325/ruff-0.0.262-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:24f989363e9bb5d0283490298102a5218682e49ebf300e445d69e24bee03ac83"}, - {url = "https://files.pythonhosted.org/packages/a2/fd/0f5de09c7fd1fd30778270d961596d6ab843b2cacac6c7a04327b8714621/ruff-0.0.262.tar.gz", hash = "sha256:faea54231c265f5349975ba6f3d855b71881a01f391b2000c47740390c6d5f68"}, - {url = "https://files.pythonhosted.org/packages/ae/b0/570c2ed3a82a2e5021b6aaa8a4810cac9d8322ae68bc6cf90c17fb339043/ruff-0.0.262-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e2813013a19b3e147e840bdb2e42db5825b53b47364e58e7b467c5fa47ffda2"}, - {url = "https://files.pythonhosted.org/packages/b2/b0/77527a1b55f02a36d5feed0b0888acbfe797932faa0831504e652bea63ce/ruff-0.0.262-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3c24e678e43ca4b67e29cc9a7a54eea05f31a5898cbf17bfec47b68f08d32a60"}, - {url = "https://files.pythonhosted.org/packages/b3/7e/f088f37524c0dee67d453d4eeb8d46dc439c70ae966d365b6eedb45b7113/ruff-0.0.262-py3-none-win_arm64.whl", hash = "sha256:f102904ebe395acd2a181d295b98120acd7a63f732b691672977fc688674f4af"}, - {url = "https://files.pythonhosted.org/packages/d0/ff/39d24f60765e294bb5d1d57622f7f39bf66da3ee748e02c1704e89150221/ruff-0.0.262-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d25a94996b2037e566c2a801c8b324c0a826194d5d4d90ad7c1ccb8cf06521fa"}, +"ruff 0.0.265" = [ + {url = "https://files.pythonhosted.org/packages/27/ee/674d3816cbffa55e4fad4446e2815c783ca0648180a905418edfd664bc50/ruff-0.0.265-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:30ddfe22de6ce4eb1260408f4480bbbce998f954dbf470228a21a9b2c45955e4"}, + {url = "https://files.pythonhosted.org/packages/40/8d/fbd44882b6ef61c8be8fa9ce6448bb99ff105816ac22e2f2116473809c6d/ruff-0.0.265-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1d5a8de2fbaf91ea5699451a06f4074e7a312accfa774ad9327cde3e4fda2081"}, + {url = "https://files.pythonhosted.org/packages/41/33/f9043ba8a20a667d888adc81768ebdaadaa0d2b760da61912682d62bbff8/ruff-0.0.265-py3-none-win_amd64.whl", hash = "sha256:f54facf286103006171a00ce20388d88ed1d6732db3b49c11feb9bf3d46f90e9"}, + {url = "https://files.pythonhosted.org/packages/77/cc/8806e6aaf8dc0cd4f41444f5423c31d691b7acf0386665406823f9528e45/ruff-0.0.265-py3-none-win_arm64.whl", hash = "sha256:c78470656e33d32ddc54e8482b1b0fc6de58f1195586731e5ff1405d74421499"}, + {url = "https://files.pythonhosted.org/packages/94/67/f875768b8ecfbc6264a9fc5e10735359f23b2ac98194bf0512555225b987/ruff-0.0.265-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5028950f7af9b119d43d91b215d5044976e43b96a0d1458d193ef0dd3c587bf8"}, + {url = "https://files.pythonhosted.org/packages/9d/07/7178c1b251c2bfed0676e7f8cd76d6107a5e89b2d1b7b464c084b311dfe6/ruff-0.0.265-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b279fa55ea175ef953208a6d8bfbcdcffac1c39b38cdb8c2bfafe9222add70bb"}, + {url = "https://files.pythonhosted.org/packages/9e/35/fb24000483a56339ffe3c00c8b0d12c4ca99201f7f2f30f4799202a20a83/ruff-0.0.265-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8b44a245b60512403a6a03a5b5212da274d33862225c5eed3bcf12037eb19bb"}, + {url = "https://files.pythonhosted.org/packages/a1/00/135639453182b6c64c507f448764d78f8df90bcd19e4b0e16427407709e0/ruff-0.0.265-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9ac13b11d9ad3001de9d637974ec5402a67cefdf9fffc3929ab44c2fcbb850a1"}, + {url = "https://files.pythonhosted.org/packages/b2/bc/b14c0de95721ef6bd3a1dcfe7b4ce9f368c72a2d5aed7c98e767771c860c/ruff-0.0.265-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0f9967f84da42d28e3d9d9354cc1575f96ed69e6e40a7d4b780a7a0418d9409"}, + {url = "https://files.pythonhosted.org/packages/b3/1d/0e544e4b73bafbe88f6c39bce5306fdbf5af07e9d004fe023eeedf9ee548/ruff-0.0.265-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d586e69ab5cbf521a1910b733412a5735936f6a610d805b89d35b6647e2a66aa"}, + {url = "https://files.pythonhosted.org/packages/b4/c4/7b8b05279a11ba30cf99eb628dbfd2fbf7219c192eb78f9585280bc3ae69/ruff-0.0.265-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:62a9578b48cfd292c64ea3d28681dc16b1aa7445b7a7709a2884510fc0822118"}, + {url = "https://files.pythonhosted.org/packages/c7/87/9b9d14b1778edb4ce03e0e80ab9378e12e402dce2b5cd27403b6670b6ed6/ruff-0.0.265-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4057eb539a1d88eb84e9f6a36e0a999e0f261ed850ae5d5817e68968e7b89ed9"}, + {url = "https://files.pythonhosted.org/packages/d1/c3/2aeec2adaff3cfa95d713283089568ee905f759e18d034646e3aa85a6282/ruff-0.0.265.tar.gz", hash = "sha256:53c17f0dab19ddc22b254b087d1381b601b155acfa8feed514f0d6a413d0ab3a"}, + {url = "https://files.pythonhosted.org/packages/e3/d1/433db20e8ef8f67ff7cfa32fdf6b9adb0592a1269fd93e230fd0dfbf64f2/ruff-0.0.265-py3-none-win32.whl", hash = "sha256:9e9db5ccb810742d621f93272e3cc23b5f277d8d00c4a79668835d26ccbe48dd"}, + {url = "https://files.pythonhosted.org/packages/e7/c3/157c421412809780430110b766f8bdad65b55f0671c00c778d827dbb6f35/ruff-0.0.265-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a9b38bdb40a998cbc677db55b6225a6c4fadcf8819eb30695e1b8470942426b"}, + {url = "https://files.pythonhosted.org/packages/f4/1c/5161404f7231c37e951f7dce3a4f623733a6563c17724189d003602ce55f/ruff-0.0.265-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa17b13cd3f29fc57d06bf34c31f21d043735cc9a681203d634549b0e41047d1"}, + {url = "https://files.pythonhosted.org/packages/fd/ac/1c40dc44ed92744c3a9ca6f07ed2d83f0431d71a02766a7a478e2112fb59/ruff-0.0.265-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:a11bd0889e88d3342e7bc514554bb4461bf6cc30ec115821c2425cfaac0b1b6a"}, ] "setuptools 67.6.1" = [ {url = "https://files.pythonhosted.org/packages/0b/fc/8781442def77b0aa22f63f266d4dadd486ebc0c5371d6290caf4320da4b7/setuptools-67.6.1-py3-none-any.whl", hash = "sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078"}, @@ -1155,13 +1218,17 @@ content_hash = "sha256:65bedbc6ab345a1fc132bbdf4698aafb129f6cf522a4c219eec17d250 {url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, ] +"sniffio 1.3.0" = [ + {url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] "tomli 2.0.1" = [ {url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -"typer 0.7.0" = [ - {url = "https://files.pythonhosted.org/packages/0d/44/56c3f48d2bb83d76f5c970aef8e2c3ebd6a832f09e3621c5395371fe6999/typer-0.7.0-py3-none-any.whl", hash = "sha256:b5e704f4e48ec263de1c0b3a2387cd405a13767d2f907f44c1a08cbad96f606d"}, - {url = "https://files.pythonhosted.org/packages/e1/45/bcbc581f87c8d8f2a56b513eb994d07ea4546322818d95dc6a3caf2c928b/typer-0.7.0.tar.gz", hash = "sha256:ff797846578a9f2a201b53442aedeb543319466870fbe1c701eab66dd7681165"}, +"typer 0.9.0" = [ + {url = "https://files.pythonhosted.org/packages/5b/49/39f10d0f75886439ab3dac889f14f8ad511982a754e382c9b6ca895b29e9/typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"}, + {url = "https://files.pythonhosted.org/packages/bf/0e/c68adf10adda05f28a6ed7b9f4cd7b8e07f641b44af88ba72d9c89e4de7a/typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"}, ] "types-pyyaml 6.0.12.9" = [ {url = "https://files.pythonhosted.org/packages/05/6f/f19081de5ba81864f89e354560a60637822f7d6d54d9a2a907785e9b5cc4/types-PyYAML-6.0.12.9.tar.gz", hash = "sha256:c51b1bd6d99ddf0aa2884a7a328810ebf70a4262c292195d3f4f9a0005f9eeb6"}, diff --git a/pyproject.toml b/pyproject.toml index afdb7a3..7c6a713 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ dependencies = [ "pyyaml>=6.0", "pyrage>=1.0.3", "pendulum>=2.1.2", + "httpx>=0.24.0", ] name = "halig" dynamic = ["version"] @@ -93,5 +94,6 @@ module = [ "pyrage", "pyrage.ssh", "pyrage.x25519", + "attr" ] ignore_missing_imports = true diff --git a/tests/test_encryption.py b/tests/test_encryption.py index f6e9179..16b7ca1 100644 --- a/tests/test_encryption.py +++ b/tests/test_encryption.py @@ -5,29 +5,40 @@ from halig.settings import Settings def test_instance_encryptor_from_age_keys(halig_path, notebooks_path): - identity = x25519.Identity.generate() - identity_path = halig_path / "identity.key" - identity_path.touch() - recipient_path = halig_path / "recipient.key" - recipient_path.touch() - with identity_path.open("w") as f: - f.write(str(identity)) + identity_paths = [] + recipient_paths = [] + identities = [] + for i in range(5): + identity = x25519.Identity.generate() + identities.append(identity) + identity_path = halig_path / f"identity_{i}.key" + identity_path.touch() + with identity_path.open("w") as f: + f.write(str(identity)) + identity_paths.append(identity_path) - with recipient_path.open("w") as f: - f.write(str(identity.to_public())) + recipient_path = halig_path / f"recipient_{i}.key" + recipient_path.touch() + + with recipient_path.open("w") as f: + f.write(str(identity.to_public())) + + recipient_paths.append(recipient_path) settings = Settings( notebooks_root_path=notebooks_path, - identity_path=identity_path, - recipient_path=recipient_path, + identity_paths=identity_paths, + recipient_paths=recipient_paths, ) - assert Encryptor(settings) + encryptor = Encryptor(settings) + for identity in identities: + assert str(identity) in [str(identity) for identity in encryptor.identities] + assert str(identity.to_public()) in [str(recipient) for recipient in encryptor.recipients] def test_encrypt(encryptor: Encryptor, ssh_identity): unencrypted_data = "foo" encrypted_data = encryptor.encrypt(unencrypted_data) - assert isinstance(encrypted_data, bytes) assert unencrypted_data == decrypt(encrypted_data, [ssh_identity]).decode()