From d4677268c48e5718f6ad8ccebd02916e68958358 Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Thu, 22 Oct 2020 08:10:53 +0100 Subject: [PATCH 1/2] Add Wycheproof tests and utilities If WYCHEPROOF_DIR environment variable is set, will attempt to find a Wycheproof repo there. If not, the `build.rs` script will clone the Wycheproof repo under OUT_DIR. --- Cargo.lock | 849 ++++++++++++++++++++++++++++++ Cargo.toml | 1 + wycheproof/Cargo.toml | 36 ++ wycheproof/build.rs | 11 + wycheproof/src/lib.rs | 112 ++++ wycheproof/tests/aead_test.rs | 185 +++++++ wycheproof/tests/aes_cmac_test.rs | 104 ++++ wycheproof/tests/aes_siv_test.rs | 95 ++++ wycheproof/tests/ecdsa_test.rs | 199 +++++++ wycheproof/tests/ed25519_test.rs | 108 ++++ wycheproof/tests/hkdf_test.rs | 91 ++++ wycheproof/tests/prf_test.rs | 161 ++++++ 12 files changed, 1952 insertions(+) create mode 100644 wycheproof/Cargo.toml create mode 100644 wycheproof/build.rs create mode 100644 wycheproof/src/lib.rs create mode 100644 wycheproof/tests/aead_test.rs create mode 100644 wycheproof/tests/aes_cmac_test.rs create mode 100644 wycheproof/tests/aes_siv_test.rs create mode 100644 wycheproof/tests/ecdsa_test.rs create mode 100644 wycheproof/tests/ed25519_test.rs create mode 100644 wycheproof/tests/hkdf_test.rs create mode 100644 wycheproof/tests/prf_test.rs diff --git a/Cargo.lock b/Cargo.lock index e0766486..88a59609 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,100 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aead" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +dependencies = [ + "aes-soft", + "aesni", + "cipher", +] + +[[package]] +name = "aes-gcm" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aes-siv" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0efed46e44a34d455eff2465e57627853b0e33c44658284f383691c7a634c8b" +dependencies = [ + "aead", + "aes", + "cipher", + "cmac", + "crypto-mac 0.10.0", + "ctr", + "dbl 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pmac", + "zeroize", +] + +[[package]] +name = "aes-soft" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c5ab1fa47ce0ddf44cbd65b1b4e626e3c03b06fd42005603acdc6fa13526296" +dependencies = [ + "byteorder", + "cipher", + "opaque-debug 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher", + "opaque-debug 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bitvec" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2838fdd79e8776dbe07a106c784b0f8dda571a21b2750a092cc4cbaa653c8e" +dependencies = [ + "funty", + "radium", + "wyz", +] + [[package]] name = "blobby" version = "0.3.0" @@ -15,10 +110,82 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-padding" version = "0.2.1" +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "cc" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chacha20" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed8738f14471a99f0e316c327e68fc82a3611cc2895fcb604b89eedaf8f39d95" +dependencies = [ + "cipher", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc30d6481323d015a91ef515af1ab66bb44686f9634f7ea97410678e7a3bf804" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f7954ae5588102b35257639b1c36a2e7425cc6540fcdb4de19dcb91055d659" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cmac" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73d4de4f7724e5fe70addfb2bd37c2abd2f95084a429d7773b0b9645499b4272" +dependencies = [ + "crypto-mac 0.10.0", + "dbl 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "collectable" version = "0.0.2" @@ -31,6 +198,65 @@ version = "0.2.0" name = "cpuid-bool" version = "0.1.2" +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "crypto-mac" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "crypto-mac" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6" +dependencies = [ + "cipher", + "generic-array", + "subtle", +] + +[[package]] +name = "ctr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8492de420e9e60bc9a1d66e2dbb91825390b738a388606600663fc529b4b307" +dependencies = [ + "byteorder", + "digest", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "dbl" version = "0.3.0" @@ -38,6 +264,91 @@ dependencies = [ "generic-array", ] +[[package]] +name = "dbl" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2735145c3b9ba15f2d7a3ae8cdafcbc8c98a7bef7f62afe9d08bd99fbf7130de" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ecdsa" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87bf8bfb05ea8a6f74ddf48c7d1774851ba77bbe51ac984fdfa6c30310e1ff5f" +dependencies = [ + "elliptic-curve", + "hmac 0.9.0", + "signature", +] + +[[package]] +name = "ed25519" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c66a534cbb46ab4ea03477eae19d5c22c01da8258030280b7bd9d8433fb6ef" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand", + "serde", + "sha2", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396db09c483e7fca5d4fdb9112685632b3e76c9a607a2649c1bf904404a01366" +dependencies = [ + "bitvec", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01646e077d4ebda82b73f1bca002ea1e91561a77df2431a9e79729bcc31950ef" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "funty" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8" + [[package]] name = "generic-array" version = "0.14.4" @@ -48,6 +359,52 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6e27f0689a6e15944bdce7e45425efb87eaa8ab0c6e87f11d0987a9133e2531" +dependencies = [ + "polyval", +] + +[[package]] +name = "git2" +version = "0.13.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6f1a0238d7f8f8fd5ee642f4ebac4dbc03e03d1f78fbe7a3ede35dcf7e2224" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + +[[package]] +name = "group" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11f9f5fbf1943b48ae7c2bf6846e7d827a512d1be4f23af708f5ca5d01dde1" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "hex" version = "0.4.2" @@ -58,18 +415,510 @@ checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" name = "hex-literal" version = "0.3.1" +[[package]] +name = "hkdf" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe1149865383e4526a43aee8495f9a325f0b806c63ce6427d06336a590abbbc9" +dependencies = [ + "digest", + "hmac 0.8.1", +] + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest", +] + +[[package]] +name = "hmac" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff" +dependencies = [ + "crypto-mac 0.9.1", + "digest", +] + +[[package]] +name = "hmac" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" +dependencies = [ + "crypto-mac 0.10.0", + "digest", +] + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "jobserver" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" + +[[package]] +name = "libgit2-sys" +version = "0.12.14+1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f25af58e6495f7caf2919d08f212de550cfa3ed2f5e744988938ea292b9f549" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libssh2-sys" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca46220853ba1c512fc82826d0834d87b06bcd3c2a42241b7de72f3d2fe17056" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + [[package]] name = "opaque-debug" version = "0.3.0" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" + +[[package]] +name = "openssl-sys" +version = "0.9.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "p256" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280ed58e7e5f3052b6e2f596fa40c7eff4c27c4b6b6deecb5d685ba5c2080980" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "pmac" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c257eb89109a7e6115f40d44ca6d1816ecf9e90a1b38f6d477c78c1de505cb" +dependencies = [ + "crypto-mac 0.10.0", + "dbl 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "poly1305" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ce46de8e53ee414ca4d02bfefac75d8c12fba948b76622a40b4be34dfce980" +dependencies = [ + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5884790f1ce3553ad55fec37b5aaac5882e0e845a2612df744d6c85c9bf046c" +dependencies = [ + "cfg-if", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64de9a0c5361e034f1aefc9f71a86871ec870e766fe31a009734a989b329286a" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170a36ea86c864a3f16dd2687712dd6646f7019f301e57537c7f4dc9f5916770" +dependencies = [ + "block-buffer 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "cpuid-bool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "digest", + "opaque-debug 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha2" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" +dependencies = [ + "block-buffer 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "cpuid-bool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "digest", + "opaque-debug 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "signature" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "subtle" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd" + +[[package]] +name = "syn" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea9c5432ff16d6152371f808fb5a871cd67368171b09bb21b43df8e4a47a3556" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tinyvec" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117" + [[package]] name = "typenum" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "universal-hash" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "url" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +dependencies = [ + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" + [[package]] name = "version_check" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wycheproof" +version = "0.1.0" +dependencies = [ + "aes", + "aes-gcm", + "aes-siv", + "chacha20poly1305", + "cmac", + "digest", + "ed25519-dalek", + "elliptic-curve", + "generic-array", + "git2", + "hex", + "hkdf", + "hmac 0.10.1", + "p256", + "serde", + "serde_json", + "sha-1", + "sha2", + "signature", +] + +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + +[[package]] +name = "zeroize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f33972566adbd2d3588b0491eb94b98b43695c4ef897903470ede4f3f5a28a" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/Cargo.toml b/Cargo.toml index 0050f778..d149531a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,5 @@ members = [ "dbl", "hex-literal", "opaque-debug", + "wycheproof", ] diff --git a/wycheproof/Cargo.toml b/wycheproof/Cargo.toml new file mode 100644 index 00000000..caa54a3f --- /dev/null +++ b/wycheproof/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "wycheproof" +version = "0.1.0" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +description = "Utilities for retrieving Wycheproof test vectors" +repository = "https://github.com/RustCrypto/utils" +keywords = ["test", "Wycheproof"] +categories = ["cryptography", "no-std"] +edition = "2018" + +[dependencies] +hex = "*" +serde = { version = "*", features = ["derive"] } + +[dev-dependencies] +aes = "*" +aes-gcm = "*" +aes-siv = "*" +cmac = "*" +chacha20poly1305 = "*" +digest = "*" +ed25519-dalek = "*" +elliptic-curve = "*" +generic-array = "*" +hex = "*" +hkdf = "*" +hmac = "*" +p256 = { version = "*", features = ["ecdsa"] } +serde_json = "*" +sha-1 = "*" +sha2 = "*" +signature = "*" + +[build-dependencies] +git2 = "*" \ No newline at end of file diff --git a/wycheproof/build.rs b/wycheproof/build.rs new file mode 100644 index 00000000..07631e8e --- /dev/null +++ b/wycheproof/build.rs @@ -0,0 +1,11 @@ +fn main() { + if std::env::var("WYCHEPROOF_DIR").is_err() { + // No WYCHEPROOF_DIR set in the environment, so try to clone out the repo. + let mut wycheproof_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); + wycheproof_dir.push("wycheproof"); + if !wycheproof_dir.is_dir() { + git2::Repository::clone("https://github.com/google/wycheproof", wycheproof_dir) + .unwrap(); + } + } +} diff --git a/wycheproof/src/lib.rs b/wycheproof/src/lib.rs new file mode 100644 index 00000000..7ffed452 --- /dev/null +++ b/wycheproof/src/lib.rs @@ -0,0 +1,112 @@ +//! Helpers for retrieving Wycheproof test vectors. + +use serde::Deserialize; + +/// `Suite` represents the common elements of the top level object in a Wycheproof json +/// file. Implementations should embed (using `#[serde(flatten)]`) `Suite` in a struct +/// that strongly types the `testGroups` field. +#[derive(Debug, Deserialize)] +pub struct Suite { + pub algorithm: String, + #[serde(rename = "generatorVersion")] + pub generator_version: String, + #[serde(rename = "numberOfTests")] + pub number_of_tests: i32, + pub notes: std::collections::HashMap, +} + +/// `Group` represents the common elements of a testGroups object in a Wycheproof suite. +/// Implementations should embed (using `#[serde(flatten)]`) Group in a struct that +/// strongly types its list of cases. +#[derive(Debug, Deserialize)] +pub struct Group { + #[serde(rename = "type")] + pub group_type: String, +} + +/// `Result` represents the possible result values for a Wycheproof test case. +#[derive(Debug, PartialEq, Eq)] +pub enum CaseResult { + /// Test case is valid, the crypto operation should succeed. + Valid, + /// Test case is invalid; the crypto operation should fail. + Invalid, + /// Test case is valid, but uses weak parameters; the crypto operation might succeed + /// or fail depending on how strict the library is. + Acceptable, +} + +impl std::fmt::Display for CaseResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + CaseResult::Valid => "valid", + CaseResult::Invalid => "invalid", + CaseResult::Acceptable => "acceptable", + } + ) + } +} + +/// `Case` represents the common elements of a tests object in a Wycheproof group. +/// Implementations should embed (using `#[serde(flatten)]`) `Case` in a struct that +/// contains fields specific to the test type. +#[derive(Debug, Deserialize)] +pub struct Case { + #[serde(rename = "tcId")] + pub case_id: i32, + pub comment: String, + #[serde(with = "case_result")] + pub result: CaseResult, + #[serde(default)] + pub flags: Vec, +} + +/// Retrieve Wycheproof test vectors from the given filename in a Wycheproof repo. +/// +/// The location of the Wycheproof repository is given by the `WYCHEPROOF_DIR` environment variable if set; otherwise +/// `${OUT_DIR}/wycheproof` will be used. +pub fn wycheproof_data(filename: &str) -> Vec { + let wycheproof_dir = std::env::var("WYCHEPROOF_DIR") + .unwrap_or(concat!(env!("OUT_DIR"), "/wycheproof").to_string()); + std::fs::read(std::path::Path::new(&wycheproof_dir).join(filename)).unwrap_or_else(|_| { + panic!( + "Test vector file {} not found under {}", + filename, wycheproof_dir + ) + }) +} + +pub mod hex_string { + //! Manual JSON deserialization implementation for hex strings. + use serde::Deserialize; + pub fn deserialize<'de, D: serde::Deserializer<'de>>( + deserializer: D, + ) -> Result, D::Error> { + let s = String::deserialize(deserializer)?; + ::hex::decode(&s).map_err(|_e| { + serde::de::Error::invalid_value(serde::de::Unexpected::Str(&s), &"hex data expected") + }) + } +} + +pub mod case_result { + //! Manual JSON deserialization for a `result` enum. + use serde::Deserialize; + pub fn deserialize<'de, D: serde::Deserializer<'de>>( + deserializer: D, + ) -> Result { + let s = String::deserialize(deserializer)?; + match s.as_ref() { + "valid" => Ok(super::CaseResult::Valid), + "invalid" => Ok(super::CaseResult::Invalid), + "acceptable" => Ok(super::CaseResult::Acceptable), + _ => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Str(&s), + &"unexpected result value", + )), + } + } +} diff --git a/wycheproof/tests/aead_test.rs b/wycheproof/tests/aead_test.rs new file mode 100644 index 00000000..0720142f --- /dev/null +++ b/wycheproof/tests/aead_test.rs @@ -0,0 +1,185 @@ +use aes_gcm::aead::{generic_array::GenericArray, Aead, NewAead, Payload}; +use serde::Deserialize; +use wycheproof::hex_string; + +#[derive(Debug, Deserialize)] +pub struct AeadTestSuite { + #[serde(flatten)] + pub suite: wycheproof::Suite, + #[serde(rename = "testGroups")] + pub test_groups: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct AeadTestGroup { + #[serde(flatten)] + pub group: wycheproof::Group, + #[serde(rename = "ivSize")] + pub iv_size: u32, + #[serde(rename = "keySize")] + pub key_size: u32, + #[serde(rename = "tagSize")] + pub tag_size: u32, + pub tests: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct AeadTestCase { + #[serde(flatten)] + pub case: wycheproof::Case, + #[serde(with = "hex_string")] + pub aad: Vec, + #[serde(with = "hex_string")] + pub ct: Vec, + #[serde(with = "hex_string")] + pub iv: Vec, + #[serde(with = "hex_string")] + pub key: Vec, + #[serde(with = "hex_string")] + pub msg: Vec, + #[serde(with = "hex_string")] + pub tag: Vec, +} + +const AES_GCM_IV_SIZE: u32 = 12; + +#[test] +fn test_aes_gcm() { + let filename = "testvectors/aes_gcm_test.json"; + println!("wycheproof file '{}'", filename); + let bytes = wycheproof::wycheproof_data(filename); + let suite: AeadTestSuite = serde_json::from_slice(&bytes).unwrap(); + assert_eq!("AES-GCM", suite.suite.algorithm); + + for g in &suite.test_groups { + let key_size = g.key_size / 8; + if key_size != 16 && key_size != 32 { + println!(" skipping tests for key_size={}", g.key_size); + continue; + } + if g.iv_size != AES_GCM_IV_SIZE * 8 { + println!(" skipping tests for iv_size={}", g.iv_size); + continue; + } + + for tc in &g.tests { + println!( + " case {} [{}] {}", + tc.case.case_id, tc.case.result, tc.case.comment + ); + let mut combined_ct = Vec::new(); + combined_ct.extend_from_slice(&tc.ct); + combined_ct.extend_from_slice(&tc.tag); + + // create cipher and do decryption + let payload = Payload { + msg: &combined_ct, + aad: &tc.aad, + }; + let result = match key_size { + 16 => { + let cipher = aes_gcm::Aes128Gcm::new(GenericArray::from_slice(&tc.key)); + cipher.decrypt(GenericArray::from_slice(&tc.iv), payload) + } + 32 => { + let cipher = aes_gcm::Aes256Gcm::new(GenericArray::from_slice(&tc.key)); + cipher.decrypt(GenericArray::from_slice(&tc.iv), payload) + } + _ => unreachable!(), + }; + + match result { + Err(e) => { + assert_eq!( + tc.case.result, + wycheproof::CaseResult::Invalid, + "unexpected error in test case {}: {}", + tc.case.case_id, + e + ); + } + Ok(decrypted) => { + assert_eq!( + tc.case.result, + wycheproof::CaseResult::Valid, + "decrypted invalid test case {}", + tc.case.case_id + ); + assert_eq!( + decrypted, tc.msg, + "incorrect decryption in test case {}", + tc.case.case_id, + ); + } + } + } + } +} + +const CHA_CHA20_KEY_SIZE: u32 = 32; +const CHA_CHA20_NONCE_SIZE: u32 = 12; + +#[test] +fn test_cha_cha20_poly1305() { + let filename = "testvectors/chacha20_poly1305_test.json"; + println!("wycheproof file '{}'", filename); + let bytes = wycheproof::wycheproof_data(filename); + let suite: AeadTestSuite = serde_json::from_slice(&bytes).unwrap(); + + for g in &suite.test_groups { + if (g.key_size / 8) != CHA_CHA20_KEY_SIZE { + println!(" skipping tests for key_size={}", g.key_size); + continue; + } + if (g.iv_size / 8) != CHA_CHA20_NONCE_SIZE { + println!(" skipping tests for iv_size={}", g.iv_size); + continue; + } + for tc in &g.tests { + println!( + " case {} [{}] {}", + tc.case.case_id, tc.case.result, tc.case.comment + ); + let mut combined_ct = Vec::new(); + combined_ct.extend_from_slice(&tc.ct); + combined_ct.extend_from_slice(&tc.tag); + + let key = chacha20poly1305::Key::clone_from_slice(&tc.key); + let cipher = chacha20poly1305::ChaCha20Poly1305::new(&key); + + let iv = chacha20poly1305::Nonce::from_slice(&tc.iv); + let result = cipher.decrypt( + iv, + Payload { + msg: &combined_ct, + aad: &tc.aad, + }, + ); + + match result { + Err(e) => { + assert_ne!( + tc.case.result, + wycheproof::CaseResult::Valid, + "#{}, unexpected error: {}", + tc.case.case_id, + e + ); + } + Ok(decrypted) => { + assert_ne!( + tc.case.result, + wycheproof::CaseResult::Invalid, + "#{}, decrypted invalid", + tc.case.case_id + ); + assert_eq!( + decrypted, tc.msg, + "#{}, incorrect decryption", + tc.case.case_id + ); + } + } + } + } +} diff --git a/wycheproof/tests/aes_cmac_test.rs b/wycheproof/tests/aes_cmac_test.rs new file mode 100644 index 00000000..ed58189c --- /dev/null +++ b/wycheproof/tests/aes_cmac_test.rs @@ -0,0 +1,104 @@ +use aes::{Aes128, Aes192, Aes256}; +use cmac::{Cmac, Mac, NewMac}; +use serde::Deserialize; +use std::cmp::min; + +#[derive(Debug, Deserialize)] +pub struct TestSuite { + #[serde(flatten)] + pub suite: wycheproof::Suite, + #[serde(rename = "testGroups")] + pub test_groups: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct TestGroup { + #[serde(flatten)] + pub group: wycheproof::Group, + #[serde(rename = "keySize")] + pub key_size: u32, + #[serde(rename = "tagSize")] + pub tag_size: u32, + pub tests: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct TestCase { + #[serde(flatten)] + pub case: wycheproof::Case, + #[serde(with = "wycheproof::hex_string")] + pub key: Vec, + #[serde(with = "wycheproof::hex_string")] + pub msg: Vec, + #[serde(with = "wycheproof::hex_string")] + pub tag: Vec, +} + +#[test] +fn test_aes_cmac() { + let filename = "testvectors/aes_cmac_test.json"; + println!("wycheproof file '{}'", filename); + let bytes = wycheproof::wycheproof_data(filename); + let suite: TestSuite = serde_json::from_slice(&bytes).unwrap(); + + for g in &suite.test_groups { + println!( + " key info: key_size={}, tag_size={}", + g.key_size, g.tag_size + ); + for tc in &g.tests { + println!( + " case {} [{}] {}", + tc.case.case_id, tc.case.result, tc.case.comment + ); + assert_eq!(tc.key.len() * 8, g.key_size as usize); + assert_eq!( + g.tag_size % 8, + 0, + "Requested tag size for test case {} ({}) is not a multiple of 8, but {}", + tc.case.case_id, + tc.case.comment, + g.tag_size + ); + let output_length = g.tag_size as usize / 8; // in bytes + + let mac = match tc.key.len() { + 16 => { + let mut mac = Cmac::::new_varkey(&tc.key).unwrap(); + mac.update(&tc.msg); + let result = mac.finalize_reset().into_bytes(); + result[..min(result.len(), output_length)].to_vec() + } + 24 => { + let mut mac = Cmac::::new_varkey(&tc.key).unwrap(); + mac.update(&tc.msg); + let result = mac.finalize_reset().into_bytes(); + result[..min(result.len(), output_length)].to_vec() + } + 32 => { + let mut mac = Cmac::::new_varkey(&tc.key).unwrap(); + mac.update(&tc.msg); + let result = mac.finalize_reset().into_bytes(); + result[..min(result.len(), output_length)].to_vec() + } + _ => { + assert_eq!(tc.case.result, wycheproof::CaseResult::Invalid); + continue; + } + }; + if tc.case.result == wycheproof::CaseResult::Valid { + assert_eq!( + mac, tc.tag, + "Could not verify MAC for test case {} ({})", + tc.case.case_id, tc.case.comment + ); + } else { + assert_ne!( + mac, tc.tag, + "Verified invalid MAC for test case {} ({})", + tc.case.case_id, tc.case.comment + ); + } + } + } +} diff --git a/wycheproof/tests/aes_siv_test.rs b/wycheproof/tests/aes_siv_test.rs new file mode 100644 index 00000000..1e3cb814 --- /dev/null +++ b/wycheproof/tests/aes_siv_test.rs @@ -0,0 +1,95 @@ +use aes_siv::{aead::generic_array::GenericArray, siv::Aes256Siv}; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct TestSuite { + #[serde(flatten)] + pub suite: wycheproof::Suite, + #[serde(rename = "testGroups")] + pub test_groups: Vec, +} + +#[derive(Debug, Deserialize)] +struct TestGroup { + #[serde(flatten)] + pub group: wycheproof::Group, + #[serde(rename = "keySize")] + pub key_size: u32, + pub tests: Vec, +} + +#[derive(Debug, Deserialize)] +struct TestCase { + #[serde(flatten)] + pub case: wycheproof::Case, + #[serde(with = "wycheproof::hex_string")] + pub key: Vec, + #[serde(with = "wycheproof::hex_string")] + pub aad: Vec, + #[serde(with = "wycheproof::hex_string")] + pub msg: Vec, + #[serde(with = "wycheproof::hex_string")] + pub ct: Vec, +} + +const AES_SIV_KEY_SIZE: usize = 64; // 512 bits + +#[test] +fn test_aes_siv() { + let filename = "testvectors/aes_siv_cmac_test.json"; + println!("wycheproof file '{}'", filename); + let bytes = wycheproof::wycheproof_data(filename); + let suite: TestSuite = serde_json::from_slice(&bytes).unwrap(); + + for g in &suite.test_groups { + if (g.key_size / 8) as usize != AES_SIV_KEY_SIZE { + println!(" skipping tests for key_size={}", g.key_size); + continue; + } + println!(" key info: key_size={}", g.key_size); + for tc in &g.tests { + println!( + " case {} [{}] {}", + tc.case.case_id, tc.case.result, tc.case.comment + ); + + let mut cipher = Aes256Siv::new(*GenericArray::from_slice(&tc.key)); + let got_ct = cipher.encrypt(&[&tc.aad], &tc.msg).unwrap(); + match &tc.case.result { + wycheproof::CaseResult::Valid => { + assert_eq!(got_ct, tc.ct, "{}: incorrect encryption", tc.case.case_id); + } + wycheproof::CaseResult::Invalid => { + assert_ne!(got_ct, tc.ct, "{}: invalid encryption", tc.case.case_id); + } + r => panic!("unknown result type {}", r), + } + + let pt_result = cipher.decrypt(&[&tc.aad], &tc.ct); + match tc.case.result { + wycheproof::CaseResult::Valid => { + assert!( + pt_result.is_ok(), + "{}: unexpected decryption error: {:?}", + tc.case.case_id, + pt_result + ); + assert_eq!( + tc.msg, + pt_result.unwrap(), + "{}: incorrect decryption", + tc.case.case_id + ); + } + wycheproof::CaseResult::Invalid => { + assert!( + pt_result.is_err(), + "{}: decryption error expected", + tc.case.case_id + ); + } + _ => unreachable!(), + } + } + } +} diff --git a/wycheproof/tests/ecdsa_test.rs b/wycheproof/tests/ecdsa_test.rs new file mode 100644 index 00000000..d1681e09 --- /dev/null +++ b/wycheproof/tests/ecdsa_test.rs @@ -0,0 +1,199 @@ +use elliptic_curve::sec1::EncodedPoint; +use generic_array::typenum::Unsigned; +use p256::ecdsa::{signature::Verifier, Signature}; +use serde::Deserialize; +use signature::Signature as _; +use std::collections::HashSet; +use wycheproof::hex_string; + +#[derive(Debug, Deserialize)] +struct TestSuite { + #[serde(flatten)] + pub suite: wycheproof::Suite, + #[serde(rename = "testGroups")] + pub test_groups: Vec, +} + +#[derive(Debug, Deserialize)] +struct TestGroup { + #[serde(flatten)] + pub group: wycheproof::Group, + #[serde(rename = "keyDer")] + pub key_der: String, + #[serde(rename = "keyPem")] + pub key_pem: String, + pub sha: String, + pub key: TestKey, + pub tests: Vec, +} + +#[derive(Debug, Deserialize)] +struct TestKey { + curve: String, + #[serde(rename = "type")] + key_type: String, + #[serde(with = "hex_string")] + wx: Vec, + #[serde(with = "hex_string")] + wy: Vec, +} + +#[derive(Debug, Deserialize)] +struct TestCase { + #[serde(flatten)] + pub case: wycheproof::Case, + #[serde(with = "hex_string")] + pub msg: Vec, + #[serde(with = "hex_string")] + pub sig: Vec, +} + +#[derive(Debug)] +enum SigEncoding { + Der, + IeeeP1363, +} + +#[test] +fn test_ecdsa() { + struct TestVector { + filename: &'static str, + encoding: SigEncoding, + skip_tests: HashSet, + } + let vectors = vec![ + TestVector { + filename: "ecdsa_test.json", + encoding: SigEncoding::Der, + // Test 4 uses ASN.1 long form encoding of sequence length, which is not DER. + // TODO: remove when upstream fix released + // 4: non-minimal SEQUENCE length + // 18, 19, 39: SEQUENCE too short + skip_tests: vec![4, 18, 19, 39].into_iter().collect(), + }, + TestVector { + filename: "ecdsa_secp256r1_sha256_p1363_test.json", + encoding: SigEncoding::IeeeP1363, + // TODO: remove when upstream fix released + // 119: SEQUENCE too short + skip_tests: vec![119].into_iter().collect(), + }, + /* TODO: more ECDSA curves + TestVector { + filename: "ecdsa_secp384r1_sha512_p1363_test.json", + encoding: SigEncoding::IeeeP1363, + skip_tests: HashSet::new(), + }, + TestVector { + filename: "ecdsa_secp521r1_sha512_p1363_test.json", + encoding: SigEncoding::IeeeP1363, + skip_tests: HashSet::new(), + }, + */ + ]; + for v in vectors { + wycheproof_test(v.filename, v.encoding, v.skip_tests) + } +} + +fn wycheproof_test(filename: &str, encoding: SigEncoding, skip_tests: HashSet) { + println!( + "wycheproof file 'testvectors/{}', encoding '{:?}'", + filename, encoding + ); + let bytes = wycheproof::wycheproof_data(&format!("testvectors/{}", filename)); + let suite: TestSuite = serde_json::from_slice(&bytes).unwrap(); + let mut skipped_hashes = HashSet::new(); + let mut skipped_curves = HashSet::new(); + for g in &suite.test_groups { + if g.key.curve != "secp256r1" { + if !skipped_curves.contains(&g.key.curve) { + println!("skipping tests for unsupported curve {}", g.key.curve); + skipped_curves.insert(g.key.curve.clone()); + } + continue; + } + if g.sha != "SHA-256" { + if !skipped_hashes.contains(&g.sha) { + println!("skipping tests for unsupported hash {}", g.sha); + skipped_hashes.insert(g.sha.clone()); + } + continue; + } + println!( + " key info: {}, {}, {:?}, {}, {}", + g.sha, + g.key.curve, + encoding, + hex::encode(&g.key.wx), + hex::encode(&g.key.wy), + ); + let x = element_from_padded_slice::(&g.key.wx); + let y = element_from_padded_slice::(&g.key.wy); + let pt = EncodedPoint::from_affine_coordinates(&x, &y, /* compress= */ false); + let verify_key = p256::ecdsa::VerifyKey::from_encoded_point(&pt).unwrap(); + + for tc in &g.tests { + if skip_tests.contains(&tc.case.case_id) { + println!( + " SKIP case {} [{}] {}: sig={}", + tc.case.case_id, + tc.case.result, + tc.case.comment, + hex::encode(&tc.sig) + ); + continue; + } + println!( + " case {} [{}] {}", + tc.case.case_id, tc.case.result, tc.case.comment + ); + let sig_result = match encoding { + SigEncoding::Der => Signature::from_asn1(&tc.sig), + SigEncoding::IeeeP1363 => Signature::from_bytes(&tc.sig), + }; + let signature = match sig_result { + Ok(s) => s, + Err(_) => { + assert_ne!(tc.case.result, wycheproof::CaseResult::Valid); + continue; + } + }; + + let result = verify_key.verify(&tc.msg, &signature); + match tc.case.result { + wycheproof::CaseResult::Valid => assert!( + result.is_ok(), + "failed in test case {} with result {:?}", + tc.case.case_id, + result + ), + wycheproof::CaseResult::Invalid => assert!( + result.is_err(), + "unexpected success in test case {}", + tc.case.case_id + ), + wycheproof::CaseResult::Acceptable => {} + } + } + } +} + +// Build a field element but allow for too-short input (left pad with zeros) +// or too-long input (check excess leftmost bytes are zeros). +fn element_from_padded_slice( + data: &[u8], +) -> elliptic_curve::FieldBytes { + let point_len = C::FieldSize::to_usize(); + if data.len() >= point_len { + let offset = data.len() - point_len; + for v in data.iter().take(offset) { + assert_eq!(*v, 0, "EcdsaVerifier: point too large"); + } + elliptic_curve::FieldBytes::::from_slice(&data[offset..]).clone() + } else { + let mut data_copy = vec![0; point_len]; + data_copy[(point_len - data.len())..].copy_from_slice(data); + elliptic_curve::FieldBytes::::clone_from_slice(&data_copy) + } +} diff --git a/wycheproof/tests/ed25519_test.rs b/wycheproof/tests/ed25519_test.rs new file mode 100644 index 00000000..070652d6 --- /dev/null +++ b/wycheproof/tests/ed25519_test.rs @@ -0,0 +1,108 @@ +use ed25519_dalek::Signer; +use serde::Deserialize; +use signature::{Signature, Verifier}; + +#[derive(Debug, Deserialize)] +struct TestSuiteEd25519 { + #[serde(flatten)] + pub suite: wycheproof::Suite, + #[serde(rename = "testGroups")] + pub test_groups: Vec, +} + +#[derive(Debug, Deserialize)] +struct TestGroupEd25519 { + #[serde(flatten)] + pub group: wycheproof::Group, + #[serde(rename = "keyDer")] + pub key_der: String, + #[serde(rename = "keyPem")] + pub key_pem: String, + pub key: TestKeyEd25519, + pub tests: Vec, +} + +#[derive(Debug, Deserialize)] +struct TestKeyEd25519 { + #[serde(with = "wycheproof::hex_string")] + sk: Vec, + #[serde(with = "wycheproof::hex_string")] + pk: Vec, +} + +#[derive(Debug, Deserialize)] +struct TestCaseEd25519 { + #[serde(flatten)] + pub case: wycheproof::Case, + #[serde(with = "wycheproof::hex_string")] + pub msg: Vec, + #[serde(with = "wycheproof::hex_string")] + pub sig: Vec, +} + +#[test] +fn test_ed25519() { + let filename = "testvectors/eddsa_test.json"; + println!("wycheproof file '{}'", filename); + let bytes = wycheproof::wycheproof_data(filename); + let suite: TestSuiteEd25519 = serde_json::from_slice(&bytes).unwrap(); + for g in &suite.test_groups { + println!( + " key info: sk={}, pk={}", + hex::encode(&g.key.sk), + hex::encode(&g.key.pk) + ); + + let secret_key = ed25519_dalek::SecretKey::from_bytes(&g.key.sk).unwrap(); + let public_key: ed25519_dalek::PublicKey = (&secret_key).into(); + let keypair = ed25519_dalek::Keypair { + secret: secret_key, + public: public_key, + }; + + let public_key = ed25519_dalek::PublicKey::from_bytes(&g.key.pk).unwrap(); + assert_eq!(public_key, keypair.public); + + for tc in &g.tests { + println!( + " case {} [{}] {}", + tc.case.case_id, tc.case.result, tc.case.comment + ); + + let got = keypair.sign(&tc.msg).as_bytes().to_vec(); + if tc.case.result == wycheproof::CaseResult::Valid { + // Ed25519 is deterministic. + assert_eq!( + tc.sig, + got, + "sign failed in test case {}: invalid signature generated {}", + tc.case.case_id, + hex::encode(&got) + ); + } + + let s = match ed25519_dalek::Signature::from_bytes(&tc.sig) { + Ok(s) => s, + Err(_) => { + assert_eq!(tc.case.result, wycheproof::CaseResult::Invalid); + continue; + } + }; + let result = public_key.verify(&tc.msg, &s); + match tc.case.result { + wycheproof::CaseResult::Valid => assert!( + result.is_ok(), + "verify failed in test case {}: valid signature rejected with {:?}", + tc.case.case_id, + result + ), + wycheproof::CaseResult::Invalid => assert!( + result.is_err(), + "verify failed in test case {}: invalid signature is accepted", + tc.case.case_id + ), + wycheproof::CaseResult::Acceptable => unimplemented!(), + } + } + } +} diff --git a/wycheproof/tests/hkdf_test.rs b/wycheproof/tests/hkdf_test.rs new file mode 100644 index 00000000..7f21acaf --- /dev/null +++ b/wycheproof/tests/hkdf_test.rs @@ -0,0 +1,91 @@ +use hkdf::Hkdf; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct HkdfTestSuite { + #[serde(flatten)] + pub suite: wycheproof::Suite, + #[serde(rename = "testGroups")] + pub test_groups: Vec, +} + +#[derive(Debug, Deserialize)] +struct HkdfTestGroup { + #[serde(flatten)] + pub group: wycheproof::Group, + #[serde(rename = "keySize")] + pub key_size: u32, + pub tests: Vec, +} + +#[derive(Debug, Deserialize)] +struct HkdfTestCase { + #[serde(flatten)] + pub case: wycheproof::Case, + #[serde(with = "wycheproof::hex_string")] + pub ikm: Vec, + #[serde(with = "wycheproof::hex_string")] + pub salt: Vec, + #[serde(with = "wycheproof::hex_string")] + pub info: Vec, + pub size: usize, + #[serde(with = "wycheproof::hex_string")] + pub okm: Vec, +} + +#[test] +fn test_hkdf() { + test_hkdf_with::("sha1"); + test_hkdf_with::("sha256"); + test_hkdf_with::("sha384"); + test_hkdf_with::("sha512"); +} + +fn test_hkdf_with(hash: &str) +where + D: digest::Update + digest::BlockInput + digest::FixedOutput + digest::Reset + Default + Clone, + D::BlockSize: generic_array::ArrayLength, + D::OutputSize: generic_array::ArrayLength, +{ + let filename = format!("testvectors/hkdf_{}_test.json", hash); + println!("wycheproof file '{}' hash {}", filename, hash); + let bytes = wycheproof::wycheproof_data(&filename); + let suite: HkdfTestSuite = serde_json::from_slice(&bytes).unwrap(); + + for g in &suite.test_groups { + println!(" key info: key_size={}", g.key_size); + for tc in &g.tests { + println!( + " case {} [{}] {}", + tc.case.case_id, tc.case.result, tc.case.comment + ); + assert_eq!(tc.ikm.len() * 8, g.key_size as usize); + let valid = tc.case.result == wycheproof::CaseResult::Valid; + + let prk = Hkdf::::new(Some(&tc.salt), &tc.ikm); + + let mut okm = vec![0; tc.size]; + if prk.expand(&tc.info, &mut okm).is_err() { + assert!( + !valid, + "Could not compute HKDF {:?} PRF for test case {} ({})", + hash, tc.case.case_id, tc.case.comment + ); + continue; + } + if valid { + assert_eq!( + okm, tc.okm, + "Compute HKDF {:?} PRF and expected for test case {} ({}) do not match", + hash, tc.case.case_id, tc.case.comment + ); + } else { + assert_ne!( + okm, tc.okm, + "Compute HKDF {:?} PRF and invalid expected for test case {} ({}) match", + hash, tc.case.case_id, tc.case.comment + ); + } + } + } +} diff --git a/wycheproof/tests/prf_test.rs b/wycheproof/tests/prf_test.rs new file mode 100644 index 00000000..0268ea29 --- /dev/null +++ b/wycheproof/tests/prf_test.rs @@ -0,0 +1,161 @@ +use aes::{Aes128, Aes192, Aes256}; +use cmac::{Cmac, Mac, NewMac}; +use hmac::Hmac; +use serde::Deserialize; +use std::cmp::min; + +#[derive(Debug, Deserialize)] +pub struct PrfTestSuite { + #[serde(flatten)] + pub suite: wycheproof::Suite, + #[serde(rename = "testGroups")] + pub test_groups: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct PrfTestGroup { + #[serde(flatten)] + pub group: wycheproof::Group, + #[serde(rename = "keySize")] + pub key_size: u32, + #[serde(rename = "tagSize")] + pub tag_size: u32, + pub tests: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct PrfTestCase { + #[serde(flatten)] + pub case: wycheproof::Case, + #[serde(with = "wycheproof::hex_string")] + pub key: Vec, + #[serde(with = "wycheproof::hex_string")] + pub msg: Vec, + #[serde(with = "wycheproof::hex_string")] + pub tag: Vec, +} + +#[test] +fn test_aes_cmac() { + let filename = "testvectors/aes_cmac_test.json"; + println!("wycheproof file '{}'", filename); + let bytes = wycheproof::wycheproof_data(filename); + let suite: PrfTestSuite = serde_json::from_slice(&bytes).unwrap(); + + for g in &suite.test_groups { + println!(" key info: key_size={}", g.key_size); + for tc in &g.tests { + println!( + " case {} [{}] {}", + tc.case.case_id, tc.case.result, tc.case.comment + ); + assert_eq!(tc.key.len() * 8, g.key_size as usize); + assert_eq!( + g.tag_size % 8, + 0, + "Requested tag size for test case {} ({}) is not a multiple of 8, but {}", + tc.case.case_id, + tc.case.comment, + g.tag_size + ); + let output_length = (g.tag_size / 8) as usize; + let valid = tc.case.result == wycheproof::CaseResult::Valid; + + let result = match tc.key.len() { + 16 => { + let mut mac = Cmac::::new_varkey(&tc.key).unwrap(); + mac.update(&tc.msg); + let result = mac.finalize_reset().into_bytes(); + result[..min(result.len(), output_length)].to_vec() + } + 24 => { + let mut mac = Cmac::::new_varkey(&tc.key).unwrap(); + mac.update(&tc.msg); + let result = mac.finalize_reset().into_bytes(); + result[..min(result.len(), output_length)].to_vec() + } + 32 => { + let mut mac = Cmac::::new_varkey(&tc.key).unwrap(); + mac.update(&tc.msg); + let result = mac.finalize_reset().into_bytes(); + result[..min(result.len(), output_length)].to_vec() + } + _ => { + assert!(!valid); + continue; + } + }; + + if valid { + assert_eq!( + result, tc.tag, + "Computed AES-CMAC and expected for test case {} ({}) do not match", + tc.case.case_id, tc.case.comment + ); + } else { + assert_ne!( + result, tc.tag, + "Computed AES-CMAC and invalid expected for test case {} ({}) match", + tc.case.case_id, tc.case.comment + ) + } + } + } +} + +#[test] +fn test_hmac() { + test_hmac_with::>("sha1"); + test_hmac_with::>("sha256"); + test_hmac_with::>("sha384"); + test_hmac_with::>("sha512"); +} + +fn test_hmac_with(hash: &str) +where + T: hmac::NewMac + hmac::Mac, +{ + let filename = format!("testvectors/hmac_{}_test.json", hash); + println!("wycheproof file '{}' hash {}", filename, hash); + let bytes = wycheproof::wycheproof_data(&filename); + let suite: PrfTestSuite = serde_json::from_slice(&bytes).unwrap(); + + for g in &suite.test_groups { + for tc in &g.tests { + println!( + " case {} [{}] {}", + tc.case.case_id, tc.case.result, tc.case.comment + ); + assert_eq!(tc.key.len() * 8, g.key_size as usize); + assert_eq!( + g.tag_size % 8, + 0, + "Requested tag size for test case {} ({}) is not a multiple of 8, but {}", + tc.case.case_id, + tc.case.comment, + g.tag_size + ); + let output_length = (g.tag_size / 8) as usize; + let valid = tc.case.result == wycheproof::CaseResult::Valid; + + let mut mac = ::new_varkey(&tc.key).unwrap(); + mac.update(&tc.msg); + let result = mac.finalize_reset().into_bytes(); + let res = result[..min(result.len(), output_length)].to_vec(); + + if valid { + assert_eq!( + res, tc.tag, + "Computed HMAC and expected for test case {} ({}) do not match", + tc.case.case_id, tc.case.comment + ); + } else { + assert_ne!( + res, tc.tag, + "Computed HMAC and invalid expected for test case {} ({}) match", + tc.case.case_id, tc.case.comment + ) + } + } + } +} From 8c7e57cac8f14ad4a5a5f9daec8d97fb42441cb7 Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Thu, 22 Oct 2020 16:51:54 +0100 Subject: [PATCH 2/2] Add workflow for wycheproof --- .github/workflows/wycheproof.yml | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/wycheproof.yml diff --git a/.github/workflows/wycheproof.yml b/.github/workflows/wycheproof.yml new file mode 100644 index 00000000..9e78e4ab --- /dev/null +++ b/.github/workflows/wycheproof.yml @@ -0,0 +1,50 @@ +name: wycheproof + +on: + pull_request: + paths: + - "wycheproof/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: wycheproof + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + target: + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + - run: cargo build --release --target ${{ matrix.target }} + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - run: cargo test --release -- --nocapture