diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..397bdaa4f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 diff --git a/.github/workflows/aead.yml b/.github/workflows/aead.yml new file mode 100644 index 000000000..dc3476c09 --- /dev/null +++ b/.github/workflows/aead.yml @@ -0,0 +1,65 @@ +name: aead + +on: + pull_request: + paths: + - "aead/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: aead + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo check --all-features + - run: cargo build --target ${{ matrix.target }} --release --no-default-features + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features bytes + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features heapless + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features rand_core + + # TODO(tarcieri): re-enable after next `crypto-common` release + # minimal-versions: + # uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + # with: + # working-directory: ${{ github.workflow }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo test --release --no-default-features + - run: cargo test --release + - run: cargo test --release --all-features diff --git a/.github/workflows/async-signature.yml b/.github/workflows/async-signature.yml new file mode 100644 index 000000000..1846df7c6 --- /dev/null +++ b/.github/workflows/async-signature.yml @@ -0,0 +1,67 @@ +name: async-signature + +on: + pull_request: + paths: + - "async-signature/**" + - "signature/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: async-signature + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + no_std: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} --release --no-default-features + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # Minimum Rust version the tests pass on + - stable + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo test --release + - run: cargo test --all-features --release + + minimal-versions: + if: false # Temporarily disabled until signature v2.3.0 is published + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@nightly + - run: cargo update -Z minimal-versions + - uses: dtolnay/rust-toolchain@stable + - uses: RustCrypto/actions/cargo-hack-install@master + - run: rm ../Cargo.toml + - run: cargo hack test --release --feature-powerset diff --git a/.github/workflows/cipher.yml b/.github/workflows/cipher.yml new file mode 100644 index 000000000..28e2eb6bf --- /dev/null +++ b/.github/workflows/cipher.yml @@ -0,0 +1,72 @@ +name: cipher + +on: + pull_request: + paths: + - ".github/workflows/cipher.yml" + - "cipher/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: cipher + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} --features rand_core + - run: cargo build --target ${{ matrix.target }} --features block-padding + + # TODO: use the reusable workflow after this crate will be part of the + # root workspace + minimal-versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@nightly + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo update -Z minimal-versions + - uses: dtolnay/rust-toolchain@stable + - run: cargo hack test --release --feature-powerset + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo check --all-features + - run: cargo test + - run: cargo test --features block-padding + - run: cargo test --features dev + - run: cargo test --all-features diff --git a/.github/workflows/crypto-common.yml b/.github/workflows/crypto-common.yml new file mode 100644 index 000000000..3c4e4edf7 --- /dev/null +++ b/.github/workflows/crypto-common.yml @@ -0,0 +1,59 @@ +name: crypto-common + +on: + pull_request: + paths: + - "crypto-common/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: crypto-common + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo check --all-features + - run: cargo test + - run: cargo test --all-features diff --git a/.github/workflows/crypto.yml b/.github/workflows/crypto.yml new file mode 100644 index 000000000..d3ec5fa81 --- /dev/null +++ b/.github/workflows/crypto.yml @@ -0,0 +1,69 @@ +name: crypto + +on: + pull_request: + paths: + - "crypto/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: crypto + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.65.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + --features aead,cipher,digest,elliptic-curve,signature,universal-hash + + minimal-versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo update -Z minimal-versions + - run: cargo hack test --release --feature-powerset + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.65.0 # MSRV + - stable + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo check --all-features + - run: cargo test --no-default-features --release + - run: cargo test --release + - run: cargo test --all-features --release diff --git a/.github/workflows/digest.yml b/.github/workflows/digest.yml new file mode 100644 index 000000000..75a70b20a --- /dev/null +++ b/.github/workflows/digest.yml @@ -0,0 +1,64 @@ +name: digest + +on: + pull_request: + paths: + - "digest/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: digest + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} + + # TODO(tarcieri): re-enable after next `crypto-common` release + #minimal-versions: + # uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + # with: + # working-directory: ${{ github.workflow }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo check --all-features + - run: cargo test --no-default-features + - run: cargo test + - run: cargo test --features dev + - run: cargo test --features alloc + - run: cargo test --features std + - run: cargo test --all-features diff --git a/.github/workflows/elliptic-curve.yml b/.github/workflows/elliptic-curve.yml new file mode 100644 index 000000000..082155f07 --- /dev/null +++ b/.github/workflows/elliptic-curve.yml @@ -0,0 +1,90 @@ +name: elliptic-curve + +on: + pull_request: + paths: + - ".github/workflows/elliptic-curve.yml" + - "elliptic-curve/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: elliptic-curve + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + RUSTDOCFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} --release --no-default-features + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features arithmetic + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features bits + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features dev + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features digest + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdh + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features hash2curve + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features jwk + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pem + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pkcs8 + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features sec1 + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features serde + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features voprf + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,arithmetic + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,arithmetic,pkcs8 + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,serde + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features arithmetic,serde + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,digest,ecdh,hash2curve,jwk,pem,pkcs8,sec1,serde,voprf + + minimal-versions: + # Temporarily disabled until elliptic-curve 0.13.0-pre.0 is published + if: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo update -Z minimal-versions + - run: cargo +stable build --release --all-features + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + - nightly + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo check --all-features + - run: cargo test --no-default-features + - run: cargo test + - run: cargo test --all-features diff --git a/.github/workflows/kem.yml b/.github/workflows/kem.yml new file mode 100644 index 000000000..70d1608f1 --- /dev/null +++ b/.github/workflows/kem.yml @@ -0,0 +1,55 @@ +name: kem + +on: + pull_request: + paths: + - "kem/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: kem + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.66.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.66.0 # MSRV + - stable + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo check --all-features + - run: cargo test --no-default-features --release + - run: cargo test --release + - run: cargo test --all-features --release diff --git a/.github/workflows/password-hash.yml b/.github/workflows/password-hash.yml new file mode 100644 index 000000000..0b4754a15 --- /dev/null +++ b/.github/workflows/password-hash.yml @@ -0,0 +1,71 @@ +name: password-hash + +on: + pull_request: + paths: + - "password-hash/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: password-hash + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} --release --no-default-features + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features rand_core + + # TODO: use the reusable workflow after this crate will be part of the + # toot workspace + minimal-versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo update -Z minimal-versions + - run: cargo hack test --release --feature-powerset + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo check --all-features + - run: cargo test --release --no-default-features + - run: cargo test --release + - run: cargo test --release --all-features diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml new file mode 100644 index 000000000..8c535dc00 --- /dev/null +++ b/.github/workflows/security-audit.yml @@ -0,0 +1,26 @@ +name: Security Audit +on: + pull_request: + paths: + - .github/workflows/security-audit.yml + - '**/Cargo.lock' + push: + branches: master + paths: '**/Cargo.lock' + schedule: + - cron: "0 0 * * *" + +jobs: + security_audit: + name: Security Audit Workspace + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Cache cargo bin + uses: actions/cache@v4 + with: + path: ~/.cargo/bin + key: ${{ runner.os }}-cargo-audit-v0.20-ubuntu-v24.04 + - uses: rustsec/audit-check@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/signature.yml b/.github/workflows/signature.yml new file mode 100644 index 000000000..a54fe0478 --- /dev/null +++ b/.github/workflows/signature.yml @@ -0,0 +1,80 @@ +name: signature + +on: + pull_request: + paths: + - "signature/**" + - "signature-derive/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: signature + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + RUSTDOCFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} --release --no-default-features + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features derive + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features rand_core + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features derive,rand_core + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # Minimum Rust version the tests pass on + - stable + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo test --release --no-default-features + - run: cargo test --release + - run: cargo test --release --all-features + + derive: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo test --release + working-directory: signature_derive diff --git a/.github/workflows/universal-hash.yml b/.github/workflows/universal-hash.yml new file mode 100644 index 000000000..25896c2e1 --- /dev/null +++ b/.github/workflows/universal-hash.yml @@ -0,0 +1,61 @@ +name: universal-hash + +on: + pull_request: + paths: + - "universal-hash/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: universal-hash + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + # TODO(tarcieri): re-enable after next `crypto-common` release + # minimal-versions: + # uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + # with: + # working-directory: ${{ github.workflow }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.81.0 # MSRV + - stable + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo check --all-features + - run: cargo test --no-default-features --release + - run: cargo test --release + - run: cargo test --all-features --release diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml new file mode 100644 index 000000000..17e37767d --- /dev/null +++ b/.github/workflows/workspace.yml @@ -0,0 +1,49 @@ +name: Workspace + +on: + pull_request: + paths-ignore: + - '**/README.md' + - '**/CHANGELOG.md' + push: + branches: master + paths-ignore: + - '**/README.md' + - '**/CHANGELOG.md' + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + RUSTDOCFLAGS: "-Dwarnings" + +jobs: + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.82.0 + components: clippy + - run: cargo clippy --all --all-features --tests -- -D warnings + + doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + # TODO(tarcieri): remove `--exclude crypto` after new release series + - run: cargo doc --workspace --all-features --no-deps --exclude crypto + + rustfmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rustfmt + - run: cargo fmt --all -- --check diff --git a/.gitignore b/.gitignore index a4048e8bb..0bac88e3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,2 @@ target/ -*/target/ -*/*/target/ -Cargo.lock - +**/target/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f459947b0..000000000 --- a/.travis.yml +++ /dev/null @@ -1,40 +0,0 @@ -language: rust -services: docker -sudo: required - -matrix: - include: - - rust: 1.21.0 - script: cargo test --verbose --all --exclude aead --exclude universal-hash --release - - rust: 1.36.0 - script: cargo test --verbose --package aead --package universal-hash --release - - rust: stable - script: cargo test --verbose --all --release - - rust: nightly - script: cargo test --verbose --all --release - # tests if crates can be built with std feature - - rust: 1.21.0 - script: ./build_std.sh - - - env: TARGET=i686-unknown-linux-gnu - rust: stable - - env: TARGET=powerpc-unknown-linux-gnu - rust: stable - - env: TARGET=powerpc64-unknown-linux-gnu - rust: stable - # tests if crates truly can be built without std - - env: TARGET=thumbv7em-none-eabi - rust: nightly - script: xargo build --all --exclude aead --no-default-features --verbose --target $TARGET - install: - - cargo install xargo || true - - rustup target install armv7-unknown-linux-gnueabihf - - rustup component add rust-src - -install: - - cargo install cross || true - -script: - - cross test --verbose --all --release --target $TARGET - -cache: cargo diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..9bb81b27f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1397 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.6", + "generic-array", +] + +[[package]] +name = "aead" +version = "0.6.0-rc.0" +dependencies = [ + "arrayvec", + "blobby", + "bytes", + "crypto-common 0.2.0-rc.1", + "heapless", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead 0.5.2", + "aes", + "cipher 0.4.4", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-signature" +version = "0.6.0-pre.4" +dependencies = [ + "signature 2.3.0-pre.4", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blobby" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847495c209977a90e8aad588b959d0ca9f5dc228096d29a6bd3defd53f35eaec" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd016a0ddc7cb13661bf5576073ce07330a693f8608a1320b4e20561cc12cdc" +dependencies = [ + "hybrid-array", + "zeroize", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.4.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6868e23cd7a5b2e18fb2e9a583910b88b8d645dd21017aafc5d0439cf16ae6d6" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + +[[package]] +name = "cc" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead 0.5.2", + "chacha20", + "cipher 0.4.4", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.6", + "inout 0.1.3", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.5.0-pre.7" +dependencies = [ + "blobby", + "crypto-common 0.2.0-rc.1", + "inout 0.2.0-rc.2", + "zeroize", +] + +[[package]] +name = "const-oid" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ff6be19477a1bd5441f382916a89bc2a0b2c35db6d41e0f6e8538bf6d6463f" + +[[package]] +name = "cpufeatures" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto" +version = "0.5.1" +dependencies = [ + "aead 0.5.2", + "cipher 0.4.4", + "crypto-common 0.1.6", + "digest 0.10.7", + "elliptic-curve 0.13.8", + "password-hash 0.5.0", + "signature 2.2.0", + "universal-hash 0.5.1", +] + +[[package]] +name = "crypto-bigint" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8658c15c5d921ddf980f7fe25b1e82f4b7a4083b2c4985fea4922edb8e43e07d" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.6.0-rc.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d748d1f5b807ee6d0df5a548d0130417295c3aaed1dcbbb3d6a2e7106e11fcca" +dependencies = [ + "hybrid-array", + "num-traits", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.0-rc.1" +dependencies = [ + "getrandom", + "hybrid-array", + "rand_core", +] + +[[package]] +name = "crypto-mac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" +dependencies = [ + "const-oid 0.6.2", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid 0.9.6", + "zeroize", +] + +[[package]] +name = "der" +version = "0.8.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82db698b33305f0134faf590b9d1259dc171b5481ac41d5c8146c3b3ee7d4319" +dependencies = [ + "const-oid 0.10.0-rc.3", + "pem-rfc7468 1.0.0-rc.2", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.6", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.0-pre.9" +dependencies = [ + "blobby", + "block-buffer 0.11.0-rc.3", + "const-oid 0.10.0-rc.3", + "crypto-common 0.2.0-rc.1", + "subtle", + "zeroize", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ecdsa" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" +dependencies = [ + "der 0.4.5", + "elliptic-curve 0.10.4", + "hmac 0.11.0", + "signature 1.3.2", +] + +[[package]] +name = "elliptic-curve" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e5c176479da93a0983f0a6fdc3c1b8e7d5be0d7fe3fe05a99f15b96582b9a8" +dependencies = [ + "crypto-bigint 0.2.5", + "ff 0.10.1", + "generic-array", + "group 0.10.0", + "pkcs8 0.7.6", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint 0.5.5", + "digest 0.10.7", + "ff 0.13.0", + "generic-array", + "group 0.13.0", + "hkdf 0.12.4", + "pkcs8 0.10.2", + "rand_core", + "sec1 0.7.3", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.14.0-rc.1" +dependencies = [ + "base16ct", + "base64ct", + "crypto-bigint 0.6.0-rc.6", + "digest 0.11.0-pre.9", + "ff 0.13.0", + "group 0.13.0", + "hex-literal", + "hkdf 0.13.0-pre.4", + "hybrid-array", + "pem-rfc7468 1.0.0-rc.2", + "pkcs8 0.11.0-rc.1", + "rand_core", + "sec1 0.8.0-rc.3", + "serde_json", + "serdect", + "sha2 0.11.0-pre.4", + "sha3", + "subtle", + "tap", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "group" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" +dependencies = [ + "ff 0.10.1", + "rand_core", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", + "rand_core", + "subtle", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac 0.11.0", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hkdf" +version = "0.13.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00176ff81091018d42ff82e8324f8e5adb0b7e0468d1358f653972562dbff031" +dependencies = [ + "hmac 0.13.0-pre.4", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b1fb14e4df79f9406b434b60acef9f45c26c50062cccf1346c6103b8c47d58" +dependencies = [ + "digest 0.11.0-pre.9", +] + +[[package]] +name = "hpke" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4917627a14198c3603282c5158b815ad5534795451d3c074b53cf3cee0960b11" +dependencies = [ + "aead 0.5.2", + "aes-gcm", + "chacha20poly1305", + "digest 0.10.7", + "generic-array", + "hkdf 0.12.4", + "hmac 0.12.1", + "p256 0.13.2", + "rand_core", + "sha2 0.10.8", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "hybrid-array" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a9a965bb102c1c891fb017c09a05c965186b1265a207640f323ddd009f9deb" +dependencies = [ + "typenum", + "zeroize", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding 0.3.3", + "generic-array", +] + +[[package]] +name = "inout" +version = "0.2.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14db49369b2c3f15deb5806de446e05c7f07a2d778b54b278c994fcd1d686f31" +dependencies = [ + "block-padding 0.4.0-rc.2", + "hybrid-array", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "keccak" +version = "0.2.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cdd4f0dc5807b9a2b25dd48a3f58e862606fe7bd47f41ecde36e97422d7e90" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "kem" +version = "0.3.0-pre.0" +dependencies = [ + "hpke", + "p256 0.9.0", + "pqcrypto", + "pqcrypto-traits", + "rand", + "rand_core", + "x3dh-ke", + "zeroize", +] + +[[package]] +name = "libc" +version = "0.2.162" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "p256" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d053368e1bae4c8a672953397bd1bd7183dde1c72b0b7612a15719173148d186" +dependencies = [ + "ecdsa", + "elliptic-curve 0.10.4", + "sha2 0.9.9", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "elliptic-curve 0.13.8", + "primeorder", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "password-hash" +version = "0.6.0-rc.0" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pem-rfc7468" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f22eb0e3c593294a99e9ff4b24cf6b752d43f193aa4415fe5077c159996d497" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pem-rfc7468" +version = "1.0.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dfbfa5c6f0906884269722c5478e72fd4d6c0e24fe600332c6d62359567ce1" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pkcs8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" +dependencies = [ + "der 0.4.5", + "pem-rfc7468 0.2.3", + "spki 0.4.1", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.9", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.11.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eacd2c7141f32aef1cfd1ad0defb5287a3d94592d7ab57c1ae20e3f9f1f0db1f" +dependencies = [ + "der 0.8.0-rc.1", + "spki 0.8.0-rc.1", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash 0.5.1", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash 0.5.1", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pqcrypto" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10caefecccc56b78d30a6b46effa2360048104d668fb7cea944c0761f9c1f11" +dependencies = [ + "pqcrypto-saber", + "pqcrypto-traits", +] + +[[package]] +name = "pqcrypto-internals" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10cdd9eee50fe65bbd4f40211f1a492f1ee52e97a51100950b6f1fa319ab7cd" +dependencies = [ + "cc", + "dunce", + "getrandom", + "libc", +] + +[[package]] +name = "pqcrypto-saber" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7453b631d7bd268fffadf70514cdf05c9e9fd9574e26eafb85b5b86402e34c5b" +dependencies = [ + "cc", + "glob", + "libc", + "pqcrypto-internals", + "pqcrypto-traits", +] + +[[package]] +name = "pqcrypto-traits" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e851c7654eed9e68d7d27164c454961a616cf8c203d500607ef22c737b51bb" + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve 0.13.8", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der 0.7.9", + "generic-array", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.8.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1988446eff153796413a73669dfaa4caa3f5ce8b25fac89e3821a39c611772e" +dependencies = [ + "base16ct", + "der 0.8.0-rc.1", + "hybrid-array", + "pkcs8 0.11.0-rc.1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serdect" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "540c0893cce56cdbcfebcec191ec8e0f470dd1889b6e7a0b503e310a94a168f5" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.0-pre.9", +] + +[[package]] +name = "sha3" +version = "0.11.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e485881f388c2818d709796dc883c1ffcadde9d1f0e054f3a5c14974185261a6" +dependencies = [ + "digest 0.11.0-pre.9", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +dependencies = [ + "digest 0.9.0", + "rand_core", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "signature" +version = "2.3.0-pre.4" +dependencies = [ + "digest 0.11.0-pre.9", + "hex-literal", + "rand_core", + "sha2 0.11.0-pre.4", + "signature_derive", +] + +[[package]] +name = "signature_derive" +version = "2.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "spki" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" +dependencies = [ + "der 0.4.5", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.9", +] + +[[package]] +name = "spki" +version = "0.8.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ac66481418fd7afdc584adcf3be9aa572cf6c2858814494dc2a01755f050bc" +dependencies = [ + "base64ct", + "der 0.8.0-rc.1", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.6", + "subtle", +] + +[[package]] +name = "universal-hash" +version = "0.6.0-rc.0" +dependencies = [ + "crypto-common 0.2.0-rc.1", + "subtle", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", +] + +[[package]] +name = "x3dh-ke" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee8f59e7095cfd040618f328897c4b07925933de33b517949a64bc97b9fcde3" +dependencies = [ + "base64ct", + "bincode", + "const-oid 0.6.2", + "getrandom", + "hkdf 0.11.0", + "p256 0.9.0", + "rand_core", + "serde", + "serde_bytes", + "sha2 0.9.9", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 0a206ff3b..354080ca9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,21 @@ [workspace] +resolver = "2" members = [ "aead", - "block-cipher-trait", - "crypto-mac", + "async-signature", + "cipher", + "crypto", + "crypto-common", "digest", - "stream-cipher", + "elliptic-curve", + "kem", + "password-hash", + "signature", + "signature_derive", "universal-hash", ] + +[patch.crates-io] +crypto-common = { path = "./crypto-common" } +digest = { path = "./digest" } +signature = { path = "./signature" } diff --git a/README.md b/README.md index 5d7291f6e..39f486ac5 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,33 @@ -# RustCrypto traits [![Build Status](https://travis-ci.org/RustCrypto/traits.svg?branch=master)](https://travis-ci.org/RustCrypto/traits) +# RustCrypto: Traits + +[![Project Chat][chat-image]][chat-link] ![Apache2/MIT licensed][license-image] [![dependency status][deps-image]][deps-link] + Collection of traits which describe functionality of cryptographic primitives. ## Crates -| Name | Crates.io | Documentation | -| ------- | :---------:| :-------------:| -| [`block-cipher-trait`](https://en.wikipedia.org/wiki/Block_cipher)| [![crates.io](https://img.shields.io/crates/v/block-cipher-trait.svg)](https://crates.io/crates/block-cipher-trait) | [![Documentation](https://docs.rs/block-cipher-trait/badge.svg)](https://docs.rs/block-cipher-trait) | -| [`crypto-mac`](https://en.wikipedia.org/wiki/Message_authentication_code) | [![crates.io](https://img.shields.io/crates/v/crypto-mac.svg)](https://crates.io/crates/crypto-mac) | [![Documentation](https://docs.rs/crypto-mac/badge.svg)](https://docs.rs/crypto-mac) | -| [`digest`](https://en.wikipedia.org/wiki/Cryptographic_hash_function) | [![crates.io](https://img.shields.io/crates/v/digest.svg)](https://crates.io/crates/digest) | [![Documentation](https://docs.rs/digest/badge.svg)](https://docs.rs/digest) | -| [`stream-cipher`](https://en.wikipedia.org/wiki/Stream_cipher) | [![crates.io](https://img.shields.io/crates/v/stream-cipher.svg)](https://crates.io/crates/stream-cipher) | [![Documentation](https://docs.rs/stream-cipher/badge.svg)](https://docs.rs/stream-cipher) | - -### Minimum Rust version -All crates in this repository support Rust 1.21 or higher. In future minimally -supported version of Rust can be changed, but it will be done with the minor -version bump. + +| Name | Algorithm | Crates.io | Docs | MSRV | +|---------------------|-----------|:---------:|:-----:|:----:| +| [`aead`] | [Authenticated encryption] | [![crates.io](https://img.shields.io/crates/v/aead.svg)](https://crates.io/crates/aead) | [![Documentation](https://docs.rs/aead/badge.svg)](https://docs.rs/aead) | ![MSRV 1.56][msrv-1.56] | +| [`async‑signature`] | [Digital signature] | [![crates.io](https://img.shields.io/crates/v/async-signature.svg)](https://crates.io/crates/async-signature) | [![Documentation](https://docs.rs/async-signature/badge.svg)](https://docs.rs/async-signature) | ![MSRV 1.56][msrv-1.56] | +| [`cipher`] | [Block] and [stream cipher] | [![crates.io](https://img.shields.io/crates/v/cipher.svg)](https://crates.io/crates/cipher) | [![Documentation](https://docs.rs/cipher/badge.svg)](https://docs.rs/cipher) | ![MSRV 1.56][msrv-1.56] | +| [`crypto‑common`] | Common cryptographic traits | [![crates.io](https://img.shields.io/crates/v/crypto-common.svg)](https://crates.io/crates/crypto-common) | [![Documentation](https://docs.rs/crypto-common/badge.svg)](https://docs.rs/crypto-common) | ![MSRV 1.56][msrv-1.56] | +| [`digest`] | [Cryptographic hash function] | [![crates.io](https://img.shields.io/crates/v/digest.svg)](https://crates.io/crates/digest) | [![Documentation](https://docs.rs/digest/badge.svg)](https://docs.rs/digest) | ![MSRV 1.57][msrv-1.57] | +| [`elliptic‑curve`] | [Elliptic curve cryptography] | [![crates.io](https://img.shields.io/crates/v/elliptic-curve.svg)](https://crates.io/crates/elliptic-curve) | [![Documentation](https://docs.rs/elliptic-curve/badge.svg)](https://docs.rs/elliptic-curve) | ![MSRV 1.65][msrv-1.65] | +| [`kem`] | [Key encapsulation mechanism] | [![crates.io](https://img.shields.io/crates/v/kem.svg)](https://crates.io/crates/kem) | [![Documentation](https://docs.rs/kem/badge.svg)](https://docs.rs/kem) | ![MSRV 1.60][msrv-1.60] | +| [`password-hash`] | [Password hashing] | [![crates.io](https://img.shields.io/crates/v/password-hash.svg)](https://crates.io/crates/password-hash) | [![Documentation](https://docs.rs/password-hash/badge.svg)](https://docs.rs/password-hash) | ![MSRV 1.60][msrv-1.60] | +| [`signature`] | [Digital signature] | [![crates.io](https://img.shields.io/crates/v/signature.svg)](https://crates.io/crates/signature) | [![Documentation](https://docs.rs/signature/badge.svg)](https://docs.rs/signature) | ![MSRV 1.56][msrv-1.56] | +| [`universal‑hash`] | [Universal hash function] | [![crates.io](https://img.shields.io/crates/v/universal-hash.svg)](https://crates.io/crates/universal-hash) | [![Documentation](https://docs.rs/universal-hash/badge.svg)](https://docs.rs/universal-hash) | ![MSRV 1.56][msrv-1.56] | + +### Additional Crates + +| Crate name | Description | Crates.io | Docs | MSRV | +|------------|-------------------------|:---------:|:-----:|:----:| +| [`crypto`] | Facade for trait crates | [![crates.io](https://img.shields.io/crates/v/crypto.svg)](https://crates.io/crates/crypto) | [![Documentation](https://docs.rs/crypto/badge.svg)](https://docs.rs/crypto) | ![MSRV 1.57][msrv-1.57] | + +### Minimum Supported Rust Version (MSRV) Policy + +MSRV bumps are considered breaking changes and will be performed only with minor version bump. ## License @@ -25,6 +40,43 @@ at your option. ### Contribution -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260050-traits +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[deps-image]: https://deps.rs/repo/github/RustCrypto/traits/status.svg +[deps-link]: https://deps.rs/repo/github/RustCrypto/traits +[msrv-1.56]: https://img.shields.io/badge/rustc-1.56.0+-blue.svg +[msrv-1.57]: https://img.shields.io/badge/rustc-1.57.0+-blue.svg +[msrv-1.60]: https://img.shields.io/badge/rustc-1.60.0+-blue.svg +[msrv-1.65]: https://img.shields.io/badge/rustc-1.65.0+-blue.svg + +[//]: # (crates) + +[`aead`]: ./aead +[`async‑signature`]: ./signature/async +[`cipher`]: ./cipher +[`crypto‑common`]: ./crypto-common +[`crypto`]: ./crypto +[`digest`]: ./digest +[`elliptic‑curve`]: ./elliptic-curve +[`kem`]: ./kem +[`password-hash`]: ./password-hash +[`signature`]: ./signature +[`universal‑hash`]: ./universal-hash + +[//]: # (algorithms) + +[Authenticated encryption]: https://en.wikipedia.org/wiki/Authenticated_encryption +[Block]: https://en.wikipedia.org/wiki/Block_cipher +[Message authentication code]: https://en.wikipedia.org/wiki/Message_authentication_code +[Cryptographic hash function]: https://en.wikipedia.org/wiki/Cryptographic_hash_function +[Digital signature]: https://en.wikipedia.org/wiki/Digital_signature +[Elliptic curve cryptography]: https://en.wikipedia.org/wiki/Elliptic-curve_cryptography +[Key encapsulation mechanism]: https://en.wikipedia.org/wiki/Key_encapsulation +[Password hashing]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Password_verification +[Stream cipher]: https://en.wikipedia.org/wiki/Stream_cipher +[Universal hash function]: https://en.wikipedia.org/wiki/Universal_hashing diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..15e176975 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,17 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the most recent release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report +it privately. **Do not disclose it as a public issue.** This gives us time to +work with you to fix the issue before public exposure, reducing the chance that +the exploit will be used before a patch is released. + +Please disclose it at [security advisory](https://github.com/RustCrypto/traits/security/advisories/new). + +This project is maintained by a team of volunteers on a reasonable-effort basis. +As such, please give us at least 90 days to work on a fix before public exposure. diff --git a/aead/CHANGELOG.md b/aead/CHANGELOG.md new file mode 100644 index 000000000..64c3f094b --- /dev/null +++ b/aead/CHANGELOG.md @@ -0,0 +1,141 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased +### Fixed +- minor documentation error in `AeadCore::TagSize` ([#1351]) +- fixup `hybrid-array` migration ([#1531]) + +### Changed +- Migrate to `doc_auto_cfg` ([#1370]) +- Exclude pre-1.60 crates from workspace ([#1380]) +- bump `crypto-common` to v0.2.0-pre; MSRV 1.65 ([#1384]) +- Bump `heapless` dependency to v0.8 ([#1398]) +- Bump `hybrid-array` to v0.2.0-pre.6 ([#1432]) +- Bump `crypto-common` to v0.2.0-pre.1 ([#1433]) +- Bump `crypto-common` to v0.2.0-pre.2 ([#1436]) +- Bump `hybrid-array` to v0.2.0-pre.8 ([#1438]) +- Bump `crypto-common` and `hybrid-array` ([#1469]) +- Bump `crypto-common` to v0.2.0-pre.5 ([#1496]) + +### Added +- enable `missing_debug_implementations` lint and add `Debug` impls ([#1411]) + + +[#1351]: https://github.com/RustCrypto/traits/pull/1351 +[#1370]: https://github.com/RustCrypto/traits/pull/1370 +[#1380]: https://github.com/RustCrypto/traits/pull/1380 +[#1384]: https://github.com/RustCrypto/traits/pull/1384 +[#1398]: https://github.com/RustCrypto/traits/pull/1398 +[#1411]: https://github.com/RustCrypto/traits/pull/1411 +[#1432]: https://github.com/RustCrypto/traits/pull/1432 +[#1433]: https://github.com/RustCrypto/traits/pull/1433 +[#1436]: https://github.com/RustCrypto/traits/pull/1436 +[#1438]: https://github.com/RustCrypto/traits/pull/1438 +[#1469]: https://github.com/RustCrypto/traits/pull/1469 +[#1496]: https://github.com/RustCrypto/traits/pull/1496 +[#1531]: https://github.com/RustCrypto/traits/pull/1531 + +## 0.5.2 (2023-04-02) +### Added +- `arrayvec` feature ([#1219]) + +[#1219]: https://github.com/RustCrypto/traits/pull/1219 + +## 0.5.1 (2022-08-09) +### Added +- `AeadCore::generate_nonce` ([#1073]) + +[#1073]: https://github.com/RustCrypto/traits/pull/1073 + +## 0.5.0 (2022-07-23) +### Added +- Optional support for `BytesMut` as a `Buffer` ([#956]) +- `getrandom` feature ([#1042]) + +### Changed +- Replace `NewAead` trait with `KeyInit` trait from `crypto-common` ([#1033]) +- Rust 2021 edition upgrade; MSRV 1.56+ ([#1044]) + +[#956]: https://github.com/RustCrypto/traits/pull/956 +[#1033]: https://github.com/RustCrypto/traits/pull/1033 +[#1042]: https://github.com/RustCrypto/traits/pull/1042 +[#1044]: https://github.com/RustCrypto/traits/pull/1044 + +## 0.4.3 (2021-08-29) +### Added +- `Result` type alias ([#725]) + +[#725]: https://github.com/RustCrypto/traits/pull/725 + +## 0.4.2 (2021-07-12) +### Added +- Re-export `rand_core` ([#682]) + +[#682]: https://github.com/RustCrypto/traits/pull/682 + +## 0.4.1 (2021-05-03) +### Changed +- Bump `heapless` dependency to v0.7 ([#628]) + +[#628]: https://github.com/RustCrypto/traits/pull/628 + +## 0.4.0 (2021-02-05) [YANKED] +### Added +- `stream` module ([#436], [#445], [#447]) +- `NewAead::generate_key` method gated under `rand_core` feature ([#513]) + +### Changed +- Extract `AeadCore` trait ([#508]) +- Rename `NewAead::new_var` to `::new_from_slice` ([#512]) +- Disable alloc by default ([#514]) +- Bump `heapless` dependency to v0.6 ([#522]) + +[#436]: https://github.com/RustCrypto/traits/pull/436 +[#445]: https://github.com/RustCrypto/traits/pull/445 +[#447]: https://github.com/RustCrypto/traits/pull/447 +[#508]: https://github.com/RustCrypto/traits/pull/508 +[#512]: https://github.com/RustCrypto/traits/pull/512 +[#513]: https://github.com/RustCrypto/traits/pull/513 +[#514]: https://github.com/RustCrypto/traits/pull/514 +[#522]: https://github.com/RustCrypto/traits/pull/522 + +## 0.3.2 (2020-07-01) +### Added +- `dev` module ([#194]) + +[#194]: https://github.com/RustCrypto/traits/pull/194 + +## 0.3.1 (2020-06-12) +### Added +- `NewAead::new_varkey` method ([#191]) + +[#191]: https://github.com/RustCrypto/traits/pull/191 + +## 0.3.0 (2020-06-04) +### Added +- Type aliases for `Key`, `Nonce`, and `Tag` ([#125]) +- Optional `std` feature ([#63]) + +### Changed +- `NewAead` now borrows the key ([#124]) +- Split `Aead`/`AeadMut` into `AeadInPlace`/`AeadMutInPlace` ([#120]) +- Bump `generic-array` dependency to v0.14 ([#95]) + +[#125]: https://github.com/RustCrypto/traits/pull/125 +[#124]: https://github.com/RustCrypto/traits/pull/124 +[#120]: https://github.com/RustCrypto/traits/pull/120 +[#95]: https://github.com/RustCrypto/traits/pull/95 +[#63]: https://github.com/RustCrypto/traits/pull/63 + +## 0.2.0 (2019-11-17) + +## 0.1.2 (2019-11-17) [YANKED] + +## 0.1.1 (2019-08-30) + +## 0.1.0 (2019-08-29) diff --git a/aead/Cargo.toml b/aead/Cargo.toml index beea5f4a2..9cfaa2ff9 100644 --- a/aead/Cargo.toml +++ b/aead/Cargo.toml @@ -1,14 +1,36 @@ [package] name = "aead" -version = "0.1.1" +version = "0.6.0-rc.0" +description = """ +Traits for Authenticated Encryption with Associated Data (AEAD) algorithms, +such as AES-GCM as ChaCha20Poly1305, which provide a high-level API +""" authors = ["RustCrypto Developers"] -edition = "2018" +edition = "2021" license = "MIT OR Apache-2.0" -description = "Traits for Authenticated Encryption with Associated Data (AEAD) algorithms" +readme = "README.md" documentation = "https://docs.rs/aead" repository = "https://github.com/RustCrypto/traits" -keywords = ["digest", "crypto", "encryption"] +keywords = ["crypto", "encryption"] categories = ["cryptography", "no-std"] +rust-version = "1.81" [dependencies] -generic-array = { version = "0.12", default-features = false } +crypto-common = "0.2.0-rc.0" + +# optional dependencies +arrayvec = { version = "0.7", optional = true, default-features = false } +blobby = { version = "0.3", optional = true } +bytes = { version = "1", optional = true, default-features = false } +heapless = { version = "0.8", optional = true, default-features = false } + +[features] +default = ["rand_core"] +alloc = [] +dev = ["blobby"] +getrandom = ["crypto-common/getrandom"] +rand_core = ["crypto-common/rand_core"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/aead/README.md b/aead/README.md index ee6d0e6b5..0a0dd045a 100644 --- a/aead/README.md +++ b/aead/README.md @@ -1,6 +1,65 @@ -# Authenticated Encryption with Additional Data +# RustCrypto: Authenticated Encryption with Additional Data Traits -This crate provides the rust trait equivilent of the AEAD API defined in -RFC5116. As a result, it should provide nearly drop-in support for any -compliant AEAD scheme, including AES-GCM, AES-CCM, ChaCha20-Poly1305, -AES-CBC-HMAC, etc. +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +This crate provides an abstract interface for [AEAD] ciphers, which guarantee +both confidentiality and integrity, even from a powerful attacker who is +able to execute [chosen-ciphertext attacks]. The resulting security property, +[ciphertext indistinguishability], is considered a basic requirement for +modern cryptographic implementations. + +See [RustCrypto/AEADs] for cipher implementations which use this trait. + +[Documentation][docs-link] + +## Minimum Supported Rust Version + +Rust **1.81** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/aead.svg +[crate-link]: https://crates.io/crates/aead +[docs-image]: https://docs.rs/aead/badge.svg +[docs-link]: https://docs.rs/aead/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260038-AEADs +[build-image]: https://github.com/RustCrypto/traits/workflows/aead/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/traits/actions?query=workflow%3Aaead + +[//]: # (general links) + +[AEAD]: https://en.wikipedia.org/wiki/Authenticated_encryption +[chosen-ciphertext attacks]: https://en.wikipedia.org/wiki/Chosen-ciphertext_attack +[ciphertext indistinguishability]: https://en.wikipedia.org/wiki/Ciphertext_indistinguishability +[RustCrypto/AEADs]: https://github.com/RustCrypto/AEADs diff --git a/aead/src/dev.rs b/aead/src/dev.rs new file mode 100644 index 000000000..b7edf89cb --- /dev/null +++ b/aead/src/dev.rs @@ -0,0 +1,77 @@ +//! Development-related functionality +pub use blobby; + +/// Define AEAD test +#[macro_export] +macro_rules! new_test { + ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { + #[test] + fn $name() { + use aead::{ + array::{typenum::Unsigned, Array}, + dev::blobby::Blob6Iterator, + Aead, KeyInit, Payload, + }; + + fn run_test( + key: &[u8], + nonce: &[u8], + aad: &[u8], + pt: &[u8], + ct: &[u8], + pass: bool, + ) -> Result<(), &'static str> { + let key = key.try_into().map_err(|_| "wrong key size")?; + let cipher = <$cipher>::new(key); + let nonce = nonce.try_into().map_err(|_| "wrong nonce size")?; + + if !pass { + let res = cipher.decrypt(nonce, Payload { aad: aad, msg: ct }); + if res.is_ok() { + return Err("decryption must return error"); + } + return Ok(()); + } + + let res = cipher + .encrypt(nonce, Payload { aad: aad, msg: pt }) + .map_err(|_| "encryption failure")?; + if res != ct { + return Err("encrypted data is different from target ciphertext"); + } + let res = cipher + .decrypt(nonce, Payload { aad: aad, msg: ct }) + .map_err(|_| "decryption failure")?; + if res != pt { + return Err("decrypted data is different from target plaintext"); + } + Ok(()) + } + + let data = include_bytes!(concat!("data/", $test_name, ".blb")); + for (i, row) in Blob6Iterator::new(data).unwrap().enumerate() { + let [key, nonce, aad, pt, ct, status] = row.unwrap(); + let pass = match status[0] { + 0 => false, + 1 => true, + _ => panic!("invalid value for pass flag"), + }; + if let Err(reason) = run_test(key, nonce, aad, pt, ct, pass) { + panic!( + "\n\ + Failed test №{}\n\ + reason: \t{:?}\n\ + key:\t{:?}\n\ + nonce:\t{:?}\n\ + aad:\t{:?}\n\ + plaintext:\t{:?}\n\ + ciphertext:\t{:?}\n\ + pass:\t{}\n\ + ", + i, reason, key, nonce, aad, pt, ct, pass, + ); + } + } + } + }; +} diff --git a/aead/src/lib.rs b/aead/src/lib.rs index 4bc0fe43b..2a3560c21 100644 --- a/aead/src/lib.rs +++ b/aead/src/lib.rs @@ -1,51 +1,163 @@ -//! Authenticated Encryption with Associated Data (AEAD) traits -//! -//! This crate provides an abstract interface for AEAD ciphers, which guarantee -//! both confidentiality and integrity, even from a powerful attacker who is -//! able to execute [chosen-ciphertext attacks]. The resulting security property, -//! [ciphertext indistinguishability], is considered a basic requirement for -//! modern cryptographic implementations. -//! -//! See [RustCrypto/AEADs] for cipher implementations which use this trait. -//! -//! [chosen-ciphertext attacks]: https://en.wikipedia.org/wiki/Chosen-ciphertext_attack -//! [ciphertext indistinguishability]: https://en.wikipedia.org/wiki/Ciphertext_indistinguishability -//! [RustCrypto/AEADs]: https://github.com/RustCrypto/AEADs - #![no_std] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" +)] +#![forbid(unsafe_code)] +#![warn( + clippy::unwrap_used, + missing_docs, + rust_2018_idioms, + missing_debug_implementations +)] +#[cfg(feature = "alloc")] extern crate alloc; -pub use generic_array; +#[cfg(feature = "dev")] +pub mod dev; + +pub mod stream; + +pub use crypto_common::{ + array::{self, typenum::consts}, + Key, KeyInit, KeySizeUser, +}; + +#[cfg(feature = "arrayvec")] +pub use arrayvec; +#[cfg(feature = "bytes")] +pub use bytes; +#[cfg(feature = "getrandom")] +pub use crypto_common::rand_core::OsRng; +#[cfg(feature = "heapless")] +pub use heapless; + +#[cfg(feature = "rand_core")] +pub use crypto_common::rand_core; +use core::fmt; +use crypto_common::array::{typenum::Unsigned, Array, ArraySize}; + +#[cfg(feature = "alloc")] use alloc::vec::Vec; -use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; +#[cfg(feature = "bytes")] +use bytes::BytesMut; +#[cfg(feature = "getrandom")] +use crypto_common::getrandom; +#[cfg(feature = "rand_core")] +use rand_core::CryptoRngCore; +/// Error type. +/// +/// This type is deliberately opaque as to avoid potential side-channel +/// leakage (e.g. padding oracle). #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Error; -/// Instantiate either a stateless [`Aead`] or stateful [`AeadMut`] algorithm. -pub trait NewAead { - /// The size of the key array required by this algorithm. - type KeySize: ArrayLength; +/// Result type alias with [`Error`]. +pub type Result = core::result::Result; - /// Construct a new stateful instance for the given key. - fn new(key: GenericArray) -> Self; +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("aead::Error") + } } -/// Authenticated Encryption with Associated Data (AEAD) algorithm. +impl core::error::Error for Error {} + +/// Nonce: single-use value for ensuring ciphertexts are unique +pub type Nonce = Array::NonceSize>; + +/// Tag: authentication code which ensures ciphertexts are authentic +pub type Tag = Array::TagSize>; + +/// Authenticated Encryption with Associated Data (AEAD) algorithm core trait. /// -/// This trait is intended for use with stateless AEAD algorithms. The -/// `AeadMut` trait provides a stateful interface. -pub trait Aead { +/// Defines nonce, tag, and overhead sizes that are consumed by various other +/// `Aead*` traits. +pub trait AeadCore { /// The length of a nonce. - type NonceSize: ArrayLength; - /// The maximum length of the nonce. - type TagSize: ArrayLength; + type NonceSize: ArraySize; + + /// The maximum length of the tag. + type TagSize: ArraySize; + /// The upper bound amount of additional space required to support a /// ciphertext vs. a plaintext. - type CiphertextOverhead: ArrayLength + Unsigned; + type CiphertextOverhead: ArraySize + Unsigned; + /// Generate a random nonce for this AEAD algorithm. + /// + /// AEAD algorithms accept a parameter to encryption/decryption called + /// a "nonce" which must be unique every time encryption is performed and + /// never repeated for the same key. The nonce is often prepended to the + /// ciphertext. The nonce used to produce a given ciphertext must be passed + /// to the decryption function in order for it to decrypt correctly. + /// + /// Nonces don't necessarily have to be random, but it is one strategy + /// which is implemented by this function. + /// + /// # ⚠️Security Warning + /// + /// AEAD algorithms often fail catastrophically if nonces are ever repeated + /// (with SIV modes being an exception). + /// + /// Using random nonces runs the risk of repeating them unless the nonce + /// size is particularly large (e.g. 192-bit extended nonces used by the + /// `XChaCha20Poly1305` and `XSalsa20Poly1305` constructions. + /// + /// [NIST SP 800-38D] recommends the following: + /// + /// > The total number of invocations of the authenticated encryption + /// > function shall not exceed 2^32, including all IV lengths and all + /// > instances of the authenticated encryption function with the given key. + /// + /// Following this guideline, only 4,294,967,296 messages with random + /// nonces can be encrypted under a given key. While this bound is high, + /// it's possible to encounter in practice, and systems which might + /// reach it should consider alternatives to purely random nonces, like + /// a counter or a combination of a random nonce + counter. + /// + /// See the [`stream`] module for a ready-made implementation of the latter. + /// + /// [NIST SP 800-38D]: https://csrc.nist.gov/publications/detail/sp/800-38d/final + #[cfg(feature = "getrandom")] + fn generate_nonce() -> core::result::Result, getrandom::Error> + where + Nonce: Default, + { + let mut nonce = Nonce::::default(); + getrandom::getrandom(&mut nonce)?; + Ok(nonce) + } + + /// Generate a random nonce for this AEAD algorithm using the specified + /// [`CryptoRngCore`]. + /// + /// See [`AeadCore::generate_nonce`] documentation for requirements for + /// random nonces. + #[cfg(feature = "rand_core")] + fn generate_nonce_with_rng( + rng: &mut impl CryptoRngCore, + ) -> core::result::Result, rand_core::Error> + where + Nonce: Default, + { + let mut nonce = Nonce::::default(); + rng.try_fill_bytes(&mut nonce)?; + Ok(nonce) + } +} + +/// Authenticated Encryption with Associated Data (AEAD) algorithm. +/// +/// This trait is intended for use with stateless AEAD algorithms. The +/// [`AeadMut`] trait provides a stateful interface. +#[cfg(feature = "alloc")] +pub trait Aead: AeadCore { /// Encrypt the given plaintext payload, and return the resulting /// ciphertext as a vector of bytes. /// @@ -64,11 +176,16 @@ pub trait Aead { /// let plaintext = b"Top secret message, handle with care"; /// let ciphertext = cipher.encrypt(nonce, plaintext); /// ``` + /// + /// The default implementation assumes a postfix tag (ala AES-GCM, + /// AES-GCM-SIV, ChaCha20Poly1305). [`Aead`] implementations which do not + /// use a postfix tag will need to override this to correctly assemble the + /// ciphertext message. fn encrypt<'msg, 'aad>( &self, - nonce: &GenericArray, + nonce: &Nonce, plaintext: impl Into>, - ) -> Result, Error>; + ) -> Result>; /// Decrypt the given ciphertext slice, and return the resulting plaintext /// as a vector of bytes. @@ -82,23 +199,21 @@ pub trait Aead { /// let ciphertext = b"..."; /// let plaintext = cipher.decrypt(nonce, ciphertext)?; /// ``` + /// + /// The default implementation assumes a postfix tag (ala AES-GCM, + /// AES-GCM-SIV, ChaCha20Poly1305). [`Aead`] implementations which do not + /// use a postfix tag will need to override this to correctly parse the + /// ciphertext message. fn decrypt<'msg, 'aad>( &self, - nonce: &GenericArray, + nonce: &Nonce, ciphertext: impl Into>, - ) -> Result, Error>; + ) -> Result>; } /// Stateful Authenticated Encryption with Associated Data algorithm. -pub trait AeadMut { - /// The length of a nonce. - type NonceSize: ArrayLength; - /// The maximum length of the nonce. - type TagSize: ArrayLength; - /// The upper bound amount of additional space required to support a - /// ciphertext vs. a plaintext. - type CiphertextOverhead: ArrayLength + Unsigned; - +#[cfg(feature = "alloc")] +pub trait AeadMut: AeadCore { /// Encrypt the given plaintext slice, and return the resulting ciphertext /// as a vector of bytes. /// @@ -106,9 +221,9 @@ pub trait AeadMut { /// Associated Additional Data (AAD). fn encrypt<'msg, 'aad>( &mut self, - nonce: &GenericArray, + nonce: &Nonce, plaintext: impl Into>, - ) -> Result, Error>; + ) -> Result>; /// Decrypt the given ciphertext slice, and return the resulting plaintext /// as a vector of bytes. @@ -117,45 +232,249 @@ pub trait AeadMut { /// message payloads and Associated Additional Data (AAD). fn decrypt<'msg, 'aad>( &mut self, - nonce: &GenericArray, + nonce: &Nonce, ciphertext: impl Into>, - ) -> Result, Error>; + ) -> Result>; } -/// A blanket implementation of the Stateful AEAD interface for Stateless -/// AEAD implementations. -impl AeadMut for Algo { - type NonceSize = Algo::NonceSize; - type TagSize = Algo::TagSize; - type CiphertextOverhead = Algo::CiphertextOverhead; +/// Implement the `decrypt_in_place` method on [`AeadInPlace`] and +/// [`AeadMutInPlace]`, using a macro to gloss over the `&self` vs `&mut self`. +/// +/// Assumes a postfix authentication tag. AEAD ciphers which do not use a +/// postfix authentication tag will need to define their own implementation. +macro_rules! impl_decrypt_in_place { + ($aead:expr, $nonce:expr, $aad:expr, $buffer:expr) => {{ + let tag_pos = $buffer + .len() + .checked_sub(Self::TagSize::to_usize()) + .ok_or(Error)?; - /// Encrypt the given plaintext slice, and return the resulting ciphertext - /// as a vector of bytes. + let (msg, tag) = $buffer.as_mut().split_at_mut(tag_pos); + let tag = Tag::::try_from(&*tag).expect("tag length mismatch"); + + $aead.decrypt_in_place_detached($nonce, $aad, msg, &tag)?; + $buffer.truncate(tag_pos); + Ok(()) + }}; +} + +/// In-place stateless AEAD trait. +/// +/// This trait is both object safe and has no dependencies on `alloc` or `std`. +pub trait AeadInPlace: AeadCore { + /// Encrypt the given buffer containing a plaintext message in-place. + /// + /// The buffer must have sufficient capacity to store the ciphertext + /// message, which will always be larger than the original plaintext. + /// The exact size needed is cipher-dependent, but generally includes + /// the size of an authentication tag. + /// + /// Returns an error if the buffer has insufficient capacity to store the + /// resulting ciphertext message. + fn encrypt_in_place( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut dyn Buffer, + ) -> Result<()> { + let tag = self.encrypt_in_place_detached(nonce, associated_data, buffer.as_mut())?; + buffer.extend_from_slice(tag.as_slice())?; + Ok(()) + } + + /// Encrypt the data in-place, returning the authentication tag + fn encrypt_in_place_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result>; + + /// Decrypt the message in-place, returning an error in the event the + /// provided authentication tag does not match the given ciphertext. + /// + /// The buffer will be truncated to the length of the original plaintext + /// message upon success. + fn decrypt_in_place( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut dyn Buffer, + ) -> Result<()> { + impl_decrypt_in_place!(self, nonce, associated_data, buffer) + } + + /// Decrypt the message in-place, returning an error in the event the provided + /// authentication tag does not match the given ciphertext (i.e. ciphertext + /// is modified/unauthentic) + fn decrypt_in_place_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> Result<()>; +} + +/// In-place stateful AEAD trait. +/// +/// This trait is both object safe and has no dependencies on `alloc` or `std`. +pub trait AeadMutInPlace: AeadCore { + /// Encrypt the given buffer containing a plaintext message in-place. + /// + /// The buffer must have sufficient capacity to store the ciphertext + /// message, which will always be larger than the original plaintext. + /// The exact size needed is cipher-dependent, but generally includes + /// the size of an authentication tag. + /// + /// Returns an error if the buffer has insufficient capacity to store the + /// resulting ciphertext message. + fn encrypt_in_place( + &mut self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut impl Buffer, + ) -> Result<()> { + let tag = self.encrypt_in_place_detached(nonce, associated_data, buffer.as_mut())?; + buffer.extend_from_slice(tag.as_slice())?; + Ok(()) + } + + /// Encrypt the data in-place, returning the authentication tag + fn encrypt_in_place_detached( + &mut self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result>; + + /// Decrypt the message in-place, returning an error in the event the + /// provided authentication tag does not match the given ciphertext. + /// + /// The buffer will be truncated to the length of the original plaintext + /// message upon success. + fn decrypt_in_place( + &mut self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut impl Buffer, + ) -> Result<()> { + impl_decrypt_in_place!(self, nonce, associated_data, buffer) + } + + /// Decrypt the data in-place, returning an error in the event the provided + /// authentication tag does not match the given ciphertext (i.e. ciphertext + /// is modified/unauthentic) + fn decrypt_in_place_detached( + &mut self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> Result<()>; +} + +#[cfg(feature = "alloc")] +impl Aead for Alg { + fn encrypt<'msg, 'aad>( + &self, + nonce: &Nonce, + plaintext: impl Into>, + ) -> Result> { + let payload = plaintext.into(); + let mut buffer = Vec::with_capacity(payload.msg.len() + Self::TagSize::to_usize()); + buffer.extend_from_slice(payload.msg); + self.encrypt_in_place(nonce, payload.aad, &mut buffer)?; + Ok(buffer) + } + + fn decrypt<'msg, 'aad>( + &self, + nonce: &Nonce, + ciphertext: impl Into>, + ) -> Result> { + let payload = ciphertext.into(); + let mut buffer = Vec::from(payload.msg); + self.decrypt_in_place(nonce, payload.aad, &mut buffer)?; + Ok(buffer) + } +} + +#[cfg(feature = "alloc")] +impl AeadMut for Alg { fn encrypt<'msg, 'aad>( &mut self, - nonce: &GenericArray, + nonce: &Nonce, plaintext: impl Into>, - ) -> Result, Error> { - ::encrypt(self, nonce, plaintext) + ) -> Result> { + let payload = plaintext.into(); + let mut buffer = Vec::with_capacity(payload.msg.len() + Self::TagSize::to_usize()); + buffer.extend_from_slice(payload.msg); + self.encrypt_in_place(nonce, payload.aad, &mut buffer)?; + Ok(buffer) } - /// Decrypt the given ciphertext slice, and return the resulting plaintext - /// as a vector of bytes. fn decrypt<'msg, 'aad>( &mut self, - nonce: &GenericArray, + nonce: &Nonce, ciphertext: impl Into>, - ) -> Result, Error> { - ::decrypt(self, nonce, ciphertext) + ) -> Result> { + let payload = ciphertext.into(); + let mut buffer = Vec::from(payload.msg); + self.decrypt_in_place(nonce, payload.aad, &mut buffer)?; + Ok(buffer) } } -/// AEAD payloads are a combination of a message (plaintext or ciphertext) -/// and "additional associated data" (AAD) to be authenticated (in cleartext) +impl AeadMutInPlace for Alg { + fn encrypt_in_place( + &mut self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut impl Buffer, + ) -> Result<()> { + ::encrypt_in_place(self, nonce, associated_data, buffer) + } + + fn encrypt_in_place_detached( + &mut self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> Result> { + ::encrypt_in_place_detached(self, nonce, associated_data, buffer) + } + + fn decrypt_in_place( + &mut self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut impl Buffer, + ) -> Result<()> { + ::decrypt_in_place(self, nonce, associated_data, buffer) + } + + fn decrypt_in_place_detached( + &mut self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> Result<()> { + ::decrypt_in_place_detached(self, nonce, associated_data, buffer, tag) + } +} + +/// AEAD payloads (message + AAD). +/// +/// Combination of a message (plaintext or ciphertext) and +/// "additional associated data" (AAD) to be authenticated (in cleartext) /// along with the message. /// /// If you don't care about AAD, you can pass a `&[u8]` as the payload to /// `encrypt`/`decrypt` and it will automatically be coerced to this type. +#[cfg(feature = "alloc")] +#[derive(Debug)] pub struct Payload<'msg, 'aad> { /// Message to be encrypted/decrypted pub msg: &'msg [u8], @@ -167,8 +486,100 @@ pub struct Payload<'msg, 'aad> { pub aad: &'aad [u8], } -impl<'msg, 'aad> From<&'msg [u8]> for Payload<'msg, 'aad> { +#[cfg(feature = "alloc")] +impl<'msg> From<&'msg [u8]> for Payload<'msg, '_> { fn from(msg: &'msg [u8]) -> Self { Self { msg, aad: b"" } } } + +/// In-place encryption/decryption byte buffers. +/// +/// This trait defines the set of methods needed to support in-place operations +/// on a `Vec`-like data type. +pub trait Buffer: AsRef<[u8]> + AsMut<[u8]> { + /// Get the length of the buffer + fn len(&self) -> usize { + self.as_ref().len() + } + + /// Is the buffer empty? + fn is_empty(&self) -> bool { + self.as_ref().is_empty() + } + + /// Extend this buffer from the given slice + fn extend_from_slice(&mut self, other: &[u8]) -> Result<()>; + + /// Truncate this buffer to the given size + fn truncate(&mut self, len: usize); +} + +#[cfg(feature = "alloc")] +impl Buffer for Vec { + fn extend_from_slice(&mut self, other: &[u8]) -> Result<()> { + Vec::extend_from_slice(self, other); + Ok(()) + } + + fn truncate(&mut self, len: usize) { + Vec::truncate(self, len); + } +} + +#[cfg(feature = "bytes")] +impl Buffer for BytesMut { + fn len(&self) -> usize { + BytesMut::len(self) + } + + fn is_empty(&self) -> bool { + BytesMut::is_empty(self) + } + + fn extend_from_slice(&mut self, other: &[u8]) -> Result<()> { + BytesMut::extend_from_slice(self, other); + Ok(()) + } + + fn truncate(&mut self, len: usize) { + BytesMut::truncate(self, len); + } +} + +#[cfg(feature = "arrayvec")] +impl Buffer for arrayvec::ArrayVec { + fn extend_from_slice(&mut self, other: &[u8]) -> Result<()> { + arrayvec::ArrayVec::try_extend_from_slice(self, other).map_err(|_| Error) + } + + fn truncate(&mut self, len: usize) { + arrayvec::ArrayVec::truncate(self, len); + } +} + +#[cfg(feature = "heapless")] +impl Buffer for heapless::Vec { + fn extend_from_slice(&mut self, other: &[u8]) -> Result<()> { + heapless::Vec::extend_from_slice(self, other).map_err(|_| Error) + } + + fn truncate(&mut self, len: usize) { + heapless::Vec::truncate(self, len); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Ensure that `AeadInPlace` is object-safe + #[allow(dead_code)] + type DynAeadInPlace = + dyn AeadInPlace; + + /// Ensure that `AeadMutInPlace` is object-safe + #[allow(dead_code)] + type DynAeadMutInPlace = + dyn AeadMutInPlace; +} diff --git a/aead/src/stream.rs b/aead/src/stream.rs new file mode 100644 index 000000000..5ee90c5f8 --- /dev/null +++ b/aead/src/stream.rs @@ -0,0 +1,311 @@ +//! Streaming AEAD support. +//! +//! See the [`aead-stream`] crate for a generic implementation of the STREAM construction. +//! +//! [`aead-stream`]: https://docs.rs/aead-stream + +#![allow(clippy::upper_case_acronyms)] + +use crate::{AeadCore, AeadInPlace, Buffer, Error, Key, KeyInit, Result}; +use core::ops::{AddAssign, Sub}; +use crypto_common::array::{Array, ArraySize}; + +#[cfg(feature = "alloc")] +use {crate::Payload, alloc::vec::Vec, crypto_common::array::typenum::Unsigned}; + +/// Nonce as used by a given AEAD construction and STREAM primitive. +pub type Nonce = Array>; + +/// Size of a nonce as used by a STREAM construction, sans the overhead of +/// the STREAM protocol itself. +pub type NonceSize = + <::NonceSize as Sub<>::NonceOverhead>>::Output; + +/// Create a new STREAM from the provided AEAD. +pub trait NewStream: StreamPrimitive +where + A: AeadInPlace, + A::NonceSize: Sub, + NonceSize: ArraySize, +{ + /// Create a new STREAM with the given key and nonce. + fn new(key: &Key, nonce: &Nonce) -> Self + where + A: KeyInit, + Self: Sized, + { + Self::from_aead(A::new(key), nonce) + } + + /// Create a new STREAM from the given AEAD cipher. + fn from_aead(aead: A, nonce: &Nonce) -> Self; +} + +/// Low-level STREAM implementation. +/// +/// This trait provides a particular "flavor" of STREAM, as there are +/// different ways the specifics of the construction can be implemented. +/// +/// Deliberately immutable and stateless to permit parallel operation. +pub trait StreamPrimitive +where + A: AeadInPlace, + A::NonceSize: Sub, + NonceSize: ArraySize, +{ + /// Number of bytes this STREAM primitive requires from the nonce. + type NonceOverhead: ArraySize; + + /// Type used as the STREAM counter. + type Counter: AddAssign + Copy + Default + Eq; + + /// Value to use when incrementing the STREAM counter (i.e. one) + const COUNTER_INCR: Self::Counter; + + /// Maximum value of the STREAM counter. + const COUNTER_MAX: Self::Counter; + + /// Encrypt an AEAD message in-place at the given position in the STREAM. + fn encrypt_in_place( + &self, + position: Self::Counter, + last_block: bool, + associated_data: &[u8], + buffer: &mut dyn Buffer, + ) -> Result<()>; + + /// Decrypt an AEAD message in-place at the given position in the STREAM. + fn decrypt_in_place( + &self, + position: Self::Counter, + last_block: bool, + associated_data: &[u8], + buffer: &mut dyn Buffer, + ) -> Result<()>; + + /// Encrypt the given plaintext payload, and return the resulting + /// ciphertext as a vector of bytes. + #[cfg(feature = "alloc")] + fn encrypt<'msg, 'aad>( + &self, + position: Self::Counter, + last_block: bool, + plaintext: impl Into>, + ) -> Result> { + let payload = plaintext.into(); + let mut buffer = Vec::with_capacity(payload.msg.len() + A::TagSize::to_usize()); + buffer.extend_from_slice(payload.msg); + self.encrypt_in_place(position, last_block, payload.aad, &mut buffer)?; + Ok(buffer) + } + + /// Decrypt the given ciphertext slice, and return the resulting plaintext + /// as a vector of bytes. + #[cfg(feature = "alloc")] + fn decrypt<'msg, 'aad>( + &self, + position: Self::Counter, + last_block: bool, + ciphertext: impl Into>, + ) -> Result> { + let payload = ciphertext.into(); + let mut buffer = Vec::from(payload.msg); + self.decrypt_in_place(position, last_block, payload.aad, &mut buffer)?; + Ok(buffer) + } + + /// Obtain [`Encryptor`] for this [`StreamPrimitive`]. + fn encryptor(self) -> Encryptor + where + Self: Sized, + { + Encryptor::from_stream_primitive(self) + } + + /// Obtain [`Decryptor`] for this [`StreamPrimitive`]. + fn decryptor(self) -> Decryptor + where + Self: Sized, + { + Decryptor::from_stream_primitive(self) + } +} + +/// Implement a stateful STREAM object (i.e. encryptor or decryptor) +macro_rules! impl_stream_object { + ( + $name:ident, + $next_method:tt, + $next_in_place_method:tt, + $last_method:tt, + $last_in_place_method:tt, + $op:tt, + $in_place_op:tt, + $op_desc:expr, + $obj_desc:expr + ) => { + #[doc = "Stateful STREAM object which can"] + #[doc = $op_desc] + #[doc = "AEAD messages one-at-a-time."] + #[doc = ""] + #[doc = "This corresponds to the "] + #[doc = $obj_desc] + #[doc = "object as defined in the paper"] + #[doc = "[Online Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance][1]."] + #[doc = ""] + #[doc = "[1]: https://eprint.iacr.org/2015/189.pdf"] + #[derive(Debug)] + pub struct $name + where + A: AeadInPlace, + S: StreamPrimitive, + A::NonceSize: Sub<>::NonceOverhead>, + NonceSize: ArraySize, + { + /// Underlying STREAM primitive. + stream: S, + + /// Current position in the STREAM. + position: S::Counter, + } + + impl $name + where + A: AeadInPlace, + S: StreamPrimitive, + A::NonceSize: Sub<>::NonceOverhead>, + NonceSize: ArraySize, + { + #[doc = "Create a"] + #[doc = $obj_desc] + #[doc = "object from the given AEAD key and nonce."] + pub fn new(key: &Key, nonce: &Nonce) -> Self + where + A: KeyInit, + S: NewStream, + { + Self::from_stream_primitive(S::new(key, nonce)) + } + + #[doc = "Create a"] + #[doc = $obj_desc] + #[doc = "object from the given AEAD primitive."] + pub fn from_aead(aead: A, nonce: &Nonce) -> Self + where + A: KeyInit, + S: NewStream, + { + Self::from_stream_primitive(S::from_aead(aead, nonce)) + } + + #[doc = "Create a"] + #[doc = $obj_desc] + #[doc = "object from the given STREAM primitive."] + pub fn from_stream_primitive(stream: S) -> Self { + Self { + stream, + position: Default::default(), + } + } + + #[doc = "Use the underlying AEAD to"] + #[doc = $op_desc] + #[doc = "the next AEAD message in this STREAM, returning the"] + #[doc = "result as a [`Vec`]."] + #[cfg(feature = "alloc")] + pub fn $next_method<'msg, 'aad>( + &mut self, + payload: impl Into>, + ) -> Result> { + if self.position == S::COUNTER_MAX { + // Counter overflow. Note that the maximum counter value is + // deliberately disallowed, as it would preclude being able + // to encrypt a last block (i.e. with `$last_in_place_method`) + return Err(Error); + } + + let result = self.stream.$op(self.position, false, payload)?; + + // Note: overflow checked above + self.position += S::COUNTER_INCR; + Ok(result) + } + + #[doc = "Use the underlying AEAD to"] + #[doc = $op_desc] + #[doc = "the next AEAD message in this STREAM in-place."] + pub fn $next_in_place_method( + &mut self, + associated_data: &[u8], + buffer: &mut dyn Buffer, + ) -> Result<()> { + if self.position == S::COUNTER_MAX { + // Counter overflow. Note that the maximum counter value is + // deliberately disallowed, as it would preclude being able + // to encrypt a last block (i.e. with `$last_in_place_method`) + return Err(Error); + } + + self.stream + .$in_place_op(self.position, false, associated_data, buffer)?; + + // Note: overflow checked above + self.position += S::COUNTER_INCR; + Ok(()) + } + + #[doc = "Use the underlying AEAD to"] + #[doc = $op_desc] + #[doc = "the last AEAD message in this STREAM,"] + #[doc = "consuming the "] + #[doc = $obj_desc] + #[doc = "object in order to prevent further use."] + #[cfg(feature = "alloc")] + pub fn $last_method<'msg, 'aad>( + self, + payload: impl Into>, + ) -> Result> { + self.stream.$op(self.position, true, payload) + } + + #[doc = "Use the underlying AEAD to"] + #[doc = $op_desc] + #[doc = "the last AEAD message in this STREAM in-place,"] + #[doc = "consuming the "] + #[doc = $obj_desc] + #[doc = "object in order to prevent further use."] + pub fn $last_in_place_method( + self, + associated_data: &[u8], + buffer: &mut dyn Buffer, + ) -> Result<()> { + self.stream + .$in_place_op(self.position, true, associated_data, buffer) + } + } + }; +} + +impl_stream_object!( + Encryptor, + encrypt_next, + encrypt_next_in_place, + encrypt_last, + encrypt_last_in_place, + encrypt, + encrypt_in_place, + "encrypt", + "ℰ STREAM encryptor" +); + +impl_stream_object!( + Decryptor, + decrypt_next, + decrypt_next_in_place, + decrypt_last, + decrypt_last_in_place, + decrypt, + decrypt_in_place, + "decrypt", + "𝒟 STREAM decryptor" +); diff --git a/async-signature/CHANGELOG.md b/async-signature/CHANGELOG.md new file mode 100644 index 000000000..70bd82f9e --- /dev/null +++ b/async-signature/CHANGELOG.md @@ -0,0 +1,73 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.5.1 (2024-04-02) +### Added +- `no_std` support ([#1544]) + +[#1544]: https://github.com/RustCrypto/traits/pull/1544 + +## 0.5.0 (2024-01-02) +### Added +- Debug impls ([#1407]) + +### Changed +- Move to AFIT; MSRV 1.75 ([#1428]) + +### Removed +- `'static` bounds ([#1430]) + +[#1407]: https://github.com/RustCrypto/traits/pull/1407 +[#1428]: https://github.com/RustCrypto/traits/pull/1428 +[#1430]: https://github.com/RustCrypto/traits/pull/1430 + +## 0.4.0 (2023-11-12) +### Changed +- MSRV 1.60 ([#1387]) + +### Removed +- Mandatory `Send` + `Sync` bounds ([#1375]) + +[#1375]: https://github.com/RustCrypto/traits/pull/1375 +[#1387]: https://github.com/RustCrypto/traits/pull/1387 + +## 0.3.0 (2023-01-15) +### Changed +- Bump `signature` to v2 ([#1141], [#1211]) + +### Removed +- `AsyncKeypair` is no longer needed due to `signature` v2 bounds changes ([#1141]) + +[#1141]: https://github.com/RustCrypto/traits/pull/1141 +[#1211]: https://github.com/RustCrypto/traits/pull/1211 + +## 0.2.1 (2022-09-15) +### Changed +- Relax `AsyncKeypair` bounds ([#1107]) +- Deprecate `AsyncKeypair` ([#1112]) + +[#1107]: https://github.com/RustCrypto/traits/pull/1107 +[#1112]: https://github.com/RustCrypto/traits/pull/1112 + +## 0.2.0 (2022-08-14) [YANKED] +### Added +- `AsyncKeypair` trait ([#1085]) + +### Changed +- Bump minimum `signature` requirement to v1.6 ([#1084]) + +[#1084]: https://github.com/RustCrypto/traits/pull/1084 +[#1085]: https://github.com/RustCrypto/traits/pull/1085 + +## 0.1.0 (2022-01-04) +### Changed +- Bump `signature` crate dependency to v1.5 ([#850], [#867]) + +[#850]: https://github.com/RustCrypto/traits/pull/850 +[#867]: https://github.com/RustCrypto/traits/pull/867 + +## 0.0.1 (2020-10-06) +- Initial release diff --git a/async-signature/Cargo.toml b/async-signature/Cargo.toml new file mode 100644 index 000000000..0fe015a43 --- /dev/null +++ b/async-signature/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "async-signature" +description = "Traits for cryptographic signature algorithms (e.g. ECDSA, Ed25519)" +version = "0.6.0-pre.4" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +documentation = "https://docs.rs/async-signature" +homepage = "https://github.com/RustCrypto/traits/tree/master/async-signature" +repository = "https://github.com/RustCrypto/traits" +readme = "README.md" +keywords = ["crypto", "ecdsa", "ed25519", "signature", "signing"] +categories = ["cryptography", "no-std"] +edition = "2021" +rust-version = "1.81" + +[dependencies] +signature = "=2.3.0-pre.4" + +[features] +digest = ["signature/digest"] +std = ["signature/std"] +rand_core = ["signature/rand_core"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/block-cipher-trait/LICENSE-APACHE b/async-signature/LICENSE-APACHE similarity index 100% rename from block-cipher-trait/LICENSE-APACHE rename to async-signature/LICENSE-APACHE diff --git a/stream-cipher/LICENSE-MIT b/async-signature/LICENSE-MIT similarity index 95% rename from stream-cipher/LICENSE-MIT rename to async-signature/LICENSE-MIT index f5b157a6c..d4ce06527 100644 --- a/stream-cipher/LICENSE-MIT +++ b/async-signature/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2018 Artyom Pavlov +Copyright (c) 2020-2022 RustCrypto Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/async-signature/README.md b/async-signature/README.md new file mode 100644 index 000000000..838661376 --- /dev/null +++ b/async-signature/README.md @@ -0,0 +1,43 @@ +# RustCrypto: `async-signature` + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +## Minimum Supported Rust Version + +Rust **1.81** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## License + +Licensed under either of + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/async-signature.svg +[crate-link]: https://crates.io/crates/async-signature +[docs-image]: https://docs.rs/async-signature/badge.svg +[docs-link]: https://docs.rs/async-signature/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260048-signatures +[build-image]: https://github.com/RustCrypto/traits/workflows/async-signature/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/traits/actions?query=workflow:async-signature diff --git a/async-signature/src/lib.rs b/async-signature/src/lib.rs new file mode 100644 index 000000000..20ebd06d6 --- /dev/null +++ b/async-signature/src/lib.rs @@ -0,0 +1,96 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![forbid(unsafe_code)] +#![warn( + missing_docs, + rust_2018_idioms, + unused_qualifications, + missing_debug_implementations +)] + +pub use signature::{self, Error}; + +#[cfg(feature = "digest")] +pub use signature::digest::{self, Digest}; + +#[cfg(feature = "rand_core")] +use signature::rand_core::CryptoRngCore; + +/// Asynchronously sign the provided message bytestring using `Self` +/// (e.g. client for a Cloud KMS or HSM), returning a digital signature. +/// +/// This trait is an async equivalent of the [`signature::Signer`] trait. +#[allow(async_fn_in_trait)] +pub trait AsyncSigner { + /// Attempt to sign the given message, returning a digital signature on + /// success, or an error if something went wrong. + /// + /// The main intended use case for signing errors is when communicating + /// with external signers, e.g. cloud KMS, HSMs, or other hardware tokens. + async fn sign_async(&self, msg: &[u8]) -> Result; +} + +impl AsyncSigner for T +where + T: signature::Signer, +{ + async fn sign_async(&self, msg: &[u8]) -> Result { + self.try_sign(msg) + } +} + +/// Asynchronously sign the given prehashed message [`Digest`] using `Self`. +/// +/// This trait is an async equivalent of the [`signature::DigestSigner`] trait. +#[cfg(feature = "digest")] +#[allow(async_fn_in_trait)] +pub trait AsyncDigestSigner +where + D: Digest, +{ + /// Attempt to sign the given prehashed message [`Digest`], returning a + /// digital signature on success, or an error if something went wrong. + async fn sign_digest_async(&self, digest: D) -> Result; +} + +/// Sign the given message using the provided external randomness source. +#[cfg(feature = "rand_core")] +#[allow(async_fn_in_trait)] +pub trait AsyncRandomizedSigner { + /// Sign the given message and return a digital signature + async fn sign_with_rng_async(&self, rng: &mut impl CryptoRngCore, msg: &[u8]) -> S { + self.try_sign_with_rng_async(rng, msg) + .await + .expect("signature operation failed") + } + + /// Attempt to sign the given message, returning a digital signature on + /// success, or an error if something went wrong. + /// + /// The main intended use case for signing errors is when communicating + /// with external signers, e.g. cloud KMS, HSMs, or other hardware tokens. + async fn try_sign_with_rng_async( + &self, + rng: &mut impl CryptoRngCore, + msg: &[u8], + ) -> Result; +} + +#[cfg(feature = "rand_core")] +impl AsyncRandomizedSigner for T +where + T: signature::RandomizedSigner, +{ + async fn try_sign_with_rng_async( + &self, + rng: &mut impl CryptoRngCore, + msg: &[u8], + ) -> Result { + self.try_sign_with_rng(rng, msg) + } +} diff --git a/async-signature/tests/mock_impl.rs b/async-signature/tests/mock_impl.rs new file mode 100644 index 000000000..20286a22a --- /dev/null +++ b/async-signature/tests/mock_impl.rs @@ -0,0 +1,38 @@ +#![allow(dead_code)] +//! Check compilation of the various traits exposed by async_signature. +//! +//! This is intended to make sure we can implement those traits without conflict from a blanket +//! implementation. + +use async_signature::{AsyncSigner, Error}; + +struct Signature; + +struct MockSigner; + +impl AsyncSigner for MockSigner { + async fn sign_async(&self, _msg: &[u8]) -> Result { + unimplemented!("just meant to check compilation") + } +} + +#[cfg(feature = "digest")] +impl async_signature::AsyncDigestSigner for MockSigner +where + D: async_signature::Digest, +{ + async fn sign_digest_async(&self, _digest: D) -> Result { + unimplemented!("just meant to check compilation") + } +} + +#[cfg(feature = "rand_core")] +impl async_signature::AsyncRandomizedSigner for MockSigner { + async fn try_sign_with_rng_async( + &self, + _rng: &mut impl async_signature::signature::rand_core::CryptoRngCore, + _msg: &[u8], + ) -> Result { + unimplemented!("just meant to check compilation") + } +} diff --git a/block-cipher-trait/Cargo.toml b/block-cipher-trait/Cargo.toml deleted file mode 100644 index 267798e1d..000000000 --- a/block-cipher-trait/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "block-cipher-trait" -version = "0.6.2" -authors = ["RustCrypto Developers"] -license = "MIT OR Apache-2.0" -description = "Traits for description of block ciphers" -documentation = "https://docs.rs/block-cipher-trait" -repository = "https://github.com/RustCrypto/traits" -keywords = ["crypto", "block-cipher", "trait"] -categories = ["cryptography", "no-std"] - -[dependencies] -generic-array = "0.12" -blobby = { version = "0.1", optional = true } - -[features] -std = [] -dev = ["blobby"] - -[badges] -travis-ci = { repository = "RustCrypto/traits" } - -[package.metadata.docs.rs] -features = [ "std" ] diff --git a/block-cipher-trait/src/dev.rs b/block-cipher-trait/src/dev.rs deleted file mode 100644 index 938df6869..000000000 --- a/block-cipher-trait/src/dev.rs +++ /dev/null @@ -1,137 +0,0 @@ -#[macro_export] -macro_rules! new_test { - ($name:ident, $test_name:expr, $cipher:ty) => { - #[test] - fn $name() { - use block_cipher_trait::blobby::Blob3Iterator; - use block_cipher_trait::generic_array::typenum::Unsigned; - use block_cipher_trait::generic_array::GenericArray; - use block_cipher_trait::BlockCipher; - - fn run_test(key: &[u8], pt: &[u8], ct: &[u8]) -> bool { - let state = <$cipher as BlockCipher>::new_varkey(key).unwrap(); - - let mut block = GenericArray::clone_from_slice(pt); - state.encrypt_block(&mut block); - if ct != block.as_slice() { - return false; - } - - state.decrypt_block(&mut block); - if pt != block.as_slice() { - return false; - } - - true - } - - fn run_par_test(key: &[u8], pt: &[u8]) -> bool { - type ParBlocks = <$cipher as BlockCipher>::ParBlocks; - type BlockSize = <$cipher as BlockCipher>::BlockSize; - type Block = GenericArray; - type ParBlock = GenericArray; - - let state = <$cipher as BlockCipher>::new_varkey(key).unwrap(); - - let block = Block::clone_from_slice(pt); - let mut blocks1 = ParBlock::default(); - for (i, b) in blocks1.iter_mut().enumerate() { - *b = block; - b[0] = b[0].wrapping_add(i as u8); - } - let mut blocks2 = blocks1.clone(); - - // check that `encrypt_blocks` and `encrypt_block` - // result in the same ciphertext - state.encrypt_blocks(&mut blocks1); - for b in blocks2.iter_mut() { - state.encrypt_block(b); - } - if blocks1 != blocks2 { - return false; - } - - // check that `encrypt_blocks` and `encrypt_block` - // result in the same plaintext - state.decrypt_blocks(&mut blocks1); - for b in blocks2.iter_mut() { - state.decrypt_block(b); - } - if blocks1 != blocks2 { - return false; - } - - true - } - - let pb = <$cipher as BlockCipher>::ParBlocks::to_usize(); - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { - let key = row[0]; - let plaintext = row[1]; - let ciphertext = row[2]; - if !run_test(key, plaintext, ciphertext) { - panic!( - "\n\ - Failed test №{}\n\ - key:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, key, plaintext, ciphertext, - ); - } - - // test parallel blocks encryption/decryption - if pb != 1 { - if !run_par_test(key, plaintext) { - panic!( - "\n\ - Failed parallel test №{}\n\ - key:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, key, plaintext, ciphertext, - ); - } - } - } - // test if cipher can be cloned - let key = Default::default(); - let _ = <$cipher as BlockCipher>::new(&key).clone(); - } - }; -} - -#[macro_export] -macro_rules! bench { - ($cipher:path, $key_len:expr) => { - extern crate test; - - use block_cipher_trait::BlockCipher; - use test::Bencher; - - #[bench] - pub fn encrypt(bh: &mut Bencher) { - let state = <$cipher>::new_varkey(&[1u8; $key_len]).unwrap(); - let mut block = Default::default(); - - bh.iter(|| { - state.encrypt_block(&mut block); - test::black_box(&block); - }); - bh.bytes = block.len() as u64; - } - - #[bench] - pub fn decrypt(bh: &mut Bencher) { - let state = <$cipher>::new_varkey(&[1u8; $key_len]).unwrap(); - let mut block = Default::default(); - - bh.iter(|| { - state.decrypt_block(&mut block); - test::black_box(&block); - }); - bh.bytes = block.len() as u64; - } - }; -} diff --git a/block-cipher-trait/src/errors.rs b/block-cipher-trait/src/errors.rs deleted file mode 100644 index 50b0f33fd..000000000 --- a/block-cipher-trait/src/errors.rs +++ /dev/null @@ -1,20 +0,0 @@ -use core::fmt; -#[cfg(feature = "std")] -use std::error; - -/// Error struct which used with `NewVarKey` -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct InvalidKeyLength; - -impl fmt::Display for InvalidKeyLength { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("invalid key length") - } -} - -#[cfg(feature = "std")] -impl error::Error for InvalidKeyLength { - fn description(&self) -> &str { - "invalid key length" - } -} diff --git a/block-cipher-trait/src/lib.rs b/block-cipher-trait/src/lib.rs deleted file mode 100644 index 497287fd7..000000000 --- a/block-cipher-trait/src/lib.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! This crate defines a set of simple traits used to define functionality of -//! block ciphers. -#![no_std] -#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")] -#[cfg(feature = "dev")] -pub extern crate blobby; -pub extern crate generic_array; -#[cfg(feature = "std")] -extern crate std; - -use generic_array::typenum::Unsigned; -use generic_array::{ArrayLength, GenericArray}; - -#[cfg(feature = "dev")] -pub mod dev; -mod errors; - -pub use errors::InvalidKeyLength; - -type ParBlocks = GenericArray, P>; - -/// The trait which defines in-place encryption and decryption -/// over single block or several blocks in parallel. -pub trait BlockCipher: core::marker::Sized { - /// Key size in bytes with which cipher guaranteed to be initialized - type KeySize: ArrayLength; - /// Size of the block in bytes - type BlockSize: ArrayLength; - /// Number of blocks which can be processed in parallel by - /// cipher implementation - type ParBlocks: ArrayLength>; - - /// Create new block cipher instance from key with fixed size. - fn new(key: &GenericArray) -> Self; - - /// Create new block cipher instance from key with variable size. - /// - /// Default implementation will accept only keys with length equal to - /// `KeySize`, but some ciphers can accept range of key lengths. - fn new_varkey(key: &[u8]) -> Result { - if key.len() != Self::KeySize::to_usize() { - Err(InvalidKeyLength) - } else { - Ok(Self::new(GenericArray::from_slice(key))) - } - } - - /// Encrypt block in-place - fn encrypt_block(&self, block: &mut GenericArray); - - /// Decrypt block in-place - fn decrypt_block(&self, block: &mut GenericArray); - - /// Encrypt several blocks in parallel using instruction level parallelism - /// if possible. - /// - /// If `ParBlocks` equals to 1 it's equivalent to `encrypt_block`. - #[inline] - fn encrypt_blocks(&self, blocks: &mut ParBlocks) { - for block in blocks.iter_mut() { - self.encrypt_block(block); - } - } - - /// Decrypt several blocks in parallel using instruction level parallelism - /// if possible. - /// - /// If `ParBlocks` equals to 1 it's equivalent to `decrypt_block`. - #[inline] - fn decrypt_blocks(&self, blocks: &mut ParBlocks) { - for block in blocks.iter_mut() { - self.decrypt_block(block); - } - } -} diff --git a/build_std.sh b/build_std.sh deleted file mode 100755 index 3ec9fdd71..000000000 --- a/build_std.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# Due to the fact that cargo does not enable features when we use -# `cargo build --all --features std` we have to explicitly iterate over -# all crates (see https://github.com/rust-lang/cargo/issues/4753 ) -DIRS=`ls -d */` -TARGET="thumbv7em-none-eabi" -cargo clean - -for DIR in $DIRS; do - if [ $DIR = "target/" -o $DIR = "aead/" -o $DIR = "universal-hash/" ] - then - continue - fi - cd $DIR - echo Building $DIR - cargo build --all-features || { - echo $DIR failed - exit 1 - } - cd .. -done diff --git a/cipher/CHANGELOG.md b/cipher/CHANGELOG.md new file mode 100644 index 000000000..4a388fb37 --- /dev/null +++ b/cipher/CHANGELOG.md @@ -0,0 +1,121 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.4.4 (2022-03-09) +### Changed +- Move `ParBlocks`/`ParBlocksSizeUser` to the `crypto-common` crate ([#1052]) + +### Fixed +- Unsoundness triggered by zero block size ([#1277]) + +[#1052]: https://github.com/RustCrypto/traits/pull/1052 +[#1277]: https://github.com/RustCrypto/traits/pull/1277 + +## 0.4.3 (2022-02-22) +### Fixed +- Do not enable the `alloc` feature by default ([#953]) + +[#953]: https://github.com/RustCrypto/traits/pull/953 + +## 0.4.2 (2022-02-16) [YANKED] +### Fixed +- Rename `BlockDecryptMut::decrypt_padded_vec` to `decrypt_padded_vec_mut` for consistency with other methods ([#941]) + +[#941]: https://github.com/RustCrypto/traits/pull/941 + +## 0.4.1 (2022-02-16) [YANKED] +### Added +- Allocating padded encrypt/decrypt ([#936]) + +### Fixed +- Minimal versions build ([#940]) + +[#940]: https://github.com/RustCrypto/traits/pull/940 +[#936]: https://github.com/RustCrypto/traits/pull/936 + +## 0.4.0 (2022-02-10) +### Changed +- Major rework of traits. Core functionality of block and stream ciphers +is defined using rank-2 closures with convenience methods built on top of +it. Expose block-level trait for stream ciphers and add generic wrapper +around it. The async stream cipher trait is defined as sub-trait of +mutable block cipher traits. ([#849]) + +### Added +- Re-export `rand_core` ([#683]) + +[#683]: https://github.com/RustCrypto/traits/pull/683 +[#849]: https://github.com/RustCrypto/traits/pull/849 + +## 0.3.0 (2021-04-28) +### Added +- Encrypt/decrypt-only block cipher traits ([#352]) +- Re-export `blobby` from root ([#435]) +- Block cipher trait blanket impls for refs ([#441]) +- `generate_key` method to `New*` trait ([#513]) + +### Changed +- Consolidate error types ([#373]) +- Change `SeekNum` impls to fit with the new `BlockBuffer` ([#435]) +- Reorganize modules ([#435]) +- Renamed `new_var` to `new_from_slice(s)` ([#442]) + +[#352]: https://github.com/RustCrypto/traits/pull/352 +[#373]: https://github.com/RustCrypto/traits/pull/373 +[#435]: https://github.com/RustCrypto/traits/pull/435 +[#441]: https://github.com/RustCrypto/traits/pull/441 +[#442]: https://github.com/RustCrypto/traits/pull/442 +[#513]: https://github.com/RustCrypto/traits/pull/513 + +## 0.2.5 (2020-11-01) +### Fixed +- Nested macros used old deprecated names ([#360]) + +[#360]: https://github.com/RustCrypto/traits/pull/360 + +## 0.2.4 (2020-11-01) +### Fixed +- Macro expansion error ([#358]) + +[#358]: https://github.com/RustCrypto/traits/pull/358 + +## 0.2.3 (2020-11-01) [YANKED] +### Fixed +- Legacy macro wrappers ([#356]) + +[#356]: https://github.com/RustCrypto/traits/pull/356 + +## 0.2.2 (2020-11-01) [YANKED] +### Added +- `BlockCipher::{encrypt_slice, decrypt_slice}` methods ([#351]) + +### Changed +- Revamp macro names ([#350]) + +[#351]: https://github.com/RustCrypto/traits/pull/351 +[#350]: https://github.com/RustCrypto/traits/pull/350 + +## 0.2.1 (2020-10-16) +### Added +- Re-export `generic_array` from toplevel ([#343]) + +### Fixed +- `dev` macro imports ([#345]) + +[#343]: https://github.com/RustCrypto/traits/pull/343 +[#345]: https://github.com/RustCrypto/traits/pull/345 + +## 0.2.0 (2020-10-15) [YANKED] +### Changed +- Unify `block-cipher` and `stream-cipher` into `cipher` ([#337]) + +[#337]: https://github.com/RustCrypto/traits/pull/337 + +## 0.1.1 (2015-06-25) + +## 0.1.0 (2015-06-24) +- Initial release diff --git a/cipher/Cargo.toml b/cipher/Cargo.toml new file mode 100644 index 000000000..95c239f7a --- /dev/null +++ b/cipher/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "cipher" +description = "Traits for describing block ciphers and stream ciphers" +version = "0.5.0-pre.7" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +readme = "README.md" +edition = "2021" +rust-version = "1.81" +documentation = "https://docs.rs/cipher" +repository = "https://github.com/RustCrypto/traits" +keywords = ["crypto", "block-cipher", "stream-cipher", "trait"] +categories = ["cryptography", "no-std"] + +[dependencies] +crypto-common = "0.2.0-rc.1" +inout = "0.2.0-rc.1" + +# optional dependencies +blobby = { version = "0.3", optional = true } +zeroize = { version = "1.8", optional = true, default-features = false } + +[features] +alloc = [] +block-padding = ["inout/block-padding"] +# Enable random key and IV generation methods +rand_core = ["crypto-common/rand_core"] +dev = ["blobby"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/crypto-mac/LICENSE-APACHE b/cipher/LICENSE-APACHE similarity index 100% rename from crypto-mac/LICENSE-APACHE rename to cipher/LICENSE-APACHE diff --git a/block-cipher-trait/LICENSE-MIT b/cipher/LICENSE-MIT similarity index 95% rename from block-cipher-trait/LICENSE-MIT rename to cipher/LICENSE-MIT index 78d6d79a4..f3a832c3b 100644 --- a/block-cipher-trait/LICENSE-MIT +++ b/cipher/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2016 Artyom Pavlov +Copyright (c) 2016-2020 RustCrypto Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/cipher/README.md b/cipher/README.md new file mode 100644 index 000000000..350be54bd --- /dev/null +++ b/cipher/README.md @@ -0,0 +1,62 @@ +# RustCrypto: Cipher Traits + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +Traits which define the functionality of [block ciphers] and [stream ciphers]. + +See [RustCrypto/block-ciphers] and [RustCrypto/stream-ciphers] for algorithm +implementations which use these traits. + +[Documentation][docs-link] + +## Minimum Supported Rust Version + +Rust **1.81** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/cipher.svg +[crate-link]: https://crates.io/crates/cipher +[docs-image]: https://docs.rs/cipher/badge.svg +[docs-link]: https://docs.rs/cipher/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260050-traits +[build-image]: https://github.com/RustCrypto/traits/workflows/cipher/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/traits/actions?query=workflow:cipher + +[//]: # (general links) + +[block ciphers]: https://en.wikipedia.org/wiki/Block_cipher +[stream ciphers]: https://en.wikipedia.org/wiki/Stream_cipher +[RustCrypto/block-ciphers]: https://github.com/RustCrypto/block-ciphers +[RustCrypto/stream-ciphers]: https://github.com/RustCrypto/stream-ciphers diff --git a/cipher/src/block.rs b/cipher/src/block.rs new file mode 100644 index 000000000..3699ebfdb --- /dev/null +++ b/cipher/src/block.rs @@ -0,0 +1,521 @@ +//! Traits used to define functionality of [block ciphers][1] and [modes of operation][2]. +//! +//! # About block ciphers +//! +//! Block ciphers are keyed, deterministic permutations of a fixed-sized input +//! "block" providing a reversible transformation to/from an encrypted output. +//! They are one of the fundamental structural components of [symmetric cryptography][3]. +//! +//! [1]: https://en.wikipedia.org/wiki/Block_cipher +//! [2]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation +//! [3]: https://en.wikipedia.org/wiki/Symmetric-key_algorithm + +#[cfg(all(feature = "block-padding", feature = "alloc"))] +use alloc::{vec, vec::Vec}; +use crypto_common::{Block, BlockSizeUser}; +#[cfg(feature = "block-padding")] +use inout::{ + block_padding::{Padding, UnpadError}, + InOutBufReserved, PadError, +}; +use inout::{InOut, InOutBuf, NotEqualError}; + +mod backends; +mod ctx; + +use ctx::{BlockCtx, BlocksCtx}; + +pub use backends::{ + BlockCipherDecBackend, BlockCipherDecClosure, BlockCipherEncBackend, BlockCipherEncClosure, + BlockModeDecBackend, BlockModeDecClosure, BlockModeEncBackend, BlockModeEncClosure, +}; + +/// Encrypt-only functionality for block ciphers. +pub trait BlockCipherEncrypt: BlockSizeUser + Sized { + /// Encrypt data using backend provided to the rank-2 closure. + fn encrypt_with_backend(&self, f: impl BlockCipherEncClosure); + + /// Encrypt single `inout` block. + #[inline] + fn encrypt_block_inout(&self, block: InOut<'_, '_, Block>) { + self.encrypt_with_backend(BlockCtx { block }); + } + + /// Encrypt `inout` blocks. + #[inline] + fn encrypt_blocks_inout(&self, blocks: InOutBuf<'_, '_, Block>) { + self.encrypt_with_backend(BlocksCtx { blocks }); + } + + /// Encrypt single block in-place. + #[inline] + fn encrypt_block(&self, block: &mut Block) { + let block = block.into(); + self.encrypt_with_backend(BlockCtx { block }); + } + + /// Encrypt `in_block` and write result to `out_block`. + #[inline] + fn encrypt_block_b2b(&self, in_block: &Block, out_block: &mut Block) { + let block = (in_block, out_block).into(); + self.encrypt_with_backend(BlockCtx { block }); + } + + /// Encrypt blocks in-place. + #[inline] + fn encrypt_blocks(&self, blocks: &mut [Block]) { + let blocks = blocks.into(); + self.encrypt_with_backend(BlocksCtx { blocks }); + } + + /// Encrypt blocks buffer-to-buffer. + /// + /// Returns [`NotEqualError`] if provided `in_blocks` and `out_blocks` + /// have different lengths. + #[inline] + fn encrypt_blocks_b2b( + &self, + in_blocks: &[Block], + out_blocks: &mut [Block], + ) -> Result<(), NotEqualError> { + InOutBuf::new(in_blocks, out_blocks) + .map(|blocks| self.encrypt_with_backend(BlocksCtx { blocks })) + } + + /// Pad input and encrypt. Returns resulting ciphertext slice. + /// + /// Returns [`PadError`] if length of output buffer is not sufficient. + #[cfg(feature = "block-padding")] + #[inline] + fn encrypt_padded_inout<'out, P: Padding>( + &self, + data: InOutBufReserved<'_, 'out, u8>, + ) -> Result<&'out [u8], PadError> { + let mut buf = data.into_padded_blocks::()?; + self.encrypt_blocks_inout(buf.get_blocks()); + if let Some(block) = buf.get_tail_block() { + self.encrypt_block_inout(block); + } + Ok(buf.into_out()) + } + + /// Pad input and encrypt in-place. Returns resulting ciphertext slice. + /// + /// Returns [`PadError`] if length of output buffer is not sufficient. + #[cfg(feature = "block-padding")] + #[inline] + fn encrypt_padded<'a, P: Padding>( + &self, + buf: &'a mut [u8], + msg_len: usize, + ) -> Result<&'a [u8], PadError> { + let buf = InOutBufReserved::from_mut_slice(buf, msg_len).map_err(|_| PadError)?; + self.encrypt_padded_inout::

(buf) + } + + /// Pad input and encrypt buffer-to-buffer. Returns resulting ciphertext slice. + /// + /// Returns [`PadError`] if length of output buffer is not sufficient. + #[cfg(feature = "block-padding")] + #[inline] + fn encrypt_padded_b2b<'a, P: Padding>( + &self, + msg: &[u8], + out_buf: &'a mut [u8], + ) -> Result<&'a [u8], PadError> { + let buf = InOutBufReserved::from_slices(msg, out_buf).map_err(|_| PadError)?; + self.encrypt_padded_inout::

(buf) + } + + /// Pad input and encrypt into a newly allocated Vec. Returns resulting ciphertext Vec. + #[cfg(all(feature = "block-padding", feature = "alloc"))] + #[inline] + fn encrypt_padded_vec>(&self, msg: &[u8]) -> Vec { + use crypto_common::typenum::Unsigned; + let bs = Self::BlockSize::USIZE; + let mut out = vec![0; bs * (msg.len() / bs + 1)]; + let len = self + .encrypt_padded_b2b::

(msg, &mut out) + .expect("enough space for encrypting is allocated") + .len(); + out.truncate(len); + out + } +} + +/// Decrypt-only functionality for block ciphers. +pub trait BlockCipherDecrypt: BlockSizeUser { + /// Decrypt data using backend provided to the rank-2 closure. + fn decrypt_with_backend(&self, f: impl BlockCipherDecClosure); + + /// Decrypt single `inout` block. + #[inline] + fn decrypt_block_inout(&self, block: InOut<'_, '_, Block>) { + self.decrypt_with_backend(BlockCtx { block }); + } + + /// Decrypt `inout` blocks. + #[inline] + fn decrypt_blocks_inout(&self, blocks: InOutBuf<'_, '_, Block>) { + self.decrypt_with_backend(BlocksCtx { blocks }); + } + + /// Decrypt single block in-place. + #[inline] + fn decrypt_block(&self, block: &mut Block) { + let block = block.into(); + self.decrypt_with_backend(BlockCtx { block }); + } + + /// Decrypt `in_block` and write result to `out_block`. + #[inline] + fn decrypt_block_b2b(&self, in_block: &Block, out_block: &mut Block) { + let block = (in_block, out_block).into(); + self.decrypt_with_backend(BlockCtx { block }); + } + + /// Decrypt blocks in-place. + #[inline] + fn decrypt_blocks(&self, blocks: &mut [Block]) { + let blocks = blocks.into(); + self.decrypt_with_backend(BlocksCtx { blocks }); + } + + /// Decrypt blocks buffer-to-buffer. + /// + /// Returns [`NotEqualError`] if provided `in_blocks` and `out_blocks` + /// have different lengths. + #[inline] + fn decrypt_blocks_b2b( + &self, + in_blocks: &[Block], + out_blocks: &mut [Block], + ) -> Result<(), NotEqualError> { + InOutBuf::new(in_blocks, out_blocks) + .map(|blocks| self.decrypt_with_backend(BlocksCtx { blocks })) + } + + /// Decrypt input and unpad it. Returns resulting plaintext slice. + /// + /// Returns [`UnpadError`] if padding is malformed or if input length is + /// not multiple of `Self::BlockSize`. + #[cfg(feature = "block-padding")] + #[inline] + fn decrypt_padded_inout<'out, P: Padding>( + &self, + data: InOutBuf<'_, 'out, u8>, + ) -> Result<&'out [u8], UnpadError> { + let (mut blocks, tail) = data.into_chunks(); + if !tail.is_empty() { + return Err(UnpadError); + } + self.decrypt_blocks_inout(blocks.reborrow()); + P::unpad_blocks(blocks.into_out()) + } + + /// Decrypt input and unpad it in-place. Returns resulting plaintext slice. + /// + /// Returns [`UnpadError`] if padding is malformed or if input length is + /// not multiple of `Self::BlockSize`. + #[cfg(feature = "block-padding")] + #[inline] + fn decrypt_padded<'a, P: Padding>( + &self, + buf: &'a mut [u8], + ) -> Result<&'a [u8], UnpadError> { + self.decrypt_padded_inout::

(buf.into()) + } + + /// Decrypt input and unpad it buffer-to-buffer. Returns resulting + /// plaintext slice. + /// + /// Returns [`UnpadError`] if padding is malformed or if input length is + /// not multiple of `Self::BlockSize`. + #[cfg(feature = "block-padding")] + #[inline] + fn decrypt_padded_b2b<'a, P: Padding>( + &self, + in_buf: &[u8], + out_buf: &'a mut [u8], + ) -> Result<&'a [u8], UnpadError> { + if out_buf.len() < in_buf.len() { + return Err(UnpadError); + } + let n = in_buf.len(); + // note: `new` always returns `Ok` here + let buf = InOutBuf::new(in_buf, &mut out_buf[..n]).map_err(|_| UnpadError)?; + self.decrypt_padded_inout::

(buf) + } + + /// Decrypt input and unpad it in a newly allocated Vec. Returns resulting + /// plaintext `Vec`. + /// + /// Returns [`UnpadError`] if padding is malformed or if input length is + /// not multiple of `Self::BlockSize`. + #[cfg(all(feature = "block-padding", feature = "alloc"))] + #[inline] + fn decrypt_padded_vec>( + &self, + buf: &[u8], + ) -> Result, UnpadError> { + let mut out = vec![0; buf.len()]; + let len = self.decrypt_padded_b2b::

(buf, &mut out)?.len(); + out.truncate(len); + Ok(out) + } +} + +impl BlockCipherEncrypt for &Alg { + fn encrypt_with_backend(&self, f: impl BlockCipherEncClosure) { + Alg::encrypt_with_backend(self, f); + } +} + +impl BlockCipherDecrypt for &Alg { + fn decrypt_with_backend(&self, f: impl BlockCipherDecClosure) { + Alg::decrypt_with_backend(self, f); + } +} + +/// Encrypt-only functionality for block ciphers and modes with mutable access to `self`. +/// +/// The main use case for this trait is blocks modes, but it also can be used +/// for hardware cryptographic engines which require `&mut self` access to an +/// underlying hardware peripheral. +pub trait BlockModeEncrypt: BlockSizeUser + Sized { + /// Encrypt data using backend provided to the rank-2 closure. + fn encrypt_with_backend(&mut self, f: impl BlockModeEncClosure); + + /// Encrypt single `inout` block. + #[inline] + fn encrypt_block_inout(&mut self, block: InOut<'_, '_, Block>) { + self.encrypt_with_backend(BlockCtx { block }); + } + + /// Encrypt `inout` blocks. + #[inline] + fn encrypt_blocks_inout(&mut self, blocks: InOutBuf<'_, '_, Block>) { + self.encrypt_with_backend(BlocksCtx { blocks }); + } + + /// Encrypt single block in-place. + #[inline] + fn encrypt_block(&mut self, block: &mut Block) { + let block = block.into(); + self.encrypt_with_backend(BlockCtx { block }); + } + + /// Encrypt `in_block` and write result to `out_block`. + #[inline] + fn encrypt_block_b2b(&mut self, in_block: &Block, out_block: &mut Block) { + let block = (in_block, out_block).into(); + self.encrypt_with_backend(BlockCtx { block }); + } + + /// Encrypt blocks in-place. + #[inline] + fn encrypt_blocks(&mut self, blocks: &mut [Block]) { + let blocks = blocks.into(); + self.encrypt_with_backend(BlocksCtx { blocks }); + } + + /// Encrypt blocks buffer-to-buffer. + /// + /// Returns [`NotEqualError`] if provided `in_blocks` and `out_blocks` + /// have different lengths. + #[inline] + fn encrypt_blocks_b2b( + &mut self, + in_blocks: &[Block], + out_blocks: &mut [Block], + ) -> Result<(), NotEqualError> { + InOutBuf::new(in_blocks, out_blocks) + .map(|blocks| self.encrypt_with_backend(BlocksCtx { blocks })) + } + + /// Pad input and encrypt. Returns resulting ciphertext slice. + /// + /// Returns [`PadError`] if length of output buffer is not sufficient. + #[cfg(feature = "block-padding")] + #[inline] + fn encrypt_padded_inout<'out, P: Padding>( + mut self, + data: InOutBufReserved<'_, 'out, u8>, + ) -> Result<&'out [u8], PadError> { + let mut buf = data.into_padded_blocks::()?; + self.encrypt_blocks_inout(buf.get_blocks()); + if let Some(block) = buf.get_tail_block() { + self.encrypt_block_inout(block); + } + Ok(buf.into_out()) + } + + /// Pad input and encrypt in-place. Returns resulting ciphertext slice. + /// + /// Returns [`PadError`] if length of output buffer is not sufficient. + #[cfg(feature = "block-padding")] + #[inline] + fn encrypt_padded>( + self, + buf: &mut [u8], + msg_len: usize, + ) -> Result<&[u8], PadError> { + let buf = InOutBufReserved::from_mut_slice(buf, msg_len).map_err(|_| PadError)?; + self.encrypt_padded_inout::

(buf) + } + + /// Pad input and encrypt buffer-to-buffer. Returns resulting ciphertext slice. + /// + /// Returns [`PadError`] if length of output buffer is not sufficient. + #[cfg(feature = "block-padding")] + #[inline] + fn encrypt_padded_b2b<'a, P: Padding>( + self, + msg: &[u8], + out_buf: &'a mut [u8], + ) -> Result<&'a [u8], PadError> { + let buf = InOutBufReserved::from_slices(msg, out_buf).map_err(|_| PadError)?; + self.encrypt_padded_inout::

(buf) + } + + /// Pad input and encrypt into a newly allocated Vec. Returns resulting ciphertext Vec. + #[cfg(all(feature = "block-padding", feature = "alloc"))] + #[inline] + fn encrypt_padded_vec>(self, msg: &[u8]) -> Vec { + use crypto_common::typenum::Unsigned; + let bs = Self::BlockSize::USIZE; + let mut out = vec![0; bs * (msg.len() / bs + 1)]; + let len = self + .encrypt_padded_b2b::

(msg, &mut out) + .expect("enough space for encrypting is allocated") + .len(); + out.truncate(len); + out + } +} + +/// Decrypt-only functionality for block ciphers and modes with mutable access to `self`. +/// +/// The main use case for this trait is blocks modes, but it also can be used +/// for hardware cryptographic engines which require `&mut self` access to an +/// underlying hardware peripheral. +pub trait BlockModeDecrypt: BlockSizeUser + Sized { + /// Decrypt data using backend provided to the rank-2 closure. + fn decrypt_with_backend(&mut self, f: impl BlockModeDecClosure); + + /// Decrypt single `inout` block. + #[inline] + fn decrypt_block_inout(&mut self, block: InOut<'_, '_, Block>) { + self.decrypt_with_backend(BlockCtx { block }); + } + + /// Decrypt `inout` blocks. + #[inline] + fn decrypt_blocks_inout(&mut self, blocks: InOutBuf<'_, '_, Block>) { + self.decrypt_with_backend(BlocksCtx { blocks }); + } + + /// Decrypt single block in-place. + #[inline] + fn decrypt_block(&mut self, block: &mut Block) { + let block = block.into(); + self.decrypt_with_backend(BlockCtx { block }); + } + + /// Decrypt `in_block` and write result to `out_block`. + #[inline] + fn decrypt_block_b2b(&mut self, in_block: &Block, out_block: &mut Block) { + let block = (in_block, out_block).into(); + self.decrypt_with_backend(BlockCtx { block }); + } + + /// Decrypt blocks in-place. + #[inline] + fn decrypt_blocks(&mut self, blocks: &mut [Block]) { + let blocks = blocks.into(); + self.decrypt_with_backend(BlocksCtx { blocks }); + } + + /// Decrypt blocks buffer-to-buffer. + /// + /// Returns [`NotEqualError`] if provided `in_blocks` and `out_blocks` + /// have different lengths. + #[inline] + fn decrypt_blocks_b2b( + &mut self, + in_blocks: &[Block], + out_blocks: &mut [Block], + ) -> Result<(), NotEqualError> { + InOutBuf::new(in_blocks, out_blocks) + .map(|blocks| self.decrypt_with_backend(BlocksCtx { blocks })) + } + + /// Decrypt input and unpad it. Returns resulting plaintext slice. + /// + /// Returns [`UnpadError`] if padding is malformed or if input length is + /// not multiple of `Self::BlockSize`. + #[cfg(feature = "block-padding")] + #[inline] + fn decrypt_padded_inout<'out, P: Padding>( + mut self, + data: InOutBuf<'_, 'out, u8>, + ) -> Result<&'out [u8], UnpadError> { + let (mut blocks, tail) = data.into_chunks(); + if !tail.is_empty() { + return Err(UnpadError); + } + self.decrypt_blocks_inout(blocks.reborrow()); + P::unpad_blocks(blocks.into_out()) + } + + /// Decrypt input and unpad it in-place. Returns resulting plaintext slice. + /// + /// Returns [`UnpadError`] if padding is malformed or if input length is + /// not multiple of `Self::BlockSize`. + #[cfg(feature = "block-padding")] + #[inline] + fn decrypt_padded>( + self, + buf: &mut [u8], + ) -> Result<&[u8], UnpadError> { + self.decrypt_padded_inout::

(buf.into()) + } + + /// Decrypt input and unpad it buffer-to-buffer. Returns resulting + /// plaintext slice. + /// + /// Returns [`UnpadError`] if padding is malformed or if input length is + /// not multiple of `Self::BlockSize`. + #[cfg(feature = "block-padding")] + #[inline] + fn decrypt_padded_b2b<'a, P: Padding>( + self, + in_buf: &[u8], + out_buf: &'a mut [u8], + ) -> Result<&'a [u8], UnpadError> { + if out_buf.len() < in_buf.len() { + return Err(UnpadError); + } + let n = in_buf.len(); + // note: `new` always returns `Ok` here + let buf = InOutBuf::new(in_buf, &mut out_buf[..n]).map_err(|_| UnpadError)?; + self.decrypt_padded_inout::

(buf) + } + + /// Decrypt input and unpad it in a newly allocated Vec. Returns resulting + /// plaintext `Vec`. + /// + /// Returns [`UnpadError`] if padding is malformed or if input length is + /// not multiple of `Self::BlockSize`. + #[cfg(all(feature = "block-padding", feature = "alloc"))] + #[inline] + fn decrypt_padded_vec>( + self, + buf: &[u8], + ) -> Result, UnpadError> { + let mut out = vec![0; buf.len()]; + let len = self.decrypt_padded_b2b::

{ + point: P, +} + +impl

NonIdentity

+where + P: ConditionallySelectable + ConstantTimeEq + Default, +{ + /// Create a [`NonIdentity`] from a point. + pub fn new(point: P) -> CtOption { + CtOption::new(Self { point }, !point.ct_eq(&P::default())) + } + + pub(crate) fn new_unchecked(point: P) -> Self { + Self { point } + } +} + +impl

NonIdentity

+where + P: ConditionallySelectable + ConstantTimeEq + Default + GroupEncoding, +{ + /// Decode a [`NonIdentity`] from its encoding. + pub fn from_repr(repr: &P::Repr) -> CtOption { + Self::from_bytes(repr) + } +} + +impl NonIdentity

{ + /// Return wrapped point. + pub fn to_point(self) -> P { + self.point + } +} + +impl

NonIdentity

+where + P: ConditionallySelectable + ConstantTimeEq + Curve + Default, +{ + /// Generate a random `NonIdentity`. + pub fn random(mut rng: impl CryptoRng + RngCore) -> Self { + loop { + if let Some(point) = Self::new(P::random(&mut rng)).into() { + break point; + } + } + } + + /// Converts this element into its affine representation. + pub fn to_affine(self) -> NonIdentity { + NonIdentity { + point: self.point.to_affine(), + } + } +} + +impl

NonIdentity

+where + P: PrimeCurveAffine, +{ + /// Converts this element to its curve representation. + pub fn to_curve(self) -> NonIdentity { + NonIdentity { + point: self.point.to_curve(), + } + } +} + +impl

AsRef

for NonIdentity

{ + fn as_ref(&self) -> &P { + &self.point + } +} + +impl

ConditionallySelectable for NonIdentity

+where + P: ConditionallySelectable, +{ + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self { + point: P::conditional_select(&a.point, &b.point, choice), + } + } +} + +impl

ConstantTimeEq for NonIdentity

+where + P: ConstantTimeEq, +{ + fn ct_eq(&self, other: &Self) -> Choice { + self.point.ct_eq(&other.point) + } +} + +impl

Deref for NonIdentity

{ + type Target = P; + + fn deref(&self) -> &Self::Target { + &self.point + } +} + +impl

GroupEncoding for NonIdentity

+where + P: ConditionallySelectable + ConstantTimeEq + Default + GroupEncoding, +{ + type Repr = P::Repr; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + let point = P::from_bytes(bytes); + point.and_then(|point| CtOption::new(Self { point }, !point.ct_eq(&P::default()))) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + P::from_bytes_unchecked(bytes).map(|point| Self { point }) + } + + fn to_bytes(&self) -> Self::Repr { + self.point.to_bytes() + } +} + +impl Mul> for NonIdentity

+where + C: CurveArithmetic, + P: Copy + Mul, Output = P>, +{ + type Output = NonIdentity

; + + fn mul(self, rhs: NonZeroScalar) -> Self::Output { + &self * &rhs + } +} + +impl Mul<&NonZeroScalar> for &NonIdentity

+where + C: CurveArithmetic, + P: Copy + Mul, Output = P>, +{ + type Output = NonIdentity

; + + fn mul(self, rhs: &NonZeroScalar) -> Self::Output { + NonIdentity { + point: self.point * *rhs.as_ref(), + } + } +} + +#[cfg(feature = "serde")] +impl

Serialize for NonIdentity

+where + P: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + self.point.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, P> Deserialize<'de> for NonIdentity

+where + P: ConditionallySelectable + ConstantTimeEq + Default + Deserialize<'de> + GroupEncoding, +{ + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + Self::new(P::deserialize(deserializer)?) + .into_option() + .ok_or_else(|| de::Error::custom("expected non-identity point")) + } +} + +#[cfg(all(test, feature = "dev"))] +mod tests { + use super::NonIdentity; + use crate::dev::{AffinePoint, ProjectivePoint}; + use group::GroupEncoding; + use hex_literal::hex; + + #[test] + fn new_success() { + let point = ProjectivePoint::from_bytes( + &hex!("02c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").into(), + ) + .unwrap(); + + assert!(bool::from(NonIdentity::new(point).is_some())); + + assert!(bool::from( + NonIdentity::new(AffinePoint::from(point)).is_some() + )); + } + + #[test] + fn new_fail() { + assert!(bool::from( + NonIdentity::new(ProjectivePoint::default()).is_none() + )); + assert!(bool::from( + NonIdentity::new(AffinePoint::default()).is_none() + )); + } + + #[test] + fn round_trip() { + let bytes = hex!("02c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721"); + let point = NonIdentity::::from_repr(&bytes.into()).unwrap(); + assert_eq!(&bytes, point.to_bytes().as_slice()); + + let bytes = hex!("02c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721"); + let point = NonIdentity::::from_repr(&bytes.into()).unwrap(); + assert_eq!(&bytes, point.to_bytes().as_slice()); + } +} diff --git a/elliptic-curve/src/public_key.rs b/elliptic-curve/src/public_key.rs new file mode 100644 index 000000000..aa728d2f0 --- /dev/null +++ b/elliptic-curve/src/public_key.rs @@ -0,0 +1,569 @@ +//! Elliptic curve public keys. + +use crate::{ + point::NonIdentity, AffinePoint, CurveArithmetic, Error, NonZeroScalar, ProjectivePoint, Result, +}; +use core::fmt::Debug; +use group::{Curve, Group}; + +#[cfg(feature = "jwk")] +use crate::{JwkEcKey, JwkParameters}; + +#[cfg(feature = "pkcs8")] +use pkcs8::spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier, ObjectIdentifier}; + +#[cfg(feature = "pem")] +use core::str::FromStr; + +#[cfg(feature = "sec1")] +use { + crate::{ + point::PointCompression, + sec1::{CompressedPoint, EncodedPoint, FromEncodedPoint, ModulusSize, ToEncodedPoint}, + FieldBytesSize, + }, + core::cmp::Ordering, + subtle::{Choice, CtOption}, +}; + +#[cfg(all(feature = "alloc", feature = "pkcs8"))] +use pkcs8::EncodePublicKey; + +#[cfg(all(feature = "alloc", feature = "sec1"))] +use alloc::boxed::Box; + +#[cfg(any(feature = "jwk", feature = "pem"))] +use alloc::string::{String, ToString}; + +#[cfg(feature = "serde")] +use serdect::serde::{de, ser, Deserialize, Serialize}; + +#[cfg(any(feature = "pem", feature = "serde"))] +use pkcs8::DecodePublicKey; + +#[cfg(all(feature = "sec1", feature = "pkcs8"))] +use { + crate::{pkcs8::AssociatedOid, ALGORITHM_OID}, + pkcs8::der, +}; + +/// Elliptic curve public keys. +/// +/// This is a wrapper type for [`AffinePoint`] which ensures an inner +/// non-identity point and provides a common place to handle encoding/decoding. +/// +/// # Parsing "SPKI" Keys +/// +/// X.509 `SubjectPublicKeyInfo` (SPKI) is a commonly used format for encoding +/// public keys, notably public keys corresponding to PKCS#8 private keys. +/// (especially ones generated by OpenSSL). +/// +/// Keys in SPKI format are either binary (ASN.1 BER/DER), or PEM encoded +/// (ASCII) and begin with the following: +/// +/// ```text +/// -----BEGIN PUBLIC KEY----- +/// ``` +/// +/// To decode an elliptic curve public key from SPKI, enable the `pkcs8` +/// feature of this crate (or the `pkcs8` feature of a specific RustCrypto +/// elliptic curve crate) and use the +/// [`elliptic_curve::pkcs8::DecodePublicKey`][`pkcs8::DecodePublicKey`] +/// trait to parse it. +/// +/// When the `pem` feature of this crate (or a specific RustCrypto elliptic +/// curve crate) is enabled, a [`FromStr`] impl is also available. +/// +/// # `serde` support +/// +/// When the optional `serde` feature of this create is enabled, [`Serialize`] +/// and [`Deserialize`] impls are provided for this type. +/// +/// The serialization is binary-oriented and supports ASN.1 DER +/// Subject Public Key Info (SPKI) as the encoding format. +/// +/// For a more text-friendly encoding of public keys, use [`JwkEcKey`] instead. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PublicKey +where + C: CurveArithmetic, +{ + point: AffinePoint, +} + +impl PublicKey +where + C: CurveArithmetic, +{ + /// Convert an [`AffinePoint`] into a [`PublicKey`] + pub fn from_affine(point: AffinePoint) -> Result { + if ProjectivePoint::::from(point).is_identity().into() { + Err(Error) + } else { + Ok(Self { point }) + } + } + + /// Compute a [`PublicKey`] from a secret [`NonZeroScalar`] value + /// (i.e. a secret key represented as a raw scalar value) + pub fn from_secret_scalar(scalar: &NonZeroScalar) -> Self { + // `NonZeroScalar` ensures the resulting point is not the identity + Self { + point: (C::ProjectivePoint::generator() * scalar.as_ref()).to_affine(), + } + } + + /// Decode [`PublicKey`] (compressed or uncompressed) from the + /// `Elliptic-Curve-Point-to-Octet-String` encoding described in + /// SEC 1: Elliptic Curve Cryptography (Version 2.0) section + /// 2.3.3 (page 10). + /// + /// + #[cfg(feature = "sec1")] + pub fn from_sec1_bytes(bytes: &[u8]) -> Result + where + FieldBytesSize: ModulusSize, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + { + let point = EncodedPoint::::from_bytes(bytes).map_err(|_| Error)?; + Self::from_encoded_point(&point).into_option().ok_or(Error) + } + + /// Convert this [`PublicKey`] into the + /// `Elliptic-Curve-Point-to-Octet-String` encoding described in + /// SEC 1: Elliptic Curve Cryptography (Version 2.0) section 2.3.3 + /// (page 10). + /// + /// + #[cfg(all(feature = "alloc", feature = "sec1"))] + pub fn to_sec1_bytes(&self) -> Box<[u8]> + where + C: PointCompression, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + { + EncodedPoint::::from(self).to_bytes() + } + + /// Borrow the inner [`AffinePoint`] from this [`PublicKey`]. + /// + /// In ECC, public keys are elliptic curve points. + pub fn as_affine(&self) -> &AffinePoint { + &self.point + } + + /// Convert this [`PublicKey`] to a [`ProjectivePoint`] for the given curve + pub fn to_projective(&self) -> ProjectivePoint { + self.point.into() + } + + /// Convert this [`PublicKey`] to a [`NonIdentity`] of the inner [`AffinePoint`] + pub fn to_nonidentity(&self) -> NonIdentity> { + NonIdentity::new_unchecked(self.point) + } + + /// Parse a [`JwkEcKey`] JSON Web Key (JWK) into a [`PublicKey`]. + #[cfg(feature = "jwk")] + pub fn from_jwk(jwk: &JwkEcKey) -> Result + where + C: JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + { + jwk.to_public_key::() + } + + /// Parse a string containing a JSON Web Key (JWK) into a [`PublicKey`]. + #[cfg(feature = "jwk")] + pub fn from_jwk_str(jwk: &str) -> Result + where + C: JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + { + jwk.parse::().and_then(|jwk| Self::from_jwk(&jwk)) + } + + /// Serialize this public key as [`JwkEcKey`] JSON Web Key (JWK). + #[cfg(feature = "jwk")] + pub fn to_jwk(&self) -> JwkEcKey + where + C: JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + { + self.into() + } + + /// Serialize this public key as JSON Web Key (JWK) string. + #[cfg(feature = "jwk")] + pub fn to_jwk_string(&self) -> String + where + C: JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + { + self.to_jwk().to_string() + } +} + +impl AsRef> for PublicKey +where + C: CurveArithmetic, +{ + fn as_ref(&self) -> &AffinePoint { + self.as_affine() + } +} + +impl Copy for PublicKey where C: CurveArithmetic {} + +#[cfg(feature = "sec1")] +impl FromEncodedPoint for PublicKey +where + C: CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + /// Initialize [`PublicKey`] from an [`EncodedPoint`] + fn from_encoded_point(encoded_point: &EncodedPoint) -> CtOption { + AffinePoint::::from_encoded_point(encoded_point).and_then(|point| { + // Defeating the point of `subtle`, but the use case is specifically a public key + let is_identity = Choice::from(u8::from(encoded_point.is_identity())); + CtOption::new(PublicKey { point }, !is_identity) + }) + } +} + +#[cfg(feature = "sec1")] +impl ToEncodedPoint for PublicKey +where + C: CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + /// Serialize this [`PublicKey`] as a SEC1 [`EncodedPoint`], optionally applying + /// point compression + fn to_encoded_point(&self, compress: bool) -> EncodedPoint { + self.point.to_encoded_point(compress) + } +} + +#[cfg(feature = "sec1")] +impl From> for CompressedPoint +where + C: CurveArithmetic + PointCompression, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn from(public_key: PublicKey) -> CompressedPoint { + CompressedPoint::::from(&public_key) + } +} + +#[cfg(feature = "sec1")] +impl From<&PublicKey> for CompressedPoint +where + C: CurveArithmetic + PointCompression, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn from(public_key: &PublicKey) -> CompressedPoint { + public_key + .to_encoded_point(true) + .as_bytes() + .try_into() + .expect("wrong compressed point size") + } +} + +#[cfg(feature = "sec1")] +impl From> for EncodedPoint +where + C: CurveArithmetic + PointCompression, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn from(public_key: PublicKey) -> EncodedPoint { + EncodedPoint::::from(&public_key) + } +} + +#[cfg(feature = "sec1")] +impl From<&PublicKey> for EncodedPoint +where + C: CurveArithmetic + PointCompression, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn from(public_key: &PublicKey) -> EncodedPoint { + public_key.to_encoded_point(C::COMPRESS_POINTS) + } +} + +impl From> for PublicKey +where + C: CurveArithmetic, + P: Copy + Into>, +{ + fn from(value: NonIdentity

) -> Self { + Self::from(&value) + } +} + +impl From<&NonIdentity

> for PublicKey +where + C: CurveArithmetic, + P: Copy + Into>, +{ + fn from(value: &NonIdentity

) -> Self { + Self { + point: value.to_point().into(), + } + } +} + +impl From> for NonIdentity> +where + C: CurveArithmetic, +{ + fn from(value: PublicKey) -> Self { + Self::from(&value) + } +} + +impl From<&PublicKey> for NonIdentity> +where + C: CurveArithmetic, +{ + fn from(value: &PublicKey) -> Self { + PublicKey::to_nonidentity(value) + } +} + +#[cfg(feature = "sec1")] +impl PartialOrd for PublicKey +where + C: CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[cfg(feature = "sec1")] +impl Ord for PublicKey +where + C: CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn cmp(&self, other: &Self) -> Ordering { + // TODO(tarcieri): more efficient implementation? + // This is implemented this way to reduce bounds for `AffinePoint` + self.to_encoded_point(false) + .cmp(&other.to_encoded_point(false)) + } +} + +#[cfg(feature = "sec1")] +impl TryFrom> for PublicKey +where + C: CurveArithmetic, + FieldBytesSize: ModulusSize, + AffinePoint: FromEncodedPoint + ToEncodedPoint, +{ + type Error = Error; + + fn try_from(point: CompressedPoint) -> Result { + Self::from_sec1_bytes(&point) + } +} + +#[cfg(feature = "sec1")] +impl TryFrom<&CompressedPoint> for PublicKey +where + C: CurveArithmetic, + FieldBytesSize: ModulusSize, + AffinePoint: FromEncodedPoint + ToEncodedPoint, +{ + type Error = Error; + + fn try_from(point: &CompressedPoint) -> Result { + Self::from_sec1_bytes(point) + } +} + +#[cfg(feature = "sec1")] +impl TryFrom> for PublicKey +where + C: CurveArithmetic, + FieldBytesSize: ModulusSize, + AffinePoint: FromEncodedPoint + ToEncodedPoint, +{ + type Error = Error; + + fn try_from(point: EncodedPoint) -> Result { + Self::from_sec1_bytes(point.as_bytes()) + } +} + +#[cfg(feature = "sec1")] +impl TryFrom<&EncodedPoint> for PublicKey +where + C: CurveArithmetic, + FieldBytesSize: ModulusSize, + AffinePoint: FromEncodedPoint + ToEncodedPoint, +{ + type Error = Error; + + fn try_from(point: &EncodedPoint) -> Result { + Self::from_sec1_bytes(point.as_bytes()) + } +} + +#[cfg(feature = "pkcs8")] +impl AssociatedAlgorithmIdentifier for PublicKey +where + C: AssociatedOid + CurveArithmetic, +{ + type Params = ObjectIdentifier; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifier = AlgorithmIdentifier { + oid: ALGORITHM_OID, + parameters: Some(C::OID), + }; +} + +#[cfg(feature = "pkcs8")] +impl TryFrom> for PublicKey +where + C: AssociatedOid + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + type Error = pkcs8::spki::Error; + + fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result { + Self::try_from(&spki) + } +} + +#[cfg(feature = "pkcs8")] +impl TryFrom<&pkcs8::SubjectPublicKeyInfoRef<'_>> for PublicKey +where + C: AssociatedOid + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + type Error = pkcs8::spki::Error; + + fn try_from(spki: &pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result { + spki.algorithm.assert_oids(ALGORITHM_OID, C::OID)?; + + let public_key_bytes = spki + .subject_public_key + .as_bytes() + .ok_or_else(|| der::Tag::BitString.value_error())?; + + Self::from_sec1_bytes(public_key_bytes) + .map_err(|_| der::Tag::BitString.value_error().into()) + } +} + +#[cfg(all(feature = "alloc", feature = "pkcs8"))] +impl EncodePublicKey for PublicKey +where + C: AssociatedOid + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn to_public_key_der(&self) -> pkcs8::spki::Result { + let public_key_bytes = self.to_encoded_point(false); + let subject_public_key = der::asn1::BitStringRef::new(0, public_key_bytes.as_bytes())?; + + pkcs8::SubjectPublicKeyInfo { + algorithm: Self::ALGORITHM_IDENTIFIER, + subject_public_key, + } + .try_into() + } +} + +#[cfg(feature = "pem")] +impl FromStr for PublicKey +where + C: AssociatedOid + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::from_public_key_pem(s).map_err(|_| Error) + } +} + +#[cfg(feature = "pem")] +#[allow(clippy::to_string_trait_impl)] +impl ToString for PublicKey +where + C: AssociatedOid + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn to_string(&self) -> String { + self.to_public_key_pem(Default::default()) + .expect("PEM encoding error") + } +} + +#[cfg(feature = "serde")] +impl Serialize for PublicKey +where + C: AssociatedOid + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn serialize(&self, serializer: S) -> core::result::Result + where + S: ser::Serializer, + { + let der = self.to_public_key_der().map_err(ser::Error::custom)?; + serdect::slice::serialize_hex_upper_or_bin(&der, serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, C> Deserialize<'de> for PublicKey +where + C: AssociatedOid + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn deserialize(deserializer: D) -> core::result::Result + where + D: de::Deserializer<'de>, + { + let der_bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?; + Self::from_public_key_der(&der_bytes).map_err(de::Error::custom) + } +} + +#[cfg(all(feature = "dev", test))] +mod tests { + use crate::{dev::MockCurve, sec1::FromEncodedPoint}; + + type EncodedPoint = crate::sec1::EncodedPoint; + type PublicKey = super::PublicKey; + + #[test] + fn from_encoded_point_rejects_identity() { + let identity = EncodedPoint::identity(); + assert!(bool::from( + PublicKey::from_encoded_point(&identity).is_none() + )); + } +} diff --git a/elliptic-curve/src/scalar.rs b/elliptic-curve/src/scalar.rs new file mode 100644 index 000000000..9f3abdad7 --- /dev/null +++ b/elliptic-curve/src/scalar.rs @@ -0,0 +1,54 @@ +//! Scalar types. + +#[cfg(feature = "arithmetic")] +mod blinded; +#[cfg(feature = "arithmetic")] +mod nonzero; +mod primitive; + +pub use self::primitive::ScalarPrimitive; + +#[cfg(feature = "arithmetic")] +pub use self::{blinded::BlindedScalar, nonzero::NonZeroScalar}; + +use crypto_bigint::Integer; +use subtle::Choice; + +#[cfg(feature = "arithmetic")] +use crate::CurveArithmetic; + +/// Scalar field element for a particular elliptic curve. +#[cfg(feature = "arithmetic")] +pub type Scalar = ::Scalar; + +/// Bit representation of a scalar field element of a given curve. +#[cfg(feature = "bits")] +pub type ScalarBits = ff::FieldBits< as ff::PrimeFieldBits>::ReprBits>; + +/// Instantiate a scalar from an unsigned integer without checking for overflow. +pub trait FromUintUnchecked { + /// Unsigned integer type (i.e. `Curve::Uint`) + type Uint: Integer; + + /// Instantiate scalar from an unsigned integer without checking + /// whether the value overflows the field modulus. + /// + /// ⚠️ WARNING! + /// + /// Incorrectly used this can lead to mathematically invalid results, + /// which can lead to potential security vulnerabilities. + /// + /// Use with care! + fn from_uint_unchecked(uint: Self::Uint) -> Self; +} + +/// Is this scalar greater than n / 2? +/// +/// # Returns +/// +/// - For scalars 0 through n / 2: `Choice::from(0)` +/// - For scalars (n / 2) + 1 through n - 1: `Choice::from(1)` +pub trait IsHigh { + /// Is this scalar greater than n / 2? + fn is_high(&self) -> Choice; +} diff --git a/elliptic-curve/src/scalar/blinded.rs b/elliptic-curve/src/scalar/blinded.rs new file mode 100644 index 000000000..914427f55 --- /dev/null +++ b/elliptic-curve/src/scalar/blinded.rs @@ -0,0 +1,81 @@ +//! Random blinding support for [`Scalar`] + +use super::Scalar; +use crate::{ops::Invert, CurveArithmetic}; +use core::fmt; +use group::ff::Field; +use rand_core::CryptoRngCore; +use subtle::CtOption; +use zeroize::Zeroize; + +/// Scalar blinded with a randomly generated masking value. +/// +/// This provides a randomly blinded impl of [`Invert`] which is useful for +/// e.g. ECDSA ephemeral (`k`) scalars. +/// +/// It implements masked variable-time inversions using Stein's algorithm, which +/// may be helpful for performance on embedded platforms. +#[derive(Clone)] +pub struct BlindedScalar +where + C: CurveArithmetic, +{ + /// Actual scalar value. + scalar: Scalar, + + /// Mask value. + mask: Scalar, +} + +impl fmt::Debug for BlindedScalar { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BlindedScalar").finish_non_exhaustive() + } +} + +impl BlindedScalar +where + C: CurveArithmetic, +{ + /// Create a new [`BlindedScalar`] from a scalar and a [`CryptoRngCore`]. + pub fn new(scalar: Scalar, rng: &mut impl CryptoRngCore) -> Self { + Self { + scalar, + mask: Scalar::::random(rng), + } + } +} + +impl AsRef> for BlindedScalar +where + C: CurveArithmetic, +{ + fn as_ref(&self) -> &Scalar { + &self.scalar + } +} + +impl Invert for BlindedScalar +where + C: CurveArithmetic, +{ + type Output = CtOption>; + + fn invert(&self) -> CtOption> { + // prevent side channel analysis of scalar inversion by pre-and-post-multiplying + // with the random masking scalar + (self.scalar * self.mask) + .invert_vartime() + .map(|s| s * self.mask) + } +} + +impl Drop for BlindedScalar +where + C: CurveArithmetic, +{ + fn drop(&mut self) { + self.scalar.zeroize(); + self.mask.zeroize(); + } +} diff --git a/elliptic-curve/src/scalar/nonzero.rs b/elliptic-curve/src/scalar/nonzero.rs new file mode 100644 index 000000000..9fd617e2a --- /dev/null +++ b/elliptic-curve/src/scalar/nonzero.rs @@ -0,0 +1,408 @@ +//! Non-zero scalar type. + +use crate::{ + ops::{Invert, Reduce, ReduceNonZero}, + scalar::IsHigh, + CurveArithmetic, Error, FieldBytes, PrimeCurve, Scalar, ScalarPrimitive, SecretKey, +}; +use base16ct::HexDisplay; +use core::{ + fmt, + ops::{Deref, Mul, Neg}, + str, +}; +use crypto_bigint::{ArrayEncoding, Integer}; +use ff::{Field, PrimeField}; +use rand_core::CryptoRngCore; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; +use zeroize::Zeroize; + +#[cfg(feature = "serde")] +use serdect::serde::{de, ser, Deserialize, Serialize}; + +/// Non-zero scalar type. +/// +/// This type ensures that its value is not zero, ala `core::num::NonZero*`. +/// To do this, the generic `S` type must impl both `Default` and +/// `ConstantTimeEq`, with the requirement that `S::default()` returns 0. +/// +/// In the context of ECC, it's useful for ensuring that scalar multiplication +/// cannot result in the point at infinity. +#[derive(Clone)] +pub struct NonZeroScalar +where + C: CurveArithmetic, +{ + scalar: Scalar, +} + +impl fmt::Debug for NonZeroScalar { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NonZeroScalar").finish_non_exhaustive() + } +} + +impl NonZeroScalar +where + C: CurveArithmetic, +{ + /// Generate a random `NonZeroScalar`. + pub fn random(mut rng: &mut impl CryptoRngCore) -> Self { + // Use rejection sampling to eliminate zero values. + // While this method isn't constant-time, the attacker shouldn't learn + // anything about unrelated outputs so long as `rng` is a secure `CryptoRng`. + loop { + // TODO: remove after `Field::random` switches to `&mut impl RngCore` + #[allow(clippy::needless_borrows_for_generic_args)] + if let Some(result) = Self::new(Field::random(&mut rng)).into() { + break result; + } + } + } + + /// Create a [`NonZeroScalar`] from a scalar. + pub fn new(scalar: Scalar) -> CtOption { + CtOption::new(Self { scalar }, !scalar.is_zero()) + } + + /// Decode a [`NonZeroScalar`] from a big endian-serialized field element. + pub fn from_repr(repr: FieldBytes) -> CtOption { + Scalar::::from_repr(repr).and_then(Self::new) + } + + /// Create a [`NonZeroScalar`] from a `C::Uint`. + pub fn from_uint(uint: C::Uint) -> CtOption { + ScalarPrimitive::new(uint).and_then(|scalar| Self::new(scalar.into())) + } +} + +impl AsRef> for NonZeroScalar +where + C: CurveArithmetic, +{ + fn as_ref(&self) -> &Scalar { + &self.scalar + } +} + +impl ConditionallySelectable for NonZeroScalar +where + C: CurveArithmetic, +{ + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self { + scalar: Scalar::::conditional_select(&a.scalar, &b.scalar, choice), + } + } +} + +impl ConstantTimeEq for NonZeroScalar +where + C: CurveArithmetic, +{ + fn ct_eq(&self, other: &Self) -> Choice { + self.scalar.ct_eq(&other.scalar) + } +} + +impl Copy for NonZeroScalar where C: CurveArithmetic {} + +impl Deref for NonZeroScalar +where + C: CurveArithmetic, +{ + type Target = Scalar; + + fn deref(&self) -> &Scalar { + &self.scalar + } +} + +impl From> for FieldBytes +where + C: CurveArithmetic, +{ + fn from(scalar: NonZeroScalar) -> FieldBytes { + Self::from(&scalar) + } +} + +impl From<&NonZeroScalar> for FieldBytes +where + C: CurveArithmetic, +{ + fn from(scalar: &NonZeroScalar) -> FieldBytes { + scalar.to_repr() + } +} + +impl From> for ScalarPrimitive +where + C: CurveArithmetic, +{ + #[inline] + fn from(scalar: NonZeroScalar) -> ScalarPrimitive { + Self::from(&scalar) + } +} + +impl From<&NonZeroScalar> for ScalarPrimitive +where + C: CurveArithmetic, +{ + fn from(scalar: &NonZeroScalar) -> ScalarPrimitive { + ScalarPrimitive::from_bytes(&scalar.to_repr()).unwrap() + } +} + +impl From> for NonZeroScalar +where + C: CurveArithmetic, +{ + fn from(sk: SecretKey) -> NonZeroScalar { + Self::from(&sk) + } +} + +impl From<&SecretKey> for NonZeroScalar +where + C: CurveArithmetic, +{ + fn from(sk: &SecretKey) -> NonZeroScalar { + let scalar = sk.as_scalar_primitive().to_scalar(); + debug_assert!(!bool::from(scalar.is_zero())); + Self { scalar } + } +} + +impl Invert for NonZeroScalar +where + C: CurveArithmetic, + Scalar: Invert>>, +{ + type Output = Self; + + fn invert(&self) -> Self { + Self { + // This will always succeed since `scalar` will never be 0 + scalar: Invert::invert(&self.scalar).unwrap(), + } + } + + fn invert_vartime(&self) -> Self::Output { + Self { + // This will always succeed since `scalar` will never be 0 + scalar: Invert::invert_vartime(&self.scalar).unwrap(), + } + } +} + +impl IsHigh for NonZeroScalar +where + C: CurveArithmetic, +{ + fn is_high(&self) -> Choice { + self.scalar.is_high() + } +} + +impl Neg for NonZeroScalar +where + C: CurveArithmetic, +{ + type Output = NonZeroScalar; + + fn neg(self) -> NonZeroScalar { + let scalar = -self.scalar; + debug_assert!(!bool::from(scalar.is_zero())); + NonZeroScalar { scalar } + } +} + +impl Mul> for NonZeroScalar +where + C: PrimeCurve + CurveArithmetic, +{ + type Output = Self; + + #[inline] + fn mul(self, other: Self) -> Self { + Self::mul(self, &other) + } +} + +impl Mul<&NonZeroScalar> for NonZeroScalar +where + C: PrimeCurve + CurveArithmetic, +{ + type Output = Self; + + fn mul(self, other: &Self) -> Self { + // Multiplication is modulo a prime, so the product of two non-zero + // scalars is also non-zero. + let scalar = self.scalar * other.scalar; + debug_assert!(!bool::from(scalar.is_zero())); + NonZeroScalar { scalar } + } +} + +/// Note: this is a non-zero reduction, as it's impl'd for [`NonZeroScalar`]. +impl Reduce for NonZeroScalar +where + C: CurveArithmetic, + I: Integer + ArrayEncoding, + Scalar: Reduce + ReduceNonZero, +{ + type Bytes = as Reduce>::Bytes; + + fn reduce(n: I) -> Self { + let scalar = Scalar::::reduce_nonzero(n); + debug_assert!(!bool::from(scalar.is_zero())); + Self { scalar } + } + + fn reduce_bytes(bytes: &Self::Bytes) -> Self { + let scalar = Scalar::::reduce_nonzero_bytes(bytes); + debug_assert!(!bool::from(scalar.is_zero())); + Self { scalar } + } +} + +/// Note: forwards to the [`Reduce`] impl. +impl ReduceNonZero for NonZeroScalar +where + Self: Reduce, + C: CurveArithmetic, + I: Integer + ArrayEncoding, + Scalar: Reduce + ReduceNonZero, +{ + fn reduce_nonzero(n: I) -> Self { + >::reduce(n) + } + + fn reduce_nonzero_bytes(bytes: &Self::Bytes) -> Self { + >::reduce_bytes(bytes) + } +} + +impl TryFrom<&[u8]> for NonZeroScalar +where + C: CurveArithmetic, +{ + type Error = Error; + + fn try_from(bytes: &[u8]) -> Result { + NonZeroScalar::from_repr(bytes.try_into()?) + .into_option() + .ok_or(Error) + } +} + +impl Zeroize for NonZeroScalar +where + C: CurveArithmetic, +{ + fn zeroize(&mut self) { + // Use zeroize's volatile writes to ensure value is cleared. + self.scalar.zeroize(); + + // Write a 1 instead of a 0 to ensure this type's non-zero invariant + // is upheld. + self.scalar = Scalar::::ONE; + } +} + +impl fmt::Display for NonZeroScalar +where + C: CurveArithmetic, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:X}") + } +} + +impl fmt::LowerHex for NonZeroScalar +where + C: CurveArithmetic, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:x}", HexDisplay(&self.to_repr())) + } +} + +impl fmt::UpperHex for NonZeroScalar +where + C: CurveArithmetic, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:}", HexDisplay(&self.to_repr())) + } +} + +impl str::FromStr for NonZeroScalar +where + C: CurveArithmetic, +{ + type Err = Error; + + fn from_str(hex: &str) -> Result { + let mut bytes = FieldBytes::::default(); + + if base16ct::mixed::decode(hex, &mut bytes)?.len() == bytes.len() { + Self::from_repr(bytes).into_option().ok_or(Error) + } else { + Err(Error) + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for NonZeroScalar +where + C: CurveArithmetic, +{ + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + ScalarPrimitive::from(self).serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, C> Deserialize<'de> for NonZeroScalar +where + C: CurveArithmetic, +{ + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let scalar = ScalarPrimitive::deserialize(deserializer)?; + Self::new(scalar.into()) + .into_option() + .ok_or_else(|| de::Error::custom("expected non-zero scalar")) + } +} + +#[cfg(all(test, feature = "dev"))] +mod tests { + use crate::dev::{NonZeroScalar, Scalar}; + use ff::{Field, PrimeField}; + use hex_literal::hex; + use zeroize::Zeroize; + + #[test] + fn round_trip() { + let bytes = hex!("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721"); + let scalar = NonZeroScalar::from_repr(bytes.into()).unwrap(); + assert_eq!(&bytes, scalar.to_repr().as_slice()); + } + + #[test] + fn zeroize() { + let mut scalar = NonZeroScalar::new(Scalar::from(42u64)).unwrap(); + scalar.zeroize(); + assert_eq!(*scalar, Scalar::ONE); + } +} diff --git a/elliptic-curve/src/scalar/primitive.rs b/elliptic-curve/src/scalar/primitive.rs new file mode 100644 index 000000000..a4d31cb3e --- /dev/null +++ b/elliptic-curve/src/scalar/primitive.rs @@ -0,0 +1,431 @@ +//! Generic scalar type with primitive functionality. + +use crate::{ + array::Array, + bigint::{prelude::*, Limb, NonZero}, + scalar::FromUintUnchecked, + scalar::IsHigh, + Curve, Error, FieldBytes, FieldBytesEncoding, Result, +}; +use base16ct::HexDisplay; +use core::{ + cmp::Ordering, + fmt, + ops::{Add, AddAssign, Neg, ShrAssign, Sub, SubAssign}, + str, +}; +use rand_core::CryptoRngCore; +use subtle::{ + Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess, + CtOption, +}; +use zeroize::DefaultIsZeroes; + +#[cfg(feature = "arithmetic")] +use super::{CurveArithmetic, Scalar}; + +#[cfg(feature = "serde")] +use serdect::serde::{de, ser, Deserialize, Serialize}; + +/// Generic scalar type with primitive functionality. +/// +/// This type provides a baseline level of scalar arithmetic functionality +/// which is always available for all curves, regardless of if they implement +/// any arithmetic traits. +/// +/// # `serde` support +/// +/// When the optional `serde` feature of this create is enabled, [`Serialize`] +/// and [`Deserialize`] impls are provided for this type. +/// +/// The serialization is a fixed-width big endian encoding. When used with +/// textual formats, the binary data is encoded as hexadecimal. +// TODO(tarcieri): use `crypto-bigint`'s `Residue` type, expose more functionality? +#[derive(Copy, Clone, Debug, Default)] +pub struct ScalarPrimitive { + /// Inner unsigned integer type. + inner: C::Uint, +} + +impl ScalarPrimitive +where + C: Curve, +{ + /// Zero scalar. + pub const ZERO: Self = Self { + inner: C::Uint::ZERO, + }; + + /// Multiplicative identity. + pub const ONE: Self = Self { + inner: C::Uint::ONE, + }; + + /// Scalar modulus. + pub const MODULUS: C::Uint = C::ORDER; + + /// Generate a random [`ScalarPrimitive`]. + pub fn random(rng: &mut impl CryptoRngCore) -> Self { + Self { + inner: C::Uint::random_mod(rng, &NonZero::new(Self::MODULUS).unwrap()), + } + } + + /// Create a new scalar from [`Curve::Uint`]. + pub fn new(uint: C::Uint) -> CtOption { + CtOption::new(Self { inner: uint }, uint.ct_lt(&Self::MODULUS)) + } + + /// Decode [`ScalarPrimitive`] from a serialized field element + pub fn from_bytes(bytes: &FieldBytes) -> CtOption { + Self::new(C::Uint::decode_field_bytes(bytes)) + } + + /// Decode [`ScalarPrimitive`] from a big endian byte slice. + pub fn from_slice(slice: &[u8]) -> Result { + let bytes = Array::try_from(slice).map_err(|_| Error)?; + Self::from_bytes(&bytes).into_option().ok_or(Error) + } + + /// Borrow the inner `C::Uint`. + pub fn as_uint(&self) -> &C::Uint { + &self.inner + } + + /// Borrow the inner limbs as a slice. + pub fn as_limbs(&self) -> &[Limb] { + self.inner.as_ref() + } + + /// Is this [`ScalarPrimitive`] value equal to zero? + pub fn is_zero(&self) -> Choice { + self.inner.is_zero() + } + + /// Is this [`ScalarPrimitive`] value even? + pub fn is_even(&self) -> Choice { + self.inner.is_even() + } + + /// Is this [`ScalarPrimitive`] value odd? + pub fn is_odd(&self) -> Choice { + self.inner.is_odd() + } + + /// Encode [`ScalarPrimitive`] as a serialized field element. + pub fn to_bytes(&self) -> FieldBytes { + self.inner.encode_field_bytes() + } + + /// Convert to a `C::Uint`. + pub fn to_uint(&self) -> C::Uint { + self.inner + } +} + +impl FromUintUnchecked for ScalarPrimitive +where + C: Curve, +{ + type Uint = C::Uint; + + fn from_uint_unchecked(uint: C::Uint) -> Self { + Self { inner: uint } + } +} + +#[cfg(feature = "arithmetic")] +impl ScalarPrimitive +where + C: CurveArithmetic, +{ + /// Convert [`ScalarPrimitive`] into a given curve's scalar type. + pub(super) fn to_scalar(self) -> Scalar { + Scalar::::from_uint_unchecked(self.inner) + } +} + +// TODO(tarcieri): better encapsulate this? +impl AsRef<[Limb]> for ScalarPrimitive +where + C: Curve, +{ + fn as_ref(&self) -> &[Limb] { + self.as_limbs() + } +} + +impl ConditionallySelectable for ScalarPrimitive +where + C: Curve, +{ + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self { + inner: C::Uint::conditional_select(&a.inner, &b.inner, choice), + } + } +} + +impl ConstantTimeEq for ScalarPrimitive +where + C: Curve, +{ + fn ct_eq(&self, other: &Self) -> Choice { + self.inner.ct_eq(&other.inner) + } +} + +impl ConstantTimeLess for ScalarPrimitive +where + C: Curve, +{ + fn ct_lt(&self, other: &Self) -> Choice { + self.inner.ct_lt(&other.inner) + } +} + +impl ConstantTimeGreater for ScalarPrimitive +where + C: Curve, +{ + fn ct_gt(&self, other: &Self) -> Choice { + self.inner.ct_gt(&other.inner) + } +} + +impl DefaultIsZeroes for ScalarPrimitive {} + +impl Eq for ScalarPrimitive {} + +impl PartialEq for ScalarPrimitive +where + C: Curve, +{ + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl PartialOrd for ScalarPrimitive +where + C: Curve, +{ + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ScalarPrimitive +where + C: Curve, +{ + fn cmp(&self, other: &Self) -> Ordering { + self.inner.cmp(&other.inner) + } +} + +impl From for ScalarPrimitive +where + C: Curve, +{ + fn from(n: u64) -> Self { + Self { + inner: C::Uint::from(n), + } + } +} + +impl Add> for ScalarPrimitive +where + C: Curve, +{ + type Output = Self; + + fn add(self, other: Self) -> Self { + self.add(&other) + } +} + +impl Add<&ScalarPrimitive> for ScalarPrimitive +where + C: Curve, +{ + type Output = Self; + + fn add(self, other: &Self) -> Self { + Self { + inner: self.inner.add_mod(&other.inner, &Self::MODULUS), + } + } +} + +impl AddAssign> for ScalarPrimitive +where + C: Curve, +{ + fn add_assign(&mut self, other: Self) { + *self = *self + other; + } +} + +impl AddAssign<&ScalarPrimitive> for ScalarPrimitive +where + C: Curve, +{ + fn add_assign(&mut self, other: &Self) { + *self = *self + other; + } +} + +impl Sub> for ScalarPrimitive +where + C: Curve, +{ + type Output = Self; + + fn sub(self, other: Self) -> Self { + self.sub(&other) + } +} + +impl Sub<&ScalarPrimitive> for ScalarPrimitive +where + C: Curve, +{ + type Output = Self; + + fn sub(self, other: &Self) -> Self { + Self { + inner: self.inner.sub_mod(&other.inner, &Self::MODULUS), + } + } +} + +impl SubAssign> for ScalarPrimitive +where + C: Curve, +{ + fn sub_assign(&mut self, other: Self) { + *self = *self - other; + } +} + +impl SubAssign<&ScalarPrimitive> for ScalarPrimitive +where + C: Curve, +{ + fn sub_assign(&mut self, other: &Self) { + *self = *self - other; + } +} + +impl Neg for ScalarPrimitive +where + C: Curve, +{ + type Output = Self; + + fn neg(self) -> Self { + Self { + inner: self.inner.neg_mod(&Self::MODULUS), + } + } +} + +impl Neg for &ScalarPrimitive +where + C: Curve, +{ + type Output = ScalarPrimitive; + + fn neg(self) -> ScalarPrimitive { + -*self + } +} + +impl ShrAssign for ScalarPrimitive +where + C: Curve, +{ + fn shr_assign(&mut self, rhs: usize) { + self.inner >>= rhs; + } +} + +impl IsHigh for ScalarPrimitive +where + C: Curve, +{ + fn is_high(&self) -> Choice { + let n_2 = C::ORDER >> 1u32; + self.inner.ct_gt(&n_2) + } +} + +impl fmt::Display for ScalarPrimitive +where + C: Curve, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:X}") + } +} + +impl fmt::LowerHex for ScalarPrimitive +where + C: Curve, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:x}", HexDisplay(&self.to_bytes())) + } +} + +impl fmt::UpperHex for ScalarPrimitive +where + C: Curve, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:X}", HexDisplay(&self.to_bytes())) + } +} + +impl str::FromStr for ScalarPrimitive +where + C: Curve, +{ + type Err = Error; + + fn from_str(hex: &str) -> Result { + let mut bytes = FieldBytes::::default(); + base16ct::lower::decode(hex, &mut bytes)?; + Self::from_slice(&bytes) + } +} + +#[cfg(feature = "serde")] +impl Serialize for ScalarPrimitive +where + C: Curve, +{ + fn serialize(&self, serializer: S) -> core::result::Result + where + S: ser::Serializer, + { + serdect::array::serialize_hex_upper_or_bin(&self.to_bytes(), serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, C> Deserialize<'de> for ScalarPrimitive +where + C: Curve, +{ + fn deserialize(deserializer: D) -> core::result::Result + where + D: de::Deserializer<'de>, + { + let mut bytes = FieldBytes::::default(); + serdect::array::deserialize_hex_or_bin(&mut bytes, deserializer)?; + Self::from_slice(&bytes).map_err(|_| de::Error::custom("scalar out of range")) + } +} diff --git a/elliptic-curve/src/sec1.rs b/elliptic-curve/src/sec1.rs new file mode 100644 index 000000000..77417cd82 --- /dev/null +++ b/elliptic-curve/src/sec1.rs @@ -0,0 +1,114 @@ +//! Support for SEC1 elliptic curve encoding formats. +//! +//! + +pub use sec1::point::{Coordinates, ModulusSize, Tag}; + +use crate::{Curve, FieldBytesSize, Result, SecretKey}; +use hybrid_array::Array; +use subtle::CtOption; + +#[cfg(feature = "arithmetic")] +use crate::{AffinePoint, CurveArithmetic, Error}; + +/// Encoded elliptic curve point with point compression. +pub type CompressedPoint = Array>; + +/// Size of a compressed elliptic curve point. +pub type CompressedPointSize = as ModulusSize>::CompressedPointSize; + +/// Encoded elliptic curve point sized appropriately for a given curve. +pub type EncodedPoint = sec1::point::EncodedPoint>; + +/// Encoded elliptic curve point *without* point compression. +pub type UncompressedPoint = Array>; + +/// Size of an uncompressed elliptic curve point. +pub type UncompressedPointSize = as ModulusSize>::UncompressedPointSize; + +/// Trait for deserializing a value from a SEC1 encoded curve point. +/// +/// This is intended for use with the `AffinePoint` type for a given elliptic curve. +pub trait FromEncodedPoint +where + Self: Sized, + C: Curve, + FieldBytesSize: ModulusSize, +{ + /// Deserialize the type this trait is impl'd on from an [`EncodedPoint`]. + fn from_encoded_point(point: &EncodedPoint) -> CtOption; +} + +/// Trait for serializing a value to a SEC1 encoded curve point. +/// +/// This is intended for use with the `AffinePoint` type for a given elliptic curve. +pub trait ToEncodedPoint +where + C: Curve, + FieldBytesSize: ModulusSize, +{ + /// Serialize this value as a SEC1 [`EncodedPoint`], optionally applying + /// point compression. + fn to_encoded_point(&self, compress: bool) -> EncodedPoint; +} + +/// Trait for serializing a value to a SEC1 encoded curve point with compaction. +/// +/// This is intended for use with the `AffinePoint` type for a given elliptic curve. +pub trait ToCompactEncodedPoint +where + C: Curve, + FieldBytesSize: ModulusSize, +{ + /// Serialize this value as a SEC1 [`EncodedPoint`], optionally applying + /// point compression. + fn to_compact_encoded_point(&self) -> CtOption>; +} + +/// Validate that the given [`EncodedPoint`] represents the encoded public key +/// value of the given secret. +/// +/// Curve implementations which also impl [`CurveArithmetic`] will receive +/// a blanket default impl of this trait. +pub trait ValidatePublicKey +where + Self: Curve, + FieldBytesSize: ModulusSize, +{ + /// Validate that the given [`EncodedPoint`] is a valid public key for the + /// provided secret value. + #[allow(unused_variables)] + fn validate_public_key( + secret_key: &SecretKey, + public_key: &EncodedPoint, + ) -> Result<()> { + // Provide a default "always succeeds" implementation. + // This is the intended default for curve implementations which + // do not provide an arithmetic implementation, since they have no + // way to verify this. + // + // Implementations with an arithmetic impl will receive a blanket impl + // of this trait. + Ok(()) + } +} + +#[cfg(feature = "arithmetic")] +impl ValidatePublicKey for C +where + C: CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn validate_public_key(secret_key: &SecretKey, public_key: &EncodedPoint) -> Result<()> { + let pk = secret_key + .public_key() + .to_encoded_point(public_key.is_compressed()); + + if public_key == &pk { + Ok(()) + } else { + Err(Error) + } + } +} diff --git a/elliptic-curve/src/secret_key.rs b/elliptic-curve/src/secret_key.rs new file mode 100644 index 000000000..09b6ba5ad --- /dev/null +++ b/elliptic-curve/src/secret_key.rs @@ -0,0 +1,396 @@ +//! Secret keys for elliptic curves (i.e. private scalars). +//! +//! The [`SecretKey`] type is a wrapper around a secret scalar value which is +//! designed to prevent unintentional exposure (e.g. via `Debug` or other +//! logging). It also handles zeroing the secret value out of memory securely +//! on drop. + +#[cfg(all(feature = "pkcs8", feature = "sec1"))] +mod pkcs8; + +use crate::{Curve, Error, FieldBytes, Result, ScalarPrimitive}; +use core::fmt::{self, Debug}; +use hybrid_array::typenum::Unsigned; +use subtle::{Choice, ConstantTimeEq}; +use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; + +#[cfg(feature = "arithmetic")] +use crate::{rand_core::CryptoRngCore, CurveArithmetic, NonZeroScalar, PublicKey}; + +#[cfg(feature = "jwk")] +use crate::jwk::{JwkEcKey, JwkParameters}; + +#[cfg(feature = "pem")] +use pem_rfc7468::{self as pem, PemLabel}; + +#[cfg(feature = "sec1")] +use { + crate::{ + sec1::{EncodedPoint, ModulusSize, ValidatePublicKey}, + FieldBytesSize, + }, + sec1::der::{self, oid::AssociatedOid}, +}; + +#[cfg(all(feature = "alloc", feature = "arithmetic", feature = "sec1"))] +use { + crate::{ + sec1::{FromEncodedPoint, ToEncodedPoint}, + AffinePoint, + }, + alloc::vec::Vec, + sec1::der::Encode, +}; + +#[cfg(all(feature = "arithmetic", any(feature = "jwk", feature = "pem")))] +use alloc::string::String; + +#[cfg(all(feature = "arithmetic", feature = "jwk"))] +use alloc::string::ToString; + +#[cfg(all(doc, feature = "pkcs8"))] +use {crate::pkcs8::DecodePrivateKey, core::str::FromStr}; + +/// Elliptic curve secret keys. +/// +/// This type wraps a secret scalar value, helping to prevent accidental +/// exposure and securely erasing the value from memory when dropped. +/// +/// # Parsing PKCS#8 Keys +/// +/// PKCS#8 is a commonly used format for encoding secret keys (especially ones +/// generated by OpenSSL). +/// +/// Keys in PKCS#8 format are either binary (ASN.1 BER/DER), or PEM encoded +/// (ASCII) and begin with the following: +/// +/// ```text +/// -----BEGIN PRIVATE KEY----- +/// ``` +/// +/// To decode an elliptic curve private key from PKCS#8, enable the `pkcs8` +/// feature of this crate (or the `pkcs8` feature of a specific RustCrypto +/// elliptic curve crate) and use the [`DecodePrivateKey`] trait to parse it. +/// +/// When the `pem` feature of this crate (or a specific RustCrypto elliptic +/// curve crate) is enabled, a [`FromStr`] impl is also available. +#[derive(Clone)] +pub struct SecretKey { + /// Scalar value + inner: ScalarPrimitive, +} + +impl SecretKey +where + C: Curve, +{ + /// Minimum allowed size of an elliptic curve secret key in bytes. + /// + /// This provides the equivalent of 96-bits of symmetric security. + const MIN_SIZE: usize = 24; + + /// Generate a random [`SecretKey`]. + #[cfg(feature = "arithmetic")] + pub fn random(rng: &mut impl CryptoRngCore) -> Self + where + C: CurveArithmetic, + { + Self { + inner: NonZeroScalar::::random(rng).into(), + } + } + + /// Create a new secret key from a scalar value. + pub fn new(scalar: ScalarPrimitive) -> Self { + Self { inner: scalar } + } + + /// Borrow the inner secret [`ScalarPrimitive`] value. + /// + /// # ⚠️ Warning + /// + /// This value is key material. + /// + /// Please treat it with the care it deserves! + pub fn as_scalar_primitive(&self) -> &ScalarPrimitive { + &self.inner + } + + /// Get the secret [`NonZeroScalar`] value for this key. + /// + /// # ⚠️ Warning + /// + /// This value is key material. + /// + /// Please treat it with the care it deserves! + #[cfg(feature = "arithmetic")] + pub fn to_nonzero_scalar(&self) -> NonZeroScalar + where + C: CurveArithmetic, + { + self.into() + } + + /// Get the [`PublicKey`] which corresponds to this secret key + #[cfg(feature = "arithmetic")] + pub fn public_key(&self) -> PublicKey + where + C: CurveArithmetic, + { + PublicKey::from_secret_scalar(&self.to_nonzero_scalar()) + } + + /// Deserialize secret key from an encoded secret scalar. + pub fn from_bytes(bytes: &FieldBytes) -> Result { + let inner = ScalarPrimitive::::from_bytes(bytes) + .into_option() + .ok_or(Error)?; + + if inner.is_zero().into() { + return Err(Error); + } + + Ok(Self { inner }) + } + + /// Deserialize secret key from an encoded secret scalar passed as a byte slice. + /// + /// The slice is expected to be a minimum of 24-bytes (192-bytes) and at most + /// `C::FieldBytesSize` bytes in length. + /// + /// Byte slices shorter than the field size are handled by zero padding the input. + /// + /// NOTE: this function is variable-time with respect to the input length. To avoid a timing + /// sidechannel, always ensure that the input has been pre-padded to `C::FieldBytesSize`. + pub fn from_slice(slice: &[u8]) -> Result { + if let Ok(field_bytes) = <&FieldBytes>::try_from(slice) { + Self::from_bytes(field_bytes) + } else if (Self::MIN_SIZE..C::FieldBytesSize::USIZE).contains(&slice.len()) { + let mut bytes = Zeroizing::new(FieldBytes::::default()); + let offset = C::FieldBytesSize::USIZE.saturating_sub(slice.len()); + bytes[offset..].copy_from_slice(slice); + Self::from_bytes(&bytes) + } else { + Err(Error) + } + } + + /// Serialize raw secret scalar as a big endian integer. + pub fn to_bytes(&self) -> FieldBytes { + self.inner.to_bytes() + } + + /// Deserialize secret key encoded in the SEC1 ASN.1 DER `ECPrivateKey` format. + #[cfg(feature = "sec1")] + pub fn from_sec1_der(der_bytes: &[u8]) -> Result + where + C: AssociatedOid + Curve + ValidatePublicKey, + FieldBytesSize: ModulusSize, + { + sec1::EcPrivateKey::try_from(der_bytes)? + .try_into() + .map_err(|_| Error) + } + + /// Serialize secret key in the SEC1 ASN.1 DER `ECPrivateKey` format. + #[cfg(all(feature = "alloc", feature = "arithmetic", feature = "sec1"))] + pub fn to_sec1_der(&self) -> der::Result>> + where + C: AssociatedOid + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + { + let private_key_bytes = Zeroizing::new(self.to_bytes()); + let public_key_bytes = self.public_key().to_encoded_point(false); + let parameters = sec1::EcParameters::NamedCurve(C::OID); + + let ec_private_key = Zeroizing::new( + sec1::EcPrivateKey { + private_key: &private_key_bytes, + parameters: Some(parameters), + public_key: Some(public_key_bytes.as_bytes()), + } + .to_der()?, + ); + + Ok(ec_private_key) + } + + /// Parse [`SecretKey`] from PEM-encoded SEC1 `ECPrivateKey` format. + /// + /// PEM-encoded SEC1 keys can be identified by the leading delimiter: + /// + /// ```text + /// -----BEGIN EC PRIVATE KEY----- + /// ``` + #[cfg(feature = "pem")] + pub fn from_sec1_pem(s: &str) -> Result + where + C: AssociatedOid + Curve + ValidatePublicKey, + FieldBytesSize: ModulusSize, + { + let (label, der_bytes) = pem::decode_vec(s.as_bytes()).map_err(|_| Error)?; + + if label != sec1::EcPrivateKey::PEM_LABEL { + return Err(Error); + } + + Self::from_sec1_der(&der_bytes).map_err(|_| Error) + } + + /// Serialize private key as self-zeroizing PEM-encoded SEC1 `ECPrivateKey` + /// with the given [`pem::LineEnding`]. + /// + /// Pass `Default::default()` to use the OS's native line endings. + #[cfg(feature = "pem")] + pub fn to_sec1_pem(&self, line_ending: pem::LineEnding) -> Result> + where + C: AssociatedOid + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + { + self.to_sec1_der() + .ok() + .and_then(|der| { + pem::encode_string(sec1::EcPrivateKey::PEM_LABEL, line_ending, &der).ok() + }) + .map(Zeroizing::new) + .ok_or(Error) + } + + /// Parse a [`JwkEcKey`] JSON Web Key (JWK) into a [`SecretKey`]. + #[cfg(feature = "jwk")] + pub fn from_jwk(jwk: &JwkEcKey) -> Result + where + C: JwkParameters + ValidatePublicKey, + FieldBytesSize: ModulusSize, + { + Self::try_from(jwk) + } + + /// Parse a string containing a JSON Web Key (JWK) into a [`SecretKey`]. + #[cfg(feature = "jwk")] + pub fn from_jwk_str(jwk: &str) -> Result + where + C: JwkParameters + ValidatePublicKey, + FieldBytesSize: ModulusSize, + { + jwk.parse::().and_then(|jwk| Self::from_jwk(&jwk)) + } + + /// Serialize this secret key as [`JwkEcKey`] JSON Web Key (JWK). + #[cfg(all(feature = "arithmetic", feature = "jwk"))] + pub fn to_jwk(&self) -> JwkEcKey + where + C: CurveArithmetic + JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + { + self.into() + } + + /// Serialize this secret key as JSON Web Key (JWK) string. + #[cfg(all(feature = "arithmetic", feature = "jwk"))] + pub fn to_jwk_string(&self) -> Zeroizing + where + C: CurveArithmetic + JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + { + Zeroizing::new(self.to_jwk().to_string()) + } +} + +impl ConstantTimeEq for SecretKey +where + C: Curve, +{ + fn ct_eq(&self, other: &Self) -> Choice { + self.inner.ct_eq(&other.inner) + } +} + +impl Debug for SecretKey +where + C: Curve, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(core::any::type_name::()) + .finish_non_exhaustive() + } +} + +impl ZeroizeOnDrop for SecretKey where C: Curve {} + +impl Drop for SecretKey +where + C: Curve, +{ + fn drop(&mut self) { + self.inner.zeroize(); + } +} + +impl Eq for SecretKey {} + +impl PartialEq for SecretKey +where + C: Curve, +{ + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +#[cfg(feature = "sec1")] +impl TryFrom> for SecretKey +where + C: AssociatedOid + Curve + ValidatePublicKey, + FieldBytesSize: ModulusSize, +{ + type Error = der::Error; + + fn try_from(sec1_private_key: sec1::EcPrivateKey<'_>) -> der::Result { + if let Some(sec1::EcParameters::NamedCurve(curve_oid)) = sec1_private_key.parameters { + if C::OID != curve_oid { + return Err(der::Tag::ObjectIdentifier.value_error()); + } + } + + let secret_key = Self::from_slice(sec1_private_key.private_key) + .map_err(|_| der::Tag::OctetString.value_error())?; + + if let Some(pk_bytes) = sec1_private_key.public_key { + let pk = EncodedPoint::::from_bytes(pk_bytes) + .map_err(|_| der::Tag::BitString.value_error())?; + + if C::validate_public_key(&secret_key, &pk).is_err() { + return Err(der::Tag::BitString.value_error()); + } + } + + Ok(secret_key) + } +} + +#[cfg(feature = "arithmetic")] +impl From> for SecretKey +where + C: CurveArithmetic, +{ + fn from(scalar: NonZeroScalar) -> SecretKey { + SecretKey::from(&scalar) + } +} + +#[cfg(feature = "arithmetic")] +impl From<&NonZeroScalar> for SecretKey +where + C: CurveArithmetic, +{ + fn from(scalar: &NonZeroScalar) -> SecretKey { + SecretKey { + inner: scalar.into(), + } + } +} diff --git a/elliptic-curve/src/secret_key/pkcs8.rs b/elliptic-curve/src/secret_key/pkcs8.rs new file mode 100644 index 000000000..433926f55 --- /dev/null +++ b/elliptic-curve/src/secret_key/pkcs8.rs @@ -0,0 +1,107 @@ +//! PKCS#8 encoding/decoding support. + +use super::SecretKey; +use crate::{ + pkcs8::{der::Decode, AssociatedOid}, + sec1::{ModulusSize, ValidatePublicKey}, + Curve, FieldBytesSize, ALGORITHM_OID, +}; +use pkcs8::spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier, ObjectIdentifier}; +use sec1::EcPrivateKey; + +// Imports for the `EncodePrivateKey` impl +#[cfg(all(feature = "alloc", feature = "arithmetic"))] +use { + crate::{ + sec1::{FromEncodedPoint, ToEncodedPoint}, + AffinePoint, CurveArithmetic, + }, + pkcs8::{ + der::{self, asn1::OctetStringRef, Encode}, + EncodePrivateKey, + }, + zeroize::Zeroizing, +}; + +// Imports for actual PEM support +#[cfg(feature = "pem")] +use { + crate::{error::Error, Result}, + core::str::FromStr, + pkcs8::DecodePrivateKey, +}; + +impl AssociatedAlgorithmIdentifier for SecretKey +where + C: AssociatedOid + Curve, +{ + type Params = ObjectIdentifier; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifier = AlgorithmIdentifier { + oid: ALGORITHM_OID, + parameters: Some(C::OID), + }; +} + +impl TryFrom> for SecretKey +where + C: AssociatedOid + Curve + ValidatePublicKey, + FieldBytesSize: ModulusSize, +{ + type Error = pkcs8::Error; + + fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result { + private_key_info + .algorithm + .assert_oids(ALGORITHM_OID, C::OID)?; + + Ok(EcPrivateKey::from_der(private_key_info.private_key.as_bytes())?.try_into()?) + } +} + +#[cfg(all(feature = "alloc", feature = "arithmetic"))] +impl EncodePrivateKey for SecretKey +where + C: AssociatedOid + CurveArithmetic, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn to_pkcs8_der(&self) -> pkcs8::Result { + let algorithm_identifier = pkcs8::AlgorithmIdentifierRef { + oid: ALGORITHM_OID, + parameters: Some((&C::OID).into()), + }; + + let private_key_bytes = Zeroizing::new(self.to_bytes()); + let public_key_bytes = self.public_key().to_encoded_point(false); + + // TODO(tarcieri): unify with `to_sec1_der()` by building an owned `EcPrivateKey` + let ec_private_key = Zeroizing::new( + EcPrivateKey { + private_key: &private_key_bytes, + parameters: None, + public_key: Some(public_key_bytes.as_bytes()), + } + .to_der()?, + ); + + let pkcs8_key = pkcs8::PrivateKeyInfoRef::new( + algorithm_identifier, + OctetStringRef::new(&ec_private_key)?, + ); + Ok(der::SecretDocument::encode_msg(&pkcs8_key)?) + } +} + +#[cfg(feature = "pem")] +impl FromStr for SecretKey +where + C: Curve + AssociatedOid + ValidatePublicKey, + FieldBytesSize: ModulusSize, +{ + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::from_pkcs8_pem(s).map_err(|_| Error) + } +} diff --git a/elliptic-curve/src/voprf.rs b/elliptic-curve/src/voprf.rs new file mode 100644 index 000000000..68f2abf18 --- /dev/null +++ b/elliptic-curve/src/voprf.rs @@ -0,0 +1,20 @@ +//! Verifiable Oblivious Pseudorandom Function (VOPRF) using prime order groups +//! +//! + +use crate::PrimeCurve; + +/// Elliptic curve parameters used by VOPRF. +pub trait VoprfParameters: PrimeCurve { + /// The `ID` parameter which identifies a particular elliptic curve + /// as defined in [section 4 of `draft-irtf-cfrg-voprf-19`][voprf]. + /// + /// [voprf]: https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-19.html#name-ciphersuites-2 + const ID: &'static str; + + /// The `Hash` parameter which assigns a particular hash function to this + /// ciphersuite as defined in [section 4 of `draft-irtf-cfrg-voprf-19`][voprf]. + /// + /// [voprf]: https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-19.html#name-ciphersuites-2 + type Hash: digest::Digest; +} diff --git a/elliptic-curve/src/weierstrass.rs b/elliptic-curve/src/weierstrass.rs new file mode 100644 index 000000000..1782d95e1 --- /dev/null +++ b/elliptic-curve/src/weierstrass.rs @@ -0,0 +1,128 @@ +//! Complete projective formulas for prime order elliptic curves as described +//! in [Renes-Costello-Batina 2015]. +//! +//! [Renes-Costello-Batina 2015]: https://eprint.iacr.org/2015/1060 + +#![allow(clippy::op_ref)] + +use ff::Field; + +/// Affine point whose coordinates are represented by the given field element. +pub type AffinePoint = (Fe, Fe); + +/// Projective point whose coordinates are represented by the given field element. +pub type ProjectivePoint = (Fe, Fe, Fe); + +/// Implements the complete addition formula from [Renes-Costello-Batina 2015] +/// (Algorithm 4). +/// +/// [Renes-Costello-Batina 2015]: https://eprint.iacr.org/2015/1060 +#[inline(always)] +pub fn add( + (ax, ay, az): ProjectivePoint, + (bx, by, bz): ProjectivePoint, + curve_equation_b: Fe, +) -> ProjectivePoint +where + Fe: Field, +{ + // The comments after each line indicate which algorithm steps are being + // performed. + let xx = ax * bx; // 1 + let yy = ay * by; // 2 + let zz = az * bz; // 3 + let xy_pairs = ((ax + ay) * &(bx + by)) - &(xx + &yy); // 4, 5, 6, 7, 8 + let yz_pairs = ((ay + az) * &(by + bz)) - &(yy + &zz); // 9, 10, 11, 12, 13 + let xz_pairs = ((ax + az) * &(bx + bz)) - &(xx + &zz); // 14, 15, 16, 17, 18 + + let bzz_part = xz_pairs - &(curve_equation_b * &zz); // 19, 20 + let bzz3_part = bzz_part.double() + &bzz_part; // 21, 22 + let yy_m_bzz3 = yy - &bzz3_part; // 23 + let yy_p_bzz3 = yy + &bzz3_part; // 24 + + let zz3 = zz.double() + &zz; // 26, 27 + let bxz_part = (curve_equation_b * &xz_pairs) - &(zz3 + &xx); // 25, 28, 29 + let bxz3_part = bxz_part.double() + &bxz_part; // 30, 31 + let xx3_m_zz3 = xx.double() + &xx - &zz3; // 32, 33, 34 + + ( + (yy_p_bzz3 * &xy_pairs) - &(yz_pairs * &bxz3_part), // 35, 39, 40 + (yy_p_bzz3 * &yy_m_bzz3) + &(xx3_m_zz3 * &bxz3_part), // 36, 37, 38 + (yy_m_bzz3 * &yz_pairs) + &(xy_pairs * &xx3_m_zz3), // 41, 42, 43 + ) +} + +/// Implements the complete mixed addition formula from +/// [Renes-Costello-Batina 2015] (Algorithm 5). +/// +/// [Renes-Costello-Batina 2015]: https://eprint.iacr.org/2015/1060 +#[inline(always)] +pub fn add_mixed( + (ax, ay, az): ProjectivePoint, + (bx, by): AffinePoint, + curve_equation_b: Fe, +) -> ProjectivePoint +where + Fe: Field, +{ + // The comments after each line indicate which algorithm steps are being + // performed. + let xx = ax * &bx; // 1 + let yy = ay * &by; // 2 + let xy_pairs = ((ax + &ay) * &(bx + &by)) - &(xx + &yy); // 3, 4, 5, 6, 7 + let yz_pairs = (by * &az) + &ay; // 8, 9 (t4) + let xz_pairs = (bx * &az) + &ax; // 10, 11 (y3) + + let bz_part = xz_pairs - &(curve_equation_b * &az); // 12, 13 + let bz3_part = bz_part.double() + &bz_part; // 14, 15 + let yy_m_bzz3 = yy - &bz3_part; // 16 + let yy_p_bzz3 = yy + &bz3_part; // 17 + + let z3 = az.double() + &az; // 19, 20 + let bxz_part = (curve_equation_b * &xz_pairs) - &(z3 + &xx); // 18, 21, 22 + let bxz3_part = bxz_part.double() + &bxz_part; // 23, 24 + let xx3_m_zz3 = xx.double() + &xx - &z3; // 25, 26, 27 + + ( + (yy_p_bzz3 * &xy_pairs) - &(yz_pairs * &bxz3_part), // 28, 32, 33 + (yy_p_bzz3 * &yy_m_bzz3) + &(xx3_m_zz3 * &bxz3_part), // 29, 30, 31 + (yy_m_bzz3 * &yz_pairs) + &(xy_pairs * &xx3_m_zz3), // 34, 35, 36 + ) +} + +/// Implements the exception-free point doubling formula from +/// [Renes-Costello-Batina 2015] (Algorithm 6). +/// +/// [Renes-Costello-Batina 2015]: https://eprint.iacr.org/2015/1060 +#[inline(always)] +pub fn double((x, y, z): ProjectivePoint, curve_equation_b: Fe) -> ProjectivePoint +where + Fe: Field, +{ + // The comments after each line indicate which algorithm steps are being + // performed. + let xx = x.square(); // 1 + let yy = y.square(); // 2 + let zz = z.square(); // 3 + let xy2 = (x * &y).double(); // 4, 5 + let xz2 = (x * &z).double(); // 6, 7 + + let bzz_part = (curve_equation_b * &zz) - &xz2; // 8, 9 + let bzz3_part = bzz_part.double() + &bzz_part; // 10, 11 + let yy_m_bzz3 = yy - &bzz3_part; // 12 + let yy_p_bzz3 = yy + &bzz3_part; // 13 + let y_frag = yy_p_bzz3 * &yy_m_bzz3; // 14 + let x_frag = yy_m_bzz3 * &xy2; // 15 + + let zz3 = zz.double() + &zz; // 16, 17 + let bxz2_part = (curve_equation_b * &xz2) - &(zz3 + &xx); // 18, 19, 20 + let bxz6_part = bxz2_part.double() + &bxz2_part; // 21, 22 + let xx3_m_zz3 = xx.double() + &xx - &zz3; // 23, 24, 25 + + let dy = y_frag + &(xx3_m_zz3 * &bxz6_part); // 26, 27 + let yz2 = (y * &z).double(); // 28, 29 + let dx = x_frag - &(bxz6_part * &yz2); // 30, 31 + let dz = (yz2 * &yy).double().double(); // 32, 33, 34 + + (dx, dy, dz) +} diff --git a/elliptic-curve/tests/examples/pkcs8-private-key.der b/elliptic-curve/tests/examples/pkcs8-private-key.der new file mode 100644 index 000000000..c0de45ef2 Binary files /dev/null and b/elliptic-curve/tests/examples/pkcs8-private-key.der differ diff --git a/elliptic-curve/tests/examples/pkcs8-private-key.pem b/elliptic-curve/tests/examples/pkcs8-private-key.pem new file mode 100644 index 000000000..62f432c74 --- /dev/null +++ b/elliptic-curve/tests/examples/pkcs8-private-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgaWJBcVYaYzQN4OfY +afKgVJJVjhoEhotqn4VKhmeIGI2hRANCAAQcrP+1Xy8s79idies3SyaBFSRSgC3u +oJkWBoE32DnPf8SBpESSME1+9mrBF77+g6jQjxVfK1L59hjdRHApBI4P +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/elliptic-curve/tests/examples/pkcs8-public-key.der b/elliptic-curve/tests/examples/pkcs8-public-key.der new file mode 100644 index 000000000..67c719c76 Binary files /dev/null and b/elliptic-curve/tests/examples/pkcs8-public-key.der differ diff --git a/elliptic-curve/tests/examples/pkcs8-public-key.pem b/elliptic-curve/tests/examples/pkcs8-public-key.pem new file mode 100644 index 000000000..ee7e5b612 --- /dev/null +++ b/elliptic-curve/tests/examples/pkcs8-public-key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHKz/tV8vLO/YnYnrN0smgRUkUoAt +7qCZFgaBN9g5z3/EgaREkjBNfvZqwRe+/oOo0I8VXytS+fYY3URwKQSODw== +-----END PUBLIC KEY----- diff --git a/elliptic-curve/tests/pkcs8.rs b/elliptic-curve/tests/pkcs8.rs new file mode 100644 index 000000000..2a27fc0c7 --- /dev/null +++ b/elliptic-curve/tests/pkcs8.rs @@ -0,0 +1,57 @@ +//! PKCS#8 tests + +#![cfg(all(feature = "dev", feature = "pkcs8"))] + +use elliptic_curve::{ + dev::{PublicKey, SecretKey}, + pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey}, + sec1::ToEncodedPoint, +}; +use hex_literal::hex; +use pkcs8::der; + +/// DER-encoded PKCS#8 public key +const PKCS8_PUBLIC_KEY_DER: &[u8; 91] = include_bytes!("examples/pkcs8-public-key.der"); + +/// PEM-encoded PKCS#8 public key +#[cfg(feature = "pem")] +const PKCS8_PUBLIC_KEY_PEM: &str = include_str!("examples/pkcs8-public-key.pem"); + +/// Example encoded scalar value +const EXAMPLE_SCALAR: [u8; 32] = + hex!("AABBCCDDEEFF0000000000000000000000000000000000000000000000000001"); + +/// Example PKCS#8 private key +fn example_private_key() -> der::SecretDocument { + SecretKey::from_slice(&EXAMPLE_SCALAR) + .unwrap() + .to_pkcs8_der() + .unwrap() +} + +#[test] +fn decode_pkcs8_private_key_from_der() { + dbg!(example_private_key().as_bytes()); + let secret_key = SecretKey::from_pkcs8_der(example_private_key().as_bytes()).unwrap(); + assert_eq!(secret_key.to_bytes().as_slice(), &EXAMPLE_SCALAR); +} + +#[test] +fn decode_pkcs8_public_key_from_der() { + let public_key = PublicKey::from_public_key_der(&PKCS8_PUBLIC_KEY_DER[..]).unwrap(); + let expected_sec1_point = hex!("041CACFFB55F2F2CEFD89D89EB374B2681152452802DEEA09916068137D839CF7FC481A44492304D7EF66AC117BEFE83A8D08F155F2B52F9F618DD447029048E0F"); + assert_eq!( + public_key.to_encoded_point(false).as_bytes(), + &expected_sec1_point[..] + ); +} + +#[test] +#[cfg(feature = "pem")] +fn decode_pkcs8_public_key_from_pem() { + let public_key = PKCS8_PUBLIC_KEY_PEM.parse::().unwrap(); + + // Ensure key parses equivalently to DER + let der_key = PublicKey::from_public_key_der(&PKCS8_PUBLIC_KEY_DER[..]).unwrap(); + assert_eq!(public_key, der_key); +} diff --git a/elliptic-curve/tests/secret_key.rs b/elliptic-curve/tests/secret_key.rs new file mode 100644 index 000000000..7be475143 --- /dev/null +++ b/elliptic-curve/tests/secret_key.rs @@ -0,0 +1,28 @@ +//! Secret key tests + +#![cfg(feature = "dev")] + +use elliptic_curve::dev::SecretKey; + +#[test] +fn from_empty_slice() { + assert!(SecretKey::from_slice(&[]).is_err()); +} + +#[test] +fn from_slice_expected_size() { + let bytes = [1u8; 32]; + assert!(SecretKey::from_slice(&bytes).is_ok()); +} + +#[test] +fn from_slice_allowed_short() { + let bytes = [1u8; 24]; + assert!(SecretKey::from_slice(&bytes).is_ok()); +} + +#[test] +fn from_slice_too_short() { + let bytes = [1u8; 23]; // min 24-bytes + assert!(SecretKey::from_slice(&bytes).is_err()); +} diff --git a/kem/CHANGELOG.md b/kem/CHANGELOG.md new file mode 100644 index 000000000..103b251f0 --- /dev/null +++ b/kem/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.2.0 (2022-05-26) +### Added +- Generic `SharedSecret` type ([#982]) +- `EncappedKey::EncappedKeySize` associated constant ([#982]) + +### Changed +- Rename `EncappedKey::NSecret` => `EncappedKey::SharedSecretSize` ([#982]) +- Add `EncappedKey::{from_bytes, as_bytes}` methods ([#982]) + +[#982]: https://github.com/RustCrypto/traits/pull/982 + +## 0.1.0 (2022-01-03) +- Initial release diff --git a/kem/Cargo.toml b/kem/Cargo.toml new file mode 100644 index 000000000..e4e1c7fb9 --- /dev/null +++ b/kem/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "kem" +description = "Traits for key encapsulation mechanisms" +version = "0.3.0-pre.0" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +documentation = "https://docs.rs/kem" +homepage = "https://github.com/RustCrypto/traits/tree/master/kem" +repository = "https://github.com/RustCrypto/traits" +readme = "README.md" +edition = "2021" +keywords = ["crypto"] +categories = ["cryptography", "no-std"] +rust-version = "1.66" + +[dependencies] +rand_core = "0.6" +zeroize = { version = "1.7", default-features = false } + +[dev-dependencies] +hpke = "0.12" +p256 = { version = "0.9", features = ["ecdsa"] } +pqcrypto = { version = "0.15", default-features = false, features = [ + "pqcrypto-saber", +] } +pqcrypto-traits = "0.3" +rand = { version = "0.8" } +x3dh-ke = "0.1" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lib] +doctest = false diff --git a/kem/LICENSE-APACHE b/kem/LICENSE-APACHE new file mode 100644 index 000000000..7a88b44dc --- /dev/null +++ b/kem/LICENSE-APACHE @@ -0,0 +1,13 @@ +Copyright 2021 RustCrypto Developers + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/kem/LICENSE-MIT b/kem/LICENSE-MIT new file mode 100644 index 000000000..a2b36f25c --- /dev/null +++ b/kem/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2021-2022 RustCrypto Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/kem/README.md b/kem/README.md new file mode 100644 index 000000000..8ca46b4ed --- /dev/null +++ b/kem/README.md @@ -0,0 +1,118 @@ +# [RustCrypto]: Key Encapsulation Mechanisms (KEMs) + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +This crate provides a common set of traits for [key encapsulation mechanisms][1]—algorithms for non-interactively establishing secrets between peers. This is intended to be implemented by libraries which produce or contain implementations of key encapsulation mechanisms, and used by libraries which want to produce or consume encapsulated secrets while generically supporting any compatible backend. + +The crate exposes two traits, `Encapsulate` and `Decapsulate`, which are both generic over the encapsulated key type and the shared secret type. They are also agnostic about the structure of `Self`. For example, a simple Saber implementation may just impl `Encapsulate` for a single public key: +```rust +// Must make a newtype to implement the trait +struct MyPubkey(SaberPublicKey); + +impl Encapsulate for MyPubkey { + // Encapsulation is infallible + type Error = !; + + fn encapsulate( + &self, + csprng: impl CryptoRngCore, + ) -> Result<(SaberEncappedKey, SaberSharedSecret), !> { + let (ss, ek) = saber_encapsulate(&csprng, &self.0); + Ok((ek, ss)) + } +} +``` +And on the other end of complexity, an [X3DH](https://www.signal.org/docs/specifications/x3dh/) implementation might impl `Encapsulate` for a public key bundle plus a sender identity key: +```rust +struct PubkeyBundle { + ik: IdentityPubkey, + spk: SignedPrePubkey, + sig: Signature, + opk: OneTimePrePubkey, +} + +// Encap context is the recipient's pubkeys and the sender's identity key +struct EncapContext(PubkeyBundle, IdentityPrivkey); + +impl Encapsulate for EncapContext { + // Encapsulation fails if signature verification fails + type Error = SigError; + + fn encapsulate( + &self, + csprng: impl CryptoRngCore, + ) -> Result<(EphemeralKey, SharedSecret), Self::Error> { + // Make a new ephemeral key. This will be the encapped key + let ek = EphemeralKey::gen(&mut csprng); + + // Deconstruct the recipient's pubkey bundle + let PubkeyBundle { + ref ik, + ref spk, + ref sig, + ref opk, + } = self.0; + let my_ik = &self.1; + + // Verify the signature + self.0.verify(&sig, &some_sig_pubkey)?; + + // Do the X3DH operation to get the shared secret + let shared_secret = x3dh_a(sig, my_ik, spk, &ek, ik, opk)?; + + Ok((ek, shared_secret)) + } +} +``` + +[Documentation][docs-link] + +## Minimum Supported Rust Version + +Rust **1.66** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## License + +Licensed under either of + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/kem.svg +[crate-link]: https://crates.io/crates/kem +[docs-image]: https://docs.rs/kem/badge.svg +[docs-link]: https://docs.rs/kem/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.66+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260048-signatures +[build-image]: https://github.com/RustCrypto/traits/workflows/kem/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/traits/actions?query=workflow%3Akem + +[//]: # (links) + +[RustCrypto]: https://github.com/RustCrypto +[1]: https://en.wikipedia.org/wiki/Key_encapsulation diff --git a/kem/src/lib.rs b/kem/src/lib.rs new file mode 100644 index 000000000..dc009d077 --- /dev/null +++ b/kem/src/lib.rs @@ -0,0 +1,36 @@ +#![doc = include_str!("../README.md")] +#![no_std] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_root_url = "https://docs.rs/kem" +)] +#![forbid(unsafe_code)] +#![warn(missing_docs, unused_qualifications, missing_debug_implementations)] + +use core::fmt::Debug; +use rand_core::CryptoRngCore; + +/// A value that can be encapsulated to. Often, this will just be a public key. However, it can +/// also be a bundle of public keys, or it can include a sender's private key for authenticated +/// encapsulation. +pub trait Encapsulate { + /// Encapsulation error + type Error: Debug; + + /// Encapsulates a fresh shared secret + fn encapsulate(&self, rng: &mut impl CryptoRngCore) -> Result<(EK, SS), Self::Error>; +} + +/// A value that can be used to decapsulate an encapsulated key. +/// +/// Often, this will just be a secret key. But, as with [`Encapsulate`], it can be a bundle +/// of secret keys, or it can include a sender's private key for authenticated encapsulation. +pub trait Decapsulate { + /// Decapsulation error + type Error: Debug; + + /// Decapsulates the given encapsulated key + fn decapsulate(&self, encapsulated_key: &EK) -> Result; +} diff --git a/kem/tests/hpke.rs b/kem/tests/hpke.rs new file mode 100644 index 000000000..43ad61c9d --- /dev/null +++ b/kem/tests/hpke.rs @@ -0,0 +1,53 @@ +use kem::{Decapsulate, Encapsulate}; + +use hpke::{ + kem::{Kem as KemTrait, X25519HkdfSha256}, + HpkeError, +}; +use rand_core::{CryptoRng, CryptoRngCore, RngCore}; + +type SharedSecret = hpke::kem::SharedSecret; +type EncappedKey = ::EncappedKey; + +// We have to define a newtype for the public and private keys because we're gonna impl +// the Encapsulate and Decapsulate traits for them +struct PublicKey(::PublicKey); +struct PrivateKey(::PrivateKey); + +impl Encapsulate for PublicKey { + type Error = HpkeError; + + fn encapsulate( + &self, + mut csprng: &mut impl CryptoRngCore, + ) -> Result<(EncappedKey, SharedSecret), HpkeError> { + ::encap(&self.0, None, &mut csprng).map(|(ek, ss)| (ss, ek)) + } +} + +impl Decapsulate for PrivateKey { + type Error = HpkeError; + + fn decapsulate(&self, encapped_key: &EncappedKey) -> Result { + ::decap(&self.0, None, encapped_key) + } +} + +// A simple wrapper around the keypair generation function +fn gen_keypair(csprng: &mut R) -> (PrivateKey, PublicKey) { + let (sk, pk) = X25519HkdfSha256::gen_keypair(csprng); + (PrivateKey(sk), PublicKey(pk)) +} + +#[test] +fn test_hpke() { + let mut rng = rand::thread_rng(); + + // Make a recipient's keypair + let (sk_recip, pk_recip) = gen_keypair(&mut rng); + + // Encapsulate to the recipient. Check that the derived shared secrets are equal + let (ek, ss1) = pk_recip.encapsulate(&mut rng).unwrap(); + let ss2 = sk_recip.decapsulate(&ek).unwrap(); + assert_eq!(ss1.0, ss2.0); +} diff --git a/kem/tests/saber.rs b/kem/tests/saber.rs new file mode 100644 index 000000000..13e85ef6b --- /dev/null +++ b/kem/tests/saber.rs @@ -0,0 +1,53 @@ +use kem::{Decapsulate, Encapsulate}; + +use pqcrypto::kem::firesaber::{ + decapsulate, encapsulate, keypair, Ciphertext as SaberEncappedKey, PublicKey, SecretKey, + SharedSecret as SaberSharedSecret, +}; +use rand_core::CryptoRngCore; + +// We have to define a newtype for the public and private keys because we're gonna impl +// the Encapsulate and Decapsulate traits for them +struct SaberPublicKey(PublicKey); +struct SaberPrivateKey(SecretKey); + +impl Encapsulate for SaberPublicKey { + // TODO: Encapsulation is infallible. Make this the never type once it's available + type Error = (); + + fn encapsulate( + &self, + _: &mut impl CryptoRngCore, + ) -> Result<(SaberEncappedKey, SaberSharedSecret), ()> { + let (ss, ek) = encapsulate(&self.0); + Ok((ek, ss)) + } +} + +impl Decapsulate for SaberPrivateKey { + // TODO: Decapsulation is infallible. Make this the never type once it's available + type Error = (); + + fn decapsulate(&self, ek: &SaberEncappedKey) -> Result { + Ok(decapsulate(ek, &self.0)) + } +} + +fn gen_keypair() -> (SaberPublicKey, SaberPrivateKey) { + let (pk, sk) = keypair(); + (SaberPublicKey(pk), SaberPrivateKey(sk)) +} + +#[test] +fn test_saber() { + use pqcrypto_traits::kem::SharedSecret as _; + let mut rng = rand::thread_rng(); + + // Make a recipient keypair + let (pk_recip, sk_recip) = gen_keypair(); + + // Encapsulate and decapsulate. Assert that the shared secrets are equal + let (ek, ss1) = pk_recip.encapsulate(&mut rng).unwrap(); + let ss2 = sk_recip.decapsulate(&ek).unwrap(); + assert_eq!(ss1.as_bytes(), ss2.as_bytes()); +} diff --git a/kem/tests/x3dh.rs b/kem/tests/x3dh.rs new file mode 100644 index 000000000..b39a5adf8 --- /dev/null +++ b/kem/tests/x3dh.rs @@ -0,0 +1,113 @@ +use kem::{Decapsulate, Encapsulate}; + +use p256::ecdsa::Signature; +use rand_core::CryptoRngCore; +use x3dh_ke::{x3dh_a, x3dh_b, EphemeralKey, IdentityKey, Key, OneTimePreKey, SignedPreKey}; + +/// The shared secret type defined by x3dh_ke +type SharedSecret = [u8; 32]; + +// Define the recipient privkey type. This is a bundle of 3 privkeys of different lifespans +struct X3DhPrivkeyBundle { + ik: IdentityKey, + spk: SignedPreKey, + sig: Signature, + opk: OneTimePreKey, +} + +impl X3DhPrivkeyBundle { + fn gen() -> X3DhPrivkeyBundle { + // The default() method does actual key generation here + let ik = IdentityKey::default(); + let spk = SignedPreKey::default(); + let sig = ik.sign(&spk.pk_to_bytes()); + let opk = OneTimePreKey::default(); + X3DhPrivkeyBundle { ik, spk, sig, opk } + } + fn as_pubkeys(&self) -> X3DhPubkeyBundle { + X3DhPubkeyBundle { + ik: self.ik.strip(), + spk: self.spk.strip(), + opk: self.opk.strip(), + sig: self.sig, + } + } +} + +// The pubkeys keys associated with a privkey bundle. In x3dh-ke, all the keys serve as both +// pubkeys and privkeys. This seems dangerous but hey this isn't prod. +type X3DhPubkeyBundle = X3DhPrivkeyBundle; + +/// To encap, we need the recipient's public keys and the sender's private key +struct EncapContext(X3DhPubkeyBundle, IdentityKey); + +/// To decap, we need the recipient's private keys and the sender's public key +struct DecapContext(X3DhPrivkeyBundle, IdentityKey); + +// Define an authenticated encapsulator. To authenticate, we need a full sender keypair. +impl Encapsulate for EncapContext { + type Error = &'static str; + + fn encapsulate( + &self, + _: &mut impl CryptoRngCore, + ) -> Result<(EphemeralKey, SharedSecret), Self::Error> { + // Make a new ephemeral key. This will be the encapped key + let ek = EphemeralKey::default(); + // Deconstruct the recipient's pubkey bundle + let X3DhPubkeyBundle { + ref ik, + ref spk, + ref sig, + ref opk, + } = self.0; + let my_ik = &self.1; + + // Do the X3DH operation to get the shared secret + let shared_secret = x3dh_a(sig, my_ik, spk, &ek, ik, opk)?; + + Ok((ek, shared_secret)) + } +} + +// Define an decapsulator. Since authenticated and unauthenticated encapped keys are represented by +// the same type (which, outside of testing, should not be the case), this can do both auth'd and +// unauth'd decapsulation. +impl Decapsulate for DecapContext { + // TODO: Decapsulation is infallible. Make the Error type `!` when it's stable. + type Error = (); + + fn decapsulate(&self, ek: &EphemeralKey) -> Result { + // Deconstruct our private keys bundle + let X3DhPrivkeyBundle { + ref ik, + ref spk, + ref opk, + .. + } = self.0; + let sender_pubkey = &self.1; + + // Now decapsulate + Ok(x3dh_b(sender_pubkey, spk, ek, ik, opk)) + } +} + +#[test] +fn test_x3dh() { + let mut rng = rand::thread_rng(); + + // We use _a and _b suffixes to denote whether a key belongs to Alice or Bob. Alice is the + // sender in this case. + let sk_ident_a = IdentityKey::default(); + let pk_ident_a = sk_ident_a.strip(); + let sk_bundle_b = X3DhPrivkeyBundle::gen(); + let pk_bundle_b = sk_bundle_b.as_pubkeys(); + + let encap_context = EncapContext(pk_bundle_b, sk_ident_a); + let decap_context = DecapContext(sk_bundle_b, pk_ident_a); + + // Now do an authenticated encap + let (encapped_key, ss1) = encap_context.encapsulate(&mut rng).unwrap(); + let ss2 = decap_context.decapsulate(&encapped_key).unwrap(); + assert_eq!(ss1, ss2); +} diff --git a/password-hash/CHANGELOG.md b/password-hash/CHANGELOG.md new file mode 100644 index 000000000..e32dd4271 --- /dev/null +++ b/password-hash/CHANGELOG.md @@ -0,0 +1,167 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.5.0 (2023-03-04) +### Added +- `Error::OutputSize` ([#1026]) +- `std::error::Error::source` for `Error` ([#1264]) +- `getrandom` feature ([#1267]) + +### Changed +- Use `Salt` type with `PasswordHasher` ([#1187]) +- Rename `Salt::new` => `Salt::from_b64` ([#1266]) +- Rename `Salt::b64_decode` => `Salt::decode_b64` ([#1266]) +- Rename `SaltString::new` => `SaltString::from_b64` ([#1266]) +- Rename `SaltString::b64_decode` => `SaltString::decode_b64` ([#1266]) +- Rename `SaltString::b64_encode` => `SaltString::encode_b64` ([#1266]) + +### Fixed +- Allow `Salt` to be exactly the same amount as `MAX_LENGTH` value ([#1246]) + +[#1026]: https://github.com/RustCrypto/traits/pull/1026 +[#1187]: https://github.com/RustCrypto/traits/pull/1187 +[#1246]: https://github.com/RustCrypto/traits/pull/1246 +[#1264]: https://github.com/RustCrypto/traits/pull/1264 +[#1266]: https://github.com/RustCrypto/traits/pull/1266 +[#1267]: https://github.com/RustCrypto/traits/pull/1267 + +## 0.4.2 (2022-06-27) +### Fixed +- docs.rs metadata ([#1031]) + +[#1031]: https://github.com/RustCrypto/traits/pull/1031 + +## 0.4.1 (2022-04-22) +### Added +- `authentication` category to Cargo.toml ([#976]) + +[#976]: https://github.com/RustCrypto/traits/pull/976 + +## 0.4.0 (2022-03-09) +### Changed +- Leverage `const_panic`; MSRV 1.57 ([#896]) +- Rust 2021 edition upgrade ([#897]) +- Make `Ident::new` fallible; add `Ident::new_unwrap` ([#896], [#960]) + +### Fixed +- Better `Debug`/`Display` impls for `SaltString` ([#804]) + +### Removed +- `base64ct` version restrictions ([#914]) + +[#804]: https://github.com/RustCrypto/traits/pull/804 +[#896]: https://github.com/RustCrypto/traits/pull/896 +[#897]: https://github.com/RustCrypto/traits/pull/897 +[#897]: https://github.com/RustCrypto/traits/pull/897 +[#914]: https://github.com/RustCrypto/traits/pull/914 +[#960]: https://github.com/RustCrypto/traits/pull/960 + +## 0.3.2 (2021-09-15) +### Fixed +- Remove unused lifetimes ([#760]) + +[#760]: https://github.com/RustCrypto/traits/pull/760 + +## 0.3.1 (2021-09-14) [YANKED] +### Added +- `PasswordHashString` ([#758]) + +### Fixed +- Handling of empty salts in `fmt::Display` impl for PasswordHash ([#748]) +- MSRV regression from `base64ct` ([#757]) + +[#748]: https://github.com/RustCrypto/traits/pull/748 +[#757]: https://github.com/RustCrypto/traits/pull/757 +[#758]: https://github.com/RustCrypto/traits/pull/758 + +## 0.3.0 (2021-08-27) [YANKED] +### Added +- More details to `ParamValueInvalid` ([#713]) +- `SaltInvalid` error ([#713]) +- `version` param to `PasswordHasher` ([#719]) +- `ParamsString::add_b64_bytes` method ([#722]) + +### Changed +- Rename `PasswordHash::hash_password_simple` => `PasswordHash::hash_password` ([#720]) +- Rename `PasswordHash::hash_password` => `PasswordHash::hash_password_customized` ([#720]) +- Rename `Error::B64` => `Error::B64Encoding` ([#721]) + +[#713]: https://github.com/RustCrypto/traits/pull/713 +[#719]: https://github.com/RustCrypto/traits/pull/719 +[#720]: https://github.com/RustCrypto/traits/pull/720 +[#721]: https://github.com/RustCrypto/traits/pull/721 +[#722]: https://github.com/RustCrypto/traits/pull/722 + +## 0.2.3 (2021-08-23) +### Changed +- Make max lengths of `Value` and `Salt` both 64 ([#707]) + +[#707]: https://github.com/RustCrypto/traits/pull/707 + +## 0.2.2 (2021-07-20) +### Changed +- Pin `subtle` dependency to v2.4 ([#689]) + +### Added +- Re-export `rand_core` ([#683]) + +[#683]: https://github.com/RustCrypto/traits/pull/683 +[#689]: https://github.com/RustCrypto/traits/pull/689 + +## 0.2.1 (2021-05-05) +### Changed +- Use `subtle` crate for comparing hash `Output` ([#631]) + +[#631]: https://github.com/RustCrypto/traits/pull/631 + +## 0.2.0 (2021-04-29) +### Changed +- Allow specifying output length and version with params ([#615]) +- Allow passing `&str`, `&Salt`, or `&SaltString` as salt ([#615]) +- Simplify error handling ([#615]) + +[#615]: https://github.com/RustCrypto/traits/pull/615 + +## 0.1.4 (2021-04-19) +### Added +- Length constants ([#600]) + +### Changed +- Deprecate functions for obtaining length constants ([#600]) + +[#600]: https://github.com/RustCrypto/traits/pull/600 + +## 0.1.3 (2021-04-17) +### Changed +- Update docs for PHC string field ([#593]) + +### Fixed +- Broken `b64` links in rustdoc ([#594]) + +[#593]: https://github.com/RustCrypto/traits/pull/593 +[#594]: https://github.com/RustCrypto/traits/pull/594 + +## 0.1.2 (2021-03-17) +### Changed +- Bump `base64ct` dependency to v1.0 ([#579]) + +[#579]: https://github.com/RustCrypto/traits/pull/579 + +## 0.1.1 (2021-02-01) +### Added +- `Encoding` enum with bcrypt and `crypt(3)` Base64 support ([#515]) +- Support for using `PasswordHash` with an alternate `Encoding` ([#518]) + +### Changed +- Bump `base64ct` dependency to v0.2 ([#519]) + +[#515]: https://github.com/RustCrypto/traits/pull/515 +[#518]: https://github.com/RustCrypto/traits/pull/518 +[#519]: https://github.com/RustCrypto/traits/pull/519 + +## 0.1.0 (2021-01-28) +- Initial release diff --git a/password-hash/Cargo.toml b/password-hash/Cargo.toml new file mode 100644 index 000000000..ca1d81c48 --- /dev/null +++ b/password-hash/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "password-hash" +description = """ +Traits which describe the functionality of password hashing algorithms, +as well as a `no_std`-friendly implementation of the PHC string format +(a well-defined subset of the Modular Crypt Format a.k.a. MCF) +""" +version = "0.6.0-rc.0" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +readme = "README.md" +documentation = "https://docs.rs/password-hash" +homepage = "https://github.com/RustCrypto/traits/tree/master/password-hash" +repository = "https://github.com/RustCrypto/traits" +categories = ["authentication", "cryptography", "no-std"] +keywords = ["crypt", "mcf", "password", "pbkdf", "phc"] +edition = "2021" +rust-version = "1.81" + +[dependencies] +base64ct = "1.6" +subtle = { version = "2", default-features = false } + +# optional dependencies +rand_core = { version = "0.6.4", optional = true, default-features = false } + +[features] +default = ["rand_core"] +alloc = ["base64ct/alloc"] + +getrandom = ["rand_core/getrandom"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/password-hash/LICENSE-APACHE b/password-hash/LICENSE-APACHE new file mode 100644 index 000000000..78173fa2e --- /dev/null +++ b/password-hash/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/password-hash/LICENSE-MIT b/password-hash/LICENSE-MIT new file mode 100644 index 000000000..50b1254c1 --- /dev/null +++ b/password-hash/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2020-2023 RustCrypto Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/password-hash/README.md b/password-hash/README.md new file mode 100644 index 000000000..20dffcd56 --- /dev/null +++ b/password-hash/README.md @@ -0,0 +1,78 @@ +# RustCrypto: Password Hashing Traits + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] + +Traits which describe the functionality of [password hashing algorithms]. + +[Documentation][docs-link] + +## About + +Provides a `no_std`-friendly implementation of the +[Password Hashing Competition (PHC) string format specification][PHC] +(a well-defined subset of the [Modular Crypt Format a.k.a. MCF][MCF]) which +works in conjunction with the traits this crate defines. + +## Supported Crates + +See [RustCrypto/password-hashes] for algorithm implementations which use +this crate for interoperability: + +- [`argon2`] - Argon2 memory hard key derivation function +- [`pbkdf2`] - Password-Based Key Derivation Function v2 +- [`scrypt`] - scrypt key derivation function + +## Minimum Supported Rust Version + +Rust **1.81** or higher. + +Minimum supported Rust version may be changed in the future, but it will be +accompanied by a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## License + +Licensed under either of: + +- [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) +- [MIT license](https://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/password-hash +[crate-link]: https://crates.io/crates/password-hash +[docs-image]: https://docs.rs/password-hash/badge.svg +[docs-link]: https://docs.rs/password-hash/ +[build-image]: https://github.com/RustCrypto/traits/workflows/password-hash/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/traits/actions?query=workflow:password-hash +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260046-password-hashes + +[//]: # (general links) + +[password hashing algorithms]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Password_verification +[PHC]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md +[MCF]: https://passlib.readthedocs.io/en/stable/modular_crypt_format.html +[RustCrypto/password-hashes]: https://github.com/RustCrypto/password-hashes +[`argon2`]: https://docs.rs/argon2 +[`pbkdf2`]: https://docs.rs/pbkdf2 +[`scrypt`]: https://docs.rs/scrypt diff --git a/password-hash/src/encoding.rs b/password-hash/src/encoding.rs new file mode 100644 index 000000000..8a914cee7 --- /dev/null +++ b/password-hash/src/encoding.rs @@ -0,0 +1,90 @@ +//! Base64 encoding variants. + +use base64ct::{ + Base64Bcrypt, Base64Crypt, Base64ShaCrypt, Base64Unpadded as B64, Encoding as _, + Error as B64Error, +}; + +/// Base64 encoding variants. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum Encoding { + /// "B64" encoding: standard Base64 without padding. + /// + /// ```text + /// [A-Z] [a-z] [0-9] + / + /// 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f + /// ``` + /// + B64, + + /// bcrypt encoding. + /// + /// ```text + /// ./ [A-Z] [a-z] [0-9] + /// 0x2e-0x2f, 0x41-0x5a, 0x61-0x7a, 0x30-0x39 + /// ``` + Bcrypt, + + /// `crypt(3)` encoding. + /// + /// ```text + /// [.-9] [A-Z] [a-z] + /// 0x2e-0x39, 0x41-0x5a, 0x61-0x7a + /// ``` + Crypt, + + /// `crypt(3)` Base64 encoding for the following schemes. + /// - sha1_crypt, + /// - sha256_crypt, + /// - sha512_crypt, + /// - md5_crypt + /// + /// ```text + /// [.-9] [A-Z] [a-z] + /// 0x2e-0x39, 0x41-0x5a, 0x61-0x7a + /// ``` + ShaCrypt, +} + +impl Default for Encoding { + fn default() -> Self { + Self::B64 + } +} + +impl Encoding { + /// Decode a Base64 string into the provided destination buffer. + pub fn decode(self, src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], B64Error> { + match self { + Self::B64 => B64::decode(src, dst), + Self::Bcrypt => Base64Bcrypt::decode(src, dst), + Self::Crypt => Base64Crypt::decode(src, dst), + Self::ShaCrypt => Base64ShaCrypt::decode(src, dst), + } + } + + /// Encode the input byte slice as Base64. + /// + /// Writes the result into the provided destination slice, returning an + /// ASCII-encoded Base64 string value. + pub fn encode<'a>(self, src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, B64Error> { + match self { + Self::B64 => B64::encode(src, dst), + Self::Bcrypt => Base64Bcrypt::encode(src, dst), + Self::Crypt => Base64Crypt::encode(src, dst), + Self::ShaCrypt => Base64ShaCrypt::encode(src, dst), + } + .map_err(Into::into) + } + + /// Get the length of Base64 produced by encoding the given bytes. + pub fn encoded_len(self, bytes: &[u8]) -> usize { + match self { + Self::B64 => B64::encoded_len(bytes), + Self::Bcrypt => Base64Bcrypt::encoded_len(bytes), + Self::Crypt => Base64Crypt::encoded_len(bytes), + Self::ShaCrypt => Base64ShaCrypt::encoded_len(bytes), + } + } +} diff --git a/password-hash/src/errors.rs b/password-hash/src/errors.rs new file mode 100644 index 000000000..11efd67bb --- /dev/null +++ b/password-hash/src/errors.rs @@ -0,0 +1,170 @@ +//! Error types. + +pub use base64ct::Error as B64Error; + +use core::cmp::Ordering; +use core::fmt; + +/// Result type. +pub type Result = core::result::Result; + +/// Password hashing errors. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum Error { + /// Unsupported algorithm. + Algorithm, + + /// "B64" encoding error. + B64Encoding(B64Error), + + /// Cryptographic error. + Crypto, + + /// Output size unexpected. + OutputSize { + /// Indicates why the output size is unexpected. + /// + /// - [`Ordering::Less`]: Size is too small. + /// - [`Ordering::Equal`]: Size is not exactly as `expected`. + /// - [`Ordering::Greater`]: Size is too long. + provided: Ordering, + /// Expected output size in relation to `provided`. + /// + /// - [`Ordering::Less`]: Minimum size. + /// - [`Ordering::Equal`]: Expected size. + /// - [`Ordering::Greater`]: Maximum size. + expected: usize, + }, + + /// Duplicate parameter name encountered. + ParamNameDuplicated, + + /// Invalid parameter name. + ParamNameInvalid, + + /// Invalid parameter value. + ParamValueInvalid(InvalidValue), + + /// Maximum number of parameters exceeded. + ParamsMaxExceeded, + + /// Invalid password. + Password, + + /// Password hash string invalid. + PhcStringField, + + /// Password hash string contains trailing data. + PhcStringTrailingData, + + /// Salt invalid. + SaltInvalid(InvalidValue), + + /// Invalid algorithm version. + Version, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::result::Result<(), fmt::Error> { + match self { + Self::Algorithm => write!(f, "unsupported algorithm"), + Self::B64Encoding(err) => write!(f, "{}", err), + Self::Crypto => write!(f, "cryptographic error"), + Self::OutputSize { provided, expected } => match provided { + Ordering::Less => write!( + f, + "output size too short, expected at least {} bytes", + expected + ), + Ordering::Equal => write!(f, "output size unexpected, expected {} bytes", expected), + Ordering::Greater => write!( + f, + "output size too long, expected at most {} bytes", + expected + ), + }, + Self::ParamNameDuplicated => f.write_str("duplicate parameter"), + Self::ParamNameInvalid => f.write_str("invalid parameter name"), + Self::ParamValueInvalid(val_err) => write!(f, "invalid parameter value: {}", val_err), + Self::ParamsMaxExceeded => f.write_str("maximum number of parameters reached"), + Self::Password => write!(f, "invalid password"), + Self::PhcStringField => write!(f, "password hash string missing field"), + Self::PhcStringTrailingData => { + write!(f, "password hash string contains trailing characters") + } + Self::SaltInvalid(val_err) => write!(f, "salt invalid: {}", val_err), + Self::Version => write!(f, "invalid algorithm version"), + } + } +} + +impl core::error::Error for Error { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + match self { + // TODO: restore after base64ct will migrate to core::error::Error + // Self::B64Encoding(err) => Some(err), + Self::ParamValueInvalid(err) => Some(err), + Self::SaltInvalid(err) => Some(err), + _ => None, + } + } +} + +impl From for Error { + fn from(err: B64Error) -> Error { + Error::B64Encoding(err) + } +} + +impl From for Error { + fn from(_: base64ct::InvalidLengthError) -> Error { + Error::B64Encoding(B64Error::InvalidLength) + } +} + +/// Parse errors relating to invalid parameter values or salts. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum InvalidValue { + /// Character is not in the allowed set. + InvalidChar(char), + + /// Format is invalid. + InvalidFormat, + + /// Value is malformed. + Malformed, + + /// Value exceeds the maximum allowed length. + TooLong, + + /// Value does not satisfy the minimum length. + TooShort, +} + +impl InvalidValue { + /// Create an [`Error::ParamValueInvalid`] which warps this error. + pub fn param_error(self) -> Error { + Error::ParamValueInvalid(self) + } + + /// Create an [`Error::SaltInvalid`] which wraps this error. + pub fn salt_error(self) -> Error { + Error::SaltInvalid(self) + } +} + +impl fmt::Display for InvalidValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::result::Result<(), fmt::Error> { + match self { + Self::InvalidChar(c) => write!(f, "contains invalid character: '{}'", c), + Self::InvalidFormat => f.write_str("value format is invalid"), + Self::Malformed => f.write_str("value malformed"), + Self::TooLong => f.write_str("value to long"), + Self::TooShort => f.write_str("value to short"), + } + } +} + +impl core::error::Error for InvalidValue {} diff --git a/password-hash/src/ident.rs b/password-hash/src/ident.rs new file mode 100644 index 000000000..784270e6d --- /dev/null +++ b/password-hash/src/ident.rs @@ -0,0 +1,171 @@ +//! Algorithm or parameter identifier. +//! +//! Implements the following parts of the [PHC string format specification][1]: +//! +//! > The function symbolic name is a sequence of characters in: `[a-z0-9-]` +//! > (lowercase letters, digits, and the minus sign). No other character is +//! > allowed. Each function defines its own identifier (or identifiers in case +//! > of a function family); identifiers should be explicit (human readable, +//! > not a single digit), with a length of about 5 to 10 characters. An +//! > identifier name MUST NOT exceed 32 characters in length. +//! > +//! > Each parameter name shall be a sequence of characters in: `[a-z0-9-]` +//! > (lowercase letters, digits, and the minus sign). No other character is +//! > allowed. Parameter names SHOULD be readable for a human user. A +//! > parameter name MUST NOT exceed 32 characters in length. +//! +//! [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md + +use crate::{Error, Result}; +use core::{fmt, ops::Deref, str}; + +/// Algorithm or parameter identifier. +/// +/// This type encompasses both the "function symbolic name" and "parameter name" +/// use cases as described in the [PHC string format specification][1]. +/// +/// # Constraints +/// - ASCII-encoded string consisting of the characters `[a-z0-9-]` +/// (lowercase letters, digits, and the minus sign) +/// - Minimum length: 1 ASCII character (i.e. 1-byte) +/// - Maximum length: 32 ASCII characters (i.e. 32-bytes) +/// +/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md +#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct Ident<'a>(&'a str); + +impl<'a> Ident<'a> { + /// Maximum length of an [`Ident`] - 32 ASCII characters (i.e. 32-bytes). + /// + /// This value corresponds to the maximum size of a function symbolic names + /// and parameter names according to the PHC string format. + /// Maximum length of an [`Ident`] - 32 ASCII characters (i.e. 32-bytes). + /// + /// This value corresponds to the maximum size of a function symbolic names + /// and parameter names according to the PHC string format. + const MAX_LENGTH: usize = 32; + + /// Parse an [`Ident`] from a string. + /// + /// String must conform to the constraints given in the type-level + /// documentation. + pub const fn new(s: &'a str) -> Result { + let input = s.as_bytes(); + + match input.len() { + 1..=Self::MAX_LENGTH => { + let mut i = 0; + + while i < input.len() { + if !matches!(input[i], b'a'..=b'z' | b'0'..=b'9' | b'-') { + return Err(Error::ParamNameInvalid); + } + + i += 1; + } + + Ok(Self(s)) + } + _ => Err(Error::ParamNameInvalid), + } + } + + /// Parse an [`Ident`] from a string, panicking on parse errors. + /// + /// This function exists as a workaround for `unwrap` not yet being + /// stable in `const fn` contexts, and is intended to allow the result to + /// be bound to a constant value. + pub const fn new_unwrap(s: &'a str) -> Self { + assert!(!s.is_empty(), "PHC ident string can't be empty"); + assert!(s.len() <= Self::MAX_LENGTH, "PHC ident string too long"); + + match Self::new(s) { + Ok(ident) => ident, + Err(_) => panic!("invalid PHC string format identifier"), + } + } + + /// Borrow this ident as a `str` + pub fn as_str(&self) -> &'a str { + self.0 + } +} + +impl AsRef for Ident<'_> { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl Deref for Ident<'_> { + type Target = str; + + fn deref(&self) -> &str { + self.as_str() + } +} + +// Note: this uses `TryFrom` instead of `FromStr` to support a lifetime on +// the `str` the value is being parsed from. +impl<'a> TryFrom<&'a str> for Ident<'a> { + type Error = Error; + + fn try_from(s: &'a str) -> Result { + Self::new(s) + } +} + +impl fmt::Display for Ident<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self) + } +} + +impl fmt::Debug for Ident<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Ident").field(&self.as_ref()).finish() + } +} + +#[cfg(test)] +mod tests { + use super::{Error, Ident}; + + // Invalid ident examples + const INVALID_EMPTY: &str = ""; + const INVALID_CHAR: &str = "argon2;d"; + const INVALID_TOO_LONG: &str = "012345678911234567892123456789312"; + const INVALID_CHAR_AND_TOO_LONG: &str = "0!2345678911234567892123456789312"; + + #[test] + fn parse_valid() { + let valid_examples = ["6", "x", "argon2d", "01234567891123456789212345678931"]; + + for &example in &valid_examples { + assert_eq!(example, &*Ident::new(example).unwrap()); + } + } + + #[test] + fn reject_empty() { + assert_eq!(Ident::new(INVALID_EMPTY), Err(Error::ParamNameInvalid)); + } + + #[test] + fn reject_invalid() { + assert_eq!(Ident::new(INVALID_CHAR), Err(Error::ParamNameInvalid)); + } + + #[test] + fn reject_too_long() { + assert_eq!(Ident::new(INVALID_TOO_LONG), Err(Error::ParamNameInvalid)); + } + + #[test] + fn reject_invalid_char_and_too_long() { + assert_eq!( + Ident::new(INVALID_CHAR_AND_TOO_LONG), + Err(Error::ParamNameInvalid) + ); + } +} diff --git a/password-hash/src/lib.rs b/password-hash/src/lib.rs new file mode 100644 index 000000000..7e3f283f1 --- /dev/null +++ b/password-hash/src/lib.rs @@ -0,0 +1,392 @@ +#![no_std] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" +)] +#![forbid(unsafe_code)] +#![warn( + missing_docs, + rust_2018_idioms, + unused_lifetimes, + missing_debug_implementations +)] + +//! +//! # Usage +//! +//! This crate represents password hashes using the [`PasswordHash`] type, which +//! represents a parsed "PHC string" with the following format: +//! +//! ```text +//! $[$v=][$=(,=)*][$[$]] +//! ``` +//! +//! For more information, please see the documentation for [`PasswordHash`]. + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "rand_core")] +pub use rand_core; + +pub mod errors; + +mod encoding; +mod ident; +mod output; +mod params; +mod salt; +mod traits; +mod value; + +pub use crate::{ + encoding::Encoding, + errors::{Error, Result}, + ident::Ident, + output::Output, + params::ParamsString, + salt::{Salt, SaltString}, + traits::{McfHasher, PasswordHasher, PasswordVerifier}, + value::{Decimal, Value}, +}; + +use core::fmt::{self, Debug}; + +#[cfg(feature = "alloc")] +use alloc::{ + str::FromStr, + string::{String, ToString}, +}; + +/// Separator character used in password hashes (e.g. `$6$...`). +const PASSWORD_HASH_SEPARATOR: char = '$'; + +/// Password hash. +/// +/// This type corresponds to the parsed representation of a PHC string as +/// described in the [PHC string format specification][1]. +/// +/// PHC strings have the following format: +/// +/// ```text +/// $[$v=][$=(,=)*][$[$]] +/// ``` +/// +/// where: +/// +/// - `` is the symbolic name for the function +/// - `` is the algorithm version +/// - `` is a parameter name +/// - `` is a parameter value +/// - `` is an encoding of the salt +/// - `` is an encoding of the hash output +/// +/// The string is then the concatenation, in that order, of: +/// +/// - a `$` sign; +/// - the function symbolic name; +/// - optionally, a `$` sign followed by the algorithm version with a `v=version` format; +/// - optionally, a `$` sign followed by one or several parameters, each with a `name=value` format; +/// the parameters are separated by commas; +/// - optionally, a `$` sign followed by the (encoded) salt value; +/// - optionally, a `$` sign followed by the (encoded) hash output (the hash output may be present +/// only if the salt is present). +/// +/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#specification +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PasswordHash<'a> { + /// Password hashing algorithm identifier. + /// + /// This corresponds to the `` field in a PHC string, a.k.a. the + /// symbolic name for the function. + pub algorithm: Ident<'a>, + + /// Optional version field. + /// + /// This corresponds to the `` field in a PHC string. + pub version: Option, + + /// Algorithm-specific parameters. + /// + /// This corresponds to the set of `$=(,=)*` + /// name/value pairs in a PHC string. + pub params: ParamsString, + + /// [`Salt`] string for personalizing a password hash output. + /// + /// This corresponds to the `` value in a PHC string. + pub salt: Option>, + + /// Password hashing function [`Output`], a.k.a. hash/digest. + /// + /// This corresponds to the `` output in a PHC string. + pub hash: Option, +} + +impl<'a> PasswordHash<'a> { + /// Parse a password hash from a string in the PHC string format. + pub fn new(s: &'a str) -> Result { + Self::parse(s, Encoding::default()) + } + + /// Parse a password hash from the given [`Encoding`]. + pub fn parse(s: &'a str, encoding: Encoding) -> Result { + if s.is_empty() { + return Err(Error::PhcStringField); + } + + let mut fields = s.split(PASSWORD_HASH_SEPARATOR); + let beginning = fields.next().expect("no first field"); + + if beginning.chars().next().is_some() { + return Err(Error::PhcStringField); + } + + let algorithm = fields + .next() + .ok_or(Error::PhcStringField) + .and_then(Ident::try_from)?; + + let mut version = None; + let mut params = ParamsString::new(); + let mut salt = None; + let mut hash = None; + + let mut next_field = fields.next(); + + if let Some(field) = next_field { + // v= + if field.starts_with("v=") && !field.contains(params::PARAMS_DELIMITER) { + version = Some(Value::new(&field[2..]).and_then(|value| value.decimal())?); + next_field = None; + } + } + + if next_field.is_none() { + next_field = fields.next(); + } + + if let Some(field) = next_field { + // = + if field.contains(params::PAIR_DELIMITER) { + params = field.parse()?; + next_field = None; + } + } + + if next_field.is_none() { + next_field = fields.next(); + } + + if let Some(s) = next_field { + salt = Some(s.try_into()?); + } + + if let Some(field) = fields.next() { + hash = Some(Output::decode(field, encoding)?); + } + + if fields.next().is_some() { + return Err(Error::PhcStringTrailingData); + } + + Ok(Self { + algorithm, + version, + params, + salt, + hash, + }) + } + + /// Generate a password hash using the supplied algorithm. + pub fn generate( + phf: impl PasswordHasher, + password: impl AsRef<[u8]>, + salt: impl Into>, + ) -> Result { + phf.hash_password(password.as_ref(), salt) + } + + /// Verify this password hash using the specified set of supported + /// [`PasswordHasher`] trait objects. + pub fn verify_password( + &self, + phfs: &[&dyn PasswordVerifier], + password: impl AsRef<[u8]>, + ) -> Result<()> { + for &phf in phfs { + if phf.verify_password(password.as_ref(), self).is_ok() { + return Ok(()); + } + } + + Err(Error::Password) + } + + /// Get the [`Encoding`] that this [`PasswordHash`] is serialized with. + pub fn encoding(&self) -> Encoding { + self.hash.map(|h| h.encoding()).unwrap_or_default() + } + + /// Serialize this [`PasswordHash`] as a [`PasswordHashString`]. + #[cfg(feature = "alloc")] + pub fn serialize(&self) -> PasswordHashString { + self.into() + } +} + +// Note: this uses `TryFrom` instead of `FromStr` to support a lifetime on +// the `str` the value is being parsed from. +impl<'a> TryFrom<&'a str> for PasswordHash<'a> { + type Error = Error; + + fn try_from(s: &'a str) -> Result { + Self::new(s) + } +} + +impl fmt::Display for PasswordHash<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.algorithm)?; + + if let Some(version) = self.version { + write!(f, "{}v={}", PASSWORD_HASH_SEPARATOR, version)?; + } + + if !self.params.is_empty() { + write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.params)?; + } + + if let Some(salt) = &self.salt { + write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, salt)?; + + if let Some(hash) = &self.hash { + write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, hash)?; + } + } + + Ok(()) + } +} + +/// Serialized [`PasswordHash`]. +/// +/// This type contains a serialized password hash string which is ensured to +/// parse successfully. +// TODO(tarcieri): cached parsed representations? or at least structural data +#[cfg(feature = "alloc")] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PasswordHashString { + /// String value + string: String, + + /// String encoding + encoding: Encoding, +} + +#[cfg(feature = "alloc")] +#[allow(clippy::len_without_is_empty)] +impl PasswordHashString { + /// Parse a password hash from a string in the PHC string format. + pub fn new(s: &str) -> Result { + Self::parse(s, Encoding::default()) + } + + /// Parse a password hash from the given [`Encoding`]. + pub fn parse(s: &str, encoding: Encoding) -> Result { + Ok(PasswordHash::parse(s, encoding)?.into()) + } + + /// Parse this owned string as a [`PasswordHash`]. + pub fn password_hash(&self) -> PasswordHash<'_> { + PasswordHash::parse(&self.string, self.encoding).expect("malformed password hash") + } + + /// Get the [`Encoding`] that this [`PasswordHashString`] is serialized with. + pub fn encoding(&self) -> Encoding { + self.encoding + } + + /// Borrow this value as a `str`. + pub fn as_str(&self) -> &str { + self.string.as_str() + } + + /// Borrow this value as bytes. + pub fn as_bytes(&self) -> &[u8] { + self.as_str().as_bytes() + } + + /// Get the length of this value in ASCII characters. + pub fn len(&self) -> usize { + self.as_str().len() + } + + /// Password hashing algorithm identifier. + pub fn algorithm(&self) -> Ident<'_> { + self.password_hash().algorithm + } + + /// Optional version field. + pub fn version(&self) -> Option { + self.password_hash().version + } + + /// Algorithm-specific parameters. + pub fn params(&self) -> ParamsString { + self.password_hash().params + } + + /// [`Salt`] string for personalizing a password hash output. + pub fn salt(&self) -> Option> { + self.password_hash().salt + } + + /// Password hashing function [`Output`], a.k.a. hash/digest. + pub fn hash(&self) -> Option { + self.password_hash().hash + } +} + +#[cfg(feature = "alloc")] +impl AsRef for PasswordHashString { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +#[cfg(feature = "alloc")] +impl From> for PasswordHashString { + fn from(hash: PasswordHash<'_>) -> PasswordHashString { + PasswordHashString::from(&hash) + } +} + +#[cfg(feature = "alloc")] +impl From<&PasswordHash<'_>> for PasswordHashString { + fn from(hash: &PasswordHash<'_>) -> PasswordHashString { + PasswordHashString { + string: hash.to_string(), + encoding: hash.encoding(), + } + } +} + +#[cfg(feature = "alloc")] +impl FromStr for PasswordHashString { + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::new(s) + } +} + +#[cfg(feature = "alloc")] +impl fmt::Display for PasswordHashString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} diff --git a/password-hash/src/output.rs b/password-hash/src/output.rs new file mode 100644 index 000000000..65ee3484c --- /dev/null +++ b/password-hash/src/output.rs @@ -0,0 +1,335 @@ +//! Outputs from password hashing functions. + +use crate::{Encoding, Error, Result}; +use core::{cmp::Ordering, fmt, str::FromStr}; +use subtle::{Choice, ConstantTimeEq}; + +/// Output from password hashing functions, i.e. the "hash" or "digest" +/// as raw bytes. +/// +/// The [`Output`] type implements the RECOMMENDED best practices described in +/// the [PHC string format specification][1], namely: +/// +/// > The hash output, for a verification, must be long enough to make preimage +/// > attacks at least as hard as password guessing. To promote wide acceptance, +/// > a default output size of 256 bits (32 bytes, encoded as 43 characters) is +/// > recommended. Function implementations SHOULD NOT allow outputs of less +/// > than 80 bits to be used for password verification. +/// +/// # Recommended length +/// Per the description above, the recommended default length for an [`Output`] +/// of a password hashing function is **32-bytes** (256-bits). +/// +/// # Constraints +/// The above guidelines are interpreted into the following constraints: +/// +/// - Minimum length: **10**-bytes (80-bits) +/// - Maximum length: **64**-bytes (512-bits) +/// +/// The specific recommendation of a 64-byte maximum length is taken as a best +/// practice from the hash output guidelines for [Argon2 Encoding][2] given in +/// the same document: +/// +/// > The hash output...length shall be between 12 and 64 bytes (16 and 86 +/// > characters, respectively). The default output length is 32 bytes +/// > (43 characters). +/// +/// Based on this guidance, this type enforces an upper bound of 64-bytes +/// as a reasonable maximum, and recommends using 32-bytes. +/// +/// # Constant-time comparisons +/// The [`Output`] type impls the [`ConstantTimeEq`] trait from the [`subtle`] +/// crate and uses it to perform constant-time comparisons. +/// +/// Additionally the [`PartialEq`] and [`Eq`] trait impls for [`Output`] use +/// [`ConstantTimeEq`] when performing comparisons. +/// +/// ## Attacks on non-constant-time password hash comparisons +/// Comparing password hashes in constant-time is known to mitigate at least +/// one [poorly understood attack][3] involving an adversary with the following +/// knowledge/capabilities: +/// +/// - full knowledge of what password hashing algorithm is being used +/// including any relevant configurable parameters +/// - knowledge of the salt for a particular victim +/// - ability to accurately measure a timing side-channel on comparisons +/// of the password hash over the network +/// +/// An attacker with the above is able to perform an offline computation of +/// the hash for any chosen password in such a way that it will match the +/// hash computed by the server. +/// +/// As noted above, they also measure timing variability in the server's +/// comparison of the hash it computes for a given password and a target hash +/// the attacker is trying to learn. +/// +/// When the attacker observes a hash comparison that takes longer than their +/// previous attempts, they learn that they guessed another byte in the +/// password hash correctly. They can leverage repeated measurements and +/// observations with different candidate passwords to learn the password +/// hash a byte-at-a-time in a manner similar to other such timing side-channel +/// attacks. +/// +/// The attack may seem somewhat counterintuitive since learning prefixes of a +/// password hash does not reveal any additional information about the password +/// itself. However, the above can be combined with an offline dictionary +/// attack where the attacker is able to determine candidate passwords to send +/// to the server by performing a brute force search offline and selecting +/// candidate passwords whose hashes match the portion of the prefix they have +/// learned so far. +/// +/// As the attacker learns a longer and longer prefix of the password hash, +/// they are able to more effectively eliminate candidate passwords offline as +/// part of a dictionary attack, until they eventually guess the correct +/// password or exhaust their set of candidate passwords. +/// +/// ## Mitigations +/// While we have taken care to ensure password hashes are compared in constant +/// time, we would also suggest preventing such attacks by using randomly +/// generated salts and keeping those salts secret. +/// +/// The [`SaltString::generate`][`crate::SaltString::generate`] function can be +/// used to generate random high-entropy salt values. +/// +/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties +/// [2]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding +/// [3]: https://web.archive.org/web/20130208100210/http://security-assessment.com/files/documents/presentations/TimingAttackPresentation2012.pdf +#[derive(Copy, Clone, Eq)] +pub struct Output { + /// Byte array containing a password hashing function output. + bytes: [u8; Self::MAX_LENGTH], + + /// Length of the password hashing function output in bytes. + length: u8, + + /// Encoding which output should be serialized with. + encoding: Encoding, +} + +#[allow(clippy::len_without_is_empty)] +impl Output { + /// Minimum length of a [`Output`] string: 10-bytes. + pub const MIN_LENGTH: usize = 10; + + /// Maximum length of [`Output`] string: 64-bytes. + /// + /// See type-level documentation about [`Output`] for more information. + pub const MAX_LENGTH: usize = 64; + + /// Maximum length of [`Output`] when encoded as B64 string: 86-bytes + /// (i.e. 86 ASCII characters) + pub const B64_MAX_LENGTH: usize = ((Self::MAX_LENGTH * 4) / 3) + 1; + + /// Create a [`Output`] from the given byte slice, validating it according + /// to [`Output::MIN_LENGTH`] and [`Output::MAX_LENGTH`] restrictions. + pub fn new(input: &[u8]) -> Result { + Self::init_with(input.len(), |bytes| { + bytes.copy_from_slice(input); + Ok(()) + }) + } + + /// Create a [`Output`] from the given byte slice and [`Encoding`], + /// validating it according to [`Output::MIN_LENGTH`] and + /// [`Output::MAX_LENGTH`] restrictions. + pub fn new_with_encoding(input: &[u8], encoding: Encoding) -> Result { + let mut result = Self::new(input)?; + result.encoding = encoding; + Ok(result) + } + + /// Initialize an [`Output`] using the provided method, which is given + /// a mutable byte slice into which it should write the output. + /// + /// The `output_size` (in bytes) must be known in advance, as well as at + /// least [`Output::MIN_LENGTH`] bytes and at most [`Output::MAX_LENGTH`] + /// bytes. + pub fn init_with(output_size: usize, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> Result<()>, + { + if output_size < Self::MIN_LENGTH { + return Err(Error::OutputSize { + provided: Ordering::Less, + expected: Self::MIN_LENGTH, + }); + } + + if output_size > Self::MAX_LENGTH { + return Err(Error::OutputSize { + provided: Ordering::Greater, + expected: Self::MAX_LENGTH, + }); + } + + let mut bytes = [0u8; Self::MAX_LENGTH]; + f(&mut bytes[..output_size])?; + + Ok(Self { + bytes, + length: output_size as u8, + encoding: Encoding::default(), + }) + } + + /// Borrow the output value as a byte slice. + pub fn as_bytes(&self) -> &[u8] { + &self.bytes[..self.len()] + } + + /// Get the [`Encoding`] that this [`Output`] is serialized with. + pub fn encoding(&self) -> Encoding { + self.encoding + } + + /// Creates a copy of this [`Output`] with the specified [`Encoding`]. + pub fn with_encoding(&self, encoding: Encoding) -> Self { + Self { encoding, ..*self } + } + + /// Get the length of the output value as a byte slice. + pub fn len(&self) -> usize { + usize::from(self.length) + } + + /// Parse B64-encoded [`Output`], i.e. using the PHC string + /// specification's restricted interpretation of Base64. + pub fn b64_decode(input: &str) -> Result { + Self::decode(input, Encoding::B64) + } + + /// Write B64-encoded [`Output`] to the provided buffer, returning + /// a sub-slice containing the encoded data. + /// + /// Returns an error if the buffer is too short to contain the output. + pub fn b64_encode<'a>(&self, out: &'a mut [u8]) -> Result<&'a str> { + self.encode(out, Encoding::B64) + } + + /// Decode the given input string using the specified [`Encoding`]. + pub fn decode(input: &str, encoding: Encoding) -> Result { + let mut bytes = [0u8; Self::MAX_LENGTH]; + encoding + .decode(input, &mut bytes) + .map_err(Into::into) + .and_then(|decoded| Self::new_with_encoding(decoded, encoding)) + } + + /// Encode this [`Output`] using the specified [`Encoding`]. + pub fn encode<'a>(&self, out: &'a mut [u8], encoding: Encoding) -> Result<&'a str> { + Ok(encoding.encode(self.as_ref(), out)?) + } + + /// Get the length of this [`Output`] when encoded as B64. + pub fn b64_len(&self) -> usize { + Encoding::B64.encoded_len(self.as_ref()) + } +} + +impl AsRef<[u8]> for Output { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl ConstantTimeEq for Output { + fn ct_eq(&self, other: &Self) -> Choice { + self.as_ref().ct_eq(other.as_ref()) + } +} + +impl FromStr for Output { + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::b64_decode(s) + } +} + +impl PartialEq for Output { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl TryFrom<&[u8]> for Output { + type Error = Error; + + fn try_from(input: &[u8]) -> Result { + Self::new(input) + } +} + +impl fmt::Display for Output { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut buffer = [0u8; Self::B64_MAX_LENGTH]; + self.encode(&mut buffer, self.encoding) + .map_err(|_| fmt::Error) + .and_then(|encoded| f.write_str(encoded)) + } +} + +impl fmt::Debug for Output { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Output(\"{}\")", self) + } +} + +#[cfg(test)] +mod tests { + use super::{Error, Ordering, Output}; + + #[test] + fn new_with_valid_min_length_input() { + let bytes = [10u8; 10]; + let output = Output::new(&bytes).unwrap(); + assert_eq!(output.as_ref(), &bytes); + } + + #[test] + fn new_with_valid_max_length_input() { + let bytes = [64u8; 64]; + let output = Output::new(&bytes).unwrap(); + assert_eq!(output.as_ref(), &bytes); + } + + #[test] + fn reject_new_too_short() { + let bytes = [9u8; 9]; + let err = Output::new(&bytes).err().unwrap(); + assert_eq!( + err, + Error::OutputSize { + provided: Ordering::Less, + expected: Output::MIN_LENGTH + } + ); + } + + #[test] + fn reject_new_too_long() { + let bytes = [65u8; 65]; + let err = Output::new(&bytes).err().unwrap(); + assert_eq!( + err, + Error::OutputSize { + provided: Ordering::Greater, + expected: Output::MAX_LENGTH + } + ); + } + + #[test] + fn partialeq_true() { + let a = Output::new(&[1u8; 32]).unwrap(); + let b = Output::new(&[1u8; 32]).unwrap(); + assert_eq!(a, b); + } + + #[test] + fn partialeq_false() { + let a = Output::new(&[1u8; 32]).unwrap(); + let b = Output::new(&[2u8; 32]).unwrap(); + assert_ne!(a, b); + } +} diff --git a/password-hash/src/params.rs b/password-hash/src/params.rs new file mode 100644 index 000000000..0d8074099 --- /dev/null +++ b/password-hash/src/params.rs @@ -0,0 +1,455 @@ +//! Algorithm parameters. + +use crate::errors::InvalidValue; +use crate::{ + value::{Decimal, Value}, + Encoding, Error, Ident, Result, +}; +use core::{ + fmt::{self, Debug, Write}, + str::{self, FromStr}, +}; + +/// Individual parameter name/value pair. +pub type Pair<'a> = (Ident<'a>, Value<'a>); + +/// Delimiter character between name/value pairs. +pub(crate) const PAIR_DELIMITER: char = '='; + +/// Delimiter character between parameters. +pub(crate) const PARAMS_DELIMITER: char = ','; + +/// Maximum number of supported parameters. +const MAX_LENGTH: usize = 127; + +/// Error message used with `expect` for when internal invariants are violated +/// (i.e. the contents of a [`ParamsString`] should always be valid) +const INVARIANT_VIOLATED_MSG: &str = "PHC params invariant violated"; + +/// Algorithm parameter string. +/// +/// The [PHC string format specification][1] defines a set of optional +/// algorithm-specific name/value pairs which can be encoded into a +/// PHC-formatted parameter string as follows: +/// +/// ```text +/// $=(,=)* +/// ``` +/// +/// This type represents that set of parameters. +/// +/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#specification +#[derive(Clone, Default, Eq, PartialEq)] +pub struct ParamsString(Buffer); + +impl ParamsString { + /// Create new empty [`ParamsString`]. + pub fn new() -> Self { + Self::default() + } + + /// Add the given byte value to the [`ParamsString`], encoding it as "B64". + pub fn add_b64_bytes<'a>(&mut self, name: impl TryInto>, bytes: &[u8]) -> Result<()> { + if !self.is_empty() { + self.0 + .write_char(PARAMS_DELIMITER) + .map_err(|_| Error::ParamsMaxExceeded)? + } + + let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?; + + // Add param name + let offset = self.0.length; + if write!(self.0, "{}=", name).is_err() { + self.0.length = offset; + return Err(Error::ParamsMaxExceeded); + } + + // Encode B64 value + let offset = self.0.length as usize; + let written = Encoding::B64 + .encode(bytes, &mut self.0.bytes[offset..])? + .len(); + + self.0.length += written as u8; + Ok(()) + } + + /// Add a key/value pair with a decimal value to the [`ParamsString`]. + pub fn add_decimal<'a>(&mut self, name: impl TryInto>, value: Decimal) -> Result<()> { + let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?; + self.add(name, value) + } + + /// Add a key/value pair with a string value to the [`ParamsString`]. + pub fn add_str<'a>( + &mut self, + name: impl TryInto>, + value: impl TryInto>, + ) -> Result<()> { + let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?; + + let value = value + .try_into() + .map_err(|_| Error::ParamValueInvalid(InvalidValue::InvalidFormat))?; + + self.add(name, value) + } + + /// Borrow the contents of this [`ParamsString`] as a byte slice. + pub fn as_bytes(&self) -> &[u8] { + self.as_str().as_bytes() + } + + /// Borrow the contents of this [`ParamsString`] as a `str`. + pub fn as_str(&self) -> &str { + self.0.as_ref() + } + + /// Get the count of the number ASCII characters in this [`ParamsString`]. + pub fn len(&self) -> usize { + self.as_str().len() + } + + /// Is this set of parameters empty? + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Iterate over the parameters. + pub fn iter(&self) -> Iter<'_> { + Iter::new(self.as_str()) + } + + /// Get a parameter [`Value`] by name. + pub fn get<'a>(&self, name: impl TryInto>) -> Option> { + let name = name.try_into().ok()?; + + for (n, v) in self.iter() { + if name == n { + return Some(v); + } + } + + None + } + + /// Get a parameter as a `str`. + pub fn get_str<'a>(&self, name: impl TryInto>) -> Option<&str> { + self.get(name).map(|value| value.as_str()) + } + + /// Get a parameter as a [`Decimal`]. + /// + /// See [`Value::decimal`] for format information. + pub fn get_decimal<'a>(&self, name: impl TryInto>) -> Option { + self.get(name).and_then(|value| value.decimal().ok()) + } + + /// Add a value to this [`ParamsString`] using the provided callback. + fn add(&mut self, name: Ident<'_>, value: impl fmt::Display) -> Result<()> { + if self.get(name).is_some() { + return Err(Error::ParamNameDuplicated); + } + + let orig_len = self.0.length; + + if !self.is_empty() { + self.0 + .write_char(PARAMS_DELIMITER) + .map_err(|_| Error::ParamsMaxExceeded)? + } + + if write!(self.0, "{}={}", name, value).is_err() { + self.0.length = orig_len; + return Err(Error::ParamsMaxExceeded); + } + + Ok(()) + } +} + +impl FromStr for ParamsString { + type Err = Error; + + fn from_str(s: &str) -> Result { + if s.len() > MAX_LENGTH { + return Err(Error::ParamsMaxExceeded); + } + + if s.is_empty() { + return Ok(ParamsString::new()); + } + + // Validate the string is well-formed + for mut param in s.split(PARAMS_DELIMITER).map(|p| p.split(PAIR_DELIMITER)) { + // Validate name + param + .next() + .ok_or(Error::ParamNameInvalid) + .and_then(Ident::try_from)?; + + // Validate value + param + .next() + .ok_or(Error::ParamValueInvalid(InvalidValue::Malformed)) + .and_then(Value::try_from)?; + + if param.next().is_some() { + return Err(Error::ParamValueInvalid(InvalidValue::Malformed)); + } + } + + let mut bytes = [0u8; MAX_LENGTH]; + bytes[..s.len()].copy_from_slice(s.as_bytes()); + + Ok(Self(Buffer { + bytes, + length: s.len() as u8, + })) + } +} + +impl<'a> FromIterator> for ParamsString { + fn from_iter(iter: I) -> Self + where + I: IntoIterator>, + { + let mut params = ParamsString::new(); + + for pair in iter { + params.add_str(pair.0, pair.1).expect("PHC params error"); + } + + params + } +} + +impl fmt::Display for ParamsString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl fmt::Debug for ParamsString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map().entries(self.iter()).finish() + } +} + +/// Iterator over algorithm parameters stored in a [`ParamsString`] struct. +#[derive(Debug)] +pub struct Iter<'a> { + inner: Option>, +} + +impl<'a> Iter<'a> { + /// Create a new [`Iter`]. + fn new(s: &'a str) -> Self { + if s.is_empty() { + Self { inner: None } + } else { + Self { + inner: Some(s.split(PARAMS_DELIMITER)), + } + } + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = Pair<'a>; + + fn next(&mut self) -> Option> { + let mut param = self.inner.as_mut()?.next()?.split(PAIR_DELIMITER); + + let name = param + .next() + .and_then(|id| Ident::try_from(id).ok()) + .expect(INVARIANT_VIOLATED_MSG); + + let value = param + .next() + .and_then(|value| Value::try_from(value).ok()) + .expect(INVARIANT_VIOLATED_MSG); + + debug_assert_eq!(param.next(), None); + Some((name, value)) + } +} + +/// Parameter buffer. +#[derive(Clone, Debug, Eq)] +struct Buffer { + /// Byte array containing an ASCII-encoded string. + bytes: [u8; MAX_LENGTH], + + /// Length of the string in ASCII characters (i.e. bytes). + length: u8, +} + +impl AsRef for Buffer { + fn as_ref(&self) -> &str { + str::from_utf8(&self.bytes[..(self.length as usize)]).expect(INVARIANT_VIOLATED_MSG) + } +} + +impl Default for Buffer { + fn default() -> Buffer { + Buffer { + bytes: [0u8; MAX_LENGTH], + length: 0, + } + } +} + +impl PartialEq for Buffer { + fn eq(&self, other: &Self) -> bool { + // Ensure comparisons always honor the initialized portion of the buffer + self.as_ref().eq(other.as_ref()) + } +} + +impl Write for Buffer { + fn write_str(&mut self, input: &str) -> fmt::Result { + let bytes = input.as_bytes(); + let length = self.length as usize; + + if length + bytes.len() > MAX_LENGTH { + return Err(fmt::Error); + } + + self.bytes[length..(length + bytes.len())].copy_from_slice(bytes); + self.length += bytes.len() as u8; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::{Error, Ident, ParamsString, Value}; + + #[cfg(feature = "alloc")] + use alloc::string::ToString; + use core::str::FromStr; + + #[test] + fn add() { + let mut params = ParamsString::new(); + params.add_str("a", "1").unwrap(); + params.add_decimal("b", 2).unwrap(); + params.add_str("c", "3").unwrap(); + + assert_eq!(params.iter().count(), 3); + assert_eq!(params.get_decimal("a").unwrap(), 1); + assert_eq!(params.get_decimal("b").unwrap(), 2); + assert_eq!(params.get_decimal("c").unwrap(), 3); + } + + #[test] + #[cfg(feature = "alloc")] + fn add_b64_bytes() { + let mut params = ParamsString::new(); + params.add_b64_bytes("a", &[1]).unwrap(); + params.add_b64_bytes("b", &[2, 3]).unwrap(); + params.add_b64_bytes("c", &[4, 5, 6]).unwrap(); + assert_eq!(params.to_string(), "a=AQ,b=AgM,c=BAUG"); + } + + #[test] + fn duplicate_names() { + let name = Ident::new("a").unwrap(); + let mut params = ParamsString::new(); + params.add_decimal(name, 1).unwrap(); + + let err = params.add_decimal(name, 2u32).err().unwrap(); + assert_eq!(err, Error::ParamNameDuplicated); + } + + #[test] + fn from_iter() { + let params = ParamsString::from_iter( + [ + (Ident::new("a").unwrap(), Value::try_from("1").unwrap()), + (Ident::new("b").unwrap(), Value::try_from("2").unwrap()), + (Ident::new("c").unwrap(), Value::try_from("3").unwrap()), + ] + .iter() + .cloned(), + ); + + assert_eq!(params.iter().count(), 3); + assert_eq!(params.get_decimal("a").unwrap(), 1); + assert_eq!(params.get_decimal("b").unwrap(), 2); + assert_eq!(params.get_decimal("c").unwrap(), 3); + } + + #[test] + fn iter() { + let mut params = ParamsString::new(); + params.add_str("a", "1").unwrap(); + params.add_str("b", "2").unwrap(); + params.add_str("c", "3").unwrap(); + + let mut i = params.iter(); + + for (name, value) in &[("a", "1"), ("b", "2"), ("c", "3")] { + let name = Ident::new(name).unwrap(); + let value = Value::try_from(*value).unwrap(); + assert_eq!(i.next(), Some((name, value))); + } + + assert_eq!(i.next(), None); + } + + // + // `FromStr` tests + // + + #[test] + fn parse_empty() { + let params = ParamsString::from_str("").unwrap(); + assert!(params.is_empty()); + } + + #[test] + fn parse_one() { + let params = ParamsString::from_str("a=1").unwrap(); + assert_eq!(params.iter().count(), 1); + assert_eq!(params.get("a").unwrap().decimal().unwrap(), 1); + } + + #[test] + fn parse_many() { + let params = ParamsString::from_str("a=1,b=2,c=3").unwrap(); + assert_eq!(params.iter().count(), 3); + assert_eq!(params.get_decimal("a").unwrap(), 1); + assert_eq!(params.get_decimal("b").unwrap(), 2); + assert_eq!(params.get_decimal("c").unwrap(), 3); + } + + // + // `Display` tests + // + + #[test] + #[cfg(feature = "alloc")] + fn display_empty() { + let params = ParamsString::new(); + assert_eq!(params.to_string(), ""); + } + + #[test] + #[cfg(feature = "alloc")] + fn display_one() { + let params = ParamsString::from_str("a=1").unwrap(); + assert_eq!(params.to_string(), "a=1"); + } + + #[test] + #[cfg(feature = "alloc")] + fn display_many() { + let params = ParamsString::from_str("a=1,b=2,c=3").unwrap(); + assert_eq!(params.to_string(), "a=1,b=2,c=3"); + } +} diff --git a/password-hash/src/salt.rs b/password-hash/src/salt.rs new file mode 100644 index 000000000..eba6876f6 --- /dev/null +++ b/password-hash/src/salt.rs @@ -0,0 +1,354 @@ +//! Salt string support. + +use crate::{Encoding, Error, Result, Value}; +use core::{fmt, str}; + +use crate::errors::InvalidValue; +#[cfg(feature = "rand_core")] +use rand_core::CryptoRngCore; + +/// Error message used with `expect` for when internal invariants are violated +/// (i.e. the contents of a [`Salt`] should always be valid) +const INVARIANT_VIOLATED_MSG: &str = "salt string invariant violated"; + +/// Salt string. +/// +/// In password hashing, a "salt" is an additional value used to +/// personalize/tweak the output of a password hashing function for a given +/// input password. +/// +/// Salts help defend against attacks based on precomputed tables of hashed +/// passwords, i.e. "[rainbow tables][1]". +/// +/// The [`Salt`] type implements the RECOMMENDED best practices for salts +/// described in the [PHC string format specification][2], namely: +/// +/// > - Maximum lengths for salt, output and parameter values are meant to help +/// > consumer implementations, in particular written in C and using +/// > stack-allocated buffers. These buffers must account for the worst case, +/// > i.e. the maximum defined length. Therefore, keep these lengths low. +/// > - The role of salts is to achieve uniqueness. A random salt is fine for +/// > that as long as its length is sufficient; a 16-byte salt would work well +/// > (by definition, UUID are very good salts, and they encode over exactly +/// > 16 bytes). 16 bytes encode as 22 characters in B64. Functions should +/// > disallow salt values that are too small for security (4 bytes should be +/// > viewed as an absolute minimum). +/// +/// # Recommended length +/// The recommended default length for a salt string is **16-bytes** (128-bits). +/// +/// See [`Salt::RECOMMENDED_LENGTH`] for more information. +/// +/// # Constraints +/// Salt strings are constrained to the following set of characters per the +/// PHC spec: +/// +/// > The salt consists in a sequence of characters in: `[a-zA-Z0-9/+.-]` +/// > (lowercase letters, uppercase letters, digits, /, +, . and -). +/// +/// Additionally the following length restrictions are enforced based on the +/// guidelines from the spec: +/// +/// - Minimum length: **4**-bytes +/// - Maximum length: **48**-bytes +/// +/// A maximum length is enforced based on the above recommendation for +/// supporting stack-allocated buffers (which this library uses), and the +/// specific determination of 48-bytes is taken as a best practice from the +/// [Argon2 Encoding][3] specification in the same document: +/// +/// > The length in bytes of the salt is between 8 and 48 bytes, thus +/// > yielding a length in characters between 11 and 64 characters (and that +/// > length is never equal to 1 modulo 4). The default byte length of the salt +/// > is 16 bytes (22 characters in B64 encoding). An encoded UUID, or a +/// > sequence of 16 bytes produced with a cryptographically strong PRNG, are +/// > appropriate salt values. +/// > +/// > The Argon2 specification states that the salt can be much longer, up +/// > to 2^32-1 bytes, but this makes little sense for password hashing. +/// > Specifying a relatively small maximum length allows for parsing with a +/// > stack allocated buffer.) +/// +/// Based on this guidance, this type enforces an upper bound of 64-bytes +/// as a reasonable maximum, and recommends using 16-bytes. +/// +/// [1]: https://en.wikipedia.org/wiki/Rainbow_table +/// [2]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties +/// [3]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Salt<'a>(Value<'a>); + +#[allow(clippy::len_without_is_empty)] +impl<'a> Salt<'a> { + /// Minimum length of a [`Salt`] string: 4-bytes. + pub const MIN_LENGTH: usize = 4; + + /// Maximum length of a [`Salt`] string: 64-bytes. + /// + /// See type-level documentation about [`Salt`] for more information. + pub const MAX_LENGTH: usize = 64; + + /// Recommended length of a salt: 16-bytes. + /// + /// This recommendation comes from the [PHC string format specification]: + /// + /// > The role of salts is to achieve uniqueness. A *random* salt is fine + /// > for that as long as its length is sufficient; a 16-byte salt would + /// > work well (by definition, UUID are very good salts, and they encode + /// > over exactly 16 bytes). 16 bytes encode as 22 characters in B64. + /// + /// [PHC string format specification]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties + pub const RECOMMENDED_LENGTH: usize = 16; + + /// Create a [`Salt`] from the given B64-encoded input string, validating + /// [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions. + pub fn from_b64(input: &'a str) -> Result { + let length = input.len(); + + if length < Self::MIN_LENGTH { + return Err(Error::SaltInvalid(InvalidValue::TooShort)); + } + + if length > Self::MAX_LENGTH { + return Err(Error::SaltInvalid(InvalidValue::TooLong)); + } + + // TODO(tarcieri): full B64 decoding check? + for char in input.chars() { + // From the PHC string format spec: + // + // > The salt consists in a sequence of characters in: `[a-zA-Z0-9/+.-]` + // > (lowercase letters, uppercase letters, digits, /, +, . and -). + if !matches!(char, 'a'..='z' | 'A'..='Z' | '0'..='9' | '/' | '+' | '.' | '-') { + return Err(Error::SaltInvalid(InvalidValue::InvalidChar(char))); + } + } + + input.try_into().map(Self).map_err(|e| match e { + Error::ParamValueInvalid(value_err) => Error::SaltInvalid(value_err), + err => err, + }) + } + + /// Attempt to decode a B64-encoded [`Salt`] into bytes, writing the + /// decoded output into the provided buffer, and returning a slice of the + /// portion of the buffer containing the decoded result on success. + pub fn decode_b64<'b>(&self, buf: &'b mut [u8]) -> Result<&'b [u8]> { + self.0.b64_decode(buf) + } + + /// Borrow this value as a `str`. + pub fn as_str(&self) -> &'a str { + self.0.as_str() + } + + /// Get the length of this value in ASCII characters. + pub fn len(&self) -> usize { + self.as_str().len() + } + + /// Create a [`Salt`] from the given B64-encoded input string, validating + /// [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions. + #[deprecated(since = "0.5.0", note = "use `from_b64` instead")] + pub fn new(input: &'a str) -> Result { + Self::from_b64(input) + } + + /// Attempt to decode a B64-encoded [`Salt`] into bytes, writing the + /// decoded output into the provided buffer, and returning a slice of the + /// portion of the buffer containing the decoded result on success. + #[deprecated(since = "0.5.0", note = "use `decode_b64` instead")] + pub fn b64_decode<'b>(&self, buf: &'b mut [u8]) -> Result<&'b [u8]> { + self.decode_b64(buf) + } +} + +impl AsRef for Salt<'_> { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl<'a> TryFrom<&'a str> for Salt<'a> { + type Error = Error; + + fn try_from(input: &'a str) -> Result { + Self::from_b64(input) + } +} + +impl fmt::Display for Salt<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl fmt::Debug for Salt<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Salt({:?})", self.as_str()) + } +} + +/// Owned stack-allocated equivalent of [`Salt`]. +#[derive(Clone, Eq)] +pub struct SaltString { + /// ASCII-encoded characters which comprise the salt. + chars: [u8; Salt::MAX_LENGTH], + + /// Length of the string in ASCII characters (i.e. bytes). + length: u8, +} + +#[allow(clippy::len_without_is_empty)] +impl SaltString { + /// Generate a random B64-encoded [`SaltString`]. + #[cfg(feature = "rand_core")] + pub fn generate(mut rng: impl CryptoRngCore) -> Self { + let mut bytes = [0u8; Salt::RECOMMENDED_LENGTH]; + rng.fill_bytes(&mut bytes); + Self::encode_b64(&bytes).expect(INVARIANT_VIOLATED_MSG) + } + + /// Create a new [`SaltString`] from the given B64-encoded input string, + /// validating [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions. + pub fn from_b64(s: &str) -> Result { + // Assert `s` parses successfully as a `Salt` + Salt::from_b64(s)?; + + let len = s.len(); + + let mut bytes = [0u8; Salt::MAX_LENGTH]; + bytes[..len].copy_from_slice(s.as_bytes()); + + Ok(SaltString { + chars: bytes, + length: len as u8, // `Salt::from_b64` check prevents overflow + }) + } + + /// Decode this [`SaltString`] from B64 into the provided output buffer. + pub fn decode_b64<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8]> { + self.as_salt().decode_b64(buf) + } + + /// Encode the given byte slice as B64 into a new [`SaltString`]. + /// + /// Returns `Error` if the slice is too long. + pub fn encode_b64(input: &[u8]) -> Result { + let mut bytes = [0u8; Salt::MAX_LENGTH]; + let length = Encoding::B64.encode(input, &mut bytes)?.len() as u8; + Ok(Self { + chars: bytes, + length, + }) + } + + /// Borrow the contents of a [`SaltString`] as a [`Salt`]. + pub fn as_salt(&self) -> Salt<'_> { + Salt::from_b64(self.as_str()).expect(INVARIANT_VIOLATED_MSG) + } + + /// Borrow the contents of a [`SaltString`] as a `str`. + pub fn as_str(&self) -> &str { + str::from_utf8(&self.chars[..(self.length as usize)]).expect(INVARIANT_VIOLATED_MSG) + } + + /// Get the length of this value in ASCII characters. + pub fn len(&self) -> usize { + self.as_str().len() + } + + /// Create a new [`SaltString`] from the given B64-encoded input string, + /// validating [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions. + #[deprecated(since = "0.5.0", note = "use `from_b64` instead")] + pub fn new(s: &str) -> Result { + Self::from_b64(s) + } + + /// Decode this [`SaltString`] from B64 into the provided output buffer. + #[deprecated(since = "0.5.0", note = "use `decode_b64` instead")] + pub fn b64_decode<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8]> { + self.decode_b64(buf) + } + + /// Encode the given byte slice as B64 into a new [`SaltString`]. + /// + /// Returns `Error` if the slice is too long. + #[deprecated(since = "0.5.0", note = "use `encode_b64` instead")] + pub fn b64_encode(input: &[u8]) -> Result { + Self::encode_b64(input) + } +} + +impl AsRef for SaltString { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl PartialEq for SaltString { + fn eq(&self, other: &Self) -> bool { + // Ensure comparisons always honor the initialized portion of the buffer + self.as_ref().eq(other.as_ref()) + } +} + +impl<'a> From<&'a SaltString> for Salt<'a> { + fn from(salt_string: &'a SaltString) -> Salt<'a> { + salt_string.as_salt() + } +} + +impl fmt::Display for SaltString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl fmt::Debug for SaltString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "SaltString({:?})", self.as_str()) + } +} + +#[cfg(test)] +mod tests { + use super::{Error, Salt}; + use crate::errors::InvalidValue; + + #[test] + fn new_with_valid_min_length_input() { + let s = "abcd"; + let salt = Salt::from_b64(s).unwrap(); + assert_eq!(salt.as_ref(), s); + } + + #[test] + fn new_with_valid_max_length_input() { + let s = "012345678911234567892123456789312345678941234567"; + let salt = Salt::from_b64(s).unwrap(); + assert_eq!(salt.as_ref(), s); + } + + #[test] + fn reject_new_too_short() { + for &too_short in &["", "a", "ab", "abc"] { + let err = Salt::from_b64(too_short).err().unwrap(); + assert_eq!(err, Error::SaltInvalid(InvalidValue::TooShort)); + } + } + + #[test] + fn reject_new_too_long() { + let s = "01234567891123456789212345678931234567894123456785234567896234567"; + let err = Salt::from_b64(s).err().unwrap(); + assert_eq!(err, Error::SaltInvalid(InvalidValue::TooLong)); + } + + #[test] + fn reject_new_invalid_char() { + let s = "01234_abcd"; + let err = Salt::from_b64(s).err().unwrap(); + assert_eq!(err, Error::SaltInvalid(InvalidValue::InvalidChar('_'))); + } +} diff --git a/password-hash/src/traits.rs b/password-hash/src/traits.rs new file mode 100644 index 000000000..4c9952e90 --- /dev/null +++ b/password-hash/src/traits.rs @@ -0,0 +1,101 @@ +//! Trait definitions. + +use crate::{Decimal, Error, Ident, ParamsString, PasswordHash, Result, Salt}; +use core::fmt::Debug; + +/// Trait for password hashing functions. +pub trait PasswordHasher { + /// Algorithm-specific parameters. + type Params: Clone + + Debug + + Default + + for<'a> TryFrom<&'a PasswordHash<'a>, Error = Error> + + TryInto; + + /// Compute a [`PasswordHash`] from the provided password using an + /// explicit set of customized algorithm parameters as opposed to the + /// defaults. + /// + /// When in doubt, use [`PasswordHasher::hash_password`] instead. + fn hash_password_customized<'a>( + &self, + password: &[u8], + algorithm: Option>, + version: Option, + params: Self::Params, + salt: impl Into>, + ) -> Result>; + + /// Simple API for computing a [`PasswordHash`] from a password and + /// salt value. + /// + /// Uses the default recommended parameters for a given algorithm. + fn hash_password<'a>( + &self, + password: &[u8], + salt: impl Into>, + ) -> Result> { + self.hash_password_customized(password, None, None, Self::Params::default(), salt) + } +} + +/// Trait for password verification. +/// +/// Automatically impl'd for any type that impls [`PasswordHasher`]. +/// +/// This trait is object safe and can be used to implement abstractions over +/// multiple password hashing algorithms. One such abstraction is provided by +/// the [`PasswordHash::verify_password`] method. +pub trait PasswordVerifier { + /// Compute this password hashing function against the provided password + /// using the parameters from the provided password hash and see if the + /// computed output matches. + fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()>; +} + +impl PasswordVerifier for T { + fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()> { + if let (Some(salt), Some(expected_output)) = (&hash.salt, &hash.hash) { + let computed_hash = self.hash_password_customized( + password, + Some(hash.algorithm), + hash.version, + T::Params::try_from(hash)?, + *salt, + )?; + + if let Some(computed_output) = &computed_hash.hash { + // See notes on `Output` about the use of a constant-time comparison + if expected_output == computed_output { + return Ok(()); + } + } + } + + Err(Error::Password) + } +} + +/// Trait for password hashing algorithms which support the legacy +/// [Modular Crypt Format (MCF)][MCF]. +/// +/// [MCF]: https://passlib.readthedocs.io/en/stable/modular_crypt_format.html +pub trait McfHasher { + /// Upgrade an MCF hash to a PHC hash. MCF follow this rough format: + /// + /// ```text + /// $$ + /// ``` + /// + /// MCF hashes are otherwise largely unstructured and parsed according to + /// algorithm-specific rules so hashers must parse a raw string themselves. + fn upgrade_mcf_hash<'a>(&self, hash: &'a str) -> Result>; + + /// Verify a password hash in MCF format against the provided password. + fn verify_mcf_hash(&self, password: &[u8], mcf_hash: &str) -> Result<()> + where + Self: PasswordVerifier, + { + self.verify_password(password, &self.upgrade_mcf_hash(mcf_hash)?) + } +} diff --git a/password-hash/src/value.rs b/password-hash/src/value.rs new file mode 100644 index 000000000..f60a75590 --- /dev/null +++ b/password-hash/src/value.rs @@ -0,0 +1,304 @@ +//! Algorithm parameter value as defined by the [PHC string format]. +//! +//! Implements the following parts of the specification: +//! +//! > The value for each parameter consists in characters in: `[a-zA-Z0-9/+.-]` +//! > (lowercase letters, uppercase letters, digits, /, +, . and -). No other +//! > character is allowed. Interpretation of the value depends on the +//! > parameter and the function. The function specification MUST unambiguously +//! > define the set of valid parameter values. The function specification MUST +//! > define a maximum length (in characters) for each parameter. For numerical +//! > parameters, functions SHOULD use plain decimal encoding (other encodings +//! > are possible as long as they are clearly defined). +//! +//! [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md + +use crate::errors::InvalidValue; +use crate::{Encoding, Error, Result}; +use core::{fmt, str}; + +/// Type used to represent decimal (i.e. integer) values. +pub type Decimal = u32; + +/// Algorithm parameter value string. +/// +/// Parameter values are defined in the [PHC string format specification][1]. +/// +/// # Constraints +/// - ASCII-encoded string consisting of the characters `[a-zA-Z0-9/+.-]` +/// (lowercase letters, digits, and the minus sign) +/// - Minimum length: 0 (i.e. empty values are allowed) +/// - Maximum length: 64 ASCII characters (i.e. 64-bytes) +/// +/// # Additional Notes +/// The PHC spec allows for algorithm-defined maximum lengths for parameter +/// values, however this library defines a [`Value::MAX_LENGTH`] of 64 ASCII +/// characters. +/// +/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md +/// [2]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct Value<'a>(&'a str); + +impl<'a> Value<'a> { + /// Maximum length of an [`Value`] - 64 ASCII characters (i.e. 64-bytes). + /// + /// This value is selected to match the maximum length of a [`Salt`][`crate::Salt`] + /// as this library internally uses this type to represent salts. + pub const MAX_LENGTH: usize = 64; + + /// Parse a [`Value`] from the provided `str`, validating it according to + /// the PHC string format's rules. + pub fn new(input: &'a str) -> Result { + if input.len() > Self::MAX_LENGTH { + return Err(Error::ParamValueInvalid(InvalidValue::TooLong)); + } + + // Check that the characters are permitted in a PHC parameter value. + assert_valid_value(input)?; + Ok(Self(input)) + } + + /// Attempt to decode a B64-encoded [`Value`], writing the decoded + /// result into the provided buffer, and returning a slice of the buffer + /// containing the decoded result on success. + /// + /// Examples of "B64"-encoded parameters in practice are the `keyid` and + /// `data` parameters used by the [Argon2 Encoding][1] as described in the + /// PHC string format specification. + /// + /// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding + pub fn b64_decode<'b>(&self, buf: &'b mut [u8]) -> Result<&'b [u8]> { + Ok(Encoding::B64.decode(self.as_str(), buf)?) + } + + /// Borrow this value as a `str`. + pub fn as_str(&self) -> &'a str { + self.0 + } + + /// Borrow this value as bytes. + pub fn as_bytes(&self) -> &'a [u8] { + self.as_str().as_bytes() + } + + /// Get the length of this value in ASCII characters. + pub fn len(&self) -> usize { + self.as_str().len() + } + + /// Is this value empty? + pub fn is_empty(&self) -> bool { + self.as_str().is_empty() + } + + /// Attempt to parse this [`Value`] as a PHC-encoded decimal (i.e. integer). + /// + /// Decimal values are integers which follow the rules given in the + /// ["Decimal Encoding" section of the PHC string format specification][1]. + /// + /// The decimal encoding rules are as follows: + /// > For an integer value x, its decimal encoding consist in the following: + /// > + /// > - If x < 0, then its decimal encoding is the minus sign - followed by the decimal + /// > encoding of -x. + /// > - If x = 0, then its decimal encoding is the single character 0. + /// > - If x > 0, then its decimal encoding is the smallest sequence of ASCII digits that + /// > matches its value (i.e. there is no leading zero). + /// > + /// > Thus, a value is a valid decimal for an integer x if and only if all of the following hold true: + /// > + /// > - The first character is either a - sign, or an ASCII digit. + /// > - All characters other than the first are ASCII digits. + /// > - If the first character is - sign, then there is at least another character, and the + /// > second character is not a 0. + /// > - If the string consists in more than one character, then the first one cannot be a 0. + /// + /// Note: this implementation does not support negative decimals despite + /// them being allowed per the spec above. If you need to parse a negative + /// number, please parse it from the string representation directly e.g. + /// `value.as_str().parse::()` + /// + /// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#decimal-encoding + pub fn decimal(&self) -> Result { + let value = self.as_str(); + + // Empty strings aren't decimals + if value.is_empty() { + return Err(Error::ParamValueInvalid(InvalidValue::Malformed)); + } + + // Ensure all characters are digits + for c in value.chars() { + if !c.is_ascii_digit() { + return Err(Error::ParamValueInvalid(InvalidValue::InvalidChar(c))); + } + } + + // Disallow leading zeroes + if value.starts_with('0') && value.len() > 1 { + return Err(Error::ParamValueInvalid(InvalidValue::InvalidFormat)); + } + + value.parse().map_err(|_| { + // In theory a value overflow should be the only potential error here. + // When `ParseIntError::kind` is stable it might be good to double check: + // + Error::ParamValueInvalid(InvalidValue::InvalidFormat) + }) + } + + /// Does this value parse successfully as a decimal? + pub fn is_decimal(&self) -> bool { + self.decimal().is_ok() + } +} + +impl AsRef for Value<'_> { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl<'a> TryFrom<&'a str> for Value<'a> { + type Error = Error; + + fn try_from(input: &'a str) -> Result { + Self::new(input) + } +} + +impl<'a> TryFrom> for Decimal { + type Error = Error; + + fn try_from(value: Value<'a>) -> Result { + Decimal::try_from(&value) + } +} + +impl<'a> TryFrom<&Value<'a>> for Decimal { + type Error = Error; + + fn try_from(value: &Value<'a>) -> Result { + value.decimal() + } +} + +impl fmt::Display for Value<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// Are all of the given bytes allowed in a [`Value`]? +fn assert_valid_value(input: &str) -> Result<()> { + for c in input.chars() { + if !is_char_valid(c) { + return Err(Error::ParamValueInvalid(InvalidValue::InvalidChar(c))); + } + } + + Ok(()) +} + +/// Ensure the given ASCII character (i.e. byte) is allowed in a [`Value`]. +fn is_char_valid(c: char) -> bool { + matches!(c, 'A' ..= 'Z' | 'a'..='z' | '0'..='9' | '/' | '+' | '.' | '-') +} + +#[cfg(test)] +mod tests { + use super::{Error, InvalidValue, Value}; + + // Invalid value examples + const INVALID_CHAR: &str = "x;y"; + const INVALID_TOO_LONG: &str = + "01234567891123456789212345678931234567894123456785234567896234567"; + const INVALID_CHAR_AND_TOO_LONG: &str = + "0!234567891123456789212345678931234567894123456785234567896234567"; + + // + // Decimal parsing tests + // + + #[test] + fn decimal_value() { + let valid_decimals = &[("0", 0u32), ("1", 1u32), ("4294967295", u32::MAX)]; + + for &(s, i) in valid_decimals { + let value = Value::new(s).unwrap(); + assert!(value.is_decimal()); + assert_eq!(value.decimal().unwrap(), i) + } + } + + #[test] + fn reject_decimal_with_leading_zero() { + let value = Value::new("01").unwrap(); + let err = u32::try_from(value).err().unwrap(); + assert!(matches!( + err, + Error::ParamValueInvalid(InvalidValue::InvalidFormat) + )); + } + + #[test] + fn reject_overlong_decimal() { + let value = Value::new("4294967296").unwrap(); + let err = u32::try_from(value).err().unwrap(); + assert_eq!(err, Error::ParamValueInvalid(InvalidValue::InvalidFormat)); + } + + #[test] + fn reject_negative() { + let value = Value::new("-1").unwrap(); + let err = u32::try_from(value).err().unwrap(); + assert!(matches!( + err, + Error::ParamValueInvalid(InvalidValue::InvalidChar(_)) + )); + } + + // + // String parsing tests + // + + #[test] + fn string_value() { + let valid_examples = [ + "", + "X", + "x", + "xXx", + "a+b.c-d", + "1/2", + "01234567891123456789212345678931", + ]; + + for &example in &valid_examples { + let value = Value::new(example).unwrap(); + assert_eq!(value.as_str(), example); + } + } + + #[test] + fn reject_invalid_char() { + let err = Value::new(INVALID_CHAR).err().unwrap(); + assert!(matches!( + err, + Error::ParamValueInvalid(InvalidValue::InvalidChar(_)) + )); + } + + #[test] + fn reject_too_long() { + let err = Value::new(INVALID_TOO_LONG).err().unwrap(); + assert_eq!(err, Error::ParamValueInvalid(InvalidValue::TooLong)); + } + + #[test] + fn reject_invalid_char_and_too_long() { + let err = Value::new(INVALID_CHAR_AND_TOO_LONG).err().unwrap(); + assert_eq!(err, Error::ParamValueInvalid(InvalidValue::TooLong)); + } +} diff --git a/password-hash/tests/encoding.rs b/password-hash/tests/encoding.rs new file mode 100644 index 000000000..0d7c16f2e --- /dev/null +++ b/password-hash/tests/encoding.rs @@ -0,0 +1,38 @@ +//! Base64 encoding tests. +//! +//! # B64 Notes +//! +//! "B64" is a ubset of the standard Base64 encoding (RFC 4648, section 4) which +//! omits padding (`=`) as well as extra whitespace, as described in the PHC +//! string format specification: +//! +//! + +use password_hash::{Output, Salt}; + +// Example salt encoded as a B64 string. +const EXAMPLE_SALT_B64: &str = "REVBREJFRUZERUFEQkVFRg"; +const EXAMPLE_SALT_RAW: &[u8] = b"DEADBEEFDEADBEEF"; + +// Example PHF output encoded as a B64 string. +const EXAMPLE_OUTPUT_B64: &str = + "REVBREJFRUZERUFEQkVFRkRFQURCRUVGREVBREJFRUZERUFEQkVFRkRFQURCRUVGREVBREJFRUZERUFEQkVFRg"; +const EXAMPLE_OUTPUT_RAW: &[u8] = + b"DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"; + +#[test] +fn salt_roundtrip() { + let mut buffer = [0u8; 64]; + let salt = Salt::from_b64(EXAMPLE_SALT_B64).unwrap(); + assert_eq!(salt.as_ref(), EXAMPLE_SALT_B64); + + let salt_decoded = salt.decode_b64(&mut buffer).unwrap(); + assert_eq!(salt_decoded, EXAMPLE_SALT_RAW); +} + +#[test] +fn output_roundtrip() { + let out = EXAMPLE_OUTPUT_B64.parse::().unwrap(); + assert_eq!(out.as_ref(), EXAMPLE_OUTPUT_RAW); + assert_eq!(out.to_string(), EXAMPLE_OUTPUT_B64); +} diff --git a/password-hash/tests/hashing.rs b/password-hash/tests/hashing.rs new file mode 100644 index 000000000..7c53a72fc --- /dev/null +++ b/password-hash/tests/hashing.rs @@ -0,0 +1,88 @@ +//! Password hashing tests + +pub use password_hash::{ + Decimal, Error, Ident, Output, ParamsString, PasswordHash, PasswordHasher, Result, Salt, +}; + +const ALG: Ident = Ident::new_unwrap("example"); + +/// Stub password hashing function for testing. +pub struct StubPasswordHasher; + +impl PasswordHasher for StubPasswordHasher { + type Params = StubParams; + + fn hash_password_customized<'a>( + &self, + password: &[u8], + algorithm: Option>, + version: Option, + params: StubParams, + salt: impl Into>, + ) -> Result> { + let salt = salt.into(); + let mut output = Vec::new(); + + if let Some(alg) = algorithm { + if alg != ALG { + return Err(Error::Algorithm); + } + } + + for slice in &[b"pw", password, b",salt:", salt.as_str().as_bytes()] { + output.extend_from_slice(slice); + } + + let hash = Output::new(&output)?; + + Ok(PasswordHash { + algorithm: ALG, + version, + params: params.try_into()?, + salt: Some(salt), + hash: Some(hash), + }) + } +} + +/// Stub parameters +#[derive(Clone, Debug, Default)] +pub struct StubParams; + +impl<'a> TryFrom<&PasswordHash<'a>> for StubParams { + type Error = Error; + + fn try_from(_: &PasswordHash<'a>) -> Result { + Ok(Self) + } +} + +impl TryFrom for ParamsString { + type Error = Error; + + fn try_from(_: StubParams) -> Result { + Ok(Self::default()) + } +} + +#[test] +fn verify_password_hash() { + let valid_password = "test password"; + let salt = Salt::from_b64("test-salt").unwrap(); + let hash = PasswordHash::generate(StubPasswordHasher, valid_password, salt).unwrap(); + + // Sanity tests for StubFunction impl above + assert_eq!(hash.algorithm, ALG); + assert_eq!(hash.salt.unwrap(), salt); + + // Tests for generic password verification logic + assert_eq!( + hash.verify_password(&[&StubPasswordHasher], valid_password), + Ok(()) + ); + + assert_eq!( + hash.verify_password(&[&StubPasswordHasher], "wrong password"), + Err(Error::Password) + ); +} diff --git a/password-hash/tests/password_hash.rs b/password-hash/tests/password_hash.rs new file mode 100644 index 000000000..a8c7a2cc9 --- /dev/null +++ b/password-hash/tests/password_hash.rs @@ -0,0 +1,146 @@ +//! Tests for `PasswordHash` encoding/decoding. +//! +//! Each test implements a different permutation of the possible combinations +//! of the string encoding, and ensures password hashes round trip under each +//! of the conditions. + +use password_hash::{Ident, ParamsString, PasswordHash, Salt}; + +const EXAMPLE_ALGORITHM: Ident = Ident::new_unwrap("argon2d"); +const EXAMPLE_SALT: &str = "saltsaltsaltsaltsalt"; +const EXAMPLE_HASH: &[u8] = &[ + 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, + 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, 0x21, 0x85, 0xab, +]; + +/// Example parameters +fn example_params() -> ParamsString { + let mut params = ParamsString::new(); + params.add_decimal("a", 1).unwrap(); + params.add_decimal("b", 2).unwrap(); + params.add_decimal("c", 3).unwrap(); + params +} + +#[test] +fn algorithm_alone() { + let ph = PasswordHash::new("$argon2d").unwrap(); + assert_eq!(ph.algorithm, EXAMPLE_ALGORITHM); + + let s = ph.to_string(); + assert_eq!(s, "$argon2d"); + + let ph2 = PasswordHash::try_from(s.as_str()).unwrap(); + assert_eq!(ph, ph2); +} + +#[test] +fn params() { + let ph = PasswordHash { + algorithm: EXAMPLE_ALGORITHM, + version: None, + params: example_params(), + salt: None, + hash: None, + }; + + let s = ph.to_string(); + assert_eq!(s, "$argon2d$a=1,b=2,c=3"); + + let ph2 = PasswordHash::try_from(s.as_str()).unwrap(); + assert_eq!(ph, ph2); +} + +#[test] +fn salt() { + let ph = PasswordHash { + algorithm: EXAMPLE_ALGORITHM, + version: None, + params: ParamsString::new(), + salt: Some(Salt::from_b64(EXAMPLE_SALT).unwrap()), + hash: None, + }; + + let s = ph.to_string(); + assert_eq!(s, "$argon2d$saltsaltsaltsaltsalt"); + + let ph2 = PasswordHash::try_from(s.as_str()).unwrap(); + assert_eq!(ph, ph2); +} + +#[test] +fn one_param_and_salt() { + let mut params = ParamsString::new(); + params.add_decimal("a", 1).unwrap(); + + let ph = PasswordHash { + algorithm: EXAMPLE_ALGORITHM, + version: None, + params, + salt: Some(Salt::from_b64(EXAMPLE_SALT).unwrap()), + hash: None, + }; + + let s = ph.to_string(); + assert_eq!(s, "$argon2d$a=1$saltsaltsaltsaltsalt"); + + let ph2 = PasswordHash::try_from(s.as_str()).unwrap(); + assert_eq!(ph, ph2); +} + +#[test] +fn params_and_salt() { + let ph = PasswordHash { + algorithm: EXAMPLE_ALGORITHM, + version: None, + params: example_params(), + salt: Some(Salt::from_b64(EXAMPLE_SALT).unwrap()), + hash: None, + }; + + let s = ph.to_string(); + assert_eq!(s, "$argon2d$a=1,b=2,c=3$saltsaltsaltsaltsalt"); + + let ph2 = PasswordHash::try_from(s.as_str()).unwrap(); + assert_eq!(ph, ph2); +} + +#[test] +fn salt_and_hash() { + let ph = PasswordHash { + algorithm: EXAMPLE_ALGORITHM, + version: None, + params: ParamsString::default(), + salt: Some(Salt::from_b64(EXAMPLE_SALT).unwrap()), + hash: Some(EXAMPLE_HASH.try_into().unwrap()), + }; + + let s = ph.to_string(); + assert_eq!( + s, + "$argon2d$saltsaltsaltsaltsalt$hashhashhashhashhashhashhashhashhashhashhas" + ); + + let ph2 = PasswordHash::try_from(s.as_str()).unwrap(); + assert_eq!(ph, ph2); +} + +#[test] +fn all_fields() { + let ph = PasswordHash { + algorithm: EXAMPLE_ALGORITHM, + version: None, + params: example_params(), + salt: Some(Salt::from_b64(EXAMPLE_SALT).unwrap()), + hash: Some(EXAMPLE_HASH.try_into().unwrap()), + }; + + let s = ph.to_string(); + assert_eq!( + s, + "$argon2d$a=1,b=2,c=3$saltsaltsaltsaltsalt$hashhashhashhashhashhashhashhashhashhashhas" + ); + + let ph2 = PasswordHash::try_from(s.as_str()).unwrap(); + assert_eq!(ph, ph2); +} diff --git a/password-hash/tests/test_vectors.rs b/password-hash/tests/test_vectors.rs new file mode 100644 index 000000000..d1c20a2b4 --- /dev/null +++ b/password-hash/tests/test_vectors.rs @@ -0,0 +1,51 @@ +//! Test vectors for commonly used password hashing algorithms. + +use password_hash::{Ident, PasswordHash}; + +const ARGON2D_HASH: &str = + "$argon2d$v=19$m=512,t=3,p=2$5VtWOO3cGWYQHEMaYGbsfQ$AcmqasQgW/wI6wAHAMk4aQ"; +const BCRYPT_HASH: &str = "$2b$MTIzNA$i5btSOiulHhaPHPbgNUGdObga/GCAVG/y5HHY1ra7L0C9dpCaw8u"; +const SCRYPT_HASH: &str = + "$scrypt$epIxT/h6HbbwHaehFnh/bw$7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0"; + +#[test] +fn argon2id() { + let ph = PasswordHash::new(ARGON2D_HASH).unwrap(); + assert_eq!(ph.algorithm, Ident::new("argon2d").unwrap()); + assert_eq!(ph.version, Some(19)); + assert_eq!(ph.params.iter().count(), 3); + assert_eq!(ph.params.get_decimal("m").unwrap(), 512); + assert_eq!(ph.params.get_decimal("t").unwrap(), 3); + assert_eq!(ph.params.get_decimal("p").unwrap(), 2); + assert_eq!(ph.salt.unwrap().as_ref(), "5VtWOO3cGWYQHEMaYGbsfQ"); + assert_eq!(ph.hash.unwrap().to_string(), "AcmqasQgW/wI6wAHAMk4aQ"); + assert_eq!(ph.to_string(), ARGON2D_HASH); +} + +#[test] +fn bcrypt() { + let ph = PasswordHash::new(BCRYPT_HASH).unwrap(); + assert_eq!(ph.algorithm, Ident::new("2b").unwrap()); + assert_eq!(ph.version, None); + assert_eq!(ph.params.len(), 0); + assert_eq!(ph.salt.unwrap().to_string(), "MTIzNA"); + assert_eq!( + ph.hash.unwrap().to_string(), + "i5btSOiulHhaPHPbgNUGdObga/GCAVG/y5HHY1ra7L0C9dpCaw8u" + ); + assert_eq!(ph.to_string(), BCRYPT_HASH); +} + +#[test] +fn scrypt() { + let ph = PasswordHash::new(SCRYPT_HASH).unwrap(); + assert_eq!(ph.algorithm, Ident::new("scrypt").unwrap()); + assert_eq!(ph.version, None); + assert_eq!(ph.params.len(), 0); + assert_eq!(ph.salt.unwrap().to_string(), "epIxT/h6HbbwHaehFnh/bw"); + assert_eq!( + ph.hash.unwrap().to_string(), + "7H0vsXlY8UxxyW/BWx/9GuY7jEvGjT71GFd6O4SZND0" + ); + assert_eq!(ph.to_string(), SCRYPT_HASH); +} diff --git a/signature/CHANGELOG.md b/signature/CHANGELOG.md new file mode 100644 index 000000000..554e0f5fd --- /dev/null +++ b/signature/CHANGELOG.md @@ -0,0 +1,253 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased +### Added +- `RandomizedSignerMut` trait + +[#1448](https://github.com/RustCrypto/traits/pull/1448) + +## 2.2.0 (2023-11-12) +### Changed +- MSRV 1.60 ([#1387]) + +### Fixed +- No longer vendoring async/derive crates unintentionally ([#1391]) + +[#1387]: https://github.com/RustCrypto/traits/pull/1387 +[#1391]: https://github.com/RustCrypto/traits/pull/1391 + +## 2.1.0 (2023-04-01) +### Added +- `SignatureEncoding::encoded_len` ([#1283]) + +[#1283]: https://github.com/RustCrypto/traits/pull/1283 + +## 2.0.0 (2023-01-15) +### Added +- `SignatureEncoding` trait as a replacement for `Signature` trait and the + now removed `AsRef<[u8]>` bound on signatures ([#1141]) +- New `Keypair` trait which returns owned keys instead of borrowed ([#1141]) + +### Changed +- `derive-preview` has been renamed to `derive` and stabilized ([#1141]) +- `digest-preview` renamed to `digest`, still unstable ([#1210]) +- `hazmat-preview` feature stabilized and removed, always on ([#1141]) +- `rand-preview` renamed to `rand_core`, still unstable ([#1210]) +- `std` feature is no longer enabled by default ([#1141]) +- Old `Keypair` trait renamed to `KeypairRef` ([#1141]) +- Signature generic parameter removed from `Keypair`/`KeypairRef` ([#1141]) +- Use `&mut impl CryptoRngCore` RNG arguments ([#1147]) + +### Removed +- `Signature` trait - replaced by `SignatureEncoding` ([#1141]) +- `hazmat-preview` feature, now always on ([#1141]) + +[#1141]: https://github.com/RustCrypto/traits/pull/1141 +[#1147]: https://github.com/RustCrypto/traits/pull/1147 +[#1210]: https://github.com/RustCrypto/traits/pull/1141 + +## 1.6.4 (2022-10-06) +### Added +- `RandomizedPrehashSigner` trait in `hazmat` module ([#1130]) + +[#1130]: https://github.com/RustCrypto/traits/pull/1130 + +## 1.6.3 (2022-09-16) +### Changed +- Bump `signature_derive` to v1.0.0-pre.7 ([#1119]) + +[#1119]: https://github.com/RustCrypto/traits/pull/1119 + +## 1.6.2 (2022-09-15) +### Changed +- Relax `Keypair` type bounds ([#1107]) + +[#1107]: https://github.com/RustCrypto/traits/pull/1107 + +## 1.6.1 (2022-09-12) [YANKED] +### Added +- `hazmat-preview` feature with `PrehashSigner`/`PrehashVerifier` traits ([#1099]) + +### Changed +- Bump `signature_derive` to v1.0.0-pre.6 ([#1104]) + +[#1099]: https://github.com/RustCrypto/traits/pull/1099 +[#1104]: https://github.com/RustCrypto/traits/pull/1104 + +## 1.6.0 (2022-08-14) [YANKED] +### Added +- `Keypair` trait ([#1080]) + +### Changed +- Rust 2021 edition upgrade; MSRV 1.56 ([#1081]) +- Bump `signature_derive` dependency to v1.0.0-pre.5 ([#1082]) +- Bump `hex-literal` dependency to v0.3 ([#1083]) + +[#1080]: https://github.com/RustCrypto/traits/pull/1080 +[#1081]: https://github.com/RustCrypto/traits/pull/1081 +[#1082]: https://github.com/RustCrypto/traits/pull/1082 +[#1083]: https://github.com/RustCrypto/traits/pull/1083 + +## 1.5.0 (2022-01-04) +### Changed +- Bump `digest` dependency to v0.10 ([#850]) +- Bump `signature-derive` dependency to v1.0.0-pre.4 ([#866]) + +[#850]: https://github.com/RustCrypto/traits/pull/850 +[#866]: https://github.com/RustCrypto/traits/pull/866 + +## 1.4.0 (2021-10-20) +### Added +- Re-export `rand_core` when the `rand-preview` feature is enabled ([#683]) +- `SignerMut` trait ([#734]) + +### Fixed +- Show error source in `Display` impl ([#791]) + +[#683]: https://github.com/RustCrypto/traits/pull/683 +[#734]: https://github.com/RustCrypto/traits/pull/734 +[#791]: https://github.com/RustCrypto/traits/pull/791 + +## 1.3.2 (2021-10-21) +### Fixed +- Backport changes from [#791] to the 1.3.x series. + +## 1.3.1 (2021-06-29) +### Added +- `Result` alias ([#676]) + +[#676]: https://github.com/RustCrypto/traits/pull/676 + +## 1.3.0 (2021-01-06) +### Changed +- Bump `rand_core` to v0.6 ([#457]) +- Bump `signature-derive` v1.0.0-pre.3 ([#459]) + +[#457]: https://github.com/RustCrypto/traits/pull/457 +[#459]: https://github.com/RustCrypto/traits/pull/459 + +## 1.2.2 (2020-07-29) +### Added +- `RandomizedDigestSigner` ([#235]) + +[#235]: https://github.com/RustCrypto/traits/pull/235 + +## 1.2.1 (2020-07-29) +### Removed +- RNG generic parameter `R` from `RandomizedSigner` ([#231]) + +[#231]: https://github.com/RustCrypto/traits/pull/231 + +## 1.2.0 (2020-07-29) [YANKED] +- Note: this release was published without the intended changes + +## 1.1.0 (2020-06-09) +### Changed +- Upgrade `digest` to v0.9; MSRV 1.41+ ([#186]) + +[#186]: https://github.com/RustCrypto/traits/pull/186 + +## 1.0.1 (2020-04-19) +### Changed +- Upgrade `signature_derive` to v1.0.0-pre.2 ([#98]) + +[#98]: https://github.com/RustCrypto/traits/pull/98 + +## 1.0.0 (2020-04-18) + +Initial 1.0 release! 🎉 + +### Changed +- Rename `DigestSignature` => `PrehashSignature` ([#96]) + +[#96]: https://github.com/RustCrypto/traits/pull/96 + +## 1.0.0-pre.5 (2020-03-16) +### Changed +- Improve `Debug` impl on `Error` ([#89]) +- Rename `Signature::as_slice` -> `as_bytes` ([#87]) + +[#89]: https://github.com/RustCrypto/traits/pull/89 +[#87]: https://github.com/RustCrypto/traits/pull/87 + +## 1.0.0-pre.4 (2020-03-15) +### Added +- Mark preview features as unstable in `Cargo.toml` ([#82]) + +### Changed +- Have `Signature::from_bytes` take a byte slice ([#84]) +- Ensure `Error::new()` is mandatory ([#83]) + +### Removed +- `BoxError` type alias ([#81]) + +[#84]: https://github.com/RustCrypto/traits/pull/84 +[#83]: https://github.com/RustCrypto/traits/pull/83 +[#82]: https://github.com/RustCrypto/traits/pull/82 +[#81]: https://github.com/RustCrypto/traits/pull/81 + +## 1.0.0-pre.3 (2020-03-08) +### Fixed +- docs.rs rendering ([#76]) + +[#76]: https://github.com/RustCrypto/traits/pull/76 + +## 1.0.0-pre.2 (2020-03-08) +### Added +- `RandomizedSigner` trait ([#73]) +- Design documentation ([#72]) + +### Changed +- Error cleanups ([#74]) +- Crate moved to `RustCrypto/traits` ([#71]) + +[#74]: https://github.com/RustCrypto/traits/pull/74 +[#73]: https://github.com/RustCrypto/traits/pull/73 +[#72]: https://github.com/RustCrypto/traits/pull/72 +[#71]: https://github.com/RustCrypto/traits/pull/71 + +## 1.0.0-pre.1 (2019-10-27) +### Changed +- Use `Error::source` instead of `::cause` ([RustCrypto/signatures#37]) + +### Removed +- Remove `alloc` feature; MSRV 1.34+ ([RustCrypto/signatures#38]) + +[RustCrypto/signatures#38]: https://github.com/RustCrypto/signatures/pull/38 +[RustCrypto/signatures#37]: https://github.com/RustCrypto/signatures/pull/37 + +## 1.0.0-pre.0 (2019-10-11) +### Changed +- Revert removal of `DigestSignature` ([RustCrypto/signatures#33]) +- 1.0 stabilization proposal ([RustCrypto/signatures#32]) + +[RustCrypto/signatures#33]: https://github.com/RustCrypto/signatures/pull/33 +[RustCrypto/signatures#32]: https://github.com/RustCrypto/signatures/pull/32 + +## 0.3.0 (2019-10-10) +### Changed +- Simplify alloc gating; MSRV 1.36+ ([RustCrypto/signatures#28]) +- Replace `DigestSignature` trait with `#[digest(...)]` attribute ([RustCrypto/signatures#27]) +- signature_derive: Upgrade to 1.x proc macro crates ([RustCrypto/signatures#26]) + +[RustCrypto/signatures#28]: https://github.com/RustCrypto/signatures/pull/28 +[RustCrypto/signatures#27]: https://github.com/RustCrypto/signatures/pull/27 +[RustCrypto/signatures#26]: https://github.com/RustCrypto/signatures/pull/27 + +## 0.2.0 (2019-06-06) +### Added +- `signature_derive`: Custom derive support for `Signer`/`Verifier` ([RustCrypto/signatures#18]) + +### Changed +- Have `DigestSigner`/`DigestVerifier` take `Digest` instance ([RustCrypto/signatures#17]) + +[RustCrypto/signatures#18]: https://github.com/RustCrypto/signatures/pull/18 +[RustCrypto/signatures#17]: https://github.com/RustCrypto/signatures/pull/17 + +## 0.1.0 (2019-05-25) + +- Initial release diff --git a/signature/Cargo.toml b/signature/Cargo.toml new file mode 100644 index 000000000..921f0756a --- /dev/null +++ b/signature/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "signature" +description = "Traits for cryptographic signature algorithms (e.g. ECDSA, Ed25519)" +version = "2.3.0-pre.4" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +documentation = "https://docs.rs/signature" +homepage = "https://github.com/RustCrypto/traits/tree/master/signature" +repository = "https://github.com/RustCrypto/traits" +readme = "README.md" +keywords = ["crypto", "ecdsa", "ed25519", "signature", "signing"] +categories = ["cryptography", "no-std"] +edition = "2021" +rust-version = "1.81" + +[dependencies] +derive = { package = "signature_derive", version = "2", optional = true, path = "../signature_derive" } +digest = { version = "=0.11.0-pre.9", optional = true, default-features = false } +rand_core = { version = "0.6.4", optional = true, default-features = false } + +[dev-dependencies] +hex-literal = "0.4" +sha2 = { version = "=0.11.0-pre.4", default-features = false } + +[features] +alloc = [] +# TODO: remove this feature in the next breaking release +std = ["alloc", "rand_core?/std"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/signature/LICENSE-APACHE b/signature/LICENSE-APACHE new file mode 100644 index 000000000..78173fa2e --- /dev/null +++ b/signature/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/signature/LICENSE-MIT b/signature/LICENSE-MIT new file mode 100644 index 000000000..d8d87fe29 --- /dev/null +++ b/signature/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018-2023 RustCrypto Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/signature/README.md b/signature/README.md new file mode 100644 index 000000000..1da58187e --- /dev/null +++ b/signature/README.md @@ -0,0 +1,71 @@ +# [RustCrypto]: Digital Signature Algorithms + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] + +This crate contains traits which provide generic, object-safe APIs for +generating and verifying [digital signatures]. + +Used by the [`dsa`], [`ecdsa`], [`ed25519`], and [`rsa`] crates maintained by +the [RustCrypto] organization, as well as [`ed25519-dalek`]. + +[Documentation][docs-link] + +## Minimum Supported Rust Version + +Rust **1.81** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above +- The `derive` feature is stable and covered by SemVer +- The off-by-default features `digest` and `rand_core` are unstable features + which are also considered exempt from SemVer as they correspond to pre-1.0 + crates which are still subject to changes. Breaking changes to these features + will, like MSRV, be done with a minor version bump. + +## License + +Licensed under either of + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/signature +[crate-link]: https://crates.io/crates/signature +[docs-image]: https://docs.rs/signature/badge.svg +[docs-link]: https://docs.rs/signature/ +[build-image]: https://github.com/RustCrypto/traits/actions/workflows/signature.yml/badge.svg +[build-link]: https://github.com/RustCrypto/traits/actions/workflows/signature.yml +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260048-signatures + +[//]: # (links) + +[RustCrypto]: https://github.com/RustCrypto/ +[digital signatures]: https://en.wikipedia.org/wiki/Digital_signature +[`dsa`]: https://github.com/RustCrypto/signatures/tree/master/dsa +[`ecdsa`]: https://github.com/RustCrypto/signatures/tree/master/ecdsa +[`ed25519`]: https://github.com/RustCrypto/signatures/tree/master/ed25519 +[`ed25519-dalek`]: https://github.com/dalek-cryptography/ed25519-dalek +[`rsa`]: https://github.com/RustCrypto/RSA diff --git a/signature/src/encoding.rs b/signature/src/encoding.rs new file mode 100644 index 000000000..8bc475b01 --- /dev/null +++ b/signature/src/encoding.rs @@ -0,0 +1,31 @@ +//! Encoding support. + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +/// Support for decoding/encoding signatures as bytes. +pub trait SignatureEncoding: + Clone + Sized + for<'a> TryFrom<&'a [u8]> + TryInto +{ + /// Byte representation of a signature. + type Repr: 'static + AsRef<[u8]> + Clone + Send + Sync; + + /// Encode signature as its byte representation. + fn to_bytes(&self) -> Self::Repr { + self.clone() + .try_into() + .ok() + .expect("signature encoding error") + } + + /// Encode signature as a byte vector. + #[cfg(feature = "alloc")] + fn to_vec(&self) -> Vec { + self.to_bytes().as_ref().to_vec() + } + + /// Get the length of this signature when encoded. + fn encoded_len(&self) -> usize { + self.to_bytes().as_ref().len() + } +} diff --git a/signature/src/error.rs b/signature/src/error.rs new file mode 100644 index 000000000..dc30176ce --- /dev/null +++ b/signature/src/error.rs @@ -0,0 +1,112 @@ +//! Signature error types + +use core::fmt::{self, Debug, Display}; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +/// Result type. +/// +/// A result with the `signature` crate's [`Error`] type. +pub type Result = core::result::Result; + +/// Signature errors. +/// +/// This type is deliberately opaque as to avoid sidechannel leakage which +/// could potentially be used recover signing private keys or forge signatures +/// (e.g. [BB'06]). +/// +/// When the `alloc` feature is enabled, it supports an optional [`core::error::Error::source`], +/// which can be used by things like remote signers (e.g. HSM, KMS) to report I/O or auth errors. +/// +/// [BB'06]: https://en.wikipedia.org/wiki/Daniel_Bleichenbacher +#[derive(Default)] +#[non_exhaustive] +pub struct Error { + /// Source of the error (if applicable). + #[cfg(feature = "alloc")] + source: Option>, +} + +impl Error { + /// Create a new error with no associated source + pub fn new() -> Self { + Self::default() + } + + /// Create a new error with an associated source. + /// + /// **NOTE:** The "source" should **NOT** be used to propagate cryptographic + /// errors e.g. signature parsing or verification errors. The intended use + /// cases are for propagating errors related to external signers, e.g. + /// communication/authentication errors with HSMs, KMS, etc. + #[cfg(feature = "alloc")] + pub fn from_source( + source: impl Into>, + ) -> Self { + Self { + source: Some(source.into()), + } + } +} + +impl Debug for Error { + #[cfg(not(feature = "alloc"))] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("signature::Error {}") + } + + #[cfg(feature = "alloc")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("signature::Error { source: ")?; + + if let Some(source) = &self.source { + write!(f, "Some({})", source)?; + } else { + f.write_str("None")?; + } + + f.write_str(" }") + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("signature error") + } +} + +#[cfg(feature = "alloc")] +impl From> for Error { + fn from(source: Box) -> Error { + Self::from_source(source) + } +} + +#[cfg(feature = "rand_core")] +impl From for Error { + #[cfg(not(feature = "alloc"))] + fn from(_source: rand_core::Error) -> Error { + Error::new() + } + + #[cfg(feature = "alloc")] + fn from(source: rand_core::Error) -> Error { + Error::from_source(source) + } +} + +impl core::error::Error for Error { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + #[cfg(not(feature = "alloc"))] + { + None + } + #[cfg(feature = "alloc")] + { + self.source + .as_ref() + .map(|source| source.as_ref() as &(dyn core::error::Error + 'static)) + } + } +} diff --git a/signature/src/hazmat.rs b/signature/src/hazmat.rs new file mode 100644 index 000000000..d2f3e9523 --- /dev/null +++ b/signature/src/hazmat.rs @@ -0,0 +1,70 @@ +//! Hazardous Materials: low-level APIs which can be insecure if misused. +//! +//! The traits in this module are not generally recommended, and should only be +//! used in special cases where they are specifically needed. +//! +//! Using them incorrectly can introduce security vulnerabilities. Please +//! carefully read the documentation before attempting to use them. + +use crate::Error; + +#[cfg(feature = "rand_core")] +use crate::rand_core::CryptoRngCore; + +/// Sign the provided message prehash, returning a digital signature. +pub trait PrehashSigner { + /// Attempt to sign the given message digest, returning a digital signature + /// on success, or an error if something went wrong. + /// + /// The `prehash` parameter should be the output of a secure cryptographic + /// hash function. + /// + /// This API takes a `prehash` byte slice as there can potentially be many + /// compatible lengths for the message digest for a given concrete signature + /// algorithm. + /// + /// Allowed lengths are algorithm-dependent and up to a particular + /// implementation to decide. + fn sign_prehash(&self, prehash: &[u8]) -> Result; +} + +/// Sign the provided message prehash using the provided external randomness source, returning a digital signature. +#[cfg(feature = "rand_core")] +pub trait RandomizedPrehashSigner { + /// Attempt to sign the given message digest, returning a digital signature + /// on success, or an error if something went wrong. + /// + /// The `prehash` parameter should be the output of a secure cryptographic + /// hash function. + /// + /// This API takes a `prehash` byte slice as there can potentially be many + /// compatible lengths for the message digest for a given concrete signature + /// algorithm. + /// + /// Allowed lengths are algorithm-dependent and up to a particular + /// implementation to decide. + fn sign_prehash_with_rng( + &self, + rng: &mut impl CryptoRngCore, + prehash: &[u8], + ) -> Result; +} + +/// Verify the provided message prehash using `Self` (e.g. a public key) +pub trait PrehashVerifier { + /// Use `Self` to verify that the provided signature for a given message + /// `prehash` is authentic. + /// + /// The `prehash` parameter should be the output of a secure cryptographic + /// hash function. + /// + /// Returns `Error` if it is inauthentic or some other error occurred, or + /// otherwise returns `Ok(())`. + /// + /// # ⚠️ Security Warning + /// + /// If `prehash` is something other than the output of a cryptographically + /// secure hash function, an attacker can potentially forge signatures by + /// solving a system of linear equations. + fn verify_prehash(&self, prehash: &[u8], signature: &S) -> Result<(), Error>; +} diff --git a/signature/src/keypair.rs b/signature/src/keypair.rs new file mode 100644 index 000000000..d4795f2f9 --- /dev/null +++ b/signature/src/keypair.rs @@ -0,0 +1,29 @@ +//! Signing keypairs. + +/// Signing keypair with an associated verifying key. +/// +/// This represents a type which holds both a signing key and a verifying key. +pub trait Keypair { + /// Verifying key type for this keypair. + type VerifyingKey: Clone; + + /// Get the verifying key which can verify signatures produced by the + /// signing key portion of this keypair. + fn verifying_key(&self) -> Self::VerifyingKey; +} + +/// Signing keypair with an associated verifying key. +/// +/// This represents a type which holds both a signing key and a verifying key. +pub trait KeypairRef: AsRef { + /// Verifying key type for this keypair. + type VerifyingKey: Clone; +} + +impl Keypair for K { + type VerifyingKey = ::VerifyingKey; + + fn verifying_key(&self) -> Self::VerifyingKey { + self.as_ref().clone() + } +} diff --git a/signature/src/lib.rs b/signature/src/lib.rs new file mode 100644 index 000000000..a3281f769 --- /dev/null +++ b/signature/src/lib.rs @@ -0,0 +1,157 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![forbid(unsafe_code)] +#![warn( + clippy::mod_module_files, + clippy::unwrap_used, + missing_docs, + rust_2018_idioms, + unused_lifetimes, + missing_debug_implementations, + unused_qualifications +)] + +//! # Design +//! +//! This crate provides a common set of traits for signing and verifying +//! digital signatures intended to be implemented by libraries which produce +//! or contain implementations of digital signature algorithms, and used by +//! libraries which want to produce or verify digital signatures while +//! generically supporting any compatible backend. +//! +//! ## Goals +//! +//! The traits provided by this crate were designed with the following goals +//! in mind: +//! +//! - Provide an easy-to-use, misuse resistant API optimized for consumers +//! (as opposed to implementers) of its traits. +//! - Support common type-safe wrappers around "bag-of-bytes" representations +//! which can be directly parsed from or written to the "wire". +//! - Expose a trait/object-safe API where signers/verifiers spanning multiple +//! homogeneous provider implementations can be seamlessly leveraged together +//! in the same logical "keyring" so long as they operate on the same +//! underlying signature type. +//! - Allow one provider type to potentially implement support (including +//! being generic over) several signature types. +//! - Keep signature algorithm customizations / "knobs" out-of-band from the +//! signing/verification APIs, ideally pushing such concerns into the type +//! system so that algorithm mismatches are caught as type errors. +//! - Opaque error type which minimizes information leaked from cryptographic +//! failures, as "rich" error types in these scenarios are often a source +//! of sidechannel information for attackers (e.g. [BB'06]) +//! +//! [BB'06]: https://en.wikipedia.org/wiki/Daniel_Bleichenbacher +//! +//! ## Implementation +//! +//! To accomplish the above goals, the [`Signer`] and [`Verifier`] traits +//! provided by this are generic over a signature value, and use generic +//! parameters rather than associated types. Notably, they use such a parameter +//! for the return value, allowing it to be inferred by the type checker based +//! on the desired signature type. +//! +//! ## Alternatives considered +//! +//! This crate is based on many years of exploration of how to encapsulate +//! digital signature systems in the most flexible, developer-friendly way. +//! During that time many design alternatives were explored, tradeoffs +//! compared, and ultimately the provided API was selected. +//! +//! The tradeoffs made in this API have all been to improve simplicity, +//! ergonomics, type safety, and flexibility for *consumers* of the traits. +//! At times, this has come at a cost to implementers. Below are some concerns +//! we are cognizant of which were considered in the design of the API: +//! +//! - "Bag-of-bytes" serialization precludes signature providers from using +//! their own internal representation of a signature, which can be helpful +//! for many reasons (e.g. advanced signature system features like batch +//! verification). +//! - Associated types, rather than generic parameters of traits, could allow +//! more customization of the types used by a particular signature system, +//! e.g. using custom error types. +//! +//! It may still make sense to continue to explore the above tradeoffs, but +//! with a *new* set of traits which are intended to be implementor-friendly, +//! rather than consumer friendly. The existing [`Signer`] and [`Verifier`] +//! traits could have blanket impls for the "provider-friendly" traits. +//! However, as noted above this is a design space easily explored after +//! stabilizing the consumer-oriented traits, and thus we consider these +//! more important. +//! +//! That said, below are some caveats of trying to design such traits, and +//! why we haven't actively pursued them: +//! +//! - Generics in the return position are already used to select which trait +//! impl to use, i.e. for a particular signature algorithm/system. Avoiding +//! a unified, concrete signature type adds another dimension to complexity +//! and compiler errors, and in our experience makes them unsuitable for this +//! sort of API. We believe such an API is the natural one for signature +//! systems, reflecting the natural way they are written absent a trait. +//! - Associated types preclude multiple implementations of the same trait. +//! These parameters are common in signature systems, notably ones which +//! support different serializations of a signature (e.g. raw vs ASN.1). +//! - Digital signatures are almost always larger than the present 32-entry +//! trait impl limitation on array types, which complicates bounds +//! for these types (particularly things like `From` or `Borrow` bounds). +//! +//! ## Unstable features +//! +//! Despite being post-1.0, this crate includes off-by-default unstable +//! optional features, each of which depends on a pre-1.0 +//! crate. +//! +//! These features are considered exempt from SemVer. See the +//! [SemVer policy](#semver-policy) above for more information. +//! +//! The following unstable features are presently supported: +//! +//! - `digest`: enables the [`DigestSigner`] and [`DigestVerifier`] +//! traits which are based on the [`Digest`] trait from the [`digest`] crate. +//! These traits are used for representing signature systems based on the +//! [Fiat-Shamir heuristic] which compute a random challenge value to sign +//! by computing a cryptographically secure digest of the input message. +//! - `rand_core`: enables the [`RandomizedSigner`] trait for signature +//! systems which rely on a cryptographically secure random number generator +//! for security. +//! +//! NOTE: the [`async-signature`] crate contains experimental `async` support +//! for [`Signer`] and [`DigestSigner`]. +//! +//! [`async-signature`]: https://docs.rs/async-signature +//! [`digest`]: https://docs.rs/digest/ +//! [`Digest`]: https://docs.rs/digest/latest/digest/trait.Digest.html +//! [Fiat-Shamir heuristic]: https://en.wikipedia.org/wiki/Fiat%E2%80%93Shamir_heuristic + +#[cfg(feature = "alloc")] +extern crate alloc; + +pub mod hazmat; + +mod encoding; +mod error; +mod keypair; +mod signer; +mod verifier; + +#[cfg(feature = "digest")] +mod prehash_signature; + +pub use crate::{encoding::*, error::*, keypair::*, signer::*, verifier::*}; + +#[cfg(feature = "derive")] +pub use derive::{Signer, Verifier}; + +#[cfg(all(feature = "derive", feature = "digest"))] +pub use derive::{DigestSigner, DigestVerifier}; + +#[cfg(feature = "digest")] +pub use {crate::prehash_signature::*, digest}; + +#[cfg(feature = "rand_core")] +pub use rand_core; diff --git a/signature/src/prehash_signature.rs b/signature/src/prehash_signature.rs new file mode 100644 index 000000000..d9a86456d --- /dev/null +++ b/signature/src/prehash_signature.rs @@ -0,0 +1,31 @@ +//! `PrehashSignature` trait. + +/// For intra-doc link resolution. +#[allow(unused_imports)] +use crate::{ + signer::{DigestSigner, Signer}, + verifier::{DigestVerifier, Verifier}, +}; + +/// Marker trait for `Signature` types computable as `𝐒(𝐇(𝒎))` +/// i.e. ones which prehash a message to be signed as `𝐇(𝒎)` +/// +/// Where: +/// +/// - `𝐒`: signature algorithm +/// - `𝐇`: hash (a.k.a. digest) function +/// - `𝒎`: message +/// +/// This approach is relatively common in signature schemes based on the +/// [Fiat-Shamir heuristic]. +/// +/// For signature types that implement this trait, when the `derive` crate +/// feature is enabled a custom derive for [`Signer`] is available for any +/// types that impl [`DigestSigner`], and likewise for deriving [`Verifier`] for +/// types which impl [`DigestVerifier`]. +/// +/// [Fiat-Shamir heuristic]: https://en.wikipedia.org/wiki/Fiat%E2%80%93Shamir_heuristic +pub trait PrehashSignature { + /// Preferred `Digest` algorithm to use when computing this signature type. + type Digest: digest::Digest; +} diff --git a/signature/src/signer.rs b/signature/src/signer.rs new file mode 100644 index 000000000..488f7da67 --- /dev/null +++ b/signature/src/signer.rs @@ -0,0 +1,145 @@ +//! Traits for generating digital signatures + +use crate::error::Error; + +#[cfg(feature = "digest")] +use crate::digest::Digest; + +#[cfg(feature = "rand_core")] +use crate::rand_core::CryptoRngCore; + +/// Sign the provided message bytestring using `Self` (e.g. a cryptographic key +/// or connection to an HSM), returning a digital signature. +pub trait Signer { + /// Sign the given message and return a digital signature + fn sign(&self, msg: &[u8]) -> S { + self.try_sign(msg).expect("signature operation failed") + } + + /// Attempt to sign the given message, returning a digital signature on + /// success, or an error if something went wrong. + /// + /// The main intended use case for signing errors is when communicating + /// with external signers, e.g. cloud KMS, HSMs, or other hardware tokens. + fn try_sign(&self, msg: &[u8]) -> Result; +} + +/// Sign the provided message bytestring using `&mut Self` (e.g. an evolving +/// cryptographic key such as a stateful hash-based signature), returning a +/// digital signature. +pub trait SignerMut { + /// Sign the given message, update the state, and return a digital signature. + fn sign(&mut self, msg: &[u8]) -> S { + self.try_sign(msg).expect("signature operation failed") + } + + /// Attempt to sign the given message, updating the state, and returning a + /// digital signature on success, or an error if something went wrong. + /// + /// Signing can fail, e.g., if the number of time periods allowed by the + /// current key is exceeded. + fn try_sign(&mut self, msg: &[u8]) -> Result; +} + +/// Blanket impl of [`SignerMut`] for all [`Signer`] types. +impl> SignerMut for T { + fn try_sign(&mut self, msg: &[u8]) -> Result { + T::try_sign(self, msg) + } +} + +/// Sign the given prehashed message [`Digest`] using `Self`. +/// +/// ## Notes +/// +/// This trait is primarily intended for signature algorithms based on the +/// [Fiat-Shamir heuristic], a method for converting an interactive +/// challenge/response-based proof-of-knowledge protocol into an offline +/// digital signature through the use of a random oracle, i.e. a digest +/// function. +/// +/// The security of such protocols critically rests upon the inability of +/// an attacker to solve for the output of the random oracle, as generally +/// otherwise such signature algorithms are a system of linear equations and +/// therefore doing so would allow the attacker to trivially forge signatures. +/// +/// To prevent misuse which would potentially allow this to be possible, this +/// API accepts a [`Digest`] instance, rather than a raw digest value. +/// +/// [Fiat-Shamir heuristic]: https://en.wikipedia.org/wiki/Fiat%E2%80%93Shamir_heuristic +#[cfg(feature = "digest")] +pub trait DigestSigner { + /// Sign the given prehashed message [`Digest`], returning a signature. + /// + /// Panics in the event of a signing error. + fn sign_digest(&self, digest: D) -> S { + self.try_sign_digest(digest) + .expect("signature operation failed") + } + + /// Attempt to sign the given prehashed message [`Digest`], returning a + /// digital signature on success, or an error if something went wrong. + fn try_sign_digest(&self, digest: D) -> Result; +} + +/// Sign the given message using the provided external randomness source. +#[cfg(feature = "rand_core")] +pub trait RandomizedSigner { + /// Sign the given message and return a digital signature + fn sign_with_rng(&self, rng: &mut impl CryptoRngCore, msg: &[u8]) -> S { + self.try_sign_with_rng(rng, msg) + .expect("signature operation failed") + } + + /// Attempt to sign the given message, returning a digital signature on + /// success, or an error if something went wrong. + /// + /// The main intended use case for signing errors is when communicating + /// with external signers, e.g. cloud KMS, HSMs, or other hardware tokens. + fn try_sign_with_rng(&self, rng: &mut impl CryptoRngCore, msg: &[u8]) -> Result; +} + +/// Combination of [`DigestSigner`] and [`RandomizedSigner`] with support for +/// computing a signature over a digest which requires entropy from an RNG. +#[cfg(all(feature = "digest", feature = "rand_core"))] +pub trait RandomizedDigestSigner { + /// Sign the given prehashed message `Digest`, returning a signature. + /// + /// Panics in the event of a signing error. + fn sign_digest_with_rng(&self, rng: &mut impl CryptoRngCore, digest: D) -> S { + self.try_sign_digest_with_rng(rng, digest) + .expect("signature operation failed") + } + + /// Attempt to sign the given prehashed message `Digest`, returning a + /// digital signature on success, or an error if something went wrong. + fn try_sign_digest_with_rng(&self, rng: &mut impl CryptoRngCore, digest: D) + -> Result; +} + +/// Sign the provided message bytestring using `&mut Self` (e.g. an evolving +/// cryptographic key such as a stateful hash-based signature), and a per-signature +/// randomizer, returning a digital signature. +#[cfg(feature = "rand_core")] +pub trait RandomizedSignerMut { + /// Sign the given message, update the state, and return a digital signature. + fn sign_with_rng(&mut self, rng: &mut impl CryptoRngCore, msg: &[u8]) -> S { + self.try_sign_with_rng(rng, msg) + .expect("signature operation failed") + } + + /// Attempt to sign the given message, updating the state, and returning a + /// digital signature on success, or an error if something went wrong. + /// + /// Signing can fail, e.g., if the number of time periods allowed by the + /// current key is exceeded. + fn try_sign_with_rng(&mut self, rng: &mut impl CryptoRngCore, msg: &[u8]) -> Result; +} + +/// Blanket impl of [`RandomizedSignerMut`] for all [`RandomizedSigner`] types. +#[cfg(feature = "rand_core")] +impl> RandomizedSignerMut for T { + fn try_sign_with_rng(&mut self, rng: &mut impl CryptoRngCore, msg: &[u8]) -> Result { + T::try_sign_with_rng(self, rng, msg) + } +} diff --git a/signature/src/verifier.rs b/signature/src/verifier.rs new file mode 100644 index 000000000..65409a929 --- /dev/null +++ b/signature/src/verifier.rs @@ -0,0 +1,41 @@ +//! Trait for verifying digital signatures + +use crate::error::Error; + +#[cfg(feature = "digest")] +use crate::digest::Digest; + +/// Verify the provided message bytestring using `Self` (e.g. a public key) +pub trait Verifier { + /// Use `Self` to verify that the provided signature for a given message + /// bytestring is authentic. + /// + /// Returns `Error` if it is inauthentic, or otherwise returns `()`. + fn verify(&self, msg: &[u8], signature: &S) -> Result<(), Error>; +} + +/// Verify the provided signature for the given prehashed message [`Digest`] +/// is authentic. +/// +/// ## Notes +/// +/// This trait is primarily intended for signature algorithms based on the +/// [Fiat-Shamir heuristic], a method for converting an interactive +/// challenge/response-based proof-of-knowledge protocol into an offline +/// digital signature through the use of a random oracle, i.e. a digest +/// function. +/// +/// The security of such protocols critically rests upon the inability of +/// an attacker to solve for the output of the random oracle, as generally +/// otherwise such signature algorithms are a system of linear equations and +/// therefore doing so would allow the attacker to trivially forge signatures. +/// +/// To prevent misuse which would potentially allow this to be possible, this +/// API accepts a [`Digest`] instance, rather than a raw digest value. +/// +/// [Fiat-Shamir heuristic]: https://en.wikipedia.org/wiki/Fiat%E2%80%93Shamir_heuristic +#[cfg(feature = "digest")] +pub trait DigestVerifier { + /// Verify the signature against the given [`Digest`] output. + fn verify_digest(&self, digest: D, signature: &S) -> Result<(), Error>; +} diff --git a/signature/tests/derive.rs b/signature/tests/derive.rs new file mode 100644 index 000000000..a63ece5ee --- /dev/null +++ b/signature/tests/derive.rs @@ -0,0 +1,85 @@ +//! Tests for code generated by `signature_derive` + +#![cfg(all(feature = "derive", feature = "digest"))] + +use digest::{array::Array, Digest, OutputSizeUser}; +use hex_literal::hex; +use sha2::Sha256; +use signature::{ + hazmat::{PrehashSigner, PrehashVerifier}, + DigestSigner, DigestVerifier, Error, PrehashSignature, SignatureEncoding, Signer, Verifier, +}; + +/// Test vector to compute SHA-256 digest of +const INPUT_STRING: &[u8] = b"abc"; + +/// Expected SHA-256 digest for the input string +const INPUT_STRING_DIGEST: [u8; 32] = + hex!("ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad"); + +type Repr = Array::OutputSize>; + +/// Dummy signature which just contains a digest output +#[derive(Clone, Debug)] +struct DummySignature(Repr); + +impl PrehashSignature for DummySignature { + type Digest = Sha256; +} + +impl SignatureEncoding for DummySignature { + type Repr = Repr; +} + +impl TryFrom<&[u8]> for DummySignature { + type Error = Error; + + fn try_from(bytes: &[u8]) -> Result { + bytes + .try_into() + .map(DummySignature) + .map_err(|_| Error::new()) + } +} + +impl From for Repr { + fn from(sig: DummySignature) -> Repr { + sig.0 + } +} + +/// Dummy signer which just returns the message digest as a `DummySignature` +#[derive(Signer, DigestSigner, Default)] +struct DummySigner {} + +impl PrehashSigner for DummySigner { + fn sign_prehash(&self, prehash: &[u8]) -> signature::Result { + DummySignature::try_from(prehash) + } +} + +/// Dummy verifier which ensures the `DummySignature` digest matches the +/// expected value. +/// +/// Panics (via `assert_eq!`) if the value is not what is expected. +#[derive(Verifier, DigestVerifier, Default)] +struct DummyVerifier {} + +impl PrehashVerifier for DummyVerifier { + fn verify_prehash(&self, prehash: &[u8], signature: &DummySignature) -> signature::Result<()> { + assert_eq!(signature.to_bytes().as_slice(), prehash); + Ok(()) + } +} + +#[test] +fn derived_signer_impl() { + let sig: DummySignature = DummySigner::default().sign(INPUT_STRING); + assert_eq!(sig.to_bytes().as_slice(), INPUT_STRING_DIGEST) +} + +#[test] +fn derived_verifier_impl() { + let sig: DummySignature = DummySigner::default().sign(INPUT_STRING); + assert!(DummyVerifier::default().verify(INPUT_STRING, &sig).is_ok()); +} diff --git a/signature_derive/CHANGELOG.md b/signature_derive/CHANGELOG.md new file mode 100644 index 000000000..0edb20cdb --- /dev/null +++ b/signature_derive/CHANGELOG.md @@ -0,0 +1,70 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 2.1.0 (2023-11-12) +### Changed +- MSRV 1.60 ([#1387]) + +[#1387]: https://github.com/RustCrypto/traits/pull/1387 + +## 2.0.1 (2023-04-17) +### Changed +- Bump `syn` to v2 ([#1299]) + +[#1299]: https://github.com/RustCrypto/traits/pull/1299 + +## 2.0.0 (2023-01-15) +### Changed +- `Signature` trait has been removed, so don't emit it in custom derive ([#1141]) + +[#1141]: https://github.com/RustCrypto/traits/pull/1141 + +## 1.0.0-pre.7 (2022-09-16) +### Fixed +- Support for `where` bounds ([#1118]) + +[#1118]: https://github.com/RustCrypto/traits/pull/1118 + +## 1.0.0-pre.6 (2022-09-12) +### Added +- `DigestSigner`/`DigestVerifier` support ([#1103]) + +### Removed +- `synstructure` dependency ([#1100]) + +[#1100]: https://github.com/RustCrypto/traits/pull/1100 +[#1103]: https://github.com/RustCrypto/traits/pull/1103 + +## 1.0.0-pre.5 (2022-08-14) +### Changed +- Rust 2021 edition upgrade; MSRV 1.56 ([#1081]) + +[#1081]: https://github.com/RustCrypto/traits/pull/1081 + +## 1.0.0-pre.4 (2022-01-04) +### Changed +- Support for new `digest` v0.10 API ([#850]) + +[#850]: https://github.com/RustCrypto/traits/pull/850 + +## 1.0.0-pre.3 (2021-01-06) +### Fixed +- rustdoc links ([#458]) + +[#458]: https://github.com/RustCrypto/traits/pull/458 + +## 1.0.0-pre.2 (2020-04-19) +### Changed +- Rename `DigestSignature` => `PrehashSignature` ([#96]) + +[#96]: https://github.com/RustCrypto/traits/pull/96 + +## 1.0.0-pre.1 (2020-03-08) +### Added +- Initial changelog for `signature_derive` +- rustdoc ([#79]) + +[#79]: https://github.com/RustCrypto/traits/pull/79 diff --git a/signature_derive/Cargo.toml b/signature_derive/Cargo.toml new file mode 100644 index 000000000..3d01b5d0b --- /dev/null +++ b/signature_derive/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "signature_derive" +version = "2.1.0" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +description = "Custom derive support for the 'signature' crate" +documentation = "https://docs.rs/signature" +homepage = "https://github.com/RustCrypto/traits/tree/master/signature_derive" +repository = "https://github.com/RustCrypto/traits" +readme = "README.md" +edition = "2021" +rust-version = "1.60" +keywords = ["crypto", "ecdsa", "ed25519", "signature", "signing"] +categories = ["cryptography", "no-std"] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = "2" diff --git a/signature_derive/LICENSE-APACHE b/signature_derive/LICENSE-APACHE new file mode 100644 index 000000000..78173fa2e --- /dev/null +++ b/signature_derive/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/signature_derive/LICENSE-MIT b/signature_derive/LICENSE-MIT new file mode 100644 index 000000000..d874d2e01 --- /dev/null +++ b/signature_derive/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2019-2022 RustCrypto Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/signature_derive/README.md b/signature_derive/README.md new file mode 100644 index 000000000..e63f6f85f --- /dev/null +++ b/signature_derive/README.md @@ -0,0 +1,25 @@ +# `signature` crate custom derive support + +This crate provides proc macros used by the `signature` crate. + +Not intended to be used directly. See the `signature` crate's documentation +for additional details: + +[Documentation] + +## License + +All crates licensed under either of + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[Documentation]: https://docs.rs/signature/ diff --git a/signature_derive/src/lib.rs b/signature_derive/src/lib.rs new file mode 100644 index 000000000..14930789b --- /dev/null +++ b/signature_derive/src/lib.rs @@ -0,0 +1,392 @@ +#![crate_type = "proc-macro"] +#![doc = include_str!("../README.md")] +#![forbid(unsafe_code)] +#![warn( + clippy::unwrap_used, + rust_2018_idioms, + trivial_casts, + unused_import_braces, + missing_debug_implementations, + unused_qualifications +)] + +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{ + parse_macro_input, parse_quote, punctuated::Punctuated, DeriveInput, Ident, PredicateType, + Token, TraitBound, Type, TypeParam, TypeParamBound, WhereClause, WherePredicate, +}; + +/// Derive the [`Signer`] trait for a type which impls [`DigestSigner`]. +/// +/// When implementing the [`DigestSigner`] trait for a signature type which +/// itself impl's the [`PrehashSignature`] trait (which marks signature +/// algorithms which are computed using a [`Digest`]), signature providers +/// can automatically derive the [`Signer`] trait when the digest algorithm +/// is [`PrehashSignature::Digest`] (i.e. the "standard" digest algorithm +/// for a given signature type) +/// +/// This automates all of the digest computation otherwise needed for a +/// complete signature algorithm implementation. +/// +/// [`Digest`]: https://docs.rs/digest/latest/digest/trait.Digest.html +/// [`DigestSigner`]: https://docs.rs/signature/latest/signature/trait.DigestSigner.html +/// [`PrehashSignature`]: https://docs.rs/signature/latest/signature/trait.PrehashSignature.html +/// [`PrehashSignature::Digest`]: https://docs.rs/signature/latest/signature/trait.PrehashSignature.html#associated-types +#[proc_macro_derive(Signer)] +pub fn derive_signer(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + emit_signer_impl(input).into() +} + +fn emit_signer_impl(input: DeriveInput) -> TokenStream2 { + let s_ident = Ident::new("__S", Span::call_site()); + + let mut params = DeriveParams::new(input); + params.add_bound(&s_ident, parse_quote!(::signature::PrehashSignature)); + params.add_bound( + &Ident::new("Self", Span::call_site()), + parse_quote!(::signature::DigestSigner<#s_ident::Digest, #s_ident>), + ); + + let name = params.name; + let impl_generics = params.impl_generics; + let ty_generics = params.ty_generics; + let where_clause = params.where_clause; + + quote! { + impl<#(#impl_generics),*> ::signature::Signer<#s_ident> for #name<#(#ty_generics),*> + #where_clause + { + fn try_sign(&self, msg: &[u8]) -> ::signature::Result<#s_ident> { + self.try_sign_digest(#s_ident::Digest::new_with_prefix(msg)) + } + } + } +} + +/// Derive the [`Verifier`] trait for a type which impls [`DigestVerifier`]. +/// +/// When implementing the [`DigestVerifier`] trait for a signature type which +/// itself impl's the [`PrehashSignature`] trait (which marks signature +/// algorithms which are computed using a [`Digest`]), signature providers +/// can automatically derive the [`Verifier`] trait when the digest algorithm +/// is [`PrehashSignature::Digest`] (i.e. the "standard" digest algorithm +/// for a given signature type) +/// +/// This automates all of the digest computation otherwise needed for a +/// complete signature algorithm implementation. +/// +/// [`Digest`]: https://docs.rs/digest/latest/digest/trait.Digest.html +/// [`DigestVerifier`]: https://docs.rs/signature/latest/signature/trait.DigestVerifier.html +/// [`PrehashSignature`]: https://docs.rs/signature/latest/signature/trait.PrehashSignature.html +/// [`PrehashSignature::Digest`]: https://docs.rs/signature/latest/signature/trait.PrehashSignature.html#associated-types +#[proc_macro_derive(Verifier)] +pub fn derive_verifier(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + emit_verifier_impl(input).into() +} + +fn emit_verifier_impl(input: DeriveInput) -> TokenStream2 { + let s_ident = Ident::new("__S", Span::call_site()); + + let mut params = DeriveParams::new(input); + params.add_bound(&s_ident, parse_quote!(::signature::PrehashSignature)); + params.add_bound( + &Ident::new("Self", Span::call_site()), + parse_quote!(::signature::DigestVerifier<#s_ident::Digest, #s_ident>), + ); + + let name = params.name; + let impl_generics = params.impl_generics; + let ty_generics = params.ty_generics; + let where_clause = params.where_clause; + + quote! { + impl<#(#impl_generics),*> ::signature::Verifier<#s_ident> for #name<#(#ty_generics),*> + #where_clause + { + fn verify(&self, msg: &[u8], signature: &#s_ident) -> ::signature::Result<()> { + self.verify_digest(#s_ident::Digest::new_with_prefix(msg), signature) + } + } + } +} + +/// Derive the [`DigestSigner`] trait for a type which impls [`PrehashSigner`]. +/// +/// [`DigestSigner`]: https://docs.rs/signature/latest/signature/trait.DigestSigner.html +/// [`PrehashSigner`]: https://docs.rs/signature/latest/signature/hazmat/trait.PrehashSigner.html +#[proc_macro_derive(DigestSigner)] +pub fn derive_digest_signer(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + emit_digest_signer_impl(input).into() +} + +fn emit_digest_signer_impl(input: DeriveInput) -> TokenStream2 { + let d_ident = Ident::new("__D", Span::call_site()); + let s_ident = Ident::new("__S", Span::call_site()); + + let mut params = DeriveParams::new(input); + params.add_bound(&d_ident, parse_quote!(::signature::digest::Digest)); + params.add_param(&s_ident); + params.add_bound( + &Ident::new("Self", Span::call_site()), + parse_quote!(::signature::hazmat::PrehashSigner<#s_ident>), + ); + + let name = params.name; + let impl_generics = params.impl_generics; + let ty_generics = params.ty_generics; + let where_clause = params.where_clause; + + quote! { + impl<#(#impl_generics),*> ::signature::DigestSigner<#d_ident, #s_ident> for #name<#(#ty_generics),*> + #where_clause + { + fn try_sign_digest(&self, digest: #d_ident) -> ::signature::Result<#s_ident> { + self.sign_prehash(&digest.finalize()) + } + } + } +} + +/// Derive the [`DigestVerifier`] trait for a type which impls [`PrehashVerifier`]. +/// +/// [`DigestVerifier`]: https://docs.rs/signature/latest/signature/trait.DigestVerifier.html +/// [`PrehashVerifier`]: https://docs.rs/signature/latest/signature/hazmat/trait.PrehashVerifier.html +#[proc_macro_derive(DigestVerifier)] +pub fn derive_digest_verifier(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + emit_digest_verifier_impl(input).into() +} + +fn emit_digest_verifier_impl(input: DeriveInput) -> TokenStream2 { + let d_ident = Ident::new("__D", Span::call_site()); + let s_ident = Ident::new("__S", Span::call_site()); + + let mut params = DeriveParams::new(input); + params.add_bound(&d_ident, parse_quote!(::signature::digest::Digest)); + params.add_param(&s_ident); + params.add_bound( + &Ident::new("Self", Span::call_site()), + parse_quote!(::signature::hazmat::PrehashVerifier<#s_ident>), + ); + + let name = params.name; + let impl_generics = params.impl_generics; + let ty_generics = params.ty_generics; + let where_clause = params.where_clause; + + quote! { + impl<#(#impl_generics),*> ::signature::DigestVerifier<#d_ident, #s_ident> for #name<#(#ty_generics),*> + #where_clause + { + fn verify_digest(&self, digest: #d_ident, signature: &#s_ident) -> ::signature::Result<()> { + self.verify_prehash(&digest.finalize(), signature) + } + } + } +} + +/// Derivation parameters parsed from `DeriveInput`. +struct DeriveParams { + /// Name of the struct the trait impls are being added to. + name: Ident, + + /// Generic parameters of `impl`. + impl_generics: Vec, + + /// Generic parameters of the type. + ty_generics: Vec, + + /// Where clause in-progress. + where_clause: WhereClause, +} + +impl DeriveParams { + /// Parse parameters from `DeriveInput`. + fn new(input: DeriveInput) -> Self { + let impl_generics = input.generics.type_params().cloned().collect(); + + let ty_generics = input + .generics + .type_params() + .map(|bound| bound.ident.clone()) + .collect(); + + let where_clause = input + .generics + .where_clause + .clone() + .unwrap_or_else(|| WhereClause { + where_token: ::default(), + predicates: Punctuated::new(), + }); + + Self { + name: input.ident, + impl_generics, + ty_generics, + where_clause, + } + } + + /// Add a generic parameter with the given bound. + fn add_bound(&mut self, name: &Ident, bound: TraitBound) { + if name != "Self" { + self.add_param(name); + } + + let type_path = parse_quote!(#name); + + let mut bounds = Punctuated::new(); + bounds.push(TypeParamBound::Trait(bound)); + + let predicate_type = PredicateType { + lifetimes: None, + bounded_ty: Type::Path(type_path), + colon_token: ::default(), + bounds, + }; + + self.where_clause + .predicates + .push(WherePredicate::Type(predicate_type)) + } + + /// Add a generic parameter without a bound. + fn add_param(&mut self, name: &Ident) { + self.impl_generics.push(TypeParam { + attrs: vec![], + ident: name.clone(), + colon_token: None, + bounds: Default::default(), + eq_token: None, + default: None, + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use syn::parse_quote; + + #[test] + fn signer() { + let input = parse_quote! { + #[derive(Signer)] + struct MySigner + where + C: EllipticCurve + { + scalar: Scalar + } + }; + + let output = emit_signer_impl(input); + + assert_eq!( + output.to_string(), + quote! { + impl ::signature::Signer<__S> for MySigner + where + C: EllipticCurve, + __S: ::signature::PrehashSignature, + Self: ::signature::DigestSigner<__S::Digest, __S> + { + fn try_sign(&self, msg: &[u8]) -> ::signature::Result<__S> { + self.try_sign_digest(__S::Digest::new_with_prefix(msg)) + } + } + } + .to_string() + ); + } + + #[test] + fn verifier() { + let input = parse_quote! { + #[derive(Verifier)] + struct MyVerifier { + point: UncompressedPoint + } + }; + + let output = emit_verifier_impl(input); + + assert_eq!( + output.to_string(), + quote! { + impl ::signature::Verifier<__S> for MyVerifier + where + __S: ::signature::PrehashSignature, + Self: ::signature::DigestVerifier<__S::Digest, __S> + { + fn verify(&self, msg: &[u8], signature: &__S) -> ::signature::Result<()> { + self.verify_digest(__S::Digest::new_with_prefix(msg), signature) + } + } + } + .to_string() + ); + } + + #[test] + fn digest_signer() { + let input = parse_quote! { + #[derive(DigestSigner)] + struct MySigner { + scalar: Scalar + } + }; + + let output = emit_digest_signer_impl(input); + + assert_eq!( + output.to_string(), + quote! { + impl ::signature::DigestSigner<__D, __S> for MySigner + where + __D: ::signature::digest::Digest, + Self: ::signature::hazmat::PrehashSigner<__S> + { + fn try_sign_digest(&self, digest: __D) -> ::signature::Result<__S> { + self.sign_prehash(&digest.finalize()) + } + } + } + .to_string() + ); + } + + #[test] + fn digest_verifier() { + let input = parse_quote! { + #[derive(DigestVerifier)] + struct MyVerifier { + point: UncompressedPoint + } + }; + + let output = emit_digest_verifier_impl(input); + + assert_eq!( + output.to_string(), + quote! { + impl ::signature::DigestVerifier<__D, __S> for MyVerifier + where + __D: ::signature::digest::Digest, + Self: ::signature::hazmat::PrehashVerifier<__S> + { + fn verify_digest(&self, digest: __D, signature: &__S) -> ::signature::Result<()> { + self.verify_prehash(&digest.finalize(), signature) + } + } + } + .to_string() + ); + } +} diff --git a/stream-cipher/Cargo.toml b/stream-cipher/Cargo.toml deleted file mode 100644 index 7e0d1196f..000000000 --- a/stream-cipher/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "stream-cipher" -version = "0.3.2" -authors = ["RustCrypto Developers"] -license = "MIT OR Apache-2.0" -description = "Stream cipher traits" -documentation = "https://docs.rs/stream-cipher" -repository = "https://github.com/RustCrypto/traits" -keywords = ["crypto", "stream-cipher", "trait"] -categories = ["cryptography", "no-std"] - -[dependencies] -generic-array = "0.12" -blobby = { version = "0.1", optional = true } - -[features] -std = [] -dev = ["blobby"] - -[badges] -travis-ci = { repository = "RustCrypto/traits" } - -[package.metadata.docs.rs] -features = [ "std" ] diff --git a/stream-cipher/src/dev.rs b/stream-cipher/src/dev.rs deleted file mode 100644 index ee66a72d9..000000000 --- a/stream-cipher/src/dev.rs +++ /dev/null @@ -1,234 +0,0 @@ -/// Test core functionality of synchronous stream cipher -#[macro_export] -macro_rules! new_sync_test { - ($name:ident, $cipher:ty, $test_name:expr) => { - #[test] - fn $name() { - use stream_cipher::blobby::Blob4Iterator; - use stream_cipher::generic_array::GenericArray; - use stream_cipher::{NewStreamCipher, SyncStreamCipher}; - - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - for (i, row) in Blob4Iterator::new(data).unwrap().enumerate() { - let key = row[0]; - let iv = row[1]; - let plaintext = row[2]; - let ciphertext = row[3]; - - for chunk_n in 1..256 { - let mut mode = <$cipher>::new_var(key, iv).unwrap(); - let mut pt = plaintext.to_vec(); - for chunk in pt.chunks_mut(chunk_n) { - mode.apply_keystream(chunk); - } - if pt != &ciphertext[..] { - panic!( - "Failed main test №{}, chunk size: {}\n\ - key:\t{:?}\n\ - iv:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, chunk_n, key, iv, plaintext, ciphertext, - ); - } - } - } - } - }; -} - -/// Test stream synchronous stream cipher seeking capabilities -#[macro_export] -macro_rules! new_seek_test { - ($name:ident, $cipher:ty, $test_name:expr) => { - #[test] - fn $name() { - use stream_cipher::blobby::Blob4Iterator; - use stream_cipher::generic_array::GenericArray; - use stream_cipher::{NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek}; - - const MAX_SEEK: usize = 512; - - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - for (i, row) in Blob4Iterator::new(data).unwrap().enumerate() { - let key = row[0]; - let iv = row[1]; - let plaintext = row[2]; - let ciphertext = row[3]; - - let mut mode = <$cipher>::new_var(key, iv).unwrap(); - let pl = plaintext.len(); - let n = if pl > MAX_SEEK { MAX_SEEK } else { pl }; - for seek_n in 0..n { - let mut pt = plaintext[seek_n..].to_vec(); - mode.seek(seek_n as u64); - mode.apply_keystream(&mut pt); - if pt != &ciphertext[seek_n..] { - panic!( - "Failed seek test №{}, seek pos: {}\n\ - key:\t{:?}\n\ - iv:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, seek_n, key, iv, plaintext, ciphertext, - ); - } - } - } - } - }; -} - -/// Test core functionality of asynchronous stream cipher -#[macro_export] -macro_rules! new_async_test { - ($name:ident, $test_name:expr, $cipher:ty) => { - #[test] - fn $name() { - use stream_cipher::blobby::Blob4Iterator; - use stream_cipher::generic_array::GenericArray; - use stream_cipher::{NewStreamCipher, StreamCipher}; - - fn run_test( - key: &[u8], - iv: &[u8], - plaintext: &[u8], - ciphertext: &[u8], - ) -> Option<&'static str> { - for n in 1..=plaintext.len() { - let mut mode = <$cipher>::new_var(key, iv).unwrap(); - let mut buf = plaintext.to_vec(); - for chunk in buf.chunks_mut(n) { - mode.encrypt(chunk); - } - if buf != &ciphertext[..] { - return Some("encrypt"); - } - } - - for n in 1..=plaintext.len() { - let mut mode = <$cipher>::new_var(key, iv).unwrap(); - let mut buf = ciphertext.to_vec(); - for chunk in buf.chunks_mut(n) { - mode.decrypt(chunk); - } - if buf != &plaintext[..] { - return Some("decrypt"); - } - } - - None - } - - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - - for (i, row) in Blob4Iterator::new(data).unwrap().enumerate() { - let key = row[0]; - let iv = row[1]; - let plaintext = row[2]; - let ciphertext = row[3]; - if let Some(desc) = run_test(key, iv, plaintext, ciphertext) { - panic!( - "\n\ - Failed test №{}: {}\n\ - key:\t{:?}\n\ - iv:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, desc, key, iv, plaintext, ciphertext, - ); - } - } - } - }; -} - -/// Create synchronous stream cipher benchmarks -#[macro_export] -macro_rules! bench_sync { - ($name:ident, $cipher:path, $data_len:expr) => { - #[bench] - pub fn $name(bh: &mut Bencher) { - let key = Default::default(); - let nonce = Default::default(); - let mut cipher = <$cipher>::new(&key, &nonce); - let mut data = get_data($data_len); - - bh.iter(|| { - cipher.apply_keystream(&mut data); - test::black_box(&data); - }); - bh.bytes = data.len() as u64; - } - }; - ($cipher:path) => { - extern crate test; - - use stream_cipher::generic_array::GenericArray; - use stream_cipher::{NewStreamCipher, SyncStreamCipher}; - use test::Bencher; - - #[inline(never)] - fn get_data(n: usize) -> Vec { - vec![77; n] - } - - bench_sync!(bench1_10, $cipher, 10); - bench_sync!(bench2_100, $cipher, 100); - bench_sync!(bench3_1000, $cipher, 1000); - bench_sync!(bench4_10000, $cipher, 10000); - bench_sync!(bench5_100000, $cipher, 100000); - }; -} - -/// Create synchronous stream cipher benchmarks -#[macro_export] -macro_rules! bench_async { - ($enc_name:ident, $dec_name:ident, $cipher:path, $data_len:expr) => { - #[bench] - pub fn $enc_name(bh: &mut Bencher) { - let key = Default::default(); - let nonce = Default::default(); - let mut cipher = <$cipher>::new(&key, &nonce); - let mut data = get_data($data_len); - - bh.iter(|| { - cipher.encrypt(&mut data); - test::black_box(&data); - }); - bh.bytes = data.len() as u64; - } - - #[bench] - pub fn $dec_name(bh: &mut Bencher) { - let key = Default::default(); - let nonce = Default::default(); - let mut cipher = <$cipher>::new(&key, &nonce); - let mut data = get_data($data_len); - - bh.iter(|| { - cipher.decrypt(&mut data); - test::black_box(&data); - }); - bh.bytes = data.len() as u64; - } - }; - ($cipher:path) => { - extern crate test; - - use stream_cipher::generic_array::GenericArray; - use stream_cipher::{NewStreamCipher, StreamCipher}; - use test::Bencher; - - #[inline(never)] - fn get_data(n: usize) -> Vec { - vec![77; n] - } - - bench_async!(encrypt_10, decrypt_10, $cipher, 10); - bench_async!(encrypt_100, decrypt_100, $cipher, 100); - bench_async!(encrypt_1000, decrypt_1000, $cipher, 1000); - bench_async!(encrypt_10000, decrypt_10000, $cipher, 10000); - bench_async!(encrypt_100000, decrypt_100000, $cipher, 100000); - }; -} diff --git a/stream-cipher/src/errors.rs b/stream-cipher/src/errors.rs deleted file mode 100644 index b44a6d8f9..000000000 --- a/stream-cipher/src/errors.rs +++ /dev/null @@ -1,38 +0,0 @@ -use core::fmt; -#[cfg(feature = "std")] -use std::error; - -/// Error which notifies that stream cipher has reached the end of a keystream. -#[derive(Copy, Clone, Debug)] -pub struct LoopError; - -impl fmt::Display for LoopError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - f.write_str("Loop Error") - } -} - -#[cfg(feature = "std")] -impl error::Error for LoopError { - fn description(&self) -> &str { - "stream cipher loop detected" - } -} - -/// Error which notifies that key or/and nonce used in stream cipher -/// initialization had an invalid length. -#[derive(Copy, Clone, Debug)] -pub struct InvalidKeyNonceLength; - -impl fmt::Display for InvalidKeyNonceLength { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - f.write_str("Loop Error") - } -} - -#[cfg(feature = "std")] -impl error::Error for InvalidKeyNonceLength { - fn description(&self) -> &str { - "stream cipher loop detected" - } -} diff --git a/stream-cipher/src/lib.rs b/stream-cipher/src/lib.rs deleted file mode 100644 index e973c39a0..000000000 --- a/stream-cipher/src/lib.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! This crate defines a set of traits which define functionality of -//! stream ciphers. -//! -//! See [RustCrypto/stream-ciphers](https://github.com/RustCrypto/stream-ciphers) -//! for ciphers implementation. -#![no_std] -#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")] -#[cfg(feature = "dev")] -pub extern crate blobby; -pub extern crate generic_array; -#[cfg(feature = "std")] -extern crate std; - -use generic_array::typenum::Unsigned; -use generic_array::{ArrayLength, GenericArray}; - -#[cfg(feature = "dev")] -pub mod dev; -mod errors; - -pub use errors::{InvalidKeyNonceLength, LoopError}; - -/// Stream cipher creation trait. -/// -/// It can be used for creation of synchronous and asynchronous ciphers. -pub trait NewStreamCipher: Sized { - /// Key size in bytes - type KeySize: ArrayLength; - /// Nonce size in bytes - type NonceSize: ArrayLength; - - /// Create new stream cipher instance from variable length key and nonce. - fn new( - key: &GenericArray, - nonce: &GenericArray, - ) -> Self; - - /// Create new stream cipher instance from variable length key and nonce. - #[inline] - fn new_var(key: &[u8], nonce: &[u8]) -> Result { - let kl = Self::KeySize::to_usize(); - let nl = Self::NonceSize::to_usize(); - if key.len() != kl || nonce.len() != nl { - Err(InvalidKeyNonceLength) - } else { - let key = GenericArray::from_slice(key); - let nonce = GenericArray::from_slice(nonce); - Ok(Self::new(key, nonce)) - } - } -} - -/// Synchronous stream cipher core trait. -pub trait SyncStreamCipher { - /// Apply keystream to the data. - /// - /// It will XOR generated keystream with the data, which can be both - /// encryption and decryption. - /// - /// # Panics - /// If end of the keystream will be reached with the given data length, - /// method will panic without modifying the provided `data`. - #[inline] - fn apply_keystream(&mut self, data: &mut [u8]) { - let res = self.try_apply_keystream(data); - if res.is_err() { - panic!("stream cipher loop detected"); - } - } - - /// Apply keystream to the data, but return an error if end of a keystream - /// will be reached. - /// - /// If end of the keystream will be achieved with the given data length, - /// method will return `Err(LoopError)` without modifying provided `data`. - fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError>; -} - -/// Synchronous stream cipher seeking trait. -pub trait SyncStreamCipherSeek { - /// Return current position of a keystream in bytes from the beginning. - fn current_pos(&self) -> u64; - - /// Seek keystream to the given `pos` in bytes. - fn seek(&mut self, pos: u64); -} - -/// Stream cipher core trait which covers both synchronous and asynchronous -/// ciphers. -/// -/// Note that for synchronous ciphers `encrypt` and `decrypt` are equivalent to -/// each other. -pub trait StreamCipher { - /// Encrypt data in place. - fn encrypt(&mut self, data: &mut [u8]); - - /// Decrypt data in place. - fn decrypt(&mut self, data: &mut [u8]); -} - -impl StreamCipher for C { - #[inline(always)] - fn encrypt(&mut self, data: &mut [u8]) { - SyncStreamCipher::apply_keystream(self, data); - } - - #[inline(always)] - fn decrypt(&mut self, data: &mut [u8]) { - SyncStreamCipher::apply_keystream(self, data); - } -} diff --git a/universal-hash/CHANGELOG.md b/universal-hash/CHANGELOG.md index b7876bd4d..3ff8bb0fa 100644 --- a/universal-hash/CHANGELOG.md +++ b/universal-hash/CHANGELOG.md @@ -1,9 +1,87 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +### Changed +- Migrate to `doc_auto_cfg` ([#1370]) +- Exclude pre-1.60 crates from workspace ([#1380]) +- bump crypto-common to v0.2.0-pre; MSRV 1.65 ([#1385]) +- bump crypto-common to v0.2.0-pre.1 ([#1433]) +- bump crypto-common to v0.2.0-pre.2 ([#1436]) +- Bump `hybrid-array` to v0.2.0-pre.8 ([#1438]) +- Bump `crypto-common` and `hybrid-array` ([#1469]) +- Bump `hybrid-array` to v0.2.0-rc.4 ([#1493]) +- bump crypto-common to v0.2.0-pre.5 ([#1496]) + +### Fixed +- Fix `missing_debug_implementations` for some crates ([#1407]) + +[#1370]: https://github.com/RustCrypto/traits/pull/1370 +[#1380]: https://github.com/RustCrypto/traits/pull/1380 +[#1385]: https://github.com/RustCrypto/traits/pull/1385 +[#1407]: https://github.com/RustCrypto/traits/pull/1407 +[#1433]: https://github.com/RustCrypto/traits/pull/1433 +[#1436]: https://github.com/RustCrypto/traits/pull/1436 +[#1438]: https://github.com/RustCrypto/traits/pull/1438 +[#1469]: https://github.com/RustCrypto/traits/pull/1469 +[#1493]: https://github.com/RustCrypto/traits/pull/1493 +[#1496]: https://github.com/RustCrypto/traits/pull/1496 + +## 0.5.1 (2023-05-19) +### Changed +- Loosen `subtle` version requirement to `^2.4` ([#1260]) + +[#1260]: https://github.com/RustCrypto/traits/pull/1260 + +## 0.5.0 (2022-07-30) +### Added +- `UhfBackend` trait ([#1051], [#1059]) +- `UhfClosure` trait ([#1051]) +- `UniversalHash::update_with_backend` method ([#1051]) + +### Changed +- Replace `NewUniversalHash` trait with `KeyInit` from `crypto-common` ([#1051]) +- Source `Block` and `Key` types from `crypto-common` ([#1051]) +- `UniversalHash::update` is now provided takes a slice of blocks ([#1051]) +- `UniversalHash::finalize` now returns a `Block` ([#1051]) +- Rust 2021 edition; MSRV 1.56 ([#1051]) + +### Removed +- `Ouput` replaced by `Block` ([#1051]) +- `UniversalHash::reset` replaced with `Reset` trait from `crypto-common` ([#1051]) + +[#1051]: https://github.com/RustCrypto/traits/pull/1051 +[#1059]: https://github.com/RustCrypto/traits/pull/1059 + +## 0.4.1 (2021-07-20) +### Changed +- Pin `subtle` dependency to v2.4 ([#689]) + +[#689]: https://github.com/RustCrypto/traits/pull/689 + +## 0.4.0 (2020-06-04) +### Added +- `Key` and `Block` type aliases ([#128]) + +### Changed +- Split `UniversalHash` initialization into `NewUniversalHash` trait ([#135]) +- Rename `update_block` => `update` ([#129]) +- Bump `generic-array` dependency to v0.14 ([#95]) + +[#135]: https://github.com/RustCrypto/traits/pull/135 +[#129]: https://github.com/RustCrypto/traits/pull/129 +[#128]: https://github.com/RustCrypto/traits/pull/128 +[#95]: https://github.com/RustCrypto/traits/pull/95 + +## 0.3.0 (2019-10-03) +- Rename `OutputSize` -> `BlockSize` ([#57]) + +[#57]: https://github.com/RustCrypto/traits/pull/57 + ## 0.2.0 (2019-08-31) ### Changed - Split KeySize/OutputSize ([#55]) @@ -11,5 +89,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#55]: https://github.com/RustCrypto/traits/pull/55 ## 0.1.0 (2019-08-30) - - Initial release diff --git a/universal-hash/Cargo.toml b/universal-hash/Cargo.toml index a2c4818f2..9068fa16e 100644 --- a/universal-hash/Cargo.toml +++ b/universal-hash/Cargo.toml @@ -1,24 +1,21 @@ [package] name = "universal-hash" -version = "0.2.0" +version = "0.6.0-rc.0" +description = "Traits which describe the functionality of universal hash functions (UHFs)" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" -description = "Trait for universal hash functions" +edition = "2021" +rust-version = "1.81" +readme = "README.md" documentation = "https://docs.rs/universal-hash" repository = "https://github.com/RustCrypto/traits" keywords = ["crypto", "mac"] categories = ["cryptography", "no-std"] -edition = "2018" [dependencies] -generic-array = "0.12" -subtle = { version = "2", default-features = false } - -[features] -std = [] - -[badges] -travis-ci = { repository = "RustCrypto/traits" } +crypto-common = "0.2.0-rc.0" +subtle = { version = "2.4", default-features = false } [package.metadata.docs.rs] all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/universal-hash/LICENSE-MIT b/universal-hash/LICENSE-MIT index 51e285981..468bccc2d 100644 --- a/universal-hash/LICENSE-MIT +++ b/universal-hash/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2019 RustCrypto Developers +Copyright (c) 2019-2020 RustCrypto Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/universal-hash/README.md b/universal-hash/README.md new file mode 100644 index 000000000..beaa9e58a --- /dev/null +++ b/universal-hash/README.md @@ -0,0 +1,59 @@ +# RustCrypto: Universal Hash Function Traits + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +Traits which describe functionality of [universal hash functions] (UHFs). + +See [RustCrypto/universal-hashes] for implementations which use this trait. + +[Documentation][docs-link] + +## Minimum Supported Rust Version + +Rust **1.81** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## License + +Licensed under either of: + +* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +* [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/universal-hash.svg +[crate-link]: https://crates.io/crates/universal-hash +[docs-image]: https://docs.rs/universal-hash/badge.svg +[docs-link]: https://docs.rs/universal-hash/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260051-universal-hashes +[build-image]: https://github.com/RustCrypto/traits/workflows/universal-hash/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/traits/actions?query=workflow%3Auniversal-hash + +[//]: # (general links) + +[universal hash functions]: https://en.wikipedia.org/wiki/Universal_hashing +[RustCrypto/universal-hashes]: https://github.com/RustCrypto/universal-hashes diff --git a/universal-hash/src/lib.rs b/universal-hash/src/lib.rs index 5f1c60645..06e75e658 100644 --- a/universal-hash/src/lib.rs +++ b/universal-hash/src/lib.rs @@ -1,44 +1,92 @@ -//! Traits for [Universal Hash Functions]. -//! -//! Universal hash functions select from a "universal family" of possible -//! hash functions selected by a key. They are well suited to the purpose -//! of "one time authenticators" for a sequence of bytestring inputs, -//! as their construction has a number of desirable properties such as -//! pairwise independence as well as amenability to efficient implementations, -//! particularly when implemented using SIMD instructions. -//! -//! When combined with a cipher, such as in Galois/Counter Mode or the -//! Salsa20 family AEAD constructions, they can provide the core functionality -//! for a Message Authentication Code (MAC). -//! -//! [Universal Hash Functions]: https://en.wikipedia.org/wiki/Universal_hashing - #![no_std] -#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")] -#![warn(missing_docs, rust_2018_idioms)] - -#[cfg(feature = "std")] -extern crate std; +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" +)] +#![forbid(unsafe_code)] +#![warn(missing_docs, rust_2018_idioms, missing_debug_implementations)] + +pub use crypto_common::{ + self, array, + typenum::{self, consts}, + Block, Key, KeyInit, ParBlocks, Reset, +}; + +use core::slice; +use crypto_common::{array::Array, BlockSizeUser, BlockSizes, ParBlocksSizeUser}; +use subtle::ConstantTimeEq; +use typenum::Unsigned; + +/// Trait implemented by UHF backends. +pub trait UhfBackend: ParBlocksSizeUser { + /// Process single block. + fn proc_block(&mut self, block: &Block); + + /// Process several blocks in parallel. + #[inline(always)] + fn proc_par_blocks(&mut self, blocks: &ParBlocks) { + for block in blocks { + self.proc_block(block); + } + } -pub use generic_array; + /// Returns the number of blocks that should be passed to `Self::proc_block` before + /// `Self::proc_par_blocks` can be used efficiently. This is always less than + /// `Self::ParBlocksSize`. + fn blocks_needed_to_align(&self) -> usize { + 0 + } +} -use generic_array::typenum::Unsigned; -use generic_array::{ArrayLength, GenericArray}; -use subtle::{Choice, ConstantTimeEq}; +/// Trait for [`UhfBackend`] users. +/// +/// This trait is used to define rank-2 closures. +pub trait UhfClosure: BlockSizeUser { + /// Execute closure with the provided UHF backend. + fn call>(self, backend: &mut B); +} -/// The `UniversalHash` trait defines a generic interface for universal hash +/// The [`UniversalHash`] trait defines a generic interface for universal hash /// functions. -pub trait UniversalHash: Clone { - /// Size of the key for the universal hash function - type KeySize: ArrayLength; - /// Size of the output from the universal hash function - type OutputSize: ArrayLength; +pub trait UniversalHash: BlockSizeUser + Sized { + /// Update hash function state using the provided rank-2 closure. + fn update_with_backend(&mut self, f: impl UhfClosure); + + /// Update hash function state with the provided block. + #[inline] + fn update(&mut self, blocks: &[Block]) { + struct Ctx<'a, BS: BlockSizes> { + blocks: &'a [Block], + } - /// Instantiate a universal hash function with the given key - fn new(key: &GenericArray) -> Self; + impl BlockSizeUser for Ctx<'_, BS> { + type BlockSize = BS; + } - /// Input a block into the universal hash function - fn update_block(&mut self, block: &GenericArray); + impl UhfClosure for Ctx<'_, BS> { + #[inline(always)] + fn call>(self, backend: &mut B) { + let pb = B::ParBlocksSize::USIZE; + if pb > 1 { + let (par_blocks, tail) = Array::slice_as_chunks(self.blocks); + for par_block in par_blocks { + backend.proc_par_blocks(par_block); + } + for block in tail { + backend.proc_block(block); + } + } else { + for block in self.blocks { + backend.proc_block(block); + } + } + } + } + + self.update_with_backend(Ctx { blocks }); + } /// Input data into the universal hash function. If the length of the /// data is not a multiple of the block size, the remaining data is @@ -46,41 +94,42 @@ pub trait UniversalHash: Clone { /// /// This approach is frequently used by AEAD modes which use /// Message Authentication Codes (MACs) based on universal hashing. + #[inline] fn update_padded(&mut self, data: &[u8]) { - let mut chunks = data.chunks_exact(Self::OutputSize::to_usize()); + let (blocks, tail) = Array::slice_as_chunks(data); - for chunk in &mut chunks { - self.update_block(GenericArray::from_slice(chunk)); - } + self.update(blocks); - let rem = chunks.remainder(); - - if !rem.is_empty() { - let mut padded_block = GenericArray::default(); - padded_block[..rem.len()].copy_from_slice(rem); - self.update_block(&padded_block); + if !tail.is_empty() { + let mut padded_block = Array::default(); + padded_block[..tail.len()].copy_from_slice(tail); + self.update(slice::from_ref(&padded_block)); } } - /// Reset `UniversalHash` instance. - fn reset(&mut self); - - /// Obtain the [`Output`] of a `UniversalHash` function and consume it. - fn result(self) -> Output; + /// Retrieve result and consume hasher instance. + fn finalize(self) -> Block; - /// Obtain the [`Output`] of a `UniversalHash` computation and reset it back + /// Obtain the output of a [`UniversalHash`] computation and reset it back /// to its initial state. - fn result_reset(&mut self) -> Output { - let res = self.clone().result(); + #[inline] + fn finalize_reset(&mut self) -> Block + where + Self: Clone + Reset, + { + let ret = self.clone().finalize(); self.reset(); - res + ret } - /// Verify the `UniversalHash` of the processed input matches a given [`Output`]. + /// Verify the [`UniversalHash`] of the processed input matches + /// a given `expected` value. + /// /// This is useful when constructing Message Authentication Codes (MACs) /// from universal hash functions. - fn verify(self, other: &GenericArray) -> Result<(), Error> { - if self.result() == other.into() { + #[inline] + fn verify(self, expected: &Block) -> Result<(), Error> { + if self.finalize().ct_eq(expected).into() { Ok(()) } else { Err(Error) @@ -88,84 +137,16 @@ pub trait UniversalHash: Clone { } } -/// Outputs of universal hash functions which are a thin wrapper around a -/// byte array. Provides a safe `Eq` implementation that runs in constant time, -/// which is useful for implementing Message Authentication Codes (MACs) based -/// on universal hashing. -#[derive(Clone)] -pub struct Output> { - bytes: GenericArray, -} - -impl Output -where - N: ArrayLength, -{ - /// Create a new `Block`. - pub fn new(bytes: GenericArray) -> Output { - Output { bytes } - } - - /// Get the inner `GenericArray` this type wraps - pub fn into_bytes(self) -> GenericArray { - self.bytes - } -} - -impl From> for Output -where - N: ArrayLength, -{ - fn from(bytes: GenericArray) -> Self { - Output { bytes } - } -} - -impl<'a, N> From<&'a GenericArray> for Output -where - N: ArrayLength, -{ - fn from(bytes: &'a GenericArray) -> Self { - bytes.clone().into() - } -} - -impl ConstantTimeEq for Output -where - N: ArrayLength, -{ - fn ct_eq(&self, other: &Self) -> Choice { - self.bytes.ct_eq(&other.bytes) - } -} - -impl PartialEq for Output -where - N: ArrayLength, -{ - fn eq(&self, x: &Output) -> bool { - self.ct_eq(x).unwrap_u8() == 1 - } -} - -impl> Eq for Output {} - -/// Error type for when the `Output` of a `UniversalHash` -/// is not equal to the expected value. +/// Error type used by the [`UniversalHash::verify`] method +/// to indicate that UHF output is not equal the expected value. #[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] pub struct Error; -#[cfg(feature = "std")] -impl std::error::Error for Error { - fn description(&self) -> &'static str { - "UHF output mismatch" +impl core::fmt::Display for Error { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("UHF output mismatch") } } -#[cfg(feature = "std")] -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use std::error::Error; - self.description().fmt(f) - } -} +impl core::error::Error for Error {}

(buf, &mut out)?.len(); + out.truncate(len); + Ok(out) + } +} diff --git a/cipher/src/block/backends.rs b/cipher/src/block/backends.rs new file mode 100644 index 000000000..e035b3993 --- /dev/null +++ b/cipher/src/block/backends.rs @@ -0,0 +1,206 @@ +use crypto_common::{typenum::Unsigned, Block, BlockSizeUser, ParBlocks, ParBlocksSizeUser}; +use inout::{InOut, InOutBuf}; + +/// Trait implemented by block cipher mode encryption backends. +pub trait BlockCipherEncBackend: ParBlocksSizeUser { + /// Encrypt single inout block. + fn encrypt_block(&self, block: InOut<'_, '_, Block>); + + /// Encrypt inout blocks in parallel. + #[inline(always)] + fn encrypt_par_blocks(&self, mut blocks: InOut<'_, '_, ParBlocks>) { + for i in 0..Self::ParBlocksSize::USIZE { + self.encrypt_block(blocks.get(i)); + } + } + + /// Encrypt buffer of inout blocks. Length of the buffer MUST be smaller + /// than `Self::ParBlocksSize`. + #[inline(always)] + fn encrypt_tail_blocks(&self, blocks: InOutBuf<'_, '_, Block>) { + assert!(blocks.len() < Self::ParBlocksSize::USIZE); + for block in blocks { + self.encrypt_block(block); + } + } + + /// Encrypt single block in-place. + #[inline(always)] + fn encrypt_block_inplace(&self, block: &mut Block) { + self.encrypt_block(block.into()); + } + + /// Encrypt blocks in parallel in-place. + #[inline(always)] + fn encrypt_par_blocks_inplace(&self, blocks: &mut ParBlocks) { + self.encrypt_par_blocks(blocks.into()); + } + + /// Encrypt buffer of blocks in-place. Length of the buffer MUST be smaller + /// than `Self::ParBlocksSize`. + #[inline(always)] + fn encrypt_tail_blocks_inplace(&self, blocks: &mut [Block]) { + self.encrypt_tail_blocks(blocks.into()); + } +} + +/// Trait for [`BlockCipherEncBackend`] users. +/// +/// This trait is used to define rank-2 closures. +pub trait BlockCipherEncClosure: BlockSizeUser { + /// Execute closure with the provided block cipher backend. + fn call>(self, backend: &B); +} + +/// Trait implemented by block cipher decryption backends. +pub trait BlockCipherDecBackend: ParBlocksSizeUser { + /// Decrypt single inout block. + fn decrypt_block(&self, block: InOut<'_, '_, Block>); + + /// Decrypt inout blocks in parallel. + #[inline(always)] + fn decrypt_par_blocks(&self, mut blocks: InOut<'_, '_, ParBlocks>) { + for i in 0..Self::ParBlocksSize::USIZE { + self.decrypt_block(blocks.get(i)); + } + } + + /// Decrypt buffer of inout blocks. Length of the buffer MUST be smaller + /// than `Self::ParBlocksSize`. + #[inline(always)] + fn decrypt_tail_blocks(&self, blocks: InOutBuf<'_, '_, Block>) { + assert!(blocks.len() < Self::ParBlocksSize::USIZE); + for block in blocks { + self.decrypt_block(block); + } + } + + /// Decrypt single block in-place. + #[inline(always)] + fn decrypt_block_inplace(&self, block: &mut Block) { + self.decrypt_block(block.into()); + } + + /// Decrypt blocks in parallel in-place. + #[inline(always)] + fn decrypt_par_blocks_inplace(&self, blocks: &mut ParBlocks) { + self.decrypt_par_blocks(blocks.into()); + } + + /// Decrypt buffer of blocks in-place. Length of the buffer MUST be smaller + /// than `Self::ParBlocksSize`. + #[inline(always)] + fn decrypt_tail_blocks_inplace(&self, blocks: &mut [Block]) { + self.decrypt_tail_blocks(blocks.into()); + } +} + +/// Trait for [`BlockCipherDecBackend`] users. +/// +/// This trait is used to define rank-2 closures. +pub trait BlockCipherDecClosure: BlockSizeUser { + /// Execute closure with the provided block cipher backend. + fn call>(self, backend: &B); +} + +/// Trait implemented by block cipher mode encryption backends. +pub trait BlockModeEncBackend: ParBlocksSizeUser { + /// Encrypt single inout block. + fn encrypt_block(&mut self, block: InOut<'_, '_, Block>); + + /// Encrypt inout blocks in parallel. + #[inline(always)] + fn encrypt_par_blocks(&mut self, mut blocks: InOut<'_, '_, ParBlocks>) { + for i in 0..Self::ParBlocksSize::USIZE { + self.encrypt_block(blocks.get(i)); + } + } + + /// Encrypt buffer of inout blocks. Length of the buffer MUST be smaller + /// than `Self::ParBlocksSize`. + #[inline(always)] + fn encrypt_tail_blocks(&mut self, blocks: InOutBuf<'_, '_, Block>) { + assert!(blocks.len() < Self::ParBlocksSize::USIZE); + for block in blocks { + self.encrypt_block(block); + } + } + + /// Encrypt single block in-place. + #[inline(always)] + fn encrypt_block_inplace(&mut self, block: &mut Block) { + self.encrypt_block(block.into()); + } + + /// Encrypt blocks in parallel in-place. + #[inline(always)] + fn encrypt_par_blocks_inplace(&mut self, blocks: &mut ParBlocks) { + self.encrypt_par_blocks(blocks.into()); + } + + /// Encrypt buffer of blocks in-place. Length of the buffer MUST be smaller + /// than `Self::ParBlocksSize`. + #[inline(always)] + fn encrypt_tail_blocks_inplace(&mut self, blocks: &mut [Block]) { + self.encrypt_tail_blocks(blocks.into()); + } +} + +/// Trait for [`BlockModeEncBackend`] users. +/// +/// This trait is used to define rank-2 closures. +pub trait BlockModeEncClosure: BlockSizeUser { + /// Execute closure with the provided block cipher backend. + fn call>(self, backend: &mut B); +} + +/// Trait implemented by block cipher mode decryption backends. +pub trait BlockModeDecBackend: ParBlocksSizeUser { + /// Decrypt single inout block. + fn decrypt_block(&mut self, block: InOut<'_, '_, Block>); + + /// Decrypt inout blocks in parallel. + #[inline(always)] + fn decrypt_par_blocks(&mut self, mut blocks: InOut<'_, '_, ParBlocks>) { + for i in 0..Self::ParBlocksSize::USIZE { + self.decrypt_block(blocks.get(i)); + } + } + + /// Decrypt buffer of inout blocks. Length of the buffer MUST be smaller + /// than `Self::ParBlocksSize`. + #[inline(always)] + fn decrypt_tail_blocks(&mut self, blocks: InOutBuf<'_, '_, Block>) { + assert!(blocks.len() < Self::ParBlocksSize::USIZE); + for block in blocks { + self.decrypt_block(block); + } + } + + /// Decrypt single block in-place. + #[inline(always)] + fn decrypt_block_inplace(&mut self, block: &mut Block) { + self.decrypt_block(block.into()); + } + + /// Decrypt blocks in parallel in-place. + #[inline(always)] + fn decrypt_par_blocks_inplace(&mut self, blocks: &mut ParBlocks) { + self.decrypt_par_blocks(blocks.into()); + } + + /// Decrypt buffer of blocks in-place. Length of the buffer MUST be smaller + /// than `Self::ParBlocksSize`. + #[inline(always)] + fn decrypt_tail_blocks_inplace(&mut self, blocks: &mut [Block]) { + self.decrypt_tail_blocks(blocks.into()); + } +} + +/// Trait for [`BlockModeDecBackend`] users. +/// +/// This trait is used to define rank-2 closures. +pub trait BlockModeDecClosure: BlockSizeUser { + /// Execute closure with the provided block cipher backend. + fn call>(self, backend: &mut B); +} diff --git a/cipher/src/block/ctx.rs b/cipher/src/block/ctx.rs new file mode 100644 index 000000000..7a7634120 --- /dev/null +++ b/cipher/src/block/ctx.rs @@ -0,0 +1,120 @@ +use crypto_common::{typenum::Unsigned, Block, BlockSizeUser, BlockSizes}; +use inout::{InOut, InOutBuf}; + +use super::{ + BlockCipherDecBackend, BlockCipherDecClosure, BlockCipherEncBackend, BlockCipherEncClosure, + BlockModeDecBackend, BlockModeDecClosure, BlockModeEncBackend, BlockModeEncClosure, +}; + +/// Closure used in methods which operate over separate blocks. +pub(super) struct BlockCtx<'inp, 'out, BS: BlockSizes> { + pub block: InOut<'inp, 'out, Block>, +} + +impl BlockSizeUser for BlockCtx<'_, '_, BS> { + type BlockSize = BS; +} + +impl BlockCipherEncClosure for BlockCtx<'_, '_, BS> { + #[inline(always)] + fn call>(self, backend: &B) { + backend.encrypt_block(self.block); + } +} + +impl BlockCipherDecClosure for BlockCtx<'_, '_, BS> { + #[inline(always)] + fn call>(self, backend: &B) { + backend.decrypt_block(self.block); + } +} + +impl BlockModeEncClosure for BlockCtx<'_, '_, BS> { + #[inline(always)] + fn call>(self, backend: &mut B) { + backend.encrypt_block(self.block); + } +} + +impl BlockModeDecClosure for BlockCtx<'_, '_, BS> { + #[inline(always)] + fn call>(self, backend: &mut B) { + backend.decrypt_block(self.block); + } +} +/// Closure used in methods which operate over slice of blocks. +pub(super) struct BlocksCtx<'inp, 'out, BS: BlockSizes> { + pub blocks: InOutBuf<'inp, 'out, Block>, +} + +impl BlockSizeUser for BlocksCtx<'_, '_, BS> { + type BlockSize = BS; +} + +impl BlockCipherEncClosure for BlocksCtx<'_, '_, BS> { + #[inline(always)] + fn call>(self, backend: &B) { + if B::ParBlocksSize::USIZE > 1 { + let (chunks, tail) = self.blocks.into_chunks(); + for chunk in chunks { + backend.encrypt_par_blocks(chunk); + } + backend.encrypt_tail_blocks(tail); + } else { + for block in self.blocks { + backend.encrypt_block(block); + } + } + } +} + +impl BlockCipherDecClosure for BlocksCtx<'_, '_, BS> { + #[inline(always)] + fn call>(self, backend: &B) { + if B::ParBlocksSize::USIZE > 1 { + let (chunks, tail) = self.blocks.into_chunks(); + for chunk in chunks { + backend.decrypt_par_blocks(chunk); + } + backend.decrypt_tail_blocks(tail); + } else { + for block in self.blocks { + backend.decrypt_block(block); + } + } + } +} + +impl BlockModeEncClosure for BlocksCtx<'_, '_, BS> { + #[inline(always)] + fn call>(self, backend: &mut B) { + if B::ParBlocksSize::USIZE > 1 { + let (chunks, tail) = self.blocks.into_chunks(); + for chunk in chunks { + backend.encrypt_par_blocks(chunk); + } + backend.encrypt_tail_blocks(tail); + } else { + for block in self.blocks { + backend.encrypt_block(block); + } + } + } +} + +impl BlockModeDecClosure for BlocksCtx<'_, '_, BS> { + #[inline(always)] + fn call>(self, backend: &mut B) { + if B::ParBlocksSize::USIZE > 1 { + let (chunks, tail) = self.blocks.into_chunks(); + for chunk in chunks { + backend.decrypt_par_blocks(chunk); + } + backend.decrypt_tail_blocks(tail); + } else { + for block in self.blocks { + backend.decrypt_block(block); + } + } + } +} diff --git a/cipher/src/dev.rs b/cipher/src/dev.rs new file mode 100644 index 000000000..6bedbe989 --- /dev/null +++ b/cipher/src/dev.rs @@ -0,0 +1,2 @@ +mod block; +mod stream; diff --git a/cipher/src/dev/block.rs b/cipher/src/dev/block.rs new file mode 100644 index 000000000..42558c561 --- /dev/null +++ b/cipher/src/dev/block.rs @@ -0,0 +1,388 @@ +//! Development-related functionality + +/// Define block cipher test +#[macro_export] +macro_rules! block_cipher_test { + ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { + #[test] + fn $name() { + use cipher::{ + array::Array, + blobby::Blob3Iterator, + block::{BlockCipherDecrypt, BlockCipherEncrypt}, + typenum::Unsigned, + BlockSizeUser, KeyInit, + }; + + fn run_test(key: &[u8], pt: &[u8], ct: &[u8]) -> bool { + let mut state = <$cipher as KeyInit>::new_from_slice(key).unwrap(); + + let mut block = Array::try_from(pt).unwrap(); + state.encrypt_block(&mut block); + if ct != block.as_slice() { + return false; + } + + state.decrypt_block(&mut block); + if pt != block.as_slice() { + return false; + } + + true + } + + fn run_par_test(key: &[u8], pt: &[u8]) -> bool { + type Block = cipher::Block<$cipher>; + + let mut state = <$cipher as KeyInit>::new_from_slice(key).unwrap(); + + let block = Block::try_from(pt).unwrap(); + let mut blocks1 = vec![block; 101]; + for (i, b) in blocks1.iter_mut().enumerate() { + *b = block; + b[0] = b[0].wrapping_add(i as u8); + } + let mut blocks2 = blocks1.clone(); + + // check that `encrypt_blocks` and `encrypt_block` + // result in the same ciphertext + state.encrypt_blocks(&mut blocks1); + for b in blocks2.iter_mut() { + state.encrypt_block(b); + } + if blocks1 != blocks2 { + return false; + } + + // check that `encrypt_blocks` and `encrypt_block` + // result in the same plaintext + state.decrypt_blocks(&mut blocks1); + for b in blocks2.iter_mut() { + state.decrypt_block(b); + } + if blocks1 != blocks2 { + return false; + } + + true + } + + let data = include_bytes!(concat!("data/", $test_name, ".blb")); + for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { + let [key, pt, ct] = row.unwrap(); + if !run_test(key, pt, ct) { + panic!( + "\n\ + Failed test №{}\n\ + key:\t{:?}\n\ + plaintext:\t{:?}\n\ + ciphertext:\t{:?}\n", + i, key, pt, ct, + ); + } + + // test parallel blocks encryption/decryption + if !run_par_test(key, pt) { + panic!( + "\n\ + Failed parallel test №{}\n\ + key:\t{:?}\n\ + plaintext:\t{:?}\n\ + ciphertext:\t{:?}\n", + i, key, pt, ct, + ); + } + } + } + }; +} + +/// Define block mode encryption test +#[macro_export] +macro_rules! block_mode_enc_test { + ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { + #[test] + fn $name() { + use cipher::{ + array::Array, blobby::Blob4Iterator, inout::InOutBuf, typenum::Unsigned, + BlockCipherEncrypt, BlockModeEncrypt, BlockSizeUser, KeyIvInit, + }; + + fn run_test(key: &[u8], iv: &[u8], pt: &[u8], ct: &[u8]) -> bool { + assert_eq!(pt.len(), ct.len()); + // test block-by-block processing + let mut state = <$cipher as KeyIvInit>::new_from_slices(key, iv).unwrap(); + + let mut out = vec![0u8; ct.len()]; + let mut buf = InOutBuf::new(pt, &mut out).unwrap(); + let (blocks, tail) = buf.reborrow().into_chunks(); + assert_eq!(tail.len(), 0); + for block in blocks { + state.encrypt_block_inout(block); + } + if buf.get_out() != ct { + return false; + } + + // test multi-block processing + let mut state = <$cipher as KeyIvInit>::new_from_slices(key, iv).unwrap(); + buf.get_out().iter_mut().for_each(|b| *b = 0); + let (blocks, _) = buf.reborrow().into_chunks(); + state.encrypt_blocks_inout(blocks); + if buf.get_out() != ct { + return false; + } + + true + } + + let data = include_bytes!(concat!("data/", $test_name, ".blb")); + for (i, row) in Blob4Iterator::new(data).unwrap().enumerate() { + let [key, iv, pt, ct] = row.unwrap(); + if !run_test(key, iv, pt, ct) { + panic!( + "\n\ + Failed test №{}\n\ + key:\t{:?}\n\ + iv:\t{:?}\n\ + plaintext:\t{:?}\n\ + ciphertext:\t{:?}\n", + i, key, iv, pt, ct, + ); + } + } + } + }; +} + +/// Define block mode decryption test +#[macro_export] +macro_rules! block_mode_dec_test { + ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { + #[test] + fn $name() { + use cipher::{ + array::Array, blobby::Blob4Iterator, inout::InOutBuf, typenum::Unsigned, + BlockCipherDecrypt, BlockModeDecrypt, BlockSizeUser, KeyIvInit, + }; + + fn run_test(key: &[u8], iv: &[u8], pt: &[u8], ct: &[u8]) -> bool { + assert_eq!(pt.len(), ct.len()); + // test block-by-block processing + let mut state = <$cipher as KeyIvInit>::new_from_slices(key, iv).unwrap(); + + let mut out = vec![0u8; pt.len()]; + let mut buf = InOutBuf::new(ct, &mut out).unwrap(); + let (blocks, tail) = buf.reborrow().into_chunks(); + assert_eq!(tail.len(), 0); + for block in blocks { + state.decrypt_block_inout(block); + } + if buf.get_out() != pt { + return false; + } + + // test multi-block processing + let mut state = <$cipher as KeyIvInit>::new_from_slices(key, iv).unwrap(); + buf.get_out().iter_mut().for_each(|b| *b = 0); + let (blocks, _) = buf.reborrow().into_chunks(); + state.decrypt_blocks_inout(blocks); + if buf.get_out() != pt { + return false; + } + + true + } + + let data = include_bytes!(concat!("data/", $test_name, ".blb")); + for (i, row) in Blob4Iterator::new(data).unwrap().enumerate() { + let [key, iv, pt, ct] = row.unwrap(); + if !run_test(key, iv, pt, ct) { + panic!( + "\n\ + Failed test №{}\n\ + key:\t{:?}\n\ + iv:\t{:?}\n\ + plaintext:\t{:?}\n\ + ciphertext:\t{:?}\n", + i, key, iv, pt, ct, + ); + } + } + } + }; +} + +/// Define `IvState` test +#[macro_export] +macro_rules! iv_state_test { + ($name:ident, $cipher:ty, encrypt $(,)?) => { + $crate::iv_state_test!($name, $cipher, encrypt_blocks); + }; + ($name:ident, $cipher:ty, decrypt $(,)?) => { + $crate::iv_state_test!($name, $cipher, decrypt_blocks); + }; + ($name:ident, $cipher:ty, apply_ks $(,)?) => { + $crate::iv_state_test!($name, $cipher, apply_keystream_blocks); + }; + ($name:ident, $cipher:ty, $method:ident $(,)?) => { + #[test] + fn $name() { + use cipher::*; + + let mut blocks = [Block::<$cipher>::default(); 32]; + + for (i, block) in blocks.iter_mut().enumerate() { + for (j, b) in block.iter_mut().enumerate() { + *b = (i + j) as u8; + } + } + + let mut key = Key::<$cipher>::default(); + let mut iv = Iv::<$cipher>::default(); + key.iter_mut().for_each(|b| *b = 0x42); + iv.iter_mut().for_each(|b| *b = 0x24); + + let mut cipher = <$cipher>::new(&key, &iv); + let mut target = blocks.clone(); + cipher.$method(&mut target); + + for i in 0..32 { + let mut blocks = blocks.clone(); + let (b1, b2) = blocks.split_at_mut(i); + let mut cipher1 = <$cipher>::new(&key, &iv); + cipher1.$method(b1); + let temp_iv = cipher1.iv_state(); + let mut cipher2 = <$cipher>::new(&key, &temp_iv); + cipher2.$method(b2); + assert_eq!(blocks, target); + } + } + }; +} + +/// Define block encryptor benchmark +#[macro_export] +macro_rules! block_encryptor_bench { + (Key: $cipher:ty, $block_name:ident, $blocks_name:ident $(,)? ) => { + $crate::block_encryptor_bench!( + { + use $crate::KeyInit; + let key = test::black_box(Default::default()); + <$cipher>::new(&key) + }, + $cipher, + $block_name, + $blocks_name, + ); + }; + (KeyIv: $cipher:ty, $block_name:ident, $blocks_name:ident $(,)? ) => { + $crate::block_encryptor_bench!( + { + use $crate::KeyIvInit; + let key = test::black_box(Default::default()); + let iv = test::black_box(Default::default()); + <$cipher>::new(&key, &iv) + }, + $cipher, + $block_name, + $blocks_name, + ); + }; + ($init:block, $cipher:ty, $block_name:ident, $blocks_name:ident $(,)? ) => { + #[bench] + pub fn $block_name(bh: &mut test::Bencher) { + #[allow(unused)] + use $crate::{BlockCipherEncrypt, BlockModeEncrypt}; + + let mut cipher = $init; + let mut blocks = vec![Default::default(); 1024]; + + bh.iter(|| { + for block in blocks.iter_mut() { + cipher.encrypt_block(block); + } + test::black_box(&blocks); + }); + bh.bytes = (blocks.len() * blocks[0].len()) as u64; + } + + #[bench] + pub fn $blocks_name(bh: &mut test::Bencher) { + #[allow(unused)] + use $crate::{BlockCipherEncrypt, BlockModeEncrypt}; + + let mut cipher = $init; + let mut blocks = vec![Default::default(); 1024]; + + bh.iter(|| { + cipher.encrypt_blocks(&mut blocks); + test::black_box(&blocks); + }); + bh.bytes = (blocks.len() * blocks[0].len()) as u64; + } + }; +} + +/// Define block decryptor benchmark +#[macro_export] +macro_rules! block_decryptor_bench { + (Key: $cipher:ty, $block_name:ident, $blocks_name:ident $(,)? ) => { + $crate::block_decryptor_bench!( + { + use $crate::KeyInit; + let key = test::black_box(Default::default()); + <$cipher>::new(&key) + }, + $cipher, + $block_name, + $blocks_name, + ); + }; + (KeyIv: $cipher:ty, $block_name:ident, $blocks_name:ident $(,)? ) => { + $crate::block_decryptor_bench!( + { + use $crate::KeyIvInit; + let key = test::black_box(Default::default()); + let iv = test::black_box(Default::default()); + <$cipher>::new(&key, &iv) + }, + $cipher, + $block_name, + $blocks_name, + ); + }; + ($init:block, $cipher:ty, $block_name:ident, $blocks_name:ident $(,)? ) => { + #[bench] + pub fn $block_name(bh: &mut test::Bencher) { + #[allow(unused)] + use $crate::{BlockCipherDecrypt, BlockModeDecrypt}; + + let mut cipher = $init; + let mut blocks = vec![Default::default(); 1024]; + + bh.iter(|| { + for block in blocks.iter_mut() { + cipher.decrypt_block(block); + } + test::black_box(&blocks); + }); + bh.bytes = (blocks.len() * blocks[0].len()) as u64; + } + + #[bench] + pub fn $blocks_name(bh: &mut test::Bencher) { + #[allow(unused)] + use $crate::{BlockCipherDecrypt, BlockModeDecrypt}; + + let mut cipher = $init; + let mut blocks = vec![Default::default(); 1024]; + + bh.iter(|| { + cipher.decrypt_blocks(&mut blocks); + test::black_box(&blocks); + }); + bh.bytes = (blocks.len() * blocks[0].len()) as u64; + } + }; +} diff --git a/cipher/src/dev/stream.rs b/cipher/src/dev/stream.rs new file mode 100644 index 000000000..5502b7fe8 --- /dev/null +++ b/cipher/src/dev/stream.rs @@ -0,0 +1,142 @@ +//! Development-related functionality + +/// Test core functionality of synchronous stream cipher +#[macro_export] +macro_rules! stream_cipher_test { + ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { + #[test] + fn $name() { + use cipher::array::Array; + use cipher::{blobby::Blob4Iterator, KeyIvInit, StreamCipher}; + + let data = include_bytes!(concat!("data/", $test_name, ".blb")); + for (i, row) in Blob4Iterator::new(data).unwrap().enumerate() { + let [key, iv, pt, ct] = row.unwrap(); + + for chunk_n in 1..256 { + let mut mode = <$cipher>::new_from_slices(key, iv).unwrap(); + let mut pt = pt.to_vec(); + for chunk in pt.chunks_mut(chunk_n) { + mode.apply_keystream(chunk); + } + if pt != &ct[..] { + panic!( + "Failed main test №{}, chunk size: {}\n\ + key:\t{:?}\n\ + iv:\t{:?}\n\ + plaintext:\t{:?}\n\ + ciphertext:\t{:?}\n", + i, chunk_n, key, iv, pt, ct, + ); + } + } + } + } + }; +} + +/// Test stream synchronous stream cipher seeking capabilities +#[macro_export] +macro_rules! stream_cipher_seek_test { + ($name:ident, $cipher:ty) => { + #[test] + fn $name() { + use cipher::array::Array; + use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek}; + + fn get_cipher() -> $cipher { + <$cipher>::new(&Default::default(), &Default::default()) + } + + const MAX_SEEK: usize = 512; + + let mut ct = [0u8; MAX_SEEK]; + get_cipher().apply_keystream(&mut ct[..]); + + for n in 0..MAX_SEEK { + let mut cipher = get_cipher(); + assert_eq!(cipher.current_pos::(), 0); + cipher.seek(n); + assert_eq!(cipher.current_pos::(), n); + let mut buf = [0u8; MAX_SEEK]; + cipher.apply_keystream(&mut buf[n..]); + assert_eq!(cipher.current_pos::(), MAX_SEEK); + assert_eq!(&buf[n..], &ct[n..]); + } + + const MAX_CHUNK: usize = 128; + const MAX_LEN: usize = 1024; + + let mut buf = [0u8; MAX_CHUNK]; + let mut cipher = get_cipher(); + assert_eq!(cipher.current_pos::(), 0); + cipher.apply_keystream(&mut []); + assert_eq!(cipher.current_pos::(), 0); + for n in 1..MAX_CHUNK { + assert_eq!(cipher.current_pos::(), 0); + for m in 1.. { + cipher.apply_keystream(&mut buf[..n]); + assert_eq!(cipher.current_pos::(), n * m); + if n * m > MAX_LEN { + break; + } + } + cipher.seek(0); + } + } + }; +} + +/// Create stream cipher benchmarks +#[macro_export] +macro_rules! stream_cipher_bench { + ( + $cipher:ty; + $($name:ident $bs:expr;)* + ) => { + $crate::stream_cipher_bench!( + Init: { + use $crate::KeyIvInit; + let key = test::black_box(Default::default()); + let iv = test::black_box(Default::default()); + <$cipher>::new(&key, &iv) + }; + $($name $bs;)* + ); + }; + ( + Key: $cipher:ty; + $($name:ident $bs:expr;)* + ) => { + $crate::stream_cipher_bench!( + Init: { + use $crate::KeyInit; + let key = test::black_box(Default::default()); + let iv = test::black_box(Default::default()); + <$cipher>::new(&key, &iv) + }; + $($name $bs;)* + ); + }; + ( + Init: $init:expr; + $($name:ident $bs:expr;)* + ) => { + $( + #[bench] + fn $name(b: &mut test::Bencher) { + use $crate::StreamCipher; + + let mut cipher = $init; + let mut buf = vec![0; $bs]; + + b.iter(|| { + cipher.apply_keystream(&mut buf); + test::black_box(&buf); + }); + + b.bytes = $bs; + } + )* + }; +} diff --git a/cipher/src/lib.rs b/cipher/src/lib.rs new file mode 100644 index 000000000..7e34bba0c --- /dev/null +++ b/cipher/src/lib.rs @@ -0,0 +1,49 @@ +//! This crate defines a set of traits which describe the functionality of +//! [block ciphers][1], [block modes][2], and [stream ciphers][3]. +//! +//! [1]: https://en.wikipedia.org/wiki/Block_cipher +//! [2]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation +//! [3]: https://en.wikipedia.org/wiki/Stream_cipher + +#![no_std] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" +)] +#![warn( + missing_docs, + rust_2018_idioms, + unused_lifetimes, + missing_debug_implementations +)] + +#[cfg(all(feature = "block-padding", feature = "alloc"))] +extern crate alloc; + +#[cfg(feature = "dev")] +pub use blobby; +pub use crypto_common; +#[cfg(feature = "rand_core")] +pub use crypto_common::rand_core; +pub use inout; +#[cfg(feature = "block-padding")] +pub use inout::block_padding; +#[cfg(feature = "zeroize")] +pub use zeroize; + +pub mod block; +#[cfg(feature = "dev")] +mod dev; +pub mod stream; + +pub use block::*; +pub use stream::*; + +pub use crypto_common::{ + array::{self, Array}, + typenum::{self, consts}, + AlgorithmName, Block, BlockSizeUser, InnerIvInit, InvalidLength, Iv, IvSizeUser, IvState, Key, + KeyInit, KeyIvInit, KeySizeUser, ParBlocks, ParBlocksSizeUser, +}; +pub use inout::{InOut, InOutBuf}; diff --git a/cipher/src/stream.rs b/cipher/src/stream.rs new file mode 100644 index 000000000..520837db5 --- /dev/null +++ b/cipher/src/stream.rs @@ -0,0 +1,240 @@ +//! Traits which define functionality of stream ciphers. +//! +//! See [RustCrypto/stream-ciphers](https://github.com/RustCrypto/stream-ciphers) +//! for ciphers implementation. + +use crate::block::{BlockModeDecrypt, BlockModeEncrypt}; +use crypto_common::Block; +use inout::{InOutBuf, NotEqualError}; + +mod core_api; +mod errors; +mod wrapper; + +pub use core_api::{ + StreamCipherBackend, StreamCipherClosure, StreamCipherCore, StreamCipherCounter, + StreamCipherSeekCore, +}; +pub use errors::{OverflowError, StreamCipherError}; +pub use wrapper::StreamCipherCoreWrapper; + +/// Marker trait for block-level asynchronous stream ciphers +pub trait AsyncStreamCipher: Sized { + /// Encrypt data using `InOutBuf`. + fn encrypt_inout(mut self, data: InOutBuf<'_, '_, u8>) + where + Self: BlockModeEncrypt, + { + let (blocks, mut tail) = data.into_chunks(); + self.encrypt_blocks_inout(blocks); + let n = tail.len(); + if n != 0 { + let mut block = Block::::default(); + block[..n].copy_from_slice(tail.get_in()); + self.encrypt_block(&mut block); + tail.get_out().copy_from_slice(&block[..n]); + } + } + + /// Decrypt data using `InOutBuf`. + fn decrypt_inout(mut self, data: InOutBuf<'_, '_, u8>) + where + Self: BlockModeDecrypt, + { + let (blocks, mut tail) = data.into_chunks(); + self.decrypt_blocks_inout(blocks); + let n = tail.len(); + if n != 0 { + let mut block = Block::::default(); + block[..n].copy_from_slice(tail.get_in()); + self.decrypt_block(&mut block); + tail.get_out().copy_from_slice(&block[..n]); + } + } + /// Encrypt data in place. + fn encrypt(self, buf: &mut [u8]) + where + Self: BlockModeEncrypt, + { + self.encrypt_inout(buf.into()); + } + + /// Decrypt data in place. + fn decrypt(self, buf: &mut [u8]) + where + Self: BlockModeDecrypt, + { + self.decrypt_inout(buf.into()); + } + + /// Encrypt data from buffer to buffer. + fn encrypt_b2b(self, in_buf: &[u8], out_buf: &mut [u8]) -> Result<(), NotEqualError> + where + Self: BlockModeEncrypt, + { + InOutBuf::new(in_buf, out_buf).map(|b| self.encrypt_inout(b)) + } + + /// Decrypt data from buffer to buffer. + fn decrypt_b2b(self, in_buf: &[u8], out_buf: &mut [u8]) -> Result<(), NotEqualError> + where + Self: BlockModeDecrypt, + { + InOutBuf::new(in_buf, out_buf).map(|b| self.decrypt_inout(b)) + } +} + +/// Synchronous stream cipher core trait. +pub trait StreamCipher { + /// Apply keystream to `inout` data. + /// + /// If end of the keystream will be achieved with the given data length, + /// method will return [`StreamCipherError`] without modifying provided `data`. + fn try_apply_keystream_inout( + &mut self, + buf: InOutBuf<'_, '_, u8>, + ) -> Result<(), StreamCipherError>; + + /// Apply keystream to data behind `buf`. + /// + /// If end of the keystream will be achieved with the given data length, + /// method will return [`StreamCipherError`] without modifying provided `data`. + #[inline] + fn try_apply_keystream(&mut self, buf: &mut [u8]) -> Result<(), StreamCipherError> { + self.try_apply_keystream_inout(buf.into()) + } + + /// Apply keystream to `inout` data. + /// + /// It will XOR generated keystream with the data behind `in` pointer + /// and will write result to `out` pointer. + /// + /// # Panics + /// If end of the keystream will be reached with the given data length, + /// method will panic without modifying the provided `data`. + #[inline] + fn apply_keystream_inout(&mut self, buf: InOutBuf<'_, '_, u8>) { + self.try_apply_keystream_inout(buf).unwrap(); + } + + /// Apply keystream to data in-place. + /// + /// It will XOR generated keystream with `data` and will write result + /// to the same buffer. + /// + /// # Panics + /// If end of the keystream will be reached with the given data length, + /// method will panic without modifying the provided `data`. + #[inline] + fn apply_keystream(&mut self, buf: &mut [u8]) { + self.try_apply_keystream(buf).unwrap(); + } + + /// Apply keystream to data buffer-to-buffer. + /// + /// It will XOR generated keystream with data from the `input` buffer + /// and will write result to the `output` buffer. + /// + /// Returns [`StreamCipherError`] if provided `in_blocks` and `out_blocks` + /// have different lengths or if end of the keystream will be reached with + /// the given input data length. + #[inline] + fn apply_keystream_b2b( + &mut self, + input: &[u8], + output: &mut [u8], + ) -> Result<(), StreamCipherError> { + InOutBuf::new(input, output) + .map_err(|_| StreamCipherError) + .and_then(|buf| self.try_apply_keystream_inout(buf)) + } +} + +/// Trait for seekable stream ciphers. +/// +/// Methods of this trait are generic over the [`SeekNum`] trait, which is +/// implemented for primitive numeric types, i.e.: `i32`, `u32`, `u64`, +/// `u128`, and `usize`. +pub trait StreamCipherSeek { + /// Try to get current keystream position + /// + /// Returns [`OverflowError`] if position can not be represented by type `T` + fn try_current_pos(&self) -> Result; + + /// Try to seek to the given position + /// + /// Returns [`StreamCipherError`] if provided position value is bigger than + /// keystream length. + fn try_seek(&mut self, pos: T) -> Result<(), StreamCipherError>; + + /// Get current keystream position + /// + /// # Panics + /// If position can not be represented by type `T` + fn current_pos(&self) -> T { + self.try_current_pos().unwrap() + } + + /// Seek to the given position + /// + /// # Panics + /// If provided position value is bigger than keystream length + fn seek(&mut self, pos: T) { + self.try_seek(pos).unwrap() + } +} + +impl StreamCipher for &mut C { + #[inline] + fn try_apply_keystream_inout( + &mut self, + buf: InOutBuf<'_, '_, u8>, + ) -> Result<(), StreamCipherError> { + C::try_apply_keystream_inout(self, buf) + } +} + +/// Trait implemented for numeric types which can be used with the +/// [`StreamCipherSeek`] trait. +/// +/// This trait is implemented for `i32`, `u32`, `u64`, `u128`, and `usize`. +/// It is not intended to be implemented in third-party crates. +pub trait SeekNum: Sized { + /// Try to get position for block number `block`, byte position inside + /// block `byte`, and block size `bs`. + fn from_block_byte( + block: T, + byte: u8, + bs: u8, + ) -> Result; + + /// Try to get block number and bytes position for given block size `bs`. + fn into_block_byte(self, bs: u8) -> Result<(T, u8), OverflowError>; +} + +macro_rules! impl_seek_num { + {$($t:ty )*} => { + $( + impl SeekNum for $t { + fn from_block_byte(block: T, byte: u8, block_size: u8) -> Result { + debug_assert!(byte != 0); + let rem = block_size.checked_sub(byte).ok_or(OverflowError)?; + let block: Self = block.try_into().map_err(|_| OverflowError)?; + block + .checked_mul(block_size.into()) + .and_then(|v| v.checked_sub(rem.into())) + .ok_or(OverflowError) + } + + fn into_block_byte(self, block_size: u8) -> Result<(T, u8), OverflowError> { + let bs: Self = block_size.into(); + let byte = (self % bs) as u8; + let block = T::try_from(self / bs).map_err(|_| OverflowError)?; + Ok((block, byte)) + } + } + )* + }; +} + +impl_seek_num! { i32 u32 u64 u128 usize } diff --git a/cipher/src/stream/core_api.rs b/cipher/src/stream/core_api.rs new file mode 100644 index 000000000..200c1fee0 --- /dev/null +++ b/cipher/src/stream/core_api.rs @@ -0,0 +1,277 @@ +use super::StreamCipherError; +use crate::{array::Array, typenum::Unsigned}; +use crypto_common::{Block, BlockSizeUser, BlockSizes, ParBlocks, ParBlocksSizeUser}; +use inout::{InOut, InOutBuf}; + +/// Trait implemented by stream cipher backends. +pub trait StreamCipherBackend: ParBlocksSizeUser { + /// Generate keystream block. + fn gen_ks_block(&mut self, block: &mut Block); + + /// Generate keystream blocks in parallel. + #[inline(always)] + fn gen_par_ks_blocks(&mut self, blocks: &mut ParBlocks) { + for block in blocks { + self.gen_ks_block(block); + } + } + + /// Generate keystream blocks. Length of the buffer MUST be smaller + /// than `Self::ParBlocksSize`. + #[inline(always)] + fn gen_tail_blocks(&mut self, blocks: &mut [Block]) { + assert!(blocks.len() < Self::ParBlocksSize::USIZE); + for block in blocks { + self.gen_ks_block(block); + } + } +} + +/// Trait for [`StreamCipherBackend`] users. +/// +/// This trait is used to define rank-2 closures. +pub trait StreamCipherClosure: BlockSizeUser { + /// Execute closure with the provided stream cipher backend. + fn call>(self, backend: &mut B); +} + +/// Block-level synchronous stream ciphers. +pub trait StreamCipherCore: BlockSizeUser + Sized { + /// Return number of remaining blocks before cipher wraps around. + /// + /// Returns `None` if number of remaining blocks can not be computed + /// (e.g. in ciphers based on the sponge construction) or it's too big + /// to fit into `usize`. + fn remaining_blocks(&self) -> Option; + + /// Process data using backend provided to the rank-2 closure. + fn process_with_backend(&mut self, f: impl StreamCipherClosure); + + /// Write keystream block. + /// + /// WARNING: this method does not check number of remaining blocks! + #[inline] + fn write_keystream_block(&mut self, block: &mut Block) { + self.process_with_backend(WriteBlockCtx { block }); + } + + /// Write keystream blocks. + /// + /// WARNING: this method does not check number of remaining blocks! + #[inline] + fn write_keystream_blocks(&mut self, blocks: &mut [Block]) { + self.process_with_backend(WriteBlocksCtx { blocks }); + } + + /// Apply keystream block. + /// + /// WARNING: this method does not check number of remaining blocks! + #[inline] + fn apply_keystream_block_inout(&mut self, block: InOut<'_, '_, Block>) { + self.process_with_backend(ApplyBlockCtx { block }); + } + + /// Apply keystream blocks. + /// + /// WARNING: this method does not check number of remaining blocks! + #[inline] + fn apply_keystream_blocks(&mut self, blocks: &mut [Block]) { + self.process_with_backend(ApplyBlocksCtx { + blocks: blocks.into(), + }); + } + + /// Apply keystream blocks. + /// + /// WARNING: this method does not check number of remaining blocks! + #[inline] + fn apply_keystream_blocks_inout(&mut self, blocks: InOutBuf<'_, '_, Block>) { + self.process_with_backend(ApplyBlocksCtx { blocks }); + } + + /// Try to apply keystream to data not divided into blocks. + /// + /// Consumes cipher since it may consume final keystream block only + /// partially. + /// + /// Returns an error if number of remaining blocks is not sufficient + /// for processing the input data. + #[inline] + fn try_apply_keystream_partial( + mut self, + mut buf: InOutBuf<'_, '_, u8>, + ) -> Result<(), StreamCipherError> { + if let Some(rem) = self.remaining_blocks() { + let blocks = if buf.len() % Self::BlockSize::USIZE == 0 { + buf.len() % Self::BlockSize::USIZE + } else { + buf.len() % Self::BlockSize::USIZE + 1 + }; + if blocks > rem { + return Err(StreamCipherError); + } + } + + if buf.len() > Self::BlockSize::USIZE { + let (blocks, tail) = buf.into_chunks(); + self.apply_keystream_blocks_inout(blocks); + buf = tail; + } + let n = buf.len(); + if n == 0 { + return Ok(()); + } + let mut block = Block::::default(); + block[..n].copy_from_slice(buf.get_in()); + let t = InOutBuf::from_mut(&mut block); + self.apply_keystream_blocks_inout(t); + buf.get_out().copy_from_slice(&block[..n]); + Ok(()) + } + + /// Try to apply keystream to data not divided into blocks. + /// + /// Consumes cipher since it may consume final keystream block only + /// partially. + /// + /// # Panics + /// If number of remaining blocks is not sufficient for processing the + /// input data. + #[inline] + fn apply_keystream_partial(self, buf: InOutBuf<'_, '_, u8>) { + self.try_apply_keystream_partial(buf).unwrap() + } +} + +// note: unfortunately, currently we can not write blanket impls of +// `BlockEncryptMut` and `BlockDecryptMut` for `T: StreamCipherCore` +// since it requires mutually exclusive traits, see: +// https://github.com/rust-lang/rfcs/issues/1053 + +/// Counter type usable with [`StreamCipherCore`]. +/// +/// This trait is implemented for `i32`, `u32`, `u64`, `u128`, and `usize`. +/// It's not intended to be implemented in third-party crates, but doing so +/// is not forbidden. +pub trait StreamCipherCounter: + TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryFrom + + TryInto + + TryInto + + TryInto + + TryInto + + TryInto +{ +} + +/// Block-level seeking trait for stream ciphers. +pub trait StreamCipherSeekCore: StreamCipherCore { + /// Counter type used inside stream cipher. + type Counter: StreamCipherCounter; + + /// Get current block position. + fn get_block_pos(&self) -> Self::Counter; + + /// Set block position. + fn set_block_pos(&mut self, pos: Self::Counter); +} + +macro_rules! impl_counter { + {$($t:ty )*} => { + $( impl StreamCipherCounter for $t { } )* + }; +} + +impl_counter! { u32 u64 u128 } + +struct WriteBlockCtx<'a, BS: BlockSizes> { + block: &'a mut Block, +} +impl BlockSizeUser for WriteBlockCtx<'_, BS> { + type BlockSize = BS; +} +impl StreamCipherClosure for WriteBlockCtx<'_, BS> { + #[inline(always)] + fn call>(self, backend: &mut B) { + backend.gen_ks_block(self.block); + } +} + +struct WriteBlocksCtx<'a, BS: BlockSizes> { + blocks: &'a mut [Block], +} +impl BlockSizeUser for WriteBlocksCtx<'_, BS> { + type BlockSize = BS; +} +impl StreamCipherClosure for WriteBlocksCtx<'_, BS> { + #[inline(always)] + fn call>(self, backend: &mut B) { + if B::ParBlocksSize::USIZE > 1 { + let (chunks, tail) = Array::slice_as_chunks_mut(self.blocks); + for chunk in chunks { + backend.gen_par_ks_blocks(chunk); + } + backend.gen_tail_blocks(tail); + } else { + for block in self.blocks { + backend.gen_ks_block(block); + } + } + } +} + +struct ApplyBlockCtx<'inp, 'out, BS: BlockSizes> { + block: InOut<'inp, 'out, Block>, +} + +impl BlockSizeUser for ApplyBlockCtx<'_, '_, BS> { + type BlockSize = BS; +} + +impl StreamCipherClosure for ApplyBlockCtx<'_, '_, BS> { + #[inline(always)] + fn call>(mut self, backend: &mut B) { + let mut t = Default::default(); + backend.gen_ks_block(&mut t); + self.block.xor_in2out(&t); + } +} + +struct ApplyBlocksCtx<'inp, 'out, BS: BlockSizes> { + blocks: InOutBuf<'inp, 'out, Block>, +} + +impl BlockSizeUser for ApplyBlocksCtx<'_, '_, BS> { + type BlockSize = BS; +} + +impl StreamCipherClosure for ApplyBlocksCtx<'_, '_, BS> { + #[inline(always)] + #[allow(clippy::needless_range_loop)] + fn call>(self, backend: &mut B) { + if B::ParBlocksSize::USIZE > 1 { + let (chunks, mut tail) = self.blocks.into_chunks::(); + for mut chunk in chunks { + let mut tmp = Default::default(); + backend.gen_par_ks_blocks(&mut tmp); + chunk.xor_in2out(&tmp); + } + let n = tail.len(); + let mut buf = Array::<_, B::ParBlocksSize>::default(); + let ks = &mut buf[..n]; + backend.gen_tail_blocks(ks); + for i in 0..n { + tail.get(i).xor_in2out(&ks[i]); + } + } else { + for mut block in self.blocks { + let mut t = Default::default(); + backend.gen_ks_block(&mut t); + block.xor_in2out(&t); + } + } + } +} diff --git a/cipher/src/stream/errors.rs b/cipher/src/stream/errors.rs new file mode 100644 index 000000000..e30576da6 --- /dev/null +++ b/cipher/src/stream/errors.rs @@ -0,0 +1,39 @@ +//! Error types. + +use core::fmt; + +/// This error is returned by the [`StreamCipher`][crate::stream::StreamCipher] +/// trait methods. +/// +/// Usually it's used in cases when stream cipher has reached the end +/// of a keystream, but also can be used if lengths of provided input +/// and output buffers are not equal. +#[derive(Copy, Clone, Debug)] +pub struct StreamCipherError; + +impl fmt::Display for StreamCipherError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("Loop Error") + } +} + +impl core::error::Error for StreamCipherError {} + +/// The error type returned when a cipher position can not be represented +/// by the requested type. +#[derive(Copy, Clone, Debug)] +pub struct OverflowError; + +impl fmt::Display for OverflowError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("Overflow Error") + } +} + +impl From for StreamCipherError { + fn from(_: OverflowError) -> StreamCipherError { + StreamCipherError + } +} + +impl core::error::Error for OverflowError {} diff --git a/cipher/src/stream/wrapper.rs b/cipher/src/stream/wrapper.rs new file mode 100644 index 000000000..16863a42c --- /dev/null +++ b/cipher/src/stream/wrapper.rs @@ -0,0 +1,251 @@ +use super::{ + errors::StreamCipherError, Block, OverflowError, SeekNum, StreamCipher, StreamCipherCore, + StreamCipherSeek, StreamCipherSeekCore, +}; +use core::fmt; +use crypto_common::{typenum::Unsigned, Iv, IvSizeUser, Key, KeyInit, KeyIvInit, KeySizeUser}; +use inout::InOutBuf; +#[cfg(feature = "zeroize")] +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// Buffering wrapper around a [`StreamCipherCore`] implementation. +/// +/// It handles data buffering and implements the slice-based traits. +pub struct StreamCipherCoreWrapper { + core: T, + // First byte is used as position + buffer: Block, +} + +impl Default for StreamCipherCoreWrapper { + #[inline] + fn default() -> Self { + Self::from_core(T::default()) + } +} + +impl Clone for StreamCipherCoreWrapper { + #[inline] + fn clone(&self) -> Self { + Self { + core: self.core.clone(), + buffer: self.buffer.clone(), + } + } +} + +impl fmt::Debug for StreamCipherCoreWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let pos = self.get_pos().into(); + let buf_data = &self.buffer[pos..]; + f.debug_struct("StreamCipherCoreWrapper") + .field("core", &self.core) + .field("buffer_data", &buf_data) + .finish() + } +} + +impl StreamCipherCoreWrapper { + /// Return reference to the core type. + pub fn get_core(&self) -> &T { + &self.core + } + + /// Return reference to the core type. + pub fn from_core(core: T) -> Self { + let mut buffer: Block = Default::default(); + buffer[0] = T::BlockSize::U8; + Self { core, buffer } + } + + /// Return current cursor position. + #[inline] + fn get_pos(&self) -> u8 { + let pos = self.buffer[0]; + if pos == 0 || pos > T::BlockSize::U8 { + debug_assert!(false); + // SAFETY: `pos` never breaks the invariant + unsafe { + core::hint::unreachable_unchecked(); + } + } + pos + } + + /// Set buffer position without checking that it's smaller + /// than buffer size. + /// + /// # Safety + /// `pos` MUST be bigger than zero and smaller or equal to `T::BlockSize::USIZE`. + #[inline] + unsafe fn set_pos_unchecked(&mut self, pos: usize) { + debug_assert!(pos != 0 && pos <= T::BlockSize::USIZE); + // Block size is always smaller than 256 because of the `BlockSizes` bound, + // so if the safety condition is satisfied, the `as` cast does not truncate + // any non-zero bits. + self.buffer[0] = pos as u8; + } + + /// Return number of remaining bytes in the internal buffer. + #[inline] + fn remaining(&self) -> u8 { + // This never underflows because of the safety invariant + T::BlockSize::U8 - self.get_pos() + } + + fn check_remaining(&self, data_len: usize) -> Result<(), StreamCipherError> { + let rem_blocks = match self.core.remaining_blocks() { + Some(v) => v, + None => return Ok(()), + }; + + let buf_rem = usize::from(self.remaining()); + let data_len = match data_len.checked_sub(buf_rem) { + Some(0) | None => return Ok(()), + Some(res) => res, + }; + + let bs = T::BlockSize::USIZE; + let blocks = data_len.div_ceil(bs); + if blocks > rem_blocks { + Err(StreamCipherError) + } else { + Ok(()) + } + } +} + +impl StreamCipher for StreamCipherCoreWrapper { + #[inline] + fn try_apply_keystream_inout( + &mut self, + mut data: InOutBuf<'_, '_, u8>, + ) -> Result<(), StreamCipherError> { + self.check_remaining(data.len())?; + + let pos = usize::from(self.get_pos()); + let rem = usize::from(self.remaining()); + let data_len = data.len(); + + if rem != 0 { + if data_len <= rem { + data.xor_in2out(&self.buffer[pos..][..data_len]); + // SAFETY: we have checked that `data_len` is less or equal to length + // of remaining keystream data, thus `pos + data_len` can not be bigger + // than block size. Since `pos` is never zero, `pos + data_len` can not + // be zero. Thus `pos + data_len` satisfies the safety invariant required + // by `set_pos_unchecked`. + unsafe { + self.set_pos_unchecked(pos + data_len); + } + return Ok(()); + } + let (mut left, right) = data.split_at(rem); + data = right; + left.xor_in2out(&self.buffer[pos..]); + } + + let (blocks, mut tail) = data.into_chunks(); + self.core.apply_keystream_blocks_inout(blocks); + + let new_pos = if tail.is_empty() { + T::BlockSize::USIZE + } else { + // Note that we temporarily write a pseudo-random byte into + // the first byte of `self.buffer`. It may break the safety invariant, + // but after XORing keystream block with `tail`, we immediately + // overwrite the first byte with a correct value. + self.core.write_keystream_block(&mut self.buffer); + tail.xor_in2out(&self.buffer[..tail.len()]); + tail.len() + }; + + // SAFETY: `into_chunks` always returns tail with size + // less than block size. If `tail.len()` is zero, we replace + // it with block size. Thus the invariant required by + // `set_pos_unchecked` is satisfied. + unsafe { + self.set_pos_unchecked(new_pos); + } + + Ok(()) + } +} + +impl StreamCipherSeek for StreamCipherCoreWrapper { + fn try_current_pos(&self) -> Result { + let pos = self.get_pos(); + SN::from_block_byte(self.core.get_block_pos(), pos, T::BlockSize::U8) + } + + fn try_seek(&mut self, new_pos: SN) -> Result<(), StreamCipherError> { + let (block_pos, byte_pos) = new_pos.into_block_byte(T::BlockSize::U8)?; + // For correct implementations of `SeekNum` compiler should be able to + // eliminate this assert + assert!(byte_pos < T::BlockSize::U8); + + self.core.set_block_pos(block_pos); + let new_pos = if byte_pos != 0 { + // See comment in `try_apply_keystream_inout` for use of `write_keystream_block` + self.core.write_keystream_block(&mut self.buffer); + byte_pos.into() + } else { + T::BlockSize::USIZE + }; + // SAFETY: we assert that `byte_pos` is always smaller than block size. + // If `byte_pos` is zero, we replace it with block size. Thus the invariant + // required by `set_pos_unchecked` is satisfied. + unsafe { + self.set_pos_unchecked(new_pos); + } + Ok(()) + } +} + +// Note: ideally we would only implement the InitInner trait and everything +// else would be handled by blanket impls, but, unfortunately, it will +// not work properly without mutually exclusive traits, see: +// https://github.com/rust-lang/rfcs/issues/1053 + +impl KeySizeUser for StreamCipherCoreWrapper { + type KeySize = T::KeySize; +} + +impl IvSizeUser for StreamCipherCoreWrapper { + type IvSize = T::IvSize; +} + +impl KeyIvInit for StreamCipherCoreWrapper { + #[inline] + fn new(key: &Key, iv: &Iv) -> Self { + let mut buffer = Block::::default(); + buffer[0] = T::BlockSize::U8; + Self { + core: T::new(key, iv), + buffer, + } + } +} + +impl KeyInit for StreamCipherCoreWrapper { + #[inline] + fn new(key: &Key) -> Self { + let mut buffer = Block::::default(); + buffer[0] = T::BlockSize::U8; + Self { + core: T::new(key), + buffer, + } + } +} + +#[cfg(feature = "zeroize")] +impl Drop for StreamCipherCoreWrapper { + fn drop(&mut self) { + // If present, `core` will be zeroized by its own `Drop`. + self.buffer.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +impl ZeroizeOnDrop for StreamCipherCoreWrapper {} diff --git a/crypto-common/CHANGELOG.md b/crypto-common/CHANGELOG.md new file mode 100644 index 000000000..448cb106c --- /dev/null +++ b/crypto-common/CHANGELOG.md @@ -0,0 +1,55 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## UNRELEASED +### Added +- Sealed `BlockSizes` trait implemented for types from `U1` to `U255` + +### Changed +- `BlockUser::BlockSize` is now bounded by the `BlockSizes` trait +- Edition changed to 2021 and MSRV bumped to 1.56 + +## 0.1.6 (2022-07-16) +### Added +- Move `ParBlocks`/`ParBlocksSizeUser` from `cipher` crate ([#1052]) + +[#1052]: https://github.com/RustCrypto/traits/pull/1052 + +## 0.1.5 (2022-07-09) +### Fixed +- Support on-label MSRV ([#1049]) + +[#1049]: https://github.com/RustCrypto/traits/pull/1049 + +## 0.1.4 (2022-07-02) +### Added +- `getrandom` feature ([#1034]) + +[#1034]: https://github.com/RustCrypto/traits/pull/1034 + +## 0.1.3 (2022-02-16) +### Fixed +- Minimal versions build ([#940]) + +[#940]: https://github.com/RustCrypto/traits/pull/940 + +## 0.1.2 (2022-02-10) +### Added +- Re-export `generic-array` and `typenum`. Enable `more_lengths` feature on +`generic-array`. Add `key_size`, `iv_size`, `block_size`, and `output_size` +helper methods. ([#849]) + +[#849]: https://github.com/RustCrypto/traits/pull/849 + +## 0.1.1 (2021-12-14) +### Added +- `rand_core` re-export and proper exposure of key/IV generation methods on docs.rs ([#847]) + +[#847]: https://github.com/RustCrypto/traits/pull/847 + +## 0.1.0 (2021-12-07) +- Initial release diff --git a/crypto-common/Cargo.lock b/crypto-common/Cargo.lock new file mode 100644 index 000000000..05f61c215 --- /dev/null +++ b/crypto-common/Cargo.lock @@ -0,0 +1,65 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crypto-common" +version = "0.2.0-pre.3" +dependencies = [ + "getrandom", + "hybrid-array", + "rand_core", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hybrid-array" +version = "0.2.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c5517ac29f08e88170b9647d85cc5f21c2596de177b4867232e20b214b8da1" +dependencies = [ + "typenum", +] + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/crypto-common/Cargo.toml b/crypto-common/Cargo.toml new file mode 100644 index 000000000..29ebe30a6 --- /dev/null +++ b/crypto-common/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "crypto-common" +description = "Common cryptographic traits" +version = "0.2.0-rc.1" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +readme = "README.md" +edition = "2021" +rust-version = "1.81" +documentation = "https://docs.rs/crypto-common" +repository = "https://github.com/RustCrypto/traits" +keywords = ["crypto", "traits"] +categories = ["cryptography", "no-std"] + +[dependencies] +hybrid-array = "0.2" + +# optional dependencies +rand_core = { version = "0.6.4", optional = true } +getrandom = { version = "0.2", optional = true } + +[features] +getrandom = ["dep:getrandom", "rand_core?/getrandom"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/stream-cipher/LICENSE-APACHE b/crypto-common/LICENSE-APACHE similarity index 100% rename from stream-cipher/LICENSE-APACHE rename to crypto-common/LICENSE-APACHE diff --git a/crypto-mac/LICENSE-MIT b/crypto-common/LICENSE-MIT similarity index 96% rename from crypto-mac/LICENSE-MIT rename to crypto-common/LICENSE-MIT index 8dcb85b30..efb0b5f8b 100644 --- a/crypto-mac/LICENSE-MIT +++ b/crypto-common/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2017 Artyom Pavlov +Copyright (c) 2021 RustCrypto Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/crypto-common/README.md b/crypto-common/README.md new file mode 100644 index 000000000..2197b4091 --- /dev/null +++ b/crypto-common/README.md @@ -0,0 +1,53 @@ +# RustCrypto: Common Cryptographic Traits + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] + +Common traits used by cryptographic algorithms. Users should generally use +higher-level trait crates instead of this one. + +[Documentation][docs-link] + +## Minimum Supported Rust Version + +Rust **1.81** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/crypto-common.svg +[crate-link]: https://crates.io/crates/crypto-common +[docs-image]: https://docs.rs/crypto-common/badge.svg +[docs-link]: https://docs.rs/crypto-common/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260041-hashes +[build-image]: https://github.com/RustCrypto/traits/workflows/crypto-common/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/traits/actions?query=workflow%3Acrypto-common diff --git a/crypto-common/src/hazmat.rs b/crypto-common/src/hazmat.rs new file mode 100644 index 000000000..b8df738aa --- /dev/null +++ b/crypto-common/src/hazmat.rs @@ -0,0 +1,360 @@ +use crate::array::{ + self, + typenum::{Diff, Prod, Sum, Unsigned, U1, U16, U2, U4, U8}, + Array, ArraySize, +}; +use core::{convert::TryInto, default::Default, fmt}; + +/// Serialized internal state. +pub type SerializedState = Array::SerializedStateSize>; + +/// Alias for `AddSerializedStateSize = Sum` +pub type AddSerializedStateSize = Sum::SerializedStateSize>; + +/// Alias for `SubSerializedStateSize = Diff` +pub type SubSerializedStateSize = Diff::SerializedStateSize>; + +/// The error type returned when an object cannot be deserialized from the state. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct DeserializeStateError; + +impl fmt::Display for DeserializeStateError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("Deserialization error") + } +} + +impl core::error::Error for DeserializeStateError {} + +/// Types which can serialize the internal state and be restored from it. +/// +/// # Compatibility +/// +/// Serialized state can be assumed to be stable across backwards compatible +/// versions of an implementation crate, i.e. any `0.x.y` version of a crate +/// should be able to decode data serialized with any other `0.x.z` version, +/// but it may not be able to correctly decode data serialized with a non-`x` +/// version. +/// +/// This guarantee is a subject to issues such as security fixes. +/// +/// # SECURITY WARNING +/// +/// Serialized state may contain sensitive data. +pub trait SerializableState +where + Self: Sized, +{ + /// Size of serialized internal state. + type SerializedStateSize: ArraySize; + + /// Serialize and return internal state. + fn serialize(&self) -> SerializedState; + /// Create an object from serialized internal state. + fn deserialize(serialized_state: &SerializedState) + -> Result; +} + +macro_rules! impl_seializable_state_unsigned { + ($type: ty, $type_size: ty) => { + impl SerializableState for $type { + type SerializedStateSize = $type_size; + + fn serialize(&self) -> SerializedState { + self.to_le_bytes().into() + } + + fn deserialize( + serialized_state: &SerializedState, + ) -> Result { + Ok(<$type>::from_le_bytes((*serialized_state).into())) + } + } + }; +} + +impl_seializable_state_unsigned!(u8, U1); +impl_seializable_state_unsigned!(u16, U2); +impl_seializable_state_unsigned!(u32, U4); +impl_seializable_state_unsigned!(u64, U8); +impl_seializable_state_unsigned!(u128, U16); + +macro_rules! impl_serializable_state_u8_array { + ($($n: ty),*) => { + $( + impl SerializableState for [u8; <$n>::USIZE] { + type SerializedStateSize = $n; + + fn serialize(&self) -> SerializedState { + (*self).into() + } + + fn deserialize( + serialized_state: &SerializedState, + ) -> Result { + Ok((*serialized_state).into()) + } + } + )* + }; +} + +macro_rules! impl_serializable_state_type_array { + ($type: ty, $type_size: ty, $n: ty) => { + impl SerializableState for [$type; <$n>::USIZE] { + type SerializedStateSize = Prod<$n, $type_size>; + + fn serialize(&self) -> SerializedState { + let mut serialized_state = SerializedState::::default(); + for (val, chunk) in self + .iter() + .zip(serialized_state.chunks_exact_mut(<$type_size>::USIZE)) + { + chunk.copy_from_slice(&val.to_le_bytes()); + } + + serialized_state + } + + fn deserialize( + serialized_state: &SerializedState, + ) -> Result { + let mut array = [0; <$n>::USIZE]; + for (val, chunk) in array + .iter_mut() + .zip(serialized_state.chunks_exact(<$type_size>::USIZE)) + { + *val = <$type>::from_le_bytes(chunk.try_into().unwrap()); + } + Ok(array) + } + } + }; +} + +macro_rules! impl_serializable_state_u16_array { + ($($n: ty),*) => { + $( + impl_serializable_state_type_array!(u16, U2, $n); + )* + }; +} + +macro_rules! impl_serializable_state_u32_array { + ($($n: ty),*) => { + $( + impl_serializable_state_type_array!(u32, U4, $n); + )* + }; +} + +macro_rules! impl_serializable_state_u64_array { + ($($n: ty),*) => { + $( + impl_serializable_state_type_array!(u64, U8, $n); + )* + }; +} + +macro_rules! impl_serializable_state_u128_array { + ($($n: ty),*) => { + $( + impl_serializable_state_type_array!(u128, U8, $n); + )* + }; +} + +impl_serializable_state_u8_array! { + array::typenum::U1, + array::typenum::U2, + array::typenum::U3, + array::typenum::U4, + array::typenum::U5, + array::typenum::U6, + array::typenum::U7, + array::typenum::U8, + array::typenum::U9, + array::typenum::U10, + array::typenum::U11, + array::typenum::U12, + array::typenum::U13, + array::typenum::U14, + array::typenum::U15, + array::typenum::U16, + array::typenum::U17, + array::typenum::U18, + array::typenum::U19, + array::typenum::U20, + array::typenum::U21, + array::typenum::U22, + array::typenum::U23, + array::typenum::U24, + array::typenum::U25, + array::typenum::U26, + array::typenum::U27, + array::typenum::U28, + array::typenum::U29, + array::typenum::U30, + array::typenum::U31, + array::typenum::U32, + array::typenum::U33, + array::typenum::U34, + array::typenum::U35, + array::typenum::U36, + array::typenum::U37, + array::typenum::U38, + array::typenum::U39, + array::typenum::U40, + array::typenum::U41, + array::typenum::U42, + array::typenum::U43, + array::typenum::U44, + array::typenum::U45, + array::typenum::U46, + array::typenum::U47, + array::typenum::U48, + array::typenum::U49, + array::typenum::U50, + array::typenum::U51, + array::typenum::U52, + array::typenum::U53, + array::typenum::U54, + array::typenum::U55, + array::typenum::U56, + array::typenum::U57, + array::typenum::U58, + array::typenum::U59, + array::typenum::U60, + array::typenum::U61, + array::typenum::U62, + array::typenum::U63, + array::typenum::U64, + array::typenum::U96, + array::typenum::U128, + array::typenum::U192, + array::typenum::U256, + array::typenum::U384, + array::typenum::U448, + array::typenum::U512, + array::typenum::U768, + array::typenum::U896, + array::typenum::U1024, + array::typenum::U2048, + array::typenum::U4096, + array::typenum::U8192 +} + +impl_serializable_state_u16_array! { + array::typenum::U1, + array::typenum::U2, + array::typenum::U3, + array::typenum::U4, + array::typenum::U5, + array::typenum::U6, + array::typenum::U7, + array::typenum::U8, + array::typenum::U9, + array::typenum::U10, + array::typenum::U11, + array::typenum::U12, + array::typenum::U13, + array::typenum::U14, + array::typenum::U15, + array::typenum::U16, + array::typenum::U17, + array::typenum::U18, + array::typenum::U19, + array::typenum::U20, + array::typenum::U21, + array::typenum::U22, + array::typenum::U23, + array::typenum::U24, + array::typenum::U25, + array::typenum::U26, + array::typenum::U27, + array::typenum::U28, + array::typenum::U29, + array::typenum::U30, + array::typenum::U31, + array::typenum::U32, + array::typenum::U48, + array::typenum::U96, + array::typenum::U128, + array::typenum::U192, + array::typenum::U256, + array::typenum::U384, + array::typenum::U448, + array::typenum::U512, + array::typenum::U2048, + array::typenum::U4096 +} + +impl_serializable_state_u32_array! { + array::typenum::U1, + array::typenum::U2, + array::typenum::U3, + array::typenum::U4, + array::typenum::U5, + array::typenum::U6, + array::typenum::U7, + array::typenum::U8, + array::typenum::U9, + array::typenum::U10, + array::typenum::U11, + array::typenum::U12, + array::typenum::U13, + array::typenum::U14, + array::typenum::U15, + array::typenum::U16, + array::typenum::U24, + array::typenum::U32, + array::typenum::U48, + array::typenum::U64, + array::typenum::U96, + array::typenum::U128, + array::typenum::U192, + array::typenum::U256, + array::typenum::U512, + array::typenum::U1024, + array::typenum::U2048 +} + +impl_serializable_state_u64_array! { + array::typenum::U1, + array::typenum::U2, + array::typenum::U3, + array::typenum::U4, + array::typenum::U5, + array::typenum::U6, + array::typenum::U7, + array::typenum::U8, + array::typenum::U12, + array::typenum::U16, + array::typenum::U24, + array::typenum::U32, + array::typenum::U48, + array::typenum::U64, + array::typenum::U96, + array::typenum::U128, + array::typenum::U256, + array::typenum::U512, + array::typenum::U1024 +} + +impl_serializable_state_u128_array! { + array::typenum::U1, + array::typenum::U2, + array::typenum::U3, + array::typenum::U4, + array::typenum::U6, + array::typenum::U8, + array::typenum::U12, + array::typenum::U16, + array::typenum::U24, + array::typenum::U32, + array::typenum::U48, + array::typenum::U64, + array::typenum::U128, + array::typenum::U256, + array::typenum::U512 +} diff --git a/crypto-common/src/lib.rs b/crypto-common/src/lib.rs new file mode 100644 index 000000000..7721b86f6 --- /dev/null +++ b/crypto-common/src/lib.rs @@ -0,0 +1,389 @@ +//! Common cryptographic traits. + +#![no_std] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" +)] +#![forbid(unsafe_code)] +#![warn(missing_docs, rust_2018_idioms, missing_debug_implementations)] + +/// Hazardous materials. +pub mod hazmat; + +#[cfg(feature = "getrandom")] +pub use getrandom; +#[cfg(feature = "rand_core")] +pub use rand_core; + +pub use hybrid_array as array; +pub use hybrid_array::typenum; + +use core::fmt; +use hybrid_array::{ + typenum::{Diff, Sum, Unsigned}, + Array, ArraySize, +}; + +#[cfg(feature = "rand_core")] +use rand_core::CryptoRngCore; + +/// Block on which [`BlockSizeUser`] implementors operate. +pub type Block = Array::BlockSize>; + +/// Parallel blocks on which [`ParBlocksSizeUser`] implementors operate. +pub type ParBlocks = Array, ::ParBlocksSize>; + +/// Output array of [`OutputSizeUser`] implementors. +pub type Output = Array>; + +/// Alias for the output size of [`OutputSizeUser`] implementors. +pub type OutputSize = ::OutputSize; + +/// Key used by [`KeySizeUser`] implementors. +pub type Key = Array::KeySize>; + +/// Initialization vector (nonce) used by [`IvSizeUser`] implementors. +pub type Iv = Array::IvSize>; + +/// Alias for `AddBlockSize = Sum` +pub type AddBlockSize = Sum::BlockSize>; + +/// Alias for `SubBlockSize = Diff` +pub type SubBlockSize = Diff::BlockSize>; + +/// Types which process data in blocks. +pub trait BlockSizeUser { + /// Size of the block in bytes. + type BlockSize: BlockSizes; + + /// Return block size in bytes. + #[inline(always)] + fn block_size() -> usize { + Self::BlockSize::USIZE + } +} + +impl BlockSizeUser for &T { + type BlockSize = T::BlockSize; +} + +impl BlockSizeUser for &mut T { + type BlockSize = T::BlockSize; +} + +/// Trait implemented for supported block sizes, i.e. for types from `U1` to `U255`. +pub trait BlockSizes: ArraySize + sealed::BlockSizes {} + +impl BlockSizes for T {} + +mod sealed { + use crate::typenum::{Gr, IsGreater, IsLess, Le, NonZero, Unsigned, U0, U256}; + + pub trait BlockSizes {} + + impl BlockSizes for T + where + Self: IsLess + IsGreater, + Le: NonZero, + Gr: NonZero, + { + } +} + +/// Types which can process blocks in parallel. +pub trait ParBlocksSizeUser: BlockSizeUser { + /// Number of blocks which can be processed in parallel. + type ParBlocksSize: ArraySize; +} + +/// Types which return data with the given size. +pub trait OutputSizeUser { + /// Size of the output in bytes. + type OutputSize: ArraySize; + + /// Return output size in bytes. + #[inline(always)] + fn output_size() -> usize { + Self::OutputSize::USIZE + } +} + +/// Types which use key for initialization. +/// +/// Generally it's used indirectly via [`KeyInit`] or [`KeyIvInit`]. +pub trait KeySizeUser { + /// Key size in bytes. + type KeySize: ArraySize; + + /// Return key size in bytes. + #[inline(always)] + fn key_size() -> usize { + Self::KeySize::USIZE + } +} + +/// Types which use initialization vector (nonce) for initialization. +/// +/// Generally it's used indirectly via [`KeyIvInit`] or [`InnerIvInit`]. +pub trait IvSizeUser { + /// Initialization vector size in bytes. + type IvSize: ArraySize; + + /// Return IV size in bytes. + #[inline(always)] + fn iv_size() -> usize { + Self::IvSize::USIZE + } +} + +/// Types which use another type for initialization. +/// +/// Generally it's used indirectly via [`InnerInit`] or [`InnerIvInit`]. +pub trait InnerUser { + /// Inner type. + type Inner; +} + +/// Resettable types. +pub trait Reset { + /// Reset state to its initial value. + fn reset(&mut self); +} + +/// Trait which stores algorithm name constant, used in `Debug` implementations. +pub trait AlgorithmName { + /// Write algorithm name into `f`. + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result; +} + +/// Types which can be initialized from key. +pub trait KeyInit: KeySizeUser + Sized { + /// Create new value from fixed size key. + fn new(key: &Key) -> Self; + + /// Create new value from variable size key. + #[inline] + fn new_from_slice(key: &[u8]) -> Result { + <&Key>::try_from(key) + .map(Self::new) + .map_err(|_| InvalidLength) + } + + /// Generate random key using the operating system's secure RNG. + #[cfg(feature = "getrandom")] + #[inline] + fn generate_key() -> Result, getrandom::Error> { + let mut key = Key::::default(); + getrandom::getrandom(&mut key)?; + Ok(key) + } + + /// Generate random key using the provided [`CryptoRngCore`]. + #[cfg(feature = "rand_core")] + #[inline] + fn generate_key_with_rng(rng: &mut impl CryptoRngCore) -> Result, rand_core::Error> { + let mut key = Key::::default(); + rng.try_fill_bytes(&mut key)?; + Ok(key) + } +} + +/// Types which can be initialized from key and initialization vector (nonce). +pub trait KeyIvInit: KeySizeUser + IvSizeUser + Sized { + /// Create new value from fixed length key and nonce. + fn new(key: &Key, iv: &Iv) -> Self; + + /// Create new value from variable length key and nonce. + #[inline] + fn new_from_slices(key: &[u8], iv: &[u8]) -> Result { + let key = <&Key>::try_from(key).map_err(|_| InvalidLength)?; + let iv = <&Iv>::try_from(iv).map_err(|_| InvalidLength)?; + Ok(Self::new(key, iv)) + } + + /// Generate random key using the operating system's secure RNG. + #[cfg(feature = "getrandom")] + #[inline] + fn generate_key() -> Result, getrandom::Error> { + let mut key = Key::::default(); + getrandom::getrandom(&mut key)?; + Ok(key) + } + + /// Generate random key using the provided [`CryptoRngCore`]. + #[cfg(feature = "rand_core")] + #[inline] + fn generate_key_with_rng(rng: &mut impl CryptoRngCore) -> Result, rand_core::Error> { + let mut key = Key::::default(); + rng.try_fill_bytes(&mut key)?; + Ok(key) + } + + /// Generate random IV using the operating system's secure RNG. + #[cfg(feature = "getrandom")] + #[inline] + fn generate_iv() -> Result, getrandom::Error> { + let mut iv = Iv::::default(); + getrandom::getrandom(&mut iv)?; + Ok(iv) + } + + /// Generate random IV using the provided [`CryptoRngCore`]. + #[cfg(feature = "rand_core")] + #[inline] + fn generate_iv_with_rng(rng: &mut impl CryptoRngCore) -> Result, rand_core::Error> { + let mut iv = Iv::::default(); + rng.try_fill_bytes(&mut iv)?; + Ok(iv) + } + + /// Generate random key and IV using the operating system's secure RNG. + #[cfg(feature = "getrandom")] + #[inline] + fn generate_key_iv() -> Result<(Key, Iv), getrandom::Error> { + let key = Self::generate_key()?; + let iv = Self::generate_iv()?; + Ok((key, iv)) + } + + /// Generate random key and IV using the provided [`CryptoRngCore`]. + #[cfg(feature = "rand_core")] + #[inline] + fn generate_key_iv_with_rng( + rng: &mut impl CryptoRngCore, + ) -> Result<(Key, Iv), rand_core::Error> { + let key = Self::generate_key_with_rng(rng)?; + let iv = Self::generate_iv_with_rng(rng)?; + Ok((key, iv)) + } +} + +/// Types which can be initialized from another type (usually block ciphers). +/// +/// Usually used for initializing types from block ciphers. +pub trait InnerInit: InnerUser + Sized { + /// Initialize value from the `inner`. + fn inner_init(inner: Self::Inner) -> Self; +} + +/// Types which can be initialized from another type and additional initialization +/// vector/nonce. +/// +/// Usually used for initializing types from block ciphers. +pub trait InnerIvInit: InnerUser + IvSizeUser + Sized { + /// Initialize value using `inner` and `iv` array. + fn inner_iv_init(inner: Self::Inner, iv: &Iv) -> Self; + + /// Initialize value using `inner` and `iv` slice. + #[inline] + fn inner_iv_slice_init(inner: Self::Inner, iv: &[u8]) -> Result { + let iv = <&Iv>::try_from(iv).map_err(|_| InvalidLength)?; + Ok(Self::inner_iv_init(inner, iv)) + } + + /// Generate random IV using the operating system's secure RNG. + #[cfg(feature = "getrandom")] + #[inline] + fn generate_iv() -> Result, getrandom::Error> { + let mut iv = Iv::::default(); + getrandom::getrandom(&mut iv)?; + Ok(iv) + } + + /// Generate random IV using the provided [`CryptoRngCore`]. + #[cfg(feature = "rand_core")] + #[inline] + fn generate_iv_with_rng(rng: &mut impl CryptoRngCore) -> Result, rand_core::Error> { + let mut iv = Iv::::default(); + rng.try_fill_bytes(&mut iv)?; + Ok(iv) + } +} + +/// Trait for loading current IV state. +pub trait IvState: IvSizeUser { + /// Returns current IV state. + fn iv_state(&self) -> Iv; +} + +impl KeySizeUser for T +where + T: InnerUser, + T::Inner: KeySizeUser, +{ + type KeySize = ::KeySize; +} + +impl KeyIvInit for T +where + T: InnerIvInit, + T::Inner: KeyInit, +{ + #[inline] + fn new(key: &Key, iv: &Iv) -> Self { + Self::inner_iv_init(T::Inner::new(key), iv) + } + + #[inline] + fn new_from_slices(key: &[u8], iv: &[u8]) -> Result { + T::Inner::new_from_slice(key).and_then(|i| T::inner_iv_slice_init(i, iv)) + } +} + +impl KeyInit for T +where + T: InnerInit, + T::Inner: KeyInit, +{ + #[inline] + fn new(key: &Key) -> Self { + Self::inner_init(T::Inner::new(key)) + } + + #[inline] + fn new_from_slice(key: &[u8]) -> Result { + T::Inner::new_from_slice(key) + .map_err(|_| InvalidLength) + .map(Self::inner_init) + } +} + +// Unfortunately this blanket impl is impossible without mutually +// exclusive traits, see: https://github.com/rust-lang/rfcs/issues/1053 +// or at the very least without: https://github.com/rust-lang/rust/issues/20400 +/* +impl KeyIvInit for T +where + T: InnerInit, + T::Inner: KeyIvInit, +{ + #[inline] + fn new(key: &Key, iv: &Iv) -> Self { + Self::inner_init(T::Inner::new(key, iv)) + } + + #[inline] + fn new_from_slices(key: &[u8], iv: &[u8]) -> Result { + T::Inner::new_from_slice(key) + .map_err(|_| InvalidLength) + .map(Self::inner_init) + } +} +*/ + +/// The error type returned when key and/or IV used in the [`KeyInit`], +/// [`KeyIvInit`], and [`InnerIvInit`] slice-based methods had +/// an invalid length. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct InvalidLength; + +impl fmt::Display for InvalidLength { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("Invalid Length") + } +} + +impl core::error::Error for InvalidLength {} diff --git a/crypto-mac/CHANGELOG.md b/crypto-mac/CHANGELOG.md deleted file mode 100644 index 2a44825bc..000000000 --- a/crypto-mac/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] -### Changed -- `subtle` is updated from `v1` to `v2`. diff --git a/crypto-mac/Cargo.toml b/crypto-mac/Cargo.toml deleted file mode 100644 index 51af928cd..000000000 --- a/crypto-mac/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "crypto-mac" -version = "0.8.0" -authors = ["RustCrypto Developers"] -license = "MIT OR Apache-2.0" -description = "Trait for Message Authentication Code (MAC) algorithms" -documentation = "https://docs.rs/crypto-mac" -repository = "https://github.com/RustCrypto/traits" -keywords = ["crypto", "mac"] -categories = ["cryptography", "no-std"] - -[dependencies] -generic-array = "0.12" -subtle = { version = "2", default-features = false } -blobby = { version = "0.1", optional = true } - -[features] -dev = ["blobby"] -std = [] - -[badges] -travis-ci = { repository = "RustCrypto/traits" } - -[package.metadata.docs.rs] -features = [ "std" ] diff --git a/crypto-mac/src/dev.rs b/crypto-mac/src/dev.rs deleted file mode 100644 index 9eb4b6b75..000000000 --- a/crypto-mac/src/dev.rs +++ /dev/null @@ -1,82 +0,0 @@ -#[macro_export] -macro_rules! new_test { - ($name:ident, $test_name:expr, $mac:ty) => { - #[test] - fn $name() { - use crypto_mac::blobby::Blob3Iterator; - use crypto_mac::Mac; - - fn run_test(key: &[u8], input: &[u8], tag: &[u8]) -> Option<&'static str> { - let mut mac = <$mac as Mac>::new_varkey(key).unwrap(); - mac.input(input); - let result = mac.result_reset(); - if &result.code()[..] != tag { - return Some("whole message"); - } - // test if reset worked correctly - mac.input(input); - if mac.verify(&tag).is_err() { - return Some("after reset"); - } - - let mut mac = <$mac as Mac>::new_varkey(key).unwrap(); - // test reading byte by byte - for i in 0..input.len() { - mac.input(&input[i..i + 1]); - } - if let Err(_) = mac.verify(tag) { - return Some("message byte-by-byte"); - } - None - } - - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - - for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { - let key = row[0]; - let input = row[1]; - let tag = row[2]; - if let Some(desc) = run_test(key, input, tag) { - panic!( - "\n\ - Failed test №{}: {}\n\ - key:\t{:?}\n\ - input:\t{:?}\n\ - tag:\t{:?}\n", - i, desc, key, input, tag, - ); - } - } - } - }; -} - -#[macro_export] -macro_rules! bench { - ($name:ident, $engine:path, $bs:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let key = Default::default(); - let mut m = <$engine>::new(&key); - let data = [0; $bs]; - - b.iter(|| { - m.input(&data); - }); - - b.bytes = $bs; - } - }; - - ($engine:path) => { - extern crate test; - - use crypto_mac::Mac; - use test::Bencher; - - bench!(bench1_10, $engine, 10); - bench!(bench2_100, $engine, 100); - bench!(bench3_1000, $engine, 1000); - bench!(bench3_10000, $engine, 10000); - }; -} diff --git a/crypto-mac/src/errors.rs b/crypto-mac/src/errors.rs deleted file mode 100644 index 8472a9637..000000000 --- a/crypto-mac/src/errors.rs +++ /dev/null @@ -1,37 +0,0 @@ -use core::fmt; -#[cfg(feature = "std")] -use std::error; - -/// Error type for signaling failed MAC verification -#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] -pub struct MacError; - -/// Error type for signaling invalid key length for MAC initialization -#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] -pub struct InvalidKeyLength; - -impl fmt::Display for MacError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("failed MAC verification") - } -} - -impl fmt::Display for InvalidKeyLength { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("invalid key length") - } -} - -#[cfg(feature = "std")] -impl error::Error for MacError { - fn description(&self) -> &str { - "failed MAC verification" - } -} - -#[cfg(feature = "std")] -impl error::Error for InvalidKeyLength { - fn description(&self) -> &str { - "invalid key length" - } -} diff --git a/crypto-mac/src/lib.rs b/crypto-mac/src/lib.rs deleted file mode 100644 index 70779cf7e..000000000 --- a/crypto-mac/src/lib.rs +++ /dev/null @@ -1,115 +0,0 @@ -//! This crate provides trait for Message Authentication Code (MAC) algorithms. -#![no_std] -#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")] -pub extern crate generic_array; -extern crate subtle; - -#[cfg(feature = "dev")] -pub extern crate blobby; -#[cfg(feature = "std")] -extern crate std; - -use generic_array::typenum::Unsigned; -use generic_array::{ArrayLength, GenericArray}; -use subtle::{Choice, ConstantTimeEq}; - -#[cfg(feature = "dev")] -pub mod dev; -mod errors; - -pub use errors::{InvalidKeyLength, MacError}; - -/// The `Mac` trait defines methods for a Message Authentication algorithm. -pub trait Mac: Clone { - type OutputSize: ArrayLength; - type KeySize: ArrayLength; - - /// Create new MAC instance from key with fixed size. - fn new(key: &GenericArray) -> Self; - - /// Create new MAC instance from key with variable size. - /// - /// Default implementation will accept only keys with length equal to - /// `KeySize`, but some MACs can accept range of key lengths. - fn new_varkey(key: &[u8]) -> Result { - if key.len() != Self::KeySize::to_usize() { - Err(InvalidKeyLength) - } else { - Ok(Self::new(GenericArray::from_slice(key))) - } - } - - /// Process input data. - fn input(&mut self, data: &[u8]); - - /// Reset `Mac` instance. - fn reset(&mut self); - - /// Obtain the result of a `Mac` computation as a `MacResult` and consume - /// `Mac` instance. - fn result(self) -> MacResult; - - /// Obtain the result of a `Mac` computation as a `MacResult` and reset - /// `Mac` instance. - fn result_reset(&mut self) -> MacResult { - let res = self.clone().result(); - self.reset(); - res - } - - /// Check if code is correct for the processed input. - fn verify(self, code: &[u8]) -> Result<(), MacError> { - let choice = self.result().code.ct_eq(code); - if choice.unwrap_u8() == 1 { - Ok(()) - } else { - Err(MacError) - } - } -} - -/// `MacResult` is a thin wrapper around bytes array which provides a safe `Eq` -/// implementation that runs in a fixed time. -#[derive(Clone)] -pub struct MacResult> { - code: GenericArray, -} - -impl MacResult -where - N: ArrayLength, -{ - /// Create a new MacResult. - pub fn new(code: GenericArray) -> MacResult { - MacResult { code } - } - - /// Get the code value as a bytes array. - /// - /// Be very careful using this method, since incorrect use of the code value - /// may permit timing attacks which defeat the security provided by the - /// `Mac` trait. - pub fn code(self) -> GenericArray { - self.code - } -} - -impl ConstantTimeEq for MacResult -where - N: ArrayLength, -{ - fn ct_eq(&self, other: &Self) -> Choice { - self.code.ct_eq(&other.code) - } -} - -impl PartialEq for MacResult -where - N: ArrayLength, -{ - fn eq(&self, x: &MacResult) -> bool { - self.ct_eq(x).unwrap_u8() == 1 - } -} - -impl Eq for MacResult where N: ArrayLength {} diff --git a/crypto/CHANGELOG.md b/crypto/CHANGELOG.md new file mode 100644 index 000000000..55339b261 --- /dev/null +++ b/crypto/CHANGELOG.md @@ -0,0 +1,64 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.5.1 (2023-05-27) +Unchanged from v0.5.0, but published to trigger a new docs.rs build. + +## 0.5.0 (2023-04-17) +### Changed +- Bump `signature` to v2 ([#1211]) +- Bump `password-hash` to v0.5 ([#1271]) +- Bump `elliptic-curve` to v0.13; MSRV 1.65 ([#1302]) + +[#1211]: https://github.com/RustCrypto/traits/pull/1211 +[#1271]: https://github.com/RustCrypto/traits/pull/1271 +[#1302]: https://github.com/RustCrypto/traits/pull/1302 + +## 0.4.0 (2022-08-09) +### Added +- Re-export `crypto-common` as `common` ([#1076]) + +### Changed +- Bump `aead` to v0.5 ([#1058]) +- Bump `cipher` to 0.4 ([#951]) +- Bump `digest` dependency to v0.10 ([#850]) +- Bump `elliptic-curve` to v0.12 ([#1009]) +- Bump `password-hash` to v0.4 ([#961]) +- Bump `signature` to v1.5 ([#867]) +- Bump `universal-hash` to v0.5 ([#1061]) + +### Removed +- `mac` feature: merged into `digest` ([#1075]) + +[#850]: https://github.com/RustCrypto/traits/pull/850 +[#867]: https://github.com/RustCrypto/traits/pull/867 +[#951]: https://github.com/RustCrypto/traits/pull/951 +[#961]: https://github.com/RustCrypto/traits/pull/961 +[#1009]: https://github.com/RustCrypto/traits/pull/1009 +[#1058]: https://github.com/RustCrypto/traits/pull/1058 +[#1061]: https://github.com/RustCrypto/traits/pull/1061 +[#1075]: https://github.com/RustCrypto/traits/pull/1075 +[#1076]: https://github.com/RustCrypto/traits/pull/1076 + +## 0.3.0 (2021-06-08) +### Changed +- Bump `elliptic-curve` crate dependency to v0.10 ([#663]) +- Bump `signature` crate dependency to v1.3.0 ([#664]) + +[#663]: https://github.com/RustCrypto/traits/pull/663 +[#664]: https://github.com/RustCrypto/traits/pull/664 + +## 0.2.0 (2021-04-29) +- Initial (re-)release of the `cryptography` crate as `crypto` + +## 0.1.2 (2020-06-19) [YANKED] + +## 0.1.1 (2020-06-19) [YANKED] + +## 0.1.0 (2020-05-21) [YANKED] + +## 0.0.2 (2014-11-21) [YANKED] diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml new file mode 100644 index 000000000..24eb941cc --- /dev/null +++ b/crypto/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "crypto" +version = "0.5.1" +description = "Facade crate for all of the RustCrypto traits (e.g. `aead`, `cipher`, `digest`)" +authors = ["The RustCrypto Project Developers"] +license = "Apache-2.0 OR MIT" +documentation = "https://docs.rs/crypto" +repository = "https://github.com/RustCrypto/traits" +keywords = ["crypto", "encryption", "rustcrypto"] +categories = ["cryptography", "no-std"] +readme = "README.md" +edition = "2021" +rust-version = "1.65" + +[dependencies] +crypto-common = { version = "0.1", default-features = false } + +# optional dependencies +aead = { version = "0.5", optional = true } +cipher = { version = "0.4", optional = true } +digest = { version = "0.10", optional = true, features = ["mac"] } +elliptic-curve = { version = "0.13", optional = true } +password-hash = { version = "0.5", optional = true } +signature = { version = "2", optional = true, default-features = false } +universal-hash = { version = "0.5", optional = true } + +[features] +std = [ + "aead/std", + "cipher/std", + "digest/std", + "elliptic-curve/std", + "password-hash/std", + "signature/std", + "universal-hash/std" +] +getrandom = ["crypto-common/getrandom"] +rand_core = ["crypto-common/rand_core"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/crypto/LICENSE-APACHE b/crypto/LICENSE-APACHE new file mode 100644 index 000000000..7650239c7 --- /dev/null +++ b/crypto/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/crypto/LICENSE-MIT b/crypto/LICENSE-MIT new file mode 100644 index 000000000..2726e14a4 --- /dev/null +++ b/crypto/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2020 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/crypto/README.md b/crypto/README.md new file mode 100644 index 000000000..7c5ac4c49 --- /dev/null +++ b/crypto/README.md @@ -0,0 +1,95 @@ +# RustCrypto: `crypto` crate + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +Facade crate for [RustCrypto Traits][1], providing a single place to +access compatible versions of all traits from the Rust Crypto project. + +[Documentation][docs-link] + +## About + +Facade crate for [RustCrypto Traits][1], providing a single place to +access compatible versions of all traits from the Rust Crypto project. + +# About + +The [RustCrypto Project][2] publishes and maintains independently versioned +crates containing traits for many different kinds of cryptographic +algorithms. + +However, these algorithms are often interdependent (e.g. many depend on digest +algorithms), which requires figuring out which versions of the trait crates +are compatible with each other. + +This crate will automatically pull in compatible versions of these crates, +with each one gated under a cargo feature, providing a single place to both +import and upgrade these crates while ensuring they remain compatible. + +# Traits + +The following traits are available as re-exports of RustCrypto crates through +this crate's facade. To access a particular re-export you (or a crate you +depend on) must enable the associated Cargo feature named below. + +| Re-export | Cargo feature | Description | +|-----------|---------------|-------------| +| [`aead`](https://docs.rs/aead) | `aead` | Authenticated Encryption with Associated Data (i.e. high-level symmetric encryption) | +| [`cipher`](https://docs.rs/cipher) | `cipher` | Block and stream ciphers (i.e. low-level symmetric encryption) | +| [`digest`](https://docs.rs/digest) | `digest` | Cryptographic hash functions | +| [`elliptic_curve`](https://docs.rs/elliptic-curve) | `elliptic-curve` | Elliptic curve cryptography | +| [`password_hash`](https://docs.rs/password-hash) | `password-hash` | Password hashing functions | +| [`signature`](https://docs.rs/signature) | `signature` | Digital signatures (i.e. public key-based message authentication) | +| [`universal_hash`](https://docs.rs/universal-hash) | `universal‑hash` | Universal Hash Functions (used to build MACs) | + +[1]: https://github.com/RustCrypto/traits +[2]: https://github.com/RustCrypto + +## Minimum Supported Rust Version + +Rust **1.65** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/crypto.svg +[crate-link]: https://crates.io/crates/crypto +[docs-image]: https://docs.rs/crypto/badge.svg +[docs-link]: https://docs.rs/crypto/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.65+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260050-Traits +[build-image]: https://github.com/RustCrypto/traits/workflows/crypto/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/traits/actions?query=workflow:crypto + +[//]: # (footnotes) + +[1]: https://github.com/RustCrypto/traits diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs new file mode 100644 index 000000000..3839e2fc8 --- /dev/null +++ b/crypto/src/lib.rs @@ -0,0 +1,32 @@ +#![no_std] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" +)] +#![forbid(unsafe_code)] +#![warn(rust_2018_idioms, missing_debug_implementations)] + +pub use crypto_common as common; + +#[cfg(feature = "aead")] +pub use aead; + +#[cfg(feature = "cipher")] +pub use cipher; + +#[cfg(feature = "digest")] +pub use digest; + +#[cfg(feature = "elliptic-curve")] +pub use elliptic_curve; + +#[cfg(feature = "password-hash")] +pub use password_hash; + +#[cfg(feature = "signature")] +pub use signature; + +#[cfg(feature = "universal-hash")] +pub use universal_hash; diff --git a/digest/CHANGELOG.md b/digest/CHANGELOG.md new file mode 100644 index 000000000..afff142f6 --- /dev/null +++ b/digest/CHANGELOG.md @@ -0,0 +1,151 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## UNRELEASED +### Added +- `CustomizedInit` trait ([#1334]). + +### Changed +- `crypto-common` dependency bumped to v0.2 ([#1173]) +- Edition changed to 2021 and MSRV bumped to 1.57 ([#1173]) + +### Removed +- `Mac::new`, `Mac::new_from_slice`, and `Mac::generate_key` methods ([#1173]) + +[#1173]: https://github.com/RustCrypto/traits/pull/1173 +[#1334]: https://github.com/RustCrypto/traits/pull/1334 + +## 0.10.7 (2023-05-19) +### Changed +- Loosen `subtle` version requirement ([#1260]) + +[#1260]: https://github.com/RustCrypto/traits/pull/1260 + +## 0.10.6 (2022-11-17) +### Added +- `Mac::verify_reset` and `Mac::verify_slice_reset` methods ([#1154]) + +[#1154]: https://github.com/RustCrypto/traits/pull/1154 + +## 0.10.5 (2022-09-16) +### Fixed +- MSRV build ([#1117]) + +[#1117]: https://github.com/RustCrypto/traits/pull/1117 + +## 0.10.4 (2022-09-16) +### Added +- Feature-gated implementation of the `const_oid::AssociatedOid` trait +for the core wrappers. ([#1098]) + +[#1098]: https://github.com/RustCrypto/traits/pull/1098 + +## 0.10.3 (2022-02-16) +### Fixed +- Minimal versions build ([#940]) + +[#940]: https://github.com/RustCrypto/traits/pull/940 + +## 0.10.2 (2022-02-10) +### Changed +- Relax bounds on the `Mac` trait ([#849]) + +[#849]: https://github.com/RustCrypto/traits/pull/849 + +## 0.10.1 (2021-12-14) [YANKED] +### Added +- `Update::chain` and `Digest::new_with_prefix` methods. ([#846]) +- `Mac::generate_key` method. ([#847]) + +### Fixed +- Doc cfg attribute for `CtOutput` and `MacError`. ([#842]) +- Expose `KeyInit::generate_key` method in docs. ([#847]) + +[#842]: https://github.com/RustCrypto/traits/pull/842 +[#846]: https://github.com/RustCrypto/traits/pull/846 +[#847]: https://github.com/RustCrypto/traits/pull/847 + +## 0.10.0 (2021-12-07) [YANKED] +### Changed +- Dirty traits are removed and instead block-level traits are introduced. +Variable output traits reworked and now support both run and compile time selection of output size. ([#380], [#819]) +- The `crypto-mac` traits are reworked and merged in. ([#819]) + +[#819]: https://github.com/RustCrypto/traits/pull/819 +[#380]: https://github.com/RustCrypto/traits/pull/380 + +## 0.9.0 (2020-06-09) +### Added +- `ExtendableOutputDirty` and `VariableOutputDirty` traits ([#183]) +- `FixedOutputDirty` trait + `finalize_into*` ([#180]) +- `XofReader::read_boxed` method ([#178], [#181], [#182]) +- `alloc` feature ([#163]) +- Re-export `typenum::consts` as `consts` ([#123]) +- `Output` type alias ([#115]) + +### Changed +- Rename `*result*` methods to `finalize` ala IUF ([#161]) +- Use `impl AsRef<[u8]>` instead of generic params on methods ([#112]) +- Rename `Input::input` to `Update::update` ala IUF ([#111]) +- Upgrade to Rust 2018 edition ([#109]) +- Bump `generic-array` to v0.14 ([#95]) + +[#183]: https://github.com/RustCrypto/traits/pull/183 +[#181]: https://github.com/RustCrypto/traits/pull/181 +[#182]: https://github.com/RustCrypto/traits/pull/182 +[#180]: https://github.com/RustCrypto/traits/pull/180 +[#178]: https://github.com/RustCrypto/traits/pull/178 +[#163]: https://github.com/RustCrypto/traits/pull/163 +[#161]: https://github.com/RustCrypto/traits/pull/161 +[#123]: https://github.com/RustCrypto/traits/pull/123 +[#115]: https://github.com/RustCrypto/traits/pull/115 +[#111]: https://github.com/RustCrypto/traits/pull/111 +[#112]: https://github.com/RustCrypto/traits/pull/112 +[#109]: https://github.com/RustCrypto/traits/pull/109 +[#95]: https://github.com/RustCrypto/traits/pull/95 + +## 0.8.1 (2019-06-30) + +## 0.8.0 (2018-10-01) + +## 0.7.6 (2018-09-21) + +## 0.7.5 (2018-07-13) + +## 0.7.4 (2018-06-21) + +## 0.7.3 (2018-06-20) + +## 0.7.2 (2017-11-17) + +## 0.7.1 (2017-11-15) + +## 0.7.0 (2017-11-14) + +## 0.6.2 (2017-07-24) + +## 0.6.1 (2017-06-18) + +## 0.6.0 (2017-06-12) + +## 0.5.2 (2017-05-02) + +## 0.5.1 (2017-05-02) + +## 0.5.0 (2017-04-06) + +## 0.4.0 (2016-12-24) + +## 0.3.1 (2016-12-16) + +## 0.3.0 (2016-11-17) + +## 0.2.1 (2016-10-14) + +## 0.2.0 (2016-10-14) + +## 0.1.0 (2016-10-06) diff --git a/digest/Cargo.toml b/digest/Cargo.toml index 5825ca0ab..c6d2639f6 100644 --- a/digest/Cargo.toml +++ b/digest/Cargo.toml @@ -1,24 +1,38 @@ [package] name = "digest" -version = "0.8.1" +description = "Traits for cryptographic hash functions and message authentication codes" +version = "0.11.0-pre.9" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" -description = "Traits for cryptographic hash functions" +readme = "README.md" +edition = "2021" +rust-version = "1.81" documentation = "https://docs.rs/digest" repository = "https://github.com/RustCrypto/traits" keywords = ["digest", "crypto", "hash"] categories = ["cryptography", "no-std"] [dependencies] -generic-array = "0.12" -blobby = { version = "0.1", optional = true } +crypto-common = "0.2.0-rc.0" + +# optional dependencies +block-buffer = { version = "0.11.0-rc.2", optional = true } +subtle = { version = "2.4", default-features = false, optional = true } +blobby = { version = "0.3", optional = true } +const-oid = { version = "0.10.0-rc.3", optional = true } +zeroize = { version = "1.7", optional = true, default-features = false } [features] -std = [] +default = ["core-api"] +core-api = ["block-buffer"] # Enable Core API traits +mac = ["subtle"] # Enable MAC traits +rand_core = ["crypto-common/rand_core"] # Enable random key generation methods +oid = ["const-oid"] +zeroize = ["dep:zeroize", "block-buffer?/zeroize"] +alloc = [] +std = ["alloc"] dev = ["blobby"] -[badges] -travis-ci = { repository = "RustCrypto/traits" } - [package.metadata.docs.rs] -features = ["std"] \ No newline at end of file +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/digest/README.md b/digest/README.md new file mode 100644 index 000000000..dc527f713 --- /dev/null +++ b/digest/README.md @@ -0,0 +1,164 @@ +# RustCrypto: Digest Algorithm Traits + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +Traits which describe functionality of [cryptographic hash functions][0], a.k.a. +digest algorithms. + +See [RustCrypto/hashes][1] for implementations which use this trait. + +[Documentation][docs-link] + +## Minimum Supported Rust Version + +Rust **1.81** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## Usage + +Let us demonstrate how to use crates in this repository using Sha256 as an +example. + +First add the `sha2` crate to your `Cargo.toml`: + +```toml +[dependencies] +sha2 = "0.11" +``` + +`sha2` and other crates re-export `digest` crate and `Digest` trait for +convenience, so you don't have to add `digest` crate as an explicit dependency. + +Now you can write the following code: + +```rust +use sha2::{Sha256, Digest}; + +let mut hasher = Sha256::new(); +let data = b"Hello world!"; +hasher.update(data); +// `input` can be called repeatedly and is generic over `AsRef<[u8]>` +hasher.update("String data"); +// Note that calling `finalize()` consumes hasher +let hash = hasher.finalize(); +println!("Result: {:x}", hash); +``` + +In this example `hash` has type [`Array`][2], which is a generic +alternative to `[u8; 32]`. + +Alternatively you can use chained approach, which is equivalent to the previous +example: + +```rust +let hash = Sha256::new() + .chain_update(b"Hello world!") + .chain_update("String data") + .finalize(); + +println!("Result: {:x}", hash); +``` + +If the whole message is available you also can use convenience `digest` method: + +```rust +let hash = Sha256::digest(b"my message"); +println!("Result: {:x}", hash); +``` + +### Hashing `Read`-able objects + +If you want to hash data from [`Read`][3] trait (e.g. from file) you can rely on +implementation of [`Write`][4] trait (requires enabled-by-default `std` feature): + +```rust +use sha2::{Sha256, Digest}; +use std::{fs, io}; + +let mut file = fs::File::open(&path)?; +let mut hasher = Sha256::new(); +let n = io::copy(&mut file, &mut hasher)?; +let hash = hasher.finalize(); + +println!("Path: {}", path); +println!("Bytes processed: {}", n); +println!("Hash value: {:x}", hash); +``` + +### Generic code + +You can write generic code over `Digest` (or other traits from `digest` crate) +trait which will work over different hash functions: + +```rust +use digest::Digest; + +// Toy example, do not use it in practice! +// Instead use crates from: https://github.com/RustCrypto/password-hashing +fn hash_password(password: &str, salt: &str, output: &mut [u8]) { + let mut hasher = D::new(); + hasher.update(password.as_bytes()); + hasher.update(b"$"); + hasher.update(salt.as_bytes()); + output.copy_from_slice(hasher.finalize().as_slice()) +} + +let mut buf1 = [0u8; 32]; +let mut buf2 = [0u8; 64]; + +hash_password::("my_password", "abcd", &mut buf1); +hash_password::("my_password", "abcd", &mut buf2); +``` + +If you want to use hash functions with trait objects, use `digest::DynDigest` +trait. + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/digest.svg +[crate-link]: https://crates.io/crates/digest +[docs-image]: https://docs.rs/digest/badge.svg +[docs-link]: https://docs.rs/digest/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260041-hashes +[build-image]: https://github.com/RustCrypto/traits/workflows/digest/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/traits/actions?query=workflow%3Adigest + +[//]: # (general links) + +[0]: https://en.wikipedia.org/wiki/Cryptographic_hash_function +[1]: https://github.com/RustCrypto/hashes +[2]: https://docs.rs/hybrid-array +[3]: https://doc.rust-lang.org/std/io/trait.Read.html +[4]: https://doc.rust-lang.org/std/io/trait.Write.html +[5]: https://en.wikipedia.org/wiki/Hash-based_message_authentication_code +[6]: https://github.com/RustCrypto/MACs diff --git a/digest/src/core_api.rs b/digest/src/core_api.rs new file mode 100644 index 000000000..6e8b8def7 --- /dev/null +++ b/digest/src/core_api.rs @@ -0,0 +1,104 @@ +//! Low-level traits operating on blocks and wrappers around them. +//! +//! Usage of traits in this module in user code is discouraged. Instead use +//! core algorithm wrapped by the wrapper types, which implement the +//! higher-level traits. +use crate::InvalidOutputSize; + +pub use crypto_common::{AlgorithmName, Block, BlockSizeUser, OutputSizeUser, Reset}; + +use block_buffer::{BlockBuffer, BufferKind}; +use crypto_common::Output; + +mod ct_variable; +mod rt_variable; +mod wrapper; +mod xof_reader; + +pub use ct_variable::CtVariableCoreWrapper; +pub use rt_variable::RtVariableCoreWrapper; +pub use wrapper::{CoreProxy, CoreWrapper}; +pub use xof_reader::XofReaderCoreWrapper; + +/// Buffer type used by type which implements [`BufferKindUser`]. +pub type Buffer = + BlockBuffer<::BlockSize, ::BufferKind>; + +/// Types which consume data in blocks. +pub trait UpdateCore: BlockSizeUser { + /// Update state using the provided data blocks. + fn update_blocks(&mut self, blocks: &[Block]); +} + +/// Types which use [`BlockBuffer`] functionality. +pub trait BufferKindUser: BlockSizeUser { + /// Block buffer kind over which type operates. + type BufferKind: BufferKind; +} + +/// Core trait for hash functions with fixed output size. +pub trait FixedOutputCore: UpdateCore + BufferKindUser + OutputSizeUser { + /// Finalize state using remaining data stored in the provided block buffer, + /// write result into provided array and leave `self` in a dirty state. + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output); +} + +/// Core trait for hash functions with extendable (XOF) output size. +pub trait ExtendableOutputCore: UpdateCore + BufferKindUser { + /// XOF reader core state. + type ReaderCore: XofReaderCore; + + /// Retrieve XOF reader using remaining data stored in the block buffer + /// and leave hasher in a dirty state. + fn finalize_xof_core(&mut self, buffer: &mut Buffer) -> Self::ReaderCore; +} + +/// Core reader trait for extendable-output function (XOF) result. +pub trait XofReaderCore: BlockSizeUser { + /// Read next XOF block. + fn read_block(&mut self) -> Block; +} + +/// Core trait for hash functions with variable output size. +/// +/// Maximum output size is equal to [`OutputSizeUser::OutputSize`]. +/// Users are expected to truncate result returned by the +/// [`finalize_variable_core`] to `output_size` passed to the [`new`] method +/// during construction. Truncation side is defined by the [`TRUNC_SIDE`] +/// associated constant. +/// +/// [`finalize_variable_core`]: VariableOutputCore::finalize_variable_core +/// [`new`]: VariableOutputCore::new +/// [`TRUNC_SIDE`]: VariableOutputCore::TRUNC_SIDE +pub trait VariableOutputCore: UpdateCore + OutputSizeUser + BufferKindUser + Sized { + /// Side which should be used in a truncated result. + const TRUNC_SIDE: TruncSide; + + /// Initialize hasher state for given output size. + /// + /// Returns [`InvalidOutputSize`] if `output_size` is not valid for + /// the algorithm, e.g. if it's bigger than the [`OutputSize`] + /// associated type. + /// + /// [`OutputSize`]: OutputSizeUser::OutputSize + fn new(output_size: usize) -> Result; + + /// Finalize hasher and write full hashing result into the `out` buffer. + /// + /// The result must be truncated to `output_size` used during hasher + /// construction. Truncation side is defined by the [`TRUNC_SIDE`] + /// associated constant. + /// + /// [`TRUNC_SIDE`]: VariableOutputCore::TRUNC_SIDE + fn finalize_variable_core(&mut self, buffer: &mut Buffer, out: &mut Output); +} + +/// Type which used for defining truncation side in the [`VariableOutputCore`] +/// trait. +#[derive(Copy, Clone, Debug)] +pub enum TruncSide { + /// Truncate left side, i.e. `&out[..n]`. + Left, + /// Truncate right side, i.e. `&out[m..]`. + Right, +} diff --git a/digest/src/core_api/ct_variable.rs b/digest/src/core_api/ct_variable.rs new file mode 100644 index 000000000..b676a352c --- /dev/null +++ b/digest/src/core_api/ct_variable.rs @@ -0,0 +1,244 @@ +use super::{ + AlgorithmName, Buffer, BufferKindUser, FixedOutputCore, Reset, TruncSide, UpdateCore, + VariableOutputCore, +}; +use crate::HashMarker; +#[cfg(feature = "mac")] +use crate::MacMarker; +#[cfg(feature = "oid")] +use const_oid::{AssociatedOid, ObjectIdentifier}; +use core::{ + fmt, + marker::PhantomData, + ops::{Add, Sub}, +}; +use crypto_common::{ + array::{Array, ArraySize}, + hazmat::{DeserializeStateError, SerializableState, SerializedState, SubSerializedStateSize}, + typenum::{IsLess, IsLessOrEqual, Le, LeEq, NonZero, Sum, U1, U256}, + Block, BlockSizeUser, OutputSizeUser, +}; + +/// Dummy type used with [`CtVariableCoreWrapper`] in cases when +/// resulting hash does not have a known OID. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct NoOid; + +/// Wrapper around [`VariableOutputCore`] which selects output size +/// at compile time. +#[derive(Clone)] +pub struct CtVariableCoreWrapper +where + T: VariableOutputCore, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ + inner: T, + _out: PhantomData<(OutSize, O)>, +} + +impl HashMarker for CtVariableCoreWrapper +where + T: VariableOutputCore + HashMarker, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ +} + +#[cfg(feature = "mac")] +impl MacMarker for CtVariableCoreWrapper +where + T: VariableOutputCore + MacMarker, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ +} + +impl BlockSizeUser for CtVariableCoreWrapper +where + T: VariableOutputCore, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ + type BlockSize = T::BlockSize; +} + +impl UpdateCore for CtVariableCoreWrapper +where + T: VariableOutputCore, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ + #[inline] + fn update_blocks(&mut self, blocks: &[Block]) { + self.inner.update_blocks(blocks); + } +} + +impl OutputSizeUser for CtVariableCoreWrapper +where + T: VariableOutputCore, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ + type OutputSize = OutSize; +} + +impl BufferKindUser for CtVariableCoreWrapper +where + T: VariableOutputCore, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ + type BufferKind = T::BufferKind; +} + +impl FixedOutputCore for CtVariableCoreWrapper +where + T: VariableOutputCore, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ + #[inline] + fn finalize_fixed_core( + &mut self, + buffer: &mut Buffer, + out: &mut Array, + ) { + let mut full_res = Default::default(); + self.inner.finalize_variable_core(buffer, &mut full_res); + let n = out.len(); + let m = full_res.len() - n; + match T::TRUNC_SIDE { + TruncSide::Left => out.copy_from_slice(&full_res[..n]), + TruncSide::Right => out.copy_from_slice(&full_res[m..]), + } + } +} + +impl Default for CtVariableCoreWrapper +where + T: VariableOutputCore, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ + #[inline] + fn default() -> Self { + Self { + inner: T::new(OutSize::USIZE).unwrap(), + _out: PhantomData, + } + } +} + +impl Reset for CtVariableCoreWrapper +where + T: VariableOutputCore, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ + #[inline] + fn reset(&mut self) { + *self = Default::default(); + } +} + +impl AlgorithmName for CtVariableCoreWrapper +where + T: VariableOutputCore + AlgorithmName, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + T::write_alg_name(f)?; + f.write_str("_")?; + write!(f, "{}", OutSize::USIZE) + } +} + +#[cfg(feature = "oid")] +impl AssociatedOid for CtVariableCoreWrapper +where + T: VariableOutputCore, + O: AssociatedOid, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ + const OID: ObjectIdentifier = O::OID; +} + +#[cfg(feature = "zeroize")] +impl zeroize::ZeroizeOnDrop for CtVariableCoreWrapper +where + T: VariableOutputCore + zeroize::ZeroizeOnDrop, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ +} + +impl fmt::Debug for CtVariableCoreWrapper +where + T: VariableOutputCore + AlgorithmName, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Self::write_alg_name(f) + } +} + +/// Implement dummy type with hidden docs which is used to "carry" hasher +/// OID for [`CtVariableCoreWrapper`]. +#[macro_export] +macro_rules! impl_oid_carrier { + ($name:ident, $oid:literal) => { + #[doc(hidden)] + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] + pub struct $name; + + #[cfg(feature = "oid")] + impl AssociatedOid for $name { + const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap($oid); + } + }; +} + +type CtVariableCoreWrapperSerializedStateSize = + Sum<::SerializedStateSize, U1>; + +impl SerializableState for CtVariableCoreWrapper +where + T: VariableOutputCore + SerializableState, + OutSize: ArraySize + IsLessOrEqual, + LeEq: NonZero, + T::BlockSize: IsLess, + Le: NonZero, + T::SerializedStateSize: Add, + CtVariableCoreWrapperSerializedStateSize: Sub + ArraySize, + SubSerializedStateSize, T>: ArraySize, +{ + type SerializedStateSize = CtVariableCoreWrapperSerializedStateSize; + + fn serialize(&self) -> SerializedState { + let serialized_inner = self.inner.serialize(); + let serialized_outsize = Array([OutSize::U8]); + + serialized_inner.concat(serialized_outsize) + } + + fn deserialize( + serialized_state: &SerializedState, + ) -> Result { + let (serialized_inner, serialized_outsize) = + serialized_state.split_ref::(); + + if serialized_outsize[0] != OutSize::U8 { + return Err(DeserializeStateError); + } + + Ok(Self { + inner: T::deserialize(serialized_inner)?, + _out: PhantomData, + }) + } +} diff --git a/digest/src/core_api/rt_variable.rs b/digest/src/core_api/rt_variable.rs new file mode 100644 index 000000000..b1a42942f --- /dev/null +++ b/digest/src/core_api/rt_variable.rs @@ -0,0 +1,200 @@ +use super::{AlgorithmName, BlockSizeUser, TruncSide, VariableOutputCore}; +#[cfg(feature = "mac")] +use crate::MacMarker; +use crate::{HashMarker, InvalidBufferSize}; +use crate::{InvalidOutputSize, Reset, Update, VariableOutput, VariableOutputReset}; +use block_buffer::BlockBuffer; +use core::{ + convert::TryInto, + fmt, + ops::{Add, Sub}, +}; +use crypto_common::{ + array::{Array, ArraySize}, + hazmat::{DeserializeStateError, SerializableState, SerializedState, SubSerializedStateSize}, + typenum::{Diff, IsLess, Le, NonZero, Sum, Unsigned, U1, U256}, + AddBlockSize, SubBlockSize, +}; +#[cfg(feature = "zeroize")] +use zeroize::ZeroizeOnDrop; + +/// Wrapper around [`VariableOutputCore`] which selects output size +/// at run time. +#[derive(Clone)] +pub struct RtVariableCoreWrapper { + core: T, + buffer: BlockBuffer, + output_size: u8, +} + +impl RtVariableCoreWrapper { + #[inline] + fn finalize_dirty(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> { + let Self { + core, + buffer, + output_size, + } = self; + let size_u8 = u8::try_from(out.len()).map_err(|_| InvalidBufferSize)?; + if out.len() > Self::MAX_OUTPUT_SIZE || size_u8 != *output_size { + return Err(InvalidBufferSize); + } + let mut full_res = Default::default(); + core.finalize_variable_core(buffer, &mut full_res); + let n = out.len(); + let m = full_res.len() - n; + match T::TRUNC_SIDE { + TruncSide::Left => out.copy_from_slice(&full_res[..n]), + TruncSide::Right => out.copy_from_slice(&full_res[m..]), + } + Ok(()) + } +} + +impl HashMarker for RtVariableCoreWrapper {} + +#[cfg(feature = "mac")] +impl MacMarker for RtVariableCoreWrapper {} + +impl BlockSizeUser for RtVariableCoreWrapper { + type BlockSize = T::BlockSize; +} + +impl Reset for RtVariableCoreWrapper { + #[inline] + fn reset(&mut self) { + self.buffer.reset(); + self.core.reset(); + } +} + +impl Update for RtVariableCoreWrapper { + #[inline] + fn update(&mut self, input: &[u8]) { + let Self { core, buffer, .. } = self; + buffer.digest_blocks(input, |blocks| core.update_blocks(blocks)); + } +} + +impl VariableOutput for RtVariableCoreWrapper { + const MAX_OUTPUT_SIZE: usize = T::OutputSize::USIZE; + + #[inline] + fn new(output_size: usize) -> Result { + let output_size = u8::try_from(output_size).map_err(|_| InvalidOutputSize)?; + let buffer = Default::default(); + T::new(output_size.into()).map(|core| Self { + core, + buffer, + output_size, + }) + } + + #[inline] + fn output_size(&self) -> usize { + self.output_size.into() + } + + #[inline] + fn finalize_variable(mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> { + self.finalize_dirty(out) + } +} + +impl VariableOutputReset for RtVariableCoreWrapper { + #[inline] + fn finalize_variable_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize> { + self.finalize_dirty(out)?; + self.core.reset(); + self.buffer.reset(); + Ok(()) + } +} + +impl Drop for RtVariableCoreWrapper { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + use zeroize::Zeroize; + self.buffer.zeroize(); + self.output_size.zeroize(); + } + } +} + +#[cfg(feature = "zeroize")] +impl ZeroizeOnDrop for RtVariableCoreWrapper {} + +impl fmt::Debug for RtVariableCoreWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + T::write_alg_name(f)?; + f.write_str(" { .. }") + } +} + +type RtVariableCoreWrapperSerializedStateSize = + Sum::SerializedStateSize, U1>, T>, U1>; + +impl SerializableState for RtVariableCoreWrapper +where + T: VariableOutputCore + SerializableState, + T::BlockSize: IsLess, + Le: NonZero, + T::SerializedStateSize: Add, + Sum: Add + ArraySize, + AddBlockSize, T>: Add + ArraySize, + RtVariableCoreWrapperSerializedStateSize: Sub + ArraySize, + SubSerializedStateSize, T>: Sub + ArraySize, + Diff, T>, U1>: + Sub + ArraySize, + SubBlockSize< + Diff, T>, U1>, + T, + >: ArraySize, +{ + type SerializedStateSize = RtVariableCoreWrapperSerializedStateSize; + + fn serialize(&self) -> SerializedState { + let serialized_core = self.core.serialize(); + let serialized_pos = Array([self.buffer.get_pos().try_into().unwrap()]); + let serialized_data = self.buffer.clone().pad_with_zeros(); + let serialized_output_size = Array([self.output_size]); + + serialized_core + .concat(serialized_pos) + .concat(serialized_data) + .concat(serialized_output_size) + } + + fn deserialize( + serialized_state: &SerializedState, + ) -> Result { + let (serialized_core, remaining_buffer) = + serialized_state.split_ref::(); + let (serialized_pos, remaining_buffer) = remaining_buffer.split_ref::(); + let (serialized_data, serialized_output_size) = + remaining_buffer.split_ref::(); + + Ok(Self { + core: T::deserialize(serialized_core)?, + buffer: BlockBuffer::try_new(&serialized_data[..serialized_pos[0].into()]) + .map_err(|_| DeserializeStateError)?, + output_size: serialized_output_size[0], + }) + } +} + +#[cfg(feature = "std")] +impl std::io::Write for RtVariableCoreWrapper { + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + Update::update(self, buf); + Ok(buf.len()) + } + + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} diff --git a/digest/src/core_api/wrapper.rs b/digest/src/core_api/wrapper.rs new file mode 100644 index 000000000..5d8f45d4d --- /dev/null +++ b/digest/src/core_api/wrapper.rs @@ -0,0 +1,253 @@ +use super::{ + AlgorithmName, BufferKindUser, ExtendableOutputCore, FixedOutputCore, OutputSizeUser, Reset, + UpdateCore, XofReaderCoreWrapper, +}; +use crate::{ + CustomizedInit, ExtendableOutput, ExtendableOutputReset, FixedOutput, FixedOutputReset, + HashMarker, Update, +}; +use block_buffer::BlockBuffer; +use core::{ + convert::TryInto, + fmt, + ops::{Add, Sub}, +}; +use crypto_common::{ + array::{Array, ArraySize}, + hazmat::{DeserializeStateError, SerializableState, SerializedState, SubSerializedStateSize}, + typenum::{Diff, IsLess, Le, NonZero, Sum, U1, U256}, + BlockSizeUser, InvalidLength, Key, KeyInit, KeySizeUser, Output, +}; + +#[cfg(feature = "mac")] +use crate::MacMarker; +#[cfg(feature = "oid")] +use const_oid::{AssociatedOid, ObjectIdentifier}; + +/// Wrapper around [`BufferKindUser`]. +/// +/// It handles data buffering and implements the slice-based traits. +#[derive(Clone, Default)] +pub struct CoreWrapper { + core: T, + buffer: BlockBuffer, +} + +impl HashMarker for CoreWrapper {} + +#[cfg(feature = "mac")] +impl MacMarker for CoreWrapper {} + +// this blanket impl is needed for HMAC +impl BlockSizeUser for CoreWrapper { + type BlockSize = T::BlockSize; +} + +impl CoreWrapper { + /// Create new wrapper from `core`. + #[inline] + pub fn from_core(core: T) -> Self { + let buffer = Default::default(); + Self { core, buffer } + } +} + +impl KeySizeUser for CoreWrapper { + type KeySize = T::KeySize; +} + +impl KeyInit for CoreWrapper { + #[inline] + fn new(key: &Key) -> Self { + Self { + core: T::new(key), + buffer: Default::default(), + } + } + + #[inline] + fn new_from_slice(key: &[u8]) -> Result { + Ok(Self { + core: T::new_from_slice(key)?, + buffer: Default::default(), + }) + } +} + +impl fmt::Debug for CoreWrapper { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + T::write_alg_name(f)?; + f.write_str(" { .. }") + } +} + +impl Reset for CoreWrapper { + #[inline] + fn reset(&mut self) { + self.core.reset(); + self.buffer.reset(); + } +} + +impl Update for CoreWrapper { + #[inline] + fn update(&mut self, input: &[u8]) { + let Self { core, buffer } = self; + buffer.digest_blocks(input, |blocks| core.update_blocks(blocks)); + } +} + +impl OutputSizeUser for CoreWrapper { + type OutputSize = T::OutputSize; +} + +impl FixedOutput for CoreWrapper { + #[inline] + fn finalize_into(mut self, out: &mut Output) { + let Self { core, buffer } = &mut self; + core.finalize_fixed_core(buffer, out); + } +} + +impl FixedOutputReset for CoreWrapper { + #[inline] + fn finalize_into_reset(&mut self, out: &mut Output) { + let Self { core, buffer } = self; + core.finalize_fixed_core(buffer, out); + core.reset(); + buffer.reset(); + } +} + +impl ExtendableOutput for CoreWrapper { + type Reader = XofReaderCoreWrapper; + + #[inline] + fn finalize_xof(mut self) -> Self::Reader { + Self::Reader { + core: self.core.finalize_xof_core(&mut self.buffer), + buffer: Default::default(), + } + } +} + +impl ExtendableOutputReset for CoreWrapper { + #[inline] + fn finalize_xof_reset(&mut self) -> Self::Reader { + let Self { core, buffer } = self; + let reader_core = core.finalize_xof_core(buffer); + core.reset(); + buffer.reset(); + let buffer = Default::default(); + Self::Reader { + core: reader_core, + buffer, + } + } +} + +impl Drop for CoreWrapper { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + use zeroize::Zeroize; + self.buffer.zeroize(); + } + } +} + +#[cfg(feature = "zeroize")] +impl zeroize::ZeroizeOnDrop for CoreWrapper {} + +impl CustomizedInit for CoreWrapper +where + T: BufferKindUser + CustomizedInit, +{ + #[inline] + fn new_customized(customization: &[u8]) -> Self { + Self::from_core(T::new_customized(customization)) + } +} + +#[cfg(feature = "oid")] +impl AssociatedOid for CoreWrapper +where + T: BufferKindUser + AssociatedOid, +{ + const OID: ObjectIdentifier = T::OID; +} + +type CoreWrapperSerializedStateSize = + Sum::SerializedStateSize, U1>, ::BlockSize>; + +impl SerializableState for CoreWrapper +where + T: BufferKindUser + SerializableState, + T::BlockSize: IsLess, + Le: NonZero, + T::SerializedStateSize: Add, + Sum: Add + ArraySize, + CoreWrapperSerializedStateSize: Sub + ArraySize, + SubSerializedStateSize, T>: Sub + ArraySize, + Diff, T>, U1>: ArraySize, +{ + type SerializedStateSize = CoreWrapperSerializedStateSize; + + fn serialize(&self) -> SerializedState { + let serialized_core = self.core.serialize(); + let serialized_pos = Array([self.buffer.get_pos().try_into().unwrap()]); + let serialized_data = self.buffer.clone().pad_with_zeros(); + + serialized_core + .concat(serialized_pos) + .concat(serialized_data) + } + + fn deserialize( + serialized_state: &SerializedState, + ) -> Result { + let (serialized_core, remaining_buffer) = + serialized_state.split_ref::(); + let (serialized_pos, serialized_data) = remaining_buffer.split_ref::(); + + Ok(Self { + core: T::deserialize(serialized_core)?, + buffer: BlockBuffer::try_new(&serialized_data[..serialized_pos[0].into()]) + .map_err(|_| DeserializeStateError)?, + }) + } +} + +#[cfg(feature = "std")] +impl std::io::Write for CoreWrapper { + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + Update::update(self, buf); + Ok(buf.len()) + } + + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +/// A proxy trait to a core type implemented by [`CoreWrapper`] +// TODO: replace with an inherent associated type on stabilization: +// https://github.com/rust-lang/rust/issues/8995 +pub trait CoreProxy: sealed::Sealed { + /// Type wrapped by [`CoreWrapper`]. + type Core; +} + +mod sealed { + pub trait Sealed {} +} + +impl sealed::Sealed for CoreWrapper {} + +impl CoreProxy for CoreWrapper { + type Core = T; +} diff --git a/digest/src/core_api/xof_reader.rs b/digest/src/core_api/xof_reader.rs new file mode 100644 index 000000000..0c832df0c --- /dev/null +++ b/digest/src/core_api/xof_reader.rs @@ -0,0 +1,63 @@ +use super::{AlgorithmName, XofReaderCore}; +use crate::XofReader; +use block_buffer::ReadBuffer; +use core::fmt; + +/// Wrapper around [`XofReaderCore`] implementations. +/// +/// It handles data buffering and implements the mid-level traits. +#[derive(Clone, Default)] +pub struct XofReaderCoreWrapper +where + T: XofReaderCore, +{ + pub(super) core: T, + pub(super) buffer: ReadBuffer, +} + +impl fmt::Debug for XofReaderCoreWrapper +where + T: XofReaderCore + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + T::write_alg_name(f)?; + f.write_str(" { .. }") + } +} + +impl XofReader for XofReaderCoreWrapper +where + T: XofReaderCore, +{ + #[inline] + fn read(&mut self, buffer: &mut [u8]) { + let Self { core, buffer: buf } = self; + buf.read(buffer, |block| *block = core.read_block()); + } +} + +#[cfg(feature = "std")] +impl std::io::Read for XofReaderCoreWrapper +where + T: XofReaderCore, +{ + #[inline] + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + XofReader::read(self, buf); + Ok(buf.len()) + } +} + +impl Drop for XofReaderCoreWrapper { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + use zeroize::Zeroize; + self.buffer.zeroize(); + } + } +} + +#[cfg(feature = "zeroize")] +impl zeroize::ZeroizeOnDrop for XofReaderCoreWrapper {} diff --git a/digest/src/dev.rs b/digest/src/dev.rs index f4d79e71e..00b93859b 100644 --- a/digest/src/dev.rs +++ b/digest/src/dev.rs @@ -1,17 +1,28 @@ -use super::{ExtendableOutput, Input, Reset, VariableOutput, XofReader}; -use core::fmt::Debug; +//! Development-related functionality +pub use blobby; + +mod fixed; +mod mac; +mod rng; +mod variable; +mod xof; + +pub use fixed::*; +pub use variable::*; +pub use xof::*; + +/// Define hash function test #[macro_export] macro_rules! new_test { - ($name:ident, $test_name:expr, $hasher:ty, $test_func:ident) => { + ($name:ident, $test_name:expr, $hasher:ty, $test_func:ident $(,)?) => { #[test] fn $name() { - use digest::blobby::Blob2Iterator; + use digest::dev::blobby::Blob2Iterator; let data = include_bytes!(concat!("data/", $test_name, ".blb")); for (i, row) in Blob2Iterator::new(data).unwrap().enumerate() { - let input = row[0]; - let output = row[1]; + let [input, output] = row.unwrap(); if let Some(desc) = $test_func::<$hasher>(input, output) { panic!( "\n\ @@ -26,210 +37,108 @@ macro_rules! new_test { }; } -// module to separate Digest from other traits -mod foo { - use super::super::Digest; - use core::fmt::Debug; - - pub fn digest_test(input: &[u8], output: &[u8]) -> Option<&'static str> - where - D: Digest + Debug + Clone, - { - let mut hasher = D::new(); - // Test that it works when accepting the message all at once - hasher.input(input); - let mut hasher2 = hasher.clone(); - if hasher.result().as_slice() != output { - return Some("whole message"); - } - - // Test if reset works correctly - hasher2.reset(); - hasher2.input(input); - if hasher2.result().as_slice() != output { - return Some("whole message after reset"); - } - - // Test that it works when accepting the message in pieces - let mut hasher = D::new(); - let len = input.len(); - let mut left = len; - while left > 0 { - let take = (left + 1) / 2; - hasher.input(&input[len - left..take + len - left]); - left = left - take; - } - if hasher.result().as_slice() != output { - return Some("message in pieces"); - } - - // Test processing byte-by-byte - let mut hasher = D::new(); - for chunk in input.chunks(1) { - hasher.input(chunk) - } - if hasher.result().as_slice() != output { - return Some("message byte-by-byte"); - } - None - } - - pub fn one_million_a(expected: &[u8]) - where - D: Digest + Debug + Clone, - { - let mut sh = D::new(); - for _ in 0..50_000 { - sh.input(&[b'a'; 10]); - } - sh.input(&[b'a'; 500_000][..]); - let out = sh.result(); - assert_eq!(out[..], expected[..]); - } -} +/// Define hash function serialization test +#[macro_export] +macro_rules! hash_serialization_test { + ($name:ident, $hasher:ty, $expected_serialized_state:expr) => { + #[test] + fn $name() { + use digest::{ + crypto_common::{hazmat::SerializableState, BlockSizeUser}, + typenum::Unsigned, + Digest, + }; -pub use self::foo::{digest_test, one_million_a}; + let mut h = <$hasher>::new(); -pub fn xof_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: Input + ExtendableOutput + Default + Debug + Reset + Clone, -{ - let mut hasher = D::default(); - let mut buf = [0u8; 1024]; - // Test that it works when accepting the message all at once - hasher.input(input); + h.update(&[0x13; <$hasher as BlockSizeUser>::BlockSize::USIZE + 1]); - let mut hasher2 = hasher.clone(); - { - let out = &mut buf[..output.len()]; - hasher.xof_result().read(out); + let serialized_state = h.serialize(); + assert_eq!(serialized_state.as_slice(), $expected_serialized_state); - if out != output { - return Some("whole message"); - } - } + let mut h = <$hasher>::deserialize(&serialized_state).unwrap(); - // Test if hasher resets correctly - hasher2.reset(); - hasher2.input(input); + h.update(&[0x13; <$hasher as BlockSizeUser>::BlockSize::USIZE + 1]); + let output1 = h.finalize(); - { - let out = &mut buf[..output.len()]; - hasher2.xof_result().read(out); + let mut h = <$hasher>::new(); + h.update(&[0x13; 2 * (<$hasher as BlockSizeUser>::BlockSize::USIZE + 1)]); + let output2 = h.finalize(); - if out != output { - return Some("whole message after reset"); + assert_eq!(output1, output2); } - } - - // Test if hasher accepts message in pieces correctly - let mut hasher = D::default(); - let len = input.len(); - let mut left = len; - while left > 0 { - let take = (left + 1) / 2; - hasher.input(&input[len - left..take + len - left]); - left = left - take; - } + }; +} - { - let out = &mut buf[..output.len()]; - hasher.xof_result().read(out); - if out != output { - return Some("message in pieces"); - } - } +/// Define hash function serialization test +#[macro_export] +macro_rules! hash_rt_outsize_serialization_test { + ($name:ident, $hasher:ty, $expected_serialized_state:expr) => { + #[test] + fn $name() { + use digest::{ + crypto_common::{hazmat::SerializableState, BlockSizeUser}, + typenum::Unsigned, + Digest, Update, VariableOutput, + }; + const HASH_OUTPUT_SIZE: usize = <$hasher>::MAX_OUTPUT_SIZE - 1; - // Test reading from reader byte by byte - let mut hasher = D::default(); - hasher.input(input); + let mut h = <$hasher>::new(HASH_OUTPUT_SIZE).unwrap(); - let mut reader = hasher.xof_result(); - let out = &mut buf[..output.len()]; - for chunk in out.chunks_mut(1) { - reader.read(chunk); - } + h.update(&[0x13; <$hasher as BlockSizeUser>::BlockSize::USIZE + 1]); - if out != output { - return Some("message in pieces"); - } - None -} + let serialized_state = h.serialize(); + assert_eq!(serialized_state.as_slice(), $expected_serialized_state); -pub fn variable_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: Input + VariableOutput + Reset + Debug + Clone, -{ - let mut hasher = D::new(output.len()).unwrap(); - let mut buf = [0u8; 128]; - let buf = &mut buf[..output.len()]; - // Test that it works when accepting the message all at once - hasher.input(input); - let mut hasher2 = hasher.clone(); - hasher.variable_result(|res| buf.copy_from_slice(res)); - if buf != output { - return Some("whole message"); - } + let mut h = <$hasher>::deserialize(&serialized_state).unwrap(); - // Test if reset works correctly - hasher2.reset(); - hasher2.input(input); - hasher2.variable_result(|res| buf.copy_from_slice(res)); - if buf != output { - return Some("whole message after reset"); - } + h.update(&[0x13; <$hasher as BlockSizeUser>::BlockSize::USIZE + 1]); + let mut output1 = [0; HASH_OUTPUT_SIZE]; + h.finalize_variable(&mut output1).unwrap(); - // Test that it works when accepting the message in pieces - let mut hasher = D::new(output.len()).unwrap(); - let len = input.len(); - let mut left = len; - while left > 0 { - let take = (left + 1) / 2; - hasher.input(&input[len - left..take + len - left]); - left = left - take; - } - hasher.variable_result(|res| buf.copy_from_slice(res)); - if buf != output { - return Some("message in pieces"); - } + let mut h = <$hasher>::new(HASH_OUTPUT_SIZE).unwrap(); + h.update(&[0x13; 2 * (<$hasher as BlockSizeUser>::BlockSize::USIZE + 1)]); + let mut output2 = [0; HASH_OUTPUT_SIZE]; + h.finalize_variable(&mut output2).unwrap(); - // Test processing byte-by-byte - let mut hasher = D::new(output.len()).unwrap(); - for chunk in input.chunks(1) { - hasher.input(chunk) - } - hasher.variable_result(|res| buf.copy_from_slice(res)); - if buf != output { - return Some("message byte-by-byte"); - } - None + assert_eq!(output1, output2); + } + }; } +/// Define [`Update`][crate::Update] impl benchmark #[macro_export] -macro_rules! bench { - ($name:ident, $engine:path, $bs:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut d = <$engine>::default(); - let data = [0; $bs]; - - b.iter(|| { - d.input(&data[..]); - }); - - b.bytes = $bs; - } +macro_rules! bench_update { + ( + $init:expr; + $($name:ident $bs:expr;)* + ) => { + $( + #[bench] + fn $name(b: &mut Bencher) { + let mut d = $init; + let data = [0; $bs]; + + b.iter(|| { + digest::Update::update(&mut d, &data[..]); + }); + + b.bytes = $bs; + } + )* }; +} - ($engine:path) => { - extern crate test; - - use digest::Digest; - use test::Bencher; - - bench!(bench1_10, $engine, 10); - bench!(bench2_100, $engine, 100); - bench!(bench3_1000, $engine, 1000); - bench!(bench4_10000, $engine, 10000); - }; +/// Feed ~1 MiB of pseudorandom data to an updatable state. +pub fn feed_rand_16mib(d: &mut D) { + let buf = &mut [0u8; 1024]; + let mut rng = rng::RNG; + let n = 16 * (1 << 20) / buf.len(); + for _ in 0..n { + rng.fill(buf); + d.update(buf); + // additional byte, so size of fed data + // will not be multiple of block size + d.update(&[42]); + } } diff --git a/digest/src/dev/fixed.rs b/digest/src/dev/fixed.rs new file mode 100644 index 000000000..24f380112 --- /dev/null +++ b/digest/src/dev/fixed.rs @@ -0,0 +1,65 @@ +use crate::{Digest, FixedOutput, FixedOutputReset, HashMarker, Update}; +use core::fmt::Debug; + +/// Fixed-output resettable digest test via the `Digest` trait +pub fn fixed_reset_test(input: &[u8], output: &[u8]) -> Option<&'static str> +where + D: FixedOutputReset + Debug + Clone + Default + Update + HashMarker, +{ + let mut hasher = D::new(); + // Test that it works when accepting the message all at once + hasher.update(input); + let mut hasher2 = hasher.clone(); + if hasher.finalize()[..] != output[..] { + return Some("whole message"); + } + + // Test if reset works correctly + hasher2.reset(); + hasher2.update(input); + if hasher2.finalize_reset()[..] != output[..] { + return Some("whole message after reset"); + } + + // Test that it works when accepting the message in chunks + for n in 1..core::cmp::min(17, input.len()) { + let mut hasher = D::new(); + for chunk in input.chunks(n) { + hasher.update(chunk); + hasher2.update(chunk); + } + if hasher.finalize()[..] != output[..] { + return Some("message in chunks"); + } + if hasher2.finalize_reset()[..] != output[..] { + return Some("message in chunks"); + } + } + + None +} + +/// Variable-output resettable digest test +pub fn fixed_test(input: &[u8], output: &[u8]) -> Option<&'static str> +where + D: FixedOutput + Default + Debug + Clone, +{ + let mut hasher = D::default(); + // Test that it works when accepting the message all at once + hasher.update(input); + if hasher.finalize_fixed()[..] != output[..] { + return Some("whole message"); + } + + // Test that it works when accepting the message in chunks + for n in 1..core::cmp::min(17, input.len()) { + let mut hasher = D::default(); + for chunk in input.chunks(n) { + hasher.update(chunk); + } + if hasher.finalize_fixed()[..] != output[..] { + return Some("message in chunks"); + } + } + None +} diff --git a/digest/src/dev/mac.rs b/digest/src/dev/mac.rs new file mode 100644 index 000000000..969a18b03 --- /dev/null +++ b/digest/src/dev/mac.rs @@ -0,0 +1,157 @@ +/// Define MAC test +#[macro_export] +#[cfg(feature = "mac")] +macro_rules! new_mac_test { + ($name:ident, $test_name:expr, $mac:ty $(,)?) => { + digest::new_mac_test!($name, $test_name, $mac, ""); + }; + ($name:ident, $test_name:expr, $mac:ty, trunc_left $(,)?) => { + digest::new_mac_test!($name, $test_name, $mac, "left"); + }; + ($name:ident, $test_name:expr, $mac:ty, trunc_right $(,)?) => { + digest::new_mac_test!($name, $test_name, $mac, "right"); + }; + ($name:ident, $test_name:expr, $mac:ty, $trunc:expr $(,)?) => { + #[test] + fn $name() { + use core::cmp::min; + use digest::dev::blobby::Blob3Iterator; + use digest::{KeyInit, Mac}; + + fn run_test(key: &[u8], input: &[u8], tag: &[u8]) -> Option<&'static str> { + let mac0 = <$mac as KeyInit>::new_from_slice(key).unwrap(); + + let mut mac = mac0.clone(); + mac.update(input); + let result = mac.finalize().into_bytes(); + let n = tag.len(); + let result_bytes = match $trunc { + "left" => &result[..n], + "right" => &result[result.len() - n..], + _ => &result[..], + }; + if result_bytes != tag { + return Some("whole message"); + } + + // test reading different chunk sizes + for chunk_size in 1..min(64, input.len()) { + let mut mac = mac0.clone(); + for chunk in input.chunks(chunk_size) { + mac.update(chunk); + } + let res = match $trunc { + "left" => mac.verify_truncated_left(tag), + "right" => mac.verify_truncated_right(tag), + _ => mac.verify_slice(tag), + }; + if res.is_err() { + return Some("chunked message"); + } + } + + None + } + + let data = include_bytes!(concat!("data/", $test_name, ".blb")); + + for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { + let [key, input, tag] = row.unwrap(); + if let Some(desc) = run_test(key, input, tag) { + panic!( + "\n\ + Failed test №{}: {}\n\ + key:\t{:?}\n\ + input:\t{:?}\n\ + tag:\t{:?}\n", + i, desc, key, input, tag, + ); + } + } + } + }; +} + +/// Define resettable MAC test +#[macro_export] +#[cfg(feature = "mac")] +macro_rules! new_resettable_mac_test { + ($name:ident, $test_name:expr, $mac:ty $(,)?) => { + digest::new_resettable_mac_test!($name, $test_name, $mac, ""); + }; + ($name:ident, $test_name:expr, $mac:ty, trunc_left $(,)?) => { + digest::new_resettable_mac_test!($name, $test_name, $mac, "left"); + }; + ($name:ident, $test_name:expr, $mac:ty, trunc_right $(,)?) => { + digest::new_resettable_mac_test!($name, $test_name, $mac, "right"); + }; + ($name:ident, $test_name:expr, $mac:ty, $trunc:expr $(,)?) => { + #[test] + fn $name() { + use core::cmp::min; + use digest::dev::blobby::Blob3Iterator; + use digest::{KeyInit, Mac}; + + fn run_test(key: &[u8], input: &[u8], tag: &[u8]) -> Option<&'static str> { + let mac0 = <$mac as KeyInit>::new_from_slice(key).unwrap(); + + let mut mac = mac0.clone(); + mac.update(input); + let result = mac.finalize_reset().into_bytes(); + let n = tag.len(); + let result_bytes = match $trunc { + "left" => &result[..n], + "right" => &result[result.len() - n..], + _ => &result[..], + }; + if result_bytes != tag { + return Some("whole message"); + } + + // test if reset worked correctly + mac.update(input); + let res = match $trunc { + "left" => mac.verify_truncated_left(tag), + "right" => mac.verify_truncated_right(tag), + _ => mac.verify_slice(tag), + }; + if res.is_err() { + return Some("after reset"); + } + + // test reading different chunk sizes + for chunk_size in 1..min(64, input.len()) { + let mut mac = mac0.clone(); + for chunk in input.chunks(chunk_size) { + mac.update(chunk); + } + let res = match $trunc { + "left" => mac.verify_truncated_left(tag), + "right" => mac.verify_truncated_right(tag), + _ => mac.verify_slice(tag), + }; + if res.is_err() { + return Some("chunked message"); + } + } + None + } + + let data = include_bytes!(concat!("data/", $test_name, ".blb")); + + for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { + let [key, input, tag] = row.unwrap(); + if let Some(desc) = run_test(key, input, tag) { + panic!( + "\n\ + Failed test №{}: {}\n\ + key:\t{:?}\n\ + input:\t{:?}\n\ + tag:\t{:?}\n", + i, desc, key, input, tag, + ); + } + } + } + }; +} diff --git a/digest/src/dev/rng.rs b/digest/src/dev/rng.rs new file mode 100644 index 000000000..d34a1cf31 --- /dev/null +++ b/digest/src/dev/rng.rs @@ -0,0 +1,38 @@ +//! Xorshift RNG used for tests. Based on the `rand_xorshift` crate. +use core::num::Wrapping; + +/// Initial RNG state used in tests. +// chosen by fair dice roll. guaranteed to be random. +pub(crate) const RNG: XorShiftRng = XorShiftRng { + x: Wrapping(0x0787_3B4A), + y: Wrapping(0xFAAB_8FFE), + z: Wrapping(0x1745_980F), + w: Wrapping(0xB0AD_B4F3), +}; + +/// Xorshift RNG instance/ +pub(crate) struct XorShiftRng { + x: Wrapping, + y: Wrapping, + z: Wrapping, + w: Wrapping, +} + +impl XorShiftRng { + pub(crate) fn fill(&mut self, buf: &mut [u8; 1024]) { + for chunk in buf.chunks_exact_mut(4) { + chunk.copy_from_slice(&self.next_u32().to_le_bytes()); + } + } + + fn next_u32(&mut self) -> u32 { + let x = self.x; + let t = x ^ (x << 11); + self.x = self.y; + self.y = self.z; + self.z = self.w; + let w = self.w; + self.w = w ^ (w >> 19) ^ (t ^ (t >> 8)); + self.w.0 + } +} diff --git a/digest/src/dev/variable.rs b/digest/src/dev/variable.rs new file mode 100644 index 000000000..ed8ff8828 --- /dev/null +++ b/digest/src/dev/variable.rs @@ -0,0 +1,82 @@ +use crate::{VariableOutput, VariableOutputReset}; +use core::fmt::Debug; + +/// Variable-output resettable digest test +pub fn variable_reset_test(input: &[u8], output: &[u8]) -> Option<&'static str> +where + D: VariableOutputReset + Debug + Clone, +{ + let mut hasher = D::new(output.len()).unwrap(); + let mut buf = [0u8; 128]; + let buf = &mut buf[..output.len()]; + // Test that it works when accepting the message all at once + hasher.update(input); + let mut hasher2 = hasher.clone(); + hasher.finalize_variable(buf).unwrap(); + if buf != output { + return Some("whole message"); + } + buf.iter_mut().for_each(|b| *b = 0); + + // Test if reset works correctly + hasher2.reset(); + hasher2.update(input); + hasher2.finalize_variable_reset(buf).unwrap(); + if buf != output { + return Some("whole message after reset"); + } + buf.iter_mut().for_each(|b| *b = 0); + + // Test that it works when accepting the message in chunks + for n in 1..core::cmp::min(17, input.len()) { + let mut hasher = D::new(output.len()).unwrap(); + for chunk in input.chunks(n) { + hasher.update(chunk); + hasher2.update(chunk); + } + hasher.finalize_variable(buf).unwrap(); + if buf != output { + return Some("message in chunks"); + } + buf.iter_mut().for_each(|b| *b = 0); + + hasher2.finalize_variable_reset(buf).unwrap(); + if buf != output { + return Some("message in chunks"); + } + buf.iter_mut().for_each(|b| *b = 0); + } + + None +} + +/// Variable-output resettable digest test +pub fn variable_test(input: &[u8], output: &[u8]) -> Option<&'static str> +where + D: VariableOutput + Debug + Clone, +{ + let mut hasher = D::new(output.len()).unwrap(); + let mut buf = [0u8; 128]; + let buf = &mut buf[..output.len()]; + // Test that it works when accepting the message all at once + hasher.update(input); + hasher.finalize_variable(buf).unwrap(); + if buf != output { + return Some("whole message"); + } + buf.iter_mut().for_each(|b| *b = 0); + + // Test that it works when accepting the message in chunks + for n in 1..core::cmp::min(17, input.len()) { + let mut hasher = D::new(output.len()).unwrap(); + for chunk in input.chunks(n) { + hasher.update(chunk); + } + hasher.finalize_variable(buf).unwrap(); + if buf != output { + return Some("message in chunks"); + } + buf.iter_mut().for_each(|b| *b = 0); + } + None +} diff --git a/digest/src/dev/xof.rs b/digest/src/dev/xof.rs new file mode 100644 index 000000000..9e5d07a09 --- /dev/null +++ b/digest/src/dev/xof.rs @@ -0,0 +1,51 @@ +use crate::ExtendableOutputReset; +use core::fmt::Debug; + +/// Resettable XOF test +pub fn xof_reset_test(input: &[u8], output: &[u8]) -> Option<&'static str> +where + D: ExtendableOutputReset + Default + Debug + Clone, +{ + let mut hasher = D::default(); + let mut buf = [0u8; 1024]; + let buf = &mut buf[..output.len()]; + // Test that it works when accepting the message all at once + hasher.update(input); + let mut hasher2 = hasher.clone(); + hasher.finalize_xof_into(buf); + if buf != output { + return Some("whole message"); + } + buf.iter_mut().for_each(|b| *b = 0); + + // Test if reset works correctly + hasher2.reset(); + hasher2.update(input); + hasher2.finalize_xof_reset_into(buf); + if buf != output { + return Some("whole message after reset"); + } + buf.iter_mut().for_each(|b| *b = 0); + + // Test that it works when accepting the message in chunks + for n in 1..core::cmp::min(17, input.len()) { + let mut hasher = D::default(); + for chunk in input.chunks(n) { + hasher.update(chunk); + hasher2.update(chunk); + } + hasher.finalize_xof_into(buf); + if buf != output { + return Some("message in chunks"); + } + buf.iter_mut().for_each(|b| *b = 0); + + hasher2.finalize_xof_reset_into(buf); + if buf != output { + return Some("message in chunks"); + } + buf.iter_mut().for_each(|b| *b = 0); + } + + None +} diff --git a/digest/src/digest.rs b/digest/src/digest.rs index 944be43fb..c8d1ca87e 100644 --- a/digest/src/digest.rs +++ b/digest/src/digest.rs @@ -1,91 +1,235 @@ -use super::{FixedOutput, Input, Reset}; -use generic_array::typenum::Unsigned; -use generic_array::{ArrayLength, GenericArray}; +use super::{FixedOutput, FixedOutputReset, InvalidBufferSize, Reset, Update}; +use crypto_common::{typenum::Unsigned, Output, OutputSizeUser}; -/// The `Digest` trait specifies an interface common for digest functions. +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +#[cfg(feature = "const-oid")] +use const_oid::DynAssociatedOid; + +/// Marker trait for cryptographic hash functions. +pub trait HashMarker {} + +/// Convenience wrapper trait covering functionality of cryptographic hash +/// functions with fixed output size. /// -/// It's a convenience wrapper around `Input`, `FixedOutput`, `Reset`, `Clone`, -/// and `Default` traits. It also provides additional convenience methods. -pub trait Digest { - type OutputSize: ArrayLength; - /// Create new hasher instance +/// This trait wraps [`Update`], [`FixedOutput`], [`Default`], and +/// [`HashMarker`] traits and provides additional convenience methods. +pub trait Digest: OutputSizeUser { + /// Create new hasher instance. fn new() -> Self; - /// Digest input data. - /// - /// This method can be called repeatedly for use with streaming messages. - fn input>(&mut self, data: B); + /// Create new hasher instance which has processed the provided data. + fn new_with_prefix(data: impl AsRef<[u8]>) -> Self; - /// Digest input data in a chained manner. - fn chain>(self, data: B) -> Self - where - Self: Sized; + /// Process data, updating the internal state. + fn update(&mut self, data: impl AsRef<[u8]>); + + /// Process input data in a chained manner. + #[must_use] + fn chain_update(self, data: impl AsRef<[u8]>) -> Self; /// Retrieve result and consume hasher instance. - fn result(self) -> GenericArray; + fn finalize(self) -> Output; + + /// Write result into provided array and consume the hasher instance. + fn finalize_into(self, out: &mut Output); /// Retrieve result and reset hasher instance. - /// - /// This method sometimes can be more efficient compared to hasher - /// re-creation. - fn result_reset(&mut self) -> GenericArray; + fn finalize_reset(&mut self) -> Output + where + Self: FixedOutputReset; + + /// Write result into provided array and reset the hasher instance. + fn finalize_into_reset(&mut self, out: &mut Output) + where + Self: FixedOutputReset; /// Reset hasher instance to its initial state. - fn reset(&mut self); + fn reset(&mut self) + where + Self: Reset; /// Get output size of the hasher fn output_size() -> usize; - /// Convenience function to compute hash of the `data`. It will handle - /// hasher creation, data feeding and finalization. - /// - /// Example: - /// - /// ```rust,ignore - /// println!("{:x}", sha2::Sha256::digest(b"Hello world")); - /// ``` - fn digest(data: &[u8]) -> GenericArray; + /// Compute hash of `data`. + fn digest(data: impl AsRef<[u8]>) -> Output; } -impl Digest for D { - type OutputSize = ::OutputSize; - +impl Digest for D { + #[inline] fn new() -> Self { Self::default() } - fn input>(&mut self, data: B) { - Input::input(self, data); - } - - fn chain>(self, data: B) -> Self + #[inline] + fn new_with_prefix(data: impl AsRef<[u8]>) -> Self where - Self: Sized, + Self: Default + Sized, { - Input::chain(self, data) + let mut h = Self::default(); + h.update(data.as_ref()); + h } - fn result(self) -> GenericArray { - self.fixed_result() + #[inline] + fn update(&mut self, data: impl AsRef<[u8]>) { + Update::update(self, data.as_ref()); } - fn result_reset(&mut self) -> GenericArray { - let res = self.clone().fixed_result(); - self.reset(); - res + #[inline] + fn chain_update(mut self, data: impl AsRef<[u8]>) -> Self { + Update::update(&mut self, data.as_ref()); + self } - fn reset(&mut self) { - ::reset(self) + #[inline] + fn finalize(self) -> Output { + FixedOutput::finalize_fixed(self) + } + + #[inline] + fn finalize_into(self, out: &mut Output) { + FixedOutput::finalize_into(self, out); + } + + #[inline] + fn finalize_reset(&mut self) -> Output + where + Self: FixedOutputReset, + { + FixedOutputReset::finalize_fixed_reset(self) } + #[inline] + fn finalize_into_reset(&mut self, out: &mut Output) + where + Self: FixedOutputReset, + { + FixedOutputReset::finalize_into_reset(self, out); + } + + #[inline] + fn reset(&mut self) + where + Self: Reset, + { + Reset::reset(self) + } + + #[inline] fn output_size() -> usize { Self::OutputSize::to_usize() } - fn digest(data: &[u8]) -> GenericArray { + #[inline] + fn digest(data: impl AsRef<[u8]>) -> Output { let mut hasher = Self::default(); - Input::input(&mut hasher, data); - hasher.fixed_result() + hasher.update(data.as_ref()); + hasher.finalize() } } + +/// Modification of the [`Digest`] trait suitable for trait objects. +pub trait DynDigest { + /// Digest input data. + /// + /// This method can be called repeatedly for use with streaming messages. + fn update(&mut self, data: &[u8]); + + /// Retrieve result and reset hasher instance + #[cfg(feature = "alloc")] + fn finalize_reset(&mut self) -> Box<[u8]> { + let mut result = vec![0; self.output_size()]; + self.finalize_into_reset(&mut result).unwrap(); + result.into_boxed_slice() + } + + /// Retrieve result and consume boxed hasher instance + #[cfg(feature = "alloc")] + #[allow(clippy::boxed_local)] + fn finalize(mut self: Box) -> Box<[u8]> { + let mut result = vec![0; self.output_size()]; + self.finalize_into_reset(&mut result).unwrap(); + result.into_boxed_slice() + } + + /// Write result into provided array and consume the hasher instance. + /// + /// Returns error if buffer length is not equal to `output_size`. + fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize>; + + /// Write result into provided array and reset the hasher instance. + /// + /// Returns error if buffer length is not equal to `output_size`. + fn finalize_into_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize>; + + /// Reset hasher instance to its initial state. + fn reset(&mut self); + + /// Get output size of the hasher + fn output_size(&self) -> usize; + + /// Clone hasher state into a boxed trait object + #[cfg(feature = "alloc")] + fn box_clone(&self) -> Box; +} + +impl DynDigest for D { + fn update(&mut self, data: &[u8]) { + Update::update(self, data); + } + + #[cfg(feature = "alloc")] + fn finalize_reset(&mut self) -> Box<[u8]> { + FixedOutputReset::finalize_fixed_reset(self) + .to_vec() + .into_boxed_slice() + } + + #[cfg(feature = "alloc")] + fn finalize(self: Box) -> Box<[u8]> { + FixedOutput::finalize_fixed(*self) + .to_vec() + .into_boxed_slice() + } + + fn finalize_into(self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> { + buf.try_into() + .map_err(|_| InvalidBufferSize) + .map(|buf| FixedOutput::finalize_into(self, buf)) + } + + fn finalize_into_reset(&mut self, buf: &mut [u8]) -> Result<(), InvalidBufferSize> { + let buf = <&mut Output>::try_from(buf).map_err(|_| InvalidBufferSize)?; + FixedOutputReset::finalize_into_reset(self, buf); + Ok(()) + } + + fn reset(&mut self) { + Reset::reset(self); + } + + fn output_size(&self) -> usize { + ::OutputSize::to_usize() + } + + #[cfg(feature = "alloc")] + fn box_clone(&self) -> Box { + Box::new(self.clone()) + } +} + +#[cfg(feature = "alloc")] +impl Clone for Box { + fn clone(&self) -> Self { + self.box_clone() + } +} + +/// Convenience wrapper trait around [DynDigest] and [DynAssociatedOid]. +#[cfg(feature = "const-oid")] +pub trait DynDigestWithOid: DynDigest + DynAssociatedOid {} + +#[cfg(feature = "const-oid")] +impl DynDigestWithOid for T {} diff --git a/digest/src/dyn_digest.rs b/digest/src/dyn_digest.rs deleted file mode 100644 index cb144bdee..000000000 --- a/digest/src/dyn_digest.rs +++ /dev/null @@ -1,63 +0,0 @@ -#![cfg(feature = "std")] -use std::boxed::Box; - -use super::{FixedOutput, Input, Reset}; -use generic_array::typenum::Unsigned; - -/// The `DynDigest` trait is a modification of `Digest` trait suitable -/// for trait objects. -pub trait DynDigest { - /// Digest input data. - /// - /// This method can be called repeatedly for use with streaming messages. - fn input(&mut self, data: &[u8]); - - /// Retrieve result and reset hasher instance - fn result_reset(&mut self) -> Box<[u8]>; - - /// Retrieve result and consume boxed hasher instance - fn result(self: Box) -> Box<[u8]>; - - /// Reset hasher instance to its initial state. - fn reset(&mut self); - - /// Get output size of the hasher - fn output_size(&self) -> usize; - - /// Clone hasher state into a boxed trait object - fn box_clone(&self) -> Box; -} - -impl DynDigest for D { - fn input(&mut self, data: &[u8]) { - Input::input(self, data); - } - - fn result_reset(&mut self) -> Box<[u8]> { - let res = self.clone().fixed_result().to_vec().into_boxed_slice(); - Reset::reset(self); - res - } - - fn result(self: Box) -> Box<[u8]> { - self.fixed_result().to_vec().into_boxed_slice() - } - - fn reset(&mut self) { - Reset::reset(self); - } - - fn output_size(&self) -> usize { - ::OutputSize::to_usize() - } - - fn box_clone(&self) -> Box { - Box::new(self.clone()) - } -} - -impl Clone for Box { - fn clone(&self) -> Self { - self.box_clone() - } -} diff --git a/digest/src/errors.rs b/digest/src/errors.rs deleted file mode 100644 index aa026a21a..000000000 --- a/digest/src/errors.rs +++ /dev/null @@ -1,20 +0,0 @@ -use core::fmt; -#[cfg(feature = "std")] -use std::error; - -/// The error type for variable hasher initialization -#[derive(Clone, Copy, Debug, Default)] -pub struct InvalidOutputSize; - -impl fmt::Display for InvalidOutputSize { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("invalid output size") - } -} - -#[cfg(feature = "std")] -impl error::Error for InvalidOutputSize { - fn description(&self) -> &str { - "invalid output size" - } -} diff --git a/digest/src/hashreader.rs b/digest/src/hashreader.rs new file mode 100644 index 000000000..eaca370b4 --- /dev/null +++ b/digest/src/hashreader.rs @@ -0,0 +1,136 @@ +//! Adds hashing to any reader +use super::{Digest, FixedOutputReset, Output, Reset}; +use std::io; + +/// Abstraction over a reader which hashes the data being read +#[derive(Debug)] +pub struct HashReader { + reader: R, + hasher: D, +} + +impl HashReader { + /// Construct a new `HashReader` given an existing `reader` by value. + pub fn new(reader: R) -> Self { + Self::new_from_parts(D::new(), reader) + } + + /// Construct a new `HashReader` given an existing `hasher` and `reader` by value. + pub fn new_from_parts(hasher: D, reader: R) -> Self { + HashReader { reader, hasher } + } + + /// Replace the reader with another reader + pub fn replace_reader(&mut self, reader: R) { + self.reader = reader; + } + + /// Gets a reference to the underlying hasher + pub fn get_hasher(&self) -> &D { + &self.hasher + } + + /// Gets a reference to the underlying reader + pub fn get_reader(&self) -> &R { + &self.reader + } + + /// Gets a mutable reference to the underlying hasher + pub fn get_hasher_mut(&mut self) -> &mut D { + &mut self.hasher + } + + /// Gets a mutable reference to the underlying reader + /// Direct reads from the underlying reader are not hashed + pub fn get_reader_mut(&mut self) -> &mut R { + &mut self.reader + } + + /// Consume the HashReader and return its hasher + pub fn into_hasher(self) -> D { + self.hasher + } + + /// Consume the HashReader and return its internal reader + pub fn into_inner_reader(self) -> R { + self.reader + } + + /// Consume the HashReader and return its hasher and internal reader + pub fn into_parts(self) -> (D, R) { + (self.hasher, self.reader) + } + + /// Retrieve result and consume HashReader instance. + pub fn finalize(self) -> Output { + self.hasher.finalize() + } + + /// Write result into provided array and consume the HashReader instance. + pub fn finalize_into(self, out: &mut Output) { + self.hasher.finalize_into(out) + } + + /// Get output size of the hasher + pub fn output_size() -> usize { + ::output_size() + } +} + +impl Clone for HashReader { + fn clone(&self) -> HashReader { + HashReader { + reader: self.reader.clone(), + hasher: self.hasher.clone(), + } + } +} + +impl io::Read for HashReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let bytes = self.reader.read(buf)?; + + if bytes > 0 { + self.hasher.update(&buf[0..bytes]); + } + + Ok(bytes) + } +} + +impl HashReader { + /// Retrieve result and reset hasher instance. + pub fn finalize_reset(&mut self) -> Output { + Digest::finalize_reset(&mut self.hasher) + } + + /// Rrite result into provided array and reset the hasher instance. + pub fn finalize_into_reset(&mut self, out: &mut Output) { + Digest::finalize_into_reset(&mut self.hasher, out) + } +} +impl Reset for HashReader { + fn reset(&mut self) { + Digest::reset(&mut self.hasher) + } +} + +impl HashReader { + /// Read and hash all bytes remaining in the reader, discarding the data + /// Based on implementation in b2sum crate, MIT License Copyright (c) 2017 John Downey + pub fn hash_to_end(&mut self) { + loop { + let count = { + let data = self.reader.fill_buf().unwrap(); + if data.is_empty() { + break; + } + + self.hasher.update(data); + data.len() + }; + + self.reader.consume(count); + } + } +} diff --git a/digest/src/hashwriter.rs b/digest/src/hashwriter.rs new file mode 100644 index 000000000..354590385 --- /dev/null +++ b/digest/src/hashwriter.rs @@ -0,0 +1,121 @@ +//! Adds hashing to any writer. Inspired by implementation in phase2 crate. +use super::{Digest, FixedOutputReset, Output, Reset}; +use std::io; + +/// Abstraction over a writer which hashes the data being written. +#[derive(Debug)] +pub struct HashWriter { + writer: W, + hasher: D, +} + +impl HashWriter { + /// Construct a new `HashWriter` given an existing `writer` by value. + pub fn new(writer: W) -> Self { + Self::new_from_parts(D::new(), writer) + } + + /// Construct a new `HashWriter` given an existing `hasher` and `writer` by value. + pub fn new_from_parts(hasher: D, writer: W) -> Self { + HashWriter { writer, hasher } + } + + /// Replace the writer with another writer + pub fn replace_writer(&mut self, writer: W) { + self.writer = writer; + } + + /// Gets a reference to the underlying hasher + pub fn get_hasher(&self) -> &D { + &self.hasher + } + + /// Gets a reference to the underlying writer + pub fn get_writer(&self) -> &W { + &self.writer + } + + /// Gets a mutable reference to the underlying hasher + /// Updates to the digest are not written to the underlying writer + pub fn get_hasher_mut(&mut self) -> &mut D { + &mut self.hasher + } + + /// Gets a mutable reference to the underlying writer + /// Direct writes to the underlying writer are not hashed + pub fn get_writer_mut(&mut self) -> &mut W { + &mut self.writer + } + + /// Consume the HashWriter and return its hasher + pub fn into_hasher(self) -> D { + self.hasher + } + + /// Consume the HashWriter and return its internal writer + pub fn into_inner_writer(self) -> W { + self.writer + } + + /// Consume the HashWriter and return its hasher and internal writer + pub fn into_parts(self) -> (D, W) { + (self.hasher, self.writer) + } + + /// Retrieve result and consume HashWriter instance. + pub fn finalize(self) -> Output { + self.hasher.finalize() + } + + /// Write result into provided array and consume the HashWriter instance. + pub fn finalize_into(self, out: &mut Output) { + self.hasher.finalize_into(out) + } + + /// Get output size of the hasher + pub fn output_size() -> usize { + ::output_size() + } +} + +impl Clone for HashWriter { + fn clone(&self) -> HashWriter { + HashWriter { + writer: self.writer.clone(), + hasher: self.hasher.clone(), + } + } +} + +impl io::Write for HashWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + let bytes = self.writer.write(buf)?; + + if bytes > 0 { + self.hasher.update(&buf[0..bytes]); + } + + Ok(bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.writer.flush() + } +} + +impl HashWriter { + /// Retrieve result and reset hasher instance. + pub fn finalize_reset(&mut self) -> Output { + Digest::finalize_reset(&mut self.hasher) + } + + /// Write result into provided array and reset the hasher instance. + pub fn finalize_into_reset(&mut self, out: &mut Output) { + Digest::finalize_into_reset(&mut self.hasher, out) + } +} +impl Reset for HashWriter { + fn reset(&mut self) { + Digest::reset(&mut self.hasher) + } +} diff --git a/digest/src/lib.rs b/digest/src/lib.rs index 3d2e06757..a3e6fca86 100644 --- a/digest/src/lib.rs +++ b/digest/src/lib.rs @@ -1,144 +1,308 @@ //! This crate provides traits which describe functionality of cryptographic hash -//! functions. +//! functions and Message Authentication algorithms. //! -//! Traits in this repository can be separated into two levels: -//! - Low level traits: `Input`, `BlockInput`, `Reset`, `FixedOutput`, -//! `VariableOutput`, `ExtendableOutput`. These traits atomically describe -//! available functionality of hash function implementations. -//! - Convenience trait: `Digest`, `DynDigest`. They are wrappers around -//! low level traits for most common hash-function use-cases. +//! Traits in this repository are organized into the following levels: //! -//! Additionally hash functions implement traits from `std`: `Default`, `Clone`, -//! `Write`. (the latter depends on enabled-by-default `std` crate feature) +//! - **High-level convenience traits**: [`Digest`], [`DynDigest`], [`Mac`]. +//! Wrappers around lower-level traits for most common use-cases. Users should +//! usually prefer using these traits. +//! - **Mid-level traits**: [`Update`], [`FixedOutput`], [`FixedOutputReset`], +//! [`ExtendableOutput`], [`ExtendableOutputReset`], [`XofReader`], +//! [`VariableOutput`], [`Reset`], [`KeyInit`], and [`InnerInit`]. These +//! traits atomically describe available functionality of an algorithm. +//! - **Marker traits**: [`HashMarker`], [`MacMarker`]. Used to distinguish +//! different algorithm classes. +//! - **Low-level traits** defined in the [`core_api`] module. These traits +//! operate at a block-level and do not contain any built-in buffering. +//! They are intended to be implemented by low-level algorithm providers only. +//! Usually they should not be used in application-level code. //! -//! The `Digest` trait is the most commonly used trait. +//! Additionally hash functions implement traits from the standard library: +//! [`Default`], [`Clone`], [`Write`][std::io::Write]. The latter is +//! feature-gated behind `std` feature, which is usually enabled by default +//! by hash implementation crates. + #![no_std] -#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")] -pub extern crate generic_array; -#[cfg(feature = "std")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![forbid(unsafe_code)] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" +)] +#![warn(missing_docs, rust_2018_idioms, missing_debug_implementations)] + +#[cfg(feature = "alloc")] #[macro_use] -extern crate std; -#[cfg(feature = "dev")] -pub extern crate blobby; -use generic_array::{ArrayLength, GenericArray}; +extern crate alloc; + #[cfg(feature = "std")] -use std::vec::Vec; +extern crate std; + +#[cfg(feature = "rand_core")] +pub use crypto_common::rand_core; + +#[cfg(feature = "zeroize")] +pub use zeroize; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; #[cfg(feature = "dev")] pub mod dev; + +#[cfg(feature = "core-api")] +pub mod core_api; mod digest; -mod dyn_digest; -mod errors; +#[cfg(feature = "mac")] +mod mac; -pub use digest::Digest; -#[cfg(feature = "std")] -pub use dyn_digest::DynDigest; -pub use errors::InvalidOutputSize; +#[cfg(feature = "core-api")] +pub use block_buffer; +#[cfg(feature = "oid")] +pub use const_oid; +pub use crypto_common; -/// Trait for processing input data -pub trait Input { - /// Digest input data. - /// - /// This method can be called repeatedly, e.g. for processing streaming - /// messages. - fn input>(&mut self, data: B); +#[cfg(feature = "const-oid")] +pub use crate::digest::DynDigestWithOid; +pub use crate::digest::{Digest, DynDigest, HashMarker}; +pub use crypto_common::{array, typenum, typenum::consts, Output, OutputSizeUser, Reset}; +#[cfg(feature = "mac")] +pub use crypto_common::{InnerInit, InvalidLength, Key, KeyInit}; +#[cfg(feature = "mac")] +pub use mac::{CtOutput, Mac, MacError, MacMarker}; + +use core::fmt; + +/// Types which consume data with byte granularity. +pub trait Update { + /// Update state using the provided data. + fn update(&mut self, data: &[u8]); /// Digest input data in a chained manner. - fn chain>(mut self, data: B) -> Self + #[must_use] + fn chain(mut self, data: impl AsRef<[u8]>) -> Self where Self: Sized, { - self.input(data); + self.update(data.as_ref()); self } } -/// Trait to indicate that digest function processes data in blocks of size -/// `BlockSize`. -/// -/// The main usage of this trait is for implementing HMAC generically. -pub trait BlockInput { - type BlockSize: ArrayLength; +/// Trait for hash functions with fixed-size output. +pub trait FixedOutput: Update + OutputSizeUser + Sized { + /// Consume value and write result into provided array. + fn finalize_into(self, out: &mut Output); + + /// Retrieve result and consume the hasher instance. + #[inline] + fn finalize_fixed(self) -> Output { + let mut out = Default::default(); + self.finalize_into(&mut out); + out + } +} + +/// Trait for hash functions with fixed-size output able to reset themselves. +pub trait FixedOutputReset: FixedOutput + Reset { + /// Write result into provided array and reset the hasher state. + fn finalize_into_reset(&mut self, out: &mut Output); + + /// Retrieve result and reset the hasher state. + #[inline] + fn finalize_fixed_reset(&mut self) -> Output { + let mut out = Default::default(); + self.finalize_into_reset(&mut out); + out + } +} + +/// Trait for reader types which are used to extract extendable output +/// from a XOF (extendable-output function) result. +pub trait XofReader { + /// Read output into the `buffer`. Can be called an unlimited number of times. + fn read(&mut self, buffer: &mut [u8]); + + /// Read output into a boxed slice of the specified size. + /// + /// Can be called an unlimited number of times in combination with `read`. + /// + /// `Box<[u8]>` is used instead of `Vec` to save stack space, since + /// they have size of 2 and 3 words respectively. + #[cfg(feature = "alloc")] + fn read_boxed(&mut self, n: usize) -> Box<[u8]> { + let mut buf = vec![0u8; n].into_boxed_slice(); + self.read(&mut buf); + buf + } +} + +/// Trait for hash functions with extendable-output (XOF). +pub trait ExtendableOutput: Sized + Update { + /// Reader + type Reader: XofReader; + + /// Retrieve XOF reader and consume hasher instance. + fn finalize_xof(self) -> Self::Reader; + + /// Finalize XOF and write result into `out`. + fn finalize_xof_into(self, out: &mut [u8]) { + self.finalize_xof().read(out); + } + + /// Compute hash of `data` and write it into `output`. + fn digest_xof(input: impl AsRef<[u8]>, output: &mut [u8]) + where + Self: Default, + { + let mut hasher = Self::default(); + hasher.update(input.as_ref()); + hasher.finalize_xof().read(output); + } + + /// Retrieve result into a boxed slice of the specified size and consume + /// the hasher. + /// + /// `Box<[u8]>` is used instead of `Vec` to save stack space, since + /// they have size of 2 and 3 words respectively. + #[cfg(feature = "alloc")] + fn finalize_boxed(self, output_size: usize) -> Box<[u8]> { + let mut buf = vec![0u8; output_size].into_boxed_slice(); + self.finalize_xof().read(&mut buf); + buf + } } -/// Trait for returning digest result with the fixed size -pub trait FixedOutput { - type OutputSize: ArrayLength; +/// Trait for hash functions with extendable-output (XOF) able to reset themselves. +pub trait ExtendableOutputReset: ExtendableOutput + Reset { + /// Retrieve XOF reader and reset hasher instance state. + fn finalize_xof_reset(&mut self) -> Self::Reader; - /// Retrieve result and consume hasher instance. - fn fixed_result(self) -> GenericArray; + /// Finalize XOF, write result into `out`, and reset the hasher state. + fn finalize_xof_reset_into(&mut self, out: &mut [u8]) { + self.finalize_xof_reset().read(out); + } + + /// Retrieve result into a boxed slice of the specified size and reset + /// the hasher state. + /// + /// `Box<[u8]>` is used instead of `Vec` to save stack space, since + /// they have size of 2 and 3 words respectively. + #[cfg(feature = "alloc")] + fn finalize_boxed_reset(&mut self, output_size: usize) -> Box<[u8]> { + let mut buf = vec![0u8; output_size].into_boxed_slice(); + self.finalize_xof_reset().read(&mut buf); + buf + } } -/// Trait for returning digest result with the variable size -pub trait VariableOutput: core::marker::Sized { - /// Create new hasher instance with the given output size. +/// Trait for hash functions with variable-size output. +pub trait VariableOutput: Sized + Update { + /// Maximum size of output hash in bytes. + const MAX_OUTPUT_SIZE: usize; + + /// Create new hasher instance with the given output size in bytes. /// /// It will return `Err(InvalidOutputSize)` in case if hasher can not return - /// specified output size. It will always return an error if output size - /// equals to zero. + /// hash of the specified output size. fn new(output_size: usize) -> Result; - /// Get output size of the hasher instance provided to the `new` method + /// Get output size in bytes of the hasher instance provided to the `new` method fn output_size(&self) -> usize; - /// Retrieve result via closure and consume hasher. + /// Write result into the output buffer. + /// + /// Returns `Err(InvalidOutputSize)` if `out` size is not equal to + /// `self.output_size()`. + fn finalize_variable(self, out: &mut [u8]) -> Result<(), InvalidBufferSize>; + + /// Compute hash of `data` and write it to `output`. /// - /// Closure is guaranteed to be called, length of the buffer passed to it - /// will be equal to `output_size`. - fn variable_result(self, f: F); - - /// Retrieve result into vector and consume hasher. - #[cfg(feature = "std")] - fn vec_result(self) -> Vec { - let mut buf = Vec::with_capacity(self.output_size()); - self.variable_result(|res| buf.extend_from_slice(res)); + /// Length of the output hash is determined by `output`. If `output` is + /// bigger than `Self::MAX_OUTPUT_SIZE`, this method returns + /// `InvalidOutputSize`. + fn digest_variable( + input: impl AsRef<[u8]>, + output: &mut [u8], + ) -> Result<(), InvalidOutputSize> { + let mut hasher = Self::new(output.len())?; + hasher.update(input.as_ref()); + hasher + .finalize_variable(output) + .map_err(|_| InvalidOutputSize) + } + + /// Retrieve result into a boxed slice and consume hasher. + /// + /// `Box<[u8]>` is used instead of `Vec` to save stack space, since + /// they have size of 2 and 3 words respectively. + #[cfg(feature = "alloc")] + fn finalize_boxed(self) -> Box<[u8]> { + let n = self.output_size(); + let mut buf = vec![0u8; n].into_boxed_slice(); + self.finalize_variable(&mut buf) + .expect("buf length is equal to output_size"); buf } } -/// Trait for describing readers which are used to extract extendable output -/// from XOF (extendable-output function) result. -pub trait XofReader { - /// Read output into the `buffer`. Can be called unlimited number of times. - fn read(&mut self, buffer: &mut [u8]); +/// Trait for hash functions with variable-size output able to reset themselves. +pub trait VariableOutputReset: VariableOutput + Reset { + /// Write result into the output buffer and reset the hasher state. + /// + /// Returns `Err(InvalidOutputSize)` if `out` size is not equal to + /// `self.output_size()`. + fn finalize_variable_reset(&mut self, out: &mut [u8]) -> Result<(), InvalidBufferSize>; + + /// Retrieve result into a boxed slice and reset the hasher state. + /// + /// `Box<[u8]>` is used instead of `Vec` to save stack space, since + /// they have size of 2 and 3 words respectively. + #[cfg(feature = "alloc")] + fn finalize_boxed_reset(&mut self) -> Box<[u8]> { + let n = self.output_size(); + let mut buf = vec![0u8; n].into_boxed_slice(); + self.finalize_variable_reset(&mut buf) + .expect("buf length is equal to output_size"); + buf + } } -/// Trait which describes extendable-output functions (XOF). -pub trait ExtendableOutput: core::marker::Sized { - type Reader: XofReader; +/// Trait for hash functions with customization string for domain separation. +pub trait CustomizedInit: Sized { + /// Create new hasher instance with the given customization string. + fn new_customized(customization: &[u8]) -> Self; +} - /// Retrieve XOF reader and consume hasher instance. - fn xof_result(self) -> Self::Reader; +/// The error type used in variable hash traits. +#[derive(Clone, Copy, Debug, Default)] +pub struct InvalidOutputSize; - /// Retrieve result into vector of specified length. - #[cfg(feature = "std")] - fn vec_result(self, n: usize) -> Vec { - let mut buf = vec![0u8; n]; - self.xof_result().read(&mut buf); - buf +impl fmt::Display for InvalidOutputSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("invalid output size") } } -/// Trait for resetting hash instances -pub trait Reset { - /// Reset hasher instance to its initial state and return current state. - fn reset(&mut self); -} +impl core::error::Error for InvalidOutputSize {} + +/// Buffer length is not equal to hash output size. +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] +pub struct InvalidBufferSize; -#[macro_export] -/// Implements `std::io::Write` trait for implementer of `Input` -macro_rules! impl_write { - ($hasher:ident) => { - #[cfg(feature = "std")] - impl ::std::io::Write for $hasher { - fn write(&mut self, buf: &[u8]) -> ::std::io::Result { - Input::input(self, buf); - Ok(buf.len()) - } - - fn flush(&mut self) -> ::std::io::Result<()> { - Ok(()) - } - } - }; +impl fmt::Display for InvalidBufferSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("invalid buffer length") + } } + +impl core::error::Error for InvalidBufferSize {} + +#[cfg(feature = "std")] +mod hashwriter; +#[cfg(feature = "std")] +pub use hashwriter::HashWriter; +#[cfg(feature = "std")] +mod hashreader; +#[cfg(feature = "std")] +pub use hashreader::HashReader; diff --git a/digest/src/mac.rs b/digest/src/mac.rs new file mode 100644 index 000000000..395f86e09 --- /dev/null +++ b/digest/src/mac.rs @@ -0,0 +1,281 @@ +use crate::{FixedOutput, FixedOutputReset, Update}; +use crypto_common::{Output, OutputSizeUser, Reset}; + +use core::fmt; +use crypto_common::typenum::Unsigned; +use subtle::{Choice, ConstantTimeEq}; + +/// Marker trait for Message Authentication algorithms. +pub trait MacMarker {} + +/// Convenience wrapper trait covering functionality of Message Authentication algorithms. +/// +/// This trait wraps [`Update`], [`FixedOutput`], and [`MacMarker`] traits +/// and provides additional convenience methods. +pub trait Mac: OutputSizeUser + Sized { + /// Update state using the provided data. + fn update(&mut self, data: &[u8]); + + /// Process input data in a chained manner. + #[must_use] + fn chain_update(self, data: impl AsRef<[u8]>) -> Self; + + /// Obtain the result of a [`Mac`] computation as a [`CtOutput`] and consume + /// [`Mac`] instance. + fn finalize(self) -> CtOutput; + + /// Obtain the result of a [`Mac`] computation as a [`CtOutput`] and reset + /// [`Mac`] instance. + fn finalize_reset(&mut self) -> CtOutput + where + Self: FixedOutputReset; + + /// Reset MAC instance to its initial state. + fn reset(&mut self) + where + Self: Reset; + + /// Check if tag/code value is correct for the processed input. + fn verify(self, tag: &Output) -> Result<(), MacError>; + + /// Check if tag/code value is correct for the processed input and reset + /// [`Mac`] instance. + fn verify_reset(&mut self, tag: &Output) -> Result<(), MacError> + where + Self: FixedOutputReset; + + /// Check truncated tag correctness using all bytes + /// of calculated tag. + /// + /// Returns `Error` if `tag` is not valid or not equal in length + /// to MAC's output. + fn verify_slice(self, tag: &[u8]) -> Result<(), MacError>; + + /// Check truncated tag correctness using all bytes + /// of calculated tag and reset [`Mac`] instance. + /// + /// Returns `Error` if `tag` is not valid or not equal in length + /// to MAC's output. + fn verify_slice_reset(&mut self, tag: &[u8]) -> Result<(), MacError> + where + Self: FixedOutputReset; + + /// Check truncated tag correctness using left side bytes + /// (i.e. `tag[..n]`) of calculated tag. + /// + /// Returns `Error` if `tag` is not valid or empty. + fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError>; + + /// Check truncated tag correctness using right side bytes + /// (i.e. `tag[n..]`) of calculated tag. + /// + /// Returns `Error` if `tag` is not valid or empty. + fn verify_truncated_right(self, tag: &[u8]) -> Result<(), MacError>; +} + +impl Mac for T { + #[inline] + fn update(&mut self, data: &[u8]) { + Update::update(self, data); + } + + #[inline] + fn chain_update(mut self, data: impl AsRef<[u8]>) -> Self { + Update::update(&mut self, data.as_ref()); + self + } + + #[inline] + fn finalize(self) -> CtOutput { + CtOutput::new(self.finalize_fixed()) + } + + #[inline(always)] + fn finalize_reset(&mut self) -> CtOutput + where + Self: FixedOutputReset, + { + CtOutput::new(self.finalize_fixed_reset()) + } + + #[inline] + fn reset(&mut self) + where + Self: Reset, + { + Reset::reset(self) + } + + #[inline] + fn verify(self, tag: &Output) -> Result<(), MacError> { + if self.finalize() == tag.into() { + Ok(()) + } else { + Err(MacError) + } + } + + #[inline] + fn verify_reset(&mut self, tag: &Output) -> Result<(), MacError> + where + Self: FixedOutputReset, + { + if self.finalize_reset() == tag.into() { + Ok(()) + } else { + Err(MacError) + } + } + + #[inline] + fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> { + let n = tag.len(); + if n != Self::OutputSize::USIZE { + return Err(MacError); + } + let choice = self.finalize_fixed().ct_eq(tag); + if choice.into() { + Ok(()) + } else { + Err(MacError) + } + } + + #[inline] + fn verify_slice_reset(&mut self, tag: &[u8]) -> Result<(), MacError> + where + Self: FixedOutputReset, + { + let n = tag.len(); + if n != Self::OutputSize::USIZE { + return Err(MacError); + } + let choice = self.finalize_fixed_reset().ct_eq(tag); + if choice.into() { + Ok(()) + } else { + Err(MacError) + } + } + + fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> { + let n = tag.len(); + if n == 0 || n > Self::OutputSize::USIZE { + return Err(MacError); + } + let choice = self.finalize_fixed()[..n].ct_eq(tag); + + if choice.into() { + Ok(()) + } else { + Err(MacError) + } + } + + fn verify_truncated_right(self, tag: &[u8]) -> Result<(), MacError> { + let n = tag.len(); + if n == 0 || n > Self::OutputSize::USIZE { + return Err(MacError); + } + let m = Self::OutputSize::USIZE - n; + let choice = self.finalize_fixed()[m..].ct_eq(tag); + + if choice.into() { + Ok(()) + } else { + Err(MacError) + } + } +} + +/// Fixed size output value which provides a safe [`Eq`] implementation that +/// runs in constant time. +/// +/// It is useful for implementing Message Authentication Codes (MACs). +#[derive(Clone)] +pub struct CtOutput { + bytes: Output, +} + +impl CtOutput { + /// Create a new [`CtOutput`] value. + #[inline(always)] + pub fn new(bytes: Output) -> Self { + Self { bytes } + } + + /// Get reference to the inner [`Output`] array this type wraps. + #[inline(always)] + pub fn as_bytes(&self) -> &Output { + &self.bytes + } + + /// Get the inner [`Output`] array this type wraps. + #[inline(always)] + pub fn into_bytes(&self) -> Output { + self.bytes.clone() + } +} + +impl From> for CtOutput { + #[inline(always)] + fn from(bytes: Output) -> Self { + Self { bytes } + } +} + +impl<'a, T: OutputSizeUser> From<&'a Output> for CtOutput { + #[inline(always)] + fn from(bytes: &'a Output) -> Self { + bytes.clone().into() + } +} + +impl ConstantTimeEq for CtOutput { + #[inline(always)] + fn ct_eq(&self, other: &Self) -> Choice { + self.bytes.ct_eq(&other.bytes) + } +} + +impl PartialEq for CtOutput { + #[inline(always)] + fn eq(&self, x: &CtOutput) -> bool { + self.ct_eq(x).into() + } +} + +impl Eq for CtOutput {} + +impl fmt::Debug for CtOutput { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("CtOutput { ... }") + } +} + +impl Drop for CtOutput { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + use zeroize::Zeroize; + self.bytes.zeroize() + } + } +} + +#[cfg(feature = "zeroize")] +impl zeroize::ZeroizeOnDrop for CtOutput {} + +/// Error type for when the [`Output`] of a [`Mac`] +/// is not equal to the expected value. +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] +pub struct MacError; + +impl fmt::Display for MacError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("MAC tag mismatch") + } +} + +impl core::error::Error for MacError {} diff --git a/elliptic-curve/CHANGELOG.md b/elliptic-curve/CHANGELOG.md new file mode 100644 index 000000000..3f47710eb --- /dev/null +++ b/elliptic-curve/CHANGELOG.md @@ -0,0 +1,811 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.13.8 (2023-11-18) +### Changed +- `SecretKey::from_slice` now allows >=24-bytes ([#1412]) + +[#1412]: https://github.com/RustCrypto/traits/pull/1412 + +## 0.13.7 (2023-11-15) +### Added +- `BatchInvert` and `BatchNormalize` traits ([#1376]) +- `LinearCombinationExt` trait ([#1405]) + +[#1376]: https://github.com/RustCrypto/traits/pull/1376 +[#1405]: https://github.com/RustCrypto/traits/pull/1405 + +## 0.13.6 (2023-10-02) +### Fixed +- Minimum supported `hkdf` version is v0.12.1 ([#1353]) +- Minimum supported `serde_json` version for `jwk` feature is v1.0.47 ([#1354]) +- Minimum supported `tap` version for `bits` feature is v1.0.1 ([#1355]) + +[#1353]: https://github.com/RustCrypto/traits/pull/1353 +[#1354]: https://github.com/RustCrypto/traits/pull/1354 +[#1355]: https://github.com/RustCrypto/traits/pull/1355 + +## 0.13.5 (2023-05-19) +### Changed +- Faster `PublicKey::from_encoded_point` ([#1310]) + +### Fixed +- `alloc`+`arithmetic` features w/o `sec1` feature ([#1301]) + +[#1301]: https://github.com/RustCrypto/traits/pull/1301 +[#1310]: https://github.com/RustCrypto/traits/pull/1310 + +## 0.13.4 (2023-04-08) +### Changed +- Bump `hex-literal` to v0.4 ([#1295]) + +### Fixed +- `NonZeroScalar::from_slice` ([#1296]) +- `ScalarPrimitive::from_slice` ([#1296]) + +[#1295]: https://github.com/RustCrypto/traits/pull/1295 +[#1296]: https://github.com/RustCrypto/traits/pull/1296 + +## 0.13.3 (2023-04-04) +### Added +- Impl `AssociatedAlgorithmIdentifier` for `SecretKey` and `PublicKey` ([#1286]) + +### Changed +- Update OSSWU code ([#1157]) +- Bump `pkcs8` to v0.10.2 ([#1291]) + +### Fixed +- `FieldBytesEncoding` provided impl ([#1287]) + +[#1157]: https://github.com/RustCrypto/traits/pull/1157 +[#1286]: https://github.com/RustCrypto/traits/pull/1286 +[#1287]: https://github.com/RustCrypto/traits/pull/1287 +[#1291]: https://github.com/RustCrypto/traits/pull/1291 + +## 0.13.2 (2023-03-08) +### Added +- Weakly activate `pkcs8?/std` ([#1263]) +- More `PublicKey` <-> SEC1 conversions ([#1272]) + +[#1263]: https://github.com/RustCrypto/traits/pull/1263 +[#1272]: https://github.com/RustCrypto/traits/pull/1272 + +## 0.13.1 (2023-03-01) +### Added +- `SecretKey::from_slice` short input support ([#1256]) + +[#1256]: https://github.com/RustCrypto/traits/pull/1256 + +## 0.13.0 (2023-02-28) +### Added +- `PublicKey::to_sec1_bytes` ([#1102]) +- Forward `std` feature to `sec1` dependency ([#1131]) +- `NonIdentity` wrapper type ([#1176]) +- Impl `serde` traits for `NonZeroScalar` ([#1178]) +- `MulByGenerator` trait ([#1198]) +- `NonZeroScalar::invert_vartime` ([#1207]) +- `BlindedScalar` type ([#1208]) +- `point::Double` trait ([#1218]) +- `FieldBytesEncoding` trait ([#1235]) +- `Invert::invert_vartime` ([#1239]) + +### Changed +- Allow bigger `c1` constant in `OsswuMapParams` ([#1024]) +- Rename `Curve::UInt` => `Curve::Uint` ([#1191]) +- Use weak feature activation ([#1192], [#1194]) +- Consolidate `CurveArithmetic` trait ([#1196]) +- Rename `SecretKey::to_pem` => `::to_sec1_pem` ([#1202]) +- Rename `ScalarCore` to `ScalarPrimitive` ([#1203]) +- Use `CryptoRngCore` trait ([#1206]) +- Refactor field element decoding/encoding ([#1220]) +- Update VOPRF identifier type ([#1175]) +- Rename `SecretKey::as_scalar_core` => `::as_scalar_primitive` ([#1228]) +- Rename `Reduce::from_bytes_reduced` => `::reduce_bytes` ([#1225], [#1229]) +- Consolidate `AffineCoordinates` trait ([#1237]) +- Allow multiple `dst`s in the `hash2curve` API ([#1238]) +- Have `serde` feature activate `pkcs8` ([#1245]) +- Dependency upgrades: + - `base16ct` ([#1254]) + - `crypto-bigint` v0.5 ([#1251]) + - `ff` and `group` v0.13 ([#1166]) + - `pem-rfc7468` v0.7 ([#1251]) + - `pkcs8` v0.10 ([#1251]) + - `sec1` v0.7 ([#1251]) + - `serdect` v0.2 ([#1251]) + +### Removed +- `impl_field_element!` macro ([#1165]) +- Direct `der` crate dependency ([#1195]) +- `AffineArithmetic`, `ProjectiveArithmetic`, `ScalarArithmetic` traits ([#1196]) +- Toplevel re-exports except for `AffinePoint`, `ProjectivePoint`, and `Scalar` ([#1223]) +- `Reduce` methods ([#1225]) +- Blanket impl for `Invert` ([#1242]) + +[#1024]: https://github.com/RustCrypto/traits/pull/1024 +[#1102]: https://github.com/RustCrypto/traits/pull/1102 +[#1131]: https://github.com/RustCrypto/traits/pull/1131 +[#1165]: https://github.com/RustCrypto/traits/pull/1165 +[#1166]: https://github.com/RustCrypto/traits/pull/1166 +[#1175]: https://github.com/RustCrypto/traits/pull/1175 +[#1176]: https://github.com/RustCrypto/traits/pull/1176 +[#1178]: https://github.com/RustCrypto/traits/pull/1178 +[#1191]: https://github.com/RustCrypto/traits/pull/1191 +[#1192]: https://github.com/RustCrypto/traits/pull/1192 +[#1194]: https://github.com/RustCrypto/traits/pull/1194 +[#1195]: https://github.com/RustCrypto/traits/pull/1195 +[#1196]: https://github.com/RustCrypto/traits/pull/1196 +[#1198]: https://github.com/RustCrypto/traits/pull/1198 +[#1202]: https://github.com/RustCrypto/traits/pull/1202 +[#1203]: https://github.com/RustCrypto/traits/pull/1203 +[#1206]: https://github.com/RustCrypto/traits/pull/1206 +[#1207]: https://github.com/RustCrypto/traits/pull/1207 +[#1208]: https://github.com/RustCrypto/traits/pull/1208 +[#1218]: https://github.com/RustCrypto/traits/pull/1218 +[#1220]: https://github.com/RustCrypto/traits/pull/1220 +[#1223]: https://github.com/RustCrypto/traits/pull/1223 +[#1225]: https://github.com/RustCrypto/traits/pull/1225 +[#1228]: https://github.com/RustCrypto/traits/pull/1228 +[#1229]: https://github.com/RustCrypto/traits/pull/1229 +[#1235]: https://github.com/RustCrypto/traits/pull/1235 +[#1237]: https://github.com/RustCrypto/traits/pull/1237 +[#1238]: https://github.com/RustCrypto/traits/pull/1238 +[#1239]: https://github.com/RustCrypto/traits/pull/1239 +[#1242]: https://github.com/RustCrypto/traits/pull/1242 +[#1245]: https://github.com/RustCrypto/traits/pull/1245 +[#1251]: https://github.com/RustCrypto/traits/pull/1251 +[#1254]: https://github.com/RustCrypto/traits/pull/1254 + +## 0.12.3 (2022-08-01) +### Added +- Aliases for SEC1 compressed/uncompressed points ([#1067]) + +### Fixed +- `arithmetic` + `serde` feature combo ([#1066]) + +[#1066]: https://github.com/RustCrypto/traits/pull/1066 +[#1067]: https://github.com/RustCrypto/traits/pull/1067 + +## 0.12.2 (2022-07-01) +### Changed +- Bump `crypto-bigint` to v0.4.8 ([#1039]) + +[#1039]: https://github.com/RustCrypto/traits/pull/1039 + +## 0.12.1 (2022-06-12) +### Added +- `impl_field_element!` macro ([#1021]) +- Generic impl of complete prime order formulas ([#1022]) + +### Changed +- Bump `crypto-bigint` to v0.4.4 ([#1018], [#1020]) + +[#1018]: https://github.com/RustCrypto/traits/pull/1018 +[#1020]: https://github.com/RustCrypto/traits/pull/1020 +[#1021]: https://github.com/RustCrypto/traits/pull/1021 +[#1022]: https://github.com/RustCrypto/traits/pull/1022 + +## 0.12.0 (2022-05-08) +### Added +- `ecdh::SharedSecret::extract` HKDF helper ([#1007]) + +### Changed +- Bump `digest` dependency to v0.10 ([#883], [#904]) +- Make `NonZeroScalar::invert` infallible ([#894]) +- `ToCompactEncodedPoint` now returns `CtOption` ([#895]) +- Move `hash2field` into `hash2curve` module ([#903]) +- Bump `ff` and `group` dependencies to v0.12 ([#994]) +- Use `serdect` crate ([#996]) +- Replace `AlgorithmParamters` with `AssociatedOid` ([#1001]) +- Bump `crypto-bigint` dependency to v0.4 ([#1005]) +- Bump `der` dependency to v0.6 ([#1006]) +- Bump `pkcs8` dependency to v0.9 ([#1006]) +- Bump `sec1` dependency to v0.3 ([#1006]) +- Bump `pem-rfc7468` dependency to v0.6 ([#1009]) + +### Removed +- `Zeroize` impl from `ecdh::SharedSecret` ([#978]) + +[#883]: https://github.com/RustCrypto/traits/pull/883 +[#894]: https://github.com/RustCrypto/traits/pull/894 +[#895]: https://github.com/RustCrypto/traits/pull/895 +[#903]: https://github.com/RustCrypto/traits/pull/903 +[#904]: https://github.com/RustCrypto/traits/pull/904 +[#978]: https://github.com/RustCrypto/traits/pull/978 +[#994]: https://github.com/RustCrypto/traits/pull/994 +[#996]: https://github.com/RustCrypto/traits/pull/996 +[#1001]: https://github.com/RustCrypto/traits/pull/1001 +[#1005]: https://github.com/RustCrypto/traits/pull/1005 +[#1006]: https://github.com/RustCrypto/traits/pull/1006 +[#1007]: https://github.com/RustCrypto/traits/pull/1007 +[#1009]: https://github.com/RustCrypto/traits/pull/1009 + +## 0.11.12 (2022-01-30) +### Changed +- Disable `bits` feature on docs.rs due to nightly breakage ([#927]) + +[#927]: https://github.com/RustCrypto/traits/pull/927 + +## 0.11.11 (2022-01-30) +- No changes; triggering a docs.rs rebuild + +## 0.11.10 (2022-01-27) +### Changed +- Revert [#884] to support a wider range of `zeroize` versions ([#923]) + +[#923]: https://github.com/RustCrypto/traits/pull/891 + +## 0.11.9 (2022-01-17) [YANKED] +### Changed +- Activate `bits`, `hash2curve`, and `voprf` features on docs.rs ([#891]) + +[#891]: https://github.com/RustCrypto/traits/pull/891 + +## 0.11.8 (2022-01-15) [YANKED] +### Added +- Impl `ZeroizeOnDrop` on appropriate items ([#884]) + +### Changed +- Use the `base16ct` crate for hex serialization ([#886], [#887], [#888]) + +[#884]: https://github.com/RustCrypto/traits/pull/884 +[#886]: https://github.com/RustCrypto/traits/pull/886 +[#887]: https://github.com/RustCrypto/traits/pull/887 +[#888]: https://github.com/RustCrypto/traits/pull/888 + +## 0.11.7 (2022-01-14) [YANKED] +### Added +- Initial hash-to-field support ([#854], [#855], [#871], [#874]) +- Initial hash-to-curve support ([#865], [#876]) +- Impl `Mul` for `NonZeroScalar` * `NonZeroScalar` ([#857], [#862]) +- `Reduce::from_*e_digest_reduced` ([#869]) +- `VoprfParameters` trait ([#878]) + +[#854]: https://github.com/RustCrypto/traits/pull/854 +[#855]: https://github.com/RustCrypto/traits/pull/855 +[#857]: https://github.com/RustCrypto/traits/pull/857 +[#862]: https://github.com/RustCrypto/traits/pull/862 +[#865]: https://github.com/RustCrypto/traits/pull/865 +[#869]: https://github.com/RustCrypto/traits/pull/869 +[#871]: https://github.com/RustCrypto/traits/pull/871 +[#874]: https://github.com/RustCrypto/traits/pull/874 +[#876]: https://github.com/RustCrypto/traits/pull/876 +[#878]: https://github.com/RustCrypto/traits/pull/878 + +## 0.11.6 (2021-12-20) +### Added +- Type conversions chart ([#852]) + +[#852]: https://github.com/RustCrypto/traits/pull/852 + +## 0.11.5 (2021-12-05) +### Changed +- Revised `LinearCombination` trait ([#835]) + +[#835]: https://github.com/RustCrypto/traits/pull/835 + +## 0.11.4 (2021-12-04) [YANKED] +### Added +- `LinearCombination` trait ([#832]) + +[#832]: https://github.com/RustCrypto/traits/pull/832 + +## 0.11.3 (2021-12-03) [YANKED] +### Added +- `ReduceNonZero` trait ([#827]) + +[#827]: https://github.com/RustCrypto/traits/pull/827 + +## 0.11.2 (2021-12-03) [YANKED] +### Changed +- Bump `pem-rfc7468` dependency to v0.3 ([#825]) + +[#825]: https://github.com/RustCrypto/traits/pull/825 + +## 0.11.1 (2021-11-21) [YANKED] +### Added +- `NonZeroScalar::from_uint` ([#822]) + +[#822]: https://github.com/RustCrypto/traits/pull/822 + +## 0.11.0 (2021-11-19) [YANKED] +### Added +- `ScalarCore` type ([#732]) +- `PrimeCurveArithmetic` trait ([#739]) +- SEC1 private key support ([#762]) +- `Reduce` trait ([#768]) +- Re-export `ff` and `PrimeField` ([#796]) +- `Encoding` bound on `Curve::UInt` ([#806]) +- `scalar::IsHigh` trait ([#814], [#815]) +- `Neg` impl for `NonZeroScalar` ([#816]) +- `AffineXCoordinate` trait ([#817]) +- `serde` support for scalar and `PublicKey` types ([#818]) + +### Changed +- Bump `ff` + `group` to v0.11 ([#730]) +- Make `SecretKey::to_jwk_string` self-zeroizing ([#742]) +- Use `sec1` crate's `EncodedPoint` ([#771]) +- Make `FromEncodedPoint` return a `CtOption` ([#782]) +- Rust 2021 edition upgrade; MSRV to 1.56 ([#795]) +- Bump `crypto-bigint` dependency to v0.3 ([#807]) +- Use `sec1` crate for `pkcs8` support ([#809]) +- Bump `spki` dependency to v0.5 release ([#810]) +- `NonZeroScalar` is now bounded on `ScalarArithmetic` instead of + `ProjectiveArithmetic` ([#812]) + +### Fixed +- `Zeroize` impl on `NonZeroScalar` ([#785]) + +[#730]: https://github.com/RustCrypto/traits/pull/730 +[#732]: https://github.com/RustCrypto/traits/pull/732 +[#739]: https://github.com/RustCrypto/traits/pull/739 +[#742]: https://github.com/RustCrypto/traits/pull/742 +[#762]: https://github.com/RustCrypto/traits/pull/762 +[#768]: https://github.com/RustCrypto/traits/pull/768 +[#771]: https://github.com/RustCrypto/traits/pull/771 +[#782]: https://github.com/RustCrypto/traits/pull/782 +[#785]: https://github.com/RustCrypto/traits/pull/785 +[#795]: https://github.com/RustCrypto/traits/pull/795 +[#796]: https://github.com/RustCrypto/traits/pull/796 +[#806]: https://github.com/RustCrypto/traits/pull/806 +[#807]: https://github.com/RustCrypto/traits/pull/807 +[#809]: https://github.com/RustCrypto/traits/pull/809 +[#810]: https://github.com/RustCrypto/traits/pull/810 +[#812]: https://github.com/RustCrypto/traits/pull/812 +[#814]: https://github.com/RustCrypto/traits/pull/814 +[#815]: https://github.com/RustCrypto/traits/pull/815 +[#816]: https://github.com/RustCrypto/traits/pull/816 +[#817]: https://github.com/RustCrypto/traits/pull/817 +[#818]: https://github.com/RustCrypto/traits/pull/818 + +## 0.10.6 (2021-08-23) +### Changed +- Bump `crypto-bigint` dependency to v0.2.4 ([#710]) + +[#710]: https://github.com/RustCrypto/traits/pull/710 + +## 0.10.5 (2021-07-20) +### Changed +- Pin `zeroize` dependency to v1.4 and `subtle` to v2.4 ([#689]) + +[#689]: https://github.com/RustCrypto/traits/pull/689 + +## 0.10.4 (2021-07-12) +### Added +- Re-export `rand_core` ([#683]) + +[#683]: https://github.com/RustCrypto/traits/pull/683 + +## 0.10.3 (2021-06-21) +### Changed +- Bump `crypto-bigint` to v0.2.1 ([#673]) + +[#673]: https://github.com/RustCrypto/traits/pull/673 + +## 0.10.2 (2021-06-14) [YANKED] +### Added +- `ConstantTimeEq` impl for `NonZeroScalar` ([#669]) + +[#669]: https://github.com/RustCrypto/traits/pull/669 + +## 0.10.1 (2021-06-09) [YANKED] +### Added +- Explicit `Copy` bounds on `PublicKey` ([#667]) + +[#667]: https://github.com/RustCrypto/traits/pull/667 + +## 0.10.0 (2021-06-07) [YANKED] +### Added +- `ScalarBytes::from_uint` ([#651]) +- `dev::ScalarBytes` ([#652]) +- `ScalarArithmetic` trait ([#654]) +- `AffineArithmetic` trait ([#658]) +- `PointCompaction` trait and SEC1 tag support ([#659]) + +### Changed +- Bump `ff` and `group` to v0.10; MSRV 1.51+ ([#643]) +- Merge `Curve` and `Order` traits ([#644]) +- Use `crypto-bigint` to represent `Curve::ORDER` ([#645]) +- Source `FieldSize` from `C::UInt` type ([#646]) +- Impl `ScalarBytes` using `C::UInt` ([#647]) +- Make `ScalarBytes` the `SecretKey` internal repr ([#649]) +- Bump `crypto-bigint` to v0.2 ([#662]) +- Bump `pkcs8` to v0.7 ([#662]) + +### Removed +- `util` module ([#648]) + +[#643]: https://github.com/RustCrypto/traits/pull/643 +[#644]: https://github.com/RustCrypto/traits/pull/644 +[#645]: https://github.com/RustCrypto/traits/pull/645 +[#646]: https://github.com/RustCrypto/traits/pull/646 +[#647]: https://github.com/RustCrypto/traits/pull/647 +[#648]: https://github.com/RustCrypto/traits/pull/648 +[#649]: https://github.com/RustCrypto/traits/pull/649 +[#651]: https://github.com/RustCrypto/traits/pull/651 +[#652]: https://github.com/RustCrypto/traits/pull/652 +[#654]: https://github.com/RustCrypto/traits/pull/654 +[#658]: https://github.com/RustCrypto/traits/pull/658 +[#659]: https://github.com/RustCrypto/traits/pull/659 +[#662]: https://github.com/RustCrypto/traits/pull/662 + +## 0.9.12 (2021-05-18) +### Added +- `Ord` and `PartialOrd` impls on `PublicKey` ([#637]) + +[#637]: https://github.com/RustCrypto/traits/pull/637 + +## 0.9.11 (2021-04-21) +### Added +- Impl `subtle` traits on `ScalarBytes` ([#612]) + +### Fixed +- Always re-export ScalarBytes ([#613]) + +[#612]: https://github.com/RustCrypto/traits/pull/612 +[#613]: https://github.com/RustCrypto/traits/pull/613 + +## 0.9.10 (2021-04-21) +### Added +- `ScalarBytes` type ([#610]) + +[#610]: https://github.com/RustCrypto/traits/pull/610 + +## 0.9.9 (2021-04-21) [YANKED] +### Added +- `Order::is_scalar_repr_in_range` ([#608]) + +[#608]: https://github.com/RustCrypto/traits/pull/608 + +## 0.9.8 (2021-04-21) +### Added +- Define `Order` for `MockCurve` ([#606]) + +[#606]: https://github.com/RustCrypto/traits/pull/606 + +## 0.9.7 (2021-04-21) +### Added +- `Order` trait ([#603]) + +### Fixed +- Warnings from `pkcs8` imports ([#604]) + +[#603]: https://github.com/RustCrypto/traits/pull/603 +[#604]: https://github.com/RustCrypto/traits/pull/604 + +## 0.9.6 (2021-03-22) +### Changed +- Bump `pkcs8` dependency to v0.6 ([#585]) + +[#585]: https://github.com/RustCrypto/traits/pull/585 + +## 0.9.5 (2021-03-17) [YANKED] +### Added +- Implement `{to,char}_le_bits` for `MockCurve` ([#565]) +- Implement `one()` for mock `Scalar` ([#566]) + +### Changed +- Use string-based OID constants ([#561]) +- Bump `base64ct` dependency to v1.0 ([#581]) + +[#561]: https://github.com/RustCrypto/traits/pull/561 +[#565]: https://github.com/RustCrypto/traits/pull/565 +[#566]: https://github.com/RustCrypto/traits/pull/566 +[#581]: https://github.com/RustCrypto/traits/pull/581 + +## 0.9.4 (2021-02-18) [YANKED] +### Fixed +- Breakage related to the `pkcs8` v0.5.1 crate ([#556]) + +[#556]: https://github.com/RustCrypto/traits/pull/556 + +## 0.9.3 (2021-02-16) [YANKED] +### Changed +- Bump `pkcs8` dependency to v0.5.0 ([#549]) + +### Fixed +- Workaround for bitvecto-rs/bitvec#105 ([#550]) + +[#549]: https://github.com/RustCrypto/traits/pull/549 +[#550]: https://github.com/RustCrypto/traits/pull/550 + +## 0.9.2 (2021-02-12) [YANKED] +### Changed +- Flatten `weierstrass` module ([#542]) + +[#542]: https://github.com/RustCrypto/traits/pull/542 + +## 0.9.1 (2021-02-11) [YANKED] +### Removed +- `BitView` re-export ([#540]) + +[#540]: https://github.com/RustCrypto/traits/pull/540 + +## 0.9.0 (2021-02-10) [YANKED] +### Added +- JWK support ([#483]) +- `sec1::ValidatePublicKey` trait ([#485]) +- `hazmat` crate feature ([#487]) +- `Result` alias ([#534]) + +### Changed +- Bump `ff` and `group` crates to v0.9 ([#452]) +- Simplify ECDH trait bounds ([#475]) +- Flatten API ([#487]) +- Bump `pkcs8` crate dependency to v0.4 ([#493]) + +### Removed +- Direct `bitvec` dependency ([#484]) +- `FromDigest` trait ([#532]) + +[#452]: https://github.com/RustCrypto/traits/pull/452 +[#475]: https://github.com/RustCrypto/traits/pull/475 +[#483]: https://github.com/RustCrypto/traits/pull/483 +[#484]: https://github.com/RustCrypto/traits/pull/484 +[#485]: https://github.com/RustCrypto/traits/pull/485 +[#487]: https://github.com/RustCrypto/traits/pull/487 +[#493]: https://github.com/RustCrypto/traits/pull/493 +[#432]: https://github.com/RustCrypto/traits/pull/432 +[#532]: https://github.com/RustCrypto/traits/pull/532 +[#534]: https://github.com/RustCrypto/traits/pull/534 + +## 0.8.5 (2021-02-17) +### Fixed +- Workaround for bitvecto-rs/bitvec#105 ([#553]) + +[#553]: https://github.com/RustCrypto/traits/pull/553 + +## 0.8.4 (2020-12-23) +### Fixed +- Rust `nightly` regression ([#432]) + +[#432]: https://github.com/RustCrypto/traits/pull/432 + +## 0.8.3 (2020-12-22) +### Fixed +- Regression in combination of `pem`+`zeroize` features ([#429]) + +[#429]: https://github.com/RustCrypto/traits/pull/429 + +## 0.8.2 (2020-12-22) [YANKED] +### Added +- Low-level ECDH API ([#418]) +- `dev` module ([#419]) +- Impl `pkcs8::ToPrivateKey` for `SecretKey` ([#423]) +- Impl `pkcs8::ToPublicKey` for `PublicKey` ([#427]) + +### Changed +- Bump `subtle` dependency to 2.4.0 ([#414]) +- Bump `pkcs8` dependency to v0.3.3 ([#425]) +- Use `der` crate to parse `SecretKey` ([#422]) + +### Fixed +- Make `PublicKey::from_encoded_point` go through `PublicKey::from_affine` ([#416]) + +[#414]: https://github.com/RustCrypto/traits/pull/414 +[#416]: https://github.com/RustCrypto/traits/pull/416 +[#418]: https://github.com/RustCrypto/traits/pull/418 +[#419]: https://github.com/RustCrypto/traits/pull/419 +[#422]: https://github.com/RustCrypto/traits/pull/422 +[#423]: https://github.com/RustCrypto/traits/pull/423 +[#425]: https://github.com/RustCrypto/traits/pull/425 +[#427]: https://github.com/RustCrypto/traits/pull/427 + +## 0.8.1 (2020-12-16) [YANKED] +### Fixed +- Builds on Rust `nightly` compiler ([#412]) + +[#412]: https://github.com/RustCrypto/traits/pull/412 + +## 0.8.0 (2020-12-16) [YANKED] +### Added +- Impl `subtle::ConditionallySelectable` for `sec1::EncodedPoint` ([#409]) +- `sec1::EncodedPoint::identity()` method ([#408]) +- `sec1::Coordinates::tag` method ([#407]) +- Support for SEC1 identity encoding ([#401]) + +### Changed +- Bump `pkcs8` crate dependency to v0.3 ([#405]) +- Ensure `PublicKey` is not the identity point ([#404]) +- Have `SecretKey::secret_scalar` return `NonZeroScalar` ([#402]) + +### Removed +- `SecretKey::secret_value` ([#403]) + +[#409]: https://github.com/RustCrypto/traits/pull/409 +[#408]: https://github.com/RustCrypto/traits/pull/408 +[#407]: https://github.com/RustCrypto/traits/pull/407 +[#405]: https://github.com/RustCrypto/traits/pull/405 +[#404]: https://github.com/RustCrypto/traits/pull/404 +[#403]: https://github.com/RustCrypto/traits/pull/403 +[#402]: https://github.com/RustCrypto/traits/pull/402 +[#401]: https://github.com/RustCrypto/traits/pull/401 + +## 0.7.1 (2020-12-07) +### Changed +- Have `SecretKey::secret_value` always return `NonZeroScalar` ([#390]) + +[#390]: https://github.com/RustCrypto/traits/pull/390 + +## 0.7.0 (2020-12-06) [YANKED] +### Added +- Impl `pkcs8::FromPublicKey` for `PublicKey` ([#385]) +- Impl `pkcs8::FromPrivateKey` trait for `SecretKey` ([#381], [#383]) +- PKCS#8 PEM support ([#382]) +- `SecretKey::secret_value()` method ([#375]) +- `PublicKey` type ([#363], [#366]) + +### Changed +- Rename `PublicKey::from_bytes()` to `::from_sec1_bytes()` ([#376]) +- `sec1::EncodedPoint` uses `Option` instead of `subtle::CtOption` ([#367]) +- Bump `const-oid` to v0.3; MSRV 1.46+ ([#365], [#381]) + +### Fixed +- `ecdh` rustdoc ([#364]) + +[#385]: https://github.com/RustCrypto/traits/pull/385 +[#383]: https://github.com/RustCrypto/traits/pull/383 +[#382]: https://github.com/RustCrypto/traits/pull/382 +[#381]: https://github.com/RustCrypto/traits/pull/381 +[#376]: https://github.com/RustCrypto/traits/pull/376 +[#375]: https://github.com/RustCrypto/traits/pull/375 +[#367]: https://github.com/RustCrypto/traits/pull/367 +[#366]: https://github.com/RustCrypto/traits/pull/366 +[#365]: https://github.com/RustCrypto/traits/pull/365 +[#364]: https://github.com/RustCrypto/traits/pull/364 +[#363]: https://github.com/RustCrypto/traits/pull/363 + +## 0.6.6 (2020-10-08) +### Added +- Derive `Clone` on `SecretBytes` ([#330]) + +[#300]: https://github.com/RustCrypto/traits/pull/300 + +## 0.6.5 (2020-10-08) +### Fixed +- Work around `nightly-2020-10-06` breakage ([#328]) + +[#328]: https://github.com/RustCrypto/traits/pull/328 + +## 0.6.4 (2020-10-08) +### Added +- Impl `From>` for `FieldBytes` ([#326]) + +[#326]: https://github.com/RustCrypto/traits/pull/326 + +## 0.6.3 (2020-10-08) +### Added +- `SecretBytes` newtype ([#324]) + +[#324]: https://github.com/RustCrypto/traits/pull/324 + +## 0.6.2 (2020-09-24) +### Added +- `sec1::EncodedPoint::to_untagged_bytes()` method ([#312]) + +[#312]: https://github.com/RustCrypto/traits/pull/312 + +## 0.6.1 (2020-09-21) +### Fixed +- `sec1::EncodedPoint::decompress` ([#309]) + +[#309]: https://github.com/RustCrypto/traits/pull/309 + +## 0.6.0 (2020-09-11) [YANKED] +### Added +- `arithmetic` feature ([#293]) +- Generic curve/field arithmetic using the `ff` and `group` crates + ([#287], [#291], [#292]) +- `sec1::Coordinates` ([#286]) +- `weierstrass::point::Compression` trait ([#283], [#300]) +- Arithmetic helper functions ([#281]) +- `digest` feature and `FromDigest` trait ([#279]) +- impl `Deref` for `NonZeroScalar` ([#278]) +- Conditionally impl `Invert` for `NonZeroScalar` ([#277]) +- `NonZeroScalar::to_bytes` ([#276]) +- `EncodedPoint::decompress` ([#275]) +- `sec1::Tag` ([#270]) +- `weierstrass::point::Decompress` trait ([#266]) +- `alloc` feature + `EncodedPoint::to_bytes()` ([#265]) + +### Changed +- Renamed `Arithmetic` trait to `point::ProjectiveArithmetic` ([#300]) +- Replaced `Arithmetic::Scalar` and `Arithmetic::AffinePoint` + with `Scalar` and `AffinePoint` ([#300]) +- Made `SecretKey` inner type generic ([#297]) +- Renamed `ElementBytes` to `FieldBytes` ([#296]) +- MSRV 1.44 ([#292]) +- Minimum `subtle` version now v2.3 ([#290]) +- Renamed `Curve::ElementSize` to `::FieldSize` ([#282]) +- Refactor `PublicKey` into `sec1::EncodedPoint` ([#264]) + +### Removed +- `FromBytes` trait ([#300]) +- `Generate` trait ([#295]) + +[#300]: https://github.com/RustCrypto/traits/pull/300 +[#297]: https://github.com/RustCrypto/traits/pull/297 +[#296]: https://github.com/RustCrypto/traits/pull/296 +[#295]: https://github.com/RustCrypto/traits/pull/295 +[#293]: https://github.com/RustCrypto/traits/pull/293 +[#292]: https://github.com/RustCrypto/traits/pull/292 +[#291]: https://github.com/RustCrypto/traits/pull/291 +[#290]: https://github.com/RustCrypto/traits/pull/290 +[#287]: https://github.com/RustCrypto/traits/pull/293 +[#286]: https://github.com/RustCrypto/traits/pull/286 +[#283]: https://github.com/RustCrypto/traits/pull/283 +[#282]: https://github.com/RustCrypto/traits/pull/282 +[#281]: https://github.com/RustCrypto/traits/pull/281 +[#279]: https://github.com/RustCrypto/traits/pull/279 +[#278]: https://github.com/RustCrypto/traits/pull/278 +[#277]: https://github.com/RustCrypto/traits/pull/277 +[#276]: https://github.com/RustCrypto/traits/pull/276 +[#275]: https://github.com/RustCrypto/traits/pull/275 +[#270]: https://github.com/RustCrypto/traits/pull/270 +[#266]: https://github.com/RustCrypto/traits/pull/266 +[#265]: https://github.com/RustCrypto/traits/pull/265 +[#264]: https://github.com/RustCrypto/traits/pull/264 + +## 0.5.0 (2020-08-10) +### Added +- `Arithmetic` trait ([#219]) +- `Generate` trait ([#220], [#226]) +- Toplevel `Curve` trait ([#223]) +- `Invert` trait ([#228]) +- `FromPublicKey` trait ([#229], [#248]) +- Re-export `zeroize` ([#233]) +- OID support ([#240], [#245]) +- `NonZeroScalar` type ([#241]) +- `Generator` trait ([#241]) +- `weierstrass::PublicKey::compress` method ([#243]) +- Derive `Clone` on `SecretKey` ([#244]) +- Generic Elliptic Curve Diffie-Hellman support ([#251]) + +### Changed +- Moved repo to https://github.com/RustCrypto/traits ([#213]) +- Rename `ScalarBytes` to `ElementBytes` ([#246]) +- Rename `CompressedCurvePoint`/`UncompressedCurvePoint` to + `CompressedPoint`/`UncompressedPoint` + +[#213]: https://github.com/RustCrypto/traits/pull/213 +[#219]: https://github.com/RustCrypto/traits/pull/219 +[#220]: https://github.com/RustCrypto/traits/pull/220 +[#223]: https://github.com/RustCrypto/traits/pull/223 +[#226]: https://github.com/RustCrypto/traits/pull/226 +[#228]: https://github.com/RustCrypto/traits/pull/228 +[#229]: https://github.com/RustCrypto/traits/pull/229 +[#233]: https://github.com/RustCrypto/traits/pull/233 +[#240]: https://github.com/RustCrypto/traits/pull/240 +[#241]: https://github.com/RustCrypto/traits/pull/241 +[#243]: https://github.com/RustCrypto/traits/pull/243 +[#244]: https://github.com/RustCrypto/traits/pull/244 +[#245]: https://github.com/RustCrypto/traits/pull/245 +[#246]: https://github.com/RustCrypto/traits/pull/246 +[#248]: https://github.com/RustCrypto/traits/pull/248 +[#251]: https://github.com/RustCrypto/traits/pull/251 + +## 0.4.0 (2020-06-04) +### Changed +- Bump `generic-array` dependency from v0.12 to v0.14 + +## 0.3.0 (2020-01-15) +### Added +- `Scalar` struct type + +### Changed +- Repository moved to + +### Removed +- Curve definitions/arithmetic extracted out into per-curve crates + +## 0.2.0 (2019-12-11) +### Added +- `secp256r1` (P-256) point compression and decompression + +### Changed +- Bump MSRV to 1.37 + +## 0.1.0 (2019-12-06) +- Initial release diff --git a/elliptic-curve/Cargo.toml b/elliptic-curve/Cargo.toml new file mode 100644 index 000000000..775da9017 --- /dev/null +++ b/elliptic-curve/Cargo.toml @@ -0,0 +1,77 @@ +[package] +name = "elliptic-curve" +version = "0.14.0-rc.1" +description = """ +General purpose Elliptic Curve Cryptography (ECC) support, including types +and traits for representing various elliptic curve forms, scalars, points, +and public/secret keys composed thereof. +""" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +homepage = "https://github.com/RustCrypto/traits/tree/master/elliptic-curve" +repository = "https://github.com/RustCrypto/traits" +readme = "README.md" +categories = ["cryptography", "no-std"] +keywords = ["crypto", "ecc", "elliptic", "weierstrass"] +edition = "2021" +rust-version = "1.81" + +[dependencies] +base16ct = "0.2" +crypto-bigint = { version = "0.6.0-rc.6", default-features = false, features = ["rand_core", "hybrid-array", "zeroize"] } +hybrid-array = { version = "0.2", default-features = false, features = ["zeroize"] } +rand_core = { version = "0.6.4", default-features = false } +subtle = { version = "2.6", default-features = false } +zeroize = { version = "1.7", default-features = false } + +# optional dependencies +base64ct = { version = "1", optional = true, default-features = false, features = ["alloc"] } +digest = { version = "=0.11.0-pre.9", optional = true } +ff = { version = "0.13", optional = true, default-features = false } +group = { version = "0.13", optional = true, default-features = false } +hkdf = { version = "=0.13.0-pre.4", optional = true, default-features = false } +hex-literal = { version = "0.4", optional = true } +pem-rfc7468 = { version = "1.0.0-rc.2", optional = true, features = ["alloc"] } +pkcs8 = { version = "0.11.0-rc.1", optional = true, default-features = false } +sec1 = { version = "0.8.0-rc.3", optional = true, features = ["subtle", "zeroize"] } +serdect = { version = "0.3", optional = true, default-features = false, features = ["alloc"] } +serde_json = { version = "1.0.121", optional = true, default-features = false, features = ["alloc"] } +tap = { version = "1.0.1", optional = true, default-features = false } # hack for minimal-versions support for `bits` + +[dev-dependencies] +hex-literal = "0.4" +sha2 = "=0.11.0-pre.4" +sha3 = "=0.11.0-pre.4" + +[features] +default = ["arithmetic"] +alloc = [ + "base16ct/alloc", + "ff?/alloc", + "group?/alloc", + "pkcs8?/alloc", + "sec1?/alloc", + "zeroize/alloc" +] +std = [ + "alloc", + "rand_core/std", + "pkcs8?/std", + "sec1?/std" +] + +arithmetic = ["group"] +bits = ["arithmetic", "ff/bits", "dep:tap"] +dev = ["arithmetic", "dep:hex-literal", "pem", "pkcs8"] +hash2curve = ["arithmetic", "digest"] +ecdh = ["arithmetic", "digest", "dep:hkdf"] +group = ["dep:group", "ff"] +jwk = ["dep:base64ct", "dep:serde_json", "alloc", "serde", "zeroize/alloc"] +pkcs8 = ["dep:pkcs8", "sec1"] +pem = ["dep:pem-rfc7468", "alloc", "arithmetic", "pkcs8", "sec1/pem"] +serde = ["dep:serdect", "alloc", "pkcs8", "sec1/serde"] +voprf = ["digest"] + +[package.metadata.docs.rs] +features = ["bits", "ecdh", "hash2curve", "jwk", "pem", "std", "voprf"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/elliptic-curve/LICENSE-APACHE b/elliptic-curve/LICENSE-APACHE new file mode 100644 index 000000000..78173fa2e --- /dev/null +++ b/elliptic-curve/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/elliptic-curve/LICENSE-MIT b/elliptic-curve/LICENSE-MIT new file mode 100644 index 000000000..d4ce06527 --- /dev/null +++ b/elliptic-curve/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2020-2022 RustCrypto Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/elliptic-curve/README.md b/elliptic-curve/README.md new file mode 100644 index 000000000..0c8fad474 --- /dev/null +++ b/elliptic-curve/README.md @@ -0,0 +1,54 @@ +# RustCrypto: Elliptic Curve Traits + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] + +General purpose Elliptic Curve Cryptography (ECC) support, including types +and traits for representing various elliptic curve forms, scalars, points, +and public/secret keys composed thereof. + +[Documentation][docs-link] + +## Minimum Supported Rust Version + +Requires Rust **1.81** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## License + +All crates licensed under either of + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/elliptic-curve +[crate-link]: https://crates.io/crates/elliptic-curve +[docs-image]: https://docs.rs/elliptic-curve/badge.svg +[docs-link]: https://docs.rs/elliptic-curve/ +[build-image]: https://github.com/RustCrypto/traits/actions/workflows/elliptic-curve.yml/badge.svg +[build-link]: https://github.com/RustCrypto/traits/actions/workflows/elliptic-curve.yml +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260040-elliptic-curves diff --git a/elliptic-curve/src/arithmetic.rs b/elliptic-curve/src/arithmetic.rs new file mode 100644 index 000000000..150d8a9eb --- /dev/null +++ b/elliptic-curve/src/arithmetic.rs @@ -0,0 +1,87 @@ +//! Elliptic curve arithmetic traits. + +use crate::{ + ops::{Invert, LinearCombination, MulByGenerator, Reduce, ShrAssign}, + point::AffineCoordinates, + scalar::{FromUintUnchecked, IsHigh}, + Curve, FieldBytes, PrimeCurve, ScalarPrimitive, +}; +use core::fmt::Debug; +use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption}; +use zeroize::DefaultIsZeroes; + +/// Elliptic curve with an arithmetic implementation. +pub trait CurveArithmetic: Curve { + /// Elliptic curve point in affine coordinates. + type AffinePoint: 'static + + AffineCoordinates> + + Copy + + ConditionallySelectable + + ConstantTimeEq + + Debug + + Default + + DefaultIsZeroes + + Eq + + PartialEq + + Sized + + Send + + Sync; + + /// Elliptic curve point in projective coordinates. + /// + /// Note: the following bounds are provided by [`group::Group`]: + /// - `'static` + /// - [`Copy`] + /// - [`Clone`] + /// - [`Debug`] + /// - [`Eq`] + /// - [`Sized`] + /// - [`Send`] + /// - [`Sync`] + type ProjectivePoint: ConditionallySelectable + + ConstantTimeEq + + Default + + DefaultIsZeroes + + From + + Into + + LinearCombination<[(Self::ProjectivePoint, Self::Scalar)]> + + LinearCombination<[(Self::ProjectivePoint, Self::Scalar); 2]> + + MulByGenerator + + group::Curve + + group::Group; + + /// Scalar field modulo this curve's order. + /// + /// Note: the following bounds are provided by [`ff::Field`]: + /// - `'static` + /// - [`Copy`] + /// - [`Clone`] + /// - [`ConditionallySelectable`] + /// - [`ConstantTimeEq`] + /// - [`Debug`] + /// - [`Default`] + /// - [`Send`] + /// - [`Sync`] + type Scalar: AsRef + + DefaultIsZeroes + + From> + + FromUintUnchecked + + Into> + + Into> + + Into + + Invert> + + IsHigh + + PartialOrd + + Reduce> + + ShrAssign + + ff::Field + + ff::PrimeField>; +} + +/// Prime order elliptic curve with projective arithmetic implementation. +pub trait PrimeCurveArithmetic: + PrimeCurve + CurveArithmetic +{ + /// Prime order elliptic curve group. + type CurveGroup: group::prime::PrimeCurve::AffinePoint>; +} diff --git a/elliptic-curve/src/dev.rs b/elliptic-curve/src/dev.rs new file mode 100644 index 000000000..5a5c38ede --- /dev/null +++ b/elliptic-curve/src/dev.rs @@ -0,0 +1,843 @@ +//! Development-related functionality. +//! +//! Helpers and types for writing tests against concrete implementations of +//! the traits in this crate. + +use crate::{ + array::typenum::U32, + bigint::{Limb, U256}, + error::{Error, Result}, + ops::{Invert, LinearCombination, MulByGenerator, Reduce, ShrAssign}, + point::AffineCoordinates, + rand_core::RngCore, + scalar::{FromUintUnchecked, IsHigh}, + sec1::{CompressedPoint, FromEncodedPoint, ToEncodedPoint}, + subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}, + zeroize::DefaultIsZeroes, + Curve, CurveArithmetic, FieldBytesEncoding, PrimeCurve, +}; +use core::{ + iter::{Product, Sum}, + ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, +}; +use ff::{Field, PrimeField}; +use hex_literal::hex; +use pkcs8::AssociatedOid; + +#[cfg(feature = "bits")] +use ff::PrimeFieldBits; + +#[cfg(feature = "jwk")] +use crate::JwkParameters; + +/// Pseudo-coordinate for fixed-based scalar mult output +pub const PSEUDO_COORDINATE_FIXED_BASE_MUL: [u8; 32] = + hex!("deadbeef00000000000000000000000000000000000000000000000000000001"); + +/// SEC1 encoded point. +pub type EncodedPoint = crate::sec1::EncodedPoint; + +/// Field element bytes. +pub type FieldBytes = crate::FieldBytes; + +/// Non-zero scalar value. +pub type NonZeroScalar = crate::NonZeroScalar; + +/// Public key. +pub type PublicKey = crate::PublicKey; + +/// Secret key. +pub type SecretKey = crate::SecretKey; + +/// Scalar primitive type. +// TODO(tarcieri): make this the scalar type when it's more capable +pub type ScalarPrimitive = crate::ScalarPrimitive; + +/// Scalar bits. +#[cfg(feature = "bits")] +pub type ScalarBits = crate::scalar::ScalarBits; + +/// Mock elliptic curve type useful for writing tests which require a concrete +/// curve type. +/// +/// Note: this type is roughly modeled off of NIST P-256, but does not provide +/// an actual cure arithmetic implementation. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] +pub struct MockCurve; + +impl Curve for MockCurve { + type FieldBytesSize = U32; + type Uint = U256; + + const ORDER: U256 = + U256::from_be_hex("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"); +} + +impl PrimeCurve for MockCurve {} + +impl CurveArithmetic for MockCurve { + type AffinePoint = AffinePoint; + type ProjectivePoint = ProjectivePoint; + type Scalar = Scalar; +} + +impl AssociatedOid for MockCurve { + /// OID for NIST P-256 + const OID: pkcs8::ObjectIdentifier = pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); +} + +#[cfg(feature = "jwk")] +impl JwkParameters for MockCurve { + const CRV: &'static str = "P-256"; +} + +/// Example scalar type +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] +pub struct Scalar(ScalarPrimitive); + +impl Field for Scalar { + const ZERO: Self = Self(ScalarPrimitive::ZERO); + const ONE: Self = Self(ScalarPrimitive::ONE); + + fn random(mut rng: impl RngCore) -> Self { + let mut bytes = FieldBytes::default(); + + loop { + rng.fill_bytes(&mut bytes); + if let Some(scalar) = Self::from_repr(bytes).into() { + return scalar; + } + } + } + + fn is_zero(&self) -> Choice { + self.0.is_zero() + } + + #[must_use] + fn square(&self) -> Self { + unimplemented!(); + } + + #[must_use] + fn double(&self) -> Self { + self.add(self) + } + + fn invert(&self) -> CtOption { + unimplemented!(); + } + + fn sqrt(&self) -> CtOption { + unimplemented!(); + } + + fn sqrt_ratio(_num: &Self, _div: &Self) -> (Choice, Self) { + unimplemented!(); + } +} + +impl PrimeField for Scalar { + type Repr = FieldBytes; + + const MODULUS: &'static str = + "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"; + const NUM_BITS: u32 = 256; + const CAPACITY: u32 = 255; + const TWO_INV: Self = Self::ZERO; // BOGUS! + const MULTIPLICATIVE_GENERATOR: Self = Self::ZERO; // BOGUS! Should be 7 + const S: u32 = 4; + const ROOT_OF_UNITY: Self = Self::ZERO; // BOGUS! Should be 0xffc97f062a770992ba807ace842a3dfc1546cad004378daf0592d7fbb41e6602 + const ROOT_OF_UNITY_INV: Self = Self::ZERO; // BOGUS! + const DELTA: Self = Self::ZERO; // BOGUS! + + fn from_repr(bytes: FieldBytes) -> CtOption { + ScalarPrimitive::from_bytes(&bytes).map(Self) + } + + fn to_repr(&self) -> FieldBytes { + self.0.to_bytes() + } + + fn is_odd(&self) -> Choice { + self.0.is_odd() + } +} + +#[cfg(feature = "bits")] +impl PrimeFieldBits for Scalar { + #[cfg(target_pointer_width = "32")] + type ReprBits = [u32; 8]; + + #[cfg(target_pointer_width = "64")] + type ReprBits = [u64; 4]; + + fn to_le_bits(&self) -> ScalarBits { + self.0.as_uint().to_words().into() + } + + fn char_le_bits() -> ScalarBits { + MockCurve::ORDER.to_words().into() + } +} + +impl AsRef for Scalar { + fn as_ref(&self) -> &Scalar { + self + } +} + +impl ConditionallySelectable for Scalar { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self(ScalarPrimitive::conditional_select(&a.0, &b.0, choice)) + } +} + +impl ConstantTimeEq for Scalar { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} + +impl DefaultIsZeroes for Scalar {} + +impl Add for Scalar { + type Output = Scalar; + + fn add(self, other: Scalar) -> Scalar { + self.add(&other) + } +} + +impl Add<&Scalar> for Scalar { + type Output = Scalar; + + fn add(self, other: &Scalar) -> Scalar { + Self(self.0.add(&other.0)) + } +} + +impl AddAssign for Scalar { + fn add_assign(&mut self, other: Scalar) { + *self = *self + other; + } +} + +impl AddAssign<&Scalar> for Scalar { + fn add_assign(&mut self, other: &Scalar) { + *self = *self + other; + } +} + +impl Sub for Scalar { + type Output = Scalar; + + fn sub(self, other: Scalar) -> Scalar { + self.sub(&other) + } +} + +impl Sub<&Scalar> for Scalar { + type Output = Scalar; + + fn sub(self, other: &Scalar) -> Scalar { + Self(self.0.sub(&other.0)) + } +} + +impl SubAssign for Scalar { + fn sub_assign(&mut self, other: Scalar) { + *self = *self - other; + } +} + +impl SubAssign<&Scalar> for Scalar { + fn sub_assign(&mut self, other: &Scalar) { + *self = *self - other; + } +} + +impl Mul for Scalar { + type Output = Scalar; + + fn mul(self, _other: Scalar) -> Scalar { + unimplemented!(); + } +} + +impl Mul<&Scalar> for Scalar { + type Output = Scalar; + + fn mul(self, _other: &Scalar) -> Scalar { + unimplemented!(); + } +} + +impl MulAssign for Scalar { + fn mul_assign(&mut self, _rhs: Scalar) { + unimplemented!(); + } +} + +impl MulAssign<&Scalar> for Scalar { + fn mul_assign(&mut self, _rhs: &Scalar) { + unimplemented!(); + } +} + +impl Neg for Scalar { + type Output = Scalar; + + fn neg(self) -> Scalar { + Self(self.0.neg()) + } +} + +impl ShrAssign for Scalar { + fn shr_assign(&mut self, rhs: usize) { + self.0 >>= rhs; + } +} + +impl Sum for Scalar { + fn sum>(_iter: I) -> Self { + unimplemented!(); + } +} + +impl<'a> Sum<&'a Scalar> for Scalar { + fn sum>(_iter: I) -> Self { + unimplemented!(); + } +} + +impl Product for Scalar { + fn product>(_iter: I) -> Self { + unimplemented!(); + } +} + +impl<'a> Product<&'a Scalar> for Scalar { + fn product>(_iter: I) -> Self { + unimplemented!(); + } +} + +impl Invert for Scalar { + type Output = CtOption; + + fn invert(&self) -> CtOption { + unimplemented!(); + } +} + +impl Reduce for Scalar { + type Bytes = FieldBytes; + + fn reduce(w: U256) -> Self { + let (r, underflow) = w.sbb(&MockCurve::ORDER, Limb::ZERO); + let underflow = Choice::from((underflow.0 >> (Limb::BITS - 1)) as u8); + let reduced = U256::conditional_select(&w, &r, !underflow); + Self(ScalarPrimitive::new(reduced).unwrap()) + } + + fn reduce_bytes(_: &FieldBytes) -> Self { + todo!() + } +} + +impl FieldBytesEncoding for U256 {} + +impl From for Scalar { + fn from(n: u64) -> Scalar { + Self(n.into()) + } +} + +impl From for Scalar { + fn from(scalar: ScalarPrimitive) -> Scalar { + Self(scalar) + } +} + +impl From for ScalarPrimitive { + fn from(scalar: Scalar) -> ScalarPrimitive { + scalar.0 + } +} + +impl From for U256 { + fn from(scalar: Scalar) -> U256 { + scalar.0.to_uint() + } +} + +impl TryFrom for Scalar { + type Error = Error; + + fn try_from(w: U256) -> Result { + Option::from(ScalarPrimitive::new(w)).map(Self).ok_or(Error) + } +} + +impl FromUintUnchecked for Scalar { + type Uint = U256; + + fn from_uint_unchecked(uint: U256) -> Self { + Self(ScalarPrimitive::from_uint_unchecked(uint)) + } +} + +impl From for FieldBytes { + fn from(scalar: Scalar) -> Self { + Self::from(&scalar) + } +} + +impl From<&Scalar> for FieldBytes { + fn from(scalar: &Scalar) -> Self { + scalar.to_repr() + } +} + +impl IsHigh for Scalar { + fn is_high(&self) -> Choice { + self.0.is_high() + } +} + +/// Example affine point type +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AffinePoint { + /// Result of fixed-based scalar multiplication. + FixedBaseOutput(Scalar), + + /// Identity. + Identity, + + /// Base point. + Generator, + + /// Point corresponding to a given [`EncodedPoint`]. + Other(EncodedPoint), +} + +impl AffineCoordinates for AffinePoint { + type FieldRepr = FieldBytes; + + fn x(&self) -> FieldBytes { + unimplemented!(); + } + + fn y_is_odd(&self) -> Choice { + unimplemented!(); + } +} + +impl ConstantTimeEq for AffinePoint { + fn ct_eq(&self, other: &Self) -> Choice { + match (self, other) { + (Self::FixedBaseOutput(scalar), Self::FixedBaseOutput(other_scalar)) => { + scalar.ct_eq(other_scalar) + } + (Self::Identity, Self::Identity) | (Self::Generator, Self::Generator) => 1.into(), + (Self::Other(point), Self::Other(other_point)) => u8::from(point == other_point).into(), + _ => 0.into(), + } + } +} + +impl ConditionallySelectable for AffinePoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + // Not really constant time, but this is dev code + if choice.into() { + *b + } else { + *a + } + } +} + +impl Default for AffinePoint { + fn default() -> Self { + Self::Identity + } +} + +impl DefaultIsZeroes for AffinePoint {} + +impl FromEncodedPoint for AffinePoint { + fn from_encoded_point(encoded_point: &EncodedPoint) -> CtOption { + let point = if encoded_point.is_identity() { + Self::Identity + } else { + Self::Other(*encoded_point) + }; + + CtOption::new(point, Choice::from(1)) + } +} + +impl ToEncodedPoint for AffinePoint { + fn to_encoded_point(&self, compress: bool) -> EncodedPoint { + match self { + Self::FixedBaseOutput(scalar) => EncodedPoint::from_affine_coordinates( + &scalar.to_repr(), + &PSEUDO_COORDINATE_FIXED_BASE_MUL.into(), + false, + ), + Self::Other(point) => { + if compress == point.is_compressed() { + *point + } else { + unimplemented!(); + } + } + _ => unimplemented!(), + } + } +} + +impl Mul for AffinePoint { + type Output = AffinePoint; + + fn mul(self, _scalar: NonZeroScalar) -> Self { + unimplemented!(); + } +} + +/// Example projective point type +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ProjectivePoint { + /// Result of fixed-based scalar multiplication + FixedBaseOutput(Scalar), + + /// Is this point the identity point? + Identity, + + /// Is this point the generator point? + Generator, + + /// Is this point a different point corresponding to a given [`AffinePoint`] + Other(AffinePoint), +} + +impl ConstantTimeEq for ProjectivePoint { + fn ct_eq(&self, other: &Self) -> Choice { + match (self, other) { + (Self::FixedBaseOutput(scalar), Self::FixedBaseOutput(other_scalar)) => { + scalar.ct_eq(other_scalar) + } + (Self::Identity, Self::Identity) | (Self::Generator, Self::Generator) => 1.into(), + (Self::Other(point), Self::Other(other_point)) => point.ct_eq(other_point), + _ => 0.into(), + } + } +} + +impl ConditionallySelectable for ProjectivePoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + if choice.into() { + *b + } else { + *a + } + } +} + +impl Default for ProjectivePoint { + fn default() -> Self { + Self::Identity + } +} + +impl DefaultIsZeroes for ProjectivePoint {} + +impl From for ProjectivePoint { + fn from(point: AffinePoint) -> ProjectivePoint { + match point { + AffinePoint::FixedBaseOutput(scalar) => ProjectivePoint::FixedBaseOutput(scalar), + AffinePoint::Identity => ProjectivePoint::Identity, + AffinePoint::Generator => ProjectivePoint::Generator, + other => ProjectivePoint::Other(other), + } + } +} + +impl From for AffinePoint { + fn from(point: ProjectivePoint) -> AffinePoint { + group::Curve::to_affine(&point) + } +} + +impl FromEncodedPoint for ProjectivePoint { + fn from_encoded_point(_point: &EncodedPoint) -> CtOption { + unimplemented!(); + } +} + +impl ToEncodedPoint for ProjectivePoint { + fn to_encoded_point(&self, _compress: bool) -> EncodedPoint { + unimplemented!(); + } +} + +impl group::Group for ProjectivePoint { + type Scalar = Scalar; + + fn random(_rng: impl RngCore) -> Self { + unimplemented!(); + } + + fn identity() -> Self { + Self::Identity + } + + fn generator() -> Self { + Self::Generator + } + + fn is_identity(&self) -> Choice { + Choice::from(u8::from(self == &Self::Identity)) + } + + #[must_use] + fn double(&self) -> Self { + unimplemented!(); + } +} + +impl group::GroupEncoding for AffinePoint { + type Repr = CompressedPoint; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + EncodedPoint::from_bytes(bytes) + .map(|point| CtOption::new(point, Choice::from(1))) + .unwrap_or_else(|_| { + let is_identity = bytes.ct_eq(&Self::Repr::default()); + CtOption::new(EncodedPoint::identity(), is_identity) + }) + .and_then(|point| Self::from_encoded_point(&point)) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + Self::from_bytes(bytes) + } + + fn to_bytes(&self) -> Self::Repr { + let encoded = self.to_encoded_point(true); + let mut result = CompressedPoint::::default(); + result[..encoded.len()].copy_from_slice(encoded.as_bytes()); + result + } +} + +impl group::GroupEncoding for ProjectivePoint { + type Repr = CompressedPoint; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + ::from_bytes(bytes).map(Into::into) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + Self::from_bytes(bytes) + } + + fn to_bytes(&self) -> Self::Repr { + group::Curve::to_affine(self).to_bytes() + } +} + +impl group::Curve for ProjectivePoint { + type AffineRepr = AffinePoint; + + fn to_affine(&self) -> AffinePoint { + match self { + Self::FixedBaseOutput(scalar) => AffinePoint::FixedBaseOutput(*scalar), + Self::Other(affine) => *affine, + _ => unimplemented!(), + } + } +} + +impl LinearCombination<[(ProjectivePoint, Scalar)]> for ProjectivePoint {} +impl LinearCombination<[(ProjectivePoint, Scalar); N]> for ProjectivePoint {} + +impl Add for ProjectivePoint { + type Output = ProjectivePoint; + + fn add(self, _other: ProjectivePoint) -> ProjectivePoint { + unimplemented!(); + } +} + +impl Add<&ProjectivePoint> for ProjectivePoint { + type Output = ProjectivePoint; + + fn add(self, _other: &ProjectivePoint) -> ProjectivePoint { + unimplemented!(); + } +} + +impl AddAssign for ProjectivePoint { + fn add_assign(&mut self, _rhs: ProjectivePoint) { + unimplemented!(); + } +} + +impl AddAssign<&ProjectivePoint> for ProjectivePoint { + fn add_assign(&mut self, _rhs: &ProjectivePoint) { + unimplemented!(); + } +} + +impl Sub for ProjectivePoint { + type Output = ProjectivePoint; + + fn sub(self, _other: ProjectivePoint) -> ProjectivePoint { + unimplemented!(); + } +} + +impl Sub<&ProjectivePoint> for ProjectivePoint { + type Output = ProjectivePoint; + + fn sub(self, _other: &ProjectivePoint) -> ProjectivePoint { + unimplemented!(); + } +} + +impl SubAssign for ProjectivePoint { + fn sub_assign(&mut self, _rhs: ProjectivePoint) { + unimplemented!(); + } +} + +impl SubAssign<&ProjectivePoint> for ProjectivePoint { + fn sub_assign(&mut self, _rhs: &ProjectivePoint) { + unimplemented!(); + } +} + +impl Add for ProjectivePoint { + type Output = ProjectivePoint; + + fn add(self, _other: AffinePoint) -> ProjectivePoint { + unimplemented!(); + } +} + +impl Add<&AffinePoint> for ProjectivePoint { + type Output = ProjectivePoint; + + fn add(self, _other: &AffinePoint) -> ProjectivePoint { + unimplemented!(); + } +} + +impl AddAssign for ProjectivePoint { + fn add_assign(&mut self, _rhs: AffinePoint) { + unimplemented!(); + } +} + +impl AddAssign<&AffinePoint> for ProjectivePoint { + fn add_assign(&mut self, _rhs: &AffinePoint) { + unimplemented!(); + } +} + +impl Sum for ProjectivePoint { + fn sum>(_iter: I) -> Self { + unimplemented!(); + } +} + +impl<'a> Sum<&'a ProjectivePoint> for ProjectivePoint { + fn sum>(_iter: I) -> Self { + unimplemented!(); + } +} + +impl Sub for ProjectivePoint { + type Output = ProjectivePoint; + + fn sub(self, _other: AffinePoint) -> ProjectivePoint { + unimplemented!(); + } +} + +impl Sub<&AffinePoint> for ProjectivePoint { + type Output = ProjectivePoint; + + fn sub(self, _other: &AffinePoint) -> ProjectivePoint { + unimplemented!(); + } +} + +impl SubAssign for ProjectivePoint { + fn sub_assign(&mut self, _rhs: AffinePoint) { + unimplemented!(); + } +} + +impl SubAssign<&AffinePoint> for ProjectivePoint { + fn sub_assign(&mut self, _rhs: &AffinePoint) { + unimplemented!(); + } +} + +impl Mul for ProjectivePoint { + type Output = ProjectivePoint; + + fn mul(self, scalar: Scalar) -> ProjectivePoint { + match self { + Self::Generator => Self::FixedBaseOutput(scalar), + _ => unimplemented!(), + } + } +} + +impl Mul<&Scalar> for ProjectivePoint { + type Output = ProjectivePoint; + + fn mul(self, scalar: &Scalar) -> ProjectivePoint { + self * *scalar + } +} + +impl MulAssign for ProjectivePoint { + fn mul_assign(&mut self, _rhs: Scalar) { + unimplemented!(); + } +} + +impl MulAssign<&Scalar> for ProjectivePoint { + fn mul_assign(&mut self, _rhs: &Scalar) { + unimplemented!(); + } +} + +impl MulByGenerator for ProjectivePoint {} + +impl Neg for ProjectivePoint { + type Output = ProjectivePoint; + + fn neg(self) -> ProjectivePoint { + unimplemented!(); + } +} + +#[cfg(test)] +mod tests { + use super::Scalar; + use ff::PrimeField; + use hex_literal::hex; + + #[test] + fn round_trip() { + let bytes = hex!("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721"); + let scalar = Scalar::from_repr(bytes.into()).unwrap(); + assert_eq!(&bytes, scalar.to_repr().as_slice()); + } +} diff --git a/elliptic-curve/src/ecdh.rs b/elliptic-curve/src/ecdh.rs new file mode 100644 index 000000000..243c61789 --- /dev/null +++ b/elliptic-curve/src/ecdh.rs @@ -0,0 +1,248 @@ +//! Elliptic Curve Diffie-Hellman Support. +//! +//! This module contains a generic ECDH implementation which is usable with +//! any elliptic curve which implements the [`CurveArithmetic`] trait (presently +//! the `k256` and `p256` crates) +//! +//! # ECDH Ephemeral (ECDHE) Usage +//! +//! Ephemeral Diffie-Hellman provides a one-time key exchange between two peers +//! using a randomly generated set of keys for each exchange. +//! +//! In practice ECDHE is used as part of an [Authenticated Key Exchange (AKE)][AKE] +//! protocol (e.g. [SIGMA]), where an existing cryptographic trust relationship +//! can be used to determine the authenticity of the ephemeral keys, such as +//! a digital signature. Without such an additional step, ECDHE is insecure! +//! (see security warning below) +//! +//! See the documentation for the [`EphemeralSecret`] type for more information +//! on performing ECDH ephemeral key exchanges. +//! +//! # Static ECDH Usage +//! +//! Static ECDH key exchanges are supported via the low-level +//! [`diffie_hellman`] function. +//! +//! [AKE]: https://en.wikipedia.org/wiki/Authenticated_Key_Exchange +//! [SIGMA]: https://webee.technion.ac.il/~hugo/sigma-pdf.pdf + +use crate::{ + point::AffineCoordinates, AffinePoint, Curve, CurveArithmetic, FieldBytes, NonZeroScalar, + ProjectivePoint, PublicKey, +}; +use core::{borrow::Borrow, fmt}; +use digest::{crypto_common::BlockSizeUser, Digest}; +use group::Curve as _; +use hkdf::{hmac::SimpleHmac, Hkdf}; +use rand_core::CryptoRngCore; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// Low-level Elliptic Curve Diffie-Hellman (ECDH) function. +/// +/// Whenever possible, we recommend using the high-level ECDH ephemeral API +/// provided by [`EphemeralSecret`]. +/// +/// However, if you are implementing a protocol which requires a static scalar +/// value as part of an ECDH exchange, this API can be used to compute a +/// [`SharedSecret`] from that value. +/// +/// Note that this API operates on the low-level [`NonZeroScalar`] and +/// [`AffinePoint`] types. If you are attempting to use the higher-level +/// [`SecretKey`][`crate::SecretKey`] and [`PublicKey`] types, you will +/// need to use the following conversions: +/// +/// ```ignore +/// let shared_secret = elliptic_curve::ecdh::diffie_hellman( +/// secret_key.to_nonzero_scalar(), +/// public_key.as_affine() +/// ); +/// ``` +pub fn diffie_hellman( + secret_key: impl Borrow>, + public_key: impl Borrow>, +) -> SharedSecret +where + C: CurveArithmetic, +{ + let public_point = ProjectivePoint::::from(*public_key.borrow()); + let secret_point = (public_point * secret_key.borrow().as_ref()).to_affine(); + SharedSecret::new(secret_point) +} + +/// Ephemeral Diffie-Hellman Secret. +/// +/// These are ephemeral "secret key" values which are deliberately designed +/// to avoid being persisted. +/// +/// To perform an ephemeral Diffie-Hellman exchange, do the following: +/// +/// - Have each participant generate an [`EphemeralSecret`] value +/// - Compute the [`PublicKey`] for that value +/// - Have each peer provide their [`PublicKey`] to their counterpart +/// - Use [`EphemeralSecret`] and the other participant's [`PublicKey`] +/// to compute a [`SharedSecret`] value. +/// +/// # ⚠️ SECURITY WARNING ⚠️ +/// +/// Ephemeral Diffie-Hellman exchanges are unauthenticated and without a +/// further authentication step are trivially vulnerable to man-in-the-middle +/// attacks! +/// +/// These exchanges should be performed in the context of a protocol which +/// takes further steps to authenticate the peers in a key exchange. +pub struct EphemeralSecret +where + C: CurveArithmetic, +{ + scalar: NonZeroScalar, +} + +impl fmt::Debug for EphemeralSecret { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EphemeralSecret").finish_non_exhaustive() + } +} + +impl EphemeralSecret +where + C: CurveArithmetic, +{ + /// Generate a cryptographically random [`EphemeralSecret`]. + pub fn random(rng: &mut impl CryptoRngCore) -> Self { + Self { + scalar: NonZeroScalar::random(rng), + } + } + + /// Get the public key associated with this ephemeral secret. + /// + /// The `compress` flag enables point compression. + pub fn public_key(&self) -> PublicKey { + PublicKey::from_secret_scalar(&self.scalar) + } + + /// Compute a Diffie-Hellman shared secret from an ephemeral secret and the + /// public key of the other participant in the exchange. + pub fn diffie_hellman(&self, public_key: &PublicKey) -> SharedSecret { + diffie_hellman(self.scalar, public_key.as_affine()) + } +} + +impl From<&EphemeralSecret> for PublicKey +where + C: CurveArithmetic, +{ + fn from(ephemeral_secret: &EphemeralSecret) -> Self { + ephemeral_secret.public_key() + } +} + +impl Zeroize for EphemeralSecret +where + C: CurveArithmetic, +{ + fn zeroize(&mut self) { + self.scalar.zeroize() + } +} + +impl ZeroizeOnDrop for EphemeralSecret where C: CurveArithmetic {} + +impl Drop for EphemeralSecret +where + C: CurveArithmetic, +{ + fn drop(&mut self) { + self.zeroize(); + } +} + +/// Shared secret value computed via ECDH key agreement. +pub struct SharedSecret { + /// Computed secret value + secret_bytes: FieldBytes, +} + +impl fmt::Debug for SharedSecret { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SharedSecret").finish_non_exhaustive() + } +} + +impl SharedSecret { + /// Create a new [`SharedSecret`] from an [`AffinePoint`] for this curve. + #[inline] + fn new(point: AffinePoint) -> Self + where + C: CurveArithmetic, + { + Self { + secret_bytes: point.x(), + } + } + + /// Use [HKDF] (HMAC-based Extract-and-Expand Key Derivation Function) to + /// extract entropy from this shared secret. + /// + /// This method can be used to transform the shared secret into uniformly + /// random values which are suitable as key material. + /// + /// The `D` type parameter is a cryptographic digest function. + /// `sha2::Sha256` is a common choice for use with HKDF. + /// + /// The `salt` parameter can be used to supply additional randomness. + /// Some examples include: + /// + /// - randomly generated (but authenticated) string + /// - fixed application-specific value + /// - previous shared secret used for rekeying (as in TLS 1.3 and Noise) + /// + /// After initializing HKDF, use [`Hkdf::expand`] to obtain output key + /// material. + /// + /// [HKDF]: https://en.wikipedia.org/wiki/HKDF + pub fn extract(&self, salt: Option<&[u8]>) -> Hkdf> + where + D: BlockSizeUser + Clone + Digest, + { + Hkdf::new(salt, &self.secret_bytes) + } + + /// This value contains the raw serialized x-coordinate of the elliptic curve + /// point computed from a Diffie-Hellman exchange, serialized as bytes. + /// + /// When in doubt, use [`SharedSecret::extract`] instead. + /// + /// # ⚠️ WARNING: NOT UNIFORMLY RANDOM! ⚠️ + /// + /// This value is not uniformly random and should not be used directly + /// as a cryptographic key for anything which requires that property + /// (e.g. symmetric ciphers). + /// + /// Instead, the resulting value should be used as input to a Key Derivation + /// Function (KDF) or cryptographic hash function to produce a symmetric key. + /// The [`SharedSecret::extract`] function will do this for you. + pub fn raw_secret_bytes(&self) -> &FieldBytes { + &self.secret_bytes + } +} + +impl From> for SharedSecret { + /// NOTE: this impl is intended to be used by curve implementations to + /// instantiate a [`SharedSecret`] value from their respective + /// [`AffinePoint`] type. + /// + /// Curve implementations should provide the field element representing + /// the affine x-coordinate as `secret_bytes`. + fn from(secret_bytes: FieldBytes) -> Self { + Self { secret_bytes } + } +} + +impl ZeroizeOnDrop for SharedSecret {} + +impl Drop for SharedSecret { + fn drop(&mut self) { + self.secret_bytes.zeroize() + } +} diff --git a/elliptic-curve/src/error.rs b/elliptic-curve/src/error.rs new file mode 100644 index 000000000..1944839bc --- /dev/null +++ b/elliptic-curve/src/error.rs @@ -0,0 +1,44 @@ +//! Error type. + +use core::fmt::{self, Display}; + +/// Result type with the `elliptic-curve` crate's [`Error`] type. +pub type Result = core::result::Result; + +/// Elliptic curve errors. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Error; + +impl core::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("crypto error") + } +} + +impl From for Error { + fn from(_: base16ct::Error) -> Error { + Error + } +} + +impl From for Error { + fn from(_: core::array::TryFromSliceError) -> Error { + Error + } +} + +#[cfg(feature = "pkcs8")] +impl From for Error { + fn from(_: pkcs8::Error) -> Error { + Error + } +} + +#[cfg(feature = "sec1")] +impl From for Error { + fn from(_: sec1::Error) -> Error { + Error + } +} diff --git a/elliptic-curve/src/field.rs b/elliptic-curve/src/field.rs new file mode 100644 index 000000000..2884e241a --- /dev/null +++ b/elliptic-curve/src/field.rs @@ -0,0 +1,51 @@ +//! Field elements. + +use crate::{ + bigint::{ArrayEncoding, ByteArray, Integer}, + Curve, +}; +use hybrid_array::{typenum::Unsigned, Array}; + +/// Size of serialized field elements of this elliptic curve. +pub type FieldBytesSize = ::FieldBytesSize; + +/// Byte representation of a base/scalar field element of a given curve. +pub type FieldBytes = Array>; + +/// Trait for decoding/encoding `Curve::Uint` from/to [`FieldBytes`] using +/// curve-specific rules. +/// +/// Namely a curve's modulus may be smaller than the big integer type used to +/// internally represent field elements (since the latter are multiples of the +/// limb size), such as in the case of curves like NIST P-224 and P-521, and so +/// it may need to be padded/truncated to the right length. +/// +/// Additionally, different curves have different endianness conventions, also +/// captured here. +pub trait FieldBytesEncoding: ArrayEncoding + Integer +where + C: Curve, +{ + /// Decode unsigned integer from serialized field element. + /// + /// The default implementation assumes a big endian encoding. + fn decode_field_bytes(field_bytes: &FieldBytes) -> Self { + debug_assert!(field_bytes.len() <= Self::ByteSize::USIZE); + let mut byte_array = ByteArray::::default(); + let offset = Self::ByteSize::USIZE.saturating_sub(field_bytes.len()); + byte_array[offset..].copy_from_slice(field_bytes); + Self::from_be_byte_array(byte_array) + } + + /// Encode unsigned integer into serialized field element. + /// + /// The default implementation assumes a big endian encoding. + fn encode_field_bytes(&self) -> FieldBytes { + let mut field_bytes = FieldBytes::::default(); + debug_assert!(field_bytes.len() <= Self::ByteSize::USIZE); + + let offset = Self::ByteSize::USIZE.saturating_sub(field_bytes.len()); + field_bytes.copy_from_slice(&self.to_be_byte_array()[offset..]); + field_bytes + } +} diff --git a/elliptic-curve/src/hash2curve.rs b/elliptic-curve/src/hash2curve.rs new file mode 100644 index 000000000..3df394f79 --- /dev/null +++ b/elliptic-curve/src/hash2curve.rs @@ -0,0 +1,15 @@ +//! Traits for hashing byte sequences to curve points. +//! +//! + +mod group_digest; +mod hash2field; +mod isogeny; +mod map2curve; +mod osswu; + +pub use group_digest::*; +pub use hash2field::*; +pub use isogeny::*; +pub use map2curve::*; +pub use osswu::*; diff --git a/elliptic-curve/src/hash2curve/group_digest.rs b/elliptic-curve/src/hash2curve/group_digest.rs new file mode 100644 index 000000000..ea7f0471f --- /dev/null +++ b/elliptic-curve/src/hash2curve/group_digest.rs @@ -0,0 +1,123 @@ +//! Traits for handling hash to curve. + +use super::{hash_to_field, ExpandMsg, FromOkm, MapToCurve}; +use crate::{CurveArithmetic, ProjectivePoint, Result}; +use group::cofactor::CofactorGroup; + +/// Adds hashing arbitrary byte sequences to a valid group element +pub trait GroupDigest: CurveArithmetic +where + ProjectivePoint: CofactorGroup, +{ + /// The field element representation for a group value with multiple elements + type FieldElement: FromOkm + MapToCurve> + Default + Copy; + + /// Computes the hash to curve routine. + /// + /// From : + /// + /// > Uniform encoding from byte strings to points in G. + /// > That is, the distribution of its output is statistically close + /// > to uniform in G. + /// > This function is suitable for most applications requiring a random + /// > oracle returning points in G assuming a cryptographically secure + /// > hash function is used. + /// + /// # Examples + /// + /// ## Using a fixed size hash function + /// + /// ```ignore + /// let pt = ProjectivePoint::hash_from_bytes::>(b"test data", b"CURVE_XMD:SHA-256_SSWU_RO_"); + /// ``` + /// + /// ## Using an extendable output function + /// + /// ```ignore + /// let pt = ProjectivePoint::hash_from_bytes::>(b"test data", b"CURVE_XOF:SHAKE-256_SSWU_RO_"); + /// ``` + /// + /// # Errors + /// See implementors of [`ExpandMsg`] for errors: + /// - [`ExpandMsgXmd`] + /// - [`ExpandMsgXof`] + /// + /// `len_in_bytes = ::Length * 2` + /// + /// [`ExpandMsgXmd`]: crate::hash2curve::ExpandMsgXmd + /// [`ExpandMsgXof`]: crate::hash2curve::ExpandMsgXof + fn hash_from_bytes<'a, X: ExpandMsg<'a>>( + msgs: &[&[u8]], + dsts: &'a [&'a [u8]], + ) -> Result> { + let mut u = [Self::FieldElement::default(), Self::FieldElement::default()]; + hash_to_field::(msgs, dsts, &mut u)?; + let q0 = u[0].map_to_curve(); + let q1 = u[1].map_to_curve(); + // Ideally we could add and then clear cofactor once + // thus saving a call but the field elements may not + // add properly due to the underlying implementation + // which could result in an incorrect subgroup. + // This is caused curve coefficients being different than + // what is usually implemented. + // FieldElement expects the `a` and `b` to be the original values + // isogenies are different with curves like k256 and bls12-381. + // This problem doesn't manifest for curves with no isogeny like p256. + // For k256 and p256 clear_cofactor doesn't do anything anyway so it will be a no-op. + Ok(q0.clear_cofactor().into() + q1.clear_cofactor()) + } + + /// Computes the encode to curve routine. + /// + /// From : + /// + /// > Nonuniform encoding from byte strings to + /// > points in G. That is, the distribution of its output is not + /// > uniformly random in G: the set of possible outputs of + /// > encode_to_curve is only a fraction of the points in G, and some + /// > points in this set are more likely to be output than others. + /// + /// # Errors + /// See implementors of [`ExpandMsg`] for errors: + /// - [`ExpandMsgXmd`] + /// - [`ExpandMsgXof`] + /// + /// `len_in_bytes = ::Length` + /// + /// [`ExpandMsgXmd`]: crate::hash2curve::ExpandMsgXmd + /// [`ExpandMsgXof`]: crate::hash2curve::ExpandMsgXof + fn encode_from_bytes<'a, X: ExpandMsg<'a>>( + msgs: &[&[u8]], + dsts: &'a [&'a [u8]], + ) -> Result> { + let mut u = [Self::FieldElement::default()]; + hash_to_field::(msgs, dsts, &mut u)?; + let q0 = u[0].map_to_curve(); + Ok(q0.clear_cofactor().into()) + } + + /// Computes the hash to field routine according to + /// + /// and returns a scalar. + /// + /// # Errors + /// See implementors of [`ExpandMsg`] for errors: + /// - [`ExpandMsgXmd`] + /// - [`ExpandMsgXof`] + /// + /// `len_in_bytes = ::Length` + /// + /// [`ExpandMsgXmd`]: crate::hash2curve::ExpandMsgXmd + /// [`ExpandMsgXof`]: crate::hash2curve::ExpandMsgXof + fn hash_to_scalar<'a, X: ExpandMsg<'a>>( + msgs: &[&[u8]], + dsts: &'a [&'a [u8]], + ) -> Result + where + Self::Scalar: FromOkm, + { + let mut u = [Self::Scalar::default()]; + hash_to_field::(msgs, dsts, &mut u)?; + Ok(u[0]) + } +} diff --git a/elliptic-curve/src/hash2curve/hash2field.rs b/elliptic-curve/src/hash2curve/hash2field.rs new file mode 100644 index 000000000..c9a980c1a --- /dev/null +++ b/elliptic-curve/src/hash2curve/hash2field.rs @@ -0,0 +1,48 @@ +//! Traits for hashing to field elements. +//! +//! + +mod expand_msg; + +pub use expand_msg::{xmd::*, xof::*, *}; + +use crate::{Error, Result}; +use hybrid_array::{typenum::Unsigned, Array, ArraySize}; + +/// The trait for helping to convert to a field element. +pub trait FromOkm { + /// The number of bytes needed to convert to a field element. + type Length: ArraySize; + + /// Convert a byte sequence into a field element. + fn from_okm(data: &Array) -> Self; +} + +/// Convert an arbitrary byte sequence into a field element. +/// +/// +/// +/// # Errors +/// See implementors of [`ExpandMsg`] for errors: +/// - [`ExpandMsgXmd`] +/// - [`ExpandMsgXof`] +/// +/// `len_in_bytes = T::Length * out.len()` +/// +/// [`ExpandMsgXmd`]: crate::hash2field::ExpandMsgXmd +/// [`ExpandMsgXof`]: crate::hash2field::ExpandMsgXof +#[doc(hidden)] +pub fn hash_to_field<'a, E, T>(data: &[&[u8]], domain: &'a [&'a [u8]], out: &mut [T]) -> Result<()> +where + E: ExpandMsg<'a>, + T: FromOkm + Default, +{ + let len_in_bytes = T::Length::to_usize().checked_mul(out.len()).ok_or(Error)?; + let mut tmp = Array::::Length>::default(); + let mut expander = E::expand_message(data, domain, len_in_bytes)?; + for o in out.iter_mut() { + expander.fill_bytes(&mut tmp); + *o = T::from_okm(&tmp); + } + Ok(()) +} diff --git a/elliptic-curve/src/hash2curve/hash2field/expand_msg.rs b/elliptic-curve/src/hash2curve/hash2field/expand_msg.rs new file mode 100644 index 000000000..510ce5b2f --- /dev/null +++ b/elliptic-curve/src/hash2curve/hash2field/expand_msg.rs @@ -0,0 +1,146 @@ +//! `expand_message` interface `for hash_to_field`. + +pub(super) mod xmd; +pub(super) mod xof; + +use crate::{Error, Result}; +use digest::{Digest, ExtendableOutput, Update, XofReader}; +use hybrid_array::typenum::{IsLess, U256}; +use hybrid_array::{Array, ArraySize}; + +/// Salt when the DST is too long +const OVERSIZE_DST_SALT: &[u8] = b"H2C-OVERSIZE-DST-"; +/// Maximum domain separation tag length +const MAX_DST_LEN: usize = 255; + +/// Trait for types implementing expand_message interface for `hash_to_field`. +/// +/// # Errors +/// See implementors of [`ExpandMsg`] for errors. +pub trait ExpandMsg<'a> { + /// Type holding data for the [`Expander`]. + type Expander: Expander + Sized; + + /// Expands `msg` to the required number of bytes. + /// + /// Returns an expander that can be used to call `read` until enough + /// bytes have been consumed + fn expand_message( + msgs: &[&[u8]], + dsts: &'a [&'a [u8]], + len_in_bytes: usize, + ) -> Result; +} + +/// Expander that, call `read` until enough bytes have been consumed. +pub trait Expander { + /// Fill the array with the expanded bytes + fn fill_bytes(&mut self, okm: &mut [u8]); +} + +/// The domain separation tag +/// +/// Implements [section 5.4.3 of `draft-irtf-cfrg-hash-to-curve-13`][dst]. +/// +/// [dst]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-13#section-5.4.3 +#[derive(Debug)] +pub(crate) enum Domain<'a, L> +where + L: ArraySize + IsLess, +{ + /// > 255 + Hashed(Array), + /// <= 255 + Array(&'a [&'a [u8]]), +} + +impl<'a, L> Domain<'a, L> +where + L: ArraySize + IsLess, +{ + pub fn xof(dsts: &'a [&'a [u8]]) -> Result + where + X: Default + ExtendableOutput + Update, + { + if dsts.is_empty() { + Err(Error) + } else if dsts.iter().map(|dst| dst.len()).sum::() > MAX_DST_LEN { + let mut data = Array::::default(); + let mut hash = X::default(); + hash.update(OVERSIZE_DST_SALT); + + for dst in dsts { + hash.update(dst); + } + + hash.finalize_xof().read(&mut data); + + Ok(Self::Hashed(data)) + } else { + Ok(Self::Array(dsts)) + } + } + + pub fn xmd(dsts: &'a [&'a [u8]]) -> Result + where + X: Digest, + { + if dsts.is_empty() { + Err(Error) + } else if dsts.iter().map(|dst| dst.len()).sum::() > MAX_DST_LEN { + Ok(Self::Hashed({ + let mut hash = X::new(); + hash.update(OVERSIZE_DST_SALT); + + for dst in dsts { + hash.update(dst); + } + + hash.finalize() + })) + } else { + Ok(Self::Array(dsts)) + } + } + + pub fn update_hash(&self, hash: &mut HashT) { + match self { + Self::Hashed(d) => hash.update(d), + Self::Array(d) => { + for d in d.iter() { + hash.update(d) + } + } + } + } + + pub fn len(&self) -> u8 { + match self { + // Can't overflow because it's enforced on a type level. + Self::Hashed(_) => L::to_u8(), + // Can't overflow because it's checked on creation. + Self::Array(d) => { + u8::try_from(d.iter().map(|d| d.len()).sum::()).expect("length overflow") + } + } + } + + #[cfg(test)] + pub fn assert(&self, bytes: &[u8]) { + let data = match self { + Domain::Hashed(d) => d.to_vec(), + Domain::Array(d) => d.iter().copied().flatten().copied().collect(), + }; + assert_eq!(data, bytes); + } + + #[cfg(test)] + pub fn assert_dst(&self, bytes: &[u8]) { + let data = match self { + Domain::Hashed(d) => d.to_vec(), + Domain::Array(d) => d.iter().copied().flatten().copied().collect(), + }; + assert_eq!(data, &bytes[..bytes.len() - 1]); + assert_eq!(self.len(), bytes[bytes.len() - 1]); + } +} diff --git a/elliptic-curve/src/hash2curve/hash2field/expand_msg/xmd.rs b/elliptic-curve/src/hash2curve/hash2field/expand_msg/xmd.rs new file mode 100644 index 000000000..fdd298c63 --- /dev/null +++ b/elliptic-curve/src/hash2curve/hash2field/expand_msg/xmd.rs @@ -0,0 +1,453 @@ +//! `expand_message_xmd` based on a hash function. + +use core::marker::PhantomData; + +use super::{Domain, ExpandMsg, Expander}; +use crate::{Error, Result}; +use digest::{ + array::{ + typenum::{IsLess, IsLessOrEqual, Unsigned, U256}, + Array, + }, + core_api::BlockSizeUser, + FixedOutput, HashMarker, +}; + +/// Placeholder type for implementing `expand_message_xmd` based on a hash function +/// +/// # Errors +/// - `dst.is_empty()` +/// - `len_in_bytes == 0` +/// - `len_in_bytes > u16::MAX` +/// - `len_in_bytes > 255 * HashT::OutputSize` +#[derive(Debug)] +pub struct ExpandMsgXmd(PhantomData) +where + HashT: BlockSizeUser + Default + FixedOutput + HashMarker, + HashT::OutputSize: IsLess, + HashT::OutputSize: IsLessOrEqual; + +/// ExpandMsgXmd implements expand_message_xmd for the ExpandMsg trait +impl<'a, HashT> ExpandMsg<'a> for ExpandMsgXmd +where + HashT: BlockSizeUser + Default + FixedOutput + HashMarker, + // If `len_in_bytes` is bigger then 256, length of the `DST` will depend on + // the output size of the hash, which is still not allowed to be bigger then 256: + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5.4.1-6 + HashT::OutputSize: IsLess, + // Constraint set by `expand_message_xmd`: + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5.4.1-4 + HashT::OutputSize: IsLessOrEqual, +{ + type Expander = ExpanderXmd<'a, HashT>; + + fn expand_message( + msgs: &[&[u8]], + dsts: &'a [&'a [u8]], + len_in_bytes: usize, + ) -> Result { + if len_in_bytes == 0 { + return Err(Error); + } + + let len_in_bytes_u16 = u16::try_from(len_in_bytes).map_err(|_| Error)?; + + let b_in_bytes = HashT::OutputSize::to_usize(); + let ell = u8::try_from(len_in_bytes.div_ceil(b_in_bytes)).map_err(|_| Error)?; + + let domain = Domain::xmd::(dsts)?; + let mut b_0 = HashT::default(); + b_0.update(&Array::::default()); + + for msg in msgs { + b_0.update(msg); + } + + b_0.update(&len_in_bytes_u16.to_be_bytes()); + b_0.update(&[0]); + domain.update_hash(&mut b_0); + b_0.update(&[domain.len()]); + let b_0 = b_0.finalize_fixed(); + + let mut b_vals = HashT::default(); + b_vals.update(&b_0[..]); + b_vals.update(&[1u8]); + domain.update_hash(&mut b_vals); + b_vals.update(&[domain.len()]); + let b_vals = b_vals.finalize_fixed(); + + Ok(ExpanderXmd { + b_0, + b_vals, + domain, + index: 1, + offset: 0, + ell, + }) + } +} + +/// [`Expander`] type for [`ExpandMsgXmd`]. +#[derive(Debug)] +pub struct ExpanderXmd<'a, HashT> +where + HashT: BlockSizeUser + Default + FixedOutput + HashMarker, + HashT::OutputSize: IsLess, + HashT::OutputSize: IsLessOrEqual, +{ + b_0: Array, + b_vals: Array, + domain: Domain<'a, HashT::OutputSize>, + index: u8, + offset: usize, + ell: u8, +} + +impl ExpanderXmd<'_, HashT> +where + HashT: BlockSizeUser + Default + FixedOutput + HashMarker, + HashT::OutputSize: IsLess, + HashT::OutputSize: IsLessOrEqual, +{ + fn next(&mut self) -> bool { + if self.index < self.ell { + self.index += 1; + self.offset = 0; + // b_0 XOR b_(idx - 1) + let mut tmp = Array::::default(); + self.b_0 + .iter() + .zip(&self.b_vals[..]) + .enumerate() + .for_each(|(j, (b0val, bi1val))| tmp[j] = b0val ^ bi1val); + let mut b_vals = HashT::default(); + b_vals.update(&tmp); + b_vals.update(&[self.index]); + self.domain.update_hash(&mut b_vals); + b_vals.update(&[self.domain.len()]); + self.b_vals = b_vals.finalize_fixed(); + true + } else { + false + } + } +} + +impl Expander for ExpanderXmd<'_, HashT> +where + HashT: BlockSizeUser + Default + FixedOutput + HashMarker, + HashT::OutputSize: IsLess, + HashT::OutputSize: IsLessOrEqual, +{ + fn fill_bytes(&mut self, okm: &mut [u8]) { + for b in okm { + if self.offset == self.b_vals.len() && !self.next() { + return; + } + *b = self.b_vals[self.offset]; + self.offset += 1; + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use core::mem::size_of; + use hex_literal::hex; + use hybrid_array::{ + typenum::{U128, U32}, + ArraySize, + }; + use sha2::Sha256; + + fn assert_message( + msg: &[u8], + domain: &Domain<'_, HashT::OutputSize>, + len_in_bytes: u16, + bytes: &[u8], + ) where + HashT: BlockSizeUser + Default + FixedOutput + HashMarker, + HashT::OutputSize: IsLess, + { + let block = HashT::BlockSize::to_usize(); + assert_eq!( + Array::::default().as_slice(), + &bytes[..block] + ); + + let msg_len = block + msg.len(); + assert_eq!(msg, &bytes[block..msg_len]); + + let l = msg_len + size_of::(); + assert_eq!(len_in_bytes.to_be_bytes(), &bytes[msg_len..l]); + + let pad = l + size_of::(); + assert_eq!([0], &bytes[l..pad]); + + let dst = pad + usize::from(domain.len()); + domain.assert(&bytes[pad..dst]); + + let dst_len = dst + size_of::(); + assert_eq!([domain.len()], &bytes[dst..dst_len]); + + assert_eq!(dst_len, bytes.len()); + } + + struct TestVector { + msg: &'static [u8], + msg_prime: &'static [u8], + uniform_bytes: &'static [u8], + } + + impl TestVector { + #[allow(clippy::panic_in_result_fn)] + fn assert( + &self, + dst: &'static [u8], + domain: &Domain<'_, HashT::OutputSize>, + ) -> Result<()> + where + HashT: BlockSizeUser + Default + FixedOutput + HashMarker, + HashT::OutputSize: IsLess + IsLessOrEqual, + { + assert_message::(self.msg, domain, L::to_u16(), self.msg_prime); + + let dst = [dst]; + let mut expander = + ExpandMsgXmd::::expand_message(&[self.msg], &dst, L::to_usize())?; + + let mut uniform_bytes = Array::::default(); + expander.fill_bytes(&mut uniform_bytes); + + assert_eq!(uniform_bytes.as_slice(), self.uniform_bytes); + Ok(()) + } + } + + #[test] + fn expand_message_xmd_sha_256() -> Result<()> { + const DST: &[u8] = b"QUUX-V01-CS02-with-expander-SHA256-128"; + const DST_PRIME: &[u8] = + &hex!("515555582d5630312d435330322d776974682d657870616e6465722d5348413235362d31323826"); + + let dst_prime = Domain::xmd::(&[DST])?; + dst_prime.assert_dst(DST_PRIME); + + const TEST_VECTORS_32: &[TestVector] = &[ + TestVector { + msg: b"", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000515555582d5630312d435330322d776974682d657870616e6465722d5348413235362d31323826"), + uniform_bytes: &hex!("68a985b87eb6b46952128911f2a4412bbc302a9d759667f87f7a21d803f07235"), + }, + TestVector { + msg: b"abc", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000616263002000515555582d5630312d435330322d776974682d657870616e6465722d5348413235362d31323826"), + uniform_bytes: &hex!("d8ccab23b5985ccea865c6c97b6e5b8350e794e603b4b97902f53a8a0d605615"), + }, + TestVector { + msg: b"abcdef0123456789", + msg_prime: &hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061626364656630313233343536373839002000515555582d5630312d435330322d776974682d657870616e6465722d5348413235362d31323826"), + uniform_bytes: &hex!("eff31487c770a893cfb36f912fbfcbff40d5661771ca4b2cb4eafe524333f5c1"), + }, + TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000713132385f7171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171002000515555582d5630312d435330322d776974682d657870616e6465722d5348413235362d31323826"), + uniform_bytes: &hex!("b23a1d2b4d97b2ef7785562a7e8bac7eed54ed6e97e29aa51bfe3f12ddad1ff9"), + }, + TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000613531325f6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161002000515555582d5630312d435330322d776974682d657870616e6465722d5348413235362d31323826"), + uniform_bytes: &hex!("4623227bcc01293b8c130bf771da8c298dede7383243dc0993d2d94823958c4c"), + }, + ]; + + for test_vector in TEST_VECTORS_32 { + test_vector.assert::(DST, &dst_prime)?; + } + + const TEST_VECTORS_128: &[TestVector] = &[ + TestVector { + msg: b"", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000515555582d5630312d435330322d776974682d657870616e6465722d5348413235362d31323826"), + uniform_bytes: &hex!("af84c27ccfd45d41914fdff5df25293e221afc53d8ad2ac06d5e3e29485dadbee0d121587713a3e0dd4d5e69e93eb7cd4f5df4cd103e188cf60cb02edc3edf18eda8576c412b18ffb658e3dd6ec849469b979d444cf7b26911a08e63cf31f9dcc541708d3491184472c2c29bb749d4286b004ceb5ee6b9a7fa5b646c993f0ced"), + }, TestVector { + msg: b"abc", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000616263008000515555582d5630312d435330322d776974682d657870616e6465722d5348413235362d31323826"), + uniform_bytes: &hex!("abba86a6129e366fc877aab32fc4ffc70120d8996c88aee2fe4b32d6c7b6437a647e6c3163d40b76a73cf6a5674ef1d890f95b664ee0afa5359a5c4e07985635bbecbac65d747d3d2da7ec2b8221b17b0ca9dc8a1ac1c07ea6a1e60583e2cb00058e77b7b72a298425cd1b941ad4ec65e8afc50303a22c0f99b0509b4c895f40"), + }, TestVector { + msg: b"abcdef0123456789", + msg_prime: &hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061626364656630313233343536373839008000515555582d5630312d435330322d776974682d657870616e6465722d5348413235362d31323826"), + uniform_bytes: &hex!("ef904a29bffc4cf9ee82832451c946ac3c8f8058ae97d8d629831a74c6572bd9ebd0df635cd1f208e2038e760c4994984ce73f0d55ea9f22af83ba4734569d4bc95e18350f740c07eef653cbb9f87910d833751825f0ebefa1abe5420bb52be14cf489b37fe1a72f7de2d10be453b2c9d9eb20c7e3f6edc5a60629178d9478df"), + }, TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000713132385f7171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171008000515555582d5630312d435330322d776974682d657870616e6465722d5348413235362d31323826"), + uniform_bytes: &hex!("80be107d0884f0d881bb460322f0443d38bd222db8bd0b0a5312a6fedb49c1bbd88fd75d8b9a09486c60123dfa1d73c1cc3169761b17476d3c6b7cbbd727acd0e2c942f4dd96ae3da5de368d26b32286e32de7e5a8cb2949f866a0b80c58116b29fa7fabb3ea7d520ee603e0c25bcaf0b9a5e92ec6a1fe4e0391d1cdbce8c68a"), + }, TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000613531325f6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161008000515555582d5630312d435330322d776974682d657870616e6465722d5348413235362d31323826"), + uniform_bytes: &hex!("546aff5444b5b79aa6148bd81728704c32decb73a3ba76e9e75885cad9def1d06d6792f8a7d12794e90efed817d96920d728896a4510864370c207f99bd4a608ea121700ef01ed879745ee3e4ceef777eda6d9e5e38b90c86ea6fb0b36504ba4a45d22e86f6db5dd43d98a294bebb9125d5b794e9d2a81181066eb954966a487"), + }, + ]; + + for test_vector in TEST_VECTORS_128 { + test_vector.assert::(DST, &dst_prime)?; + } + + Ok(()) + } + + #[test] + fn expand_message_xmd_sha_256_long() -> Result<()> { + const DST: &[u8] = b"QUUX-V01-CS02-with-expander-SHA256-128-long-DST-1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"; + const DST_PRIME: &[u8] = + &hex!("412717974da474d0f8c420f320ff81e8432adb7c927d9bd082b4fb4d16c0a23620"); + + let dst_prime = Domain::xmd::(&[DST])?; + dst_prime.assert_dst(DST_PRIME); + + const TEST_VECTORS_32: &[TestVector] = &[ + TestVector { + msg: b"", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000412717974da474d0f8c420f320ff81e8432adb7c927d9bd082b4fb4d16c0a23620"), + uniform_bytes: &hex!("e8dc0c8b686b7ef2074086fbdd2f30e3f8bfbd3bdf177f73f04b97ce618a3ed3"), + }, + TestVector { + msg: b"abc", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000616263002000412717974da474d0f8c420f320ff81e8432adb7c927d9bd082b4fb4d16c0a23620"), + uniform_bytes: &hex!("52dbf4f36cf560fca57dedec2ad924ee9c266341d8f3d6afe5171733b16bbb12"), + }, + TestVector { + msg: b"abcdef0123456789", + msg_prime: &hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061626364656630313233343536373839002000412717974da474d0f8c420f320ff81e8432adb7c927d9bd082b4fb4d16c0a23620"), + uniform_bytes: &hex!("35387dcf22618f3728e6c686490f8b431f76550b0b2c61cbc1ce7001536f4521"), + }, + TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000713132385f7171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171002000412717974da474d0f8c420f320ff81e8432adb7c927d9bd082b4fb4d16c0a23620"), + uniform_bytes: &hex!("01b637612bb18e840028be900a833a74414140dde0c4754c198532c3a0ba42bc"), + }, + TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000613531325f6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161002000412717974da474d0f8c420f320ff81e8432adb7c927d9bd082b4fb4d16c0a23620"), + uniform_bytes: &hex!("20cce7033cabc5460743180be6fa8aac5a103f56d481cf369a8accc0c374431b"), + }, + ]; + + for test_vector in TEST_VECTORS_32 { + test_vector.assert::(DST, &dst_prime)?; + } + + const TEST_VECTORS_128: &[TestVector] = &[ + TestVector { + msg: b"", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000412717974da474d0f8c420f320ff81e8432adb7c927d9bd082b4fb4d16c0a23620"), + uniform_bytes: &hex!("14604d85432c68b757e485c8894db3117992fc57e0e136f71ad987f789a0abc287c47876978e2388a02af86b1e8d1342e5ce4f7aaa07a87321e691f6fba7e0072eecc1218aebb89fb14a0662322d5edbd873f0eb35260145cd4e64f748c5dfe60567e126604bcab1a3ee2dc0778102ae8a5cfd1429ebc0fa6bf1a53c36f55dfc"), + }, + TestVector { + msg: b"abc", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000616263008000412717974da474d0f8c420f320ff81e8432adb7c927d9bd082b4fb4d16c0a23620"), + uniform_bytes: &hex!("1a30a5e36fbdb87077552b9d18b9f0aee16e80181d5b951d0471d55b66684914aef87dbb3626eaabf5ded8cd0686567e503853e5c84c259ba0efc37f71c839da2129fe81afdaec7fbdc0ccd4c794727a17c0d20ff0ea55e1389d6982d1241cb8d165762dbc39fb0cee4474d2cbbd468a835ae5b2f20e4f959f56ab24cd6fe267"), + }, + TestVector { + msg: b"abcdef0123456789", + msg_prime: &hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061626364656630313233343536373839008000412717974da474d0f8c420f320ff81e8432adb7c927d9bd082b4fb4d16c0a23620"), + uniform_bytes: &hex!("d2ecef3635d2397f34a9f86438d772db19ffe9924e28a1caf6f1c8f15603d4028f40891044e5c7e39ebb9b31339979ff33a4249206f67d4a1e7c765410bcd249ad78d407e303675918f20f26ce6d7027ed3774512ef5b00d816e51bfcc96c3539601fa48ef1c07e494bdc37054ba96ecb9dbd666417e3de289d4f424f502a982"), + }, + TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000713132385f7171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171008000412717974da474d0f8c420f320ff81e8432adb7c927d9bd082b4fb4d16c0a23620"), + uniform_bytes: &hex!("ed6e8c036df90111410431431a232d41a32c86e296c05d426e5f44e75b9a50d335b2412bc6c91e0a6dc131de09c43110d9180d0a70f0d6289cb4e43b05f7ee5e9b3f42a1fad0f31bac6a625b3b5c50e3a83316783b649e5ecc9d3b1d9471cb5024b7ccf40d41d1751a04ca0356548bc6e703fca02ab521b505e8e45600508d32"), + }, + TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + msg_prime: &hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000613531325f6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161008000412717974da474d0f8c420f320ff81e8432adb7c927d9bd082b4fb4d16c0a23620"), + uniform_bytes: &hex!("78b53f2413f3c688f07732c10e5ced29a17c6a16f717179ffbe38d92d6c9ec296502eb9889af83a1928cd162e845b0d3c5424e83280fed3d10cffb2f8431f14e7a23f4c68819d40617589e4c41169d0b56e0e3535be1fd71fbb08bb70c5b5ffed953d6c14bf7618b35fc1f4c4b30538236b4b08c9fbf90462447a8ada60be495"), + }, + ]; + + for test_vector in TEST_VECTORS_128 { + test_vector.assert::(DST, &dst_prime)?; + } + + Ok(()) + } + + #[test] + fn expand_message_xmd_sha_512() -> Result<()> { + use sha2::Sha512; + + const DST: &[u8] = b"QUUX-V01-CS02-with-expander-SHA512-256"; + const DST_PRIME: &[u8] = + &hex!("515555582d5630312d435330322d776974682d657870616e6465722d5348413531322d32353626"); + + let dst_prime = Domain::xmd::(&[DST])?; + dst_prime.assert_dst(DST_PRIME); + + const TEST_VECTORS_32: &[TestVector] = &[ + TestVector { + msg: b"", + msg_prime: &hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000515555582d5630312d435330322d776974682d657870616e6465722d5348413531322d32353626"), + uniform_bytes: &hex!("6b9a7312411d92f921c6f68ca0b6380730a1a4d982c507211a90964c394179ba"), + }, + TestVector { + msg: b"abc", + msg_prime: &hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000616263002000515555582d5630312d435330322d776974682d657870616e6465722d5348413531322d32353626"), + uniform_bytes: &hex!("0da749f12fbe5483eb066a5f595055679b976e93abe9be6f0f6318bce7aca8dc"), + }, + TestVector { + msg: b"abcdef0123456789", + msg_prime: &hex!("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061626364656630313233343536373839002000515555582d5630312d435330322d776974682d657870616e6465722d5348413531322d32353626"), + uniform_bytes: &hex!("087e45a86e2939ee8b91100af1583c4938e0f5fc6c9db4b107b83346bc967f58"), + }, + TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + msg_prime: &hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000713132385f7171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171002000515555582d5630312d435330322d776974682d657870616e6465722d5348413531322d32353626"), + uniform_bytes: &hex!("7336234ee9983902440f6bc35b348352013becd88938d2afec44311caf8356b3"), + }, + TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + msg_prime: &hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000613531325f6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161002000515555582d5630312d435330322d776974682d657870616e6465722d5348413531322d32353626"), + uniform_bytes: &hex!("57b5f7e766d5be68a6bfe1768e3c2b7f1228b3e4b3134956dd73a59b954c66f4"), + }, + ]; + + for test_vector in TEST_VECTORS_32 { + test_vector.assert::(DST, &dst_prime)?; + } + + const TEST_VECTORS_128: &[TestVector] = &[ + TestVector { + msg: b"", + msg_prime: &hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000515555582d5630312d435330322d776974682d657870616e6465722d5348413531322d32353626"), + uniform_bytes: &hex!("41b037d1734a5f8df225dd8c7de38f851efdb45c372887be655212d07251b921b052b62eaed99b46f72f2ef4cc96bfaf254ebbbec091e1a3b9e4fb5e5b619d2e0c5414800a1d882b62bb5cd1778f098b8eb6cb399d5d9d18f5d5842cf5d13d7eb00a7cff859b605da678b318bd0e65ebff70bec88c753b159a805d2c89c55961"), + }, + TestVector { + msg: b"abc", + msg_prime: &hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000616263008000515555582d5630312d435330322d776974682d657870616e6465722d5348413531322d32353626"), + uniform_bytes: &hex!("7f1dddd13c08b543f2e2037b14cefb255b44c83cc397c1786d975653e36a6b11bdd7732d8b38adb4a0edc26a0cef4bb45217135456e58fbca1703cd6032cb1347ee720b87972d63fbf232587043ed2901bce7f22610c0419751c065922b488431851041310ad659e4b23520e1772ab29dcdeb2002222a363f0c2b1c972b3efe1"), + }, + TestVector { + msg: b"abcdef0123456789", + msg_prime: &hex!("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061626364656630313233343536373839008000515555582d5630312d435330322d776974682d657870616e6465722d5348413531322d32353626"), + uniform_bytes: &hex!("3f721f208e6199fe903545abc26c837ce59ac6fa45733f1baaf0222f8b7acb0424814fcb5eecf6c1d38f06e9d0a6ccfbf85ae612ab8735dfdf9ce84c372a77c8f9e1c1e952c3a61b7567dd0693016af51d2745822663d0c2367e3f4f0bed827feecc2aaf98c949b5ed0d35c3f1023d64ad1407924288d366ea159f46287e61ac"), + }, + TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + msg_prime: &hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000713132385f7171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171008000515555582d5630312d435330322d776974682d657870616e6465722d5348413531322d32353626"), + uniform_bytes: &hex!("b799b045a58c8d2b4334cf54b78260b45eec544f9f2fb5bd12fb603eaee70db7317bf807c406e26373922b7b8920fa29142703dd52bdf280084fb7ef69da78afdf80b3586395b433dc66cde048a258e476a561e9deba7060af40adf30c64249ca7ddea79806ee5beb9a1422949471d267b21bc88e688e4014087a0b592b695ed"), + }, + TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + msg_prime: &hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000613531325f6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161008000515555582d5630312d435330322d776974682d657870616e6465722d5348413531322d32353626"), + uniform_bytes: &hex!("05b0bfef265dcee87654372777b7c44177e2ae4c13a27f103340d9cd11c86cb2426ffcad5bd964080c2aee97f03be1ca18e30a1f14e27bc11ebbd650f305269cc9fb1db08bf90bfc79b42a952b46daf810359e7bc36452684784a64952c343c52e5124cd1f71d474d5197fefc571a92929c9084ffe1112cf5eea5192ebff330b"), + }, + ]; + + for test_vector in TEST_VECTORS_128 { + test_vector.assert::(DST, &dst_prime)?; + } + + Ok(()) + } +} diff --git a/elliptic-curve/src/hash2curve/hash2field/expand_msg/xof.rs b/elliptic-curve/src/hash2curve/hash2field/expand_msg/xof.rs new file mode 100644 index 000000000..96eebe1e5 --- /dev/null +++ b/elliptic-curve/src/hash2curve/hash2field/expand_msg/xof.rs @@ -0,0 +1,358 @@ +//! `expand_message_xof` for the `ExpandMsg` trait + +use super::{Domain, ExpandMsg, Expander}; +use crate::{Error, Result}; +use core::fmt; +use digest::{ExtendableOutput, Update, XofReader}; +use hybrid_array::typenum::U32; + +/// Placeholder type for implementing `expand_message_xof` based on an extendable output function +/// +/// # Errors +/// - `dst.is_empty()` +/// - `len_in_bytes == 0` +/// - `len_in_bytes > u16::MAX` +pub struct ExpandMsgXof +where + HashT: Default + ExtendableOutput + Update, +{ + reader: ::Reader, +} + +impl fmt::Debug for ExpandMsgXof +where + HashT: Default + ExtendableOutput + Update, + ::Reader: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExpandMsgXof") + .field("reader", &self.reader) + .finish() + } +} + +/// ExpandMsgXof implements `expand_message_xof` for the [`ExpandMsg`] trait +impl<'a, HashT> ExpandMsg<'a> for ExpandMsgXof +where + HashT: Default + ExtendableOutput + Update, +{ + type Expander = Self; + + fn expand_message( + msgs: &[&[u8]], + dsts: &'a [&'a [u8]], + len_in_bytes: usize, + ) -> Result { + if len_in_bytes == 0 { + return Err(Error); + } + + let len_in_bytes = u16::try_from(len_in_bytes).map_err(|_| Error)?; + + let domain = Domain::::xof::(dsts)?; + let mut reader = HashT::default(); + + for msg in msgs { + reader = reader.chain(msg); + } + + reader.update(&len_in_bytes.to_be_bytes()); + domain.update_hash(&mut reader); + reader.update(&[domain.len()]); + let reader = reader.finalize_xof(); + Ok(Self { reader }) + } +} + +impl Expander for ExpandMsgXof +where + HashT: Default + ExtendableOutput + Update, +{ + fn fill_bytes(&mut self, okm: &mut [u8]) { + self.reader.read(okm); + } +} + +#[cfg(test)] +mod test { + use super::*; + use core::mem::size_of; + use hex_literal::hex; + use hybrid_array::{typenum::U128, Array, ArraySize}; + use sha3::Shake128; + + fn assert_message(msg: &[u8], domain: &Domain<'_, U32>, len_in_bytes: u16, bytes: &[u8]) { + let msg_len = msg.len(); + assert_eq!(msg, &bytes[..msg_len]); + + let len_in_bytes_len = msg_len + size_of::(); + assert_eq!( + len_in_bytes.to_be_bytes(), + &bytes[msg_len..len_in_bytes_len] + ); + + let dst = len_in_bytes_len + usize::from(domain.len()); + domain.assert(&bytes[len_in_bytes_len..dst]); + + let dst_len = dst + size_of::(); + assert_eq!([domain.len()], &bytes[dst..dst_len]); + + assert_eq!(dst_len, bytes.len()); + } + + struct TestVector { + msg: &'static [u8], + msg_prime: &'static [u8], + uniform_bytes: &'static [u8], + } + + impl TestVector { + #[allow(clippy::panic_in_result_fn)] + fn assert(&self, dst: &'static [u8], domain: &Domain<'_, U32>) -> Result<()> + where + HashT: Default + ExtendableOutput + Update, + L: ArraySize, + { + assert_message(self.msg, domain, L::to_u16(), self.msg_prime); + + let mut expander = + ExpandMsgXof::::expand_message(&[self.msg], &[dst], L::to_usize())?; + + let mut uniform_bytes = Array::::default(); + expander.fill_bytes(&mut uniform_bytes); + + assert_eq!(uniform_bytes.as_slice(), self.uniform_bytes); + Ok(()) + } + } + + #[test] + fn expand_message_xof_shake_128() -> Result<()> { + const DST: &[u8] = b"QUUX-V01-CS02-with-expander-SHAKE128"; + const DST_PRIME: &[u8] = + &hex!("515555582d5630312d435330322d776974682d657870616e6465722d5348414b4531323824"); + + let dst_prime = Domain::::xof::(&[DST])?; + dst_prime.assert_dst(DST_PRIME); + + const TEST_VECTORS_32: &[TestVector] = &[ + TestVector { + msg: b"", + msg_prime: &hex!("0020515555582d5630312d435330322d776974682d657870616e6465722d5348414b4531323824"), + uniform_bytes: &hex!("86518c9cd86581486e9485aa74ab35ba150d1c75c88e26b7043e44e2acd735a2"), + }, + TestVector { + msg: b"abc", + msg_prime: &hex!("6162630020515555582d5630312d435330322d776974682d657870616e6465722d5348414b4531323824"), + uniform_bytes: &hex!("8696af52a4d862417c0763556073f47bc9b9ba43c99b505305cb1ec04a9ab468"), + }, + TestVector { + msg: b"abcdef0123456789", + msg_prime: &hex!("616263646566303132333435363738390020515555582d5630312d435330322d776974682d657870616e6465722d5348414b4531323824"), + uniform_bytes: &hex!("912c58deac4821c3509dbefa094df54b34b8f5d01a191d1d3108a2c89077acca"), + }, + TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + msg_prime: &hex!("713132385f71717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171710020515555582d5630312d435330322d776974682d657870616e6465722d5348414b4531323824"), + uniform_bytes: &hex!("1adbcc448aef2a0cebc71dac9f756b22e51839d348e031e63b33ebb50faeaf3f"), + }, + TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + msg_prime: &hex!("613531325f61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161610020515555582d5630312d435330322d776974682d657870616e6465722d5348414b4531323824"), + uniform_bytes: &hex!("df3447cc5f3e9a77da10f819218ddf31342c310778e0e4ef72bbaecee786a4fe"), + }, + ]; + + for test_vector in TEST_VECTORS_32 { + test_vector.assert::(DST, &dst_prime)?; + } + + const TEST_VECTORS_128: &[TestVector] = &[ + TestVector { + msg: b"", + msg_prime: &hex!("0080515555582d5630312d435330322d776974682d657870616e6465722d5348414b4531323824"), + uniform_bytes: &hex!("7314ff1a155a2fb99a0171dc71b89ab6e3b2b7d59e38e64419b8b6294d03ffee42491f11370261f436220ef787f8f76f5b26bdcd850071920ce023f3ac46847744f4612b8714db8f5db83205b2e625d95afd7d7b4d3094d3bdde815f52850bb41ead9822e08f22cf41d615a303b0d9dde73263c049a7b9898208003a739a2e57"), + }, + TestVector { + msg: b"abc", + msg_prime: &hex!("6162630080515555582d5630312d435330322d776974682d657870616e6465722d5348414b4531323824"), + uniform_bytes: &hex!("c952f0c8e529ca8824acc6a4cab0e782fc3648c563ddb00da7399f2ae35654f4860ec671db2356ba7baa55a34a9d7f79197b60ddae6e64768a37d699a78323496db3878c8d64d909d0f8a7de4927dcab0d3dbbc26cb20a49eceb0530b431cdf47bc8c0fa3e0d88f53b318b6739fbed7d7634974f1b5c386d6230c76260d5337a"), + }, + TestVector { + msg: b"abcdef0123456789", + msg_prime: &hex!("616263646566303132333435363738390080515555582d5630312d435330322d776974682d657870616e6465722d5348414b4531323824"), + uniform_bytes: &hex!("19b65ee7afec6ac06a144f2d6134f08eeec185f1a890fe34e68f0e377b7d0312883c048d9b8a1d6ecc3b541cb4987c26f45e0c82691ea299b5e6889bbfe589153016d8131717ba26f07c3c14ffbef1f3eff9752e5b6183f43871a78219a75e7000fbac6a7072e2b83c790a3a5aecd9d14be79f9fd4fb180960a3772e08680495"), + }, + TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + msg_prime: &hex!("713132385f71717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171710080515555582d5630312d435330322d776974682d657870616e6465722d5348414b4531323824"), + uniform_bytes: &hex!("ca1b56861482b16eae0f4a26212112362fcc2d76dcc80c93c4182ed66c5113fe41733ed68be2942a3487394317f3379856f4822a611735e50528a60e7ade8ec8c71670fec6661e2c59a09ed36386513221688b35dc47e3c3111ee8c67ff49579089d661caa29db1ef10eb6eace575bf3dc9806e7c4016bd50f3c0e2a6481ee6d"), + }, + TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + msg_prime: &hex!("613531325f61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161610080515555582d5630312d435330322d776974682d657870616e6465722d5348414b4531323824"), + uniform_bytes: &hex!("9d763a5ce58f65c91531b4100c7266d479a5d9777ba761693d052acd37d149e7ac91c796a10b919cd74a591a1e38719fb91b7203e2af31eac3bff7ead2c195af7d88b8bc0a8adf3d1e90ab9bed6ddc2b7f655dd86c730bdeaea884e73741097142c92f0e3fc1811b699ba593c7fbd81da288a29d423df831652e3a01a9374999"), + }, + ]; + + for test_vector in TEST_VECTORS_128 { + test_vector.assert::(DST, &dst_prime)?; + } + + Ok(()) + } + + #[test] + fn expand_message_xof_shake_128_long() -> Result<()> { + const DST: &[u8] = b"QUUX-V01-CS02-with-expander-SHAKE128-long-DST-111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"; + const DST_PRIME: &[u8] = + &hex!("acb9736c0867fdfbd6385519b90fc8c034b5af04a958973212950132d035792f20"); + + let dst_prime = Domain::::xof::(&[DST])?; + dst_prime.assert_dst(DST_PRIME); + + const TEST_VECTORS_32: &[TestVector] = &[ + TestVector { + msg: b"", + msg_prime: &hex!("0020acb9736c0867fdfbd6385519b90fc8c034b5af04a958973212950132d035792f20"), + uniform_bytes: &hex!("827c6216330a122352312bccc0c8d6e7a146c5257a776dbd9ad9d75cd880fc53"), + }, + TestVector { + msg: b"abc", + msg_prime: &hex!("6162630020acb9736c0867fdfbd6385519b90fc8c034b5af04a958973212950132d035792f20"), + uniform_bytes: &hex!("690c8d82c7213b4282c6cb41c00e31ea1d3e2005f93ad19bbf6da40f15790c5c"), + }, + TestVector { + msg: b"abcdef0123456789", + msg_prime: &hex!("616263646566303132333435363738390020acb9736c0867fdfbd6385519b90fc8c034b5af04a958973212950132d035792f20"), + uniform_bytes: &hex!("979e3a15064afbbcf99f62cc09fa9c85028afcf3f825eb0711894dcfc2f57057"), + }, + TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + msg_prime: &hex!("713132385f71717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171710020acb9736c0867fdfbd6385519b90fc8c034b5af04a958973212950132d035792f20"), + uniform_bytes: &hex!("c5a9220962d9edc212c063f4f65b609755a1ed96e62f9db5d1fd6adb5a8dc52b"), + }, + TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + msg_prime: &hex!("613531325f61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161610020acb9736c0867fdfbd6385519b90fc8c034b5af04a958973212950132d035792f20"), + uniform_bytes: &hex!("f7b96a5901af5d78ce1d071d9c383cac66a1dfadb508300ec6aeaea0d62d5d62"), + }, + ]; + + for test_vector in TEST_VECTORS_32 { + test_vector.assert::(DST, &dst_prime)?; + } + + const TEST_VECTORS_128: &[TestVector] = &[ + TestVector { + msg: b"", + msg_prime: &hex!("0080acb9736c0867fdfbd6385519b90fc8c034b5af04a958973212950132d035792f20"), + uniform_bytes: &hex!("3890dbab00a2830be398524b71c2713bbef5f4884ac2e6f070b092effdb19208c7df943dc5dcbaee3094a78c267ef276632ee2c8ea0c05363c94b6348500fae4208345dd3475fe0c834c2beac7fa7bc181692fb728c0a53d809fc8111495222ce0f38468b11becb15b32060218e285c57a60162c2c8bb5b6bded13973cd41819"), + }, + TestVector { + msg: b"abc", + msg_prime: &hex!("6162630080acb9736c0867fdfbd6385519b90fc8c034b5af04a958973212950132d035792f20"), + uniform_bytes: &hex!("41b7ffa7a301b5c1441495ebb9774e2a53dbbf4e54b9a1af6a20fd41eafd69ef7b9418599c5545b1ee422f363642b01d4a53449313f68da3e49dddb9cd25b97465170537d45dcbdf92391b5bdff344db4bd06311a05bca7dcd360b6caec849c299133e5c9194f4e15e3e23cfaab4003fab776f6ac0bfae9144c6e2e1c62e7d57"), + }, + TestVector { + msg: b"abcdef0123456789", + msg_prime: &hex!("616263646566303132333435363738390080acb9736c0867fdfbd6385519b90fc8c034b5af04a958973212950132d035792f20"), + uniform_bytes: &hex!("55317e4a21318472cd2290c3082957e1242241d9e0d04f47026f03401643131401071f01aa03038b2783e795bdfa8a3541c194ad5de7cb9c225133e24af6c86e748deb52e560569bd54ef4dac03465111a3a44b0ea490fb36777ff8ea9f1a8a3e8e0de3cf0880b4b2f8dd37d3a85a8b82375aee4fa0e909f9763319b55778e71"), + }, + TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + msg_prime: &hex!("713132385f71717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171710080acb9736c0867fdfbd6385519b90fc8c034b5af04a958973212950132d035792f20"), + uniform_bytes: &hex!("19fdd2639f082e31c77717ac9bb032a22ff0958382b2dbb39020cdc78f0da43305414806abf9a561cb2d0067eb2f7bc544482f75623438ed4b4e39dd9e6e2909dd858bd8f1d57cd0fce2d3150d90aa67b4498bdf2df98c0100dd1a173436ba5d0df6be1defb0b2ce55ccd2f4fc05eb7cb2c019c35d5398b85adc676da4238bc7"), + }, + TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + msg_prime: &hex!("613531325f61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161610080acb9736c0867fdfbd6385519b90fc8c034b5af04a958973212950132d035792f20"), + uniform_bytes: &hex!("945373f0b3431a103333ba6a0a34f1efab2702efde41754c4cb1d5216d5b0a92a67458d968562bde7fa6310a83f53dda1383680a276a283438d58ceebfa7ab7ba72499d4a3eddc860595f63c93b1c5e823ea41fc490d938398a26db28f61857698553e93f0574eb8c5017bfed6249491f9976aaa8d23d9485339cc85ca329308"), + }, + ]; + + for test_vector in TEST_VECTORS_128 { + test_vector.assert::(DST, &dst_prime)?; + } + + Ok(()) + } + + #[test] + fn expand_message_xof_shake_256() -> Result<()> { + use sha3::Shake256; + + const DST: &[u8] = b"QUUX-V01-CS02-with-expander-SHAKE256"; + const DST_PRIME: &[u8] = + &hex!("515555582d5630312d435330322d776974682d657870616e6465722d5348414b4532353624"); + + let dst_prime = Domain::::xof::(&[DST])?; + dst_prime.assert_dst(DST_PRIME); + + const TEST_VECTORS_32: &[TestVector] = &[ + TestVector { + msg: b"", + msg_prime: &hex!("0020515555582d5630312d435330322d776974682d657870616e6465722d5348414b4532353624"), + uniform_bytes: &hex!("2ffc05c48ed32b95d72e807f6eab9f7530dd1c2f013914c8fed38c5ccc15ad76"), + }, + TestVector { + msg: b"abc", + msg_prime: &hex!("6162630020515555582d5630312d435330322d776974682d657870616e6465722d5348414b4532353624"), + uniform_bytes: &hex!("b39e493867e2767216792abce1f2676c197c0692aed061560ead251821808e07"), + }, + TestVector { + msg: b"abcdef0123456789", + msg_prime: &hex!("616263646566303132333435363738390020515555582d5630312d435330322d776974682d657870616e6465722d5348414b4532353624"), + uniform_bytes: &hex!("245389cf44a13f0e70af8665fe5337ec2dcd138890bb7901c4ad9cfceb054b65"), + }, + TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + msg_prime: &hex!("713132385f71717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171710020515555582d5630312d435330322d776974682d657870616e6465722d5348414b4532353624"), + uniform_bytes: &hex!("719b3911821e6428a5ed9b8e600f2866bcf23c8f0515e52d6c6c019a03f16f0e"), + }, + TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + msg_prime: &hex!("613531325f61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161610020515555582d5630312d435330322d776974682d657870616e6465722d5348414b4532353624"), + uniform_bytes: &hex!("9181ead5220b1963f1b5951f35547a5ea86a820562287d6ca4723633d17ccbbc"), + }, + ]; + + for test_vector in TEST_VECTORS_32 { + test_vector.assert::(DST, &dst_prime)?; + } + + const TEST_VECTORS_128: &[TestVector] = &[ + TestVector { + msg: b"", + msg_prime: &hex!("0080515555582d5630312d435330322d776974682d657870616e6465722d5348414b4532353624"), + uniform_bytes: &hex!("7a1361d2d7d82d79e035b8880c5a3c86c5afa719478c007d96e6c88737a3f631dd74a2c88df79a4cb5e5d9f7504957c70d669ec6bfedc31e01e2bacc4ff3fdf9b6a00b17cc18d9d72ace7d6b81c2e481b4f73f34f9a7505dccbe8f5485f3d20c5409b0310093d5d6492dea4e18aa6979c23c8ea5de01582e9689612afbb353df"), + }, + TestVector { + msg: b"abc", + msg_prime: &hex!("6162630080515555582d5630312d435330322d776974682d657870616e6465722d5348414b4532353624"), + uniform_bytes: &hex!("a54303e6b172909783353ab05ef08dd435a558c3197db0c132134649708e0b9b4e34fb99b92a9e9e28fc1f1d8860d85897a8e021e6382f3eea10577f968ff6df6c45fe624ce65ca25932f679a42a404bc3681efe03fcd45ef73bb3a8f79ba784f80f55ea8a3c367408f30381299617f50c8cf8fbb21d0f1e1d70b0131a7b6fbe"), + }, + TestVector { + msg: b"abcdef0123456789", + msg_prime: &hex!("616263646566303132333435363738390080515555582d5630312d435330322d776974682d657870616e6465722d5348414b4532353624"), + uniform_bytes: &hex!("e42e4d9538a189316e3154b821c1bafb390f78b2f010ea404e6ac063deb8c0852fcd412e098e231e43427bd2be1330bb47b4039ad57b30ae1fc94e34993b162ff4d695e42d59d9777ea18d3848d9d336c25d2acb93adcad009bcfb9cde12286df267ada283063de0bb1505565b2eb6c90e31c48798ecdc71a71756a9110ff373"), + }, + TestVector { + msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + msg_prime: &hex!("713132385f71717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171717171710080515555582d5630312d435330322d776974682d657870616e6465722d5348414b4532353624"), + uniform_bytes: &hex!("4ac054dda0a38a65d0ecf7afd3c2812300027c8789655e47aecf1ecc1a2426b17444c7482c99e5907afd9c25b991990490bb9c686f43e79b4471a23a703d4b02f23c669737a886a7ec28bddb92c3a98de63ebf878aa363a501a60055c048bea11840c4717beae7eee28c3cfa42857b3d130188571943a7bd747de831bd6444e0"), + }, + TestVector { + msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + msg_prime: &hex!("613531325f61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161610080515555582d5630312d435330322d776974682d657870616e6465722d5348414b4532353624"), + uniform_bytes: &hex!("09afc76d51c2cccbc129c2315df66c2be7295a231203b8ab2dd7f95c2772c68e500bc72e20c602abc9964663b7a03a389be128c56971ce81001a0b875e7fd17822db9d69792ddf6a23a151bf470079c518279aef3e75611f8f828994a9988f4a8a256ddb8bae161e658d5a2a09bcfe839c6396dc06ee5c8ff3c22d3b1f9deb7e"), + }, + ]; + + for test_vector in TEST_VECTORS_128 { + test_vector.assert::(DST, &dst_prime)?; + } + + Ok(()) + } +} diff --git a/elliptic-curve/src/hash2curve/isogeny.rs b/elliptic-curve/src/hash2curve/isogeny.rs new file mode 100644 index 000000000..0d7e0853c --- /dev/null +++ b/elliptic-curve/src/hash2curve/isogeny.rs @@ -0,0 +1,58 @@ +//! Traits for mapping an isogeny to another curve +//! +//! + +use core::ops::{AddAssign, Mul}; +use ff::Field; +use hybrid_array::{typenum::Unsigned, Array, ArraySize}; + +/// The coefficients for mapping from one isogenous curve to another +#[derive(Debug)] +pub struct IsogenyCoefficients> { + /// The coefficients for the x numerator + pub xnum: &'static [F], + /// The coefficients for the x denominator + pub xden: &'static [F], + /// The coefficients for the y numerator + pub ynum: &'static [F], + /// The coefficients for the x denominator + pub yden: &'static [F], +} + +/// The [`Isogeny`] methods to map to another curve. +pub trait Isogeny: Field + AddAssign + Mul { + /// The maximum number of coefficients + type Degree: ArraySize; + /// The isogeny coefficients + const COEFFICIENTS: IsogenyCoefficients; + + /// Map from the isogeny points to the main curve + fn isogeny(x: Self, y: Self) -> (Self, Self) { + let mut xs = Array::::default(); + xs[0] = Self::ONE; + xs[1] = x; + xs[2] = x.square(); + for i in 3..Self::Degree::to_usize() { + xs[i] = xs[i - 1] * x; + } + let x_num = Self::compute_iso(&xs, Self::COEFFICIENTS.xnum); + let x_den = Self::compute_iso(&xs, Self::COEFFICIENTS.xden) + .invert() + .unwrap(); + let y_num = Self::compute_iso(&xs, Self::COEFFICIENTS.ynum) * y; + let y_den = Self::compute_iso(&xs, Self::COEFFICIENTS.yden) + .invert() + .unwrap(); + + (x_num * x_den, y_num * y_den) + } + + /// Compute the ISO transform + fn compute_iso(xxs: &[Self], k: &[Self]) -> Self { + let mut xx = Self::ZERO; + for (xi, ki) in xxs.iter().zip(k.iter()) { + xx += *xi * ki; + } + xx + } +} diff --git a/elliptic-curve/src/hash2curve/map2curve.rs b/elliptic-curve/src/hash2curve/map2curve.rs new file mode 100644 index 000000000..6092e57ba --- /dev/null +++ b/elliptic-curve/src/hash2curve/map2curve.rs @@ -0,0 +1,12 @@ +//! Traits for mapping field elements to points on the curve. + +/// Trait for converting field elements into a point +/// via a mapping method like Simplified Shallue-van de Woestijne-Ulas +/// or Elligator +pub trait MapToCurve { + /// The output point + type Output; + + /// Map a field element into a point + fn map_to_curve(&self) -> Self::Output; +} diff --git a/elliptic-curve/src/hash2curve/osswu.rs b/elliptic-curve/src/hash2curve/osswu.rs new file mode 100644 index 000000000..dc5d16d57 --- /dev/null +++ b/elliptic-curve/src/hash2curve/osswu.rs @@ -0,0 +1,131 @@ +//! Optimized simplified Shallue-van de Woestijne-Ulas methods. +//! +//! + +use ff::Field; +use subtle::Choice; +use subtle::ConditionallySelectable; +use subtle::ConstantTimeEq; + +/// The Optimized Simplified Shallue-van de Woestijne-Ulas parameters +#[derive(Debug)] +pub struct OsswuMapParams +where + F: Field, +{ + /// The first constant term + pub c1: &'static [u64], + /// The second constant term + pub c2: F, + /// The ISO A variable or Curve A variable + pub map_a: F, + /// The ISO A variable or Curve A variable + pub map_b: F, + /// The Z parameter + pub z: F, +} + +/// Trait for determining the parity of the field +pub trait Sgn0 { + /// Return the parity of the field + /// 1 == negative + /// 0 == non-negative + fn sgn0(&self) -> Choice; +} + +/// The optimized simplified Shallue-van de Woestijne-Ulas method +/// for mapping elliptic curve scalars to affine points. +pub trait OsswuMap: Field + Sgn0 { + /// The OSSWU parameters for mapping the field to affine points. + /// For Weierstrass curves having A==0 or B==0, the parameters + /// should be for isogeny where A≠0 and B≠0. + const PARAMS: OsswuMapParams; + + /// Optimized sqrt_ratio for q = 3 mod 4. + fn sqrt_ratio_3mod4(u: Self, v: Self) -> (Choice, Self) { + // 1. tv1 = v^2 + let tv1 = v.square(); + // 2. tv2 = u * v + let tv2 = u * v; + // 3. tv1 = tv1 * tv2 + let tv1 = tv1 * tv2; + // 4. y1 = tv1^c1 + let y1 = tv1.pow_vartime(Self::PARAMS.c1); + // 5. y1 = y1 * tv2 + let y1 = y1 * tv2; + // 6. y2 = y1 * c2 + let y2 = y1 * Self::PARAMS.c2; + // 7. tv3 = y1^2 + let tv3 = y1.square(); + // 8. tv3 = tv3 * v + let tv3 = tv3 * v; + // 9. isQR = tv3 == u + let is_qr = tv3.ct_eq(&u); + // 10. y = CMOV(y2, y1, isQR) + let y = ConditionallySelectable::conditional_select(&y2, &y1, is_qr); + // 11. return (isQR, y) + (is_qr, y) + } + + /// Convert this field element into an affine point on the elliptic curve + /// returning (X, Y). For Weierstrass curves having A==0 or B==0 + /// the result is a point on an isogeny. + fn osswu(&self) -> (Self, Self) { + // 1. tv1 = u^2 + let tv1 = self.square(); + // 2. tv1 = Z * tv1 + let tv1 = Self::PARAMS.z * tv1; + // 3. tv2 = tv1^2 + let tv2 = tv1.square(); + // 4. tv2 = tv2 + tv1 + let tv2 = tv2 + tv1; + // 5. tv3 = tv2 + 1 + let tv3 = tv2 + Self::ONE; + // 6. tv3 = B * tv3 + let tv3 = Self::PARAMS.map_b * tv3; + // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) + let tv4 = ConditionallySelectable::conditional_select( + &Self::PARAMS.z, + &-tv2, + !Field::is_zero(&tv2), + ); + // 8. tv4 = A * tv4 + let tv4 = Self::PARAMS.map_a * tv4; + // 9. tv2 = tv3^2 + let tv2 = tv3.square(); + // 10. tv6 = tv4^2 + let tv6 = tv4.square(); + // 11. tv5 = A * tv6 + let tv5 = Self::PARAMS.map_a * tv6; + // 12. tv2 = tv2 + tv5 + let tv2 = tv2 + tv5; + // 13. tv2 = tv2 * tv3 + let tv2 = tv2 * tv3; + // 14. tv6 = tv6 * tv4 + let tv6 = tv6 * tv4; + // 15. tv5 = B * tv6 + let tv5 = Self::PARAMS.map_b * tv6; + // 16. tv2 = tv2 + tv5 + let tv2 = tv2 + tv5; + // 17. x = tv1 * tv3 + let x = tv1 * tv3; + // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) + let (is_gx1_square, y1) = Self::sqrt_ratio_3mod4(tv2, tv6); + // 19. y = tv1 * u + let y = tv1 * self; + // 20. y = y * y1 + let y = y * y1; + // 21. x = CMOV(x, tv3, is_gx1_square) + let x = ConditionallySelectable::conditional_select(&x, &tv3, is_gx1_square); + // 22. y = CMOV(y, y1, is_gx1_square) + let y = ConditionallySelectable::conditional_select(&y, &y1, is_gx1_square); + // 23. e1 = sgn0(u) == sgn0(y) + let e1 = self.sgn0().ct_eq(&y.sgn0()); + // 24. y = CMOV(-y, y, e1) + let y = ConditionallySelectable::conditional_select(&-y, &y, e1); + // 25. x = x / tv4 + let x = x * tv4.invert().unwrap(); + // 26. return (x, y) + (x, y) + } +} diff --git a/elliptic-curve/src/jwk.rs b/elliptic-curve/src/jwk.rs new file mode 100644 index 000000000..581448d71 --- /dev/null +++ b/elliptic-curve/src/jwk.rs @@ -0,0 +1,676 @@ +//! JSON Web Key (JWK) Support. +//! +//! Specified in RFC 7518 Section 6: Cryptographic Algorithms for Keys: +//! + +use crate::{ + sec1::{Coordinates, EncodedPoint, ModulusSize, ValidatePublicKey}, + secret_key::SecretKey, + Curve, Error, FieldBytes, FieldBytesSize, Result, +}; +use alloc::{ + borrow::ToOwned, + format, + string::{String, ToString}, +}; +use base64ct::{Base64UrlUnpadded as Base64Url, Encoding}; +use core::{ + fmt::{self, Debug}, + marker::PhantomData, + str::{self, FromStr}, +}; +use serdect::serde::{de, ser, Deserialize, Serialize}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[cfg(feature = "arithmetic")] +use crate::{ + public_key::PublicKey, + sec1::{FromEncodedPoint, ToEncodedPoint}, + AffinePoint, CurveArithmetic, +}; + +/// Key Type (`kty`) for elliptic curve keys. +pub const EC_KTY: &str = "EC"; + +/// Deserialization error message. +const DE_ERROR_MSG: &str = "struct JwkEcKey with 5 elements"; + +/// Name of the JWK type +const JWK_TYPE_NAME: &str = "JwkEcKey"; + +/// Field names +const FIELDS: &[&str] = &["kty", "crv", "x", "y", "d"]; + +/// Elliptic curve parameters used by JSON Web Keys. +pub trait JwkParameters: Curve { + /// The `crv` parameter which identifies a particular elliptic curve + /// as defined in RFC 7518 Section 6.2.1.1: + /// + /// + /// Curve values are registered in the IANA "JSON Web Key Elliptic Curve" + /// registry defined in RFC 7518 Section 7.6: + /// + const CRV: &'static str; +} + +/// JSON Web Key (JWK) with a `kty` of `"EC"` (elliptic curve). +/// +/// Specified in [RFC 7518 Section 6: Cryptographic Algorithms for Keys][1]. +/// +/// This type can represent either a public/private keypair, or just a +/// public key, depending on whether or not the `d` parameter is present. +/// +/// [1]: https://tools.ietf.org/html/rfc7518#section-6 +// TODO(tarcieri): eagerly decode or validate `x`, `y`, and `d` as Base64 +#[derive(Clone)] +pub struct JwkEcKey { + /// The `crv` parameter which identifies a particular elliptic curve + /// as defined in RFC 7518 Section 6.2.1.1: + /// + crv: String, + + /// The x-coordinate of the elliptic curve point which is the public key + /// value associated with this JWK as defined in RFC 7518 6.2.1.2: + /// + x: String, + + /// The y-coordinate of the elliptic curve point which is the public key + /// value associated with this JWK as defined in RFC 7518 6.2.1.3: + /// + y: String, + + /// The `d` ECC private key parameter as described in RFC 7518 6.2.2.1: + /// + /// + /// Value is optional and if omitted, this JWK represents a private key. + /// + /// Inner value is encoded according to the `Integer-to-Octet-String` + /// conversion as defined in SEC1 section 2.3.7: + /// + d: Option, +} + +impl JwkEcKey { + /// Get the `crv` parameter for this JWK. + pub fn crv(&self) -> &str { + &self.crv + } + + /// Is this JWK a keypair that includes a private key? + pub fn is_keypair(&self) -> bool { + self.d.is_some() + } + + /// Does this JWK contain only a public key? + pub fn is_public_key(&self) -> bool { + self.d.is_none() + } + + /// Decode a JWK into a [`PublicKey`]. + #[cfg(feature = "arithmetic")] + pub fn to_public_key(&self) -> Result> + where + C: CurveArithmetic + JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + { + PublicKey::from_sec1_bytes(self.to_encoded_point::()?.as_bytes()) + } + + /// Create a JWK from a SEC1 [`EncodedPoint`]. + pub fn from_encoded_point(point: &EncodedPoint) -> Option + where + C: Curve + JwkParameters, + FieldBytesSize: ModulusSize, + { + match point.coordinates() { + Coordinates::Uncompressed { x, y } => Some(JwkEcKey { + crv: C::CRV.to_owned(), + x: Base64Url::encode_string(x), + y: Base64Url::encode_string(y), + d: None, + }), + _ => None, + } + } + + /// Get the public key component of this JWK as a SEC1 [`EncodedPoint`]. + pub fn to_encoded_point(&self) -> Result> + where + C: Curve + JwkParameters, + FieldBytesSize: ModulusSize, + { + if self.crv != C::CRV { + return Err(Error); + } + + let x = decode_base64url_fe::(&self.x)?; + let y = decode_base64url_fe::(&self.y)?; + Ok(EncodedPoint::::from_affine_coordinates(&x, &y, false)) + } + + /// Decode a JWK into a [`SecretKey`]. + #[cfg(feature = "arithmetic")] + pub fn to_secret_key(&self) -> Result> + where + C: Curve + JwkParameters + ValidatePublicKey, + FieldBytesSize: ModulusSize, + { + self.try_into() + } +} + +impl FromStr for JwkEcKey { + type Err = Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s).map_err(|_| Error) + } +} + +#[allow(clippy::to_string_trait_impl)] +impl ToString for JwkEcKey { + fn to_string(&self) -> String { + serde_json::to_string(self).expect("JWK encoding error") + } +} + +impl TryFrom for SecretKey +where + C: Curve + JwkParameters + ValidatePublicKey, + FieldBytesSize: ModulusSize, +{ + type Error = Error; + + fn try_from(jwk: JwkEcKey) -> Result> { + (&jwk).try_into() + } +} + +impl TryFrom<&JwkEcKey> for SecretKey +where + C: Curve + JwkParameters + ValidatePublicKey, + FieldBytesSize: ModulusSize, +{ + type Error = Error; + + fn try_from(jwk: &JwkEcKey) -> Result> { + if let Some(d_base64) = &jwk.d { + let pk = jwk.to_encoded_point::()?; + let mut d_bytes = decode_base64url_fe::(d_base64)?; + let result = SecretKey::from_slice(&d_bytes); + d_bytes.zeroize(); + + result.and_then(|secret_key| { + C::validate_public_key(&secret_key, &pk)?; + Ok(secret_key) + }) + } else { + Err(Error) + } + } +} + +#[cfg(feature = "arithmetic")] +impl From> for JwkEcKey +where + C: CurveArithmetic + JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn from(sk: SecretKey) -> JwkEcKey { + (&sk).into() + } +} + +#[cfg(feature = "arithmetic")] +impl From<&SecretKey> for JwkEcKey +where + C: CurveArithmetic + JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn from(sk: &SecretKey) -> JwkEcKey { + let mut jwk = sk.public_key().to_jwk(); + let mut d = sk.to_bytes(); + jwk.d = Some(Base64Url::encode_string(&d)); + d.zeroize(); + jwk + } +} + +#[cfg(feature = "arithmetic")] +impl TryFrom for PublicKey +where + C: CurveArithmetic + JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + type Error = Error; + + fn try_from(jwk: JwkEcKey) -> Result> { + (&jwk).try_into() + } +} + +#[cfg(feature = "arithmetic")] +impl TryFrom<&JwkEcKey> for PublicKey +where + C: CurveArithmetic + JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + type Error = Error; + + fn try_from(jwk: &JwkEcKey) -> Result> { + PublicKey::from_sec1_bytes(jwk.to_encoded_point::()?.as_bytes()) + } +} + +#[cfg(feature = "arithmetic")] +impl From> for JwkEcKey +where + C: CurveArithmetic + JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn from(pk: PublicKey) -> JwkEcKey { + (&pk).into() + } +} + +#[cfg(feature = "arithmetic")] +impl From<&PublicKey> for JwkEcKey +where + C: CurveArithmetic + JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + fn from(pk: &PublicKey) -> JwkEcKey { + Self::from_encoded_point::(&pk.to_encoded_point(false)).expect("JWK encoding error") + } +} + +impl Debug for JwkEcKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let d = if self.d.is_some() { + "Some(...)" + } else { + "None" + }; + + // NOTE: this implementation omits the `d` private key parameter + f.debug_struct(JWK_TYPE_NAME) + .field("crv", &self.crv) + .field("x", &self.x) + .field("y", &self.y) + .field("d", &d) + .finish() + } +} + +impl PartialEq for JwkEcKey { + fn eq(&self, other: &Self) -> bool { + use subtle::ConstantTimeEq; + + // Compare private key in constant time + let d_eq = match &self.d { + Some(d1) => match &other.d { + Some(d2) => d1.as_bytes().ct_eq(d2.as_bytes()).into(), + None => other.d.is_none(), + }, + None => other.d.is_none(), + }; + + self.crv == other.crv && self.x == other.x && self.y == other.y && d_eq + } +} + +impl Eq for JwkEcKey {} + +impl ZeroizeOnDrop for JwkEcKey {} + +impl Drop for JwkEcKey { + fn drop(&mut self) { + self.zeroize(); + } +} + +impl Zeroize for JwkEcKey { + fn zeroize(&mut self) { + if let Some(d) = &mut self.d { + d.zeroize(); + } + } +} + +impl<'de> Deserialize<'de> for JwkEcKey { + fn deserialize(deserializer: D) -> core::result::Result + where + D: de::Deserializer<'de>, + { + /// Field positions + enum Field { + Kty, + Crv, + X, + Y, + D, + } + + /// Field visitor + struct FieldVisitor; + + impl de::Visitor<'_> for FieldVisitor { + type Value = Field; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Formatter::write_str(formatter, "field identifier") + } + + fn visit_u64(self, value: u64) -> core::result::Result + where + E: de::Error, + { + match value { + 0 => Ok(Field::Kty), + 1 => Ok(Field::Crv), + 2 => Ok(Field::X), + 3 => Ok(Field::Y), + 4 => Ok(Field::D), + _ => Err(de::Error::invalid_value( + de::Unexpected::Unsigned(value), + &"field index 0 <= i < 5", + )), + } + } + + fn visit_str(self, value: &str) -> core::result::Result + where + E: de::Error, + { + self.visit_bytes(value.as_bytes()) + } + + fn visit_bytes(self, value: &[u8]) -> core::result::Result + where + E: de::Error, + { + match value { + b"kty" => Ok(Field::Kty), + b"crv" => Ok(Field::Crv), + b"x" => Ok(Field::X), + b"y" => Ok(Field::Y), + b"d" => Ok(Field::D), + _ => Err(de::Error::unknown_field( + &String::from_utf8_lossy(value), + FIELDS, + )), + } + } + } + + impl<'de> Deserialize<'de> for Field { + #[inline] + fn deserialize(__deserializer: D) -> core::result::Result + where + D: de::Deserializer<'de>, + { + de::Deserializer::deserialize_identifier(__deserializer, FieldVisitor) + } + } + + struct Visitor<'de> { + marker: PhantomData, + lifetime: PhantomData<&'de ()>, + } + + impl<'de> de::Visitor<'de> for Visitor<'de> { + type Value = JwkEcKey; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Formatter::write_str(formatter, "struct JwkEcKey") + } + + #[inline] + fn visit_seq(self, mut seq: A) -> core::result::Result + where + A: de::SeqAccess<'de>, + { + let kty = de::SeqAccess::next_element::(&mut seq)? + .ok_or_else(|| de::Error::invalid_length(0, &DE_ERROR_MSG))?; + + if kty != EC_KTY { + return Err(de::Error::custom(format!("unsupported JWK kty: {kty:?}"))); + } + + let crv = de::SeqAccess::next_element::(&mut seq)? + .ok_or_else(|| de::Error::invalid_length(1, &DE_ERROR_MSG))?; + + let x = de::SeqAccess::next_element::(&mut seq)? + .ok_or_else(|| de::Error::invalid_length(2, &DE_ERROR_MSG))?; + + let y = de::SeqAccess::next_element::(&mut seq)? + .ok_or_else(|| de::Error::invalid_length(3, &DE_ERROR_MSG))?; + + let d = de::SeqAccess::next_element::>(&mut seq)? + .ok_or_else(|| de::Error::invalid_length(4, &DE_ERROR_MSG))?; + + Ok(JwkEcKey { crv, x, y, d }) + } + + #[inline] + fn visit_map(self, mut map: A) -> core::result::Result + where + A: de::MapAccess<'de>, + { + let mut kty: Option = None; + let mut crv: Option = None; + let mut x: Option = None; + let mut y: Option = None; + let mut d: Option = None; + + while let Some(key) = de::MapAccess::next_key::(&mut map)? { + match key { + Field::Kty => { + if kty.is_none() { + kty = Some(de::MapAccess::next_value::(&mut map)?); + } else { + return Err(de::Error::duplicate_field(FIELDS[0])); + } + } + Field::Crv => { + if crv.is_none() { + crv = Some(de::MapAccess::next_value::(&mut map)?); + } else { + return Err(de::Error::duplicate_field(FIELDS[1])); + } + } + Field::X => { + if x.is_none() { + x = Some(de::MapAccess::next_value::(&mut map)?); + } else { + return Err(de::Error::duplicate_field(FIELDS[2])); + } + } + Field::Y => { + if y.is_none() { + y = Some(de::MapAccess::next_value::(&mut map)?); + } else { + return Err(de::Error::duplicate_field(FIELDS[3])); + } + } + Field::D => { + if d.is_none() { + d = de::MapAccess::next_value::>(&mut map)?; + } else { + return Err(de::Error::duplicate_field(FIELDS[4])); + } + } + } + } + + let kty = kty.ok_or_else(|| de::Error::missing_field("kty"))?; + + if kty != EC_KTY { + return Err(de::Error::custom(format!("unsupported JWK kty: {kty}"))); + } + + let crv = crv.ok_or_else(|| de::Error::missing_field("crv"))?; + let x = x.ok_or_else(|| de::Error::missing_field("x"))?; + let y = y.ok_or_else(|| de::Error::missing_field("y"))?; + + Ok(JwkEcKey { crv, x, y, d }) + } + } + + de::Deserializer::deserialize_struct( + deserializer, + JWK_TYPE_NAME, + FIELDS, + Visitor { + marker: PhantomData::, + lifetime: PhantomData, + }, + ) + } +} + +impl Serialize for JwkEcKey { + fn serialize(&self, serializer: S) -> core::result::Result + where + S: ser::Serializer, + { + use ser::SerializeStruct; + + let mut state = serializer.serialize_struct(JWK_TYPE_NAME, 5)?; + + for (i, field) in [EC_KTY, &self.crv, &self.x, &self.y].iter().enumerate() { + state.serialize_field(FIELDS[i], field)?; + } + + if let Some(d) = &self.d { + state.serialize_field("d", d)?; + } + + SerializeStruct::end(state) + } +} + +/// Decode a Base64url-encoded field element +fn decode_base64url_fe(s: &str) -> Result> { + let mut result = FieldBytes::::default(); + Base64Url::decode(s, &mut result).map_err(|_| Error)?; + Ok(result) +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used, clippy::panic)] + use super::*; + + #[cfg(feature = "dev")] + use crate::dev::MockCurve; + + /// Example private key. From RFC 7518 Appendix C: + /// + const JWK_PRIVATE_KEY: &str = r#" + { + "kty":"EC", + "crv":"P-256", + "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", + "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps", + "d":"0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo" + } + "#; + + /// Example public key. + const JWK_PUBLIC_KEY: &str = r#" + { + "kty":"EC", + "crv":"P-256", + "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", + "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps" + } + "#; + + /// Example unsupported JWK (RSA key) + const UNSUPPORTED_JWK: &str = r#" + { + "kty":"RSA", + "kid":"cc34c0a0-bd5a-4a3c-a50d-a2a7db7643df", + "use":"sig", + "n":"pjdss8ZaDfEH6K6U7GeW2nxDqR4IP049fk1fK0lndimbMMVBdPv_hSpm8T8EtBDxrUdi1OHZfMhUixGaut-3nQ4GG9nM249oxhCtxqqNvEXrmQRGqczyLxuh-fKn9Fg--hS9UpazHpfVAFnB5aCfXoNhPuI8oByyFKMKaOVgHNqP5NBEqabiLftZD3W_lsFCPGuzr4Vp0YS7zS2hDYScC2oOMu4rGU1LcMZf39p3153Cq7bS2Xh6Y-vw5pwzFYZdjQxDn8x8BG3fJ6j8TGLXQsbKH1218_HcUJRvMwdpbUQG5nvA2GXVqLqdwp054Lzk9_B_f1lVrmOKuHjTNHq48w", + "e":"AQAB", + "d":"ksDmucdMJXkFGZxiomNHnroOZxe8AmDLDGO1vhs-POa5PZM7mtUPonxwjVmthmpbZzla-kg55OFfO7YcXhg-Hm2OWTKwm73_rLh3JavaHjvBqsVKuorX3V3RYkSro6HyYIzFJ1Ek7sLxbjDRcDOj4ievSX0oN9l-JZhaDYlPlci5uJsoqro_YrE0PRRWVhtGynd-_aWgQv1YzkfZuMD-hJtDi1Im2humOWxA4eZrFs9eG-whXcOvaSwO4sSGbS99ecQZHM2TcdXeAs1PvjVgQ_dKnZlGN3lTWoWfQP55Z7Tgt8Nf1q4ZAKd-NlMe-7iqCFfsnFwXjSiaOa2CRGZn-Q", + "p":"4A5nU4ahEww7B65yuzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ--wwfpRwHvSxtNU9qXb8ewo-BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3InKF4JvIlchyqs0RQ8wx7lULqwnn0", + "q":"ven83GM6SfrmO-TBHbjTk6JhP_3CMsIvmSdo4KrbQNvp4vHO3w1_0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEBpxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA-k4UoH_eQmGKGK44TRzYj5hZYGWIC8", + "dp":"lmmU_AG5SGxBhJqb8wxfNXDPJjf__i92BgJT2Vp4pskBbr5PGoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ-m0_XSWx13v9t9DIbheAtgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpE", + "dq":"mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe__EjuCBbwHfcT8OG3hWOv8vpzokQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p-AF2p6Yfahscjtq-GY9cB85NxLy2IXCC0PF--Sq9LOrTE9QV988SJy_yUrAjcZ5MmECk", + "qi":"ldHXIrEmMZVaNwGzDF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uYiqewXfCKw_UngrJt8Xwfq1Zruz0YY869zPN4GiE9-9rzdZB33RBw8kIOquY3MK74FMwCihYx_LiU2YTHkaoJ3ncvtvg" + } + "#; + + #[test] + fn parse_private_key() { + let jwk = JwkEcKey::from_str(JWK_PRIVATE_KEY).unwrap(); + assert_eq!(jwk.crv, "P-256"); + assert_eq!(jwk.x, "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0"); + assert_eq!(jwk.y, "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps"); + assert_eq!( + jwk.d.as_ref().unwrap(), + "0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo" + ); + } + + #[test] + fn parse_public_key() { + let jwk = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap(); + assert_eq!(jwk.crv, "P-256"); + assert_eq!(jwk.x, "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0"); + assert_eq!(jwk.y, "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps"); + assert_eq!(jwk.d, None); + } + + #[test] + fn parse_unsupported() { + assert_eq!(JwkEcKey::from_str(UNSUPPORTED_JWK), Err(Error)); + } + + #[test] + fn serialize_private_key() { + let actual = JwkEcKey::from_str(JWK_PRIVATE_KEY).unwrap().to_string(); + let expected: String = JWK_PRIVATE_KEY.split_whitespace().collect(); + assert_eq!(actual, expected); + } + + #[test] + fn serialize_public_key() { + let actual = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap().to_string(); + let expected: String = JWK_PUBLIC_KEY.split_whitespace().collect(); + assert_eq!(actual, expected); + } + + #[cfg(feature = "dev")] + #[test] + fn jwk_into_encoded_point() { + let jwk = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap(); + let point = jwk.to_encoded_point::().unwrap(); + let (x, y) = match point.coordinates() { + Coordinates::Uncompressed { x, y } => (x, y), + other => panic!("unexpected coordinates: {other:?}"), + }; + + assert_eq!(&decode_base64url_fe::(&jwk.x).unwrap(), x); + assert_eq!(&decode_base64url_fe::(&jwk.y).unwrap(), y); + } + + #[cfg(feature = "dev")] + #[test] + fn encoded_point_into_jwk() { + let jwk = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap(); + let point = jwk.to_encoded_point::().unwrap(); + let jwk2 = JwkEcKey::from_encoded_point::(&point).unwrap(); + assert_eq!(jwk, jwk2); + } +} diff --git a/elliptic-curve/src/lib.rs b/elliptic-curve/src/lib.rs new file mode 100644 index 000000000..ff5acd88f --- /dev/null +++ b/elliptic-curve/src/lib.rs @@ -0,0 +1,200 @@ +#![no_std] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" +)] +#![forbid(unsafe_code)] +#![warn( + clippy::cast_lossless, + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::cast_sign_loss, + clippy::checked_conversions, + clippy::implicit_saturating_sub, + clippy::mod_module_files, + clippy::panic, + clippy::panic_in_result_fn, + clippy::std_instead_of_alloc, + clippy::std_instead_of_core, + clippy::unwrap_used, + missing_debug_implementations, + missing_docs, + rust_2018_idioms, + unused_lifetimes, + unused_qualifications +)] + +//! ## Usage +//! +//! This crate provides traits for describing elliptic curves, along with +//! types which are generic over elliptic curves which can be used as the +//! basis of curve-agnostic code. +//! +//! It's intended to be used with the following concrete elliptic curve +//! implementations from the [`RustCrypto/elliptic-curves`] project: +//! +//! - [`bp256`]: brainpoolP256r1 and brainpoolP256t1 +//! - [`bp384`]: brainpoolP384r1 and brainpoolP384t1 +//! - [`k256`]: secp256k1 a.k.a. K-256 +//! - [`p224`]: NIST P-224 a.k.a. secp224r1 +//! - [`p256`]: NIST P-256 a.k.a secp256r1, prime256v1 +//! - [`p384`]: NIST P-384 a.k.a. secp384r1 +//! - [`p521`]: NIST P-521 a.k.a. secp521r1 +//! +//! The [`ecdsa`] crate provides a generic implementation of the +//! Elliptic Curve Digital Signature Algorithm which can be used with any of +//! the above crates, either via an external ECDSA implementation, or +//! using native curve arithmetic where applicable. +//! +//! ## Type conversions +//! +//! The following chart illustrates the various conversions possible between +//! the various types defined by this crate. +//! +//! ![Type Conversion Map](https://raw.githubusercontent.com/RustCrypto/media/master/img/elliptic-curve/type-transforms.svg) +//! +//! ## `serde` support +//! +//! When the `serde` feature of this crate is enabled, `Serialize` and +//! `Deserialize` impls are provided for the following types: +//! +//! - [`JwkEcKey`] +//! - [`PublicKey`] +//! - [`ScalarPrimitive`] +//! +//! Please see type-specific documentation for more information. +//! +//! [`RustCrypto/elliptic-curves`]: https://github.com/RustCrypto/elliptic-curves +//! [`bp256`]: https://github.com/RustCrypto/elliptic-curves/tree/master/bp256 +//! [`bp384`]: https://github.com/RustCrypto/elliptic-curves/tree/master/bp384 +//! [`k256`]: https://github.com/RustCrypto/elliptic-curves/tree/master/k256 +//! [`p224`]: https://github.com/RustCrypto/elliptic-curves/tree/master/p224 +//! [`p256`]: https://github.com/RustCrypto/elliptic-curves/tree/master/p256 +//! [`p384`]: https://github.com/RustCrypto/elliptic-curves/tree/master/p384 +//! [`p521`]: https://github.com/RustCrypto/elliptic-curves/tree/master/p521 +//! [`ecdsa`]: https://github.com/RustCrypto/signatures/tree/master/ecdsa + +#[cfg(feature = "alloc")] +#[allow(unused_imports)] +#[macro_use] +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + +pub mod point; +pub mod scalar; + +#[cfg(feature = "dev")] +pub mod dev; +#[cfg(feature = "ecdh")] +pub mod ecdh; +#[cfg(feature = "hash2curve")] +pub mod hash2curve; +#[cfg(feature = "arithmetic")] +pub mod ops; +#[cfg(feature = "sec1")] +pub mod sec1; +#[cfg(feature = "arithmetic")] +pub mod weierstrass; + +mod error; +mod field; +mod secret_key; + +#[cfg(feature = "arithmetic")] +mod arithmetic; +#[cfg(feature = "arithmetic")] +mod public_key; + +#[cfg(feature = "jwk")] +mod jwk; + +#[cfg(feature = "voprf")] +mod voprf; + +pub use crate::{ + error::{Error, Result}, + field::{FieldBytes, FieldBytesEncoding, FieldBytesSize}, + scalar::ScalarPrimitive, + secret_key::SecretKey, +}; +pub use crypto_bigint as bigint; +pub use hybrid_array as array; +pub use hybrid_array::typenum::consts; +pub use rand_core; +pub use subtle; +pub use zeroize; + +#[cfg(feature = "arithmetic")] +pub use { + crate::{ + arithmetic::{CurveArithmetic, PrimeCurveArithmetic}, + point::{AffinePoint, BatchNormalize, ProjectivePoint}, + public_key::PublicKey, + scalar::{NonZeroScalar, Scalar}, + }, + ff::{self, Field, PrimeField}, + group::{self, Group}, +}; + +#[cfg(feature = "jwk")] +pub use crate::jwk::{JwkEcKey, JwkParameters}; + +#[cfg(feature = "pkcs8")] +pub use pkcs8; + +#[cfg(feature = "voprf")] +pub use crate::voprf::VoprfParameters; + +use core::{ + fmt::Debug, + ops::{Add, ShrAssign}, +}; +use hybrid_array::ArraySize; + +/// Algorithm [`ObjectIdentifier`][`pkcs8::ObjectIdentifier`] for elliptic +/// curve public key cryptography (`id-ecPublicKey`). +/// +/// +#[cfg(feature = "pkcs8")] +pub const ALGORITHM_OID: pkcs8::ObjectIdentifier = + pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); + +/// Elliptic curve. +/// +/// This trait is intended to be impl'd by a ZST which represents a concrete +/// elliptic curve. +/// +/// Other traits in this crate which are bounded by [`Curve`] are intended to +/// be impl'd by these ZSTs, facilitating types which are generic over elliptic +/// curves (e.g. [`SecretKey`]). +pub trait Curve: 'static + Copy + Clone + Debug + Default + Eq + Ord + Send + Sync { + /// Size of a serialized field element in bytes. + /// + /// This is typically the same as `Self::Uint::ByteSize` but for curves + /// with an unusual field modulus (e.g. P-224, P-521) it may be different. + type FieldBytesSize: ArraySize + Add + Eq; + + /// Integer type used to represent field elements of this elliptic curve. + type Uint: bigint::ArrayEncoding + + bigint::AddMod + + bigint::Encoding + + bigint::FixedInteger + + bigint::NegMod + + bigint::Random + + bigint::RandomMod + + bigint::SubMod + + zeroize::Zeroize + + FieldBytesEncoding + + ShrAssign; + + /// Order of this elliptic curve, i.e. number of elements in the scalar + /// field. + const ORDER: Self::Uint; +} + +/// Marker trait for elliptic curves with prime order. +pub trait PrimeCurve: Curve {} diff --git a/elliptic-curve/src/ops.rs b/elliptic-curve/src/ops.rs new file mode 100644 index 000000000..46bde0b87 --- /dev/null +++ b/elliptic-curve/src/ops.rs @@ -0,0 +1,209 @@ +//! Traits for arithmetic operations on elliptic curve field elements. + +pub use core::ops::{Add, AddAssign, Mul, Neg, Shr, ShrAssign, Sub, SubAssign}; + +use crypto_bigint::Integer; +use group::Group; +use subtle::{Choice, ConditionallySelectable, CtOption}; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +/// Perform an inversion on a field element (i.e. base field element or scalar) +pub trait Invert { + /// Field element type + type Output; + + /// Invert a field element. + fn invert(&self) -> Self::Output; + + /// Invert a field element in variable time. + /// + /// ⚠️ WARNING! + /// + /// This method should not be used with secret values, as its variable-time + /// operation can potentially leak secrets through sidechannels. + fn invert_vartime(&self) -> Self::Output { + // Fall back on constant-time implementation by default. + self.invert() + } +} + +/// Perform a batched inversion on a sequence of field elements (i.e. base field elements or scalars) +/// at an amortized cost that should be practically as efficient as a single inversion. +pub trait BatchInvert: Invert + Sized { + /// The output of batch inversion. A container of field elements. + type Output: AsRef<[Self]>; + + /// Invert a batch of field elements. + fn batch_invert( + field_elements: &FieldElements, + ) -> CtOption<>::Output>; +} + +impl BatchInvert<[T; N]> for T +where + T: Invert> + + Mul + + Copy + + Default + + ConditionallySelectable, +{ + type Output = [Self; N]; + + fn batch_invert(field_elements: &[Self; N]) -> CtOption<[Self; N]> { + let mut field_elements_multiples = [Self::default(); N]; + let mut field_elements_multiples_inverses = [Self::default(); N]; + let mut field_elements_inverses = [Self::default(); N]; + + let inversion_succeeded = invert_batch_internal( + field_elements, + &mut field_elements_multiples, + &mut field_elements_multiples_inverses, + &mut field_elements_inverses, + ); + + CtOption::new(field_elements_inverses, inversion_succeeded) + } +} + +#[cfg(feature = "alloc")] +impl BatchInvert<[T]> for T +where + T: Invert> + + Mul + + Copy + + Default + + ConditionallySelectable, +{ + type Output = Vec; + + fn batch_invert(field_elements: &[Self]) -> CtOption> { + let mut field_elements_multiples: Vec = vec![Self::default(); field_elements.len()]; + let mut field_elements_multiples_inverses: Vec = + vec![Self::default(); field_elements.len()]; + let mut field_elements_inverses: Vec = vec![Self::default(); field_elements.len()]; + + let inversion_succeeded = invert_batch_internal( + field_elements, + field_elements_multiples.as_mut(), + field_elements_multiples_inverses.as_mut(), + field_elements_inverses.as_mut(), + ); + + CtOption::new( + field_elements_inverses.into_iter().collect(), + inversion_succeeded, + ) + } +} + +/// Implements "Montgomery's trick", a trick for computing many modular inverses at once. +/// +/// "Montgomery's trick" works by reducing the problem of computing `n` inverses +/// to computing a single inversion, plus some storage and `O(n)` extra multiplications. +/// +/// See: https://iacr.org/archive/pkc2004/29470042/29470042.pdf section 2.2. +fn invert_batch_internal< + T: Invert> + Mul + Default + ConditionallySelectable, +>( + field_elements: &[T], + field_elements_multiples: &mut [T], + field_elements_multiples_inverses: &mut [T], + field_elements_inverses: &mut [T], +) -> Choice { + let batch_size = field_elements.len(); + if batch_size == 0 + || batch_size != field_elements_multiples.len() + || batch_size != field_elements_multiples_inverses.len() + { + return Choice::from(0); + } + + field_elements_multiples[0] = field_elements[0]; + for i in 1..batch_size { + // $ a_n = a_{n-1}*x_n $ + field_elements_multiples[i] = field_elements_multiples[i - 1] * field_elements[i]; + } + + field_elements_multiples[batch_size - 1] + .invert() + .map(|multiple_of_inverses_of_all_field_elements| { + field_elements_multiples_inverses[batch_size - 1] = + multiple_of_inverses_of_all_field_elements; + for i in (1..batch_size).rev() { + // $ a_{n-1} = {a_n}^{-1}*x_n $ + field_elements_multiples_inverses[i - 1] = + field_elements_multiples_inverses[i] * field_elements[i]; + } + + field_elements_inverses[0] = field_elements_multiples_inverses[0]; + for i in 1..batch_size { + // $ {x_n}^{-1} = a_{n}^{-1}*a_{n-1} $ + field_elements_inverses[i] = + field_elements_multiples_inverses[i] * field_elements_multiples[i - 1]; + } + }) + .is_some() +} + +/// Linear combination. +/// +/// This trait enables optimized implementations of linear combinations (e.g. Shamir's Trick). +/// +/// It's generic around `PointsAndScalars` to allow overlapping impls. For example, const generic +/// impls can use the input size to determine the size needed to store temporary variables. +pub trait LinearCombination: group::Curve +where + PointsAndScalars: AsRef<[(Self, Self::Scalar)]> + ?Sized, +{ + /// Calculates `x1 * k1 + ... + xn * kn`. + fn lincomb(points_and_scalars: &PointsAndScalars) -> Self { + points_and_scalars + .as_ref() + .iter() + .copied() + .map(|(point, scalar)| point * scalar) + .sum() + } +} + +/// Multiplication by the generator. +/// +/// May use optimizations (e.g. precomputed tables) when available. +// TODO(tarcieri): replace this with `Group::mul_by_generator``? (see zkcrypto/group#44) +pub trait MulByGenerator: Group { + /// Multiply by the generator of the prime-order subgroup. + #[must_use] + fn mul_by_generator(scalar: &Self::Scalar) -> Self { + Self::generator() * scalar + } +} + +/// Modular reduction. +pub trait Reduce: Sized { + /// Bytes used as input to [`Reduce::reduce_bytes`]. + type Bytes: AsRef<[u8]>; + + /// Perform a modular reduction, returning a field element. + fn reduce(n: Uint) -> Self; + + /// Interpret the given bytes as an integer and perform a modular reduction. + fn reduce_bytes(bytes: &Self::Bytes) -> Self; +} + +/// Modular reduction to a non-zero output. +/// +/// This trait is primarily intended for use by curve implementations such +/// as the `k256` and `p256` crates. +/// +/// End users should use the [`Reduce`] impl on +/// [`NonZeroScalar`][`crate::NonZeroScalar`] instead. +pub trait ReduceNonZero: Reduce + Sized { + /// Perform a modular reduction, returning a field element. + fn reduce_nonzero(n: Uint) -> Self; + + /// Interpret the given bytes as an integer and perform a modular reduction + /// to a non-zero output. + fn reduce_nonzero_bytes(bytes: &Self::Bytes) -> Self; +} diff --git a/elliptic-curve/src/point.rs b/elliptic-curve/src/point.rs new file mode 100644 index 000000000..ee4eded44 --- /dev/null +++ b/elliptic-curve/src/point.rs @@ -0,0 +1,81 @@ +//! Traits for elliptic curve points. + +#[cfg(feature = "arithmetic")] +mod non_identity; + +#[cfg(feature = "arithmetic")] +pub use {self::non_identity::NonIdentity, crate::CurveArithmetic}; + +use crate::{Curve, FieldBytes}; +use subtle::{Choice, CtOption}; + +/// Affine point type for a given curve with a [`CurveArithmetic`] +/// implementation. +#[cfg(feature = "arithmetic")] +pub type AffinePoint = ::AffinePoint; + +/// Projective point type for a given curve with a [`CurveArithmetic`] +/// implementation. +#[cfg(feature = "arithmetic")] +pub type ProjectivePoint = ::ProjectivePoint; + +/// Access to the affine coordinates of an elliptic curve point. +// TODO: use zkcrypto/group#30 coordinate API when available +pub trait AffineCoordinates { + /// Field element representation. + type FieldRepr: AsRef<[u8]>; + + /// Get the affine x-coordinate as a serialized field element. + fn x(&self) -> Self::FieldRepr; + + /// Is the affine y-coordinate odd? + fn y_is_odd(&self) -> Choice; +} + +/// Normalize point(s) in projective representation by converting them to their affine ones. +#[cfg(feature = "arithmetic")] +pub trait BatchNormalize: group::Curve { + /// The output of the batch normalization; a container of affine points. + type Output: AsRef<[Self::AffineRepr]>; + + /// Perform a batched conversion to affine representation on a sequence of projective points + /// at an amortized cost that should be practically as efficient as a single conversion. + /// Internally, implementors should rely upon `InvertBatch`. + fn batch_normalize(points: &Points) -> >::Output; +} + +/// Double a point (i.e. add it to itself) +pub trait Double { + /// Double this point. + fn double(&self) -> Self; +} + +/// Decompress an elliptic curve point. +/// +/// Point decompression recovers an original curve point from its x-coordinate +/// and a boolean flag indicating whether or not the y-coordinate is odd. +pub trait DecompressPoint: Sized { + /// Attempt to decompress an elliptic curve point. + fn decompress(x: &FieldBytes, y_is_odd: Choice) -> CtOption; +} + +/// Decompact an elliptic curve point from an x-coordinate. +/// +/// Decompaction relies on properties of specially-generated keys but provides +/// a more compact representation than standard point compression. +pub trait DecompactPoint: Sized { + /// Attempt to decompact an elliptic curve point + fn decompact(x: &FieldBytes) -> CtOption; +} + +/// Point compression settings. +pub trait PointCompression { + /// Should point compression be applied by default? + const COMPRESS_POINTS: bool; +} + +/// Point compaction settings. +pub trait PointCompaction { + /// Should point compaction be applied by default? + const COMPACT_POINTS: bool; +} diff --git a/elliptic-curve/src/point/non_identity.rs b/elliptic-curve/src/point/non_identity.rs new file mode 100644 index 000000000..a0f1e85ff --- /dev/null +++ b/elliptic-curve/src/point/non_identity.rs @@ -0,0 +1,238 @@ +//! Non-identity point type. + +use core::ops::{Deref, Mul}; + +use group::{prime::PrimeCurveAffine, Curve, GroupEncoding}; +use rand_core::{CryptoRng, RngCore}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +#[cfg(feature = "serde")] +use serdect::serde::{de, ser, Deserialize, Serialize}; + +use crate::{CurveArithmetic, NonZeroScalar, Scalar}; + +/// Non-identity point type. +/// +/// This type ensures that its value is not the identity point, ala `core::num::NonZero*`. +/// +/// In the context of ECC, it's useful for ensuring that certain arithmetic +/// cannot result in the identity point. +#[derive(Clone, Copy, Debug)] +pub struct NonIdentity