Skip to content

Commit 00a9f7e

Browse files
committed
Merge remote-tracking branch 'origin/9.4' into 9.4
2 parents c508d03 + daaa3a2 commit 00a9f7e

12 files changed

+861
-373
lines changed

.readthedocs.yaml

+39-8
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,44 @@ build:
2222
# # See https://github.com/readthedocs/readthedocs.org/pull/11152/
2323
# - POETRY=/home/docs/.local/bin/poetry VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH make install-packages
2424
## - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --extras "crypto" --with docs
25-
commands:
25+
26+
# commands:
27+
# - POETRY=/home/docs/.local/bin/poetry make install-poetry
28+
# - POETRY=/home/docs/.local/bin/poetry make install
29+
# - POETRY=/home/docs/.local/bin/poetry make docs
30+
# - ls -l ./docs/_build/html/*
31+
# - mkdir --parents $READTHEDOCS_OUTPUT/html/
32+
# - cp --recursive ./docs/_build/html/* $READTHEDOCS_OUTPUT/html/
33+
34+
jobs:
35+
install:
2636
- POETRY=/home/docs/.local/bin/poetry make install-poetry
2737
- POETRY=/home/docs/.local/bin/poetry make install
28-
- POETRY=/home/docs/.local/bin/poetry make docs
29-
- ls -l ./docs/_build/html/*
30-
- mkdir --parents $READTHEDOCS_OUTPUT/html/
31-
- cp --recursive ./docs/_build/html/* $READTHEDOCS_OUTPUT/html/
38+
build:
39+
html:
40+
- echo "Override default build command for html format"
41+
- POETRY=/home/docs/.local/bin/poetry make docs
42+
- ls -l ./docs/_build/html/*
43+
- mkdir --parents $READTHEDOCS_OUTPUT/html/
44+
- cp --recursive ./docs/_build/html/* $READTHEDOCS_OUTPUT/html/
45+
epub:
46+
- echo "Override default build command for epub format"
47+
- POETRY=/home/docs/.local/bin/poetry make docs-epub
48+
- ls -l ./docs/_build/epub/*
49+
- mkdir -p $READTHEDOCS_OUTPUT/epub/
50+
- cp ./docs/_build/epub/eventsourcing.epub $READTHEDOCS_OUTPUT/epub/
51+
pdf:
52+
- echo "Override default build command for pdf format"
53+
- POETRY=/home/docs/.local/bin/poetry make docs-pdf
54+
- ls -l ./docs/_build/latex/*
55+
- mkdir -p $READTHEDOCS_OUTPUT/pdf/
56+
- cp ./docs/_build/latex/eventsourcing.pdf $READTHEDOCS_OUTPUT/pdf/
57+
# htmlzip:
58+
# - echo "Override default build command for htmlzip format"
59+
# - mkdir -p $READTHEDOCS_OUTPUT/htmlzip/
60+
61+
62+
3263
# jobs:
3364
# pre_create_environment:
3465
# # Select Python version (keep in sync with other versions):
@@ -56,9 +87,9 @@ sphinx:
5687
# fail_on_warning: true
5788

5889
# Optionally build your docs in additional formats such as PDF and ePub
59-
# formats:
60-
# - pdf
61-
# - epub
90+
formats:
91+
- pdf
92+
- epub
6293

6394
# Optional but recommended, declare the Python requirements required
6495
# to build your documentation

Makefile

+12-3
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,16 @@ install-poetry:
1515

1616
.PHONY: install
1717
install:
18-
$(POETRY) install --sync --extras "crypto" --with "docs" -vv $(opts)
18+
$(POETRY) install --sync --extras "crypto cryptography" --with "docs" -vv $(opts)
1919

2020
.PHONY: install-packages
2121
install-packages:
22-
$(POETRY) install --sync --no-root --extras "crypto" --with "docs" -vv $(opts)
22+
$(POETRY) install --sync --no-root --extras "crypto cryptography" --with "docs" -vv $(opts)
2323

2424
.PHONY: update-lockfile
2525
update-lockfile:
26-
$(POETRY) lock --no-update
26+
$(POETRY) lock
27+
# $(POETRY) lock --no-update
2728

2829
.PHONY: update-packages
2930
update-packages: update-lockfile install-packages
@@ -173,6 +174,14 @@ docker-logs:
173174
docs:
174175
cd docs && make html
175176

177+
.PHONY: docs-epub
178+
docs-epub:
179+
cd docs && make epub
180+
181+
.PHONY: docs-pdf
182+
docs-pdf:
183+
cd docs && make latexpdf
184+
176185
#
177186
# .PHONY: brew-services-start
178187
# brew-services-start:

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[![Build Status](https://github.com/pyeventsourcing/eventsourcing/actions/workflows/runtests.yaml/badge.svg?branch=main)](https://github.com/pyeventsourcing/eventsourcing/tree/main)
1+
[![Build Status](https://github.com/pyeventsourcing/eventsourcing/actions/workflows/runtests.yaml/badge.svg)](https://github.com/pyeventsourcing/eventsourcing)
22
[![Coverage Status](https://coveralls.io/repos/github/pyeventsourcing/eventsourcing/badge.svg?branch=main)](https://coveralls.io/github/pyeventsourcing/eventsourcing?branch=main)
33
[![Documentation Status](https://readthedocs.org/projects/eventsourcing/badge/?version=stable)](https://eventsourcing.readthedocs.io/en/stable/)
44
[![Latest Release](https://badge.fury.io/py/eventsourcing.svg)](https://pypi.org/project/eventsourcing/)

dev/MACOS_SETUP_NOTES.md

+4
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,7 @@ postgres=> ALTER DATABASE eventsourcing OWNER TO eventsourcing;
3030
- use psql with the eventsourcing user to create schema in eventsourcing database
3131
$ psql -U eventsourcing
3232
eventsourcing=> CREATE SCHEMA myschema AUTHORIZATION eventsourcing;
33+
34+
35+
To build PDF docs (make docs-pdf), download and install MacTeX from https://www.tug.org/mactex/mactex-download.html
36+
and then make sure latexmk is on your PATH (export PATH="$PATH:/Library/TeX/texbin").

eventsourcing/cipher.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616

1717
class AESCipher(Cipher):
1818
"""
19-
Cipher strategy that uses AES cipher in GCM mode.
19+
Cipher strategy that uses AES cipher (in GCM mode)
20+
from the Python pycryptodome package.
2021
"""
2122

2223
CIPHER_KEY = "CIPHER_KEY"
@@ -71,6 +72,7 @@ def encrypt(self, plaintext: bytes) -> bytes:
7172

7273
# Return ciphertext.
7374
return nonce + tag + encrypted
75+
# return nonce + tag + encrypted
7476

7577
def construct_cipher(self, nonce: bytes) -> GcmMode:
7678
cipher = AES.new(

eventsourcing/cryptography.py

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from __future__ import annotations
2+
3+
import os
4+
from base64 import b64decode, b64encode
5+
from typing import TYPE_CHECKING
6+
7+
from cryptography.exceptions import InvalidTag
8+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
9+
10+
from eventsourcing.persistence import Cipher
11+
12+
if TYPE_CHECKING:
13+
from eventsourcing.utils import Environment
14+
15+
16+
class AESCipher(Cipher):
17+
"""
18+
Cipher strategy that uses AES cipher (in GCM mode)
19+
from the Python cryptography package.
20+
"""
21+
22+
CIPHER_KEY = "CIPHER_KEY"
23+
KEY_SIZES = (16, 24, 32)
24+
25+
@staticmethod
26+
def create_key(num_bytes: int) -> str:
27+
"""
28+
Creates AES cipher key, with length num_bytes.
29+
30+
:param num_bytes: An int value, either 16, 24, or 32.
31+
32+
"""
33+
AESCipher.check_key_size(num_bytes)
34+
key = AESGCM.generate_key(num_bytes * 8)
35+
return b64encode(key).decode("utf8")
36+
37+
@staticmethod
38+
def check_key_size(num_bytes: int) -> None:
39+
if num_bytes not in AESCipher.KEY_SIZES:
40+
msg = f"Invalid key size: {num_bytes} not in {AESCipher.KEY_SIZES}"
41+
raise ValueError(msg)
42+
43+
@staticmethod
44+
def random_bytes(num_bytes: int) -> bytes:
45+
return os.urandom(num_bytes)
46+
47+
def __init__(self, environment: Environment):
48+
"""
49+
Initialises AES cipher with ``cipher_key``.
50+
51+
:param str cipher_key: 16, 24, or 32 bytes encoded as base64
52+
"""
53+
cipher_key = environment.get(self.CIPHER_KEY)
54+
if not cipher_key:
55+
msg = f"'{self.CIPHER_KEY}' not in env"
56+
raise OSError(msg)
57+
key = b64decode(cipher_key.encode("utf8"))
58+
AESCipher.check_key_size(len(key))
59+
self.key = key
60+
61+
def encrypt(self, plaintext: bytes) -> bytes:
62+
"""Return ciphertext for given plaintext."""
63+
64+
# Construct AES-GCM cipher, with 96-bit nonce.
65+
aesgcm = AESGCM(self.key)
66+
nonce = AESCipher.random_bytes(12)
67+
res = aesgcm.encrypt(nonce, plaintext, None)
68+
# Put tag at the front for compatibility with eventsourcing.crypto.AESCipher.
69+
tag = res[-16:]
70+
encrypted = res[:-16]
71+
return nonce + tag + encrypted
72+
73+
def decrypt(self, ciphertext: bytes) -> bytes:
74+
"""Return plaintext for given ciphertext."""
75+
76+
# Split out the nonce, tag, and encrypted data.
77+
nonce = ciphertext[:12]
78+
if len(nonce) != 12:
79+
msg = "Damaged cipher text: invalid nonce length"
80+
raise ValueError(msg)
81+
82+
# Expect tag at the front.
83+
tag = ciphertext[12:28]
84+
if len(tag) != 16:
85+
msg = "Damaged cipher text: invalid tag length"
86+
raise ValueError(msg)
87+
encrypted = ciphertext[28:]
88+
89+
aesgcm = AESGCM(self.key)
90+
try:
91+
plaintext = aesgcm.decrypt(nonce, encrypted + tag, None)
92+
except InvalidTag as e:
93+
msg = "Invalid cipher tag"
94+
raise ValueError(msg) from e
95+
# Decrypt and verify.
96+
return plaintext

0 commit comments

Comments
 (0)