Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ By participating in this project, you agree to abide by our Code of Conduct.
- Run mypy to check static typing:
```bash
.venv/bin/python -m mypy \
crypto_config.py crypto_core.py pqc_agent_tools.py pqc_app.py \
api_app.py crypto_config.py crypto_core.py pqc_agent_tools.py ui_helpers.py \
tests/test_agent_tools.py tests/test_crypto_core.py
```

Expand Down
76 changes: 67 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ jobs:
run: python -m pip install -r requirements-dev.txt

- name: Check formatting
run: python -m black --check api_app.py crypto_config.py crypto_core.py pqc_agent_tools.py pqc_app.py ui_helpers.py setup.py tests/test_agent_tools.py tests/test_api_app.py tests/test_crypto_core.py tests/test_ui_helpers.py
run: python -m black --check api_app.py crypto_config.py crypto_core.py pqc_agent_tools.py ui_helpers.py setup.py tests/test_agent_tools.py tests/test_api_app.py tests/test_crypto_core.py tests/test_ui_helpers.py

- name: Lint
run: python -m flake8 api_app.py crypto_config.py crypto_core.py pqc_agent_tools.py pqc_app.py ui_helpers.py setup.py tests/test_agent_tools.py tests/test_api_app.py tests/test_crypto_core.py tests/test_ui_helpers.py
run: python -m flake8 api_app.py crypto_config.py crypto_core.py pqc_agent_tools.py ui_helpers.py setup.py tests/test_agent_tools.py tests/test_api_app.py tests/test_crypto_core.py tests/test_ui_helpers.py

- name: Type check
run: python -m mypy api_app.py crypto_config.py crypto_core.py pqc_agent_tools.py pqc_app.py ui_helpers.py tests/test_agent_tools.py tests/test_api_app.py tests/test_crypto_core.py tests/test_ui_helpers.py
run: python -m mypy api_app.py crypto_config.py crypto_core.py pqc_agent_tools.py ui_helpers.py tests/test_agent_tools.py tests/test_api_app.py tests/test_crypto_core.py tests/test_ui_helpers.py

- name: Unit tests without native liboqs
run: >
Expand Down Expand Up @@ -93,15 +93,21 @@ jobs:
/tmp/qe-wheel-test/bin/quantum-encryptor-agent health --json
/tmp/qe-wheel-test/bin/python -I - <<'PY'
from pathlib import Path
from importlib.metadata import requires
from importlib.util import find_spec
import sys

import api_app
import pqc_agent_tools
import ui_helpers

prefix = Path(sys.prefix).resolve()
for module in (api_app, pqc_agent_tools):
for module in (api_app, pqc_agent_tools, ui_helpers):
module_path = Path(module.__file__).resolve()
assert module_path.is_relative_to(prefix), (module.__name__, module_path, prefix)
assert find_spec("pqc_app") is None
package_requirements = requires("quantum-encryptor") or ()
assert all(not requirement.lower().startswith("streamlit") for requirement in package_requirements)
PY

- name: Smoke installed web UI
Expand Down Expand Up @@ -155,7 +161,7 @@ jobs:
run: npm audit --package-lock-only --audit-level=high

- name: Run Python security lint
run: python -m bandit -q -r api_app.py crypto_core.py pqc_agent_tools.py pqc_app.py ui_helpers.py
run: python -m bandit -q -r api_app.py crypto_core.py pqc_agent_tools.py ui_helpers.py

web:
name: Custom web UI
Expand Down Expand Up @@ -262,9 +268,9 @@ jobs:
run: |
git init .ci/liboqs
git -C .ci/liboqs remote add origin https://github.com/open-quantum-safe/liboqs.git
# liboqs 0.15.0
git -C .ci/liboqs fetch --depth=1 origin 97f6b86b1b6d109cfd43cf276ae39c2e776aed80
git -C .ci/liboqs checkout --detach 97f6b86b1b6d109cfd43cf276ae39c2e776aed80
# liboqs 0.16.0
git -C .ci/liboqs fetch --depth=1 origin 5a1a854b0dc9f2141bdc771c555ee60c37950183
git -C .ci/liboqs checkout --detach 5a1a854b0dc9f2141bdc771c555ee60c37950183
cmake -S .ci/liboqs -B .ci/liboqs/build \
-GNinja \
-DBUILD_SHARED_LIBS=ON \
Expand All @@ -273,7 +279,22 @@ jobs:
cmake --install .ci/liboqs/build

- name: Install Python dependencies
run: python -m pip install -r requirements-dev.txt
run: python -m pip install --require-hashes -r requirements-dev-lock.txt

- name: Set up Node
uses: actions/setup-node@v7
with:
node-version: "22"
cache: npm

- name: Install frontend dependencies
run: npm ci

- name: Build frontend
run: npm run build

- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium

- name: Verify native backend is active
run: |
Expand Down Expand Up @@ -314,3 +335,40 @@ jobs:
--private-key agent-private.pem \
--output agent-output.txt
cmp agent-input.txt agent-output.txt

- name: Native browser encryption round trip
run: |
SKIP_WEB_BUILD=1 PYTHON=python ./start.sh &
server_pid=$!
trap 'kill "$server_pid" 2>/dev/null || true; wait "$server_pid" 2>/dev/null || true' EXIT
python - <<'PY'
import json
import time
from urllib.request import urlopen

required = ("generate", "encrypt", "decrypt")
deadline = time.monotonic() + 30

while (remaining := deadline - time.monotonic()) > 0:
try:
with urlopen("http://127.0.0.1:4000/api/health", timeout=min(1, remaining)) as response:
health = json.load(response)
except Exception:
health = None

ready = bool(
health
and health.get("ok")
and health.get("backendReady")
and all(health.get("capabilities", {}).get(name, {}).get("available") for name in required)
)
if ready:
raise SystemExit(0)

remaining = deadline - time.monotonic()
if remaining > 0:
time.sleep(min(0.25, remaining))

raise SystemExit("Native local app did not become ready within 30 seconds.")
PY
npm run ui-native
18 changes: 12 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
run: python -m pip_audit -r requirements-dev-lock.txt

- name: Run Python security lint
run: python -m bandit -q -r crypto_core.py pqc_agent_tools.py pqc_app.py ui_helpers.py api_app.py
run: python -m bandit -q -r crypto_core.py pqc_agent_tools.py ui_helpers.py api_app.py

- name: Install frontend dependencies
run: npm ci
Expand Down Expand Up @@ -82,15 +82,21 @@ jobs:
/tmp/qe-release-test/bin/quantum-encryptor-agent health --json
/tmp/qe-release-test/bin/python -I - <<'PY'
from pathlib import Path
from importlib.metadata import requires
from importlib.util import find_spec
import sys

import api_app
import pqc_agent_tools
import ui_helpers

prefix = Path(sys.prefix).resolve()
for module in (api_app, pqc_agent_tools):
for module in (api_app, pqc_agent_tools, ui_helpers):
module_path = Path(module.__file__).resolve()
assert module_path.is_relative_to(prefix), (module.__name__, module_path, prefix)
assert find_spec("pqc_app") is None
package_requirements = requires("quantum-encryptor") or ()
assert all(not requirement.lower().startswith("streamlit") for requirement in package_requirements)
PY

- name: Smoke installed web UI
Expand Down Expand Up @@ -177,9 +183,9 @@ jobs:
run: |
git init .ci/liboqs
git -C .ci/liboqs remote add origin https://github.com/open-quantum-safe/liboqs.git
# liboqs 0.15.0
git -C .ci/liboqs fetch --depth=1 origin 97f6b86b1b6d109cfd43cf276ae39c2e776aed80
git -C .ci/liboqs checkout --detach 97f6b86b1b6d109cfd43cf276ae39c2e776aed80
# liboqs 0.16.0
git -C .ci/liboqs fetch --depth=1 origin 5a1a854b0dc9f2141bdc771c555ee60c37950183
git -C .ci/liboqs checkout --detach 5a1a854b0dc9f2141bdc771c555ee60c37950183
cmake -S .ci/liboqs -B .ci/liboqs/build \
-GNinja \
-DBUILD_SHARED_LIBS=ON \
Expand All @@ -188,7 +194,7 @@ jobs:
cmake --install .ci/liboqs/build

- name: Install Python dependencies
run: python -m pip install -r requirements-dev.txt
run: python -m pip install --require-hashes -r requirements-dev-lock.txt

- name: Verify migration backends are active
run: |
Expand Down
5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,6 @@ ENV/
env.bak/
venv.bak/

# Streamlit local state and secrets. Keep committed UI theme config.
.streamlit/*
!.streamlit/
!.streamlit/config.toml

# Spyder project settings
.spyderproject
.spyproject
Expand Down
7 changes: 0 additions & 7 deletions .streamlit/config.toml

This file was deleted.

11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ This project follows a practical semantic-versioning style.

- ML-KEM-768 + X25519 composite key generation and format-v4 encrypted containers.
- SHA3-256 hybrid key combiner binding both key shares, X25519 context, suite identifier, and application domain.
- Polished monochrome local web workflows with progressive technical details,
responsive navigation, component tests, accessibility checks, and a native
browser encryption round trip.

### Changed

- Retired the Streamlit application, temporary startup fallback, packaging
surface, and superseded reference screenshots. `./start.sh` now serves the
React/Vite interface through the loopback-only Python API as the sole GUI.
- Preserved decrypt-only compatibility for authenticated earlier containers and
private-key formats, including the bounded ML-KEM/Kyber migration path.

### Security

Expand Down
55 changes: 32 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,27 @@ A post-quantum cryptography tool for file encryption. New files combine ML-KEM-7
<a href="docs/SCREENSHOTS.md">
<img
src="docs/screenshots/custom-web-encrypt-workflow.png"
alt="Quantum Encryptor custom web app showing the encrypt file workflow and backend readiness warning"
alt="Quantum Encryptor custom web app showing the Encrypt workflow and its technical details"
width="900"
>
</a>
</p>

<p align="center">
<strong>Dark local web interface for ML-KEM-768 + X25519 key generation, file encryption, decryption, and PEM key inspection.</strong>
<strong>Monochrome local web interface for ML-KEM-768 + X25519 key generation, file encryption, decryption, and PEM key inspection.</strong>
</p>

## Features

- **Post-Quantum/Traditional Security**: Combines ML-KEM-768 with X25519 so confidentiality does not depend on one key-establishment algorithm
- **Authenticated File Encryption**: Derives AES-256-GCM keys from both ML-KEM and X25519 shared secrets
- **Password-Protected Keys**: Private keys are always encrypted with scrypt-derived AES-256-GCM keys
- **User-Friendly Interface**: Custom local web UI with a Python ASGI API
- **User-Friendly Interface**: Custom local web UI with progressive technical details and a Python ASGI API
- **PEM Key Format**: Keys stored in PEM-like format with quantum algorithm extensions

## Screenshots

The backend readiness warning shown here is expected when native `liboqs` is not installed in the local environment. Click any image to open the full screenshot page.
The current browser smoke captures show the responsive Encrypt and Inspect key workflows. Click either image to open the full screenshot page.

<p>
<a href="docs/SCREENSHOTS.md#custom-web-encrypt-workflow">
Expand Down Expand Up @@ -111,21 +111,22 @@ See [docs/SCREENSHOTS.md](docs/SCREENSHOTS.md) for the dedicated screenshot page
PYTHON=.venv/bin/python ./test.sh
```

To run the legacy Streamlit UI during transition:
```bash
LEGACY_STREAMLIT=1 ./start.sh
```

Frontend development can run Vite separately on `127.0.0.1:4001`:
```bash
npm run dev
```

2. Open the web interface in your browser. You can:
- Generate a new post-quantum key pair
- Encrypt files using a recipient's public key
- Decrypt files using your private key
- Access key utilities
2. Open the web interface in your browser. Choose the intent that matches your task:
- **Encrypt**: protect a file for the holder of a recipient public key.
- **Decrypt**: recover a file with the matching encrypted private key and password.
- **Generate keys**: create a new public key and password-protected private key.
- **Inspect key**: check supported key metadata without exposing key material.

Each workflow starts with plain-language guidance. Expand **Technical details** only when you need suite, format, or key-policy information.

### Local-only interface privacy

The custom interface processes selected files through the local Python service at `127.0.0.1`. It does not use browser persistence, telemetry, remote fonts, or remote application assets. The UI does not display plaintext previews, passwords, private-key content, or the local API token.

## Verification

Expand All @@ -138,8 +139,9 @@ Run the Python test suite:
Run the custom frontend checks:

```bash
npm run build
npm run test:unit
npm run check
npm run build
```

With the app already running on `127.0.0.1:4000`, run the browser smoke test:
Expand All @@ -150,32 +152,40 @@ npm run ui-smoke

The UI smoke test writes ignored screenshots under `tmp/ui-smoke/`.

When a native `liboqs` installation is available to the running app, also run the real browser encryption round trip:

```bash
npm run ui-native
```

Do not treat the browser smoke test as proof that the native cryptographic backend is installed; it verifies the built interface against the local API contract. `npm run ui-native` verifies key generation, encryption, and decryption through the available native backend.

### Key Generation

1. Select "Generate Keys" from the sidebar
1. Select "Generate keys" from the workflow navigation
2. Enter and confirm a strong private-key password
3. Generate the keys and download both public and private key files
4. Share your public key with others who want to send you encrypted files

### File Encryption

1. Select "Encrypt File" from the sidebar
1. Select "Encrypt" from the workflow navigation
2. Upload the file you want to encrypt
3. Upload the recipient's public key (.pem file)
4. Specify the output filename
5. Download the encrypted file

### File Decryption

1. Select "Decrypt File" from the sidebar
1. Select "Decrypt" from the workflow navigation
2. Upload the encrypted file (.pqc file)
3. Upload your private key (.pem file)
4. Enter your private-key password
5. Download the decrypted file

## Agent Usage
## Automation Usage

Local automation agents can use the deterministic JSON CLI instead of driving the Streamlit UI. Run commands from the repository workspace and pass only workspace-relative paths. Absolute paths, `..` traversal, symlink escapes, and accidental output overwrites are rejected.
Automation tools can use the deterministic JSON CLI instead of driving the browser interface. Run commands from the repository workspace and pass only workspace-relative paths. Absolute paths, `..` traversal, symlink escapes, and accidental output overwrites are rejected.

```bash
mkdir -p keys data
Expand Down Expand Up @@ -229,7 +239,7 @@ The CLI prints JSON only and never includes plaintext, private keys, passwords,
- State-changing local web API requests require a per-process API token and reject non-local browser origins when an `Origin` header is present
- The local agent CLI accepts only workspace-relative paths, returns machine-readable JSON without secret material, and writes private keys plus decrypted outputs with owner-only permissions on POSIX systems; non-overwrite output creation uses exclusive file creation
- Native `liboqs` is loaded lazily and missing backend support disables key generation/encryption instead of crashing the app
- CI runs Python formatting, linting, type checks, unit tests, custom web UI build/type checks, API client tests, browser UI smoke, isolated installed-wheel checks, Python/npm dependency audits, locked runtime install, and a native `liboqs` integration test job pinned to the matching 0.15.0 release commit; repository CodeQL default setup provides static analysis
- CI runs Python formatting, linting, type checks, unit tests, custom web UI build/type checks, API client tests, browser UI smoke, isolated installed-wheel checks, Python/npm dependency audits, locked runtime install, and a native `liboqs` integration test job pinned to the matching 0.16.0 release commit; repository CodeQL default setup provides static analysis
- See [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md) for repository trust boundaries, assets, abuse cases, and invariants
- **Disclaimer**: This software has not undergone an independent security audit and should be reviewed before production use

Expand All @@ -238,8 +248,7 @@ The CLI prints JSON only and never includes plaintext, private keys, passwords,
- `crypto_config.py` - Configuration parameters for cryptographic operations
- `crypto_core.py` - Core cryptographic functions (key generation, encryption, decryption)
- `api_app.py` - Local ASGI API and static web UI server
- `pqc_agent_tools.py` - Local JSON CLI for agentic workflows
- `pqc_app.py` - Legacy Streamlit web application interface
- `pqc_agent_tools.py` - Local JSON CLI for automation workflows
- `web/` - React frontend source for the custom UI
- `package.json` / `vite.config.ts` - Frontend build configuration
- `ui_helpers.py` - UI-safe filename helpers
Expand Down
Loading