diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6330417..102246a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -71,6 +71,7 @@ jobs: - --all-features - --features=verify - --features=verify-aws + - --features=verify-rustcrypto - --features=validate steps: - uses: actions/checkout@v4 diff --git a/Cargo.lock b/Cargo.lock index be67eae..a9654e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,18 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bindgen" version = "0.69.5" @@ -109,6 +121,15 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "cc" version = "1.2.32" @@ -155,12 +176,87 @@ dependencies = [ "cc", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "data-encoding" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "der-parser" version = "10.0.0" @@ -184,6 +280,18 @@ dependencies = [ "powerfmt", ] +[[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", + "subtle", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -201,12 +309,71 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[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", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "errno" version = "0.3.13" @@ -217,12 +384,39 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -252,6 +446,35 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.11" @@ -291,6 +514,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "lazycell" @@ -314,6 +540,12 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -358,6 +590,22 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -373,6 +621,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -380,6 +639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -397,12 +657,75 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +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", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.36" @@ -413,6 +736,15 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.97" @@ -437,6 +769,35 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + [[package]] name = "regex" version = "1.11.1" @@ -466,6 +827,16 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -480,12 +851,41 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rusticata-macros" version = "4.1.0" @@ -508,6 +908,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.219" @@ -528,12 +948,72 @@ dependencies = [ "syn", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[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", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.105" @@ -607,6 +1087,12 @@ dependencies = [ "time-core", ] +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -625,6 +1111,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -831,15 +1323,41 @@ dependencies = [ "aws-lc-rs", "data-encoding", "der-parser", + "ed25519-dalek", "lazy_static", "nom", "oid-registry", + "p256", + "p384", "ring", + "rsa", "rusticata-macros", + "sha1", + "sha2", "thiserror", "time", ] +[[package]] +name = "zerocopy" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.8.1" diff --git a/Cargo.toml b/Cargo.toml index 9c9117c..c378432 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,18 +40,25 @@ rustdoc-args = ["--cfg", "docsrs"] default = [] verify-aws = ["aws-lc-rs"] verify = ["ring"] +verify-rustcrypto = ["dep:rsa", "dep:p256", "dep:p384", "dep:ed25519-dalek", "dep:sha1", "dep:sha2"] validate = [] [dependencies] aws-lc-rs = { version = "1.0", optional = true } asn1-rs = { version = "0.7.0", features=["datetime"] } data-encoding = "2.2.1" +der-parser = { version = "10.0", features=["bigint"] } +ed25519-dalek = { version = "2", optional = true, default-features = false, features = ["std"] } lazy_static = "1.4" nom = "7.0" oid-registry = { version="0.8.1", features=["crypto", "x509", "x962"] } -rusticata-macros = "4.0" +p256 = { version = "0.13", optional = true, features = ["ecdsa"] } +p384 = { version = "0.13", optional = true, features = ["ecdsa"] } ring = { version="0.17.12", optional=true } -der-parser = { version = "10.0", features=["bigint"] } +rsa = { version = "0.9", optional = true } +rusticata-macros = "4.0" +sha1 = { version = "0.10", optional = true, features = ["oid"] } +sha2 = { version = "0.10", optional = true, features = ["oid"] } thiserror = "2.0" time = { version="0.3.35", features=["formatting"] } diff --git a/assets/ecdsa_p256_sha256.der b/assets/ecdsa_p256_sha256.der new file mode 100644 index 0000000..c5b6586 Binary files /dev/null and b/assets/ecdsa_p256_sha256.der differ diff --git a/assets/ecdsa_p256_sha384.der b/assets/ecdsa_p256_sha384.der new file mode 100644 index 0000000..1040d10 Binary files /dev/null and b/assets/ecdsa_p256_sha384.der differ diff --git a/assets/ecdsa_p384_sha256.der b/assets/ecdsa_p384_sha256.der new file mode 100644 index 0000000..e962a9d Binary files /dev/null and b/assets/ecdsa_p384_sha256.der differ diff --git a/assets/ecdsa_p384_sha384.der b/assets/ecdsa_p384_sha384.der new file mode 100644 index 0000000..decf952 Binary files /dev/null and b/assets/ecdsa_p384_sha384.der differ diff --git a/assets/rsa-pss/self_signed_sha256_saltlen42.der b/assets/rsa-pss/self_signed_sha256_saltlen42.der new file mode 100644 index 0000000..355872f Binary files /dev/null and b/assets/rsa-pss/self_signed_sha256_saltlen42.der differ diff --git a/examples/print-cert.rs b/examples/print-cert.rs index 46e9b63..2f51de4 100644 --- a/examples/print-cert.rs +++ b/examples/print-cert.rs @@ -208,7 +208,11 @@ fn print_x509_info(x509: &X509Certificate) -> io::Result<()> { { println!("Unknown (feature 'validate' not enabled)"); } - #[cfg(any(feature = "verify", feature = "verify-aws"))] + #[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" + ))] { print!("Signature verification: "); if x509.subject() == x509.issuer() { diff --git a/src/certificate.rs b/src/certificate.rs index a954d92..1c29750 100644 --- a/src/certificate.rs +++ b/src/certificate.rs @@ -11,7 +11,11 @@ use crate::x509::{ X509Version, }; -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" +))] use crate::verify::verify_signature; use asn1_rs::{BitString, FromDer, OptTaggedImplicit}; use core::ops::Deref; @@ -91,9 +95,20 @@ impl<'a> X509Certificate<'a> { /// For a leaf certificate, this is the public key of the certificate that signed it. /// It is usually an intermediate authority. /// - /// Not all algorithms are supported, this function is limited to what `ring` supports. - #[cfg(any(feature = "verify", feature = "verify-aws"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "verify", feature = "verify-aws"))))] + /// Not all algorithms are supported, this function is limited to what the selected backend supports. + #[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" + ))) + )] pub fn verify_signature( &self, public_key: Option<&SubjectPublicKeyInfo>, diff --git a/src/certification_request.rs b/src/certification_request.rs index f832c09..eebb60a 100644 --- a/src/certification_request.rs +++ b/src/certification_request.rs @@ -5,7 +5,11 @@ use crate::x509::{ parse_signature_value, AlgorithmIdentifier, SubjectPublicKeyInfo, X509Name, X509Version, }; -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" +))] use crate::verify::verify_signature; use asn1_rs::{BitString, FromDer}; use der_parser::der::*; @@ -50,8 +54,19 @@ impl<'a> X509CertificationRequest<'a> { /// /// Uses the public key contained in the CSR, which must be the one of the entity /// requesting the certification for this verification to succeed. - #[cfg(any(feature = "verify", feature = "verify-aws"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "verify", feature = "verify-aws"))))] + #[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" + ))) + )] pub fn verify_signature(&self) -> Result<(), X509Error> { let spki = &self.certification_request_info.subject_pki; verify_signature( diff --git a/src/lib.rs b/src/lib.rs index 24e9d9d..42ae74d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,10 +93,10 @@ //! to `X509Certificate`. //! //! ```rust -//! # #[cfg(any(feature = "verify", feature = "verify-aws"))] +//! # #[cfg(any(feature = "verify", feature = "verify-aws", feature = "verify-rustcrypto"))] //! # use x509_parser::certificate::X509Certificate; //! /// Cryptographic signature verification: returns true if certificate was signed by issuer -//! #[cfg(any(feature = "verify", feature = "verify-aws"))] +//! #[cfg(any(feature = "verify", feature = "verify-aws", feature = "verify-rustcrypto"))] //! pub fn check_signature(cert: &X509Certificate<'_>, issuer: &X509Certificate<'_>) -> bool { //! let issuer_public_key = issuer.public_key(); //! cert @@ -157,8 +157,19 @@ pub mod utils; #[cfg(feature = "validate")] #[cfg_attr(docsrs, doc(cfg(feature = "validate")))] pub mod validate; -#[cfg(any(feature = "verify", feature = "verify-aws"))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "verify", feature = "verify-aws"))))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" +))] +#[cfg_attr( + docsrs, + doc(cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" + ))) +)] pub mod verify; pub mod visitor; pub mod x509; diff --git a/src/revocation_list.rs b/src/revocation_list.rs index 70da281..75ccd6d 100644 --- a/src/revocation_list.rs +++ b/src/revocation_list.rs @@ -6,9 +6,17 @@ use crate::x509::{ parse_serial, parse_signature_value, AlgorithmIdentifier, ReasonCode, X509Name, X509Version, }; -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" +))] use crate::verify::verify_signature; -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" +))] use crate::x509::SubjectPublicKeyInfo; use asn1_rs::{BitString, FromDer}; use der_parser::der::*; @@ -113,9 +121,20 @@ impl<'a> CertificateRevocationList<'a> { /// /// `public_key` is the public key of the **signer**. /// - /// Not all algorithms are supported, this function is limited to what `ring` supports. - #[cfg(any(feature = "verify", feature = "verify-aws"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "verify", feature = "verify-aws"))))] + /// Not all algorithms are supported, this function is limited to what the selected backend supports. + #[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" + ))] + #[cfg_attr( + docsrs, + doc(cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" + ))) + )] pub fn verify_signature(&self, public_key: &SubjectPublicKeyInfo) -> Result<(), X509Error> { verify_signature( public_key, diff --git a/src/verify.rs b/src/verify.rs index 94a2fce..59c83a4 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -9,6 +9,8 @@ use oid_registry::{ }; use std::convert::TryFrom; +// ---- Ring / aws-lc-rs backend ---- + // Since the `signature` object is similar in ring and in aws-lc-rs, we just use simple logic // to determine which one to use. // If both verify and verify-aws features are enabled, aws will be used. @@ -22,6 +24,7 @@ use ring::signature; /// `public_key` is the public key of the **signer**. /// /// Not all algorithms are supported, this function is limited to what `aws_lc_rs` or `ring` supports. +#[cfg(any(feature = "verify-aws", feature = "verify"))] pub fn verify_signature( public_key: &SubjectPublicKeyInfo, signature_algorithm: &AlgorithmIdentifier, @@ -70,12 +73,12 @@ pub fn verify_signature( /// Find the verification algorithm for the given EC curve and SHA digest size /// /// Not all algorithms are supported, we are limited to what `aws_lc_rs` or `ring`supports. +#[cfg(any(feature = "verify-aws", feature = "verify"))] fn get_ec_curve_sha( pubkey_alg: &AlgorithmIdentifier, sha_len: usize, ) -> Option<&'static dyn signature::VerificationAlgorithm> { let curve_oid = pubkey_alg.parameters.as_ref()?.as_oid().ok()?; - // let curve_oid = pubkey_alg.parameters.as_ref()?.as_oid().ok()?; if curve_oid == OID_EC_P256 { match sha_len { 256 => Some(&signature::ECDSA_P256_SHA256_ASN1), @@ -97,6 +100,7 @@ fn get_ec_curve_sha( /// /// Not all algorithms are supported, we are limited to what `aws_lc_rs` or `ring` supports. /// Notably, the SHA-1 hash algorithm is not supported. +#[cfg(any(feature = "verify-aws", feature = "verify"))] fn get_rsa_pss_verification_algo( params: &Option, ) -> Option<&'static dyn signature::VerificationAlgorithm> { @@ -114,3 +118,247 @@ fn get_rsa_pss_verification_algo( None } } + +// ---- RustCrypto backend ---- + +/// Verify the cryptographic signature of the raw data (can be a certificate, a CRL or a CSR). +/// +/// `public_key` is the public key of the **signer**. +/// +/// Not all algorithms are supported, this function is limited to what the RustCrypto crates support. +#[cfg(all( + feature = "verify-rustcrypto", + not(feature = "verify"), + not(feature = "verify-aws") +))] +pub fn verify_signature( + public_key: &SubjectPublicKeyInfo, + signature_algorithm: &AlgorithmIdentifier, + signature_value: &BitString, + raw_data: &[u8], +) -> Result<(), X509Error> { + let AlgorithmIdentifier { + algorithm: sig_alg, + parameters: sig_params, + } = &signature_algorithm; + + let key_bytes: &[u8] = public_key.subject_public_key.as_ref(); + let sig_bytes: &[u8] = signature_value.as_ref(); + + if *sig_alg == OID_PKCS1_SHA1WITHRSA || *sig_alg == OID_SHA1_WITH_RSA { + rc_verify_rsa_pkcs1v15::(key_bytes, sig_bytes, raw_data) + } else if *sig_alg == OID_PKCS1_SHA256WITHRSA { + rc_verify_rsa_pkcs1v15::(key_bytes, sig_bytes, raw_data) + } else if *sig_alg == OID_PKCS1_SHA384WITHRSA { + rc_verify_rsa_pkcs1v15::(key_bytes, sig_bytes, raw_data) + } else if *sig_alg == OID_PKCS1_SHA512WITHRSA { + rc_verify_rsa_pkcs1v15::(key_bytes, sig_bytes, raw_data) + } else if *sig_alg == OID_PKCS1_RSASSAPSS { + rc_verify_rsa_pss(key_bytes, sig_params, sig_bytes, raw_data) + } else if *sig_alg == OID_SIG_ECDSA_WITH_SHA256 { + rc_verify_ecdsa(&public_key.algorithm, key_bytes, sig_bytes, raw_data, 256) + } else if *sig_alg == OID_SIG_ECDSA_WITH_SHA384 { + rc_verify_ecdsa(&public_key.algorithm, key_bytes, sig_bytes, raw_data, 384) + } else if *sig_alg == OID_SIG_ED25519 { + rc_verify_ed25519(key_bytes, sig_bytes, raw_data) + } else { + Err(X509Error::SignatureUnsupportedAlgorithm) + } +} + +/// Verify an RSA PKCS#1 v1.5 signature using the selected digest. +#[cfg(all( + feature = "verify-rustcrypto", + not(feature = "verify"), + not(feature = "verify-aws") +))] +fn rc_verify_rsa_pkcs1v15( + key_bytes: &[u8], + sig_bytes: &[u8], + data: &[u8], +) -> Result<(), X509Error> +where + D: sha2::digest::Digest + sha2::digest::const_oid::AssociatedOid, +{ + use core::convert::TryFrom; + use rsa::pkcs1::DecodeRsaPublicKey; + use rsa::signature::Verifier; + + let rsa_key = rsa::RsaPublicKey::from_pkcs1_der(key_bytes) + .map_err(|_| X509Error::SignatureVerificationError)?; + let verifying_key = rsa::pkcs1v15::VerifyingKey::::new(rsa_key); + let sig = rsa::pkcs1v15::Signature::try_from(sig_bytes) + .map_err(|_| X509Error::SignatureVerificationError)?; + verifying_key + .verify(data, &sig) + .map_err(|_| X509Error::SignatureVerificationError) +} + +/// Verify an RSA-PSS signature using RustCrypto. +/// +/// Validates the full RSASSA-PSS-params: hash algorithm, mask generation algorithm, +/// salt length, and trailer field. The SHA-1 hash algorithm is not supported. +#[cfg(all( + feature = "verify-rustcrypto", + not(feature = "verify"), + not(feature = "verify-aws") +))] +fn rc_verify_rsa_pss( + key_bytes: &[u8], + params: &Option, + sig_bytes: &[u8], + data: &[u8], +) -> Result<(), X509Error> { + let params = params + .as_ref() + .ok_or(X509Error::SignatureUnsupportedAlgorithm)?; + let params = + RsaSsaPssParams::try_from(params).map_err(|_| X509Error::SignatureUnsupportedAlgorithm)?; + + // RFC 4055: trailerField must be 1 + if params.trailer_field() != 1 { + return Err(X509Error::SignatureUnsupportedAlgorithm); + } + + let hash_oid = params.hash_algorithm_oid(); + + // Validate that the MGF1 hash matches the signature hash. + // The rsa crate uses the same digest for both, so we must reject mismatches. + let mgf = params + .mask_gen_algorithm() + .map_err(|_| X509Error::SignatureUnsupportedAlgorithm)?; + // id-mgf1 OID: 1.2.840.113549.1.1.8 + if mgf.mgf != asn1_rs::oid!(1.2.840 .113549 .1 .1 .8) { + return Err(X509Error::SignatureUnsupportedAlgorithm); + } + if mgf.hash != *hash_oid { + return Err(X509Error::SignatureUnsupportedAlgorithm); + } + + let salt_len = params.salt_length() as usize; + + if *hash_oid == OID_NIST_HASH_SHA256 { + rc_verify_rsa_pss_with_hash::(key_bytes, sig_bytes, data, salt_len) + } else if *hash_oid == OID_NIST_HASH_SHA384 { + rc_verify_rsa_pss_with_hash::(key_bytes, sig_bytes, data, salt_len) + } else if *hash_oid == OID_NIST_HASH_SHA512 { + rc_verify_rsa_pss_with_hash::(key_bytes, sig_bytes, data, salt_len) + } else { + Err(X509Error::SignatureUnsupportedAlgorithm) + } +} + +/// Verify an RSA-PSS signature using the selected digest and salt length. +#[cfg(all( + feature = "verify-rustcrypto", + not(feature = "verify"), + not(feature = "verify-aws") +))] +fn rc_verify_rsa_pss_with_hash( + key_bytes: &[u8], + sig_bytes: &[u8], + data: &[u8], + salt_len: usize, +) -> Result<(), X509Error> +where + D: sha2::digest::Digest + sha2::digest::FixedOutputReset, +{ + use core::convert::TryFrom; + use rsa::pkcs1::DecodeRsaPublicKey; + use rsa::signature::Verifier; + + let rsa_key = rsa::RsaPublicKey::from_pkcs1_der(key_bytes) + .map_err(|_| X509Error::SignatureVerificationError)?; + let verifying_key = rsa::pss::VerifyingKey::::new_with_salt_len(rsa_key, salt_len); + let sig = rsa::pss::Signature::try_from(sig_bytes) + .map_err(|_| X509Error::SignatureVerificationError)?; + verifying_key + .verify(data, &sig) + .map_err(|_| X509Error::SignatureVerificationError) +} + +/// Verify an ECDSA signature using the curve from the signer's SPKI and the hash +/// implied by the signature algorithm. +/// +/// RustCrypto's `Verifier` trait uses the curve's default digest, so non-default +/// curve/hash pairings are handled via `verify_prehash`. +#[cfg(all( + feature = "verify-rustcrypto", + not(feature = "verify"), + not(feature = "verify-aws") +))] +fn rc_verify_ecdsa( + pubkey_alg: &AlgorithmIdentifier, + key_bytes: &[u8], + sig_bytes: &[u8], + data: &[u8], + sha_len: usize, +) -> Result<(), X509Error> { + let curve_oid = pubkey_alg + .parameters + .as_ref() + .and_then(|p| p.as_oid().ok()) + .ok_or(X509Error::SignatureUnsupportedAlgorithm)?; + + if curve_oid == OID_EC_P256 && sha_len == 256 { + use p256::ecdsa::signature::Verifier; + let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(|_| X509Error::SignatureVerificationError)?; + let sig = p256::ecdsa::DerSignature::from_bytes(sig_bytes) + .map_err(|_| X509Error::SignatureVerificationError)?; + vk.verify(data, &sig) + .map_err(|_| X509Error::SignatureVerificationError) + } else if curve_oid == OID_EC_P256 && sha_len == 384 { + use p256::ecdsa::signature::hazmat::PrehashVerifier; + use sha2::Digest; + let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(|_| X509Error::SignatureVerificationError)?; + let sig = p256::ecdsa::DerSignature::from_bytes(sig_bytes) + .map_err(|_| X509Error::SignatureVerificationError)?; + let digest = sha2::Sha384::digest(data); + vk.verify_prehash(&digest, &sig) + .map_err(|_| X509Error::SignatureVerificationError) + } else if curve_oid == OID_NIST_EC_P384 && sha_len == 384 { + use p384::ecdsa::signature::Verifier; + let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(|_| X509Error::SignatureVerificationError)?; + let sig = p384::ecdsa::DerSignature::from_bytes(sig_bytes) + .map_err(|_| X509Error::SignatureVerificationError)?; + vk.verify(data, &sig) + .map_err(|_| X509Error::SignatureVerificationError) + } else if curve_oid == OID_NIST_EC_P384 && sha_len == 256 { + use p384::ecdsa::signature::hazmat::PrehashVerifier; + use sha2::Digest; + let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(key_bytes) + .map_err(|_| X509Error::SignatureVerificationError)?; + let sig = p384::ecdsa::DerSignature::from_bytes(sig_bytes) + .map_err(|_| X509Error::SignatureVerificationError)?; + let digest = sha2::Sha256::digest(data); + vk.verify_prehash(&digest, &sig) + .map_err(|_| X509Error::SignatureVerificationError) + } else { + Err(X509Error::SignatureUnsupportedAlgorithm) + } +} + +#[cfg(all( + feature = "verify-rustcrypto", + not(feature = "verify"), + not(feature = "verify-aws") +))] +fn rc_verify_ed25519(key_bytes: &[u8], sig_bytes: &[u8], data: &[u8]) -> Result<(), X509Error> { + use core::convert::TryInto; + use ed25519_dalek::Verifier; + + let key_array: [u8; 32] = key_bytes + .try_into() + .map_err(|_| X509Error::SignatureVerificationError)?; + let vk = ed25519_dalek::VerifyingKey::from_bytes(&key_array) + .map_err(|_| X509Error::SignatureVerificationError)?; + let sig_array: [u8; 64] = sig_bytes + .try_into() + .map_err(|_| X509Error::SignatureVerificationError)?; + let sig = ed25519_dalek::Signature::from_bytes(&sig_array); + vk.verify(data, &sig) + .map_err(|_| X509Error::SignatureVerificationError) +} diff --git a/tests/readcrl.rs b/tests/readcrl.rs index 18a30a5..812526b 100644 --- a/tests/readcrl.rs +++ b/tests/readcrl.rs @@ -1,6 +1,10 @@ use x509_parser::prelude::*; -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" +))] #[test] fn read_crl_verify() { const CA_DATA: &[u8] = include_bytes!("../assets/ca_minimalcrl.der"); diff --git a/tests/readcsr.rs b/tests/readcsr.rs index 777753c..92ce7b4 100644 --- a/tests/readcsr.rs +++ b/tests/readcsr.rs @@ -106,7 +106,11 @@ fn read_csr_with_challenge_password() { assert!(found_san); } -#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" +))] #[test] fn read_csr_verify() { let pem = pem::parse_x509_pem(CSR_DATA).unwrap().1; diff --git a/tests/verify.rs b/tests/verify.rs index d376aee..7674a79 100644 --- a/tests/verify.rs +++ b/tests/verify.rs @@ -1,4 +1,8 @@ -#![cfg(any(feature = "verify", feature = "verify-aws"))] +#![cfg(any( + feature = "verify", + feature = "verify-aws", + feature = "verify-rustcrypto" +))] use x509_parser::parse_x509_certificate; @@ -34,12 +38,60 @@ fn test_signature_verification_ed25519() { assert!(res.is_ok()); } +static ECDSA_P256_SHA256_DER: &[u8] = include_bytes!("../assets/ecdsa_p256_sha256.der"); +static ECDSA_P256_SHA384_DER: &[u8] = include_bytes!("../assets/ecdsa_p256_sha384.der"); +static ECDSA_P384_SHA256_DER: &[u8] = include_bytes!("../assets/ecdsa_p384_sha256.der"); +static ECDSA_P384_SHA384_DER: &[u8] = include_bytes!("../assets/ecdsa_p384_sha384.der"); + +#[test] +fn test_signature_verification_ecdsa_p256_sha256() { + let (_, x509) = + parse_x509_certificate(ECDSA_P256_SHA256_DER).expect("could not parse certificate"); + let res = x509.verify_signature(None); + eprintln!("Verification: {res:?}"); + assert!(res.is_ok()); +} + +#[test] +fn test_signature_verification_ecdsa_p384_sha384() { + let (_, x509) = + parse_x509_certificate(ECDSA_P384_SHA384_DER).expect("could not parse certificate"); + let res = x509.verify_signature(None); + eprintln!("Verification: {res:?}"); + assert!(res.is_ok()); +} + +#[test] +fn test_signature_verification_ecdsa_p256_sha384() { + let (_, x509) = + parse_x509_certificate(ECDSA_P256_SHA384_DER).expect("could not parse certificate"); + let res = x509.verify_signature(None); + eprintln!("Verification: {res:?}"); + assert!(res.is_ok()); +} + +#[test] +fn test_signature_verification_ecdsa_p384_sha256() { + let (_, x509) = + parse_x509_certificate(ECDSA_P384_SHA256_DER).expect("could not parse certificate"); + let res = x509.verify_signature(None); + eprintln!("Verification: {res:?}"); + assert!(res.is_ok()); +} + static RSA_PSS_SELF_SIGNED_SHA256: &[u8] = include_bytes!("../assets/rsa-pss/self_signed_sha256.der"); static RSA_PSS_SELF_SIGNED_SHA384: &[u8] = include_bytes!("../assets/rsa-pss/self_signed_sha384.der"); static RSA_PSS_SELF_SIGNED_SHA512: &[u8] = include_bytes!("../assets/rsa-pss/self_signed_sha512.der"); +#[cfg(all( + feature = "verify-rustcrypto", + not(feature = "verify"), + not(feature = "verify-aws") +))] +static RSA_PSS_SELF_SIGNED_SHA256_SALTLEN42: &[u8] = + include_bytes!("../assets/rsa-pss/self_signed_sha256_saltlen42.der"); #[test] fn test_signature_verification_rsa_pss_sha256() { @@ -67,3 +119,20 @@ fn test_signature_verification_rsa_pss_sha512() { eprintln!("Verification: {res:?}"); assert!(res.is_ok()); } + +/// This test exercises non-default PSS salt length (42 bytes instead of hash-length 32). +/// Only the RustCrypto backend honors the full RSASSA-PSS-params including salt length; +/// ring and aws-lc-rs use fixed params (salt_len = hash_len) that cannot represent this. +#[cfg(all( + feature = "verify-rustcrypto", + not(feature = "verify"), + not(feature = "verify-aws") +))] +#[test] +fn test_signature_verification_rsa_pss_custom_salt_len() { + let (_, x509) = parse_x509_certificate(RSA_PSS_SELF_SIGNED_SHA256_SALTLEN42) + .expect("could not parse certificate"); + let res = x509.verify_signature(None); + eprintln!("Verification: {res:?}"); + assert!(res.is_ok()); +}