diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 0000000..2e06d15 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,2 @@ +allow-unwrap-in-consts = true +allow-unwrap-in-tests = true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5cde165..e621f15 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,19 @@ version: 2 updates: - package-ecosystem: cargo + versioning-strategy: lockfile-only directory: "/" + allow: + - dependency-type: "all" + groups: + all-deps: + patterns: + - "*" schedule: - interval: daily + interval: weekly + open-pull-requests-limit: 10 +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly open-pull-requests-limit: 10 diff --git a/.github/workflows/belt-mac.yml b/.github/workflows/belt-mac.yml new file mode 100644 index 0000000..12418a5 --- /dev/null +++ b/.github/workflows/belt-mac.yml @@ -0,0 +1,60 @@ +name: belt-mac + +on: + pull_request: + paths: + - ".github/workflows/belt-mac.yml" + - "belt-mac/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: belt-mac + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.85.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --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.85.0 # MSRV + - stable + steps: + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack test --feature-powerset + - run: cargo test --release --all-features diff --git a/.github/workflows/cbc-mac.yml b/.github/workflows/cbc-mac.yml index 7a8cf96..f164c32 100644 --- a/.github/workflows/cbc-mac.yml +++ b/.github/workflows/cbc-mac.yml @@ -3,6 +3,7 @@ name: cbc-mac on: pull_request: paths: + - ".github/workflows/cbc-mac.yml" - "cbc-mac/**" - "Cargo.*" push: @@ -23,48 +24,38 @@ jobs: strategy: matrix: rust: - - 1.56.0 # MSRV + - 1.85.0 # MSRV - stable target: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --no-default-features --release --target ${{ matrix.target }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --target ${{ matrix.target }} minimal-versions: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - profile: minimal - - run: rm ../Cargo.toml - - run: cargo update -Z minimal-versions - - run: cargo test --release - - run: cargo test --release --all-features + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} test: runs-on: ubuntu-latest strategy: matrix: rust: - - 1.56.0 # MSRV + - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release --no-default-features - - run: cargo test --release + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack test --feature-powerset + - run: cargo test --release --all-features diff --git a/.github/workflows/cmac.yml b/.github/workflows/cmac.yml index 643e093..0ab57e5 100644 --- a/.github/workflows/cmac.yml +++ b/.github/workflows/cmac.yml @@ -3,6 +3,7 @@ name: cmac on: pull_request: paths: + - ".github/workflows/cmac.yml" - "cmac/**" - "Cargo.*" push: @@ -22,48 +23,38 @@ jobs: strategy: matrix: rust: - - 1.56.0 # MSRV + - 1.85.0 # MSRV - stable target: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --no-default-features --release --target ${{ matrix.target }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --target ${{ matrix.target }} minimal-versions: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - profile: minimal - - run: rm ../Cargo.toml - - run: cargo update -Z minimal-versions - - run: cargo test --release - - run: cargo test --release --all-features + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} test: runs-on: ubuntu-latest strategy: matrix: rust: - - 1.56.0 # MSRV + - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release --no-default-features - - run: cargo test --release + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack test --feature-powerset + - run: cargo test --release --all-features diff --git a/.github/workflows/hmac.yml b/.github/workflows/hmac.yml index f5cbce4..c0cbd20 100644 --- a/.github/workflows/hmac.yml +++ b/.github/workflows/hmac.yml @@ -3,6 +3,7 @@ name: hmac on: pull_request: paths: + - ".github/workflows/hmac.yml" - "hmac/**" - "Cargo.*" push: @@ -23,49 +24,38 @@ jobs: strategy: matrix: rust: - - 1.41.0 # MSRV + - 1.85.0 # MSRV - stable target: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --no-default-features --release --target ${{ matrix.target }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --target ${{ matrix.target }} minimal-versions: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - profile: minimal - - run: rm ../Cargo.toml - - run: cargo update -Z minimal-versions - - run: cargo test --release - - run: cargo test --release --all-features + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} test: runs-on: ubuntu-latest strategy: matrix: rust: - - 1.41.0 # MSRV + - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release --no-default-features - - run: cargo test --release --features reset - - run: cargo test --release + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack test --feature-powerset + - run: cargo test --release --all-features diff --git a/.github/workflows/pmac.yml b/.github/workflows/pmac.yml index 6037e0b..7bc4a0a 100644 --- a/.github/workflows/pmac.yml +++ b/.github/workflows/pmac.yml @@ -3,6 +3,7 @@ name: pmac on: pull_request: paths: + - ".github/workflows/pmac.yml" - "pmac/**" - "Cargo.*" push: @@ -22,48 +23,38 @@ jobs: strategy: matrix: rust: - - 1.56.0 # MSRV + - 1.85.0 # MSRV - stable target: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --no-default-features --release --target ${{ matrix.target }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --target ${{ matrix.target }} minimal-versions: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - profile: minimal - - run: rm ../Cargo.toml - - run: cargo update -Z minimal-versions - - run: cargo test --release - - run: cargo test --release --all-features + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} test: runs-on: ubuntu-latest strategy: matrix: rust: - - 1.56.0 # MSRV + - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release --no-default-features - - run: cargo test --release + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack test --feature-powerset + - run: cargo test --release --all-features diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..34a19fc --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,44 @@ +name: Publish to crates.io +on: + push: + tags: [ + 'belt-mac-v*', + 'cbc-mac-v*', + 'cmac-v*', + 'hmac-v*', + 'pmac-v*', + 'retail-mac-v*', + ] + +jobs: + publish: + runs-on: ubuntu-latest + environment: publish + permissions: + id-token: write + steps: + - uses: actions/checkout@v6 + - uses: rust-lang/crates-io-auth-action@v1 + id: auth + + - name: Extract Crate Name and Version + run: | + TAG_NAME="${{ github.ref_name }}" + CRATE_NAME=${TAG_NAME%-v*} + CRATE_VERSION=${TAG_NAME##*-v} + echo $CRATE_NAME $CRATE_VERSION + echo "CRATE_NAME=${CRATE_NAME}" >> $GITHUB_ENV + echo "CRATE_VERSION=${CRATE_VERSION}" >> $GITHUB_ENV + + - name: Check crate version + working-directory: ${{ env.CRATE_NAME }} + run: | + CRATE_TOML_VERSION=$(grep "^version =" Cargo.toml | cut -d'"' -f2) + echo $CRATE_TOML_VERSION + [[ $CRATE_TOML_VERSION == $CRATE_VERSION ]] + + - name: Publish + working-directory: ${{ env.CRATE_NAME }} + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + run: cargo publish diff --git a/.github/workflows/retail-mac.yml b/.github/workflows/retail-mac.yml new file mode 100644 index 0000000..38d9c7f --- /dev/null +++ b/.github/workflows/retail-mac.yml @@ -0,0 +1,60 @@ +name: retail-mac + +on: + pull_request: + paths: + - ".github/workflows/retail-mac.yml" + - "retail-mac/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: retail-mac + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.85.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --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.85.0 # MSRV + - stable + steps: + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack test --feature-powerset + - run: cargo test --release --all-features diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index a7c75b0..17a9222 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -6,36 +6,50 @@ on: - README.md push: branches: master - paths-ignore: - - README.md jobs: clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v6 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master with: - profile: minimal - toolchain: 1.56.0 # MSRV + toolchain: 1.93.0 components: clippy - - run: cargo clippy --all -- -D warnings + - run: cargo clippy --workspace --all-features --lib --bins --tests -- -D warnings - rustfmt: + doc: runs-on: ubuntu-latest steps: - - name: Checkout sources - uses: actions/checkout@v1 + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + - env: + RUSTDOCFLAGS: "-Dwarnings" + run: cargo doc --workspace --all-features - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 + rustfmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: stable components: rustfmt + - run: cargo fmt --all -- --check - - name: Run cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + typos: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: crate-ci/typos@v1 + + lock: + name: Check Cargo.lock + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@stable + - run: cargo check --workspace --locked diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 0000000..45626b3 --- /dev/null +++ b/.typos.toml @@ -0,0 +1,9 @@ +[files] +extend-exclude = [ + ".git/" +] + +[default.extend-words] +"GOST" = "GOST" +"DAA" = "DAA" +"EDE" = "EDE" diff --git a/Cargo.lock b/Cargo.lock index 821452d..fa677c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,36 +1,56 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aes" -version = "0.8.1" +version = "0.9.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" +checksum = "04097e08a47d9ad181c2e1f4a5fabc9ae06ce8839a333ba9a949bcb0d31fd2a3" dependencies = [ - "cfg-if", "cipher", - "cpufeatures", + "cpubits", + "cpufeatures 0.2.17", +] + +[[package]] +name = "belt-block" +version = "0.2.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e3b1e9d1ad19c345095575076767febd525013fc5782276a21069901815ea45" +dependencies = [ + "cipher", +] + +[[package]] +name = "belt-mac" +version = "0.2.0-pre" +dependencies = [ + "belt-block", + "cipher", + "digest", + "hex-literal", ] [[package]] name = "blobby" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "847495c209977a90e8aad588b959d0ca9f5dc228096d29a6bd3defd53f35eaec" +checksum = "89af0b093cc13baa4e51e64e65ec2422f7e73aea0e612e5ad3872986671622f1" [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ - "generic-array", + "hybrid-array", + "zeroize", ] [[package]] name = "cbc-mac" -version = "0.1.0" +version = "0.2.0-rc.5" dependencies = [ "aes", "cipher", @@ -41,16 +61,17 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cipher" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "035bd298db1557b73a277e9c599c5a50e0d2e6ee9dcac78f3f951cb2f7d88d8c" +checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" dependencies = [ + "block-buffer", "crypto-common", "inout", "zeroize", @@ -58,7 +79,7 @@ dependencies = [ [[package]] name = "cmac" -version = "0.7.0" +version = "0.8.0-rc.5" dependencies = [ "aes", "cipher", @@ -70,107 +91,160 @@ dependencies = [ "magma", ] +[[package]] +name = "cmov" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3" + +[[package]] +name = "cpubits" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef0c543070d296ea414df2dd7625d1b24866ce206709d8a4a424f28377f5861" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ "libc", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ - "generic-array", - "typenum", + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1005a6d4446f5120ef475ad3d2af2b30c49c2c9c6904258e3bb30219bebed5e4" +dependencies = [ + "cmov", ] [[package]] name = "dbl" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" +checksum = "f0d7a944e61df464668c5f51f56cc667396a8821434273112948ea0b66e405d7" dependencies = [ - "generic-array", + "hybrid-array", ] [[package]] name = "des" -version = "0.8.1" +version = "0.9.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +checksum = "3214053e68a813b9c06ef61075c844f3a1cdeb307d8998ea8555c063caa52fa9" dependencies = [ "cipher", ] [[package]] name = "digest" -version = "0.10.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" dependencies = [ "blobby", "block-buffer", "crypto-common", - "subtle", + "ctutils", + "zeroize", ] [[package]] -name = "generic-array" -version = "0.14.5" +name = "hex-literal" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" + +[[package]] +name = "hmac" +version = "0.13.0" dependencies = [ - "typenum", - "version_check", + "digest", + "hex-literal", + "md-5", + "sha1", + "sha2", + "streebog", ] [[package]] -name = "hex-literal" -version = "0.3.4" +name = "hybrid-array" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" +checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" +dependencies = [ + "typenum", + "zeroize", +] [[package]] name = "inout" -version = "0.1.2" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1f03d4ab4d5dc9ec2d219f86c15d2a15fc08239d1cd3b2d6a19717c0a2f443" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" dependencies = [ - "generic-array", + "hybrid-array", ] [[package]] name = "kuznyechik" -version = "0.8.1" +version = "0.9.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8483b65417c9aca2348085fecc5eaef5af04834190b329dddd90f562204e632b" +checksum = "1845d0271ee188d9eddbb2b027738da1e492fe5622d6f5353aea2e1bd40a62ff" dependencies = [ + "cfg-if", "cipher", ] [[package]] name = "libc" -version = "0.2.118" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "magma" -version = "0.8.1" +version = "0.10.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2769afcfdd8e74b86e29ad12ef7f89e1e9c5d0b78cb50a6233a502c60d62f0" +checksum = "287314ef5d338202e7be522fbbae35cad5d1dff1b8cb079a395eec3a9e31104a" dependencies = [ "cipher", ] +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "pmac" -version = "0.7.0" +version = "0.8.0-rc.5" dependencies = [ "aes", "cipher", @@ -179,25 +253,55 @@ dependencies = [ ] [[package]] -name = "subtle" -version = "2.4.1" +name = "retail-mac" +version = "0.1.0-pre.1" +dependencies = [ + "aes", + "cipher", + "des", + "digest", + "hex-literal", +] + +[[package]] +name = "sha1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest", +] [[package]] -name = "typenum" -version = "1.15.0" +name = "sha2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest", +] + +[[package]] +name = "streebog" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b8aca83a17a387b90643abe15430abd0aabc91d6db511e85827e7060746ef" +dependencies = [ + "digest", +] [[package]] -name = "version_check" -version = "0.9.4" +name = "typenum" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "zeroize" -version = "1.5.2" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c88870063c39ee00ec285a2f8d6a966e5b6fb2becc4e8dac77ed0d370ed6006" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" diff --git a/Cargo.toml b/Cargo.toml index 550da60..e6b334a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,54 @@ [workspace] +resolver = "3" members = [ + "belt-mac", "cbc-mac", "cmac", + "hmac", "pmac", + "retail-mac", ] [profile.dev] opt-level = 2 + +[workspace.lints.clippy] +borrow_as_ptr = "warn" +cast_lossless = "warn" +cast_possible_truncation = "warn" +cast_possible_wrap = "warn" +cast_precision_loss = "warn" +cast_sign_loss = "warn" +checked_conversions = "warn" +doc_markdown = "warn" +from_iter_instead_of_collect = "warn" +implicit_saturating_sub = "warn" +manual_assert = "warn" +map_unwrap_or = "warn" +missing_errors_doc = "warn" +missing_panics_doc = "warn" +mod_module_files = "warn" +must_use_candidate = "warn" +needless_range_loop = "allow" +ptr_as_ptr = "warn" +redundant_closure_for_method_calls = "warn" +ref_as_ptr = "warn" +return_self_not_must_use = "warn" +semicolon_if_nothing_returned = "warn" +trivially_copy_pass_by_ref = "warn" +std_instead_of_alloc = "warn" +std_instead_of_core = "warn" +undocumented_unsafe_blocks = "warn" +unnecessary_safety_comment = "warn" +unwrap_in_result = "warn" +unwrap_used = "warn" + +[workspace.lints.rust] +missing_copy_implementations = "warn" +missing_debug_implementations = "warn" +missing_docs = "warn" +trivial_casts = "warn" +trivial_numeric_casts = "warn" +unsafe_code = "forbid" +unused_lifetimes = "warn" +unused_qualifications = "warn" diff --git a/README.md b/README.md index b5a4d0f..7a86a01 100644 --- a/README.md +++ b/README.md @@ -8,23 +8,21 @@ Collection of [Message Authentication Code][1] (MAC) algorithms written in pure ## Supported Algorithms -| Algorithm | Crate | Crates.io | Documentation | MSRV | -|-----------|----------|:-------------:|:-------------:|:----:| -| [CBC-MAC] | [`cbc-mac`] | [![crates.io](https://img.shields.io/crates/v/cbc-mac.svg)](https://crates.io/crates/cbc-mac) | [![Documentation](https://docs.rs/cbc-mac/badge.svg)](https://docs.rs/cbc-mac) | ![MSRV 1.56][msrv-1.56] | -| [CMAC] | [`cmac`] | [![crates.io](https://img.shields.io/crates/v/cmac.svg)](https://crates.io/crates/cmac) | [![Documentation](https://docs.rs/cmac/badge.svg)](https://docs.rs/cmac) | ![MSRV 1.56][msrv-1.56] | -| [HMAC] | [`hmac`] | [![crates.io](https://img.shields.io/crates/v/hmac.svg)](https://crates.io/crates/hmac) | [![Documentation](https://docs.rs/hmac/badge.svg)](https://docs.rs/hmac) | ![MSRV 1.41][msrv-1.41] | -| [PMAC] | [`pmac`] | [![crates.io](https://img.shields.io/crates/v/pmac.svg)](https://crates.io/crates/pmac) | [![Documentation](https://docs.rs/pmac/badge.svg)](https://docs.rs/pmac) | ![MSRV 1.56][msrv-1.56] | - -### Minimum Supported Rust Version (MSRV) Policy - -MSRV bumps are considered breaking changes and will be performed only with minor version bump. +| Algorithm | Crate | Crates.io | Documentation | MSRV | +|--------------|----------------|:---------:|:-------------:|:----:| +| [BelT MAC] | [`belt-mac`] | [![crates.io](https://img.shields.io/crates/v/belt-mac.svg)](https://crates.io/crates/belt-mac) | [![Documentation](https://docs.rs/belt-mac/badge.svg)](https://docs.rs/belt-mac) | ![MSRV 1.85][msrv-1.85] | +| [CBC-MAC] | [`cbc-mac`] | [![crates.io](https://img.shields.io/crates/v/cbc-mac.svg)](https://crates.io/crates/cbc-mac) | [![Documentation](https://docs.rs/cbc-mac/badge.svg)](https://docs.rs/cbc-mac) | ![MSRV 1.85][msrv-1.85] | +| [CMAC] | [`cmac`] | [![crates.io](https://img.shields.io/crates/v/cmac.svg)](https://crates.io/crates/cmac) | [![Documentation](https://docs.rs/cmac/badge.svg)](https://docs.rs/cmac) | ![MSRV 1.85][msrv-1.85] | +| [HMAC] | [`hmac`] | [![crates.io](https://img.shields.io/crates/v/hmac.svg)](https://crates.io/crates/hmac) | [![Documentation](https://docs.rs/hmac/badge.svg)](https://docs.rs/hmac) | ![MSRV 1.85][msrv-1.85] | +| [PMAC] | [`pmac`] | [![crates.io](https://img.shields.io/crates/v/pmac.svg)](https://crates.io/crates/pmac) | [![Documentation](https://docs.rs/pmac/badge.svg)](https://docs.rs/pmac) | ![MSRV 1.85][msrv-1.85] | +| [Retail MAC] | [`retail-mac`] | [![crates.io](https://img.shields.io/crates/v/retail-mac.svg)](https://crates.io/crates/retail-mac) | [![Documentation](https://docs.rs/retail-mac/badge.svg)](https://docs.rs/retail-mac) | ![MSRV 1.85][msrv-1.85] | ## 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) +* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +* [MIT license](http://opensource.org/licenses/MIT) at your option. @@ -39,15 +37,16 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg [deps-image]: https://deps.rs/repo/github/RustCrypto/MACs/status.svg [deps-link]: https://deps.rs/repo/github/RustCrypto/MACs -[msrv-1.41]: https://img.shields.io/badge/rustc-1.41.0+-blue.svg -[msrv-1.56]: https://img.shields.io/badge/rustc-1.56.0+-blue.svg +[msrv-1.85]: https://img.shields.io/badge/rustc-1.85+-blue.svg [//]: # (crates) +[`belt-mac`]: ./belt-mac [`cbc-mac`]: ./cbc-mac [`cmac`]: ./cmac [`hmac`]: ./hmac [`pmac`]: ./pmac +[`retail-mac`]: ./retail-mac [//]: # (footnotes) @@ -55,8 +54,9 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [//]: # (algorithms) +[BelT MAC]: https://apmi.bsu.by/assets/files/std/belt-spec371.pdf [CBC-MAC]: https://en.wikipedia.org/wiki/CBC-MAC [CMAC]: https://en.wikipedia.org/wiki/One-key_MAC [HMAC]: https://en.wikipedia.org/wiki/HMAC [PMAC]: https://en.wikipedia.org/wiki/PMAC_(cryptography) - +[Retail MAC]: https://en.wikipedia.org/wiki/ISO/IEC_9797-1#MAC_algorithm_3 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..37d058c --- /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/MACs/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/belt-mac/CHANGELOG.md b/belt-mac/CHANGELOG.md new file mode 100644 index 0000000..db46f89 --- /dev/null +++ b/belt-mac/CHANGELOG.md @@ -0,0 +1,22 @@ +# 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 (UNRELEASED) +### Changed +- Edition changed to 2024 and MSRV bumped to 1.85 +- Relax MSRV policy and allow MSRV bumps in patch releases +- Update to `digest` v0.11 +- Update to `cipher` v0.5 +- Replace type aliases with newtypes ([#186]) + +### Removed +- `std` crate feature ([#186]) + +[#186]: https://github.com/RustCrypto/MACs/pull/186 + +## 0.1.0 (2023-04-03) +Initial Release \ No newline at end of file diff --git a/belt-mac/Cargo.toml b/belt-mac/Cargo.toml new file mode 100644 index 0000000..dc05494 --- /dev/null +++ b/belt-mac/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "belt-mac" +version = "0.2.0-pre" +description = "MAC specified by the BelT standard" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2024" +rust-version = "1.85" +readme = "README.md" +documentation = "https://docs.rs/belt-mac" +repository = "https://github.com/RustCrypto/MACs" +keywords = ["crypto", "mac", "belt-mac"] +categories = ["cryptography", "no-std"] + +[dependencies] +belt-block = "0.2.0-rc.3" +cipher = "0.5" +digest = { version = "0.11", features = ["mac"] } + +[dev-dependencies] +digest = { version = "0.11", features = ["dev"] } +hex-literal = "1" + +[features] +zeroize = ["cipher/zeroize", "digest/zeroize"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +all-features = true diff --git a/belt-mac/LICENSE-APACHE b/belt-mac/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/belt-mac/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/belt-mac/LICENSE-MIT b/belt-mac/LICENSE-MIT new file mode 100644 index 0000000..50c6180 --- /dev/null +++ b/belt-mac/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2023 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/belt-mac/README.md b/belt-mac/README.md new file mode 100644 index 0000000..7315f91 --- /dev/null +++ b/belt-mac/README.md @@ -0,0 +1,65 @@ +# [RustCrypto]: belt-mac + +[![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] + +Pure Rust implementation of [`belt-mac`][1]. + +# Example +```rust +use belt_mac::{digest::KeyInit, BeltMac, Mac}; +use hex_literal::hex; + +let key = [0x42; 32]; +let msg = b"input message"; +let expected_tag = hex!("9f5c9623b4eff8802195e81bcd841959"); + +// To get the authentication code: +let mut mac: BeltMac = BeltMac::new_from_slice(&key).unwrap(); +mac.update(msg); +let tag = mac.finalize(); +let tag_bytes = tag.into_bytes(); +assert_eq!(&tag_bytes[..], &expected_tag[..]); + +// To verify the message: +let mut mac: BeltMac = BeltMac::new_from_slice(&key).unwrap(); +mac.update(b"input message"); +mac.verify(&tag_bytes).unwrap(); +``` + +## 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/belt-mac.svg?logo=rust +[crate-link]: https://crates.io/crates/belt-mac +[docs-image]: https://docs.rs/belt-mac/badge.svg +[docs-link]: https://docs.rs/belt-mac/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260044-MACs +[build-image]: https://github.com/RustCrypto/MACs/actions/workflows/belt-mac.yml/badge.svg +[build-link]: https://github.com/RustCrypto/MACs/actions/workflows/belt-mac.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto +[1]: https://apmi.bsu.by/assets/files/std/belt-spec371.pdf diff --git a/belt-mac/benches/mod.rs b/belt-mac/benches/mod.rs new file mode 100644 index 0000000..8433217 --- /dev/null +++ b/belt-mac/benches/mod.rs @@ -0,0 +1,13 @@ +#![feature(test)] +extern crate test; + +use belt_mac::{BeltMac, KeyInit}; +use test::Bencher; + +digest::bench_update!( + BeltMac::new(&Default::default()); + belt_mac_10 10; + belt_mac_100 100; + belt_mac_1000 1000; + belt_mac_10000 10000; +); diff --git a/belt-mac/src/block_api.rs b/belt-mac/src/block_api.rs new file mode 100644 index 0000000..2c01c58 --- /dev/null +++ b/belt-mac/src/block_api.rs @@ -0,0 +1,187 @@ +use cipher::{BlockCipherEncBackend, BlockCipherEncClosure, BlockCipherEncrypt}; +use core::fmt; +use digest::{ + MacMarker, Output, OutputSizeUser, Reset, + array::{Array, ArraySize}, + block_api::{ + AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, FixedOutputCore, Lazy, + SmallBlockSizeUser, UpdateCore, + }, + block_buffer::BlockSizes, + common::{InnerInit, InnerUser}, +}; + +#[cfg(feature = "zeroize")] +use digest::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// Generic core Belt MAC instance, which operates over blocks. +#[derive(Clone)] +pub struct BeltMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + cipher: C, + state: Block, + r: Block, +} + +impl BlockSizeUser for BeltMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + type BlockSize = C::BlockSize; +} + +impl OutputSizeUser for BeltMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + type OutputSize = C::BlockSize; +} + +impl InnerUser for BeltMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + type Inner = C; +} + +impl MacMarker for BeltMacCore where C: BlockCipherEncrypt + SmallBlockSizeUser {} + +impl InnerInit for BeltMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + #[inline] + fn inner_init(cipher: C) -> Self { + let state = Default::default(); + let mut r = Default::default(); + cipher.encrypt_block(&mut r); + Self { cipher, state, r } + } +} + +impl BufferKindUser for BeltMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + type BufferKind = Lazy; +} + +impl UpdateCore for BeltMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + #[inline] + fn update_blocks(&mut self, blocks: &[Block]) { + struct Closure<'a, N: BlockSizes> { + state: &'a mut Block, + blocks: &'a [Block], + } + + impl BlockSizeUser for Closure<'_, N> { + type BlockSize = N; + } + + impl BlockCipherEncClosure for Closure<'_, N> { + #[inline(always)] + fn call>(self, backend: &B) { + for block in self.blocks { + xor(self.state, block); + backend.encrypt_block((self.state).into()); + } + } + } + + let Self { cipher, state, .. } = self; + cipher.encrypt_with_backend(Closure { state, blocks }); + } +} + +impl Reset for BeltMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + #[inline(always)] + fn reset(&mut self) { + self.state = Default::default(); + } +} + +impl FixedOutputCore for BeltMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + #[inline] + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { + let pos = buffer.get_pos(); + let mut buf = buffer.pad_with_zeros(); + + let cipher = &mut self.cipher; + let r = &self.r; + let bs = r.len(); + let mut new_r = Block::::default(); + if pos == bs { + // phi1 + let (h1, h2) = new_r.split_at_mut(bs - 4); + h1.copy_from_slice(&r[4..]); + for i in 0..4 { + h2[i] = r[i] ^ r[4 + i]; + } + } else { + buf[pos] = 0x80; + // phi2 + let (h1, h2) = new_r.split_at_mut(4); + for i in 0..4 { + h1[i] = r[i] ^ r[bs - 4 + i]; + } + h2.copy_from_slice(&r[..bs - 4]); + } + + let mut state = self.state.clone(); + xor(&mut state, &buf); + xor(&mut state, &new_r); + cipher.encrypt_block_b2b(&state, out); + } +} + +impl AlgorithmName for BeltMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("BeltMac") + } +} + +impl fmt::Debug for BeltMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("BeltMacCore { ... }") + } +} + +#[cfg(feature = "zeroize")] +impl Drop for BeltMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + fn drop(&mut self) { + self.state.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +impl ZeroizeOnDrop for BeltMacCore where + C: BlockCipherEncrypt + SmallBlockSizeUser + ZeroizeOnDrop +{ +} + +#[inline(always)] +fn xor(buf: &mut Array, data: &Array) { + for i in 0..N::USIZE { + buf[i] ^= data[i]; + } +} diff --git a/belt-mac/src/lib.rs b/belt-mac/src/lib.rs new file mode 100644 index 0000000..3d6644c --- /dev/null +++ b/belt-mac/src/lib.rs @@ -0,0 +1,25 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +pub use digest::{self, KeyInit, Mac}; + +/// Block-level implementation. +pub mod block_api; + +use cipher::BlockCipherEncrypt; +use digest::block_api::SmallBlockSizeUser; + +digest::buffer_fixed!( + /// Belt MAC instance generic over block cipher. + #[derive(Clone)] + pub struct GenericBeltMac(block_api::BeltMacCore); + impl: ResetMacTraits AlgorithmName InnerInit; +); + +/// Belt MAC instance. +pub type BeltMac = GenericBeltMac; diff --git a/belt-mac/tests/data/belt_mac_stb.blb b/belt-mac/tests/data/belt_mac_stb.blb new file mode 100644 index 0000000..0593330 Binary files /dev/null and b/belt-mac/tests/data/belt_mac_stb.blb differ diff --git a/belt-mac/tests/mod.rs b/belt-mac/tests/mod.rs new file mode 100644 index 0000000..45fd9bb --- /dev/null +++ b/belt-mac/tests/mod.rs @@ -0,0 +1,7 @@ +//! Test vectors from STB 34.101.31-2020: +//! + +use belt_mac::BeltMac; +use digest::dev::reset_mac_test; + +digest::new_mac_test!(belt_mac_stb, BeltMac, reset_mac_test, trunc_left); diff --git a/cbc-mac/CHANGELOG.md b/cbc-mac/CHANGELOG.md index 58b335f..0400c81 100644 --- a/cbc-mac/CHANGELOG.md +++ b/cbc-mac/CHANGELOG.md @@ -5,6 +5,19 @@ 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 (UNRELEASED) +### Changed +- Edition changed to 2024 and MSRV bumped to 1.85 +- Relax MSRV policy and allow MSRV bumps in patch releases +- Update to `digest` v0.11 +- Update to `cipher` v0.5 +- Replace type aliases with newtypes ([#186]) + +### Removed +- `std` crate feature ([#186]) + +[#186]: https://github.com/RustCrypto/MACs/pull/186 + ## 0.1.1 (2022-02-17) ### Fixed - Minimal versions build ([#108]) diff --git a/cbc-mac/Cargo.toml b/cbc-mac/Cargo.toml index 0afdb7e..6c4c18a 100644 --- a/cbc-mac/Cargo.toml +++ b/cbc-mac/Cargo.toml @@ -1,30 +1,32 @@ [package] name = "cbc-mac" -version = "0.1.1" # Also update html_root_url in lib.rs when bumping this +version = "0.2.0-rc.5" description = "Implementation of Cipher Block Chaining Message Authentication Code (CBC-MAC)" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.56" +edition = "2024" +rust-version = "1.85" readme = "README.md" documentation = "https://docs.rs/cbc-mac" repository = "https://github.com/RustCrypto/MACs" keywords = ["crypto", "mac", "daa"] [dependencies] -digest = { version = "0.10.3", features = ["mac"] } -cipher = "0.4.2" +cipher = "0.5" +digest = { version = "0.11", features = ["mac"] } [dev-dependencies] -aes = "0.8" -des = "0.8" -digest = { version = "0.10.3", features = ["dev"] } -hex-literal = "0.3" +digest = { version = "0.11", features = ["dev"] } +hex-literal = "1" + +aes = "0.9.0-rc.4" +des = "0.9.0-rc.3" [features] -std = ["digest/std"] -zeroize = ["cipher/zeroize"] +zeroize = ["cipher/zeroize", "digest/zeroize"] + +[lints] +workspace = true [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/cbc-mac/README.md b/cbc-mac/README.md index e958abb..2b177ce 100644 --- a/cbc-mac/README.md +++ b/cbc-mac/README.md @@ -1,27 +1,34 @@ -# RustCrypto: CBC-MAC +# [RustCrypto]: CBC-MAC [![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] -[![Build Status][build-image]][build-link] - -Pure Rust implementation of the [Cipher Block Chaining Message Authentication Code (CBC-MAC)][CBC-MAC]. -[Documentation][docs-link] +Generic implementation of [Cipher Block Chaining Message Authentication Code (CBC-MAC)][CBC-MAC]. -## Minimum Supported Rust Version +**WARNING!** The algorithm has known weaknesses in case of variable-length +messages. See the linked Wikipedia article for more information. -Rust **1.56** or higher. +## Examples -Minimum supported Rust version can be changed in the future, but it will be -done with a minor version bump. +```rust +use cbc_mac::{digest::KeyInit, CbcMac, Mac}; +use des::Des; +use hex_literal::hex; -## SemVer Policy +// CBC-MAC with the DES block cipher is equivalent to DAA +type Daa = CbcMac; -- All on-by-default features of this library are covered by SemVer -- MSRV is considered exempt from SemVer as noted above +// test from FIPS 113 +let key = hex!("0123456789ABCDEF"); +let mut mac = Daa::new_from_slice(&key).unwrap(); +mac.update(b"7654321 Now is the time for "); +let correct = hex!("F1D30F6849312CA4"); +mac.verify_slice(&correct).unwrap(); +``` ## License @@ -40,17 +47,18 @@ dual licensed as above, without any additional terms or conditions. [//]: # (badges) -[crate-image]: https://img.shields.io/crates/v/cbc-mac.svg +[crate-image]: https://img.shields.io/crates/v/cbc-mac.svg?logo=rust [crate-link]: https://crates.io/crates/cbc-mac [docs-image]: https://docs.rs/cbc-mac/badge.svg [docs-link]: https://docs.rs/cbc-mac/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260044-MACs -[build-image]: https://github.com/RustCrypto/MACs/workflows/cbc-mac/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/MACs/actions?query=workflow%3Acbc-mac +[build-image]: https://github.com/RustCrypto/MACs/actions/workflows/cbc-mac.yml/badge.svg +[build-link]: https://github.com/RustCrypto/MACs/actions/workflows/cbc-mac.yml [//]: # (general links) +[RustCrypto]: https://github.com/RustCrypto [CBC-MAC]: https://en.wikipedia.org/wiki/CBC-MAC diff --git a/cbc-mac/benches/mod.rs b/cbc-mac/benches/mod.rs index 3478920..acc55ca 100644 --- a/cbc-mac/benches/mod.rs +++ b/cbc-mac/benches/mod.rs @@ -2,7 +2,7 @@ extern crate test; use aes::Aes128; -use cbc_mac::{CbcMac, Mac}; +use cbc_mac::{CbcMac, KeyInit}; use des::Des; use test::Bencher; diff --git a/cbc-mac/src/block_api.rs b/cbc-mac/src/block_api.rs new file mode 100644 index 0000000..61e5450 --- /dev/null +++ b/cbc-mac/src/block_api.rs @@ -0,0 +1,167 @@ +use cipher::{BlockCipherEncBackend, BlockCipherEncClosure, BlockCipherEncrypt}; +use core::fmt; +use digest::{ + MacMarker, Output, OutputSizeUser, Reset, + array::{Array, ArraySize}, + block_api::{ + AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, Eager, FixedOutputCore, + SmallBlockSizeUser, UpdateCore, + }, + block_buffer::BlockSizes, + common::{InnerInit, InnerUser}, +}; + +#[cfg(feature = "zeroize")] +use digest::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// Generic core CMAC instance, which operates over blocks. +#[derive(Clone)] +pub struct CbcMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + cipher: C, + state: Block, +} + +impl BlockSizeUser for CbcMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + type BlockSize = C::BlockSize; +} + +impl OutputSizeUser for CbcMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + type OutputSize = C::BlockSize; +} + +impl InnerUser for CbcMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + type Inner = C; +} + +impl MacMarker for CbcMacCore where C: BlockCipherEncrypt + SmallBlockSizeUser {} + +impl InnerInit for CbcMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + #[inline] + fn inner_init(cipher: C) -> Self { + let state = Default::default(); + Self { cipher, state } + } +} + +impl BufferKindUser for CbcMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + type BufferKind = Eager; +} + +impl UpdateCore for CbcMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + #[inline] + fn update_blocks(&mut self, blocks: &[Block]) { + struct Closure<'a, N: BlockSizes> { + state: &'a mut Block, + blocks: &'a [Block], + } + + impl BlockSizeUser for Closure<'_, N> { + type BlockSize = N; + } + + impl BlockCipherEncClosure for Closure<'_, N> { + #[inline(always)] + fn call>(self, backend: &B) { + for block in self.blocks { + xor(self.state, block); + backend.encrypt_block((self.state).into()); + } + } + } + + let Self { cipher, state } = self; + cipher.encrypt_with_backend(Closure { state, blocks }); + } +} + +impl Reset for CbcMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + #[inline(always)] + fn reset(&mut self) { + self.state = Default::default(); + } +} + +impl FixedOutputCore for CbcMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + #[inline] + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { + let Self { state, cipher } = self; + let pos = buffer.get_pos(); + if pos != 0 { + xor(state, &buffer.pad_with_zeros()); + cipher.encrypt_block(state); + } + out.copy_from_slice(state); + } +} + +impl AlgorithmName for CbcMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("CbcMac<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for CbcMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("CbcMacCore<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +impl Drop for CbcMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser, +{ + fn drop(&mut self) { + self.state.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +impl ZeroizeOnDrop for CbcMacCore where + C: BlockCipherEncrypt + SmallBlockSizeUser + ZeroizeOnDrop +{ +} + +#[inline(always)] +fn xor(buf: &mut Array, data: &Array) { + for i in 0..N::USIZE { + buf[i] ^= data[i]; + } +} diff --git a/cbc-mac/src/lib.rs b/cbc-mac/src/lib.rs index 8c11027..41ee94a 100644 --- a/cbc-mac/src/lib.rs +++ b/cbc-mac/src/lib.rs @@ -1,215 +1,31 @@ -//! [Cipher Block Chaining Message Authentication Code (CBC-MAC)][CBC-MAC] -//! implemented in pure Rust and generic over block cipher. -//! -//! **WARNING!** The algorithm has known weaknesses in case of variable-length -//! messages. See the linked Wikipedia article for more information. -//! -//! # Examples -//! -//! ``` -//! use cbc_mac::{CbcMac, Mac}; -//! use des::Des; -//! use hex_literal::hex; -//! -//! // CBC-MAC with the DES block cipher is equivalent to DAA -//! type Daa = CbcMac; -//! -//! // test from FIPS 113 -//! let key = hex!("0123456789ABCDEF"); -//! let mut mac = Daa::new_from_slice(&key).unwrap(); -//! mac.update(b"7654321 Now is the time for "); -//! let correct = hex!("F1D30F6849312CA4"); -//! mac.verify_slice(&correct).unwrap(); -//! ``` -//! -//! [CBC-MAC]: https://en.wikipedia.org/wiki/CBC-MAC#Security_with_fixed_and_variable-length_messages - #![no_std] +#![doc = include_str!("../README.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", - html_root_url = "https://docs.rs/cbc-mac/0.1.1" + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg" )] -#![deny(unsafe_code)] #![cfg_attr(docsrs, feature(doc_cfg))] -#![warn(missing_docs, rust_2018_idioms)] - -pub use digest::{self, Mac}; - -use cipher::{BlockBackend, BlockCipher, BlockClosure, BlockEncryptMut}; -use core::fmt; -use digest::{ - block_buffer::Eager, - core_api::{ - AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, CoreWrapper, FixedOutputCore, - UpdateCore, - }, - crypto_common::{InnerInit, InnerUser}, - generic_array::{ - typenum::{IsLess, Le, NonZero, U256}, - ArrayLength, GenericArray, - }, - MacMarker, Output, OutputSizeUser, Reset, -}; - -#[cfg(feature = "zeroize")] -use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; - -/// Generic CMAC instance. -pub type CbcMac = CoreWrapper>; - -/// Generic core CMAC instance, which operates over blocks. -#[derive(Clone)] -pub struct CbcMacCore -where - C: BlockCipher + BlockEncryptMut + Clone, -{ - cipher: C, - state: Block, -} - -impl BlockSizeUser for CbcMacCore -where - C: BlockCipher + BlockEncryptMut + Clone, -{ - type BlockSize = C::BlockSize; -} - -impl OutputSizeUser for CbcMacCore -where - C: BlockCipher + BlockEncryptMut + Clone, -{ - type OutputSize = C::BlockSize; -} - -impl InnerUser for CbcMacCore -where - C: BlockCipher + BlockEncryptMut + Clone, -{ - type Inner = C; -} -impl MacMarker for CbcMacCore where C: BlockCipher + BlockEncryptMut + Clone {} - -impl InnerInit for CbcMacCore -where - C: BlockCipher + BlockEncryptMut + Clone, -{ - #[inline] - fn inner_init(cipher: C) -> Self { - let state = Default::default(); - Self { cipher, state } - } -} +pub use digest::{self, KeyInit, Mac, block_api::SmallBlockSizeUser}; -impl BufferKindUser for CbcMacCore -where - C: BlockCipher + BlockEncryptMut + Clone, -{ - type BufferKind = Eager; -} +mod block_api; -impl UpdateCore for CbcMacCore -where - C: BlockCipher + BlockEncryptMut + Clone, -{ - #[inline] - fn update_blocks(&mut self, blocks: &[Block]) { - struct Ctx<'a, N: ArrayLength> { - state: &'a mut Block, - blocks: &'a [Block], - } - - impl<'a, N: ArrayLength> BlockSizeUser for Ctx<'a, N> { - type BlockSize = N; - } - - impl<'a, N: ArrayLength> BlockClosure for Ctx<'a, N> { - #[inline(always)] - fn call>(self, backend: &mut B) { - for block in self.blocks { - xor(self.state, block); - backend.proc_block((self.state).into()); - } - } - } - - let Self { cipher, state } = self; - cipher.encrypt_with_backend_mut(Ctx { state, blocks }) - } -} - -impl Reset for CbcMacCore -where - C: BlockCipher + BlockEncryptMut + Clone, -{ - #[inline(always)] - fn reset(&mut self) { - self.state = Default::default(); - } -} +use cipher::{AlgorithmName, BlockCipherEncrypt}; +use core::fmt; +use digest::block_api::CoreProxy; -impl FixedOutputCore for CbcMacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - C::BlockSize: IsLess, - Le: NonZero, -{ - #[inline] - fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { - let Self { state, cipher } = self; - let pos = buffer.get_pos(); - if pos != 0 { - xor(state, buffer.pad_with_zeros()); - cipher.encrypt_block_mut(state); - } - out.copy_from_slice(state); - } -} +digest::buffer_fixed!( + /// Generic CBC-MAC instance. + #[derive(Clone)] + pub struct CbcMac(block_api::CbcMacCore); + impl: ResetMacTraits InnerInit; +); -impl AlgorithmName for CbcMacCore +impl AlgorithmName for CbcMac where - C: BlockCipher + BlockEncryptMut + Clone + AlgorithmName, + C: BlockCipherEncrypt + SmallBlockSizeUser + AlgorithmName, { fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("CbcMac<")?; - ::write_alg_name(f)?; - f.write_str(">") - } -} - -impl fmt::Debug for CbcMacCore -where - C: BlockCipher + BlockEncryptMut + Clone + AlgorithmName, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("CbcMacCore<")?; - ::write_alg_name(f)?; - f.write_str("> { ... }") - } -} - -#[cfg(feature = "zeroize")] -#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] -impl Drop for CbcMacCore -where - C: BlockCipher + BlockEncryptMut + Clone, -{ - fn drop(&mut self) { - self.state.zeroize(); - } -} - -#[cfg(feature = "zeroize")] -#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] -impl ZeroizeOnDrop for CbcMacCore where - C: BlockCipher + BlockEncryptMut + Clone + ZeroizeOnDrop -{ -} - -#[inline(always)] -fn xor>(buf: &mut GenericArray, data: &GenericArray) { - for i in 0..N::USIZE { - buf[i] ^= data[i]; + ::Core::write_alg_name(f) } } diff --git a/cmac/CHANGELOG.md b/cmac/CHANGELOG.md index 096cc1a..27be9fa 100644 --- a/cmac/CHANGELOG.md +++ b/cmac/CHANGELOG.md @@ -5,6 +5,25 @@ 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.8.0 (UNRELEASED) +### Changed +- Edition changed to 2024 and MSRV bumped to 1.85 +- Relax MSRV policy and allow MSRV bumps in patch releases +- Update to `digest` v0.11 +- Update to `cipher` v0.5 +- Replace type aliases with newtypes ([#186]) + +### Removed +- `std` crate feature ([#186]) + +[#186]: https://github.com/RustCrypto/MACs/pull/186 + +## 0.7.2 (2022-03-14) +### Changed +- Do not include large CAVP test vectors in published packages ([#128]) + +[#128]: https://github.com/RustCrypto/MACs/pull/128 + ## 0.7.1 (2022-02-17) ### Fixed - Minimal versions build ([#108]) diff --git a/cmac/Cargo.toml b/cmac/Cargo.toml index 56b6804..675380a 100644 --- a/cmac/Cargo.toml +++ b/cmac/Cargo.toml @@ -1,34 +1,37 @@ [package] name = "cmac" -version = "0.7.1" # Also update html_root_url in lib.rs when bumping this +version = "0.8.0-rc.5" description = "Generic implementation of Cipher-based Message Authentication Code" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.56" +edition = "2024" +rust-version = "1.85" readme = "README.md" documentation = "https://docs.rs/cmac" repository = "https://github.com/RustCrypto/MACs" keywords = ["crypto", "mac", "cmac", "omac"] categories = ["cryptography", "no-std"] +exclude = ["tests/cavp_large.rs", "tests/data/cavp_aes128_large.blb"] [dependencies] -digest = { version = "0.10.3", features = ["mac"] } -cipher = "0.4.2" -dbl = "0.3" +cipher = "0.5" +digest = { version = "0.11", features = ["mac"] } +dbl = "0.5" [dev-dependencies] -digest = { version = "0.10.3", features = ["dev"] } -hex-literal = "0.3" -aes = "0.8" -des = "0.8" -kuznyechik = "0.8" -magma = "0.8" +digest = { version = "0.11", features = ["dev"] } +hex-literal = "1" + +aes = "0.9.0-rc.4" +des = "0.9.0-rc.3" +kuznyechik = "0.9.0-rc.3" +magma = "0.10.0-rc.3" [features] -std = ["digest/std"] -zeroize = ["cipher/zeroize"] +zeroize = ["cipher/zeroize", "digest/zeroize"] + +[lints] +workspace = true [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/cmac/README.md b/cmac/README.md index 2e2b21c..8404775 100644 --- a/cmac/README.md +++ b/cmac/README.md @@ -1,27 +1,51 @@ -# RustCrypto: CMAC +# [RustCrypto]: CMAC [![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] -[![Build Status][build-image]][build-link] -Pure Rust implementation of the [Cipher-based Message Authentication Code (CMAC)][1]. +Generic implementation of [Cipher-based Message Authentication Code (CMAC)][CMAC], +otherwise known as OMAC1. + +## Examples +We will use AES-128 block cipher from the [`aes`] crate. + +To get the authentication code: + +```rust +use aes::Aes128; +use cmac::{digest::KeyInit, Cmac, Mac}; + +// Create `Mac` trait implementation, namely CMAC-AES128 +let mut mac = Cmac::::new_from_slice(b"very secret key.").unwrap(); +mac.update(b"input message"); -[Documentation][docs-link] +// `result` has type `Output` which is a thin wrapper around array of +// bytes for providing constant time equality check +let result = mac.finalize(); +// To get underlying array use the `into_bytes` method, but be careful, +// since incorrect use of the tag value may permit timing attacks which +// defeat the security provided by the `Output` wrapper +let tag_bytes = result.into_bytes(); +``` -## Minimum Supported Rust Version +To verify the message: -Rust **1.56** or higher. +```rust +use aes::Aes128; +use cmac::{digest::KeyInit, Cmac, Mac}; -Minimum supported Rust version can be changed in the future, but it will be -done with a minor version bump. +let mut mac = Cmac::::new_from_slice(b"very secret key.").unwrap(); -## SemVer Policy +mac.update(b"input message"); -- All on-by-default features of this library are covered by SemVer -- MSRV is considered exempt from SemVer as noted above +# let tag_bytes = mac.clone().finalize().into_bytes(); +// `verify` will return `Ok(())` if tag is correct, `Err(MacError)` otherwise +mac.verify(&tag_bytes).unwrap(); +``` ## License @@ -40,17 +64,19 @@ dual licensed as above, without any additional terms or conditions. [//]: # (badges) -[crate-image]: https://img.shields.io/crates/v/cmac.svg +[crate-image]: https://img.shields.io/crates/v/cmac.svg?logo=rust [crate-link]: https://crates.io/crates/cmac [docs-image]: https://docs.rs/cmac/badge.svg [docs-link]: https://docs.rs/cmac/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260044-MACs -[build-image]: https://github.com/RustCrypto/MACs/workflows/cmac/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/MACs/actions?query=workflow%3Acmac +[build-image]: https://github.com/RustCrypto/MACs/actions/workflows/cmac.yml/badge.svg +[build-link]: https://github.com/RustCrypto/MACs/actions/workflows/cmac.yml [//]: # (general links) -[1]: https://en.wikipedia.org/wiki/One-key_MAC +[RustCrypto]: https://github.com/RustCrypto +[CMAC]: https://en.wikipedia.org/wiki/One-key_MAC +[`aes`]: https://docs.rs/aes diff --git a/cmac/benches/mod.rs b/cmac/benches/mod.rs index 4f3f7eb..76b71e7 100644 --- a/cmac/benches/mod.rs +++ b/cmac/benches/mod.rs @@ -2,7 +2,7 @@ extern crate test; use aes::{Aes128, Aes256}; -use cmac::{Cmac, Mac}; +use cmac::{Cmac, KeyInit}; use kuznyechik::Kuznyechik; use test::Bencher; diff --git a/cmac/src/block_api.rs b/cmac/src/block_api.rs new file mode 100644 index 0000000..0aadb21 --- /dev/null +++ b/cmac/src/block_api.rs @@ -0,0 +1,156 @@ +use cipher::{BlockCipherEncBackend, BlockCipherEncClosure, BlockCipherEncrypt}; +use core::fmt; +use dbl::Dbl; +use digest::{ + MacMarker, Output, OutputSizeUser, Reset, + array::{Array, ArraySize}, + block_api::{ + AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, FixedOutputCore, Lazy, + SmallBlockSizeUser, UpdateCore, + }, + block_buffer::BlockSizes, + common::{InnerInit, InnerUser}, +}; + +#[cfg(feature = "zeroize")] +use digest::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// Generic core CMAC instance, which operates over blocks. +#[derive(Clone)] +pub struct CmacCore { + cipher: C, + state: Block, +} + +impl BlockSizeUser for CmacCore { + type BlockSize = C::BlockSize; +} + +impl OutputSizeUser for CmacCore { + type OutputSize = C::BlockSize; +} + +impl InnerUser for CmacCore { + type Inner = C; +} + +impl MacMarker for CmacCore {} + +impl InnerInit for CmacCore { + #[inline] + fn inner_init(cipher: C) -> Self { + let state = Default::default(); + Self { cipher, state } + } +} + +impl BufferKindUser for CmacCore { + type BufferKind = Lazy; +} + +impl UpdateCore for CmacCore { + #[inline] + fn update_blocks(&mut self, blocks: &[Block]) { + struct Closure<'a, N: BlockSizes> { + state: &'a mut Block, + blocks: &'a [Block], + } + + impl BlockSizeUser for Closure<'_, N> { + type BlockSize = N; + } + + impl BlockCipherEncClosure for Closure<'_, N> { + #[inline(always)] + fn call>(self, backend: &B) { + for block in self.blocks { + xor(self.state, block); + backend.encrypt_block((self.state).into()); + } + } + } + + let Self { cipher, state } = self; + cipher.encrypt_with_backend(Closure { state, blocks }); + } +} + +impl Reset for CmacCore { + #[inline(always)] + fn reset(&mut self) { + self.state = Default::default(); + } +} + +impl FixedOutputCore for CmacCore { + #[inline] + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { + let Self { state, cipher } = self; + let pos = buffer.get_pos(); + let buf = buffer.pad_with_zeros(); + + let mut subkey = Default::default(); + cipher.encrypt_block(&mut subkey); + let key1 = C::dbl(subkey); + + xor(state, &buf); + if pos == buf.len() { + xor(state, &key1); + } else { + state[pos] ^= 0x80; + let key2 = C::dbl(key1); + xor(state, &key2); + } + cipher.encrypt_block(state); + out.copy_from_slice(state); + } +} + +impl AlgorithmName for CmacCore { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Cmac<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for CmacCore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("CmacCore { ... }") + } +} + +impl Drop for CmacCore { + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + self.state.zeroize(); + } + } +} + +#[cfg(feature = "zeroize")] +impl ZeroizeOnDrop for CmacCore {} + +#[inline(always)] +fn xor(buf: &mut Array, data: &Array) { + for i in 0..N::USIZE { + buf[i] ^= data[i]; + } +} + +/// Helper trait implemented for cipher supported by CMAC +pub trait CmacCipher: SmallBlockSizeUser + BlockCipherEncrypt { + /// Double block. See the [`Dbl`] trait docs for more information. + fn dbl(block: Block) -> Block; +} + +impl CmacCipher for C +where + Self: SmallBlockSizeUser + BlockCipherEncrypt, + Block: Dbl, +{ + fn dbl(block: Block) -> Block { + block.dbl() + } +} diff --git a/cmac/src/lib.rs b/cmac/src/lib.rs index 52f1778..ca7062b 100644 --- a/cmac/src/lib.rs +++ b/cmac/src/lib.rs @@ -1,261 +1,29 @@ -//! Generic implementation of [Cipher-based Message Authentication Code (CMAC)][1], -//! otherwise known as OMAC1. -//! -//! # Examples -//! We will use AES-128 block cipher from [aes](https://docs.rs/aes) crate. -//! -//! To get the authentication code: -//! -//! ```rust -//! use aes::Aes128; -//! use cmac::{Cmac, Mac}; -//! -//! // Create `Mac` trait implementation, namely CMAC-AES128 -//! let mut mac = Cmac::::new_from_slice(b"very secret key.").unwrap(); -//! mac.update(b"input message"); -//! -//! // `result` has type `Output` which is a thin wrapper around array of -//! // bytes for providing constant time equality check -//! let result = mac.finalize(); -//! // To get underlying array use the `into_bytes` method, but be careful, -//! // since incorrect use of the tag value may permit timing attacks which -//! // defeat the security provided by the `Output` wrapper -//! let tag_bytes = result.into_bytes(); -//! ``` -//! -//! To verify the message: -//! -//! ```rust -//! # use aes::Aes128; -//! # use cmac::{Cmac, Mac}; -//! let mut mac = Cmac::::new_from_slice(b"very secret key.").unwrap(); -//! -//! mac.update(b"input message"); -//! -//! # let tag_bytes = mac.clone().finalize().into_bytes(); -//! // `verify` will return `Ok(())` if tag is correct, `Err(MacError)` otherwise -//! mac.verify(&tag_bytes).unwrap(); -//! ``` -//! -//! [1]: https://en.wikipedia.org/wiki/One-key_MAC - #![no_std] +#![doc = include_str!("../README.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", - html_root_url = "https://docs.rs/cmac/0.7.1" + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg" )] -#![forbid(unsafe_code)] #![cfg_attr(docsrs, feature(doc_cfg))] -#![warn(missing_docs, rust_2018_idioms)] - -pub use digest::{self, Mac}; - -use cipher::{BlockBackend, BlockCipher, BlockClosure, BlockEncryptMut}; -use core::fmt; -use dbl::Dbl; -use digest::{ - block_buffer::Lazy, - core_api::{ - AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, CoreWrapper, FixedOutputCore, - UpdateCore, - }, - crypto_common::{InnerInit, InnerUser}, - generic_array::{ - typenum::{IsLess, Le, NonZero, U256}, - ArrayLength, GenericArray, - }, - MacMarker, Output, OutputSizeUser, Reset, -}; - -#[cfg(feature = "zeroize")] -use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; - -/// Generic CMAC instance. -pub type Cmac = CoreWrapper>; - -/// Generic core CMAC instance, which operates over blocks. -#[derive(Clone)] -pub struct CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - cipher: C, - state: Block, -} - -impl BlockSizeUser for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - type BlockSize = C::BlockSize; -} - -impl OutputSizeUser for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - type OutputSize = C::BlockSize; -} - -impl InnerUser for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - type Inner = C; -} - -impl MacMarker for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ -} - -impl InnerInit for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - #[inline] - fn inner_init(cipher: C) -> Self { - let state = Default::default(); - Self { cipher, state } - } -} - -impl BufferKindUser for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - type BufferKind = Lazy; -} - -impl UpdateCore for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - #[inline] - fn update_blocks(&mut self, blocks: &[Block]) { - struct Ctx<'a, N: ArrayLength> { - state: &'a mut Block, - blocks: &'a [Block], - } - impl<'a, N: ArrayLength> BlockSizeUser for Ctx<'a, N> { - type BlockSize = N; - } +pub use digest::{self, KeyInit, Mac}; - impl<'a, N: ArrayLength> BlockClosure for Ctx<'a, N> { - #[inline(always)] - fn call>(self, backend: &mut B) { - for block in self.blocks { - xor(self.state, block); - backend.proc_block((self.state).into()); - } - } - } +/// Block-level implementation. +pub mod block_api; - let Self { cipher, state } = self; - cipher.encrypt_with_backend_mut(Ctx { state, blocks }) - } -} - -impl Reset for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - #[inline(always)] - fn reset(&mut self) { - self.state = Default::default(); - } -} - -impl FixedOutputCore for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, - C::BlockSize: IsLess, - Le: NonZero, -{ - #[inline] - fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { - let Self { state, cipher } = self; - let pos = buffer.get_pos(); - let buf = buffer.pad_with_zeros(); +use block_api::CmacCipher; +use core::fmt; +use digest::block_api::{AlgorithmName, CoreProxy}; - let mut subkey = Default::default(); - cipher.encrypt_block_mut(&mut subkey); - let key1 = subkey.dbl(); +digest::buffer_fixed!( + /// Generic CMAC instance. + #[derive(Clone)] + pub struct Cmac(block_api::CmacCore); + impl: ResetMacTraits InnerInit; +); - xor(state, buf); - if pos == buf.len() { - xor(state, &key1); - } else { - state[pos] ^= 0x80; - let key2 = key1.dbl(); - xor(state, &key2); - } - cipher.encrypt_block_mut(state); - out.copy_from_slice(state); - } -} - -impl AlgorithmName for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone + AlgorithmName, - Block: Dbl, -{ +impl AlgorithmName for Cmac { fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Cmac<")?; - ::write_alg_name(f)?; - f.write_str(">") - } -} - -impl fmt::Debug for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone + AlgorithmName, - Block: Dbl, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("CmacCore<")?; - ::write_alg_name(f)?; - f.write_str("> { ... }") - } -} - -#[cfg(feature = "zeroize")] -#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] -impl Drop for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - fn drop(&mut self) { - self.state.zeroize(); - } -} - -#[cfg(feature = "zeroize")] -#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] -impl ZeroizeOnDrop for CmacCore -where - C: BlockCipher + BlockEncryptMut + Clone + ZeroizeOnDrop, - Block: Dbl, -{ -} - -#[inline(always)] -fn xor>(buf: &mut GenericArray, data: &GenericArray) { - for i in 0..N::USIZE { - buf[i] ^= data[i]; + ::Core::write_alg_name(f) } } diff --git a/cmac/tests/cavp_large.rs b/cmac/tests/cavp_large.rs new file mode 100644 index 0000000..f151bdb --- /dev/null +++ b/cmac/tests/cavp_large.rs @@ -0,0 +1,15 @@ +//! Tests from CAVP (excluding all 64 KiB vectors for AES-128 except the first one): +//! +//! +//! Test vectors in this file use 64 KiB of data, so they are excluded from published packages. + +use aes::Aes128; +use cmac::Cmac; +use digest::dev::reset_mac_test; + +digest::new_mac_test!( + cmac_aes128_cavp_large, + Cmac, + reset_mac_test, + trunc_left, +); diff --git a/cmac/tests/data/aes192.blb b/cmac/tests/data/aes192.blb deleted file mode 100644 index 09ee9d4..0000000 Binary files a/cmac/tests/data/aes192.blb and /dev/null differ diff --git a/cmac/tests/data/aes256.blb b/cmac/tests/data/aes256.blb deleted file mode 100644 index ec5b834..0000000 Binary files a/cmac/tests/data/aes256.blb and /dev/null differ diff --git a/cmac/tests/data/cmac_aes128_cavp.blb b/cmac/tests/data/cmac_aes128_cavp.blb new file mode 100644 index 0000000..2e1c2d8 Binary files /dev/null and b/cmac/tests/data/cmac_aes128_cavp.blb differ diff --git a/cmac/tests/data/cavp_aes128.blb b/cmac/tests/data/cmac_aes128_cavp_large.blb similarity index 99% rename from cmac/tests/data/cavp_aes128.blb rename to cmac/tests/data/cmac_aes128_cavp_large.blb index 78bf9ea..78f6d50 100644 Binary files a/cmac/tests/data/cavp_aes128.blb and b/cmac/tests/data/cmac_aes128_cavp_large.blb differ diff --git a/cmac/tests/data/aes128.blb b/cmac/tests/data/cmac_aes128_nist.blb similarity index 69% rename from cmac/tests/data/aes128.blb rename to cmac/tests/data/cmac_aes128_nist.blb index ed39d51..f7ed9a7 100644 Binary files a/cmac/tests/data/aes128.blb and b/cmac/tests/data/cmac_aes128_nist.blb differ diff --git a/cmac/tests/data/wycheproof-aes128.blb b/cmac/tests/data/cmac_aes128_wycheproof.blb similarity index 77% rename from cmac/tests/data/wycheproof-aes128.blb rename to cmac/tests/data/cmac_aes128_wycheproof.blb index dfde593..ab09cc5 100644 Binary files a/cmac/tests/data/wycheproof-aes128.blb and b/cmac/tests/data/cmac_aes128_wycheproof.blb differ diff --git a/cmac/tests/data/cavp_aes192.blb b/cmac/tests/data/cmac_aes192_cavp.blb similarity index 97% rename from cmac/tests/data/cavp_aes192.blb rename to cmac/tests/data/cmac_aes192_cavp.blb index 921f68a..de68390 100644 Binary files a/cmac/tests/data/cavp_aes192.blb and b/cmac/tests/data/cmac_aes192_cavp.blb differ diff --git a/cmac/tests/data/cmac_aes192_nist.blb b/cmac/tests/data/cmac_aes192_nist.blb new file mode 100644 index 0000000..a01370c Binary files /dev/null and b/cmac/tests/data/cmac_aes192_nist.blb differ diff --git a/cmac/tests/data/wycheproof-aes192.blb b/cmac/tests/data/cmac_aes192_wycheproof.blb similarity index 86% rename from cmac/tests/data/wycheproof-aes192.blb rename to cmac/tests/data/cmac_aes192_wycheproof.blb index cb21624..e61a5ba 100644 Binary files a/cmac/tests/data/wycheproof-aes192.blb and b/cmac/tests/data/cmac_aes192_wycheproof.blb differ diff --git a/cmac/tests/data/cavp_aes256.blb b/cmac/tests/data/cmac_aes256_cavp.blb similarity index 99% rename from cmac/tests/data/cavp_aes256.blb rename to cmac/tests/data/cmac_aes256_cavp.blb index bd51d82..f7d92c4 100644 Binary files a/cmac/tests/data/cavp_aes256.blb and b/cmac/tests/data/cmac_aes256_cavp.blb differ diff --git a/cmac/tests/data/cmac_aes256_nist.blb b/cmac/tests/data/cmac_aes256_nist.blb new file mode 100644 index 0000000..6c69080 Binary files /dev/null and b/cmac/tests/data/cmac_aes256_nist.blb differ diff --git a/cmac/tests/data/wycheproof-aes256.blb b/cmac/tests/data/cmac_aes256_wycheproof.blb similarity index 75% rename from cmac/tests/data/wycheproof-aes256.blb rename to cmac/tests/data/cmac_aes256_wycheproof.blb index 1502473..4423260 100644 Binary files a/cmac/tests/data/wycheproof-aes256.blb and b/cmac/tests/data/cmac_aes256_wycheproof.blb differ diff --git a/cmac/tests/data/cmac_kuznyechik_gost.blb b/cmac/tests/data/cmac_kuznyechik_gost.blb new file mode 100644 index 0000000..49747f9 Binary files /dev/null and b/cmac/tests/data/cmac_kuznyechik_gost.blb differ diff --git a/cmac/tests/data/cmac_magma_gost.blb b/cmac/tests/data/cmac_magma_gost.blb new file mode 100644 index 0000000..7bdb6ac Binary files /dev/null and b/cmac/tests/data/cmac_magma_gost.blb differ diff --git a/cmac/tests/data/cavp_tdes2.blb b/cmac/tests/data/cmac_tdes2_cavp.blb similarity index 99% rename from cmac/tests/data/cavp_tdes2.blb rename to cmac/tests/data/cmac_tdes2_cavp.blb index df3ecd1..be67ab4 100644 Binary files a/cmac/tests/data/cavp_tdes2.blb and b/cmac/tests/data/cmac_tdes2_cavp.blb differ diff --git a/cmac/tests/data/cavp_tdes3.blb b/cmac/tests/data/cmac_tdes3_cavp.blb similarity index 99% rename from cmac/tests/data/cavp_tdes3.blb rename to cmac/tests/data/cmac_tdes3_cavp.blb index 5f44305..60deaa6 100644 Binary files a/cmac/tests/data/cavp_tdes3.blb and b/cmac/tests/data/cmac_tdes3_cavp.blb differ diff --git a/cmac/tests/data/kuznyechik.blb b/cmac/tests/data/kuznyechik.blb deleted file mode 100644 index 660fba2..0000000 Binary files a/cmac/tests/data/kuznyechik.blb and /dev/null differ diff --git a/cmac/tests/data/magma.blb b/cmac/tests/data/magma.blb deleted file mode 100644 index c787d90..0000000 Binary files a/cmac/tests/data/magma.blb and /dev/null differ diff --git a/cmac/tests/mod.rs b/cmac/tests/mod.rs index 1efc2dc..11e0e66 100644 --- a/cmac/tests/mod.rs +++ b/cmac/tests/mod.rs @@ -1,31 +1,33 @@ +//! Test vectors. + use aes::{Aes128, Aes192, Aes256}; use cmac::Cmac; use des::{TdesEde2, TdesEde3}; -use digest::new_resettable_mac_test; +use digest::{dev::reset_mac_test, new_mac_test}; use kuznyechik::Kuznyechik; use magma::Magma; // Tests from NIST SP 800-38B: // https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/ -new_resettable_mac_test!(cmac_aes128_nist, "aes128", Cmac); -new_resettable_mac_test!(cmac_aes192_nist, "aes192", Cmac); -new_resettable_mac_test!(cmac_aes256_nist, "aes256", Cmac); +new_mac_test!(cmac_aes128_nist, Cmac, reset_mac_test); +new_mac_test!(cmac_aes192_nist, Cmac, reset_mac_test); +new_mac_test!(cmac_aes256_nist, Cmac, reset_mac_test); // Tests from CAVP (excluding all 64 KiB vectors for AES-128 except the first one): // https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/CAVP-TESTING-BLOCK-CIPHER-MODES -new_resettable_mac_test!(cmac_aes128_cavp, "cavp_aes128", Cmac, trunc_left); -new_resettable_mac_test!(cmac_aes192_cavp, "cavp_aes192", Cmac, trunc_left); -new_resettable_mac_test!(cmac_aes256_cavp, "cavp_aes256", Cmac, trunc_left); -new_resettable_mac_test!(cmac_tdes2_cavp, "cavp_tdes2", Cmac, trunc_left); -new_resettable_mac_test!(cmac_tdes3_cavp, "cavp_tdes3", Cmac, trunc_left); +new_mac_test!(cmac_aes128_cavp, Cmac, reset_mac_test, trunc_left); +new_mac_test!(cmac_aes192_cavp, Cmac, reset_mac_test, trunc_left); +new_mac_test!(cmac_aes256_cavp, Cmac, reset_mac_test, trunc_left); +new_mac_test!(cmac_tdes2_cavp, Cmac, reset_mac_test, trunc_left); +new_mac_test!(cmac_tdes3_cavp, Cmac, reset_mac_test, trunc_left); // Tests from Project Wycheproof: // https://github.com/google/wycheproof -new_resettable_mac_test!(cmac_aes128_wycheproof, "wycheproof-aes128", Cmac); -new_resettable_mac_test!(cmac_aes192_wycheproof, "wycheproof-aes192", Cmac); -new_resettable_mac_test!(cmac_aes256_wycheproof, "wycheproof-aes256", Cmac); +new_mac_test!(cmac_aes128_wycheproof, Cmac, reset_mac_test); +new_mac_test!(cmac_aes192_wycheproof, Cmac, reset_mac_test); +new_mac_test!(cmac_aes256_wycheproof, Cmac, reset_mac_test); // Test from GOST R 34.13-2015: // https://tc26.ru/standard/gost/GOST_R_3413-2015.pdf -new_resettable_mac_test!(cmac_kuznyechik_gost, "kuznyechik", Cmac); -new_resettable_mac_test!(cmac_magma_gost, "magma", Cmac); +new_mac_test!(cmac_kuznyechik_gost, Cmac, reset_mac_test); +new_mac_test!(cmac_magma_gost, Cmac, reset_mac_test); diff --git a/hmac/CHANGELOG.md b/hmac/CHANGELOG.md index 2569cc5..de96f2b 100644 --- a/hmac/CHANGELOG.md +++ b/hmac/CHANGELOG.md @@ -5,6 +5,26 @@ 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.0 (2026-03-29) +### Added +- `HmacReset` and `SimpleHmacReset` types ([#186]) + +### Changed +- Use `EagerHash` trait for bounds ([#151], [#212]) +- Edition changed to 2024 and MSRV bumped to 1.85 ([#178]) +- Relax MSRV policy and allow MSRV bumps in patch releases +- Update to `digest` v0.11 ([#243]) +- Replace type aliases with newtypes ([#186]) + +### Removed +- `std` and `reset` crate features ([#186]) + +[#151]: https://github.com/RustCrypto/MACs/pull/151 +[#178]: https://github.com/RustCrypto/MACs/pull/178 +[#186]: https://github.com/RustCrypto/MACs/pull/186 +[#212]: https://github.com/RustCrypto/MACs/pull/212 +[#243]: https://github.com/RustCrypto/MACs/pull/243 + ## 0.12.1 (2022-02-17) ### Fixed - Minimal versions build ([#108]) @@ -15,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `digest` crate dependency to v0.10 and remove `crypto-mac` ([#97]) - Use a more efficient state representation by using block-level hash API ([#97]) +- Reset functionality is now optional and gated on disabled-by-default reset feature ([#97]) ### Added - `SimpleHmac` as a less constrained alternative to `Hmac` ([#97]) diff --git a/hmac/Cargo.lock b/hmac/Cargo.lock deleted file mode 100644 index 2804e1d..0000000 --- a/hmac/Cargo.lock +++ /dev/null @@ -1,166 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[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.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" -dependencies = [ - "generic-array", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cpufeatures" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" -dependencies = [ - "blobby", - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "generic-array" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "hex-literal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d70693199b3cf4552f3fa720b54163927a3ebed2aef240efaf556033ab336a11" -dependencies = [ - "hex-literal-impl", - "proc-macro-hack", -] - -[[package]] -name = "hex-literal-impl" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59448fc2f82a5fb6907f78c3d69d843e82ff5b051923313cc4438cb0c7b745a8" -dependencies = [ - "proc-macro-hack", -] - -[[package]] -name = "hmac" -version = "0.12.0" -dependencies = [ - "digest", - "hex-literal", - "md-5", - "sha-1", - "sha2", - "streebog", -] - -[[package]] -name = "libc" -version = "0.2.118" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" - -[[package]] -name = "md-5" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" -dependencies = [ - "digest", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "streebog" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d94bd56cd86f4e14c55c935baa313bfd37846284f6bb1f35fc71bc30e621241" -dependencies = [ - "digest", -] - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/hmac/Cargo.toml b/hmac/Cargo.toml index d04c7cf..e94b0ba 100644 --- a/hmac/Cargo.toml +++ b/hmac/Cargo.toml @@ -1,35 +1,33 @@ [package] name = "hmac" -version = "0.12.1" # Also update html_root_url in lib.rs when bumping this +version = "0.13.0" description = "Generic implementation of Hash-based Message Authentication Code (HMAC)" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" -edition = "2018" +edition = "2024" readme = "README.md" documentation = "https://docs.rs/hmac" repository = "https://github.com/RustCrypto/MACs" keywords = ["crypto", "mac", "hmac", "digest"] categories = ["cryptography", "no-std"] - -# Hack to allow this crate to coexist with 2021 edition crates -[workspace] -members = ["."] +rust-version = "1.85" [dependencies] -digest = { version = "0.10.3", features = ["mac"] } +digest = { version = "0.11.2", features = ["mac"] } [dev-dependencies] -digest = { version = "0.10", features = ["dev"] } -md-5 = { version = "0.10", default-features = false } -sha-1 = { version = "0.10", default-features = false } -sha2 = { version = "0.10", default-features = false } -streebog = { version = "0.10", default-features = false } -hex-literal = "0.2.2" +digest = { version = "0.11.2", features = ["dev"] } +md-5 = { version = "0.11", default-features = false } +sha1 = { version = "0.11", default-features = false } +sha2 = { version = "0.11", default-features = false } +streebog = { version = "0.11", default-features = false } +hex-literal = "1" [features] -std = ["digest/std"] -reset = [] # Enable ability to reset HMAC instances +zeroize = ["digest/zeroize"] + +[lints] +workspace = true [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/hmac/README.md b/hmac/README.md index 52d4ef9..dc3de4f 100644 --- a/hmac/README.md +++ b/hmac/README.md @@ -1,26 +1,98 @@ -# RustCrypto: HMAC +# [RustCrypto]: HMAC [![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] -Pure Rust implementation of the [Hash-based Message Authentication Code (HMAC)][1]. +Generic implementation of [Hash-based Message Authentication Code (HMAC)][HMAC]. -[Documentation][docs-link] +To use it you will need a cryptographic hash function implementation which +implements the [`digest`] crate traits. You can find compatible crates +(e.g. [`sha2`]) in the [`RustCrypto/hashes`] repository. -## Minimum Supported Rust Version +This crate provides four HMAC implementations: [`Hmac`], [`HmacReset`], +[`SimpleHmac`], and [`SimpleHmacReset`]. -Rust **1.41** or higher. +The first two types are buffered wrappers around block-level +[`block_api::HmacCore`] and [`block_api::HmacResetCore`] types respectively. +Internally they uses efficient state representation, but work only with +hash functions which expose block-level API and consume blocks eagerly +(e.g. they will not work with the BLAKE2 family of hash functions). -Minimum supported Rust version can be changed in the future, but it will be -done with a minor version bump. +On the other hand, [`SimpleHmac`] and [`SimpleHmacReset`] are a bit less +efficient, but work with all hash functions which implement +the [`Digest`] trait. -## SemVer Policy +[`Hmac`] and [`SimpleHmac`] do not support resetting MAC state (i.e. they +do not implement the [`Reset`] and [`FixedOutputReset`] traits). Use +[`HmacReset`] or [`SimpleHmacReset`] if you want to reuse MAC state. -- All on-by-default features of this library are covered by SemVer -- MSRV is considered exempt from SemVer as noted above +## Examples + +Let us demonstrate how to use HMAC using the SHA-256 hash function +implemented in the [`sha2`] crate. + +In the following examples [`Hmac`] is interchangeable with [`SimpleHmac`]. + +To get authentication code: + +```rust +use sha2::Sha256; +use hmac::{Hmac, KeyInit, Mac}; +use hex_literal::hex; + +// Create alias for HMAC-SHA256 +type HmacSha256 = Hmac; + +let mut mac = HmacSha256::new_from_slice(b"my secret and secure key") + .expect("HMAC can take key of any size"); +mac.update(b"input message"); + +// `result` has type `CtOutput` which is a thin wrapper around array of +// bytes for providing constant time equality check +let result = mac.finalize(); +// To get underlying array use `into_bytes`, but be careful, since +// incorrect use of the code value may permit timing attacks which defeats +// the security provided by the `CtOutput` +let code_bytes = result.into_bytes(); +let expected = hex!(" + 97d2a569059bbcd8ead4444ff99071f4 + c01d005bcefe0d3567e1be628e5fdcd9 +"); +assert_eq!(code_bytes[..], expected[..]); +``` + +To verify the message: + +```rust +use sha2::Sha256; +use hmac::{Hmac, KeyInit, Mac}; +use hex_literal::hex; + +type HmacSha256 = Hmac; + +let mut mac = HmacSha256::new_from_slice(b"my secret and secure key") + .expect("HMAC can take key of any size"); + +mac.update(b"input message"); + +let code_bytes = hex!(" + 97d2a569059bbcd8ead4444ff99071f4 + c01d005bcefe0d3567e1be628e5fdcd9 +"); +// `verify_slice` will return `Ok(())` if code is correct, `Err(MacError)` otherwise +mac.verify_slice(&code_bytes[..]).unwrap(); +``` + +## Block and input sizes + +Usually it is assumed that block size is larger than output size. Due to the +generic nature of the implementation, we must handle cases when this assumption +does not hold. This is done by truncating hash output to the hash +block size if needed. ## License @@ -39,15 +111,32 @@ dual licensed as above, without any additional terms or conditions. [//]: # (badges) -[crate-image]: https://img.shields.io/crates/v/hmac.svg +[crate-image]: https://img.shields.io/crates/v/hmac.svg?logo=rust [crate-link]: https://crates.io/crates/hmac [docs-image]: https://docs.rs/hmac/badge.svg [docs-link]: https://docs.rs/hmac/ +[build-image]: https://github.com/RustCrypto/MACs/actions/workflows/hmac.yml/badge.svg +[build-link]: https://github.com/RustCrypto/MACs/actions/workflows/hmac.yml [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260044-MACs [//]: # (general links) -[1]: https://en.wikipedia.org/wiki/HMAC +[RustCrypto]: https://github.com/RustCrypto +[HMAC]: https://en.wikipedia.org/wiki/HMAC +[`digest`]: https://docs.rs/digest +[`sha2`]: https://docs.rs/sha2 +[`RustCrypto/hashes`]: https://github.com/RustCrypto/hashes + +[//]: # (intra-crate links) +[`Reset`]: https://docs.rs/digest/latest/digest/trait.Reset.html +[`Digest`]: https://docs.rs/digest/latest/digest/trait.Digest.html +[`FixedOutputReset`]: https://docs.rs/digest/latest/digest/trait.FixedOutputReset.html +[`Hmac`]: https://docs.rs/hmac/latest/hmac/struct.Hmac.html +[`HmacReset`]: https://docs.rs/hmac/latest/hmac/struct.HmacReset.html +[`SimpleHmac`]: https://docs.rs/hmac/latest/hmac/struct.SimpleHmac.html +[`SimpleHmacReset`]: https://docs.rs/hmac/latest/hmac/struct.SimpleHmacReset.html +[`block_api::HmacCore`]: https://docs.rs/hmac/latest/hmac/block_api/struct.HmacCore.html +[`block_api::HmacResetCore`]: https://docs.rs/hmac/latest/hmac/block_api/struct.HmacResetCore.html diff --git a/hmac/src/block_api.rs b/hmac/src/block_api.rs new file mode 100644 index 0000000..3071f68 --- /dev/null +++ b/hmac/src/block_api.rs @@ -0,0 +1,215 @@ +use crate::utils::{IPAD, OPAD, get_der_key}; +use core::{fmt, slice}; +use digest::{ + InvalidLength, KeyInit, MacMarker, Output, Reset, + block_api::{ + AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, EagerHash, FixedOutputCore, + OutputSizeUser, UpdateCore, + }, + block_buffer::Eager, + common::{Key, KeySizeUser}, +}; + +/// Generic core HMAC instance, which operates over blocks. +pub struct HmacCore { + digest: D::Core, + opad_digest: D::Core, +} + +impl Clone for HmacCore { + fn clone(&self) -> Self { + Self { + digest: self.digest.clone(), + opad_digest: self.opad_digest.clone(), + } + } +} + +impl MacMarker for HmacCore {} + +impl BufferKindUser for HmacCore { + type BufferKind = Eager; +} + +impl KeySizeUser for HmacCore { + type KeySize = <::Core as BlockSizeUser>::BlockSize; +} + +impl BlockSizeUser for HmacCore { + type BlockSize = <::Core as BlockSizeUser>::BlockSize; +} + +impl OutputSizeUser for HmacCore { + type OutputSize = <::Core as OutputSizeUser>::OutputSize; +} + +impl KeyInit for HmacCore { + #[inline(always)] + fn new(key: &Key) -> Self { + Self::new_from_slice(key.as_slice()).expect("HMAC accepts keys of any length") + } + + #[inline(always)] + fn new_from_slice(key: &[u8]) -> Result { + let mut buf = get_der_key::(key); + buf.iter_mut().for_each(|b: &mut u8| *b ^= IPAD); + + let mut digest = D::Core::default(); + digest.update_blocks(slice::from_ref(&buf)); + + buf.iter_mut().for_each(|b: &mut u8| *b ^= IPAD ^ OPAD); + + let mut opad_digest = D::Core::default(); + opad_digest.update_blocks(slice::from_ref(&buf)); + + Ok(Self { + opad_digest, + digest, + }) + } +} + +impl UpdateCore for HmacCore { + #[inline(always)] + fn update_blocks(&mut self, blocks: &[Block]) { + self.digest.update_blocks(blocks); + } +} + +impl FixedOutputCore for HmacCore { + #[inline(always)] + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { + let mut hash = Output::::default(); + self.digest.finalize_fixed_core(buffer, &mut hash); + // finalize_fixed_core should reset the buffer as well, but + // to be extra safe we reset it explicitly again. + buffer.reset(); + let h = &mut self.opad_digest; + buffer.digest_blocks(&hash, |b| h.update_blocks(b)); + h.finalize_fixed_core(buffer, out); + } +} + +impl AlgorithmName for HmacCore { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Hmac<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for HmacCore +where + D::Core: AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("HmacCore { ... }") + } +} + +/// Generic core HMAC instance, which operates over blocks. +pub struct HmacResetCore { + digest: D::Core, + opad_digest: D::Core, + ipad_digest: D::Core, +} + +impl Clone for HmacResetCore { + fn clone(&self) -> Self { + Self { + digest: self.digest.clone(), + opad_digest: self.opad_digest.clone(), + ipad_digest: self.ipad_digest.clone(), + } + } +} + +impl MacMarker for HmacResetCore {} + +impl BufferKindUser for HmacResetCore { + type BufferKind = Eager; +} + +impl KeySizeUser for HmacResetCore { + type KeySize = <::Core as BlockSizeUser>::BlockSize; +} + +impl BlockSizeUser for HmacResetCore { + type BlockSize = <::Core as BlockSizeUser>::BlockSize; +} + +impl OutputSizeUser for HmacResetCore { + type OutputSize = <::Core as OutputSizeUser>::OutputSize; +} + +impl KeyInit for HmacResetCore { + #[inline(always)] + fn new(key: &Key) -> Self { + Self::new_from_slice(key.as_slice()).expect("HMAC accepts keys of any length") + } + + #[inline(always)] + fn new_from_slice(key: &[u8]) -> Result { + let mut buf = get_der_key::(key); + buf.iter_mut().for_each(|b: &mut u8| *b ^= IPAD); + + let mut digest = D::Core::default(); + digest.update_blocks(slice::from_ref(&buf)); + + buf.iter_mut().for_each(|b: &mut u8| *b ^= IPAD ^ OPAD); + + let mut opad_digest = D::Core::default(); + opad_digest.update_blocks(slice::from_ref(&buf)); + + Ok(Self { + ipad_digest: digest.clone(), + opad_digest, + digest, + }) + } +} + +impl UpdateCore for HmacResetCore { + #[inline(always)] + fn update_blocks(&mut self, blocks: &[Block]) { + self.digest.update_blocks(blocks); + } +} + +impl FixedOutputCore for HmacResetCore { + #[inline(always)] + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { + let mut hash = Output::::default(); + self.digest.finalize_fixed_core(buffer, &mut hash); + // finalize_fixed_core should reset the buffer as well, but + // to be extra safe we reset it explicitly again. + buffer.reset(); + let mut h = self.opad_digest.clone(); + buffer.digest_blocks(&hash, |b| h.update_blocks(b)); + h.finalize_fixed_core(buffer, out); + } +} + +impl Reset for HmacResetCore { + #[inline(always)] + fn reset(&mut self) { + self.digest = self.ipad_digest.clone(); + } +} + +impl AlgorithmName for HmacResetCore { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Hmac<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for HmacResetCore +where + D::Core: AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("HmacResetCore { ... }") + } +} diff --git a/hmac/src/lib.rs b/hmac/src/lib.rs index e79c068..d21153b 100644 --- a/hmac/src/lib.rs +++ b/hmac/src/lib.rs @@ -1,131 +1,58 @@ -//! Generic implementation of Hash-based Message Authentication Code (HMAC). -//! -//! To use it you will need a cryptographic hash function implementation which -//! implements the [`digest`] crate traits. You can find compatible crates -//! (e.g. [`sha2`]) in the [`RustCrypto/hashes`] repository. -//! -//! This crate provides two HMAC implementation [`Hmac`] and [`SimpleHmac`]. -//! The first one is a buffered wrapper around block-level [`HmacCore`]. -//! Internally it uses efficient state representation, but works only with -//! hash functions which expose block-level API and consume blocks eagerly -//! (e.g. it will not work with the BLAKE2 family of hash functions). -//! On the other hand, [`SimpleHmac`] is a bit less efficient memory-wise, -//! but works with all hash functions which implement the [`Digest`] trait. -//! -//! # Examples -//! Let us demonstrate how to use HMAC using the SHA-256 hash function. -//! -//! In the following examples [`Hmac`] is interchangeable with [`SimpleHmac`]. -//! -//! To get authentication code: -//! -//! ```rust -//! use sha2::Sha256; -//! use hmac::{Hmac, Mac}; -//! use hex_literal::hex; -//! -//! // Create alias for HMAC-SHA256 -//! type HmacSha256 = Hmac; -//! -//! let mut mac = HmacSha256::new_from_slice(b"my secret and secure key") -//! .expect("HMAC can take key of any size"); -//! mac.update(b"input message"); -//! -//! // `result` has type `CtOutput` which is a thin wrapper around array of -//! // bytes for providing constant time equality check -//! let result = mac.finalize(); -//! // To get underlying array use `into_bytes`, but be careful, since -//! // incorrect use of the code value may permit timing attacks which defeats -//! // the security provided by the `CtOutput` -//! let code_bytes = result.into_bytes(); -//! let expected = hex!(" -//! 97d2a569059bbcd8ead4444ff99071f4 -//! c01d005bcefe0d3567e1be628e5fdcd9 -//! "); -//! assert_eq!(code_bytes[..], expected[..]); -//! ``` -//! -//! To verify the message: -//! -//! ```rust -//! # use sha2::Sha256; -//! # use hmac::{Hmac, Mac}; -//! # use hex_literal::hex; -//! # type HmacSha256 = Hmac; -//! let mut mac = HmacSha256::new_from_slice(b"my secret and secure key") -//! .expect("HMAC can take key of any size"); -//! -//! mac.update(b"input message"); -//! -//! let code_bytes = hex!(" -//! 97d2a569059bbcd8ead4444ff99071f4 -//! c01d005bcefe0d3567e1be628e5fdcd9 -//! "); -//! // `verify_slice` will return `Ok(())` if code is correct, `Err(MacError)` otherwise -//! mac.verify_slice(&code_bytes[..]).unwrap(); -//! ``` -//! -//! # Block and input sizes -//! Usually it is assumed that block size is larger than output size. Due to the -//! generic nature of the implementation, this edge case must be handled as well -//! to remove potential panic. This is done by truncating hash output to the hash -//! block size if needed. -//! -//! [`digest`]: https://docs.rs/digest -//! [`sha2`]: https://docs.rs/sha2 -//! [`RustCrypto/hashes`]: https://github.com/RustCrypto/hashes +// Overwrite intra-crate links +//! [`Reset`]: digest::Reset +//! [`Digest`]: digest::Digest +//! [`FixedOutputReset`]: digest::FixedOutputReset +//! [`Hmac`]: Hmac +//! [`HmacReset`]: HmacReset +//! [`SimpleHmac`]: SimpleHmac +//! [`SimpleHmacReset`]: SimpleHmacReset +//! [`block_api::HmacCore`]: block_api::HmacCore +//! [`block_api::HmacResetCore`]: block_api::HmacResetCore #![no_std] +#![doc = include_str!("../README.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", - html_root_url = "https://docs.rs/hmac/0.12.1" + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg" )] -#![forbid(unsafe_code)] #![cfg_attr(docsrs, feature(doc_cfg))] -#![warn(missing_docs, rust_2018_idioms)] -#[cfg(feature = "std")] -extern crate std; +pub use digest::{self, KeyInit, Mac, block_api::EagerHash}; -pub use digest; -pub use digest::Mac; - -use digest::{ - core_api::{Block, BlockSizeUser}, - Digest, -}; - -mod optim; +/// Block-level implementation. +pub mod block_api; mod simple; +mod simple_reset; +mod utils; -pub use optim::{Hmac, HmacCore}; pub use simple::SimpleHmac; +pub use simple_reset::SimpleHmacReset; + +use core::fmt; +use digest::block_api::{AlgorithmName, CoreProxy}; + +digest::buffer_fixed!( + /// Generic HMAC instance. + #[derive(Clone)] + pub struct Hmac(block_api::HmacCore); + impl: MacTraits KeyInit; +); + +impl AlgorithmName for Hmac { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::Core::write_alg_name(f) + } +} -const IPAD: u8 = 0x36; -const OPAD: u8 = 0x5C; +digest::buffer_fixed!( + /// Generic HMAC instance with reset support. + #[derive(Clone)] + pub struct HmacReset(block_api::HmacResetCore); + impl: ResetMacTraits KeyInit; +); -fn get_der_key(key: &[u8]) -> Block { - let mut der_key = Block::::default(); - // The key that HMAC processes must be the same as the block size of the - // underlying hash function. If the provided key is smaller than that, - // we just pad it with zeros. If its larger, we hash it and then pad it - // with zeros. - if key.len() <= der_key.len() { - der_key[..key.len()].copy_from_slice(key); - } else { - let hash = D::digest(key); - // All commonly used hash functions have block size bigger - // than output hash size, but to be extra rigorous we - // handle the potential uncommon cases as well. - // The condition is calcualted at compile time, so this - // branch gets removed from the final binary. - if hash.len() <= der_key.len() { - der_key[..hash.len()].copy_from_slice(&hash); - } else { - let n = der_key.len(); - der_key.copy_from_slice(&hash[..n]); - } +impl AlgorithmName for HmacReset { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::Core::write_alg_name(f) } - der_key } diff --git a/hmac/src/optim.rs b/hmac/src/optim.rs deleted file mode 100644 index 32d6277..0000000 --- a/hmac/src/optim.rs +++ /dev/null @@ -1,280 +0,0 @@ -use super::{get_der_key, IPAD, OPAD}; -use core::{fmt, slice}; -#[cfg(feature = "reset")] -use digest::Reset; -use digest::{ - block_buffer::Eager, - core_api::{ - AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, CoreProxy, CoreWrapper, - FixedOutputCore, OutputSizeUser, UpdateCore, - }, - crypto_common::{Key, KeySizeUser}, - generic_array::typenum::{IsLess, Le, NonZero, U256}, - HashMarker, InvalidLength, KeyInit, MacMarker, Output, -}; - -/// Generic HMAC instance. -pub type Hmac = CoreWrapper>; - -/// Generic core HMAC instance, which operates over blocks. -pub struct HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - digest: D::Core, - opad_digest: D::Core, - #[cfg(feature = "reset")] - ipad_digest: D::Core, -} - -impl Clone for HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - fn clone(&self) -> Self { - Self { - digest: self.digest.clone(), - opad_digest: self.opad_digest.clone(), - #[cfg(feature = "reset")] - ipad_digest: self.ipad_digest.clone(), - } - } -} - -impl MacMarker for HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ -} - -impl BufferKindUser for HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - type BufferKind = Eager; -} - -impl KeySizeUser for HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - type KeySize = <::Core as BlockSizeUser>::BlockSize; -} - -impl BlockSizeUser for HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - type BlockSize = <::Core as BlockSizeUser>::BlockSize; -} - -impl OutputSizeUser for HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - type OutputSize = <::Core as OutputSizeUser>::OutputSize; -} - -impl KeyInit for HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - #[inline(always)] - fn new(key: &Key) -> Self { - Self::new_from_slice(key.as_slice()).unwrap() - } - - #[inline(always)] - fn new_from_slice(key: &[u8]) -> Result { - let mut buf = get_der_key::>(key); - for b in buf.iter_mut() { - *b ^= IPAD; - } - let mut digest = D::Core::default(); - digest.update_blocks(slice::from_ref(&buf)); - - for b in buf.iter_mut() { - *b ^= IPAD ^ OPAD; - } - - let mut opad_digest = D::Core::default(); - opad_digest.update_blocks(slice::from_ref(&buf)); - - Ok(Self { - #[cfg(feature = "reset")] - ipad_digest: digest.clone(), - opad_digest, - digest, - }) - } -} - -impl UpdateCore for HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - #[inline(always)] - fn update_blocks(&mut self, blocks: &[Block]) { - self.digest.update_blocks(blocks); - } -} - -impl FixedOutputCore for HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - #[inline(always)] - fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { - let mut hash = Output::::default(); - self.digest.finalize_fixed_core(buffer, &mut hash); - // finalize_fixed_core should reset the buffer as well, but - // to be extra safe we reset it explicitly again. - buffer.reset(); - #[cfg(not(feature = "reset"))] - let h = &mut self.opad_digest; - #[cfg(feature = "reset")] - let mut h = self.opad_digest.clone(); - buffer.digest_blocks(&hash, |b| h.update_blocks(b)); - h.finalize_fixed_core(buffer, out); - } -} - -#[cfg(feature = "reset")] -#[cfg_attr(docsrs, doc(cfg(feature = "reset")))] -impl Reset for HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - #[inline(always)] - fn reset(&mut self) { - self.digest = self.ipad_digest.clone(); - } -} - -impl AlgorithmName for HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + AlgorithmName - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Hmac<")?; - ::write_alg_name(f)?; - f.write_str(">") - } -} - -impl fmt::Debug for HmacCore -where - D: CoreProxy, - D::Core: HashMarker - + AlgorithmName - + UpdateCore - + FixedOutputCore - + BufferKindUser - + Default - + Clone, - ::BlockSize: IsLess, - Le<::BlockSize, U256>: NonZero, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("HmacCore<")?; - ::write_alg_name(f)?; - f.write_str("> { ... }") - } -} diff --git a/hmac/src/simple.rs b/hmac/src/simple.rs index e28c4c3..7c8de58 100644 --- a/hmac/src/simple.rs +++ b/hmac/src/simple.rs @@ -1,11 +1,9 @@ -use super::{get_der_key, IPAD, OPAD}; +use crate::utils::{IPAD, OPAD, get_der_key}; use core::fmt; use digest::{ - crypto_common::{Block, BlockSizeUser, InvalidLength, Key, KeySizeUser}, Digest, FixedOutput, KeyInit, MacMarker, Output, OutputSizeUser, Update, + common::{Block, BlockSizeUser, InvalidLength, Key, KeySizeUser}, }; -#[cfg(feature = "reset")] -use digest::{FixedOutputReset, Reset}; /// Simplified HMAC instance able to operate over hash functions /// which do not expose block-level API and hash functions which @@ -14,8 +12,6 @@ use digest::{FixedOutputReset, Reset}; pub struct SimpleHmac { digest: D, opad_key: Block, - #[cfg(feature = "reset")] - ipad_key: Block, } impl KeySizeUser for SimpleHmac { @@ -26,29 +22,22 @@ impl MacMarker for SimpleHmac {} impl KeyInit for SimpleHmac { fn new(key: &Key) -> Self { - Self::new_from_slice(key.as_slice()).unwrap() + Self::new_from_slice(key.as_slice()).expect("HMAC accepts keys of any length") } #[inline] fn new_from_slice(key: &[u8]) -> Result { - let der_key = get_der_key::(key); - let mut ipad_key = der_key.clone(); - for b in ipad_key.iter_mut() { - *b ^= IPAD; - } + let mut buf = get_der_key::(key); + buf.iter_mut().for_each(|b: &mut u8| *b ^= IPAD); + let mut digest = D::new(); - digest.update(&ipad_key); + digest.update(&buf); - let mut opad_key = der_key; - for b in opad_key.iter_mut() { - *b ^= OPAD; - } + buf.iter_mut().for_each(|b: &mut u8| *b ^= OPAD ^ IPAD); Ok(Self { digest, - opad_key, - #[cfg(feature = "reset")] - ipad_key, + opad_key: buf, }) } } @@ -68,39 +57,13 @@ impl FixedOutput for SimpleHmac { fn finalize_into(self, out: &mut Output) { let mut h = D::new(); h.update(&self.opad_key); - h.update(&self.digest.finalize()); + h.update(self.digest.finalize()); h.finalize_into(out); } } impl fmt::Debug for SimpleHmac { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SimpleHmac") - .field("digest", &self.digest) - // TODO: replace with `finish_non_exhaustive` on MSRV - // bump to 1.53 - .field("..", &"..") - .finish() - } -} - -#[cfg(feature = "reset")] -#[cfg_attr(docsrs, doc(cfg(feature = "reset")))] -impl Reset for SimpleHmac { - fn reset(&mut self) { - Reset::reset(&mut self.digest); - self.digest.update(&self.ipad_key); - } -} - -#[cfg(feature = "reset")] -#[cfg_attr(docsrs, doc(cfg(feature = "reset")))] -impl FixedOutputReset for SimpleHmac { - fn finalize_into_reset(&mut self, out: &mut Output) { - let mut h = D::new(); - Update::update(&mut h, &self.opad_key); - Update::update(&mut h, &self.digest.finalize_reset()); - Update::update(&mut self.digest, &self.ipad_key); - Digest::finalize_into(h, out); + f.write_str("SimpleHmac { ... }") } } diff --git a/hmac/src/simple_reset.rs b/hmac/src/simple_reset.rs new file mode 100644 index 0000000..c676aed --- /dev/null +++ b/hmac/src/simple_reset.rs @@ -0,0 +1,92 @@ +use crate::utils::{IPAD, OPAD, get_der_key}; +use core::fmt; +use digest::{ + Digest, FixedOutput, KeyInit, MacMarker, Output, OutputSizeUser, Update, + common::{Block, BlockSizeUser, InvalidLength, Key, KeySizeUser}, +}; +use digest::{FixedOutputReset, Reset}; + +/// Simplified HMAC instance with reset support able to operate +/// over hash functions which do not expose block-level API and +/// hash functions which process blocks lazily (e.g. BLAKE2). +#[derive(Clone)] +pub struct SimpleHmacReset { + digest: D, + opad_key: Block, + ipad_key: Block, +} + +impl KeySizeUser for SimpleHmacReset { + type KeySize = D::BlockSize; +} + +impl MacMarker for SimpleHmacReset {} + +impl KeyInit for SimpleHmacReset { + fn new(key: &Key) -> Self { + Self::new_from_slice(key.as_slice()).expect("HMAC accepts keys of any length") + } + + #[inline] + fn new_from_slice(key: &[u8]) -> Result { + let der_key = get_der_key::(key); + + let mut ipad_key = der_key.clone(); + ipad_key.iter_mut().for_each(|b: &mut u8| *b ^= IPAD); + + let mut digest = D::new(); + digest.update(&ipad_key); + + let mut opad_key = der_key; + opad_key.iter_mut().for_each(|b: &mut u8| *b ^= OPAD); + + Ok(Self { + digest, + opad_key, + ipad_key, + }) + } +} + +impl Update for SimpleHmacReset { + #[inline(always)] + fn update(&mut self, data: &[u8]) { + self.digest.update(data); + } +} + +impl OutputSizeUser for SimpleHmacReset { + type OutputSize = D::OutputSize; +} + +impl FixedOutput for SimpleHmacReset { + fn finalize_into(self, out: &mut Output) { + let mut h = D::new(); + h.update(&self.opad_key); + h.update(self.digest.finalize()); + h.finalize_into(out); + } +} + +impl fmt::Debug for SimpleHmacReset { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("SimpleResetHmac") + } +} + +impl Reset for SimpleHmacReset { + fn reset(&mut self) { + Reset::reset(&mut self.digest); + self.digest.update(&self.ipad_key); + } +} + +impl FixedOutputReset for SimpleHmacReset { + fn finalize_into_reset(&mut self, out: &mut Output) { + let mut h = D::new(); + Update::update(&mut h, &self.opad_key); + Update::update(&mut h, &self.digest.finalize_reset()); + Update::update(&mut self.digest, &self.ipad_key); + Digest::finalize_into(h, out); + } +} diff --git a/hmac/src/utils.rs b/hmac/src/utils.rs new file mode 100644 index 0000000..833108c --- /dev/null +++ b/hmac/src/utils.rs @@ -0,0 +1,32 @@ +use digest::{ + Digest, + block_api::{Block, BlockSizeUser}, +}; + +pub(crate) const IPAD: u8 = 0x36; +pub(crate) const OPAD: u8 = 0x5C; + +pub(crate) fn get_der_key(key: &[u8]) -> Block { + let mut der_key = Block::::default(); + // The key that HMAC processes must be the same as the block size of the + // underlying hash function. If the provided key is smaller than that, + // we just pad it with zeros. If its larger, we hash it and then pad it + // with zeros. + if key.len() <= der_key.len() { + der_key[..key.len()].copy_from_slice(key); + } else { + let hash = D::digest(key); + // All commonly used hash functions have block size bigger + // than output hash size, but to be extra rigorous we + // handle the potential uncommon cases as well. + // The condition is calculated at compile time, so this + // branch gets removed from the final binary. + if hash.len() <= der_key.len() { + der_key[..hash.len()].copy_from_slice(&hash); + } else { + let n = der_key.len(); + der_key.copy_from_slice(&hash[..n]); + } + } + der_key +} diff --git a/hmac/tests/data/md5.blb b/hmac/tests/data/hmac_md5_rfc2104.blb similarity index 62% rename from hmac/tests/data/md5.blb rename to hmac/tests/data/hmac_md5_rfc2104.blb index 731a0ae..967b59f 100644 Binary files a/hmac/tests/data/md5.blb and b/hmac/tests/data/hmac_md5_rfc2104.blb differ diff --git a/hmac/tests/data/wycheproof-sha1.blb b/hmac/tests/data/hmac_sha1_wycheproof.blb similarity index 94% rename from hmac/tests/data/wycheproof-sha1.blb rename to hmac/tests/data/hmac_sha1_wycheproof.blb index a4f8f43..133a5cc 100644 Binary files a/hmac/tests/data/wycheproof-sha1.blb and b/hmac/tests/data/hmac_sha1_wycheproof.blb differ diff --git a/hmac/tests/data/sha224.blb b/hmac/tests/data/hmac_sha224_rfc4231.blb similarity index 62% rename from hmac/tests/data/sha224.blb rename to hmac/tests/data/hmac_sha224_rfc4231.blb index dabb20b..1bfa421 100644 Binary files a/hmac/tests/data/sha224.blb and b/hmac/tests/data/hmac_sha224_rfc4231.blb differ diff --git a/hmac/tests/data/sha256.blb b/hmac/tests/data/hmac_sha256_rfc4231.blb similarity index 61% rename from hmac/tests/data/sha256.blb rename to hmac/tests/data/hmac_sha256_rfc4231.blb index 6b5b288..66e1816 100644 Binary files a/hmac/tests/data/sha256.blb and b/hmac/tests/data/hmac_sha256_rfc4231.blb differ diff --git a/hmac/tests/data/wycheproof-sha256.blb b/hmac/tests/data/hmac_sha256_wycheproof.blb similarity index 93% rename from hmac/tests/data/wycheproof-sha256.blb rename to hmac/tests/data/hmac_sha256_wycheproof.blb index 62f1d35..591b8fd 100644 Binary files a/hmac/tests/data/wycheproof-sha256.blb and b/hmac/tests/data/hmac_sha256_wycheproof.blb differ diff --git a/hmac/tests/data/sha384.blb b/hmac/tests/data/hmac_sha384_rfc4231.blb similarity index 66% rename from hmac/tests/data/sha384.blb rename to hmac/tests/data/hmac_sha384_rfc4231.blb index d5cddb3..868b892 100644 Binary files a/hmac/tests/data/sha384.blb and b/hmac/tests/data/hmac_sha384_rfc4231.blb differ diff --git a/hmac/tests/data/wycheproof-sha384.blb b/hmac/tests/data/hmac_sha384_wycheproof.blb similarity index 97% rename from hmac/tests/data/wycheproof-sha384.blb rename to hmac/tests/data/hmac_sha384_wycheproof.blb index 69a7874..1b24e76 100644 Binary files a/hmac/tests/data/wycheproof-sha384.blb and b/hmac/tests/data/hmac_sha384_wycheproof.blb differ diff --git a/hmac/tests/data/sha512.blb b/hmac/tests/data/hmac_sha512_rfc4231.blb similarity index 75% rename from hmac/tests/data/sha512.blb rename to hmac/tests/data/hmac_sha512_rfc4231.blb index b79ae49..c278f9e 100644 Binary files a/hmac/tests/data/sha512.blb and b/hmac/tests/data/hmac_sha512_rfc4231.blb differ diff --git a/hmac/tests/data/wycheproof-sha512.blb b/hmac/tests/data/hmac_sha512_wycheproof.blb similarity index 99% rename from hmac/tests/data/wycheproof-sha512.blb rename to hmac/tests/data/hmac_sha512_wycheproof.blb index e061fd5..f2e3790 100644 Binary files a/hmac/tests/data/wycheproof-sha512.blb and b/hmac/tests/data/hmac_sha512_wycheproof.blb differ diff --git a/hmac/tests/data/streebog256.blb b/hmac/tests/data/hmac_streebog256_gost.blb similarity index 83% rename from hmac/tests/data/streebog256.blb rename to hmac/tests/data/hmac_streebog256_gost.blb index 99d9722..4cec3d5 100644 Binary files a/hmac/tests/data/streebog256.blb and b/hmac/tests/data/hmac_streebog256_gost.blb differ diff --git a/hmac/tests/data/streebog512.blb b/hmac/tests/data/hmac_streebog512_gost.blb similarity index 88% rename from hmac/tests/data/streebog512.blb rename to hmac/tests/data/hmac_streebog512_gost.blb index 8f0f151..ef212f0 100644 Binary files a/hmac/tests/data/streebog512.blb and b/hmac/tests/data/hmac_streebog512_gost.blb differ diff --git a/hmac/tests/mod.rs b/hmac/tests/mod.rs index d1798c8..d81bc7e 100644 --- a/hmac/tests/mod.rs +++ b/hmac/tests/mod.rs @@ -1,88 +1,82 @@ -#[cfg(not(feature = "reset"))] -use digest::new_mac_test as test; -#[cfg(feature = "reset")] -use digest::new_resettable_mac_test as test; -use hmac::{Hmac, SimpleHmac}; -use sha1::Sha1; -use sha2::{Sha224, Sha256, Sha384, Sha512}; -use streebog::{Streebog256, Streebog512}; +//! Test vectors. -// Test vectors from RFC 2104, plus wiki test -test!(hmac_md5_rfc2104, "md5", Hmac); -test!(hmac_md5_rfc2104_simple, "md5", SimpleHmac); +macro_rules! new_test { + ($name:ident, $digest:ty $(,)?) => { + new_test!($name, $digest, digest::dev::MacTruncSide::None); + }; + ($name:ident, $digest:ty, trunc_left $(,)?) => { + new_test!($name, $digest, digest::dev::MacTruncSide::Left); + }; + ($name:ident, $digest:ty, $trunc:expr $(,)?) => { + #[test] + fn $name() { + use digest::dev::{MacTestVector, mac_test, reset_mac_test}; + + digest::dev::blobby::parse_into_structs!( + include_bytes!(concat!("data/", stringify!($name), ".blb")); + static TEST_VECTORS: &[MacTestVector { key, input, tag }]; + ); + + type Hmac = hmac::Hmac<$digest>; + type HmacReset = hmac::HmacReset<$digest>; + type SimpleHmac = hmac::SimpleHmac<$digest>; + type SimpleHmacReset = hmac::SimpleHmacReset<$digest>; + + for (i, tv) in TEST_VECTORS.iter().enumerate() { + if let Err(reason) = mac_test::(tv, $trunc) { + panic!( + "\n\ + Failed `Hmac` test #{i}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" + ); + } + if let Err(reason) = reset_mac_test::(tv, $trunc) { + panic!( + "\n\ + Failed `HmacReset` test #{i}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" + ); + } + if let Err(reason) = mac_test::(tv, $trunc) { + panic!( + "\n\ + Failed `SimpleHmac` test #{i}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" + ); + } + if let Err(reason) = reset_mac_test::(tv, $trunc) { + panic!( + "\n\ + Failed `SimpleHmacReset` test #{i}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" + ); + } + } + } + }; +} + +// Test vectors from RFC 2104, plus Wikipedia test +new_test!(hmac_md5_rfc2104, md5::Md5); // Test vectors from RFC 4231 -test!(hmac_sha224_rfc4231, "sha224", Hmac); -test!(hmac_sha256_rfc4231, "sha256", Hmac); -test!(hmac_sha384_rfc4231, "sha384", Hmac); -test!(hmac_sha512_rfc4231, "sha512", Hmac); -test!(hmac_sha224_rfc4231_simple, "sha224", SimpleHmac); -test!(hmac_sha256_rfc4231_simple, "sha256", SimpleHmac); -test!(hmac_sha384_rfc4231_simple, "sha384", SimpleHmac); -test!(hmac_sha512_rfc4231_simple, "sha512", SimpleHmac); +new_test!(hmac_sha224_rfc4231, sha2::Sha224); +new_test!(hmac_sha256_rfc4231, sha2::Sha256); +new_test!(hmac_sha384_rfc4231, sha2::Sha384); +new_test!(hmac_sha512_rfc4231, sha2::Sha512); // Test vectors from R 50.1.113-2016: -// https://tc26.ru/standard/rs/Р 50.1.113-2016.pdf -test!(hmac_streebog256, "streebog256", Hmac); -test!(hmac_streebog512, "streebog512", Hmac); -test!( - hmac_streebog256_simple, - "streebog256", - SimpleHmac -); -test!( - hmac_streebog512_simple, - "streebog512", - SimpleHmac -); +// https://tc26.ru/standard/rs/Р%2050.1.113-2016.pdf +new_test!(hmac_streebog256_gost, streebog::Streebog256); +new_test!(hmac_streebog512_gost, streebog::Streebog512); // Tests from Project Wycheproof: // https://github.com/google/wycheproof -test!( - hmac_sha1_wycheproof, - "wycheproof-sha1", - Hmac, - trunc_left, -); -test!( - hmac_sha256_wycheproof, - "wycheproof-sha256", - Hmac, - trunc_left, -); -test!( - hmac_sha384_wycheproof, - "wycheproof-sha384", - Hmac, - trunc_left, -); -test!( - hmac_sha512_wycheproof, - "wycheproof-sha512", - Hmac, - trunc_left, -); -test!( - hmac_sha1_wycheproof_simple, - "wycheproof-sha1", - SimpleHmac, - trunc_left, -); -test!( - hmac_sha256_wycheproof_simple, - "wycheproof-sha256", - SimpleHmac, - trunc_left, -); -test!( - hmac_sha384_wycheproof_simple, - "wycheproof-sha384", - SimpleHmac, - trunc_left, -); -test!( - hmac_sha512_wycheproof_simple, - "wycheproof-sha512", - SimpleHmac, - trunc_left, -); +new_test!(hmac_sha1_wycheproof, sha1::Sha1, trunc_left); +new_test!(hmac_sha256_wycheproof, sha2::Sha256, trunc_left); +new_test!(hmac_sha384_wycheproof, sha2::Sha384, trunc_left); +new_test!(hmac_sha512_wycheproof, sha2::Sha512, trunc_left); diff --git a/pmac/CHANGELOG.md b/pmac/CHANGELOG.md index 2cee94a..c4a7a3e 100644 --- a/pmac/CHANGELOG.md +++ b/pmac/CHANGELOG.md @@ -5,6 +5,19 @@ 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.8.0 (UNRELEASED) +### Changed +- Edition changed to 2024 and MSRV bumped to 1.85 +- Relax MSRV policy and allow MSRV bumps in patch releases +- Update to `digest` v0.11 +- Update to `cipher` v0.5 +- Replace type aliases with newtypes ([#186]) + +### Removed +- `std` crate feature ([#186]) + +[#186]: https://github.com/RustCrypto/MACs/pull/186 + ## 0.7.1 (2022-02-17) ### Fixed - Minimal versions build ([#108]) diff --git a/pmac/Cargo.toml b/pmac/Cargo.toml index a512e5d..a7ceec0 100644 --- a/pmac/Cargo.toml +++ b/pmac/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "pmac" -version = "0.7.1" # Also update html_root_url in lib.rs when bumping this +version = "0.8.0-rc.5" description = "Generic implementation of Parallelizable Message Authentication Code" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.56" +edition = "2024" +rust-version = "1.85" readme = "README.md" documentation = "https://docs.rs/pmac" repository = "https://github.com/RustCrypto/MACs" @@ -13,18 +13,19 @@ keywords = ["crypto", "mac", "pmac"] categories = ["cryptography", "no-std"] [dependencies] -digest = { version = "0.10.3", features = ["mac"] } -cipher = "0.4.2" -dbl = "0.3" +cipher = "0.5" +digest = { version = "0.11", features = ["mac"] } +dbl = "0.5" [dev-dependencies] -aes = "0.8" -digest = { version = "0.10.3", features = ["dev"] } +aes = "0.9.0-rc.4" +digest = { version = "0.11", features = ["dev"] } [features] -std = ["digest/std"] -zeroize = ["cipher/zeroize"] +zeroize = ["cipher/zeroize", "digest/zeroize"] + +[lints] +workspace = true [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/pmac/README.md b/pmac/README.md index 93dfbba..bccef2c 100644 --- a/pmac/README.md +++ b/pmac/README.md @@ -1,27 +1,49 @@ -# RustCrypto: PMAC +# [RustCrypto]: PMAC [![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] -[![Build Status][build-image]][build-link] -Pure Rust implementation of the [Parallel Message Authentication Code (PMAC)][1]. +Generic implementation of [Parallelizable Message Authentication Code (PMAC)][PMAC]. + +## Examples +We will use AES-128 block cipher from the [`aes`] crate. + +To get authentication code: + +```rust +use aes::Aes128; +use pmac::{digest::KeyInit, Pmac, Mac}; -[Documentation][docs-link] +// Create `Mac` trait implementation, namely PMAC-AES128 +let mut mac = Pmac::::new_from_slice(b"very secret key.").unwrap(); +mac.update(b"input message"); -## Minimum Supported Rust Version +// `result` has type `Output` which is a thin wrapper around array of +// bytes for providing constant time equality check +let result = mac.finalize(); +// To get underlying array use `into_bytes` method, but be careful, since +// incorrect use of the tag value may permit timing attacks which defeat +// the security provided by the `Output` wrapper +let tag_bytes = result.into_bytes(); +``` -Rust **1.56** or higher. +To verify the message: -Minimum supported Rust version can be changed in the future, but it will be -done with a minor version bump. +```rust +# use aes::Aes128; +# use pmac::{digest::KeyInit, Pmac, Mac}; +let mut mac = Pmac::::new_from_slice(b"very secret key.").unwrap(); -## SemVer Policy +mac.update(b"input message"); -- All on-by-default features of this library are covered by SemVer -- MSRV is considered exempt from SemVer as noted above +# let tag_bytes = mac.clone().finalize().into_bytes(); +// `verify` will return `Ok(())` if tag is correct, `Err(MacError)` otherwise +mac.verify(&tag_bytes).unwrap(); +``` ## License @@ -40,17 +62,19 @@ dual licensed as above, without any additional terms or conditions. [//]: # (badges) -[crate-image]: https://img.shields.io/crates/v/pmac.svg +[crate-image]: https://img.shields.io/crates/v/pmac.svg?logo=rust [crate-link]: https://crates.io/crates/pmac [docs-image]: https://docs.rs/pmac/badge.svg [docs-link]: https://docs.rs/pmac/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260044-MACs -[build-image]: https://github.com/RustCrypto/MACs/workflows/pmac/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/MACs/actions?query=workflow%3Apmac +[build-image]: https://github.com/RustCrypto/MACs/actions/workflows/pmac.yml/badge.svg +[build-link]: https://github.com/RustCrypto/MACs/actions/workflows/pmac.yml [//]: # (general links) -[1]: https://en.wikipedia.org/wiki/PMAC_(cryptography) +[RustCrypto]: https://github.com/RustCrypto +[PMAC]: https://en.wikipedia.org/wiki/PMAC_(cryptography) +[`aes`]: https://docs.rs/aes diff --git a/pmac/benches/mod.rs b/pmac/benches/mod.rs index 726e57b..996a281 100644 --- a/pmac/benches/mod.rs +++ b/pmac/benches/mod.rs @@ -2,7 +2,7 @@ extern crate test; use aes::{Aes128, Aes256}; -use pmac::{Mac, Pmac}; +use pmac::{KeyInit, Pmac}; use test::Bencher; digest::bench_update!( diff --git a/pmac/src/block_api.rs b/pmac/src/block_api.rs new file mode 100644 index 0000000..1df4f1e --- /dev/null +++ b/pmac/src/block_api.rs @@ -0,0 +1,239 @@ +use cipher::{BlockCipherEncBackend, BlockCipherEncClosure, BlockCipherEncrypt, ParBlocks}; +use core::fmt; +use dbl::Dbl; +use digest::{ + MacMarker, Output, OutputSizeUser, Reset, + array::{Array, ArraySize}, + block_api::{ + AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, FixedOutputCore, Lazy, + SmallBlockSizeUser, UpdateCore, + }, + common::{InnerInit, InnerUser}, + typenum::Unsigned, +}; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// Generic PMAC instance +/// +/// `LC_SIZE` regulates size of pre-computed table used in PMAC computation. +/// With `LC_SIZE = 20` and for 128-bit block cipher the table is sufficient +/// for 16*2^20 = 16 MiB of input data. For longer messages the `l` value will +/// be computed on the fly from the last table value, which will be a bit less +/// efficient. +#[derive(Clone)] +pub struct PmacCore { + state: PmacState, + cipher: C, +} + +#[derive(Clone)] +struct PmacState { + counter: usize, + l_inv: Block, + l_cache: [Block; LC_SIZE], + tag: Block, + offset: Block, +} + +impl PmacState { + #[inline(always)] + fn next_offset(&mut self) -> &Block { + let ntz = self.counter.trailing_zeros() as usize; + self.counter += 1; + let l = if ntz < LC_SIZE { + self.l_cache[ntz].clone() + } else { + let mut block = self.l_cache[LC_SIZE - 1].clone(); + for _ in LC_SIZE - 1..ntz { + block = C::dbl(block); + } + block + }; + xor(&mut self.offset, &l); + &self.offset + } +} + +impl Drop for PmacState { + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + self.counter.zeroize(); + self.l_inv.zeroize(); + self.l_cache.iter_mut().for_each(Zeroize::zeroize); + self.tag.zeroize(); + self.offset.zeroize(); + } + } +} + +impl BlockSizeUser for PmacCore { + type BlockSize = C::BlockSize; +} + +impl OutputSizeUser for PmacCore { + type OutputSize = C::BlockSize; +} + +impl InnerUser for PmacCore { + type Inner = C; +} + +impl MacMarker for PmacCore {} + +impl Reset for PmacCore { + #[inline(always)] + fn reset(&mut self) { + self.state.tag = Default::default(); + self.state.offset = Default::default(); + self.state.counter = 1; + } +} + +impl BufferKindUser for PmacCore { + type BufferKind = Lazy; +} + +impl AlgorithmName for PmacCore { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Pmac<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for PmacCore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("PmacCore { ... }") + } +} + +impl InnerInit for PmacCore { + #[inline] + fn inner_init(cipher: C) -> Self { + let mut l = Default::default(); + cipher.encrypt_block(&mut l); + let l_inv = C::inv_dbl(l.clone()); + + let l_cache = [(); LC_SIZE].map(|_| { + let next_l = C::dbl(l.clone()); + core::mem::replace(&mut l, next_l) + }); + + let state = PmacState { + l_cache, + l_inv, + tag: Default::default(), + offset: Default::default(), + counter: 1, + }; + Self { cipher, state } + } +} + +impl UpdateCore for PmacCore { + #[inline] + fn update_blocks(&mut self, blocks: &[Block]) { + struct Closure<'a, C: PmacCipher, const LC_SIZE: usize> { + state: &'a mut PmacState, + blocks: &'a [Block], + } + + impl BlockSizeUser for Closure<'_, C, LC_SIZE> { + type BlockSize = C::BlockSize; + } + + impl BlockCipherEncClosure for Closure<'_, C, LC_SIZE> { + #[inline(always)] + fn call>(self, backend: &B) { + let Self { mut blocks, state } = self; + if B::ParBlocksSize::USIZE > 1 { + // TODO: replace with `slice::as_chunks` on stabilization + // and migration to const generics + let mut iter = blocks.chunks_exact(B::ParBlocksSize::USIZE); + for chunk in &mut iter { + let mut tmp = ParBlocks::::try_from(chunk).expect("size mismatch"); + + for block in tmp.iter_mut() { + xor(block, state.next_offset()); + } + + backend.encrypt_par_blocks((&mut tmp).into()); + + for t in tmp.iter() { + xor(&mut state.tag, t); + } + } + blocks = iter.remainder(); + } + + for block in blocks { + let mut block = block.clone(); + xor(&mut block, state.next_offset()); + backend.encrypt_block((&mut block).into()); + xor(&mut state.tag, &block); + } + } + } + + let Self { cipher, state } = self; + cipher.encrypt_with_backend(Closure { blocks, state }); + } +} + +impl FixedOutputCore for PmacCore { + #[inline] + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { + let Self { + cipher, + state: PmacState { tag, l_inv, .. }, + } = self; + let pos = buffer.get_pos(); + let buf = buffer.pad_with_zeros(); + if pos == buf.len() { + xor(tag, &buf); + xor(tag, l_inv); + } else { + tag[pos] ^= 0x80; + xor(tag, &buf); + } + cipher.encrypt_block_b2b(tag, out); + } +} + +#[cfg(feature = "zeroize")] +impl ZeroizeOnDrop for PmacCore {} + +#[inline(always)] +fn xor(buf: &mut Array, data: &Array) { + for i in 0..N::USIZE { + buf[i] ^= data[i]; + } +} + +/// Helper trait implemented for block ciphers supported by PMAC. +/// +/// Currently this trait is implemented for all block cipher encryptors +/// with block size equal to 64 and 128 bits. +pub trait PmacCipher: SmallBlockSizeUser + BlockCipherEncrypt { + /// Double block. See the [`Dbl`] trait docs for more information. + fn dbl(block: Block) -> Block; + /// Reverse double block.. See the [`Dbl`] trait docs for more information. + fn inv_dbl(block: Block) -> Block; +} + +impl PmacCipher for C +where + Self: SmallBlockSizeUser + BlockCipherEncrypt, + Block: Dbl, +{ + fn dbl(block: Block) -> Block { + block.dbl() + } + + fn inv_dbl(block: Block) -> Block { + block.inv_dbl() + } +} diff --git a/pmac/src/lib.rs b/pmac/src/lib.rs index 41ee11d..df9a282 100644 --- a/pmac/src/lib.rs +++ b/pmac/src/lib.rs @@ -1,365 +1,29 @@ -//! Generic implementation of [Parallelizable Message Authentication Code (PMAC)][1], -//! otherwise known as OMAC1. -//! -//! # Examples -//! We will use AES-128 block cipher from the [aes](https://docs.rs/aes) crate. -//! -//! To get authentication code: -//! -//! ```rust -//! use aes::Aes128; -//! use pmac::{Pmac, Mac}; -//! -//! // Create `Mac` trait implementation, namely PMAC-AES128 -//! let mut mac = Pmac::::new_from_slice(b"very secret key.").unwrap(); -//! mac.update(b"input message"); -//! -//! // `result` has type `Output` which is a thin wrapper around array of -//! // bytes for providing constant time equality check -//! let result = mac.finalize(); -//! // To get underlying array use `into_bytes` method, but be careful, since -//! // incorrect use of the tag value may permit timing attacks which defeat -//! // the security provided by the `Output` wrapper -//! let tag_bytes = result.into_bytes(); -//! ``` -//! -//! To verify the message: -//! -//! ```rust -//! # use aes::Aes128; -//! # use pmac::{Pmac, Mac}; -//! let mut mac = Pmac::::new_from_slice(b"very secret key.").unwrap(); -//! -//! mac.update(b"input message"); -//! -//! # let tag_bytes = mac.clone().finalize().into_bytes(); -//! // `verify` will return `Ok(())` if tag is correct, `Err(MacError)` otherwise -//! mac.verify(&tag_bytes).unwrap(); -//! ``` -//! -//! [1]: https://en.wikipedia.org/wiki/PMAC_(cryptography) - #![no_std] +#![doc = include_str!("../README.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", - html_root_url = "https://docs.rs/pmac/0.7.1" + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg" )] -#![forbid(unsafe_code)] #![cfg_attr(docsrs, feature(doc_cfg))] -#![warn(missing_docs, rust_2018_idioms)] -#[cfg(feature = "std")] -extern crate std; +pub use digest::{self, KeyInit, Mac}; -pub use digest::{self, Mac}; +/// Block-level implementation. +pub mod block_api; -use cipher::{BlockBackend, BlockCipher, BlockClosure, BlockEncryptMut, ParBlocks}; +use block_api::PmacCipher; use core::fmt; -use dbl::Dbl; -use digest::{ - block_buffer::Lazy, - core_api::{ - AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, CoreWrapper, FixedOutputCore, - UpdateCore, - }, - crypto_common::{InnerInit, InnerUser}, - generic_array::{ - typenum::{IsLess, Le, NonZero, Unsigned, U256}, - ArrayLength, GenericArray, - }, - MacMarker, Output, OutputSizeUser, Reset, -}; - -#[cfg(feature = "zeroize")] -use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; - -/// Generic CMAC instance. -pub type Pmac = CoreWrapper>; - -/// Generic PMAC instance -/// -/// `LC_SIZE` regulates size of pre-computed table used in PMAC computation. -/// With `LC_SIZE = 20` and for 128-bit block cipher the table is sufficient -/// for 16*2^20 = 16 MiB of input data. For longer messages the `l` value will -/// be computed on the fly from the last table value, which will be a bit less -/// efficient. -// TODO: make LC_SIZE default to 20 on stabilization of -// https://github.com/rust-lang/rust/issues/44580 -#[derive(Clone)] -pub struct PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - state: PmacState, - cipher: C, -} - -#[derive(Clone)] -struct PmacState -where - N: ArrayLength, - GenericArray: Dbl, -{ - counter: usize, - l_inv: Block, - l_cache: [Block; LC_SIZE], - tag: Block, - offset: Block, -} - -impl BlockSizeUser for PmacState -where - N: ArrayLength, - GenericArray: Dbl, -{ - type BlockSize = N; -} - -impl PmacState -where - N: ArrayLength, - GenericArray: Dbl, -{ - #[inline(always)] - fn next_offset(&mut self) -> &Block { - let ntz = self.counter.trailing_zeros() as usize; - self.counter += 1; - let l = if ntz < LC_SIZE { - self.l_cache[ntz].clone() - } else { - let mut block = self.l_cache[LC_SIZE - 1].clone(); - for _ in LC_SIZE - 1..ntz { - block = block.dbl(); - } - block - }; - xor(&mut self.offset, &l); - &self.offset - } -} - -#[cfg(feature = "zeroize")] -impl Drop for PmacState -where - N: ArrayLength, - GenericArray: Dbl, -{ - fn drop(&mut self) { - self.counter.zeroize(); - self.l_inv.zeroize(); - self.l_cache.iter_mut().for_each(|c| c.zeroize()); - self.tag.zeroize(); - self.offset.zeroize(); - } -} - -impl BlockSizeUser for PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - type BlockSize = C::BlockSize; -} - -impl OutputSizeUser for PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - type OutputSize = C::BlockSize; -} - -impl InnerUser for PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - type Inner = C; -} - -impl MacMarker for PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ -} - -impl Reset for PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - #[inline(always)] - fn reset(&mut self) { - self.state.tag = Default::default(); - self.state.offset = Default::default(); - self.state.counter = 1; - } -} +use digest::block_api::{AlgorithmName, CoreProxy}; -impl BufferKindUser for PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - type BufferKind = Lazy; -} +digest::buffer_fixed!( + /// Generic PMAC instance with `LC_SIZE` = 20. + #[derive(Clone)] + pub struct Pmac(block_api::PmacCore); + impl: ResetMacTraits InnerInit; +); -impl AlgorithmName for PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone + AlgorithmName, - Block: Dbl, -{ +impl AlgorithmName for Pmac { fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Pmac<")?; - ::write_alg_name(f)?; - f.write_str(">") - } -} - -impl fmt::Debug for PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone + AlgorithmName, - Block: Dbl, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("PmacCore<")?; - ::write_alg_name(f)?; - f.write_str("> { ... }") - } -} - -impl InnerInit for PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - #[inline] - fn inner_init(mut cipher: C) -> Self { - let mut l = Default::default(); - cipher.encrypt_block_mut(&mut l); - let l_inv = l.clone().inv_dbl(); - - let l_cache = [(); LC_SIZE].map(|_| { - let next_l = l.clone().dbl(); - core::mem::replace(&mut l, next_l) - }); - - let state = PmacState { - l_cache, - l_inv, - tag: Default::default(), - offset: Default::default(), - counter: 1, - }; - Self { cipher, state } - } -} - -impl UpdateCore for PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, -{ - #[inline] - fn update_blocks(&mut self, blocks: &[Block]) { - struct Ctx<'a, N, const LC_SIZE: usize> - where - N: ArrayLength, - GenericArray: Dbl, - { - state: &'a mut PmacState, - blocks: &'a [Block], - } - - impl<'a, N, const LC_SIZE: usize> BlockSizeUser for Ctx<'a, N, LC_SIZE> - where - N: ArrayLength, - GenericArray: Dbl, - { - type BlockSize = N; - } - - impl<'a, N, const LC_SIZE: usize> BlockClosure for Ctx<'a, N, LC_SIZE> - where - N: ArrayLength, - GenericArray: Dbl, - { - #[inline(always)] - fn call>(self, backend: &mut B) { - let Self { mut blocks, state } = self; - if B::ParBlocksSize::USIZE > 1 { - // TODO: replace with `slice::as_chunks` on stabilization - // and migration to const generics - let mut iter = blocks.chunks_exact(B::ParBlocksSize::USIZE); - for chunk in &mut iter { - let mut tmp = ParBlocks::::clone_from_slice(chunk); - for block in tmp.iter_mut() { - xor(block, state.next_offset()); - } - backend.proc_par_blocks((&mut tmp).into()); - for t in tmp.iter() { - xor(&mut state.tag, t); - } - } - blocks = iter.remainder(); - } - - for block in blocks { - let mut block = block.clone(); - xor(&mut block, state.next_offset()); - backend.proc_block((&mut block).into()); - xor(&mut state.tag, &block); - } - } - } - - let Self { cipher, state } = self; - cipher.encrypt_with_backend_mut(Ctx { blocks, state }) - } -} - -impl FixedOutputCore for PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone, - Block: Dbl, - C::BlockSize: IsLess, - Le: NonZero, -{ - #[inline] - fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { - let Self { - cipher, - state: PmacState { tag, l_inv, .. }, - } = self; - let pos = buffer.get_pos(); - let buf = buffer.pad_with_zeros(); - if pos == buf.len() { - xor(tag, buf); - xor(tag, l_inv); - } else { - tag[pos] ^= 0x80; - xor(tag, buf); - } - cipher.encrypt_block_b2b_mut(tag, out); - } -} - -#[cfg(feature = "zeroize")] -impl ZeroizeOnDrop for PmacCore -where - C: BlockCipher + BlockEncryptMut + Clone + ZeroizeOnDrop, - Block: Dbl, - C::BlockSize: IsLess, - Le: NonZero, -{ -} - -#[inline(always)] -fn xor>(buf: &mut GenericArray, data: &GenericArray) { - for i in 0..N::USIZE { - buf[i] ^= data[i]; + ::Core::write_alg_name(f) } } diff --git a/pmac/tests/data/aes128.blb b/pmac/tests/data/pmac_aes128.blb similarity index 98% rename from pmac/tests/data/aes128.blb rename to pmac/tests/data/pmac_aes128.blb index 0c9bc77..dd8dbe1 100644 Binary files a/pmac/tests/data/aes128.blb and b/pmac/tests/data/pmac_aes128.blb differ diff --git a/pmac/tests/data/aes192.blb b/pmac/tests/data/pmac_aes192.blb similarity index 98% rename from pmac/tests/data/aes192.blb rename to pmac/tests/data/pmac_aes192.blb index 5add08d..d72869d 100644 Binary files a/pmac/tests/data/aes192.blb and b/pmac/tests/data/pmac_aes192.blb differ diff --git a/pmac/tests/data/aes256.blb b/pmac/tests/data/pmac_aes256.blb similarity index 98% rename from pmac/tests/data/aes256.blb rename to pmac/tests/data/pmac_aes256.blb index 77a8864..7642050 100644 Binary files a/pmac/tests/data/aes256.blb and b/pmac/tests/data/pmac_aes256.blb differ diff --git a/pmac/tests/mod.rs b/pmac/tests/mod.rs index 30d017a..4f53f4c 100644 --- a/pmac/tests/mod.rs +++ b/pmac/tests/mod.rs @@ -1,8 +1,9 @@ +//! Test vectors from: + use aes::{Aes128, Aes192, Aes256}; -use digest::new_resettable_mac_test; +use digest::{dev::reset_mac_test, new_mac_test}; use pmac::Pmac; -// Test vectors from: http://web.cs.ucdavis.edu/~rogaway/ocb/pmac-test.htm -new_resettable_mac_test!(pmac_aes128, "aes128", Pmac); -new_resettable_mac_test!(pmac_aes192, "aes192", Pmac); -new_resettable_mac_test!(pmac_aes256, "aes256", Pmac); +new_mac_test!(pmac_aes128, Pmac, reset_mac_test); +new_mac_test!(pmac_aes192, Pmac, reset_mac_test); +new_mac_test!(pmac_aes256, Pmac, reset_mac_test); diff --git a/retail-mac/CHANGELOG.md b/retail-mac/CHANGELOG.md new file mode 100644 index 0000000..956a57d --- /dev/null +++ b/retail-mac/CHANGELOG.md @@ -0,0 +1,9 @@ +# 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.1.0 (UNRELEASED) +- Initial release diff --git a/retail-mac/Cargo.toml b/retail-mac/Cargo.toml new file mode 100644 index 0000000..e03775d --- /dev/null +++ b/retail-mac/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "retail-mac" +version = "0.1.0-pre.1" +description = "Implementation of Retail Message Authentication Code (Retail MAC)" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2024" +rust-version = "1.85" +readme = "README.md" +documentation = "https://docs.rs/retail-mac" +repository = "https://github.com/RustCrypto/MACs" +keywords = ["crypto", "mac"] + +[dependencies] +cipher = "0.5" +digest = { version = "0.11", features = ["mac"] } + +[dev-dependencies] +digest = { version = "0.11", features = ["dev"] } +hex-literal = "1" + +aes = "0.9.0-rc.4" +des = "0.9.0-rc.3" + +[features] +zeroize = ["cipher/zeroize", "digest/zeroize"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +all-features = true diff --git a/retail-mac/LICENSE-APACHE b/retail-mac/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/retail-mac/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/retail-mac/LICENSE-MIT b/retail-mac/LICENSE-MIT new file mode 100644 index 0000000..f5b157a --- /dev/null +++ b/retail-mac/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018 Artyom Pavlov + +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/retail-mac/README.md b/retail-mac/README.md new file mode 100644 index 0000000..b0f2ace --- /dev/null +++ b/retail-mac/README.md @@ -0,0 +1,72 @@ +# [RustCrypto]: Retail MAC + +[![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] + +Pure Rust implementation of the [Retail Message Authentication Code][Retail MAC], +also known as ISO/IEC 9797-1 MAC algorithm 3. + +**WARNING!** The algorithm has known weaknesses in case of variable-length +messages. See the Wikipedia article for [CBC-MAC] for more information. + +## Examples + +```rust +use retail_mac::{digest::KeyInit, RetailMac, Mac}; +use des::Des; +use hex_literal::hex; + +type RetailMacDes = RetailMac; + +// test from ISO/IEC 9797-1:2011 section B.4 +// K and K' are concatenated: +let key = hex!("0123456789ABCDEFFEDCBA9876543210"); + +let mut mac = RetailMacDes::new_from_slice(&key).unwrap(); +mac.update(b"Now is the time for all "); +let correct = hex!("A1C72E74EA3FA9B6"); +mac.verify_slice(&correct).unwrap(); + +let mut mac2 = RetailMacDes::new_from_slice(&key).unwrap(); +mac2.update(b"Now is the time for it"); +let correct2 = hex!("2E2B1428CC78254F"); +mac2.verify_slice(&correct2).unwrap(); +``` + +## 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/retail-mac.svg?logo=rust +[crate-link]: https://crates.io/crates/retail-mac +[docs-image]: https://docs.rs/retail-mac/badge.svg +[docs-link]: https://docs.rs/retail-mac/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260044-MACs +[build-image]: https://github.com/RustCrypto/MACs/actions/workflows/retail-mac.yml/badge.svg +[build-link]: https://github.com/RustCrypto/MACs/actions/workflows/retail-mac.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto +[Retail MAC]: https://en.wikipedia.org/wiki/ISO/IEC_9797-1#MAC_algorithm_3 +[CBC-MAC]: https://en.wikipedia.org/wiki/CBC-MAC#Security_with_fixed_and_variable-length_messages diff --git a/retail-mac/benches/mod.rs b/retail-mac/benches/mod.rs new file mode 100644 index 0000000..adf8ee5 --- /dev/null +++ b/retail-mac/benches/mod.rs @@ -0,0 +1,23 @@ +#![feature(test)] +extern crate test; + +use aes::Aes128; +use des::Des; +use retail_mac::{KeyInit, RetailMac}; +use test::Bencher; + +digest::bench_update!( + RetailMac::::new(&Default::default()); + retail_mac_aes128_10 10; + retail_mac_aes128_100 100; + retail_mac_aes128_1000 1000; + retail_mac_aes128_10000 10000; +); + +digest::bench_update!( + RetailMac::::new(&Default::default()); + retail_mac_des_10 10; + retail_mac_des_100 100; + retail_mac_des_1000 1000; + retail_mac_des_10000 10000; +); diff --git a/retail-mac/src/block_api.rs b/retail-mac/src/block_api.rs new file mode 100644 index 0000000..bc27d19 --- /dev/null +++ b/retail-mac/src/block_api.rs @@ -0,0 +1,194 @@ +use cipher::{ + BlockCipherDecrypt, BlockCipherEncBackend, BlockCipherEncClosure, BlockCipherEncrypt, + InvalidLength, KeySizeUser, +}; +use core::{fmt, ops::Mul}; +use digest::{ + Key, KeyInit, MacMarker, Output, OutputSizeUser, Reset, + array::{Array, ArraySize}, + block_api::{ + AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, Eager, FixedOutputCore, + SmallBlockSizeUser, UpdateCore, + }, + block_buffer::BlockSizes, + typenum::{Prod, U2}, +}; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// Generic core Retail MAC instance, which operates over blocks. +#[derive(Clone)] +pub struct RetailMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt, +{ + cipher: C, + cipher_prime: C, + state: Block, +} + +impl BlockSizeUser for RetailMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt, +{ + type BlockSize = C::BlockSize; +} + +impl OutputSizeUser for RetailMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt, +{ + type OutputSize = C::BlockSize; +} + +impl KeySizeUser for RetailMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt, + ::_BlockSize: Mul, + Prod<::_BlockSize, U2>: ArraySize, +{ + type KeySize = Prod<::_BlockSize, U2>; +} + +impl MacMarker for RetailMacCore where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt +{ +} + +impl BufferKindUser for RetailMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt, +{ + type BufferKind = Eager; +} + +impl KeyInit for RetailMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt + KeyInit, + ::_BlockSize: Mul, + Prod<::_BlockSize, U2>: ArraySize, +{ + #[inline(always)] + fn new(key: &Key) -> Self { + Self::new_from_slice(key.as_slice()).expect("HMAC accepts keys of any length") + } + + #[inline(always)] + fn new_from_slice(key: &[u8]) -> Result { + let cipher = C::new_from_slice(&key[..key.len() / 2])?; + let cipher_prime = C::new_from_slice(&key[key.len() / 2..])?; + Ok(Self { + cipher, + cipher_prime, + state: Block::::default(), + }) + } +} + +impl UpdateCore for RetailMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt, +{ + #[inline] + fn update_blocks(&mut self, blocks: &[Block]) { + struct Closure<'a, N: BlockSizes> { + state: &'a mut Block, + blocks: &'a [Block], + } + + impl BlockSizeUser for Closure<'_, N> { + type BlockSize = N; + } + + impl BlockCipherEncClosure for Closure<'_, N> { + #[inline(always)] + fn call>(self, backend: &B) { + for block in self.blocks { + xor(self.state, block); + backend.encrypt_block((self.state).into()); + } + } + } + + let Self { cipher, state, .. } = self; + cipher.encrypt_with_backend(Closure { state, blocks }); + } +} + +impl Reset for RetailMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt, +{ + #[inline(always)] + fn reset(&mut self) { + self.state = Default::default(); + } +} + +impl FixedOutputCore for RetailMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt, +{ + #[inline] + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { + let Self { + state, + cipher, + cipher_prime, + } = self; + let pos = buffer.get_pos(); + if pos != 0 { + xor(state, &buffer.pad_with_zeros()); + cipher.encrypt_block(state); + } + cipher_prime.decrypt_block(state); + cipher.encrypt_block(state); + out.copy_from_slice(state); + } +} + +impl AlgorithmName for RetailMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("RetailMac<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for RetailMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("RetailMacCore<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +impl Drop for RetailMacCore +where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt, +{ + fn drop(&mut self) { + self.state.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +impl ZeroizeOnDrop for RetailMacCore where + C: BlockCipherEncrypt + SmallBlockSizeUser + BlockCipherDecrypt + ZeroizeOnDrop +{ +} + +#[inline(always)] +fn xor(buf: &mut Array, data: &Array) { + for i in 0..N::USIZE { + buf[i] ^= data[i]; + } +} diff --git a/retail-mac/src/lib.rs b/retail-mac/src/lib.rs new file mode 100644 index 0000000..2d2ac88 --- /dev/null +++ b/retail-mac/src/lib.rs @@ -0,0 +1,71 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +pub use digest::{self, Key, KeyInit, Mac}; + +/// Block-level implementation. +pub mod block_api; + +use block_api::RetailMacCore; +use cipher::{AlgorithmName, BlockCipherDecrypt, BlockCipherEncrypt, KeySizeUser}; +use core::{fmt, ops::Mul}; +use digest::{ + InvalidLength, + array::ArraySize, + block_api::CoreProxy, + block_api::SmallBlockSizeUser, + typenum::{Prod, U2}, +}; + +digest::buffer_fixed!( + /// Generic Retail MAC instance. + #[derive(Clone)] + pub struct RetailMac(RetailMacCore); + impl: ResetMacTraits; +); + +impl KeySizeUser for RetailMac +where + C: BlockCipherEncrypt + BlockCipherDecrypt + SmallBlockSizeUser, + ::_BlockSize: Mul, + Prod<::_BlockSize, U2>: ArraySize, +{ + type KeySize = Prod<::_BlockSize, U2>; +} + +impl KeyInit for RetailMac +where + C: BlockCipherEncrypt + BlockCipherDecrypt + SmallBlockSizeUser + KeyInit, + ::_BlockSize: Mul, + Prod<::_BlockSize, U2>: ArraySize, +{ + #[inline(always)] + fn new(key: &Key) -> Self { + Self { + core: KeyInit::new(key), + buffer: Default::default(), + } + } + + #[inline(always)] + fn new_from_slice(key: &[u8]) -> Result { + KeyInit::new_from_slice(key).map(|core| Self { + core, + buffer: Default::default(), + }) + } +} + +impl AlgorithmName for RetailMac +where + C: BlockCipherEncrypt + BlockCipherDecrypt + SmallBlockSizeUser + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::Core::write_alg_name(f) + } +}