diff --git a/.bumpversion-capi.toml b/.bumpversion-capi.toml index 1269e314..0f81f0c3 100644 --- a/.bumpversion-capi.toml +++ b/.bumpversion-capi.toml @@ -13,3 +13,6 @@ filename = "crates/capi/examples/version.hpp" [[tool.bumpversion.files]] filename = "crates/capi/src/lib.rs" + +[[tool.bumpversion.files]] +glob = "docs/man/man3/*.3.adoc" diff --git a/.bumpversion-cli.toml b/.bumpversion-cli.toml new file mode 100644 index 00000000..a66e098c --- /dev/null +++ b/.bumpversion-cli.toml @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2024 Shun Sakai +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[tool.bumpversion] +current_version = "0.3.3" + +[[tool.bumpversion.files]] +glob = "docs/man/man1/*.1.adoc" + +[[tool.bumpversion.files]] +filename = "docs/man/man5/abcrypt.5.adoc" diff --git a/.bumpversion-lib.toml b/.bumpversion-lib.toml index 9d756614..9fa59e5a 100644 --- a/.bumpversion-lib.toml +++ b/.bumpversion-lib.toml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [tool.bumpversion] -current_version = "0.3.7" +current_version = "0.4.0" [[tool.bumpversion.files]] filename = "crates/abcrypt/README.md" diff --git a/.github/workflows/CD.yaml b/.github/workflows/CD.yaml index ea223dbb..13cdc2a6 100644 --- a/.github/workflows/CD.yaml +++ b/.github/workflows/CD.yaml @@ -20,7 +20,7 @@ env: jobs: get-version: name: Get version - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 outputs: version: ${{ steps.get_version.outputs.version }} version_without_v: ${{ steps.get_version_without_v.outputs.version-without-v }} @@ -53,10 +53,10 @@ jobs: - x86_64-pc-windows-msvc include: - target: aarch64-unknown-linux-musl - os: ubuntu-22.04 + os: ubuntu-24.04 use-cross: true - target: x86_64-unknown-linux-musl - os: ubuntu-22.04 + os: ubuntu-24.04 use-cross: true - target: aarch64-apple-darwin os: macos-14 @@ -76,18 +76,18 @@ jobs: targets: ${{ matrix.target }} - name: Install cross if: ${{ matrix.use-cross }} - uses: taiki-e/install-action@v2.44.35 + uses: taiki-e/install-action@v2.47.7 with: tool: cross - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.5 + uses: Swatinem/rust-cache@v2.7.7 with: key: ${{ matrix.target }} - name: Setup Ruby if: matrix.os != 'windows-2022' && !matrix.use-cross uses: ruby/setup-ruby@v1 with: - ruby-version: 3.2 + ruby-version: 3.3 - name: Install Asciidoctor if: matrix.os != 'windows-2022' && !matrix.use-cross run: | @@ -136,7 +136,7 @@ jobs: needs: - get-version - build - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Download artifact uses: actions/download-artifact@v4 @@ -147,7 +147,7 @@ jobs: sha256sum abcrypt-* | tee sha256sums.txt b2sum abcrypt-* | tee b2sums.txt - name: Release - uses: softprops/action-gh-release@v2.0.8 + uses: softprops/action-gh-release@v2.1.0 if: startsWith(github.ref, 'refs/tags/') with: draft: true diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 491537d6..1dcce5b2 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -14,25 +14,81 @@ on: - cron: "0 0 * * 0" jobs: + check: + name: Check + runs-on: ${{ matrix.os }} + strategy: + matrix: + os-alias: + - ubuntu + - macos + - windows + toolchain-alias: + - msrv + - stable + include: + - os-alias: ubuntu + os: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + - os-alias: macos + os: macos-14 + target: aarch64-apple-darwin + - os-alias: windows + os: windows-2022 + target: x86_64-pc-windows-msvc + - toolchain-alias: msrv + toolchain: 1.74.0 + - toolchain-alias: stable + toolchain: stable + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + targets: ${{ matrix.target }} + - name: Cache build artifacts + uses: Swatinem/rust-cache@v2.7.7 + with: + key: ${{ matrix.target }} + - name: Check packages + run: cargo check --target ${{ matrix.target }} + - name: Check packages (no default features) + run: cargo check -p abcrypt -p abcrypt-cli --target ${{ matrix.target }} --no-default-features + - name: Check packages (`alloc` feature) + run: cargo check -p abcrypt --target ${{ matrix.target }} --no-default-features -F alloc + - name: Check packages (`serde` feature) + run: cargo check -p abcrypt --target ${{ matrix.target }} -F serde + - name: Check packages (`serde` feature with no default features) + run: cargo check -p abcrypt --target ${{ matrix.target }} --no-default-features -F serde + test: name: Test runs-on: ${{ matrix.os }} strategy: matrix: - target: - - x86_64-unknown-linux-gnu - - aarch64-apple-darwin - - x86_64-pc-windows-msvc - toolchain: - - 1.74.0 # MSRV + os-alias: + - ubuntu + - macos + - windows + toolchain-alias: + - msrv - stable include: - - target: x86_64-unknown-linux-gnu - os: ubuntu-22.04 - - target: aarch64-apple-darwin + - os-alias: ubuntu + os: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + - os-alias: macos os: macos-14 - - target: x86_64-pc-windows-msvc + target: aarch64-apple-darwin + - os-alias: windows os: windows-2022 + target: x86_64-pc-windows-msvc + - toolchain-alias: msrv + toolchain: 1.74.0 + - toolchain-alias: stable + toolchain: stable steps: - name: Set Git to use LF if: matrix.os == 'windows-2022' @@ -47,11 +103,11 @@ jobs: toolchain: ${{ matrix.toolchain }} targets: ${{ matrix.target }} - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.5 + uses: Swatinem/rust-cache@v2.7.7 with: key: ${{ matrix.target }} - name: Run tests - run: cargo test --target ${{ matrix.target }} + run: cargo test -p abcrypt -p abcrypt-cli -p abcrypt-capi --target ${{ matrix.target }} - name: Run tests (no default features) run: cargo test -p abcrypt -p abcrypt-cli --target ${{ matrix.target }} --no-default-features - name: Run tests (`alloc` feature) @@ -63,14 +119,52 @@ jobs: - name: Check if the header file is up-to-date run: git diff --exit-code + wasm-check: + name: Check Wasm bindings + runs-on: ubuntu-24.04 + strategy: + matrix: + toolchain-alias: + - msrv + - stable + include: + - toolchain-alias: msrv + toolchain: 1.74.0 + - toolchain-alias: stable + toolchain: stable + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + - name: Install wasm-pack + uses: jetli/wasm-pack-action@v0.4.0 + with: + version: "latest" + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Cache build artifacts + uses: Swatinem/rust-cache@v2.7.7 + - name: Check a package + run: wasm-pack build -s sorairolake -t nodejs --dev crates/wasm + wasm-test: name: Test Wasm bindings - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: - toolchain: - - 1.74.0 # MSRV + toolchain-alias: + - msrv - stable + include: + - toolchain-alias: msrv + toolchain: 1.74.0 + - toolchain-alias: stable + toolchain: stable steps: - name: Checkout code uses: actions/checkout@v4 @@ -85,9 +179,9 @@ jobs: - name: Setup Node.js environment uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.5 + uses: Swatinem/rust-cache@v2.7.7 - name: Run tests run: wasm-pack test --node crates/wasm @@ -96,16 +190,31 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: - - ubuntu-22.04 - - macos-14 - - windows-2022 - rust-version: - - 1.74.0 # MSRV + os-alias: + - ubuntu + - macos + - windows + rust-version-alias: + - msrv - stable - python-version: - - "3.8" - - "3.x" # latest stable version of Python 3 + python-version-alias: + - minimum + - latest + include: + - os-alias: ubuntu + os: ubuntu-24.04 + - os-alias: macos + os: macos-14 + - os-alias: windows + os: windows-2022 + - rust-version-alias: msrv + rust-version: 1.74.0 + - rust-version-alias: stable + rust-version: stable + - python-version-alias: minimum + python-version: "3.8" + - python-version-alias: latest + python-version: "3.x" steps: - name: Set Git to use LF if: matrix.os == 'windows-2022' @@ -130,7 +239,7 @@ jobs: rustfmt: name: Rustfmt - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 @@ -140,13 +249,13 @@ jobs: toolchain: stable components: rustfmt - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.5 + uses: Swatinem/rust-cache@v2.7.7 - name: Check code formatted run: cargo fmt --all -- --check clippy: name: Clippy - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 @@ -156,7 +265,7 @@ jobs: toolchain: stable components: clippy - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.5 + uses: Swatinem/rust-cache@v2.7.7 - name: Check no lint warnings run: cargo clippy --workspace -- -D warnings - name: Check no lint warnings (no default features) @@ -164,7 +273,7 @@ jobs: doc: name: Documentation - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 @@ -173,13 +282,13 @@ jobs: with: toolchain: stable - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.5 + uses: Swatinem/rust-cache@v2.7.7 - name: Check no `rustdoc` lint warnings run: RUSTDOCFLAGS="-D warnings" cargo doc --workspace --exclude abcrypt-cli --no-deps --document-private-items benchmark: name: Benchmark - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 @@ -188,13 +297,13 @@ jobs: with: toolchain: nightly - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.5 + uses: Swatinem/rust-cache@v2.7.7 - name: Run benchmarks run: cargo bench -p abcrypt python-lint: name: Lint Python bindings - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 @@ -218,12 +327,17 @@ jobs: capi-examples: name: Examples for the C API - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: - toolchain: - - 1.74.0 # MSRV + toolchain-alias: + - msrv - stable + include: + - toolchain-alias: msrv + toolchain: 1.74.0 + - toolchain-alias: stable + toolchain: stable steps: - name: Checkout code uses: actions/checkout@v4 @@ -234,12 +348,12 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install libcli11-dev libfmt-dev meson + sudo apt-get install libcli11-dev meson meson -v - name: Setup just uses: extractions/setup-just@v2 - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.5 + uses: Swatinem/rust-cache@v2.7.7 - name: Build examples for the C API run: just build-capi-examples - name: Check code formatted @@ -251,7 +365,7 @@ jobs: wasm-examples: name: Examples for the Wasm bindings - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 @@ -268,7 +382,7 @@ jobs: with: version: "latest" - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.5 + uses: Swatinem/rust-cache@v2.7.7 - name: Check code formatted run: deno fmt --check crates/wasm/examples/*.ts - name: Check no lint warnings diff --git a/.github/workflows/REUSE.yaml b/.github/workflows/REUSE.yaml index d7cb1ebc..a9a7a36c 100644 --- a/.github/workflows/REUSE.yaml +++ b/.github/workflows/REUSE.yaml @@ -14,9 +14,9 @@ on: jobs: reuse: name: REUSE - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 - name: REUSE Compliance Check - uses: fsfe/reuse-action@v4.0.0 + uses: fsfe/reuse-action@v5.0.0 diff --git a/.github/workflows/SemverChecks.yaml b/.github/workflows/SemverChecks.yaml index 66167610..fd3c9e23 100644 --- a/.github/workflows/SemverChecks.yaml +++ b/.github/workflows/SemverChecks.yaml @@ -15,7 +15,7 @@ on: jobs: semver: name: Check Semantic Versioning - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/actionlint.yaml b/.github/workflows/actionlint.yaml index 988209ea..831e4321 100644 --- a/.github/workflows/actionlint.yaml +++ b/.github/workflows/actionlint.yaml @@ -19,7 +19,7 @@ permissions: jobs: validation: name: Validate - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/dependabot_auto_merge.yaml b/.github/workflows/dependabot_auto_merge.yaml index a18ca565..1efd5ae5 100644 --- a/.github/workflows/dependabot_auto_merge.yaml +++ b/.github/workflows/dependabot_auto_merge.yaml @@ -14,7 +14,7 @@ jobs: dependabot: name: Dependabot auto-merge if: github.actor == 'dependabot[bot]' && github.repository_owner == 'sorairolake' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Dependabot metadata id: metadata diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 55a75786..79ee2d89 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -16,14 +16,14 @@ permissions: jobs: deploy: name: Deploy - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js environment uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: "npm" - name: Install dependencies run: | diff --git a/.github/workflows/mirror.yaml b/.github/workflows/mirror.yaml index af072402..2f2a05dd 100644 --- a/.github/workflows/mirror.yaml +++ b/.github/workflows/mirror.yaml @@ -10,14 +10,14 @@ on: - "develop" - "master" schedule: - - cron: "0 0 * * *" + - cron: "0 0 * * FRI" workflow_dispatch: jobs: gitlab: name: Mirror to GitLab if: (github.actor == 'sorairolake' || github.event_name == 'schedule') && github.repository_owner == 'sorairolake' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 @@ -34,7 +34,7 @@ jobs: codeberg: name: Mirror to Codeberg if: (github.actor == 'sorairolake' || github.event_name == 'schedule') && github.repository_owner == 'sorairolake' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/release_python.yaml b/.github/workflows/release_python.yaml index eed5ada1..bb3181e5 100644 --- a/.github/workflows/release_python.yaml +++ b/.github/workflows/release_python.yaml @@ -21,7 +21,7 @@ env: jobs: linux: name: Build wheels for Linux - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: target: @@ -41,7 +41,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Build wheels - uses: PyO3/maturin-action@v1.44.0 + uses: PyO3/maturin-action@v1.45.0 with: command: build args: --strip -i ${{ matrix.python-version }} -o dist -r @@ -77,7 +77,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Build wheels - uses: PyO3/maturin-action@v1.44.0 + uses: PyO3/maturin-action@v1.45.0 with: command: build args: --strip -i ${{ matrix.python-version }} -o dist -r @@ -112,7 +112,7 @@ jobs: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.target }} - name: Build wheels - uses: PyO3/maturin-action@v1.44.0 + uses: PyO3/maturin-action@v1.45.0 with: command: build args: --strip -i ${{ matrix.python-version }} -o dist -r @@ -127,12 +127,12 @@ jobs: sdist: name: Build sdist - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code uses: actions/checkout@v4 - name: Build sdist - uses: PyO3/maturin-action@v1.44.0 + uses: PyO3/maturin-action@v1.45.0 with: command: sdist args: -o dist @@ -150,7 +150,7 @@ jobs: - macos - windows - sdist - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Download wheels uses: actions/download-artifact@v4 @@ -158,7 +158,7 @@ jobs: pattern: wheels-* merge-multiple: true - name: Publish to PyPI - uses: PyO3/maturin-action@v1.44.0 + uses: PyO3/maturin-action@v1.45.0 if: startsWith(github.ref, 'refs/tags/') with: command: upload diff --git a/Cargo.lock b/Cargo.lock index f1a8cd58..849a7567 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "abcrypt" -version = "0.3.7" +version = "0.4.0" dependencies = [ "anyhow", "argon2", @@ -93,9 +93,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -108,43 +108,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.90" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "argon2" @@ -230,9 +230,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" dependencies = [ "borsh-derive", "cfg_aliases", @@ -240,23 +240,22 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.79", - "syn_derive", + "syn 2.0.95", ] [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "regex-automata", @@ -271,9 +270,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-unit" -version = "5.1.4" +version = "5.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ac19bdf0b2665407c39d82dbc937e951e7e2001609f0fb32edd0af45a2d63e" +checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174" dependencies = [ "rust_decimal", "serde", @@ -310,9 +309,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cbindgen" @@ -327,16 +326,16 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.79", + "syn 2.0.95", "tempfile", "toml", ] [[package]] name = "cc" -version = "1.1.31" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "shlex", ] @@ -390,9 +389,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "9560b07a799281c7e0958b9296854d6fafd4c5f31444a7e5bb1ad6dde5ccf1bd" dependencies = [ "clap_builder", "clap_derive", @@ -400,9 +399,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "874e0dd3eb68bf99058751ac9712f622e61e6f393a94f7128fa26e3f02f5c7cd" dependencies = [ "anstream", "anstyle", @@ -413,18 +412,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.33" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9646e2e245bf62f45d39a0f3f36f1171ad1ea0d6967fd114bca72cb02a8fcdfb" +checksum = "942dc5991a34d8cf58937ec33201856feba9cbceeeab5adf04116ec7c763bff1" dependencies = [ "clap", ] [[package]] name = "clap_complete_nushell" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "315902e790cc6e5ddd20cbd313c1d0d49db77f191e149f96397230fb82a17677" +checksum = "c6a8b1593457dfc2fe539002b795710d022dc62a65bf15023f039f9760c7b18a" dependencies = [ "clap", "clap_complete", @@ -432,56 +431,46 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.95", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", + "once_cell", "unicode-width", - "windows-sys 0.52.0", -] - -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", + "windows-sys", ] [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -534,9 +523,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "equivalent" @@ -546,25 +535,25 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "float-cmp" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" dependencies = [ "num-traits", ] @@ -609,9 +598,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -627,12 +616,12 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", ] [[package]] @@ -658,36 +647,31 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" -version = "0.2.161" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "log" @@ -712,9 +696,9 @@ dependencies = [ [[package]] name = "minicov" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" dependencies = [ "cc", "walkdir", @@ -771,9 +755,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "ppv-lite86" @@ -786,9 +770,9 @@ dependencies = [ [[package]] name = "predicates" -version = "3.1.2" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" dependencies = [ "anstyle", "difflib", @@ -800,15 +784,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" [[package]] name = "predicates-tree" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" dependencies = [ "predicates-core", "termtree", @@ -823,34 +807,11 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.88" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -877,9 +838,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.22.5" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" +checksum = "e484fd2c8b4cb67ab05a318f1fd6fa8f199fcc30819f08f07d200809dba26c15" dependencies = [ "cfg-if", "indoc", @@ -895,9 +856,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.5" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" +checksum = "dc0e0469a84f208e20044b98965e1561028180219e35352a2afaf2b942beff3b" dependencies = [ "once_cell", "target-lexicon", @@ -905,9 +866,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.22.5" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" +checksum = "eb1547a7f9966f6f1a0f0227564a9945fe36b90da5a93b3933fc3dc03fae372d" dependencies = [ "libc", "pyo3-build-config", @@ -915,34 +876,34 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.5" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" +checksum = "fdb6da8ec6fa5cedd1626c886fc8749bdcbb09424a86461eb8cdf096b7c33257" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.79", + "syn 2.0.95", ] [[package]] name = "pyo3-macros-backend" -version = "0.22.5" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" +checksum = "38a385202ff5a92791168b1136afae5059d3ac118457bb7bc304c197c2d33e7d" dependencies = [ "heck 0.5.0", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.79", + "syn 2.0.95", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -985,9 +946,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -997,9 +958,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1068,15 +1029,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -1108,29 +1069,29 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.95", ] [[package]] name = "serde_json" -version = "1.0.131" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67d42a0bd4ac281beff598909bb56a86acaf979b84483e1c79c10dcaf98f8cf3" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", "memchr", @@ -1199,32 +1160,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.79", -] - [[package]] name = "sysexits" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5649c51a0a23b49261cc66a328925546beaf86d7c9af801b3993732deccec7a" +checksum = "3a70e00b0ea6c3e7154dfc48fee004bf61bdc154fd99b7e17c05318503b9660b" [[package]] name = "tap" @@ -1240,58 +1189,59 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.13.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] name = "terminal_size" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] name = "termtree" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.95", ] [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -1344,15 +1294,15 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-width" -version = "0.1.14" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unindent" @@ -1421,9 +1371,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -1432,36 +1382,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.95", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1469,30 +1419,29 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.95", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-bindgen-test" -version = "0.3.45" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" +checksum = "c61d44563646eb934577f2772656c7ad5e9c90fac78aa8013d776fcdaf24625d" dependencies = [ - "console_error_panic_hook", "js-sys", "minicov", "scoped-tls", @@ -1503,20 +1452,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.45" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" +checksum = "54171416ce73aa0b9c377b51cc3cb542becee1cd678204812e8392e5b0e4a031" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.95", ] [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -1528,16 +1477,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", + "windows-sys", ] [[package]] @@ -1615,9 +1555,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" dependencies = [ "memchr", ] @@ -1649,7 +1589,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.95", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0d9c32cd..4e5972c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,19 @@ repository = "https://github.com/sorairolake/abcrypt" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace.dependencies] -anyhow = "1.0.90" -clap = { version = "4.5.20", features = ["derive"] } +anyhow = "1.0.95" +clap = { version = "4.5.24", features = ["derive"] } dialoguer = { version = "0.11.0", default-features = false, features = ["password"] } +[workspace.lints.clippy] +cargo = "warn" +nursery = "warn" +pedantic = "warn" + +[workspace.lints.rust] +missing_debug_implementations = "deny" +rust_2018_idioms = { level = "warn", priority = -1 } + [profile.release.package.abcrypt-cli] codegen-units = 1 # The `lto` setting cannot be specified yet, see https://github.com/rust-lang/cargo/issues/9330 diff --git a/README.adoc b/README.adoc index ddb7624d..2d6089af 100644 --- a/README.adoc +++ b/README.adoc @@ -35,8 +35,6 @@ image:{ci-badge}[CI,link={ci-url}] *abcrypt* is a simple, modern and secure file encryption tool, file format and Rust library. -image::crates/cli/assets/screenshot.webp[Screenshot of abcrypt] - == Crates |=== @@ -80,9 +78,13 @@ https://github.com/sorairolake/abcrypt.git. Please see link:CONTRIBUTING.adoc[]. +== Home page + +https://sorairolake.github.io/abcrypt/ + == License -Copyright (C) 2022–2024 Shun Sakai (see link:AUTHORS.adoc[]) +Copyright (C) 2022 Shun Sakai (see link:AUTHORS.adoc[]) . Unless otherwise noted, each file is distributed under the terms of either the _Apache License 2.0_ or the _MIT License_. diff --git a/assets/abcrypt.magic b/assets/abcrypt.magic new file mode 100644 index 00000000..8dc7776f --- /dev/null +++ b/assets/abcrypt.magic @@ -0,0 +1,30 @@ +# abcrypt: file(1) magic for abcrypt encrypted file +# +0 string abcrypt abcrypt encrypted data +!:mime application/x-abcrypt +!:ext abcrypt +>7 ubyte x - version %d +# version 0 +>7 ubyte 0 +>>8 use argon2-parameters +# version 1 +>7 ubyte 1 +>>8 use argon2-type +>>12 use argon2-version +>>16 use argon2-parameters + +# Argon2 type +0 name argon2-type +>0 ulelong 0 \b, Argon2d +>0 ulelong 1 \b, Argon2i +>0 ulelong 2 \b, Argon2id + +# Argon2 version +0 name argon2-version +>0 ulelong x \b, Argon2 version %#x + +# Argon2 parameters +0 name argon2-parameters +>0 ulelong x \b, m=%u +>4 ulelong x \b, t=%u +>8 ulelong x \b, p=%u diff --git a/crates/capi/cbindgen.toml.license b/assets/abcrypt.magic.license similarity index 52% rename from crates/capi/cbindgen.toml.license rename to assets/abcrypt.magic.license index 90382fef..df26b1a7 100644 --- a/crates/capi/cbindgen.toml.license +++ b/assets/abcrypt.magic.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2023 Shun Sakai +SPDX-FileCopyrightText: 2024 Shun Sakai SPDX-License-Identifier: Apache-2.0 OR MIT diff --git a/crates/abcrypt/CHANGELOG.adoc b/crates/abcrypt/CHANGELOG.adoc index 892340b6..2e90034a 100644 --- a/crates/abcrypt/CHANGELOG.adoc +++ b/crates/abcrypt/CHANGELOG.adoc @@ -14,6 +14,18 @@ All notable changes to this project will be documented in this file. The format is based on https://keepachangelog.com/[Keep a Changelog], and this project adheres to https://semver.org/[Semantic Versioning]. +== {compare-url}/abcrypt-v0.3.7\...abcrypt-v0.4.0[0.4.0] - 2025-01-09 + +=== Added + +* Supports the abcrypt version 1 file format ({pull-request-url}/619[#619]) +* Add `Argon2` struct to get the Argon2 context ({pull-request-url}/619[#619]) + +=== Removed + +* Remove the abcrypt version 0 file format support + ({pull-request-url}/619[#619]) + == {compare-url}/abcrypt-v0.3.6\...abcrypt-v0.3.7[0.3.7] - 2024-10-19 === Fixed diff --git a/crates/abcrypt/Cargo.toml b/crates/abcrypt/Cargo.toml index 26303371..82d078db 100644 --- a/crates/abcrypt/Cargo.toml +++ b/crates/abcrypt/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "abcrypt" -version = "0.3.7" +version = "0.4.0" authors.workspace = true edition.workspace = true rust-version.workspace = true @@ -26,30 +26,30 @@ all-features = true [[example]] name = "decrypt" path = "examples/decrypt.rs" -required-features = ["default"] +required-features = ["std"] [[example]] name = "encrypt" path = "examples/encrypt.rs" -required-features = ["default"] +required-features = ["std"] [[example]] name = "info" path = "examples/info.rs" -required-features = ["default", "serde"] +required-features = ["std", "serde"] [dependencies] argon2 = { version = "0.5.3", default-features = false } blake2 = { version = "0.10.6", default-features = false } chacha20poly1305 = { version = "0.10.1", default-features = false, features = ["getrandom"] } rand = { version = "0.8.5", default-features = false, features = ["getrandom", "std_rng"] } -serde = { version = "1.0.210", default-features = false, features = ["derive"], optional = true } +serde = { version = "1.0.217", default-features = false, features = ["derive"], optional = true } [dev-dependencies] anyhow.workspace = true clap.workspace = true dialoguer.workspace = true -serde_json = "1.0.131" +serde_json = "1.0.135" serde_test = "1.0.177" [features] @@ -57,3 +57,6 @@ default = ["std"] alloc = ["argon2/alloc"] std = ["alloc", "argon2/std", "blake2/std", "chacha20poly1305/std"] serde = ["dep:serde"] + +[lints] +workspace = true diff --git a/crates/abcrypt/README.md b/crates/abcrypt/README.md index 223c02bd..a011ad74 100644 --- a/crates/abcrypt/README.md +++ b/crates/abcrypt/README.md @@ -14,13 +14,15 @@ SPDX-License-Identifier: Apache-2.0 OR MIT **abcrypt** is an implementation of the [abcrypt encrypted data format]. +This crate supports the abcrypt version 1 file format. + ## Usage Add this to your `Cargo.toml`: ```toml [dependencies] -abcrypt = "0.3.7" +abcrypt = "0.4.0" ``` ### Crate features @@ -72,9 +74,13 @@ Please see [CHANGELOG.adoc]. Please see [CONTRIBUTING.adoc]. +## Home page + + + ## License -Copyright © 2022–2024 Shun Sakai (see [AUTHORS.adoc]) +Copyright (C) 2022 Shun Sakai (see [AUTHORS.adoc]) This library is distributed under the terms of either the _Apache License 2.0_ or the _MIT License_. diff --git a/crates/abcrypt/benches/argon2_context.rs b/crates/abcrypt/benches/argon2_context.rs new file mode 100644 index 00000000..ce65a553 --- /dev/null +++ b/crates/abcrypt/benches/argon2_context.rs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Shun Sakai +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#![feature(test)] + +extern crate test; + +use abcrypt::Argon2; +use test::Bencher; + +// Generated using `abcrypt` crate version 0.4.0. +const TEST_DATA_ENC: &[u8] = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); + +#[bench] +fn params(b: &mut Bencher) { + b.iter(|| Argon2::new(TEST_DATA_ENC)); +} diff --git a/crates/abcrypt/benches/decrypt.rs b/crates/abcrypt/benches/decrypt.rs index eb1e53e9..81dbdd42 100644 --- a/crates/abcrypt/benches/decrypt.rs +++ b/crates/abcrypt/benches/decrypt.rs @@ -3,12 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #![feature(test)] -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] extern crate test; @@ -16,8 +10,8 @@ use abcrypt::Decryptor; use test::Bencher; const PASSPHRASE: &str = "passphrase"; -// Generated using `abcrypt` crate version 0.1.0. -const TEST_DATA_ENC: &[u8] = include_bytes!("../tests/data/data.txt.abcrypt"); +// Generated using `abcrypt` crate version 0.4.0. +const TEST_DATA_ENC: &[u8] = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); #[bench] fn decrypt(b: &mut Bencher) { diff --git a/crates/abcrypt/benches/encrypt.rs b/crates/abcrypt/benches/encrypt.rs index c252b677..34b3aa48 100644 --- a/crates/abcrypt/benches/encrypt.rs +++ b/crates/abcrypt/benches/encrypt.rs @@ -3,12 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #![feature(test)] -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] extern crate test; diff --git a/crates/abcrypt/benches/params.rs b/crates/abcrypt/benches/params.rs index 11185100..9e1c7fc3 100644 --- a/crates/abcrypt/benches/params.rs +++ b/crates/abcrypt/benches/params.rs @@ -3,20 +3,14 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT #![feature(test)] -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] extern crate test; use abcrypt::Params; use test::Bencher; -// Generated using `abcrypt` crate version 0.1.0. -const TEST_DATA_ENC: &[u8] = include_bytes!("../tests/data/data.txt.abcrypt"); +// Generated using `abcrypt` crate version 0.4.0. +const TEST_DATA_ENC: &[u8] = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); #[bench] fn params(b: &mut Bencher) { diff --git a/crates/abcrypt/examples/decrypt.rs b/crates/abcrypt/examples/decrypt.rs index 52c292f1..50c69661 100644 --- a/crates/abcrypt/examples/decrypt.rs +++ b/crates/abcrypt/examples/decrypt.rs @@ -4,13 +4,6 @@ //! An example of decrypting a file from the abcrypt encrypted data format. -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] - use std::{ fs, io::{self, Read, Write}, diff --git a/crates/abcrypt/examples/encrypt.rs b/crates/abcrypt/examples/encrypt.rs index 570f36a7..b9705f27 100644 --- a/crates/abcrypt/examples/encrypt.rs +++ b/crates/abcrypt/examples/encrypt.rs @@ -4,22 +4,15 @@ //! An example of encrypting a file to the abcrypt encrypted data format. -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] - use std::{ fs, io::{self, Read, Write}, path::PathBuf, }; -use abcrypt::argon2::Params; +use abcrypt::argon2::{Algorithm, Params, Version}; use anyhow::Context; -use clap::Parser; +use clap::{Parser, ValueEnum}; use dialoguer::{theme::ColorfulTheme, Password}; #[derive(Debug, Parser)] @@ -29,6 +22,26 @@ struct Opt { #[arg(short, long, value_name("FILE"))] output: Option, + /// Set the Argon2 type. + #[arg( + long, + value_enum, + default_value_t, + value_name("TYPE"), + ignore_case(true) + )] + argon2_type: Argon2Type, + + /// Set the Argon2 version. + #[arg( + long, + value_enum, + default_value_t, + value_name("VERSION"), + ignore_case(true) + )] + argon2_version: Argon2Version, + /// Set the memory size in KiB. #[arg(short, long, default_value("19456"), value_name("NUM"))] memory_cost: u32, @@ -48,6 +61,50 @@ struct Opt { input: Option, } +#[derive(Clone, Debug, Default, ValueEnum)] +enum Argon2Type { + /// Argon2d. + Argon2d, + + /// Argon2i. + Argon2i, + + /// Argon2id. + #[default] + Argon2id, +} + +impl From for Algorithm { + fn from(argon2_type: Argon2Type) -> Self { + match argon2_type { + Argon2Type::Argon2d => Self::Argon2d, + Argon2Type::Argon2i => Self::Argon2i, + Argon2Type::Argon2id => Self::Argon2id, + } + } +} + +#[derive(Clone, Debug, Default, ValueEnum)] +enum Argon2Version { + /// Version 0x10. + #[value(name = "0x10", alias("16"))] + V0x10, + + /// Version 0x13. + #[default] + #[value(name = "0x13", alias("19"))] + V0x13, +} + +impl From for Version { + fn from(argon2_version: Argon2Version) -> Self { + match argon2_version { + Argon2Version::V0x10 => Self::V0x10, + Argon2Version::V0x13 => Self::V0x13, + } + } +} + fn main() -> anyhow::Result<()> { let opt = Opt::parse(); @@ -67,7 +124,13 @@ fn main() -> anyhow::Result<()> { .interact() .context("could not read passphrase")?; let params = Params::new(opt.memory_cost, opt.time_cost, opt.parallelism, None)?; - let ciphertext = abcrypt::encrypt_with_params(plaintext, passphrase, params)?; + let ciphertext = abcrypt::encrypt_with_context( + plaintext, + passphrase, + opt.argon2_type.into(), + opt.argon2_version.into(), + params, + )?; if let Some(file) = opt.output { fs::write(&file, ciphertext) diff --git a/crates/abcrypt/examples/info.rs b/crates/abcrypt/examples/info.rs index c5d1d146..9f8fb5cd 100644 --- a/crates/abcrypt/examples/info.rs +++ b/crates/abcrypt/examples/info.rs @@ -4,13 +4,6 @@ //! An example of reading the Argon2 parameters from a file. -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] - use std::{ fs, io::{self, Read}, diff --git a/crates/abcrypt/src/argon2_context.rs b/crates/abcrypt/src/argon2_context.rs new file mode 100644 index 00000000..e41a5183 --- /dev/null +++ b/crates/abcrypt/src/argon2_context.rs @@ -0,0 +1,299 @@ +// SPDX-FileCopyrightText: 2024 Shun Sakai +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! The Argon2 context. + +use argon2::Algorithm; + +use crate::{format::Header, Error, Result}; + +/// The Argon2 context used for the encrypted data. +#[derive(Clone, Copy, Debug)] +pub struct Argon2 { + variant: Algorithm, + version: argon2::Version, +} + +impl Argon2 { + /// Creates a new instance of the Argon2 context from `ciphertext`. + /// + /// # Errors + /// + /// Returns [`Err`] if any of the following are true: + /// + /// - `ciphertext` is shorter than 164 bytes. + /// - The magic number is invalid. + /// - The version number is the unsupported abcrypt version number. + /// - The version number is the unrecognized abcrypt version number. + /// - The Argon2 type is invalid. + /// - The Argon2 version is invalid. + /// - The Argon2 parameters are invalid. + /// + /// # Examples + /// + /// ``` + /// # use abcrypt::Argon2; + /// # + /// let ciphertext = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); + /// + /// assert!(Argon2::new(ciphertext).is_ok()); + /// ``` + #[inline] + pub fn new(ciphertext: impl AsRef<[u8]>) -> Result { + let inner = |ciphertext: &[u8]| -> Result { + let header = Header::parse(ciphertext)?; + let variant = header.argon2_type().into(); + let version = header.argon2_version().into(); + Ok(Self { variant, version }) + }; + inner(ciphertext.as_ref()) + } + + /// Gets the Argon2 type. + /// + /// # Examples + /// + /// ``` + /// # use abcrypt::{argon2::Algorithm, Argon2}; + /// # + /// let ciphertext = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); + /// + /// let argon2 = Argon2::new(ciphertext).unwrap(); + /// assert_eq!(argon2.variant(), Algorithm::Argon2id); + /// ``` + #[must_use] + #[inline] + pub const fn variant(&self) -> Algorithm { + self.variant + } + + /// Gets the Argon2 version. + /// + /// # Examples + /// + /// ``` + /// # use abcrypt::{argon2::Version, Argon2}; + /// # + /// let ciphertext = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); + /// + /// let argon2 = Argon2::new(ciphertext).unwrap(); + /// assert_eq!(argon2.version(), Version::V0x13); + /// ``` + #[must_use] + #[inline] + pub const fn version(&self) -> argon2::Version { + self.version + } +} + +/// Type of Argon2. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum Variant { + /// Argon2d. + Argon2d, + + /// Argon2i. + Argon2i, + + /// Argon2id. + Argon2id, +} + +impl From for u32 { + #[inline] + fn from(variant: Variant) -> Self { + variant as Self + } +} + +impl From for Algorithm { + #[inline] + fn from(variant: Variant) -> Self { + match variant { + Variant::Argon2d => Self::Argon2d, + Variant::Argon2i => Self::Argon2i, + Variant::Argon2id => Self::Argon2id, + } + } +} + +impl TryFrom for Variant { + type Error = Error; + + #[inline] + fn try_from(variant: u32) -> Result { + match variant { + 0 => Ok(Self::Argon2d), + 1 => Ok(Self::Argon2i), + 2 => Ok(Self::Argon2id), + v => Err(Error::InvalidArgon2Type(v)), + } + } +} + +impl From for Variant { + #[inline] + fn from(algorithm: Algorithm) -> Self { + match algorithm { + Algorithm::Argon2d => Self::Argon2d, + Algorithm::Argon2i => Self::Argon2i, + Algorithm::Argon2id => Self::Argon2id, + } + } +} + +/// Version of Argon2. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum Version { + /// Version 0x10. + V0x10 = 0x10, + + /// Version 0x13. + V0x13 = 0x13, +} + +impl From for u32 { + #[inline] + fn from(version: Version) -> Self { + version as Self + } +} + +impl From for argon2::Version { + #[inline] + fn from(version: Version) -> Self { + match version { + Version::V0x10 => Self::V0x10, + Version::V0x13 => Self::V0x13, + } + } +} + +impl TryFrom for Version { + type Error = Error; + + #[inline] + fn try_from(version: u32) -> Result { + match version { + 0x10 => Ok(Self::V0x10), + 0x13 => Ok(Self::V0x13), + v => Err(Error::InvalidArgon2Version(v)), + } + } +} + +impl From for Version { + #[inline] + fn from(version: argon2::Version) -> Self { + match version { + argon2::Version::V0x10 => Self::V0x10, + argon2::Version::V0x13 => Self::V0x13, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_variant_to_u32() { + assert_eq!(u32::from(Variant::Argon2d), 0); + assert_eq!(u32::from(Variant::Argon2i), 1); + assert_eq!(u32::from(Variant::Argon2id), 2); + } + + #[test] + fn from_variant_to_algorithm() { + assert_eq!(Algorithm::from(Variant::Argon2d), Algorithm::Argon2d); + assert_eq!(Algorithm::from(Variant::Argon2i), Algorithm::Argon2i); + assert_eq!(Algorithm::from(Variant::Argon2id), Algorithm::Argon2id); + } + + #[test] + fn try_from_u32_to_variant() { + assert_eq!(Variant::try_from(0).unwrap(), Variant::Argon2d); + assert_eq!(Variant::try_from(1).unwrap(), Variant::Argon2i); + assert_eq!(Variant::try_from(2).unwrap(), Variant::Argon2id); + } + + #[test] + fn try_from_u32_to_variant_with_invalid_argon2_type() { + assert_eq!( + Variant::try_from(3).unwrap_err(), + Error::InvalidArgon2Type(3) + ); + assert_eq!( + Variant::try_from(u32::MAX).unwrap_err(), + Error::InvalidArgon2Type(u32::MAX) + ); + } + + #[test] + fn from_algorithm_to_variant() { + assert_eq!(Variant::from(Algorithm::Argon2d), Variant::Argon2d); + assert_eq!(Variant::from(Algorithm::Argon2i), Variant::Argon2i); + assert_eq!(Variant::from(Algorithm::Argon2id), Variant::Argon2id); + } + + #[test] + fn from_version_to_u32() { + assert_eq!(u32::from(Version::V0x10), 0x10); + assert_eq!(u32::from(Version::V0x13), 0x13); + } + + #[test] + fn from_version_to_argon2_version() { + assert_eq!( + argon2::Version::from(Version::V0x10), + argon2::Version::V0x10 + ); + assert_eq!( + argon2::Version::from(Version::V0x13), + argon2::Version::V0x13 + ); + } + + #[test] + fn try_from_u32_to_version() { + assert_eq!(Version::try_from(0x10).unwrap(), Version::V0x10); + assert_eq!(Version::try_from(0x13).unwrap(), Version::V0x13); + } + + #[test] + fn try_from_u32_to_version_with_invalid_argon2_version() { + assert_eq!( + Version::try_from(u32::MIN).unwrap_err(), + Error::InvalidArgon2Version(u32::MIN) + ); + assert_eq!( + Version::try_from(0xf).unwrap_err(), + Error::InvalidArgon2Version(0xf) + ); + assert_eq!( + Version::try_from(0x11).unwrap_err(), + Error::InvalidArgon2Version(0x11) + ); + assert_eq!( + Version::try_from(0x12).unwrap_err(), + Error::InvalidArgon2Version(0x12) + ); + assert_eq!( + Version::try_from(0x14).unwrap_err(), + Error::InvalidArgon2Version(0x14) + ); + assert_eq!( + Version::try_from(u32::MAX).unwrap_err(), + Error::InvalidArgon2Version(u32::MAX) + ); + } + + #[test] + fn from_argon2_version_to_version() { + assert_eq!(Version::from(argon2::Version::V0x10), Version::V0x10); + assert_eq!(Version::from(argon2::Version::V0x13), Version::V0x13); + } +} diff --git a/crates/abcrypt/src/decrypt.rs b/crates/abcrypt/src/decrypt.rs index f7d8381f..fba3c3df 100644 --- a/crates/abcrypt/src/decrypt.rs +++ b/crates/abcrypt/src/decrypt.rs @@ -9,7 +9,7 @@ use chacha20poly1305::{AeadInPlace, KeyInit, Tag, XChaCha20Poly1305}; use crate::{ format::{DerivedKey, Header}, - Error, Result, AAD, ARGON2_ALGORITHM, ARGON2_VERSION, HEADER_SIZE, TAG_SIZE, + Error, Result, AAD, HEADER_SIZE, TAG_SIZE, }; /// Decryptor for the abcrypt encrypted data format. @@ -28,9 +28,12 @@ impl<'c> Decryptor<'c> { /// /// Returns [`Err`] if any of the following are true: /// - /// - `ciphertext` is shorter than 156 bytes. + /// - `ciphertext` is shorter than 164 bytes. /// - The magic number is invalid. + /// - The version number is the unsupported abcrypt version number. /// - The version number is the unrecognized abcrypt version number. + /// - The Argon2 type is invalid. + /// - The Argon2 version is invalid. /// - The Argon2 parameters are invalid. /// - The Argon2 context is invalid. /// - The MAC (authentication tag) of the header is invalid. @@ -40,7 +43,7 @@ impl<'c> Decryptor<'c> { /// ``` /// # use abcrypt::Decryptor; /// # - /// let ciphertext = include_bytes!("../tests/data/data.txt.abcrypt"); + /// let ciphertext = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); /// let passphrase = "passphrase"; /// /// let cipher = Decryptor::new(&ciphertext, passphrase).unwrap(); @@ -52,7 +55,11 @@ impl<'c> Decryptor<'c> { // The derived key size is 96 bytes. The first 256 bits are for // XChaCha20-Poly1305 key, and the last 512 bits are for BLAKE2b-512-MAC key. let mut dk = [u8::default(); DerivedKey::SIZE]; - let argon2 = Argon2::new(ARGON2_ALGORITHM, ARGON2_VERSION, header.params().into()); + let argon2 = Argon2::new( + header.argon2_type().into(), + header.argon2_version().into(), + header.params().into(), + ); #[cfg(feature = "alloc")] argon2 .hash_password_into(passphrase, &header.salt(), &mut dk) @@ -71,7 +78,7 @@ impl<'c> Decryptor<'c> { } let dk = DerivedKey::new(dk); - header.verify_mac(&dk.mac(), ciphertext[76..HEADER_SIZE].into())?; + header.verify_mac(&dk.mac(), ciphertext[84..HEADER_SIZE].into())?; let (ciphertext, tag) = ciphertext[HEADER_SIZE..].split_at(ciphertext.len() - HEADER_SIZE - TAG_SIZE); let tag = *Tag::from_slice(tag); @@ -102,13 +109,13 @@ impl<'c> Decryptor<'c> { /// # use abcrypt::Decryptor; /// # /// let data = b"Hello, world!\n"; - /// let ciphertext = include_bytes!("../tests/data/data.txt.abcrypt"); + /// let ciphertext = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); /// let passphrase = "passphrase"; /// /// let cipher = Decryptor::new(&ciphertext, passphrase).unwrap(); /// let mut buf = [u8::default(); 14]; /// cipher.decrypt(&mut buf).unwrap(); - /// # assert_eq!(buf, data.as_slice()); + /// # assert_eq!(buf, *data); /// ``` pub fn decrypt(&self, buf: &mut (impl AsMut<[u8]> + ?Sized)) -> Result<()> { let inner = |decryptor: &Self, buf: &mut [u8]| -> Result<()> { @@ -140,7 +147,7 @@ impl<'c> Decryptor<'c> { /// # use abcrypt::Decryptor; /// # /// let data = b"Hello, world!\n"; - /// let ciphertext = include_bytes!("../tests/data/data.txt.abcrypt"); + /// let ciphertext = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); /// let passphrase = "passphrase"; /// /// let cipher = Decryptor::new(&ciphertext, passphrase).unwrap(); @@ -148,6 +155,7 @@ impl<'c> Decryptor<'c> { /// # assert_eq!(plaintext, data); /// ``` #[cfg(feature = "alloc")] + #[inline] pub fn decrypt_to_vec(&self) -> Result> { let mut buf = vec![u8::default(); self.out_len()]; self.decrypt(&mut buf)?; @@ -161,7 +169,7 @@ impl<'c> Decryptor<'c> { /// ``` /// # use abcrypt::Decryptor; /// # - /// let ciphertext = include_bytes!("../tests/data/data.txt.abcrypt"); + /// let ciphertext = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); /// let passphrase = "passphrase"; /// /// let cipher = Decryptor::new(&ciphertext, passphrase).unwrap(); @@ -183,9 +191,12 @@ impl<'c> Decryptor<'c> { /// /// Returns [`Err`] if any of the following are true: /// -/// - `ciphertext` is shorter than 156 bytes. +/// - `ciphertext` is shorter than 164 bytes. /// - The magic number is invalid. +/// - The version number is the unsupported abcrypt version number. /// - The version number is the unrecognized abcrypt version number. +/// - The Argon2 type is invalid. +/// - The Argon2 version is invalid. /// - The Argon2 parameters are invalid. /// - The Argon2 context is invalid. /// - The MAC (authentication tag) of the header is invalid. @@ -195,13 +206,14 @@ impl<'c> Decryptor<'c> { /// /// ``` /// let data = b"Hello, world!\n"; -/// let ciphertext = include_bytes!("../tests/data/data.txt.abcrypt"); +/// let ciphertext = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); /// let passphrase = "passphrase"; /// /// let plaintext = abcrypt::decrypt(ciphertext, passphrase).unwrap(); /// # assert_eq!(plaintext, data); /// ``` #[cfg(feature = "alloc")] +#[inline] pub fn decrypt( ciphertext: impl AsRef<[u8]>, passphrase: impl AsRef<[u8]>, diff --git a/crates/abcrypt/src/encrypt.rs b/crates/abcrypt/src/encrypt.rs index 125d3f64..9c91f303 100644 --- a/crates/abcrypt/src/encrypt.rs +++ b/crates/abcrypt/src/encrypt.rs @@ -4,12 +4,12 @@ //! Encrypts to the abcrypt encrypted data format. -use argon2::{Argon2, Params}; +use argon2::{Algorithm, Argon2, Params, Version}; use chacha20poly1305::{AeadInPlace, KeyInit, XChaCha20Poly1305}; use crate::{ format::{DerivedKey, Header}, - Error, Result, AAD, ARGON2_ALGORITHM, ARGON2_VERSION, HEADER_SIZE, TAG_SIZE, + Error, Result, AAD, HEADER_SIZE, TAG_SIZE, }; /// Encryptor for the abcrypt encrypted data format. @@ -23,8 +23,10 @@ pub struct Encryptor<'m> { impl<'m> Encryptor<'m> { /// Creates a new `Encryptor`. /// - /// This uses the [recommended Argon2 parameters] created by - /// [`Params::default`]. + /// This uses the recommended Argon2 parameters according to the [OWASP + /// Password Storage Cheat Sheet] created by [`Params::default`]. This also + /// uses the Argon2 type created by [`Algorithm::default`] and the Argon2 + /// version created by [`Version::default`]. /// /// # Errors /// @@ -41,14 +43,18 @@ impl<'m> Encryptor<'m> { /// let cipher = Encryptor::new(data, passphrase).unwrap(); /// ``` /// - /// [recommended Argon2 parameters]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html + /// [OWASP Password Storage Cheat Sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id #[cfg(feature = "alloc")] + #[inline] pub fn new(plaintext: &'m impl AsRef<[u8]>, passphrase: impl AsRef<[u8]>) -> Result { Self::with_params(plaintext, passphrase, Params::default()) } /// Creates a new `Encryptor` with the specified [`Params`]. /// + /// This uses the Argon2 type created by [`Algorithm::default`] and the + /// Argon2 version created by [`Version::default`]. + /// /// # Errors /// /// Returns [`Err`] if the Argon2 context is invalid. @@ -64,18 +70,67 @@ impl<'m> Encryptor<'m> { /// let params = Params::new(32, 3, 4, None).unwrap(); /// let cipher = Encryptor::with_params(data, passphrase, params).unwrap(); /// ``` + #[inline] pub fn with_params( plaintext: &'m impl AsRef<[u8]>, passphrase: impl AsRef<[u8]>, params: Params, ) -> Result { - let inner = |plaintext: &'m [u8], passphrase: &[u8], params: Params| -> Result { - let mut header = Header::new(params); + Self::with_context( + plaintext, + passphrase, + Algorithm::default(), + Version::default(), + params, + ) + } + + /// Creates a new `Encryptor` with the specified [`Algorithm`], [`Version`] + /// and [`Params`]. + /// + /// # Errors + /// + /// Returns [`Err`] if the Argon2 context is invalid. + /// + /// # Examples + /// + /// ``` + /// # use abcrypt::{ + /// # argon2::{Algorithm, Params, Version}, + /// # Encryptor, + /// # }; + /// # + /// let data = b"Hello, world!\n"; + /// let passphrase = "passphrase"; + /// + /// let params = Params::new(32, 3, 4, None).unwrap(); + /// let cipher = + /// Encryptor::with_context(data, passphrase, Algorithm::Argon2i, Version::V0x10, params) + /// .unwrap(); + /// ``` + pub fn with_context( + plaintext: &'m impl AsRef<[u8]>, + passphrase: impl AsRef<[u8]>, + argon2_type: Algorithm, + argon2_version: Version, + params: Params, + ) -> Result { + let inner = |plaintext: &'m [u8], + passphrase: &[u8], + argon2_type: Algorithm, + argon2_version: Version, + params: Params| + -> Result { + let mut header = Header::new(argon2_type, argon2_version, params); // The derived key size is 96 bytes. The first 256 bits are for // XChaCha20-Poly1305 key, and the last 512 bits are for BLAKE2b-512-MAC key. let mut dk = [u8::default(); DerivedKey::SIZE]; - let argon2 = Argon2::new(ARGON2_ALGORITHM, ARGON2_VERSION, header.params().into()); + let argon2 = Argon2::new( + header.argon2_type().into(), + header.argon2_version().into(), + header.params().into(), + ); #[cfg(feature = "alloc")] argon2 .hash_password_into(passphrase, &header.salt(), &mut dk) @@ -101,7 +156,13 @@ impl<'m> Encryptor<'m> { plaintext, }) }; - inner(plaintext.as_ref(), passphrase.as_ref(), params) + inner( + plaintext.as_ref(), + passphrase.as_ref(), + argon2_type, + argon2_version, + params, + ) } /// Encrypts the plaintext into `buf`. @@ -123,9 +184,9 @@ impl<'m> Encryptor<'m> { /// /// let params = Params::new(32, 3, 4, None).unwrap(); /// let cipher = Encryptor::with_params(data, passphrase, params).unwrap(); - /// let mut buf = [u8::default(); 170]; + /// let mut buf = [u8::default(); 178]; /// cipher.encrypt(&mut buf); - /// # assert_ne!(buf, data.as_slice()); + /// # assert_ne!(buf.as_slice(), data); /// ``` pub fn encrypt(&self, buf: &mut (impl AsMut<[u8]> + ?Sized)) { let inner = |encryptor: &Self, buf: &mut [u8]| { @@ -160,13 +221,13 @@ impl<'m> Encryptor<'m> { /// ``` #[cfg(feature = "alloc")] #[must_use] + #[inline] pub fn encrypt_to_vec(&self) -> alloc::vec::Vec { let mut buf = vec![u8::default(); self.out_len()]; self.encrypt(&mut buf); buf } - #[allow(clippy::missing_panics_doc)] /// Returns the number of output bytes of the encrypted data. /// /// # Examples @@ -179,7 +240,7 @@ impl<'m> Encryptor<'m> { /// /// let params = Params::new(32, 3, 4, None).unwrap(); /// let cipher = Encryptor::with_params(data, passphrase, params).unwrap(); - /// assert_eq!(cipher.out_len(), 170); + /// assert_eq!(cipher.out_len(), 178); /// ``` #[must_use] #[inline] @@ -191,8 +252,10 @@ impl<'m> Encryptor<'m> { /// Encrypts `plaintext` and into a newly allocated [`Vec`](alloc::vec::Vec). /// -/// This uses the [recommended Argon2 parameters] created by -/// [`Params::default`]. +/// This uses the recommended Argon2 parameters according to the [OWASP Password +/// Storage Cheat Sheet] created by [`Params::default`]. This also uses the +/// Argon2 type created by [`Algorithm::default`] and the Argon2 version created +/// by [`Version::default`]. /// /// This is a convenience function for using [`Encryptor::new`] and /// [`Encryptor::encrypt_to_vec`]. @@ -211,8 +274,9 @@ impl<'m> Encryptor<'m> { /// # assert_ne!(ciphertext, data); /// ``` /// -/// [recommended Argon2 parameters]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html +/// [OWASP Password Storage Cheat Sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id #[cfg(feature = "alloc")] +#[inline] pub fn encrypt( plaintext: impl AsRef<[u8]>, passphrase: impl AsRef<[u8]>, @@ -224,6 +288,9 @@ pub fn encrypt( /// Encrypts `plaintext` with the specified [`Params`] and into a newly /// allocated [`Vec`](alloc::vec::Vec). /// +/// This uses the Argon2 type created by [`Algorithm::default`] and the Argon2 +/// version created by [`Version::default`]. +/// /// This is a convenience function for using [`Encryptor::with_params`] and /// [`Encryptor::encrypt_to_vec`]. /// @@ -244,6 +311,7 @@ pub fn encrypt( /// # assert_ne!(ciphertext, data); /// ``` #[cfg(feature = "alloc")] +#[inline] pub fn encrypt_with_params( plaintext: impl AsRef<[u8]>, passphrase: impl AsRef<[u8]>, @@ -251,3 +319,41 @@ pub fn encrypt_with_params( ) -> Result> { Encryptor::with_params(&plaintext, passphrase, params).map(|c| c.encrypt_to_vec()) } + +#[allow(clippy::module_name_repetitions)] +/// Encrypts `plaintext` with the specified [`Algorithm`], [`Version`] and +/// [`Params`] and into a newly allocated [`Vec`](alloc::vec::Vec). +/// +/// This is a convenience function for using [`Encryptor::with_context`] and +/// [`Encryptor::encrypt_to_vec`]. +/// +/// # Errors +/// +/// Returns [`Err`] if the Argon2 context is invalid. +/// +/// # Examples +/// +/// ``` +/// # use abcrypt::argon2::{Algorithm, Params, Version}; +/// # +/// let data = b"Hello, world!\n"; +/// let passphrase = "passphrase"; +/// +/// let params = Params::new(32, 3, 4, None).unwrap(); +/// let ciphertext = +/// abcrypt::encrypt_with_context(data, passphrase, Algorithm::Argon2i, Version::V0x10, params) +/// .unwrap(); +/// # assert_ne!(ciphertext, data); +/// ``` +#[cfg(feature = "alloc")] +#[inline] +pub fn encrypt_with_context( + plaintext: impl AsRef<[u8]>, + passphrase: impl AsRef<[u8]>, + argon2_type: Algorithm, + argon2_version: Version, + params: Params, +) -> Result> { + Encryptor::with_context(&plaintext, passphrase, argon2_type, argon2_version, params) + .map(|c| c.encrypt_to_vec()) +} diff --git a/crates/abcrypt/src/error.rs b/crates/abcrypt/src/error.rs index 2cb47315..73385107 100644 --- a/crates/abcrypt/src/error.rs +++ b/crates/abcrypt/src/error.rs @@ -11,15 +11,24 @@ use blake2::digest::MacError; /// The error type for the abcrypt encrypted data format. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Error { - /// The encrypted data was shorter than 156 bytes. + /// The encrypted data was shorter than 164 bytes. InvalidLength, /// The magic number (file signature) was invalid. InvalidMagicNumber, + /// The version was the unsupported abcrypt version number. + UnsupportedVersion(u8), + /// The version was the unrecognized abcrypt version number. UnknownVersion(u8), + /// The Argon2 type were invalid. + InvalidArgon2Type(u32), + + /// The Argon2 version were invalid. + InvalidArgon2Version(u32), + /// The Argon2 parameters were invalid. InvalidArgon2Params(argon2::Error), @@ -37,9 +46,16 @@ impl fmt::Display for Error { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::InvalidLength => write!(f, "encrypted data is shorter than 156 bytes"), + Self::InvalidLength => write!(f, "encrypted data is shorter than 164 bytes"), Self::InvalidMagicNumber => write!(f, "invalid magic number"), + Self::UnsupportedVersion(version) => { + write!(f, "unsupported version number `{version}`") + } Self::UnknownVersion(version) => write!(f, "unknown version number `{version}`"), + Self::InvalidArgon2Type(_) => write!(f, "invalid Argon2 type"), + Self::InvalidArgon2Version(version) => { + write!(f, "invalid Argon2 version `{version:#x}`") + } Self::InvalidArgon2Params(_) => write!(f, "invalid Argon2 parameters"), Self::InvalidArgon2Context(_) => write!(f, "invalid Argon2 context"), Self::InvalidHeaderMac(_) => write!(f, "invalid header MAC"), @@ -62,12 +78,14 @@ impl std::error::Error for Error { } impl From for Error { + #[inline] fn from(err: MacError) -> Self { Self::InvalidHeaderMac(err) } } impl From for Error { + #[inline] fn from(err: chacha20poly1305::Error) -> Self { Self::InvalidMac(err) } @@ -111,10 +129,22 @@ mod tests { fn clone() { assert_eq!(Error::InvalidLength.clone(), Error::InvalidLength); assert_eq!(Error::InvalidMagicNumber.clone(), Error::InvalidMagicNumber); + assert_eq!( + Error::UnsupportedVersion(u8::MIN).clone(), + Error::UnsupportedVersion(u8::MIN) + ); assert_eq!( Error::UnknownVersion(u8::MAX).clone(), Error::UnknownVersion(u8::MAX) ); + assert_eq!( + Error::InvalidArgon2Type(u32::MAX).clone(), + Error::InvalidArgon2Type(u32::MAX) + ); + assert_eq!( + Error::InvalidArgon2Version(u32::MAX).clone(), + Error::InvalidArgon2Version(u32::MAX) + ); assert_eq!( Error::InvalidArgon2Params(argon2::Error::AdTooLong).clone(), Error::InvalidArgon2Params(argon2::Error::AdTooLong) @@ -147,12 +177,30 @@ mod tests { assert_eq!(a, b); } + { + let a = Error::UnsupportedVersion(u8::MIN); + let b = a; + assert_eq!(a, b); + } + { let a = Error::UnknownVersion(u8::MAX); let b = a; assert_eq!(a, b); } + { + let a = Error::InvalidArgon2Type(u32::MAX); + let b = a; + assert_eq!(a, b); + } + + { + let a = Error::InvalidArgon2Version(u32::MAX); + let b = a; + assert_eq!(a, b); + } + { let a = Error::InvalidArgon2Params(argon2::Error::AdTooLong); let b = a; @@ -186,10 +234,22 @@ mod tests { format!("{:?}", Error::InvalidMagicNumber), "InvalidMagicNumber" ); + assert_eq!( + format!("{:?}", Error::UnsupportedVersion(u8::MIN)), + "UnsupportedVersion(0)" + ); assert_eq!( format!("{:?}", Error::UnknownVersion(u8::MAX)), "UnknownVersion(255)" ); + assert_eq!( + format!("{:?}", Error::InvalidArgon2Type(u32::MAX)), + "InvalidArgon2Type(4294967295)" + ); + assert_eq!( + format!("{:?}", Error::InvalidArgon2Version(u32::MAX)), + "InvalidArgon2Version(4294967295)" + ); assert_eq!( format!("{:?}", Error::InvalidArgon2Params(argon2::Error::AdTooLong)), "InvalidArgon2Params(AdTooLong)" @@ -212,11 +272,13 @@ mod tests { } #[test] - #[allow(clippy::cognitive_complexity, clippy::too_many_lines)] fn equality() { assert_eq!(Error::InvalidLength, Error::InvalidLength); assert_ne!(Error::InvalidLength, Error::InvalidMagicNumber); + assert_ne!(Error::InvalidLength, Error::UnsupportedVersion(u8::MIN)); assert_ne!(Error::InvalidLength, Error::UnknownVersion(u8::MAX)); + assert_ne!(Error::InvalidLength, Error::InvalidArgon2Type(u32::MAX)); + assert_ne!(Error::InvalidLength, Error::InvalidArgon2Version(u32::MAX)); assert_ne!( Error::InvalidLength, Error::InvalidArgon2Params(argon2::Error::AdTooLong) @@ -232,7 +294,19 @@ mod tests { ); assert_ne!(Error::InvalidMagicNumber, Error::InvalidLength); assert_eq!(Error::InvalidMagicNumber, Error::InvalidMagicNumber); + assert_ne!( + Error::InvalidMagicNumber, + Error::UnsupportedVersion(u8::MIN) + ); assert_ne!(Error::InvalidMagicNumber, Error::UnknownVersion(u8::MAX)); + assert_ne!( + Error::InvalidMagicNumber, + Error::InvalidArgon2Type(u32::MAX) + ); + assert_ne!( + Error::InvalidMagicNumber, + Error::InvalidArgon2Version(u32::MAX) + ); assert_ne!( Error::InvalidMagicNumber, Error::InvalidArgon2Params(argon2::Error::AdTooLong) @@ -246,12 +320,61 @@ mod tests { Error::InvalidMagicNumber, Error::InvalidMac(chacha20poly1305::Error) ); + assert_ne!(Error::UnsupportedVersion(u8::MIN), Error::InvalidLength); + assert_ne!( + Error::UnsupportedVersion(u8::MIN), + Error::InvalidMagicNumber + ); + assert_eq!( + Error::UnsupportedVersion(u8::MIN), + Error::UnsupportedVersion(u8::MIN) + ); + assert_ne!( + Error::UnsupportedVersion(u8::MIN), + Error::UnknownVersion(u8::MAX) + ); + assert_ne!( + Error::UnsupportedVersion(u8::MIN), + Error::InvalidArgon2Type(u32::MAX) + ); + assert_ne!( + Error::UnsupportedVersion(u8::MIN), + Error::InvalidArgon2Version(u32::MAX) + ); + assert_ne!( + Error::UnsupportedVersion(u8::MIN), + Error::InvalidArgon2Params(argon2::Error::AdTooLong) + ); + assert_ne!( + Error::UnsupportedVersion(u8::MIN), + Error::InvalidArgon2Context(argon2::Error::AdTooLong) + ); + assert_ne!( + Error::UnsupportedVersion(u8::MIN), + Error::InvalidHeaderMac(MacError) + ); + assert_ne!( + Error::UnsupportedVersion(u8::MIN), + Error::InvalidMac(chacha20poly1305::Error) + ); assert_ne!(Error::UnknownVersion(u8::MAX), Error::InvalidLength); assert_ne!(Error::UnknownVersion(u8::MAX), Error::InvalidMagicNumber); + assert_ne!( + Error::UnknownVersion(u8::MAX), + Error::UnsupportedVersion(u8::MIN) + ); assert_eq!( Error::UnknownVersion(u8::MAX), Error::UnknownVersion(u8::MAX) ); + assert_ne!( + Error::UnknownVersion(u8::MAX), + Error::InvalidArgon2Type(u32::MAX) + ); + assert_ne!( + Error::UnknownVersion(u8::MAX), + Error::InvalidArgon2Version(u32::MAX) + ); assert_ne!( Error::UnknownVersion(u8::MAX), Error::InvalidArgon2Params(argon2::Error::AdTooLong) @@ -268,6 +391,80 @@ mod tests { Error::UnknownVersion(u8::MAX), Error::InvalidMac(chacha20poly1305::Error) ); + assert_ne!(Error::InvalidArgon2Type(u32::MAX), Error::InvalidLength); + assert_ne!( + Error::InvalidArgon2Type(u32::MAX), + Error::InvalidMagicNumber + ); + assert_ne!( + Error::InvalidArgon2Type(u32::MAX), + Error::UnsupportedVersion(u8::MIN) + ); + assert_ne!( + Error::InvalidArgon2Type(u32::MAX), + Error::UnknownVersion(u8::MAX) + ); + assert_eq!( + Error::InvalidArgon2Type(u32::MAX), + Error::InvalidArgon2Type(u32::MAX) + ); + assert_ne!( + Error::InvalidArgon2Type(u32::MAX), + Error::InvalidArgon2Version(u32::MAX) + ); + assert_ne!( + Error::InvalidArgon2Type(u32::MAX), + Error::InvalidArgon2Params(argon2::Error::AdTooLong) + ); + assert_ne!( + Error::InvalidArgon2Type(u32::MAX), + Error::InvalidArgon2Context(argon2::Error::AdTooLong) + ); + assert_ne!( + Error::InvalidArgon2Type(u32::MAX), + Error::InvalidHeaderMac(MacError) + ); + assert_ne!( + Error::InvalidArgon2Type(u32::MAX), + Error::InvalidMac(chacha20poly1305::Error) + ); + assert_ne!(Error::InvalidArgon2Version(u32::MAX), Error::InvalidLength); + assert_ne!( + Error::InvalidArgon2Version(u32::MAX), + Error::InvalidMagicNumber + ); + assert_ne!( + Error::InvalidArgon2Version(u32::MAX), + Error::UnsupportedVersion(u8::MIN) + ); + assert_ne!( + Error::InvalidArgon2Version(u32::MAX), + Error::UnknownVersion(u8::MAX) + ); + assert_ne!( + Error::InvalidArgon2Version(u32::MAX), + Error::InvalidArgon2Type(u32::MAX) + ); + assert_eq!( + Error::InvalidArgon2Version(u32::MAX), + Error::InvalidArgon2Version(u32::MAX) + ); + assert_ne!( + Error::InvalidArgon2Version(u32::MAX), + Error::InvalidArgon2Params(argon2::Error::AdTooLong) + ); + assert_ne!( + Error::InvalidArgon2Version(u32::MAX), + Error::InvalidArgon2Context(argon2::Error::AdTooLong) + ); + assert_ne!( + Error::InvalidArgon2Version(u32::MAX), + Error::InvalidHeaderMac(MacError) + ); + assert_ne!( + Error::InvalidArgon2Version(u32::MAX), + Error::InvalidMac(chacha20poly1305::Error) + ); assert_ne!( Error::InvalidArgon2Params(argon2::Error::AdTooLong), Error::InvalidLength @@ -276,10 +473,22 @@ mod tests { Error::InvalidArgon2Params(argon2::Error::AdTooLong), Error::InvalidMagicNumber ); + assert_ne!( + Error::InvalidArgon2Params(argon2::Error::AdTooLong), + Error::UnsupportedVersion(u8::MIN) + ); assert_ne!( Error::InvalidArgon2Params(argon2::Error::AdTooLong), Error::UnknownVersion(u8::MAX) ); + assert_ne!( + Error::InvalidArgon2Params(argon2::Error::AdTooLong), + Error::InvalidArgon2Type(u32::MAX) + ); + assert_ne!( + Error::InvalidArgon2Params(argon2::Error::AdTooLong), + Error::InvalidArgon2Version(u32::MAX) + ); assert_eq!( Error::InvalidArgon2Params(argon2::Error::AdTooLong), Error::InvalidArgon2Params(argon2::Error::AdTooLong) @@ -304,10 +513,22 @@ mod tests { Error::InvalidArgon2Context(argon2::Error::AdTooLong), Error::InvalidMagicNumber ); + assert_ne!( + Error::InvalidArgon2Context(argon2::Error::AdTooLong), + Error::UnsupportedVersion(u8::MIN) + ); assert_ne!( Error::InvalidArgon2Context(argon2::Error::AdTooLong), Error::UnknownVersion(u8::MAX) ); + assert_ne!( + Error::InvalidArgon2Context(argon2::Error::AdTooLong), + Error::InvalidArgon2Type(u32::MAX) + ); + assert_ne!( + Error::InvalidArgon2Context(argon2::Error::AdTooLong), + Error::InvalidArgon2Version(u32::MAX) + ); assert_ne!( Error::InvalidArgon2Context(argon2::Error::AdTooLong), Error::InvalidArgon2Params(argon2::Error::AdTooLong) @@ -326,10 +547,22 @@ mod tests { ); assert_ne!(Error::InvalidHeaderMac(MacError), Error::InvalidLength); assert_ne!(Error::InvalidHeaderMac(MacError), Error::InvalidMagicNumber); + assert_ne!( + Error::InvalidHeaderMac(MacError), + Error::UnsupportedVersion(u8::MIN) + ); assert_ne!( Error::InvalidHeaderMac(MacError), Error::UnknownVersion(u8::MAX) ); + assert_ne!( + Error::InvalidHeaderMac(MacError), + Error::InvalidArgon2Type(u32::MAX) + ); + assert_ne!( + Error::InvalidHeaderMac(MacError), + Error::InvalidArgon2Version(u32::MAX) + ); assert_ne!( Error::InvalidHeaderMac(MacError), Error::InvalidArgon2Params(argon2::Error::AdTooLong) @@ -354,10 +587,22 @@ mod tests { Error::InvalidMac(chacha20poly1305::Error), Error::InvalidMagicNumber ); + assert_ne!( + Error::InvalidMac(chacha20poly1305::Error), + Error::UnsupportedVersion(u8::MIN) + ); assert_ne!( Error::InvalidMac(chacha20poly1305::Error), Error::UnknownVersion(u8::MAX) ); + assert_ne!( + Error::InvalidMac(chacha20poly1305::Error), + Error::InvalidArgon2Type(u32::MAX) + ); + assert_ne!( + Error::InvalidMac(chacha20poly1305::Error), + Error::InvalidArgon2Version(u32::MAX) + ); assert_ne!( Error::InvalidMac(chacha20poly1305::Error), Error::InvalidArgon2Params(argon2::Error::AdTooLong) @@ -381,16 +626,28 @@ mod tests { fn display() { assert_eq!( format!("{}", Error::InvalidLength), - "encrypted data is shorter than 156 bytes" + "encrypted data is shorter than 164 bytes" ); assert_eq!( format!("{}", Error::InvalidMagicNumber), "invalid magic number" ); + assert_eq!( + format!("{}", Error::UnsupportedVersion(u8::MIN)), + "unsupported version number `0`" + ); assert_eq!( format!("{}", Error::UnknownVersion(u8::MAX)), "unknown version number `255`" ); + assert_eq!( + format!("{}", Error::InvalidArgon2Type(u32::MAX)), + "invalid Argon2 type" + ); + assert_eq!( + format!("{}", Error::InvalidArgon2Version(u32::MAX)), + "invalid Argon2 version `0xffffffff`" + ); assert_eq!( format!("{}", Error::InvalidArgon2Params(argon2::Error::AdTooLong)), "invalid Argon2 parameters" @@ -416,7 +673,10 @@ mod tests { assert!(Error::InvalidLength.source().is_none()); assert!(Error::InvalidMagicNumber.source().is_none()); + assert!(Error::UnsupportedVersion(u8::MIN).source().is_none()); assert!(Error::UnknownVersion(u8::MAX).source().is_none()); + assert!(Error::InvalidArgon2Type(u32::MAX).source().is_none()); + assert!(Error::InvalidArgon2Version(u32::MAX).source().is_none()); assert!(Error::InvalidArgon2Params(argon2::Error::AdTooLong) .source() .unwrap() diff --git a/crates/abcrypt/src/format.rs b/crates/abcrypt/src/format.rs index 0a45e359..b0493d35 100644 --- a/crates/abcrypt/src/format.rs +++ b/crates/abcrypt/src/format.rs @@ -6,6 +6,7 @@ use core::mem; +use argon2::Algorithm; use blake2::{ digest::{self, typenum::Unsigned, Mac, Output, OutputSizeUser}, Blake2bMac512, @@ -15,7 +16,7 @@ use chacha20poly1305::{ }; use rand::{rngs::StdRng, Rng, SeedableRng}; -use crate::{Error, Params, Result}; +use crate::{argon2_context, Error, Params, Result}; /// A type alias for magic number of the abcrypt encrypted data format. type MagicNumber = [u8; 7]; @@ -37,28 +38,42 @@ pub const TAG_SIZE: usize = ::TagSize::USIZE; /// Version of the abcrypt encrypted data format. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -#[non_exhaustive] -pub enum Version { +enum Version { /// Version 0. - #[default] V0, /// Version 1. - #[doc(hidden)] + #[default] V1, } impl From for u8 { + #[inline] fn from(version: Version) -> Self { version as Self } } +impl TryFrom for Version { + type Error = Error; + + #[inline] + fn try_from(version: u8) -> Result { + match version { + 0 => Ok(Self::V0), + 1 => Ok(Self::V1), + v => Err(Error::UnknownVersion(v)), + } + } +} + /// Header of the abcrypt encrypted data format. #[derive(Clone, Debug)] pub struct Header { magic_number: MagicNumber, version: Version, + argon2_type: argon2_context::Variant, + argon2_version: argon2_context::Version, params: Params, salt: Salt, nonce: XNonce, @@ -74,15 +89,23 @@ impl Header { /// The number of bytes of the header. const SIZE: usize = mem::size_of::() + mem::size_of::() + + mem::size_of::() + + mem::size_of::() + mem::size_of::() + mem::size_of::() + ::NonceSize::USIZE + ::OutputSize::USIZE; /// Creates a new `Header`. - pub fn new(params: argon2::Params) -> Self { + pub fn new( + argon2_type: Algorithm, + argon2_version: argon2::Version, + params: argon2::Params, + ) -> Self { let magic_number = Self::MAGIC_NUMBER; let version = Version::default(); + let argon2_type = argon2_type.into(); + let argon2_version = argon2_version.into(); let params = params.into(); let salt = StdRng::from_entropy().gen(); let nonce = XChaCha20Poly1305::generate_nonce(StdRng::from_entropy()); @@ -90,6 +113,8 @@ impl Header { Self { magic_number, version, + argon2_type, + argon2_version, params, salt, nonce, @@ -106,36 +131,50 @@ impl Header { let Some(magic_number) = Some(Self::MAGIC_NUMBER).filter(|mn| &data[..7] == mn) else { return Err(Error::InvalidMagicNumber); }; - let version = match data[7] { - 0 => Version::V0, - v => return Err(Error::UnknownVersion(v)), - }; - let memory_cost = u32::from_le_bytes( + let version = Version::try_from(data[7])?; + if version != Version::V1 { + return Err(Error::UnsupportedVersion(version.into())); + } + let argon2_type = u32::from_le_bytes( data[8..12] + .try_into() + .expect("size of the Argon2 type should be 4 bytes"), + ) + .try_into()?; + let argon2_version = u32::from_le_bytes( + data[12..16] + .try_into() + .expect("size of the Argon2 version should be 4 bytes"), + ) + .try_into()?; + let memory_cost = u32::from_le_bytes( + data[16..20] .try_into() .expect("size of `memoryCost` should be 4 bytes"), ); let time_cost = u32::from_le_bytes( - data[12..16] + data[20..24] .try_into() .expect("size of `timeCost` should be 4 bytes"), ); let parallelism = u32::from_le_bytes( - data[16..20] + data[24..28] .try_into() .expect("size of `parallelism` should be 4 bytes"), ); let params = argon2::Params::new(memory_cost, time_cost, parallelism, None) .map(Params::from) .map_err(Error::InvalidArgon2Params)?; - let salt = data[20..52] + let salt = data[28..60] .try_into() .expect("size of salt should be 32 bytes"); - let nonce = *XNonce::from_slice(&data[52..76]); + let nonce = *XNonce::from_slice(&data[60..84]); let mac = Blake2bMac512Output::default(); Ok(Self { magic_number, version, + argon2_type, + argon2_version, params, salt, nonce, @@ -144,16 +183,17 @@ impl Header { } /// Gets a BLAKE2b-512-MAC of this header. + #[inline] pub fn compute_mac(&mut self, key: &Blake2bMac512Key) { let mut mac = Blake2bMac512::new(key); - mac.update(&self.as_bytes()[..76]); + mac.update(&self.as_bytes()[..84]); self.mac.copy_from_slice(&mac.finalize().into_bytes()); } /// Verifies a BLAKE2b-512-MAC stored in this header. pub fn verify_mac(&mut self, key: &Blake2bMac512Key, tag: &Blake2bMac512Output) -> Result<()> { let mut mac = Blake2bMac512::new(key); - mac.update(&self.as_bytes()[..76]); + mac.update(&self.as_bytes()[..84]); mac.verify(tag)?; self.mac.copy_from_slice(tag); Ok(()) @@ -164,26 +204,43 @@ impl Header { let mut header = [u8::default(); Self::SIZE]; header[..7].copy_from_slice(&self.magic_number); header[7] = self.version.into(); - header[8..12].copy_from_slice(&self.params.memory_cost().to_le_bytes()); - header[12..16].copy_from_slice(&self.params.time_cost().to_le_bytes()); - header[16..20].copy_from_slice(&self.params.parallelism().to_le_bytes()); - header[20..52].copy_from_slice(&self.salt); - header[52..76].copy_from_slice(&self.nonce); - header[76..].copy_from_slice(&self.mac); + header[8..12].copy_from_slice(&u32::from(self.argon2_type).to_le_bytes()); + header[12..16].copy_from_slice(&u32::from(self.argon2_version).to_le_bytes()); + header[16..20].copy_from_slice(&self.params.memory_cost().to_le_bytes()); + header[20..24].copy_from_slice(&self.params.time_cost().to_le_bytes()); + header[24..28].copy_from_slice(&self.params.parallelism().to_le_bytes()); + header[28..60].copy_from_slice(&self.salt); + header[60..84].copy_from_slice(&self.nonce); + header[84..].copy_from_slice(&self.mac); header } + /// Returns the Argon2 type stored in this header. + #[inline] + pub const fn argon2_type(&self) -> argon2_context::Variant { + self.argon2_type + } + + /// Returns the Argon2 version stored in this header. + #[inline] + pub const fn argon2_version(&self) -> argon2_context::Version { + self.argon2_version + } + /// Returns the Argon2 parameters stored in this header. + #[inline] pub const fn params(&self) -> Params { self.params } /// Returns a salt stored in this header. + #[inline] pub const fn salt(&self) -> Salt { self.salt } /// Returns a nonce stored in this header. + #[inline] pub const fn nonce(&self) -> XNonce { self.nonce } @@ -202,6 +259,7 @@ impl DerivedKey { + ::KeySize::USIZE; /// Creates a new `DerivedKey`. + #[inline] pub fn new(dk: [u8; Self::SIZE]) -> Self { let encrypt = *XChaCha20Poly1305Key::from_slice(&dk[..32]); let mac = *Blake2bMac512Key::from_slice(&dk[32..]); @@ -209,11 +267,13 @@ impl DerivedKey { } /// Returns the key for encrypted. + #[inline] pub const fn encrypt(&self) -> XChaCha20Poly1305Key { self.encrypt } /// Returns the key for a MAC. + #[inline] pub const fn mac(&self) -> Blake2bMac512Key { self.mac } @@ -227,7 +287,7 @@ mod tests { #[test] fn header_size() { - assert_eq!(HEADER_SIZE, 140); + assert_eq!(HEADER_SIZE, 148); assert_eq!(HEADER_SIZE, Header::SIZE); } @@ -278,7 +338,7 @@ mod tests { #[test] fn default_version() { - assert_eq!(Version::default(), Version::V0); + assert_eq!(Version::default(), Version::V1); } #[test] @@ -295,6 +355,21 @@ mod tests { assert_eq!(u8::from(Version::V1), 1); } + #[test] + fn try_from_u8_to_version() { + assert_eq!(Version::try_from(0).unwrap(), Version::V0); + assert_eq!(Version::try_from(1).unwrap(), Version::V1); + } + + #[test] + fn try_from_u8_to_version_with_invalid_version() { + assert_eq!(Version::try_from(2).unwrap_err(), Error::UnknownVersion(2)); + assert_eq!( + Version::try_from(u8::MAX).unwrap_err(), + Error::UnknownVersion(u8::MAX) + ); + } + #[test] fn magic_number() { assert_eq!(str::from_utf8(&Header::MAGIC_NUMBER).unwrap(), "abcrypt"); diff --git a/crates/abcrypt/src/lib.rs b/crates/abcrypt/src/lib.rs index f1a8374a..e7926967 100644 --- a/crates/abcrypt/src/lib.rs +++ b/crates/abcrypt/src/lib.rs @@ -5,6 +5,8 @@ //! The `abcrypt` crate is an implementation of the [abcrypt encrypted data //! format]. //! +//! This crate supports the abcrypt version 1 file format. +//! //! # Examples //! //! ## Encryption and decryption @@ -41,7 +43,10 @@ //! limited to 256 KiB when the `alloc` feature is disabled. //! //! ``` -//! use abcrypt::{argon2::Params, Decryptor, Encryptor}; +//! use abcrypt::{ +//! argon2::{Algorithm, Params, Version}, +//! Argon2, Decryptor, Encryptor, +//! }; //! //! let data = b"Hello, world!\n"; //! let passphrase = "passphrase"; @@ -49,15 +54,19 @@ //! // Encrypt `data` using `passphrase`. //! let params = Params::new(32, 3, 4, None).unwrap(); //! let cipher = Encryptor::with_params(data, passphrase, params).unwrap(); -//! let mut buf = [u8::default(); 170]; +//! let mut buf = [u8::default(); 178]; //! cipher.encrypt(&mut buf); -//! assert_ne!(buf, data.as_slice()); +//! assert_ne!(buf.as_slice(), data); +//! +//! let argon2 = Argon2::new(buf).unwrap(); +//! assert_eq!(argon2.variant(), Algorithm::Argon2id); +//! assert_eq!(argon2.version(), Version::V0x13); //! //! // And decrypt it back. //! let cipher = Decryptor::new(&buf, passphrase).unwrap(); //! let mut buf = [u8::default(); 14]; //! cipher.decrypt(&mut buf).unwrap(); -//! assert_eq!(buf, data.as_slice()); +//! assert_eq!(buf, *data); //! ``` //! //! ## Extracting the Argon2 parameters in the encrypted data @@ -85,15 +94,12 @@ //! //! [abcrypt encrypted data format]: https://sorairolake.github.io/abcrypt/book/format.html -#![doc(html_root_url = "https://docs.rs/abcrypt/0.3.7/")] +#![doc(html_root_url = "https://docs.rs/abcrypt/0.4.0/")] #![no_std] #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))] // Lint levels of rustc. #![forbid(unsafe_code)] -#![deny(missing_debug_implementations, missing_docs)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] +#![deny(missing_docs)] #[cfg(feature = "alloc")] #[macro_use] @@ -101,6 +107,7 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; +mod argon2_context; mod decrypt; mod encrypt; mod error; @@ -111,21 +118,19 @@ pub use argon2; pub use blake2; pub use chacha20poly1305; -#[cfg(feature = "alloc")] -pub use crate::{ - decrypt::decrypt, - encrypt::{encrypt, encrypt_with_params}, -}; pub use crate::{ + argon2_context::Argon2, decrypt::Decryptor, encrypt::Encryptor, error::{Error, Result}, format::{HEADER_SIZE, TAG_SIZE}, params::Params, }; - -const ARGON2_ALGORITHM: argon2::Algorithm = argon2::Algorithm::Argon2id; -const ARGON2_VERSION: argon2::Version = argon2::Version::V0x13; +#[cfg(feature = "alloc")] +pub use crate::{ + decrypt::decrypt, + encrypt::{encrypt, encrypt_with_context, encrypt_with_params}, +}; #[cfg(not(feature = "alloc"))] // 1 MiB. diff --git a/crates/abcrypt/src/params.rs b/crates/abcrypt/src/params.rs index b4e31947..80e0cd64 100644 --- a/crates/abcrypt/src/params.rs +++ b/crates/abcrypt/src/params.rs @@ -23,9 +23,12 @@ impl Params { /// /// Returns [`Err`] if any of the following are true: /// - /// - `ciphertext` is shorter than 156 bytes. + /// - `ciphertext` is shorter than 164 bytes. /// - The magic number is invalid. + /// - The version number is the unsupported abcrypt version number. /// - The version number is the unrecognized abcrypt version number. + /// - The Argon2 type is invalid. + /// - The Argon2 version is invalid. /// - The Argon2 parameters are invalid. /// /// # Examples @@ -33,10 +36,11 @@ impl Params { /// ``` /// # use abcrypt::Params; /// # - /// let ciphertext = include_bytes!("../tests/data/data.txt.abcrypt"); + /// let ciphertext = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); /// /// assert!(Params::new(ciphertext).is_ok()); /// ``` + #[inline] pub fn new(ciphertext: impl AsRef<[u8]>) -> Result { let inner = |ciphertext: &[u8]| -> Result { let params = Header::parse(ciphertext).map(|h| h.params())?; @@ -52,7 +56,7 @@ impl Params { /// ``` /// # use abcrypt::Params; /// # - /// let ciphertext = include_bytes!("../tests/data/data.txt.abcrypt"); + /// let ciphertext = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); /// /// let params = Params::new(ciphertext).unwrap(); /// assert_eq!(params.memory_cost(), 32); @@ -70,7 +74,7 @@ impl Params { /// ``` /// # use abcrypt::Params; /// # - /// let ciphertext = include_bytes!("../tests/data/data.txt.abcrypt"); + /// let ciphertext = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); /// /// let params = Params::new(ciphertext).unwrap(); /// assert_eq!(params.time_cost(), 3); @@ -88,7 +92,7 @@ impl Params { /// ``` /// # use abcrypt::Params; /// # - /// let ciphertext = include_bytes!("../tests/data/data.txt.abcrypt"); + /// let ciphertext = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); /// /// let params = Params::new(ciphertext).unwrap(); /// assert_eq!(params.parallelism(), 4); @@ -101,6 +105,7 @@ impl Params { } impl From for argon2::Params { + #[inline] fn from(params: Params) -> Self { Self::new( params.memory_cost(), @@ -113,6 +118,7 @@ impl From for argon2::Params { } impl From for Params { + #[inline] fn from(params: argon2::Params) -> Self { let (memory_cost, time_cost, parallelism) = (params.m_cost(), params.t_cost(), params.p_cost()); diff --git a/crates/abcrypt/tests/argon2_context.rs b/crates/abcrypt/tests/argon2_context.rs new file mode 100644 index 00000000..afa25cff --- /dev/null +++ b/crates/abcrypt/tests/argon2_context.rs @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2024 Shun Sakai +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use abcrypt::{ + argon2::{Algorithm, Version}, + Argon2, +}; + +// Generated using `abcrypt` crate version 0.4.0. +const TEST_DATA_ENC: &[u8] = include_bytes!("data/v1/argon2id/v0x13/data.txt.abcrypt"); + +#[test] +fn success() { + let argon2 = Argon2::new(TEST_DATA_ENC); + assert!(argon2.is_ok()); +} + +#[test] +fn variant() { + { + let argon2 = Argon2::new(include_bytes!("data/v1/argon2d/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2d); + } + { + let argon2 = Argon2::new(include_bytes!("data/v1/argon2d/v0x13/data.txt.abcrypt")).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2d); + } + { + let argon2 = Argon2::new(include_bytes!("data/v1/argon2i/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2i); + } + { + let argon2 = Argon2::new(include_bytes!("data/v1/argon2i/v0x13/data.txt.abcrypt")).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2i); + } + { + let argon2 = + Argon2::new(include_bytes!("data/v1/argon2id/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + } + { + let argon2 = Argon2::new(TEST_DATA_ENC).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + } +} + +#[test] +fn version() { + { + let argon2 = Argon2::new(include_bytes!("data/v1/argon2d/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(argon2.version(), Version::V0x10); + } + { + let argon2 = Argon2::new(include_bytes!("data/v1/argon2d/v0x13/data.txt.abcrypt")).unwrap(); + assert_eq!(argon2.version(), Version::V0x13); + } + { + let argon2 = Argon2::new(include_bytes!("data/v1/argon2i/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(argon2.version(), Version::V0x10); + } + { + let argon2 = Argon2::new(include_bytes!("data/v1/argon2i/v0x13/data.txt.abcrypt")).unwrap(); + assert_eq!(argon2.version(), Version::V0x13); + } + { + let argon2 = + Argon2::new(include_bytes!("data/v1/argon2id/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(argon2.version(), Version::V0x10); + } + { + let argon2 = Argon2::new(TEST_DATA_ENC).unwrap(); + assert_eq!(argon2.version(), Version::V0x13); + } +} diff --git a/crates/abcrypt/tests/data/data.txt.abcrypt b/crates/abcrypt/tests/data/v0/data.txt.abcrypt similarity index 100% rename from crates/abcrypt/tests/data/data.txt.abcrypt rename to crates/abcrypt/tests/data/v0/data.txt.abcrypt diff --git a/crates/abcrypt/tests/data/data.txt.abcrypt.license b/crates/abcrypt/tests/data/v0/data.txt.abcrypt.license similarity index 100% rename from crates/abcrypt/tests/data/data.txt.abcrypt.license rename to crates/abcrypt/tests/data/v0/data.txt.abcrypt.license diff --git a/crates/abcrypt/tests/data/v1/argon2d/v0x10/data.txt.abcrypt b/crates/abcrypt/tests/data/v1/argon2d/v0x10/data.txt.abcrypt new file mode 100644 index 00000000..c34dca4a Binary files /dev/null and b/crates/abcrypt/tests/data/v1/argon2d/v0x10/data.txt.abcrypt differ diff --git a/crates/abcrypt/tests/data/v1/argon2d/v0x10/data.txt.abcrypt.license b/crates/abcrypt/tests/data/v1/argon2d/v0x10/data.txt.abcrypt.license new file mode 100644 index 00000000..df26b1a7 --- /dev/null +++ b/crates/abcrypt/tests/data/v1/argon2d/v0x10/data.txt.abcrypt.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Shun Sakai + +SPDX-License-Identifier: Apache-2.0 OR MIT diff --git a/crates/abcrypt/tests/data/v1/argon2d/v0x13/data.txt.abcrypt b/crates/abcrypt/tests/data/v1/argon2d/v0x13/data.txt.abcrypt new file mode 100644 index 00000000..7839ca69 Binary files /dev/null and b/crates/abcrypt/tests/data/v1/argon2d/v0x13/data.txt.abcrypt differ diff --git a/crates/abcrypt/tests/data/v1/argon2d/v0x13/data.txt.abcrypt.license b/crates/abcrypt/tests/data/v1/argon2d/v0x13/data.txt.abcrypt.license new file mode 100644 index 00000000..df26b1a7 --- /dev/null +++ b/crates/abcrypt/tests/data/v1/argon2d/v0x13/data.txt.abcrypt.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Shun Sakai + +SPDX-License-Identifier: Apache-2.0 OR MIT diff --git a/crates/abcrypt/tests/data/v1/argon2i/v0x10/data.txt.abcrypt b/crates/abcrypt/tests/data/v1/argon2i/v0x10/data.txt.abcrypt new file mode 100644 index 00000000..3c470deb Binary files /dev/null and b/crates/abcrypt/tests/data/v1/argon2i/v0x10/data.txt.abcrypt differ diff --git a/crates/abcrypt/tests/data/v1/argon2i/v0x10/data.txt.abcrypt.license b/crates/abcrypt/tests/data/v1/argon2i/v0x10/data.txt.abcrypt.license new file mode 100644 index 00000000..df26b1a7 --- /dev/null +++ b/crates/abcrypt/tests/data/v1/argon2i/v0x10/data.txt.abcrypt.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Shun Sakai + +SPDX-License-Identifier: Apache-2.0 OR MIT diff --git a/crates/abcrypt/tests/data/v1/argon2i/v0x13/data.txt.abcrypt b/crates/abcrypt/tests/data/v1/argon2i/v0x13/data.txt.abcrypt new file mode 100644 index 00000000..d8938583 Binary files /dev/null and b/crates/abcrypt/tests/data/v1/argon2i/v0x13/data.txt.abcrypt differ diff --git a/crates/abcrypt/tests/data/v1/argon2i/v0x13/data.txt.abcrypt.license b/crates/abcrypt/tests/data/v1/argon2i/v0x13/data.txt.abcrypt.license new file mode 100644 index 00000000..df26b1a7 --- /dev/null +++ b/crates/abcrypt/tests/data/v1/argon2i/v0x13/data.txt.abcrypt.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Shun Sakai + +SPDX-License-Identifier: Apache-2.0 OR MIT diff --git a/crates/abcrypt/tests/data/v1/argon2id/v0x10/data.txt.abcrypt b/crates/abcrypt/tests/data/v1/argon2id/v0x10/data.txt.abcrypt new file mode 100644 index 00000000..e44a4bd3 Binary files /dev/null and b/crates/abcrypt/tests/data/v1/argon2id/v0x10/data.txt.abcrypt differ diff --git a/crates/abcrypt/tests/data/v1/argon2id/v0x10/data.txt.abcrypt.license b/crates/abcrypt/tests/data/v1/argon2id/v0x10/data.txt.abcrypt.license new file mode 100644 index 00000000..df26b1a7 --- /dev/null +++ b/crates/abcrypt/tests/data/v1/argon2id/v0x10/data.txt.abcrypt.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Shun Sakai + +SPDX-License-Identifier: Apache-2.0 OR MIT diff --git a/crates/abcrypt/tests/data/v1/argon2id/v0x13/data.txt.abcrypt b/crates/abcrypt/tests/data/v1/argon2id/v0x13/data.txt.abcrypt new file mode 100644 index 00000000..a0a58380 Binary files /dev/null and b/crates/abcrypt/tests/data/v1/argon2id/v0x13/data.txt.abcrypt differ diff --git a/crates/abcrypt/tests/data/v1/argon2id/v0x13/data.txt.abcrypt.license b/crates/abcrypt/tests/data/v1/argon2id/v0x13/data.txt.abcrypt.license new file mode 100644 index 00000000..df26b1a7 --- /dev/null +++ b/crates/abcrypt/tests/data/v1/argon2id/v0x13/data.txt.abcrypt.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Shun Sakai + +SPDX-License-Identifier: Apache-2.0 OR MIT diff --git a/crates/abcrypt/tests/decrypt.rs b/crates/abcrypt/tests/decrypt.rs index 1279e61f..0be29170 100644 --- a/crates/abcrypt/tests/decrypt.rs +++ b/crates/abcrypt/tests/decrypt.rs @@ -2,28 +2,78 @@ // // SPDX-License-Identifier: Apache-2.0 OR MIT -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] - use abcrypt::{ argon2, blake2::digest::MacError, chacha20poly1305, Decryptor, Error, HEADER_SIZE, TAG_SIZE, }; const PASSPHRASE: &str = "passphrase"; const TEST_DATA: &[u8] = include_bytes!("data/data.txt"); -// Generated using `abcrypt` crate version 0.1.0. -const TEST_DATA_ENC: &[u8] = include_bytes!("data/data.txt.abcrypt"); +// Generated using `abcrypt` crate version 0.4.0. +const TEST_DATA_ENC: &[u8] = include_bytes!("data/v1/argon2id/v0x13/data.txt.abcrypt"); #[test] fn success() { - let cipher = Decryptor::new(&TEST_DATA_ENC, PASSPHRASE).unwrap(); - let mut buf = [u8::default(); TEST_DATA.len()]; - cipher.decrypt(&mut buf).unwrap(); - assert_eq!(buf, TEST_DATA); + #[cfg(feature = "alloc")] + { + let cipher = Decryptor::new( + &include_bytes!("data/v1/argon2d/v0x10/data.txt.abcrypt"), + PASSPHRASE, + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + #[cfg(feature = "alloc")] + { + let cipher = Decryptor::new( + &include_bytes!("data/v1/argon2d/v0x13/data.txt.abcrypt"), + PASSPHRASE, + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + #[cfg(feature = "alloc")] + { + let cipher = Decryptor::new( + &include_bytes!("data/v1/argon2i/v0x10/data.txt.abcrypt"), + PASSPHRASE, + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + #[cfg(feature = "alloc")] + { + let cipher = Decryptor::new( + &include_bytes!("data/v1/argon2i/v0x13/data.txt.abcrypt"), + PASSPHRASE, + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + #[cfg(feature = "alloc")] + { + let cipher = Decryptor::new( + &include_bytes!("data/v1/argon2id/v0x10/data.txt.abcrypt"), + PASSPHRASE, + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + { + let cipher = Decryptor::new(&TEST_DATA_ENC, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } } #[cfg(feature = "alloc")] @@ -72,12 +122,18 @@ fn invalid_magic_number() { assert_eq!(err, Error::InvalidMagicNumber); } +#[test] +fn unsupported_version() { + let err = Decryptor::new(include_bytes!("data/v0/data.txt.abcrypt"), PASSPHRASE).unwrap_err(); + assert_eq!(err, Error::UnsupportedVersion(0)); +} + #[test] fn unknown_version() { let mut data: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); - data[7] = 1; + data[7] = 2; let err = Decryptor::new(&data, PASSPHRASE).unwrap_err(); - assert_eq!(err, Error::UnknownVersion(1)); + assert_eq!(err, Error::UnknownVersion(2)); } #[test] @@ -85,7 +141,7 @@ fn invalid_params() { let mut data: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); { - data[8..12].copy_from_slice(&u32::to_le_bytes(7)); + data[16..20].copy_from_slice(&u32::to_le_bytes(7)); let err = Decryptor::new(&data, PASSPHRASE).unwrap_err(); assert_eq!( err, @@ -94,7 +150,7 @@ fn invalid_params() { } { - data[12..16].copy_from_slice(&u32::to_le_bytes(0)); + data[20..24].copy_from_slice(&u32::to_le_bytes(0)); let err = Decryptor::new(&data, PASSPHRASE).unwrap_err(); assert_eq!( err, @@ -103,7 +159,7 @@ fn invalid_params() { } { - data[16..20].copy_from_slice(&u32::pow(2, 24).to_le_bytes()); + data[24..28].copy_from_slice(&u32::pow(2, 24).to_le_bytes()); let err = Decryptor::new(&data, PASSPHRASE).unwrap_err(); assert_eq!( err, @@ -116,9 +172,9 @@ fn invalid_params() { #[test] fn too_large_memory_blocks() { let mut data: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); - data[8..12].copy_from_slice(&u32::to_le_bytes(abcrypt::argon2::Params::DEFAULT_M_COST)); - data[12..16].copy_from_slice(&u32::to_le_bytes(abcrypt::argon2::Params::DEFAULT_T_COST)); - data[16..20].copy_from_slice(&u32::to_le_bytes(abcrypt::argon2::Params::DEFAULT_P_COST)); + data[16..20].copy_from_slice(&u32::to_le_bytes(argon2::Params::DEFAULT_M_COST)); + data[20..24].copy_from_slice(&u32::to_le_bytes(argon2::Params::DEFAULT_T_COST)); + data[24..28].copy_from_slice(&u32::to_le_bytes(argon2::Params::DEFAULT_P_COST)); let err = Decryptor::new(&data, PASSPHRASE).unwrap_err(); assert_eq!( err, @@ -129,9 +185,9 @@ fn too_large_memory_blocks() { #[test] fn invalid_header_mac() { let mut data: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); - let mut header_mac: [u8; 64] = data[76..140].try_into().unwrap(); + let mut header_mac: [u8; 64] = data[84..148].try_into().unwrap(); header_mac.reverse(); - data[76..140].copy_from_slice(&header_mac); + data[84..148].copy_from_slice(&header_mac); let err = Decryptor::new(&data, PASSPHRASE).unwrap_err(); assert_eq!(err, MacError.into()); } @@ -159,6 +215,48 @@ fn out_len() { #[cfg(feature = "alloc")] #[test] fn success_convenience_function() { - let plaintext = abcrypt::decrypt(TEST_DATA_ENC, PASSPHRASE).unwrap(); - assert_eq!(plaintext, TEST_DATA); + { + let plaintext = abcrypt::decrypt( + include_bytes!("data/v1/argon2d/v0x10/data.txt.abcrypt"), + PASSPHRASE, + ) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let plaintext = abcrypt::decrypt( + include_bytes!("data/v1/argon2d/v0x13/data.txt.abcrypt"), + PASSPHRASE, + ) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let plaintext = abcrypt::decrypt( + include_bytes!("data/v1/argon2i/v0x10/data.txt.abcrypt"), + PASSPHRASE, + ) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let plaintext = abcrypt::decrypt( + include_bytes!("data/v1/argon2i/v0x13/data.txt.abcrypt"), + PASSPHRASE, + ) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let plaintext = abcrypt::decrypt( + include_bytes!("data/v1/argon2id/v0x10/data.txt.abcrypt"), + PASSPHRASE, + ) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let plaintext = abcrypt::decrypt(TEST_DATA_ENC, PASSPHRASE).unwrap(); + assert_eq!(plaintext, TEST_DATA); + } } diff --git a/crates/abcrypt/tests/encrypt.rs b/crates/abcrypt/tests/encrypt.rs index 702ba32f..45935b4c 100644 --- a/crates/abcrypt/tests/encrypt.rs +++ b/crates/abcrypt/tests/encrypt.rs @@ -2,14 +2,10 @@ // // SPDX-License-Identifier: Apache-2.0 OR MIT -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] - -use abcrypt::{argon2::Params, Decryptor, Encryptor, HEADER_SIZE, TAG_SIZE}; +use abcrypt::{ + argon2::{Algorithm, Params, Version}, + Argon2, Decryptor, Encryptor, HEADER_SIZE, TAG_SIZE, +}; const PASSPHRASE: &str = "passphrase"; const TEST_DATA: &[u8] = include_bytes!("data/data.txt"); @@ -22,6 +18,10 @@ fn success() { cipher.encrypt(&mut buf); assert_ne!(buf, TEST_DATA); + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + assert_eq!(argon2.version(), Version::V0x13); + let params = abcrypt::Params::new(buf).unwrap(); assert_eq!(params.memory_cost(), 19456); assert_eq!(params.time_cost(), 2); @@ -42,6 +42,10 @@ fn success_with_params() { cipher.encrypt(&mut buf); assert_ne!(buf, TEST_DATA); + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + assert_eq!(argon2.version(), Version::V0x13); + let params = abcrypt::Params::new(buf).unwrap(); assert_eq!(params.memory_cost(), 32); assert_eq!(params.time_cost(), 3); @@ -53,6 +57,177 @@ fn success_with_params() { assert_eq!(buf, TEST_DATA); } +#[test] +fn success_with_context() { + #[cfg(feature = "alloc")] + { + let cipher = Encryptor::with_context( + &TEST_DATA, + PASSPHRASE, + Algorithm::Argon2d, + Version::V0x10, + Params::new(47104, 1, 1, None).unwrap(), + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + cipher.encrypt(&mut buf); + assert_ne!(buf, TEST_DATA); + + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2d); + assert_eq!(argon2.version(), Version::V0x10); + + let params = abcrypt::Params::new(buf).unwrap(); + assert_eq!(params.memory_cost(), 47104); + assert_eq!(params.time_cost(), 1); + assert_eq!(params.parallelism(), 1); + + let cipher = Decryptor::new(&buf, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + #[cfg(feature = "alloc")] + { + let cipher = Encryptor::with_context( + &TEST_DATA, + PASSPHRASE, + Algorithm::Argon2d, + Version::V0x13, + Params::new(19456, 2, 1, None).unwrap(), + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + cipher.encrypt(&mut buf); + assert_ne!(buf, TEST_DATA); + + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2d); + assert_eq!(argon2.version(), Version::V0x13); + + let params = abcrypt::Params::new(buf).unwrap(); + assert_eq!(params.memory_cost(), 19456); + assert_eq!(params.time_cost(), 2); + assert_eq!(params.parallelism(), 1); + + let cipher = Decryptor::new(&buf, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + #[cfg(feature = "alloc")] + { + let cipher = Encryptor::with_context( + &TEST_DATA, + PASSPHRASE, + Algorithm::Argon2i, + Version::V0x10, + Params::new(12288, 3, 1, None).unwrap(), + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + cipher.encrypt(&mut buf); + assert_ne!(buf, TEST_DATA); + + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2i); + assert_eq!(argon2.version(), Version::V0x10); + + let params = abcrypt::Params::new(buf).unwrap(); + assert_eq!(params.memory_cost(), 12288); + assert_eq!(params.time_cost(), 3); + assert_eq!(params.parallelism(), 1); + + let cipher = Decryptor::new(&buf, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + #[cfg(feature = "alloc")] + { + let cipher = Encryptor::with_context( + &TEST_DATA, + PASSPHRASE, + Algorithm::Argon2i, + Version::V0x13, + Params::new(9216, 4, 1, None).unwrap(), + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + cipher.encrypt(&mut buf); + assert_ne!(buf, TEST_DATA); + + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2i); + assert_eq!(argon2.version(), Version::V0x13); + + let params = abcrypt::Params::new(buf).unwrap(); + assert_eq!(params.memory_cost(), 9216); + assert_eq!(params.time_cost(), 4); + assert_eq!(params.parallelism(), 1); + + let cipher = Decryptor::new(&buf, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + #[cfg(feature = "alloc")] + { + let cipher = Encryptor::with_context( + &TEST_DATA, + PASSPHRASE, + Algorithm::Argon2id, + Version::V0x10, + Params::new(7168, 5, 1, None).unwrap(), + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + cipher.encrypt(&mut buf); + assert_ne!(buf, TEST_DATA); + + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + assert_eq!(argon2.version(), Version::V0x10); + + let params = abcrypt::Params::new(buf).unwrap(); + assert_eq!(params.memory_cost(), 7168); + assert_eq!(params.time_cost(), 5); + assert_eq!(params.parallelism(), 1); + + let cipher = Decryptor::new(&buf, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + { + let cipher = Encryptor::with_context( + &TEST_DATA, + PASSPHRASE, + Algorithm::Argon2id, + Version::V0x13, + Params::new(32, 3, 4, None).unwrap(), + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + cipher.encrypt(&mut buf); + assert_ne!(buf, TEST_DATA); + + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + assert_eq!(argon2.version(), Version::V0x13); + + let params = abcrypt::Params::new(buf).unwrap(); + assert_eq!(params.memory_cost(), 32); + assert_eq!(params.time_cost(), 3); + assert_eq!(params.parallelism(), 4); + + let cipher = Decryptor::new(&buf, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } +} + #[cfg(feature = "alloc")] #[test] fn success_to_vec() { @@ -110,7 +285,95 @@ fn version() { .unwrap(); let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; cipher.encrypt(&mut buf); - assert_eq!(buf[7], 0); + assert_eq!(buf[7], 1); +} + +#[test] +fn argon2_type() { + { + let cipher = Encryptor::with_context( + &TEST_DATA, + PASSPHRASE, + Algorithm::Argon2d, + Version::default(), + Params::new(32, 3, 4, None).unwrap(), + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + cipher.encrypt(&mut buf); + assert_eq!(&buf[8..12], u32::to_le_bytes(0)); + + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2d); + } + { + let cipher = Encryptor::with_context( + &TEST_DATA, + PASSPHRASE, + Algorithm::Argon2i, + Version::default(), + Params::new(32, 3, 4, None).unwrap(), + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + cipher.encrypt(&mut buf); + assert_eq!(&buf[8..12], u32::to_le_bytes(1)); + + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2i); + } + { + let cipher = Encryptor::with_context( + &TEST_DATA, + PASSPHRASE, + Algorithm::Argon2id, + Version::default(), + Params::new(32, 3, 4, None).unwrap(), + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + cipher.encrypt(&mut buf); + assert_eq!(&buf[8..12], u32::to_le_bytes(2)); + + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + } +} + +#[test] +fn argon2_version() { + { + let cipher = Encryptor::with_context( + &TEST_DATA, + PASSPHRASE, + Algorithm::default(), + Version::V0x10, + Params::new(32, 3, 4, None).unwrap(), + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + cipher.encrypt(&mut buf); + assert_eq!(&buf[12..16], u32::to_le_bytes(0x10)); + + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.version(), Version::V0x10); + } + { + let cipher = Encryptor::with_context( + &TEST_DATA, + PASSPHRASE, + Algorithm::default(), + Version::V0x13, + Params::new(32, 3, 4, None).unwrap(), + ) + .unwrap(); + let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + cipher.encrypt(&mut buf); + assert_eq!(&buf[12..16], u32::to_le_bytes(0x13)); + + let argon2 = Argon2::new(buf).unwrap(); + assert_eq!(argon2.version(), Version::V0x13); + } } #[test] @@ -120,7 +383,10 @@ fn memory_cost() { .unwrap(); let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; cipher.encrypt(&mut buf); - assert_eq!(&buf[8..12], u32::to_le_bytes(32)); + assert_eq!(&buf[16..20], u32::to_le_bytes(32)); + + let params = abcrypt::Params::new(buf).unwrap(); + assert_eq!(params.memory_cost(), 32); } #[test] @@ -130,7 +396,10 @@ fn time_cost() { .unwrap(); let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; cipher.encrypt(&mut buf); - assert_eq!(&buf[12..16], u32::to_le_bytes(3)); + assert_eq!(&buf[20..24], u32::to_le_bytes(3)); + + let params = abcrypt::Params::new(buf).unwrap(); + assert_eq!(params.time_cost(), 3); } #[test] @@ -140,7 +409,10 @@ fn parallelism() { .unwrap(); let mut buf = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; cipher.encrypt(&mut buf); - assert_eq!(&buf[16..20], u32::to_le_bytes(4)); + assert_eq!(&buf[24..28], u32::to_le_bytes(4)); + + let params = abcrypt::Params::new(buf).unwrap(); + assert_eq!(params.parallelism(), 4); } #[cfg(not(feature = "alloc"))] @@ -179,13 +451,19 @@ fn success_convenience_function() { assert_ne!(ciphertext, TEST_DATA); assert_eq!(ciphertext.len(), TEST_DATA.len() + HEADER_SIZE + TAG_SIZE); + let argon2 = Argon2::new(&ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + assert_eq!(argon2.version(), Version::V0x13); + let params = abcrypt::Params::new(&ciphertext).unwrap(); assert_eq!(params.memory_cost(), 19456); assert_eq!(params.time_cost(), 2); assert_eq!(params.parallelism(), 1); - let plaintext = abcrypt::decrypt(ciphertext, PASSPHRASE).unwrap(); - assert_eq!(plaintext, TEST_DATA); + let cipher = Decryptor::new(&ciphertext, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); } #[cfg(feature = "alloc")] @@ -197,11 +475,178 @@ fn success_convenience_function_with_params() { assert_ne!(ciphertext, TEST_DATA); assert_eq!(ciphertext.len(), TEST_DATA.len() + HEADER_SIZE + TAG_SIZE); + let argon2 = Argon2::new(&ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + assert_eq!(argon2.version(), Version::V0x13); + let params = abcrypt::Params::new(&ciphertext).unwrap(); assert_eq!(params.memory_cost(), 32); assert_eq!(params.time_cost(), 3); assert_eq!(params.parallelism(), 4); - let plaintext = abcrypt::decrypt(ciphertext, PASSPHRASE).unwrap(); - assert_eq!(plaintext, TEST_DATA); + let cipher = Decryptor::new(&ciphertext, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); +} + +#[cfg(feature = "alloc")] +#[test] +fn success_convenience_function_with_context() { + { + let ciphertext = abcrypt::encrypt_with_context( + TEST_DATA, + PASSPHRASE, + Algorithm::Argon2d, + Version::V0x10, + Params::new(47104, 1, 1, None).unwrap(), + ) + .unwrap(); + assert_ne!(ciphertext, TEST_DATA); + assert_eq!(ciphertext.len(), TEST_DATA.len() + HEADER_SIZE + TAG_SIZE); + + let argon2 = Argon2::new(&ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2d); + assert_eq!(argon2.version(), Version::V0x10); + + let params = abcrypt::Params::new(&ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 47104); + assert_eq!(params.time_cost(), 1); + assert_eq!(params.parallelism(), 1); + + let cipher = Decryptor::new(&ciphertext, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + { + let ciphertext = abcrypt::encrypt_with_context( + TEST_DATA, + PASSPHRASE, + Algorithm::Argon2d, + Version::V0x13, + Params::new(19456, 2, 1, None).unwrap(), + ) + .unwrap(); + assert_ne!(ciphertext, TEST_DATA); + assert_eq!(ciphertext.len(), TEST_DATA.len() + HEADER_SIZE + TAG_SIZE); + + let argon2 = Argon2::new(&ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2d); + assert_eq!(argon2.version(), Version::V0x13); + + let params = abcrypt::Params::new(&ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 19456); + assert_eq!(params.time_cost(), 2); + assert_eq!(params.parallelism(), 1); + + let cipher = Decryptor::new(&ciphertext, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + { + let ciphertext = abcrypt::encrypt_with_context( + TEST_DATA, + PASSPHRASE, + Algorithm::Argon2i, + Version::V0x10, + Params::new(12288, 3, 1, None).unwrap(), + ) + .unwrap(); + assert_ne!(ciphertext, TEST_DATA); + assert_eq!(ciphertext.len(), TEST_DATA.len() + HEADER_SIZE + TAG_SIZE); + + let argon2 = Argon2::new(&ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2i); + assert_eq!(argon2.version(), Version::V0x10); + + let params = abcrypt::Params::new(&ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 12288); + assert_eq!(params.time_cost(), 3); + assert_eq!(params.parallelism(), 1); + + let cipher = Decryptor::new(&ciphertext, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + { + let ciphertext = abcrypt::encrypt_with_context( + TEST_DATA, + PASSPHRASE, + Algorithm::Argon2i, + Version::V0x13, + Params::new(9216, 4, 1, None).unwrap(), + ) + .unwrap(); + assert_ne!(ciphertext, TEST_DATA); + assert_eq!(ciphertext.len(), TEST_DATA.len() + HEADER_SIZE + TAG_SIZE); + + let argon2 = Argon2::new(&ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2i); + assert_eq!(argon2.version(), Version::V0x13); + + let params = abcrypt::Params::new(&ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 9216); + assert_eq!(params.time_cost(), 4); + assert_eq!(params.parallelism(), 1); + + let cipher = Decryptor::new(&ciphertext, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + { + let ciphertext = abcrypt::encrypt_with_context( + TEST_DATA, + PASSPHRASE, + Algorithm::Argon2id, + Version::V0x10, + Params::new(7168, 5, 1, None).unwrap(), + ) + .unwrap(); + assert_ne!(ciphertext, TEST_DATA); + assert_eq!(ciphertext.len(), TEST_DATA.len() + HEADER_SIZE + TAG_SIZE); + + let argon2 = Argon2::new(&ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + assert_eq!(argon2.version(), Version::V0x10); + + let params = abcrypt::Params::new(&ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 7168); + assert_eq!(params.time_cost(), 5); + assert_eq!(params.parallelism(), 1); + + let cipher = Decryptor::new(&ciphertext, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } + { + let ciphertext = abcrypt::encrypt_with_context( + TEST_DATA, + PASSPHRASE, + Algorithm::Argon2id, + Version::V0x13, + Params::new(32, 3, 4, None).unwrap(), + ) + .unwrap(); + assert_ne!(ciphertext, TEST_DATA); + assert_eq!(ciphertext.len(), TEST_DATA.len() + HEADER_SIZE + TAG_SIZE); + + let argon2 = Argon2::new(&ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + assert_eq!(argon2.version(), Version::V0x13); + + let params = abcrypt::Params::new(&ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 32); + assert_eq!(params.time_cost(), 3); + assert_eq!(params.parallelism(), 4); + + let cipher = Decryptor::new(&ciphertext, PASSPHRASE).unwrap(); + let mut buf = [u8::default(); TEST_DATA.len()]; + cipher.decrypt(&mut buf).unwrap(); + assert_eq!(buf, TEST_DATA); + } } diff --git a/crates/abcrypt/tests/params.rs b/crates/abcrypt/tests/params.rs index 2302781c..a34caf4b 100644 --- a/crates/abcrypt/tests/params.rs +++ b/crates/abcrypt/tests/params.rs @@ -2,17 +2,10 @@ // // SPDX-License-Identifier: Apache-2.0 OR MIT -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] - use abcrypt::Params; -// Generated using `abcrypt` crate version 0.1.0. -const TEST_DATA_ENC: &[u8] = include_bytes!("data/data.txt.abcrypt"); +// Generated using `abcrypt` crate version 0.4.0. +const TEST_DATA_ENC: &[u8] = include_bytes!("data/v1/argon2id/v0x13/data.txt.abcrypt"); #[test] fn success() { @@ -22,20 +15,89 @@ fn success() { #[test] fn memory_cost() { - let params = Params::new(TEST_DATA_ENC).unwrap(); - assert_eq!(params.memory_cost(), 32); + { + let params = Params::new(include_bytes!("data/v1/argon2d/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(params.memory_cost(), 47104); + } + { + let params = Params::new(include_bytes!("data/v1/argon2d/v0x13/data.txt.abcrypt")).unwrap(); + assert_eq!(params.memory_cost(), 19456); + } + { + let params = Params::new(include_bytes!("data/v1/argon2i/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(params.memory_cost(), 12288); + } + { + let params = Params::new(include_bytes!("data/v1/argon2i/v0x13/data.txt.abcrypt")).unwrap(); + assert_eq!(params.memory_cost(), 9216); + } + { + let params = + Params::new(include_bytes!("data/v1/argon2id/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(params.memory_cost(), 7168); + } + { + let params = Params::new(TEST_DATA_ENC).unwrap(); + assert_eq!(params.memory_cost(), 32); + } } #[test] fn time_cost() { - let params = Params::new(TEST_DATA_ENC).unwrap(); - assert_eq!(params.time_cost(), 3); + { + let params = Params::new(include_bytes!("data/v1/argon2d/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(params.time_cost(), 1); + } + { + let params = Params::new(include_bytes!("data/v1/argon2d/v0x13/data.txt.abcrypt")).unwrap(); + assert_eq!(params.time_cost(), 2); + } + { + let params = Params::new(include_bytes!("data/v1/argon2i/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(params.time_cost(), 3); + } + { + let params = Params::new(include_bytes!("data/v1/argon2i/v0x13/data.txt.abcrypt")).unwrap(); + assert_eq!(params.time_cost(), 4); + } + { + let params = + Params::new(include_bytes!("data/v1/argon2id/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(params.time_cost(), 5); + } + { + let params = Params::new(TEST_DATA_ENC).unwrap(); + assert_eq!(params.time_cost(), 3); + } } #[test] fn parallelism() { - let params = Params::new(TEST_DATA_ENC).unwrap(); - assert_eq!(params.parallelism(), 4); + { + let params = Params::new(include_bytes!("data/v1/argon2d/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(params.parallelism(), 1); + } + { + let params = Params::new(include_bytes!("data/v1/argon2d/v0x13/data.txt.abcrypt")).unwrap(); + assert_eq!(params.parallelism(), 1); + } + { + let params = Params::new(include_bytes!("data/v1/argon2i/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(params.parallelism(), 1); + } + { + let params = Params::new(include_bytes!("data/v1/argon2i/v0x13/data.txt.abcrypt")).unwrap(); + assert_eq!(params.parallelism(), 1); + } + { + let params = + Params::new(include_bytes!("data/v1/argon2id/v0x10/data.txt.abcrypt")).unwrap(); + assert_eq!(params.parallelism(), 1); + } + { + let params = Params::new(TEST_DATA_ENC).unwrap(); + assert_eq!(params.parallelism(), 4); + } } #[cfg(feature = "serde")] diff --git a/crates/capi/CHANGELOG.adoc b/crates/capi/CHANGELOG.adoc index cde9611c..43ec0a87 100644 --- a/crates/capi/CHANGELOG.adoc +++ b/crates/capi/CHANGELOG.adoc @@ -16,11 +16,20 @@ project adheres to https://semver.org/[Semantic Versioning]. == {compare-url}/abcrypt-capi-v0.3.2\...HEAD[Unreleased] +=== Added + +* Supports the abcrypt version 1 file format ({pull-request-url}/619[#619]) + === Changed * Re-enable `clang-format` and `clang-tidy` on CI ({pull-request-url}/384[#384]) * Use `extern "C-unwind"` instead of `extern "C"` ({pull-request-url}/542[#542]) +=== Removed + +* Remove the abcrypt version 0 file format support + ({pull-request-url}/619[#619]) + == {compare-url}/abcrypt-capi-v0.3.1\...abcrypt-capi-v0.3.2[0.3.2] - 2024-04-16 === Fixed diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml index 2fc963ef..7918b1bc 100644 --- a/crates/capi/Cargo.toml +++ b/crates/capi/Cargo.toml @@ -24,7 +24,10 @@ include = ["/LICENSES", "/README.md", "/src"] crate-type = ["staticlib", "cdylib"] [dependencies] -abcrypt = { version = "0.3.7", path = "../abcrypt" } +abcrypt = { version = "0.4.0", path = "../abcrypt" } [build-dependencies] cbindgen = { version = "0.27.0", default-features = false } + +[lints] +workspace = true diff --git a/crates/capi/README.md b/crates/capi/README.md index 0e3d1022..e4c5a540 100644 --- a/crates/capi/README.md +++ b/crates/capi/README.md @@ -62,9 +62,13 @@ Please see [CHANGELOG.adoc]. Please see [CONTRIBUTING.adoc]. +## Home page + + + ## License -Copyright © 2022–2024 Shun Sakai (see [AUTHORS.adoc]) +Copyright (C) 2022 Shun Sakai (see [AUTHORS.adoc]) This library is distributed under the terms of either the _Apache License 2.0_ or the _MIT License_. diff --git a/crates/capi/build.rs b/crates/capi/build.rs index ed1f889b..667a679f 100644 --- a/crates/capi/build.rs +++ b/crates/capi/build.rs @@ -2,13 +2,6 @@ // // SPDX-License-Identifier: Apache-2.0 OR MIT -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] - use std::{ env, fs, io, path::Path, @@ -20,7 +13,6 @@ fn generate_man_page(out_dir: &str) -> io::Result { let mut command = Command::new("asciidoctor"); command .args(["-b", "manpage"]) - .args(["-a", concat!("revnumber=", env!("CARGO_PKG_VERSION"))]) .args(["-D", out_dir]) .arg(man_dir.join("*.3.adoc")) .status() diff --git a/crates/capi/cbindgen.toml b/crates/capi/cbindgen.toml index f8f6920e..f930702d 100644 --- a/crates/capi/cbindgen.toml +++ b/crates/capi/cbindgen.toml @@ -1,14 +1,23 @@ +# SPDX-FileCopyrightText: 2023 Shun Sakai +# +# SPDX-License-Identifier: Apache-2.0 OR MIT + language = "C" + +# REUSE-IgnoreStart header = """ // SPDX-FileCopyrightText: 2023 Shun Sakai // // SPDX-License-Identifier: Apache-2.0 OR MIT""" +# REUSE-IgnoreEnd pragma_once = true autogen_warning = """ -// This file is automatically generated by cbindgen.""" +// This file is automatically generated by cbindgen. Don't modify this manually.""" sys_includes = ["stdint.h"] no_includes = true cpp_compat = true + +line_length = 80 documentation_style = "c99" [export.rename] diff --git a/crates/capi/examples/.clang-tidy b/crates/capi/examples/.clang-tidy index 649035ef..07cc6e5e 100644 --- a/crates/capi/examples/.clang-tidy +++ b/crates/capi/examples/.clang-tidy @@ -6,7 +6,6 @@ Checks: "clang-diagnostic-*,clang-analyzer-*" WarningsAsErrors: "" HeaderFilterRegex: "" -AnalyzeTemporaryDtors: false FormatStyle: none User: user CheckOptions: diff --git a/crates/capi/examples/decrypt.cpp b/crates/capi/examples/decrypt.cpp index 4d14574a..d7c47afa 100644 --- a/crates/capi/examples/decrypt.cpp +++ b/crates/capi/examples/decrypt.cpp @@ -4,7 +4,6 @@ // An example of decrypting a file from the abcrypt encrypted data format. -#include #include #include @@ -13,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -35,7 +35,7 @@ int main(int argc, char *argv[]) { std::ifstream input_file(input_filename); if (!input_file) { - std::clog << fmt::format("Error: could not open {}: {}", input_filename, + std::clog << std::format("Error: could not open {}: {}", input_filename, std::strerror(errno)) << std::endl; return EXIT_FAILURE; @@ -68,18 +68,18 @@ int main(int argc, char *argv[]) { std::string error_message(buf.cbegin(), buf.cend()); switch (error_code) { case ABCRYPT_ERROR_CODE_INVALID_HEADER_MAC: - std::clog << fmt::format("Error: passphrase is incorrect: {}", + std::clog << std::format("Error: passphrase is incorrect: {}", error_message) << std::endl; break; case ABCRYPT_ERROR_CODE_INVALID_MAC: - std::clog << fmt::format("Error: the encrypted data is corrupted: {}", + std::clog << std::format("Error: the encrypted data is corrupted: {}", error_message) << std::endl; break; default: std::clog - << fmt::format( + << std::format( "Error: the header in the encrypted data is invalid: {}", error_message) << std::endl; @@ -92,7 +92,7 @@ int main(int argc, char *argv[]) { auto ofn = output_filename.value(); std::ofstream output_file(ofn); if (!output_file) { - std::clog << fmt::format("Error: could not open {}: {}", ofn, + std::clog << std::format("Error: could not open {}: {}", ofn, std::strerror(errno)) << std::endl; return EXIT_FAILURE; diff --git a/crates/capi/examples/encrypt.cpp b/crates/capi/examples/encrypt.cpp index 049dafdb..74c2fa4d 100644 --- a/crates/capi/examples/encrypt.cpp +++ b/crates/capi/examples/encrypt.cpp @@ -4,7 +4,6 @@ // An example of encrypting a file to the abcrypt encrypted data format. -#include #include #include @@ -13,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +24,12 @@ int main(int argc, char *argv[]) { CLI::App app{"An example of encrypting to the abcrypt encrypted data format"}; + std::uint32_t argon2_type{2}; + app.add_option("--argon2-type", argon2_type, "Set the Argon2 type") + ->capture_default_str(); + std::uint32_t argon2_version{0x13}; + app.add_option("--argon2-version", argon2_version, "Set the Argon2 version") + ->capture_default_str(); std::uint32_t memory_cost{19456}; app.add_option("-m,--memory-cost", memory_cost, "Set the memory size in KiB") ->capture_default_str(); @@ -43,7 +49,7 @@ int main(int argc, char *argv[]) { std::ifstream input_file(input_filename); if (!input_file) { - std::clog << fmt::format("Error: could not open {}: {}", input_filename, + std::clog << std::format("Error: could not open {}: {}", input_filename, std::strerror(errno)) << std::endl; return EXIT_FAILURE; @@ -75,22 +81,22 @@ int main(int argc, char *argv[]) { std::vector ciphertext( plaintext.size() + (ABCRYPT_HEADER_SIZE + ABCRYPT_TAG_SIZE)); - auto error_code = abcrypt_encrypt_with_params( + auto error_code = abcrypt_encrypt_with_context( plaintext.data(), plaintext.size(), reinterpret_cast(passphrase.data()), passphrase.size(), - ciphertext.data(), ciphertext.size(), memory_cost, time_cost, - parallelism); + ciphertext.data(), ciphertext.size(), argon2_type, argon2_version, + memory_cost, time_cost, parallelism); if (error_code != ABCRYPT_ERROR_CODE_OK) { std::vector buf(abcrypt_error_message_out_len(error_code)); abcrypt_error_message(error_code, buf.data(), buf.size()); std::string error_message(buf.cbegin(), buf.cend()); - std::clog << fmt::format("Error: {}", error_message) << std::endl; + std::clog << std::format("Error: {}", error_message) << std::endl; return EXIT_FAILURE; } std::ofstream output_file(output_filename); if (!output_file) { - std::clog << fmt::format("Error: could not open {}: {}", output_filename, + std::clog << std::format("Error: could not open {}: {}", output_filename, std::strerror(errno)) << std::endl; return EXIT_FAILURE; diff --git a/crates/capi/examples/info.cpp b/crates/capi/examples/info.cpp index 40cec857..5a3a7aef 100644 --- a/crates/capi/examples/info.cpp +++ b/crates/capi/examples/info.cpp @@ -4,13 +4,12 @@ // An example of reading the Argon2 parameters from a file. -#include - #include #include #include #include #include +#include #include #include #include @@ -33,7 +32,7 @@ int main(int argc, char *argv[]) { auto ifn = input_filename.value(); std::ifstream input_file(ifn); if (!input_file) { - std::clog << fmt::format("Error: could not open {}: {}", ifn, + std::clog << std::format("Error: could not open {}: {}", ifn, std::strerror(errno)) << std::endl; return EXIT_FAILURE; @@ -52,7 +51,7 @@ int main(int argc, char *argv[]) { std::vector buf(abcrypt_error_message_out_len(error_code)); abcrypt_error_message(error_code, buf.data(), buf.size()); std::string error_message(buf.cbegin(), buf.cend()); - std::clog << fmt::format( + std::clog << std::format( "Error: data is not a valid abcrypt encrypted file: {}", error_message) << std::endl; @@ -64,7 +63,7 @@ int main(int argc, char *argv[]) { auto parallelism = abcrypt_params_parallelism(params); abcrypt_params_free(params); - std::cout << fmt::format( + std::cout << std::format( "Parameters used: memoryCost = {}; timeCost = {}; " "parallelism = {};", memory_cost, time_cost, parallelism) diff --git a/crates/capi/examples/meson.build b/crates/capi/examples/meson.build index 9a86ee96..0bb99bdf 100644 --- a/crates/capi/examples/meson.build +++ b/crates/capi/examples/meson.build @@ -4,7 +4,7 @@ project('abcrypt_capi_examples', 'cpp', - default_options: ['warning_level=3', 'cpp_std=c++17'], + default_options: ['warning_level=3', 'cpp_std=c++20'], license: 'Apache-2.0 OR MIT', meson_version: '>=0.53.0', version: '0.3.2', @@ -16,7 +16,7 @@ cpp = meson.get_compiler('cpp') cpp.check_header('termios.h', required: true) cpp.check_header('unistd.h', required: true) -deps = [dependency('CLI11'), dependency('fmt')] +deps = dependency('CLI11') libdir = meson.current_source_dir() / '../../../target' if fs.exists(libdir / 'release') diff --git a/crates/capi/include/abcrypt.h b/crates/capi/include/abcrypt.h index 4247c506..0ef1a07c 100644 --- a/crates/capi/include/abcrypt.h +++ b/crates/capi/include/abcrypt.h @@ -4,12 +4,12 @@ #pragma once -// This file is automatically generated by cbindgen. +// This file is automatically generated by cbindgen. Don't modify this manually. #include // The number of bytes of the header. -#define ABCRYPT_HEADER_SIZE 140 +#define ABCRYPT_HEADER_SIZE 148 // The number of bytes of the MAC (authentication tag) of the ciphertext. #define ABCRYPT_TAG_SIZE 16 @@ -20,12 +20,18 @@ typedef enum abcrypt_error_code { ABCRYPT_ERROR_CODE_OK, // General error. ABCRYPT_ERROR_CODE_ERROR, - // The encrypted data was shorter than 156 bytes. + // The encrypted data was shorter than 164 bytes. ABCRYPT_ERROR_CODE_INVALID_LENGTH, // The magic number (file signature) was invalid. ABCRYPT_ERROR_CODE_INVALID_MAGIC_NUMBER, + // The version was the unsupported abcrypt version number. + ABCRYPT_ERROR_CODE_UNSUPPORTED_VERSION, // The version was the unrecognized abcrypt version number. ABCRYPT_ERROR_CODE_UNKNOWN_VERSION, + // The Argon2 type were invalid. + ABCRYPT_ERROR_CODE_INVALID_ARGON2_TYPE, + // The Argon2 version were invalid. + ABCRYPT_ERROR_CODE_INVALID_ARGON2_VERSION, // The Argon2 parameters were invalid. ABCRYPT_ERROR_CODE_INVALID_ARGON2_PARAMS, // The Argon2 context was invalid. @@ -53,9 +59,12 @@ extern "C" { // // Returns an error if any of the following are true: // -// - `ciphertext` is shorter than 156 bytes. +// - `ciphertext` is shorter than 164 bytes. // - The magic number is invalid. +// - The version number is the unsupported abcrypt version number. // - The version number is the unrecognized abcrypt version number. +// - The Argon2 type is invalid. +// - The Argon2 version is invalid. // - The Argon2 parameters are invalid. // - The Argon2 context is invalid. // - The MAC (authentication tag) of the header is invalid. @@ -79,7 +88,9 @@ enum abcrypt_error_code abcrypt_decrypt(uint8_t *ciphertext, // Encrypts `plaintext` and write to `out`. // -// This uses the recommended Argon2 parameters. +// This uses the recommended Argon2 parameters according to the OWASP Password +// Storage Cheat Sheet. This also uses Argon2id as the Argon2 type and version +// 0x13 as the Argon2 version. // // # Errors // @@ -106,10 +117,14 @@ enum abcrypt_error_code abcrypt_encrypt(uint8_t *plaintext, // Encrypts `plaintext` with the specified Argon2 parameters and write to // `out`. // +// This uses Argon2id as the Argon2 type and version 0x13 as the Argon2 +// version. +// // # Errors // // Returns an error if any of the following are true: // +// - The Argon2 parameters are invalid. // - The Argon2 context is invalid. // - One of the parameters is null. // @@ -131,6 +146,39 @@ enum abcrypt_error_code abcrypt_encrypt_with_params(uint8_t *plaintext, uint32_t time_cost, uint32_t parallelism); +// Encrypts `plaintext` with the specified Argon2 type, Argon2 version and +// Argon2 parameters and write to `out`. +// +// # Errors +// +// Returns an error if any of the following are true: +// +// - The Argon2 type is invalid. +// - The Argon2 version is invalid. +// - The Argon2 parameters are invalid. +// - The Argon2 context is invalid. +// - One of the parameters is null. +// +// # Safety +// +// Behavior is undefined if any of the following violates the safety conditions +// of `slice::from_raw_parts`: +// +// - `plaintext` and `plaintext_len`. +// - `passphrase` and `passphrase_len`. +// - `out` and `out_len`. +enum abcrypt_error_code abcrypt_encrypt_with_context(uint8_t *plaintext, + uintptr_t plaintext_len, + uint8_t *passphrase, + uintptr_t passphrase_len, + uint8_t *out, + uintptr_t out_len, + uint32_t argon2_type, + uint32_t argon2_version, + uint32_t memory_cost, + uint32_t time_cost, + uint32_t parallelism); + // Gets a detailed error message. // // # Errors @@ -164,7 +212,7 @@ void abcrypt_params_free(struct abcrypt_params *params); // // Returns an error if any of the following are true: // -// - `ciphertext` is shorter than 156 bytes. +// - `ciphertext` is shorter than 164 bytes. // - The magic number is invalid. // - The version number is the unrecognized abcrypt version number. // - The Argon2 parameters are invalid. diff --git a/crates/capi/src/decrypt.rs b/crates/capi/src/decrypt.rs index bb3df791..e1b5d0df 100644 --- a/crates/capi/src/decrypt.rs +++ b/crates/capi/src/decrypt.rs @@ -17,9 +17,12 @@ use crate::ErrorCode; /// /// Returns an error if any of the following are true: /// -/// - `ciphertext` is shorter than 156 bytes. +/// - `ciphertext` is shorter than 164 bytes. /// - The magic number is invalid. +/// - The version number is the unsupported abcrypt version number. /// - The version number is the unrecognized abcrypt version number. +/// - The Argon2 type is invalid. +/// - The Argon2 version is invalid. /// - The Argon2 parameters are invalid. /// - The Argon2 context is invalid. /// - The MAC (authentication tag) of the header is invalid. @@ -77,27 +80,129 @@ mod tests { const PASSPHRASE: &str = "passphrase"; const TEST_DATA: &[u8] = include_bytes!("../tests/data/data.txt"); - // Generated using `abcrypt` crate version 0.1.0. - const TEST_DATA_ENC: &[u8] = include_bytes!("../tests/data/data.txt.abcrypt"); + // Generated using `abcrypt` crate version 0.4.0. + const TEST_DATA_ENC: &[u8] = include_bytes!("../tests/data/v1/argon2id/v0x13/data.txt.abcrypt"); #[test] fn success() { - let mut ciphertext: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); - let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); - let mut plaintext = [u8::default(); TEST_DATA.len()]; - assert_ne!(plaintext, TEST_DATA); - let code = unsafe { - abcrypt_decrypt( - NonNull::new(ciphertext.as_mut_ptr()), - ciphertext.len(), - NonNull::new(passphrase.as_mut_ptr()), - passphrase.len(), - NonNull::new(plaintext.as_mut_ptr()), - plaintext.len(), - ) - }; - assert_eq!(code, ErrorCode::Ok); - assert_eq!(plaintext, TEST_DATA); + { + const TEST_DATA_ENC: &[u8] = + include_bytes!("../tests/data/v1/argon2d/v0x10/data.txt.abcrypt"); + let mut ciphertext: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(plaintext, TEST_DATA); + } + { + const TEST_DATA_ENC: &[u8] = + include_bytes!("../tests/data/v1/argon2d/v0x13/data.txt.abcrypt"); + let mut ciphertext: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(plaintext, TEST_DATA); + } + { + const TEST_DATA_ENC: &[u8] = + include_bytes!("../tests/data/v1/argon2i/v0x10/data.txt.abcrypt"); + let mut ciphertext: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(plaintext, TEST_DATA); + } + { + const TEST_DATA_ENC: &[u8] = + include_bytes!("../tests/data/v1/argon2i/v0x13/data.txt.abcrypt"); + let mut ciphertext: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(plaintext, TEST_DATA); + } + { + const TEST_DATA_ENC: &[u8] = + include_bytes!("../tests/data/v1/argon2id/v0x10/data.txt.abcrypt"); + let mut ciphertext: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(plaintext, TEST_DATA); + } + { + let mut ciphertext: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(plaintext, TEST_DATA); + } } #[test] @@ -200,10 +305,30 @@ mod tests { assert_eq!(code, ErrorCode::InvalidMagicNumber); } + #[test] + fn unsupported_version() { + const TEST_DATA_V0: &[u8] = include_bytes!("../tests/data/v0/data.txt.abcrypt"); + let mut ciphertext: [u8; TEST_DATA_V0.len()] = TEST_DATA_V0.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::UnsupportedVersion); + } + #[test] fn unknown_version() { let mut ciphertext: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); - ciphertext[7] = 1; + ciphertext[7] = 2; let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); let mut plaintext = [u8::default(); TEST_DATA.len()]; assert_ne!(plaintext, TEST_DATA); @@ -225,7 +350,7 @@ mod tests { let mut ciphertext: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); { - ciphertext[8..12].copy_from_slice(&u32::to_le_bytes(7)); + ciphertext[16..20].copy_from_slice(&u32::to_le_bytes(7)); let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); let mut plaintext = [u8::default(); TEST_DATA.len()]; assert_ne!(plaintext, TEST_DATA); @@ -243,7 +368,7 @@ mod tests { } { - ciphertext[12..16].copy_from_slice(&u32::to_le_bytes(0)); + ciphertext[20..24].copy_from_slice(&u32::to_le_bytes(0)); let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); let mut plaintext = [u8::default(); TEST_DATA.len()]; assert_ne!(plaintext, TEST_DATA); @@ -261,7 +386,7 @@ mod tests { } { - ciphertext[16..20].copy_from_slice(&u32::pow(2, 24).to_le_bytes()); + ciphertext[24..28].copy_from_slice(&u32::pow(2, 24).to_le_bytes()); let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); let mut plaintext = [u8::default(); TEST_DATA.len()]; assert_ne!(plaintext, TEST_DATA); @@ -285,9 +410,9 @@ mod tests { let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); let mut plaintext = [u8::default(); TEST_DATA.len()]; assert_ne!(plaintext, TEST_DATA); - let mut header_mac: [u8; 64] = ciphertext[76..140].try_into().unwrap(); + let mut header_mac: [u8; 64] = ciphertext[84..148].try_into().unwrap(); header_mac.reverse(); - ciphertext[76..140].copy_from_slice(&header_mac); + ciphertext[84..148].copy_from_slice(&header_mac); let code = unsafe { abcrypt_decrypt( NonNull::new(ciphertext.as_mut_ptr()), diff --git a/crates/capi/src/encrypt.rs b/crates/capi/src/encrypt.rs index 9cb92fe1..804da0ae 100644 --- a/crates/capi/src/encrypt.rs +++ b/crates/capi/src/encrypt.rs @@ -6,14 +6,19 @@ use std::{ptr::NonNull, slice}; -use abcrypt::{argon2::Params, Encryptor}; +use abcrypt::{ + argon2::{Algorithm, Params}, + Encryptor, +}; use crate::ErrorCode; #[allow(clippy::module_name_repetitions)] /// Encrypts `plaintext` and write to `out`. /// -/// This uses the recommended Argon2 parameters. +/// This uses the recommended Argon2 parameters according to the OWASP Password +/// Storage Cheat Sheet. This also uses Argon2id as the Argon2 type and version +/// 0x13 as the Argon2 version. /// /// # Errors /// @@ -65,14 +70,17 @@ pub unsafe extern "C-unwind" fn abcrypt_encrypt( ErrorCode::Ok } -#[allow(clippy::module_name_repetitions)] /// Encrypts `plaintext` with the specified Argon2 parameters and write to /// `out`. /// +/// This uses Argon2id as the Argon2 type and version 0x13 as the Argon2 +/// version. +/// /// # Errors /// /// Returns an error if any of the following are true: /// +/// - The Argon2 parameters are invalid. /// - The Argon2 context is invalid. /// - One of the parameters is null. /// @@ -125,8 +133,89 @@ pub unsafe extern "C-unwind" fn abcrypt_encrypt_with_params( ErrorCode::Ok } +/// Encrypts `plaintext` with the specified Argon2 type, Argon2 version and +/// Argon2 parameters and write to `out`. +/// +/// # Errors +/// +/// Returns an error if any of the following are true: +/// +/// - The Argon2 type is invalid. +/// - The Argon2 version is invalid. +/// - The Argon2 parameters are invalid. +/// - The Argon2 context is invalid. +/// - One of the parameters is null. +/// +/// # Safety +/// +/// Behavior is undefined if any of the following violates the safety conditions +/// of `slice::from_raw_parts`: +/// +/// - `plaintext` and `plaintext_len`. +/// - `passphrase` and `passphrase_len`. +/// - `out` and `out_len`. +#[no_mangle] +pub unsafe extern "C-unwind" fn abcrypt_encrypt_with_context( + plaintext: Option>, + plaintext_len: usize, + passphrase: Option>, + passphrase_len: usize, + out: Option>, + out_len: usize, + argon2_type: u32, + argon2_version: u32, + memory_cost: u32, + time_cost: u32, + parallelism: u32, +) -> ErrorCode { + let Some(plaintext) = plaintext else { + return ErrorCode::Error; + }; + // SAFETY: just checked that `plaintext` is not a null pointer. + let plaintext = unsafe { slice::from_raw_parts(plaintext.as_ptr(), plaintext_len) }; + let Some(passphrase) = passphrase else { + return ErrorCode::Error; + }; + // SAFETY: just checked that `passphrase` is not a null pointer. + let passphrase = unsafe { slice::from_raw_parts(passphrase.as_ptr(), passphrase_len) }; + let argon2_type = match argon2_type { + 0 => Algorithm::Argon2d, + 1 => Algorithm::Argon2i, + 2 => Algorithm::Argon2id, + _ => return ErrorCode::InvalidArgon2Type, + }; + let Ok(argon2_version) = argon2_version.try_into() else { + return ErrorCode::InvalidArgon2Version; + }; + let Ok(params) = Params::new(memory_cost, time_cost, parallelism, None) else { + return ErrorCode::InvalidArgon2Params; + }; + let cipher = match Encryptor::with_context( + &plaintext, + passphrase, + argon2_type, + argon2_version, + params, + ) { + Ok(c) => c, + Err(err) => { + return err.into(); + } + }; + + let Some(out) = out else { + return ErrorCode::Error; + }; + // SAFETY: just checked that `out` is not a null pointer. + let out = unsafe { slice::from_raw_parts_mut(out.as_ptr(), out_len) }; + cipher.encrypt(out); + ErrorCode::Ok +} + #[cfg(test)] mod tests { + use abcrypt::{argon2::Version, Argon2}; + use super::*; use crate::{abcrypt_decrypt, HEADER_SIZE, TAG_SIZE}; @@ -151,6 +240,10 @@ mod tests { assert_eq!(code, ErrorCode::Ok); assert_ne!(ciphertext, TEST_DATA); + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + assert_eq!(argon2.version(), Version::V0x13); + let params = abcrypt::Params::new(ciphertext).unwrap(); assert_eq!(params.memory_cost(), 19456); assert_eq!(params.time_cost(), 2); @@ -193,6 +286,10 @@ mod tests { assert_eq!(code, ErrorCode::Ok); assert_ne!(ciphertext, TEST_DATA); + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + assert_eq!(argon2.version(), Version::V0x13); + let params = abcrypt::Params::new(ciphertext).unwrap(); assert_eq!(params.memory_cost(), 32); assert_eq!(params.time_cost(), 3); @@ -214,6 +311,286 @@ mod tests { assert_eq!(plaintext, TEST_DATA); } + #[test] + fn success_with_context() { + { + let mut plaintext: [u8; TEST_DATA.len()] = TEST_DATA.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut ciphertext = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + let code = unsafe { + abcrypt_encrypt_with_context( + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + 0, + 0x10, + 47104, + 1, + 1, + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_ne!(ciphertext, TEST_DATA); + + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2d); + assert_eq!(argon2.version(), Version::V0x10); + + let params = abcrypt::Params::new(ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 47104); + assert_eq!(params.time_cost(), 1); + assert_eq!(params.parallelism(), 1); + + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(plaintext, TEST_DATA); + } + { + let mut plaintext: [u8; TEST_DATA.len()] = TEST_DATA.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut ciphertext = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + let code = unsafe { + abcrypt_encrypt_with_context( + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + 0, + 0x13, + 19456, + 2, + 1, + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_ne!(ciphertext, TEST_DATA); + + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2d); + assert_eq!(argon2.version(), Version::V0x13); + + let params = abcrypt::Params::new(ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 19456); + assert_eq!(params.time_cost(), 2); + assert_eq!(params.parallelism(), 1); + + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(plaintext, TEST_DATA); + } + { + let mut plaintext: [u8; TEST_DATA.len()] = TEST_DATA.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut ciphertext = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + let code = unsafe { + abcrypt_encrypt_with_context( + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + 1, + 0x10, + 12288, + 3, + 1, + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_ne!(ciphertext, TEST_DATA); + + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2i); + assert_eq!(argon2.version(), Version::V0x10); + + let params = abcrypt::Params::new(ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 12288); + assert_eq!(params.time_cost(), 3); + assert_eq!(params.parallelism(), 1); + + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(plaintext, TEST_DATA); + } + { + let mut plaintext: [u8; TEST_DATA.len()] = TEST_DATA.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut ciphertext = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + let code = unsafe { + abcrypt_encrypt_with_context( + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + 1, + 0x13, + 9216, + 4, + 1, + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_ne!(ciphertext, TEST_DATA); + + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2i); + assert_eq!(argon2.version(), Version::V0x13); + + let params = abcrypt::Params::new(ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 9216); + assert_eq!(params.time_cost(), 4); + assert_eq!(params.parallelism(), 1); + + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(plaintext, TEST_DATA); + } + { + let mut plaintext: [u8; TEST_DATA.len()] = TEST_DATA.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut ciphertext = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + let code = unsafe { + abcrypt_encrypt_with_context( + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + 2, + 0x10, + 7168, + 5, + 1, + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_ne!(ciphertext, TEST_DATA); + + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + assert_eq!(argon2.version(), Version::V0x10); + + let params = abcrypt::Params::new(ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 7168); + assert_eq!(params.time_cost(), 5); + assert_eq!(params.parallelism(), 1); + + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(plaintext, TEST_DATA); + } + { + let mut plaintext: [u8; TEST_DATA.len()] = TEST_DATA.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut ciphertext = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + let code = unsafe { + abcrypt_encrypt_with_context( + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + 2, + 0x13, + 32, + 3, + 4, + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_ne!(ciphertext, TEST_DATA); + + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + assert_eq!(argon2.version(), Version::V0x13); + + let params = abcrypt::Params::new(ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 32); + assert_eq!(params.time_cost(), 3); + assert_eq!(params.parallelism(), 4); + + let mut plaintext = [u8::default(); TEST_DATA.len()]; + assert_ne!(plaintext, TEST_DATA); + let code = unsafe { + abcrypt_decrypt( + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(plaintext, TEST_DATA); + } + } + #[test] #[should_panic( expected = "source slice length (16) does not match destination slice length (15)" @@ -299,7 +676,140 @@ mod tests { ) }; assert_eq!(code, ErrorCode::Ok); - assert_eq!(ciphertext[7], 0); + assert_eq!(ciphertext[7], 1); + } + + #[test] + fn argon2_type() { + { + let mut plaintext: [u8; TEST_DATA.len()] = TEST_DATA.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut ciphertext = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + let code = unsafe { + abcrypt_encrypt_with_context( + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + 0, + 0x13, + 32, + 3, + 4, + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(&ciphertext[8..12], u32::to_le_bytes(0)); + + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2d); + } + { + let mut plaintext: [u8; TEST_DATA.len()] = TEST_DATA.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut ciphertext = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + let code = unsafe { + abcrypt_encrypt_with_context( + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + 1, + 0x13, + 32, + 3, + 4, + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(&ciphertext[8..12], u32::to_le_bytes(1)); + + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2i); + } + { + let mut plaintext: [u8; TEST_DATA.len()] = TEST_DATA.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut ciphertext = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + let code = unsafe { + abcrypt_encrypt_with_context( + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + 2, + 0x13, + 32, + 3, + 4, + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(&ciphertext[8..12], u32::to_le_bytes(2)); + + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.variant(), Algorithm::Argon2id); + } + } + + #[test] + fn argon2_version() { + { + let mut plaintext: [u8; TEST_DATA.len()] = TEST_DATA.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut ciphertext = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + let code = unsafe { + abcrypt_encrypt_with_context( + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + 2, + 0x10, + 32, + 3, + 4, + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(&ciphertext[12..16], u32::to_le_bytes(0x10)); + + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.version(), Version::V0x10); + } + { + let mut plaintext: [u8; TEST_DATA.len()] = TEST_DATA.try_into().unwrap(); + let mut passphrase: [u8; PASSPHRASE.len()] = PASSPHRASE.as_bytes().try_into().unwrap(); + let mut ciphertext = [u8::default(); TEST_DATA.len() + HEADER_SIZE + TAG_SIZE]; + let code = unsafe { + abcrypt_encrypt_with_context( + NonNull::new(plaintext.as_mut_ptr()), + plaintext.len(), + NonNull::new(passphrase.as_mut_ptr()), + passphrase.len(), + NonNull::new(ciphertext.as_mut_ptr()), + ciphertext.len(), + 2, + 0x13, + 32, + 3, + 4, + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(&ciphertext[12..16], u32::to_le_bytes(0x13)); + + let argon2 = Argon2::new(ciphertext).unwrap(); + assert_eq!(argon2.version(), Version::V0x13); + } } #[test] @@ -321,7 +831,10 @@ mod tests { ) }; assert_eq!(code, ErrorCode::Ok); - assert_eq!(&ciphertext[8..12], u32::to_le_bytes(32)); + assert_eq!(&ciphertext[16..20], u32::to_le_bytes(32)); + + let params = abcrypt::Params::new(ciphertext).unwrap(); + assert_eq!(params.memory_cost(), 32); } #[test] @@ -343,7 +856,10 @@ mod tests { ) }; assert_eq!(code, ErrorCode::Ok); - assert_eq!(&ciphertext[12..16], u32::to_le_bytes(3)); + assert_eq!(&ciphertext[20..24], u32::to_le_bytes(3)); + + let params = abcrypt::Params::new(ciphertext).unwrap(); + assert_eq!(params.time_cost(), 3); } #[test] @@ -365,6 +881,9 @@ mod tests { ) }; assert_eq!(code, ErrorCode::Ok); - assert_eq!(&ciphertext[16..20], u32::to_le_bytes(4)); + assert_eq!(&ciphertext[24..28], u32::to_le_bytes(4)); + + let params = abcrypt::Params::new(ciphertext).unwrap(); + assert_eq!(params.parallelism(), 4); } } diff --git a/crates/capi/src/error.rs b/crates/capi/src/error.rs index 3cd6a44d..3132b67b 100644 --- a/crates/capi/src/error.rs +++ b/crates/capi/src/error.rs @@ -19,15 +19,24 @@ pub enum ErrorCode { /// General error. Error, - /// The encrypted data was shorter than 156 bytes. + /// The encrypted data was shorter than 164 bytes. InvalidLength, /// The magic number (file signature) was invalid. InvalidMagicNumber, + /// The version was the unsupported abcrypt version number. + UnsupportedVersion, + /// The version was the unrecognized abcrypt version number. UnknownVersion, + /// The Argon2 type were invalid. + InvalidArgon2Type, + + /// The Argon2 version were invalid. + InvalidArgon2Version, + /// The Argon2 parameters were invalid. InvalidArgon2Params, @@ -42,7 +51,6 @@ pub enum ErrorCode { } impl ErrorCode { - #[allow(clippy::missing_panics_doc)] /// Gets a detailed error message. /// /// # Errors @@ -82,7 +90,10 @@ impl fmt::Display for ErrorCode { Self::Error => write!(f, "general error"), Self::InvalidLength => write!(f, "{}", Error::InvalidLength), Self::InvalidMagicNumber => write!(f, "{}", Error::InvalidMagicNumber), + Self::UnsupportedVersion => write!(f, "unsupported version number"), Self::UnknownVersion => write!(f, "unknown version number"), + Self::InvalidArgon2Type => write!(f, "invalid Argon2 type"), + Self::InvalidArgon2Version => write!(f, "invalid Argon2 version"), Self::InvalidArgon2Params => write!(f, "invalid Argon2 parameters"), Self::InvalidArgon2Context => write!(f, "invalid Argon2 context"), Self::InvalidHeaderMac => write!(f, "invalid header MAC"), @@ -97,7 +108,10 @@ impl From for ErrorCode { match error { Error::InvalidLength => Self::InvalidLength, Error::InvalidMagicNumber => Self::InvalidMagicNumber, + Error::UnsupportedVersion(_) => Self::UnsupportedVersion, Error::UnknownVersion(_) => Self::UnknownVersion, + Error::InvalidArgon2Type(_) => Self::InvalidArgon2Type, + Error::InvalidArgon2Version(_) => Self::InvalidArgon2Version, Error::InvalidArgon2Params(_) => Self::InvalidArgon2Params, Error::InvalidArgon2Context(_) => Self::InvalidArgon2Context, Error::InvalidHeaderMac(_) => Self::InvalidHeaderMac, @@ -106,7 +120,6 @@ impl From for ErrorCode { } } -#[allow(clippy::missing_panics_doc)] /// Gets a detailed error message. /// /// # Errors @@ -119,6 +132,7 @@ impl From for ErrorCode { /// of `slice::from_raw_parts`. #[must_use] #[no_mangle] +#[inline] pub unsafe extern "C-unwind" fn abcrypt_error_message( error_code: ErrorCode, buf: Option>, @@ -129,27 +143,31 @@ pub unsafe extern "C-unwind" fn abcrypt_error_message( /// Returns the number of output bytes of the error message. #[no_mangle] +#[inline] pub extern "C-unwind" fn abcrypt_error_message_out_len(error_code: ErrorCode) -> usize { error_code.error_message_out_len() } #[cfg(test)] mod tests { + use std::ffi::c_int; + use super::*; #[test] fn error_code() { - use std::ffi::c_int; - assert_eq!(ErrorCode::Ok as c_int, 0); assert_eq!(ErrorCode::Error as c_int, 1); assert_eq!(ErrorCode::InvalidLength as c_int, 2); assert_eq!(ErrorCode::InvalidMagicNumber as c_int, 3); - assert_eq!(ErrorCode::UnknownVersion as c_int, 4); - assert_eq!(ErrorCode::InvalidArgon2Params as c_int, 5); - assert_eq!(ErrorCode::InvalidArgon2Context as c_int, 6); - assert_eq!(ErrorCode::InvalidHeaderMac as c_int, 7); - assert_eq!(ErrorCode::InvalidMac as c_int, 8); + assert_eq!(ErrorCode::UnsupportedVersion as c_int, 4); + assert_eq!(ErrorCode::UnknownVersion as c_int, 5); + assert_eq!(ErrorCode::InvalidArgon2Type as c_int, 6); + assert_eq!(ErrorCode::InvalidArgon2Version as c_int, 7); + assert_eq!(ErrorCode::InvalidArgon2Params as c_int, 8); + assert_eq!(ErrorCode::InvalidArgon2Context as c_int, 9); + assert_eq!(ErrorCode::InvalidHeaderMac as c_int, 10); + assert_eq!(ErrorCode::InvalidMac as c_int, 11); } #[test] @@ -161,7 +179,19 @@ mod tests { ErrorCode::InvalidMagicNumber.clone(), ErrorCode::InvalidMagicNumber ); + assert_eq!( + ErrorCode::UnsupportedVersion.clone(), + ErrorCode::UnsupportedVersion + ); assert_eq!(ErrorCode::UnknownVersion.clone(), ErrorCode::UnknownVersion); + assert_eq!( + ErrorCode::InvalidArgon2Type.clone(), + ErrorCode::InvalidArgon2Type + ); + assert_eq!( + ErrorCode::InvalidArgon2Version.clone(), + ErrorCode::InvalidArgon2Version + ); assert_eq!( ErrorCode::InvalidArgon2Params.clone(), ErrorCode::InvalidArgon2Params @@ -203,12 +233,30 @@ mod tests { assert_eq!(a, b); } + { + let a = ErrorCode::UnsupportedVersion; + let b = a; + assert_eq!(a, b); + } + { let a = ErrorCode::UnknownVersion; let b = a; assert_eq!(a, b); } + { + let a = ErrorCode::InvalidArgon2Type; + let b = a; + assert_eq!(a, b); + } + + { + let a = ErrorCode::InvalidArgon2Version; + let b = a; + assert_eq!(a, b); + } + { let a = ErrorCode::InvalidArgon2Params; let b = a; @@ -243,7 +291,19 @@ mod tests { format!("{:?}", ErrorCode::InvalidMagicNumber), "InvalidMagicNumber" ); + assert_eq!( + format!("{:?}", ErrorCode::UnsupportedVersion), + "UnsupportedVersion" + ); assert_eq!(format!("{:?}", ErrorCode::UnknownVersion), "UnknownVersion"); + assert_eq!( + format!("{:?}", ErrorCode::InvalidArgon2Type), + "InvalidArgon2Type" + ); + assert_eq!( + format!("{:?}", ErrorCode::InvalidArgon2Version), + "InvalidArgon2Version" + ); assert_eq!( format!("{:?}", ErrorCode::InvalidArgon2Params), "InvalidArgon2Params" @@ -260,13 +320,15 @@ mod tests { } #[test] - #[allow(clippy::cognitive_complexity, clippy::too_many_lines)] fn equality() { assert_eq!(ErrorCode::Ok, ErrorCode::Ok); assert_ne!(ErrorCode::Ok, ErrorCode::Error); assert_ne!(ErrorCode::Ok, ErrorCode::InvalidLength); assert_ne!(ErrorCode::Ok, ErrorCode::InvalidMagicNumber); + assert_ne!(ErrorCode::Ok, ErrorCode::UnsupportedVersion); assert_ne!(ErrorCode::Ok, ErrorCode::UnknownVersion); + assert_ne!(ErrorCode::Ok, ErrorCode::InvalidArgon2Type); + assert_ne!(ErrorCode::Ok, ErrorCode::InvalidArgon2Version); assert_ne!(ErrorCode::Ok, ErrorCode::InvalidArgon2Params); assert_ne!(ErrorCode::Ok, ErrorCode::InvalidArgon2Context); assert_ne!(ErrorCode::Ok, ErrorCode::InvalidHeaderMac); @@ -275,7 +337,10 @@ mod tests { assert_eq!(ErrorCode::Error, ErrorCode::Error); assert_ne!(ErrorCode::Error, ErrorCode::InvalidLength); assert_ne!(ErrorCode::Error, ErrorCode::InvalidMagicNumber); + assert_ne!(ErrorCode::Error, ErrorCode::UnsupportedVersion); assert_ne!(ErrorCode::Error, ErrorCode::UnknownVersion); + assert_ne!(ErrorCode::Error, ErrorCode::InvalidArgon2Type); + assert_ne!(ErrorCode::Error, ErrorCode::InvalidArgon2Version); assert_ne!(ErrorCode::Error, ErrorCode::InvalidArgon2Params); assert_ne!(ErrorCode::Error, ErrorCode::InvalidArgon2Context); assert_ne!(ErrorCode::Error, ErrorCode::InvalidHeaderMac); @@ -284,7 +349,10 @@ mod tests { assert_ne!(ErrorCode::InvalidLength, ErrorCode::Error); assert_eq!(ErrorCode::InvalidLength, ErrorCode::InvalidLength); assert_ne!(ErrorCode::InvalidLength, ErrorCode::InvalidMagicNumber); + assert_ne!(ErrorCode::InvalidLength, ErrorCode::UnsupportedVersion); assert_ne!(ErrorCode::InvalidLength, ErrorCode::UnknownVersion); + assert_ne!(ErrorCode::InvalidLength, ErrorCode::InvalidArgon2Type); + assert_ne!(ErrorCode::InvalidLength, ErrorCode::InvalidArgon2Version); assert_ne!(ErrorCode::InvalidLength, ErrorCode::InvalidArgon2Params); assert_ne!(ErrorCode::InvalidLength, ErrorCode::InvalidArgon2Context); assert_ne!(ErrorCode::InvalidLength, ErrorCode::InvalidHeaderMac); @@ -293,7 +361,13 @@ mod tests { assert_ne!(ErrorCode::InvalidMagicNumber, ErrorCode::Error); assert_ne!(ErrorCode::InvalidMagicNumber, ErrorCode::InvalidLength); assert_eq!(ErrorCode::InvalidMagicNumber, ErrorCode::InvalidMagicNumber); + assert_ne!(ErrorCode::InvalidMagicNumber, ErrorCode::UnsupportedVersion); assert_ne!(ErrorCode::InvalidMagicNumber, ErrorCode::UnknownVersion); + assert_ne!(ErrorCode::InvalidMagicNumber, ErrorCode::InvalidArgon2Type); + assert_ne!( + ErrorCode::InvalidMagicNumber, + ErrorCode::InvalidArgon2Version + ); assert_ne!( ErrorCode::InvalidMagicNumber, ErrorCode::InvalidArgon2Params @@ -304,15 +378,87 @@ mod tests { ); assert_ne!(ErrorCode::InvalidMagicNumber, ErrorCode::InvalidHeaderMac); assert_ne!(ErrorCode::InvalidMagicNumber, ErrorCode::InvalidMac); + assert_ne!(ErrorCode::UnsupportedVersion, ErrorCode::Ok); + assert_ne!(ErrorCode::UnsupportedVersion, ErrorCode::Error); + assert_ne!(ErrorCode::UnsupportedVersion, ErrorCode::InvalidLength); + assert_ne!(ErrorCode::UnsupportedVersion, ErrorCode::InvalidMagicNumber); + assert_eq!(ErrorCode::UnsupportedVersion, ErrorCode::UnsupportedVersion); + assert_ne!(ErrorCode::UnsupportedVersion, ErrorCode::UnknownVersion); + assert_ne!(ErrorCode::UnsupportedVersion, ErrorCode::InvalidArgon2Type); + assert_ne!( + ErrorCode::UnsupportedVersion, + ErrorCode::InvalidArgon2Version + ); + assert_ne!( + ErrorCode::UnsupportedVersion, + ErrorCode::InvalidArgon2Params + ); + assert_ne!( + ErrorCode::UnsupportedVersion, + ErrorCode::InvalidArgon2Context + ); + assert_ne!(ErrorCode::UnsupportedVersion, ErrorCode::InvalidHeaderMac); + assert_ne!(ErrorCode::UnsupportedVersion, ErrorCode::InvalidMac); assert_ne!(ErrorCode::UnknownVersion, ErrorCode::Ok); assert_ne!(ErrorCode::UnknownVersion, ErrorCode::Error); assert_ne!(ErrorCode::UnknownVersion, ErrorCode::InvalidLength); assert_ne!(ErrorCode::UnknownVersion, ErrorCode::InvalidMagicNumber); + assert_ne!(ErrorCode::UnknownVersion, ErrorCode::UnsupportedVersion); assert_eq!(ErrorCode::UnknownVersion, ErrorCode::UnknownVersion); + assert_ne!(ErrorCode::UnknownVersion, ErrorCode::InvalidArgon2Type); + assert_ne!(ErrorCode::UnknownVersion, ErrorCode::InvalidArgon2Version); assert_ne!(ErrorCode::UnknownVersion, ErrorCode::InvalidArgon2Params); assert_ne!(ErrorCode::UnknownVersion, ErrorCode::InvalidArgon2Context); assert_ne!(ErrorCode::UnknownVersion, ErrorCode::InvalidHeaderMac); assert_ne!(ErrorCode::UnknownVersion, ErrorCode::InvalidMac); + assert_ne!(ErrorCode::InvalidArgon2Type, ErrorCode::Ok); + assert_ne!(ErrorCode::InvalidArgon2Type, ErrorCode::Error); + assert_ne!(ErrorCode::InvalidArgon2Type, ErrorCode::InvalidLength); + assert_ne!(ErrorCode::InvalidArgon2Type, ErrorCode::InvalidMagicNumber); + assert_ne!(ErrorCode::InvalidArgon2Type, ErrorCode::UnsupportedVersion); + assert_ne!(ErrorCode::InvalidArgon2Type, ErrorCode::UnknownVersion); + assert_eq!(ErrorCode::InvalidArgon2Type, ErrorCode::InvalidArgon2Type); + assert_ne!( + ErrorCode::InvalidArgon2Type, + ErrorCode::InvalidArgon2Version + ); + assert_ne!(ErrorCode::InvalidArgon2Type, ErrorCode::InvalidArgon2Params); + assert_ne!( + ErrorCode::InvalidArgon2Type, + ErrorCode::InvalidArgon2Context + ); + assert_ne!(ErrorCode::InvalidArgon2Type, ErrorCode::InvalidHeaderMac); + assert_ne!(ErrorCode::InvalidArgon2Type, ErrorCode::InvalidMac); + assert_ne!(ErrorCode::InvalidArgon2Version, ErrorCode::Ok); + assert_ne!(ErrorCode::InvalidArgon2Version, ErrorCode::Error); + assert_ne!(ErrorCode::InvalidArgon2Version, ErrorCode::InvalidLength); + assert_ne!( + ErrorCode::InvalidArgon2Version, + ErrorCode::InvalidMagicNumber + ); + assert_ne!( + ErrorCode::InvalidArgon2Version, + ErrorCode::UnsupportedVersion + ); + assert_ne!(ErrorCode::InvalidArgon2Version, ErrorCode::UnknownVersion); + assert_ne!( + ErrorCode::InvalidArgon2Version, + ErrorCode::InvalidArgon2Type + ); + assert_eq!( + ErrorCode::InvalidArgon2Version, + ErrorCode::InvalidArgon2Version + ); + assert_ne!( + ErrorCode::InvalidArgon2Version, + ErrorCode::InvalidArgon2Params + ); + assert_ne!( + ErrorCode::InvalidArgon2Version, + ErrorCode::InvalidArgon2Context + ); + assert_ne!(ErrorCode::InvalidArgon2Version, ErrorCode::InvalidHeaderMac); + assert_ne!(ErrorCode::InvalidArgon2Version, ErrorCode::InvalidMac); assert_ne!(ErrorCode::InvalidArgon2Params, ErrorCode::Ok); assert_ne!(ErrorCode::InvalidArgon2Params, ErrorCode::Error); assert_ne!(ErrorCode::InvalidArgon2Params, ErrorCode::InvalidLength); @@ -320,7 +466,16 @@ mod tests { ErrorCode::InvalidArgon2Params, ErrorCode::InvalidMagicNumber ); + assert_ne!( + ErrorCode::InvalidArgon2Params, + ErrorCode::UnsupportedVersion + ); assert_ne!(ErrorCode::InvalidArgon2Params, ErrorCode::UnknownVersion); + assert_ne!(ErrorCode::InvalidArgon2Params, ErrorCode::InvalidArgon2Type); + assert_ne!( + ErrorCode::InvalidArgon2Params, + ErrorCode::InvalidArgon2Version + ); assert_eq!( ErrorCode::InvalidArgon2Params, ErrorCode::InvalidArgon2Params @@ -338,7 +493,19 @@ mod tests { ErrorCode::InvalidArgon2Context, ErrorCode::InvalidMagicNumber ); + assert_ne!( + ErrorCode::InvalidArgon2Context, + ErrorCode::UnsupportedVersion + ); assert_ne!(ErrorCode::InvalidArgon2Context, ErrorCode::UnknownVersion); + assert_ne!( + ErrorCode::InvalidArgon2Context, + ErrorCode::InvalidArgon2Type + ); + assert_ne!( + ErrorCode::InvalidArgon2Context, + ErrorCode::InvalidArgon2Version + ); assert_ne!( ErrorCode::InvalidArgon2Context, ErrorCode::InvalidArgon2Params @@ -353,7 +520,10 @@ mod tests { assert_ne!(ErrorCode::InvalidHeaderMac, ErrorCode::Error); assert_ne!(ErrorCode::InvalidHeaderMac, ErrorCode::InvalidLength); assert_ne!(ErrorCode::InvalidHeaderMac, ErrorCode::InvalidMagicNumber); + assert_ne!(ErrorCode::InvalidHeaderMac, ErrorCode::UnsupportedVersion); assert_ne!(ErrorCode::InvalidHeaderMac, ErrorCode::UnknownVersion); + assert_ne!(ErrorCode::InvalidHeaderMac, ErrorCode::InvalidArgon2Type); + assert_ne!(ErrorCode::InvalidHeaderMac, ErrorCode::InvalidArgon2Version); assert_ne!(ErrorCode::InvalidHeaderMac, ErrorCode::InvalidArgon2Params); assert_ne!(ErrorCode::InvalidHeaderMac, ErrorCode::InvalidArgon2Context); assert_eq!(ErrorCode::InvalidHeaderMac, ErrorCode::InvalidHeaderMac); @@ -362,7 +532,10 @@ mod tests { assert_ne!(ErrorCode::InvalidMac, ErrorCode::Error); assert_ne!(ErrorCode::InvalidMac, ErrorCode::InvalidLength); assert_ne!(ErrorCode::InvalidMac, ErrorCode::InvalidMagicNumber); + assert_ne!(ErrorCode::InvalidMac, ErrorCode::UnsupportedVersion); assert_ne!(ErrorCode::InvalidMac, ErrorCode::UnknownVersion); + assert_ne!(ErrorCode::InvalidMac, ErrorCode::InvalidArgon2Type); + assert_ne!(ErrorCode::InvalidMac, ErrorCode::InvalidArgon2Version); assert_ne!(ErrorCode::InvalidMac, ErrorCode::InvalidArgon2Params); assert_ne!(ErrorCode::InvalidMac, ErrorCode::InvalidArgon2Context); assert_ne!(ErrorCode::InvalidMac, ErrorCode::InvalidHeaderMac); @@ -375,16 +548,28 @@ mod tests { assert_eq!(format!("{}", ErrorCode::Error), "general error"); assert_eq!( format!("{}", ErrorCode::InvalidLength), - "encrypted data is shorter than 156 bytes" + "encrypted data is shorter than 164 bytes" ); assert_eq!( format!("{}", ErrorCode::InvalidMagicNumber), "invalid magic number" ); + assert_eq!( + format!("{}", ErrorCode::UnsupportedVersion), + "unsupported version number" + ); assert_eq!( format!("{}", ErrorCode::UnknownVersion), "unknown version number" ); + assert_eq!( + format!("{}", ErrorCode::InvalidArgon2Type), + "invalid Argon2 type" + ); + assert_eq!( + format!("{}", ErrorCode::InvalidArgon2Version), + "invalid Argon2 version" + ); assert_eq!( format!("{}", ErrorCode::InvalidArgon2Params), "invalid Argon2 parameters" @@ -404,7 +589,6 @@ mod tests { } #[test] - #[allow(clippy::too_many_lines)] fn error_message() { { let expected = CString::new("everything is ok").unwrap(); @@ -429,7 +613,7 @@ mod tests { } { - let expected = CString::new("encrypted data is shorter than 156 bytes").unwrap(); + let expected = CString::new("encrypted data is shorter than 164 bytes").unwrap(); let expected = expected.as_bytes_with_nul(); let mut buf = vec![u8::default(); expected.len()]; let code = unsafe { @@ -458,6 +642,21 @@ mod tests { assert_eq!(buf, expected); } + { + let expected = CString::new("unsupported version number").unwrap(); + let expected = expected.as_bytes_with_nul(); + let mut buf = vec![u8::default(); expected.len()]; + let code = unsafe { + abcrypt_error_message( + ErrorCode::UnsupportedVersion, + NonNull::new(buf.as_mut_ptr()), + buf.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(buf, expected); + } + { let expected = CString::new("unknown version number").unwrap(); let expected = expected.as_bytes_with_nul(); @@ -473,6 +672,36 @@ mod tests { assert_eq!(buf, expected); } + { + let expected = CString::new("invalid Argon2 type").unwrap(); + let expected = expected.as_bytes_with_nul(); + let mut buf = vec![u8::default(); expected.len()]; + let code = unsafe { + abcrypt_error_message( + ErrorCode::InvalidArgon2Type, + NonNull::new(buf.as_mut_ptr()), + buf.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(buf, expected); + } + + { + let expected = CString::new("invalid Argon2 version").unwrap(); + let expected = expected.as_bytes_with_nul(); + let mut buf = vec![u8::default(); expected.len()]; + let code = unsafe { + abcrypt_error_message( + ErrorCode::InvalidArgon2Version, + NonNull::new(buf.as_mut_ptr()), + buf.len(), + ) + }; + assert_eq!(code, ErrorCode::Ok); + assert_eq!(buf, expected); + } + { let expected = CString::new("invalid Argon2 parameters").unwrap(); let expected = expected.as_bytes_with_nul(); @@ -543,7 +772,19 @@ mod tests { abcrypt_error_message_out_len(ErrorCode::InvalidMagicNumber), 21 ); + assert_eq!( + abcrypt_error_message_out_len(ErrorCode::UnsupportedVersion), + 27 + ); assert_eq!(abcrypt_error_message_out_len(ErrorCode::UnknownVersion), 23); + assert_eq!( + abcrypt_error_message_out_len(ErrorCode::InvalidArgon2Type), + 20 + ); + assert_eq!( + abcrypt_error_message_out_len(ErrorCode::InvalidArgon2Version), + 23 + ); assert_eq!( abcrypt_error_message_out_len(ErrorCode::InvalidArgon2Params), 26 @@ -569,10 +810,22 @@ mod tests { ErrorCode::from(Error::InvalidMagicNumber), ErrorCode::InvalidMagicNumber ); + assert_eq!( + ErrorCode::from(Error::UnsupportedVersion(u8::MIN)), + ErrorCode::UnsupportedVersion + ); assert_eq!( ErrorCode::from(Error::UnknownVersion(u8::MAX)), ErrorCode::UnknownVersion ); + assert_eq!( + ErrorCode::from(Error::InvalidArgon2Type(u32::MAX)), + ErrorCode::InvalidArgon2Type + ); + assert_eq!( + ErrorCode::from(Error::InvalidArgon2Version(u32::MAX)), + ErrorCode::InvalidArgon2Version + ); assert_eq!( ErrorCode::from(Error::InvalidArgon2Params( abcrypt::argon2::Error::AdTooLong diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index 9cf59937..06f72063 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -6,10 +6,7 @@ #![doc(html_root_url = "https://docs.rs/abcrypt-capi/0.3.2/")] // Lint levels of rustc. -#![deny(missing_debug_implementations, missing_docs)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] +#![deny(missing_docs)] mod decrypt; mod encrypt; @@ -18,7 +15,7 @@ mod params; pub use crate::{ decrypt::abcrypt_decrypt, - encrypt::{abcrypt_encrypt, abcrypt_encrypt_with_params}, + encrypt::{abcrypt_encrypt, abcrypt_encrypt_with_context, abcrypt_encrypt_with_params}, error::{abcrypt_error_message, abcrypt_error_message_out_len, ErrorCode}, params::{ abcrypt_params_free, abcrypt_params_memory_cost, abcrypt_params_new, @@ -27,7 +24,7 @@ pub use crate::{ }; /// The number of bytes of the header. -pub const HEADER_SIZE: usize = 140; +pub const HEADER_SIZE: usize = 148; /// The number of bytes of the MAC (authentication tag) of the ciphertext. pub const TAG_SIZE: usize = 16; @@ -38,7 +35,7 @@ mod tests { #[test] fn header_size() { - assert_eq!(HEADER_SIZE, 140); + assert_eq!(HEADER_SIZE, 148); assert_eq!(HEADER_SIZE, abcrypt::HEADER_SIZE); } diff --git a/crates/capi/src/params.rs b/crates/capi/src/params.rs index ea082a11..337171e2 100644 --- a/crates/capi/src/params.rs +++ b/crates/capi/src/params.rs @@ -46,9 +46,12 @@ impl Params { /// /// Returns an error if any of the following are true: /// - /// - `ciphertext` is shorter than 156 bytes. + /// - `ciphertext` is shorter than 164 bytes. /// - The magic number is invalid. + /// - The version number is the unsupported abcrypt version number. /// - The version number is the unrecognized abcrypt version number. + /// - The Argon2 type is invalid. + /// - The Argon2 version is invalid. /// - The Argon2 parameters are invalid. /// - One of the parameters is null. /// @@ -122,6 +125,7 @@ impl Params { } impl Default for Params { + #[inline] fn default() -> Self { let (memory_cost, time_cost, parallelism) = ( argon2::Params::DEFAULT_M_COST, @@ -137,6 +141,7 @@ impl Default for Params { } impl From for Params { + #[inline] fn from(params: abcrypt::Params) -> Self { let (memory_cost, time_cost, parallelism) = ( params.memory_cost(), @@ -176,7 +181,7 @@ pub unsafe extern "C-unwind" fn abcrypt_params_free(params: Option>, ciphertext_len: usize, @@ -230,8 +236,8 @@ pub extern "C-unwind" fn abcrypt_params_parallelism(params: Option + ## License -Copyright © 2022–2024 Shun Sakai (see [AUTHORS.adoc]) +Copyright (C) 2022 Shun Sakai (see [AUTHORS.adoc]) 1. This program is distributed under the terms of the _GNU General Public License v3.0 or later_. @@ -154,6 +157,7 @@ licensing information. [`abcrypt(1)`]: https://sorairolake.github.io/abcrypt/book/cli/man/man1/abcrypt.1.html [`abcrypt-encrypt(1)`]: https://sorairolake.github.io/abcrypt/book/cli/man/man1/abcrypt-encrypt.1.html [`abcrypt-decrypt(1)`]: https://sorairolake.github.io/abcrypt/book/cli/man/man1/abcrypt-decrypt.1.html +[`abcrypt-argon2(1)`]: https://sorairolake.github.io/abcrypt/book/cli/man/man1/abcrypt-argon2.1.html [`abcrypt-information(1)`]: https://sorairolake.github.io/abcrypt/book/cli/man/man1/abcrypt-information.1.html [`abcrypt-help(1)`]: https://sorairolake.github.io/abcrypt/book/cli/man/man1/abcrypt-help.1.html [CHANGELOG.adoc]: CHANGELOG.adoc diff --git a/crates/cli/assets/screenshot.webp b/crates/cli/assets/screenshot.webp deleted file mode 100644 index 2593972e..00000000 Binary files a/crates/cli/assets/screenshot.webp and /dev/null differ diff --git a/crates/cli/build.rs b/crates/cli/build.rs index 35dc3d8c..57a4028c 100644 --- a/crates/cli/build.rs +++ b/crates/cli/build.rs @@ -2,13 +2,6 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] - use std::{ env, io, process::{Command, ExitStatus}, @@ -19,12 +12,11 @@ fn generate_man_page(out_dir: &str) -> io::Result { let mut command = Command::new("asciidoctor"); command .args(["-b", "manpage"]) - .args(["-a", concat!("revnumber=", env!("CARGO_PKG_VERSION"))]); - #[cfg(feature = "json")] - command.args(["-a", "json"]); - command .args(["-D", out_dir]) - .args([man_dir.join("man1/*.1.adoc"), man_dir.join("man5/*.5.adoc")]) + .args([ + man_dir.join("man1/*.1.adoc"), + man_dir.join("man5/abcrypt.5.adoc"), + ]) .status() } diff --git a/crates/cli/src/app.rs b/crates/cli/src/app.rs index d971193a..72466183 100644 --- a/crates/cli/src/app.rs +++ b/crates/cli/src/app.rs @@ -4,7 +4,7 @@ use std::path::Path; -use abcrypt::{argon2, Decryptor}; +use abcrypt::{argon2, Argon2, Decryptor}; use anyhow::{bail, Context}; use clap::Parser; @@ -62,7 +62,13 @@ pub fn run() -> anyhow::Result<()> { params::displayln(params.m_cost(), params.t_cost(), params.p_cost()); } - let ciphertext = abcrypt::encrypt_with_params(input, passphrase, params)?; + let ciphertext = abcrypt::encrypt_with_context( + input, + passphrase, + arg.argon2_type.into(), + arg.argon2_version.into(), + params, + )?; if let Some(file) = arg.output { output::write_to_file(&file, &ciphertext)?; @@ -113,6 +119,14 @@ pub fn run() -> anyhow::Result<()> { output::write_to_stdout(&plaintext)?; } } + Command::Argon2(arg) => { + let input = input::read(arg.input.as_deref())?; + + let argon2 = + Argon2::new(input).context("data is not a valid abcrypt encrypted file")?; + eprintln!("Type: {:?}", argon2.variant()); + eprintln!("Version: {:#x}", u32::from(argon2.version())); + } Command::Information(arg) => { let input = input::read(arg.input.as_deref())?; diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index 89f996fd..21fe0396 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -11,7 +11,7 @@ use std::{ str::FromStr, }; -use abcrypt::argon2::Params; +use abcrypt::argon2::{Algorithm, Params, Version}; use anyhow::anyhow; use byte_unit::{Byte, Unit}; use clap::{ @@ -23,7 +23,7 @@ use clap_complete::Generator; const LONG_VERSION: &str = concat!( env!("CARGO_PKG_VERSION"), '\n', - "Copyright (C) 2022-2024 Shun Sakai\n", + "Copyright (C) 2022 Shun Sakai\n", '\n', "This program is distributed under the terms of the GNU General Public License\n", "v3.0 or later.\n", @@ -48,11 +48,9 @@ const DECRYPT_AFTER_LONG_HELP: &str = concat!( "See `abcrypt-decrypt(1)` for more details." ); -const INFORMATION_AFTER_LONG_HELP: &str = concat!( - "The result will be write to standard output.\n", - '\n', - "See `abcrypt-information(1)` for more details." -); +const ARGON2_AFTER_LONG_HELP: &str = "See `abcrypt-argon2(1)` for more details."; + +const INFORMATION_AFTER_LONG_HELP: &str = "See `abcrypt-information(1)` for more details."; #[derive(Debug, Parser)] #[command( @@ -95,6 +93,10 @@ pub enum Command { )] Decrypt(Decrypt), + /// Provides information about the Argon2 context. + #[command(after_long_help(ARGON2_AFTER_LONG_HELP))] + Argon2(Argon2), + /// Provides information about the encryption parameters. #[command( after_long_help(INFORMATION_AFTER_LONG_HELP), @@ -112,6 +114,26 @@ pub struct Encrypt { #[arg(short, long, value_name("FILE"), value_hint(ValueHint::FilePath))] pub output: Option, + /// Set the Argon2 type. + #[arg( + long, + value_enum, + default_value_t, + value_name("TYPE"), + ignore_case(true) + )] + pub argon2_type: Argon2Type, + + /// Set the Argon2 version. + #[arg( + long, + value_enum, + default_value_t, + value_name("VERSION"), + ignore_case(true) + )] + pub argon2_version: Argon2Version, + /// Set the memory size in bytes. /// /// can be suffixed with the symbol (B) and the byte prefix (such as @@ -174,7 +196,6 @@ pub struct Encrypt { } #[derive(Args, Debug)] -#[allow(clippy::struct_excessive_bools)] #[command(group(ArgGroup::new("passphrase")))] pub struct Decrypt { /// Output the result to a file. @@ -220,6 +241,15 @@ pub struct Decrypt { pub input: Option, } +#[derive(Args, Debug)] +pub struct Argon2 { + /// Input file. + /// + /// If [FILE] is not specified, data will be read from standard input. + #[arg(value_name("FILE"), value_hint(ValueHint::FilePath))] + pub input: Option, +} + #[derive(Args, Debug)] pub struct Information { /// Output the encryption parameters as JSON. @@ -294,6 +324,50 @@ impl Generator for Shell { } } +#[derive(Clone, Debug, Default, ValueEnum)] +pub enum Argon2Type { + /// Argon2d. + Argon2d, + + /// Argon2i. + Argon2i, + + /// Argon2id. + #[default] + Argon2id, +} + +impl From for Algorithm { + fn from(argon2_type: Argon2Type) -> Self { + match argon2_type { + Argon2Type::Argon2d => Self::Argon2d, + Argon2Type::Argon2i => Self::Argon2i, + Argon2Type::Argon2id => Self::Argon2id, + } + } +} + +#[derive(Clone, Debug, Default, ValueEnum)] +pub enum Argon2Version { + /// Version 0x10. + #[value(name = "0x10", alias("16"))] + V0x10, + + /// Version 0x13. + #[default] + #[value(name = "0x13", alias("19"))] + V0x13, +} + +impl From for Version { + fn from(argon2_version: Argon2Version) -> Self { + match argon2_version { + Argon2Version::V0x10 => Self::V0x10, + Argon2Version::V0x13 => Self::V0x13, + } + } +} + /// Memory size in 1 KiB memory blocks. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct MemoryCost(u32); diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 58d7b761..24b77b33 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -4,10 +4,7 @@ // Lint levels of rustc. #![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] // Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] #![allow(clippy::multiple_crate_versions)] mod app; diff --git a/crates/cli/tests/argon2.rs b/crates/cli/tests/argon2.rs new file mode 100644 index 00000000..d45d3696 --- /dev/null +++ b/crates/cli/tests/argon2.rs @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: 2024 Shun Sakai +// +// SPDX-License-Identifier: GPL-3.0-or-later + +mod utils; + +use predicates::prelude::predicate; + +#[test] +fn basic_argon2() { + utils::command::command() + .arg("argon2") + .arg("data/v1/argon2d/v0x10/data.txt.abcrypt") + .assert() + .success() + .stderr(predicate::str::contains("Type: Argon2d")) + .stderr(predicate::str::contains("Version: 0x10")); + utils::command::command() + .arg("argon2") + .arg("data/v1/argon2d/v0x13/data.txt.abcrypt") + .assert() + .success() + .stderr(predicate::str::contains("Type: Argon2d")) + .stderr(predicate::str::contains("Version: 0x13")); + utils::command::command() + .arg("argon2") + .arg("data/v1/argon2i/v0x10/data.txt.abcrypt") + .assert() + .success() + .stderr(predicate::str::contains("Type: Argon2i")) + .stderr(predicate::str::contains("Version: 0x10")); + utils::command::command() + .arg("argon2") + .arg("data/v1/argon2i/v0x13/data.txt.abcrypt") + .assert() + .success() + .stderr(predicate::str::contains("Type: Argon2i")) + .stderr(predicate::str::contains("Version: 0x13")); + utils::command::command() + .arg("argon2") + .arg("data/v1/argon2id/v0x10/data.txt.abcrypt") + .assert() + .success() + .stderr(predicate::str::contains("Type: Argon2id")) + .stderr(predicate::str::contains("Version: 0x10")); + utils::command::command() + .arg("argon2") + .arg("data/v1/argon2id/v0x13/data.txt.abcrypt") + .assert() + .success() + .stderr(predicate::str::contains("Type: Argon2id")) + .stderr(predicate::str::contains("Version: 0x13")); +} + +#[test] +fn argon2_if_non_existent_input_file() { + let command = utils::command::command() + .arg("argon2") + .arg("non_existent.txt.abcrypt") + .assert() + .failure() + .code(66) + .stderr(predicate::str::contains( + "could not read data from non_existent.txt.abcrypt", + )); + if cfg!(windows) { + command.stderr(predicate::str::contains( + "The system cannot find the file specified. (os error 2)", + )); + } else { + command.stderr(predicate::str::contains( + "No such file or directory (os error 2)", + )); + } +} + +#[test] +fn argon2_if_input_file_is_invalid() { + utils::command::command() + .arg("argon2") + .arg("data/data.txt") + .assert() + .failure() + .code(65) + .stderr(predicate::str::contains( + "data is not a valid abcrypt encrypted file", + )) + .stderr(predicate::str::contains( + "encrypted data is shorter than 164 bytes", + )); +} + +#[test] +fn long_version_for_argon2_command() { + utils::command::command() + .arg("argon2") + .arg("--version") + .assert() + .success() + .stdout(predicate::str::contains(include_str!( + "assets/long-version.md" + ))); +} + +#[test] +fn after_long_help_for_argon2_command() { + utils::command::command() + .arg("argon2") + .arg("--help") + .assert() + .success() + .stdout(predicate::str::contains(include_str!( + "assets/argon2-after-long-help.md" + ))); +} diff --git a/crates/cli/tests/assets/argon2-after-long-help.md b/crates/cli/tests/assets/argon2-after-long-help.md new file mode 100644 index 00000000..0db16676 --- /dev/null +++ b/crates/cli/tests/assets/argon2-after-long-help.md @@ -0,0 +1 @@ +See `abcrypt-argon2(1)` for more details. diff --git a/crates/cli/tests/assets/argon2-after-long-help.md.license b/crates/cli/tests/assets/argon2-after-long-help.md.license new file mode 100644 index 00000000..28dcc968 --- /dev/null +++ b/crates/cli/tests/assets/argon2-after-long-help.md.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Shun Sakai + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/crates/cli/tests/assets/information-after-long-help.md b/crates/cli/tests/assets/information-after-long-help.md index d2f2a7ea..e3312bff 100644 --- a/crates/cli/tests/assets/information-after-long-help.md +++ b/crates/cli/tests/assets/information-after-long-help.md @@ -1,3 +1 @@ -The result will be write to standard output. - See `abcrypt-information(1)` for more details. diff --git a/crates/cli/tests/assets/long-version.md b/crates/cli/tests/assets/long-version.md index 5ed4bcc5..3d504ccf 100644 --- a/crates/cli/tests/assets/long-version.md +++ b/crates/cli/tests/assets/long-version.md @@ -1,4 +1,4 @@ -Copyright (C) 2022-2024 Shun Sakai +Copyright (C) 2022 Shun Sakai This program is distributed under the terms of the GNU General Public License v3.0 or later. diff --git a/crates/cli/tests/data/data.txt.abcrypt b/crates/cli/tests/data/data.txt.abcrypt deleted file mode 120000 index c2f3c6dc..00000000 --- a/crates/cli/tests/data/data.txt.abcrypt +++ /dev/null @@ -1 +0,0 @@ -../../../abcrypt/tests/data/data.txt.abcrypt \ No newline at end of file diff --git a/crates/cli/tests/data/v0 b/crates/cli/tests/data/v0 new file mode 120000 index 00000000..5e179fcd --- /dev/null +++ b/crates/cli/tests/data/v0 @@ -0,0 +1 @@ +../../../abcrypt/tests/data/v0 \ No newline at end of file diff --git a/crates/cli/tests/data/v1 b/crates/cli/tests/data/v1 new file mode 120000 index 00000000..71f44989 --- /dev/null +++ b/crates/cli/tests/data/v1 @@ -0,0 +1 @@ +../../../abcrypt/tests/data/v1 \ No newline at end of file diff --git a/crates/cli/tests/decrypt.rs b/crates/cli/tests/decrypt.rs index 2b0a6d46..ab2d068c 100644 --- a/crates/cli/tests/decrypt.rs +++ b/crates/cli/tests/decrypt.rs @@ -2,14 +2,6 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] -#![allow(clippy::multiple_crate_versions)] - mod utils; use predicates::prelude::predicate; @@ -19,7 +11,47 @@ fn basic_decrypt() { utils::command::command() .arg("decrypt") .arg("--passphrase-from-stdin") - .arg("data/data.txt.abcrypt") + .arg("data/v1/argon2d/v0x10/data.txt.abcrypt") + .write_stdin("passphrase") + .assert() + .success() + .stdout(predicate::eq("Hello, world!\n")); + utils::command::command() + .arg("decrypt") + .arg("--passphrase-from-stdin") + .arg("data/v1/argon2d/v0x13/data.txt.abcrypt") + .write_stdin("passphrase") + .assert() + .success() + .stdout(predicate::eq("Hello, world!\n")); + utils::command::command() + .arg("decrypt") + .arg("--passphrase-from-stdin") + .arg("data/v1/argon2i/v0x10/data.txt.abcrypt") + .write_stdin("passphrase") + .assert() + .success() + .stdout(predicate::eq("Hello, world!\n")); + utils::command::command() + .arg("decrypt") + .arg("--passphrase-from-stdin") + .arg("data/v1/argon2i/v0x13/data.txt.abcrypt") + .write_stdin("passphrase") + .assert() + .success() + .stdout(predicate::eq("Hello, world!\n")); + utils::command::command() + .arg("decrypt") + .arg("--passphrase-from-stdin") + .arg("data/v1/argon2id/v0x10/data.txt.abcrypt") + .write_stdin("passphrase") + .assert() + .success() + .stdout(predicate::eq("Hello, world!\n")); + utils::command::command() + .arg("decrypt") + .arg("--passphrase-from-stdin") + .arg("data/v1/argon2id/v0x13/data.txt.abcrypt") .write_stdin("passphrase") .assert() .success() @@ -71,7 +103,7 @@ fn decrypt_if_output_is_directory() { .arg("-o") .arg("data/dummy") .arg("--passphrase-from-stdin") - .arg("data/data.txt.abcrypt") + .arg("data/v1/argon2id/v0x13/data.txt.abcrypt") .write_stdin("passphrase") .assert() .failure() @@ -112,7 +144,7 @@ fn decrypt_if_input_file_is_invalid() { "data is not a valid abcrypt encrypted file", )) .stderr(predicate::str::contains( - "encrypted data is shorter than 156 bytes", + "encrypted data is shorter than 164 bytes", )); } @@ -121,7 +153,7 @@ fn decrypt_if_passphrase_is_incorrect() { utils::command::command() .arg("decrypt") .arg("--passphrase-from-stdin") - .arg("data/data.txt.abcrypt") + .arg("data/v1/argon2id/v0x13/data.txt.abcrypt") .write_stdin("password") .assert() .failure() @@ -131,13 +163,89 @@ fn decrypt_if_passphrase_is_incorrect() { .stderr(predicate::str::contains("MAC tag mismatch")); } +#[test] +fn decrypt_from_unsupported_version() { + utils::command::command() + .arg("decrypt") + .arg("--passphrase-from-stdin") + .arg("data/v0/data.txt.abcrypt") + .write_stdin("passphrase") + .assert() + .failure() + .code(65) + .stderr(predicate::str::contains( + "data is not a valid abcrypt encrypted file", + )) + .stderr(predicate::str::contains("unsupported version number `0`")); +} + #[test] fn decrypt_verbose() { utils::command::command() .arg("decrypt") .arg("--passphrase-from-stdin") .arg("-v") - .arg("data/data.txt.abcrypt") + .arg("data/v1/argon2d/v0x10/data.txt.abcrypt") + .write_stdin("passphrase") + .assert() + .success() + .stdout(predicate::eq("Hello, world!\n")) + .stderr(predicate::str::starts_with( + "Parameters used: memoryCost = 47104; timeCost = 1; parallelism = 1;", + )); + utils::command::command() + .arg("decrypt") + .arg("--passphrase-from-stdin") + .arg("-v") + .arg("data/v1/argon2d/v0x13/data.txt.abcrypt") + .write_stdin("passphrase") + .assert() + .success() + .stdout(predicate::eq("Hello, world!\n")) + .stderr(predicate::str::starts_with( + "Parameters used: memoryCost = 19456; timeCost = 2; parallelism = 1;", + )); + utils::command::command() + .arg("decrypt") + .arg("--passphrase-from-stdin") + .arg("-v") + .arg("data/v1/argon2i/v0x10/data.txt.abcrypt") + .write_stdin("passphrase") + .assert() + .success() + .stdout(predicate::eq("Hello, world!\n")) + .stderr(predicate::str::starts_with( + "Parameters used: memoryCost = 12288; timeCost = 3; parallelism = 1;", + )); + utils::command::command() + .arg("decrypt") + .arg("--passphrase-from-stdin") + .arg("-v") + .arg("data/v1/argon2i/v0x13/data.txt.abcrypt") + .write_stdin("passphrase") + .assert() + .success() + .stdout(predicate::eq("Hello, world!\n")) + .stderr(predicate::str::starts_with( + "Parameters used: memoryCost = 9216; timeCost = 4; parallelism = 1;", + )); + utils::command::command() + .arg("decrypt") + .arg("--passphrase-from-stdin") + .arg("-v") + .arg("data/v1/argon2id/v0x10/data.txt.abcrypt") + .write_stdin("passphrase") + .assert() + .success() + .stdout(predicate::eq("Hello, world!\n")) + .stderr(predicate::str::starts_with( + "Parameters used: memoryCost = 7168; timeCost = 5; parallelism = 1;", + )); + utils::command::command() + .arg("decrypt") + .arg("--passphrase-from-stdin") + .arg("-v") + .arg("data/v1/argon2id/v0x13/data.txt.abcrypt") .write_stdin("passphrase") .assert() .success() diff --git a/crates/cli/tests/encrypt.rs b/crates/cli/tests/encrypt.rs index 2470e434..87381b45 100644 --- a/crates/cli/tests/encrypt.rs +++ b/crates/cli/tests/encrypt.rs @@ -2,14 +2,6 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] -#![allow(clippy::multiple_crate_versions)] - mod utils; use predicates::prelude::predicate; @@ -84,6 +76,164 @@ fn encrypt_if_output_is_directory() { } } +#[test] +fn encrypt_with_argon2_type() { + { + let output = utils::command::command() + .arg("encrypt") + .arg("--argon2-type") + .arg("argon2d") + .arg("--passphrase-from-stdin") + .arg("data/data.txt") + .write_stdin("passphrase") + .output() + .unwrap(); + assert_eq!(&output.stdout[8..12], u32::to_le_bytes(0)); + } + { + let output = utils::command::command() + .arg("encrypt") + .arg("--argon2-type") + .arg("argon2i") + .arg("--passphrase-from-stdin") + .arg("data/data.txt") + .write_stdin("passphrase") + .output() + .unwrap(); + assert_eq!(&output.stdout[8..12], u32::to_le_bytes(1)); + } + { + let output = utils::command::command() + .arg("encrypt") + .arg("--argon2-type") + .arg("argon2id") + .arg("--passphrase-from-stdin") + .arg("data/data.txt") + .write_stdin("passphrase") + .output() + .unwrap(); + assert_eq!(&output.stdout[8..12], u32::to_le_bytes(2)); + } +} + +#[test] +fn encrypt_with_default_argon2_type() { + let output = utils::command::command() + .arg("encrypt") + .arg("--argon2-type") + .arg("argon2id") + .arg("--passphrase-from-stdin") + .arg("data/data.txt") + .write_stdin("passphrase") + .output() + .unwrap(); + assert_eq!(&output.stdout[8..12], u32::to_le_bytes(2)); +} + +#[test] +fn encrypt_with_invalid_argon2_type() { + utils::command::command() + .arg("encrypt") + .arg("--argon2-type") + .arg("scrypt") + .arg("--passphrase-from-stdin") + .arg("data/data.txt") + .write_stdin("passphrase") + .assert() + .failure() + .code(2) + .stderr(predicate::str::contains( + "invalid value 'scrypt' for '--argon2-type '", + )); +} + +#[test] +fn encrypt_with_argon2_version() { + { + let output = utils::command::command() + .arg("encrypt") + .arg("--argon2-version") + .arg("0x10") + .arg("--passphrase-from-stdin") + .arg("data/data.txt") + .write_stdin("passphrase") + .output() + .unwrap(); + assert_eq!(&output.stdout[12..16], u32::to_le_bytes(0x10)); + } + { + let output = utils::command::command() + .arg("encrypt") + .arg("--argon2-version") + .arg("0x13") + .arg("--passphrase-from-stdin") + .arg("data/data.txt") + .write_stdin("passphrase") + .output() + .unwrap(); + assert_eq!(&output.stdout[12..16], u32::to_le_bytes(0x13)); + } +} + +#[test] +fn encrypt_with_default_argon2_version() { + let output = utils::command::command() + .arg("encrypt") + .arg("--argon2-version") + .arg("0x13") + .arg("--passphrase-from-stdin") + .arg("data/data.txt") + .write_stdin("passphrase") + .output() + .unwrap(); + assert_eq!(&output.stdout[12..16], u32::to_le_bytes(0x13)); +} + +#[test] +fn encrypt_with_alias_for_argon2_version() { + { + let output = utils::command::command() + .arg("encrypt") + .arg("--argon2-version") + .arg("16") + .arg("--passphrase-from-stdin") + .arg("data/data.txt") + .write_stdin("passphrase") + .output() + .unwrap(); + assert_eq!(&output.stdout[12..16], u32::to_le_bytes(0x10)); + } + { + let output = utils::command::command() + .arg("encrypt") + .arg("--argon2-version") + .arg("19") + .arg("--passphrase-from-stdin") + .arg("data/data.txt") + .write_stdin("passphrase") + .output() + .unwrap(); + assert_eq!(&output.stdout[12..16], u32::to_le_bytes(0x13)); + } +} + +#[test] +fn encrypt_with_invalid_argon2_version() { + utils::command::command() + .arg("encrypt") + .arg("--argon2-version") + .arg("a") + .arg("--passphrase-from-stdin") + .arg("data/data.txt") + .write_stdin("passphrase") + .assert() + .failure() + .code(2) + .stderr(predicate::str::contains( + "invalid value 'a' for '--argon2-version '", + )); +} + #[test] fn validate_memory_cost_with_unit_for_encrypt_command() { utils::command::command() diff --git a/crates/cli/tests/information.rs b/crates/cli/tests/information.rs index 773ed132..1c008166 100644 --- a/crates/cli/tests/information.rs +++ b/crates/cli/tests/information.rs @@ -2,14 +2,6 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] -#![allow(clippy::multiple_crate_versions)] - mod utils; use predicates::prelude::predicate; @@ -18,7 +10,47 @@ use predicates::prelude::predicate; fn basic_information() { utils::command::command() .arg("information") - .arg("data/data.txt.abcrypt") + .arg("data/v1/argon2d/v0x10/data.txt.abcrypt") + .assert() + .success() + .stderr(predicate::str::starts_with( + "Parameters used: memoryCost = 47104; timeCost = 1; parallelism = 1;", + )); + utils::command::command() + .arg("information") + .arg("data/v1/argon2d/v0x13/data.txt.abcrypt") + .assert() + .success() + .stderr(predicate::str::starts_with( + "Parameters used: memoryCost = 19456; timeCost = 2; parallelism = 1;", + )); + utils::command::command() + .arg("information") + .arg("data/v1/argon2i/v0x10/data.txt.abcrypt") + .assert() + .success() + .stderr(predicate::str::starts_with( + "Parameters used: memoryCost = 12288; timeCost = 3; parallelism = 1;", + )); + utils::command::command() + .arg("information") + .arg("data/v1/argon2i/v0x13/data.txt.abcrypt") + .assert() + .success() + .stderr(predicate::str::starts_with( + "Parameters used: memoryCost = 9216; timeCost = 4; parallelism = 1;", + )); + utils::command::command() + .arg("information") + .arg("data/v1/argon2id/v0x10/data.txt.abcrypt") + .assert() + .success() + .stderr(predicate::str::starts_with( + "Parameters used: memoryCost = 7168; timeCost = 5; parallelism = 1;", + )); + utils::command::command() + .arg("information") + .arg("data/v1/argon2id/v0x13/data.txt.abcrypt") .assert() .success() .stderr(predicate::str::starts_with( @@ -68,7 +100,7 @@ fn information_command_without_default_feature() { utils::command::command() .arg("information") .arg("-j") - .arg("data/data.txt.abcrypt") + .arg("data/v1/argon2id/v0x13/data.txt.abcrypt") .assert() .failure() .code(2) @@ -81,7 +113,7 @@ fn information_as_json() { utils::command::command() .arg("information") .arg("-j") - .arg("data/data.txt.abcrypt") + .arg("data/v1/argon2id/v0x13/data.txt.abcrypt") .assert() .success() .stdout(predicate::eq(concat!( @@ -102,7 +134,7 @@ fn information_if_input_file_is_invalid() { "data is not a valid abcrypt encrypted file", )) .stderr(predicate::str::contains( - "encrypted data is shorter than 156 bytes", + "encrypted data is shorter than 164 bytes", )); } diff --git a/crates/cli/tests/integration.rs b/crates/cli/tests/integration.rs index 743d0203..f067c628 100644 --- a/crates/cli/tests/integration.rs +++ b/crates/cli/tests/integration.rs @@ -2,14 +2,6 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] -#![allow(clippy::multiple_crate_versions)] - mod utils; use predicates::prelude::predicate; diff --git a/crates/python/CHANGELOG.adoc b/crates/python/CHANGELOG.adoc index b2c3f4e8..4f5e849c 100644 --- a/crates/python/CHANGELOG.adoc +++ b/crates/python/CHANGELOG.adoc @@ -14,6 +14,17 @@ All notable changes to this project will be documented in this file. The format is based on https://keepachangelog.com/[Keep a Changelog], and this project adheres to https://semver.org/[Semantic Versioning]. +== {compare-url}/abcrypt-py-v0.1.4\...HEAD[Unreleased] + +=== Added + +* Supports the abcrypt version 1 file format ({pull-request-url}/619[#619]) + +=== Removed + +* Remove the abcrypt version 0 file format support + ({pull-request-url}/619[#619]) + == {compare-url}/abcrypt-py-v0.1.3\...abcrypt-py-v0.1.4[0.1.4] - 2024-03-17 === Fixed diff --git a/crates/python/Cargo.toml b/crates/python/Cargo.toml index e219d076..4c79fe7f 100644 --- a/crates/python/Cargo.toml +++ b/crates/python/Cargo.toml @@ -25,5 +25,8 @@ name = "abcrypt_py" crate-type = ["cdylib", "rlib"] [dependencies] -abcrypt = { version = "0.3.7", path = "../abcrypt" } -pyo3 = "0.22.5" +abcrypt = { version = "0.4.0", path = "../abcrypt" } +pyo3 = "0.23.3" + +[lints] +workspace = true diff --git a/crates/python/README.md b/crates/python/README.md index 3d649dea..3a21b892 100644 --- a/crates/python/README.md +++ b/crates/python/README.md @@ -63,9 +63,13 @@ Please see [CHANGELOG.adoc]. Please see [CONTRIBUTING.adoc]. +## Home page + + + ## License -Copyright © 2022–2024 Shun Sakai (see [AUTHORS.adoc]) +Copyright (C) 2022 Shun Sakai (see [AUTHORS.adoc]) This library is distributed under the terms of either the _Apache License 2.0_ or the _MIT License_. diff --git a/crates/python/abcrypt_py.pyi b/crates/python/abcrypt_py.pyi index f59072ce..4cf62e8c 100644 --- a/crates/python/abcrypt_py.pyi +++ b/crates/python/abcrypt_py.pyi @@ -12,6 +12,15 @@ def encrypt_with_params( time_cost: int, parallelism: int, ) -> bytes: ... +def encrypt_with_context( + plaintext: bytes, + passphrase: bytes, + argon2_type: int, + argon2_version: int, + memory_cost: int, + time_cost: int, + parallelism: int, +) -> bytes: ... def decrypt(ciphertext: bytes, passphrase: bytes) -> bytes: ... class Params: diff --git a/crates/python/examples/encrypt.py b/crates/python/examples/encrypt.py index 2e66b8a8..764a556a 100755 --- a/crates/python/examples/encrypt.py +++ b/crates/python/examples/encrypt.py @@ -28,6 +28,22 @@ def main() -> None: help="output the result to a file", metavar="FILE", ) + parser.add_argument( + "--argon2-type", + default=2, + type=int, + choices=range(3), + help="set the Argon2 type", + metavar="TYPE", + ) + parser.add_argument( + "--argon2-version", + default=0x13, + type=int, + choices=[0x10, 0x13], + help="set the Argon2 version", + metavar="VERSION", + ) parser.add_argument( "-m", "--memory-cost", @@ -65,9 +81,11 @@ def main() -> None: plaintext = args.input.read() passphrase = bytes(getpass.getpass("Enter passphrase: "), encoding="utf-8") - ciphertext = abcrypt_py.encrypt_with_params( + ciphertext = abcrypt_py.encrypt_with_context( plaintext, passphrase, + args.argon2_type, + args.argon2_version, args.memory_cost, args.time_cost, args.parallelism, diff --git a/crates/python/pyproject.toml b/crates/python/pyproject.toml index bd77f5ad..4f802dd5 100644 --- a/crates/python/pyproject.toml +++ b/crates/python/pyproject.toml @@ -49,7 +49,6 @@ ignore = [ "D103", "D203", "D212", - "ANN101", "S101", "COM812", "ISC001", diff --git a/crates/python/src/error.rs b/crates/python/src/error.rs index f69c7c61..bae4bb67 100644 --- a/crates/python/src/error.rs +++ b/crates/python/src/error.rs @@ -11,12 +11,14 @@ use pyo3::{exceptions::PyValueError, PyErr}; pub struct Error(abcrypt::Error); impl From for PyErr { + #[inline] fn from(err: Error) -> Self { PyValueError::new_err(err.0.to_string()) } } impl From for Error { + #[inline] fn from(err: abcrypt::Error) -> Self { Self(err) } diff --git a/crates/python/src/lib.rs b/crates/python/src/lib.rs index 1d0b2566..4e0f55d4 100644 --- a/crates/python/src/lib.rs +++ b/crates/python/src/lib.rs @@ -6,10 +6,9 @@ #![doc(html_root_url = "https://docs.rs/abcrypt-py/0.1.4/")] // Lint levels of rustc. -#![deny(missing_debug_implementations, missing_docs)] -#![warn(rust_2018_idioms)] +#![forbid(unsafe_code)] +#![deny(missing_docs)] // Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] #![allow(clippy::redundant_pub_crate)] mod error; @@ -17,7 +16,7 @@ mod params; use std::borrow::Cow; -use abcrypt::argon2; +use abcrypt::argon2::{self, Algorithm, Version}; use pyo3::{ exceptions::PyValueError, prelude::PyModuleMethods, pyclass, pyfunction, pymethods, pymodule, types::PyModule, wrap_pyfunction, Bound, PyResult, @@ -44,11 +43,14 @@ impl Format { /// Encrypts `plaintext` and into a newly allocated `bytes`. /// -/// This uses the recommended Argon2 parameters. +/// This uses the recommended Argon2 parameters according to the OWASP Password +/// Storage Cheat Sheet. This also uses Argon2id as the Argon2 type and version +/// 0x13 as the Argon2 version. /// /// # Errors /// /// Returns an error if the Argon2 context is invalid. +#[inline] #[pyfunction] pub fn encrypt<'a>(plaintext: &[u8], passphrase: &[u8]) -> PyResult> { let ciphertext = abcrypt::encrypt(plaintext, passphrase).map_err(Error::from)?; @@ -58,9 +60,16 @@ pub fn encrypt<'a>(plaintext: &[u8], passphrase: &[u8]) -> PyResult( plaintext: &[u8], @@ -76,19 +85,62 @@ pub fn encrypt_with_params<'a>( Ok(ciphertext.into()) } +/// Encrypts `plaintext` with the specified Argon2 type, Argon2 version and +/// Argon2 parameters and into a newly allocated `bytes`. +/// +/// # Errors +/// +/// Returns an error if any of the following are true: +/// +/// - The Argon2 type is invalid. +/// - The Argon2 version is invalid. +/// - The Argon2 parameters are invalid. +/// - The Argon2 context is invalid. +#[inline] +#[pyfunction] +pub fn encrypt_with_context<'a>( + plaintext: &[u8], + passphrase: &[u8], + argon2_type: u32, + argon2_version: u32, + memory_cost: u32, + time_cost: u32, + parallelism: u32, +) -> PyResult> { + let argon2_type = match argon2_type { + 0 => Ok(Algorithm::Argon2d), + 1 => Ok(Algorithm::Argon2i), + 2 => Ok(Algorithm::Argon2id), + t => Err(abcrypt::Error::InvalidArgon2Type(t)), + } + .map_err(Error::from)?; + let argon2_version = + Version::try_from(argon2_version).map_err(|e| PyValueError::new_err(e.to_string()))?; + let params = argon2::Params::new(memory_cost, time_cost, parallelism, None) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + let ciphertext = + abcrypt::encrypt_with_context(plaintext, passphrase, argon2_type, argon2_version, params) + .map_err(Error::from)?; + Ok(ciphertext.into()) +} + /// Decrypts `ciphertext` and into a newly allocated `bytes`. /// /// # Errors /// /// Returns an error if any of the following are true: /// -/// - `ciphertext` is shorter than 156 bytes. +/// - `ciphertext` is shorter than 164 bytes. /// - The magic number is invalid. +/// - The version number is the unsupported abcrypt version number. /// - The version number is the unrecognized abcrypt version number. +/// - The Argon2 type is invalid. +/// - The Argon2 version is invalid. /// - The Argon2 parameters are invalid. /// - The Argon2 context is invalid. /// - The MAC (authentication tag) of the header is invalid. /// - The MAC (authentication tag) of the ciphertext is invalid. +#[inline] #[pyfunction] pub fn decrypt<'a>(ciphertext: &[u8], passphrase: &[u8]) -> PyResult> { let plaintext = abcrypt::decrypt(ciphertext, passphrase).map_err(Error::from)?; @@ -100,6 +152,7 @@ pub fn decrypt<'a>(ciphertext: &[u8], passphrase: &[u8]) -> PyResult) -> PyResult<()> { m.add_function(wrap_pyfunction!(encrypt, m)?)?; m.add_function(wrap_pyfunction!(encrypt_with_params, m)?)?; + m.add_function(wrap_pyfunction!(encrypt_with_context, m)?)?; m.add_function(wrap_pyfunction!(decrypt, m)?)?; m.add_class::()?; m.add_class::()?; diff --git a/crates/python/src/params.rs b/crates/python/src/params.rs index 9c732d67..0fde7af8 100644 --- a/crates/python/src/params.rs +++ b/crates/python/src/params.rs @@ -21,10 +21,14 @@ impl Params { /// /// Returns an error if any of the following are true: /// - /// - `ciphertext` is shorter than 156 bytes. + /// - `ciphertext` is shorter than 164 bytes. /// - The magic number is invalid. + /// - The version number is the unsupported abcrypt version number. /// - The version number is the unrecognized abcrypt version number. + /// - The Argon2 type is invalid. + /// - The Argon2 version is invalid. /// - The Argon2 parameters are invalid. + #[inline] #[new] pub fn new(ciphertext: &[u8]) -> PyResult { let params = abcrypt::Params::new(ciphertext) diff --git a/crates/python/tests/data b/crates/python/tests/data new file mode 120000 index 00000000..07081484 --- /dev/null +++ b/crates/python/tests/data @@ -0,0 +1 @@ +../../abcrypt/tests/data \ No newline at end of file diff --git a/crates/python/tests/data/data.txt b/crates/python/tests/data/data.txt deleted file mode 120000 index ef291efe..00000000 --- a/crates/python/tests/data/data.txt +++ /dev/null @@ -1 +0,0 @@ -../../../abcrypt/tests/data/data.txt \ No newline at end of file diff --git a/crates/python/tests/data/data.txt.abcrypt b/crates/python/tests/data/data.txt.abcrypt deleted file mode 120000 index c2f3c6dc..00000000 --- a/crates/python/tests/data/data.txt.abcrypt +++ /dev/null @@ -1 +0,0 @@ -../../../abcrypt/tests/data/data.txt.abcrypt \ No newline at end of file diff --git a/crates/python/tests/test_decrypt.py b/crates/python/tests/test_decrypt.py index 7cc813d4..e69ee71f 100644 --- a/crates/python/tests/test_decrypt.py +++ b/crates/python/tests/test_decrypt.py @@ -12,11 +12,53 @@ TEST_DIR: Final[Path] = Path(__file__).resolve().parent TEST_DATA: Final[bytes] = Path(TEST_DIR / "data/data.txt").read_bytes() TEST_DATA_ENC: Final[bytes] = Path( - TEST_DIR / "data/data.txt.abcrypt" + TEST_DIR / "data/v1/argon2id/v0x13/data.txt.abcrypt" ).read_bytes() -def test_success() -> None: +def test_success_from_argon2d_and_v0x10() -> None: + plaintext = abcrypt_py.decrypt( + Path(TEST_DIR / "data/v1/argon2d/v0x10/data.txt.abcrypt").read_bytes(), + PASSPHRASE, + ) + assert plaintext == TEST_DATA + + +def test_success_from_argon2d_and_v0x13() -> None: + plaintext = abcrypt_py.decrypt( + Path(TEST_DIR / "data/v1/argon2d/v0x13/data.txt.abcrypt").read_bytes(), + PASSPHRASE, + ) + assert plaintext == TEST_DATA + + +def test_success_from_argon2i_and_v0x10() -> None: + plaintext = abcrypt_py.decrypt( + Path(TEST_DIR / "data/v1/argon2i/v0x10/data.txt.abcrypt").read_bytes(), + PASSPHRASE, + ) + assert plaintext == TEST_DATA + + +def test_success_from_argon2i_and_v0x13() -> None: + plaintext = abcrypt_py.decrypt( + Path(TEST_DIR / "data/v1/argon2i/v0x13/data.txt.abcrypt").read_bytes(), + PASSPHRASE, + ) + assert plaintext == TEST_DATA + + +def test_success_from_argon2id_and_v0x10() -> None: + plaintext = abcrypt_py.decrypt( + Path( + TEST_DIR / "data/v1/argon2id/v0x10/data.txt.abcrypt" + ).read_bytes(), + PASSPHRASE, + ) + assert plaintext == TEST_DATA + + +def test_success_from_argon2id_and_v0x13() -> None: plaintext = abcrypt_py.decrypt(TEST_DATA_ENC, PASSPHRASE) assert plaintext == TEST_DATA @@ -33,7 +75,7 @@ def test_invalid_input_length_1() -> None: ) with pytest.raises(ValueError) as e: abcrypt_py.decrypt(data, PASSPHRASE) - assert str(e.value) == "encrypted data is shorter than 156 bytes" + assert str(e.value) == "encrypted data is shorter than 164 bytes" def test_invalid_input_length_2() -> None: @@ -51,17 +93,24 @@ def test_invalid_magic_number() -> None: assert str(e.value) == "invalid magic number" +def test_unsupported_version() -> None: + data = Path(TEST_DIR / "data/v0/data.txt.abcrypt").read_bytes() + with pytest.raises(ValueError) as e: + abcrypt_py.decrypt(data, PASSPHRASE) + assert str(e.value) == "unsupported version number `0`" + + def test_unknown_version() -> None: data = bytearray(TEST_DATA_ENC) - data[7] = 1 + data[7] = 2 with pytest.raises(ValueError) as e: abcrypt_py.decrypt(bytes(data), PASSPHRASE) - assert str(e.value) == "unknown version number `1`" + assert str(e.value) == "unknown version number `2`" def test_invalid_memory_cost() -> None: data = bytearray(TEST_DATA_ENC) - data[8:12] = (7).to_bytes(4, byteorder="little") + data[16:20] = (7).to_bytes(4, byteorder="little") with pytest.raises(ValueError) as e: abcrypt_py.decrypt(bytes(data), PASSPHRASE) assert str(e.value) == "invalid Argon2 parameters" @@ -69,7 +118,7 @@ def test_invalid_memory_cost() -> None: def test_invalid_time_cost() -> None: data = bytearray(TEST_DATA_ENC) - data[12:16] = (0).to_bytes(4, byteorder="little") + data[20:24] = (0).to_bytes(4, byteorder="little") with pytest.raises(ValueError) as e: abcrypt_py.decrypt(bytes(data), PASSPHRASE) assert str(e.value) == "invalid Argon2 parameters" @@ -77,7 +126,7 @@ def test_invalid_time_cost() -> None: def test_invalid_parallelism() -> None: data = bytearray(TEST_DATA_ENC) - data[16:20] = (2**24).to_bytes(4, byteorder="little") + data[24:28] = (2**24).to_bytes(4, byteorder="little") with pytest.raises(ValueError) as e: abcrypt_py.decrypt(bytes(data), PASSPHRASE) assert str(e.value) == "invalid Argon2 parameters" @@ -85,9 +134,9 @@ def test_invalid_parallelism() -> None: def test_invalid_header_mac() -> None: data = bytearray(TEST_DATA_ENC) - header_mac = data[76:140] + header_mac = data[84:148] header_mac.reverse() - data[76:140] = header_mac + data[84:148] = header_mac with pytest.raises(ValueError) as e: abcrypt_py.decrypt(bytes(data), PASSPHRASE) assert str(e.value) == "invalid header MAC" diff --git a/crates/python/tests/test_encrypt.py b/crates/python/tests/test_encrypt.py index 57e2d034..1e1e02bc 100644 --- a/crates/python/tests/test_encrypt.py +++ b/crates/python/tests/test_encrypt.py @@ -52,6 +52,132 @@ def test_success_with_params() -> None: assert plaintext == TEST_DATA +def test_success_with_context_to_argon2d_and_v0x10() -> None: + ciphertext = abcrypt_py.encrypt_with_context( + TEST_DATA, PASSPHRASE, 0, 0x10, 47104, 1, 1 + ) + assert ciphertext != TEST_DATA + assert ( + len(ciphertext) + == len(TEST_DATA) + + abcrypt_py.Format.HEADER_SIZE + + abcrypt_py.Format.TAG_SIZE + ) + + params = abcrypt_py.Params(ciphertext) + assert params.memory_cost == 47104 + assert params.time_cost == 1 + assert params.parallelism == 1 + + plaintext = abcrypt_py.decrypt(ciphertext, PASSPHRASE) + assert plaintext == TEST_DATA + + +def test_success_with_context_to_argon2d_and_v0x13() -> None: + ciphertext = abcrypt_py.encrypt_with_context( + TEST_DATA, PASSPHRASE, 0, 0x13, 19456, 2, 1 + ) + assert ciphertext != TEST_DATA + assert ( + len(ciphertext) + == len(TEST_DATA) + + abcrypt_py.Format.HEADER_SIZE + + abcrypt_py.Format.TAG_SIZE + ) + + params = abcrypt_py.Params(ciphertext) + assert params.memory_cost == 19456 + assert params.time_cost == 2 + assert params.parallelism == 1 + + plaintext = abcrypt_py.decrypt(ciphertext, PASSPHRASE) + assert plaintext == TEST_DATA + + +def test_success_with_context_to_argon2i_and_v0x10() -> None: + ciphertext = abcrypt_py.encrypt_with_context( + TEST_DATA, PASSPHRASE, 1, 0x10, 12288, 3, 1 + ) + assert ciphertext != TEST_DATA + assert ( + len(ciphertext) + == len(TEST_DATA) + + abcrypt_py.Format.HEADER_SIZE + + abcrypt_py.Format.TAG_SIZE + ) + + params = abcrypt_py.Params(ciphertext) + assert params.memory_cost == 12288 + assert params.time_cost == 3 + assert params.parallelism == 1 + + plaintext = abcrypt_py.decrypt(ciphertext, PASSPHRASE) + assert plaintext == TEST_DATA + + +def test_success_with_context_to_argon2i_and_v0x13() -> None: + ciphertext = abcrypt_py.encrypt_with_context( + TEST_DATA, PASSPHRASE, 1, 0x13, 9216, 4, 1 + ) + assert ciphertext != TEST_DATA + assert ( + len(ciphertext) + == len(TEST_DATA) + + abcrypt_py.Format.HEADER_SIZE + + abcrypt_py.Format.TAG_SIZE + ) + + params = abcrypt_py.Params(ciphertext) + assert params.memory_cost == 9216 + assert params.time_cost == 4 + assert params.parallelism == 1 + + plaintext = abcrypt_py.decrypt(ciphertext, PASSPHRASE) + assert plaintext == TEST_DATA + + +def test_success_with_context_to_argon2id_and_v0x10() -> None: + ciphertext = abcrypt_py.encrypt_with_context( + TEST_DATA, PASSPHRASE, 2, 0x10, 7168, 5, 1 + ) + assert ciphertext != TEST_DATA + assert ( + len(ciphertext) + == len(TEST_DATA) + + abcrypt_py.Format.HEADER_SIZE + + abcrypt_py.Format.TAG_SIZE + ) + + params = abcrypt_py.Params(ciphertext) + assert params.memory_cost == 7168 + assert params.time_cost == 5 + assert params.parallelism == 1 + + plaintext = abcrypt_py.decrypt(ciphertext, PASSPHRASE) + assert plaintext == TEST_DATA + + +def test_success_with_context_to_argon2id_and_v0x13() -> None: + ciphertext = abcrypt_py.encrypt_with_context( + TEST_DATA, PASSPHRASE, 2, 0x13, 32, 3, 4 + ) + assert ciphertext != TEST_DATA + assert ( + len(ciphertext) + == len(TEST_DATA) + + abcrypt_py.Format.HEADER_SIZE + + abcrypt_py.Format.TAG_SIZE + ) + + params = abcrypt_py.Params(ciphertext) + assert params.memory_cost == 32 + assert params.time_cost == 3 + assert params.parallelism == 4 + + plaintext = abcrypt_py.decrypt(ciphertext, PASSPHRASE) + assert plaintext == TEST_DATA + + def test_minimum_output_length() -> None: ciphertext = abcrypt_py.encrypt_with_params(b"", PASSPHRASE, 32, 3, 4) assert ( @@ -71,25 +197,69 @@ def test_version() -> None: ciphertext = abcrypt_py.encrypt_with_params( TEST_DATA, PASSPHRASE, 32, 3, 4 ) - assert ciphertext[7] == 0 + assert ciphertext[7] == 1 + + +def test_argon2_type_is_argon2d() -> None: + ciphertext = abcrypt_py.encrypt_with_context( + TEST_DATA, PASSPHRASE, 0, 0x13, 32, 3, 4 + ) + assert ciphertext[8:12] == (0).to_bytes(4, byteorder="little") + + +def test_argon2_type_is_argon2i() -> None: + ciphertext = abcrypt_py.encrypt_with_context( + TEST_DATA, PASSPHRASE, 1, 0x13, 32, 3, 4 + ) + assert ciphertext[8:12] == (1).to_bytes(4, byteorder="little") + + +def test_argon2_type_is_argon2id() -> None: + ciphertext = abcrypt_py.encrypt_with_context( + TEST_DATA, PASSPHRASE, 2, 0x13, 32, 3, 4 + ) + assert ciphertext[8:12] == (2).to_bytes(4, byteorder="little") + + +def test_argon2_version_is_v0x10() -> None: + ciphertext = abcrypt_py.encrypt_with_context( + TEST_DATA, PASSPHRASE, 2, 0x10, 32, 3, 4 + ) + assert ciphertext[12:16] == (0x10).to_bytes(4, byteorder="little") + + +def test_argon2_version_is_v0x13() -> None: + ciphertext = abcrypt_py.encrypt_with_context( + TEST_DATA, PASSPHRASE, 2, 0x13, 32, 3, 4 + ) + assert ciphertext[12:16] == (0x13).to_bytes(4, byteorder="little") def test_memory_cost() -> None: ciphertext = abcrypt_py.encrypt_with_params( TEST_DATA, PASSPHRASE, 32, 3, 4 ) - assert ciphertext[8:12] == (32).to_bytes(4, byteorder="little") + assert ciphertext[16:20] == (32).to_bytes(4, byteorder="little") + + params = abcrypt_py.Params(ciphertext) + assert params.memory_cost == 32 def test_time_cost() -> None: ciphertext = abcrypt_py.encrypt_with_params( TEST_DATA, PASSPHRASE, 32, 3, 4 ) - assert ciphertext[12:16] == (3).to_bytes(4, byteorder="little") + assert ciphertext[20:24] == (3).to_bytes(4, byteorder="little") + + params = abcrypt_py.Params(ciphertext) + assert params.time_cost == 3 def test_parallelism() -> None: ciphertext = abcrypt_py.encrypt_with_params( TEST_DATA, PASSPHRASE, 32, 3, 4 ) - assert ciphertext[16:20] == (4).to_bytes(4, byteorder="little") + assert ciphertext[24:28] == (4).to_bytes(4, byteorder="little") + + params = abcrypt_py.Params(ciphertext) + assert params.parallelism == 4 diff --git a/crates/python/tests/test_format.py b/crates/python/tests/test_format.py index b6144819..19fe38d0 100644 --- a/crates/python/tests/test_format.py +++ b/crates/python/tests/test_format.py @@ -6,7 +6,7 @@ def test_header_size() -> None: - assert abcrypt_py.Format.HEADER_SIZE == 140 + assert abcrypt_py.Format.HEADER_SIZE == 148 def test_tag_size() -> None: diff --git a/crates/python/tests/test_params.py b/crates/python/tests/test_params.py index 2024cd7f..2ab59c47 100644 --- a/crates/python/tests/test_params.py +++ b/crates/python/tests/test_params.py @@ -9,7 +9,7 @@ TEST_DIR: Final[Path] = Path(__file__).resolve().parent TEST_DATA_ENC: Final[bytes] = Path( - TEST_DIR / "data/data.txt.abcrypt" + TEST_DIR / "data/v1/argon2id/v0x13/data.txt.abcrypt" ).read_bytes() @@ -17,16 +17,121 @@ def test_success() -> None: abcrypt_py.Params(TEST_DATA_ENC) -def test_memory_cost() -> None: +def test_memory_cost_from_argon2d_and_v0x10() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2d/v0x10/data.txt.abcrypt").read_bytes() + ) + assert params.memory_cost == 47104 + + +def test_memory_cost_from_argon2d_and_v0x13() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2d/v0x13/data.txt.abcrypt").read_bytes() + ) + assert params.memory_cost == 19456 + + +def test_memory_cost_from_argon2i_and_v0x10() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2i/v0x10/data.txt.abcrypt").read_bytes() + ) + assert params.memory_cost == 12288 + + +def test_memory_cost_from_argon2i_and_v0x13() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2i/v0x13/data.txt.abcrypt").read_bytes() + ) + assert params.memory_cost == 9216 + + +def test_memory_cost_from_argon2id_and_v0x10() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2id/v0x10/data.txt.abcrypt").read_bytes() + ) + assert params.memory_cost == 7168 + + +def test_memory_cost_from_argon2id_and_v0x13() -> None: params = abcrypt_py.Params(TEST_DATA_ENC) assert params.memory_cost == 32 -def test_time_cost() -> None: +def test_time_cost_from_argon2d_and_v0x10() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2d/v0x10/data.txt.abcrypt").read_bytes() + ) + assert params.time_cost == 1 + + +def test_time_cost_from_argon2d_and_v0x13() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2d/v0x13/data.txt.abcrypt").read_bytes() + ) + assert params.time_cost == 2 + + +def test_time_cost_from_argon2i_and_v0x10() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2i/v0x10/data.txt.abcrypt").read_bytes() + ) + assert params.time_cost == 3 + + +def test_time_cost_from_argon2i_and_v0x13() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2i/v0x13/data.txt.abcrypt").read_bytes() + ) + assert params.time_cost == 4 + + +def test_time_cost_from_argon2id_and_v0x10() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2id/v0x10/data.txt.abcrypt").read_bytes() + ) + assert params.time_cost == 5 + + +def test_time_cost_from_argon2id_and_v0x13() -> None: params = abcrypt_py.Params(TEST_DATA_ENC) assert params.time_cost == 3 -def test_parallelism() -> None: +def test_parallelism_from_argon2d_and_v0x10() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2d/v0x10/data.txt.abcrypt").read_bytes() + ) + assert params.parallelism == 1 + + +def test_parallelism_from_argon2d_and_v0x13() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2d/v0x13/data.txt.abcrypt").read_bytes() + ) + assert params.parallelism == 1 + + +def test_parallelism_from_argon2i_and_v0x10() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2i/v0x10/data.txt.abcrypt").read_bytes() + ) + assert params.parallelism == 1 + + +def test_parallelism_from_argon2i_and_v0x13() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2i/v0x13/data.txt.abcrypt").read_bytes() + ) + assert params.parallelism == 1 + + +def test_parallelism_from_argon2id_and_v0x10() -> None: + params = abcrypt_py.Params( + Path(TEST_DIR / "data/v1/argon2id/v0x10/data.txt.abcrypt").read_bytes() + ) + assert params.parallelism == 1 + + +def test_parallelism_from_argon2id_and_v0x13() -> None: params = abcrypt_py.Params(TEST_DATA_ENC) assert params.parallelism == 4 diff --git a/crates/wasm/CHANGELOG.adoc b/crates/wasm/CHANGELOG.adoc index 042b3bf2..939a5131 100644 --- a/crates/wasm/CHANGELOG.adoc +++ b/crates/wasm/CHANGELOG.adoc @@ -14,6 +14,17 @@ All notable changes to this project will be documented in this file. The format is based on https://keepachangelog.com/[Keep a Changelog], and this project adheres to https://semver.org/[Semantic Versioning]. +== {compare-url}/abcrypt-wasm-v0.3.2\...HEAD[Unreleased] + +=== Added + +* Supports the abcrypt version 1 file format ({pull-request-url}/619[#619]) + +=== Removed + +* Remove the abcrypt version 0 file format support + ({pull-request-url}/619[#619]) + == {compare-url}/abcrypt-wasm-v0.3.1\...abcrypt-wasm-v0.3.2[0.3.2] - 2024-02-28 === Changed diff --git a/crates/wasm/Cargo.toml b/crates/wasm/Cargo.toml index 5ba89d09..f9e09339 100644 --- a/crates/wasm/Cargo.toml +++ b/crates/wasm/Cargo.toml @@ -24,9 +24,12 @@ include = ["/LICENSES", "/README.md", "/src"] crate-type = ["cdylib", "rlib"] [dependencies] -abcrypt = { version = "0.3.7", path = "../abcrypt" } +abcrypt = { version = "0.4.0", path = "../abcrypt" } getrandom = { version = "0.2.15", features = ["js"] } -wasm-bindgen = "0.2.95" +wasm-bindgen = "0.2.99" [dev-dependencies] -wasm-bindgen-test = "0.3.45" +wasm-bindgen-test = "0.3.49" + +[lints] +workspace = true diff --git a/crates/wasm/README.md b/crates/wasm/README.md index cae65af3..e019fd75 100644 --- a/crates/wasm/README.md +++ b/crates/wasm/README.md @@ -61,9 +61,13 @@ Please see [CHANGELOG.adoc]. Please see [CONTRIBUTING.adoc]. +## Home page + + + ## License -Copyright © 2022–2024 Shun Sakai (see [AUTHORS.adoc]) +Copyright (C) 2022 Shun Sakai (see [AUTHORS.adoc]) This library is distributed under the terms of either the _Apache License 2.0_ or the _MIT License_. diff --git a/crates/wasm/examples/encrypt.ts b/crates/wasm/examples/encrypt.ts index bc4f00e9..986e8cdf 100755 --- a/crates/wasm/examples/encrypt.ts +++ b/crates/wasm/examples/encrypt.ts @@ -16,6 +16,12 @@ const { args, options } = await new command.Command() .name("encrypt") .version(VERSION) .description("An example of encrypting to the abcrypt encrypted data format.") + .option("--argon2-type ", "Set the Argon2 type.", { + default: 2, + }) + .option("--argon2-version ", "Set the Argon2 version.", { + default: 0x13, + }) .option("-m, --memory-cost ", "Set the memory size in KiB.", { default: 19456, }) @@ -32,9 +38,11 @@ const plaintext = Deno.readFileSync(args[0]); const passphrase = new TextEncoder() .encode(cli.promptSecret("Enter passphrase: ")!); -const ciphertext = abcrypt.encryptWithParams( +const ciphertext = abcrypt.encryptWithContext( plaintext, passphrase, + options.argon2Type, + options.argon2Version, options.memoryCost, options.timeCost, options.parallelism, diff --git a/crates/wasm/src/decrypt.rs b/crates/wasm/src/decrypt.rs index 8441d479..831f161b 100644 --- a/crates/wasm/src/decrypt.rs +++ b/crates/wasm/src/decrypt.rs @@ -12,13 +12,17 @@ use wasm_bindgen::{prelude::wasm_bindgen, JsError}; /// /// Returns an error if any of the following are true: /// -/// - `ciphertext` is shorter than 156 bytes. +/// - `ciphertext` is shorter than 164 bytes. /// - The magic number is invalid. +/// - The version number is the unsupported abcrypt version number. /// - The version number is the unrecognized abcrypt version number. +/// - The Argon2 type is invalid. +/// - The Argon2 version is invalid. /// - The Argon2 parameters are invalid. /// - The Argon2 context is invalid. /// - The MAC (authentication tag) of the header is invalid. /// - The MAC (authentication tag) of the ciphertext is invalid. +#[inline] #[wasm_bindgen] pub fn decrypt(ciphertext: &[u8], passphrase: &[u8]) -> Result, JsError> { abcrypt::decrypt(ciphertext, passphrase).map_err(JsError::from) diff --git a/crates/wasm/src/encrypt.rs b/crates/wasm/src/encrypt.rs index bd5520a8..435a260a 100644 --- a/crates/wasm/src/encrypt.rs +++ b/crates/wasm/src/encrypt.rs @@ -4,16 +4,22 @@ //! Encrypts to the abcrypt encrypted data format. -use abcrypt::argon2::Params; +use abcrypt::{ + argon2::{Algorithm, Params}, + Error, +}; use wasm_bindgen::{prelude::wasm_bindgen, JsError}; /// Encrypts `plaintext` and into a newly allocated `Uint8Array`. /// -/// This uses the recommended Argon2 parameters. +/// This uses the recommended Argon2 parameters according to the OWASP Password +/// Storage Cheat Sheet. This also uses Argon2id as the Argon2 type and version +/// 0x13 as the Argon2 version. /// /// # Errors /// /// Returns an error if the Argon2 context is invalid. +#[inline] #[wasm_bindgen] pub fn encrypt(plaintext: &[u8], passphrase: &[u8]) -> Result, JsError> { abcrypt::encrypt(plaintext, passphrase).map_err(JsError::from) @@ -23,9 +29,16 @@ pub fn encrypt(plaintext: &[u8], passphrase: &[u8]) -> Result, JsError> /// Encrypts `plaintext` with the specified Argon2 parameters and into a newly /// allocated `Uint8Array`. /// +/// This uses Argon2id as the Argon2 type and version 0x13 as the Argon2 +/// version. +/// /// # Errors /// -/// Returns an error if the Argon2 context is invalid. +/// Returns an error if any of the following are true: +/// +/// - The Argon2 parameters are invalid. +/// - The Argon2 context is invalid. +#[inline] #[wasm_bindgen(js_name = encryptWithParams)] pub fn encrypt_with_params( plaintext: &[u8], @@ -37,3 +50,38 @@ pub fn encrypt_with_params( let params = Params::new(memory_cost, time_cost, parallelism, None)?; abcrypt::encrypt_with_params(plaintext, passphrase, params).map_err(JsError::from) } + +#[allow(clippy::module_name_repetitions)] +/// Encrypts `plaintext` with the specified Argon2 type, Argon2 version and +/// Argon2 parameters and into a newly allocated `Uint8Array`. +/// +/// # Errors +/// +/// Returns an error if any of the following are true: +/// +/// - The Argon2 type is invalid. +/// - The Argon2 version is invalid. +/// - The Argon2 parameters are invalid. +/// - The Argon2 context is invalid. +#[inline] +#[wasm_bindgen(js_name = encryptWithContext)] +pub fn encrypt_with_context( + plaintext: &[u8], + passphrase: &[u8], + argon2_type: u32, + argon2_version: u32, + memory_cost: u32, + time_cost: u32, + parallelism: u32, +) -> Result, JsError> { + let argon2_type = match argon2_type { + 0 => Ok(Algorithm::Argon2d), + 1 => Ok(Algorithm::Argon2i), + 2 => Ok(Algorithm::Argon2id), + t => Err(Error::InvalidArgon2Type(t)), + }?; + let argon2_version = argon2_version.try_into()?; + let params = Params::new(memory_cost, time_cost, parallelism, None)?; + abcrypt::encrypt_with_context(plaintext, passphrase, argon2_type, argon2_version, params) + .map_err(JsError::from) +} diff --git a/crates/wasm/src/lib.rs b/crates/wasm/src/lib.rs index b1dc0267..02df645e 100644 --- a/crates/wasm/src/lib.rs +++ b/crates/wasm/src/lib.rs @@ -7,10 +7,7 @@ #![doc(html_root_url = "https://docs.rs/abcrypt-wasm/0.3.2/")] // Lint levels of rustc. #![forbid(unsafe_code)] -#![deny(missing_debug_implementations, missing_docs)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] +#![deny(missing_docs)] mod decrypt; mod encrypt; @@ -20,7 +17,7 @@ use wasm_bindgen::prelude::wasm_bindgen; pub use crate::{ decrypt::decrypt, - encrypt::{encrypt, encrypt_with_params}, + encrypt::{encrypt, encrypt_with_context, encrypt_with_params}, params::Params, }; @@ -49,7 +46,7 @@ mod tests { #[wasm_bindgen_test] fn header_size() { - assert_eq!(super::header_size(), 140); + assert_eq!(super::header_size(), 148); assert_eq!(super::header_size(), abcrypt::HEADER_SIZE); } diff --git a/crates/wasm/src/params.rs b/crates/wasm/src/params.rs index 02cd0e0d..7e21a205 100644 --- a/crates/wasm/src/params.rs +++ b/crates/wasm/src/params.rs @@ -13,19 +13,22 @@ pub struct Params(abcrypt::Params); #[wasm_bindgen] impl Params { - #[allow(clippy::use_self)] /// Creates a new instance of the Argon2 parameters from `ciphertext`. /// /// # Errors /// /// Returns an error if any of the following are true: /// - /// - `ciphertext` is shorter than 156 bytes. + /// - `ciphertext` is shorter than 164 bytes. /// - The magic number is invalid. + /// - The version number is the unsupported abcrypt version number. /// - The version number is the unrecognized abcrypt version number. + /// - The Argon2 type is invalid. + /// - The Argon2 version is invalid. /// - The Argon2 parameters are invalid. + #[inline] #[wasm_bindgen(constructor)] - pub fn new(ciphertext: &[u8]) -> Result { + pub fn new(ciphertext: &[u8]) -> Result { abcrypt::Params::new(ciphertext) .map(Self) .map_err(JsError::from) diff --git a/crates/wasm/tests/data b/crates/wasm/tests/data new file mode 120000 index 00000000..07081484 --- /dev/null +++ b/crates/wasm/tests/data @@ -0,0 +1 @@ +../../abcrypt/tests/data \ No newline at end of file diff --git a/crates/wasm/tests/data/data.txt b/crates/wasm/tests/data/data.txt deleted file mode 120000 index ef291efe..00000000 --- a/crates/wasm/tests/data/data.txt +++ /dev/null @@ -1 +0,0 @@ -../../../abcrypt/tests/data/data.txt \ No newline at end of file diff --git a/crates/wasm/tests/data/data.txt.abcrypt b/crates/wasm/tests/data/data.txt.abcrypt deleted file mode 120000 index c2f3c6dc..00000000 --- a/crates/wasm/tests/data/data.txt.abcrypt +++ /dev/null @@ -1 +0,0 @@ -../../../abcrypt/tests/data/data.txt.abcrypt \ No newline at end of file diff --git a/crates/wasm/tests/decrypt.rs b/crates/wasm/tests/decrypt.rs index 533565b4..c3b222a6 100644 --- a/crates/wasm/tests/decrypt.rs +++ b/crates/wasm/tests/decrypt.rs @@ -2,27 +2,67 @@ // // SPDX-License-Identifier: Apache-2.0 OR MIT -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] - use wasm_bindgen::JsValue; use wasm_bindgen_test::wasm_bindgen_test; const PASSPHRASE: &[u8] = b"passphrase"; const TEST_DATA: &[u8] = include_bytes!("data/data.txt"); -// Generated using `abcrypt` crate version 0.1.0. -const TEST_DATA_ENC: &[u8] = include_bytes!("data/data.txt.abcrypt"); +// Generated using `abcrypt` crate version 0.4.0. +const TEST_DATA_ENC: &[u8] = include_bytes!("data/v1/argon2id/v0x13/data.txt.abcrypt"); #[wasm_bindgen_test] fn success() { - let plaintext = abcrypt_wasm::decrypt(TEST_DATA_ENC, PASSPHRASE) + { + let plaintext = abcrypt_wasm::decrypt( + include_bytes!("data/v1/argon2d/v0x10/data.txt.abcrypt"), + PASSPHRASE, + ) .map_err(JsValue::from) .unwrap(); - assert_eq!(plaintext, TEST_DATA); + assert_eq!(plaintext, TEST_DATA); + } + { + let plaintext = abcrypt_wasm::decrypt( + include_bytes!("data/v1/argon2d/v0x13/data.txt.abcrypt"), + PASSPHRASE, + ) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let plaintext = abcrypt_wasm::decrypt( + include_bytes!("data/v1/argon2i/v0x10/data.txt.abcrypt"), + PASSPHRASE, + ) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let plaintext = abcrypt_wasm::decrypt( + include_bytes!("data/v1/argon2i/v0x13/data.txt.abcrypt"), + PASSPHRASE, + ) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let plaintext = abcrypt_wasm::decrypt( + include_bytes!("data/v1/argon2id/v0x10/data.txt.abcrypt"), + PASSPHRASE, + ) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let plaintext = abcrypt_wasm::decrypt(TEST_DATA_ENC, PASSPHRASE) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } } #[wasm_bindgen_test] @@ -55,10 +95,16 @@ fn invalid_magic_number() { assert!(result.is_err()); } +#[wasm_bindgen_test] +fn unsupported_version() { + let result = abcrypt_wasm::decrypt(include_bytes!("data/v0/data.txt.abcrypt"), PASSPHRASE); + assert!(result.is_err()); +} + #[wasm_bindgen_test] fn unknown_version() { let mut data: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); - data[7] = 1; + data[7] = 2; let result = abcrypt_wasm::decrypt(&data, PASSPHRASE); assert!(result.is_err()); } @@ -68,19 +114,19 @@ fn invalid_params() { let mut data: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); { - data[8..12].copy_from_slice(&u32::to_le_bytes(7)); + data[16..20].copy_from_slice(&u32::to_le_bytes(7)); let result = abcrypt_wasm::decrypt(&data, PASSPHRASE); assert!(result.is_err()); } { - data[12..16].copy_from_slice(&u32::to_le_bytes(0)); + data[20..24].copy_from_slice(&u32::to_le_bytes(0)); let result = abcrypt_wasm::decrypt(&data, PASSPHRASE); assert!(result.is_err()); } { - data[16..20].copy_from_slice(&u32::pow(2, 24).to_le_bytes()); + data[24..28].copy_from_slice(&u32::pow(2, 24).to_le_bytes()); let result = abcrypt_wasm::decrypt(&data, PASSPHRASE); assert!(result.is_err()); } @@ -89,9 +135,9 @@ fn invalid_params() { #[wasm_bindgen_test] fn invalid_header_mac() { let mut data: [u8; TEST_DATA_ENC.len()] = TEST_DATA_ENC.try_into().unwrap(); - let mut header_mac: [u8; 64] = data[76..140].try_into().unwrap(); + let mut header_mac: [u8; 64] = data[84..148].try_into().unwrap(); header_mac.reverse(); - data[76..140].copy_from_slice(&header_mac); + data[84..148].copy_from_slice(&header_mac); let result = abcrypt_wasm::decrypt(&data, PASSPHRASE); assert!(result.is_err()); } diff --git a/crates/wasm/tests/encrypt.rs b/crates/wasm/tests/encrypt.rs index 6103ac55..4e33da95 100644 --- a/crates/wasm/tests/encrypt.rs +++ b/crates/wasm/tests/encrypt.rs @@ -2,13 +2,6 @@ // // SPDX-License-Identifier: Apache-2.0 OR MIT -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] - use abcrypt_wasm::Params; use wasm_bindgen::JsValue; use wasm_bindgen_test::wasm_bindgen_test; @@ -60,6 +53,136 @@ fn success_with_params() { assert_eq!(plaintext, TEST_DATA); } +#[wasm_bindgen_test] +fn success_with_context() { + { + let ciphertext = + abcrypt_wasm::encrypt_with_context(TEST_DATA, PASSPHRASE, 0, 0x10, 47104, 1, 1) + .map_err(JsValue::from) + .unwrap(); + assert_ne!(ciphertext, TEST_DATA); + assert_eq!( + ciphertext.len(), + TEST_DATA.len() + abcrypt_wasm::header_size() + abcrypt_wasm::tag_size() + ); + + let params = Params::new(&ciphertext).map_err(JsValue::from).unwrap(); + assert_eq!(params.memory_cost(), 47104); + assert_eq!(params.time_cost(), 1); + assert_eq!(params.parallelism(), 1); + + let plaintext = abcrypt_wasm::decrypt(&ciphertext, PASSPHRASE) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let ciphertext = + abcrypt_wasm::encrypt_with_context(TEST_DATA, PASSPHRASE, 0, 0x13, 19456, 2, 1) + .map_err(JsValue::from) + .unwrap(); + assert_ne!(ciphertext, TEST_DATA); + assert_eq!( + ciphertext.len(), + TEST_DATA.len() + abcrypt_wasm::header_size() + abcrypt_wasm::tag_size() + ); + + let params = Params::new(&ciphertext).map_err(JsValue::from).unwrap(); + assert_eq!(params.memory_cost(), 19456); + assert_eq!(params.time_cost(), 2); + assert_eq!(params.parallelism(), 1); + + let plaintext = abcrypt_wasm::decrypt(&ciphertext, PASSPHRASE) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let ciphertext = + abcrypt_wasm::encrypt_with_context(TEST_DATA, PASSPHRASE, 1, 0x10, 12288, 3, 1) + .map_err(JsValue::from) + .unwrap(); + assert_ne!(ciphertext, TEST_DATA); + assert_eq!( + ciphertext.len(), + TEST_DATA.len() + abcrypt_wasm::header_size() + abcrypt_wasm::tag_size() + ); + + let params = Params::new(&ciphertext).map_err(JsValue::from).unwrap(); + assert_eq!(params.memory_cost(), 12288); + assert_eq!(params.time_cost(), 3); + assert_eq!(params.parallelism(), 1); + + let plaintext = abcrypt_wasm::decrypt(&ciphertext, PASSPHRASE) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let ciphertext = + abcrypt_wasm::encrypt_with_context(TEST_DATA, PASSPHRASE, 1, 0x13, 9216, 4, 1) + .map_err(JsValue::from) + .unwrap(); + assert_ne!(ciphertext, TEST_DATA); + assert_eq!( + ciphertext.len(), + TEST_DATA.len() + abcrypt_wasm::header_size() + abcrypt_wasm::tag_size() + ); + + let params = Params::new(&ciphertext).map_err(JsValue::from).unwrap(); + assert_eq!(params.memory_cost(), 9216); + assert_eq!(params.time_cost(), 4); + assert_eq!(params.parallelism(), 1); + + let plaintext = abcrypt_wasm::decrypt(&ciphertext, PASSPHRASE) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let ciphertext = + abcrypt_wasm::encrypt_with_context(TEST_DATA, PASSPHRASE, 2, 0x10, 7168, 5, 1) + .map_err(JsValue::from) + .unwrap(); + assert_ne!(ciphertext, TEST_DATA); + assert_eq!( + ciphertext.len(), + TEST_DATA.len() + abcrypt_wasm::header_size() + abcrypt_wasm::tag_size() + ); + + let params = Params::new(&ciphertext).map_err(JsValue::from).unwrap(); + assert_eq!(params.memory_cost(), 7168); + assert_eq!(params.time_cost(), 5); + assert_eq!(params.parallelism(), 1); + + let plaintext = abcrypt_wasm::decrypt(&ciphertext, PASSPHRASE) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } + { + let ciphertext = + abcrypt_wasm::encrypt_with_context(TEST_DATA, PASSPHRASE, 2, 0x10, 32, 3, 4) + .map_err(JsValue::from) + .unwrap(); + assert_ne!(ciphertext, TEST_DATA); + assert_eq!( + ciphertext.len(), + TEST_DATA.len() + abcrypt_wasm::header_size() + abcrypt_wasm::tag_size() + ); + + let params = Params::new(&ciphertext).map_err(JsValue::from).unwrap(); + assert_eq!(params.memory_cost(), 32); + assert_eq!(params.time_cost(), 3); + assert_eq!(params.parallelism(), 4); + + let plaintext = abcrypt_wasm::decrypt(&ciphertext, PASSPHRASE) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(plaintext, TEST_DATA); + } +} + #[wasm_bindgen_test] fn minimum_output_length() { let ciphertext = abcrypt_wasm::encrypt_with_params(&[], PASSPHRASE, 32, 3, 4) @@ -84,7 +207,50 @@ fn version() { let ciphertext = abcrypt_wasm::encrypt_with_params(TEST_DATA, PASSPHRASE, 32, 3, 4) .map_err(JsValue::from) .unwrap(); - assert_eq!(ciphertext[7], 0); + assert_eq!(ciphertext[7], 1); +} + +#[wasm_bindgen_test] +fn argon2_type() { + { + let ciphertext = + abcrypt_wasm::encrypt_with_context(TEST_DATA, PASSPHRASE, 0, 0x13, 32, 3, 4) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(&ciphertext[8..12], u32::to_le_bytes(0)); + } + { + let ciphertext = + abcrypt_wasm::encrypt_with_context(TEST_DATA, PASSPHRASE, 1, 0x13, 32, 3, 4) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(&ciphertext[8..12], u32::to_le_bytes(1)); + } + { + let ciphertext = + abcrypt_wasm::encrypt_with_context(TEST_DATA, PASSPHRASE, 2, 0x13, 32, 3, 4) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(&ciphertext[8..12], u32::to_le_bytes(2)); + } +} + +#[wasm_bindgen_test] +fn argon2_version() { + { + let ciphertext = + abcrypt_wasm::encrypt_with_context(TEST_DATA, PASSPHRASE, 2, 0x10, 32, 3, 4) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(&ciphertext[12..16], u32::to_le_bytes(0x10)); + } + { + let ciphertext = + abcrypt_wasm::encrypt_with_context(TEST_DATA, PASSPHRASE, 2, 0x13, 32, 3, 4) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(&ciphertext[12..16], u32::to_le_bytes(0x13)); + } } #[wasm_bindgen_test] @@ -92,7 +258,7 @@ fn memory_cost() { let ciphertext = abcrypt_wasm::encrypt_with_params(TEST_DATA, PASSPHRASE, 32, 3, 4) .map_err(JsValue::from) .unwrap(); - assert_eq!(&ciphertext[8..12], u32::to_le_bytes(32)); + assert_eq!(&ciphertext[16..20], u32::to_le_bytes(32)); } #[wasm_bindgen_test] @@ -100,7 +266,7 @@ fn time_cost() { let ciphertext = abcrypt_wasm::encrypt_with_params(TEST_DATA, PASSPHRASE, 32, 3, 4) .map_err(JsValue::from) .unwrap(); - assert_eq!(&ciphertext[12..16], u32::to_le_bytes(3)); + assert_eq!(&ciphertext[20..24], u32::to_le_bytes(3)); } #[wasm_bindgen_test] @@ -108,5 +274,5 @@ fn parallelism() { let ciphertext = abcrypt_wasm::encrypt_with_params(TEST_DATA, PASSPHRASE, 32, 3, 4) .map_err(JsValue::from) .unwrap(); - assert_eq!(&ciphertext[16..20], u32::to_le_bytes(4)); + assert_eq!(&ciphertext[24..28], u32::to_le_bytes(4)); } diff --git a/crates/wasm/tests/params.rs b/crates/wasm/tests/params.rs index c272ddcf..f51f7669 100644 --- a/crates/wasm/tests/params.rs +++ b/crates/wasm/tests/params.rs @@ -2,19 +2,12 @@ // // SPDX-License-Identifier: Apache-2.0 OR MIT -// Lint levels of rustc. -#![forbid(unsafe_code)] -#![deny(missing_debug_implementations)] -#![warn(rust_2018_idioms)] -// Lint levels of Clippy. -#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)] - use abcrypt_wasm::Params; use wasm_bindgen::JsValue; use wasm_bindgen_test::wasm_bindgen_test; -// Generated using `abcrypt` crate version 0.1.0. -const TEST_DATA_ENC: &[u8] = include_bytes!("data/data.txt.abcrypt"); +// Generated using `abcrypt` crate version 0.4.0. +const TEST_DATA_ENC: &[u8] = include_bytes!("data/v1/argon2id/v0x13/data.txt.abcrypt"); #[wasm_bindgen_test] fn success() { @@ -24,18 +17,114 @@ fn success() { #[wasm_bindgen_test] fn memory_cost() { - let params = Params::new(TEST_DATA_ENC).map_err(JsValue::from).unwrap(); - assert_eq!(params.memory_cost(), 32); + { + let params = Params::new(include_bytes!("data/v1/argon2d/v0x10/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.memory_cost(), 47104); + } + { + let params = Params::new(include_bytes!("data/v1/argon2d/v0x13/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.memory_cost(), 19456); + } + { + let params = Params::new(include_bytes!("data/v1/argon2i/v0x10/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.memory_cost(), 12288); + } + { + let params = Params::new(include_bytes!("data/v1/argon2i/v0x13/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.memory_cost(), 9216); + } + { + let params = Params::new(include_bytes!("data/v1/argon2id/v0x10/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.memory_cost(), 7168); + } + { + let params = Params::new(TEST_DATA_ENC).map_err(JsValue::from).unwrap(); + assert_eq!(params.memory_cost(), 32); + } } #[wasm_bindgen_test] fn time_cost() { - let params = Params::new(TEST_DATA_ENC).map_err(JsValue::from).unwrap(); - assert_eq!(params.time_cost(), 3); + { + let params = Params::new(include_bytes!("data/v1/argon2d/v0x10/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.time_cost(), 1); + } + { + let params = Params::new(include_bytes!("data/v1/argon2d/v0x13/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.time_cost(), 2); + } + { + let params = Params::new(include_bytes!("data/v1/argon2i/v0x10/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.time_cost(), 3); + } + { + let params = Params::new(include_bytes!("data/v1/argon2i/v0x13/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.time_cost(), 4); + } + { + let params = Params::new(include_bytes!("data/v1/argon2id/v0x10/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.time_cost(), 5); + } + { + let params = Params::new(TEST_DATA_ENC).map_err(JsValue::from).unwrap(); + assert_eq!(params.time_cost(), 3); + } } #[wasm_bindgen_test] fn parallelism() { - let params = Params::new(TEST_DATA_ENC).map_err(JsValue::from).unwrap(); - assert_eq!(params.parallelism(), 4); + { + let params = Params::new(include_bytes!("data/v1/argon2d/v0x10/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.parallelism(), 1); + } + { + let params = Params::new(include_bytes!("data/v1/argon2d/v0x13/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.parallelism(), 1); + } + { + let params = Params::new(include_bytes!("data/v1/argon2i/v0x10/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.parallelism(), 1); + } + { + let params = Params::new(include_bytes!("data/v1/argon2i/v0x13/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.parallelism(), 1); + } + { + let params = Params::new(include_bytes!("data/v1/argon2id/v0x10/data.txt.abcrypt")) + .map_err(JsValue::from) + .unwrap(); + assert_eq!(params.parallelism(), 1); + } + { + let params = Params::new(TEST_DATA_ENC).map_err(JsValue::from).unwrap(); + assert_eq!(params.parallelism(), 4); + } } diff --git a/deno.jsonc b/deno.jsonc index f801335c..bc27e786 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -5,7 +5,7 @@ { "imports": { "@cliffy/command": "jsr:@cliffy/command@1.0.0-rc.7", - "@std/cli": "jsr:@std/cli@^1.0.6", + "@std/cli": "jsr:@std/cli@^1.0.9", "@std/io": "jsr:@std/io@^0.225.0" }, "lock": false diff --git a/docs/book/modules/ROOT/images/screenshot.webp b/docs/book/modules/ROOT/images/screenshot.webp deleted file mode 120000 index d570d0bf..00000000 --- a/docs/book/modules/ROOT/images/screenshot.webp +++ /dev/null @@ -1 +0,0 @@ -../../../../../crates/cli/assets/screenshot.webp \ No newline at end of file diff --git a/docs/book/modules/ROOT/pages/awesome.adoc b/docs/book/modules/ROOT/pages/awesome.adoc index ac5d0f76..d3cbf554 100644 --- a/docs/book/modules/ROOT/pages/awesome.adoc +++ b/docs/book/modules/ROOT/pages/awesome.adoc @@ -19,3 +19,9 @@ ecosystem. {github-url}/sorairolake/abcrypt-go[abcrypt-go]:: A simple, modern and secure file encryption library for Go. + +=== Zig + +{github-url}/sorairolake/abcrypt-zig[abcrypt-zig]:: + + A simple, modern and secure file encryption library for Zig. diff --git a/docs/book/modules/ROOT/pages/index.adoc b/docs/book/modules/ROOT/pages/index.adoc index d3beb516..8434333b 100644 --- a/docs/book/modules/ROOT/pages/index.adoc +++ b/docs/book/modules/ROOT/pages/index.adoc @@ -30,8 +30,6 @@ image:{ci-badge}[CI,link={ci-url}] *abcrypt* is a simple, modern and secure file encryption tool, file format and Rust library. -image::screenshot.webp[Screenshot of abcrypt] - == Crates |=== diff --git a/docs/book/modules/ROOT/pages/license.adoc b/docs/book/modules/ROOT/pages/license.adoc index eb515bfa..7a506ebb 100644 --- a/docs/book/modules/ROOT/pages/license.adoc +++ b/docs/book/modules/ROOT/pages/license.adoc @@ -5,7 +5,7 @@ = License :reuse-spec-url: https://reuse.software/spec/ -Copyright (C) 2022–2024 Shun Sakai +Copyright (C) 2022 Shun Sakai . Unless otherwise noted, each file is distributed under the terms of either the _Apache License 2.0_ or the _MIT License_. diff --git a/docs/book/modules/capi/nav.adoc b/docs/book/modules/capi/nav.adoc index 8a54bf48..1cd64b2b 100644 --- a/docs/book/modules/capi/nav.adoc +++ b/docs/book/modules/capi/nav.adoc @@ -16,6 +16,7 @@ ** xref:man/man3/abcrypt_decrypt.3.adoc[`abcrypt_decrypt(3)`] ** xref:man/man3/abcrypt_encrypt.3.adoc[`abcrypt_encrypt(3)`] ** xref:man/man3/abcrypt_encrypt_with_params.3.adoc[`abcrypt_encrypt_with_params(3)`] +** xref:man/man3/abcrypt_encrypt_with_context.3.adoc[`abcrypt_encrypt_with_context(3)`] ** xref:man/man3/abcrypt_error_message.3.adoc[`abcrypt_error_message(3)`] ** xref:man/man3/abcrypt_error_message_out_len.3.adoc[`abcrypt_error_message_out_len(3)`] ** xref:man/man3/abcrypt_params_new.3.adoc[`abcrypt_params_new(3)`] diff --git a/docs/book/modules/capi/pages/man/man3/abcrypt_encrypt_with_context.3.adoc b/docs/book/modules/capi/pages/man/man3/abcrypt_encrypt_with_context.3.adoc new file mode 120000 index 00000000..18093fc2 --- /dev/null +++ b/docs/book/modules/capi/pages/man/man3/abcrypt_encrypt_with_context.3.adoc @@ -0,0 +1 @@ +../../../../../../man/man3/abcrypt_encrypt_with_context.3.adoc \ No newline at end of file diff --git a/docs/book/modules/cli/images/screenshot.webp b/docs/book/modules/cli/images/screenshot.webp deleted file mode 120000 index d570d0bf..00000000 --- a/docs/book/modules/cli/images/screenshot.webp +++ /dev/null @@ -1 +0,0 @@ -../../../../../crates/cli/assets/screenshot.webp \ No newline at end of file diff --git a/docs/book/modules/cli/nav.adoc b/docs/book/modules/cli/nav.adoc index f699893a..6b89f4d0 100644 --- a/docs/book/modules/cli/nav.adoc +++ b/docs/book/modules/cli/nav.adoc @@ -11,6 +11,7 @@ ** xref:man/man1/abcrypt.1.adoc[`abcrypt(1)`] ** xref:man/man1/abcrypt-encrypt.1.adoc[`abcrypt-encrypt(1)`] ** xref:man/man1/abcrypt-decrypt.1.adoc[`abcrypt-decrypt(1)`] +** xref:man/man1/abcrypt-argon2.1.adoc[`abcrypt-argon2(1)`] ** xref:man/man1/abcrypt-information.1.adoc[`abcrypt-information(1)`] ** xref:man/man1/abcrypt-help.1.adoc[`abcrypt-help(1)`] * xref:changelog.adoc[] diff --git a/docs/book/modules/cli/pages/index.adoc b/docs/book/modules/cli/pages/index.adoc index bd6509d6..3fb92a3c 100644 --- a/docs/book/modules/cli/pages/index.adoc +++ b/docs/book/modules/cli/pages/index.adoc @@ -19,5 +19,3 @@ image:{license-badge}[License] *abcrypt* ({version-url}[`abcrypt-cli`]) is a command-line utility for encrypt and decrypt files using the abcrypt encrypted data format. - -image::screenshot.webp[Screenshot of abcrypt] diff --git a/docs/book/modules/cli/pages/man/man1/abcrypt-argon2.1.adoc b/docs/book/modules/cli/pages/man/man1/abcrypt-argon2.1.adoc new file mode 120000 index 00000000..a3768a0b --- /dev/null +++ b/docs/book/modules/cli/pages/man/man1/abcrypt-argon2.1.adoc @@ -0,0 +1 @@ +../../../../../../man/man1/abcrypt-argon2.1.adoc \ No newline at end of file diff --git a/docs/book/modules/lib/pages/index.adoc b/docs/book/modules/lib/pages/index.adoc index 620414ea..a408fbec 100644 --- a/docs/book/modules/lib/pages/index.adoc +++ b/docs/book/modules/lib/pages/index.adoc @@ -21,3 +21,5 @@ image:{docs-badge}[Docs,link={docs-url}] image:{license-badge}[License] *abcrypt* is an implementation of the abcrypt encrypted data format. + +This crate supports the abcrypt version 1 file format. diff --git a/docs/book/modules/lib/pages/usage.adoc b/docs/book/modules/lib/pages/usage.adoc index 244d2089..d0449336 100644 --- a/docs/book/modules/lib/pages/usage.adoc +++ b/docs/book/modules/lib/pages/usage.adoc @@ -8,7 +8,7 @@ [source,toml] ---- [dependencies] -abcrypt = "0.3.7" +abcrypt = "0.4.0" ---- == Crate features diff --git a/docs/book/supplemental-ui/partials/footer-content.hbs b/docs/book/supplemental-ui/partials/footer-content.hbs index 5005b1e1..3c1cec09 100644 --- a/docs/book/supplemental-ui/partials/footer-content.hbs +++ b/docs/book/supplemental-ui/partials/footer-content.hbs @@ -5,6 +5,6 @@ SPDX-License-Identifier: CC-BY-4.0 --}} diff --git a/docs/man/man1/abcrypt-argon2.1.adoc b/docs/man/man1/abcrypt-argon2.1.adoc new file mode 100644 index 00000000..355658d8 --- /dev/null +++ b/docs/man/man1/abcrypt-argon2.1.adoc @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2024 Shun Sakai +// +// SPDX-License-Identifier: CC-BY-4.0 + += abcrypt-argon2(1) +// Specify in UTC. +:docdate: 2024-12-07 +:revnumber: 0.3.3 +:doctype: manpage +:mansource: abcrypt {revnumber} +:manmanual: General Commands Manual +ifndef::site-gen-antora[:includedir: ./include] + +== NAME + +abcrypt-argon2 - provides information about the Argon2 context + +== SYNOPSIS + +*abcrypt argon2* [_OPTION_]... [_FILE_] + +== DESCRIPTION + +This command provides information about the Argon2 context from _FILE_. If +_FILE_ is not specified, data will be read from standard input. + +== POSITIONAL ARGUMENTS + +_FILE_:: + + Input file. If _FILE_ is not specified, data will be read from standard input. + +== OPTIONS + +*-h*, *--help*:: + + Print help message. The short flag (*-h*) will print a condensed help message + while the long flag (*--help*) will print a detailed help message. + +*-V*, *--version*:: + + Print version number. The long flag (*--version*) will also print the + copyright notice, the license notice and where to report bugs. + +ifndef::site-gen-antora[include::{includedir}/section-exit-status.adoc[]] +ifdef::site-gen-antora[include::partial$man/man1/include/section-exit-status.adoc[]] + +ifndef::site-gen-antora[include::{includedir}/section-notes.adoc[]] +ifdef::site-gen-antora[include::partial$man/man1/include/section-notes.adoc[]] + +== EXAMPLES + +Print the Argon2 context:{blank}:: + + $ *abcrypt argon2 data.txt.abcrypt* + +ifndef::site-gen-antora[include::{includedir}/section-reporting-bugs.adoc[]] +ifdef::site-gen-antora[include::partial$man/man1/include/section-reporting-bugs.adoc[]] + +ifndef::site-gen-antora[include::{includedir}/section-copyright.adoc[]] +ifdef::site-gen-antora[include::partial$man/man1/include/section-copyright.adoc[]] + +== SEE ALSO + +*abcrypt*(1), *abcrypt-decrypt*(1), *abcrypt-encrypt*(1), *abcrypt-help*(1), +*abcrypt-information*(1) diff --git a/docs/man/man1/abcrypt-decrypt.1.adoc b/docs/man/man1/abcrypt-decrypt.1.adoc index 02aa972a..0aeae4b4 100644 --- a/docs/man/man1/abcrypt-decrypt.1.adoc +++ b/docs/man/man1/abcrypt-decrypt.1.adoc @@ -4,10 +4,10 @@ = abcrypt-decrypt(1) // Specify in UTC. -:docdate: 2024-08-02 +:docdate: 2024-12-07 +:revnumber: 0.3.3 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt {revnumber}] -ifndef::revnumber[:mansource: abcrypt] +:mansource: abcrypt {revnumber} :manmanual: General Commands Manual ifndef::site-gen-antora[:includedir: ./include] @@ -107,4 +107,5 @@ ifdef::site-gen-antora[include::partial$man/man1/include/section-copyright.adoc[ == SEE ALSO -*abcrypt*(1), *abcrypt-encrypt*(1), *abcrypt-help*(1), *abcrypt-information*(1) +*abcrypt*(1), *abcrypt-argon2*(1), *abcrypt-encrypt*(1), *abcrypt-help*(1), +*abcrypt-information*(1) diff --git a/docs/man/man1/abcrypt-encrypt.1.adoc b/docs/man/man1/abcrypt-encrypt.1.adoc index 2a572ee5..2fd168fa 100644 --- a/docs/man/man1/abcrypt-encrypt.1.adoc +++ b/docs/man/man1/abcrypt-encrypt.1.adoc @@ -4,10 +4,10 @@ = abcrypt-encrypt(1) // Specify in UTC. -:docdate: 2024-08-02 +:docdate: 2024-12-07 +:revnumber: 0.3.3 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt {revnumber}] -ifndef::revnumber[:mansource: abcrypt] +:mansource: abcrypt {revnumber} :manmanual: General Commands Manual ifndef::site-gen-antora[:includedir: ./include] @@ -42,6 +42,38 @@ _FILE_:: Output the result to a file. +*--argon2-type* _TYPE_:: + + Set the Argon2 type. + + The possible values are:{blank}::: + + *argon2d*:::: + + Argon2d. + + *argon2i*:::: + + Argon2i. + + *argon2id*:::: + + Argon2id. This is the default value. + +*--argon2-version* _VERSION_:: + + Set the Argon2 version. + + The possible values are:{blank}::: + + *0x10*:::: + + Version 0x10. *16* is an alias for this value. + + *0x13*:::: + + Version 0x13. *19* is an alias for this value. This is the default value. + *-m*, *--memory-cost* _BYTE_:: Set the memory size in bytes. _BYTE_ can be suffixed with the symbol (B) and @@ -134,4 +166,5 @@ ifdef::site-gen-antora[include::partial$man/man1/include/section-copyright.adoc[ == SEE ALSO -*abcrypt*(1), *abcrypt-decrypt*(1), *abcrypt-help*(1), *abcrypt-information*(1) +*abcrypt*(1), *abcrypt-argon2*(1), *abcrypt-decrypt*(1), *abcrypt-help*(1), +*abcrypt-information*(1) diff --git a/docs/man/man1/abcrypt-help.1.adoc b/docs/man/man1/abcrypt-help.1.adoc index c8fc9105..04c02421 100644 --- a/docs/man/man1/abcrypt-help.1.adoc +++ b/docs/man/man1/abcrypt-help.1.adoc @@ -4,10 +4,10 @@ = abcrypt-help(1) // Specify in UTC. -:docdate: 2023-08-23 +:docdate: 2024-12-07 +:revnumber: 0.3.3 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt {revnumber}] -ifndef::revnumber[:mansource: abcrypt] +:mansource: abcrypt {revnumber} :manmanual: General Commands Manual ifndef::site-gen-antora[:includedir: ./include] @@ -47,5 +47,5 @@ ifdef::site-gen-antora[include::partial$man/man1/include/section-copyright.adoc[ == SEE ALSO -*abcrypt*(1), *abcrypt-decrypt*(1), *abcrypt-encrypt*(1), +*abcrypt*(1), *abcrypt-argon2*(1), *abcrypt-decrypt*(1), *abcrypt-encrypt*(1), *abcrypt-information*(1) diff --git a/docs/man/man1/abcrypt-information.1.adoc b/docs/man/man1/abcrypt-information.1.adoc index 90b27c33..2ce1845b 100644 --- a/docs/man/man1/abcrypt-information.1.adoc +++ b/docs/man/man1/abcrypt-information.1.adoc @@ -4,10 +4,10 @@ = abcrypt-information(1) // Specify in UTC. -:docdate: 2024-08-02 +:docdate: 2025-01-08 +:revnumber: 0.3.3 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt {revnumber}] -ifndef::revnumber[:mansource: abcrypt] +:mansource: abcrypt {revnumber} :manmanual: General Commands Manual ifndef::site-gen-antora[:includedir: ./include] :ietf-datatracker: https://datatracker.ietf.org @@ -25,8 +25,7 @@ abcrypt-information - provides information about the encryption parameters == DESCRIPTION This command provides information about the encryption parameters from _FILE_. -The result will be write to standard output. If _FILE_ is not specified, data -will be read from standard input. +If _FILE_ is not specified, data will be read from standard input. The encryption parameters outputs either a human-readable string or JSON. @@ -57,11 +56,10 @@ _FILE_:: == OPTIONS -ifdef::json,env-github,site-gen-antora[] *-j*, *--json*:: - Output the encryption parameters as JSON. -endif::[] + Output the encryption parameters as JSON. This option is available if the + `json` feature is enabled at compile time. *-h*, *--help*:: @@ -85,11 +83,9 @@ Print the encryption parameters:{blank}:: $ *abcrypt information data.txt.abcrypt* -ifdef::json,env-github,site-gen-antora[] Print the encryption parameters as JSON:{blank}:: $ *abcrypt information -j data.txt.abcrypt* -endif::[] ifndef::site-gen-antora[include::{includedir}/section-reporting-bugs.adoc[]] ifdef::site-gen-antora[include::partial$man/man1/include/section-reporting-bugs.adoc[]] @@ -99,4 +95,5 @@ ifdef::site-gen-antora[include::partial$man/man1/include/section-copyright.adoc[ == SEE ALSO -*abcrypt*(1), *abcrypt-decrypt*(1), *abcrypt-encrypt*(1), *abcrypt-help*(1) +*abcrypt*(1), *abcrypt-argon2*(1), *abcrypt-decrypt*(1), *abcrypt-encrypt*(1), +*abcrypt-help*(1) diff --git a/docs/man/man1/abcrypt.1.adoc b/docs/man/man1/abcrypt.1.adoc index e9e6c896..9f774926 100644 --- a/docs/man/man1/abcrypt.1.adoc +++ b/docs/man/man1/abcrypt.1.adoc @@ -4,10 +4,10 @@ = abcrypt(1) // Specify in UTC. -:docdate: 2024-08-02 +:docdate: 2024-12-07 +:revnumber: 0.3.3 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt {revnumber}] -ifndef::revnumber[:mansource: abcrypt] +:mansource: abcrypt {revnumber} :manmanual: General Commands Manual ifndef::site-gen-antora[:includedir: ./include] @@ -34,6 +34,10 @@ abcrypt - an utility for encrypt and decrypt files Decrypt files. +*abcrypt-argon2*(1):: + + Provides information about the Argon2 context. + *abcrypt-information*(1):: Provides information about the encryption parameters. diff --git a/docs/man/man1/include/section-copyright.adoc b/docs/man/man1/include/section-copyright.adoc index 89ac9fd6..73a25655 100644 --- a/docs/man/man1/include/section-copyright.adoc +++ b/docs/man/man1/include/section-copyright.adoc @@ -4,7 +4,7 @@ == COPYRIGHT -Copyright (C) 2022-2024 Shun Sakai +Copyright (C) 2022 Shun Sakai . This program is distributed under the terms of the GNU General Public License v3.0 or later. diff --git a/docs/man/man3/ABCRYPT_HEADER_SIZE.3.adoc b/docs/man/man3/ABCRYPT_HEADER_SIZE.3.adoc index 35a274cc..6eb94300 100644 --- a/docs/man/man3/ABCRYPT_HEADER_SIZE.3.adoc +++ b/docs/man/man3/ABCRYPT_HEADER_SIZE.3.adoc @@ -4,10 +4,10 @@ = ABCRYPT_HEADER_SIZE(3) // Specify in UTC. -:docdate: 2024-04-16 +:docdate: 2024-12-07 +:revnumber: 0.3.2 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] @@ -21,7 +21,7 @@ ABCRYPT_HEADER_SIZE - API constant ---- #include -#define ABCRYPT_HEADER_SIZE 140 +#define ABCRYPT_HEADER_SIZE 148 ---- == DESCRIPTION diff --git a/docs/man/man3/ABCRYPT_TAG_SIZE.3.adoc b/docs/man/man3/ABCRYPT_TAG_SIZE.3.adoc index 62071305..d9bd5231 100644 --- a/docs/man/man3/ABCRYPT_TAG_SIZE.3.adoc +++ b/docs/man/man3/ABCRYPT_TAG_SIZE.3.adoc @@ -5,9 +5,9 @@ = ABCRYPT_TAG_SIZE(3) // Specify in UTC. :docdate: 2024-04-16 +:revnumber: 0.3.2 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] diff --git a/docs/man/man3/abcrypt_decrypt.3.adoc b/docs/man/man3/abcrypt_decrypt.3.adoc index 061f4179..b2504b71 100644 --- a/docs/man/man3/abcrypt_decrypt.3.adoc +++ b/docs/man/man3/abcrypt_decrypt.3.adoc @@ -4,11 +4,11 @@ = abcrypt_decrypt(3) // Specify in UTC. -:docdate: 2024-04-13 +:docdate: 2024-12-10 +:revnumber: 0.3.2 :doctype: manpage :icons: font -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] @@ -85,4 +85,5 @@ ifdef::site-gen-antora[include::partial$man/man3/include/section-copyright.adoc[ == SEE ALSO -*abcrypt_encrypt*(3), *abcrypt_encrypt_with_params*(3) +*abcrypt_encrypt*(3), *abcrypt_encrypt_with_params*(3), +*abcrypt_encrypt_with_context*(3) diff --git a/docs/man/man3/abcrypt_encrypt.3.adoc b/docs/man/man3/abcrypt_encrypt.3.adoc index 67a74063..74b16148 100644 --- a/docs/man/man3/abcrypt_encrypt.3.adoc +++ b/docs/man/man3/abcrypt_encrypt.3.adoc @@ -4,14 +4,14 @@ = abcrypt_encrypt(3) // Specify in UTC. -:docdate: 2024-04-13 +:docdate: 2024-12-10 +:revnumber: 0.3.2 :doctype: manpage :icons: font -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] -:owasp-cheatsheets: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html +:owasp-cheatsheets: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id == NAME @@ -35,7 +35,9 @@ enum abcrypt_error_code abcrypt_encrypt(uint8_t *plaintext, This function encrypts _plaintext_ and write to _out_. -This uses the {owasp-cheatsheets}[recommended Argon2 parameters]. +This uses the recommended Argon2 parameters according to the +{owasp-cheatsheets}[OWASP Password Storage Cheat Sheet]. This also uses +Argon2id as the Argon2 type and version 0x13 as the Argon2 version. === Arguments @@ -88,4 +90,5 @@ ifdef::site-gen-antora[include::partial$man/man3/include/section-copyright.adoc[ == SEE ALSO -*abcrypt_decrypt*(1), *abcrypt_encrypt_with_params*(1) +*abcrypt_decrypt*(3), *abcrypt_encrypt_with_params*(3), +*abcrypt_encrypt_with_context*(3) diff --git a/docs/man/man3/abcrypt_encrypt_with_context.3.adoc b/docs/man/man3/abcrypt_encrypt_with_context.3.adoc new file mode 100644 index 00000000..204c5664 --- /dev/null +++ b/docs/man/man3/abcrypt_encrypt_with_context.3.adoc @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2024 Shun Sakai +// +// SPDX-License-Identifier: CC-BY-4.0 + += abcrypt_encrypt_with_context(3) +// Specify in UTC. +:docdate: 2024-12-10 +:revnumber: 0.3.2 +:doctype: manpage +:icons: font +:mansource: abcrypt-capi {revnumber} +:manmanual: Library Functions Manual +ifndef::site-gen-antora[:includedir: ./include] + +== NAME + +abcrypt_encrypt_with_context - API function + +== SYNOPSIS + +[source,c] +---- +#include + +enum abcrypt_error_code abcrypt_encrypt_with_context(uint8_t *plaintext, + uintptr_t plaintext_len, + uint8_t *passphrase, + uintptr_t passphrase_len, + uint8_t *out, + uintptr_t out_len, + uint32_t argon2_type, + uint32_t argon2_version, + uint32_t memory_cost, + uint32_t time_cost, + uint32_t parallelism); +---- + +== DESCRIPTION + +This function encrypts _plaintext_ with the specified Argon2 type, Argon2 +version and Argon2 parameters and write to _out_. + +=== Arguments + +_plaintext_:: + + A pointer to the plaintext to encrypt. + +_plaintext_len_:: + + Length of _plaintext_. + +_passphrase_:: + + A pointer to the passphrase used for encryption. + +_passphrase_len_:: + + Length of _passphrase_. + +_out_:: + + A pointer to where to write the ciphertext. + +_out_len_:: + + Length of _out_. + +_argon2_type_:: + + The Argon2 type. + +_argon2_version_:: + + The Argon2 version. + +_memory_cost_:: + + The memory size in KiB. + +_time_cost_:: + + The number of iterations. + +_parallelism_:: + + The degree of parallelism. + +[CAUTION] +.Behavior is undefined if any of the following are true: +==== +* _plaintext_ or _plaintext_len_ is invalid. +* _passphrase_ or _passphrase_len_ is invalid. +* _out_ or _out_len_ is invalid. +==== + +== RETURN VALUE + +Returns `ABCRYPT_ERROR_CODE_OK` if successful, otherwise returns an error value +defined in *abcrypt_error_code*(3). + +== VERSIONS + +This function was added in version 0.4.0. + +ifndef::site-gen-antora[include::{includedir}/section-reporting-bugs.adoc[]] +ifdef::site-gen-antora[include::partial$man/man3/include/section-reporting-bugs.adoc[]] + +ifndef::site-gen-antora[include::{includedir}/section-copyright.adoc[]] +ifdef::site-gen-antora[include::partial$man/man3/include/section-copyright.adoc[]] + +== SEE ALSO + +*abcrypt_decrypt*(3), *abcrypt_encrypt*(3), *abcrypt_encrypt_with_params*(3) diff --git a/docs/man/man3/abcrypt_encrypt_with_params.3.adoc b/docs/man/man3/abcrypt_encrypt_with_params.3.adoc index 608cde10..b2c96e29 100644 --- a/docs/man/man3/abcrypt_encrypt_with_params.3.adoc +++ b/docs/man/man3/abcrypt_encrypt_with_params.3.adoc @@ -4,11 +4,11 @@ = abcrypt_encrypt_with_params(3) // Specify in UTC. -:docdate: 2024-04-13 +:docdate: 2024-12-10 +:revnumber: 0.3.2 :doctype: manpage :icons: font -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] @@ -38,6 +38,8 @@ enum abcrypt_error_code abcrypt_encrypt_with_params(uint8_t *plaintext, This function encrypts _plaintext_ with the specified Argon2 parameters and write to _out_. +This uses Argon2id as the Argon2 type and version 0x13 as the Argon2 version. + === Arguments _plaintext_:: @@ -101,4 +103,4 @@ ifdef::site-gen-antora[include::partial$man/man3/include/section-copyright.adoc[ == SEE ALSO -*abcrypt_decrypt*(3), *abcrypt_encrypt*(3) +*abcrypt_decrypt*(3), *abcrypt_encrypt*(3), *abcrypt_encrypt_with_context*(3) diff --git a/docs/man/man3/abcrypt_error_code.3.adoc b/docs/man/man3/abcrypt_error_code.3.adoc index ba86e886..7e98c13c 100644 --- a/docs/man/man3/abcrypt_error_code.3.adoc +++ b/docs/man/man3/abcrypt_error_code.3.adoc @@ -4,10 +4,10 @@ = abcrypt_error_code(3) // Specify in UTC. -:docdate: 2024-04-13 +:docdate: 2024-12-07 +:revnumber: 0.3.2 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] @@ -26,7 +26,10 @@ typedef enum abcrypt_error_code { ABCRYPT_ERROR_CODE_ERROR, ABCRYPT_ERROR_CODE_INVALID_LENGTH, ABCRYPT_ERROR_CODE_INVALID_MAGIC_NUMBER, + ABCRYPT_ERROR_CODE_UNSUPPORTED_VERSION, ABCRYPT_ERROR_CODE_UNKNOWN_VERSION, + ABCRYPT_ERROR_CODE_INVALID_ARGON2_TYPE, + ABCRYPT_ERROR_CODE_INVALID_ARGON2_VERSION, ABCRYPT_ERROR_CODE_INVALID_ARGON2_PARAMS, ABCRYPT_ERROR_CODE_INVALID_ARGON2_CONTEXT, ABCRYPT_ERROR_CODE_INVALID_HEADER_MAC, @@ -50,16 +53,28 @@ _ABCRYPT_ERROR_CODE_ERROR_:: _ABCRYPT_ERROR_CODE_INVALID_LENGTH_:: - The encrypted data was shorter than 156 bytes. + The encrypted data was shorter than 164 bytes. _ABCRYPT_ERROR_CODE_INVALID_MAGIC_NUMBER_:: The magic number (file signature) was invalid. +_ABCRYPT_ERROR_CODE_UNSUPPORTED_VERSION_:: + + The version was the unsupported abcrypt version number. + _ABCRYPT_ERROR_CODE_UNKNOWN_VERSION_:: The version was the unrecognized abcrypt version number. +_ABCRYPT_ERROR_CODE_INVALID_ARGON2_TYPE_:: + + The Argon2 type were invalid. + +_ABCRYPT_ERROR_CODE_INVALID_ARGON2_VERSION_:: + + The Argon2 version were invalid. + _ABCRYPT_ERROR_CODE_INVALID_ARGON2_PARAMS_:: The Argon2 parameters were invalid. diff --git a/docs/man/man3/abcrypt_error_message.3.adoc b/docs/man/man3/abcrypt_error_message.3.adoc index b19ea9ce..ca5da600 100644 --- a/docs/man/man3/abcrypt_error_message.3.adoc +++ b/docs/man/man3/abcrypt_error_message.3.adoc @@ -5,10 +5,10 @@ = abcrypt_error_message(3) // Specify in UTC. :docdate: 2024-04-13 +:revnumber: 0.3.2 :doctype: manpage :icons: font -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] diff --git a/docs/man/man3/abcrypt_error_message_out_len.3.adoc b/docs/man/man3/abcrypt_error_message_out_len.3.adoc index 3c33aef3..3957ae07 100644 --- a/docs/man/man3/abcrypt_error_message_out_len.3.adoc +++ b/docs/man/man3/abcrypt_error_message_out_len.3.adoc @@ -5,9 +5,9 @@ = abcrypt_error_message_out_len(3) // Specify in UTC. :docdate: 2024-04-13 +:revnumber: 0.3.2 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] diff --git a/docs/man/man3/abcrypt_params.3.adoc b/docs/man/man3/abcrypt_params.3.adoc index ab98e7f6..edff363b 100644 --- a/docs/man/man3/abcrypt_params.3.adoc +++ b/docs/man/man3/abcrypt_params.3.adoc @@ -5,9 +5,9 @@ = abcrypt_params(3) // Specify in UTC. :docdate: 2024-04-13 +:revnumber: 0.3.2 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] diff --git a/docs/man/man3/abcrypt_params_free.3.adoc b/docs/man/man3/abcrypt_params_free.3.adoc index 99b3c34f..e1e24c99 100644 --- a/docs/man/man3/abcrypt_params_free.3.adoc +++ b/docs/man/man3/abcrypt_params_free.3.adoc @@ -5,10 +5,10 @@ = abcrypt_params_free(3) // Specify in UTC. :docdate: 2024-04-13 +:revnumber: 0.3.2 :doctype: manpage :icons: font -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] diff --git a/docs/man/man3/abcrypt_params_memory_cost.3.adoc b/docs/man/man3/abcrypt_params_memory_cost.3.adoc index 36111fd5..c3f8f87c 100644 --- a/docs/man/man3/abcrypt_params_memory_cost.3.adoc +++ b/docs/man/man3/abcrypt_params_memory_cost.3.adoc @@ -5,9 +5,9 @@ = abcrypt_params_memory_cost(3) // Specify in UTC. :docdate: 2024-04-13 +:revnumber: 0.3.2 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] diff --git a/docs/man/man3/abcrypt_params_new.3.adoc b/docs/man/man3/abcrypt_params_new.3.adoc index c4301c8c..ef271674 100644 --- a/docs/man/man3/abcrypt_params_new.3.adoc +++ b/docs/man/man3/abcrypt_params_new.3.adoc @@ -5,9 +5,9 @@ = abcrypt_params_new(3) // Specify in UTC. :docdate: 2024-04-13 +:revnumber: 0.3.2 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] diff --git a/docs/man/man3/abcrypt_params_parallelism.3.adoc b/docs/man/man3/abcrypt_params_parallelism.3.adoc index 5b51241a..b246fe7a 100644 --- a/docs/man/man3/abcrypt_params_parallelism.3.adoc +++ b/docs/man/man3/abcrypt_params_parallelism.3.adoc @@ -5,9 +5,9 @@ = abcrypt_params_parallelism(3) // Specify in UTC. :docdate: 2024-04-13 +:revnumber: 0.3.2 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] diff --git a/docs/man/man3/abcrypt_params_read.3.adoc b/docs/man/man3/abcrypt_params_read.3.adoc index 449208e1..7ded345d 100644 --- a/docs/man/man3/abcrypt_params_read.3.adoc +++ b/docs/man/man3/abcrypt_params_read.3.adoc @@ -5,10 +5,10 @@ = abcrypt_params_read(3) // Specify in UTC. :docdate: 2024-04-13 +:revnumber: 0.3.2 :doctype: manpage :icons: font -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] diff --git a/docs/man/man3/abcrypt_params_time_cost.3.adoc b/docs/man/man3/abcrypt_params_time_cost.3.adoc index 8c6effcf..d697a780 100644 --- a/docs/man/man3/abcrypt_params_time_cost.3.adoc +++ b/docs/man/man3/abcrypt_params_time_cost.3.adoc @@ -5,9 +5,9 @@ = abcrypt_params_time_cost(3) // Specify in UTC. :docdate: 2024-04-13 +:revnumber: 0.3.2 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt-capi {revnumber}] -ifndef::revnumber[:mansource: abcrypt-capi] +:mansource: abcrypt-capi {revnumber} :manmanual: Library Functions Manual ifndef::site-gen-antora[:includedir: ./include] diff --git a/docs/man/man3/include/section-copyright.adoc b/docs/man/man3/include/section-copyright.adoc index 51b7e493..855eb820 100644 --- a/docs/man/man3/include/section-copyright.adoc +++ b/docs/man/man3/include/section-copyright.adoc @@ -4,7 +4,7 @@ == COPYRIGHT -Copyright (C) 2022-2024 Shun Sakai +Copyright (C) 2022 Shun Sakai This manual page is distributed under the terms of the Creative Commons Attribution 4.0 International Public License. diff --git a/docs/man/man5/abcrypt.5.adoc b/docs/man/man5/abcrypt.5.adoc index 739a48d4..bb4b139e 100644 --- a/docs/man/man5/abcrypt.5.adoc +++ b/docs/man/man5/abcrypt.5.adoc @@ -4,10 +4,10 @@ = abcrypt(5) // Specify in UTC. -:docdate: 2024-07-30 +:docdate: 2024-12-09 +:revnumber: 0.3.3 :doctype: manpage -ifdef::revnumber[:mansource: abcrypt {revnumber}] -ifndef::revnumber[:mansource: abcrypt] +:mansource: abcrypt {revnumber} :manmanual: File Formats Manual :includedir: ./include :scrypt-encrypted-data-format: https://www.tarsnap.com/scrypt.html @@ -26,7 +26,7 @@ abcrypt - abcrypt encrypted data format *{manname}* is a modern file encryption format with the data authenticity inspired by the {scrypt-encrypted-data-format}[scrypt encrypted data format]. -It uses Argon2id as defined in {rfc9106}[RFC 9106] for key derivation, +It uses Argon2 as defined in {rfc9106}[RFC 9106] for key derivation, BLAKE2b-512-MAC as defined in {rfc7693}[RFC 7693] for header integrity checking and XChaCha20-Poly1305 as defined in {draft-arciszewski-xchacha-03}[draft-arciszewski-xchacha-03] for encryption. @@ -50,42 +50,55 @@ data and a file body encrypted with the derived key. |8 |4 -|Memory size `m` (`memoryCost`). +|Argon2 type. |12 |4 -|Number of iterations `t` (`timeCost`). +|Argon2 version. |16 |4 -|Degree of parallelism `p` (`parallelism`). +|Memory size `m` (`memoryCost`). |20 +|4 +|Number of iterations `t` (`timeCost`). + +|24 +|4 +|Degree of parallelism `p` (`parallelism`). + +|28 |32 -|Salt. +|Salt for Argon2. -|52 +|60 |24 -|Nonce. +|Nonce for XChaCha20-Poly1305. -|76 +|84 |64 |MAC of the header. -|140 +|148 |n |Ciphertext. -|140 + n +|148 + n |16 |MAC of the ciphertext. |=== All multibyte values are stored in little-endian. -=== Filename +=== Filename extension + +{manname} files should use the extension `.abcrypt`. + +=== MIME type -{manname} files may use the extension `.abcrypt`. +When transferring {manname} files over the Internet, the appropriate MIME type +is `application/x-abcrypt`. include::{includedir}/section-copyright.adoc[] diff --git a/docs/spec/FORMAT.adoc b/docs/spec/FORMAT.adoc index e1e0f75c..9c931246 100644 --- a/docs/spec/FORMAT.adoc +++ b/docs/spec/FORMAT.adoc @@ -5,7 +5,7 @@ = Abcrypt Encrypted Data Format Shun Sakai // Specify in UTC. -v0.3.7, 2024-07-02 +v0.4.0, 2024-12-09 :icons: font :idprefix: :idseparator: - @@ -26,8 +26,8 @@ document describes the abcrypt encrypted data format. abcrypt is a modern file encryption format inspired by the {scrypt-encrypted-data-format}[scrypt encrypted data format]. abcrypt uses -<> for key derivation, <> for header -integrity checking and <> for encryption. +<> for key derivation, <> for header integrity +checking and <> for encryption. == Conventions used in this document @@ -47,8 +47,6 @@ XChaCha20-Poly1305 is the AEAD algorithm from An abcrypt file is composed of two parts: a <> containing the required data and a <> encrypted with the derived key. -abcrypt files may use the extension `.abcrypt`. - .The structure of the abcrypt encrypted data format |=== |Offset |Bytes |Description |Detail @@ -56,7 +54,7 @@ abcrypt files may use the extension `.abcrypt`. |stem:[0] |stem:[7] |Magic number ("abcrypt"). -|<> +|<> |stem:[7] |stem:[1] @@ -65,40 +63,50 @@ abcrypt files may use the extension `.abcrypt`. |stem:[8] |stem:[4] +|Argon2 type. +|<> + +|stem:[12] +|stem:[4] +|Argon2 version. +|<> + +|stem:[16] +|stem:[4] |Memory size `m` (`memoryCost`). |<> -|stem:[12] +|stem:[20] |stem:[4] |Number of iterations `t` (`timeCost`). |<> -|stem:[16] +|stem:[24] |stem:[4] |Degree of parallelism `p` (`parallelism`). |<> -|stem:[20] +|stem:[28] |stem:[32] -|Salt. -|<> +|Salt for <>. +|<> -|stem:[52] +|stem:[60] |stem:[24] -|Nonce. -|<> +|Nonce for <>. +|<> -|stem:[76] +|stem:[84] |stem:[64] |MAC of the header. |<> -|stem:[140] +|stem:[148] |stem:[n] |Ciphertext. |<> -|stem:[140 + n] +|stem:[148 + n] |stem:[16] |MAC of the ciphertext. |<> @@ -109,30 +117,43 @@ All multibyte values are stored in little-endian. == Key derivation The derived key for computing the header MAC and the derived key for encryption -are produced by <>. The abcrypt encrypted data format uses Argon2id as -the type and 0x13 (19) as the version. +are produced by <>. .The derived key is produced as follows ---- -derived_key = Argon2( - memoryCost = header[8..12], - timeCost = header[12..16], - parallelism = header[16..20], - output_len = 96, - algorithm = Argon2id, - version = 0x13, - pwd = password, - salt = header[20..52], +derivedKey = Argon2( + password = password, + salt = header[28..60], + parallelism = header[24..28], + tagLength = 96, + memoryCost = header[16..20], + timeCost = header[20..24], + version = header[12..16], + secretKey = [], + associatedData = [], + type = header[8..12], ) ---- -The resulting derived key (`derived_key`) length is 96 bytes. The first 32 -bytes of `derived_key` are for encryption (<> key), and the -last 64 bytes are for computing the header MAC (<> key). +The size of `secretKey` (pepper) and `associatedData` (associated data) are +zero (empty). + +The resulting derived key (`derivedKey`) length is 96 bytes. The first 32 bytes +of `derivedKey` are the <> key (`encryptionKey`) for +encryption, and the last 64 bytes are the <> key +(`headerMacKey`) for computing the header MAC. + +.The derived key is split as follows +---- +encryptionKey = derivedKey[..32] +headerMacKey = derivedKey[32..] +---- +<>, <>, <>, <>, -<>, and <> used when encrypting -are stored in the header, and these stored values are used when decrypting. +<>, and <> used when +encrypting are stored in the header, and these stored values are used when +decrypting. == Header format @@ -144,7 +165,40 @@ A 7-byte string for identifying the abcrypt encrypted data format. The value is === Version number A 1-byte version number of the abcrypt encrypted data format. The current value -is 0. +is 1. + +=== Argon2 type + +.The following Argon2 types are valid +|=== +|Value |Description + +|stem:[0] +|Argon2d. + +|stem:[1] +|Argon2i. + +|stem:[2] +|Argon2id. +|=== + +The Argon2 type is represented as 4 bytes in little-endian. + +=== Argon2 version + +.The following Argon2 versions are valid +|=== +|Value |Description + +|stem:[16] +|Version 0x10 (16 in decimal). + +|stem:[19] +|Version 0x13 (19 in decimal). +|=== + +The Argon2 version is represented as 4 bytes in little-endian. === Argon2 parameters @@ -170,13 +224,13 @@ is 0. Each parameter is represented as 4 bytes in little-endian. -=== Salt +=== Salt for Argon2 A 32-byte salt for <>. NOTE: The salt should be generated from a CSPRNG. -=== Nonce +=== Nonce for XChaCha20-Poly1305 A 24-byte nonce for <>. @@ -186,20 +240,21 @@ NOTE: The nonce should be generated from a CSPRNG. The MAC (authentication tag) of the header. The MAC is computed with <> over the whole header up to and including the nonce (first -76 bytes of the header). +84 bytes of the header). .The MAC is computed as follows ---- mac = BLAKE2b( - data = header[..76], - output_size = 64, - key = derived_key[32..], + data = header[..84], + digestLength = 64, + key = headerMacKey, salt = [], - person = [], + personalization = [], ) ---- -The size of `salt` and `person` (personalization string) is zero (empty). +The size of `salt` and `personalization` (personalization string) are zero +(empty). == File body @@ -208,17 +263,29 @@ The file body is encrypted with XChaCha20-Poly1305. .The ciphertext is computed as follows ---- ciphertext = XChaCha20-Poly1305( - key = derived_key[..32], - nonce = header[20..52], plaintext = plaintext, aad = [], + key = encryptionKey, + nonce = header[60..84], ) ---- The size of `aad` (additional authenticated data) is zero (empty). +<> used when encrypting is stored in the +header, and the stored value is used when decrypting. + IMPORTANT: The abcrypt encrypted data format uses a postfix tag. +== Filename extension + +abcrypt files should use the extension `.abcrypt`. + +== MIME type + +When transferring abcrypt files over the Internet, the appropriate MIME type is +`application/x-abcrypt`. + == ABNF definition of the file format [source,abnf] @@ -233,6 +300,11 @@ endif::[] == Format changelog +Version 1:: + + * Add the Argon2 type field to allow choosing the Argon2 type. + * Add the Argon2 version field to allow choosing the Argon2 version. + Version 0:: - Initial release. + * Initial release. diff --git a/docs/spec/abcrypt.abnf b/docs/spec/abcrypt.abnf index 3fbf7d13..b0dec0a4 100644 --- a/docs/spec/abcrypt.abnf +++ b/docs/spec/abcrypt.abnf @@ -6,13 +6,15 @@ abcrypt = header payload ; Header -header = signature version-number argon2-parameters salt nonce header-mac - -signature = %s"abcrypt" ; magic number -version-number = OCTET -salt = 32OCTET ; 32-byte salt for Argon2 -nonce = 24OCTET ; 24-byte nonce for XChaCha20-Poly1305 -header-mac = 64OCTET ; BLAKE2b-512-MAC of the header +header = signature version-number argon2-type argon2-version argon2-parameters argon2-salt xchacha20-poly1305-nonce header-mac + +signature = %s"abcrypt" ; magic number +version-number = %x01 ; current version number +argon2-type = %x00000001-00000003 ; Argon2 type +argon2-version = %x00000010 / %x00000013 ; Argon2 version +argon2-salt = 32OCTET ; 32-byte salt for Argon2 +xchacha20-poly1305-nonce = 24OCTET ; 24-byte nonce for XChaCha20-Poly1305 +header-mac = 64OCTET ; BLAKE2b-512-MAC of the header ; Argon2 parameters diff --git a/justfile b/justfile index eb446535..09e1f4cf 100644 --- a/justfile +++ b/justfile @@ -22,7 +22,7 @@ default: build # Run tests @test: - cargo test + cargo test -p abcrypt -p abcrypt-cli -p abcrypt-capi # Run the formatter @fmt: @@ -65,6 +65,10 @@ clang-tidy: setup-meson cd crates/capi/examples ninja -C builddir clang-tidy +# Run tests for the Wasm bindings +@wasm-test: + wasm-pack test --node crates/wasm + # Build examples for the Wasm bindings @build-wasm-examples: wasm-pack build -t deno crates/wasm @@ -156,6 +160,7 @@ publish-wasm: build-wasm # Increment the version of the command-line utility @bump-cli part: + bump-my-version bump --config-file .bumpversion-cli.toml {{part}} cargo set-version --bump {{part}} -p abcrypt-cli # Increment the version of the C API diff --git a/package-lock.json b/package-lock.json index 3fb27790..533c863e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,21 +9,21 @@ "version": "1.0.0", "license": "CC-BY-4.0", "devDependencies": { - "@antora/cli": "^3.1.9", - "@antora/lunr-extension": "^1.0.0-alpha.8", - "@antora/site-generator": "^3.1.9", + "@antora/cli": "^3.1.10", + "@antora/lunr-extension": "^1.0.0-alpha.9", + "@antora/site-generator": "^3.1.10", "@djencks/asciidoctor-mathjax": "^0.0.9" } }, "node_modules/@antora/asciidoc-loader": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/asciidoc-loader/-/asciidoc-loader-3.1.9.tgz", - "integrity": "sha512-flE27T2yI8TX7rUNjbBHWN3iR6s+kBuRBbUPncUFcWjx6mXzll8JLiTkxnc8JXHGzgKlveT+t5AkPYGACLfasg==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/asciidoc-loader/-/asciidoc-loader-3.1.10.tgz", + "integrity": "sha512-np0JkOV37CK7V4eDZUZXf4fQuCKYW3Alxl8FlyzBevXi2Ujv29O82JLbHbv1cyTsvGkGNNB+gzJIx9XBsQ7+Nw==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/logger": "3.1.9", - "@antora/user-require-helper": "~2.0", + "@antora/logger": "3.1.10", + "@antora/user-require-helper": "~3.0", "@asciidoctor/core": "~2.2" }, "engines": { @@ -31,15 +31,15 @@ } }, "node_modules/@antora/cli": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/cli/-/cli-3.1.9.tgz", - "integrity": "sha512-kCUqWX3G/9Pvf8SWZ45ioHwWdOc9uamy2E5/FFwyGiTeu4ubNbadOauLVvMzSZHUxVDnGxXwCsmmQ2HwM919ew==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/cli/-/cli-3.1.10.tgz", + "integrity": "sha512-gp8u9aVM0w1DtWSsB5PwvEfFYKrooPENLhN58RAfdgTrcsTsWw+CDysFZPgEaHB0Y1ZbanR82ZH/f6JVKGcZfQ==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/logger": "3.1.9", - "@antora/playbook-builder": "3.1.9", - "@antora/user-require-helper": "~2.0", + "@antora/logger": "3.1.10", + "@antora/playbook-builder": "3.1.10", + "@antora/user-require-helper": "~3.0", "commander": "~11.1" }, "bin": { @@ -50,15 +50,15 @@ } }, "node_modules/@antora/content-aggregator": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/content-aggregator/-/content-aggregator-3.1.9.tgz", - "integrity": "sha512-g+UzevPSm5c4R0j1U9uysJfdIUfp++QOHIEBmqjhfx/aIEnOL70zA+WF55Mm+syAfzU3877puI27sOp8qtPglw==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/content-aggregator/-/content-aggregator-3.1.10.tgz", + "integrity": "sha512-OT6ZcCA7LrtNfrAZUr3hFh+Z/1isKpsfnqFjCDC66NEMqIyzJO99jq0CM66rYlYhyX7mb5BwEua8lHcwpOXNow==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/expand-path-helper": "~2.0", - "@antora/logger": "3.1.9", - "@antora/user-require-helper": "~2.0", + "@antora/expand-path-helper": "~3.0", + "@antora/logger": "3.1.10", + "@antora/user-require-helper": "~3.0", "braces": "~3.0", "cache-directory": "~2.0", "fast-glob": "~3.3", @@ -77,14 +77,14 @@ } }, "node_modules/@antora/content-classifier": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/content-classifier/-/content-classifier-3.1.9.tgz", - "integrity": "sha512-PVJqwp5uvZE1PlpeJtb0p6al75fN+fmXGIC6DHcKysRnr0xo+sgz8X2r4mnNWdTWRqum2yVigMmmuXYTg3cJlQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/content-classifier/-/content-classifier-3.1.10.tgz", + "integrity": "sha512-3JJl4IIiTX00v/MirK603NoqIcHjGYAaRWt3Q4U03tI1Fv2Aho/ypO3FE45069jFf0Dx2uDJfp5kapb9gaIjdQ==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/asciidoc-loader": "3.1.9", - "@antora/logger": "3.1.9", + "@antora/asciidoc-loader": "3.1.10", + "@antora/logger": "3.1.10", "mime-types": "~2.1", "vinyl": "~3.0" }, @@ -93,37 +93,37 @@ } }, "node_modules/@antora/document-converter": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/document-converter/-/document-converter-3.1.9.tgz", - "integrity": "sha512-pH7tQaIjcPsFdYkaBEAvA/5ki04IQwQGHoR+2jadKdMl6P+J5KA1VzNnMgyIL6gHn7auJIkoOKadfItRB9lHGQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/document-converter/-/document-converter-3.1.10.tgz", + "integrity": "sha512-qi9ctgcKal8tZtWflVo66w+4zCJoBmUKRV+eA9aRRR09KDdU9r514vu1adWNgniPppISr90zD13V5l2JUy/2CQ==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/asciidoc-loader": "3.1.9" + "@antora/asciidoc-loader": "3.1.10" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@antora/expand-path-helper": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@antora/expand-path-helper/-/expand-path-helper-2.0.0.tgz", - "integrity": "sha512-CSMBGC+tI21VS2kGW3PV7T2kQTM5eT3f2GTPVLttwaNYbNxDve08en/huzszHJfxo11CcEs26Ostr0F2c1QqeA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@antora/expand-path-helper/-/expand-path-helper-3.0.0.tgz", + "integrity": "sha512-7PdEIhk97v85/CSm3HynCsX14TR6oIVz1s233nNLsiWubE8tTnpPt4sNRJR+hpmIZ6Bx9c6QDp3XIoiyu/WYYA==", "dev": true, "license": "MPL-2.0", "engines": { - "node": ">=10.17.0" + "node": ">=16.0.0" } }, "node_modules/@antora/file-publisher": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/file-publisher/-/file-publisher-3.1.9.tgz", - "integrity": "sha512-C0VwVjuFbE1CVpZDgwYR1gZCNr1tMw5vueyF9wHZH0KCqAsp9iwo7bwj8wKWMPogxcxdYhnAvtDJnYmYFCuDWQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/file-publisher/-/file-publisher-3.1.10.tgz", + "integrity": "sha512-DPR/0d1P+kr3qV4T0Gh81POEO/aCmNWIp/oLUYAhr0HHOcFzgpTUUoLStgcYynZPFRIB7EYKSab+oYSCK17DGA==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/expand-path-helper": "~2.0", - "@antora/user-require-helper": "~2.0", + "@antora/expand-path-helper": "~3.0", + "@antora/user-require-helper": "~3.0", "vinyl": "~3.0", "yazl": "~2.5" }, @@ -132,13 +132,13 @@ } }, "node_modules/@antora/logger": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/logger/-/logger-3.1.9.tgz", - "integrity": "sha512-MKuANodcX0lfRyiB+Rxl/Kv7UOxc2glzTYFoIoBB7uzxF0A+AhvUJDmpGQFRFN2ihxy99N3nLJmZpDebwXyE+A==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/logger/-/logger-3.1.10.tgz", + "integrity": "sha512-WSuIxEP2tVrhWtTj/sIrwBDjpi4ldB/1Kpiu4PXmY4/qeWP8thW6u8nXdwdDcWss5zqkZWjourvWKwVq7y8Wjg==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/expand-path-helper": "~2.0", + "@antora/expand-path-helper": "~3.0", "pino": "~9.2", "pino-pretty": "~11.2", "sonic-boom": "~4.0" @@ -148,45 +148,44 @@ } }, "node_modules/@antora/lunr-extension": { - "version": "1.0.0-alpha.8", - "resolved": "https://registry.npmjs.org/@antora/lunr-extension/-/lunr-extension-1.0.0-alpha.8.tgz", - "integrity": "sha512-vdBgW3rsvbnmA236kT2Dckh9n0Db5za2/WxiLnFLgZ05ZO1KJQa9+R2WHaIFuGE7bKKbY+lqfM/i3KiezbL9YQ==", + "version": "1.0.0-alpha.9", + "resolved": "https://registry.npmjs.org/@antora/lunr-extension/-/lunr-extension-1.0.0-alpha.9.tgz", + "integrity": "sha512-DBbHsSok4ar/XWHUAGPK7xY4wyd3HWdCwh3hy7G5Bvy9/SmaTMePnnA8PfvWJievh3z0kG1NYGp33Y+nZkc1PA==", "dev": true, "license": "MPL-2.0", "workspaces": [ "." ], "dependencies": { - "cheerio": "1.0.0-rc.10", - "html-entities": "~2.3", + "htmlparser2": "~9.1", "lunr": "~2.3", - "lunr-languages": "~1.9" + "lunr-languages": "~1.10" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@antora/navigation-builder": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/navigation-builder/-/navigation-builder-3.1.9.tgz", - "integrity": "sha512-zyl2yNjK31Dl6TRJgnoFb4Czwt9ar3wLTycAdMeZ+U/8YcAUHD8z7NCssPFFvZ0BbUr00NP+gbqDmCr6yz32NQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/navigation-builder/-/navigation-builder-3.1.10.tgz", + "integrity": "sha512-aLMK49nYsSB3mEZbLkmUXDAUYmscv2AFWu+5c3eqVGkQ6Wgyd79WQ6Bz3/TN9YqkzGL+PqGs0G39F0VQzD23Hw==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/asciidoc-loader": "3.1.9" + "@antora/asciidoc-loader": "3.1.10" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@antora/page-composer": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/page-composer/-/page-composer-3.1.9.tgz", - "integrity": "sha512-X6Qj+J5dfFAGXoCAOaA+R6xRp8UoNMDHsRsB1dUTT2QNzk1Lrq6YkYyljdD2cxkWjLVqQ/pQSP+BJVNFGbqDAQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/page-composer/-/page-composer-3.1.10.tgz", + "integrity": "sha512-JoEg8J8HVsnPmAgUrYSGzf0C8rQefXyCi/18ucy0utyfUvlJNsZvUbGUPx62Het9p0JP0FkAz2MTLyDlNdArVg==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/logger": "3.1.9", + "@antora/logger": "3.1.10", "handlebars": "~4.7", "require-from-string": "~2.0" }, @@ -195,9 +194,9 @@ } }, "node_modules/@antora/playbook-builder": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/playbook-builder/-/playbook-builder-3.1.9.tgz", - "integrity": "sha512-MJ/OWz4pReC98nygGTXC5bOL/TDDtCYpSkHFBz2ST4L6tuM8rv9c5+cp//JkwY/QlTOvcuJ0f2xq4a7a5nI7Qw==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/playbook-builder/-/playbook-builder-3.1.10.tgz", + "integrity": "sha512-UB8UmRYfkKgActTUlotdVS4FKGjaZgTnSXE7Fns1xb3/3HRanWvI+Yze1OmCkGC33cTpoQFnSYp7ySEH8LaiBw==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -211,9 +210,9 @@ } }, "node_modules/@antora/redirect-producer": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/redirect-producer/-/redirect-producer-3.1.9.tgz", - "integrity": "sha512-9OLwoMhqifsBxTebInh/5W16GdDsdj+YkKG3TiCASlAOYsDbuhbeRPFUlyKKSRkMrtKKnFgHR0Z3DNPXYlH2NQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/redirect-producer/-/redirect-producer-3.1.10.tgz", + "integrity": "sha512-IbWJGh6LmsxJQ821h0B9JfooofFZBgFLZxsbp/IoTLkBFGLFAY5tDRvB6rvubfNLRoSjM8VjEUXGqVLlwZOb+g==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -224,39 +223,39 @@ } }, "node_modules/@antora/site-generator": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/site-generator/-/site-generator-3.1.9.tgz", - "integrity": "sha512-YYESPG22tGX1CxRPSAr6acKILCO8JfGkM1OYc7Sw3D7ZvCy1YgZMAaTYK0T5yl9LXg+l/UZi1xq/Ej0qHnYQiw==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/site-generator/-/site-generator-3.1.10.tgz", + "integrity": "sha512-NCULYtwUjIyr5FGCymhfG/zDVUmZ6pfmCPorka8mAzo4/GDx1T7bgaRL9rEIyf2AMqcm7apQiAz03mpU4kucsw==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/asciidoc-loader": "3.1.9", - "@antora/content-aggregator": "3.1.9", - "@antora/content-classifier": "3.1.9", - "@antora/document-converter": "3.1.9", - "@antora/file-publisher": "3.1.9", - "@antora/logger": "3.1.9", - "@antora/navigation-builder": "3.1.9", - "@antora/page-composer": "3.1.9", - "@antora/playbook-builder": "3.1.9", - "@antora/redirect-producer": "3.1.9", - "@antora/site-mapper": "3.1.9", - "@antora/site-publisher": "3.1.9", - "@antora/ui-loader": "3.1.9", - "@antora/user-require-helper": "~2.0" + "@antora/asciidoc-loader": "3.1.10", + "@antora/content-aggregator": "3.1.10", + "@antora/content-classifier": "3.1.10", + "@antora/document-converter": "3.1.10", + "@antora/file-publisher": "3.1.10", + "@antora/logger": "3.1.10", + "@antora/navigation-builder": "3.1.10", + "@antora/page-composer": "3.1.10", + "@antora/playbook-builder": "3.1.10", + "@antora/redirect-producer": "3.1.10", + "@antora/site-mapper": "3.1.10", + "@antora/site-publisher": "3.1.10", + "@antora/ui-loader": "3.1.10", + "@antora/user-require-helper": "~3.0" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@antora/site-mapper": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/site-mapper/-/site-mapper-3.1.9.tgz", - "integrity": "sha512-9FCObL+JIjBoby8z+beu2uuvAtCjm5EsEQt+16gCIMX1ktVP3W3gVsdRSvVcGcVEpizILFhMawkcQknZPUp5mg==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/site-mapper/-/site-mapper-3.1.10.tgz", + "integrity": "sha512-KY1j/y0uxC2Y7RAo4r4yKv9cgFm8aZoRylZXEODJnwj3tffbZ2ZdRzSWHp6fN0QX/Algrr9JNd9CWrjcj2f3Zw==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/content-classifier": "3.1.9", + "@antora/content-classifier": "3.1.10", "vinyl": "~3.0" }, "engines": { @@ -264,26 +263,26 @@ } }, "node_modules/@antora/site-publisher": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/site-publisher/-/site-publisher-3.1.9.tgz", - "integrity": "sha512-L5To8f4QswZliXu6yB6O7O8CuBbLctjNbxZqP3m0FP7VaOONp85ftzEq1BFEm4BXXSwH1n4ujZx1qGBHP9ooOQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/site-publisher/-/site-publisher-3.1.10.tgz", + "integrity": "sha512-G4xcUWvgth8oeEQwiu9U1cE0miQtYHwKHOobUbDBt2Y6LlC5H31zQQmAyvMwTsGRlvYRgLVtG6j9d6JBwQ6w9Q==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/file-publisher": "3.1.9" + "@antora/file-publisher": "3.1.10" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@antora/ui-loader": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@antora/ui-loader/-/ui-loader-3.1.9.tgz", - "integrity": "sha512-g0/9dRE5JVMYukIU3x+Rvr41bPdK3sUD2xQIAniRjE6usIZs1mEsTGshVKVEoOqqnSekXE85HVhybjNHsC+qbQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/@antora/ui-loader/-/ui-loader-3.1.10.tgz", + "integrity": "sha512-H1f5wI5a5HjLuE/Wexvc8NZy8w83Bhqjka7t1DbwOOqP+LyxFGLx/QbBVKdTtgFNDHVMtNBlplQq0ixeoTSh0A==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/expand-path-helper": "~2.0", + "@antora/expand-path-helper": "~3.0", "braces": "~3.0", "cache-directory": "~2.0", "fast-glob": "~3.3", @@ -300,16 +299,16 @@ } }, "node_modules/@antora/user-require-helper": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@antora/user-require-helper/-/user-require-helper-2.0.0.tgz", - "integrity": "sha512-5fMfBZfw4zLoFdDAPMQX6Frik90uvfD8rXOA4UpXPOUikkX4uT1Rk6m0/4oi8oS3fcjiIl0k/7Nc+eTxW5TcQQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@antora/user-require-helper/-/user-require-helper-3.0.0.tgz", + "integrity": "sha512-KIXb8WYhnrnwH7Jj21l1w+et9k5GvcgcqvLOwxqWLEd0uVZOiMFdqFjqbVm3M+zcrs1JXWMeh2LLvxBbQs3q/Q==", "dev": true, "license": "MPL-2.0", "dependencies": { - "@antora/expand-path-helper": "~2.0" + "@antora/expand-path-helper": "~3.0" }, "engines": { - "node": ">=10.17.0" + "node": ">=16.0.0" } }, "node_modules/@asciidoctor/core": { @@ -434,6 +433,13 @@ "node": ">=8.0.0" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -442,9 +448,9 @@ "license": "MIT" }, "node_modules/bare-events": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", - "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.3.tgz", + "integrity": "sha512-pCO3aoRJ0MBiRMu8B7vUga0qL3L7gO1+SW7ku6qlSsMLwuhaawnuvZDyzJY/kyC63Un0XAB0OPUcfF1eTO/V+Q==", "dev": true, "license": "Apache-2.0", "optional": true @@ -470,13 +476,6 @@ ], "license": "MIT" }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -549,45 +548,6 @@ "node": ">=4" } }, - "node_modules/cheerio": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", - "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cheerio-select": "^1.5.0", - "dom-serializer": "^1.3.2", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" - }, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.6.0.tgz", - "integrity": "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "css-select": "^4.3.0", - "css-what": "^6.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.3.1", - "domutils": "^2.8.0" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/clean-git-ref": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", @@ -663,36 +623,6 @@ "node": ">=0.8" } }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", @@ -727,15 +657,15 @@ "license": "MIT" }, "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, "license": "MIT", "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" }, "funding": { "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" @@ -755,13 +685,13 @@ "license": "BSD-2-Clause" }, "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "domelementtype": "^2.2.0" + "domelementtype": "^2.3.0" }, "engines": { "node": ">= 4" @@ -771,15 +701,15 @@ } }, "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" }, "funding": { "url": "https://github.com/fb55/domutils?sponsor=1" @@ -796,11 +726,14 @@ } }, "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -850,9 +783,9 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -860,7 +793,7 @@ "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -884,9 +817,9 @@ "license": "MIT" }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", "dev": true, "license": "ISC", "dependencies": { @@ -984,27 +917,10 @@ "node": ">=14" } }, - "node_modules/html-entities": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.6.tgz", - "integrity": "sha512-9o0+dcpIw2/HxkNuYKxSJUF/MMRZQECK4GnF+oQOmJ83yCVHTWgCH5aOXxK5bozNRmM8wtgryjHD3uloPBDEGw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "license": "MIT" - }, "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -1015,10 +931,10 @@ ], "license": "MIT", "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" } }, "node_modules/ieee754": { @@ -1181,9 +1097,9 @@ "license": "MIT" }, "node_modules/lunr-languages": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.9.0.tgz", - "integrity": "sha512-Be5vFuc8NAheOIjviCRms3ZqFFBlzns3u9DXpPSZvALetgnydAN0poV71pVLFn0keYy/s4VblMMkqewTLe+KPg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.10.0.tgz", + "integrity": "sha512-BBjKKcwrieJlzwwc9M5H/MRXGJ2qyOSDx/NXYiwkuKjiLOOoouh0WsDzeqcLoUWcX31y7i8sb8IgsZKObdUCkw==", "dev": true, "license": "MPL-1.1" }, @@ -1337,19 +1253,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", @@ -1377,23 +1280,6 @@ "dev": true, "license": "(MIT AND Zlib)" }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse5": "^6.0.1" - } - }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1469,9 +1355,9 @@ } }, "node_modules/pino-abstract-transport/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "dev": true, "license": "MIT", "dependencies": { @@ -1512,9 +1398,9 @@ } }, "node_modules/pino-pretty/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "dev": true, "license": "MIT", "dependencies": { @@ -1857,9 +1743,9 @@ } }, "node_modules/streamx": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.1.tgz", - "integrity": "sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==", + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.21.1.tgz", + "integrity": "sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==", "dev": true, "license": "MIT", "dependencies": { @@ -1905,11 +1791,14 @@ } }, "node_modules/text-decoder": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.1.tgz", - "integrity": "sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } }, "node_modules/thread-stream": { "version": "3.1.0", @@ -1934,13 +1823,6 @@ "node": ">=8.0" } }, - "node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", - "dev": true, - "license": "0BSD" - }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", diff --git a/package.json b/package.json index 609ae0c7..3f5a1555 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,9 @@ "url": "https://github.com/sorairolake/abcrypt/issues" }, "devDependencies": { - "@antora/cli": "^3.1.9", - "@antora/lunr-extension": "^1.0.0-alpha.8", - "@antora/site-generator": "^3.1.9", + "@antora/cli": "^3.1.10", + "@antora/lunr-extension": "^1.0.0-alpha.9", + "@antora/site-generator": "^3.1.10", "@djencks/asciidoctor-mathjax": "^0.0.9" } } diff --git a/rustfmt.toml b/rustfmt.toml index 1397e0f4..ba5100fa 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -4,6 +4,7 @@ edition = "2021" format_code_in_doc_comments = true +format_macro_matchers = true group_imports = "StdExternalCrate" imports_granularity = "Crate" wrap_comments = true