diff --git a/.github/workflows/gmac.yml b/.github/workflows/gmac.yml new file mode 100644 index 0000000..aa8c8bb --- /dev/null +++ b/.github/workflows/gmac.yml @@ -0,0 +1,61 @@ +name: gmac + +on: + pull_request: + paths: + - ".github/workflows/gmac.yml" + - "gmac/**" + - "Cargo.*" + push: + branches: master + + +defaults: + run: + working-directory: gmac + +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 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 76400ca..5747770 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,24 +2,42 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", + "zeroize", +] + [[package]] name = "aes" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" dependencies = [ - "cipher", + "cipher 0.5.1", "cpubits", - "cpufeatures", + "cpufeatures 0.3.0", ] +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "belt-block" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0304188fd8684b910d24cae451c724cd5140a037c407e696247e94bb57b06434" dependencies = [ - "cipher", + "cipher 0.5.1", ] [[package]] @@ -27,11 +45,17 @@ name = "belt-mac" version = "0.2.0" dependencies = [ "belt-block", - "cipher", + "cipher 0.5.1", "digest", "hex-literal", ] +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + [[package]] name = "blobby" version = "0.4.0" @@ -52,8 +76,8 @@ dependencies = [ name = "cbc-mac" version = "0.2.0" dependencies = [ - "aes", - "cipher", + "aes 0.9.0", + "cipher 0.5.1", "des", "digest", "hex-literal", @@ -65,6 +89,27 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout 0.1.4", +] + [[package]] name = "cipher" version = "0.5.1" @@ -72,8 +117,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" dependencies = [ "block-buffer", - "crypto-common", - "inout", + "crypto-common 0.2.1", + "inout 0.2.2", "zeroize", ] @@ -81,8 +126,8 @@ dependencies = [ name = "cmac" version = "0.8.0" dependencies = [ - "aes", - "cipher", + "aes 0.9.0", + "cipher 0.5.1", "dbl", "des", "digest", @@ -103,6 +148,15 @@ 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.3.0" @@ -112,6 +166,16 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "crypto-common" version = "0.2.1" @@ -119,6 +183,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ "hybrid-array", + "rand_core", ] [[package]] @@ -145,22 +210,106 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a" dependencies = [ - "cipher", + "cipher 0.5.1", ] [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "blobby", "block-buffer", - "crypto-common", + "crypto-common 0.2.1", "ctutils", "zeroize", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core", + "wasip2", + "wasip3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", + "zeroize", +] + +[[package]] +name = "gmac" +version = "0.1.0" +dependencies = [ + "aes 0.8.4", + "cipher 0.5.1", + "crypto-common 0.2.1", + "ctutils", + "digest", + "generic-array", + "ghash", + "hex-literal", + "rand", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex-literal" version = "1.1.0" @@ -189,6 +338,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "inout" version = "0.2.2" @@ -198,6 +374,12 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + [[package]] name = "kuznyechik" version = "0.9.0" @@ -205,22 +387,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "003764d77f5f2e2a57a4d17dc6547e0595f14a3c289283c9eeaf7170d3a4ff83" dependencies = [ "cfg-if", - "cipher", + "cipher 0.5.1", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "magma" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84fcdd6a71a6af65297be074d9d26cda7a2e98b86294ee0de0732b00c2063a61" dependencies = [ - "cipher", + "cipher 0.5.1", ] [[package]] @@ -233,27 +427,150 @@ dependencies = [ "digest", ] +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "pmac" version = "0.8.0" dependencies = [ - "aes", - "cipher", + "aes 0.9.0", + "cipher 0.5.1", "dbl", "digest", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "retail-mac" version = "0.1.0" dependencies = [ - "aes", - "cipher", + "aes 0.9.0", + "cipher 0.5.1", "des", "digest", "hex-literal", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "sha1" version = "0.11.0" @@ -261,7 +578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.3.0", "digest", ] @@ -272,7 +589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.3.0", "digest", ] @@ -285,14 +602,211 @@ dependencies = [ "digest", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index e6b334a..1cea546 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "belt-mac", "cbc-mac", "cmac", + "gmac", "hmac", "pmac", "retail-mac", diff --git a/gmac/CHANGELOG.md b/gmac/CHANGELOG.md new file mode 100644 index 0000000..c72577e --- /dev/null +++ b/gmac/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 (2026-04-??) +Initial Release \ No newline at end of file diff --git a/gmac/Cargo.toml b/gmac/Cargo.toml new file mode 100644 index 0000000..300073c --- /dev/null +++ b/gmac/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "gmac" +version = "0.1.0" +description = "Generic implementation of the Galois Counter Mode Message Authentication Code (GMAC)" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2024" +readme = "README.md" +documentation = "https://docs.rs/gmac" +repository = "https://github.com/RustCrypto/MACs" +keywords = ["crypto", "mac", "gmac", "digest"] +categories = ["cryptography", "no-std"] +rust-version = "1.85" + +[dependencies] +aes = "0.8.4" +cipher = "0.5" +common = { version = "0.2", package = "crypto-common" } +ctutils = "0.4" +digest = { version = "0.11.3", features = ["mac"] } +generic-array = { version = "0.14.7" } +ghash = "0.5" + +[dev-dependencies] +digest = { version = "0.11.3", features = ["dev"] } +rand = { version = "0.10.0"} +hex-literal = "1" + +[features] +default = ["zeroize", "rand_core" ] +zeroize = [ + "aes/zeroize", + "cipher/zeroize", + "common/zeroize", + "digest/zeroize", + "generic-array/zeroize", + "ghash/zeroize", + ] +rand_core = ["common/rand_core"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +all-features = true diff --git a/gmac/LICENSE-APACHE b/gmac/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/gmac/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/gmac/LICENSE-MIT b/gmac/LICENSE-MIT new file mode 100644 index 0000000..50c6180 --- /dev/null +++ b/gmac/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/gmac/README.md b/gmac/README.md new file mode 100644 index 0000000..9dd5687 --- /dev/null +++ b/gmac/README.md @@ -0,0 +1,103 @@ +# [RustCrypto]: GMAC + +[![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] + +Generic implementation of the [Galois Message Authentication Code (GMAC)][GMAC]. + +GMAC is defined by NIST [SP 800-38D] as an authentication-only specialization of +GCM (Galois Counter Mode). It is equivalent to GCM encryption with an empty plaintext +and data only provided in the AAD. + +**WARNING!** This is a nonce-based MAC and must have a unique nonce for each generation. +This is identical to the issues with nonce-reuse and AES-GCM (which uses GMAC internally). +This also means that it is dangerous to clone an instance of GMAC when generating a MAC. +(It is safe to clone for verification purposes only.) + +**WARNING!** GMAC has known weaknesses when used with variable tag lengths associated with the same key. This is identical to the issues with AES-GCM (which uses GMAC internally). +Ensure that only a single tag length is ever used with any given key. + +## Examples + +We will use AES-128 with a 12 byte IV. +(A good default cipher backed by the `aes` crate.) + +To get the authentication code: + +```rust +use gmac::{KeyIvInit, Gmac, Gmac128, Mac}; +use rand::rngs::SysRng; + +// Use the predefined type of `Gmac128`. +# #[cfg(feature = "rand_core" )] +let iv = Gmac128::generate_nonce(SysRng).unwrap(); +# #[cfg(not(feature = "rand_core" ))] +# let iv = b"000000000000"; +let iv = iv.as_slice(); +let mut mac = Gmac128::new_from_slices(b"very secret key.", iv).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 gmac::{KeyIvInit, Gmac, Gmac128, Mac}; +use rand::rngs::SysRng; + +# let iv = b"000000000000"; + +let mut mac = Gmac128::new_from_slices(b"very secret key.", iv).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(); +``` + +## 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/gmac.svg?logo=rust +[crate-link]: https://crates.io/crates/gmac +[docs-image]: https://docs.rs/gmac/badge.svg +[docs-link]: https://docs.rs/gmac/ +[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/gmac.yml/badge.svg +[build-link]: https://github.com/RustCrypto/MACs/actions/workflows/gmac.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto +[GMAC]: https://en.wikipedia.org/wiki/Galois/Counter_Mode +[`aes`]: https://docs.rs/aes +[SP 800-38D]: https://doi.org/10.6028/NIST.SP.800-38D \ No newline at end of file diff --git a/gmac/src/lib.rs b/gmac/src/lib.rs new file mode 100644 index 0000000..aa00ce9 --- /dev/null +++ b/gmac/src/lib.rs @@ -0,0 +1,292 @@ +#![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 aes::cipher::KeyIvInit; +pub use digest::{self, Mac}; + +use aes::{ + Aes128Enc, Aes192Enc, Aes256Enc, + cipher::{BlockEncrypt, BlockSizeUser, IvSizeUser, KeyInit, KeySizeUser}, +}; +use cipher::consts::{U12, U16}; +use core::marker::PhantomData; +use digest::{FixedOutput, MacMarker, OutputSizeUser, Update}; +use generic_array::GenericArray; +use ghash::{GHash, universal_hash::UniversalHash}; + +#[cfg(feature = "rand_core")] +use common::rand_core::{TryCryptoRng, TryRng}; + +/// Marker trait used to identify specific ciphers which may be used with GMAC. +pub trait GmacCipher: BlockEncrypt + BlockSizeUser + KeyInit {} +impl GmacCipher for Aes128Enc {} +impl GmacCipher for Aes192Enc {} +impl GmacCipher for Aes256Enc {} + +/// GMAC with a 128-bit key and 12 byte nonce. +pub type Gmac128 = Gmac; +/// GMAC with a 192-bit key and 12 byte nonce. +pub type Gmac192 = Gmac; +/// GMAC with a 256-bit key and 12 byte nonce. +pub type Gmac256 = Gmac; + +/// GMAC: Generic over an underlying AES implementation and nonce size. +#[derive(Debug, Clone)] +pub struct Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + /// Encryption cipher. + cipher: PhantomData, + + /// GHASH authenticator. + ghash: GHash, + + /// Length of the nonce. + nonce_size: PhantomData, + + /// Length of the data processed + data_size: usize, + + /// Mask for final tag creation + mask: ghash::Block, + + /// Buffer for unaligned data + buffer: ghash::Block, + + /// Data available in buffer + buffer_len: usize, +} + +impl Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + /// Fills the internal buffered block and returns the number of bytes copied from `data` + #[inline] + fn update_buffer(&mut self, data: &[u8]) -> usize { + let data_to_copy = usize::min(Aes::block_size() - self.buffer_len, data.len()); + let buffer_end = self.buffer_len + data_to_copy; + self.buffer.as_mut_slice()[self.buffer_len..buffer_end] + .copy_from_slice(&data[..data_to_copy]); + self.buffer_len = buffer_end; + data_to_copy + } + + /// Hash the buffered block. Panics (in debug) if an entire block has not been buffered. + #[inline] + fn hash_buffer(&mut self) { + debug_assert_eq!(self.buffer_len, Aes::block_size()); + self.ghash.update(&[self.buffer]); + self.buffer_len = 0; + } + + /// Calculates and sets the mask value used for finalizing the tag value. + // Mostly stolen from aes-gcm + #[inline] + fn init_mask(&mut self, cipher: Aes, nonce: &GenericArray) { + let j0 = if NonceSize::to_usize() == 12 { + let mut block = ghash::Block::default(); + block[..12].copy_from_slice(nonce); + block[15] = 1; + block + } else { + let mut ghash = self.ghash.clone(); + ghash.update_padded(nonce); + + let mut block = ghash::Block::default(); + let nonce_bits = (NonceSize::to_usize() as u64) * 8; + block[8..].copy_from_slice(&nonce_bits.to_be_bytes()); + ghash.update(&[block]); + ghash.finalize() + }; + + self.mask = aes::Block::default(); + self.mask.copy_from_slice(&j0); + cipher.encrypt_block(&mut self.mask); + } + + /// Generate a random nonce for use with GMAC. + /// + /// GMAC accepts a parameter to encryption/decryption called a "nonce" + /// which must be unique every time a MAC is generated and never repeated for the same key. + /// The nonce is often prepended to the tag. The nonce used to produce a given tag must be + /// passed to the verification MAC calculation. + /// + /// Nonces don’t necessarily have to be random, but it is one strategy which is implemented by this function. + /// + /// # ⚠️Security Warning + /// + /// GMAC fails catastrophically if the nonce is ever repeated. + /// + /// Using random nonces runs the risk of repeating them. The best case for GMAC is with a 12 byte nonce. + /// With a 12-byte (96-bit) nonce, you can safely generate 2^32 (4,294,967,296) random nonces before the risk + /// of repeating one becomes too high. + /// + /// # Errors + /// - Returns an error if and only if `rng.try_fill_bytes()` returns an error. + #[cfg(feature = "rand_core")] + #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] + #[inline] + pub fn generate_nonce( + mut rng: Rng, + ) -> Result, ::Error> + where + Rng: TryCryptoRng, + { + let mut nonce = GenericArray::::default(); + rng.try_fill_bytes(&mut nonce)?; + Ok(nonce) + } +} + +impl OutputSizeUser for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + type OutputSize = U16; +} + +impl KeySizeUser for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + type KeySize = Aes::KeySize; +} + +impl IvSizeUser for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + type IvSize = NonceSize; +} + +impl MacMarker for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ +} + +impl KeyIvInit for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + fn new(key: &aes::cipher::Key, nonce: &aes::cipher::Iv) -> Self { + let cipher = Aes::new(key); + + let mut ghash_key = ghash::Key::default(); + cipher.encrypt_block(&mut ghash_key); + + let ghash = GHash::new(&ghash_key); + + let mut result = Self { + cipher: PhantomData, + ghash, + nonce_size: PhantomData, + data_size: 0, + mask: ghash::Block::default(), + buffer: ghash::Block::default(), + buffer_len: 0, + }; + result.init_mask(cipher, nonce); + result + } +} + +impl Update for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + fn update(&mut self, data: &[u8]) { + self.data_size += data.len(); + // First handle any buffered data + let mut offset = 0; + + if self.buffer_len > 0 { + offset += self.update_buffer(data); + if self.buffer_len < Aes::block_size() { + // We don't have enough data for an entire block, so just return + return; + } + self.hash_buffer(); + } + let data = &data[offset..]; + let tail = data.len() % Aes::block_size(); + let data_end = data.len() - tail; + let (body, tail) = data.split_at(data_end); + debug_assert_eq!(body.len() % Aes::block_size(), 0); + self.ghash.update_padded(body); + if !tail.is_empty() { + self.update_buffer(tail); + } + } +} + +impl FixedOutput for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + fn finalize_into(self, out: &mut digest::Output) { + let mut ghash = self.ghash.clone(); + // First, process any buffered data + if self.buffer_len != 0 { + ghash.update_padded(&self.buffer[..self.buffer_len]); + } + let bits_hashed = (self.data_size as u64) * 8; + let mut block = ghash::Block::default(); + block[..8].copy_from_slice(&bits_hashed.to_be_bytes()); + ghash.update(&[block]); + let tag = ghash.finalize(); + for (r, (a, b)) in out + .as_mut_slice() + .iter_mut() + .zip(tag.as_slice().iter().zip(self.mask.as_slice())) + { + *r = *a ^ *b; + } + } +} + +// Optional features +// Zeroize +#[cfg(feature = "zeroize")] +use digest::zeroize::{Zeroize, ZeroizeOnDrop}; + +#[cfg(feature = "zeroize")] +impl Drop for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ + fn drop(&mut self) { + // cipher is PhantomData + // ghash implements ZeroizeOnDrop + // nonce_size is PhantomData + self.data_size.zeroize(); + self.mask.zeroize(); + self.buffer.zeroize(); + // buffer_len is not sensitive + } +} + +#[cfg(feature = "zeroize")] +impl ZeroizeOnDrop for Gmac +where + Aes: GmacCipher, + NonceSize: generic_array::ArrayLength, +{ +} diff --git a/gmac/tests/data/gmac_aes128.blb b/gmac/tests/data/gmac_aes128.blb new file mode 100644 index 0000000..44d66cb Binary files /dev/null and b/gmac/tests/data/gmac_aes128.blb differ diff --git a/gmac/tests/data/gmac_aes192.blb b/gmac/tests/data/gmac_aes192.blb new file mode 100644 index 0000000..2ff25b2 Binary files /dev/null and b/gmac/tests/data/gmac_aes192.blb differ diff --git a/gmac/tests/data/gmac_aes256.blb b/gmac/tests/data/gmac_aes256.blb new file mode 100644 index 0000000..035bc0c Binary files /dev/null and b/gmac/tests/data/gmac_aes256.blb differ diff --git a/gmac/tests/mod.rs b/gmac/tests/mod.rs new file mode 100644 index 0000000..e15aac1 --- /dev/null +++ b/gmac/tests/mod.rs @@ -0,0 +1,159 @@ +//! Test vectors. + +use aes::{ + Aes128Enc, Aes192Enc, Aes256Enc, + cipher::{KeyIvInit, Unsigned}, +}; +use cipher::consts::{U1, U128}; +use digest::dev::blobby; +use digest::dev::initialized_mac_test; +use gmac::*; +use hex_literal::hex; + +#[derive(Copy, Clone, Debug)] +struct GmacTestVector { + pub key: &'static [u8], + pub iv: &'static [u8], + pub data: &'static [u8], + pub tag: &'static [u8], +} + +// Source for CAVP test vectors: https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/CAVP-TESTING-BLOCK-CIPHER-MODES + +blobby::parse_into_structs!( + // All CAVP SP 800-38D test vectors for 128 bit keys and empty plaintexts + include_bytes!("data/gmac_aes128.blb"); + static GMAC_128_KATS: &[GmacTestVector { key, iv, data, tag}]; +); +blobby::parse_into_structs!( + // All CAVP SP 800-38D test vectors for 192 bit keys and empty plaintexts + include_bytes!("data/gmac_aes192.blb"); + static GMAC_192_KATS: &[GmacTestVector { key, iv, data, tag}]; +); +blobby::parse_into_structs!( + // All CAVP SP 800-38D test vectors for 256 bit keys and empty plaintexts + include_bytes!("data/gmac_aes256.blb"); + static GMAC_256_KATS: &[GmacTestVector { key, iv, data, tag}]; +); + +#[test] +fn debugging_kat() { + let key = hex!("2fb45e5b8f993a2bfebc4b15b533e0b4"); + let iv = hex!("5b05755f984d2b90f94b8027"); + let expected = hex!("c75b7832b2a2d9bd827412b6ef5769db"); + + let mut mac = Gmac128::new_from_slices(&key, &iv).unwrap(); + mac.update(&hex!("e85491b2202caf1d7dce03b97e09331c32473941")); + let actual = mac.finalize(); + assert_eq!(&expected, actual.as_bytes().as_slice()); +} + +#[cfg(feature = "rand_core")] +#[test] +fn nonce_generation() { + use rand::rngs::SysRng; + + let fake_key = [0u8; 16]; + let nonce = Gmac128::generate_nonce(SysRng).expect("SysRng failed"); + let _ = Gmac128::new(&fake_key.into(), &nonce); +} + +#[cfg(feature = "rand_core")] +#[test] +fn nonce_generation_16() { + use cipher::consts::U16; + use rand::rngs::SysRng; + + let fake_key = [0u8; 16]; + let nonce = Gmac::::generate_nonce(SysRng).expect("SysRng failed"); + let _ = Gmac::::new(&fake_key.into(), &nonce); +} + +#[test] +fn gmac128_defaults() { + test_kats::("gmac128_defaults", GMAC_128_KATS); +} + +#[test] +fn gmac128_iv8() { + test_kats::>("gmac128_iv8", GMAC_128_KATS); +} + +#[test] +fn gmac128_iv1024() { + test_kats::>("gmac128_iv1024", GMAC_128_KATS); +} + +#[test] +fn gmac192_defaults() { + test_kats::("gmac192_defaults", GMAC_192_KATS); +} + +#[test] +fn gmac192_iv8() { + test_kats::>("gmac192_iv8", GMAC_192_KATS); +} + +#[test] +fn gmac192_iv1024() { + test_kats::>("gmac192_iv1024", GMAC_192_KATS); +} + +#[test] +fn gmac256_defaults() { + test_kats::("gmac256_defaults", GMAC_256_KATS); +} + +#[test] +fn gmac256_iv8() { + test_kats::>("gmac256_iv8", GMAC_256_KATS); +} + +#[test] +fn gmac256_iv1024() { + test_kats::>("gmac256_iv1024", GMAC_256_KATS); +} + +fn test_kats(name: &str, kats: &[GmacTestVector]) +where + MAC: Mac + KeyIvInit + Clone, +{ + for (idx, tv) in kats.iter().enumerate() { + if MAC::KeySize::to_usize() != tv.key.len() { + continue; + } + if MAC::IvSize::to_usize() != tv.iv.len() { + continue; + } + if MAC::OutputSize::to_usize() < tv.tag.len() { + continue; + } + let mac = MAC::new_from_slices(tv.key, tv.iv).expect("Incorrect key or IV length"); + + if MAC::OutputSize::to_usize() == tv.tag.len() { + if let Err(reason) = initialized_mac_test( + mac.clone(), + tv.data, + tv.tag, + digest::dev::MacTruncSide::None, + ) { + panic!( + "\n\ + Failed test {name}#{idx}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" + ) + } + } + if let Err(reason) = + initialized_mac_test(mac, tv.data, tv.tag, digest::dev::MacTruncSide::Left) + { + panic!( + "\n\ + Failed test (truncated) {name}#{idx}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" + ) + } + } +}