diff --git a/Cargo.lock b/Cargo.lock index 2643e4a..a57cdc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,47 +1,48 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aead" -version = "0.5.2" +version = "0.6.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +checksum = "67a578e7d4edaef88aeb9cdd81556f4a62266ce26601317c006a79e8bc58b5af" dependencies = [ - "crypto-common", - "generic-array", + "crypto-common 0.2.0-rc.9", + "inout", ] [[package]] name = "aes" -version = "0.8.4" +version = "0.9.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +checksum = "fd9e1c818b25efb32214df89b0ec22f01aa397aaeb718d1022bf0635a3bfd1a8" dependencies = [ "cfg-if", "cipher", "cpufeatures", + "zeroize", ] [[package]] name = "aes-gcm" -version = "0.10.3" +version = "0.11.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +checksum = "7f5c07f414d7dc0755870f84c7900425360288d24e0eae4836f9dee19a30fa5f" dependencies = [ "aead", - "aes", "cipher", "ctr", "ghash", "subtle", + "zeroize", ] [[package]] name = "autocfg" -version = "1.0.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base16ct" @@ -49,11 +50,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" [[package]] name = "block-buffer" @@ -65,28 +72,53 @@ dependencies = [ ] [[package]] -name = "byteorder" -version = "1.5.0" +name = "block-buffer" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "96eb4cdd6cf1b31d671e9efe75c5d1ec614776856cefbe109ca373554a6d514f" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.0.90" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "ccm" +version = "0.6.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ad0c3a709bfe243b5585554f3d697dc015d04ebad7598fd4d650bc2d7591a3" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" -version = "0.9.1" +version = "0.10.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +checksum = "f895fb33c1ad22da4bc79d37c0bddff8aee2ba4575705345eb73b8ffbc386074" dependencies = [ "cfg-if", "cipher", @@ -95,39 +127,50 @@ dependencies = [ [[package]] name = "chacha20poly1305" -version = "0.10.1" +version = "0.11.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +checksum = "c662d31454533832974f2b2b3fcbd552ed3cde94c95e614a5039d297dd97076f" dependencies = [ "aead", "chacha20", "cipher", "poly1305", - "zeroize", ] [[package]] name = "cipher" -version = "0.4.4" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +checksum = "98d708bac5451350d56398433b19a7889022fa9187df1a769c0edbc3b2c03167" dependencies = [ - "crypto-common", + "block-buffer 0.11.0", + "crypto-common 0.2.0-rc.9", "inout", - "zeroize", ] +[[package]] +name = "cmov" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11ed919bd3bae4af5ab56372b627dfc32622aba6cec36906e8ab46746037c9d" + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e" + [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -139,7 +182,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.7.0-rc.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9e36ac79ac44866b74e08a0b4925f97b984e3fff17680d2c6fbce8317ab0f6" +dependencies = [ + "ctutils", + "getrandom 0.4.0-rc.0", + "hybrid-array", + "num-traits", + "rand_core 0.10.0-rc-3", + "serdect", "subtle", "zeroize", ] @@ -154,27 +213,56 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.0-rc.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b8986f836d4aeb30ccf4c9d3bd562fd716074cfd7fc4a2948359fbd21ed809" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "crypto-primes" +version = "0.7.0-pre.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0b07a7a616370e8b6efca0c6a25e5f4c6d02fde11f3d570e4af64d8ed7e2e9" +dependencies = [ + "crypto-bigint 0.7.0-rc.15", + "libm", + "rand_core 0.10.0-rc-3", +] + [[package]] name = "ctr" -version = "0.9.2" +version = "0.10.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "3d0ec605a95e78815a4c4b8040217d56d5a1ab37043851ee9e7e65b89afa00e3" dependencies = [ "cipher", ] +[[package]] +name = "ctutils" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c67c81499f542d1dd38c6a2a2fe825f4dd4bca5162965dd2eea0c8119873d3c" +dependencies = [ + "cmov", + "subtle", +] + [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "5.0.0-pre.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "6ae8b2fe5e4995d7fd08a7604e794dc569a65ed19659f5939d529813ed816d38" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.11.0-rc.5", "fiat-crypto", - "platforms", "rustc_version", "subtle", "zeroize", @@ -193,24 +281,60 @@ dependencies = [ [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "der_derive", + "flagset", + "pem-rfc7468 0.7.0", + "zeroize", +] + +[[package]] +name = "der" +version = "0.8.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "02c1d73e9668ea6b6a28172aa55f3ebec38507131ce179051c8033b5c6037653" dependencies = [ - "const-oid", - "pem-rfc7468", + "const-oid 0.10.1", + "pem-rfc7468 1.0.0", "zeroize", ] +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.6", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.0-rc.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebf9423bafb058e4142194330c52273c343f8a5beb7176d052f0e73b17dd35b9" +dependencies = [ + "block-buffer 0.11.0", + "const-oid 0.10.1", + "crypto-common 0.2.0-rc.9", "subtle", ] @@ -220,80 +344,173 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", + "der 0.7.10", + "digest 0.10.7", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.3", +] + +[[package]] +name = "ecdsa" +version = "0.17.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "569a1f3377df19ab839b2811061095ff7d9fb7ea3c0e500b7a4724343cf6ee3d" +dependencies = [ + "der 0.8.0-rc.10", + "digest 0.11.0-rc.5", + "elliptic-curve 0.14.0-rc.20", + "rfc6979 0.5.0-rc.3", + "signature 3.0.0-rc.6", + "spki 0.8.0-rc.4", + "zeroize", ] [[package]] name = "ed25519" -version = "2.2.3" +version = "3.0.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +checksum = "594435fe09e345ee388e4e8422072ff7dfeca8729389fbd997b3f5504c44cd47" dependencies = [ - "pkcs8", - "signature", + "pkcs8 0.11.0-rc.8", + "signature 3.0.0-rc.6", ] [[package]] name = "ed25519-dalek" -version = "2.1.1" +version = "3.0.0-pre.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "a4b9f613e0c236c699bf70d39f825594d9b03aadfd8dd856ea40685f782a4ef2" dependencies = [ "curve25519-dalek", "ed25519", "serde", - "sha2", + "sha2 0.11.0-rc.3", + "signature 3.0.0-rc.6", "subtle", "zeroize", ] +[[package]] +name = "ed448" +version = "0.5.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca586ed223f9d5ecdfe636576d2d284fd26ddfbfb8b5289b527c736293c7926" +dependencies = [ + "pkcs8 0.11.0-rc.8", + "signature 3.0.0-rc.6", +] + +[[package]] +name = "ed448-goldilocks" +version = "0.14.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6a621fdf3703a16cc2bfbe144b8ef52e182d70bb63b9a6da70c0a0c4296da4" +dependencies = [ + "ed448", + "elliptic-curve 0.14.0-rc.20", + "hash2curve", + "rand_core 0.10.0-rc-3", + "serdect", + "sha3", + "signature 3.0.0-rc.6", + "subtle", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "elliptic-curve" version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct", - "crypto-bigint", - "digest", + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest 0.10.7", "ff", "generic-array", "group", + "pem-rfc7468 0.7.0", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sec1 0.7.3", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.14.0-rc.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4874d0de0bf58704a6917f26154afcdd49ebc11cae10b6d53950217aee9408" +dependencies = [ + "base16ct 1.0.0", + "crypto-bigint 0.7.0-rc.15", + "digest 0.11.0-rc.5", + "getrandom 0.4.0-rc.0", "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core", - "sec1", + "hybrid-array", + "pem-rfc7468 1.0.0", + "pkcs8 0.11.0-rc.8", + "rand_core 0.10.0-rc-3", + "rustcrypto-ff", + "rustcrypto-group", + "sec1 0.8.0-rc.11", "subtle", "zeroize", ] +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] [[package]] name = "fiat-crypto" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "find-msvc-tools" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + +[[package]] +name = "flagset" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -302,22 +519,46 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b99f0d993a2b9b97b9a201193aa8ad21305cde06a3be9a7e1f8f4201e5cc27e" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core 0.10.0-rc-3", + "wasip2", +] + [[package]] name = "ghash" -version = "0.5.1" +version = "0.6.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +checksum = "333de57ed9494a40df4bbb866752b100819dde0d18f2264c48f5a08a85fe673d" dependencies = [ - "opaque-debug", "polyval", ] @@ -328,17 +569,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] +[[package]] +name = "hash2curve" +version = "0.14.0-rc.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc0e7662142a8a33aabae672892e6f25fdf1e7bea701387c119c0043a61d539" +dependencies = [ + "digest 0.11.0-rc.5", + "elliptic-curve 0.14.0-rc.20", +] + [[package]] name = "hkdf" -version = "0.12.4" +version = "0.13.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "cfbb4225acf2b5cc4e12d384672cd6d1f0cb980ff5859ffcf144db25b593a24d" dependencies = [ - "hmac", + "hmac 0.13.0-rc.3", ] [[package]] @@ -347,52 +598,89 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c597ac7d6cc8143e30e83ef70915e7f883b18d8bec2e2b2bce47f5bbb06d57" +dependencies = [ + "digest 0.11.0-rc.5", +] + +[[package]] +name = "hybrid-array" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" +dependencies = [ + "subtle", + "typenum", + "zeroize", ] [[package]] name = "inout" -version = "0.1.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" dependencies = [ - "generic-array", + "hybrid-array", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "keccak" +version = "0.2.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d546793a04a1d3049bd192856f804cfe96356e2cf36b54b4e575155babe9f41" +dependencies = [ + "cpufeatures", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] name = "libc" -version = "0.2.154" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "log" -version = "0.4.21" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -414,9 +702,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -425,9 +713,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -435,45 +723,62 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "opaque-debug" -version = "0.3.1" +name = "p256" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder 0.13.6", + "sha2 0.10.9", +] [[package]] name = "p256" -version = "0.13.2" +version = "0.14.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +checksum = "4caab26e75ab3d0790a0f29df73f006308ada2e1fbbfcbab03e92346adc43dd9" dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", + "ecdsa 0.17.0-rc.11", + "elliptic-curve 0.14.0-rc.20", + "primefield", + "primeorder 0.14.0-rc.3", + "sha2 0.11.0-rc.3", ] [[package]] name = "p384" -version = "0.13.0" +version = "0.14.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +checksum = "30732b26c446549117425e4e905456494ff6c87da40216fd6c71ccc7c3976080" dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", + "ecdsa 0.17.0-rc.11", + "elliptic-curve 0.14.0-rc.20", + "fiat-crypto", + "primefield", + "primeorder 0.14.0-rc.3", + "sha2 0.11.0-rc.3", ] [[package]] -name = "paste" -version = "1.0.15" +name = "p521" +version = "0.14.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "3dd4780ae0e4fc6a0a722123508ee69e88d45f94bdff67ef25528f659dda9dbd" +dependencies = [ + "base16ct 1.0.0", + "ecdsa 0.17.0-rc.11", + "elliptic-curve 0.14.0-rc.20", + "primefield", + "primeorder 0.14.0-rc.3", + "sha2 0.11.0-rc.3", +] [[package]] name = "pem-rfc7468" @@ -484,25 +789,34 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", ] [[package]] -name = "pkcs5" -version = "0.7.1" +name = "pkcs1" +version = "0.8.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" dependencies = [ - "der", - "spki", + "der 0.8.0-rc.10", + "spki 0.8.0-rc.4", ] [[package]] @@ -511,45 +825,63 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "pkcs5", - "spki", + "der 0.7.10", + "spki 0.7.3", ] [[package]] -name = "platforms" -version = "3.3.0" +name = "pkcs8" +version = "0.11.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" +checksum = "77089aec8290d0b7bb01b671b091095cf1937670725af4fd73d47249f03b12c0" +dependencies = [ + "der 0.8.0-rc.10", + "spki 0.8.0-rc.4", + "subtle", +] [[package]] name = "poly1305" -version = "0.8.0" +version = "0.9.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +checksum = "d9c0749ae91cfe6e68c77c4d48802d9720ee06aed3f7100a38975fb0962d50bc" dependencies = [ "cpufeatures", - "opaque-debug", "universal-hash", ] [[package]] name = "polyval" -version = "0.6.2" +version = "0.7.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +checksum = "1ad60831c19edda4b20878a676595c357e93a9b4e6dca2ba98d75b01066b317b" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug", "universal-hash", ] [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primefield" +version = "0.14.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b2bd4ddf14d08c2bc8d9cceaf362f28c146b0737d58c7fee6534b99e19a3ee" +dependencies = [ + "crypto-bigint 0.7.0-rc.15", + "rand_core 0.10.0-rc-3", + "rustcrypto-ff", + "subtle", + "zeroize", +] [[package]] name = "primeorder" @@ -557,27 +889,42 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ - "elliptic-curve", + "elliptic-curve 0.13.8", +] + +[[package]] +name = "primeorder" +version = "0.14.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e56388fad6b8c7576e6987fd0c8c7f3bf94d73d74ae794edaac3e420f9cabfe" +dependencies = [ + "elliptic-curve 0.14.0-rc.20", ] [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -585,7 +932,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -595,7 +942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -604,69 +951,133 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.0-rc-3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f66ee92bc15280519ef199a274fe0cafff4245d31bc39aaa31c011ad56cb1f05" + [[package]] name = "rfc6979" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", + "hmac 0.12.1", + "subtle", +] + +[[package]] +name = "rfc6979" +version = "0.5.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8e2323084c987a72875b2fd682b7307d5cf14d47e3875bb5e89948e8809d4" +dependencies = [ + "hmac 0.13.0-rc.3", "subtle", ] [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.16", "libc", - "spin 0.9.8", "untrusted", "windows-sys", ] [[package]] name = "rsa" -version = "0.9.6" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", - "digest", + "const-oid 0.9.6", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", - "pkcs1", - "pkcs8", - "rand_core", - "sha2", - "signature", - "spki", + "pkcs1 0.7.5", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sha2 0.10.9", + "signature 2.2.0", + "spki 0.7.3", "subtle", "zeroize", ] +[[package]] +name = "rsa" +version = "0.10.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27d813937fdf8e9ad15e3e422a55da4021d29639000139ca19d99f3949060da" +dependencies = [ + "const-oid 0.10.1", + "crypto-bigint 0.7.0-rc.15", + "crypto-primes", + "digest 0.11.0-rc.5", + "pkcs1 0.8.0-rc.4", + "pkcs8 0.11.0-rc.8", + "rand_core 0.10.0-rc-3", + "sha2 0.11.0-rc.3", + "signature 3.0.0-rc.6", + "spki 0.8.0-rc.4", + "zeroize", +] + [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] +[[package]] +name = "rustcrypto-ff" +version = "0.14.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9cd37111549306f79b09aa2618e15b1e8241b7178c286821e3dd71579db4db" +dependencies = [ + "rand_core 0.10.0-rc-3", + "subtle", +] + +[[package]] +name = "rustcrypto-group" +version = "0.14.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e394cd734b5f97dfc3484fa42aad7acd912961c2bcd96c99aa05b3d6cab7cafd" +dependencies = [ + "rand_core 0.10.0-rc-3", + "rustcrypto-ff", + "subtle", +] + [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "log", "once_cell", @@ -678,44 +1089,66 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-rustcrypto" version = "0.0.2-alpha" dependencies = [ "aead", + "aes", "aes-gcm", + "bytes", + "ccm", + "chacha20", "chacha20poly1305", - "crypto-common", - "der", - "digest", - "ecdsa", + "cipher", + "crypto-common 0.2.0-rc.9", + "der 0.8.0-rc.10", + "digest 0.11.0-rc.5", + "ecdsa 0.17.0-rc.11", "ed25519-dalek", - "getrandom", - "hmac", - "p256", + "ed448-goldilocks", + "elliptic-curve 0.14.0-rc.20", + "enum_dispatch", + "getrandom 0.3.4", + "hmac 0.13.0-rc.3", + "itertools", + "p256 0.13.2", + "p256 0.14.0-rc.3", "p384", - "paste", - "pkcs8", - "rand_core", - "rsa", + "p521", + "pkcs1 0.8.0-rc.4", + "pkcs8 0.11.0-rc.8", + "rand_core 0.6.4", + "rand_core 0.9.3", + "rsa 0.10.0-rc.11", + "rsa 0.9.10", "rustls", "rustls-pki-types", "rustls-webpki", - "sec1", - "sha2", - "signature", + "sec1 0.8.0-rc.11", + "sha2 0.11.0-rc.3", + "signature 2.2.0", + "signature 3.0.0-rc.6", + "thiserror", + "tinyvec", + "typenum", "x25519-dalek", + "x448", + "x509-cert", ] [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", @@ -728,72 +1161,147 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct", - "der", + "base16ct 0.2.0", + "der 0.7.10", "generic-array", - "pkcs8", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.8.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2568531a8ace88b848310caa98fb2115b151ef924d54aa523e659c21b9d32d71" +dependencies = [ + "base16ct 1.0.0", + "ctutils", + "der 0.8.0-rc.10", + "hybrid-array", "subtle", "zeroize", ] [[package]] name = "semver" -version = "1.0.22" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +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.197" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "serdect" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06" +dependencies = [ + "base16ct 1.0.0", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] +[[package]] +name = "sha2" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d43dc0354d88b791216bb5c1bfbb60c0814460cc653ae0ebd71f286d0bd927" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.0-rc.5", +] + +[[package]] +name = "sha3" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2103ca0e6f4e9505eae906de5e5883e06fc3b2232fb5d6914890c7bbcb62f478" +dependencies = [ + "digest 0.11.0-rc.5", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", - "rand_core", + "digest 0.10.7", + "rand_core 0.6.4", ] [[package]] -name = "smallvec" -version = "1.13.1" +name = "signature" +version = "3.0.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "597a96996ccff7dfa16f052bd995b4cecc72af22c35138738dc029f0ead6608d" +dependencies = [ + "digest 0.11.0-rc.5", + "rand_core 0.10.0-rc-3", +] [[package]] -name = "spin" -version = "0.5.2" +name = "smallvec" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "spin" @@ -808,45 +1316,111 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", +] + +[[package]] +name = "spki" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" +dependencies = [ + "base64ct", + "der 0.8.0-rc.10", ] [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.52" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "typenum" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "universal-hash" -version = "0.5.1" +version = "0.6.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +checksum = "0386f227888b17b65d3e38219a7d41185035471300855c285667811907bb1677" dependencies = [ - "crypto-common", + "crypto-common 0.2.0-rc.9", "subtle", ] @@ -858,15 +1432,24 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] [[package]] name = "windows-sys" @@ -879,13 +1462,14 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -894,71 +1478,129 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "x25519-dalek" -version = "2.0.1" +version = "3.0.0-pre.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +checksum = "a5887899407ca8fb861126d509bb08465c14a9c60fad1f24c59ed59630a45586" dependencies = [ "curve25519-dalek", - "rand_core", + "getrandom 0.4.0-rc.0", + "rand_core 0.10.0-rc-3", + "zeroize", +] + +[[package]] +name = "x448" +version = "0.14.0-pre.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9032114307e655b8d73d26bebfd1910b5168de23dcdeeaa12e02c5e21c9273e1" +dependencies = [ + "ed448-goldilocks", + "rand_core 0.10.0-rc-3", "zeroize", ] +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid 0.9.6", + "der 0.7.10", + "sha1", + "signature 2.2.0", + "spki 0.7.3", + "tls_codec", +] + +[[package]] +name = "zerocopy" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 01ab6b0..ac7fa09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,49 +11,250 @@ readme = "README.md" repository = "https://github.com/RustCrypto/rustls-rustcrypto" categories = ["cryptography", "no-std"] keywords = ["rustls", "tls"] -edition = "2021" -rust-version = "1.75" -resolver = "1" # Hack to enable the `custom` feature of `getrandom` +edition = "2024" +rust-version = "1.88.0" +resolver = "2" # Ensure all dependencies + feats are mapped to crate features for correct usage # default features often have std breaking no_std and potentially other unwanted [dependencies] -aead = { version = "0.5.2", default-features = false } -aes-gcm = { version = "0.10.3", default-features = false, features = ["aes", "alloc"] } -chacha20poly1305 = { version = "0.10.1", default-features = false } -crypto-common = { version = "0.1.6", default-features = false } -der = { version = "0.7.9", default-features = false } -digest = { version = "0.10.7", default-features = false } -ecdsa = { version = "0.16.8", default-features = false, features = ["alloc"] } -ed25519-dalek = { version = "2", default-features = false, features = ["pkcs8"] } -hmac = { version = "0.12.1", default-features = false } -p256 = { version = "0.13.2", default-features = false, features = ["pem", "ecdsa", "ecdh"] } -p384 = { version = "0.13.0", default-features = false, features = ["pem", "ecdsa", "ecdh"] } -paste = { version = "1.0.15", default-features = false } -pkcs8 = { version = "0.10.2", default-features = false, features = ["pem", "pkcs5"] } -pki-types = { package = "rustls-pki-types", version = "1.0.1", default-features = false } -rand_core = { version = "0.6.4", default-features = false, features = ["getrandom"] } -rsa = { version = "0.9.2", default-features = false, features = ["sha2"] } -rustls = { version = "0.23.12", default-features = false } -sec1 = { version = "0.7.3", default-features = false, features = ["pkcs8", "pem"] } -sha2 = { version = "0.10.7", default-features = false } -signature = { version = "2.1.0", default-features = false } -webpki = { package = "rustls-webpki", version = "0.102.0", default-features = false } -x25519-dalek = { version = "2", default-features = false } +# Cryptographic dependencies +aead = { version = "0.6.0-rc.5", default-features = false, optional = true } +aes = { version = "0.9.0-rc.2", default-features = false, optional = true } +aes-gcm = { version = "0.11.0-rc.2", default-features = false, optional = true } +ccm = { version = "0.6.0-rc.2", default-features = false, optional = true } +chacha20 = { version = "0.10.0-rc.6", default-features = false, optional = true } +chacha20poly1305 = { version = "0.11.0-rc.2", default-features = false, optional = true } +cipher = "0.5.0-rc.3" +crypto-common = { version = "0.2.0-rc.9", default-features = false } +der = { version = "0.8.0-rc.10", default-features = false, optional = true } +digest = { version = "0.11.0-rc.5", default-features = false } +ecdsa = { version = "0.17.0-rc.11", default-features = false, optional = true } +ed25519-dalek = { version = "3.0.0-pre.4", default-features = false, optional = true } +ed448-goldilocks = { version = "0.14.0-pre.5", default-features = false, optional = true } +elliptic-curve = { version = "0.14.0-rc.20", default-features = false, optional = true } +hmac = { version = "0.13.0-rc.3", default-features = false } +p256 = { version = "0.14.0-rc.3", default-features = false, optional = true } +p384 = { version = "0.14.0-rc.3", default-features = false, optional = true } +p521 = { version = "0.14.0-rc.3", default-features = false, optional = true } +pkcs1 = { version = "0.8.0-rc.4", default-features = false, optional = true } +pkcs8 = { version = "0.11.0-rc.8", default-features = false, optional = true } +rsa = { version = "0.10.0-rc.11", default-features = false, optional = true } +sec1 = { version = "0.8.0-rc.11", default-features = false, optional = true } +sha2 = { version = "0.11.0-rc.3", default-features = false } +signature = { version = "3.0.0-rc.6", default-features = false, optional = true } +typenum = { version = "1.19.0", features = ["no_std", "const-generics"] } +x25519-dalek = { version = "3.0.0-pre.4", default-features = false, optional = true } +x448 = { version = "=0.14.0-pre.3", default-features = false, optional = true } + +# External groups +pki-types = { package = "rustls-pki-types", version = "1.13.2", default-features = false } +rand_core = { version = "0.9.3", default-features = false, features = [ + "os_rng", +], optional = true } +rustls = { version = "0.23.36", default-features = false } +webpki = { package = "rustls-webpki", version = "0.103.8", default-features = false, optional = true } +enum_dispatch = "0.3.13" +tinyvec = { version = "1.10.0", default-features = false, optional = true } +thiserror = { version = "2.0.17", default-features = false } +getrandom = "0.3.4" [dev-dependencies] -getrandom = { version = "0.2", features = ["custom"] } # workaround to build on no_std targets +bytes = { version = "1.10.1", default-features = false } +itertools = { version = "0.14.0", default-features = false } +rsa_098 = { package = "rsa", version = "0.9.8", features = ["sha2"] } +signature_220 = { package = "signature", version = "2.2.0" } +rustls = { version = "0.23.32", default-features = false, features = ["std"] } +x509-cert = { version = "0.2.5", default-features = false, features = [ + "builder", +] } +rand_core_064 = { package = "rand_core", version = "0.6.4" } +p256_0132 = { package = "p256", version = "0.13.2" } [features] -default = ["std", "tls12", "zeroize"] +default = ["std", "tls12", "zeroize", "full", "fast", "quic", "ticketer"] +full = [ + "aead-full", + "sign-full", + "verify-full", + "kx-full", + "hash-full", + "format", +] +format = ["pem", "pkcs1", "pkcs8", "sec1"] logging = ["rustls/logging"] tls12 = ["rustls/tls12"] -# Only enable feature in upstream if there is an overall effect e.g. aead/alloc in-place -# zeroize is another typical that can be turned off +# RustCrypto is preparing to migrate to core::error::Error +# and in before most of the use case for std is just std::error::Error +std = ["alloc", "rustls/std", "ed448-goldilocks?/std", "tinyvec?/std", "thiserror/std"] +alloc = [ + "ecdsa?/alloc", + "ed448-goldilocks?/alloc", + "elliptic-curve?/alloc", + "pkcs8?/alloc", + "sec1?/alloc", + "signature?/alloc", +] +zeroize = [ + "aes-gcm?/zeroize", + "aes?/zeroize", + "der?/zeroize", + "ed25519-dalek?/zeroize", + "pkcs1?/zeroize", + "sec1?/zeroize", + "x25519-dalek?/zeroize", +] +subtle = ["digest/subtle", "pkcs8?/subtle", "sec1?/subtle"] +fast = [ + "ed25519-dalek?/fast", + # "rsa?/u64_digit", + "x25519-dalek?/precomputed-tables", +] + +nist = [] +p256 = ["dep:p256", "nist", "p256/pkcs8"] +p384 = ["dep:p384", "nist", "p384/pkcs8"] +p521 = ["dep:p521", "nist"] +ed25519 = ["dep:ed25519-dalek"] +ed448 = ["dep:ed448-goldilocks"] + +ecdsa = ["dep:ecdsa", "verify", "signature", "rand", "der", "elliptic-curve"] +ecdsa-p256 = ["ecdsa", "p256", "p256/ecdsa"] +ecdsa-p384 = ["ecdsa", "p384", "p384/ecdsa"] +ecdsa-p521 = ["ecdsa", "p521", "p521/ecdsa"] +ecdsa-full = ["ecdsa-p256", "ecdsa-p384", "ecdsa-p521"] + +eddsa = ["verify", "signature", "elliptic-curve"] +eddsa-ed25519 = ["eddsa", "ed25519"] +eddsa-ed448 = ["eddsa", "ed448"] +eddsa-full = ["eddsa-ed25519", "eddsa-ed448"] + +kx = ["rand", "elliptic-curve"] +kx-x448 = ["kx", "x448"] +kx-x25519 = ["kx", "dep:x25519-dalek"] +kx-nist = ["sec1"] +kx-p256 = ["kx", "p256", "kx-nist", "p256/ecdh"] +kx-p384 = ["kx", "p384", "kx-nist", "p384/ecdh"] +kx-p521 = ["kx", "p521", "kx-nist", "p521/ecdh"] +kx-full = ["kx-x448", "kx-x25519", "kx-p256", "kx-p384", "kx-p521"] + +rsa = ["dep:rsa", "rsa/sha2", "pkcs1"] +rsa-pkcs1 = ["rsa", "pkcs1"] +rsa-pss = ["rsa"] + +aead = ["dep:aead"] +aead-aes-gcm = ["aead", "aes-gcm"] +aead-aes-ccm = ["aead", "aes-ccm"] +aead-chacha20poly1305 = ["aead", "chacha20poly1305"] +aead-full = ["aead-aes-gcm", "aead-aes-ccm", "aead-chacha20poly1305"] + +sign = ["signature", "der"] +sign-ecdsa-nist = ["sign"] +sign-ecdsa-p256 = ["sign-ecdsa-nist", "ecdsa-p256"] +sign-ecdsa-p384 = ["sign-ecdsa-nist", "ecdsa-p384"] +sign-ecdsa-p521 = ["sign-ecdsa-nist", "ecdsa-p521"] +sign-eddsa = ["sign"] +sign-eddsa-ed25519 = ["sign-eddsa", "eddsa-ed25519"] +sign-eddsa-ed448 = ["sign-eddsa", "eddsa-ed448", "ed448-goldilocks?/signing"] +sign-rsa = ["sign", "rsa"] +sign-rsa-pkcs1 = ["sign-rsa", "rsa-pkcs1"] +sign-rsa-pss = ["sign-rsa", "rsa-pss"] +sign-full = [ + "sign-ecdsa-p256", + "sign-ecdsa-p384", + "sign-ecdsa-p521", + "sign-eddsa-ed25519", + "sign-eddsa-ed448", + "sign-rsa-pkcs1", + "sign-rsa-pss", +] + +verify = ["dep:webpki"] +verify-ecdsa-nist = ["verify"] +verify-ecdsa-p256 = ["verify-ecdsa-nist", "ecdsa-p256"] +verify-ecdsa-p256-sha256 = ["verify-ecdsa-p256", "hash-sha256"] +verify-ecdsa-p256-sha384 = ["verify-ecdsa-p256", "hash-sha384"] +verify-ecdsa-p256-sha512 = ["verify-ecdsa-p256", "hash-sha512"] +verify-ecdsa-p384 = ["verify-ecdsa-nist", "ecdsa-p384"] +verify-ecdsa-p384-sha256 = ["verify-ecdsa-p384", "hash-sha256"] +verify-ecdsa-p384-sha384 = ["verify-ecdsa-p384", "hash-sha384"] +verify-ecdsa-p384-sha512 = ["verify-ecdsa-p384", "hash-sha512"] +verify-ecdsa-p521 = ["verify-ecdsa-nist", "ecdsa-p521"] +verify-ecdsa-p521-sha256 = ["verify-ecdsa-p521", "hash-sha256"] +verify-ecdsa-p521-sha384 = ["verify-ecdsa-p521", "hash-sha384"] +verify-ecdsa-p521-sha512 = ["verify-ecdsa-p521", "hash-sha512"] +verify-eddsa = ["verify"] +verify-eddsa-ed25519 = ["verify-eddsa", "eddsa-ed25519"] +verify-eddsa-ed448 = ["verify-eddsa", "eddsa-ed448"] +verify-rsa = ["verify"] +verify-rsa-pkcs1 = ["verify-rsa", "rsa-pkcs1"] +verify-rsa-pkcs1-sha256 = ["verify-rsa-pkcs1", "hash-sha256"] +verify-rsa-pkcs1-sha384 = ["verify-rsa-pkcs1", "hash-sha384"] +verify-rsa-pkcs1-sha512 = ["verify-rsa-pkcs1", "hash-sha512"] +verify-rsa-pss = ["verify-rsa", "rsa-pss"] +verify-rsa-pss-sha256 = ["verify-rsa-pss", "hash-sha256"] +verify-rsa-pss-sha384 = ["verify-rsa-pss", "hash-sha384"] +verify-rsa-pss-sha512 = ["verify-rsa-pss", "hash-sha512"] +verify-full = [ + "verify-ecdsa-p256-sha256", + "verify-ecdsa-p256-sha384", + "verify-ecdsa-p384-sha256", + "verify-ecdsa-p384-sha384", + "verify-ecdsa-p256-sha512", + "verify-ecdsa-p384-sha512", + "verify-ecdsa-p521-sha256", + "verify-ecdsa-p521-sha384", + "verify-ecdsa-p521-sha512", + "verify-eddsa-ed25519", + "verify-eddsa-ed448", + "verify-rsa-pkcs1-sha256", + "verify-rsa-pkcs1-sha384", + "verify-rsa-pkcs1-sha512", + "verify-rsa-pss-sha256", + "verify-rsa-pss-sha384", + "verify-rsa-pss-sha512", +] + +hash = [] +hash-sha224 = ["hash"] +hash-sha256 = ["hash"] +hash-sha384 = ["hash"] +hash-sha512 = ["hash"] +hash-full = ["hash-sha224", "hash-sha256", "hash-sha384", "hash-sha512"] + +quic = ["aead", "chacha20?/cipher", "tinyvec"] +ticketer = ["aead", "chacha20poly1305", "rand"] + +# Formats +der = ["dep:der", "sec1?/der"] +sec1 = ["dep:sec1", "elliptic-curve?/sec1"] +pem = ["elliptic-curve?/pem", "ecdsa?/pem", "ed25519-dalek?/pem"] +pkcs1 = ["dep:pkcs1", "rsa?/encoding"] +pkcs8 = [ + "dep:pkcs8", + "ecdsa?/pkcs8", + "ed25519-dalek?/pkcs8", + "ed448-goldilocks?/pkcs8", + "elliptic-curve?/pkcs8", + "p256?/pkcs8", + "p384?/pkcs8", + "p521?/pkcs8", +] -# TODO: go through all of these that what gets exposed re: std error type -std = ["alloc", "webpki/std", "pki-types/std", "rustls/std", "ed25519-dalek/std"] -# TODO: go through all of these to ensure to_vec etc. impls are exposed -alloc = ["webpki/alloc", "pki-types/alloc", "aead/alloc", "ed25519-dalek/alloc"] -zeroize = ["ed25519-dalek/zeroize", "x25519-dalek/zeroize"] +aes = ["dep:aes"] +aes-ccm = ["aes", "ccm"] +aes-gcm = ["dep:aes-gcm", "aes", "gcm"] +ccm = ["dep:ccm"] +chacha20 = ["dep:chacha20"] +chacha20poly1305 = ["dep:chacha20poly1305", "chacha20"] +elliptic-curve = [ + "dep:elliptic-curve", + "elliptic-curve/ecdh", + "elliptic-curve/sec1", +] +gcm = [] +rand = ["dep:rand_core", "signature?/rand_core", "x25519-dalek?/getrandom"] +signature = ["dep:signature"] +x448 = ["dep:x448"] +tinyvec = ["dep:tinyvec"] diff --git a/src/aead.rs b/src/aead.rs index 6ff57b7..40ab042 100644 --- a/src/aead.rs +++ b/src/aead.rs @@ -1,45 +1,89 @@ use aead::Buffer; use rustls::crypto::cipher::{BorrowedPayload, PrefixedPayload}; -pub mod chacha20; +#[cfg(all(feature = "quic", feature = "alloc"))] +use alloc::vec::Vec; + +#[cfg(feature = "gcm")] pub mod gcm; -pub(crate) struct EncryptBufferAdapter<'a>(&'a mut PrefixedPayload); +#[cfg(feature = "ccm")] +pub mod ccm; + +#[macro_use] +pub(crate) mod common; + +#[cfg(feature = "tinyvec")] +use tinyvec::SliceVec; + +pub(crate) enum EncryptBufferAdapter<'a> { + PrefixedPayload(&'a mut PrefixedPayload), + #[cfg(feature = "quic")] + Vec(Vec), +} impl AsRef<[u8]> for EncryptBufferAdapter<'_> { fn as_ref(&self) -> &[u8] { - self.0.as_ref() + match self { + EncryptBufferAdapter::PrefixedPayload(payload) => payload.as_ref(), + #[cfg(feature = "quic")] + EncryptBufferAdapter::Vec(payload) => payload.as_ref(), + } } } impl AsMut<[u8]> for EncryptBufferAdapter<'_> { fn as_mut(&mut self) -> &mut [u8] { - self.0.as_mut() + match self { + EncryptBufferAdapter::PrefixedPayload(payload) => payload.as_mut(), + #[cfg(feature = "quic")] + EncryptBufferAdapter::Vec(payload) => payload.as_mut(), + } } } impl Buffer for EncryptBufferAdapter<'_> { fn extend_from_slice(&mut self, other: &[u8]) -> aead::Result<()> { - self.0.extend_from_slice(other); + match self { + EncryptBufferAdapter::PrefixedPayload(payload) => payload.extend_from_slice(other), + #[cfg(feature = "quic")] + EncryptBufferAdapter::Vec(payload) => payload.extend_from_slice(other), + } Ok(()) } fn truncate(&mut self, len: usize) { - self.0.truncate(len) + match self { + EncryptBufferAdapter::PrefixedPayload(payload) => payload.truncate(len), + #[cfg(feature = "quic")] + EncryptBufferAdapter::Vec(payload) => payload.truncate(len), + } } } -pub(crate) struct DecryptBufferAdapter<'a, 'p>(&'a mut BorrowedPayload<'p>); +pub(crate) enum DecryptBufferAdapter<'a, 'p> { + BorrowedPayload(&'a mut BorrowedPayload<'p>), + #[cfg(feature = "tinyvec")] + Slice(SliceVec<'a, u8>), +} impl AsRef<[u8]> for DecryptBufferAdapter<'_, '_> { fn as_ref(&self) -> &[u8] { - self.0 + match self { + DecryptBufferAdapter::BorrowedPayload(payload) => payload, + #[cfg(feature = "tinyvec")] + DecryptBufferAdapter::Slice(slice) => slice, + } } } impl AsMut<[u8]> for DecryptBufferAdapter<'_, '_> { fn as_mut(&mut self) -> &mut [u8] { - self.0 + match self { + DecryptBufferAdapter::BorrowedPayload(payload) => payload, + #[cfg(feature = "tinyvec")] + DecryptBufferAdapter::Slice(slice) => slice, + } } } @@ -49,6 +93,13 @@ impl Buffer for DecryptBufferAdapter<'_, '_> { } fn truncate(&mut self, len: usize) { - self.0.truncate(len) + match self { + DecryptBufferAdapter::BorrowedPayload(payload) => payload.truncate(len), + #[cfg(feature = "tinyvec")] + DecryptBufferAdapter::Slice(payload) => payload.truncate(len), + } } } + +#[cfg(feature = "aes")] +pub mod aes; diff --git a/src/aead/aes.rs b/src/aead/aes.rs new file mode 100644 index 0000000..04c3e8e --- /dev/null +++ b/src/aead/aes.rs @@ -0,0 +1,42 @@ +use ::aes::{Aes128, Aes256}; + +#[cfg(feature = "gcm")] +use aes_gcm::AesGcm; + +#[cfg(feature = "ccm")] +use { + ccm::Ccm, + typenum::{U8, U16}, +}; + +#[cfg(any(feature = "gcm", feature = "ccm"))] +use typenum::U12; + +// The AEAD_AES_128_CCM authenticated encryption algorithm works as +// specified in [CCM], using AES-128 as the block cipher, by providing +// the key, nonce, associated data, and plaintext to that mode of +// operation. The formatting and counter generation function are as +// specified in Appendix A of that reference, and the values of the +// parameters identified in that appendix are as follows: +// the nonce length n is 12, +// the tag length t is 16, and +// the value of q is 3. +#[cfg(feature = "ccm")] +pub type Aes128Ccm = Ccm; +#[cfg(feature = "ccm")] +pub type Aes256Ccm = Ccm; + +// The AEAD_AES_128_CCM_8 authenticated encryption algorithm is +// identical to the AEAD_AES_128_CCM algorithm (see Section 5.3 of +// [RFC5116]), except that it uses 8 octets for authentication, instead +// of the full 16 octets used by AEAD_AES_128_CCM. +#[cfg(feature = "ccm")] +pub type Aes128Ccm8 = Ccm; +#[cfg(feature = "ccm")] +pub type Aes256Ccm8 = Ccm; + +#[cfg(feature = "gcm")] +pub type Aes128Gcm = AesGcm; + +#[cfg(feature = "gcm")] +pub type Aes256Gcm = AesGcm; diff --git a/src/aead/ccm.rs b/src/aead/ccm.rs new file mode 100644 index 0000000..caa9fe2 --- /dev/null +++ b/src/aead/ccm.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "aes-ccm")] +pub struct Aes128Ccm; + +#[cfg(feature = "aes-ccm")] +pub struct Aes256Ccm; + +#[cfg(feature = "aes-ccm")] +pub struct Aes128Ccm8; + +#[cfg(feature = "aes-ccm")] +pub struct Aes256Ccm8; diff --git a/src/aead/common.rs b/src/aead/common.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/aead/common.rs @@ -0,0 +1 @@ + diff --git a/src/aead/gcm.rs b/src/aead/gcm.rs index 14be62b..5dbdfbf 100644 --- a/src/aead/gcm.rs +++ b/src/aead/gcm.rs @@ -1,235 +1,5 @@ -#[cfg(feature = "alloc")] -use alloc::boxed::Box; +#[cfg(feature = "aes-gcm")] +pub struct Aes128Gcm; -use super::{DecryptBufferAdapter, EncryptBufferAdapter}; - -use aead::AeadInPlace; -use crypto_common::{KeyInit, KeySizeUser}; -use paste::paste; -use rustls::crypto::cipher::{ - self, AeadKey, InboundOpaqueMessage, InboundPlainMessage, MessageDecrypter, MessageEncrypter, - OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, Tls13AeadAlgorithm, -}; -use rustls::{ConnectionTrafficSecrets, ContentType, ProtocolVersion}; - -#[cfg(feature = "tls12")] -use { - aead::AeadCore, - crypto_common::typenum::Unsigned, - rustls::crypto::cipher::{Iv, KeyBlockShape, Tls12AeadAlgorithm}, -}; - -#[cfg(feature = "tls12")] -const TLS12_GCM_EXPLICIT_NONCE_LEN: usize = 8; - -#[cfg(feature = "tls12")] -const TLS12_GCM_OVERHEAD: usize = TLS12_GCM_EXPLICIT_NONCE_LEN + 16; - -macro_rules! impl_gcm_tls13 { - ($name: ident, $aead: ty, $overhead: expr) => { - paste! { - pub struct []; - - impl Tls13AeadAlgorithm for [] { - fn encrypter(&self, key: AeadKey, iv: cipher::Iv) -> Box { - Box::new([]( - $aead::new_from_slice(key.as_ref()).unwrap(), - iv, - )) - } - - fn decrypter(&self, key: AeadKey, iv: cipher::Iv) -> Box { - Box::new([]( - $aead::new_from_slice(key.as_ref()).unwrap(), - iv, - )) - } - - fn key_len(&self) -> usize { - $aead::key_size() - } - fn extract_keys( - &self, - key: AeadKey, - iv: cipher::Iv, - ) -> Result { - Ok(ConnectionTrafficSecrets::Aes256Gcm { key, iv }) - } - } - - struct []($aead, cipher::Iv); - - impl MessageEncrypter for [] { - fn encrypt(&mut self, m: OutboundPlainMessage<'_>, seq: u64) -> Result { - let total_len = self.encrypted_payload_len(m.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); - - let nonce = cipher::Nonce::new(&self.1, seq).0; - let aad = cipher::make_tls13_aad(total_len); - payload.extend_from_chunks(&m.payload); - payload.extend_from_slice(&m.typ.to_array()); - - self.0 - .encrypt_in_place(&nonce.into(), &aad, &mut EncryptBufferAdapter(&mut payload)) - .map_err(|_| rustls::Error::EncryptError) - .map(|_| OutboundOpaqueMessage::new( - ContentType::ApplicationData, - ProtocolVersion::TLSv1_2, - payload, - )) - } - - fn encrypted_payload_len(&self, payload_len: usize) -> usize { - payload_len + 1 + $overhead - } - } - - impl MessageDecrypter for [] { - fn decrypt<'a>(&mut self, mut m: InboundOpaqueMessage<'a>, seq: u64) -> Result, rustls::Error> { - let payload = &mut m.payload; - let nonce = cipher::Nonce::new(&self.1, seq).0; - let aad = cipher::make_tls13_aad(payload.len()); - - self.0 - .decrypt_in_place(&nonce.into(), &aad, &mut DecryptBufferAdapter(payload)) - .map_err(|_| rustls::Error::DecryptError)?; - - m.into_tls13_unpadded_message() - } - } - - } - }; -} - -#[cfg(feature = "tls12")] -macro_rules! impl_gcm_tls12 { - ($name: ident, $aead: ty, $nonce: expr, $overhead: expr) => { - paste! { - #[cfg(feature = "tls12")] - pub struct []; - - #[cfg(feature = "tls12")] - impl Tls12AeadAlgorithm for [] { - fn encrypter(&self, key: AeadKey, write_iv: &[u8], explicit: &[u8]) -> Box { - Box::new([]( - $aead::new_from_slice(key.as_ref()).unwrap(), - { - let mut iv: [u8; 12] = [0; 12]; - iv[..4].copy_from_slice(write_iv); - iv[4..].copy_from_slice(explicit); - iv - }, - )) - } - - fn decrypter(&self, dec_key: AeadKey, dec_iv: &[u8]) -> Box { - Box::new([]( - $aead::new_from_slice(dec_key.as_ref()).unwrap(), - dec_iv.try_into().unwrap(), - )) - } - - fn key_block_shape(&self) -> KeyBlockShape { - KeyBlockShape { - enc_key_len: $aead::key_size(), - fixed_iv_len: 4, - explicit_nonce_len: 8, - } - } - - fn extract_keys( - &self, - key: AeadKey, - iv: &[u8], - _explicit: &[u8], - ) -> Result { - Ok(ConnectionTrafficSecrets::Aes128Gcm { - key, - iv: Iv::new(iv[..].try_into().unwrap()), - }) - } - } - - #[cfg(feature = "tls12")] - struct []($aead, [u8; 12]); - - #[cfg(feature = "tls12")] - impl MessageEncrypter for [] { - fn encrypt(&mut self, m: OutboundPlainMessage<'_>, seq: u64) -> Result { - let total_len = self.encrypted_payload_len(m.payload.len()); - let mut payload = PrefixedPayload::with_capacity(total_len); - - let nonce = cipher::Nonce::new(&self.1.into(), seq).0; - let aad = cipher::make_tls12_aad(seq, m.typ, m.version, m.payload.len()); - payload.extend_from_slice(&nonce.as_ref()[4..]); // explicit - payload.extend_from_chunks(&m.payload); - - self.0 - .encrypt_in_place_detached(&nonce.into(), &aad, &mut payload.as_mut()[$nonce..]) - .map(|tag| payload.extend(tag.as_ref() as &[u8])) - .map_err(|_| rustls::Error::EncryptError) - .map(|_| OutboundOpaqueMessage::new(m.typ, m.version, payload)) - } - fn encrypted_payload_len(&self, payload_len: usize) -> usize { - payload_len + $nonce + <$aead as AeadCore>::TagSize::USIZE - } - } - - #[cfg(feature = "tls12")] - struct []($aead, [u8; 4]); - - #[cfg(feature = "tls12")] - impl MessageDecrypter for [] { - fn decrypt<'a>(&mut self, mut m: InboundOpaqueMessage<'a>, seq: u64) -> Result, rustls::Error> { - type TagSize = <$aead as AeadCore>::TagSize; - - let payload = &m.payload; - - if payload.len() < $overhead { - return Err(rustls::Error::DecryptError); - } - - let nonce: aead::Nonce<$aead> = { - let mut nonce = [0u8; 12]; - nonce[..4].copy_from_slice(&self.1); // dec_iv - nonce[4..].copy_from_slice(&payload[..$nonce]); - nonce.into() - }; - - let aad = cipher::make_tls12_aad(seq, m.typ, m.version, payload.len() - $overhead); - - let payload = &mut m.payload; - let tag_pos = { - let payload = &mut payload[$nonce..]; - let tag_pos = payload.len() - TagSize::to_usize(); - let (msg, tag) = payload.split_at_mut(tag_pos); - - let tag = aes_gcm::Tag::::from_slice(tag); - self.0 - .decrypt_in_place_detached(&nonce, &aad, msg, tag) - .map_err(|_| rustls::Error::DecryptError)?; - tag_pos - }; - - // We defer the truncation to here, because we may inadvertently shifted the - // original data if the decryption failed. Another way to avoid this is - // to clone the payload slice starting after the explicit nonce, - // but this will cause an additional cloning and copying - payload.rotate_left($nonce); - payload.truncate(tag_pos); - Ok(m.into_plain_message()) - } - } - } - }; -} - -impl_gcm_tls13! {Aes128Gcm, aes_gcm::Aes128Gcm, 16} -impl_gcm_tls13! {Aes256Gcm, aes_gcm::Aes256Gcm, 16} - -#[cfg(feature = "tls12")] -impl_gcm_tls12! {Aes128Gcm, aes_gcm::Aes128Gcm, TLS12_GCM_EXPLICIT_NONCE_LEN, TLS12_GCM_OVERHEAD} - -#[cfg(feature = "tls12")] -impl_gcm_tls12! {Aes256Gcm, aes_gcm::Aes256Gcm, TLS12_GCM_EXPLICIT_NONCE_LEN, TLS12_GCM_OVERHEAD} +#[cfg(feature = "aes-gcm")] +pub struct Aes256Gcm; diff --git a/src/hash.rs b/src/hash.rs index dfa58e1..ccbbf95 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,62 +1,88 @@ #[cfg(feature = "alloc")] use alloc::boxed::Box; +use core::marker::PhantomData; use digest::{Digest, OutputSizeUser}; -use paste::paste; -use rustls::crypto::{self, hash}; -use sha2::{Sha256, Sha384}; +/// Trait to provide hash algorithm for different hash types +pub trait HashAlgorithm { + const ALGORITHM: rustls::crypto::hash::HashAlgorithm; +} + +// Generic hash implementation +#[derive(Default)] +pub struct GenericHash { + _phantom: PhantomData, +} + +impl GenericHash { + pub const DEFAULT: Self = Self { + _phantom: PhantomData, + }; +} + +impl rustls::crypto::hash::Hash for GenericHash +where + H: Digest + OutputSizeUser + Clone + Send + Sync + 'static + HashAlgorithm, +{ + fn start(&self) -> Box { + Box::new(GenericHashContext(H::new())) + } + + fn hash(&self, data: &[u8]) -> rustls::crypto::hash::Output { + rustls::crypto::hash::Output::new(&H::digest(data)[..]) + } + + fn output_len(&self) -> usize { + ::output_size() + } + + fn algorithm(&self) -> rustls::crypto::hash::HashAlgorithm { + H::ALGORITHM + } +} + +pub struct GenericHashContext(H); + +impl rustls::crypto::hash::Context for GenericHashContext +where + H: Digest + Clone + Send + Sync + 'static, +{ + fn fork_finish(&self) -> rustls::crypto::hash::Output { + rustls::crypto::hash::Output::new(&self.0.clone().finalize()[..]) + } + + fn fork(&self) -> Box { + Box::new(GenericHashContext(self.0.clone())) + } + + fn finish(self: Box) -> rustls::crypto::hash::Output { + rustls::crypto::hash::Output::new(&self.0.finalize()[..]) + } + + fn update(&mut self, data: &[u8]) { + self.0.update(data); + } +} + +/// Macro to generate hash constants macro_rules! impl_hash { - ($name:ident, $ty:ty, $algo:ty) => { - paste! { - #[allow(non_camel_case_types)] - struct []; - - impl hash::Hash for [] { - fn start(&self) -> Box { - Box::new([]($ty::new())) - } - - fn hash(&self, data: &[u8]) -> hash::Output { - hash::Output::new(&$ty::digest(data)[..]) - } - - fn output_len(&self) -> usize { - <$ty as OutputSizeUser>::output_size() - } - - fn algorithm(&self) -> hash::HashAlgorithm { - $algo - } - } - - #[allow(non_camel_case_types)] - struct []($ty); - - impl hash::Context for [] { - fn fork_finish(&self) -> hash::Output { - hash::Output::new(&self.0.clone().finalize()[..]) - } - - fn fork(&self) -> Box { - Box::new([](self.0.clone())) - } - - fn finish(self: Box) -> hash::Output { - hash::Output::new(&self.0.finalize()[..]) - } - - fn update(&mut self, data: &[u8]) { - self.0.update(data); - } - } - - pub const $name: &dyn crypto::hash::Hash = &[]; + ($name:ident, $hash:ty) => { + impl HashAlgorithm for $hash { + const ALGORITHM: rustls::crypto::hash::HashAlgorithm = + rustls::crypto::hash::HashAlgorithm::$name; } + + pub const $name: &dyn rustls::crypto::hash::Hash = &GenericHash::<$hash>::DEFAULT; }; } -// impl_hash! {SHA224, Sha224, hash::HashAlgorithm::SHA224} -impl_hash! {SHA256, Sha256, hash::HashAlgorithm::SHA256} -impl_hash! {SHA384, Sha384, hash::HashAlgorithm::SHA384} -// impl_hash! {SHA512, Sha512, hash::HashAlgorithm::SHA512} +// Generate hash constants using macro +#[cfg(feature = "hash-sha224")] +impl_hash!(SHA224, ::sha2::Sha224); +#[cfg(feature = "hash-sha256")] +impl_hash!(SHA256, ::sha2::Sha256); +#[cfg(feature = "hash-sha384")] +impl_hash!(SHA384, ::sha2::Sha384); +#[cfg(feature = "hash-sha512")] +impl_hash!(SHA512, ::sha2::Sha512); diff --git a/src/hmac.rs b/src/hmac.rs index 8388c27..8e113f2 100644 --- a/src/hmac.rs +++ b/src/hmac.rs @@ -1,56 +1,73 @@ #[cfg(feature = "alloc")] use alloc::boxed::Box; +use core::marker::PhantomData; use crypto_common::OutputSizeUser; -use hmac::Mac; -use paste::paste; -use rustls::crypto; -use sha2::{Sha256, Sha384}; - -macro_rules! impl_hmac { - ( - $name: ident, - $ty: ty - ) => { - paste! { - #[allow(non_camel_case_types)] - struct []; - - impl crypto::hmac::Hmac for [] { - fn with_key(&self, key: &[u8]) -> Box { - Box::new([]( - hmac::Hmac::<$ty>::new_from_slice(key).unwrap(), - )) - } - - fn hash_output_len(&self) -> usize { - $ty::output_size() - } - } - - #[allow(non_camel_case_types)] - struct [](hmac::Hmac<$ty>); - - impl crypto::hmac::Key for [] { - fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> crypto::hmac::Tag { - let mut ctx = self.0.clone(); - ctx.update(first); - for m in middle { - ctx.update(m); - } - ctx.update(last); - crypto::hmac::Tag::new(&ctx.finalize().into_bytes()[..]) - } - - fn tag_len(&self) -> usize { - $ty::output_size() - } - } - pub const $name: &dyn crypto::hmac::Hmac = &[]; +use hmac::{EagerHash, Hmac}; +use rustls::crypto::hmac::{Hmac as RustlsHmac, Key, Tag}; + +pub trait HmacHash: EagerHash + Send + Sync + 'static {} + +impl HmacHash for T where T: EagerHash + Send + Sync + 'static {} + +pub struct GenericHmac { + _phantom: PhantomData, +} + +impl GenericHmac { + pub const DEFAULT: Self = Self { + _phantom: PhantomData, + }; +} + +impl RustlsHmac for GenericHmac +where + H: HmacHash, + ::Core: Send + Sync, +{ + fn with_key(&self, key: &[u8]) -> Box { + Box::new(GenericHmacKey::( + <::hmac::Hmac as hmac::KeyInit>::new_from_slice(key) + .expect("Invalid key length for HMAC"), + )) + } + + fn hash_output_len(&self) -> usize { + ::output_size() + } +} + +pub struct GenericHmacKey(Hmac); + +impl Key for GenericHmacKey +where + H: HmacHash, + ::Core: Send + Sync, +{ + fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> Tag { + use ::hmac::Mac; + let mut ctx = self.0.clone(); + ctx.update(first); + for m in middle { + ctx.update(m); } + ctx.update(last); + Tag::new(&ctx.finalize().into_bytes()[..]) + } + + fn tag_len(&self) -> usize { + ::output_size() + } +} + +/// Macro to generate HMAC constants +macro_rules! hmac_const { + ($name:ident, $hash:ty) => { + pub const $name: &GenericHmac<$hash> = &GenericHmac::DEFAULT; }; } -impl_hmac! {SHA256, Sha256} -impl_hmac! {SHA384, Sha384} -// impl_hmac! {SHA512, Sha512} +// Generate HMAC constants using macro +hmac_const!(SHA256, ::sha2::Sha256); +hmac_const!(SHA384, ::sha2::Sha384); +hmac_const!(SHA512, ::sha2::Sha512); diff --git a/src/kx.rs b/src/kx.rs index 66341f2..49ba074 100644 --- a/src/kx.rs +++ b/src/kx.rs @@ -1,108 +1,23 @@ -#[cfg(feature = "alloc")] -use alloc::boxed::Box; - -use crypto::{SharedSecret, SupportedKxGroup}; -use paste::paste; -use rustls::crypto; - -#[derive(Debug)] -pub struct X25519; - -impl crypto::SupportedKxGroup for X25519 { - fn name(&self) -> rustls::NamedGroup { - rustls::NamedGroup::X25519 - } - - fn start(&self) -> Result, rustls::Error> { - let priv_key = x25519_dalek::EphemeralSecret::random_from_rng(rand_core::OsRng); - let pub_key = (&priv_key).into(); - Ok(Box::new(X25519KeyExchange { priv_key, pub_key })) - } -} - -pub struct X25519KeyExchange { - priv_key: x25519_dalek::EphemeralSecret, - pub_key: x25519_dalek::PublicKey, -} - -impl crypto::ActiveKeyExchange for X25519KeyExchange { - fn complete(self: Box, peer: &[u8]) -> Result { - let peer_array: [u8; 32] = peer - .try_into() - .map_err(|_| rustls::Error::from(rustls::PeerMisbehaved::InvalidKeyShare))?; - Ok(self - .priv_key - .diffie_hellman(&peer_array.into()) - .as_ref() - .into()) - } - - fn pub_key(&self) -> &[u8] { - self.pub_key.as_bytes() - } - - fn group(&self) -> rustls::NamedGroup { - X25519.name() - } -} - -macro_rules! impl_kx { - ($name:ident, $kx_name:ty, $secret:ty, $public_key:ty) => { - paste! { - - #[derive(Debug)] - #[allow(non_camel_case_types)] - pub struct $name; - - impl crypto::SupportedKxGroup for $name { - fn name(&self) -> rustls::NamedGroup { - $kx_name - } - - fn start(&self) -> Result, rustls::Error> { - let priv_key = $secret::random(&mut rand_core::OsRng); - let pub_key: $public_key = (&priv_key).into(); - Ok(Box::new([<$name KeyExchange>] { - priv_key, - pub_key: pub_key.to_sec1_bytes(), - })) - } - } - - #[allow(non_camel_case_types)] - pub struct [<$name KeyExchange>] { - priv_key: $secret, - pub_key: Box<[u8]>, - } - - impl crypto::ActiveKeyExchange for [<$name KeyExchange>] { - fn complete( - self: Box<[<$name KeyExchange>]>, - peer: &[u8], - ) -> Result { - let their_pub = $public_key::from_sec1_bytes(peer) - .map_err(|_| rustls::Error::from(rustls::PeerMisbehaved::InvalidKeyShare))?; - Ok(self - .priv_key - .diffie_hellman(&their_pub) - .raw_secret_bytes() - .as_slice() - .into()) - } - - fn pub_key(&self) -> &[u8] { - &self.pub_key - } - - fn group(&self) -> rustls::NamedGroup { - $name.name() - } - } - } - }; -} - -impl_kx! {SecP256R1, rustls::NamedGroup::secp256r1, p256::ecdh::EphemeralSecret, p256::PublicKey} -impl_kx! {SecP384R1, rustls::NamedGroup::secp384r1, p384::ecdh::EphemeralSecret, p384::PublicKey} - -pub const ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[&X25519, &SecP256R1, &SecP384R1]; +use rustls::crypto::SupportedKxGroup; + +pub const ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[ + #[cfg(feature = "kx-x448")] + &x448::X448, + #[cfg(feature = "kx-x25519")] + &x25519::X25519, + #[cfg(feature = "kx-p256")] + &nist::SEC_P256_R1, + #[cfg(feature = "kx-p384")] + &nist::SEC_P384_R1, + #[cfg(feature = "kx-p521")] + &nist::SEC_P521_R1, +]; + +#[cfg(feature = "kx-nist")] +pub mod nist; + +#[cfg(feature = "kx-x25519")] +pub mod x25519; + +#[cfg(feature = "kx-x448")] +pub mod x448; diff --git a/src/kx/nist.rs b/src/kx/nist.rs new file mode 100644 index 0000000..56229b9 --- /dev/null +++ b/src/kx/nist.rs @@ -0,0 +1,125 @@ +#[cfg(feature = "alloc")] +use alloc::{boxed::Box, string::ToString}; +use core::fmt::Debug; +use core::marker::PhantomData; + +use crypto::{ActiveKeyExchange, SharedSecret, SupportedKxGroup}; +use elliptic_curve::{ + Curve, CurveArithmetic, PublicKey, + ecdh::EphemeralSecret, + point::PointCompression, + sec1::{FromEncodedPoint, ToEncodedPoint}, +}; +use rand_core::OsRng; +use rustls::{Error, NamedGroup, crypto}; +use sec1::point::ModulusSize; + +/// Errors that can occur in NIST key exchange +#[derive(Debug, thiserror::Error)] +pub enum NistKxError { + /// Failed to generate private key + #[error("failed to generate private key: {0}")] + KeyGenerationFailed(rand_core::OsError), +} + +impl From for NistKxError { + fn from(e: rand_core::OsError) -> Self { + Self::KeyGenerationFailed(e) + } +} + +impl From for rustls::Error { + fn from(e: NistKxError) -> Self { + rustls::Error::General(e.to_string()) + } +} + +pub trait NistCurve: Curve + CurveArithmetic + PointCompression { + const NAMED_GROUP: NamedGroup; +} + +#[derive(Debug)] +pub struct NistKxGroup(PhantomData) +where + C: NistCurve; + +impl NistKxGroup +where + C: NistCurve, +{ + const DEFAULT: Self = Self(PhantomData); +} + +impl SupportedKxGroup for NistKxGroup +where + ::AffinePoint: FromEncodedPoint + ToEncodedPoint, + ::FieldBytesSize: ModulusSize, + C: NistCurve, +{ + fn name(&self) -> NamedGroup { + C::NAMED_GROUP + } + + fn start(&self) -> Result, Error> { + let priv_key = EphemeralSecret::::try_from_rng(&mut OsRng).map_err(NistKxError::from)?; + + Ok(Box::new(NistKeyExchange:: { + pub_key: priv_key.public_key().to_sec1_bytes(), + priv_key, + })) + } +} + +#[allow(non_camel_case_types)] +pub struct NistKeyExchange +where + C: NistCurve, +{ + priv_key: EphemeralSecret, + pub_key: Box<[u8]>, +} + +impl ActiveKeyExchange for NistKeyExchange +where + ::AffinePoint: FromEncodedPoint + ToEncodedPoint, + ::FieldBytesSize: ModulusSize, + C: NistCurve, +{ + fn complete(self: Box, peer: &[u8]) -> Result { + let their_pub = PublicKey::::from_sec1_bytes(peer) + .map_err(|_| rustls::PeerMisbehaved::InvalidKeyShare)?; + Ok(self + .priv_key + .diffie_hellman(&their_pub) + .raw_secret_bytes() + .as_slice() + .into()) + } + + fn pub_key(&self) -> &[u8] { + &self.pub_key + } + + fn group(&self) -> NamedGroup { + C::NAMED_GROUP + } +} + +macro_rules! impl_nist_curve { + ($ty:ty, $named_group:expr, $const_name:ident) => { + impl NistCurve for $ty { + const NAMED_GROUP: NamedGroup = $named_group; + } + + pub const $const_name: NistKxGroup<$ty> = NistKxGroup::DEFAULT; + }; +} + +#[cfg(feature = "kx-p256")] +impl_nist_curve!(::p256::NistP256, NamedGroup::secp256r1, SEC_P256_R1); + +#[cfg(feature = "kx-p384")] +impl_nist_curve!(::p384::NistP384, NamedGroup::secp384r1, SEC_P384_R1); + +#[cfg(feature = "kx-p521")] +impl_nist_curve!(::p521::NistP521, NamedGroup::secp521r1, SEC_P521_R1); diff --git a/src/kx/x25519.rs b/src/kx/x25519.rs new file mode 100644 index 0000000..cb14203 --- /dev/null +++ b/src/kx/x25519.rs @@ -0,0 +1,47 @@ +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +use crypto::{SharedSecret, SupportedKxGroup}; +use rustls::crypto::{self, ActiveKeyExchange}; +use x25519_dalek::{EphemeralSecret, PublicKey}; + +#[derive(Debug)] +pub struct X25519; + +impl crypto::SupportedKxGroup for X25519 { + fn name(&self) -> rustls::NamedGroup { + rustls::NamedGroup::X25519 + } + + fn start(&self) -> Result, rustls::Error> { + let priv_key = EphemeralSecret::random(); + let pub_key = PublicKey::from(&priv_key); + Ok(Box::new(X25519KeyExchange { priv_key, pub_key })) + } +} + +pub struct X25519KeyExchange { + priv_key: EphemeralSecret, + pub_key: PublicKey, +} + +impl ActiveKeyExchange for X25519KeyExchange { + fn complete(self: Box, peer: &[u8]) -> Result { + let peer_array: [u8; 32] = peer + .try_into() + .map_err(|_| rustls::PeerMisbehaved::InvalidKeyShare)?; + Ok(self + .priv_key + .diffie_hellman(&peer_array.into()) + .as_ref() + .into()) + } + + fn pub_key(&self) -> &[u8] { + self.pub_key.as_bytes() + } + + fn group(&self) -> rustls::NamedGroup { + X25519.name() + } +} diff --git a/src/kx/x448.rs b/src/kx/x448.rs new file mode 100644 index 0000000..f0973ff --- /dev/null +++ b/src/kx/x448.rs @@ -0,0 +1,53 @@ +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +use crypto::{SharedSecret, SupportedKxGroup}; +use rustls::crypto::{self, ActiveKeyExchange}; + +#[derive(Debug)] +pub struct X448; + +impl crypto::SupportedKxGroup for X448 { + fn name(&self) -> rustls::NamedGroup { + rustls::NamedGroup::X448 + } + + fn start(&self) -> Result, rustls::Error> { + let mut priv_key = [0u8; 56]; + + getrandom::fill(&mut priv_key) + .map_err(|_| rustls::Error::FailedToGetRandomBytes)?; + let priv_key: x448::Secret = priv_key.into(); + let pub_key = x448::PublicKey::from(&priv_key); + + Ok(Box::new(X448KeyExchange { priv_key, pub_key })) + } +} + +pub struct X448KeyExchange { + priv_key: x448::Secret, + pub_key: x448::PublicKey, +} + +impl ActiveKeyExchange for X448KeyExchange { + fn complete(self: Box, peer: &[u8]) -> Result { + Ok(self + .priv_key + .as_diffie_hellman( + &x448::PublicKey::from_bytes(peer) + .ok_or(rustls::PeerMisbehaved::InvalidKeyShare)?, + ) + .ok_or(rustls::PeerMisbehaved::InvalidKeyShare)? + .as_bytes() + .as_ref() + .into()) + } + + fn pub_key(&self) -> &[u8] { + self.pub_key.as_bytes() + } + + fn group(&self) -> rustls::NamedGroup { + X448.name() + } +} diff --git a/src/lib.rs b/src/lib.rs index 1b108b8..551eeb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,11 +27,6 @@ unused_lifetimes )] -//! # Usage -//! -//! See [`examples-xsmall`](https://github.com/RustCrypto/rustls-rustcrypto/tree/master/examples-xsmall) -//! for a usage example. - #[cfg(not(feature = "alloc"))] compile_error!("Rustls currently does not support alloc-less environments"); @@ -41,13 +36,10 @@ extern crate alloc; #[cfg(feature = "alloc")] use alloc::sync::Arc; -use rustls::crypto::{ - CipherSuiteCommon, CryptoProvider, GetRandomFailed, KeyProvider, SecureRandom, -}; -use rustls::{CipherSuite, SupportedCipherSuite, Tls13CipherSuite}; - -#[cfg(feature = "tls12")] -use rustls::SignatureScheme; +use pki_types::PrivateKeyDer; +use rustls::SupportedCipherSuite; +use rustls::crypto::{CryptoProvider, GetRandomFailed, KeyProvider, SecureRandom}; +use rustls::sign::SigningKey; #[derive(Debug)] pub struct Provider; @@ -63,209 +55,68 @@ pub fn provider() -> CryptoProvider { } impl SecureRandom for Provider { - fn fill(&self, bytes: &mut [u8]) -> Result<(), GetRandomFailed> { - use rand_core::RngCore; - rand_core::OsRng - .try_fill_bytes(bytes) - .map_err(|_| GetRandomFailed) + fn fill(&self, #[allow(unused_variables)] bytes: &mut [u8]) -> Result<(), GetRandomFailed> { + feature_eval_expr!( + [feature = "rand"], + { + use rand_core::TryRngCore; + rand_core::OsRng + .try_fill_bytes(bytes) + .map_err(|_| GetRandomFailed) + }, + else Err(GetRandomFailed) + ) } } impl KeyProvider for Provider { fn load_private_key( &self, - key_der: pki_types::PrivateKeyDer<'static>, - ) -> Result, rustls::Error> { - sign::any_supported_type(&key_der) + #[allow(unused_variables)] key_der: PrivateKeyDer<'static>, + ) -> Result, rustls::Error> { + feature_eval_expr!( + [feature = "sign"], + sign::any_supported_type(&key_der).map_err(Into::into), + else Err(rustls::Error::General("no key providers supported".into())) + ) } } -#[cfg(feature = "tls12")] -const TLS12_ECDSA_SCHEMES: [SignatureScheme; 4] = [ - SignatureScheme::ECDSA_NISTP256_SHA256, - SignatureScheme::ECDSA_NISTP384_SHA384, - SignatureScheme::ECDSA_NISTP521_SHA512, - SignatureScheme::ED25519, -]; - -#[cfg(feature = "tls12")] -const TLS12_RSA_SCHEMES: [SignatureScheme; 6] = [ - SignatureScheme::RSA_PKCS1_SHA256, - SignatureScheme::RSA_PKCS1_SHA384, - SignatureScheme::RSA_PKCS1_SHA512, - SignatureScheme::RSA_PSS_SHA256, - SignatureScheme::RSA_PSS_SHA384, - SignatureScheme::RSA_PSS_SHA512, -]; - -#[cfg(feature = "tls12")] -pub const TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&rustls::Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - hash_provider: hash::SHA256, - confidentiality_limit: u64::MAX, - }, - kx: rustls::crypto::KeyExchangeAlgorithm::ECDHE, - sign: &TLS12_ECDSA_SCHEMES, - aead_alg: &aead::gcm::Tls12Aes128Gcm, - prf_provider: &rustls::crypto::tls12::PrfUsingHmac(hmac::SHA256), - }); - -#[cfg(feature = "tls12")] -pub const TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&rustls::Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - hash_provider: hash::SHA384, - confidentiality_limit: u64::MAX, - }, - kx: rustls::crypto::KeyExchangeAlgorithm::ECDHE, - sign: &TLS12_ECDSA_SCHEMES, - prf_provider: &rustls::crypto::tls12::PrfUsingHmac(hmac::SHA384), - aead_alg: &aead::gcm::Tls12Aes256Gcm, - }); - -#[cfg(feature = "tls12")] -pub const TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&rustls::Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - hash_provider: hash::SHA256, - confidentiality_limit: u64::MAX, - }, - prf_provider: &rustls::crypto::tls12::PrfUsingHmac(hmac::SHA256), - kx: rustls::crypto::KeyExchangeAlgorithm::ECDHE, - sign: &TLS12_ECDSA_SCHEMES, - aead_alg: &aead::chacha20::Chacha20Poly1305, - }); - -#[cfg(feature = "tls12")] -const TLS_ECDHE_ECDSA_SUITES: &[SupportedCipherSuite] = &[ - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, -]; - -#[cfg(feature = "tls12")] -pub const TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&rustls::Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - hash_provider: hash::SHA256, - confidentiality_limit: u64::MAX, - }, - kx: rustls::crypto::KeyExchangeAlgorithm::ECDHE, - sign: &TLS12_RSA_SCHEMES, - aead_alg: &aead::gcm::Tls12Aes128Gcm, - prf_provider: &rustls::crypto::tls12::PrfUsingHmac(hmac::SHA256), - }); - -#[cfg(feature = "tls12")] -pub const TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&rustls::Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - hash_provider: hash::SHA384, - confidentiality_limit: u64::MAX, - }, - kx: rustls::crypto::KeyExchangeAlgorithm::ECDHE, - sign: &TLS12_RSA_SCHEMES, - prf_provider: &rustls::crypto::tls12::PrfUsingHmac(hmac::SHA384), - aead_alg: &aead::gcm::Tls12Aes256Gcm, - }); - -#[cfg(feature = "tls12")] -pub const TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls12(&rustls::Tls12CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - hash_provider: hash::SHA256, - confidentiality_limit: u64::MAX, - }, - kx: rustls::crypto::KeyExchangeAlgorithm::ECDHE, - sign: &TLS12_RSA_SCHEMES, - prf_provider: &rustls::crypto::tls12::PrfUsingHmac(hmac::SHA256), - aead_alg: &aead::chacha20::Chacha20Poly1305, - }); - -#[cfg(feature = "tls12")] -const TLS_ECDHE_RSA_SUITES: &[SupportedCipherSuite] = &[ - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, -]; - -#[cfg(feature = "tls12")] -const TLS12_SUITES: &[SupportedCipherSuite] = misc::const_concat_slices!( +pub const ALL_CIPHER_SUITES: &[SupportedCipherSuite] = misc::const_concat_slices!( SupportedCipherSuite, - TLS_ECDHE_ECDSA_SUITES, - TLS_ECDHE_RSA_SUITES + feature_slice!([feature = "tls12"], tls12::suites::TLS12_SUITES), + tls13::suites::TLS13_SUITES ); -#[cfg(not(feature = "tls12"))] -const TLS12_SUITES: &[SupportedCipherSuite] = &[]; - -pub const TLS13_AES_128_GCM_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls13(&Tls13CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS13_AES_128_GCM_SHA256, - hash_provider: hash::SHA256, - confidentiality_limit: u64::MAX, - }, - hkdf_provider: &rustls::crypto::tls13::HkdfUsingHmac(hmac::SHA256), - aead_alg: &aead::gcm::Tls13Aes128Gcm, - quic: None, - }); - -pub const TLS13_AES_256_GCM_SHA384: SupportedCipherSuite = - SupportedCipherSuite::Tls13(&Tls13CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS13_AES_256_GCM_SHA384, - hash_provider: hash::SHA384, - confidentiality_limit: u64::MAX, - }, - hkdf_provider: &rustls::crypto::tls13::HkdfUsingHmac(hmac::SHA384), - aead_alg: &aead::gcm::Tls13Aes256Gcm, - quic: None, - }); +#[cfg(feature = "aead")] +pub mod aead; +#[cfg(feature = "hash")] +pub mod hash; +#[cfg(feature = "hash")] +pub mod hmac; +#[cfg(feature = "kx")] +pub mod kx; +pub mod misc; +#[cfg(feature = "sign")] +pub mod sign; +#[cfg(feature = "tls12")] +pub mod tls12; +pub mod tls13; +#[cfg(feature = "verify")] +pub mod verify; -const TLS13_AES_SUITES: &[SupportedCipherSuite] = - &[TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384]; +#[cfg(feature = "quic")] +pub mod quic; -pub const TLS13_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = - SupportedCipherSuite::Tls13(&Tls13CipherSuite { - common: CipherSuiteCommon { - suite: CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, - hash_provider: hash::SHA256, - confidentiality_limit: u64::MAX, - }, - hkdf_provider: &rustls::crypto::tls13::HkdfUsingHmac(hmac::SHA256), - aead_alg: &aead::chacha20::Chacha20Poly1305, - quic: None, - }); +#[cfg(feature = "ticketer")] +pub mod ticketer; -const TLS13_SUITES: &[SupportedCipherSuite] = misc::const_concat_slices!( - SupportedCipherSuite, - TLS13_AES_SUITES, - &[TLS13_CHACHA20_POLY1305_SHA256] +const _: () = assert!( + !ALL_CIPHER_SUITES.is_empty(), + "At least one cipher suite should be enabled" ); -static ALL_CIPHER_SUITES: &[SupportedCipherSuite] = misc::const_concat_slices!( - SupportedCipherSuite, - if cfg!(feature = "tls12") { - TLS12_SUITES - } else { - &[] - }, - TLS13_SUITES, +const _: () = assert!( + !kx::ALL_KX_GROUPS.is_empty(), + "At least one key exchange algorithm should be enabled" ); - -mod aead; -mod hash; -mod hmac; -mod kx; -mod misc; -pub mod quic; -pub mod sign; -mod verify; diff --git a/src/misc.rs b/src/misc.rs index 8e25563..2130fca 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -1,36 +1,88 @@ #[macro_export] macro_rules! const_concat_slices { - ($ty:ty, $a:expr, $b:expr $(,)*) => {{ - const A: &[$ty] = $a; - const B: &[$ty] = $b; - const __LEN: usize = A.len() + B.len(); - const __CONCATENATED: &[$ty; __LEN] = &{ - let mut out: [$ty; __LEN] = if __LEN == 0 { - unsafe { core::mem::transmute([0u8; core::mem::size_of::<$ty>() * __LEN]) } - } else if A.len() == 0 { - [B[0]; __LEN] - } else { - [A[0]; __LEN] - }; - let mut i = 0; - while i < A.len() { - out[i] = A[i]; - i += 1; + ($ty:ty, $($s:expr),* $(,)*) => { + const { + use ::core::mem::{MaybeUninit, transmute}; + const TOTAL_LEN: usize = $(const {const VALUE: &[$ty] = $s; VALUE.len()} + )* 0; + const SLICES: &[&[$ty]] = &[$($s),*]; + + let mut out: [MaybeUninit<$ty>; TOTAL_LEN] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut offset = 0; + let mut slice_idx = 0; + while slice_idx < SLICES.len() { + let slice = SLICES[slice_idx]; + let mut i = 0; + while i < slice.len() { + out[offset] = MaybeUninit::new(slice[i]); + offset += 1; + i += 1; + } + slice_idx += 1; } - i = 0; - while i < B.len() { - out[i + A.len()] = B[i]; - i += 1; + &unsafe { transmute::<[MaybeUninit<$ty>; TOTAL_LEN], [$ty; TOTAL_LEN]>(out) } + } + }; +} + +#[macro_export] +macro_rules! feature_eval_expr { + ( + [$($cfg:tt)*], + $on_true:expr, + else $on_false:expr + ) => { + { + #[cfg($($cfg)*)] + { + $on_true } - out + #[cfg(not($($cfg)*))] + { + $on_false + } + } + }; +} + +#[macro_export] +macro_rules! feature_slice { + ( + [$($cfg:tt)*], + $slice:expr + ) => { $crate::feature_eval_expr!([$($cfg)*], $slice, else &[]) }; +} + +#[macro_export] +macro_rules! tls13_cipher_suite { + ($name:ident, $suite:expr, $hash:expr, $hkdf:expr, $aead:expr, $quic:expr) => { + pub const $name: Tls13CipherSuite = Tls13CipherSuite { + common: CipherSuiteCommon { + suite: $suite, + hash_provider: $hash, + confidentiality_limit: u64::MAX, + }, + hkdf_provider: &$hkdf, + aead_alg: $aead, + quic: $quic, }; + }; +} - __CONCATENATED - }}; - ($ty:ty, $a:expr, $b:expr, $($c:expr), + $(,)* ) => {{ - const CON: &[$ty] = const_concat_slices!($ty, $a, $b); - const_concat_slices!($ty, CON, $($c), +) - }} +#[macro_export] +macro_rules! tls12_ecdhe_cipher_suite { + ($name:ident, $suite:expr, $hash:expr, $prf:expr, $sign:expr, $aead:expr) => { + pub const $name: Tls12CipherSuite = Tls12CipherSuite { + common: CipherSuiteCommon { + suite: $suite, + hash_provider: $hash, + confidentiality_limit: u64::MAX, + }, + kx: KeyExchangeAlgorithm::ECDHE, + sign: $sign, + aead_alg: $aead, + prf_provider: &$prf, + }; + }; } pub(crate) use const_concat_slices; diff --git a/src/quic.rs b/src/quic.rs index f849835..a712b10 100644 --- a/src/quic.rs +++ b/src/quic.rs @@ -1,142 +1,445 @@ -#![allow(clippy::duplicate_mod)] - #[cfg(feature = "alloc")] -use alloc::boxed::Box; +use alloc::{boxed::Box, string::ToString}; + +use crate::aead::{DecryptBufferAdapter, EncryptBufferAdapter}; +use aead::{AeadCore, AeadInOut, KeyInit}; +use enum_dispatch::enum_dispatch; +use rustls::Error; +use rustls::crypto::cipher::{AeadKey, Iv, Nonce}; +use rustls::quic; +use typenum::Unsigned; + +#[cfg(feature = "aes-ccm")] +use crate::aead::aes::Aes128Ccm; +#[cfg(feature = "aes-gcm")] +use crate::aead::aes::{Aes128Gcm, Aes256Gcm}; +#[cfg(feature = "chacha20")] +use chacha20::{ + ChaCha20, + cipher::{KeyIvInit, StreamCipher}, +}; +#[cfg(feature = "chacha20poly1305")] +use chacha20poly1305::ChaCha20Poly1305; +#[cfg(feature = "aes")] +use cipher::BlockCipherEncrypt; +#[cfg(feature = "chacha20")] +use cipher::StreamCipherSeek; + +/// Errors that can occur in QUIC operations +#[derive(Debug, thiserror::Error)] +pub enum QuicError { + /// Failed to convert sample to block + #[error("failed to convert sample to block: {0}")] + SampleToBlock(#[from] core::array::TryFromSliceError), + + /// Failed to convert encrypted block to mask + #[error("failed to convert encrypted block to mask: {0}")] + BlockToMask(core::array::TryFromSliceError), + + /// Failed to convert first 4 bytes of sample to counter + #[error("failed to convert first 4 bytes of sample to counter: {0}")] + SampleToCounter(core::array::TryFromSliceError), + + /// Invalid ChaCha20 nonce length + #[error("invalid ChaCha20 nonce length: {0}")] + InvalidNonceLength(core::array::TryFromSliceError), + + /// ChaCha20 seek failed + #[cfg(feature = "chacha20")] + #[error("ChaCha20 seek failed: {0}")] + SeekFailed(#[from] cipher::StreamCipherError), + + /// ChaCha20 keystream failed + #[cfg(feature = "chacha20")] + #[error("ChaCha20 keystream failed: {0}")] + KeystreamFailed(cipher::StreamCipherError), + + /// Invalid AES key + #[cfg(feature = "aes")] + #[error("invalid AES key: {0}")] + InvalidAesKey(#[from] cipher::InvalidLength), + + /// Invalid ChaCha20-Poly1305 key + #[cfg(feature = "chacha20")] + #[error("invalid ChaCha20-Poly1305 key: {0}")] + InvalidChaCha20Key(core::array::TryFromSliceError), + + /// Invalid QUIC header protection mask length + #[error("invalid QUIC header protection mask length")] + InvalidMaskLength, + + /// Invalid QUIC header protection packet number length + #[error("invalid QUIC header protection packet number length")] + InvalidPacketNumberLength, + + /// Invalid nonce for AEAD operation + #[error("invalid nonce for AEAD operation: {0}")] + InvalidNonce(core::array::TryFromSliceError), + + #[error("Encryption failed: {0}")] + Encrypt(aead::Error), + + #[error("Decryption failed: {0}")] + Decrypt(aead::Error), +} + +impl From for rustls::Error { + fn from(e: QuicError) -> Self { + match e { + QuicError::Encrypt(_) => rustls::Error::EncryptError, + QuicError::Decrypt(_) => rustls::Error::DecryptError, + _ => rustls::Error::General(e.to_string()), + } + } +} + +trait HasHeaderKey { + #[allow(clippy::new_ret_no_self)] + fn new(key: AeadKey) -> Result; +} + +#[cfg(feature = "aes-gcm")] +impl HasHeaderKey for Aes128Gcm { + fn new(key: AeadKey) -> Result { + Ok(HeaderProtectionKey::Aes128Ecb(aes::Aes128::new_from_slice( + key.as_ref(), + )?)) + } +} + +#[cfg(feature = "aes-gcm")] +impl HasHeaderKey for Aes256Gcm { + fn new(key: AeadKey) -> Result { + Ok(HeaderProtectionKey::Aes256Ecb(aes::Aes256::new_from_slice( + key.as_ref(), + )?)) + } +} + +#[cfg(feature = "aes-ccm")] +impl HasHeaderKey for Aes128Ccm { + fn new(key: AeadKey) -> Result { + Ok(HeaderProtectionKey::Aes128Ecb(aes::Aes128::new_from_slice( + key.as_ref(), + )?)) + } +} + +#[cfg(feature = "chacha20poly1305")] +impl HasHeaderKey for ChaCha20Poly1305 { + fn new(key: AeadKey) -> Result { + let key = chacha20::Key::try_from(key.as_ref()).map_err(QuicError::InvalidChaCha20Key)?; + Ok(HeaderProtectionKey::ChaCha20(key)) + } +} + +#[enum_dispatch(MaskSample)] +#[allow(clippy::large_enum_variant)] +pub enum HeaderProtectionKey { + #[cfg(feature = "aes")] + Aes128Ecb(aes::Aes128), + #[cfg(feature = "aes")] + Aes256Ecb(aes::Aes256), + #[cfg(feature = "chacha20")] + ChaCha20(chacha20::Key), +} + +#[enum_dispatch] +trait MaskSample { + fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5], QuicError>; +} + +// 5.4.3. AES-Based Header Protection +// This section defines the packet protection algorithm for AEAD_AES_128_GCM, AEAD_AES_128_CCM, and AEAD_AES_256_GCM. AEAD_AES_128_GCM and AEAD_AES_128_CCM use 128-bit AES in Electronic Codebook (ECB) mode. +// AEAD_AES_256_GCM uses 256-bit AES in ECB mode. AES is defined in [AES]. + +// This algorithm samples 16 bytes from the packet ciphertext. This value is used as the input to AES-ECB. In pseudocode, the header protection function is defined as: + +// header_protection(hp_key, sample): +// mask = AES-ECB(hp_key, sample) +#[cfg(feature = "aes")] +impl MaskSample for aes::Aes128 { + fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5], QuicError> { + let mut block = (&sample[..16]) + .try_into() + .map_err(QuicError::SampleToBlock)?; + + self.encrypt_block(&mut block); + block[..5].try_into().map_err(QuicError::BlockToMask) + } +} + +#[cfg(feature = "aes")] +impl MaskSample for aes::Aes256 { + fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5], QuicError> { + let mut block = (&sample[..16]) + .try_into() + .map_err(QuicError::SampleToBlock)?; + + self.encrypt_block(&mut block); + block[..5].try_into().map_err(QuicError::BlockToMask) + } +} + +#[cfg(feature = "chacha20")] +impl MaskSample for chacha20::Key { + fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5], QuicError> { + // 5.4.4. ChaCha20-Based Header Protection + // When AEAD_CHACHA20_POLY1305 is in use, header protection uses the raw ChaCha20 function as defined in Section 2.4 of [CHACHA]. This uses a 256-bit key and 16 bytes sampled from the packet protection output. + // + // The first 4 bytes of the sampled ciphertext are the block counter. A ChaCha20 implementation could take a 32-bit integer in place of a byte sequence, in which case, the byte sequence is interpreted as a little-endian value. + // + // The remaining 12 bytes are used as the nonce. A ChaCha20 implementation might take an array of three 32-bit integers in place of a byte sequence, in which case, the nonce bytes are interpreted as a sequence of 32-bit little-endian integers. + // + // The encryption mask is produced by invoking ChaCha20 to protect 5 zero bytes. In pseudocode, the header protection function is defined as: + // + // header_protection(hp_key, sample): + // counter = sample[0..3] + // nonce = sample[4..15] + // mask = ChaCha20(hp_key, counter, nonce, {0,0,0,0,0}) + + let counter = u32::from_le_bytes( + sample[0..4] + .try_into() + .map_err(QuicError::SampleToCounter)?, + ); + let nonce = &sample[4..16]; + let mut chacha = ChaCha20::new( + self, + nonce.try_into().map_err(QuicError::InvalidNonceLength)?, + ); + + chacha.try_seek(counter).map_err(QuicError::SeekFailed)?; + + let mut mask = [0u8; 5]; + chacha + .try_apply_keystream_b2b(&[0u8; 5], &mut mask) + .map_err(QuicError::KeystreamFailed)?; + + Ok(mask) + } +} + +trait XorInPlace { + fn xor_in_place( + &self, + sample: &[u8], + first: &mut u8, + packet_number: &mut [u8], + masked: bool, + ) -> Result<(), QuicError>; +} + +impl XorInPlace for T { + fn xor_in_place( + &self, + sample: &[u8], + first: &mut u8, + packet_number: &mut [u8], + masked: bool, + ) -> Result<(), QuicError> { + // This implements "Header Protection Application" almost verbatim. + // + + let mask = self.new_mask(sample)?; + + // The `unwrap()` will not panic because `new_mask` returns a + // non-empty result. + let (first_mask, pn_mask) = mask.split_first().ok_or(QuicError::InvalidMaskLength)?; + + // It is OK for the `mask` to be longer than `packet_number`, + // but a valid `packet_number` will never be longer than `mask`. + if packet_number.len() > pn_mask.len() { + return Err(QuicError::InvalidPacketNumberLength); + } + + // Infallible from this point on. Before this point, `first` and + // `packet_number` are unchanged. -use aead::AeadCore; -use chacha20poly1305::{AeadInPlace, KeyInit, KeySizeUser}; -use crypto_common::typenum::Unsigned; -use rustls::crypto::cipher::{self, AeadKey, Iv}; -use rustls::{quic, Error, Tls13CipherSuite}; + const LONG_HEADER_FORM: u8 = 0x80; + let bits = match *first & LONG_HEADER_FORM == LONG_HEADER_FORM { + true => 0x0f, // Long header: 4 bits masked + false => 0x1f, // Short header: 5 bits masked + }; -#[allow(dead_code)] // TODO -pub struct HeaderProtectionKey(AeadKey); + let first_plain = *first ^ if masked { first_mask & bits } else { 0 }; + let pn_len = (first_plain & 0x03) as usize + 1; -impl HeaderProtectionKey { - pub fn new(key: AeadKey) -> Self { - Self(key) + *first ^= first_mask & bits; + for (dst, m) in packet_number.iter_mut().zip(pn_mask).take(pn_len) { + *dst ^= m; + } + + Ok(()) } } impl quic::HeaderProtectionKey for HeaderProtectionKey { fn encrypt_in_place( &self, - _sample: &[u8], - _first: &mut u8, - _packet_number: &mut [u8], + sample: &[u8], + first: &mut u8, + packet_number: &mut [u8], ) -> Result<(), Error> { - todo!() + self.xor_in_place(sample, first, packet_number, false) + .map_err(Into::into) } fn decrypt_in_place( &self, - _sample: &[u8], - _first: &mut u8, - _packet_number: &mut [u8], + sample: &[u8], + first: &mut u8, + packet_number: &mut [u8], ) -> Result<(), Error> { - todo!() + self.xor_in_place(sample, first, packet_number, true) + .map_err(Into::into) } #[inline] fn sample_len(&self) -> usize { - todo!() + 16 } } -pub struct PacketKey { +pub(crate) struct PacketKey { + /// Encrypts or decrypts a packet's payload + key: A, /// Computes unique nonces for each packet iv: Iv, - - /// The cipher suite used for this packet key - #[allow(dead_code)] - suite: &'static Tls13CipherSuite, - - crypto: chacha20poly1305::ChaCha20Poly1305, + /// Confidentiality limit (see [`quic::PacketKey::confidentiality_limit`]) + confidentiality_limit: u64, + /// Integrity limit (see [`quic::PacketKey::integrity_limit`]) + integrity_limit: u64, } -impl PacketKey { - pub fn new(suite: &'static Tls13CipherSuite, key: AeadKey, iv: Iv) -> Self { +impl PacketKey +where + A: KeyInit + AeadInOut + Send + Sync, +{ + pub(crate) fn new( + key: AeadKey, + iv: Iv, + confidentiality_limit: u64, + integrity_limit: u64, + ) -> Self { Self { + key: A::new_from_slice(key.as_ref()).expect("Invalid key length for AEAD algorithm"), iv, - suite, - crypto: chacha20poly1305::ChaCha20Poly1305::new_from_slice(key.as_ref()) - .expect("key should be valid"), + confidentiality_limit, + integrity_limit, } } } -impl quic::PacketKey for PacketKey { +impl quic::PacketKey for PacketKey +where + A: AeadInOut + Send + Sync, +{ fn encrypt_in_place( &self, packet_number: u64, - aad: &[u8], + header: &[u8], payload: &mut [u8], ) -> Result { - let nonce = cipher::Nonce::new(&self.iv, packet_number).0; - - let tag = self - .crypto - .encrypt_in_place_detached(&nonce.into(), aad, payload) - .map_err(|_| rustls::Error::EncryptError)?; - Ok(quic::Tag::from(tag.as_ref())) - } - - /// Decrypt a QUIC packet - /// - /// Takes the packet `header`, which is used as the additional authenticated - /// data, and the `payload`, which includes the authentication tag. - /// - /// If the return value is `Ok`, the decrypted payload can be found in - /// `payload`, up to the length found in the return value. + let nonce_aead: cipher::Array::NonceSize> = + aead::Nonce::::try_from(&Nonce::new(&self.iv, packet_number).0[..]) + .map_err(QuicError::InvalidNonce)?; + + // Create a buffer with the payload + let mut buffer = EncryptBufferAdapter::Vec(payload.to_vec()); + + self.key + .encrypt_in_place(&nonce_aead, header, &mut buffer) + .map_err(QuicError::Encrypt)?; + + let buffer = buffer.as_ref(); + + // Copy the encrypted payload back + payload.copy_from_slice(&buffer[..payload.len()]); + + // Extract the tag from the end + Ok(quic::Tag::from(&buffer[payload.len()..])) + } + fn decrypt_in_place<'a>( &self, packet_number: u64, - aad: &[u8], + header: &[u8], payload: &'a mut [u8], ) -> Result<&'a [u8], Error> { - let mut payload_ = payload.to_vec(); - let payload_len = payload_.len(); - let nonce = chacha20poly1305::Nonce::from(cipher::Nonce::new(&self.iv, packet_number).0); + let nonce_aead = aead::Nonce::::try_from(&Nonce::new(&self.iv, packet_number).0[..]) + .map_err(QuicError::InvalidNonce)?; - self.crypto - .decrypt_in_place(&nonce, aad, &mut payload_) - .map_err(|_| rustls::Error::DecryptError)?; + // Append the tag to the payload for decryption + if payload.len() < A::TagSize::USIZE { + return Err(QuicError::Decrypt(aead::Error).into()); + } + let plaintext_len = payload.len() - A::TagSize::USIZE; - // Unfortunately the lifetime bound on decrypt_in_place sucks - payload.copy_from_slice(&payload_); + self.key + .decrypt_in_place( + &nonce_aead, + header, + &mut DecryptBufferAdapter::Slice(payload.into()), + ) + .map_err(QuicError::Decrypt)?; - let plain_len = payload_len - self.tag_len(); - Ok(&payload[..plain_len]) + Ok(&payload[..plaintext_len]) } /// Tag length for the underlying AEAD algorithm #[inline] fn tag_len(&self) -> usize { - ::TagSize::to_usize() + A::TagSize::USIZE } - fn integrity_limit(&self) -> u64 { - 1 << 36 + /// Confidentiality limit (see [`quic::PacketKey::confidentiality_limit`]) + fn confidentiality_limit(&self) -> u64 { + self.confidentiality_limit } - fn confidentiality_limit(&self) -> u64 { - u64::MAX + /// Integrity limit (see [`quic::PacketKey::integrity_limit`]) + fn integrity_limit(&self) -> u64 { + self.integrity_limit } } -#[allow(dead_code)] // TODO -pub struct KeyBuilder(AeadKey); +pub(crate) struct QuicCrypto { + pub(crate) packet_alg: core::marker::PhantomData, + pub(crate) confidentiality_limit: u64, + pub(crate) integrity_limit: u64, +} -impl rustls::quic::Algorithm for KeyBuilder { - fn packet_key(&self, _key: AeadKey, _iv: Iv) -> Box { - todo!() +impl QuicCrypto { + pub const DEFAULT: Self = Self { + packet_alg: core::marker::PhantomData, + confidentiality_limit: u64::MAX, + integrity_limit: u64::MAX, + }; +} + +impl quic::Algorithm for QuicCrypto +where + A: AeadCore + AeadInOut + KeyInit + HasHeaderKey + Send + Sync + 'static, +{ + fn packet_key(&self, key: AeadKey, iv: Iv) -> Box { + Box::new(PacketKey::::new( + key, + iv, + self.confidentiality_limit, + self.integrity_limit, + )) } fn header_protection_key(&self, key: AeadKey) -> Box { - Box::new(HeaderProtectionKey::new(key)) + Box::new(::new(key).expect("Invalid key length for header protection")) } fn aead_key_len(&self) -> usize { - chacha20poly1305::ChaCha20Poly1305::key_size() + A::key_size() + } + + fn fips(&self) -> bool { + false // RustCrypto doesn't have FIPS certification } } diff --git a/src/sign.rs b/src/sign.rs index e6109f5..9e7ed17 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -1,41 +1,33 @@ #[cfg(feature = "alloc")] -use alloc::{sync::Arc, vec::Vec}; +use alloc::{string::ToString, sync::Arc, vec::Vec}; use core::marker::PhantomData; -use self::ecdsa::{EcdsaSigningKeyP256, EcdsaSigningKeyP384}; -use self::eddsa::Ed25519SigningKey; -use self::rsa::RsaSigningKey; - use pki_types::PrivateKeyDer; use rustls::sign::{Signer, SigningKey}; use rustls::{Error, SignatureScheme}; -use signature::{RandomizedSigner, SignatureEncoding}; +use signature::SignatureEncoding; -#[derive(Debug)] -pub struct GenericRandomizedSigner -where - S: SignatureEncoding, - T: RandomizedSigner, -{ - _marker: PhantomData, - key: Arc, - scheme: SignatureScheme, +/// Errors that can occur in the signing module +#[derive(Debug, thiserror::Error)] +pub enum SignError { + /// Signing operation failed + #[error("signing failed: {0}")] + SigningFailed(signature::Error), + + /// Key type not supported + #[error("key type not supported")] + NotSupported, } -impl Signer for GenericRandomizedSigner -where - S: SignatureEncoding + Send + Sync + core::fmt::Debug, - T: RandomizedSigner + Send + Sync + core::fmt::Debug, -{ - fn sign(&self, message: &[u8]) -> Result, Error> { - self.key - .try_sign_with_rng(&mut rand_core::OsRng, message) - .map_err(|_| rustls::Error::General("signing failed".into())) - .map(|sig: S| sig.to_vec()) +impl From for SignError { + fn from(e: signature::Error) -> Self { + Self::SigningFailed(e) } +} - fn scheme(&self) -> SignatureScheme { - self.scheme +impl From for rustls::Error { + fn from(e: SignError) -> Self { + rustls::Error::General(e.to_string()) } } @@ -58,8 +50,9 @@ where fn sign(&self, message: &[u8]) -> Result, Error> { self.key .try_sign(message) - .map_err(|_| rustls::Error::General("signing failed".into())) + .map_err(SignError::SigningFailed) .map(|sig: S| sig.to_vec()) + .map_err(Into::into) } fn scheme(&self) -> SignatureScheme { @@ -72,11 +65,24 @@ where /// # Errors /// /// Returns an error if the key couldn't be decoded. -pub fn any_supported_type(der: &PrivateKeyDer<'_>) -> Result, rustls::Error> { - RsaSigningKey::try_from(der) - .map(|x| Arc::new(x) as _) - .or_else(|_| any_ecdsa_type(der)) - .or_else(|_| any_eddsa_type(der)) +#[allow(unused_variables)] +pub fn any_supported_type(der: &PrivateKeyDer<'_>) -> Result, SignError> { + #[cfg(feature = "sign-rsa")] + if let Ok(key) = rsa::RsaSigningKey::try_from(der) { + return Ok(Arc::new(key) as _); + } + + #[cfg(feature = "sign-ecdsa-nist")] + if let Ok(key) = any_ecdsa_type(der) { + return Ok(key); + } + + #[cfg(feature = "sign-eddsa")] + if let Ok(key) = any_eddsa_type(der) { + return Ok(key); + } + + Err(SignError::NotSupported) } /// Extract any supported ECDSA key from the given DER input. @@ -84,10 +90,19 @@ pub fn any_supported_type(der: &PrivateKeyDer<'_>) -> Result /// # Errors /// /// Returns an error if the key couldn't be decoded. -pub fn any_ecdsa_type(der: &PrivateKeyDer<'_>) -> Result, rustls::Error> { - let p256 = |_| EcdsaSigningKeyP256::try_from(der).map(|x| Arc::new(x) as _); - let p384 = |_| EcdsaSigningKeyP384::try_from(der).map(|x| Arc::new(x) as _); - p256(()).or_else(p384) +#[allow(unused_variables)] +#[cfg(feature = "sign-ecdsa-nist")] +pub fn any_ecdsa_type(der: &PrivateKeyDer<'_>) -> Result, SignError> { + #[cfg(all(feature = "der", feature = "ecdsa-p256"))] + if let Ok(key) = ecdsa::nist::EcdsaSigningKeyP256::try_from(der) { + return Ok(Arc::new(key) as _); + } + #[cfg(all(feature = "der", feature = "ecdsa-p384"))] + if let Ok(key) = ecdsa::nist::EcdsaSigningKeyP384::try_from(der) { + return Ok(Arc::new(key) as _); + } + + Err(SignError::NotSupported) } /// Extract any supported EDDSA key from the given DER input. @@ -95,11 +110,28 @@ pub fn any_ecdsa_type(der: &PrivateKeyDer<'_>) -> Result, ru /// # Errors /// /// Returns an error if the key couldn't be decoded. -pub fn any_eddsa_type(der: &PrivateKeyDer<'_>) -> Result, rustls::Error> { - // TODO: Add support for Ed448 - Ed25519SigningKey::try_from(der).map(|x| Arc::new(x) as _) +#[allow(unused_variables)] +#[cfg(feature = "sign-eddsa")] +pub fn any_eddsa_type(der: &PrivateKeyDer<'_>) -> Result, SignError> { + #[cfg(all(feature = "der", feature = "eddsa-ed25519"))] + if let Ok(key) = eddsa::ed25519::Ed25519SigningKey::try_from(der) { + return Ok(Arc::new(key) as _); + } + + #[cfg(all(feature = "der", feature = "eddsa-ed448"))] + if let Ok(key) = eddsa::ed448::Ed448SigningKey::try_from(der) { + return Ok(Arc::new(key) as _); + } + + Err(SignError::NotSupported) } +#[cfg(feature = "ecdsa")] pub mod ecdsa; +#[cfg(feature = "eddsa")] pub mod eddsa; +#[cfg(feature = "rsa")] pub mod rsa; + +#[cfg(feature = "rand")] +pub mod rand; diff --git a/src/sign/ecdsa.rs b/src/sign/ecdsa.rs index 3f4a2bc..1931d6e 100644 --- a/src/sign/ecdsa.rs +++ b/src/sign/ecdsa.rs @@ -1,66 +1,2 @@ -#[cfg(feature = "alloc")] -use alloc::{boxed::Box, format, sync::Arc}; -use core::marker::PhantomData; - -use paste::paste; -use pkcs8::DecodePrivateKey; -use pki_types::PrivateKeyDer; -use rustls::sign::SigningKey; -use rustls::{SignatureAlgorithm, SignatureScheme}; -use sec1::DecodeEcPrivateKey; - -macro_rules! impl_ecdsa { - ($name: ident, $scheme: expr, $signing_key: ty, $signature: ty) => { - paste! { - #[derive(Debug)] - pub struct [] { - key: Arc<$signing_key>, - scheme: SignatureScheme, - } - - impl TryFrom<&PrivateKeyDer<'_>> for [] { - type Error = rustls::Error; - - fn try_from(value: &PrivateKeyDer<'_>) -> Result { - let pkey = match value { - PrivateKeyDer::Pkcs8(der) => { - $signing_key::from_pkcs8_der(der.secret_pkcs8_der()).map_err(|e| format!("failed to decrypt private key: {e}")) - }, - PrivateKeyDer::Sec1(sec1) => { - $signing_key::from_sec1_der(sec1.secret_sec1_der()).map_err(|e| format!("failed to decrypt private key: {e}")) - }, - PrivateKeyDer::Pkcs1(_) => Err(format!("ECDSA does not support PKCS#1 key")), - _ => Err("not supported".into()), - }; - pkey.map(|kp| { - Self { - key: Arc::new(kp), - scheme: $scheme, - } - }).map_err(rustls::Error::General) - } - } - - impl SigningKey for [] { - fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { - if offered.contains(&self.scheme) { - Some(Box::new(super::GenericRandomizedSigner::<$signature, _> { - _marker: PhantomData, - key: self.key.clone(), - scheme: self.scheme, - })) - } else { - None - } - } - - fn algorithm(&self) -> SignatureAlgorithm { - SignatureAlgorithm::ECDSA - } - } - } - }; -} - -impl_ecdsa! {P256, SignatureScheme::ECDSA_NISTP256_SHA256, p256::ecdsa::SigningKey, p256::ecdsa::DerSignature} -impl_ecdsa! {P384, SignatureScheme::ECDSA_NISTP384_SHA384, p384::ecdsa::SigningKey, p384::ecdsa::DerSignature} +#[cfg(feature = "sign-ecdsa-nist")] +pub mod nist; diff --git a/src/sign/ecdsa/nist.rs b/src/sign/ecdsa/nist.rs new file mode 100644 index 0000000..361ac66 --- /dev/null +++ b/src/sign/ecdsa/nist.rs @@ -0,0 +1,160 @@ +#[cfg(feature = "alloc")] +use alloc::{boxed::Box, string::ToString, sync::Arc}; +use core::fmt::Debug; +use core::marker::PhantomData; + +use crate::sign::rand::GenericRandomizedSigner; +use rustls::sign::SigningKey; +use rustls::{SignatureAlgorithm, SignatureScheme}; + +#[cfg(feature = "der")] +use ::pki_types::PrivateKeyDer; + +/// Errors that can occur when loading an ECDSA private key +#[derive(Debug, thiserror::Error)] +pub enum EcdsaKeyError { + /// Failed to decode PKCS#8 private key + #[cfg(feature = "pkcs8")] + #[error("failed to decrypt PKCS#8 private key: {0}")] + Pkcs8(::pkcs8::Error), + + /// Failed to decode SEC1 private key + #[cfg(feature = "sec1")] + #[error("failed to decrypt SEC1 private key: {0}")] + Sec1(#[from] ::sec1::Error), + + /// ECDSA does not support PKCS#1 keys + #[error("ECDSA does not support PKCS#1 key")] + Pkcs1NotSupported, + + /// Key format not supported + #[error("key format not supported")] + NotSupported, +} + +#[cfg(feature = "pkcs8")] +impl From<::pkcs8::Error> for EcdsaKeyError { + fn from(e: ::pkcs8::Error) -> Self { + Self::Pkcs8(e) + } +} + +impl From for rustls::Error { + fn from(e: EcdsaKeyError) -> Self { + rustls::Error::General(e.to_string()) + } +} + +trait EcdsaKey: Sized { + const SCHEME: SignatureScheme; +} + +#[cfg(all(feature = "pkcs8", not(feature = "sec1")))] +trait DecodePrivateKey: ::pkcs8::DecodePrivateKey {} + +#[cfg(all(feature = "sec1", not(feature = "pkcs8")))] +trait DecodePrivateKey: ::sec1::DecodeEcPrivateKey {} + +#[cfg(all(feature = "pkcs8", feature = "sec1"))] +trait DecodePrivateKey: ::pkcs8::DecodePrivateKey + ::sec1::DecodeEcPrivateKey {} + +#[cfg(feature = "der")] +impl TryFrom<&PrivateKeyDer<'_>> + for EcdsaSigningKey +where + SecretKey: Debug + DecodePrivateKey, + SigningKey: EcdsaKey + Send + Sync + 'static + From, + Signature: Send + Sync + 'static, +{ + type Error = rustls::Error; + + fn try_from(value: &PrivateKeyDer<'_>) -> Result { + let pkey = match value { + #[cfg(feature = "pkcs8")] + PrivateKeyDer::Pkcs8(der) => { + SecretKey::from_pkcs8_der(der.secret_pkcs8_der()).map_err(Into::into) + } + #[cfg(feature = "sec1")] + PrivateKeyDer::Sec1(sec1) => { + SecretKey::from_sec1_der(sec1.secret_sec1_der()).map_err(Into::into) + } + PrivateKeyDer::Pkcs1(_) => Err(EcdsaKeyError::Pkcs1NotSupported), + _ => Err(EcdsaKeyError::NotSupported), + }; + pkey.map(|kp| Self { + key: Arc::new(kp.into()), + scheme: SigningKey::SCHEME, + _phantom: PhantomData, + _phantom_sk: PhantomData, + }) + .map_err(Into::into) + } +} + +#[derive(Debug)] +pub struct EcdsaSigningKey { + key: Arc, + scheme: SignatureScheme, + _phantom: PhantomData, + _phantom_sk: PhantomData, +} + +impl SigningKey for EcdsaSigningKey +where + SecretKey: Debug + Send + Sync, + SK: Send + Sync + 'static + Debug + ecdsa::signature::RandomizedSigner, + SIG: Send + Sync + 'static + Debug + ecdsa::signature::SignatureEncoding, +{ + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { + if offered.contains(&self.scheme) { + Some(Box::new(GenericRandomizedSigner:: { + _marker: PhantomData, + key: self.key.clone(), + scheme: self.scheme, + })) + } else { + None + } + } + + fn algorithm(&self) -> SignatureAlgorithm { + SignatureAlgorithm::ECDSA + } +} + +macro_rules! impl_ecdsa_curve { + ($curve:ident, $scheme:expr, $type_name:ident) => { + pub type $type_name = EcdsaSigningKey< + ::$curve::SecretKey, + ::$curve::ecdsa::SigningKey, + ::$curve::ecdsa::DerSignature, + >; + + impl EcdsaKey for ::$curve::ecdsa::SigningKey { + const SCHEME: SignatureScheme = $scheme; + } + + impl DecodePrivateKey for ::$curve::SecretKey {} + }; +} + +#[cfg(all(feature = "ecdsa-p256", feature = "hash-sha256"))] +impl_ecdsa_curve!( + p256, + SignatureScheme::ECDSA_NISTP256_SHA256, + EcdsaSigningKeyP256 +); + +#[cfg(all(feature = "ecdsa-p384", feature = "hash-sha384"))] +impl_ecdsa_curve!( + p384, + SignatureScheme::ECDSA_NISTP384_SHA384, + EcdsaSigningKeyP384 +); + +#[cfg(all(feature = "ecdsa-p521", feature = "hash-sha512"))] +impl_ecdsa_curve!( + p521, + SignatureScheme::ECDSA_NISTP521_SHA512, + EcdsaSigningKeyP521 +); diff --git a/src/sign/eddsa.rs b/src/sign/eddsa.rs index e6f0d1a..fb7e140 100644 --- a/src/sign/eddsa.rs +++ b/src/sign/eddsa.rs @@ -1,57 +1,5 @@ -#[cfg(feature = "alloc")] -use alloc::{boxed::Box, format, string::ToString, sync::Arc}; -use core::marker::PhantomData; +#[cfg(feature = "ed25519")] +pub mod ed25519; -use pkcs8::DecodePrivateKey; -use pki_types::PrivateKeyDer; -use rustls::sign::{Signer, SigningKey}; -use rustls::{SignatureAlgorithm, SignatureScheme}; -use sec1::DecodeEcPrivateKey; - -#[derive(Debug)] -pub struct Ed25519SigningKey { - key: Arc, - scheme: SignatureScheme, -} - -impl TryFrom<&PrivateKeyDer<'_>> for Ed25519SigningKey { - type Error = rustls::Error; - - fn try_from(value: &PrivateKeyDer<'_>) -> Result { - let pkey = match value { - PrivateKeyDer::Pkcs8(der) => { - ed25519_dalek::SigningKey::from_pkcs8_der(der.secret_pkcs8_der()) - .map_err(|e| format!("failed to decrypt private key: {e}")) - } - PrivateKeyDer::Sec1(sec1) => { - ed25519_dalek::SigningKey::from_sec1_der(sec1.secret_sec1_der()) - .map_err(|e| format!("failed to decrypt private key: {e}")) - } - PrivateKeyDer::Pkcs1(_) => Err("ED25519 does not support PKCS#1 key".to_string()), - _ => Err("not supported".into()), - }; - pkey.map(|kp| Self { - key: Arc::new(kp), - scheme: SignatureScheme::ED25519, - }) - .map_err(rustls::Error::General) - } -} - -impl SigningKey for Ed25519SigningKey { - fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { - if offered.contains(&self.scheme) { - Some(Box::new(super::GenericSigner { - _marker: PhantomData, - key: self.key.clone(), - scheme: self.scheme, - })) - } else { - None - } - } - - fn algorithm(&self) -> SignatureAlgorithm { - SignatureAlgorithm::ED25519 - } -} +#[cfg(feature = "ed448")] +pub mod ed448; diff --git a/src/sign/eddsa/ed25519.rs b/src/sign/eddsa/ed25519.rs new file mode 100644 index 0000000..394fc43 --- /dev/null +++ b/src/sign/eddsa/ed25519.rs @@ -0,0 +1,90 @@ +#[cfg(feature = "alloc")] +use alloc::{boxed::Box, string::ToString, sync::Arc}; + +use crate::sign::GenericSigner; +use core::marker::PhantomData; +use ed25519_dalek::SigningKey; +use rustls::{SignatureAlgorithm, SignatureScheme, sign::Signer}; + +#[cfg(feature = "der")] +use pki_types::PrivateKeyDer; + +/// Errors that can occur when loading an Ed25519 private key +#[derive(Debug, thiserror::Error)] +pub enum Ed25519KeyError { + /// Failed to decode PKCS#8 private key + #[cfg(feature = "pkcs8")] + #[error("failed to decrypt PKCS#8 private key: {0}")] + Pkcs8(::pkcs8::Error), + + /// ED25519 does not support SEC-1 keys + #[error("ED25519 does not support SEC-1 key")] + Sec1NotSupported, + + /// ED25519 does not support PKCS#1 keys + #[error("ED25519 does not support PKCS#1 key")] + Pkcs1NotSupported, + + /// Key format not supported + #[error("key format not supported")] + NotSupported, +} + +#[cfg(feature = "pkcs8")] +impl From<::pkcs8::Error> for Ed25519KeyError { + fn from(e: ::pkcs8::Error) -> Self { + Self::Pkcs8(e) + } +} + +impl From for rustls::Error { + fn from(e: Ed25519KeyError) -> Self { + rustls::Error::General(e.to_string()) + } +} + +#[derive(Debug)] +pub struct Ed25519SigningKey(Arc); + +#[cfg(feature = "der")] +impl TryFrom<&PrivateKeyDer<'_>> for Ed25519SigningKey { + type Error = Ed25519KeyError; + + fn try_from(value: &PrivateKeyDer<'_>) -> Result { + let pkey = match value { + #[cfg(feature = "pkcs8")] + PrivateKeyDer::Pkcs8(der) => { + use pkcs8::DecodePrivateKey; + SigningKey::from_pkcs8_der(der.secret_pkcs8_der()).map_err(Into::into) + } + + // (chat log from tony in zulip) + // Per RFC 8410, only PKCS#8 is supported for ED25519 keys + // https://datatracker.ietf.org/doc/html/rfc8410#section-7 + // So no SEC 1 support for ED25519 (despite we do have it compile before?!) + PrivateKeyDer::Sec1(_) => Err(Ed25519KeyError::Sec1NotSupported), + PrivateKeyDer::Pkcs1(_) => Err(Ed25519KeyError::Pkcs1NotSupported), + _ => Err(Ed25519KeyError::NotSupported), + }; + pkey.map(|kp| Self(Arc::new(kp))) + } +} + +impl rustls::sign::SigningKey for Ed25519SigningKey { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { + const SCHEME: SignatureScheme = SignatureScheme::ED25519; + if offered.contains(&SCHEME) { + Some(Box::new(GenericSigner { + _marker: PhantomData, + key: self.0.clone(), + scheme: SCHEME, + })) + } else { + None + } + } + + fn algorithm(&self) -> SignatureAlgorithm { + SignatureAlgorithm::ED25519 + } +} diff --git a/src/sign/eddsa/ed448.rs b/src/sign/eddsa/ed448.rs new file mode 100644 index 0000000..10c8f38 --- /dev/null +++ b/src/sign/eddsa/ed448.rs @@ -0,0 +1,134 @@ +#[cfg(feature = "alloc")] +use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec}; + +use ed448_goldilocks::{Signature, SigningKey}; +use rustls::{SignatureAlgorithm, SignatureScheme, sign::Signer}; +use signature::{SignatureEncoding, Signer as SignatureSigner}; + +#[cfg(feature = "der")] +use pki_types::PrivateKeyDer; + +/// Errors that can occur when loading an Ed448 private key +#[derive(Debug, thiserror::Error)] +pub enum Ed448KeyError { + /// Failed to decode PKCS#8 private key + #[cfg(feature = "pkcs8")] + #[error("failed to decrypt PKCS#8 private key: {0}")] + Pkcs8(::pkcs8::Error), + + /// ED448 does not support SEC-1 keys + #[error("ED448 does not support SEC-1 key")] + Sec1NotSupported, + + /// ED448 does not support PKCS#1 keys + #[error("ED448 does not support PKCS#1 key")] + Pkcs1NotSupported, + + /// Key format not supported + #[error("key format not supported")] + NotSupported, +} + +#[cfg(feature = "pkcs8")] +impl From<::pkcs8::Error> for Ed448KeyError { + fn from(e: ::pkcs8::Error) -> Self { + Self::Pkcs8(e) + } +} + +impl From for rustls::Error { + fn from(e: Ed448KeyError) -> Self { + rustls::Error::General(e.to_string()) + } +} + +// Wrapper for Ed448 signature to implement SignatureEncoding +#[derive(Debug, Clone)] +pub struct Ed448Signature(Signature); + +impl SignatureEncoding for Ed448Signature { + type Repr = [u8; Signature::BYTE_SIZE]; // Ed448 signature is 114 bytes + + fn to_bytes(&self) -> Self::Repr { + self.0.to_bytes() + } + + fn encoded_len(&self) -> usize { + Signature::BYTE_SIZE + } +} + +impl TryFrom<&[u8]> for Ed448Signature { + type Error = signature::Error; + + fn try_from(bytes: &[u8]) -> Result { + Signature::from_slice(bytes).map(Self) + } +} + +impl From for [u8; Signature::BYTE_SIZE] { + fn from(sig: Ed448Signature) -> Self { + sig.0.to_bytes() + } +} + +#[derive(Debug)] +pub struct Ed448SigningKey(Arc); + +#[cfg(feature = "der")] +impl TryFrom<&PrivateKeyDer<'_>> for Ed448SigningKey { + type Error = rustls::Error; + + fn try_from(value: &PrivateKeyDer<'_>) -> Result { + let pkey = match value { + #[cfg(feature = "pkcs8")] + PrivateKeyDer::Pkcs8(der) => { + use pkcs8::DecodePrivateKey; + SigningKey::from_pkcs8_der(der.secret_pkcs8_der()).map_err(Into::into) + } + + // Per RFC 8410, only PKCS#8 is supported for ED448 keys + // https://datatracker.ietf.org/doc/html/rfc8410#section-7 + PrivateKeyDer::Sec1(_) => Err(Ed448KeyError::Sec1NotSupported), + PrivateKeyDer::Pkcs1(_) => Err(Ed448KeyError::Pkcs1NotSupported), + _ => Err(Ed448KeyError::NotSupported), + }; + pkey.map(|kp| Self(Arc::new(kp))).map_err(Into::into) + } +} + +impl rustls::sign::SigningKey for Ed448SigningKey { + fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { + const SCHEME: SignatureScheme = SignatureScheme::ED448; + if offered.contains(&SCHEME) { + Some(Box::new(Ed448Signer { + key: self.0.clone(), + scheme: SCHEME, + })) + } else { + None + } + } + + fn algorithm(&self) -> SignatureAlgorithm { + SignatureAlgorithm::ED448 + } +} + +// Custom signer for Ed448 +#[derive(Debug)] +pub struct Ed448Signer { + key: Arc, + scheme: SignatureScheme, +} + +impl Signer for Ed448Signer { + fn sign(&self, message: &[u8]) -> Result, rustls::Error> { + let sig = self.key.sign(message); + Ok(sig.to_bytes().to_vec()) + } + + fn scheme(&self) -> SignatureScheme { + self.scheme + } +} diff --git a/src/sign/rand.rs b/src/sign/rand.rs new file mode 100644 index 0000000..981378a --- /dev/null +++ b/src/sign/rand.rs @@ -0,0 +1,53 @@ +#[cfg(feature = "alloc")] +use alloc::{string::ToString, sync::Arc, vec::Vec}; +use core::marker::PhantomData; + +use rustls::sign::Signer; +use rustls::{Error, SignatureScheme}; +use signature::{RandomizedSigner, SignatureEncoding}; + +/// Error that occurs during signing +#[derive(Debug, thiserror::Error)] +#[error("signing failed: {0}")] +pub struct SigningError(signature::Error); + +impl From for SigningError { + fn from(e: signature::Error) -> Self { + Self(e) + } +} + +impl From for rustls::Error { + fn from(e: SigningError) -> Self { + rustls::Error::General(e.to_string()) + } +} + +#[derive(Debug)] +pub struct GenericRandomizedSigner +where + S: SignatureEncoding, + T: RandomizedSigner, +{ + pub(crate) _marker: PhantomData, + pub(crate) key: Arc, + pub(crate) scheme: SignatureScheme, +} + +impl Signer for GenericRandomizedSigner +where + S: SignatureEncoding + Send + Sync + core::fmt::Debug, + T: RandomizedSigner + Send + Sync + core::fmt::Debug, +{ + fn sign(&self, message: &[u8]) -> Result, Error> { + self.key + .try_sign_with_rng(&mut rand_core::OsRng, message) + .map_err(SigningError::from) + .map(|sig: S| sig.to_vec()) + .map_err(Into::into) + } + + fn scheme(&self) -> SignatureScheme { + self.scheme + } +} diff --git a/src/sign/rsa.rs b/src/sign/rsa.rs index ab27d36..50e5bda 100644 --- a/src/sign/rsa.rs +++ b/src/sign/rsa.rs @@ -1,40 +1,94 @@ #[cfg(feature = "alloc")] -use alloc::{boxed::Box, format, string::ToString, sync::Arc}; +use alloc::{boxed::Box, string::ToString, sync::Arc}; -use pkcs8::DecodePrivateKey; -use pki_types::PrivateKeyDer; -use rsa::pkcs1::DecodeRsaPrivateKey; use rsa::RsaPrivateKey; use rustls::sign::{Signer, SigningKey}; use rustls::{SignatureAlgorithm, SignatureScheme}; -use sha2::{Sha256, Sha384, Sha512}; + +#[cfg(feature = "hash-sha256")] +use sha2::Sha256; +#[cfg(feature = "hash-sha384")] +use sha2::Sha384; +#[cfg(feature = "hash-sha512")] +use sha2::Sha512; + +#[cfg(feature = "der")] +use pki_types::PrivateKeyDer; + +/// Errors that can occur when loading an RSA private key +#[derive(Debug, thiserror::Error)] +pub enum RsaKeyError { + /// Failed to decode PKCS#8 private key + #[cfg(feature = "pkcs8")] + #[error("failed to decrypt PKCS#8 private key: {0}")] + Pkcs8(::pkcs8::Error), + + /// Failed to decode PKCS#1 private key + #[cfg(all(feature = "pkcs8", feature = "pkcs1"))] + #[error("failed to decrypt PKCS#1 private key: {0}")] + Pkcs1(#[from] ::pkcs1::Error), + + /// RSA does not support SEC-1 keys + #[error("RSA does not support SEC-1 key")] + Sec1NotSupported, + + /// Key format not supported + #[error("key format not supported")] + NotSupported, +} + +#[cfg(feature = "pkcs8")] +impl From<::pkcs8::Error> for RsaKeyError { + fn from(e: ::pkcs8::Error) -> Self { + Self::Pkcs8(e) + } +} + +impl From for rustls::Error { + fn from(e: RsaKeyError) -> Self { + rustls::Error::General(e.to_string()) + } +} const ALL_RSA_SCHEMES: &[SignatureScheme] = &[ + #[cfg(all(feature = "rsa-pss", feature = "hash-sha512"))] SignatureScheme::RSA_PSS_SHA512, + #[cfg(all(feature = "rsa-pss", feature = "hash-sha384"))] SignatureScheme::RSA_PSS_SHA384, + #[cfg(all(feature = "rsa-pss", feature = "hash-sha256"))] SignatureScheme::RSA_PSS_SHA256, + #[cfg(all(feature = "rsa-pkcs1", feature = "hash-sha512"))] SignatureScheme::RSA_PKCS1_SHA512, + #[cfg(all(feature = "rsa-pkcs1", feature = "hash-sha384"))] SignatureScheme::RSA_PKCS1_SHA384, + #[cfg(all(feature = "rsa-pkcs1", feature = "hash-sha256"))] SignatureScheme::RSA_PKCS1_SHA256, ]; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct RsaSigningKey(RsaPrivateKey); +#[cfg(feature = "der")] impl TryFrom<&PrivateKeyDer<'_>> for RsaSigningKey { type Error = rustls::Error; fn try_from(value: &PrivateKeyDer<'_>) -> Result { let pkey = match value { - PrivateKeyDer::Pkcs8(der) => RsaPrivateKey::from_pkcs8_der(der.secret_pkcs8_der()) - .map_err(|e| format!("failed to decrypt private key: {e}")), - PrivateKeyDer::Pkcs1(der) => RsaPrivateKey::from_pkcs1_der(der.secret_pkcs1_der()) - .map_err(|e| format!("failed to decrypt private key: {e}")), - PrivateKeyDer::Sec1(_) => Err("RSA does not support SEC-1 key".to_string()), - _ => Err("not supported".into()), + #[cfg(feature = "pkcs8")] + PrivateKeyDer::Pkcs8(der) => { + use pkcs8::DecodePrivateKey; + RsaPrivateKey::from_pkcs8_der(der.secret_pkcs8_der()).map_err(Into::into) + } + #[cfg(all(feature = "pkcs8", feature = "pkcs1"))] + PrivateKeyDer::Pkcs1(der) => { + use pkcs1::DecodeRsaPrivateKey; + RsaPrivateKey::from_pkcs1_der(der.secret_pkcs1_der()).map_err(Into::into) + } + PrivateKeyDer::Sec1(_) => Err(RsaKeyError::Sec1NotSupported), + _ => Err(RsaKeyError::NotSupported), }; - pkey.map(Self).map_err(rustls::Error::General) + pkey.map(Self).map_err(Into::into) } } @@ -46,7 +100,7 @@ impl SigningKey for RsaSigningKey { .and_then(|&scheme| { macro_rules! signer { ($key:ty) => {{ - Some(Box::new(super::GenericRandomizedSigner { + Some(Box::new(super::rand::GenericRandomizedSigner { _marker: Default::default(), key: Arc::new(<$key>::new(self.0.clone())), scheme, @@ -55,17 +109,23 @@ impl SigningKey for RsaSigningKey { } match scheme { - SignatureScheme::RSA_PSS_SHA512 => signer! {rsa::pss::SigningKey::}, - SignatureScheme::RSA_PSS_SHA384 => signer! {rsa::pss::SigningKey::}, - SignatureScheme::RSA_PSS_SHA256 => signer! {rsa::pss::SigningKey::}, + #[cfg(all(feature = "rsa-pss", feature = "hash-sha512"))] + SignatureScheme::RSA_PSS_SHA512 => signer! {::rsa::pss::SigningKey::}, + #[cfg(all(feature = "rsa-pss", feature = "hash-sha384"))] + SignatureScheme::RSA_PSS_SHA384 => signer! {::rsa::pss::SigningKey::}, + #[cfg(all(feature = "rsa-pss", feature = "hash-sha256"))] + SignatureScheme::RSA_PSS_SHA256 => signer! {::rsa::pss::SigningKey::}, + #[cfg(all(feature = "rsa-pkcs1", feature = "hash-sha512"))] SignatureScheme::RSA_PKCS1_SHA512 => { - signer! {rsa::pkcs1v15::SigningKey::} + signer! {::rsa::pkcs1v15::SigningKey::} } + #[cfg(all(feature = "rsa-pkcs1", feature = "hash-sha384"))] SignatureScheme::RSA_PKCS1_SHA384 => { - signer! {rsa::pkcs1v15::SigningKey::} + signer! {::rsa::pkcs1v15::SigningKey::} } + #[cfg(all(feature = "rsa-pkcs1", feature = "hash-sha256"))] SignatureScheme::RSA_PKCS1_SHA256 => { - signer! {rsa::pkcs1v15::SigningKey::} + signer! {::rsa::pkcs1v15::SigningKey::} } _ => None, } diff --git a/src/ticketer.rs b/src/ticketer.rs new file mode 100644 index 0000000..40a3b60 --- /dev/null +++ b/src/ticketer.rs @@ -0,0 +1,159 @@ +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use alloc::sync::Arc; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +use core::fmt::{Debug, Formatter}; +use core::sync::atomic::{AtomicUsize, Ordering}; +use core::{fmt, time}; + +use aead::{AeadInOut, KeyInit}; +use elliptic_curve::subtle::ConstantTimeEq; +use rustls::crypto::GetRandomFailed; +use rustls::server::ProducesTickets; +use rustls::{Error, ticketer::TicketRotator}; + +#[cfg(feature = "chacha20poly1305")] +use chacha20poly1305::ChaCha20Poly1305; + +fn try_split_at(data: &[u8], at: usize) -> Option<(&[u8], &[u8])> { + if data.len() < at { + None + } else { + Some(data.split_at(at)) + } +} + +/// A concrete, safe ticket creation mechanism. +#[non_exhaustive] +pub struct Ticketer {} + +impl Ticketer { + #[allow(clippy::new_ret_no_self, clippy::missing_errors_doc)] + pub fn new() -> Result, Error> { + Ok(Arc::new(TicketRotator::new( + #[allow(clippy::cast_possible_truncation)] + { + time::Duration::from_secs(6 * 60 * 60).as_secs() as u32 + }, + || Ok(Box::new(AeadTicketProducer::new()?)), + )?)) + } +} + +struct AeadTicketProducer { + key: ChaCha20Poly1305, + key_name: [u8; 16], + maximum_ciphertext_len: AtomicUsize, +} + +impl AeadTicketProducer { + fn new() -> Result { + let mut key_bytes = [0u8; 32]; + getrandom::fill(&mut key_bytes).map_err(|_| GetRandomFailed)?; + + let key = ChaCha20Poly1305::new_from_slice(&key_bytes).map_err(|_| GetRandomFailed)?; + + let mut key_name = [0u8; 16]; + getrandom::fill(&mut key_name).map_err(|_| GetRandomFailed)?; + + Ok(Self { + key, + key_name, + maximum_ciphertext_len: AtomicUsize::new(0), + }) + } +} + +impl ProducesTickets for AeadTicketProducer { + fn enabled(&self) -> bool { + true + } + + fn lifetime(&self) -> u32 { + // this is not used, as this ticketer is only used via a `TicketRotator` + // that is responsible for defining and managing the lifetime of tickets. + 0 + } + + /// Encrypt `message` and return the ciphertext. + fn encrypt(&self, message: &[u8]) -> Option> { + // Random nonce, because a counter is a privacy leak. + let mut nonce_buf = [0u8; 12]; + getrandom::fill(&mut nonce_buf).ok()?; + let nonce = nonce_buf.into(); + + // ciphertext structure is: + // key_name: [u8; 16] + // nonce: [u8; 12] + // message: [u8, _] + // tag: [u8; 16] + + let mut ciphertext = + Vec::with_capacity(self.key_name.len() + nonce_buf.len() + message.len() + 16); + ciphertext.extend(self.key_name); + ciphertext.extend(nonce_buf); + ciphertext.extend(message); + let tag = self + .key + .encrypt_inout_detached( + &nonce, + &self.key_name, + (&mut ciphertext[self.key_name.len() + nonce_buf.len()..]).into(), + ) + .ok()?; + ciphertext.extend(tag); + + self.maximum_ciphertext_len + .fetch_max(ciphertext.len(), Ordering::SeqCst); + Some(ciphertext) + } + + /// Decrypt `ciphertext` and recover the original message. + fn decrypt(&self, ciphertext: &[u8]) -> Option> { + if ciphertext.len() > self.maximum_ciphertext_len.load(Ordering::SeqCst) { + return None; + } + + let (alleged_key_name, ciphertext) = try_split_at(ciphertext, self.key_name.len())?; + + let (nonce_bytes, ciphertext) = try_split_at(ciphertext, 12)?; + + // checking the key_name is the expected one, *and* then putting it into the + // additionally authenticated data is duplicative. this check quickly rejects + // tickets for a different ticketer (see `TicketRotator`), while including it + // in the AAD ensures it is authenticated independent of that check and that + // any attempted attack on the integrity such as [^1] must happen for each + // `key_label`, not over a population of potential keys. this approach + // is overall similar to [^2]. + // + // [^1]: https://eprint.iacr.org/2020/1491.pdf + // [^2]: "Authenticated Encryption with Key Identification", fig 6 + // + if ConstantTimeEq::ct_ne(&self.key_name[..], alleged_key_name).into() { + return None; + } + + let nonce = nonce_bytes.try_into().ok()?; + + let mut out = Vec::from(ciphertext); + let tag_vec = out.split_off(out.len() - 16); + let tag = tag_vec.try_into().ok()?; + + self.key + .decrypt_inout_detached(&nonce, alleged_key_name, (&mut out[..]).into(), &tag) + .ok()?; + let plain_len = out.len(); + out.truncate(plain_len); + + Some(out) + } +} + +impl Debug for AeadTicketProducer { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // Note: we deliberately omit the key from the debug output. + f.debug_struct("AeadTicketer").finish() + } +} diff --git a/src/tls12.rs b/src/tls12.rs new file mode 100644 index 0000000..3864c43 --- /dev/null +++ b/src/tls12.rs @@ -0,0 +1,3 @@ +#[cfg(feature = "aead")] +pub mod aead; +pub mod suites; diff --git a/src/tls12/aead.rs b/src/tls12/aead.rs new file mode 100644 index 0000000..8be18e7 --- /dev/null +++ b/src/tls12/aead.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "chacha20poly1305")] +pub mod chacha20; + +#[cfg(feature = "gcm")] +pub mod gcm; + +#[cfg(feature = "ccm")] +pub mod ccm; + +#[cfg(any(feature = "aes-gcm", feature = "aes-ccm"))] +pub(crate) mod explicit_nonce; diff --git a/src/tls12/aead/ccm.rs b/src/tls12/aead/ccm.rs new file mode 100644 index 0000000..e3fc468 --- /dev/null +++ b/src/tls12/aead/ccm.rs @@ -0,0 +1,13 @@ +use super::explicit_nonce::Tls12AeadAlgorithmWithExplicitNonce; + +macro_rules! impl_ccm { + ($name:ident, $type:ty) => { + pub const $name: &Tls12AeadAlgorithmWithExplicitNonce<$type> = + &Tls12AeadAlgorithmWithExplicitNonce::DEFAULT; + }; +} + +impl_ccm!(AES_128_CCM, crate::aead::aes::Aes128Ccm); +impl_ccm!(AES_256_CCM, crate::aead::aes::Aes256Ccm); +impl_ccm!(AES_128_CCM_8, crate::aead::aes::Aes128Ccm8); +impl_ccm!(AES_256_CCM_8, crate::aead::aes::Aes256Ccm8); diff --git a/src/tls12/aead/chacha20.rs b/src/tls12/aead/chacha20.rs new file mode 100644 index 0000000..0d8764b --- /dev/null +++ b/src/tls12/aead/chacha20.rs @@ -0,0 +1,107 @@ +use aead::AeadInOut; +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +use crate::aead::{DecryptBufferAdapter, EncryptBufferAdapter}; + +use ::chacha20poly1305::KeyInit; +use rustls::{ + ConnectionTrafficSecrets, + crypto::cipher::{ + self, AeadKey, InboundOpaqueMessage, InboundPlainMessage, Iv, KeyBlockShape, + MessageDecrypter, MessageEncrypter, OutboundOpaqueMessage, OutboundPlainMessage, + PrefixedPayload, Tls12AeadAlgorithm, UnsupportedOperationError, make_tls12_aad, + }, +}; + +pub const CHACHAPOLY1305_OVERHEAD: usize = 16; +pub struct ChaCha20Poly1305; + +pub struct Tls12AeadAlgorithmChacha20Poly1305Adapter(chacha20poly1305::ChaCha20Poly1305, Iv); + +impl Tls12AeadAlgorithm for ChaCha20Poly1305 { + fn encrypter(&self, key: AeadKey, iv: &[u8], _: &[u8]) -> Box { + Box::new(Tls12AeadAlgorithmChacha20Poly1305Adapter( + ::chacha20poly1305::ChaCha20Poly1305::new_from_slice(key.as_ref()) + .expect("key should be valid"), + Iv::copy(iv), + )) + } + + fn decrypter(&self, key: AeadKey, iv: &[u8]) -> Box { + Box::new(Tls12AeadAlgorithmChacha20Poly1305Adapter( + chacha20poly1305::ChaCha20Poly1305::new_from_slice(key.as_ref()) + .expect("key should be valid"), + Iv::copy(iv), + )) + } + + fn key_block_shape(&self) -> KeyBlockShape { + KeyBlockShape { + enc_key_len: 32, + fixed_iv_len: 12, + explicit_nonce_len: 0, + } + } + + fn extract_keys( + &self, + key: AeadKey, + iv: &[u8], + _explicit: &[u8], + ) -> Result { + Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { + key, + iv: Iv::copy(iv), + }) + } +} + +impl MessageEncrypter for Tls12AeadAlgorithmChacha20Poly1305Adapter { + fn encrypt( + &mut self, + m: OutboundPlainMessage<'_>, + seq: u64, + ) -> Result { + let mut payload = + PrefixedPayload::with_capacity(self.encrypted_payload_len(m.payload.len())); + + payload.extend_from_chunks(&m.payload); + + self.0 + .encrypt_in_place( + &cipher::Nonce::new(&self.1, seq).0.into(), + &make_tls12_aad(seq, m.typ, m.version, m.payload.len()), + &mut EncryptBufferAdapter::PrefixedPayload(&mut payload), + ) + .map(|_| OutboundOpaqueMessage::new(m.typ, m.version, payload)) + .map_err(|_| rustls::Error::EncryptError) + } + + fn encrypted_payload_len(&self, payload_len: usize) -> usize { + payload_len + CHACHAPOLY1305_OVERHEAD + } +} + +impl MessageDecrypter for Tls12AeadAlgorithmChacha20Poly1305Adapter { + fn decrypt<'a>( + &mut self, + mut m: InboundOpaqueMessage<'a>, + seq: u64, + ) -> Result, rustls::Error> { + self.0 + .decrypt_in_place( + &cipher::Nonce::new(&self.1, seq).0.into(), + &make_tls12_aad( + seq, + m.typ, + m.version, + m.payload.len() - CHACHAPOLY1305_OVERHEAD, + ), + &mut DecryptBufferAdapter::BorrowedPayload(&mut m.payload), + ) + .map_err(|_| rustls::Error::DecryptError)?; + + Ok(m.into_plain_message()) + } +} diff --git a/src/tls12/aead/explicit_nonce.rs b/src/tls12/aead/explicit_nonce.rs new file mode 100644 index 0000000..faa32b3 --- /dev/null +++ b/src/tls12/aead/explicit_nonce.rs @@ -0,0 +1,187 @@ +use core::marker::PhantomData; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +use ::aead::{AeadInOut, Nonce, Tag}; +use ::crypto_common::KeyInit; +use rustls::ConnectionTrafficSecrets; +use rustls::crypto::cipher::{ + self, AeadKey, InboundOpaqueMessage, InboundPlainMessage, KeyBlockShape, MessageDecrypter, + MessageEncrypter, OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, + Tls12AeadAlgorithm, make_tls12_aad, +}; +use typenum::Unsigned; + +/// Length of the explicit nonce in TLS 1.2 AEAD. +const EXPLICIT_NONCE_LEN: usize = 8; + +/// TLS 1.2 AEAD Encrypter. +/// Wraps an AEAD cipher and the initialization vector for encryption. +pub struct Tls12AeadEncrypterWithExplicitNonce { + /// The underlying AEAD cipher. + pub aead: A, + /// The initialization vector (12 bytes). + pub iv: [u8; 12], +} + +impl MessageEncrypter for Tls12AeadEncrypterWithExplicitNonce +where + A: AeadInOut + Send + Sync, +{ + fn encrypt( + &mut self, + m: OutboundPlainMessage<'_>, + seq: u64, + ) -> Result { + let total_len = self.encrypted_payload_len(m.payload.len()); + let mut payload = PrefixedPayload::with_capacity(total_len); + let aad = make_tls12_aad(seq, m.typ, m.version, m.payload.len()); + + let nonce = cipher::Nonce::new(&self.iv.into(), seq).0; + payload.extend_from_slice(&nonce.as_ref()[4..]); // explicit nonce + payload.extend_from_chunks(&m.payload); + + let tag = self + .aead + .encrypt_inout_detached( + &Nonce::::try_from(&nonce[..]).map_err(|_| rustls::Error::EncryptError)?, + &aad, + (&mut payload.as_mut()[EXPLICIT_NONCE_LEN..]).into(), + ) + .map_err(|_| rustls::Error::EncryptError)?; + payload.extend(&tag); + Ok(OutboundOpaqueMessage::new(m.typ, m.version, payload)) + } + + fn encrypted_payload_len(&self, payload_len: usize) -> usize { + payload_len + EXPLICIT_NONCE_LEN + A::TagSize::USIZE + } +} + +pub struct Tls12AeadDecrypterWithExplicitNonce { + /// The underlying AEAD cipher. + pub aead: A, + /// The decryption initialization vector (4 bytes). + pub dec_iv: [u8; 4], +} + +impl MessageDecrypter for Tls12AeadDecrypterWithExplicitNonce +where + A: AeadInOut + Send + Sync, +{ + fn decrypt<'a>( + &mut self, + mut m: InboundOpaqueMessage<'a>, + seq: u64, + ) -> Result, rustls::Error> { + macro_rules! nonce_tag_len { + () => {{ EXPLICIT_NONCE_LEN + A::TagSize::USIZE }}; + } + + // The payload must be large enough to hold the explicit nonce and the AEAD tag. + if m.payload.len() < nonce_tag_len!() { + return Err(rustls::Error::DecryptError); + } + // Calculate the length of the plaintext. + let plaintext_len = m.payload.len() - nonce_tag_len!(); + + // Split the payload into the ciphertext and tag. + if let Some((nonce, ciphertext_and_tag)) = + m.payload.split_at_mut_checked(EXPLICIT_NONCE_LEN) + && let Some((ciphertext, tag)) = ciphertext_and_tag.split_at_mut_checked(plaintext_len) + { + // Decrypt the ciphertext in place. + self.aead + .decrypt_inout_detached( + &Nonce::::from_iter([self.dec_iv.as_ref(), nonce].concat()), + &make_tls12_aad(seq, m.typ, m.version, plaintext_len), + ciphertext.into(), + &Tag::::try_from(&tag[..]).map_err(|_| rustls::Error::DecryptError)?, + ) + .map_err(|_| rustls::Error::DecryptError)?; + + // The plaintext is now at an offset in the payload buffer. We need to move it + // to the beginning of the buffer to conform to the `InboundPlainMessage` requirements. + m.payload + .copy_within(EXPLICIT_NONCE_LEN..EXPLICIT_NONCE_LEN + plaintext_len, 0); + m.payload.truncate(plaintext_len); + + Ok(m.into_plain_message()) + } else { + Err(rustls::Error::DecryptError) + } + } +} + +pub trait Extractor { + fn extract( + _key: AeadKey, + _iv: &[u8], + _explicit: &[u8], + ) -> Result { + Err(cipher::UnsupportedOperationError) + } +} + +impl Extractor for () {} + +#[derive(Default)] +pub struct Tls12AeadAlgorithmWithExplicitNonce { + _aead: PhantomData, + _extractor: PhantomData, +} + +impl Tls12AeadAlgorithmWithExplicitNonce { + pub const DEFAULT: Self = Self { + _aead: PhantomData, + _extractor: PhantomData, + }; +} + +impl Tls12AeadAlgorithm for Tls12AeadAlgorithmWithExplicitNonce +where + A: KeyInit + AeadInOut + Send + Sync + 'static, + E: Extractor + Send + Sync + 'static, +{ + fn encrypter( + &self, + key: AeadKey, + write_iv: &[u8], + explicit: &[u8], + ) -> Box { + Box::new(Tls12AeadEncrypterWithExplicitNonce:: { + aead: A::new_from_slice(key.as_ref()).expect("key should be valid"), + iv: { + let mut iv: [u8; 12] = [0; 12]; + iv[..4].copy_from_slice(write_iv); + iv[4..].copy_from_slice(explicit); + iv + }, + }) + } + + fn decrypter(&self, dec_key: AeadKey, dec_iv: &[u8]) -> Box { + Box::new(Tls12AeadDecrypterWithExplicitNonce:: { + aead: A::new_from_slice(dec_key.as_ref()).expect("key should be valid"), + dec_iv: dec_iv.try_into().expect("iv should be valid"), + }) + } + + fn key_block_shape(&self) -> KeyBlockShape { + KeyBlockShape { + enc_key_len: A::key_size(), + fixed_iv_len: 4, + explicit_nonce_len: EXPLICIT_NONCE_LEN, + } + } + + fn extract_keys( + &self, + key: AeadKey, + iv: &[u8], + explicit: &[u8], + ) -> Result { + E::extract(key, iv, explicit) + } +} diff --git a/src/tls12/aead/gcm.rs b/src/tls12/aead/gcm.rs new file mode 100644 index 0000000..0726214 --- /dev/null +++ b/src/tls12/aead/gcm.rs @@ -0,0 +1,38 @@ +use rustls::ConnectionTrafficSecrets; +use rustls::crypto::cipher::{self, AeadKey, Iv}; + +use super::explicit_nonce::{Extractor, Tls12AeadAlgorithmWithExplicitNonce}; + +macro_rules! tls12_gcm_aead { + ($const_name:ident, $extractor_name:ident, $variant:ident, $type:ty) => { + pub struct $extractor_name; + impl Extractor for $extractor_name { + fn extract( + key: AeadKey, + iv: &[u8], + _explicit: &[u8], + ) -> Result { + Ok(ConnectionTrafficSecrets::$variant { + key, + iv: Iv::copy(iv), + }) + } + } + + pub const $const_name: &Tls12AeadAlgorithmWithExplicitNonce<$type, $extractor_name> = + &Tls12AeadAlgorithmWithExplicitNonce::DEFAULT; + }; +} + +tls12_gcm_aead!( + AES_128_GCM, + Aes128GcmExtractor, + Aes128Gcm, + crate::aead::aes::Aes128Gcm +); +tls12_gcm_aead!( + AES_256_GCM, + Aes256GcmExtractor, + Aes256Gcm, + crate::aead::aes::Aes256Gcm +); diff --git a/src/tls12/suites.rs b/src/tls12/suites.rs new file mode 100644 index 0000000..c755275 --- /dev/null +++ b/src/tls12/suites.rs @@ -0,0 +1,18 @@ +use crate::feature_slice; +use crate::misc::const_concat_slices; +use rustls::SupportedCipherSuite; + +pub const TLS12_SUITES: &[SupportedCipherSuite] = const_concat_slices!( + SupportedCipherSuite, + feature_slice!([feature = "ecdsa"], ecdsa::TLS_ECDHE_ECDSA_SUITES), + feature_slice!( + [any(feature = "rsa-pss", feature = "rsa-pkcs1")], + rsa::TLS_ECDHE_RSA_SUITES + ) +); + +#[cfg(feature = "ecdsa")] +pub mod ecdsa; +#[cfg(any(feature = "rsa-pss", feature = "rsa-pkcs1"))] +pub mod rsa; +pub mod schemes; diff --git a/src/tls12/suites/ecdsa.rs b/src/tls12/suites/ecdsa.rs new file mode 100644 index 0000000..82a68a1 --- /dev/null +++ b/src/tls12/suites/ecdsa.rs @@ -0,0 +1,110 @@ +use rustls::SupportedCipherSuite; + +#[cfg(feature = "aead")] +use crate::tls12::suites::schemes::TLS12_ECDSA_SCHEMES; +#[cfg(feature = "aead")] +use crate::{hash, hmac, tls12_ecdhe_cipher_suite}; +#[cfg(feature = "aead")] +use rustls::crypto::{CipherSuiteCommon, KeyExchangeAlgorithm, tls12::PrfUsingHmac}; +#[cfg(feature = "aead")] +use rustls::{CipherSuite, Tls12CipherSuite}; + +#[cfg(feature = "aes-gcm")] +use crate::tls12::aead::gcm::{AES_128_GCM, AES_256_GCM}; + +#[cfg(all(feature = "aes-ccm", feature = "hash-sha256"))] +use crate::tls12::aead::ccm::{AES_128_CCM, AES_128_CCM_8, AES_256_CCM, AES_256_CCM_8}; + +#[cfg(all(feature = "chacha20poly1305", feature = "hash-sha256"))] +use crate::tls12::aead::chacha20::ChaCha20Poly1305; + +#[cfg(all(feature = "aes-gcm", feature = "hash-sha256"))] +tls12_ecdhe_cipher_suite!( + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + hash::SHA256, + PrfUsingHmac(hmac::SHA256), + TLS12_ECDSA_SCHEMES, + AES_128_GCM +); + +#[cfg(all(feature = "aes-gcm", feature = "hash-sha384"))] +tls12_ecdhe_cipher_suite!( + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + hash::SHA384, + PrfUsingHmac(hmac::SHA384), + TLS12_ECDSA_SCHEMES, + AES_256_GCM +); + +// https://ciphersuite.info/cs/TLS_ECDHE_ECDSA_WITH_AES_128_CCM/ +#[cfg(all(feature = "aes-ccm", feature = "hash-sha256"))] +tls12_ecdhe_cipher_suite!( + TLS_ECDHE_ECDSA_WITH_AES_128_CCM, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CCM, + hash::SHA256, + PrfUsingHmac(hmac::SHA256), + TLS12_ECDSA_SCHEMES, + AES_128_CCM +); + +// https://ciphersuite.info/cs/TLS_ECDHE_ECDSA_WITH_AES_256_CCM/ +#[cfg(all(feature = "aes-ccm", feature = "hash-sha256"))] +tls12_ecdhe_cipher_suite!( + TLS_ECDHE_ECDSA_WITH_AES_256_CCM, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CCM, + hash::SHA256, + PrfUsingHmac(hmac::SHA256), + TLS12_ECDSA_SCHEMES, + AES_256_CCM +); + +// https://ciphersuite.info/cs/TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8/ +#[cfg(all(feature = "aes-ccm", feature = "hash-sha256"))] +tls12_ecdhe_cipher_suite!( + TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + hash::SHA256, + PrfUsingHmac(hmac::SHA256), + TLS12_ECDSA_SCHEMES, + AES_128_CCM_8 +); + +// https://ciphersuite.info/cs/TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8/ +#[cfg(all(feature = "aes-ccm", feature = "hash-sha256"))] +tls12_ecdhe_cipher_suite!( + TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, + hash::SHA256, + PrfUsingHmac(hmac::SHA256), + TLS12_ECDSA_SCHEMES, + AES_256_CCM_8 +); + +#[cfg(all(feature = "chacha20poly1305", feature = "hash-sha256"))] +tls12_ecdhe_cipher_suite!( + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + hash::SHA256, + PrfUsingHmac(hmac::SHA256), + TLS12_ECDSA_SCHEMES, + &ChaCha20Poly1305 +); + +pub const TLS_ECDHE_ECDSA_SUITES: &[SupportedCipherSuite] = &[ + #[cfg(all(feature = "aes-gcm", feature = "hash-sha256"))] + SupportedCipherSuite::Tls12(&TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), + #[cfg(all(feature = "aes-gcm", feature = "hash-sha384"))] + SupportedCipherSuite::Tls12(&TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), + #[cfg(all(feature = "aes-ccm", feature = "hash-sha256"))] + SupportedCipherSuite::Tls12(&TLS_ECDHE_ECDSA_WITH_AES_128_CCM), + #[cfg(all(feature = "aes-ccm", feature = "hash-sha256"))] + SupportedCipherSuite::Tls12(&TLS_ECDHE_ECDSA_WITH_AES_256_CCM), + #[cfg(all(feature = "aes-ccm", feature = "hash-sha256"))] + SupportedCipherSuite::Tls12(&TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8), + #[cfg(all(feature = "aes-ccm", feature = "hash-sha256"))] + SupportedCipherSuite::Tls12(&TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8), + #[cfg(all(feature = "chacha20poly1305", feature = "hash-sha256"))] + SupportedCipherSuite::Tls12(&TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), +]; diff --git a/src/tls12/suites/rsa.rs b/src/tls12/suites/rsa.rs new file mode 100644 index 0000000..a76b580 --- /dev/null +++ b/src/tls12/suites/rsa.rs @@ -0,0 +1,55 @@ +use rustls::SupportedCipherSuite; + +#[cfg(feature = "aead")] +use crate::tls12::suites::schemes::TLS12_RSA_SCHEMES; +#[cfg(feature = "aead")] +use crate::{hash, hmac, tls12_ecdhe_cipher_suite}; +#[cfg(feature = "aead")] +use rustls::crypto::{CipherSuiteCommon, KeyExchangeAlgorithm, tls12::PrfUsingHmac}; +#[cfg(feature = "aead")] +use rustls::{CipherSuite, Tls12CipherSuite}; + +#[cfg(feature = "gcm")] +use crate::tls12::aead::gcm::{AES_128_GCM, AES_256_GCM}; + +#[cfg(feature = "chacha20poly1305")] +use crate::tls12::aead::chacha20::ChaCha20Poly1305; + +#[cfg(all(feature = "aes-gcm", feature = "hash-sha256"))] +tls12_ecdhe_cipher_suite!( + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + hash::SHA256, + PrfUsingHmac(hmac::SHA256), + TLS12_RSA_SCHEMES, + AES_128_GCM +); + +#[cfg(all(feature = "aes-gcm", feature = "hash-sha384"))] +tls12_ecdhe_cipher_suite!( + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + hash::SHA384, + PrfUsingHmac(hmac::SHA384), + TLS12_RSA_SCHEMES, + AES_256_GCM +); + +#[cfg(all(feature = "chacha20poly1305", feature = "hash-sha256"))] +tls12_ecdhe_cipher_suite!( + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + hash::SHA256, + PrfUsingHmac(hmac::SHA256), + TLS12_RSA_SCHEMES, + &ChaCha20Poly1305 +); + +pub const TLS_ECDHE_RSA_SUITES: &[SupportedCipherSuite] = &[ + #[cfg(all(feature = "aes-gcm", feature = "hash-sha256"))] + SupportedCipherSuite::Tls12(&TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), + #[cfg(all(feature = "aes-gcm", feature = "hash-sha384"))] + SupportedCipherSuite::Tls12(&TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), + #[cfg(all(feature = "chacha20poly1305", feature = "hash-sha256"))] + SupportedCipherSuite::Tls12(&TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), +]; diff --git a/src/tls12/suites/schemes.rs b/src/tls12/suites/schemes.rs new file mode 100644 index 0000000..36a12aa --- /dev/null +++ b/src/tls12/suites/schemes.rs @@ -0,0 +1,32 @@ +#[cfg(any(feature = "ecdsa", feature = "rsa"))] +use rustls::SignatureScheme; + +#[cfg(feature = "ecdsa")] +pub const TLS12_ECDSA_SCHEMES: &[SignatureScheme] = &[ + #[cfg(all(feature = "ecdsa-p256", feature = "hash-sha256"))] + SignatureScheme::ECDSA_NISTP256_SHA256, + #[cfg(all(feature = "ecdsa-p384", feature = "hash-sha384"))] + SignatureScheme::ECDSA_NISTP384_SHA384, + #[cfg(all(feature = "ecdsa-p521", feature = "hash-sha512"))] + SignatureScheme::ECDSA_NISTP521_SHA512, + #[cfg(feature = "eddsa-ed25519")] + SignatureScheme::ED25519, + #[cfg(feature = "eddsa-ed448")] + SignatureScheme::ED448, +]; + +#[cfg(feature = "rsa")] +pub const TLS12_RSA_SCHEMES: &[SignatureScheme] = &[ + #[cfg(all(feature = "rsa-pkcs1", feature = "hash-sha256"))] + SignatureScheme::RSA_PKCS1_SHA256, + #[cfg(all(feature = "rsa-pkcs1", feature = "hash-sha384"))] + SignatureScheme::RSA_PKCS1_SHA384, + #[cfg(all(feature = "rsa-pkcs1", feature = "hash-sha512"))] + SignatureScheme::RSA_PKCS1_SHA512, + #[cfg(all(feature = "rsa-pss", feature = "hash-sha256"))] + SignatureScheme::RSA_PSS_SHA256, + #[cfg(all(feature = "rsa-pss", feature = "hash-sha384"))] + SignatureScheme::RSA_PSS_SHA384, + #[cfg(all(feature = "rsa-pss", feature = "hash-sha512"))] + SignatureScheme::RSA_PSS_SHA512, +]; diff --git a/src/tls13.rs b/src/tls13.rs new file mode 100644 index 0000000..3864c43 --- /dev/null +++ b/src/tls13.rs @@ -0,0 +1,3 @@ +#[cfg(feature = "aead")] +pub mod aead; +pub mod suites; diff --git a/src/tls13/aead.rs b/src/tls13/aead.rs new file mode 100644 index 0000000..3de7f60 --- /dev/null +++ b/src/tls13/aead.rs @@ -0,0 +1,12 @@ +#[cfg(feature = "chacha20poly1305")] +pub mod chacha20; +#[cfg(feature = "chacha20poly1305")] +pub use chacha20::CHACHA20_POLY1305; + +#[cfg(feature = "gcm")] +pub mod gcm; + +#[cfg(feature = "ccm")] +pub mod ccm; + +pub(crate) mod common; diff --git a/src/tls13/aead/ccm.rs b/src/tls13/aead/ccm.rs new file mode 100644 index 0000000..a79e978 --- /dev/null +++ b/src/tls13/aead/ccm.rs @@ -0,0 +1,6 @@ +use super::common::Tls13AeadAlgorithmCommon; + +pub const AES_128_CCM: &Tls13AeadAlgorithmCommon = + &Tls13AeadAlgorithmCommon::DEFAULT; +pub const AES_128_CCM_8: &Tls13AeadAlgorithmCommon = + &Tls13AeadAlgorithmCommon::DEFAULT; diff --git a/src/tls13/aead/chacha20.rs b/src/tls13/aead/chacha20.rs new file mode 100644 index 0000000..f796480 --- /dev/null +++ b/src/tls13/aead/chacha20.rs @@ -0,0 +1,20 @@ +use rustls::ConnectionTrafficSecrets; +use rustls::crypto::cipher::{AeadKey, Iv, UnsupportedOperationError}; + +use crate::tls13::aead::common::{Extractor, Tls13AeadAlgorithmCommon}; + +pub struct ChaCha20Poly1305Extractor; + +impl Extractor for ChaCha20Poly1305Extractor { + fn extract( + key: AeadKey, + iv: Iv, + ) -> Result { + Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }) + } +} + +pub const CHACHA20_POLY1305: &Tls13AeadAlgorithmCommon< + chacha20poly1305::ChaCha20Poly1305, + ChaCha20Poly1305Extractor, +> = &Tls13AeadAlgorithmCommon::DEFAULT; diff --git a/src/tls13/aead/common.rs b/src/tls13/aead/common.rs new file mode 100644 index 0000000..04befec --- /dev/null +++ b/src/tls13/aead/common.rs @@ -0,0 +1,142 @@ +use core::marker::PhantomData; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +use aead::{AeadInOut, KeyInit, Nonce}; +use rustls::{ + ConnectionTrafficSecrets, ContentType, ProtocolVersion, + crypto::cipher::{ + self, AeadKey, InboundOpaqueMessage, InboundPlainMessage, Iv, MessageDecrypter, + MessageEncrypter, OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, + Tls13AeadAlgorithm, make_tls13_aad, + }, +}; +use typenum::Unsigned; + +use crate::aead::{DecryptBufferAdapter, EncryptBufferAdapter}; + +pub struct Tls13AeadEncrypter { + aead: A, + iv: Iv, +} + +impl MessageEncrypter for Tls13AeadEncrypter +where + A: AeadInOut + Send + Sync, +{ + fn encrypt( + &mut self, + m: OutboundPlainMessage<'_>, + seq: u64, + ) -> Result { + let total_len = self.encrypted_payload_len(m.payload.len()); + let mut payload = PrefixedPayload::with_capacity(total_len); + + payload.extend_from_chunks(&m.payload); + payload.extend_from_slice(&m.typ.to_array()); + + self.aead + .encrypt_in_place( + &Nonce::::try_from(&cipher::Nonce::new(&self.iv, seq).0[..]) + .map_err(|_| rustls::Error::EncryptError)?, + &make_tls13_aad(total_len), + &mut EncryptBufferAdapter::PrefixedPayload(&mut payload), + ) + .map_err(|_| rustls::Error::EncryptError) + .map(|_| { + OutboundOpaqueMessage::new( + ContentType::ApplicationData, + ProtocolVersion::TLSv1_2, + payload, + ) + }) + } + + fn encrypted_payload_len(&self, payload_len: usize) -> usize { + payload_len + 1 + A::TagSize::USIZE + } +} + +pub struct Tls13AeadDecrypter { + aead: A, + iv: Iv, +} + +impl MessageDecrypter for Tls13AeadDecrypter +where + A: AeadInOut + Send + Sync, +{ + fn decrypt<'a>( + &mut self, + mut m: InboundOpaqueMessage<'a>, + seq: u64, + ) -> Result, rustls::Error> { + self.aead + .decrypt_in_place( + &Nonce::::try_from(&cipher::Nonce::new(&self.iv, seq).0[..]) + .map_err(|_| rustls::Error::DecryptError)?, + &make_tls13_aad(m.payload.len()), + &mut DecryptBufferAdapter::BorrowedPayload(&mut m.payload), + ) + .map_err(|_| rustls::Error::DecryptError)?; + + m.into_tls13_unpadded_message() + } +} + +pub trait Extractor { + fn extract( + _key: AeadKey, + _iv: Iv, + ) -> Result { + Err(cipher::UnsupportedOperationError) + } +} + +impl Extractor for () {} + +#[derive(Default)] +pub struct Tls13AeadAlgorithmCommon { + _aead: PhantomData, + _extractor: PhantomData, +} + +impl Tls13AeadAlgorithmCommon { + pub const DEFAULT: Self = Self { + _aead: PhantomData, + _extractor: PhantomData, + }; +} + +impl Tls13AeadAlgorithm for Tls13AeadAlgorithmCommon +where + A: KeyInit + AeadInOut + Send + Sync + 'static, + E: Extractor + Send + Sync + 'static, +{ + fn encrypter(&self, key: AeadKey, iv: Iv) -> Box { + Box::new(Tls13AeadEncrypter:: { + aead: A::new_from_slice(key.as_ref()).expect("Invalid key length for AEAD algorithm"), + iv, + }) + } + + fn decrypter(&self, key: AeadKey, iv: Iv) -> Box { + Box::new(Tls13AeadDecrypter:: { + aead: A::new_from_slice(key.as_ref()).expect("Invalid key length for AEAD algorithm"), + iv, + }) + } + + fn key_len(&self) -> usize { + A::key_size() + } + + fn extract_keys( + &self, + key: AeadKey, + iv: Iv, + ) -> Result { + E::extract(key, iv) + } +} diff --git a/src/tls13/aead/gcm.rs b/src/tls13/aead/gcm.rs new file mode 100644 index 0000000..0d69674 --- /dev/null +++ b/src/tls13/aead/gcm.rs @@ -0,0 +1,36 @@ +use crate::tls13::aead::common::{Extractor, Tls13AeadAlgorithmCommon}; +use rustls::{ + ConnectionTrafficSecrets, + crypto::cipher::{self, AeadKey, Iv}, +}; + +macro_rules! impl_gcm_aead { + ($const_name:ident, $extractor_name:ident, $variant:ident, $type:ty) => { + pub struct $extractor_name; + + impl Extractor for $extractor_name { + fn extract( + key: AeadKey, + iv: Iv, + ) -> Result { + Ok(ConnectionTrafficSecrets::$variant { key, iv }) + } + } + + pub const $const_name: &Tls13AeadAlgorithmCommon<$type, $extractor_name> = + &Tls13AeadAlgorithmCommon::DEFAULT; + }; +} + +impl_gcm_aead!( + AES_128_GCM, + Aes128GcmExtractor, + Aes128Gcm, + crate::aead::aes::Aes128Gcm +); +impl_gcm_aead!( + AES_256_GCM, + Aes256GcmExtractor, + Aes256Gcm, + crate::aead::aes::Aes256Gcm +); diff --git a/src/tls13/suites.rs b/src/tls13/suites.rs new file mode 100644 index 0000000..b8bcfb6 --- /dev/null +++ b/src/tls13/suites.rs @@ -0,0 +1,21 @@ +use crate::const_concat_slices; +use crate::feature_slice; +use rustls::SupportedCipherSuite; + +pub const TLS13_SUITES: &[SupportedCipherSuite] = const_concat_slices!( + SupportedCipherSuite, + feature_slice!([feature = "aes"], aes::TLS13_AES_SUITES), + feature_slice!( + [feature = "chacha20poly1305"], + &[ + #[cfg(feature = "hash-sha256")] + SupportedCipherSuite::Tls13(&chacha20::TLS13_CHACHA20_POLY1305_SHA256), + ] + ) +); + +#[cfg(feature = "aes")] +pub mod aes; + +#[cfg(feature = "chacha20poly1305")] +pub mod chacha20; diff --git a/src/tls13/suites/aes.rs b/src/tls13/suites/aes.rs new file mode 100644 index 0000000..7cdc763 --- /dev/null +++ b/src/tls13/suites/aes.rs @@ -0,0 +1,87 @@ +use crate::const_concat_slices; +use crate::feature_eval_expr; +use crate::feature_slice; +use crate::tls13_cipher_suite; +use crate::{hash, hmac}; +use rustls::crypto::{CipherSuiteCommon, tls13::HkdfUsingHmac}; +use rustls::{CipherSuite, SupportedCipherSuite, Tls13CipherSuite}; + +#[cfg(all(feature = "ccm", feature = "hash-sha256", feature = "quic"))] +use crate::aead::aes::Aes128Ccm; +#[cfg(all(feature = "gcm", feature = "hash-sha256", feature = "quic"))] +use crate::aead::aes::Aes128Gcm; +#[cfg(all(feature = "gcm", feature = "hash-sha384", feature = "quic"))] +use crate::aead::aes::Aes256Gcm; +#[cfg(all(feature = "ccm", feature = "hash-sha256"))] +use crate::tls13::aead::ccm::{AES_128_CCM, AES_128_CCM_8}; +#[cfg(all(feature = "gcm", feature = "hash-sha256"))] +use crate::tls13::aead::gcm::AES_128_GCM; +#[cfg(all(feature = "gcm", feature = "hash-sha384"))] +use crate::tls13::aead::gcm::AES_256_GCM; + +#[cfg(all(feature = "gcm", feature = "hash-sha256"))] +tls13_cipher_suite!( + TLS13_AES_128_GCM_SHA256, + CipherSuite::TLS13_AES_128_GCM_SHA256, + hash::SHA256, + HkdfUsingHmac(hmac::SHA256), + AES_128_GCM, + feature_eval_expr!([feature = "quic"], Some(&crate::quic::QuicCrypto::::DEFAULT), else None) +); + +#[cfg(all(feature = "gcm", feature = "hash-sha384"))] +tls13_cipher_suite!( + TLS13_AES_256_GCM_SHA384, + CipherSuite::TLS13_AES_256_GCM_SHA384, + hash::SHA384, + HkdfUsingHmac(hmac::SHA384), + AES_256_GCM, + feature_eval_expr!([feature = "quic"], Some(&crate::quic::QuicCrypto::::DEFAULT), else None) +); + +#[cfg(all(feature = "ccm", feature = "hash-sha256"))] +tls13_cipher_suite!( + TLS13_AES_128_CCM_SHA256, + CipherSuite::TLS13_AES_128_CCM_SHA256, + hash::SHA256, + HkdfUsingHmac(hmac::SHA256), + AES_128_CCM, + feature_eval_expr!([feature = "quic"], Some(&crate::quic::QuicCrypto::::DEFAULT), else None) +); + +#[cfg(all(feature = "ccm", feature = "hash-sha256"))] +tls13_cipher_suite!( + TLS13_AES_128_CCM_8_SHA256, + CipherSuite::TLS13_AES_128_CCM_8_SHA256, + hash::SHA256, + HkdfUsingHmac(hmac::SHA256), + AES_128_CCM_8, + // The AEAD for that ciphersuite, AEAD_AES_128_CCM_8 [CCM], does not produce a large + // enough authentication tag for use with the header protection designs + // provided (see Section 5.4). All other ciphersuites defined in + // [TLS13] have a 16-byte authentication tag and produce an output 16 + // bytes larger than their input. + None +); + +pub const TLS13_AES_SUITES: &[SupportedCipherSuite] = const_concat_slices!( + SupportedCipherSuite, + feature_slice!( + [feature = "gcm"], + &[ + #[cfg(feature = "hash-sha256")] + SupportedCipherSuite::Tls13(&TLS13_AES_128_GCM_SHA256), + #[cfg(feature = "hash-sha384")] + SupportedCipherSuite::Tls13(&TLS13_AES_256_GCM_SHA384), + ] + ), + feature_slice!( + [feature = "ccm"], + &[ + #[cfg(feature = "hash-sha256")] + SupportedCipherSuite::Tls13(&TLS13_AES_128_CCM_SHA256), + #[cfg(feature = "hash-sha256")] + SupportedCipherSuite::Tls13(&TLS13_AES_128_CCM_8_SHA256), + ] + ) +); diff --git a/src/tls13/suites/chacha20.rs b/src/tls13/suites/chacha20.rs new file mode 100644 index 0000000..b86bdae --- /dev/null +++ b/src/tls13/suites/chacha20.rs @@ -0,0 +1,20 @@ +use crate::feature_eval_expr; +#[cfg(feature = "hash-sha256")] +use crate::tls13::aead::CHACHA20_POLY1305; +use crate::{hash, hmac, tls13_cipher_suite}; + +use rustls::crypto::{CipherSuiteCommon, tls13::HkdfUsingHmac}; +use rustls::{CipherSuite, Tls13CipherSuite}; + +#[cfg(feature = "quic")] +use chacha20poly1305::ChaCha20Poly1305; + +#[cfg(feature = "hash-sha256")] +tls13_cipher_suite!( + TLS13_CHACHA20_POLY1305_SHA256, + CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, + hash::SHA256, + HkdfUsingHmac(hmac::SHA256), + CHACHA20_POLY1305, + feature_eval_expr!([feature = "quic"], Some(&crate::quic::QuicCrypto::::DEFAULT), else None) +); diff --git a/src/verify.rs b/src/verify.rs index 2043b64..f4f84e3 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -1,46 +1,214 @@ -use rustls::crypto::WebPkiSupportedAlgorithms; +use core::array::TryFromSliceError; + +use crate::const_concat_slices; +use crate::feature_slice; + +use pki_types::SignatureVerificationAlgorithm; use rustls::SignatureScheme; +use rustls::crypto::WebPkiSupportedAlgorithms; -use self::ecdsa::{ECDSA_P256_SHA256, ECDSA_P256_SHA384, ECDSA_P384_SHA256, ECDSA_P384_SHA384}; -use self::eddsa::ED25519; -use self::rsa::{ - RSA_PKCS1_SHA256, RSA_PKCS1_SHA384, RSA_PKCS1_SHA512, RSA_PSS_SHA256, RSA_PSS_SHA384, - RSA_PSS_SHA512, -}; +pub(crate) enum Error { + #[cfg(feature = "signature")] + Signature, + TryFromSlice, + #[cfg(feature = "der")] + Der, + #[cfg(feature = "pkcs1")] + Pkcs1, +} + +#[cfg(feature = "signature")] +impl From for Error { + fn from(_: signature::Error) -> Self { + Self::Signature + } +} + +#[cfg(feature = "der")] +impl From for Error { + fn from(_: der::Error) -> Self { + Self::Der + } +} -pub static ALGORITHMS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { - all: &[ - ECDSA_P256_SHA256, - ECDSA_P256_SHA384, - ECDSA_P384_SHA256, - ECDSA_P384_SHA384, - ED25519, - RSA_PKCS1_SHA256, - RSA_PKCS1_SHA384, - RSA_PKCS1_SHA512, - RSA_PSS_SHA256, - RSA_PSS_SHA384, - RSA_PSS_SHA512, - ], - mapping: &[ - ( - SignatureScheme::ECDSA_NISTP384_SHA384, - &[ECDSA_P384_SHA384, ECDSA_P256_SHA384], - ), - ( - SignatureScheme::ECDSA_NISTP256_SHA256, - &[ECDSA_P256_SHA256, ECDSA_P384_SHA256], - ), - (SignatureScheme::ED25519, &[ED25519]), - (SignatureScheme::RSA_PKCS1_SHA256, &[RSA_PKCS1_SHA256]), - (SignatureScheme::RSA_PKCS1_SHA384, &[RSA_PKCS1_SHA384]), - (SignatureScheme::RSA_PKCS1_SHA512, &[RSA_PKCS1_SHA512]), - (SignatureScheme::RSA_PSS_SHA256, &[RSA_PSS_SHA256]), - (SignatureScheme::RSA_PSS_SHA384, &[RSA_PSS_SHA384]), - (SignatureScheme::RSA_PSS_SHA512, &[RSA_PSS_SHA512]), - ], +#[cfg(feature = "pkcs1")] +impl From for Error { + fn from(_: pkcs1::Error) -> Self { + Self::Pkcs1 + } +} + +impl From for Error { + fn from(_: TryFromSliceError) -> Self { + Self::TryFromSlice + } +} + +pub const ALL: &[&dyn SignatureVerificationAlgorithm] = const_concat_slices!( + &dyn SignatureVerificationAlgorithm, + feature_slice!( + [feature = "verify-ecdsa-nist"], + &[ + #[cfg(all(feature = "ecdsa-p256", feature = "hash-sha256"))] + ecdsa::nist::ECDSA_P256_SHA256, + #[cfg(all(feature = "ecdsa-p256", feature = "hash-sha384"))] + ecdsa::nist::ECDSA_P256_SHA384, + #[cfg(all(feature = "ecdsa-p256", feature = "hash-sha512"))] + ecdsa::nist::ECDSA_P256_SHA512, + #[cfg(all(feature = "ecdsa-p384", feature = "hash-sha256"))] + ecdsa::nist::ECDSA_P384_SHA256, + #[cfg(all(feature = "ecdsa-p384", feature = "hash-sha384"))] + ecdsa::nist::ECDSA_P384_SHA384, + #[cfg(all(feature = "ecdsa-p384", feature = "hash-sha512"))] + ecdsa::nist::ECDSA_P384_SHA512, + #[cfg(all(feature = "ecdsa-p521", feature = "hash-sha256"))] + ecdsa::nist::ECDSA_P521_SHA256, + #[cfg(all(feature = "ecdsa-p521", feature = "hash-sha384"))] + ecdsa::nist::ECDSA_P521_SHA384, + #[cfg(all(feature = "ecdsa-p521", feature = "hash-sha512"))] + ecdsa::nist::ECDSA_P521_SHA512, + ] + ), + feature_slice!( + [feature = "verify-eddsa"], + &[ + #[cfg(all(feature = "eddsa-ed25519", feature = "hash-sha256"))] + eddsa::ed25519::ED25519, + #[cfg(all(feature = "eddsa-ed448", feature = "hash-sha512"))] + eddsa::ed448::ED448, + ] + ), + feature_slice!( + [feature = "rsa-pkcs1"], + &[ + #[cfg(feature = "hash-sha256")] + rsa::RSA_PKCS1_SHA256, + #[cfg(feature = "hash-sha384")] + rsa::RSA_PKCS1_SHA384, + #[cfg(feature = "hash-sha512")] + rsa::RSA_PKCS1_SHA512, + ] + ), + feature_slice!( + [feature = "rsa-pss"], + &[ + #[cfg(feature = "hash-sha256")] + rsa::RSA_PSS_SHA256, + #[cfg(feature = "hash-sha384")] + rsa::RSA_PSS_SHA384, + #[cfg(feature = "hash-sha512")] + rsa::RSA_PSS_SHA512, + ] + ), +); + +pub const MAPPING: &[(SignatureScheme, &[&dyn SignatureVerificationAlgorithm])] = const_concat_slices!( + (SignatureScheme, &[&dyn SignatureVerificationAlgorithm],), + feature_slice!( + [feature = "verify-ecdsa-nist"], + &[ + #[cfg(all(feature = "ecdsa-p384", feature = "hash-sha384"))] + ( + SignatureScheme::ECDSA_NISTP384_SHA384, + &[ + ecdsa::nist::ECDSA_P384_SHA384, + #[cfg(feature = "ecdsa-p256")] + ecdsa::nist::ECDSA_P256_SHA384, + #[cfg(feature = "ecdsa-p521")] + ecdsa::nist::ECDSA_P521_SHA384, + ], + ), + #[cfg(all(feature = "ecdsa-p521", feature = "hash-sha512"))] + ( + SignatureScheme::ECDSA_NISTP521_SHA512, + &[ + ecdsa::nist::ECDSA_P521_SHA512, + #[cfg(feature = "ecdsa-p256")] + ecdsa::nist::ECDSA_P256_SHA512, + #[cfg(feature = "ecdsa-p384")] + ecdsa::nist::ECDSA_P384_SHA512, + ], + ), + #[cfg(all(feature = "ecdsa-p256", feature = "hash-sha256"))] + ( + SignatureScheme::ECDSA_NISTP256_SHA256, + &[ + ecdsa::nist::ECDSA_P256_SHA256, + #[cfg(feature = "ecdsa-p384")] + ecdsa::nist::ECDSA_P384_SHA256, + #[cfg(feature = "ecdsa-p521")] + ecdsa::nist::ECDSA_P521_SHA256, + ], + ), + ] + ), + feature_slice!( + [all(feature = "rsa-pkcs1", feature = "verify-rsa")], + &[ + #[cfg(feature = "hash-sha256")] + (SignatureScheme::RSA_PKCS1_SHA256, &[rsa::RSA_PKCS1_SHA256]), + #[cfg(feature = "hash-sha384")] + (SignatureScheme::RSA_PKCS1_SHA384, &[rsa::RSA_PKCS1_SHA384]), + #[cfg(feature = "hash-sha512")] + (SignatureScheme::RSA_PKCS1_SHA512, &[rsa::RSA_PKCS1_SHA512]), + ] + ), + feature_slice!( + [all(feature = "rsa-pss", feature = "verify-rsa")], + &[ + #[cfg(feature = "hash-sha256")] + (SignatureScheme::RSA_PSS_SHA256, &[rsa::RSA_PSS_SHA256]), + #[cfg(feature = "hash-sha384")] + (SignatureScheme::RSA_PSS_SHA384, &[rsa::RSA_PSS_SHA384]), + #[cfg(feature = "hash-sha512")] + (SignatureScheme::RSA_PSS_SHA512, &[rsa::RSA_PSS_SHA512]), + ] + ), + feature_slice!( + [feature = "verify-eddsa"], + &[ + #[cfg(all(feature = "eddsa-ed25519", feature = "hash-sha256"))] + (SignatureScheme::ED25519, &[eddsa::ed25519::ED25519]), + #[cfg(all(feature = "eddsa-ed448", feature = "hash-sha512"))] + (SignatureScheme::ED448, &[eddsa::ed448::ED448]), + ] + ), +); + +pub const ALGORITHMS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { + all: const_concat_slices!( + &dyn SignatureVerificationAlgorithm, + ALL, + feature_slice!( + [feature = "eddsa"], + &[ + #[cfg(feature = "eddsa-ed25519")] + eddsa::ed25519::ED25519, + #[cfg(feature = "eddsa-ed448")] + eddsa::ed448::ED448, + ] + ) + ), + mapping: const_concat_slices!( + (SignatureScheme, &[&dyn SignatureVerificationAlgorithm],), + MAPPING, + feature_slice!( + [feature = "eddsa"], + &[ + #[cfg(feature = "eddsa-ed25519")] + (SignatureScheme::ED25519, &[eddsa::ed25519::ED25519]), + #[cfg(feature = "eddsa-ed448")] + (SignatureScheme::ED448, &[eddsa::ed448::ED448]), + ] + ) + ), }; +#[cfg(feature = "ecdsa")] pub mod ecdsa; + +#[cfg(feature = "eddsa")] pub mod eddsa; + +#[cfg(feature = "rsa")] pub mod rsa; diff --git a/src/verify/ecdsa.rs b/src/verify/ecdsa.rs index 669f03d..2d5c7c7 100644 --- a/src/verify/ecdsa.rs +++ b/src/verify/ecdsa.rs @@ -1,54 +1,2 @@ -use der::Decode; -use digest::Digest; -use paste::paste; -use pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm}; -use signature::hazmat::PrehashVerifier; -use webpki::alg_id; - -macro_rules! impl_generic_ecdsa_verifer { - ( - $name:ident, - $public_key_algo:expr, - $signature_alg_id:expr, - $verifying_key:ty, - $signature:ty, - $hash:ty - ) => { - paste! { - #[allow(non_camel_case_types)] - #[derive(Debug)] - struct []; - - impl SignatureVerificationAlgorithm for [] { - fn public_key_alg_id(&self) -> AlgorithmIdentifier { - $public_key_algo - } - - fn signature_alg_id(&self) -> AlgorithmIdentifier { - $signature_alg_id - } - - fn verify_signature( - &self, - public_key: &[u8], - message: &[u8], - signature: &[u8], - ) -> Result<(), InvalidSignature> { - let signature = <$signature>::from_der(signature).map_err(|_| InvalidSignature)?; - let verifying_key = <$verifying_key>::from_sec1_bytes(public_key).map_err(|_| InvalidSignature)?; - let digest = &<$hash>::digest(&message); - verifying_key - .verify_prehash(digest, &signature) - .map_err(|_| InvalidSignature) - } - } - - pub const $name: &dyn SignatureVerificationAlgorithm = &[]; - } - }; -} - -impl_generic_ecdsa_verifer! {ECDSA_P256_SHA256, alg_id::ECDSA_P256, alg_id::ECDSA_SHA256, p256::ecdsa::VerifyingKey, p256::ecdsa::DerSignature, sha2::Sha256} -impl_generic_ecdsa_verifer! {ECDSA_P256_SHA384, alg_id::ECDSA_P256, alg_id::ECDSA_SHA384, p256::ecdsa::VerifyingKey, p256::ecdsa::DerSignature, sha2::Sha384} -impl_generic_ecdsa_verifer! {ECDSA_P384_SHA256, alg_id::ECDSA_P384, alg_id::ECDSA_SHA256, p384::ecdsa::VerifyingKey, p384::ecdsa::DerSignature, sha2::Sha256} -impl_generic_ecdsa_verifer! {ECDSA_P384_SHA384, alg_id::ECDSA_P384, alg_id::ECDSA_SHA384, p384::ecdsa::VerifyingKey, p384::ecdsa::DerSignature, sha2::Sha384} +#[cfg(feature = "verify-ecdsa-nist")] +pub mod nist; diff --git a/src/verify/ecdsa/nist.rs b/src/verify/ecdsa/nist.rs new file mode 100644 index 0000000..b550d07 --- /dev/null +++ b/src/verify/ecdsa/nist.rs @@ -0,0 +1,158 @@ +use core::marker::PhantomData; + +use ::aead::array::ArraySize; +use ::digest::Digest; +use ::ecdsa::EcdsaCurve; +use ::ecdsa::VerifyingKey; +use ::ecdsa::der::{MaxOverhead, MaxSize, Signature}; +use ::elliptic_curve::ops::Add; +use ::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint}; +use ::elliptic_curve::{Curve, CurveArithmetic, FieldBytesSize}; +use ::pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm, alg_id}; +use ::sec1::point::ModulusSize; +use ::signature::hazmat::PrehashVerifier; +use core::fmt::Debug; + +/// Trait for ECDSA curve algorithm identifiers. +pub trait EcdsaCurveAlgId { + const PUBLIC_KEY_ALG_ID: AlgorithmIdentifier; +} + +/// Trait for ECDSA hash algorithm identifiers. +pub trait EcdsaHashAlgId { + const SIGNATURE_ALG_ID: AlgorithmIdentifier; +} + +/// Trait to simplify generic bounds for ECDSA curve types. +pub trait EcdsaVerifierCurve: EcdsaCurve + CurveArithmetic + EcdsaCurveAlgId +where + H: EcdsaHashAlgId, +{ +} + +impl EcdsaVerifierCurve for C +where + C: EcdsaCurve + CurveArithmetic + EcdsaCurveAlgId, + H: EcdsaHashAlgId, +{ +} + +#[derive(Debug, Default)] +pub struct EcdsaVerifier +where + C: EcdsaVerifierCurve, + H: Digest + EcdsaHashAlgId, +{ + _curve: PhantomData, + _hash: PhantomData, +} + +impl EcdsaVerifier +where + C: EcdsaVerifierCurve, + H: Digest + EcdsaHashAlgId, +{ + pub const DEFAULT: Self = Self { + _curve: PhantomData, + _hash: PhantomData, + }; +} + +impl EcdsaVerifier +where + C: EcdsaVerifierCurve, + ::AffinePoint: FromEncodedPoint + ToEncodedPoint, + ::FieldBytesSize: Debug + ModulusSize, + MaxSize: ArraySize, + as Add>::Output: Add + ArraySize, + H: Digest + EcdsaHashAlgId, +{ + fn verify_inner( + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), crate::verify::Error> { + use der::Decode; + let signature = Signature::::from_der(signature)?; + let verifying_key = VerifyingKey::::from_sec1_bytes(public_key)?; + let digest = &H::digest(message); + verifying_key.verify_prehash(digest, &signature)?; + Ok(()) + } +} +impl SignatureVerificationAlgorithm for EcdsaVerifier +where + C: EcdsaVerifierCurve, + ::AffinePoint: FromEncodedPoint + ToEncodedPoint, + ::FieldBytesSize: Debug + ModulusSize, + MaxSize: ArraySize, + as Add>::Output: Add + ArraySize, + H: Digest + Debug + Send + Sync + EcdsaHashAlgId, +{ + fn public_key_alg_id(&self) -> AlgorithmIdentifier { + C::PUBLIC_KEY_ALG_ID + } + fn signature_alg_id(&self) -> AlgorithmIdentifier { + H::SIGNATURE_ALG_ID + } + fn verify_signature( + &self, + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), InvalidSignature> { + Self::verify_inner(public_key, message, signature).map_err(|_| InvalidSignature) + } +} + +/// Macro to generate all ECDSA hash impls, curve impls, and constants +macro_rules! ecdsa_setup { + ( + hashes: $( ($hash_ty:ty, $hash_alg:expr, $hash_feat:literal) ),* $(,)? ; + curves: $( ($curve_ty:ty, $curve_alg:expr, $curve_feat:literal, $( ($const_name:ident, $hash_for_const:ty, $hash_feat_for_const:literal) ),* $(,)? ) ),* $(,)? + ) => { + $( + #[cfg(feature = $hash_feat)] + impl EcdsaHashAlgId for $hash_ty { + const SIGNATURE_ALG_ID: AlgorithmIdentifier = $hash_alg; + } + )* + + $( + #[cfg(feature = $curve_feat)] + impl EcdsaCurveAlgId for $curve_ty { + const PUBLIC_KEY_ALG_ID: AlgorithmIdentifier = $curve_alg; + } + + $( + #[cfg(all(feature = $curve_feat, feature = $hash_feat_for_const))] + pub const $const_name: &dyn SignatureVerificationAlgorithm = + &EcdsaVerifier::<$curve_ty, $hash_for_const>::DEFAULT; + )* + )* + }; +} + +ecdsa_setup! { + hashes: + (::sha2::Sha256, alg_id::ECDSA_SHA256, "hash-sha256"), + (::sha2::Sha384, alg_id::ECDSA_SHA384, "hash-sha384"), + (::sha2::Sha512, alg_id::ECDSA_SHA512, "hash-sha512"); + + curves: + (::p256::NistP256, alg_id::ECDSA_P256, "ecdsa-p256", + (ECDSA_P256_SHA256, ::sha2::Sha256, "hash-sha256"), + (ECDSA_P256_SHA384, ::sha2::Sha384, "hash-sha384"), + (ECDSA_P256_SHA512, ::sha2::Sha512, "hash-sha512") + ), + (::p384::NistP384, alg_id::ECDSA_P384, "ecdsa-p384", + (ECDSA_P384_SHA256, ::sha2::Sha256, "hash-sha256"), + (ECDSA_P384_SHA384, ::sha2::Sha384, "hash-sha384"), + (ECDSA_P384_SHA512, ::sha2::Sha512, "hash-sha512") + ), + (::p521::NistP521, alg_id::ECDSA_P521, "ecdsa-p521", + (ECDSA_P521_SHA256, ::sha2::Sha256, "hash-sha256"), + (ECDSA_P521_SHA384, ::sha2::Sha384, "hash-sha384"), + (ECDSA_P521_SHA512, ::sha2::Sha512, "hash-sha512") + ) +} diff --git a/src/verify/eddsa.rs b/src/verify/eddsa.rs index 334321c..e3018eb 100644 --- a/src/verify/eddsa.rs +++ b/src/verify/eddsa.rs @@ -1,33 +1,5 @@ -use pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm}; -use signature::Verifier; -use webpki::alg_id; +#[cfg(feature = "verify-eddsa-ed25519")] +pub mod ed25519; -#[derive(Debug)] -struct Ed25519Verify; - -impl SignatureVerificationAlgorithm for Ed25519Verify { - fn public_key_alg_id(&self) -> AlgorithmIdentifier { - alg_id::ED25519 - } - - fn signature_alg_id(&self) -> AlgorithmIdentifier { - alg_id::ED25519 - } - - fn verify_signature( - &self, - public_key: &[u8], - message: &[u8], - signature: &[u8], - ) -> Result<(), InvalidSignature> { - let public_key = public_key.try_into().map_err(|_| InvalidSignature)?; - let signature = - ed25519_dalek::Signature::from_slice(signature).map_err(|_| InvalidSignature)?; - ed25519_dalek::VerifyingKey::from_bytes(public_key) - .map_err(|_| InvalidSignature)? - .verify(message, &signature) - .map_err(|_| InvalidSignature) - } -} - -pub const ED25519: &dyn SignatureVerificationAlgorithm = &Ed25519Verify; +#[cfg(feature = "verify-eddsa-ed448")] +pub mod ed448; diff --git a/src/verify/eddsa/ed25519.rs b/src/verify/eddsa/ed25519.rs new file mode 100644 index 0000000..9435ca7 --- /dev/null +++ b/src/verify/eddsa/ed25519.rs @@ -0,0 +1,42 @@ +use ed25519_dalek::{Signature, VerifyingKey}; +use pki_types::alg_id; +use pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm}; +use signature::Verifier; + +#[derive(Debug)] +pub struct Ed25519Verify; + +impl Ed25519Verify { + fn verify_inner( + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), crate::verify::Error> { + let public_key = public_key.try_into()?; + let signature = Signature::from_slice(signature)?; + let verifying_key = VerifyingKey::from_bytes(public_key)?; + verifying_key.verify(message, &signature)?; + Ok(()) + } +} + +impl SignatureVerificationAlgorithm for Ed25519Verify { + fn public_key_alg_id(&self) -> AlgorithmIdentifier { + alg_id::ED25519 + } + + fn signature_alg_id(&self) -> AlgorithmIdentifier { + alg_id::ED25519 + } + + fn verify_signature( + &self, + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), InvalidSignature> { + Self::verify_inner(public_key, message, signature).map_err(|_| InvalidSignature) + } +} + +pub const ED25519: &dyn SignatureVerificationAlgorithm = &Ed25519Verify; diff --git a/src/verify/eddsa/ed448.rs b/src/verify/eddsa/ed448.rs new file mode 100644 index 0000000..269c8ab --- /dev/null +++ b/src/verify/eddsa/ed448.rs @@ -0,0 +1,45 @@ +use ed448_goldilocks::{Signature, VerifyingKey}; +use pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm}; +use signature::Verifier; + +#[derive(Debug)] +pub struct Ed448Verify; + +impl Ed448Verify { + fn verify_inner( + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), crate::verify::Error> { + let public_key = public_key.try_into()?; + let signature = Signature::from_slice(signature)?; + let verifying_key = VerifyingKey::from_bytes(public_key)?; + verifying_key.verify(message, &signature)?; + Ok(()) + } +} + +// Until https://github.com/rustls/pki-types/pull/87 was released, we need to use this hack +const ED448_IDENTIFIER: AlgorithmIdentifier = + AlgorithmIdentifier::from_slice(&[0x06, 0x03, 0x2B, 0x65, 0x71]); + +impl SignatureVerificationAlgorithm for Ed448Verify { + fn public_key_alg_id(&self) -> AlgorithmIdentifier { + ED448_IDENTIFIER + } + + fn signature_alg_id(&self) -> AlgorithmIdentifier { + ED448_IDENTIFIER + } + + fn verify_signature( + &self, + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), InvalidSignature> { + Self::verify_inner(public_key, message, signature).map_err(|_| InvalidSignature) + } +} + +pub const ED448: &dyn SignatureVerificationAlgorithm = &Ed448Verify; diff --git a/src/verify/rsa.rs b/src/verify/rsa.rs index f7a1889..f594b89 100644 --- a/src/verify/rsa.rs +++ b/src/verify/rsa.rs @@ -1,92 +1,176 @@ -use paste::paste; +use core::fmt::Debug; +use core::marker::PhantomData; +use digest::{Digest, FixedOutputReset}; +use pkcs1::DecodeRsaPublicKey; +use pkcs8::AssociatedOid; +use pki_types::alg_id; use pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm}; -use rsa::pkcs1::DecodeRsaPublicKey; -use rsa::{pkcs1v15, pss, RsaPublicKey}; -use sha2::{Sha256, Sha384, Sha512}; +use rsa::RsaPublicKey; use signature::Verifier; -use webpki::alg_id; -macro_rules! impl_generic_rsa_verifer { +pub trait RsaHash: Digest + FixedOutputReset + AssociatedOid + Debug + Send + Sync { + const PKCS1_ALG_ID: AlgorithmIdentifier; + const PSS_ALG_ID: AlgorithmIdentifier; +} + +pub trait RsaScheme { + type VerifyingKey; + type Signature: for<'a> TryFrom<&'a [u8], Error = signature::Error>; + + fn signature_alg_id() -> AlgorithmIdentifier; + fn new_verifying_key(public_key: rsa::RsaPublicKey) -> Self::VerifyingKey; + /// Verifies the signature. + /// + /// # Errors + /// Returns an error if the signature verification fails. + fn verify( + key: &Self::VerifyingKey, + message: &[u8], + signature: &Self::Signature, + ) -> Result<(), signature::Error>; +} + +#[derive(Debug)] +pub struct Pkcs1; + +impl RsaScheme for Pkcs1 { + type VerifyingKey = rsa::pkcs1v15::VerifyingKey; + type Signature = rsa::pkcs1v15::Signature; + + fn signature_alg_id() -> AlgorithmIdentifier { + H::PKCS1_ALG_ID + } + + fn new_verifying_key(public_key: rsa::RsaPublicKey) -> Self::VerifyingKey { + rsa::pkcs1v15::VerifyingKey::new(public_key) + } + + fn verify( + key: &Self::VerifyingKey, + message: &[u8], + signature: &Self::Signature, + ) -> Result<(), signature::Error> { + key.verify(message, signature) + } +} + +#[derive(Debug)] +pub struct Pss; + +impl RsaScheme for Pss { + type VerifyingKey = rsa::pss::VerifyingKey; + type Signature = rsa::pss::Signature; + + fn signature_alg_id() -> AlgorithmIdentifier { + H::PSS_ALG_ID + } + + fn new_verifying_key(public_key: rsa::RsaPublicKey) -> Self::VerifyingKey { + rsa::pss::VerifyingKey::new(public_key) + } + + fn verify( + key: &Self::VerifyingKey, + message: &[u8], + signature: &Self::Signature, + ) -> Result<(), signature::Error> { + key.verify(message, signature) + } +} + +#[derive(Debug, Default)] +pub struct RsaVerifier { + _phantom: PhantomData<(H, S)>, +} + +impl RsaVerifier { + pub const DEFAULT: Self = Self { + _phantom: PhantomData, + }; +} + +impl RsaVerifier { + fn verify_inner( + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), crate::verify::Error> { + let public_key = RsaPublicKey::from_pkcs1_der(public_key)?; + let signature = ::try_from(signature)?; + let key = S::new_verifying_key::(public_key); + S::verify::(&key, message, &signature)?; + Ok(()) + } +} + +impl SignatureVerificationAlgorithm + for RsaVerifier +{ + fn public_key_alg_id(&self) -> AlgorithmIdentifier { + alg_id::RSA_ENCRYPTION + } + + fn signature_alg_id(&self) -> AlgorithmIdentifier { + S::signature_alg_id::() + } + + fn verify_signature( + &self, + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), InvalidSignature> { + Self::verify_inner(public_key, message, signature).map_err(|_| InvalidSignature) + } +} + +/// Macro to generate RSA hash impl and verifier constants +macro_rules! rsa_hash_and_consts { ( - $name:ident, - $public_key_algo:expr, - $signature_alg_id:expr, - $verifying_key:ty, - $signature:ty + $hash:ty, + $pkcs1_const:ident, + $pss_const:ident, + $pkcs1_alg:expr, + $pss_alg:expr, + $hash_feat:literal ) => { - paste! { - #[allow(non_camel_case_types)] - #[derive(Debug)] - struct []; - - impl SignatureVerificationAlgorithm for [] { - fn public_key_alg_id(&self) -> AlgorithmIdentifier { - $public_key_algo - } - - fn signature_alg_id(&self) -> AlgorithmIdentifier { - $signature_alg_id - } - - fn verify_signature( - &self, - public_key: &[u8], - message: &[u8], - signature: &[u8], - ) -> Result<(), InvalidSignature> { - let public_key = RsaPublicKey::from_pkcs1_der(public_key).map_err(|_| InvalidSignature)?; - let signature = <$signature>::try_from(signature).map_err(|_| InvalidSignature)?; - <$verifying_key>::new(public_key) - .verify(message, &signature) - .map_err(|_| InvalidSignature) - } - } - - pub const $name: &dyn SignatureVerificationAlgorithm = &[]; + #[cfg(feature = $hash_feat)] + impl RsaHash for $hash { + const PKCS1_ALG_ID: AlgorithmIdentifier = $pkcs1_alg; + const PSS_ALG_ID: AlgorithmIdentifier = $pss_alg; } + + #[cfg(all(feature = "rsa-pkcs1", feature = $hash_feat))] + pub const $pkcs1_const: &dyn SignatureVerificationAlgorithm = + &RsaVerifier::<$hash, Pkcs1>::DEFAULT; + + #[cfg(all(feature = "rsa-pss", feature = $hash_feat))] + pub const $pss_const: &dyn SignatureVerificationAlgorithm = + &RsaVerifier::<$hash, Pss>::DEFAULT; }; } -impl_generic_rsa_verifer!( +rsa_hash_and_consts!( + sha2::Sha256, RSA_PKCS1_SHA256, - alg_id::RSA_ENCRYPTION, - alg_id::RSA_PKCS1_SHA256, - pkcs1v15::VerifyingKey, - pkcs1v15::Signature -); -impl_generic_rsa_verifer!( - RSA_PKCS1_SHA384, - alg_id::RSA_ENCRYPTION, - alg_id::RSA_PKCS1_SHA384, - pkcs1v15::VerifyingKey, - pkcs1v15::Signature -); -impl_generic_rsa_verifer!( - RSA_PKCS1_SHA512, - alg_id::RSA_ENCRYPTION, - alg_id::RSA_PKCS1_SHA512, - pkcs1v15::VerifyingKey, - pkcs1v15::Signature -); - -impl_generic_rsa_verifer!( RSA_PSS_SHA256, - alg_id::RSA_ENCRYPTION, + alg_id::RSA_PKCS1_SHA256, alg_id::RSA_PSS_SHA256, - pss::VerifyingKey, - pss::Signature + "hash-sha256" ); -impl_generic_rsa_verifer!( +rsa_hash_and_consts!( + sha2::Sha384, + RSA_PKCS1_SHA384, RSA_PSS_SHA384, - alg_id::RSA_ENCRYPTION, + alg_id::RSA_PKCS1_SHA384, alg_id::RSA_PSS_SHA384, - pss::VerifyingKey, - pss::Signature + "hash-sha384" ); -impl_generic_rsa_verifer!( +rsa_hash_and_consts!( + sha2::Sha512, + RSA_PKCS1_SHA512, RSA_PSS_SHA512, - alg_id::RSA_ENCRYPTION, + alg_id::RSA_PKCS1_SHA512, alg_id::RSA_PSS_SHA512, - pss::VerifyingKey, - pss::Signature + "hash-sha512" ); diff --git a/tests/builder.rs b/tests/builder.rs index bab5d85..ed93849 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -1,83 +1,164 @@ -use std::sync::Arc; +use std::io::{Read, Write}; +use std::sync::{Arc, OnceLock}; -use rustls::ClientConfig as RusTlsClientConfig; -use rustls::ServerConfig as RusTlsServerConfig; - -use rustls_rustcrypto::provider as rustcrypto_provider; - -mod fake_time; +use fake_cert_server_resolver::FakeServerCertResolver; use fake_time::FakeTime; - -mod fake_cert_server_verifier; -use fake_cert_server_verifier::FakeServerCertVerifier; - -mod fake_cert_client_verifier; -use fake_cert_client_verifier::FakeClientCertVerifier; +use itertools::iproduct; +use mem_socket::MemorySocket; +use rustls::crypto::CryptoProvider; +use rustls::{ + ClientConfig as RusTlsClientConfig, RootCertStore, ServerConfig as RusTlsServerConfig, +}; +use rustls_rustcrypto::{Provider, provider as rustcrypto_provider, verify}; mod fake_cert_server_resolver; -use fake_cert_server_resolver::FakeServerCertResolver; +mod fake_time; -// Test integration between rustls and rustls in Client builder context -#[test] -fn integrate_client_builder_with_details_fake() { - let provider = rustcrypto_provider(); - let time_provider = FakeTime {}; +static SERVER_RESOLVER: OnceLock> = OnceLock::new(); - let fake_server_cert_verifier = FakeServerCertVerifier {}; +fn make_client_config(provider: CryptoProvider) -> RusTlsClientConfig { + let resolver = SERVER_RESOLVER.get_or_init(|| Arc::new(FakeServerCertResolver::new())); + let mut store = RootCertStore::empty(); - let builder_init = - RusTlsClientConfig::builder_with_details(Arc::new(provider), Arc::new(time_provider)); + store.add(resolver.rsa_root_cert()).unwrap(); + store.add(resolver.ecdsa_root_cert()).unwrap(); - let builder_default_versions = builder_init + RusTlsClientConfig::builder_with_details(Arc::new(provider), Arc::new(FakeTime {})) .with_safe_default_protocol_versions() - .expect("Default protocol versions error?"); + .expect("Default protocol versions error?") + .with_root_certificates(store) + // .dangerous() + // .with_custom_certificate_verifier(Arc::new(FakeServerCertVerifier {})) + .with_no_client_auth() +} - let dangerous_verifier = builder_default_versions - .dangerous() - .with_custom_certificate_verifier(Arc::new(fake_server_cert_verifier)); +fn make_server_config(provider: CryptoProvider) -> RusTlsServerConfig { + let resolver = SERVER_RESOLVER + .get_or_init(|| Arc::new(FakeServerCertResolver::new())) + .clone(); + RusTlsServerConfig::builder_with_details(Arc::new(provider), Arc::new(FakeTime {})) + .with_safe_default_protocol_versions() + .expect("Default protocol versions error?") + .with_no_client_auth() + .with_cert_resolver(resolver) +} +// Test integration between rustls and rustls in Client builder context +#[test] +fn integrate_client_builder_with_details_fake() { // Out of scope - let rustls_client_config = dangerous_verifier.with_no_client_auth(); + let rustls_client_config = make_client_config(rustcrypto_provider()); // RustCrypto is not fips assert!(!rustls_client_config.fips()); } -use rustls::DistinguishedName; - // Test integration between rustls and rustls in Server builder context #[test] fn integrate_server_builder_with_details_fake() { - let provider = rustcrypto_provider(); - let time_provider = FakeTime {}; - - let builder_init = - RusTlsServerConfig::builder_with_details(Arc::new(provider), Arc::new(time_provider)); - - let builder_default_versions = builder_init - .with_safe_default_protocol_versions() - .expect("Default protocol versions error?"); - - // A DistinguishedName is a Vec wrapped in internal types. - // DER or BER encoded Subject field from RFC 5280 for a single certificate. - // The Subject field is encoded as an RFC 5280 Name - //let b_wrap_in: &[u8] = b""; // TODO: should have constant somewhere - - let dummy_entry: &[u8] = b""; - - let client_dn = [DistinguishedName::in_sequence(dummy_entry)]; - - let client_cert_verifier = FakeClientCertVerifier { dn: client_dn }; + let rustls_server_config = make_server_config(rustcrypto_provider()); - let dangerous_verifier = - builder_default_versions.with_client_cert_verifier(Arc::new(client_cert_verifier)); + // RustCrypto is not fips + assert!(!rustls_server_config.fips()); +} - let server_cert_resolver = FakeServerCertResolver {}; +const CLIENT_MAGIC: &[u8; 18] = b"Hello from Client!"; +const SERVER_MAGIC: &[u8; 18] = b"Hello from Server!"; - // Out of scope - let rustls_client_config = - dangerous_verifier.with_cert_resolver(Arc::new(server_cert_resolver)); +// Test integration +#[test] +fn test_basic_round_trip() { + std::thread::scope(move |s| { + for provider in generate_providers() { + let base_name = format!( + "{:?}-{:?}", + provider.cipher_suites[0], provider.kx_groups[0] + ); + println!("Testing with {base_name}"); + // Creates a pair of sockets that interconnect from client to server, and server to client + let (socket_c2s, socket_s2c) = MemorySocket::new_pair(); + + let mut random_data: [u8; 64 * 1024] = [0; 64 * 1024]; + + getrandom::fill(&mut random_data).unwrap(); + + std::thread::Builder::new() + .name(format!("{base_name}-server")) + .spawn_scoped(s, { + let provider: CryptoProvider = provider.clone(); + move || { + let config = Arc::new(make_server_config(provider)); + let mut stream = socket_s2c; + let mut conn = rustls::ServerConnection::new(config.clone()) + .expect("failed to create server config"); + + let mut tls = rustls::Stream::new(&mut conn, &mut stream); + + { + let mut buf = [0; CLIENT_MAGIC.len()]; + tls.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, CLIENT_MAGIC); + } + + tls.write_all(SERVER_MAGIC) + .expect("failed to write to client"); + tls.write_all(&random_data) + .expect("failed to write random data to client"); + tls.conn.send_close_notify(); + tls.flush().expect("failed to flush connection"); + } + }) + .unwrap(); + + std::thread::Builder::new() + .name(format!("{base_name}-client")) + .spawn_scoped(s, move || { + let mut sock = socket_c2s; + let server_name = "acme.com".try_into().expect("failed to get server name"); + let mut conn = rustls::ClientConnection::new( + Arc::new(make_client_config(provider)), + server_name, + ) + .expect("failed to create client config"); + let mut tls = rustls::Stream::new(&mut conn, &mut sock); + tls.write_all(CLIENT_MAGIC) + .expect("failed to write to server"); + + { + let mut buf = [0; SERVER_MAGIC.len()]; + tls.read_exact(&mut buf) + .expect("failed to read from server"); + assert_eq!(&buf, SERVER_MAGIC); + } + + { + let mut plaintext = Vec::new(); + tls.write_all(&random_data) + .expect("failed to write random data to server"); + tls.read_to_end(&mut plaintext) + .expect("failed to read from server"); + assert_eq!(plaintext, random_data); + } + }) + .unwrap(); + } + }); +} - // RustCrypto is not fips - assert!(!rustls_client_config.fips()); +fn generate_providers() -> impl Iterator { + let CryptoProvider { + cipher_suites, + kx_groups, + .. + } = rustcrypto_provider(); + + iproduct!(cipher_suites, kx_groups).map(|(cipher_suite, kx_group)| CryptoProvider { + cipher_suites: vec![cipher_suite], + kx_groups: vec![kx_group], + signature_verification_algorithms: verify::ALGORITHMS, + secure_random: &Provider, + key_provider: &Provider, + }) } + +mod mem_socket; diff --git a/tests/fake_cert_client_verifier.rs b/tests/fake_cert_client_verifier.rs deleted file mode 100644 index a8dc9cc..0000000 --- a/tests/fake_cert_client_verifier.rs +++ /dev/null @@ -1,71 +0,0 @@ -use rustls::DistinguishedName; -use rustls::Error; - -use rustls::SignatureScheme; - -use rustls::pki_types::CertificateDer; -use rustls::pki_types::UnixTime; -use rustls::DigitallySignedStruct; - -use rustls::client::danger::HandshakeSignatureValid; -use rustls::server::danger::ClientCertVerified; -use rustls::server::danger::ClientCertVerifier; - -#[derive(Debug)] -pub struct FakeClientCertVerifier { - pub dn: [DistinguishedName; 1], -} - -impl ClientCertVerifier for FakeClientCertVerifier { - fn root_hint_subjects(&self) -> &[DistinguishedName] { - &self.dn - } - fn verify_client_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _now: UnixTime, - ) -> Result { - Ok(ClientCertVerified::assertion()) - } - fn verify_tls12_signature( - &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &DigitallySignedStruct, - ) -> Result { - Ok(HandshakeSignatureValid::assertion()) - } - fn verify_tls13_signature( - &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &DigitallySignedStruct, - ) -> Result { - Ok(HandshakeSignatureValid::assertion()) - } - fn supported_verify_schemes(&self) -> Vec { - vec![ - SignatureScheme::RSA_PKCS1_SHA1, - SignatureScheme::ECDSA_SHA1_Legacy, - SignatureScheme::RSA_PKCS1_SHA256, - SignatureScheme::ECDSA_NISTP256_SHA256, - SignatureScheme::RSA_PKCS1_SHA384, - SignatureScheme::ECDSA_NISTP384_SHA384, - SignatureScheme::RSA_PKCS1_SHA512, - SignatureScheme::ECDSA_NISTP521_SHA512, - SignatureScheme::RSA_PSS_SHA256, - SignatureScheme::RSA_PSS_SHA384, - SignatureScheme::RSA_PSS_SHA512, - SignatureScheme::ED25519, - SignatureScheme::ED448, - //SignatureScheme::Unknown(u16), - ] - } - fn offer_client_auth(&self) -> bool { - true - } - fn client_auth_mandatory(&self) -> bool { - false - } -} diff --git a/tests/fake_cert_server_resolver.rs b/tests/fake_cert_server_resolver.rs index 7028c8b..3b49ee4 100644 --- a/tests/fake_cert_server_resolver.rs +++ b/tests/fake_cert_server_resolver.rs @@ -1,15 +1,196 @@ +use core::time::Duration; +use std::str::FromStr; use std::sync::Arc; -use rustls::server::ClientHello; - -use rustls::server::ResolvesServerCert; +use itertools::iproduct; +use pki_types::{CertificateDer, PrivateKeyDer}; +use rand_core_064::{OsRng, RngCore}; +use rsa_098::pkcs8::{EncodePrivateKey, EncodePublicKey}; +use rustls::CipherSuite::{ + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, +}; +use rustls::server::{ClientHello, ResolvesServerCert}; use rustls::sign::CertifiedKey; +use rustls_rustcrypto::sign::any_supported_type; +use signature_220::{Keypair, Signer}; +use x509_cert::builder::{Builder, CertificateBuilder, Profile, RequestBuilder}; +use x509_cert::der::{ + Encode, + asn1::{GeneralizedTime, Ia5String}, +}; +use x509_cert::ext::pkix::{SubjectAltName, name::GeneralName}; +use x509_cert::name::Name; +use x509_cert::serial_number::SerialNumber; +use x509_cert::spki::{ + SignatureAlgorithmIdentifier, SignatureBitStringEncoding, SubjectPublicKeyInfoOwned, +}; +use x509_cert::time::{Time, Validity}; #[derive(Debug)] -pub struct FakeServerCertResolver; +pub struct FakeServerCertResolver { + rsa_cert_key: Arc, + ecdsa_cert_key: Arc, + rsa_root_cert: CertificateDer<'static>, + ecdsa_root_cert: CertificateDer<'static>, +} + +impl FakeServerCertResolver { + pub fn new() -> Self { + let (rsa_root_cert, rsa_root_key) = Self::generate_root_cert(|| { + // by running a binary search between 1024 bit and 2048 bit, 1034 bit is the first possible bit size after 1024 bit + rsa_098::pkcs1v15::SigningKey::::random(&mut OsRng, 1034) + .unwrap() + }); + let (ecdsa_root_cert, ecdsa_root_key) = + Self::generate_root_cert::<_, p256_0132::ecdsa::DerSignature>(|| { + p256_0132::ecdsa::SigningKey::random(&mut OsRng) + }); + + let (rsa_cert, rsa_key) = Self::generate_cert( + || { + // by running a binary search between 1024 bit and 2048 bit, 1034 bit is the first possible bit size after 1024 bit + rsa_098::pkcs1v15::SigningKey::::random(&mut OsRng, 1034) + .unwrap() + }, + rsa_root_key, + ); + let (ecdsa_cert, ecdsa_key) = Self::generate_cert::<_, _, p256_0132::ecdsa::DerSignature>( + || p256_0132::ecdsa::SigningKey::random(&mut OsRng), + ecdsa_root_key, + ); + + Self { + rsa_root_cert: rsa_root_cert.clone(), + ecdsa_root_cert: ecdsa_root_cert.clone(), + rsa_cert_key: Arc::new(CertifiedKey::new( + vec![rsa_cert], + any_supported_type(&rsa_key).unwrap(), + )), + ecdsa_cert_key: Arc::new(CertifiedKey::new( + vec![ecdsa_cert], + any_supported_type(&ecdsa_key).unwrap(), + )), + } + } + + pub fn rsa_root_cert(&self) -> CertificateDer<'static> { + self.rsa_root_cert.clone() + } + + pub fn ecdsa_root_cert(&self) -> CertificateDer<'static> { + self.ecdsa_root_cert.clone() + } + + fn generate_root_cert( + key_fn: impl Fn() -> Key, + ) -> (CertificateDer<'static>, Key) + where + Key: Signer + Keypair + SignatureAlgorithmIdentifier + EncodePrivateKey, + Signature: SignatureBitStringEncoding, + ::VerifyingKey: EncodePublicKey, + { + let signing_key = key_fn(); + ( + CertificateBuilder::new( + Profile::Root, + SerialNumber::from(OsRng.next_u64()), + Validity { + not_before: Time::GeneralTime( + GeneralizedTime::from_unix_duration(Duration::ZERO).unwrap(), + ), + not_after: Time::INFINITY, + }, + Name::from_str("CN=ACME Corporation CA,O=ACME Corporation,C=US").unwrap(), + SubjectPublicKeyInfoOwned::from_key(signing_key.verifying_key()).unwrap(), + &signing_key, + ) + .unwrap() + .build::() + .unwrap() + .to_der() + .unwrap() + .into(), + signing_key, + ) + } + fn generate_cert( + key_fn: impl Fn() -> Key, + ca_key: CaKey, + ) -> (CertificateDer<'static>, PrivateKeyDer<'static>) + where + Key: Signer + Keypair + SignatureAlgorithmIdentifier + EncodePrivateKey, + CaKey: Signer + Keypair + SignatureAlgorithmIdentifier + EncodePrivateKey, + Signature: SignatureBitStringEncoding, + ::VerifyingKey: EncodePublicKey, + ::VerifyingKey: EncodePublicKey, + { + let signing_key = key_fn(); + + let request = RequestBuilder::new(Name::from_str("CN=acme.com").unwrap(), &signing_key) + .unwrap() + .build() + .unwrap(); + + let mut builder = CertificateBuilder::new( + Profile::Leaf { + issuer: Name::from_str("CN=ACME Corporation CA,O=ACME Corporation,C=US").unwrap(), + enable_key_agreement: true, + enable_key_encipherment: true, + }, + SerialNumber::from(OsRng.next_u64()), + Validity { + not_before: Time::GeneralTime( + GeneralizedTime::from_unix_duration(Duration::ZERO).unwrap(), + ), + not_after: Time::INFINITY, + }, + request.info.subject, + request.info.public_key, + &ca_key, + ) + .unwrap(); + builder + .add_extension(&SubjectAltName(vec![GeneralName::DnsName( + Ia5String::new(b"acme.com").unwrap(), + )])) + .unwrap(); + ( + builder + .build::() + .unwrap() + .to_der() + .unwrap() + .into(), + PrivateKeyDer::Pkcs8( + signing_key + .to_pkcs8_der() + .unwrap() + .as_bytes() + .to_vec() + .into(), + ), + ) + } +} impl ResolvesServerCert for FakeServerCertResolver { - fn resolve(&self, _client_hello: ClientHello<'_>) -> Option> { - None + fn resolve(&self, hello: ClientHello<'_>) -> Option> { + Some( + if iproduct!( + [ + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + ], + hello.cipher_suites() + ) + .any(|(a, &b)| a == b) + { + self.rsa_cert_key.clone() + } else { + self.ecdsa_cert_key.clone() + }, + ) } } diff --git a/tests/fake_cert_server_verifier.rs b/tests/fake_cert_server_verifier.rs deleted file mode 100644 index 2786058..0000000 --- a/tests/fake_cert_server_verifier.rs +++ /dev/null @@ -1,59 +0,0 @@ -use rustls::client::danger::HandshakeSignatureValid; -use rustls::client::danger::ServerCertVerified; -use rustls::client::danger::ServerCertVerifier; -use rustls::pki_types::CertificateDer; -use rustls::pki_types::ServerName; -use rustls::pki_types::UnixTime; -use rustls::DigitallySignedStruct; -use rustls::Error; -use rustls::SignatureScheme; - -#[derive(Debug)] -pub struct FakeServerCertVerifier; - -impl ServerCertVerifier for FakeServerCertVerifier { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp_response: &[u8], - _now: UnixTime, - ) -> Result { - Ok(ServerCertVerified::assertion()) - } - fn verify_tls12_signature( - &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &DigitallySignedStruct, - ) -> Result { - Ok(HandshakeSignatureValid::assertion()) - } - fn verify_tls13_signature( - &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &DigitallySignedStruct, - ) -> Result { - Ok(HandshakeSignatureValid::assertion()) - } - fn supported_verify_schemes(&self) -> Vec { - vec![ - SignatureScheme::RSA_PKCS1_SHA1, - SignatureScheme::ECDSA_SHA1_Legacy, - SignatureScheme::RSA_PKCS1_SHA256, - SignatureScheme::ECDSA_NISTP256_SHA256, - SignatureScheme::RSA_PKCS1_SHA384, - SignatureScheme::ECDSA_NISTP384_SHA384, - SignatureScheme::RSA_PKCS1_SHA512, - SignatureScheme::ECDSA_NISTP521_SHA512, - SignatureScheme::RSA_PSS_SHA256, - SignatureScheme::RSA_PSS_SHA384, - SignatureScheme::RSA_PSS_SHA512, - SignatureScheme::ED25519, - SignatureScheme::ED448, - //SignatureScheme::Unknown(u16), - ] - } -} diff --git a/tests/fake_time.rs b/tests/fake_time.rs index 83dc3fa..12d82e9 100644 --- a/tests/fake_time.rs +++ b/tests/fake_time.rs @@ -1,3 +1,5 @@ +use core::time::Duration; + use rustls::pki_types::UnixTime; use rustls::time_provider::TimeProvider; @@ -6,6 +8,6 @@ pub struct FakeTime; impl TimeProvider for FakeTime { fn current_time(&self) -> Option { - None + Some(UnixTime::since_unix_epoch(Duration::ZERO)) } } diff --git a/tests/mem_socket.rs b/tests/mem_socket.rs new file mode 100644 index 0000000..63e3fc7 --- /dev/null +++ b/tests/mem_socket.rs @@ -0,0 +1,102 @@ +use std::{ + io::{self, ErrorKind, Read, Write}, + sync::mpsc::{Receiver, Sender, channel}, +}; + +use bytes::{Buf, Bytes, BytesMut}; + +// The code is derived from: https://github.com/bmwill/memory-socket/blob/74110b18318c261e86d08aa53a7abe3e2a881538/src/lib.rs#L271-L405 +pub struct MemorySocket { + incoming: Receiver, + outgoing: Sender, + write_buffer: BytesMut, + current_buffer: Option, + seen_eof: bool, +} + +impl MemorySocket { + fn new(incoming: Receiver, outgoing: Sender) -> Self { + Self { + incoming, + outgoing, + write_buffer: BytesMut::new(), + current_buffer: None, + seen_eof: false, + } + } + + pub fn new_pair() -> (Self, Self) { + let (a_tx, a_rx) = channel(); + let (b_tx, b_rx) = channel(); + let a = Self::new(a_rx, b_tx); + let b = Self::new(b_rx, a_tx); + + (a, b) + } +} + +impl Read for MemorySocket { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut bytes_read = 0; + + loop { + // If we've already filled up the buffer then we can return + if bytes_read == buf.len() { + return Ok(bytes_read); + } + + match self.current_buffer { + // We still have data to copy to `buf` + Some(ref mut current_buffer) if current_buffer.has_remaining() => { + let bytes_to_read = current_buffer.remaining().min(buf.len() - bytes_read); + debug_assert!(bytes_to_read > 0); + + current_buffer + .take(bytes_to_read) + .copy_to_slice(&mut buf[bytes_read..(bytes_read + bytes_to_read)]); + bytes_read += bytes_to_read; + } + + // Either we've exhausted our current buffer or we don't have one + _ => { + // If we've read anything up to this point return the bytes read + if bytes_read > 0 { + return Ok(bytes_read); + } + + self.current_buffer = match self.incoming.recv() { + Ok(buf) => Some(buf), + + // The remote side hung up, if this is the first time we've seen EOF then + // we should return `Ok(0)` otherwise an UnexpectedEof Error + Err(_) if self.seen_eof => { + return Err(ErrorKind::UnexpectedEof.into()); + } + + Err(_) => { + self.seen_eof = true; + return Ok(0); + } + } + } + } + } + } +} + +impl Write for MemorySocket { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.write_buffer.extend_from_slice(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + if !self.write_buffer.is_empty() { + self.outgoing + .send(self.write_buffer.split().freeze()) + .map_err(|_| ErrorKind::BrokenPipe.into()) + } else { + Ok(()) + } + } +}