chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"8f27666f421c205fcd18d8c3a514f1c6173250edea11356cef653924c4de8ac8","Cargo.lock":"717c98e4da1dadaf2b835c53300955d5206c19d0c513e97f94369c1cef012e09","Cargo.toml":"2b8b5fd6e8f240f6231fafdd3f3cd8af945e0a670b68e4a3a4626532ea197796","Cargo.toml.orig":"e0b06f37dafeb9246156960f52cef877e641b48e86c0f43218ffc17cf8d02bc2","LICENSE":"5b698ca13897be3afdb7174256fa1574f8c6892b8bea1a66dd6469d3fe27885a","README.md":"084c37ee4b35887c813373cb98f3712e20d42e238cd16b59bd90dd8ae870dbf2","src/alg_tests.rs":"aa05065896cebe413d8d03a57842e5c34d201f2b914d2ff16f351230d810f19f","src/aws_lc_rs_algs.rs":"f220ede4189835dbeb3d63d5f3629c59fb3d51651b19a3835eec73381c5f558d","src/cert.rs":"57c19c040cd3fcbf253de8dda5ee5b6219c14afac5e2cf9a6aaafb5d82d4921f","src/crl/mod.rs":"6b59ca00ed53c0883ce13a36880636a1c41053013c9ced732859b0c1b3d60ff3","src/crl/types.rs":"1ef30fdcfcb9cd3370886028c611523042f472755879d69d4e56257a706cd72c","src/data/alg-rsa-pkcs1-sha256-absent-params.der":"269462d02de5597c547cc473cefb4471ee93d836c75d272a0e5a36863e96d228","src/data/alg-rsa-pkcs1-sha384-absent-params.der":"dc7e012a12d034dd581de85ad49913a020df7c8a684213f03629c1fd53dcf14f","src/data/alg-rsa-pkcs1-sha512-absent-params.der":"1c9021c531e93720d2539bd989fbd9f6ce34d24aa0b694c376a87c0a1ba9dd29","src/der.rs":"73e7db1a260b9c48d247dad0ea9211cd046cede29363a8c54cbe4dc025370ef4","src/end_entity.rs":"dcd6176df075627bbd38881bb1b729462721ecb53e7968d56b116a8532043c42","src/error.rs":"6d79c7ec3b9063e156670c4db7155eb04cdf67b82ea384bc33c1f2c7109a32fc","src/lib.rs":"4fc2b9e67c3b68fabf65d4aadda9ed3fea39e3af61e41ad40fe36bdd1cae52dd","src/ring_algs.rs":"f6b17aa2e26442b6c940c5bf4b8e431a9e74152ba8b0573a3f74b99a73ba5412","src/rpk_entity.rs":"29a59f4c5123fca851ff6b8a6a7f3120f2890df90d3169367f611dc05f31ed3b","src/signed_data.rs":"9edbc160fd6ec158bbcc67e408c2bc09078142c4feee3ce6e8dae6b7da806fe0","src/subject_name/dns_name.rs":"d854571327233bffa7a98c3a01e77a6d2d428b52a814d28ecd8abc762915af98","src/subject_name/ip_address.rs":"e76e24bfb15d367732fccd4cc0ebae3bb3970d4a209b7c30a67263ab14200185","src/subject_name/mod.rs":"885ebb0ab799a4c52a0c10b4f5093cd5a08c15501a224b2e9b0655cf64dbaf8f","src/time.rs":"e92721be14ac2f99b4a9a28125947307bbb1f87136b64e2b51ea07644fafa7a6","src/trust_anchor.rs":"52ad6e4658c55d7435bd1297dd035ef2e274c043fe972d2bda48d69311e6344c","src/verify_cert.rs":"235986cb11713f256caea24f29fece0c767896bf4a6de35764205cf1adb5fed9","src/x509.rs":"d86f0e3d6d6153d01c3c183cd26491c6a6a426f5adae021e4be56ca22743dd1a"},"package":"df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"}

View File

@@ -0,0 +1,6 @@
{
"git": {
"sha1": "348ce01c01cf8ce21199090c98853992c9c047a8"
},
"path_in_vcs": ""
}

829
vendor/rustls-webpki/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,829 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "asn1-rs"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60"
dependencies = [
"asn1-rs-derive",
"asn1-rs-impl",
"displaydoc",
"nom",
"num-traits",
"rusticata-macros",
"thiserror",
"time",
]
[[package]]
name = "asn1-rs-derive"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "asn1-rs-impl"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "aws-lc-fips-sys"
version = "0.13.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8bce4948d2520386c6d92a6ea2d472300257702242e5a1d01d6add52bd2e7c1"
dependencies = [
"bindgen",
"cc",
"cmake",
"dunce",
"fs_extra",
"regex",
]
[[package]]
name = "aws-lc-rs"
version = "1.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc"
dependencies = [
"aws-lc-fips-sys",
"aws-lc-sys",
"untrusted 0.7.1",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bencher"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dfdb4953a096c551ce9ace855a604d702e6e62d77fac690575ae347571717f5"
[[package]]
name = "bindgen"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "bzip2"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c"
dependencies = [
"libbz2-rs-sys",
]
[[package]]
name = "cc"
version = "1.2.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "cmake"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
dependencies = [
"cc",
]
[[package]]
name = "data-encoding"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
[[package]]
name = "der-parser"
version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
dependencies = [
"asn1-rs",
"displaydoc",
"nom",
"num-bigint",
"num-traits",
"rusticata-macros",
]
[[package]]
name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
"powerfmt",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
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 = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jobserver"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.4",
"libc",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libbz2-rs-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
[[package]]
name = "libc"
version = "0.2.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-conv"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "oid-registry"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
dependencies = [
"asn1-rs",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rcgen"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e"
dependencies = [
"aws-lc-rs",
"rustls-pki-types",
"time",
"x509-parser",
"yasna",
]
[[package]]
name = "regex"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted 0.9.0",
"windows-sys",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rusticata-macros"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
dependencies = [
"nom",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.10"
dependencies = [
"aws-lc-rs",
"base64",
"bencher",
"bzip2",
"once_cell",
"rcgen",
"ring",
"rustls-pki-types",
"serde",
"serde_json",
"untrusted 0.9.0",
"x509-parser",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "time-macros"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "wasi"
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.2+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
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",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
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 = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
[[package]]
name = "x509-parser"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
dependencies = [
"asn1-rs",
"aws-lc-rs",
"data-encoding",
"der-parser",
"lazy_static",
"nom",
"oid-registry",
"rusticata-macros",
"thiserror",
"time",
]
[[package]]
name = "yasna"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
dependencies = [
"time",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

171
vendor/rustls-webpki/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,171 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.71"
name = "rustls-webpki"
version = "0.103.10"
build = false
include = [
"Cargo.toml",
"/LICENSE",
"/README.md",
"src/aws_lc_rs_algs.rs",
"src/calendar.rs",
"src/cert.rs",
"src/crl/mod.rs",
"src/crl/types.rs",
"src/data/",
"src/der.rs",
"src/end_entity.rs",
"src/error.rs",
"src/rpk_entity.rs",
"src/subject_name/dns_name.rs",
"src/subject_name/ip_address.rs",
"src/subject_name/mod.rs",
"src/subject_name/name.rs",
"src/subject_name/verify.rs",
"src/name/verify.rs",
"src/name/name.rs",
"src/signed_data.rs",
"src/ring_algs.rs",
"src/alg_tests.rs",
"src/time.rs",
"src/trust_anchor.rs",
"src/x509.rs",
"src/verify_cert.rs",
"src/lib.rs",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Web PKI X.509 Certificate Verification."
readme = "README.md"
categories = [
"cryptography",
"no-std",
]
license = "ISC"
repository = "https://github.com/rustls/webpki"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"webpki_docsrs",
]
[package.metadata.cargo_check_external_types]
allowed_external_types = [
"rustls_pki_types::*",
"rustls_pki_types",
]
[package.metadata.cargo-semver-checks.lints]
enum_variant_marked_deprecated = "warn"
[features]
alloc = [
"ring?/alloc",
"pki-types/alloc",
]
aws-lc-rs = [
"dep:aws-lc-rs",
"aws-lc-rs/aws-lc-sys",
"aws-lc-rs/prebuilt-nasm",
]
aws-lc-rs-fips = [
"dep:aws-lc-rs",
"aws-lc-rs/fips",
]
aws-lc-rs-unstable = [
"aws-lc-rs",
"aws-lc-rs/unstable",
]
default = ["std"]
ring = ["dep:ring"]
std = [
"alloc",
"pki-types/std",
]
[lib]
name = "webpki"
path = "src/lib.rs"
[dependencies.aws-lc-rs]
version = "1.14"
optional = true
default-features = false
[dependencies.pki-types]
version = "1.12"
default-features = false
package = "rustls-pki-types"
[dependencies.ring]
version = "0.17"
optional = true
default-features = false
[dependencies.untrusted]
version = "0.9"
[dev-dependencies.base64]
version = "0.22"
[dev-dependencies.bencher]
version = "0.1.5"
[dev-dependencies.bzip2]
version = "0.6"
[dev-dependencies.once_cell]
version = "1.17.2"
[dev-dependencies.rcgen]
version = "0.14.2"
features = ["aws_lc_rs"]
default-features = false
[dev-dependencies.serde]
version = "1.0"
features = ["derive"]
[dev-dependencies.serde_json]
version = "1.0"
[dev-dependencies.x509-parser]
version = "0.18.1"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = ["cfg(webpki_docsrs)"]
[profile.bench]
opt-level = 3
lto = true
codegen-units = 1
debug = 0
debug-assertions = false
rpath = false
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
debug = 0
debug-assertions = false
rpath = false

19
vendor/rustls-webpki/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Except as otherwise noted, this project is licensed under the following
(ISC-style) terms:
Copyright 2015 Brian Smith.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
The files under third-party/chromium are licensed as described in
third-party/chromium/LICENSE.

79
vendor/rustls-webpki/README.md vendored Normal file
View File

@@ -0,0 +1,79 @@
[![Build Status](https://github.com/rustls/webpki/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/rustls/webpki/actions/workflows/ci.yml?query=branch%3Amain)
[![Coverage Status (codecov.io)](https://codecov.io/gh/rustls/webpki/branch/main/graph/badge.svg)](https://codecov.io/gh/rustls/webpki/)
[![Documentation](https://docs.rs/rustls-webpki/badge.svg)](https://docs.rs/rustls-webpki/)
[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/MCSB76RU96)
webpki is a library that validates Web PKI (TLS/SSL) certificates. It's
used by [Rustls](https://github.com/rustls/rustls) to handle certificate-related
tasks required for implementing TLS clients and servers.
webpki is written in [Rust](https://www.rust-lang.org/) and uses
[*ring*](https://github.com/briansmith/ring) for cryptographic operations and
low-level parsing.
This is a fork of the [original webpki project](https://github.com/briansmith/webpki)
which adds a number of features required by the rustls project. This fork is
released as the `rustls-webpki` crate, with versions starting 0.100.0 so as to
not confusingly overlap with `webpki` versions.
Features
===============
* Representing trust anchors - webpki requires the caller to bootstrap trust by
explicitly specifying a set of trust anchors using the `TrustAnchor` type.
* Parsing certificates - webpki can convert from the raw encoded form of
a certificate into something that can be used for making trust decisions.
* Path building - webpki can determine if a certificate for an end entity like
a website or client identity was issued by a trust anchor, or a series of
intermediate certificates the trust anchor has endorsed.
* Name/usage validation - webpki can determine if a certificate is valid for
a given DNS name or IP address by considering the allowed usage of the
certificate and additional constraints.
Limitations
===============
webpki offers a minimal feature set tailored to the needs of Rustls. Notably it
does not offer:
* Support for self-signed certificates
* Certificate or keypair generation
* Access to arbitrary certificate extensions
* Parsing/representation of certificate subjects, or human-friendly display of
these fields
For these tasks you may prefer using webpki in combination with libraries like
[x509-parser](https://github.com/rusticata/x509-parser) and
[rcgen](https://github.com/est31/rcgen).
Changelog
=========
Release history can be found [on GitHub](https://github.com/rustls/webpki/releases).
Demo
====
See https://github.com/rustls/rustls#example-code for an example of using
webpki.
License
=======
See [LICENSE](LICENSE). This project happily accepts pull requests without any
formal copyright/contributor license agreement.
Bug Reporting
=============
Please refer to the [SECURITY](SECURITY.md) policy for security issues. All
other bugs should be reported as [GitHub issues](https://github.com/rustls/webpki/issues/new).

648
vendor/rustls-webpki/src/alg_tests.rs vendored Normal file
View File

@@ -0,0 +1,648 @@
// Copyright 2015 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#![allow(clippy::duplicate_mod)]
use alloc::string::String;
use alloc::vec::Vec;
use base64::{Engine as _, engine::general_purpose};
use pki_types::alg_id;
use crate::error::{DerTypeId, Error, UnsupportedSignatureAlgorithmForPublicKeyContext};
use crate::verify_cert::Budget;
use crate::{der, signed_data};
use super::{
OK_IF_POINT_COMPRESSION_SUPPORTED, SUPPORTED_ALGORITHMS_IN_TESTS, invalid_rsa_signature,
maybe_rsa, unsupported, unsupported_for_ecdsa, unsupported_for_rsa,
};
macro_rules! test_file_bytes {
( $file_name:expr ) => {
include_bytes!(concat!(
"../third-party/chromium/data/verify_signed_data/",
$file_name
))
};
}
// TODO: The expected results need to be modified for SHA-1 deprecation.
fn test_verify_signed_data(file_contents: &[u8]) -> Result<(), Error> {
let tsd = parse_test_signed_data(file_contents);
let spki_value = untrusted::Input::from(&tsd.spki);
let spki_value = spki_value
.read_all(Error::BadDer, |input| {
der::expect_tag(input, der::Tag::Sequence)
})
.unwrap();
// we can't use `parse_signed_data` because it requires `data`
// to be an ASN.1 SEQUENCE, and that isn't the case with
// Chromium's test data. TODO: The test data set should be
// expanded with SEQUENCE-wrapped data so that we can actually
// test `parse_signed_data`.
let algorithm = untrusted::Input::from(&tsd.algorithm);
let algorithm = algorithm
.read_all(
Error::TrailingData(DerTypeId::SignatureAlgorithm),
|input| der::expect_tag(input, der::Tag::Sequence),
)
.unwrap();
let signature = untrusted::Input::from(&tsd.signature);
let signature = signature
.read_all(Error::TrailingData(DerTypeId::Signature), |input| {
der::bit_string_with_no_unused_bits(input)
})
.unwrap();
let signed_data = signed_data::SignedData {
data: untrusted::Input::from(&tsd.data),
algorithm,
signature,
};
signed_data::verify_signed_data(
SUPPORTED_ALGORITHMS_IN_TESTS,
spki_value,
&signed_data,
&mut Budget::default(),
)
}
fn test_verify_signed_data_signature_outer(file_contents: &[u8]) -> Result<(), Error> {
let tsd = parse_test_signed_data(file_contents);
let signature = untrusted::Input::from(&tsd.signature);
signature.read_all(Error::TrailingData(DerTypeId::Signature), |input| {
der::bit_string_with_no_unused_bits(input)
})?;
Ok(())
}
fn test_parse_spki_bad_outer(file_contents: &[u8]) -> Result<(), Error> {
let tsd = parse_test_signed_data(file_contents);
let spki = untrusted::Input::from(&tsd.spki);
spki.read_all(Error::BadDer, |input| {
der::expect_tag(input, der::Tag::Sequence)
})?;
Ok(())
}
// XXX: Some of the BadDer tests should have better error codes, maybe?
// XXX: We should have a variant of this test with a SHA-256 digest that gives
// `Error::UnsupportedSignatureAlgorithmForPublicKey`.
#[test]
fn test_ecdsa_prime256v1_sha512_spki_params_null() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"ecdsa-prime256v1-sha512-spki-params-null.pem"
)),
Err(unsupported_for_ecdsa(
&alg_id::ECDSA_SHA512,
include_bytes!("../tests/signatures/alg-id-ecpublickey-params-null.der")
))
);
}
#[test]
fn test_ecdsa_prime256v1_sha512_unused_bits_signature() {
assert_eq!(
test_verify_signed_data_signature_outer(test_file_bytes!(
"ecdsa-prime256v1-sha512-unused-bits-signature.pem"
)),
Err(Error::BadDer)
);
}
// XXX: We should have a variant of this test with a SHA-256 digest that gives
// `Error::UnsupportedSignatureAlgorithmForPublicKey`.
#[test]
fn test_ecdsa_prime256v1_sha512_using_ecdh_key() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"ecdsa-prime256v1-sha512-using-ecdh-key.pem"
)),
Err(unsupported_for_ecdsa(
&alg_id::ECDSA_SHA512,
include_bytes!("../tests/signatures/alg-ecdh-secp256r1.der")
))
);
}
// XXX: We should have a variant of this test with a SHA-256 digest that gives
// `Error::UnsupportedSignatureAlgorithmForPublicKey`.
#[test]
fn test_ecdsa_prime256v1_sha512_using_ecmqv_key() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"ecdsa-prime256v1-sha512-using-ecmqv-key.pem"
)),
Err(unsupported_for_ecdsa(
&alg_id::ECDSA_SHA512,
include_bytes!("../tests/signatures/alg-ecmqv-secp256r1.der")
))
);
}
#[test]
fn test_ecdsa_prime256v1_sha512_using_rsa_algorithm() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"ecdsa-prime256v1-sha512-using-rsa-algorithm.pem"
),),
Err(unsupported_for_rsa(
&alg_id::RSA_PKCS1_SHA512,
&alg_id::ECDSA_P256,
))
);
}
// XXX: We should have a variant of this test with a SHA-256 digest that gives
// `Error::InvalidSignatureForPublicKey`.
#[test]
fn test_ecdsa_prime256v1_sha512_wrong_signature_format() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"ecdsa-prime256v1-sha512-wrong-signature-format.pem"
)),
Err(unsupported_for_ecdsa(
&alg_id::ECDSA_SHA512,
&alg_id::ECDSA_P256,
))
);
}
// Differs from Chromium because we don't support P-256 with SHA-512.
#[test]
fn test_ecdsa_prime256v1_sha512() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("ecdsa-prime256v1-sha512.pem")),
Err(unsupported_for_ecdsa(
&alg_id::ECDSA_SHA512,
&alg_id::ECDSA_P256,
))
);
}
#[test]
fn test_ecdsa_secp384r1_sha256_corrupted_data() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"ecdsa-secp384r1-sha256-corrupted-data.pem"
)),
Err(Error::InvalidSignatureForPublicKey)
);
}
#[test]
fn test_ecdsa_secp384r1_sha256() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("ecdsa-secp384r1-sha256.pem")),
Ok(())
);
}
#[test]
fn test_ecdsa_using_rsa_key() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("ecdsa-using-rsa-key.pem")),
Err(Error::UnsupportedSignatureAlgorithmForPublicKeyContext(
UnsupportedSignatureAlgorithmForPublicKeyContext {
#[cfg(feature = "alloc")]
signature_algorithm_id: alg_id::ECDSA_SHA256.as_ref().to_vec(),
#[cfg(feature = "alloc")]
public_key_algorithm_id: alg_id::RSA_ENCRYPTION.as_ref().to_vec(),
}
))
);
}
#[test]
fn test_rsa_pkcs1_sha1_bad_key_der_length() {
assert_eq!(
test_parse_spki_bad_outer(test_file_bytes!("rsa-pkcs1-sha1-bad-key-der-length.pem")),
Err(Error::BadDer)
);
}
#[test]
fn test_rsa_pkcs1_sha1_bad_key_der_null() {
assert_eq!(
test_parse_spki_bad_outer(test_file_bytes!("rsa-pkcs1-sha1-bad-key-der-null.pem")),
Err(Error::BadDer)
);
}
#[test]
fn test_rsa_pkcs1_sha1_key_params_absent() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("rsa-pkcs1-sha1-key-params-absent.pem")),
Err(unsupported(include_bytes!(
"../tests/signatures/alg-rsae-sha1.der"
)))
);
}
#[test]
fn test_rsa_pkcs1_sha1_using_pss_key_no_params() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"rsa-pkcs1-sha1-using-pss-key-no-params.pem"
)),
Err(unsupported(include_bytes!(
"../tests/signatures/alg-rsae-sha1.der"
)))
);
}
#[test]
fn test_rsa_pkcs1_sha1_wrong_algorithm() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("rsa-pkcs1-sha1-wrong-algorithm.pem")),
Err(invalid_rsa_signature())
);
}
#[test]
fn test_rsa_pkcs1_sha1() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("rsa-pkcs1-sha1.pem")),
Err(unsupported(include_bytes!(
"../tests/signatures/alg-rsae-sha1.der"
)))
);
}
// XXX: RSA PKCS#1 with SHA-1 is a supported algorithm, but we only accept
// 2048-8192 bit keys, and this test file is using a 1024 bit key. Thus,
// our results differ from Chromium's. TODO: this means we need a 2048+ bit
// version of this test.
#[test]
fn test_rsa_pkcs1_sha256() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("rsa-pkcs1-sha256.pem")),
Err(invalid_rsa_signature())
);
}
#[test]
fn test_rsa_pkcs1_sha256_key_encoded_ber() {
assert_eq!(
test_parse_spki_bad_outer(test_file_bytes!("rsa-pkcs1-sha256-key-encoded-ber.pem")),
Err(Error::BadDer)
);
}
#[test]
fn test_rsa_pkcs1_sha256_spki_non_null_params() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"rsa-pkcs1-sha256-spki-non-null-params.pem"
)),
Err(unsupported_for_rsa(
&alg_id::RSA_PKCS1_SHA256,
include_bytes!("../tests/signatures/alg-rsae-bad-params.der")
))
);
}
#[test]
fn test_rsa_pkcs1_sha256_using_ecdsa_algorithm() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"rsa-pkcs1-sha256-using-ecdsa-algorithm.pem"
)),
Err(Error::UnsupportedSignatureAlgorithmForPublicKeyContext(
UnsupportedSignatureAlgorithmForPublicKeyContext {
#[cfg(feature = "alloc")]
signature_algorithm_id: alg_id::ECDSA_SHA256.as_ref().to_vec(),
#[cfg(feature = "alloc")]
public_key_algorithm_id: alg_id::RSA_ENCRYPTION.as_ref().to_vec(),
}
))
);
}
#[test]
fn test_rsa_pkcs1_sha256_using_id_ea_rsa() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("rsa-pkcs1-sha256-using-id-ea-rsa.pem")),
Err(unsupported_for_rsa(
&alg_id::RSA_PKCS1_SHA256,
include_bytes!("../tests/signatures/alg-rsa-null-params.der")
))
);
}
// Chromium's PSS test are for parameter combinations we don't support.
#[test]
fn test_rsa_pss_sha1_salt20_using_pss_key_no_params() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"rsa-pss-sha1-salt20-using-pss-key-no-params.pem"
)),
Err(unsupported(include_bytes!(
"../tests/signatures/alg-rsapss-defaults.der"
)))
);
}
#[test]
fn test_rsa_pss_sha1_salt20_using_pss_key_with_null_params() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"rsa-pss-sha1-salt20-using-pss-key-with-null-params.pem"
)),
Err(unsupported(include_bytes!(
"../tests/signatures/alg-rsapss-defaults.der"
)))
);
}
#[test]
fn test_rsa_pss_sha1_salt20() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("rsa-pss-sha1-salt20.pem")),
Err(unsupported(include_bytes!(
"../tests/signatures/alg-rsapss-defaults.der"
)))
);
}
#[test]
fn test_rsa_pss_sha1_wrong_salt() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("rsa-pss-sha1-wrong-salt.pem")),
Err(unsupported(include_bytes!(
"../tests/signatures/alg-rsapss-salt23.der"
)))
);
}
#[test]
fn test_rsa_pss_sha256_mgf1_sha512_salt33() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("rsa-pss-sha256-mgf1-sha512-salt33.pem")),
Err(unsupported(include_bytes!(
"../tests/signatures/alg-rsapss-sha256-mgf1-sha512-salt33.der"
)))
);
}
#[test]
fn test_rsa_pss_sha256_salt10_using_pss_key_with_params() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"rsa-pss-sha256-salt10-using-pss-key-with-params.pem"
)),
Err(unsupported(include_bytes!(
"../tests/signatures/alg-rsapss-sha256-mgf1-sha256-salt10.der"
)))
);
}
#[test]
fn test_rsa_pss_sha256_salt10_using_pss_key_with_wrong_params() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"rsa-pss-sha256-salt10-using-pss-key-with-wrong-params.pem"
)),
Err(unsupported(include_bytes!(
"../tests/signatures/alg-rsapss-sha256-mgf1-sha256-salt10.der"
)))
);
}
#[test]
fn test_rsa_pss_sha256_salt10() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("rsa-pss-sha256-salt10.pem")),
Err(unsupported(include_bytes!(
"../tests/signatures/alg-rsapss-sha256-mgf1-sha256-salt10.der"
)))
);
}
// Our PSS tests that should work.
#[test]
fn test_rsa_pss_sha256_salt32() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("ours/rsa-pss-sha256-salt32.pem")),
maybe_rsa()
);
}
#[test]
fn test_rsa_pss_sha384_salt48() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("ours/rsa-pss-sha384-salt48.pem")),
maybe_rsa()
);
}
#[test]
fn test_rsa_pss_sha512_salt64() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("ours/rsa-pss-sha512-salt64.pem")),
maybe_rsa()
);
}
#[test]
fn test_rsa_pss_sha256_salt32_corrupted_data() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"ours/rsa-pss-sha256-salt32-corrupted-data.pem"
)),
Err(invalid_rsa_signature())
);
}
#[test]
fn test_rsa_pss_sha384_salt48_corrupted_data() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"ours/rsa-pss-sha384-salt48-corrupted-data.pem"
)),
Err(invalid_rsa_signature())
);
}
#[test]
fn test_rsa_pss_sha512_salt64_corrupted_data() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"ours/rsa-pss-sha512-salt64-corrupted-data.pem"
)),
Err(invalid_rsa_signature())
);
}
#[test]
fn test_rsa_using_ec_key() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("rsa-using-ec-key.pem")),
Err(unsupported_for_rsa(
&alg_id::RSA_PKCS1_SHA256,
&alg_id::ECDSA_P256
))
);
}
#[test]
fn test_rsa2048_pkcs1_sha512() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("rsa2048-pkcs1-sha512.pem")),
maybe_rsa()
);
}
#[test]
fn test_ecdsa_prime256v1_sha256() {
assert_eq!(
test_verify_signed_data(test_file_bytes!("ours/ecdsa-prime256v1-sha256.pem")),
(Ok(()))
);
}
#[test]
fn test_ecdsa_prime256v1_sha256_compressed() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"ours/ecdsa-prime256v1-sha256-compressed.pem"
)),
OK_IF_POINT_COMPRESSION_SUPPORTED
);
}
#[test]
fn test_ecdsa_prime256v1_sha256_spki_inside_spki() {
assert_eq!(
test_verify_signed_data(test_file_bytes!(
"ours/ecdsa-prime256v1-sha256-spki-inside-spki.pem"
)),
Err(Error::InvalidSignatureForPublicKey)
);
}
#[cfg(all(feature = "aws-lc-rs-unstable", not(feature = "aws-lc-rs-fips")))]
mod aws_lc_rs_unstable {
use pki_types::CertificateDer;
use pki_types::pem::PemObject;
use untrusted::Input;
use super::*;
use crate::cert::Cert;
use crate::signed_data::verify_signed_data;
/// From <https://www.ietf.org/archive/id/draft-ietf-lamps-dilithium-certificates-11.html#name-example-certificates>.
#[test]
fn self_signed_ml_dsa_44() {
let pem = include_bytes!("../tests/tls_server_certs/ml-dsa-44-certificate.pem");
let der = CertificateDer::from_pem_slice(pem).unwrap();
let cert = Cert::from_der(Input::from(&der)).unwrap();
verify_signed_data(
SUPPORTED_ALGORITHMS_IN_TESTS,
cert.spki,
&cert.signed_data,
&mut Budget::default(),
)
.unwrap();
}
/// From <https://www.ietf.org/archive/id/draft-ietf-lamps-dilithium-certificates-11.html#name-example-certificates>.
#[test]
fn self_signed_ml_dsa_65() {
let pem = include_bytes!("../tests/tls_server_certs/ml-dsa-65-certificate.pem");
let der = CertificateDer::from_pem_slice(pem).unwrap();
let cert = Cert::from_der(Input::from(&der)).unwrap();
verify_signed_data(
SUPPORTED_ALGORITHMS_IN_TESTS,
cert.spki,
&cert.signed_data,
&mut Budget::default(),
)
.unwrap();
}
/// From <https://www.ietf.org/archive/id/draft-ietf-lamps-dilithium-certificates-11.html#name-example-certificates>.
#[test]
fn self_signed_ml_dsa_87() {
let pem = include_bytes!("../tests/tls_server_certs/ml-dsa-87-certificate.pem");
let der = CertificateDer::from_pem_slice(pem).unwrap();
let cert = Cert::from_der(Input::from(&der)).unwrap();
verify_signed_data(
SUPPORTED_ALGORITHMS_IN_TESTS,
cert.spki,
&cert.signed_data,
&mut Budget::default(),
)
.unwrap();
}
}
struct TestSignedData {
spki: Vec<u8>,
data: Vec<u8>,
algorithm: Vec<u8>,
signature: Vec<u8>,
}
fn parse_test_signed_data(file_contents: &[u8]) -> TestSignedData {
let mut lines = core::str::from_utf8(file_contents).unwrap().lines();
let spki = read_pem_section(&mut lines, "PUBLIC KEY");
let algorithm = read_pem_section(&mut lines, "ALGORITHM");
let data = read_pem_section(&mut lines, "DATA");
let signature = read_pem_section(&mut lines, "SIGNATURE");
TestSignedData {
spki,
data,
algorithm,
signature,
}
}
use alloc::str::Lines;
fn read_pem_section(lines: &mut Lines<'_>, section_name: &str) -> Vec<u8> {
// Skip comments and header
let begin_section = format!("-----BEGIN {section_name}-----");
loop {
let line = lines.next().unwrap();
if line == begin_section {
break;
}
}
let mut base64 = String::new();
let end_section = format!("-----END {section_name}-----");
loop {
let line = lines.next().unwrap();
if line == end_section {
break;
}
base64.push_str(line);
}
general_purpose::STANDARD.decode(&base64).unwrap()
}

View File

@@ -0,0 +1,381 @@
#[cfg(all(feature = "aws-lc-rs-unstable", not(feature = "aws-lc-rs-fips")))]
use aws_lc_rs::unstable;
use aws_lc_rs::{signature, try_fips_mode};
use pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm, alg_id};
// nb. aws-lc-rs has an API that is broadly compatible with *ring*,
// so this is very similar to ring_algs.rs.
/// A `SignatureVerificationAlgorithm` implemented using aws-lc-rs.
#[derive(Debug)]
struct AwsLcRsAlgorithm {
public_key_alg_id: AlgorithmIdentifier,
signature_alg_id: AlgorithmIdentifier,
verification_alg: &'static dyn signature::VerificationAlgorithm,
in_fips_submission: bool,
}
impl SignatureVerificationAlgorithm for AwsLcRsAlgorithm {
fn public_key_alg_id(&self) -> AlgorithmIdentifier {
self.public_key_alg_id
}
fn signature_alg_id(&self) -> AlgorithmIdentifier {
self.signature_alg_id
}
fn verify_signature(
&self,
public_key: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), InvalidSignature> {
if matches!(
self.public_key_alg_id,
alg_id::ECDSA_P256 | alg_id::ECDSA_P384 | alg_id::ECDSA_P521
) {
// Restrict the allowed encodings of EC public keys.
//
// "The first octet of the OCTET STRING indicates whether the key is
// compressed or uncompressed. The uncompressed form is indicated
// by 0x04 and the compressed form is indicated by either 0x02 or
// 0x03 (see 2.3.3 in [SEC1]). The public key MUST be rejected if
// any other value is included in the first octet."
// -- <https://datatracker.ietf.org/doc/html/rfc5480#section-2.2>
match public_key.first() {
Some(0x04) | Some(0x02) | Some(0x03) => {}
_ => return Err(InvalidSignature),
};
}
signature::UnparsedPublicKey::new(self.verification_alg, public_key)
.verify(message, signature)
.map_err(|_| InvalidSignature)
}
fn fips(&self) -> bool {
self.in_fips_submission && try_fips_mode().is_ok()
}
}
/// ML-DSA signatures using the [4, 4] matrix (security strength category 2).
#[cfg(all(feature = "aws-lc-rs-unstable", not(feature = "aws-lc-rs-fips")))]
pub static ML_DSA_44: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ML_DSA_44,
signature_alg_id: alg_id::ML_DSA_44,
verification_alg: &unstable::signature::ML_DSA_44,
// Not included in AWS-LC-FIPS 3.0 FIPS scope
in_fips_submission: false,
};
/// ML-DSA signatures using the [6, 5] matrix (security strength category 3).
#[cfg(all(feature = "aws-lc-rs-unstable", not(feature = "aws-lc-rs-fips")))]
pub static ML_DSA_65: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ML_DSA_65,
signature_alg_id: alg_id::ML_DSA_65,
verification_alg: &unstable::signature::ML_DSA_65,
// Not included in AWS-LC-FIPS 3.0 FIPS scope
in_fips_submission: false,
};
/// ML-DSA signatures using the [8. 7] matrix (security strength category 5).
#[cfg(all(feature = "aws-lc-rs-unstable", not(feature = "aws-lc-rs-fips")))]
pub static ML_DSA_87: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ML_DSA_87,
signature_alg_id: alg_id::ML_DSA_87,
verification_alg: &unstable::signature::ML_DSA_87,
// Not included in AWS-LC-FIPS 3.0 FIPS scope
in_fips_submission: false,
};
/// ECDSA signatures using the P-256 curve and SHA-256.
pub static ECDSA_P256_SHA256: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ECDSA_P256,
signature_alg_id: alg_id::ECDSA_SHA256,
verification_alg: &signature::ECDSA_P256_SHA256_ASN1,
in_fips_submission: true,
};
/// ECDSA signatures using the P-256 curve and SHA-384. Deprecated.
pub static ECDSA_P256_SHA384: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ECDSA_P256,
signature_alg_id: alg_id::ECDSA_SHA384,
verification_alg: &signature::ECDSA_P256_SHA384_ASN1,
in_fips_submission: true,
};
/// ECDSA signatures using the P-256 curve and SHA-512. Deprecated.
pub static ECDSA_P256_SHA512: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ECDSA_P256,
signature_alg_id: alg_id::ECDSA_SHA512,
verification_alg: &signature::ECDSA_P256_SHA512_ASN1,
in_fips_submission: true,
};
/// ECDSA signatures using the P-384 curve and SHA-256. Deprecated.
pub static ECDSA_P384_SHA256: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ECDSA_P384,
signature_alg_id: alg_id::ECDSA_SHA256,
verification_alg: &signature::ECDSA_P384_SHA256_ASN1,
in_fips_submission: true,
};
/// ECDSA signatures using the P-384 curve and SHA-384.
pub static ECDSA_P384_SHA384: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ECDSA_P384,
signature_alg_id: alg_id::ECDSA_SHA384,
verification_alg: &signature::ECDSA_P384_SHA384_ASN1,
in_fips_submission: true,
};
/// ECDSA signatures using the P-384 curve and SHA-512. Deprecated.
pub static ECDSA_P384_SHA512: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ECDSA_P384,
signature_alg_id: alg_id::ECDSA_SHA512,
verification_alg: &signature::ECDSA_P384_SHA512_ASN1,
in_fips_submission: true,
};
/// ECDSA signatures using the P-521 curve and SHA-256.
pub static ECDSA_P521_SHA256: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ECDSA_P521,
signature_alg_id: alg_id::ECDSA_SHA256,
verification_alg: &signature::ECDSA_P521_SHA256_ASN1,
in_fips_submission: true,
};
/// ECDSA signatures using the P-521 curve and SHA-384.
pub static ECDSA_P521_SHA384: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ECDSA_P521,
signature_alg_id: alg_id::ECDSA_SHA384,
verification_alg: &signature::ECDSA_P521_SHA384_ASN1,
in_fips_submission: true,
};
/// ECDSA signatures using the P-521 curve and SHA-512.
pub static ECDSA_P521_SHA512: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ECDSA_P521,
signature_alg_id: alg_id::ECDSA_SHA512,
verification_alg: &signature::ECDSA_P521_SHA512_ASN1,
in_fips_submission: true,
};
/// RSA PKCS#1 1.5 signatures using SHA-256 for keys of 2048-8192 bits.
pub static RSA_PKCS1_2048_8192_SHA256: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PKCS1_SHA256,
verification_alg: &signature::RSA_PKCS1_2048_8192_SHA256,
in_fips_submission: true,
};
/// RSA PKCS#1 1.5 signatures using SHA-384 for keys of 2048-8192 bits.
pub static RSA_PKCS1_2048_8192_SHA384: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PKCS1_SHA384,
verification_alg: &signature::RSA_PKCS1_2048_8192_SHA384,
in_fips_submission: true,
};
/// RSA PKCS#1 1.5 signatures using SHA-512 for keys of 2048-8192 bits.
pub static RSA_PKCS1_2048_8192_SHA512: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PKCS1_SHA512,
verification_alg: &signature::RSA_PKCS1_2048_8192_SHA512,
in_fips_submission: true,
};
/// RSA PKCS#1 1.5 signatures using SHA-256 for keys of 2048-8192 bits,
/// with illegally absent AlgorithmIdentifier parameters.
///
/// RFC4055 says on sha256WithRSAEncryption and company:
///
/// > When any of these four object identifiers appears within an
/// > AlgorithmIdentifier, the parameters MUST be NULL. Implementations
/// > MUST accept the parameters being absent as well as present.
///
/// This algorithm covers the absent case, [`RSA_PKCS1_2048_8192_SHA256`] covers
/// the present case.
pub static RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm =
&AwsLcRsAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::AlgorithmIdentifier::from_slice(include_bytes!(
"data/alg-rsa-pkcs1-sha256-absent-params.der"
)),
verification_alg: &signature::RSA_PKCS1_2048_8192_SHA256,
in_fips_submission: true,
};
/// RSA PKCS#1 1.5 signatures using SHA-384 for keys of 2048-8192 bits,
/// with illegally absent AlgorithmIdentifier parameters.
///
/// RFC4055 says on sha256WithRSAEncryption and company:
///
/// > When any of these four object identifiers appears within an
/// > AlgorithmIdentifier, the parameters MUST be NULL. Implementations
/// > MUST accept the parameters being absent as well as present.
///
/// This algorithm covers the absent case, [`RSA_PKCS1_2048_8192_SHA384`] covers
/// the present case.
pub static RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm =
&AwsLcRsAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::AlgorithmIdentifier::from_slice(include_bytes!(
"data/alg-rsa-pkcs1-sha384-absent-params.der"
)),
verification_alg: &signature::RSA_PKCS1_2048_8192_SHA384,
in_fips_submission: true,
};
/// RSA PKCS#1 1.5 signatures using SHA-512 for keys of 2048-8192 bits,
/// with illegally absent AlgorithmIdentifier parameters.
///
/// RFC4055 says on sha256WithRSAEncryption and company:
///
/// > When any of these four object identifiers appears within an
/// > AlgorithmIdentifier, the parameters MUST be NULL. Implementations
/// > MUST accept the parameters being absent as well as present.
///
/// This algorithm covers the absent case, [`RSA_PKCS1_2048_8192_SHA512`] covers
/// the present case.
pub static RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm =
&AwsLcRsAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::AlgorithmIdentifier::from_slice(include_bytes!(
"data/alg-rsa-pkcs1-sha512-absent-params.der"
)),
verification_alg: &signature::RSA_PKCS1_2048_8192_SHA512,
in_fips_submission: true,
};
/// RSA PKCS#1 1.5 signatures using SHA-384 for keys of 3072-8192 bits.
pub static RSA_PKCS1_3072_8192_SHA384: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PKCS1_SHA384,
verification_alg: &signature::RSA_PKCS1_3072_8192_SHA384,
in_fips_submission: true,
};
/// RSA PSS signatures using SHA-256 for keys of 2048-8192 bits and of
/// type rsaEncryption; see [RFC 4055 Section 1.2].
///
/// [RFC 4055 Section 1.2]: https://tools.ietf.org/html/rfc4055#section-1.2
pub static RSA_PSS_2048_8192_SHA256_LEGACY_KEY: &dyn SignatureVerificationAlgorithm =
&AwsLcRsAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PSS_SHA256,
verification_alg: &signature::RSA_PSS_2048_8192_SHA256,
in_fips_submission: true,
};
/// RSA PSS signatures using SHA-384 for keys of 2048-8192 bits and of
/// type rsaEncryption; see [RFC 4055 Section 1.2].
///
/// [RFC 4055 Section 1.2]: https://tools.ietf.org/html/rfc4055#section-1.2
pub static RSA_PSS_2048_8192_SHA384_LEGACY_KEY: &dyn SignatureVerificationAlgorithm =
&AwsLcRsAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PSS_SHA384,
verification_alg: &signature::RSA_PSS_2048_8192_SHA384,
in_fips_submission: true,
};
/// RSA PSS signatures using SHA-512 for keys of 2048-8192 bits and of
/// type rsaEncryption; see [RFC 4055 Section 1.2].
///
/// [RFC 4055 Section 1.2]: https://tools.ietf.org/html/rfc4055#section-1.2
pub static RSA_PSS_2048_8192_SHA512_LEGACY_KEY: &dyn SignatureVerificationAlgorithm =
&AwsLcRsAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PSS_SHA512,
verification_alg: &signature::RSA_PSS_2048_8192_SHA512,
in_fips_submission: true,
};
/// ED25519 signatures according to RFC 8410
pub static ED25519: &dyn SignatureVerificationAlgorithm = &AwsLcRsAlgorithm {
public_key_alg_id: alg_id::ED25519,
signature_alg_id: alg_id::ED25519,
verification_alg: &signature::ED25519,
in_fips_submission: true,
};
#[cfg(test)]
#[path = "."]
mod tests {
use crate::error::{
Error, UnsupportedSignatureAlgorithmContext,
UnsupportedSignatureAlgorithmForPublicKeyContext,
};
static SUPPORTED_ALGORITHMS_IN_TESTS: &[&dyn super::SignatureVerificationAlgorithm] = &[
// Reasonable algorithms.
super::ECDSA_P256_SHA256,
super::ECDSA_P384_SHA384,
super::ECDSA_P521_SHA256,
super::ECDSA_P521_SHA384,
super::ECDSA_P521_SHA512,
super::ED25519,
super::RSA_PKCS1_2048_8192_SHA256,
super::RSA_PKCS1_2048_8192_SHA384,
super::RSA_PKCS1_2048_8192_SHA512,
super::RSA_PKCS1_3072_8192_SHA384,
super::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
super::RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
super::RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
// Algorithms deprecated because they are nonsensical combinations.
super::ECDSA_P256_SHA384, // Truncates digest.
super::ECDSA_P384_SHA256, // Digest is unnecessarily short.
#[cfg(all(feature = "aws-lc-rs-unstable", not(feature = "aws-lc-rs-fips")))]
super::ML_DSA_44,
#[cfg(all(feature = "aws-lc-rs-unstable", not(feature = "aws-lc-rs-fips")))]
super::ML_DSA_65,
#[cfg(all(feature = "aws-lc-rs-unstable", not(feature = "aws-lc-rs-fips")))]
super::ML_DSA_87,
];
const OK_IF_POINT_COMPRESSION_SUPPORTED: Result<(), Error> = Ok(());
#[path = "alg_tests.rs"]
mod alg_tests;
fn maybe_rsa() -> Result<(), Error> {
Ok(())
}
fn unsupported_for_rsa(_sig_alg_id: &[u8], _public_key_alg_id: &[u8]) -> Error {
Error::UnsupportedSignatureAlgorithmForPublicKeyContext(
UnsupportedSignatureAlgorithmForPublicKeyContext {
#[cfg(feature = "alloc")]
signature_algorithm_id: _sig_alg_id.to_vec(),
#[cfg(feature = "alloc")]
public_key_algorithm_id: _public_key_alg_id.to_vec(),
},
)
}
fn invalid_rsa_signature() -> Error {
Error::InvalidSignatureForPublicKey
}
fn unsupported_for_ecdsa(_sig_alg_id: &[u8], _public_key_alg_id: &[u8]) -> Error {
Error::UnsupportedSignatureAlgorithmForPublicKeyContext(
UnsupportedSignatureAlgorithmForPublicKeyContext {
#[cfg(feature = "alloc")]
signature_algorithm_id: _sig_alg_id.to_vec(),
#[cfg(feature = "alloc")]
public_key_algorithm_id: _public_key_alg_id.to_vec(),
},
)
}
fn unsupported(_sig_alg_id: &[u8]) -> Error {
Error::UnsupportedSignatureAlgorithmContext(UnsupportedSignatureAlgorithmContext {
#[cfg(feature = "alloc")]
signature_algorithm_id: _sig_alg_id.to_vec(),
#[cfg(feature = "alloc")]
supported_algorithms: SUPPORTED_ALGORITHMS_IN_TESTS
.iter()
.map(|&alg| alg.signature_alg_id())
.collect(),
})
}
}

733
vendor/rustls-webpki/src/cert.rs vendored Normal file
View File

@@ -0,0 +1,733 @@
// Copyright 2015 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#[cfg(feature = "alloc")]
use pki_types::SubjectPublicKeyInfoDer;
use pki_types::{CertificateDer, DnsName};
use crate::der::{self, CONSTRUCTED, CONTEXT_SPECIFIC, DerIterator, FromDer, Tag};
use crate::error::{DerTypeId, Error};
use crate::public_values_eq;
use crate::signed_data::SignedData;
use crate::subject_name::{GeneralName, NameIterator, WildcardDnsNameRef};
use crate::x509::{DistributionPointName, Extension, remember_extension, set_extension_once};
/// A parsed X509 certificate.
pub struct Cert<'a> {
pub(crate) serial: untrusted::Input<'a>,
pub(crate) signed_data: SignedData<'a>,
pub(crate) issuer: untrusted::Input<'a>,
pub(crate) validity: untrusted::Input<'a>,
pub(crate) subject: untrusted::Input<'a>,
pub(crate) spki: untrusted::Input<'a>,
pub(crate) basic_constraints: Option<untrusted::Input<'a>>,
// key usage (KU) extension (if any). When validating certificate revocation lists (CRLs) this
// field will be consulted to determine if the cert is allowed to sign CRLs. For cert validation
// this field is ignored (for more detail see in `verify_cert.rs` and
// `check_issuer_independent_properties`).
pub(crate) key_usage: Option<untrusted::Input<'a>>,
pub(crate) eku: Option<untrusted::Input<'a>>,
pub(crate) name_constraints: Option<untrusted::Input<'a>>,
pub(crate) subject_alt_name: Option<untrusted::Input<'a>>,
pub(crate) crl_distribution_points: Option<untrusted::Input<'a>>,
der: CertificateDer<'a>,
}
impl<'a> Cert<'a> {
pub(crate) fn from_der(cert_der: untrusted::Input<'a>) -> Result<Self, Error> {
let (tbs, signed_data) =
cert_der.read_all(Error::TrailingData(DerTypeId::Certificate), |cert_der| {
der::nested(
cert_der,
der::Tag::Sequence,
Error::TrailingData(DerTypeId::SignedData),
|der| {
// limited to SEQUENCEs of size 2^16 or less.
SignedData::from_der(der, der::TWO_BYTE_DER_SIZE)
},
)
})?;
tbs.read_all(
Error::TrailingData(DerTypeId::CertificateTbsCertificate),
|tbs| {
version3(tbs)?;
let serial = lenient_certificate_serial_number(tbs)?;
let signature = der::expect_tag(tbs, der::Tag::Sequence)?;
// TODO: In mozilla::pkix, the comparison is done based on the
// normalized value (ignoring whether or not there is an optional NULL
// parameter for RSA-based algorithms), so this may be too strict.
if !public_values_eq(signature, signed_data.algorithm) {
return Err(Error::SignatureAlgorithmMismatch);
}
let issuer = der::expect_tag(tbs, der::Tag::Sequence)?;
let validity = der::expect_tag(tbs, der::Tag::Sequence)?;
let subject = der::expect_tag(tbs, der::Tag::Sequence)?;
let spki = der::expect_tag(tbs, der::Tag::Sequence)?;
// In theory there could be fields [1] issuerUniqueID and [2]
// subjectUniqueID, but in practice there never are, and to keep the
// code small and simple we don't accept any certificates that do
// contain them.
let mut cert = Cert {
signed_data,
serial,
issuer,
validity,
subject,
spki,
basic_constraints: None,
key_usage: None,
eku: None,
name_constraints: None,
subject_alt_name: None,
crl_distribution_points: None,
der: CertificateDer::from(cert_der.as_slice_less_safe()),
};
// When used to read X509v3 Certificate.tbsCertificate.extensions, we allow
// the extensions to be empty. This is in spite of RFC5280:
//
// "If present, this field is a SEQUENCE of one or more certificate extensions."
//
// Unfortunately other implementations don't get this right, eg:
// - https://github.com/golang/go/issues/52319
// - https://github.com/openssl/openssl/issues/20877
const ALLOW_EMPTY: bool = true;
if !tbs.at_end() {
der::nested(
tbs,
der::Tag::ContextSpecificConstructed3,
Error::TrailingData(DerTypeId::CertificateExtensions),
|tagged| {
der::nested_of_mut(
tagged,
der::Tag::Sequence,
der::Tag::Sequence,
Error::TrailingData(DerTypeId::Extension),
ALLOW_EMPTY,
|extension| {
remember_cert_extension(
&mut cert,
&Extension::from_der(extension)?,
)
},
)
},
)?;
}
Ok(cert)
},
)
}
/// Returns a list of valid DNS names provided in the subject alternative names extension
///
/// This function must not be used to implement custom DNS name verification.
/// Checking that a certificate is valid for a given subject name should always be done with
/// [EndEntityCert::verify_is_valid_for_subject_name].
///
/// [EndEntityCert::verify_is_valid_for_subject_name]: crate::EndEntityCert::verify_is_valid_for_subject_name
pub fn valid_dns_names(&self) -> impl Iterator<Item = &'a str> {
NameIterator::new(self.subject_alt_name).filter_map(|result| {
let presented_id = match result.ok()? {
GeneralName::DnsName(presented) => presented,
_ => return None,
};
// if the name could be converted to a DNS name, return it; otherwise,
// keep going.
let dns_str = core::str::from_utf8(presented_id.as_slice_less_safe()).ok()?;
match DnsName::try_from(dns_str) {
Ok(_) => Some(dns_str),
Err(_) => {
match WildcardDnsNameRef::try_from_ascii(presented_id.as_slice_less_safe()) {
Ok(wildcard_dns_name) => Some(wildcard_dns_name.as_str()),
Err(_) => None,
}
}
}
})
}
/// Returns a list of valid URI names provided in the subject alternative names extension
///
/// This function returns URIs as strings without performing validation beyond checking that
/// they are valid UTF-8.
pub fn valid_uri_names(&self) -> impl Iterator<Item = &'a str> {
NameIterator::new(self.subject_alt_name).filter_map(|result| {
let presented_id = match result.ok()? {
GeneralName::UniformResourceIdentifier(presented) => presented,
_ => return None,
};
// if the URI can be converted to a valid UTF-8 string, return it; otherwise,
// keep going.
core::str::from_utf8(presented_id.as_slice_less_safe()).ok()
})
}
/// Raw certificate serial number.
///
/// This is in big-endian byte order, in twos-complement encoding.
///
/// If the caller were to add an `INTEGER` tag and suitable length, this
/// would become a valid DER encoding.
pub fn serial(&self) -> &[u8] {
self.serial.as_slice_less_safe()
}
/// Raw DER-encoded certificate issuer.
///
/// This does not include the outer `SEQUENCE` tag or length.
pub fn issuer(&self) -> &[u8] {
self.issuer.as_slice_less_safe()
}
/// Raw DER encoded certificate subject.
///
/// This does not include the outer `SEQUENCE` tag or length.
pub fn subject(&self) -> &[u8] {
self.subject.as_slice_less_safe()
}
/// Get the RFC 5280-compliant [`SubjectPublicKeyInfoDer`] (SPKI) of this [`Cert`].
///
/// This **does** include the outer `SEQUENCE` tag and length.
#[cfg(feature = "alloc")]
pub fn subject_public_key_info(&self) -> SubjectPublicKeyInfoDer<'static> {
// Our SPKI representation contains only the content of the RFC 5280 SEQUENCE
// So we wrap the SPKI contents back into a properly-encoded ASN.1 SEQUENCE
SubjectPublicKeyInfoDer::from(der::asn1_wrap(
Tag::Sequence,
self.spki.as_slice_less_safe(),
))
}
/// Returns an iterator over the certificate's cRLDistributionPoints extension values, if any.
pub(crate) fn crl_distribution_points(
&self,
) -> Option<impl Iterator<Item = Result<CrlDistributionPoint<'a>, Error>>> {
self.crl_distribution_points.map(DerIterator::new)
}
/// Raw DER-encoded representation of the certificate.
pub fn der(&self) -> CertificateDer<'a> {
self.der.clone() // This is cheap, just cloning a reference.
}
}
// mozilla::pkix supports v1, v2, v3, and v4, including both the implicit
// (correct) and explicit (incorrect) encoding of v1. We allow only v3.
fn version3(input: &mut untrusted::Reader<'_>) -> Result<(), Error> {
der::nested(
input,
der::Tag::ContextSpecificConstructed0,
Error::UnsupportedCertVersion,
|input| {
let version = u8::from_der(input)?;
if version != 2 {
// v3
return Err(Error::UnsupportedCertVersion);
}
Ok(())
},
)
}
pub(crate) fn lenient_certificate_serial_number<'a>(
input: &mut untrusted::Reader<'a>,
) -> Result<untrusted::Input<'a>, Error> {
// https://tools.ietf.org/html/rfc5280#section-4.1.2.2:
// * Conforming CAs MUST NOT use serialNumber values longer than 20 octets."
// * "The serial number MUST be a positive integer [...]"
//
// However, we don't enforce these constraints, as there are widely-deployed trust anchors
// and many X.509 implementations in common use that violate these constraints. This is called
// out by the same section of RFC 5280 as cited above:
// Note: Non-conforming CAs may issue certificates with serial numbers
// that are negative or zero. Certificate users SHOULD be prepared to
// gracefully handle such certificates.
der::expect_tag(input, Tag::Integer)
}
fn remember_cert_extension<'a>(
cert: &mut Cert<'a>,
extension: &Extension<'a>,
) -> Result<(), Error> {
// We don't do anything with certificate policies so we can safely ignore
// all policy-related stuff. We assume that the policy-related extensions
// are not marked critical.
remember_extension(extension, |id| {
let out = match id {
// id-ce-keyUsage 2.5.29.15.
15 => &mut cert.key_usage,
// id-ce-subjectAltName 2.5.29.17
17 => &mut cert.subject_alt_name,
// id-ce-basicConstraints 2.5.29.19
19 => &mut cert.basic_constraints,
// id-ce-nameConstraints 2.5.29.30
30 => &mut cert.name_constraints,
// id-ce-cRLDistributionPoints 2.5.29.31
31 => &mut cert.crl_distribution_points,
// id-ce-extKeyUsage 2.5.29.37
37 => &mut cert.eku,
// Unsupported extension
_ => return extension.unsupported(),
};
set_extension_once(out, || {
extension.value.read_all(Error::BadDer, |value| match id {
// Unlike the other extensions we remember KU is a BitString and not a Sequence. We
// read the raw bytes here and parse at the time of use.
15 => Ok(value.read_bytes_to_end()),
// All other remembered certificate extensions are wrapped in a Sequence.
_ => der::expect_tag(value, Tag::Sequence),
})
})
})
}
/// A certificate revocation list (CRL) distribution point, describing a source of
/// CRL information for a given certificate as described in RFC 5280 section 4.2.3.13[^1].
///
/// [^1]: <https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13>
pub(crate) struct CrlDistributionPoint<'a> {
/// distributionPoint describes the location of CRL information.
distribution_point: Option<untrusted::Input<'a>>,
/// reasons holds a bit flag set of certificate revocation reasons associated with the
/// CRL distribution point.
pub(crate) reasons: Option<der::BitStringFlags<'a>>,
/// when the CRL issuer is not the certificate issuer, crl_issuer identifies the issuer of the
/// CRL.
pub(crate) crl_issuer: Option<untrusted::Input<'a>>,
}
impl<'a> CrlDistributionPoint<'a> {
/// Return the distribution point names (if any).
pub(crate) fn names(&self) -> Result<Option<DistributionPointName<'a>>, Error> {
self.distribution_point
.map(|input| DistributionPointName::from_der(&mut untrusted::Reader::new(input)))
.transpose()
}
}
impl<'a> FromDer<'a> for CrlDistributionPoint<'a> {
fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
// RFC 5280 section §4.2.1.13:
// A DistributionPoint consists of three fields, each of which is optional:
// distributionPoint, reasons, and cRLIssuer.
let mut result = CrlDistributionPoint {
distribution_point: None,
reasons: None,
crl_issuer: None,
};
der::nested(
reader,
Tag::Sequence,
Error::TrailingData(Self::TYPE_ID),
|der| {
const DISTRIBUTION_POINT_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED;
const REASONS_TAG: u8 = CONTEXT_SPECIFIC | 1;
const CRL_ISSUER_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 2;
while !der.at_end() {
let (tag, value) = der::read_tag_and_get_value(der)?;
match tag {
DISTRIBUTION_POINT_TAG => {
set_extension_once(&mut result.distribution_point, || Ok(value))?
}
REASONS_TAG => set_extension_once(&mut result.reasons, || {
der::bit_string_flags(value)
})?,
CRL_ISSUER_TAG => set_extension_once(&mut result.crl_issuer, || Ok(value))?,
_ => return Err(Error::BadDer),
}
}
// RFC 5280 section §4.2.1.13:
// a DistributionPoint MUST NOT consist of only the reasons field; either distributionPoint or
// cRLIssuer MUST be present.
match (result.distribution_point, result.crl_issuer) {
(None, None) => Err(Error::MalformedExtensions),
_ => Ok(result),
}
},
)
}
const TYPE_ID: DerTypeId = DerTypeId::CrlDistributionPoint;
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use super::*;
#[cfg(feature = "alloc")]
use crate::crl::RevocationReason;
#[test]
// Note: cert::parse_cert is crate-local visibility, and EndEntityCert doesn't expose the
// inner Cert, or the serial number. As a result we test that the raw serial value
// is read correctly here instead of in tests/integration.rs.
fn test_serial_read() {
let ee = include_bytes!("../tests/misc/serial_neg_ee.der");
let cert = Cert::from_der(untrusted::Input::from(ee)).expect("failed to parse certificate");
assert_eq!(cert.serial.as_slice_less_safe(), &[255, 33, 82, 65, 17]);
let ee = include_bytes!("../tests/misc/serial_large_positive.der");
let cert = Cert::from_der(untrusted::Input::from(ee)).expect("failed to parse certificate");
assert_eq!(
cert.serial.as_slice_less_safe(),
&[
0, 230, 9, 254, 122, 234, 0, 104, 140, 224, 36, 180, 237, 32, 27, 31, 239, 82, 180,
68, 209
]
)
}
#[cfg(feature = "alloc")]
#[test]
fn test_spki_read() {
let ee = include_bytes!("../tests/ed25519/ee.der");
let cert = Cert::from_der(untrusted::Input::from(ee)).expect("failed to parse certificate");
// How did I get this lovely string of hex bytes?
// openssl x509 -in tests/ed25519/ee.der -pubkey -noout > pubkey.pem
// openssl ec -pubin -in pubkey.pem -outform DER -out pubkey.der
// xxd -plain -cols 1 pubkey.der
let expected_spki = [
0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, 0xfe, 0x5a,
0x1e, 0x36, 0x6c, 0x17, 0x27, 0x5b, 0xf1, 0x58, 0x1e, 0x3a, 0x0e, 0xe6, 0x56, 0x29,
0x8d, 0x9e, 0x1b, 0x3f, 0xd3, 0x3f, 0x96, 0x46, 0xef, 0xbf, 0x04, 0x6b, 0xc7, 0x3d,
0x47, 0x5c,
];
assert_eq!(expected_spki, *cert.subject_public_key_info())
}
#[test]
#[cfg(feature = "alloc")]
fn test_crl_distribution_point_netflix() {
let ee = include_bytes!("../tests/netflix/ee.der");
let inter = include_bytes!("../tests/netflix/inter.der");
let ee_cert = Cert::from_der(untrusted::Input::from(ee)).expect("failed to parse EE cert");
let cert =
Cert::from_der(untrusted::Input::from(inter)).expect("failed to parse certificate");
// The end entity certificate shouldn't have a distribution point.
assert!(ee_cert.crl_distribution_points.is_none());
// We expect to be able to parse the intermediate certificate's CRL distribution points.
let crl_distribution_points = cert
.crl_distribution_points()
.expect("missing distribution points extension")
.collect::<Result<Vec<_>, Error>>()
.expect("failed to parse distribution points");
// There should be one distribution point present.
assert_eq!(crl_distribution_points.len(), 1);
let crl_distribution_point = crl_distribution_points
.first()
.expect("missing distribution point");
// The distribution point shouldn't have revocation reasons listed.
assert!(crl_distribution_point.reasons.is_none());
// The distribution point shouldn't have a CRL issuer listed.
assert!(crl_distribution_point.crl_issuer.is_none());
// We should be able to parse the distribution point name.
let distribution_point_name = crl_distribution_point
.names()
.expect("failed to parse distribution point names")
.expect("missing distribution point name");
// We expect the distribution point name to be a sequence of GeneralNames, not a name
// relative to the CRL issuer.
let names = match distribution_point_name {
DistributionPointName::NameRelativeToCrlIssuer => {
panic!("unexpected name relative to crl issuer")
}
DistributionPointName::FullName(names) => names,
};
// The general names should parse.
let names = names
.collect::<Result<Vec<_>, Error>>()
.expect("failed to parse general names");
// There should be one general name.
assert_eq!(names.len(), 1);
let name = names.first().expect("missing general name");
// The general name should be a URI matching the expected value.
match name {
GeneralName::UniformResourceIdentifier(uri) => {
assert_eq!(
uri.as_slice_less_safe(),
"http://s.symcb.com/pca3-g3.crl".as_bytes()
);
}
_ => panic!("unexpected general name type"),
}
}
#[test]
#[cfg(feature = "alloc")]
fn test_crl_distribution_point_with_reasons() {
let der = include_bytes!("../tests/crl_distrib_point/with_reasons.der");
let cert =
Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
// We expect to be able to parse the intermediate certificate's CRL distribution points.
let crl_distribution_points = cert
.crl_distribution_points()
.expect("missing distribution points extension")
.collect::<Result<Vec<_>, Error>>()
.expect("failed to parse distribution points");
// There should be one distribution point present.
assert_eq!(crl_distribution_points.len(), 1);
let crl_distribution_point = crl_distribution_points
.first()
.expect("missing distribution point");
// The distribution point should include the expected revocation reasons, and no others.
let reasons = crl_distribution_point
.reasons
.as_ref()
.expect("missing revocation reasons");
let expected = &[
RevocationReason::KeyCompromise,
RevocationReason::AffiliationChanged,
];
for reason in RevocationReason::iter() {
#[allow(clippy::as_conversions)]
// revocation reason is u8, infallible to convert to usize.
match expected.contains(&reason) {
true => assert!(reasons.bit_set(reason as usize)),
false => assert!(!reasons.bit_set(reason as usize)),
}
}
}
#[test]
#[cfg(feature = "alloc")]
fn test_crl_distribution_point_with_crl_issuer() {
let der = include_bytes!("../tests/crl_distrib_point/with_crl_issuer.der");
let cert =
Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
// We expect to be able to parse the intermediate certificate's CRL distribution points.
let crl_distribution_points = cert
.crl_distribution_points()
.expect("missing distribution points extension")
.collect::<Result<Vec<_>, Error>>()
.expect("failed to parse distribution points");
// There should be one distribution point present.
assert_eq!(crl_distribution_points.len(), 1);
let crl_distribution_point = crl_distribution_points
.first()
.expect("missing distribution point");
// The CRL issuer should be present, but not anything else.
assert!(crl_distribution_point.crl_issuer.is_some());
assert!(crl_distribution_point.distribution_point.is_none());
assert!(crl_distribution_point.reasons.is_none());
}
#[test]
#[cfg(feature = "alloc")]
fn test_crl_distribution_point_bad_der() {
// Created w/
// ascii2der -i tests/crl_distrib_point/unknown_tag.der.txt -o tests/crl_distrib_point/unknown_tag.der
let der = include_bytes!("../tests/crl_distrib_point/unknown_tag.der");
let cert =
Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
// We expect there to be a distribution point extension, but parsing it should fail
// due to the unknown tag in the SEQUENCE.
let result = cert
.crl_distribution_points()
.expect("missing distribution points extension")
.collect::<Result<Vec<_>, Error>>();
assert!(matches!(result, Err(Error::BadDer)));
}
#[test]
#[cfg(feature = "alloc")]
fn test_crl_distribution_point_only_reasons() {
// Created w/
// ascii2der -i tests/crl_distrib_point/only_reasons.der.txt -o tests/crl_distrib_point/only_reasons.der
let der = include_bytes!("../tests/crl_distrib_point/only_reasons.der");
let cert =
Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
// We expect there to be a distribution point extension, but parsing it should fail
// because no distribution points or cRLIssuer are set in the SEQUENCE, just reason codes.
let result = cert
.crl_distribution_points()
.expect("missing distribution points extension")
.collect::<Result<Vec<_>, Error>>();
assert!(matches!(result, Err(Error::MalformedExtensions)));
}
#[test]
#[cfg(feature = "alloc")]
fn test_crl_distribution_point_name_relative_to_issuer() {
let der = include_bytes!("../tests/crl_distrib_point/dp_name_relative_to_issuer.der");
let cert =
Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
// We expect to be able to parse the intermediate certificate's CRL distribution points.
let crl_distribution_points = cert
.crl_distribution_points()
.expect("missing distribution points extension")
.collect::<Result<Vec<_>, Error>>()
.expect("failed to parse distribution points");
// There should be one distribution point present.
assert_eq!(crl_distribution_points.len(), 1);
let crl_distribution_point = crl_distribution_points
.first()
.expect("missing distribution point");
assert!(crl_distribution_point.crl_issuer.is_none());
assert!(crl_distribution_point.reasons.is_none());
// We should be able to parse the distribution point name.
let distribution_point_name = crl_distribution_point
.names()
.expect("failed to parse distribution point names")
.expect("missing distribution point name");
// We expect the distribution point name to be a name relative to the CRL issuer.
assert!(matches!(
distribution_point_name,
DistributionPointName::NameRelativeToCrlIssuer
));
}
#[test]
#[cfg(feature = "alloc")]
fn test_crl_distribution_point_unknown_name_tag() {
// Created w/
// ascii2der -i tests/crl_distrib_point/unknown_dp_name_tag.der.txt > tests/crl_distrib_point/unknown_dp_name_tag.der
let der = include_bytes!("../tests/crl_distrib_point/unknown_dp_name_tag.der");
let cert =
Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
// We expect to be able to parse the intermediate certificate's CRL distribution points.
let crl_distribution_points = cert
.crl_distribution_points()
.expect("missing distribution points extension")
.collect::<Result<Vec<_>, Error>>()
.expect("failed to parse distribution points");
// There should be one distribution point present.
assert_eq!(crl_distribution_points.len(), 1);
let crl_distribution_point = crl_distribution_points
.first()
.expect("missing distribution point");
// Parsing the distrubition point names should fail due to the unknown name tag.
let result = crl_distribution_point.names();
assert!(matches!(result, Err(Error::BadDer)))
}
#[test]
#[cfg(feature = "alloc")]
fn test_crl_distribution_point_multiple() {
let der = include_bytes!("../tests/crl_distrib_point/multiple_distribution_points.der");
let cert =
Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
// We expect to be able to parse the intermediate certificate's CRL distribution points.
let crl_distribution_points = cert
.crl_distribution_points()
.expect("missing distribution points extension")
.collect::<Result<Vec<_>, Error>>()
.expect("failed to parse distribution points");
// There should be two distribution points present.
let (point_a, point_b) = (
crl_distribution_points
.first()
.expect("missing first distribution point"),
crl_distribution_points
.get(1)
.expect("missing second distribution point"),
);
fn get_names<'a>(
point: &'a CrlDistributionPoint<'a>,
) -> impl Iterator<Item = Result<GeneralName<'a>, Error>> {
match point
.names()
.expect("failed to parse distribution point names")
.expect("missing distribution point name")
{
DistributionPointName::NameRelativeToCrlIssuer => {
panic!("unexpected relative name")
}
DistributionPointName::FullName(names) => names,
}
}
fn uri_bytes<'a>(name: &'a GeneralName<'a>) -> &'a [u8] {
match name {
GeneralName::UniformResourceIdentifier(uri) => uri.as_slice_less_safe(),
_ => panic!("unexpected name type"),
}
}
// We expect to find three URIs across the two distribution points.
let expected_names = [
"http://example.com/crl.1.der".as_bytes(),
"http://example.com/crl.2.der".as_bytes(),
"http://example.com/crl.3.der".as_bytes(),
];
let all_names = get_names(point_a)
.chain(get_names(point_b))
.collect::<Result<Vec<_>, Error>>()
.expect("failed to parse names");
assert_eq!(
all_names.iter().map(uri_bytes).collect::<Vec<_>>(),
expected_names
);
}
}

374
vendor/rustls-webpki/src/crl/mod.rs vendored Normal file
View File

@@ -0,0 +1,374 @@
// Copyright 2023 Daniel McCarney.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
use pki_types::{SignatureVerificationAlgorithm, UnixTime};
use crate::error::Error;
use crate::verify_cert::{Budget, PathNode, Role};
use crate::{der, public_values_eq};
use core::fmt::Debug;
mod types;
pub use types::{
BorrowedCertRevocationList, BorrowedRevokedCert, CertRevocationList, RevocationReason,
};
#[cfg(feature = "alloc")]
pub use types::{OwnedCertRevocationList, OwnedRevokedCert};
/// Builds a RevocationOptions instance to control how revocation checking is performed.
#[derive(Debug, Copy, Clone)]
pub struct RevocationOptionsBuilder<'a> {
crls: &'a [&'a CertRevocationList<'a>],
depth: RevocationCheckDepth,
status_policy: UnknownStatusPolicy,
expiration_policy: ExpirationPolicy,
}
impl<'a> RevocationOptionsBuilder<'a> {
/// Create a builder that will perform revocation checking using the provided certificate
/// revocation lists (CRLs). At least one CRL must be provided.
///
/// Use [RevocationOptionsBuilder::build] to create a [RevocationOptions] instance.
///
/// By default revocation checking will be performed on both the end-entity (leaf) certificate
/// and intermediate certificates. This can be customized using the
/// [RevocationOptionsBuilder::with_depth] method.
///
/// By default revocation checking will fail if the revocation status of a certificate cannot
/// be determined. This can be customized using the
/// [RevocationOptionsBuilder::with_status_policy] method.
///
/// By default revocation checking will *not* fail if the verification time is beyond the time
/// in the CRL nextUpdate field. This can be customized using the
/// [RevocationOptionsBuilder::with_expiration_policy] method.
pub fn new(crls: &'a [&'a CertRevocationList<'a>]) -> Result<Self, CrlsRequired> {
if crls.is_empty() {
return Err(CrlsRequired(()));
}
Ok(Self {
crls,
depth: RevocationCheckDepth::Chain,
status_policy: UnknownStatusPolicy::Deny,
expiration_policy: ExpirationPolicy::Ignore,
})
}
/// Customize the depth at which revocation checking will be performed, controlling
/// whether only the end-entity (leaf) certificate in the chain to a trust anchor will
/// have its revocation status checked, or whether the intermediate certificates will as well.
pub fn with_depth(mut self, depth: RevocationCheckDepth) -> Self {
self.depth = depth;
self
}
/// Customize whether unknown revocation status is an error, or permitted.
pub fn with_status_policy(mut self, policy: UnknownStatusPolicy) -> Self {
self.status_policy = policy;
self
}
/// Customize whether the CRL nextUpdate field (i.e. expiration) is enforced.
pub fn with_expiration_policy(mut self, policy: ExpirationPolicy) -> Self {
self.expiration_policy = policy;
self
}
/// Construct a [RevocationOptions] instance based on the builder's configuration.
pub fn build(self) -> RevocationOptions<'a> {
RevocationOptions {
crls: self.crls,
depth: self.depth,
status_policy: self.status_policy,
expiration_policy: self.expiration_policy,
}
}
}
/// Describes how revocation checking is performed, if at all. Can be constructed with a
/// [RevocationOptionsBuilder] instance.
#[derive(Debug, Copy, Clone)]
pub struct RevocationOptions<'a> {
pub(crate) crls: &'a [&'a CertRevocationList<'a>],
pub(crate) depth: RevocationCheckDepth,
pub(crate) status_policy: UnknownStatusPolicy,
pub(crate) expiration_policy: ExpirationPolicy,
}
impl RevocationOptions<'_> {
#[allow(clippy::too_many_arguments)]
pub(crate) fn check(
&self,
path: &PathNode<'_>,
issuer_subject: untrusted::Input<'_>,
issuer_spki: untrusted::Input<'_>,
issuer_ku: Option<untrusted::Input<'_>>,
supported_sig_algs: &[&dyn SignatureVerificationAlgorithm],
budget: &mut Budget,
time: UnixTime,
) -> Result<Option<CertNotRevoked>, Error> {
assert!(public_values_eq(path.cert.issuer, issuer_subject));
// If the policy only specifies checking EndEntity revocation state and we're looking at an
// issuer certificate, return early without considering the certificate's revocation state.
if let (RevocationCheckDepth::EndEntity, Role::Issuer) = (self.depth, path.role()) {
return Ok(None);
}
let crl = self
.crls
.iter()
.find(|candidate_crl| candidate_crl.authoritative(path));
use UnknownStatusPolicy::*;
let crl = match (crl, self.status_policy) {
(Some(crl), _) => crl,
// If the policy allows unknown, return Ok(None) to indicate that the certificate
// was not confirmed as CertNotRevoked, but that this isn't an error condition.
(None, Allow) => return Ok(None),
// Otherwise, this is an error condition based on the provided policy.
(None, _) => return Err(Error::UnknownRevocationStatus),
};
// Verify the CRL signature with the issuer SPKI.
// TODO(XXX): consider whether we can refactor so this happens once up-front, instead
// of per-lookup.
// https://github.com/rustls/webpki/issues/81
crl.verify_signature(supported_sig_algs, issuer_spki, budget)
.map_err(crl_signature_err)?;
if self.expiration_policy == ExpirationPolicy::Enforce {
crl.check_expiration(time)?;
}
// Verify that if the issuer has a KeyUsage bitstring it asserts cRLSign.
KeyUsageMode::CrlSign.check(issuer_ku)?;
// Try to find the cert serial in the verified CRL contents.
let cert_serial = path.cert.serial.as_slice_less_safe();
match crl.find_serial(cert_serial)? {
None => Ok(Some(CertNotRevoked::assertion())),
Some(_) => Err(Error::CertRevoked),
}
}
}
// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3
#[repr(u8)]
#[derive(Clone, Copy)]
enum KeyUsageMode {
// DigitalSignature = 0,
// ContentCommitment = 1,
// KeyEncipherment = 2,
// DataEncipherment = 3,
// KeyAgreement = 4,
// CertSign = 5,
CrlSign = 6,
// EncipherOnly = 7,
// DecipherOnly = 8,
}
impl KeyUsageMode {
// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3
fn check(self, input: Option<untrusted::Input<'_>>) -> Result<(), Error> {
let bit_string = match input {
Some(input) => {
der::expect_tag(&mut untrusted::Reader::new(input), der::Tag::BitString)?
}
// While RFC 5280 requires KeyUsage be present, historically the absence of a KeyUsage
// has been treated as "Any Usage". We follow that convention here and assume the absence
// of KeyUsage implies the required_ku_bit_if_present we're checking for.
None => return Ok(()),
};
let flags = der::bit_string_flags(bit_string)?;
#[allow(clippy::as_conversions)] // u8 always fits in usize.
match flags.bit_set(self as usize) {
true => Ok(()),
false => Err(Error::IssuerNotCrlSigner),
}
}
}
// When verifying CRL signed data we want to disambiguate the context of possible errors by mapping
// them to CRL specific variants that a consumer can use to tell the issue was with the CRL's
// signature, not a certificate.
fn crl_signature_err(err: Error) -> Error {
match err {
#[allow(deprecated)]
Error::UnsupportedSignatureAlgorithm => Error::UnsupportedCrlSignatureAlgorithm,
Error::UnsupportedSignatureAlgorithmContext(cx) => {
Error::UnsupportedCrlSignatureAlgorithmContext(cx)
}
#[allow(deprecated)]
Error::UnsupportedSignatureAlgorithmForPublicKey => {
Error::UnsupportedCrlSignatureAlgorithmForPublicKey
}
Error::UnsupportedSignatureAlgorithmForPublicKeyContext(cx) => {
Error::UnsupportedCrlSignatureAlgorithmForPublicKeyContext(cx)
}
Error::InvalidSignatureForPublicKey => Error::InvalidCrlSignatureForPublicKey,
_ => err,
}
}
/// Describes how much of a certificate chain is checked for revocation status.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum RevocationCheckDepth {
/// Only check the end entity (leaf) certificate's revocation status.
EndEntity,
/// Check the revocation status of the end entity (leaf) and all intermediates.
Chain,
}
/// Describes how to handle the case where a certificate's revocation status is unknown.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum UnknownStatusPolicy {
/// Treat unknown revocation status permissively, acting as if the certificate were
/// not revoked.
Allow,
/// Treat unknown revocation status as an error condition, yielding
/// [Error::UnknownRevocationStatus].
Deny,
}
/// Describes how to handle the nextUpdate field of the CRL (i.e. expiration).
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ExpirationPolicy {
/// Enforce the verification time is before the time in the nextUpdate field.
/// Treats an expired CRL as an error condition yielding [Error::CrlExpired].
Enforce,
/// Ignore the CRL nextUpdate field.
Ignore,
}
// Zero-sized marker type representing positive assertion that revocation status was checked
// for a certificate and the result was that the certificate is not revoked.
pub(crate) struct CertNotRevoked(());
impl CertNotRevoked {
// Construct a CertNotRevoked marker.
fn assertion() -> Self {
Self(())
}
}
#[derive(Debug, Copy, Clone)]
/// An opaque error indicating the caller must provide at least one CRL when building a
/// [RevocationOptions] instance.
pub struct CrlsRequired(pub(crate) ());
#[cfg(test)]
mod tests {
use super::*;
#[test]
// redundant clone, clone_on_copy allowed to verify derived traits.
#[allow(clippy::redundant_clone, clippy::clone_on_copy)]
fn test_revocation_opts_builder() {
// Trying to build a RevocationOptionsBuilder w/o CRLs should err.
let result = RevocationOptionsBuilder::new(&[]);
assert!(matches!(result, Err(CrlsRequired(_))));
// The CrlsRequired error should be debug and clone when alloc is enabled.
#[cfg(feature = "alloc")]
{
let err = result.unwrap_err();
std::println!("{:?}", err.clone());
}
// It should be possible to build a revocation options builder with defaults.
let crl = include_bytes!("../../tests/crls/crl.valid.der");
let crl = BorrowedCertRevocationList::from_der(&crl[..])
.unwrap()
.into();
let crls = [&crl];
let builder = RevocationOptionsBuilder::new(&crls).unwrap();
#[cfg(feature = "alloc")]
{
// The builder should be debug, and clone when alloc is enabled
std::println!("{builder:?}");
_ = builder.clone();
}
let opts = builder.build();
assert_eq!(opts.depth, RevocationCheckDepth::Chain);
assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny);
assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
assert_eq!(opts.crls.len(), 1);
// It should be possible to build a revocation options builder with custom depth.
let opts = RevocationOptionsBuilder::new(&crls)
.unwrap()
.with_depth(RevocationCheckDepth::EndEntity)
.build();
assert_eq!(opts.depth, RevocationCheckDepth::EndEntity);
assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny);
assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
assert_eq!(opts.crls.len(), 1);
// It should be possible to build a revocation options builder that allows unknown
// revocation status.
let opts = RevocationOptionsBuilder::new(&crls)
.unwrap()
.with_status_policy(UnknownStatusPolicy::Allow)
.build();
assert_eq!(opts.depth, RevocationCheckDepth::Chain);
assert_eq!(opts.status_policy, UnknownStatusPolicy::Allow);
assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
assert_eq!(opts.crls.len(), 1);
// It should be possible to specify both depth and unknown status policy together.
let opts = RevocationOptionsBuilder::new(&crls)
.unwrap()
.with_status_policy(UnknownStatusPolicy::Allow)
.with_depth(RevocationCheckDepth::EndEntity)
.build();
assert_eq!(opts.depth, RevocationCheckDepth::EndEntity);
assert_eq!(opts.status_policy, UnknownStatusPolicy::Allow);
assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
assert_eq!(opts.crls.len(), 1);
// The same should be true for explicitly forbidding unknown status.
let opts = RevocationOptionsBuilder::new(&crls)
.unwrap()
.with_status_policy(UnknownStatusPolicy::Deny)
.with_depth(RevocationCheckDepth::EndEntity)
.build();
assert_eq!(opts.depth, RevocationCheckDepth::EndEntity);
assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny);
assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
assert_eq!(opts.crls.len(), 1);
// It should be possible to build a revocation options builder that allows unknown
// revocation status.
let opts = RevocationOptionsBuilder::new(&crls)
.unwrap()
.with_expiration_policy(ExpirationPolicy::Enforce)
.build();
assert_eq!(opts.depth, RevocationCheckDepth::Chain);
assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny);
assert_eq!(opts.expiration_policy, ExpirationPolicy::Enforce);
assert_eq!(opts.crls.len(), 1);
// Built revocation options should be debug and clone when alloc is enabled.
#[cfg(feature = "alloc")]
{
std::println!("{:?}", opts.clone());
}
}
}

1270
vendor/rustls-webpki/src/crl/types.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
 *├H├В

View File

@@ -0,0 +1 @@
 *├H├В

View File

@@ -0,0 +1 @@
 *├H├В

883
vendor/rustls-webpki/src/der.rs vendored Normal file
View File

@@ -0,0 +1,883 @@
// Copyright 2015 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::marker::PhantomData;
use crate::{Error, error::DerTypeId};
/// Iterator to parse a sequence of DER-encoded values of type `T`.
#[derive(Debug)]
pub struct DerIterator<'a, T> {
reader: untrusted::Reader<'a>,
marker: PhantomData<T>,
}
impl<'a, T> DerIterator<'a, T> {
/// [`DerIterator`] will consume all of the bytes in `input` reading values of type `T`.
pub(crate) fn new(input: untrusted::Input<'a>) -> Self {
Self {
reader: untrusted::Reader::new(input),
marker: PhantomData,
}
}
}
impl<'a, T: FromDer<'a>> Iterator for DerIterator<'a, T> {
type Item = Result<T, Error>;
fn next(&mut self) -> Option<Self::Item> {
(!self.reader.at_end()).then(|| T::from_der(&mut self.reader))
}
}
pub(crate) trait FromDer<'a>: Sized + 'a {
/// Parse a value of type `Self` from the given DER-encoded input.
fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error>;
const TYPE_ID: DerTypeId;
}
pub(crate) fn read_all<'a, T: FromDer<'a>>(input: untrusted::Input<'a>) -> Result<T, Error> {
input.read_all(Error::TrailingData(T::TYPE_ID), T::from_der)
}
// Copied (and extended) from ring's src/der.rs
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Eq, PartialEq)]
#[repr(u8)]
pub(crate) enum Tag {
Boolean = 0x01,
Integer = 0x02,
BitString = 0x03,
OctetString = 0x04,
OID = 0x06,
Enum = 0x0A,
Sequence = CONSTRUCTED | 0x10, // 0x30
UTCTime = 0x17,
GeneralizedTime = 0x18,
#[allow(clippy::identity_op)]
ContextSpecificConstructed0 = CONTEXT_SPECIFIC | CONSTRUCTED | 0,
ContextSpecificConstructed1 = CONTEXT_SPECIFIC | CONSTRUCTED | 1,
ContextSpecificConstructed3 = CONTEXT_SPECIFIC | CONSTRUCTED | 3,
}
pub(crate) const CONSTRUCTED: u8 = 0x20;
pub(crate) const CONTEXT_SPECIFIC: u8 = 0x80;
impl From<Tag> for usize {
#[allow(clippy::as_conversions)]
fn from(tag: Tag) -> Self {
tag as Self
}
}
impl From<Tag> for u8 {
#[allow(clippy::as_conversions)]
fn from(tag: Tag) -> Self {
tag as Self
} // XXX: narrowing conversion.
}
#[inline(always)]
pub(crate) fn expect_tag_and_get_value_limited<'a>(
input: &mut untrusted::Reader<'a>,
tag: Tag,
size_limit: usize,
) -> Result<untrusted::Input<'a>, Error> {
let (actual_tag, inner) = read_tag_and_get_value_limited(input, size_limit)?;
if usize::from(tag) != usize::from(actual_tag) {
return Err(Error::BadDer);
}
Ok(inner)
}
pub(crate) fn nested_limited<'a, R>(
input: &mut untrusted::Reader<'a>,
tag: Tag,
error: Error,
decoder: impl FnOnce(&mut untrusted::Reader<'a>) -> Result<R, Error>,
size_limit: usize,
) -> Result<R, Error> {
match expect_tag_and_get_value_limited(input, tag, size_limit) {
Ok(value) => value.read_all(error, decoder),
Err(_) => Err(error),
}
}
// TODO: investigate taking decoder as a reference to reduce generated code
// size.
pub(crate) fn nested<'a, R>(
input: &mut untrusted::Reader<'a>,
tag: Tag,
error: Error,
decoder: impl FnOnce(&mut untrusted::Reader<'a>) -> Result<R, Error>,
) -> Result<R, Error> {
nested_limited(input, tag, error, decoder, TWO_BYTE_DER_SIZE)
}
pub(crate) fn expect_tag<'a>(
input: &mut untrusted::Reader<'a>,
tag: Tag,
) -> Result<untrusted::Input<'a>, Error> {
let (actual_tag, value) = read_tag_and_get_value_limited(input, TWO_BYTE_DER_SIZE)?;
if usize::from(tag) != usize::from(actual_tag) {
return Err(Error::BadDer);
}
Ok(value)
}
#[inline(always)]
pub(crate) fn read_tag_and_get_value<'a>(
input: &mut untrusted::Reader<'a>,
) -> Result<(u8, untrusted::Input<'a>), Error> {
read_tag_and_get_value_limited(input, TWO_BYTE_DER_SIZE)
}
#[inline(always)]
pub(crate) fn read_tag_and_get_value_limited<'a>(
input: &mut untrusted::Reader<'a>,
size_limit: usize,
) -> Result<(u8, untrusted::Input<'a>), Error> {
let tag = input.read_byte().map_err(end_of_input_err)?;
if (tag & HIGH_TAG_RANGE_START) == HIGH_TAG_RANGE_START {
return Err(Error::BadDer); // High tag number form is not allowed.
}
// If the high order bit of the first byte is set to zero then the length
// is encoded in the seven remaining bits of that byte. Otherwise, those
// seven bits represent the number of bytes used to encode the length.
let length = match input.read_byte().map_err(end_of_input_err)? {
n if (n & SHORT_FORM_LEN_MAX) == 0 => usize::from(n),
LONG_FORM_LEN_ONE_BYTE => {
let length_byte = input.read_byte().map_err(end_of_input_err)?;
if length_byte < SHORT_FORM_LEN_MAX {
return Err(Error::BadDer); // Not the canonical encoding.
}
usize::from(length_byte)
}
LONG_FORM_LEN_TWO_BYTES => {
let length_byte_one = usize::from(input.read_byte().map_err(end_of_input_err)?);
let length_byte_two = usize::from(input.read_byte().map_err(end_of_input_err)?);
let combined = (length_byte_one << 8) | length_byte_two;
if combined <= LONG_FORM_LEN_ONE_BYTE_MAX {
return Err(Error::BadDer); // Not the canonical encoding.
}
combined
}
LONG_FORM_LEN_THREE_BYTES => {
let length_byte_one = usize::from(input.read_byte().map_err(end_of_input_err)?);
let length_byte_two = usize::from(input.read_byte().map_err(end_of_input_err)?);
let length_byte_three = usize::from(input.read_byte().map_err(end_of_input_err)?);
let combined = (length_byte_one << 16) | (length_byte_two << 8) | length_byte_three;
if combined <= LONG_FORM_LEN_TWO_BYTES_MAX {
return Err(Error::BadDer); // Not the canonical encoding.
}
combined
}
LONG_FORM_LEN_FOUR_BYTES => {
let length_byte_one = usize::from(input.read_byte().map_err(end_of_input_err)?);
let length_byte_two = usize::from(input.read_byte().map_err(end_of_input_err)?);
let length_byte_three = usize::from(input.read_byte().map_err(end_of_input_err)?);
let length_byte_four = usize::from(input.read_byte().map_err(end_of_input_err)?);
let combined = (length_byte_one << 24)
| (length_byte_two << 16)
| (length_byte_three << 8)
| length_byte_four;
if combined <= LONG_FORM_LEN_THREE_BYTES_MAX {
return Err(Error::BadDer); // Not the canonical encoding.
}
combined
}
_ => {
return Err(Error::BadDer); // We don't support longer lengths.
}
};
if length >= size_limit {
return Err(Error::BadDer); // The length is larger than the caller accepts.
}
let inner = input.read_bytes(length).map_err(end_of_input_err)?;
Ok((tag, inner))
}
/// Prepend `bytes` with the given ASN.1 [`Tag`] and appropriately encoded length byte(s).
/// Useful for "adding back" ASN.1 bytes to parsed content.
#[cfg(feature = "alloc")]
#[allow(clippy::as_conversions)]
pub(crate) fn asn1_wrap(tag: Tag, bytes: &[u8]) -> Vec<u8> {
let len = bytes.len();
// The length is encoded differently depending on how many bytes there are
if len < usize::from(SHORT_FORM_LEN_MAX) {
// Short form: the length is encoded using a single byte
// Contents: Tag byte, single length byte, and passed bytes
let mut ret = Vec::with_capacity(2 + len);
ret.push(tag.into()); // Tag byte
ret.push(len as u8); // Single length byte
ret.extend_from_slice(bytes); // Passed bytes
ret
} else {
// Long form: The length is encoded using multiple bytes
// Contents: Tag byte, number-of-length-bytes byte, length bytes, and passed bytes
// The first byte indicates how many more bytes will be used to encode the length
// First, get a big-endian representation of the byte slice's length
let size = len.to_be_bytes();
// Find the number of leading empty bytes in that representation
// This will determine the smallest number of bytes we need to encode the length
let leading_zero_bytes = size
.iter()
.position(|&byte| byte != 0)
.unwrap_or(size.len());
assert!(leading_zero_bytes < size.len());
// Number of bytes used - number of not needed bytes = smallest number needed
let encoded_bytes = size.len() - leading_zero_bytes;
let mut ret = Vec::with_capacity(2 + encoded_bytes + len);
// Indicate this is a number-of-length-bytes byte by setting the high order bit
let number_of_length_bytes_byte = SHORT_FORM_LEN_MAX + encoded_bytes as u8;
ret.push(tag.into()); // Tag byte
ret.push(number_of_length_bytes_byte); // Number-of-length-bytes byte
ret.extend_from_slice(&size[leading_zero_bytes..]); // Length bytes
ret.extend_from_slice(bytes); // Passed bytes
ret
}
}
// Long-form DER encoded lengths of two bytes can express lengths up to the following limit.
//
// The upstream ring::io::der::read_tag_and_get_value() function limits itself to up to two byte
// long-form DER lengths, and so this limit represents the maximum length that was possible to
// read before the introduction of the read_tag_and_get_value_limited function.
pub(crate) const TWO_BYTE_DER_SIZE: usize = LONG_FORM_LEN_TWO_BYTES_MAX;
// The maximum size of a DER value that Webpki can support reading.
//
// Webpki limits itself to four byte long-form DER lengths, and so this limit represents
// the maximum size tagged DER value that can be read for any purpose.
pub(crate) const MAX_DER_SIZE: usize = LONG_FORM_LEN_FOUR_BYTES_MAX;
// DER Tag identifiers have two forms:
// * Low tag number form (for tags values in the range [0..30]
// * High tag number form (for tag values in the range [31..]
// We only support low tag number form.
const HIGH_TAG_RANGE_START: u8 = 31;
// DER length octets have two forms:
// * Short form: 1 octet supporting lengths between 0 and 127.
// * Long definite form: 2 to 127 octets, number of octets encoded into first octet.
const SHORT_FORM_LEN_MAX: u8 = 128;
// Leading octet for long definite form DER length expressed in second byte.
const LONG_FORM_LEN_ONE_BYTE: u8 = 0x81;
// Maximum size that can be expressed in a one byte long form len.
const LONG_FORM_LEN_ONE_BYTE_MAX: usize = 0xff;
// Leading octet for long definite form DER length expressed in subsequent two bytes.
const LONG_FORM_LEN_TWO_BYTES: u8 = 0x82;
// Maximum size that can be expressed in a two byte long form len.
const LONG_FORM_LEN_TWO_BYTES_MAX: usize = 0xff_ff;
// Leading octet for long definite form DER length expressed in subsequent three bytes.
const LONG_FORM_LEN_THREE_BYTES: u8 = 0x83;
// Maximum size that can be expressed in a three byte long form len.
const LONG_FORM_LEN_THREE_BYTES_MAX: usize = 0xff_ff_ff;
// Leading octet for long definite form DER length expressed in subsequent four bytes.
const LONG_FORM_LEN_FOUR_BYTES: u8 = 0x84;
// Maximum size that can be expressed in a four byte long form der len.
const LONG_FORM_LEN_FOUR_BYTES_MAX: usize = 0xff_ff_ff_ff;
// TODO: investigate taking decoder as a reference to reduce generated code
// size.
pub(crate) fn nested_of_mut<'a>(
input: &mut untrusted::Reader<'a>,
outer_tag: Tag,
inner_tag: Tag,
error: Error,
allow_empty: bool,
mut decoder: impl FnMut(&mut untrusted::Reader<'a>) -> Result<(), Error>,
) -> Result<(), Error> {
nested(input, outer_tag, error.clone(), |outer| {
if allow_empty && outer.at_end() {
return Ok(());
}
loop {
nested(outer, inner_tag, error.clone(), |inner| decoder(inner))?;
if outer.at_end() {
break;
}
}
Ok(())
})
}
pub(crate) fn bit_string_with_no_unused_bits<'a>(
input: &mut untrusted::Reader<'a>,
) -> Result<untrusted::Input<'a>, Error> {
nested(
input,
Tag::BitString,
Error::TrailingData(DerTypeId::BitString),
|value| {
let unused_bits_at_end = value.read_byte().map_err(|_| Error::BadDer)?;
if unused_bits_at_end != 0 {
return Err(Error::BadDer);
}
Ok(value.read_bytes_to_end())
},
)
}
pub(crate) struct BitStringFlags<'a> {
raw_bits: &'a [u8],
}
impl BitStringFlags<'_> {
pub(crate) fn bit_set(&self, bit: usize) -> bool {
let byte_index = bit / 8;
let bit_shift = 7 - (bit % 8);
if self.raw_bits.len() < (byte_index + 1) {
false
} else {
((self.raw_bits[byte_index] >> bit_shift) & 1) != 0
}
}
}
// ASN.1 BIT STRING fields for sets of flags are encoded in DER with some peculiar details related
// to padding. Notably this means we expect an indicator of the number of bits of padding, and then
// the actual bit values. See this Stack Overflow discussion[0], and ITU X690-0207[1] Section 8.6
// and Section 11.2 for more information.
//
// [0]: https://security.stackexchange.com/a/10396
// [1]: https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
pub(crate) fn bit_string_flags(input: untrusted::Input<'_>) -> Result<BitStringFlags<'_>, Error> {
input.read_all(Error::BadDer, |bit_string| {
// ITU X690-0207 11.2:
// "The initial octet shall encode, as an unsigned binary integer with bit 1 as the least
// significant bit, the number of unused bits in the final subsequent octet.
// The number shall be in the range zero to seven"
let padding_bits = bit_string.read_byte().map_err(|_| Error::BadDer)?;
let raw_bits = bit_string.read_bytes_to_end().as_slice_less_safe();
// It's illegal to have more than 7 bits of padding. Similarly, if the raw bitflags
// are empty there should be no padding.
if padding_bits > 7 || (raw_bits.is_empty() && padding_bits != 0) {
return Err(Error::BadDer);
}
// If there are padding bits then the last bit of the last raw byte must be 0 or the
// distinguished encoding rules are not being followed.
let last_byte = raw_bits[raw_bits.len() - 1];
let padding_mask = (1 << padding_bits) - 1;
match padding_bits > 0 && (last_byte & padding_mask) != 0 {
true => Err(Error::BadDer),
false => Ok(BitStringFlags { raw_bits }),
}
})
}
impl<'a> FromDer<'a> for u8 {
fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
match *nonnegative_integer(reader)?.as_slice_less_safe() {
[b] => Ok(b),
_ => Err(Error::BadDer),
}
}
const TYPE_ID: DerTypeId = DerTypeId::U8;
}
pub(crate) fn nonnegative_integer<'a>(
input: &mut untrusted::Reader<'a>,
) -> Result<untrusted::Input<'a>, Error> {
let value = expect_tag(input, Tag::Integer)?;
match value
.as_slice_less_safe()
.split_first()
.ok_or(Error::BadDer)?
{
// Zero or leading zero.
(0, rest) => {
match rest.first() {
// Zero
None => Ok(value),
// Necessary leading zero.
Some(&second) if second & 0x80 == 0x80 => Ok(untrusted::Input::from(rest)),
// Unnecessary leading zero.
_ => Err(Error::BadDer),
}
}
// Positive value with no leading zero.
(first, _) if first & 0x80 == 0x00 => Ok(value),
// Negative value.
(_, _) => Err(Error::BadDer),
}
}
pub(crate) fn end_of_input_err(_: untrusted::EndOfInput) -> Error {
Error::BadDer
}
// Like mozilla::pkix, we accept the nonconformant explicit encoding of
// the default value (false) for compatibility with real-world certificates.
impl<'a> FromDer<'a> for bool {
fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
if !reader.peek(Tag::Boolean.into()) {
return Ok(false);
}
nested(
reader,
Tag::Boolean,
Error::TrailingData(Self::TYPE_ID),
|input| match input.read_byte() {
Ok(0xff) => Ok(true),
Ok(0x00) => Ok(false),
_ => Err(Error::BadDer),
},
)
}
const TYPE_ID: DerTypeId = DerTypeId::Bool;
}
macro_rules! oid {
( $first:expr, $second:expr, $( $tail:expr ),* ) =>
(
[(40 * $first) + $second, $( $tail ),*]
)
}
#[cfg(test)]
mod tests {
use super::DerTypeId;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
#[test]
fn test_asn1_wrap() {
// Prepend stuff to `bytes` to put it in a DER SEQUENCE.
let wrap_in_sequence = |bytes: &[u8]| super::asn1_wrap(super::Tag::Sequence, bytes);
// Empty slice
assert_eq!(vec![0x30, 0x00], wrap_in_sequence(&[]));
// Small size
assert_eq!(
vec![0x30, 0x04, 0x00, 0x11, 0x22, 0x33],
wrap_in_sequence(&[0x00, 0x11, 0x22, 0x33])
);
// Medium size
let mut val = Vec::new();
val.resize(255, 0x12);
assert_eq!(
vec![0x30, 0x81, 0xff, 0x12, 0x12, 0x12],
wrap_in_sequence(&val)[..6]
);
// Large size
let mut val = Vec::new();
val.resize(4660, 0x12);
wrap_in_sequence(&val);
assert_eq!(
vec![0x30, 0x82, 0x12, 0x34, 0x12, 0x12],
wrap_in_sequence(&val)[..6]
);
// Huge size
let mut val = Vec::new();
val.resize(0xffff, 0x12);
let result = wrap_in_sequence(&val);
assert_eq!(vec![0x30, 0x82, 0xff, 0xff, 0x12, 0x12], result[..6]);
assert_eq!(result.len(), 0xffff + 4);
// Gigantic size
let mut val = Vec::new();
val.resize(0x100000, 0x12);
let result = wrap_in_sequence(&val);
assert_eq!(vec![0x30, 0x83, 0x10, 0x00, 0x00, 0x12, 0x12], result[..7]);
assert_eq!(result.len(), 0x100000 + 5);
// Ludicrous size
let mut val = Vec::new();
val.resize(0x1000000, 0x12);
let result = wrap_in_sequence(&val);
assert_eq!(
vec![0x30, 0x84, 0x01, 0x00, 0x00, 0x00, 0x12, 0x12],
result[..8]
);
assert_eq!(result.len(), 0x1000000 + 6);
}
#[test]
fn test_optional_boolean() {
use super::{Error, FromDer};
// Empty input results in false
assert!(!bool::from_der(&mut bytes_reader(&[])).unwrap());
// Optional, so another data type results in false
assert!(!bool::from_der(&mut bytes_reader(&[0x05, 0x00])).unwrap());
// Only 0x00 and 0xff are accepted values
assert_eq!(
Err(Error::BadDer),
bool::from_der(&mut bytes_reader(&[0x01, 0x01, 0x42]))
);
// True
assert!(bool::from_der(&mut bytes_reader(&[0x01, 0x01, 0xff])).unwrap());
// False
assert!(!bool::from_der(&mut bytes_reader(&[0x01, 0x01, 0x00])).unwrap());
}
#[test]
fn test_bit_string_with_no_unused_bits() {
use super::{Error, bit_string_with_no_unused_bits};
// Unexpected type
assert_eq!(
bit_string_with_no_unused_bits(&mut bytes_reader(&[0x01, 0x01, 0xff])).unwrap_err(),
Error::TrailingData(DerTypeId::BitString),
);
// Unexpected nonexistent type
assert_eq!(
bit_string_with_no_unused_bits(&mut bytes_reader(&[0x42, 0xff, 0xff])).unwrap_err(),
Error::TrailingData(DerTypeId::BitString),
);
// Unexpected empty input
assert_eq!(
bit_string_with_no_unused_bits(&mut bytes_reader(&[])).unwrap_err(),
Error::TrailingData(DerTypeId::BitString),
);
// Valid input with non-zero unused bits
assert_eq!(
bit_string_with_no_unused_bits(&mut bytes_reader(&[0x03, 0x03, 0x04, 0x12, 0x34]))
.unwrap_err(),
Error::BadDer,
);
// Valid input
assert_eq!(
bit_string_with_no_unused_bits(&mut bytes_reader(&[0x03, 0x03, 0x00, 0x12, 0x34]))
.unwrap()
.as_slice_less_safe(),
&[0x12, 0x34],
);
}
fn bytes_reader(bytes: &[u8]) -> untrusted::Reader<'_> {
untrusted::Reader::new(untrusted::Input::from(bytes))
}
#[test]
fn read_tag_and_get_value_default_limit() {
use super::{Error, read_tag_and_get_value};
let inputs = &[
// DER with short-form length encoded as three bytes.
&[EXAMPLE_TAG, 0x83, 0xFF, 0xFF, 0xFF].as_slice(),
// DER with short-form length encoded as four bytes.
&[EXAMPLE_TAG, 0x84, 0xFF, 0xFF, 0xFF, 0xFF].as_slice(),
];
for input in inputs {
let mut bytes = untrusted::Reader::new(untrusted::Input::from(input));
// read_tag_and_get_value should reject DER with encoded lengths larger than two
// bytes as BadDer.
assert!(matches!(
read_tag_and_get_value(&mut bytes),
Err(Error::BadDer)
));
}
}
#[test]
fn read_tag_and_get_value_limited_high_form() {
use super::{Error, LONG_FORM_LEN_TWO_BYTES_MAX, read_tag_and_get_value_limited};
let mut bytes = untrusted::Reader::new(untrusted::Input::from(&[0xFF]));
// read_tag_and_get_value_limited_high_form should reject DER with "high tag number form" tags.
assert!(matches!(
read_tag_and_get_value_limited(&mut bytes, LONG_FORM_LEN_TWO_BYTES_MAX),
Err(Error::BadDer)
));
}
#[test]
fn read_tag_and_get_value_limited_non_canonical() {
use super::{Error, LONG_FORM_LEN_TWO_BYTES_MAX, read_tag_and_get_value_limited};
let inputs = &[
// Two byte length, with expressed length < 128.
&[EXAMPLE_TAG, 0x81, 0x01].as_slice(),
// Three byte length, with expressed length < 256.
&[EXAMPLE_TAG, 0x82, 0x00, 0x01].as_slice(),
// Four byte length, with expressed length, < 65536.
&[EXAMPLE_TAG, 0x83, 0x00, 0x00, 0x01].as_slice(),
// Five byte length, with expressed length < 16777216.
&[EXAMPLE_TAG, 0x84, 0x00, 0x00, 0x00, 0x01].as_slice(),
];
for input in inputs {
let mut bytes = untrusted::Reader::new(untrusted::Input::from(input));
// read_tag_and_get_value_limited should reject DER with non-canonical lengths.
assert!(matches!(
read_tag_and_get_value_limited(&mut bytes, LONG_FORM_LEN_TWO_BYTES_MAX),
Err(Error::BadDer)
));
}
}
#[test]
#[cfg(feature = "alloc")]
fn read_tag_and_get_value_limited_limits() {
use super::{Error, read_tag_and_get_value_limited};
let short_input = &[0xFF];
let short_input_encoded = &[
&[EXAMPLE_TAG],
der_encode_length(short_input.len()).as_slice(),
short_input,
]
.concat();
let long_input = &[1_u8; 65537];
let long_input_encoded = &[
&[EXAMPLE_TAG],
der_encode_length(long_input.len()).as_slice(),
long_input,
]
.concat();
struct Testcase<'a> {
input: &'a [u8],
limit: usize,
err: Option<Error>,
}
let testcases = &[
Testcase {
input: short_input_encoded,
limit: 1,
err: Some(Error::BadDer),
},
Testcase {
input: short_input_encoded,
limit: short_input_encoded.len() + 1,
err: None,
},
Testcase {
input: long_input_encoded,
limit: long_input.len(),
err: Some(Error::BadDer),
},
Testcase {
input: long_input_encoded,
limit: long_input.len() + 1,
err: None,
},
];
for tc in testcases {
let mut bytes = untrusted::Reader::new(untrusted::Input::from(tc.input));
let res = read_tag_and_get_value_limited(&mut bytes, tc.limit);
match &tc.err {
None => assert!(res.is_ok()),
Some(e) => {
let actual = res.unwrap_err();
assert_eq!(&actual, e)
}
}
}
}
#[allow(clippy::as_conversions)] // infallible.
const EXAMPLE_TAG: u8 = super::Tag::Sequence as u8;
#[cfg(feature = "alloc")]
#[allow(clippy::as_conversions)] // test code.
fn der_encode_length(length: usize) -> Vec<u8> {
if length < 128 {
vec![length as u8]
} else {
let mut encoded: Vec<u8> = Vec::new();
let mut remaining_length = length;
while remaining_length > 0 {
let byte = (remaining_length & 0xFF) as u8;
encoded.insert(0, byte);
remaining_length >>= 8;
}
let length_octet = encoded.len() as u8 | 0x80;
encoded.insert(0, length_octet);
encoded
}
}
#[test]
fn misencoded_bit_string_flags() {
use super::{Error, bit_string_flags};
let bad_padding_example = untrusted::Input::from(&[
0x08, // 8 bit of padding (illegal!).
0x06, // 1 byte of bit flags asserting bits 5 and 6.
]);
assert!(matches!(
bit_string_flags(bad_padding_example),
Err(Error::BadDer)
));
let bad_padding_example = untrusted::Input::from(&[
0x01, // 1 bit of padding.
// No flags value (illegal with padding!).
]);
assert!(matches!(
bit_string_flags(bad_padding_example),
Err(Error::BadDer)
));
}
#[test]
fn valid_bit_string_flags() {
use super::bit_string_flags;
let example_key_usage = untrusted::Input::from(&[
0x01, // 1 bit of padding.
0x06, // 1 byte of bit flags asserting bits 5 and 6.
]);
let res = bit_string_flags(example_key_usage).unwrap();
assert!(!res.bit_set(0));
assert!(!res.bit_set(1));
assert!(!res.bit_set(2));
assert!(!res.bit_set(3));
assert!(!res.bit_set(4));
// NB: Bits 5 and 6 should be set.
assert!(res.bit_set(5));
assert!(res.bit_set(6));
assert!(!res.bit_set(7));
assert!(!res.bit_set(8));
// Bits outside the range of values shouldn't be considered set.
assert!(!res.bit_set(256));
}
#[test]
fn test_small_nonnegative_integer() {
use super::{Error, FromDer, Tag};
for value in 0..=127 {
let data = [Tag::Integer.into(), 1, value];
let mut rd = untrusted::Reader::new(untrusted::Input::from(&data));
assert_eq!(u8::from_der(&mut rd), Ok(value),);
}
for value in 128..=255 {
let data = [Tag::Integer.into(), 2, 0x00, value];
let mut rd = untrusted::Reader::new(untrusted::Input::from(&data));
assert_eq!(u8::from_der(&mut rd), Ok(value),);
}
// not an integer
assert_eq!(
u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[
Tag::Sequence.into(),
1,
1
]))),
Err(Error::BadDer)
);
// negative
assert_eq!(
u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[
Tag::Integer.into(),
1,
0xff
]))),
Err(Error::BadDer)
);
// positive but too large
assert_eq!(
u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[
Tag::Integer.into(),
2,
0x01,
0x00
]))),
Err(Error::BadDer)
);
// unnecessary leading zero
assert_eq!(
u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[
Tag::Integer.into(),
2,
0x00,
0x05
]))),
Err(Error::BadDer)
);
// truncations
assert_eq!(
u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[]))),
Err(Error::BadDer)
);
assert_eq!(
u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[
Tag::Integer.into(),
]))),
Err(Error::BadDer)
);
assert_eq!(
u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[
Tag::Integer.into(),
1,
]))),
Err(Error::BadDer)
);
assert_eq!(
u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[
Tag::Integer.into(),
2,
0
]))),
Err(Error::BadDer)
);
}
}

240
vendor/rustls-webpki/src/end_entity.rs vendored Normal file
View File

@@ -0,0 +1,240 @@
// Copyright 2015-2021 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
use core::ops::Deref;
use pki_types::{
CertificateDer, ServerName, SignatureVerificationAlgorithm, TrustAnchor, UnixTime,
};
use crate::crl::RevocationOptions;
use crate::error::Error;
use crate::subject_name::{verify_dns_names, verify_ip_address_names};
use crate::verify_cert::{self, ExtendedKeyUsageValidator, VerifiedPath};
use crate::{cert, signed_data};
/// An end-entity certificate.
///
/// Server certificate processing in a TLS connection consists of several
/// steps. All of these steps are necessary:
///
/// * [`EndEntityCert::verify_for_usage()`]: Verify that the peer's certificate
/// is valid for the current usage scenario. For server authentication, use
/// [`crate::KeyUsage::server_auth()`].
/// * [`EndEntityCert::verify_is_valid_for_subject_name()`]: Verify that the server's
/// certificate is valid for the host or IP address that is being connected to.
/// * [`EndEntityCert::verify_signature()`]: Verify that the signature of server's
/// `ServerKeyExchange` message is valid for the server's certificate.
///
/// Client certificate processing in a TLS connection consists of analogous
/// steps. All of these steps are necessary:
///
/// * [`EndEntityCert::verify_for_usage()`]: Verify that the peer's certificate
/// is valid for the current usage scenario. For client authentication, use
/// [`crate::KeyUsage::client_auth()`].
/// * [`EndEntityCert::verify_signature()`]: Verify that the signature of client's
/// `CertificateVerify` message is valid using the public key from the
/// client's certificate.
///
/// Although it would be less error-prone to combine all these steps into a
/// single function call, some significant optimizations are possible if the
/// three steps are processed separately (in parallel). It does not matter much
/// which order the steps are done in, but **all of these steps must completed
/// before application data is sent and before received application data is
/// processed**. The [`TryFrom`] conversion from `&CertificateDer<'_>` is an
/// inexpensive operation and is deterministic, so if these tasks are done in
/// multiple threads, it is probably best to just create multiple [`EndEntityCert`]
/// instances for the same DER-encoded ASN.1 certificate bytes.
pub struct EndEntityCert<'a> {
inner: cert::Cert<'a>,
}
impl<'a> TryFrom<&'a CertificateDer<'a>> for EndEntityCert<'a> {
type Error = Error;
/// Parse the ASN.1 DER-encoded X.509 encoding of the certificate
/// `cert_der`.
fn try_from(cert: &'a CertificateDer<'a>) -> Result<Self, Self::Error> {
Ok(Self {
inner: cert::Cert::from_der(untrusted::Input::from(cert.as_ref()))?,
})
}
}
impl EndEntityCert<'_> {
/// Verifies that the end-entity certificate is valid for use against the
/// specified Extended Key Usage (EKU).
///
/// * `supported_sig_algs` is the list of signature algorithms that are
/// trusted for use in certificate signatures; the end-entity certificate's
/// public key is not validated against this list.
/// * `trust_anchors` is the list of root CAs to trust in the built path.
/// * `intermediate_certs` is the sequence of intermediate certificates that
/// a peer sent for the purpose of path building.
/// * `time` is the time for which the validation is effective (usually the
/// current time).
/// * `usage` is the intended usage of the certificate, indicating what kind
/// of usage we're verifying the certificate for. The default [`ExtendedKeyUsageValidator`]
/// implementation is [`KeyUsage`](crate::KeyUsage).
/// * `crls` is the list of certificate revocation lists to check
/// the certificate against.
/// * `verify_path` is an optional verification function for path candidates.
///
/// If successful, yields a `VerifiedPath` type that can be used to inspect a verified chain
/// of certificates that leads from the `end_entity` to one of the `self.trust_anchors`.
///
/// `verify_path` will only be called for potentially verified paths, that is, paths that
/// have been verified up to the trust anchor. As such, `verify_path()` cannot be used to
/// verify a path that doesn't satisfy the constraints listed above; it can only be used to
/// reject a path that does satisfy the aforementioned constraints. If `verify_path` returns
/// an error, path building will continue in order to try other options.
#[allow(clippy::too_many_arguments)]
pub fn verify_for_usage<'p>(
&'p self,
supported_sig_algs: &[&dyn SignatureVerificationAlgorithm],
trust_anchors: &'p [TrustAnchor<'_>],
intermediate_certs: &'p [CertificateDer<'p>],
time: UnixTime,
usage: impl ExtendedKeyUsageValidator,
revocation: Option<RevocationOptions<'_>>,
verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>,
) -> Result<VerifiedPath<'p>, Error> {
verify_cert::ChainOptions {
eku: usage,
supported_sig_algs,
trust_anchors,
intermediate_certs,
revocation,
}
.build_chain(self, time, verify_path)
}
/// Verifies that the certificate is valid for the given Subject Name.
pub fn verify_is_valid_for_subject_name(
&self,
server_name: &ServerName<'_>,
) -> Result<(), Error> {
match server_name {
ServerName::DnsName(dns_name) => verify_dns_names(dns_name, &self.inner),
// IP addresses are not compared against the subject field;
// only against Subject Alternative Names.
ServerName::IpAddress(ip_address) => verify_ip_address_names(ip_address, &self.inner),
_ => Err(Error::UnsupportedNameType),
}
}
/// Verifies the signature `signature` of message `msg` using the
/// certificate's public key.
///
/// `signature_alg` is the algorithm to use to
/// verify the signature; the certificate's public key is verified to be
/// compatible with this algorithm.
///
/// For TLS 1.2, `signature` corresponds to TLS's
/// `DigitallySigned.signature` and `signature_alg` corresponds to TLS's
/// `DigitallySigned.algorithm` of TLS type `SignatureAndHashAlgorithm`. In
/// TLS 1.2 a single `SignatureAndHashAlgorithm` may map to multiple
/// `SignatureVerificationAlgorithm`s. For example, a TLS 1.2
/// `SignatureAndHashAlgorithm` of (ECDSA, SHA-256) may map to any or all
/// of {`ECDSA_P256_SHA256`, `ECDSA_P384_SHA256`}, depending on how the TLS
/// implementation is configured.
///
/// For current TLS 1.3 drafts, `signature_alg` corresponds to TLS's
/// `algorithm` fields of type `SignatureScheme`. There is (currently) a
/// one-to-one correspondence between TLS 1.3's `SignatureScheme` and
/// `SignatureVerificationAlgorithm`.
pub fn verify_signature(
&self,
signature_alg: &dyn SignatureVerificationAlgorithm,
msg: &[u8],
signature: &[u8],
) -> Result<(), Error> {
signed_data::verify_signature(
signature_alg,
self.inner.spki,
untrusted::Input::from(msg),
untrusted::Input::from(signature),
)
}
}
impl<'a> Deref for EndEntityCert<'a> {
type Target = cert::Cert<'a>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[cfg(feature = "alloc")]
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
use crate::test_utils;
use crate::test_utils::RCGEN_SIGNATURE_ALG;
// This test reproduces https://github.com/rustls/webpki/issues/167 --- an
// end-entity cert where the common name is a `PrintableString` rather than
// a `UTF8String` cannot iterate over its subject alternative names.
#[test]
fn printable_string_common_name() {
const DNS_NAME: &str = "test.example.com";
let issuer = test_utils::make_issuer("Test");
let ee_cert = {
let mut params = test_utils::end_entity_params(vec![DNS_NAME.to_string()]);
// construct a certificate that uses `PrintableString` as the
// common name value, rather than `UTF8String`.
params.distinguished_name.push(
rcgen::DnType::CommonName,
rcgen::DnValue::PrintableString(
rcgen::string::PrintableString::try_from("example.com").unwrap(),
),
);
params
.signed_by(
&rcgen::KeyPair::generate_for(RCGEN_SIGNATURE_ALG).unwrap(),
&issuer,
)
.expect("failed to make ee cert (this is a test bug)")
};
expect_dns_name(ee_cert.der(), DNS_NAME);
}
// This test reproduces https://github.com/rustls/webpki/issues/167 --- an
// end-entity cert where the common name is an empty SEQUENCE.
#[test]
fn empty_sequence_common_name() {
let ee_cert_der = {
// handcrafted cert DER produced using `ascii2der`, since `rcgen` is
// unwilling to generate this particular weird cert.
let bytes = include_bytes!("../tests/misc/empty_sequence_common_name.der");
CertificateDer::from(&bytes[..])
};
expect_dns_name(&ee_cert_der, "example.com");
}
fn expect_dns_name(der: &CertificateDer<'_>, name: &str) {
let cert =
EndEntityCert::try_from(der).expect("should parse end entity certificate correctly");
let mut names = cert.valid_dns_names();
assert_eq!(names.next(), Some(name));
assert_eq!(names.next(), None);
}
}

460
vendor/rustls-webpki/src/error.rs vendored Normal file
View File

@@ -0,0 +1,460 @@
// Copyright 2015 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#[cfg(feature = "alloc")]
use alloc::string::String;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::fmt;
use core::ops::ControlFlow;
use pki_types::UnixTime;
#[cfg(feature = "alloc")]
use pki_types::{AlgorithmIdentifier, ServerName};
use crate::verify_cert::RequiredEkuNotFoundContext;
/// An error that occurs during certificate validation or name validation.
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
/// The encoding of some ASN.1 DER-encoded item is invalid.
BadDer,
/// The encoding of an ASN.1 DER-encoded time is invalid.
BadDerTime,
/// A CA certificate is being used as an end-entity certificate.
CaUsedAsEndEntity,
/// The certificate is expired; i.e. the time it is being validated for is
/// later than the certificate's notAfter time.
CertExpired {
/// The validation time.
time: UnixTime,
/// The notAfter time of the certificate.
not_after: UnixTime,
},
/// The certificate is not valid for the name it is being validated for.
CertNotValidForName(InvalidNameContext),
/// The certificate is not valid yet; i.e. the time it is being validated
/// for is earlier than the certificate's notBefore time.
CertNotValidYet {
/// The validation time.
time: UnixTime,
/// The notBefore time of the certificate.
not_before: UnixTime,
},
/// The certificate, or one of its issuers, has been revoked.
CertRevoked,
/// The CRL is expired; i.e. the verification time is not before the time
/// in the CRL nextUpdate field.
CrlExpired {
/// The validation time.
time: UnixTime,
/// The nextUpdate time of the CRL.
next_update: UnixTime,
},
/// The certificate has an Extended Key Usage extension without any EKU values.
EmptyEkuExtension,
/// An end-entity certificate is being used as a CA certificate.
EndEntityUsedAsCa,
/// An X.509 extension is invalid.
ExtensionValueInvalid,
/// The certificate validity period (notBefore, notAfter) is invalid; e.g.
/// the notAfter time is earlier than the notBefore time.
InvalidCertValidity,
/// A CRL number extension was invalid:
/// - it was mis-encoded
/// - it was negative
/// - it was too long
InvalidCrlNumber,
/// A iPAddress name constraint was invalid:
/// - it had a sparse network mask (ie, cannot be written in CIDR form).
/// - it was too long or short
InvalidNetworkMaskConstraint,
/// A serial number was invalid:
/// - it was misencoded
/// - it was negative
/// - it was too long
InvalidSerialNumber,
/// The CRL signature is invalid for the issuer's public key.
InvalidCrlSignatureForPublicKey,
/// The signature is invalid for the given public key.
InvalidSignatureForPublicKey,
/// A CRL was signed by an issuer that has a KeyUsage bitstring that does not include
/// the cRLSign key usage bit.
IssuerNotCrlSigner,
/// A presented or reference DNS identifier was malformed, potentially
/// containing invalid characters or invalid labels.
MalformedDnsIdentifier,
/// The certificate extensions are malformed.
///
/// In particular, webpki requires the DNS name(s) be in the subjectAltName
/// extension as required by the CA/Browser Forum Baseline Requirements
/// and as recommended by RFC6125.
MalformedExtensions,
/// A name constraint was malformed, potentially containing invalid characters or
/// invalid labels.
MalformedNameConstraint,
/// The maximum number of name constraint comparisons has been reached.
MaximumNameConstraintComparisonsExceeded,
/// The maximum number of internal path building calls has been reached. Path complexity is too great.
MaximumPathBuildCallsExceeded,
/// The path search was terminated because it became too deep.
MaximumPathDepthExceeded,
/// The maximum number of signature checks has been reached. Path complexity is too great.
MaximumSignatureChecksExceeded,
/// The certificate violates one or more name constraints.
NameConstraintViolation,
/// The certificate violates one or more path length constraints.
PathLenConstraintViolated,
/// The certificate is not valid for the Extended Key Usage for which it is
/// being validated.
#[deprecated(since = "0.103.2", note = "use RequiredEkuNotFoundContext instead")]
RequiredEkuNotFound,
/// The certificate is not valid for the Extended Key Usage for which it is
/// being validated.
RequiredEkuNotFoundContext(RequiredEkuNotFoundContext),
/// The algorithm in the TBSCertificate "signature" field of a certificate
/// does not match the algorithm in the signature of the certificate.
SignatureAlgorithmMismatch,
/// Trailing data was found while parsing DER-encoded input for the named type.
TrailingData(DerTypeId),
/// A valid issuer for the certificate could not be found.
UnknownIssuer,
/// The certificate's revocation status could not be determined.
UnknownRevocationStatus,
/// The certificate is not a v3 X.509 certificate.
///
/// This error may be also reported if the certificate version field
/// is malformed.
UnsupportedCertVersion,
/// The certificate contains an unsupported critical extension.
UnsupportedCriticalExtension,
/// The CRL contains an issuing distribution point with no distribution point name,
/// or a distribution point name relative to an issuer.
UnsupportedCrlIssuingDistributionPoint,
/// The CRL is not a v2 X.509 CRL.
///
/// The RFC 5280 web PKI profile mandates only version 2 be used. See section
/// 5.1.2.1 for more information.
///
/// This error may also be reported if the CRL version field is malformed.
UnsupportedCrlVersion,
/// The CRL is an unsupported "delta" CRL.
UnsupportedDeltaCrl,
/// The CRL contains unsupported "indirect" entries.
UnsupportedIndirectCrl,
/// The `ServerName` contained an unsupported type of value.
UnsupportedNameType,
/// The revocation reason is not in the set of supported revocation reasons.
UnsupportedRevocationReason,
/// The CRL is partitioned by revocation reasons.
UnsupportedRevocationReasonsPartitioning,
/// The signature algorithm for a signature over a CRL is not in the set of supported
/// signature algorithms given.
#[deprecated(
since = "0.103.4",
note = "use UnsupportedCrlSignatureAlgorithmContext instead"
)]
UnsupportedCrlSignatureAlgorithm,
/// The signature algorithm for a signature is not in the set of supported
/// signature algorithms given.
UnsupportedCrlSignatureAlgorithmContext(UnsupportedSignatureAlgorithmContext),
/// The signature algorithm for a signature is not in the set of supported
/// signature algorithms given.
#[deprecated(
since = "0.103.4",
note = "use UnsupportedSignatureAlgorithmContext instead"
)]
UnsupportedSignatureAlgorithm,
/// The signature algorithm for a signature is not in the set of supported
/// signature algorithms given.
UnsupportedSignatureAlgorithmContext(UnsupportedSignatureAlgorithmContext),
/// The CRL signature's algorithm does not match the algorithm of the issuer
/// public key it is being validated for. This may be because the public key
/// algorithm's OID isn't recognized (e.g. DSA), or the public key
/// algorithm's parameters don't match the supported parameters for that
/// algorithm (e.g. ECC keys for unsupported curves), or the public key
/// algorithm and the signature algorithm simply don't match (e.g.
/// verifying an RSA signature with an ECC public key).
#[deprecated(
since = "0.103.4",
note = "use UnsupportedCrlSignatureAlgorithmForPublicKeyContext instead"
)]
UnsupportedCrlSignatureAlgorithmForPublicKey,
/// The signature's algorithm does not match the algorithm of the public
/// key it is being validated for. This may be because the public key
/// algorithm's OID isn't recognized (e.g. DSA), or the public key
/// algorithm's parameters don't match the supported parameters for that
/// algorithm (e.g. ECC keys for unsupported curves), or the public key
/// algorithm and the signature algorithm simply don't match (e.g.
/// verifying an RSA signature with an ECC public key).
UnsupportedCrlSignatureAlgorithmForPublicKeyContext(
UnsupportedSignatureAlgorithmForPublicKeyContext,
),
/// The signature's algorithm does not match the algorithm of the public
/// key it is being validated for. This may be because the public key
/// algorithm's OID isn't recognized (e.g. DSA), or the public key
/// algorithm's parameters don't match the supported parameters for that
/// algorithm (e.g. ECC keys for unsupported curves), or the public key
/// algorithm and the signature algorithm simply don't match (e.g.
/// verifying an RSA signature with an ECC public key).
#[deprecated(
since = "0.103.4",
note = "use UnsupportedSignatureAlgorithmForPublicKeyContext instead"
)]
UnsupportedSignatureAlgorithmForPublicKey,
/// The signature's algorithm does not match the algorithm of the public
/// key it is being validated for. This may be because the public key
/// algorithm's OID isn't recognized (e.g. DSA), or the public key
/// algorithm's parameters don't match the supported parameters for that
/// algorithm (e.g. ECC keys for unsupported curves), or the public key
/// algorithm and the signature algorithm simply don't match (e.g.
/// verifying an RSA signature with an ECC public key).
UnsupportedSignatureAlgorithmForPublicKeyContext(
UnsupportedSignatureAlgorithmForPublicKeyContext,
),
}
impl Error {
// Compare the Error with the new error by rank, returning the higher rank of the two as
// the most specific error.
pub(crate) fn most_specific(self, new: Self) -> Self {
// Assign an error a numeric value ranking it by specificity.
if self.rank() >= new.rank() { self } else { new }
}
// Return a numeric indication of how specific the error is, where an error with a higher rank
// is considered more useful to an end user than an error with a lower rank. This is used by
// Error::most_specific to compare two errors in order to return which is more specific.
#[allow(clippy::as_conversions)] // We won't exceed u32 errors.
pub(crate) fn rank(&self) -> u32 {
match &self {
// Errors related to certificate validity
Self::CertNotValidYet { .. } | Self::CertExpired { .. } => 290,
Self::CertNotValidForName(_) => 280,
Self::CertRevoked | Self::UnknownRevocationStatus | Self::CrlExpired { .. } => 270,
Self::InvalidCrlSignatureForPublicKey | Self::InvalidSignatureForPublicKey => 260,
Self::SignatureAlgorithmMismatch => 250,
Self::EmptyEkuExtension => 245,
#[allow(deprecated)]
Self::RequiredEkuNotFound | Self::RequiredEkuNotFoundContext(_) => 240,
Self::NameConstraintViolation => 230,
Self::PathLenConstraintViolated => 220,
Self::CaUsedAsEndEntity | Self::EndEntityUsedAsCa => 210,
Self::IssuerNotCrlSigner => 200,
// Errors related to supported features used in an invalid way.
Self::InvalidCertValidity => 190,
Self::InvalidNetworkMaskConstraint => 180,
Self::InvalidSerialNumber => 170,
Self::InvalidCrlNumber => 160,
// Errors related to unsupported features.
#[allow(deprecated)]
Self::UnsupportedCrlSignatureAlgorithmForPublicKey
| Self::UnsupportedCrlSignatureAlgorithmForPublicKeyContext(_)
| Self::UnsupportedSignatureAlgorithmForPublicKey
| Self::UnsupportedSignatureAlgorithmForPublicKeyContext(_) => 150,
#[allow(deprecated)]
Self::UnsupportedCrlSignatureAlgorithm
| Self::UnsupportedCrlSignatureAlgorithmContext(_)
| Self::UnsupportedSignatureAlgorithm
| Self::UnsupportedSignatureAlgorithmContext(_) => 140,
Self::UnsupportedCriticalExtension => 130,
Self::UnsupportedCertVersion => 130,
Self::UnsupportedCrlVersion => 120,
Self::UnsupportedDeltaCrl => 110,
Self::UnsupportedIndirectCrl => 100,
Self::UnsupportedNameType => 95,
Self::UnsupportedRevocationReason => 90,
Self::UnsupportedRevocationReasonsPartitioning => 80,
Self::UnsupportedCrlIssuingDistributionPoint => 70,
Self::MaximumPathDepthExceeded => 61,
// Errors related to malformed data.
Self::MalformedDnsIdentifier => 60,
Self::MalformedNameConstraint => 50,
Self::MalformedExtensions | Self::TrailingData(_) => 40,
Self::ExtensionValueInvalid => 30,
// Generic DER errors.
Self::BadDerTime => 20,
Self::BadDer => 10,
// Special case errors - not subject to ranking.
Self::MaximumSignatureChecksExceeded => 0,
Self::MaximumPathBuildCallsExceeded => 0,
Self::MaximumNameConstraintComparisonsExceeded => 0,
// Default catch all error - should be renamed in the future.
Self::UnknownIssuer => 0,
}
}
/// Returns true for errors that should be considered fatal during path building. Errors of
/// this class should halt any further path building and be returned immediately.
#[inline]
pub(crate) fn is_fatal(&self) -> bool {
matches!(
self,
Self::MaximumSignatureChecksExceeded
| Self::MaximumPathBuildCallsExceeded
| Self::MaximumNameConstraintComparisonsExceeded
)
}
}
impl From<Error> for ControlFlow<Error, Error> {
fn from(value: Error) -> Self {
match value {
// If an error is fatal, we've exhausted the potential for continued search.
err if err.is_fatal() => Self::Break(err),
// Otherwise we've rejected one candidate chain, but may continue to search for others.
err => Self::Continue(err),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[cfg(feature = "std")]
impl ::std::error::Error for Error {}
/// Additional context for the `CertNotValidForName` error variant.
///
/// The contents of this type depend on whether the `alloc` feature is enabled.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InvalidNameContext {
/// Expected server name.
#[cfg(feature = "alloc")]
pub expected: ServerName<'static>,
/// The names presented in the end entity certificate.
///
/// These are the subject names as present in the leaf certificate and may contain DNS names
/// with or without a wildcard label as well as IP address names.
#[cfg(feature = "alloc")]
pub presented: Vec<String>,
}
/// Additional context for the `UnsupportedSignatureAlgorithmForPublicKey` error variant.
///
/// The contents of this type depend on whether the `alloc` feature is enabled.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnsupportedSignatureAlgorithmForPublicKeyContext {
/// The signature algorithm OID.
#[cfg(feature = "alloc")]
pub signature_algorithm_id: Vec<u8>,
/// The public key algorithm OID.
#[cfg(feature = "alloc")]
pub public_key_algorithm_id: Vec<u8>,
}
/// Additional context for the `UnsupportedSignatureAlgorithm` error variant.
///
/// The contents of this type depend on whether the `alloc` feature is enabled.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnsupportedSignatureAlgorithmContext {
/// The signature algorithm OID that was unsupported.
#[cfg(feature = "alloc")]
pub signature_algorithm_id: Vec<u8>,
/// Supported algorithms that were available for signature verification.
#[cfg(feature = "alloc")]
pub supported_algorithms: Vec<AlgorithmIdentifier>,
}
/// Trailing data was found while parsing DER-encoded input for the named type.
#[allow(missing_docs)]
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DerTypeId {
BitString,
Bool,
Certificate,
CertificateExtensions,
CertificateTbsCertificate,
CertRevocationList,
CertRevocationListExtension,
CrlDistributionPoint,
CommonNameInner,
CommonNameOuter,
DistributionPointName,
Extension,
GeneralName,
RevocationReason,
Signature,
SignatureAlgorithm,
SignedData,
SubjectPublicKeyInfo,
Time,
TrustAnchorV1,
TrustAnchorV1TbsCertificate,
U8,
RevokedCertificate,
RevokedCertificateExtension,
RevokedCertEntry,
IssuingDistributionPoint,
}

221
vendor/rustls-webpki/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,221 @@
// Copyright 2015 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//! webpki: Web PKI X.509 Certificate Validation.
//!
//! See `EndEntityCert`'s documentation for a description of the certificate
//! processing steps necessary for a TLS connection.
//!
//! # Features
//!
//! | Feature | Description |
//! | ------- | ----------- |
//! | `alloc` | Enable features that require use of the heap. Currently all RSA signature algorithms require this feature. |
//! | `std` | Enable features that require libstd. Implies `alloc`. |
//! | `ring` | Enable use of the *ring* crate for cryptography. |
//! | `aws-lc-rs` | Enable use of the aws-lc-rs crate for cryptography. Previously this feature was named `aws_lc_rs`. |
#![no_std]
#![warn(
elided_lifetimes_in_paths,
unnameable_types,
unreachable_pub,
clippy::use_self
)]
#![deny(missing_docs, clippy::as_conversions)]
#![allow(
clippy::len_without_is_empty,
clippy::manual_let_else,
clippy::new_without_default,
clippy::single_match,
clippy::single_match_else,
clippy::type_complexity,
clippy::upper_case_acronyms
)]
// Enable documentation for all features on docs.rs
#![cfg_attr(webpki_docsrs, feature(doc_cfg))]
#[cfg(any(feature = "std", test))]
extern crate std;
#[cfg(any(test, feature = "alloc"))]
#[cfg_attr(test, macro_use)]
extern crate alloc;
#[macro_use]
mod der;
#[cfg(feature = "aws-lc-rs")]
mod aws_lc_rs_algs;
mod cert;
mod end_entity;
mod error;
#[cfg(feature = "ring")]
mod ring_algs;
mod rpk_entity;
mod signed_data;
mod subject_name;
mod time;
mod trust_anchor;
mod crl;
mod verify_cert;
mod x509;
#[cfg(test)]
pub(crate) mod test_utils;
pub use {
cert::Cert,
crl::{
BorrowedCertRevocationList, BorrowedRevokedCert, CertRevocationList, CrlsRequired,
ExpirationPolicy, RevocationCheckDepth, RevocationOptions, RevocationOptionsBuilder,
RevocationReason, UnknownStatusPolicy,
},
der::DerIterator,
end_entity::EndEntityCert,
error::{
DerTypeId, Error, InvalidNameContext, UnsupportedSignatureAlgorithmContext,
UnsupportedSignatureAlgorithmForPublicKeyContext,
},
rpk_entity::RawPublicKeyEntity,
trust_anchor::anchor_from_trusted_cert,
verify_cert::{
ExtendedKeyUsageValidator, IntermediateIterator, KeyPurposeId, KeyPurposeIdIter, KeyUsage,
RequiredEkuNotFoundContext, VerifiedPath,
},
};
#[cfg(feature = "alloc")]
pub use crl::{OwnedCertRevocationList, OwnedRevokedCert};
#[cfg(feature = "ring")]
/// Signature verification algorithm implementations using the *ring* crypto library.
pub mod ring {
pub use super::ring_algs::{
ECDSA_P256_SHA256, ECDSA_P256_SHA384, ECDSA_P384_SHA256, ECDSA_P384_SHA384, ED25519,
};
#[cfg(feature = "alloc")]
pub use super::ring_algs::{
RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS,
RSA_PKCS1_2048_8192_SHA384, RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS,
RSA_PKCS1_2048_8192_SHA512, RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS,
RSA_PKCS1_3072_8192_SHA384, RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
RSA_PSS_2048_8192_SHA384_LEGACY_KEY, RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
};
}
#[cfg(feature = "aws-lc-rs")]
/// Signature verification algorithm implementations using the aws-lc-rs crypto library.
pub mod aws_lc_rs {
pub use super::aws_lc_rs_algs::{
ECDSA_P256_SHA256, ECDSA_P256_SHA384, ECDSA_P256_SHA512, ECDSA_P384_SHA256,
ECDSA_P384_SHA384, ECDSA_P384_SHA512, ECDSA_P521_SHA256, ECDSA_P521_SHA384,
ECDSA_P521_SHA512, ED25519, RSA_PKCS1_2048_8192_SHA256,
RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS, RSA_PKCS1_2048_8192_SHA384,
RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS, RSA_PKCS1_2048_8192_SHA512,
RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS, RSA_PKCS1_3072_8192_SHA384,
RSA_PSS_2048_8192_SHA256_LEGACY_KEY, RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
};
#[cfg(all(feature = "aws-lc-rs-unstable", not(feature = "aws-lc-rs-fips")))]
pub use super::aws_lc_rs_algs::{ML_DSA_44, ML_DSA_65, ML_DSA_87};
}
/// An array of all the verification algorithms exported by this crate.
///
/// This will be empty if the crate is built without the `ring` and `aws-lc-rs` features.
pub static ALL_VERIFICATION_ALGS: &[&dyn pki_types::SignatureVerificationAlgorithm] = &[
#[cfg(feature = "ring")]
ring::ECDSA_P256_SHA256,
#[cfg(feature = "ring")]
ring::ECDSA_P256_SHA384,
#[cfg(feature = "ring")]
ring::ECDSA_P384_SHA256,
#[cfg(feature = "ring")]
ring::ECDSA_P384_SHA384,
#[cfg(feature = "ring")]
ring::ED25519,
#[cfg(all(feature = "ring", feature = "alloc"))]
ring::RSA_PKCS1_2048_8192_SHA256,
#[cfg(all(feature = "ring", feature = "alloc"))]
ring::RSA_PKCS1_2048_8192_SHA384,
#[cfg(all(feature = "ring", feature = "alloc"))]
ring::RSA_PKCS1_2048_8192_SHA512,
#[cfg(all(feature = "ring", feature = "alloc"))]
ring::RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS,
#[cfg(all(feature = "ring", feature = "alloc"))]
ring::RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS,
#[cfg(all(feature = "ring", feature = "alloc"))]
ring::RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS,
#[cfg(all(feature = "ring", feature = "alloc"))]
ring::RSA_PKCS1_3072_8192_SHA384,
#[cfg(all(feature = "ring", feature = "alloc"))]
ring::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
#[cfg(all(feature = "ring", feature = "alloc"))]
ring::RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
#[cfg(all(feature = "ring", feature = "alloc"))]
ring::RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::ECDSA_P256_SHA256,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::ECDSA_P256_SHA384,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::ECDSA_P256_SHA512,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::ECDSA_P384_SHA256,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::ECDSA_P384_SHA384,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::ECDSA_P384_SHA512,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::ECDSA_P521_SHA256,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::ECDSA_P521_SHA384,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::ECDSA_P521_SHA512,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::ED25519,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::RSA_PKCS1_2048_8192_SHA256,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::RSA_PKCS1_2048_8192_SHA384,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::RSA_PKCS1_2048_8192_SHA512,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::RSA_PKCS1_3072_8192_SHA384,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
#[cfg(feature = "aws-lc-rs")]
aws_lc_rs::RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
#[cfg(all(feature = "aws-lc-rs-unstable", not(feature = "aws-lc-rs-fips")))]
aws_lc_rs::ML_DSA_44,
#[cfg(all(feature = "aws-lc-rs-unstable", not(feature = "aws-lc-rs-fips")))]
aws_lc_rs::ML_DSA_65,
#[cfg(all(feature = "aws-lc-rs-unstable", not(feature = "aws-lc-rs-fips")))]
aws_lc_rs::ML_DSA_87,
];
fn public_values_eq(a: untrusted::Input<'_>, b: untrusted::Input<'_>) -> bool {
a.as_slice_less_safe() == b.as_slice_less_safe()
}

303
vendor/rustls-webpki/src/ring_algs.rs vendored Normal file
View File

@@ -0,0 +1,303 @@
// Copyright 2015 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
use pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm, alg_id};
use ring::signature;
/// A `SignatureVerificationAlgorithm` implemented using *ring*.
#[derive(Debug)]
struct RingAlgorithm {
public_key_alg_id: AlgorithmIdentifier,
signature_alg_id: AlgorithmIdentifier,
verification_alg: &'static dyn signature::VerificationAlgorithm,
}
impl SignatureVerificationAlgorithm for RingAlgorithm {
fn public_key_alg_id(&self) -> AlgorithmIdentifier {
self.public_key_alg_id
}
fn signature_alg_id(&self) -> AlgorithmIdentifier {
self.signature_alg_id
}
fn verify_signature(
&self,
public_key: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), InvalidSignature> {
signature::UnparsedPublicKey::new(self.verification_alg, public_key)
.verify(message, signature)
.map_err(|_| InvalidSignature)
}
}
/// ECDSA signatures using the P-256 curve and SHA-256.
pub static ECDSA_P256_SHA256: &dyn SignatureVerificationAlgorithm = &RingAlgorithm {
public_key_alg_id: alg_id::ECDSA_P256,
signature_alg_id: alg_id::ECDSA_SHA256,
verification_alg: &signature::ECDSA_P256_SHA256_ASN1,
};
/// ECDSA signatures using the P-256 curve and SHA-384. Deprecated.
pub static ECDSA_P256_SHA384: &dyn SignatureVerificationAlgorithm = &RingAlgorithm {
public_key_alg_id: alg_id::ECDSA_P256,
signature_alg_id: alg_id::ECDSA_SHA384,
verification_alg: &signature::ECDSA_P256_SHA384_ASN1,
};
/// ECDSA signatures using the P-384 curve and SHA-256. Deprecated.
pub static ECDSA_P384_SHA256: &dyn SignatureVerificationAlgorithm = &RingAlgorithm {
public_key_alg_id: alg_id::ECDSA_P384,
signature_alg_id: alg_id::ECDSA_SHA256,
verification_alg: &signature::ECDSA_P384_SHA256_ASN1,
};
/// ECDSA signatures using the P-384 curve and SHA-384.
pub static ECDSA_P384_SHA384: &dyn SignatureVerificationAlgorithm = &RingAlgorithm {
public_key_alg_id: alg_id::ECDSA_P384,
signature_alg_id: alg_id::ECDSA_SHA384,
verification_alg: &signature::ECDSA_P384_SHA384_ASN1,
};
/// RSA PKCS#1 1.5 signatures using SHA-256 for keys of 2048-8192 bits.
#[cfg(feature = "alloc")]
pub static RSA_PKCS1_2048_8192_SHA256: &dyn SignatureVerificationAlgorithm = &RingAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PKCS1_SHA256,
verification_alg: &signature::RSA_PKCS1_2048_8192_SHA256,
};
/// RSA PKCS#1 1.5 signatures using SHA-384 for keys of 2048-8192 bits.
#[cfg(feature = "alloc")]
pub static RSA_PKCS1_2048_8192_SHA384: &dyn SignatureVerificationAlgorithm = &RingAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PKCS1_SHA384,
verification_alg: &signature::RSA_PKCS1_2048_8192_SHA384,
};
/// RSA PKCS#1 1.5 signatures using SHA-512 for keys of 2048-8192 bits.
#[cfg(feature = "alloc")]
pub static RSA_PKCS1_2048_8192_SHA512: &dyn SignatureVerificationAlgorithm = &RingAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PKCS1_SHA512,
verification_alg: &signature::RSA_PKCS1_2048_8192_SHA512,
};
/// RSA PKCS#1 1.5 signatures using SHA-256 for keys of 2048-8192 bits,
/// with illegally absent AlgorithmIdentifier parameters.
///
/// RFC4055 says on sha256WithRSAEncryption and company:
///
/// > When any of these four object identifiers appears within an
/// > AlgorithmIdentifier, the parameters MUST be NULL. Implementations
/// > MUST accept the parameters being absent as well as present.
///
/// This algorithm covers the absent case, [`RSA_PKCS1_2048_8192_SHA256`] covers
/// the present case.
#[cfg(feature = "alloc")]
pub static RSA_PKCS1_2048_8192_SHA256_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm =
&RingAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::AlgorithmIdentifier::from_slice(include_bytes!(
"data/alg-rsa-pkcs1-sha256-absent-params.der"
)),
verification_alg: &signature::RSA_PKCS1_2048_8192_SHA256,
};
/// RSA PKCS#1 1.5 signatures using SHA-384 for keys of 2048-8192 bits,
/// with illegally absent AlgorithmIdentifier parameters.
///
/// RFC4055 says on sha256WithRSAEncryption and company:
///
/// > When any of these four object identifiers appears within an
/// > AlgorithmIdentifier, the parameters MUST be NULL. Implementations
/// > MUST accept the parameters being absent as well as present.
///
/// This algorithm covers the absent case, [`RSA_PKCS1_2048_8192_SHA384`] covers
/// the present case.
#[cfg(feature = "alloc")]
pub static RSA_PKCS1_2048_8192_SHA384_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm =
&RingAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::AlgorithmIdentifier::from_slice(include_bytes!(
"data/alg-rsa-pkcs1-sha384-absent-params.der"
)),
verification_alg: &signature::RSA_PKCS1_2048_8192_SHA384,
};
/// RSA PKCS#1 1.5 signatures using SHA-512 for keys of 2048-8192 bits,
/// with illegally absent AlgorithmIdentifier parameters.
///
/// RFC4055 says on sha256WithRSAEncryption and company:
///
/// > When any of these four object identifiers appears within an
/// > AlgorithmIdentifier, the parameters MUST be NULL. Implementations
/// > MUST accept the parameters being absent as well as present.
///
/// This algorithm covers the absent case, [`RSA_PKCS1_2048_8192_SHA512`] covers
/// the present case.
#[cfg(feature = "alloc")]
pub static RSA_PKCS1_2048_8192_SHA512_ABSENT_PARAMS: &dyn SignatureVerificationAlgorithm =
&RingAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::AlgorithmIdentifier::from_slice(include_bytes!(
"data/alg-rsa-pkcs1-sha512-absent-params.der"
)),
verification_alg: &signature::RSA_PKCS1_2048_8192_SHA512,
};
/// RSA PKCS#1 1.5 signatures using SHA-384 for keys of 3072-8192 bits.
#[cfg(feature = "alloc")]
pub static RSA_PKCS1_3072_8192_SHA384: &dyn SignatureVerificationAlgorithm = &RingAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PKCS1_SHA384,
verification_alg: &signature::RSA_PKCS1_3072_8192_SHA384,
};
/// RSA PSS signatures using SHA-256 for keys of 2048-8192 bits and of
/// type rsaEncryption; see [RFC 4055 Section 1.2].
///
/// [RFC 4055 Section 1.2]: https://tools.ietf.org/html/rfc4055#section-1.2
#[cfg(feature = "alloc")]
pub static RSA_PSS_2048_8192_SHA256_LEGACY_KEY: &dyn SignatureVerificationAlgorithm =
&RingAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PSS_SHA256,
verification_alg: &signature::RSA_PSS_2048_8192_SHA256,
};
/// RSA PSS signatures using SHA-384 for keys of 2048-8192 bits and of
/// type rsaEncryption; see [RFC 4055 Section 1.2].
///
/// [RFC 4055 Section 1.2]: https://tools.ietf.org/html/rfc4055#section-1.2
#[cfg(feature = "alloc")]
pub static RSA_PSS_2048_8192_SHA384_LEGACY_KEY: &dyn SignatureVerificationAlgorithm =
&RingAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PSS_SHA384,
verification_alg: &signature::RSA_PSS_2048_8192_SHA384,
};
/// RSA PSS signatures using SHA-512 for keys of 2048-8192 bits and of
/// type rsaEncryption; see [RFC 4055 Section 1.2].
///
/// [RFC 4055 Section 1.2]: https://tools.ietf.org/html/rfc4055#section-1.2
#[cfg(feature = "alloc")]
pub static RSA_PSS_2048_8192_SHA512_LEGACY_KEY: &dyn SignatureVerificationAlgorithm =
&RingAlgorithm {
public_key_alg_id: alg_id::RSA_ENCRYPTION,
signature_alg_id: alg_id::RSA_PSS_SHA512,
verification_alg: &signature::RSA_PSS_2048_8192_SHA512,
};
/// ED25519 signatures according to RFC 8410
pub static ED25519: &dyn SignatureVerificationAlgorithm = &RingAlgorithm {
public_key_alg_id: alg_id::ED25519,
signature_alg_id: alg_id::ED25519,
verification_alg: &signature::ED25519,
};
#[cfg(test)]
#[path = "."]
mod tests {
#[cfg(feature = "alloc")]
use crate::error::UnsupportedSignatureAlgorithmForPublicKeyContext;
use crate::error::{Error, UnsupportedSignatureAlgorithmContext};
static SUPPORTED_ALGORITHMS_IN_TESTS: &[&dyn super::SignatureVerificationAlgorithm] = &[
// Reasonable algorithms.
super::ECDSA_P256_SHA256,
super::ECDSA_P384_SHA384,
super::ED25519,
#[cfg(feature = "alloc")]
super::RSA_PKCS1_2048_8192_SHA256,
#[cfg(feature = "alloc")]
super::RSA_PKCS1_2048_8192_SHA384,
#[cfg(feature = "alloc")]
super::RSA_PKCS1_2048_8192_SHA512,
#[cfg(feature = "alloc")]
super::RSA_PKCS1_3072_8192_SHA384,
#[cfg(feature = "alloc")]
super::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
#[cfg(feature = "alloc")]
super::RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
#[cfg(feature = "alloc")]
super::RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
// Algorithms deprecated because they are nonsensical combinations.
super::ECDSA_P256_SHA384, // Truncates digest.
super::ECDSA_P384_SHA256, // Digest is unnecessarily short.
];
const OK_IF_POINT_COMPRESSION_SUPPORTED: Result<(), Error> =
Err(Error::InvalidSignatureForPublicKey);
#[path = "alg_tests.rs"]
mod alg_tests;
fn maybe_rsa() -> Result<(), Error> {
#[cfg(feature = "alloc")]
{
Ok(())
}
#[cfg(not(feature = "alloc"))]
{
Err(unsupported(&[]))
}
}
fn unsupported_for_rsa(sig_alg_id: &[u8], _public_key_alg_id: &[u8]) -> Error {
#[cfg(feature = "alloc")]
{
Error::UnsupportedSignatureAlgorithmForPublicKeyContext(
UnsupportedSignatureAlgorithmForPublicKeyContext {
signature_algorithm_id: sig_alg_id.to_vec(),
public_key_algorithm_id: _public_key_alg_id.to_vec(),
},
)
}
#[cfg(not(feature = "alloc"))]
{
unsupported(sig_alg_id)
}
}
fn invalid_rsa_signature() -> Error {
#[cfg(feature = "alloc")]
{
Error::InvalidSignatureForPublicKey
}
#[cfg(not(feature = "alloc"))]
{
unsupported(&[])
}
}
fn unsupported_for_ecdsa(sig_alg_id: &[u8], _public_key_alg_id: &[u8]) -> Error {
unsupported(sig_alg_id)
}
fn unsupported(_sig_alg_id: &[u8]) -> Error {
Error::UnsupportedSignatureAlgorithmContext(UnsupportedSignatureAlgorithmContext {
#[cfg(feature = "alloc")]
signature_algorithm_id: _sig_alg_id.to_vec(),
#[cfg(feature = "alloc")]
supported_algorithms: SUPPORTED_ALGORITHMS_IN_TESTS
.iter()
.map(|&alg| alg.signature_alg_id())
.collect(),
})
}
}

84
vendor/rustls-webpki/src/rpk_entity.rs vendored Normal file
View File

@@ -0,0 +1,84 @@
use crate::error::Error;
use crate::signed_data::SubjectPublicKeyInfo;
use crate::{DerTypeId, der, signed_data};
use pki_types::{SignatureVerificationAlgorithm, SubjectPublicKeyInfoDer};
/// A Raw Public Key, used for connections using raw public keys as specified
/// in [RFC 7250](https://www.rfc-editor.org/rfc/rfc7250).
#[derive(Debug)]
pub struct RawPublicKeyEntity<'a> {
inner: untrusted::Input<'a>,
}
impl<'a> TryFrom<&'a SubjectPublicKeyInfoDer<'a>> for RawPublicKeyEntity<'a> {
type Error = Error;
/// Parse the ASN.1 DER-encoded SPKI encoding of the raw public key `spki`.
/// Since we are parsing a raw public key, we first strip the outer sequence tag.
fn try_from(spki: &'a SubjectPublicKeyInfoDer<'a>) -> Result<Self, Self::Error> {
let input = untrusted::Input::from(spki.as_ref());
let spki = input.read_all(
Error::TrailingData(DerTypeId::SubjectPublicKeyInfo),
|reader| {
let untagged_spki = der::expect_tag(reader, der::Tag::Sequence)?;
der::read_all::<SubjectPublicKeyInfo<'_>>(untagged_spki)?;
Ok(untagged_spki)
},
)?;
Ok(Self { inner: spki })
}
}
impl RawPublicKeyEntity<'_> {
/// Verifies the signature `signature` of message `msg` using a raw public key,
/// supporting RFC 7250.
///
/// For more information on `signature_alg` and `signature` see the documentation for [`crate::end_entity::EndEntityCert::verify_signature`].
pub fn verify_signature(
&self,
signature_alg: &dyn SignatureVerificationAlgorithm,
msg: &[u8],
signature: &[u8],
) -> Result<(), Error> {
signed_data::verify_signature(
signature_alg,
self.inner,
untrusted::Input::from(msg),
untrusted::Input::from(signature),
)
}
}
#[cfg(feature = "alloc")]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ee_read_for_rpk() {
// Try to read an end entity certificate into a RawPublicKeyEntity.
// It will fail to parse the key value since we expect no unused bits.
let ee = include_bytes!("../tests/ed25519/ee.der");
let ee_der = SubjectPublicKeyInfoDer::from(ee.as_slice());
assert_eq!(
RawPublicKeyEntity::try_from(&ee_der).expect_err("unexpectedly parsed certificate"),
Error::TrailingData(DerTypeId::BitString)
);
}
#[test]
fn test_spki_read_for_rpk() {
let pubkey = include_bytes!("../tests/ed25519/ee-pubkey.der");
let spki_der = SubjectPublicKeyInfoDer::from(pubkey.as_slice());
let rpk = RawPublicKeyEntity::try_from(&spki_der).expect("failed to parse rpk");
// Retrieved the SPKI from the pubkey.der using the following commands (as in [`cert::test_spki_read`]):
// xxd -plain -cols 1 tests/ed255519/ee-pubkey.der
let expected_spki = [
0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, 0xfe, 0x5a, 0x1e, 0x36,
0x6c, 0x17, 0x27, 0x5b, 0xf1, 0x58, 0x1e, 0x3a, 0x0e, 0xe6, 0x56, 0x29, 0x8d, 0x9e,
0x1b, 0x3f, 0xd3, 0x3f, 0x96, 0x46, 0xef, 0xbf, 0x04, 0x6b, 0xc7, 0x3d, 0x47, 0x5c,
];
assert_eq!(expected_spki, rpk.inner.as_slice_less_safe())
}
}

269
vendor/rustls-webpki/src/signed_data.rs vendored Normal file
View File

@@ -0,0 +1,269 @@
// Copyright 2015 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
use crate::der::{self, FromDer};
use crate::error::{
DerTypeId, Error, UnsupportedSignatureAlgorithmContext,
UnsupportedSignatureAlgorithmForPublicKeyContext,
};
use crate::verify_cert::Budget;
use pki_types::SignatureVerificationAlgorithm;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
/// X.509 certificates and related items that are signed are almost always
/// encoded in the format "tbs||signatureAlgorithm||signature". This structure
/// captures this pattern as an owned data type.
#[cfg(feature = "alloc")]
#[derive(Clone, Debug)]
pub(crate) struct OwnedSignedData {
/// The signed data. This would be `tbsCertificate` in the case of an X.509
/// certificate, `tbsResponseData` in the case of an OCSP response, `tbsCertList`
/// in the case of a CRL, and the data nested in the `digitally-signed` construct for
/// TLS 1.2 signed data.
pub(crate) data: Vec<u8>,
/// The value of the `AlgorithmIdentifier`. This would be
/// `signatureAlgorithm` in the case of an X.509 certificate, OCSP
/// response or CRL. This would have to be synthesized in the case of TLS 1.2
/// signed data, since TLS does not identify algorithms by ASN.1 OIDs.
pub(crate) algorithm: Vec<u8>,
/// The value of the signature. This would be `signature` in an X.509
/// certificate, OCSP response or CRL. This would be the value of
/// `DigitallySigned.signature` for TLS 1.2 signed data.
pub(crate) signature: Vec<u8>,
}
#[cfg(feature = "alloc")]
impl OwnedSignedData {
/// Return a borrowed [`SignedData`] from the owned representation.
pub(crate) fn borrow(&self) -> SignedData<'_> {
SignedData {
data: untrusted::Input::from(&self.data),
algorithm: untrusted::Input::from(&self.algorithm),
signature: untrusted::Input::from(&self.signature),
}
}
}
/// X.509 certificates and related items that are signed are almost always
/// encoded in the format "tbs||signatureAlgorithm||signature". This structure
/// captures this pattern.
#[derive(Debug)]
pub(crate) struct SignedData<'a> {
/// The signed data. This would be `tbsCertificate` in the case of an X.509
/// certificate, `tbsResponseData` in the case of an OCSP response, `tbsCertList`
/// in the case of a CRL, and the data nested in the `digitally-signed` construct for
/// TLS 1.2 signed data.
pub(crate) data: untrusted::Input<'a>,
/// The value of the `AlgorithmIdentifier`. This would be
/// `signatureAlgorithm` in the case of an X.509 certificate, OCSP
/// response or CRL. This would have to be synthesized in the case of TLS 1.2
/// signed data, since TLS does not identify algorithms by ASN.1 OIDs.
pub(crate) algorithm: untrusted::Input<'a>,
/// The value of the signature. This would be `signature` in an X.509
/// certificate, OCSP response or CRL. This would be the value of
/// `DigitallySigned.signature` for TLS 1.2 signed data.
pub(crate) signature: untrusted::Input<'a>,
}
impl<'a> SignedData<'a> {
/// Parses the concatenation of "tbs||signatureAlgorithm||signature" that
/// is common in the X.509 certificate and OCSP response syntaxes.
///
/// X.509 Certificates (RFC 5280) look like this:
///
/// ```ASN.1
/// Certificate (SEQUENCE) {
/// tbsCertificate TBSCertificate,
/// signatureAlgorithm AlgorithmIdentifier,
/// signatureValue BIT STRING
/// }
/// ```
///
/// OCSP responses (RFC 6960) look like this:
/// ```ASN.1
/// BasicOCSPResponse {
/// tbsResponseData ResponseData,
/// signatureAlgorithm AlgorithmIdentifier,
/// signature BIT STRING,
/// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL
/// }
/// ```
///
/// Note that this function does NOT parse the outermost `SEQUENCE` or the
/// `certs` value.
///
/// The return value's first component is the contents of
/// `tbsCertificate`/`tbsResponseData`; the second component is a `SignedData`
/// structure that can be passed to `verify_signed_data`.
///
/// The provided size_limit will enforce the largest possible outermost `SEQUENCE` this
/// function will read.
pub(crate) fn from_der(
der: &mut untrusted::Reader<'a>,
size_limit: usize,
) -> Result<(untrusted::Input<'a>, Self), Error> {
let (data, tbs) = der.read_partial(|input| {
der::expect_tag_and_get_value_limited(input, der::Tag::Sequence, size_limit)
})?;
let algorithm = der::expect_tag(der, der::Tag::Sequence)?;
let signature = der::bit_string_with_no_unused_bits(der)?;
Ok((
tbs,
SignedData {
data,
algorithm,
signature,
},
))
}
/// Convert the borrowed signed data to an [`OwnedSignedData`].
#[cfg(feature = "alloc")]
pub(crate) fn to_owned(&self) -> OwnedSignedData {
OwnedSignedData {
data: self.data.as_slice_less_safe().to_vec(),
algorithm: self.algorithm.as_slice_less_safe().to_vec(),
signature: self.signature.as_slice_less_safe().to_vec(),
}
}
}
/// Verify `signed_data` using the public key in the DER-encoded
/// SubjectPublicKeyInfo `spki` using one of the algorithms in
/// `supported_algorithms`.
///
/// The algorithm is chosen based on the algorithm information encoded in the
/// algorithm identifiers in `public_key` and `signed_data.algorithm`. The
/// ordering of the algorithms in `supported_algorithms` does not really matter,
/// but generally more common algorithms should go first, as it is scanned
/// linearly for matches.
pub(crate) fn verify_signed_data(
supported_algorithms: &[&dyn SignatureVerificationAlgorithm],
spki_value: untrusted::Input<'_>,
signed_data: &SignedData<'_>,
budget: &mut Budget,
) -> Result<(), Error> {
budget.consume_signature()?;
// We need to verify the signature in `signed_data` using the public key
// in `public_key`. In order to know which *ring* signature verification
// algorithm to use, we need to know the public key algorithm (ECDSA,
// RSA PKCS#1, etc.), the curve (if applicable), and the digest algorithm.
// `signed_data` identifies only the public key algorithm and the digest
// algorithm, and `public_key` identifies only the public key algorithm and
// the curve (if any). Thus, we have to combine information from both
// inputs to figure out which `ring::signature::VerificationAlgorithm` to
// use to verify the signature.
//
// This is all further complicated by the fact that we don't have any
// implicit knowledge about any algorithms or identifiers, since all of
// that information is encoded in `supported_algorithms.` In particular, we
// avoid hard-coding any of that information so that (link-time) dead code
// elimination will work effectively in eliminating code for unused
// algorithms.
// Parse the signature.
//
let mut invalid_for_public_key = None;
for supported_alg in supported_algorithms
.iter()
.filter(|alg| alg.signature_alg_id().as_ref() == signed_data.algorithm.as_slice_less_safe())
{
match verify_signature(
*supported_alg,
spki_value,
signed_data.data,
signed_data.signature,
) {
Err(Error::UnsupportedSignatureAlgorithmForPublicKeyContext(cx)) => {
invalid_for_public_key = Some(cx);
continue;
}
result => return result,
}
}
if let Some(cx) = invalid_for_public_key {
return Err(Error::UnsupportedSignatureAlgorithmForPublicKeyContext(cx));
}
Err(Error::UnsupportedSignatureAlgorithmContext(
UnsupportedSignatureAlgorithmContext {
#[cfg(feature = "alloc")]
signature_algorithm_id: signed_data.algorithm.as_slice_less_safe().to_vec(),
#[cfg(feature = "alloc")]
supported_algorithms: supported_algorithms
.iter()
.map(|&alg| alg.signature_alg_id())
.collect(),
},
))
}
pub(crate) fn verify_signature(
signature_alg: &dyn SignatureVerificationAlgorithm,
spki_value: untrusted::Input<'_>,
msg: untrusted::Input<'_>,
signature: untrusted::Input<'_>,
) -> Result<(), Error> {
let spki = der::read_all::<SubjectPublicKeyInfo<'_>>(spki_value)?;
if signature_alg.public_key_alg_id().as_ref() != spki.algorithm_id_value.as_slice_less_safe() {
return Err(Error::UnsupportedSignatureAlgorithmForPublicKeyContext(
UnsupportedSignatureAlgorithmForPublicKeyContext {
#[cfg(feature = "alloc")]
signature_algorithm_id: signature_alg.signature_alg_id().as_ref().to_vec(),
#[cfg(feature = "alloc")]
public_key_algorithm_id: spki.algorithm_id_value.as_slice_less_safe().to_vec(),
},
));
}
signature_alg
.verify_signature(
spki.key_value.as_slice_less_safe(),
msg.as_slice_less_safe(),
signature.as_slice_less_safe(),
)
.map_err(|_| Error::InvalidSignatureForPublicKey)
}
pub(crate) struct SubjectPublicKeyInfo<'a> {
algorithm_id_value: untrusted::Input<'a>,
key_value: untrusted::Input<'a>,
}
impl<'a> FromDer<'a> for SubjectPublicKeyInfo<'a> {
// Parse the public key into an algorithm OID, an optional curve OID, and the
// key value. The caller needs to check whether these match the
// `PublicKeyAlgorithm` for the `SignatureVerificationAlgorithm` that is matched when
// parsing the signature.
fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
let algorithm_id_value = der::expect_tag(reader, der::Tag::Sequence)?;
let key_value = der::bit_string_with_no_unused_bits(reader)?;
Ok(SubjectPublicKeyInfo {
algorithm_id_value,
key_value,
})
}
const TYPE_ID: DerTypeId = DerTypeId::SubjectPublicKeyInfo;
}

View File

@@ -0,0 +1,979 @@
// Copyright 2015-2020 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#[cfg(feature = "alloc")]
use alloc::format;
use core::fmt::Write;
#[cfg(feature = "alloc")]
use pki_types::ServerName;
use pki_types::{DnsName, InvalidDnsNameError};
use super::{GeneralName, NameIterator};
use crate::cert::Cert;
use crate::error::{Error, InvalidNameContext};
pub(crate) fn verify_dns_names(reference: &DnsName<'_>, cert: &Cert<'_>) -> Result<(), Error> {
let dns_name = untrusted::Input::from(reference.as_ref().as_bytes());
let result = NameIterator::new(cert.subject_alt_name).find_map(|result| {
let name = match result {
Ok(name) => name,
Err(err) => return Some(Err(err)),
};
let presented_id = match name {
GeneralName::DnsName(presented) => presented,
_ => return None,
};
match presented_id_matches_reference_id(presented_id, IdRole::Reference, dns_name) {
Ok(true) => Some(Ok(())),
Ok(false) | Err(Error::MalformedDnsIdentifier) => None,
Err(e) => Some(Err(e)),
}
});
match result {
Some(result) => return result,
#[cfg(feature = "alloc")]
None => {}
#[cfg(not(feature = "alloc"))]
None => Err(Error::CertNotValidForName(InvalidNameContext {})),
}
// Try to yield a more useful error. To avoid allocating on the happy path,
// we reconstruct the same `NameIterator` and replay it.
#[cfg(feature = "alloc")]
{
Err(Error::CertNotValidForName(InvalidNameContext {
expected: ServerName::DnsName(reference.to_owned()),
presented: NameIterator::new(cert.subject_alt_name)
.filter_map(|result| Some(format!("{:?}", result.ok()?)))
.collect(),
}))
}
}
/// A reference to a DNS Name presented by a server that may include a wildcard.
///
/// A `WildcardDnsNameRef` is guaranteed to be syntactically valid. The validity rules
/// are specified in [RFC 5280 Section 7.2], except that underscores are also
/// allowed.
///
/// Additionally, while [RFC6125 Section 4.1] says that a wildcard label may be of the form
/// `<x>*<y>.<DNSID>`, where `<x>` and/or `<y>` may be empty, we follow a stricter policy common
/// to most validation libraries (e.g. NSS) and only accept wildcard labels that are exactly `*`.
///
/// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
/// [RFC 6125 Section 4.1]: https://www.rfc-editor.org/rfc/rfc6125#section-4.1
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
pub(crate) struct WildcardDnsNameRef<'a>(&'a [u8]);
impl<'a> WildcardDnsNameRef<'a> {
/// Constructs a `WildcardDnsNameRef` from the given input if the input is a
/// syntactically-valid DNS name.
pub(crate) fn try_from_ascii(dns_name: &'a [u8]) -> Result<Self, InvalidDnsNameError> {
if !is_valid_dns_id(
untrusted::Input::from(dns_name),
IdRole::Reference,
Wildcards::Allow,
) {
return Err(InvalidDnsNameError);
}
Ok(Self(dns_name))
}
/// Yields a reference to the DNS name as a `&str`.
pub(crate) fn as_str(&self) -> &'a str {
// The unwrap won't fail because a `WildcardDnsNameRef` is guaranteed to be ASCII and
// ASCII is a subset of UTF-8.
core::str::from_utf8(self.0).unwrap()
}
}
impl core::fmt::Debug for WildcardDnsNameRef<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
f.write_str("WildcardDnsNameRef(\"")?;
// Convert each byte of the underlying ASCII string to a `char` and
// downcase it prior to formatting it. We avoid self.to_owned() since
// it requires allocation.
for &ch in self.0 {
f.write_char(char::from(ch).to_ascii_lowercase())?;
}
f.write_str("\")")
}
}
// We assume that both presented_dns_id and reference_dns_id are encoded in
// such a way that US-ASCII (7-bit) characters are encoded in one byte and no
// encoding of a non-US-ASCII character contains a code point in the range
// 0-127. For example, UTF-8 is OK but UTF-16 is not.
//
// RFC6125 says that a wildcard label may be of the form <x>*<y>.<DNSID>, where
// <x> and/or <y> may be empty. However, NSS requires <y> to be empty, and we
// follow NSS's stricter policy by accepting wildcards only of the form
// <x>*.<DNSID>, where <x> may be empty.
//
// An relative presented DNS ID matches both an absolute reference ID and a
// relative reference ID. Absolute presented DNS IDs are not supported:
//
// Presented ID Reference ID Result
// -------------------------------------
// example.com example.com Match
// example.com. example.com Mismatch
// example.com example.com. Match
// example.com. example.com. Mismatch
//
// There are more subtleties documented inline in the code.
//
// Name constraints ///////////////////////////////////////////////////////////
//
// This is all RFC 5280 has to say about dNSName constraints:
//
// DNS name restrictions are expressed as host.example.com. Any DNS
// name that can be constructed by simply adding zero or more labels to
// the left-hand side of the name satisfies the name constraint. For
// example, www.host.example.com would satisfy the constraint but
// host1.example.com would not.
//
// This lack of specificity has lead to a lot of uncertainty regarding
// subdomain matching. In particular, the following questions have been
// raised and answered:
//
// Q: Does a presented identifier equal (case insensitive) to the name
// constraint match the constraint? For example, does the presented
// ID "host.example.com" match a "host.example.com" constraint?
// A: Yes. RFC5280 says "by simply adding zero or more labels" and this
// is the case of adding zero labels.
//
// Q: When the name constraint does not start with ".", do subdomain
// presented identifiers match it? For example, does the presented
// ID "www.host.example.com" match a "host.example.com" constraint?
// A: Yes. RFC5280 says "by simply adding zero or more labels" and this
// is the case of adding more than zero labels. The example is the
// one from RFC 5280.
//
// Q: When the name constraint does not start with ".", does a
// non-subdomain prefix match it? For example, does "bigfoo.bar.com"
// match "foo.bar.com"? [4]
// A: No. We interpret RFC 5280's language of "adding zero or more labels"
// to mean that whole labels must be prefixed.
//
// (Note that the above three scenarios are the same as the RFC 6265
// domain matching rules [0].)
//
// Q: Is a name constraint that starts with "." valid, and if so, what
// semantics does it have? For example, does a presented ID of
// "www.example.com" match a constraint of ".example.com"? Does a
// presented ID of "example.com" match a constraint of ".example.com"?
// A: This implementation, NSS[1], and SChannel[2] all support a
// leading ".", but OpenSSL[3] does not yet. Amongst the
// implementations that support it, a leading "." is legal and means
// the same thing as when the "." is omitted, EXCEPT that a
// presented identifier equal (case insensitive) to the name
// constraint is not matched; i.e. presented dNSName identifiers
// must be subdomains. Some CAs in Mozilla's CA program (e.g. HARICA)
// have name constraints with the leading "." in their root
// certificates. The name constraints imposed on DCISS by Mozilla also
// have the it, so supporting this is a requirement for backward
// compatibility, even if it is not yet standardized. So, for example, a
// presented ID of "www.example.com" matches a constraint of
// ".example.com" but a presented ID of "example.com" does not.
//
// Q: Is there a way to prevent subdomain matches?
// A: Yes.
//
// Some people have proposed that dNSName constraints that do not
// start with a "." should be restricted to exact (case insensitive)
// matches. However, such a change of semantics from what RFC5280
// specifies would be a non-backward-compatible change in the case of
// permittedSubtrees constraints, and it would be a security issue for
// excludedSubtrees constraints.
//
// However, it can be done with a combination of permittedSubtrees and
// excludedSubtrees, e.g. "example.com" in permittedSubtrees and
// ".example.com" in excludedSubtrees.
//
// Q: Are name constraints allowed to be specified as absolute names?
// For example, does a presented ID of "example.com" match a name
// constraint of "example.com." and vice versa.
// A: Absolute names are not supported as presented IDs or name
// constraints. Only reference IDs may be absolute.
//
// Q: Is "" a valid dNSName constraint? If so, what does it mean?
// A: Yes. Any valid presented dNSName can be formed "by simply adding zero
// or more labels to the left-hand side" of "". In particular, an
// excludedSubtrees dNSName constraint of "" forbids all dNSNames.
//
// Q: Is "." a valid dNSName constraint? If so, what does it mean?
// A: No, because absolute names are not allowed (see above).
//
// [0] RFC 6265 (Cookies) Domain Matching rules:
// http://tools.ietf.org/html/rfc6265#section-5.1.3
// [1] NSS source code:
// https://mxr.mozilla.org/nss/source/lib/certdb/genname.c?rev=2a7348f013cb#1209
// [2] Description of SChannel's behavior from Microsoft:
// http://www.imc.org/ietf-pkix/mail-archive/msg04668.html
// [3] Proposal to add such support to OpenSSL:
// http://www.mail-archive.com/openssl-dev%40openssl.org/msg36204.html
// https://rt.openssl.org/Ticket/Display.html?id=3562
// [4] Feedback on the lack of clarify in the definition that never got
// incorporated into the spec:
// https://www.ietf.org/mail-archive/web/pkix/current/msg21192.html
pub(super) fn presented_id_matches_reference_id(
presented_dns_id: untrusted::Input<'_>,
reference_dns_id_role: IdRole,
reference_dns_id: untrusted::Input<'_>,
) -> Result<bool, Error> {
if !is_valid_dns_id(presented_dns_id, IdRole::Presented, Wildcards::Allow) {
return Err(Error::MalformedDnsIdentifier);
}
if !is_valid_dns_id(reference_dns_id, reference_dns_id_role, Wildcards::Deny) {
return Err(match reference_dns_id_role {
IdRole::NameConstraint => Error::MalformedNameConstraint,
_ => Error::MalformedDnsIdentifier,
});
}
let mut presented = untrusted::Reader::new(presented_dns_id);
let mut reference = untrusted::Reader::new(reference_dns_id);
match reference_dns_id_role {
IdRole::Reference => (),
IdRole::NameConstraint if presented_dns_id.len() > reference_dns_id.len() => {
if reference_dns_id.is_empty() {
// An empty constraint matches everything.
return Ok(true);
}
// If the reference ID starts with a dot then skip the prefix of
// the presented ID and start the comparison at the position of
// that dot. Examples:
//
// Matches Doesn't Match
// -----------------------------------------------------------
// original presented ID: www.example.com badexample.com
// skipped: www ba
// presented ID w/o prefix: .example.com dexample.com
// reference ID: .example.com .example.com
//
// If the reference ID does not start with a dot then we skip
// the prefix of the presented ID but also verify that the
// prefix ends with a dot. Examples:
//
// Matches Doesn't Match
// -----------------------------------------------------------
// original presented ID: www.example.com badexample.com
// skipped: www ba
// must be '.': . d
// presented ID w/o prefix: example.com example.com
// reference ID: example.com example.com
//
if reference.peek(b'.') {
if presented
.skip(presented_dns_id.len() - reference_dns_id.len())
.is_err()
{
unreachable!();
}
} else {
if presented
.skip(presented_dns_id.len() - reference_dns_id.len() - 1)
.is_err()
{
unreachable!();
}
if presented.read_byte() != Ok(b'.') {
return Ok(false);
}
}
}
IdRole::NameConstraint => (),
IdRole::Presented => unreachable!(),
}
// Only allow wildcard labels that consist only of '*'.
if presented.peek(b'*') {
if presented.skip(1).is_err() {
unreachable!();
}
loop {
if reference.read_byte().is_err() {
return Ok(false);
}
if reference.peek(b'.') {
break;
}
}
}
loop {
let presented_byte = match (presented.read_byte(), reference.read_byte()) {
(Ok(p), Ok(r)) if ascii_lower(p) == ascii_lower(r) => p,
_ => {
return Ok(false);
}
};
if presented.at_end() {
// Don't allow presented IDs to be absolute.
if presented_byte == b'.' {
return Err(Error::MalformedDnsIdentifier);
}
break;
}
}
// Allow a relative presented DNS ID to match an absolute reference DNS ID,
// unless we're matching a name constraint.
if !reference.at_end() {
if reference_dns_id_role != IdRole::NameConstraint {
match reference.read_byte() {
Ok(b'.') => (),
_ => {
return Ok(false);
}
};
}
if !reference.at_end() {
return Ok(false);
}
}
assert!(presented.at_end());
assert!(reference.at_end());
Ok(true)
}
#[inline]
fn ascii_lower(b: u8) -> u8 {
match b {
b'A'..=b'Z' => b + b'a' - b'A',
_ => b,
}
}
#[derive(Clone, Copy, PartialEq)]
enum Wildcards {
Deny,
Allow,
}
#[derive(Clone, Copy, PartialEq)]
pub(super) enum IdRole {
Reference,
Presented,
NameConstraint,
}
// https://tools.ietf.org/html/rfc5280#section-4.2.1.6:
//
// When the subjectAltName extension contains a domain name system
// label, the domain name MUST be stored in the dNSName (an IA5String).
// The name MUST be in the "preferred name syntax", as specified by
// Section 3.5 of [RFC1034] and as modified by Section 2.1 of
// [RFC1123].
//
// https://bugzilla.mozilla.org/show_bug.cgi?id=1136616: As an exception to the
// requirement above, underscores are also allowed in names for compatibility.
fn is_valid_dns_id(
hostname: untrusted::Input<'_>,
id_role: IdRole,
allow_wildcards: Wildcards,
) -> bool {
// https://blogs.msdn.microsoft.com/oldnewthing/20120412-00/?p=7873/
if hostname.len() > 253 {
return false;
}
let mut input = untrusted::Reader::new(hostname);
if id_role == IdRole::NameConstraint && input.at_end() {
return true;
}
let mut dot_count = 0;
let mut label_length = 0;
let mut label_is_all_numeric = false;
let mut label_ends_with_hyphen = false;
// Only presented IDs are allowed to have wildcard labels. And, like
// Chromium, be stricter than RFC 6125 requires by insisting that a
// wildcard label consist only of '*'.
let is_wildcard = allow_wildcards == Wildcards::Allow && input.peek(b'*');
let mut is_first_byte = !is_wildcard;
if is_wildcard {
if input.read_byte() != Ok(b'*') || input.read_byte() != Ok(b'.') {
return false;
}
dot_count += 1;
}
loop {
const MAX_LABEL_LENGTH: usize = 63;
match input.read_byte() {
Ok(b'-') => {
if label_length == 0 {
return false; // Labels must not start with a hyphen.
}
label_is_all_numeric = false;
label_ends_with_hyphen = true;
label_length += 1;
if label_length > MAX_LABEL_LENGTH {
return false;
}
}
Ok(b'0'..=b'9') => {
if label_length == 0 {
label_is_all_numeric = true;
}
label_ends_with_hyphen = false;
label_length += 1;
if label_length > MAX_LABEL_LENGTH {
return false;
}
}
Ok(b'a'..=b'z') | Ok(b'A'..=b'Z') | Ok(b'_') => {
label_is_all_numeric = false;
label_ends_with_hyphen = false;
label_length += 1;
if label_length > MAX_LABEL_LENGTH {
return false;
}
}
Ok(b'.') => {
dot_count += 1;
if label_length == 0 && (id_role != IdRole::NameConstraint || !is_first_byte) {
return false;
}
if label_ends_with_hyphen {
return false; // Labels must not end with a hyphen.
}
label_length = 0;
}
_ => {
return false;
}
}
is_first_byte = false;
if input.at_end() {
break;
}
}
// Only reference IDs, not presented IDs or name constraints, may be
// absolute.
if label_length == 0 && id_role != IdRole::Reference {
return false;
}
if label_ends_with_hyphen {
return false; // Labels must not end with a hyphen.
}
if label_is_all_numeric {
return false; // Last label must not be all numeric.
}
if is_wildcard {
// If the DNS ID ends with a dot, the last dot signifies an absolute ID.
let label_count = if label_length == 0 {
dot_count
} else {
dot_count + 1
};
// Like NSS, require at least two labels to follow the wildcard label.
// TODO: Allow the TrustDomain to control this on a per-eTLD+1 basis,
// similar to Chromium. Even then, it might be better to still enforce
// that there are at least two labels after the wildcard.
if label_count < 3 {
return false;
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::type_complexity)]
const PRESENTED_MATCHES_REFERENCE: &[(&[u8], &[u8], Result<bool, Error>)] = &[
(b"", b"a", Err(Error::MalformedDnsIdentifier)),
(b"a", b"a", Ok(true)),
(b"b", b"a", Ok(false)),
(b"*.b.a", b"c.b.a", Ok(true)),
(b"*.b.a", b"b.a", Ok(false)),
(b"*.b.a", b"b.a.", Ok(false)),
// Wildcard not in leftmost label
(b"d.c.b.a", b"d.c.b.a", Ok(true)),
(b"d.*.b.a", b"d.c.b.a", Err(Error::MalformedDnsIdentifier)),
(b"d.c*.b.a", b"d.c.b.a", Err(Error::MalformedDnsIdentifier)),
(b"d.c*.b.a", b"d.cc.b.a", Err(Error::MalformedDnsIdentifier)),
// case sensitivity
(
b"abcdefghijklmnopqrstuvwxyz",
b"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
Ok(true),
),
(
b"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
b"abcdefghijklmnopqrstuvwxyz",
Ok(true),
),
(b"aBc", b"Abc", Ok(true)),
// digits
(b"a1", b"a1", Ok(true)),
// A trailing dot indicates an absolute name, and absolute names can match
// relative names, and vice-versa.
(b"example", b"example", Ok(true)),
(b"example.", b"example.", Err(Error::MalformedDnsIdentifier)),
(b"example", b"example.", Ok(true)),
(b"example.", b"example", Err(Error::MalformedDnsIdentifier)),
(b"example.com", b"example.com", Ok(true)),
(
b"example.com.",
b"example.com.",
Err(Error::MalformedDnsIdentifier),
),
(b"example.com", b"example.com.", Ok(true)),
(
b"example.com.",
b"example.com",
Err(Error::MalformedDnsIdentifier),
),
(
b"example.com..",
b"example.com.",
Err(Error::MalformedDnsIdentifier),
),
(
b"example.com..",
b"example.com",
Err(Error::MalformedDnsIdentifier),
),
(
b"example.com...",
b"example.com.",
Err(Error::MalformedDnsIdentifier),
),
// xn-- IDN prefix
(b"x*.b.a", b"xa.b.a", Err(Error::MalformedDnsIdentifier)),
(b"x*.b.a", b"xna.b.a", Err(Error::MalformedDnsIdentifier)),
(b"x*.b.a", b"xn-a.b.a", Err(Error::MalformedDnsIdentifier)),
(b"x*.b.a", b"xn--a.b.a", Err(Error::MalformedDnsIdentifier)),
(b"xn*.b.a", b"xn--a.b.a", Err(Error::MalformedDnsIdentifier)),
(
b"xn-*.b.a",
b"xn--a.b.a",
Err(Error::MalformedDnsIdentifier),
),
(
b"xn--*.b.a",
b"xn--a.b.a",
Err(Error::MalformedDnsIdentifier),
),
(b"xn*.b.a", b"xn--a.b.a", Err(Error::MalformedDnsIdentifier)),
(
b"xn-*.b.a",
b"xn--a.b.a",
Err(Error::MalformedDnsIdentifier),
),
(
b"xn--*.b.a",
b"xn--a.b.a",
Err(Error::MalformedDnsIdentifier),
),
(
b"xn---*.b.a",
b"xn--a.b.a",
Err(Error::MalformedDnsIdentifier),
),
// "*" cannot expand to nothing.
(b"c*.b.a", b"c.b.a", Err(Error::MalformedDnsIdentifier)),
// --------------------------------------------------------------------------
// The rest of these are test cases adapted from Chromium's
// x509_certificate_unittest.cc. The parameter order is the opposite in
// Chromium's tests. Also, they Ok tests were modified to fit into this
// framework or due to intentional differences between mozilla::pkix and
// Chromium.
(b"foo.com", b"foo.com", Ok(true)),
(b"f", b"f", Ok(true)),
(b"i", b"h", Ok(false)),
(b"*.foo.com", b"bar.foo.com", Ok(true)),
(b"*.test.fr", b"www.test.fr", Ok(true)),
(b"*.test.FR", b"wwW.tESt.fr", Ok(true)),
(b".uk", b"f.uk", Err(Error::MalformedDnsIdentifier)),
(
b"?.bar.foo.com",
b"w.bar.foo.com",
Err(Error::MalformedDnsIdentifier),
),
(
b"(www|ftp).foo.com",
b"www.foo.com",
Err(Error::MalformedDnsIdentifier),
), // regex!
(
b"www.foo.com\0",
b"www.foo.com",
Err(Error::MalformedDnsIdentifier),
),
(
b"www.foo.com\0*.foo.com",
b"www.foo.com",
Err(Error::MalformedDnsIdentifier),
),
(b"ww.house.example", b"www.house.example", Ok(false)),
(b"www.test.org", b"test.org", Ok(false)),
(b"*.test.org", b"test.org", Ok(false)),
(b"*.org", b"test.org", Err(Error::MalformedDnsIdentifier)),
// '*' must be the only character in the wildcard label
(
b"w*.bar.foo.com",
b"w.bar.foo.com",
Err(Error::MalformedDnsIdentifier),
),
(
b"ww*ww.bar.foo.com",
b"www.bar.foo.com",
Err(Error::MalformedDnsIdentifier),
),
(
b"ww*ww.bar.foo.com",
b"wwww.bar.foo.com",
Err(Error::MalformedDnsIdentifier),
),
(
b"w*w.bar.foo.com",
b"wwww.bar.foo.com",
Err(Error::MalformedDnsIdentifier),
),
(
b"w*w.bar.foo.c0m",
b"wwww.bar.foo.com",
Err(Error::MalformedDnsIdentifier),
),
(
b"wa*.bar.foo.com",
b"WALLY.bar.foo.com",
Err(Error::MalformedDnsIdentifier),
),
(
b"*Ly.bar.foo.com",
b"wally.bar.foo.com",
Err(Error::MalformedDnsIdentifier),
),
// Chromium does URL decoding of the reference ID, but we don't, and we also
// require that the reference ID is valid, so we can't test these two.
// (b"www.foo.com", b"ww%57.foo.com", Ok(true)),
// (b"www&.foo.com", b"www%26.foo.com", Ok(true)),
(b"*.test.de", b"www.test.co.jp", Ok(false)),
(
b"*.jp",
b"www.test.co.jp",
Err(Error::MalformedDnsIdentifier),
),
(b"www.test.co.uk", b"www.test.co.jp", Ok(false)),
(
b"www.*.co.jp",
b"www.test.co.jp",
Err(Error::MalformedDnsIdentifier),
),
(b"www.bar.foo.com", b"www.bar.foo.com", Ok(true)),
(b"*.foo.com", b"www.bar.foo.com", Ok(false)),
(
b"*.*.foo.com",
b"www.bar.foo.com",
Err(Error::MalformedDnsIdentifier),
),
// Our matcher requires the reference ID to be a valid DNS name, so we cannot
// test this case.
// (b"*.*.bar.foo.com", b"*..bar.foo.com", Ok(false)),
(b"www.bath.org", b"www.bath.org", Ok(true)),
// Our matcher requires the reference ID to be a valid DNS name, so we cannot
// test these cases.
// DNS_ID_MISMATCH("www.bath.org", ""),
// (b"www.bath.org", b"20.30.40.50", Ok(false)),
// (b"www.bath.org", b"66.77.88.99", Ok(false)),
// IDN tests
(
b"xn--poema-9qae5a.com.br",
b"xn--poema-9qae5a.com.br",
Ok(true),
),
(
b"*.xn--poema-9qae5a.com.br",
b"www.xn--poema-9qae5a.com.br",
Ok(true),
),
(
b"*.xn--poema-9qae5a.com.br",
b"xn--poema-9qae5a.com.br",
Ok(false),
),
(
b"xn--poema-*.com.br",
b"xn--poema-9qae5a.com.br",
Err(Error::MalformedDnsIdentifier),
),
(
b"xn--*-9qae5a.com.br",
b"xn--poema-9qae5a.com.br",
Err(Error::MalformedDnsIdentifier),
),
(
b"*--poema-9qae5a.com.br",
b"xn--poema-9qae5a.com.br",
Err(Error::MalformedDnsIdentifier),
),
// The following are adapted from the examples quoted from
// http://tools.ietf.org/html/rfc6125#section-6.4.3
// (e.g., *.example.com would match foo.example.com but
// not bar.foo.example.com or example.com).
(b"*.example.com", b"foo.example.com", Ok(true)),
(b"*.example.com", b"bar.foo.example.com", Ok(false)),
(b"*.example.com", b"example.com", Ok(false)),
(
b"baz*.example.net",
b"baz1.example.net",
Err(Error::MalformedDnsIdentifier),
),
(
b"*baz.example.net",
b"foobaz.example.net",
Err(Error::MalformedDnsIdentifier),
),
(
b"b*z.example.net",
b"buzz.example.net",
Err(Error::MalformedDnsIdentifier),
),
// Wildcards should not be valid for public registry controlled domains,
// and unknown/unrecognized domains, at least three domain components must
// be present. For mozilla::pkix and NSS, there must always be at least two
// labels after the wildcard label.
(b"*.test.example", b"www.test.example", Ok(true)),
(b"*.example.co.uk", b"test.example.co.uk", Ok(true)),
(
b"*.example",
b"test.example",
Err(Error::MalformedDnsIdentifier),
),
// The result is different than Chromium, because Chromium takes into account
// the additional knowledge it has that "co.uk" is a TLD. mozilla::pkix does
// not know that.
(b"*.co.uk", b"example.co.uk", Ok(true)),
(b"*.com", b"foo.com", Err(Error::MalformedDnsIdentifier)),
(b"*.us", b"foo.us", Err(Error::MalformedDnsIdentifier)),
(b"*", b"foo", Err(Error::MalformedDnsIdentifier)),
// IDN variants of wildcards and registry controlled domains.
(
b"*.xn--poema-9qae5a.com.br",
b"www.xn--poema-9qae5a.com.br",
Ok(true),
),
(
b"*.example.xn--mgbaam7a8h",
b"test.example.xn--mgbaam7a8h",
Ok(true),
),
// RFC6126 allows this, and NSS accepts it, but Chromium disallows it.
// TODO: File bug against Chromium.
(b"*.com.br", b"xn--poema-9qae5a.com.br", Ok(true)),
(
b"*.xn--mgbaam7a8h",
b"example.xn--mgbaam7a8h",
Err(Error::MalformedDnsIdentifier),
),
// Wildcards should be permissible for 'private' registry-controlled
// domains. (In mozilla::pkix, we do not know if it is a private registry-
// controlled domain or not.)
(b"*.appspot.com", b"www.appspot.com", Ok(true)),
(b"*.s3.amazonaws.com", b"foo.s3.amazonaws.com", Ok(true)),
// Multiple wildcards are not valid.
(
b"*.*.com",
b"foo.example.com",
Err(Error::MalformedDnsIdentifier),
),
(
b"*.bar.*.com",
b"foo.bar.example.com",
Err(Error::MalformedDnsIdentifier),
),
// Absolute vs relative DNS name tests. Although not explicitly specified
// in RFC 6125, absolute reference names (those ending in a .) should
// match either absolute or relative presented names.
// TODO: File errata against RFC 6125 about this.
(b"foo.com.", b"foo.com", Err(Error::MalformedDnsIdentifier)),
(b"foo.com", b"foo.com.", Ok(true)),
(b"foo.com.", b"foo.com.", Err(Error::MalformedDnsIdentifier)),
(b"f.", b"f", Err(Error::MalformedDnsIdentifier)),
(b"f", b"f.", Ok(true)),
(b"f.", b"f.", Err(Error::MalformedDnsIdentifier)),
(
b"*.bar.foo.com.",
b"www-3.bar.foo.com",
Err(Error::MalformedDnsIdentifier),
),
(b"*.bar.foo.com", b"www-3.bar.foo.com.", Ok(true)),
(
b"*.bar.foo.com.",
b"www-3.bar.foo.com.",
Err(Error::MalformedDnsIdentifier),
),
// We require the reference ID to be a valid DNS name, so we cannot test this
// case.
// (b".", b".", Ok(false)),
(
b"*.com.",
b"example.com",
Err(Error::MalformedDnsIdentifier),
),
(
b"*.com",
b"example.com.",
Err(Error::MalformedDnsIdentifier),
),
(
b"*.com.",
b"example.com.",
Err(Error::MalformedDnsIdentifier),
),
(b"*.", b"foo.", Err(Error::MalformedDnsIdentifier)),
(b"*.", b"foo", Err(Error::MalformedDnsIdentifier)),
// The result is different than Chromium because we don't know that co.uk is
// a TLD.
(
b"*.co.uk.",
b"foo.co.uk",
Err(Error::MalformedDnsIdentifier),
),
(
b"*.co.uk.",
b"foo.co.uk.",
Err(Error::MalformedDnsIdentifier),
),
];
#[test]
fn presented_matches_reference_test() {
for (presented, reference, expected_result) in PRESENTED_MATCHES_REFERENCE {
let actual_result = presented_id_matches_reference_id(
untrusted::Input::from(presented),
IdRole::Reference,
untrusted::Input::from(reference),
);
assert_eq!(
&actual_result, expected_result,
"presented_id_matches_reference_id(\"{presented:?}\", \"{reference:?}\")"
);
}
}
// (presented_name, constraint, expected_matches)
#[allow(clippy::type_complexity)]
const PRESENTED_MATCHES_CONSTRAINT: &[(&[u8], &[u8], Result<bool, Error>)] = &[
// No absolute presented IDs allowed
(b".", b"", Err(Error::MalformedDnsIdentifier)),
(b"www.example.com.", b"", Err(Error::MalformedDnsIdentifier)),
(
b"www.example.com.",
b"www.example.com.",
Err(Error::MalformedDnsIdentifier),
),
// No absolute constraints allowed
(
b"www.example.com",
b".",
Err(Error::MalformedNameConstraint),
),
(
b"www.example.com",
b"www.example.com.",
Err(Error::MalformedNameConstraint),
),
// No wildcard in constraints allowed
(
b"www.example.com",
b"*.example.com",
Err(Error::MalformedNameConstraint),
),
// No empty presented IDs allowed
(b"", b"", Err(Error::MalformedDnsIdentifier)),
// Empty constraints match everything allowed
(b"example.com", b"", Ok(true)),
(b"*.example.com", b"", Ok(true)),
// Constraints that start with a dot
(b"www.example.com", b".example.com", Ok(true)),
(b"www.example.com", b".EXAMPLE.COM", Ok(true)),
(b"www.example.com", b".axample.com", Ok(false)),
(b"www.example.com", b".xample.com", Ok(false)),
(b"www.example.com", b".exampl.com", Ok(false)),
(b"badexample.com", b".example.com", Ok(false)),
// Constraints that do not start with a dot
(b"www.example.com", b"example.com", Ok(true)),
(b"www.example.com", b"EXAMPLE.COM", Ok(true)),
(b"www.example.com", b"axample.com", Ok(false)),
(b"www.example.com", b"xample.com", Ok(false)),
(b"www.example.com", b"exampl.com", Ok(false)),
(b"badexample.com", b"example.com", Ok(false)),
// Presented IDs with wildcard
(b"*.example.com", b".example.com", Ok(true)),
(b"*.example.com", b"example.com", Ok(true)),
(b"*.example.com", b"www.example.com", Ok(true)),
(b"*.example.com", b"www.EXAMPLE.COM", Ok(true)),
(b"*.example.com", b"www.axample.com", Ok(false)),
(b"*.example.com", b".xample.com", Ok(false)),
(b"*.example.com", b"xample.com", Ok(false)),
(b"*.example.com", b".exampl.com", Ok(false)),
(b"*.example.com", b"exampl.com", Ok(false)),
// Matching IDs
(b"www.example.com", b"www.example.com", Ok(true)),
];
#[test]
fn presented_matches_constraint_test() {
for (presented, constraint, expected_result) in PRESENTED_MATCHES_CONSTRAINT {
let actual_result = presented_id_matches_reference_id(
untrusted::Input::from(presented),
IdRole::NameConstraint,
untrusted::Input::from(constraint),
);
assert_eq!(
&actual_result, expected_result,
"presented_id_matches_constraint(\"{presented:?}\", \"{constraint:?}\")",
);
}
}
}

View File

@@ -0,0 +1,706 @@
// Copyright 2015-2020 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#[cfg(feature = "alloc")]
use alloc::format;
use pki_types::IpAddr;
#[cfg(feature = "alloc")]
use pki_types::ServerName;
use super::{GeneralName, NameIterator};
use crate::cert::Cert;
use crate::error::{Error, InvalidNameContext};
pub(crate) fn verify_ip_address_names(reference: &IpAddr, cert: &Cert<'_>) -> Result<(), Error> {
let ip_address = match reference {
IpAddr::V4(ip) => untrusted::Input::from(ip.as_ref()),
IpAddr::V6(ip) => untrusted::Input::from(ip.as_ref()),
};
let result = NameIterator::new(cert.subject_alt_name).find_map(|result| {
let name = match result {
Ok(name) => name,
Err(err) => return Some(Err(err)),
};
let presented_id = match name {
GeneralName::IpAddress(presented) => presented,
_ => return None,
};
match presented_id_matches_reference_id(presented_id, ip_address) {
true => Some(Ok(())),
false => None,
}
});
match result {
Some(result) => return result,
#[cfg(feature = "alloc")]
None => {}
#[cfg(not(feature = "alloc"))]
None => Err(Error::CertNotValidForName(InvalidNameContext {})),
}
#[cfg(feature = "alloc")]
{
Err(Error::CertNotValidForName(InvalidNameContext {
expected: ServerName::from(*reference),
presented: NameIterator::new(cert.subject_alt_name)
.filter_map(|result| Some(format!("{:?}", result.ok()?)))
.collect(),
}))
}
}
// https://tools.ietf.org/html/rfc5280#section-4.2.1.6 says:
// When the subjectAltName extension contains an iPAddress, the address
// MUST be stored in the octet string in "network byte order", as
// specified in [RFC791]. The least significant bit (LSB) of each octet
// is the LSB of the corresponding byte in the network address. For IP
// version 4, as specified in [RFC791], the octet string MUST contain
// exactly four octets. For IP version 6, as specified in
// [RFC2460], the octet string MUST contain exactly sixteen octets.
fn presented_id_matches_reference_id(
presented_id: untrusted::Input<'_>,
reference_id: untrusted::Input<'_>,
) -> bool {
match (presented_id.len(), reference_id.len()) {
(4, 4) => (),
(16, 16) => (),
_ => {
return false;
}
};
let mut presented_ip_address = untrusted::Reader::new(presented_id);
let mut reference_ip_address = untrusted::Reader::new(reference_id);
while !presented_ip_address.at_end() {
let presented_ip_address_byte = presented_ip_address.read_byte().unwrap();
let reference_ip_address_byte = reference_ip_address.read_byte().unwrap();
if presented_ip_address_byte != reference_ip_address_byte {
return false;
}
}
true
}
// https://tools.ietf.org/html/rfc5280#section-4.2.1.10 says:
//
// For IPv4 addresses, the iPAddress field of GeneralName MUST contain
// eight (8) octets, encoded in the style of RFC 4632 (CIDR) to represent
// an address range [RFC4632]. For IPv6 addresses, the iPAddress field
// MUST contain 32 octets similarly encoded. For example, a name
// constraint for "class C" subnet 192.0.2.0 is represented as the
// octets C0 00 02 00 FF FF FF 00, representing the CIDR notation
// 192.0.2.0/24 (mask 255.255.255.0).
pub(super) fn presented_id_matches_constraint(
name: untrusted::Input<'_>,
constraint: untrusted::Input<'_>,
) -> Result<bool, Error> {
match (name.len(), constraint.len()) {
(4, 8) => (),
(16, 32) => (),
// an IPv4 address never matches an IPv6 constraint, and vice versa.
(4, 32) | (16, 8) => {
return Ok(false);
}
// invalid constraint length
(4, _) | (16, _) => {
return Err(Error::InvalidNetworkMaskConstraint);
}
// invalid name length, or anything else
_ => {
return Err(Error::BadDer);
}
};
let (constraint_address, constraint_mask) = constraint.read_all(Error::BadDer, |value| {
let address = value.read_bytes(constraint.len() / 2).unwrap();
let mask = value.read_bytes(constraint.len() / 2).unwrap();
Ok((address, mask))
})?;
let mut name = untrusted::Reader::new(name);
let mut constraint_address = untrusted::Reader::new(constraint_address);
let mut constraint_mask = untrusted::Reader::new(constraint_mask);
let mut seen_zero_bit = false;
loop {
// Iterate through the name, constraint address, and constraint mask
// a byte at a time.
let name_byte = name.read_byte().unwrap();
let constraint_address_byte = constraint_address.read_byte().unwrap();
let constraint_mask_byte = constraint_mask.read_byte().unwrap();
// A valid mask consists of a sequence of 1 bits, followed by a
// sequence of 0 bits. Either sequence could be empty.
let leading = constraint_mask_byte.leading_ones();
let trailing = constraint_mask_byte.trailing_zeros();
// At the resolution of a single octet, a valid mask is one where
// leading_ones() and trailing_zeros() sums to 8.
// This includes all-ones and all-zeroes.
if leading + trailing != 8 {
return Err(Error::InvalidNetworkMaskConstraint);
}
// There should be no bits set after the first octet with a zero bit is seen.
if seen_zero_bit && constraint_mask_byte != 0x00 {
return Err(Error::InvalidNetworkMaskConstraint);
}
// Note when a zero bit is seen for later octets.
if constraint_mask_byte != 0xff {
seen_zero_bit = true;
}
if ((name_byte ^ constraint_address_byte) & constraint_mask_byte) != 0 {
return Ok(false);
}
if name.at_end() {
break;
}
}
Ok(true)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn presented_id_matches_constraint_ipv4_test() {
let names_and_constraints = vec![
(
// 192.0.2.0 matches constraint 192.0.2.0/24
[0xC0, 0x00, 0x02, 0x00],
[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00],
Ok(true),
),
(
// 192.0.2.1 matches constraint 192.0.2.0/24
[0xC0, 0x00, 0x02, 0x01],
[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00],
Ok(true),
),
(
// 192.0.2.255 matches constraint 192.0.2.0/24
[0xC0, 0x00, 0x02, 0xFF],
[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00],
Ok(true),
),
(
// 192.0.1.255 does not match constraint 192.0.2.0/24
[0xC0, 0x00, 0x01, 0xFF],
[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00],
Ok(false),
),
(
// 192.0.3.0 does not match constraint 192.0.2.0/24
[0xC0, 0x00, 0x03, 0x00],
[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00],
Ok(false),
),
];
for (name, constraint, match_result) in names_and_constraints {
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(&name),
untrusted::Input::from(&constraint),
),
match_result
)
}
// Invalid name length (shorter)
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(&[0xC0, 0x00, 0x02]),
untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00]),
),
Err(Error::BadDer),
);
// Invalid name length (longer)
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00, 0x00]),
untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00]),
),
Err(Error::BadDer),
);
// Unmatching constraint size (shorter)
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00]),
untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF]),
),
Err(Error::InvalidNetworkMaskConstraint),
);
// Unmatching constraint size (longer)
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00]),
untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00]),
),
Err(Error::InvalidNetworkMaskConstraint),
);
// Unmatching constraint size (IPv6 constraint for IPv4 address)
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00]),
untrusted::Input::from(&[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]),
),
Ok(false),
);
}
#[test]
fn presented_id_matches_constraint_ipv6_test() {
let names_and_constraints = vec![
(
// 2001:0DB8:ABCD:0012:0000:0000:0000:0000 matches constraint
// 2001:0DB8:ABCD:0012:0000:0000:0000:0000/64
[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
],
[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
Ok(true),
),
(
// 2001:0DB8:ABCD:0012:0000:0000:0000:0001 matches constraint
// 2001:0DB8:ABCD:0012:0000:0000:0000:0000/64
[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01,
],
[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
Ok(true),
),
(
// 2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFFF matches constraint
// 2001:0DB8:ABCD:0012:0000:0000:0000:0000/64
[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
],
[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
Ok(true),
),
(
// 2001:0DB8:ABCD:0011:0000:0000:0000:0000 does not match constraint
// 2001:0DB8:ABCD:0012:0000:0000:0000:0000/64
[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
],
[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
Ok(false),
),
(
// 2001:0DB8:ABCD:0013:0000:0000:0000:0000 does not match constraint
// 2001:0DB8:ABCD:0012:0000:0000:0000:0000/64
[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
],
[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
Ok(false),
),
];
for (name, constraint, match_result) in names_and_constraints {
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(&name),
untrusted::Input::from(&constraint),
),
match_result
)
}
// Invalid name length (shorter)
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(&[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00
]),
untrusted::Input::from(&[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]),
),
Err(Error::BadDer),
);
// Invalid name length (longer)
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(&[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
]),
untrusted::Input::from(&[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]),
),
Err(Error::BadDer),
);
// Unmatching constraint size (shorter)
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(&[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
]),
untrusted::Input::from(&[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00
]),
),
Err(Error::InvalidNetworkMaskConstraint),
);
// Unmatching constraint size (longer)
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(&[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
]),
untrusted::Input::from(&[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]),
),
Err(Error::InvalidNetworkMaskConstraint),
);
// Unmatching constraint size (IPv4 constraint for IPv6 address)
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(&[
0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
]),
untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00]),
),
Ok(false),
);
}
#[test]
fn test_presented_id_matches_reference_id() {
assert!(!presented_id_matches_reference_id(
untrusted::Input::from(&[]),
untrusted::Input::from(&[]),
));
assert!(!presented_id_matches_reference_id(
untrusted::Input::from(&[0x01]),
untrusted::Input::from(&[])
));
assert!(!presented_id_matches_reference_id(
untrusted::Input::from(&[]),
untrusted::Input::from(&[0x01])
));
assert!(presented_id_matches_reference_id(
untrusted::Input::from(&[1, 2, 3, 4]),
untrusted::Input::from(&[1, 2, 3, 4])
));
assert!(!presented_id_matches_reference_id(
untrusted::Input::from(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
untrusted::Input::from(&[1, 2, 3, 4])
));
assert!(!presented_id_matches_reference_id(
untrusted::Input::from(&[1, 2, 3, 4]),
untrusted::Input::from(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
));
assert!(presented_id_matches_reference_id(
untrusted::Input::from(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
untrusted::Input::from(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
));
}
#[test]
fn presented_id_matches_constraint_rejects_incorrect_length_arguments() {
// wrong length names
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(b"\x00\x00\x00"),
untrusted::Input::from(b"")
),
Err(Error::BadDer)
);
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(b"\x00\x00\x00\x00\x00"),
untrusted::Input::from(b"")
),
Err(Error::BadDer)
);
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
),
untrusted::Input::from(b"")
),
Err(Error::BadDer)
);
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
),
untrusted::Input::from(b"")
),
Err(Error::BadDer)
);
// wrong length constraints
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(b"\x00\x00\x00\x00"),
untrusted::Input::from(b"\x00\x00\x00\x00\xff\xff\xff")
),
Err(Error::InvalidNetworkMaskConstraint)
);
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(b"\x00\x00\x00\x00"),
untrusted::Input::from(b"\x00\x00\x00\x00\xff\xff\xff\xff\x00")
),
Err(Error::InvalidNetworkMaskConstraint)
);
assert_eq!(
presented_id_matches_constraint(untrusted::Input::from(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
untrusted::Input::from(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff")),
Err(Error::InvalidNetworkMaskConstraint)
);
assert_eq!(
presented_id_matches_constraint(untrusted::Input::from(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
untrusted::Input::from(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff")),
Err(Error::InvalidNetworkMaskConstraint)
);
// ipv4-length not considered for ipv6-length name, and vv
assert_eq!(
presented_id_matches_constraint(untrusted::Input::from(b"\x00\x00\x00\x00"),
untrusted::Input::from(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff")),
Ok(false)
);
assert_eq!(
presented_id_matches_constraint(
untrusted::Input::from(
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
),
untrusted::Input::from(b"\x00\x00\x00\x00\xff\xff\xff\xff")
),
Ok(false)
);
}
}
#[cfg(all(test, feature = "std"))]
mod alloc_tests {
use super::*;
// (presented_address, constraint_address, constraint_mask, expected_result)
const PRESENTED_MATCHES_CONSTRAINT: &[(&str, &str, &str, Result<bool, Error>)] = &[
// Cannot mix IpV4 with IpV6 and viceversa
("2001:db8::", "8.8.8.8", "255.255.255.255", Ok(false)),
("8.8.8.8", "2001:db8::", "ffff::", Ok(false)),
// IpV4 non-contiguous masks
(
"8.8.8.8",
"8.8.8.8",
"255.255.255.1",
Err(Error::InvalidNetworkMaskConstraint),
),
(
"8.8.8.8",
"8.8.8.8",
"255.255.0.255",
Err(Error::InvalidNetworkMaskConstraint),
),
(
"8.8.8.8",
"8.8.8.8",
"255.0.255.255",
Err(Error::InvalidNetworkMaskConstraint),
),
(
"8.8.8.8",
"8.8.8.8",
"0.255.255.255",
Err(Error::InvalidNetworkMaskConstraint),
),
(
"8.8.8.8",
"8.8.8.8",
"1.255.255.255",
Err(Error::InvalidNetworkMaskConstraint),
),
(
"8.8.8.8",
"8.8.8.8",
"128.128.128.128",
Err(Error::InvalidNetworkMaskConstraint),
),
// IpV4
("8.8.8.8", "8.8.8.8", "255.255.255.255", Ok(true)),
("8.8.8.9", "8.8.8.8", "255.255.255.255", Ok(false)),
("8.8.8.9", "8.8.8.8", "255.255.255.254", Ok(true)),
("8.8.8.10", "8.8.8.8", "255.255.255.254", Ok(false)),
("8.8.8.10", "8.8.8.8", "255.255.255.0", Ok(true)),
("8.8.15.10", "8.8.8.8", "255.255.248.0", Ok(true)),
("8.8.16.10", "8.8.8.8", "255.255.248.0", Ok(false)),
("8.8.16.10", "8.8.8.8", "255.255.0.0", Ok(true)),
("8.31.16.10", "8.8.8.8", "255.224.0.0", Ok(true)),
("8.32.16.10", "8.8.8.8", "255.224.0.0", Ok(false)),
("8.32.16.10", "8.8.8.8", "255.0.0.0", Ok(true)),
("63.32.16.10", "8.8.8.8", "192.0.0.0", Ok(true)),
("64.32.16.10", "8.8.8.8", "192.0.0.0", Ok(false)),
("64.32.16.10", "8.8.8.8", "0.0.0.0", Ok(true)),
// IpV6 non-contiguous masks
(
"2001:db8::",
"2001:db8::",
"fffe:ffff::",
Err(Error::InvalidNetworkMaskConstraint),
),
(
"2001:db8::",
"2001:db8::",
"ffff:fdff::",
Err(Error::InvalidNetworkMaskConstraint),
),
(
"2001:db8::",
"2001:db8::",
"ffff:feff::",
Err(Error::InvalidNetworkMaskConstraint),
),
(
"2001:db8::",
"2001:db8::",
"ffff:fcff::",
Err(Error::InvalidNetworkMaskConstraint),
),
(
"2001:db8::",
"2001:db8::",
"7fff:ffff::",
Err(Error::InvalidNetworkMaskConstraint),
),
// IpV6
("2001:db8::", "2001:db8::", "ffff:ffff::", Ok(true)),
("2001:db9::", "2001:db8::", "ffff:ffff::", Ok(false)),
("2001:db9::", "2001:db8::", "ffff:fffe::", Ok(true)),
("2001:dba::", "2001:db8::", "ffff:fffe::", Ok(false)),
("2001:dba::", "2001:db8::", "ffff:ff00::", Ok(true)),
("2001:dca::", "2001:db8::", "ffff:fe00::", Ok(true)),
("2001:fca::", "2001:db8::", "ffff:fe00::", Ok(false)),
("2001:fca::", "2001:db8::", "ffff:0000::", Ok(true)),
("2000:fca::", "2001:db8::", "fffe:0000::", Ok(true)),
("2003:fca::", "2001:db8::", "fffe:0000::", Ok(false)),
("2003:fca::", "2001:db8::", "ff00:0000::", Ok(true)),
("1003:fca::", "2001:db8::", "e000:0000::", Ok(false)),
("1003:fca::", "2001:db8::", "0000:0000::", Ok(true)),
];
#[test]
fn presented_matches_constraint_test() {
use std::boxed::Box;
use std::net::IpAddr;
for (presented, constraint_address, constraint_mask, expected_result) in
PRESENTED_MATCHES_CONSTRAINT
{
let presented_bytes: Box<[u8]> = match presented.parse::<IpAddr>().unwrap() {
IpAddr::V4(p) => Box::new(p.octets()),
IpAddr::V6(p) => Box::new(p.octets()),
};
let ca_bytes: Box<[u8]> = match constraint_address.parse::<IpAddr>().unwrap() {
IpAddr::V4(ca) => Box::new(ca.octets()),
IpAddr::V6(ca) => Box::new(ca.octets()),
};
let cm_bytes: Box<[u8]> = match constraint_mask.parse::<IpAddr>().unwrap() {
IpAddr::V4(cm) => Box::new(cm.octets()),
IpAddr::V6(cm) => Box::new(cm.octets()),
};
let constraint_bytes = [ca_bytes, cm_bytes].concat();
let actual_result = presented_id_matches_constraint(
untrusted::Input::from(&presented_bytes),
untrusted::Input::from(&constraint_bytes),
);
assert_eq!(
&actual_result, expected_result,
"presented_id_matches_constraint(\"{presented_bytes:?}\", \"{constraint_bytes:?}\")"
);
}
}
}

View File

@@ -0,0 +1,456 @@
// Copyright 2022 Rafael Fernández López.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#[cfg(feature = "alloc")]
use alloc::string::String;
#[cfg(feature = "alloc")]
use core::fmt;
use crate::der::{self, FromDer};
use crate::error::{DerTypeId, Error};
use crate::verify_cert::{Budget, PathNode};
mod dns_name;
use dns_name::IdRole;
pub(crate) use dns_name::{WildcardDnsNameRef, verify_dns_names};
mod ip_address;
pub(crate) use ip_address::verify_ip_address_names;
// https://tools.ietf.org/html/rfc5280#section-4.2.1.10
pub(crate) fn check_name_constraints(
constraints: Option<&mut untrusted::Reader<'_>>,
path: &PathNode<'_>,
budget: &mut Budget,
) -> Result<(), Error> {
let constraints = match constraints {
Some(input) => input,
None => return Ok(()),
};
fn parse_subtrees<'b>(
inner: &mut untrusted::Reader<'b>,
subtrees_tag: der::Tag,
) -> Result<Option<untrusted::Input<'b>>, Error> {
if !inner.peek(subtrees_tag.into()) {
return Ok(None);
}
der::expect_tag(inner, subtrees_tag).map(Some)
}
let permitted_subtrees = parse_subtrees(constraints, der::Tag::ContextSpecificConstructed0)?;
let excluded_subtrees = parse_subtrees(constraints, der::Tag::ContextSpecificConstructed1)?;
for path in path.iter() {
let result = NameIterator::new(path.cert.subject_alt_name).find_map(|result| {
let name = match result {
Ok(name) => name,
Err(err) => return Some(Err(err)),
};
check_presented_id_conforms_to_constraints(
name,
permitted_subtrees,
excluded_subtrees,
budget,
)
});
if let Some(Err(err)) = result {
return Err(err);
}
let result = check_presented_id_conforms_to_constraints(
GeneralName::DirectoryName,
permitted_subtrees,
excluded_subtrees,
budget,
);
if let Some(Err(err)) = result {
return Err(err);
}
}
Ok(())
}
fn check_presented_id_conforms_to_constraints(
name: GeneralName<'_>,
permitted_subtrees: Option<untrusted::Input<'_>>,
excluded_subtrees: Option<untrusted::Input<'_>>,
budget: &mut Budget,
) -> Option<Result<(), Error>> {
let subtrees = [
(Subtrees::PermittedSubtrees, permitted_subtrees),
(Subtrees::ExcludedSubtrees, excluded_subtrees),
];
fn general_subtree<'b>(input: &mut untrusted::Reader<'b>) -> Result<GeneralName<'b>, Error> {
der::read_all(der::expect_tag(input, der::Tag::Sequence)?)
}
for (subtrees, constraints) in subtrees {
let mut constraints = match constraints {
Some(constraints) => untrusted::Reader::new(constraints),
None => continue,
};
let mut has_permitted_subtrees_match = false;
let mut has_permitted_subtrees_mismatch = false;
while !constraints.at_end() {
if let Err(e) = budget.consume_name_constraint_comparison() {
return Some(Err(e));
}
// http://tools.ietf.org/html/rfc5280#section-4.2.1.10: "Within this
// profile, the minimum and maximum fields are not used with any name
// forms, thus, the minimum MUST be zero, and maximum MUST be absent."
//
// Since the default value isn't allowed to be encoded according to the
// DER encoding rules for DEFAULT, this is equivalent to saying that
// neither minimum or maximum must be encoded.
let base = match general_subtree(&mut constraints) {
Ok(base) => base,
Err(err) => return Some(Err(err)),
};
let matches = match (name, base) {
(GeneralName::DnsName(name), GeneralName::DnsName(base)) => {
dns_name::presented_id_matches_reference_id(name, IdRole::NameConstraint, base)
}
(GeneralName::DirectoryName, GeneralName::DirectoryName) => Ok(
// Reject any uses of directory name constraints; we don't implement this.
//
// Rejecting everything technically confirms to RFC5280:
//
// "If a name constraints extension that is marked as critical imposes constraints
// on a particular name form, and an instance of that name form appears in the
// subject field or subjectAltName extension of a subsequent certificate, then
// the application MUST either process the constraint or _reject the certificate_."
//
// TODO: rustls/webpki#19
//
// Rejection is achieved by not matching any PermittedSubtrees, and matching all
// ExcludedSubtrees.
match subtrees {
Subtrees::PermittedSubtrees => false,
Subtrees::ExcludedSubtrees => true,
},
),
(GeneralName::IpAddress(name), GeneralName::IpAddress(base)) => {
ip_address::presented_id_matches_constraint(name, base)
}
// RFC 4280 says "If a name constraints extension that is marked as
// critical imposes constraints on a particular name form, and an
// instance of that name form appears in the subject field or
// subjectAltName extension of a subsequent certificate, then the
// application MUST either process the constraint or reject the
// certificate." Later, the CABForum agreed to support non-critical
// constraints, so it is important to reject the cert without
// considering whether the name constraint it critical.
(GeneralName::Unsupported(name_tag), GeneralName::Unsupported(base_tag))
if name_tag == base_tag =>
{
Err(Error::NameConstraintViolation)
}
_ => {
// mismatch between constraint and name types; continue with current
// name and next constraint
continue;
}
};
match (subtrees, matches) {
(Subtrees::PermittedSubtrees, Ok(true)) => {
has_permitted_subtrees_match = true;
}
(Subtrees::PermittedSubtrees, Ok(false)) => {
has_permitted_subtrees_mismatch = true;
}
(Subtrees::ExcludedSubtrees, Ok(true)) => {
return Some(Err(Error::NameConstraintViolation));
}
(Subtrees::ExcludedSubtrees, Ok(false)) => (),
(_, Err(err)) => return Some(Err(err)),
}
}
if has_permitted_subtrees_mismatch && !has_permitted_subtrees_match {
// If there was any entry of the given type in permittedSubtrees, then
// it required that at least one of them must match. Since none of them
// did, we have a failure.
return Some(Err(Error::NameConstraintViolation));
}
}
None
}
#[derive(Clone, Copy)]
enum Subtrees {
PermittedSubtrees,
ExcludedSubtrees,
}
pub(crate) struct NameIterator<'a> {
subject_alt_name: Option<untrusted::Reader<'a>>,
}
impl<'a> NameIterator<'a> {
pub(crate) fn new(subject_alt_name: Option<untrusted::Input<'a>>) -> Self {
Self {
subject_alt_name: subject_alt_name.map(untrusted::Reader::new),
}
}
}
impl<'a> Iterator for NameIterator<'a> {
type Item = Result<GeneralName<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
let subject_alt_name = self.subject_alt_name.as_mut()?;
// https://bugzilla.mozilla.org/show_bug.cgi?id=1143085: An empty
// subjectAltName is not legal, but some certificates have an empty
// subjectAltName. Since we don't support CN-IDs, the certificate
// will be rejected either way, but checking `at_end` before
// attempting to parse the first entry allows us to return a better
// error code.
if subject_alt_name.at_end() {
self.subject_alt_name = None;
return None;
}
let err = match GeneralName::from_der(subject_alt_name) {
Ok(name) => return Some(Ok(name)),
Err(err) => err,
};
// Make sure we don't yield any items after this error.
self.subject_alt_name = None;
Some(Err(err))
}
}
// It is *not* valid to derive `Eq`, `PartialEq, etc. for this type. In
// particular, for the types of `GeneralName`s that we don't understand, we
// don't even store the value. Also, the meaning of a `GeneralName` in a name
// constraint is different than the meaning of the identically-represented
// `GeneralName` in other contexts.
#[derive(Clone, Copy)]
pub(crate) enum GeneralName<'a> {
DnsName(untrusted::Input<'a>),
DirectoryName,
IpAddress(untrusted::Input<'a>),
UniformResourceIdentifier(untrusted::Input<'a>),
// The value is the `tag & ~(der::CONTEXT_SPECIFIC | der::CONSTRUCTED)` so
// that the name constraint checking matches tags regardless of whether
// those bits are set.
Unsupported(u8),
}
impl<'a> FromDer<'a> for GeneralName<'a> {
fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
use GeneralName::*;
use der::{CONSTRUCTED, CONTEXT_SPECIFIC};
#[allow(clippy::identity_op)]
const OTHER_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 0;
const RFC822_NAME_TAG: u8 = CONTEXT_SPECIFIC | 1;
const DNS_NAME_TAG: u8 = CONTEXT_SPECIFIC | 2;
const X400_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 3;
const DIRECTORY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 4;
const EDI_PARTY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 5;
const UNIFORM_RESOURCE_IDENTIFIER_TAG: u8 = CONTEXT_SPECIFIC | 6;
const IP_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | 7;
const REGISTERED_ID_TAG: u8 = CONTEXT_SPECIFIC | 8;
let (tag, value) = der::read_tag_and_get_value(reader)?;
Ok(match tag {
DNS_NAME_TAG => DnsName(value),
DIRECTORY_NAME_TAG => DirectoryName,
IP_ADDRESS_TAG => IpAddress(value),
UNIFORM_RESOURCE_IDENTIFIER_TAG => UniformResourceIdentifier(value),
OTHER_NAME_TAG | RFC822_NAME_TAG | X400_ADDRESS_TAG | EDI_PARTY_NAME_TAG
| REGISTERED_ID_TAG => Unsupported(tag & !(CONTEXT_SPECIFIC | CONSTRUCTED)),
_ => return Err(Error::BadDer),
})
}
const TYPE_ID: DerTypeId = DerTypeId::GeneralName;
}
#[cfg(feature = "alloc")]
impl fmt::Debug for GeneralName<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GeneralName::DnsName(name) => write!(
f,
"DnsName(\"{}\")",
String::from_utf8_lossy(name.as_slice_less_safe())
),
GeneralName::DirectoryName => write!(f, "DirectoryName"),
GeneralName::IpAddress(ip) => {
write!(f, "IpAddress({:?})", IpAddrSlice(ip.as_slice_less_safe()))
}
GeneralName::UniformResourceIdentifier(uri) => write!(
f,
"UniformResourceIdentifier(\"{}\")",
String::from_utf8_lossy(uri.as_slice_less_safe())
),
GeneralName::Unsupported(tag) => write!(f, "Unsupported(0x{tag:02x})"),
}
}
}
#[cfg(feature = "alloc")]
struct IpAddrSlice<'a>(&'a [u8]);
#[cfg(feature = "alloc")]
impl fmt::Debug for IpAddrSlice<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0.len() {
4 => {
let mut first = true;
for byte in self.0 {
match first {
true => first = false,
false => f.write_str(".")?,
}
write!(f, "{byte}")?;
}
Ok(())
}
16 => {
let (mut first, mut skipping) = (true, false);
for group in self.0.chunks_exact(2) {
match (first, group == [0, 0], skipping) {
(true, _, _) => first = false,
(false, false, false) => f.write_str(":")?,
(false, true, _) => {
skipping = true;
continue;
}
(false, false, true) => {
skipping = false;
f.write_str("::")?;
}
}
if group[0] != 0 {
write!(f, "{:x}", group[0])?;
}
match group[0] {
0 => write!(f, "{:x}", group[1])?,
_ => write!(f, "{:02x}", group[1])?,
}
}
Ok(())
}
_ => {
f.write_str("[invalid: ")?;
let mut first = true;
for byte in self.0 {
match first {
true => first = false,
false => f.write_str(", ")?,
}
write!(f, "{byte:02x}")?;
}
f.write_str("]")
}
}
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::*;
#[test]
fn debug_names() {
assert_eq!(
format!(
"{:?}",
GeneralName::DnsName(untrusted::Input::from(b"example.com"))
),
"DnsName(\"example.com\")"
);
assert_eq!(format!("{:?}", GeneralName::DirectoryName), "DirectoryName");
assert_eq!(
format!(
"{:?}",
GeneralName::IpAddress(untrusted::Input::from(&[192, 0, 2, 1][..]))
),
"IpAddress(192.0.2.1)"
);
assert_eq!(
format!(
"{:?}",
GeneralName::IpAddress(untrusted::Input::from(
&[0x20, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x0d, 0xb8][..]
))
),
"IpAddress(2001::db8)"
);
assert_eq!(
format!(
"{:?}",
GeneralName::IpAddress(untrusted::Input::from(&[1, 2, 3, 4, 5, 6][..]))
),
"IpAddress([invalid: 01, 02, 03, 04, 05, 06])"
);
assert_eq!(
format!(
"{:?}",
GeneralName::UniformResourceIdentifier(untrusted::Input::from(
b"https://example.com"
))
),
"UniformResourceIdentifier(\"https://example.com\")"
);
assert_eq!(
format!("{:?}", GeneralName::Unsupported(0x66)),
"Unsupported(0x66)"
);
}
#[test]
fn name_iter_end_after_error() {
let input = untrusted::Input::from(&[0x30]);
let mut iter = NameIterator::new(Some(input));
assert_eq!(iter.next().unwrap().unwrap_err(), Error::BadDer);
assert!(iter.next().is_none());
}
}

283
vendor/rustls-webpki/src/time.rs vendored Normal file
View File

@@ -0,0 +1,283 @@
// Copyright 2015-2016 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//! Conversions into the library's time type.
use core::time::Duration;
use pki_types::UnixTime;
use crate::der::{self, FromDer, Tag};
use crate::error::{DerTypeId, Error};
impl<'a> FromDer<'a> for UnixTime {
fn from_der(input: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
let is_utc_time = input.peek(Tag::UTCTime.into());
let expected_tag = if is_utc_time {
Tag::UTCTime
} else {
Tag::GeneralizedTime
};
fn read_digit(inner: &mut untrusted::Reader<'_>) -> Result<u64, Error> {
const DIGIT: core::ops::RangeInclusive<u8> = b'0'..=b'9';
let b = inner.read_byte().map_err(|_| Error::BadDerTime)?;
if DIGIT.contains(&b) {
return Ok(u64::from(b - DIGIT.start()));
}
Err(Error::BadDerTime)
}
fn read_two_digits(
inner: &mut untrusted::Reader<'_>,
min: u64,
max: u64,
) -> Result<u64, Error> {
let hi = read_digit(inner)?;
let lo = read_digit(inner)?;
let value = (hi * 10) + lo;
if value < min || value > max {
return Err(Error::BadDerTime);
}
Ok(value)
}
der::nested(
input,
expected_tag,
Error::TrailingData(Self::TYPE_ID),
|value| {
let (year_hi, year_lo) = if is_utc_time {
let lo = read_two_digits(value, 0, 99)?;
let hi = if lo >= 50 { 19 } else { 20 };
(hi, lo)
} else {
let hi = read_two_digits(value, 0, 99)?;
let lo = read_two_digits(value, 0, 99)?;
(hi, lo)
};
let year = (year_hi * 100) + year_lo;
let month = read_two_digits(value, 1, 12)?;
let days_in_month = days_in_month(year, month);
let day_of_month = read_two_digits(value, 1, days_in_month)?;
let hours = read_two_digits(value, 0, 23)?;
let minutes = read_two_digits(value, 0, 59)?;
let seconds = read_two_digits(value, 0, 59)?;
let time_zone = value.read_byte().map_err(|_| Error::BadDerTime)?;
if time_zone != b'Z' {
return Err(Error::BadDerTime);
}
time_from_ymdhms_utc(year, month, day_of_month, hours, minutes, seconds)
},
)
}
const TYPE_ID: DerTypeId = DerTypeId::Time;
}
pub(crate) fn time_from_ymdhms_utc(
year: u64,
month: u64,
day_of_month: u64,
hours: u64,
minutes: u64,
seconds: u64,
) -> Result<UnixTime, Error> {
let days_before_year_since_unix_epoch = days_before_year_since_unix_epoch(year)?;
const JAN: u64 = 31;
let feb = days_in_feb(year);
const MAR: u64 = 31;
const APR: u64 = 30;
const MAY: u64 = 31;
const JUN: u64 = 30;
const JUL: u64 = 31;
const AUG: u64 = 31;
const SEP: u64 = 30;
const OCT: u64 = 31;
const NOV: u64 = 30;
let days_before_month_in_year = match month {
1 => 0,
2 => JAN,
3 => JAN + feb,
4 => JAN + feb + MAR,
5 => JAN + feb + MAR + APR,
6 => JAN + feb + MAR + APR + MAY,
7 => JAN + feb + MAR + APR + MAY + JUN,
8 => JAN + feb + MAR + APR + MAY + JUN + JUL,
9 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG,
10 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP,
11 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT,
12 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT + NOV,
_ => unreachable!(), // `read_two_digits` already bounds-checked it.
};
let days_before =
days_before_year_since_unix_epoch + days_before_month_in_year + day_of_month - 1;
let seconds_since_unix_epoch =
(days_before * 24 * 60 * 60) + (hours * 60 * 60) + (minutes * 60) + seconds;
Ok(UnixTime::since_unix_epoch(Duration::from_secs(
seconds_since_unix_epoch,
)))
}
fn days_before_year_since_unix_epoch(year: u64) -> Result<u64, Error> {
// We don't support dates before January 1, 1970 because that is the
// Unix epoch. It is likely that other software won't deal well with
// certificates that have dates before the epoch.
if year < UNIX_EPOCH_YEAR {
return Err(Error::BadDerTime);
}
let days_before_year_ad = days_before_year_ad(year);
debug_assert!(days_before_year_ad >= DAYS_BEFORE_UNIX_EPOCH_AD);
Ok(days_before_year_ad - DAYS_BEFORE_UNIX_EPOCH_AD)
}
const UNIX_EPOCH_YEAR: u64 = 1970;
fn days_before_year_ad(year: u64) -> u64 {
((year - 1) * 365)
+ ((year - 1) / 4) // leap years are every 4 years,
- ((year - 1) / 100) // except years divisible by 100,
+ ((year - 1) / 400) // except years divisible by 400.
}
pub(crate) fn days_in_month(year: u64, month: u64) -> u64 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => days_in_feb(year),
_ => unreachable!(), // `read_two_digits` already bounds-checked it.
}
}
fn days_in_feb(year: u64) -> u64 {
if (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) {
29
} else {
28
}
}
/// All the days up to and including 1969, plus the 477 leap days since AD began
/// (calculated in Gregorian rules).
const DAYS_BEFORE_UNIX_EPOCH_AD: u64 = 1969 * 365 + 477;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_days_before_unix_epoch() {
assert_eq!(
DAYS_BEFORE_UNIX_EPOCH_AD,
days_before_year_ad(UNIX_EPOCH_YEAR)
);
}
#[test]
fn test_days_before_year_since_unix_epoch() {
assert_eq!(Ok(0), days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR));
assert_eq!(
Ok(365),
days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR + 1)
);
assert_eq!(
Err(Error::BadDerTime),
days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR - 1)
);
}
#[test]
fn test_days_in_month() {
assert_eq!(days_in_month(2017, 1), 31);
assert_eq!(days_in_month(2017, 2), 28);
assert_eq!(days_in_month(2017, 3), 31);
assert_eq!(days_in_month(2017, 4), 30);
assert_eq!(days_in_month(2017, 5), 31);
assert_eq!(days_in_month(2017, 6), 30);
assert_eq!(days_in_month(2017, 7), 31);
assert_eq!(days_in_month(2017, 8), 31);
assert_eq!(days_in_month(2017, 9), 30);
assert_eq!(days_in_month(2017, 10), 31);
assert_eq!(days_in_month(2017, 11), 30);
assert_eq!(days_in_month(2017, 12), 31);
// leap cases
assert_eq!(days_in_month(2000, 2), 29);
assert_eq!(days_in_month(2004, 2), 29);
assert_eq!(days_in_month(2016, 2), 29);
assert_eq!(days_in_month(2100, 2), 28);
}
#[test]
fn test_time_from_ymdhms_utc() {
// 1969-12-31 00:00:00
assert_eq!(
Err(Error::BadDerTime),
time_from_ymdhms_utc(UNIX_EPOCH_YEAR - 1, 1, 1, 0, 0, 0)
);
// 1969-12-31 23:59:59
assert_eq!(
Err(Error::BadDerTime),
time_from_ymdhms_utc(UNIX_EPOCH_YEAR - 1, 12, 31, 23, 59, 59)
);
// 1970-01-01 00:00:00
assert_eq!(
UnixTime::since_unix_epoch(Duration::from_secs(0)),
time_from_ymdhms_utc(UNIX_EPOCH_YEAR, 1, 1, 0, 0, 0).unwrap()
);
// 1970-01-01 00:00:01
assert_eq!(
UnixTime::since_unix_epoch(Duration::from_secs(1)),
time_from_ymdhms_utc(UNIX_EPOCH_YEAR, 1, 1, 0, 0, 1).unwrap()
);
// 1971-01-01 00:00:00
assert_eq!(
UnixTime::since_unix_epoch(Duration::from_secs(365 * 86400)),
time_from_ymdhms_utc(UNIX_EPOCH_YEAR + 1, 1, 1, 0, 0, 0).unwrap()
);
// year boundary
assert_eq!(
UnixTime::since_unix_epoch(Duration::from_secs(1_483_228_799)),
time_from_ymdhms_utc(2016, 12, 31, 23, 59, 59).unwrap()
);
assert_eq!(
UnixTime::since_unix_epoch(Duration::from_secs(1_483_228_800)),
time_from_ymdhms_utc(2017, 1, 1, 0, 0, 0).unwrap()
);
// not a leap year
assert_eq!(
UnixTime::since_unix_epoch(Duration::from_secs(1_492_449_162)),
time_from_ymdhms_utc(2017, 4, 17, 17, 12, 42).unwrap()
);
// leap year, post-feb
assert_eq!(
UnixTime::since_unix_epoch(Duration::from_secs(1_460_913_162)),
time_from_ymdhms_utc(2016, 4, 17, 17, 12, 42).unwrap()
);
}
}

103
vendor/rustls-webpki/src/trust_anchor.rs vendored Normal file
View File

@@ -0,0 +1,103 @@
use pki_types::{CertificateDer, TrustAnchor};
use crate::cert::{Cert, lenient_certificate_serial_number};
use crate::der;
use crate::error::{DerTypeId, Error};
/// Interprets the given pre-validated DER-encoded certificate as a `TrustAnchor`.
///
/// This function extracts the components of a trust anchor (see [RFC 5280 6.1.1]) from
/// an X.509 certificate obtained from a source trusted to have appropriately validated
/// the subject name, public key, and name constraints in the certificate, for example your
/// operating system's trust store.
///
/// No additional checks on the content of the certificate, including whether it is self-signed,
/// or has a basic constraints extension indicating the `cA` boolean is true, will be performed.
/// [RFC 5280 6.2] notes:
/// > Implementations that use self-signed certificates to specify trust
/// > anchor information are free to process or ignore such information.
///
/// This function is intended for users constructing `TrustAnchor`'s from existing trust stores
/// that express trust anchors as X.509 certificates. It should **not** be used to treat an
/// end-entity certificate as a `TrustAnchor` in an effort to validate the same end-entity
/// certificate during path building. Webpki has no support for self-signed certificates.
///
/// [RFC 5280 6.1.1]: <https://datatracker.ietf.org/doc/html/rfc5280#section-6.1.1>
/// [RFC 5280 6.2]: <https://www.rfc-editor.org/rfc/rfc5280#section-6.2>
pub fn anchor_from_trusted_cert<'a>(
cert: &'a CertificateDer<'a>,
) -> Result<TrustAnchor<'a>, Error> {
let cert_der = untrusted::Input::from(cert.as_ref());
// v1 certificates will result in `Error::BadDer` because `parse_cert` will
// expect a version field that isn't there. In that case, try to parse the
// certificate using a special parser for v1 certificates. Notably, that
// parser doesn't allow extensions, so there's no need to worry about
// embedded name constraints in a v1 certificate.
match Cert::from_der(cert_der) {
Ok(cert) => Ok(TrustAnchor::from(cert)),
Err(Error::UnsupportedCertVersion) => {
extract_trust_anchor_from_v1_cert_der(cert_der).or(Err(Error::BadDer))
}
Err(err) => Err(err),
}
}
/// Parses a v1 certificate directly into a TrustAnchor.
fn extract_trust_anchor_from_v1_cert_der(
cert_der: untrusted::Input<'_>,
) -> Result<TrustAnchor<'_>, Error> {
// X.509 Certificate: https://tools.ietf.org/html/rfc5280#section-4.1.
cert_der.read_all(Error::BadDer, |cert_der| {
der::nested(
cert_der,
der::Tag::Sequence,
Error::TrailingData(DerTypeId::TrustAnchorV1),
|cert_der| {
let anchor = der::nested(
cert_der,
der::Tag::Sequence,
Error::TrailingData(DerTypeId::TrustAnchorV1TbsCertificate),
|tbs| {
// The version number field does not appear in v1 certificates.
lenient_certificate_serial_number(tbs)?;
skip(tbs, der::Tag::Sequence)?; // signature.
skip(tbs, der::Tag::Sequence)?; // issuer.
skip(tbs, der::Tag::Sequence)?; // validity.
let subject = der::expect_tag(tbs, der::Tag::Sequence)?;
let spki = der::expect_tag(tbs, der::Tag::Sequence)?;
Ok(TrustAnchor {
subject: subject.as_slice_less_safe().into(),
subject_public_key_info: spki.as_slice_less_safe().into(),
name_constraints: None,
})
},
);
// read and discard signatureAlgorithm + signature
skip(cert_der, der::Tag::Sequence)?;
skip(cert_der, der::Tag::BitString)?;
anchor
},
)
})
}
impl<'a> From<Cert<'a>> for TrustAnchor<'a> {
fn from(cert: Cert<'a>) -> Self {
Self {
subject: cert.subject.as_slice_less_safe().into(),
subject_public_key_info: cert.spki.as_slice_less_safe().into(),
name_constraints: cert
.name_constraints
.map(|nc| nc.as_slice_less_safe().into()),
}
}
}
fn skip(input: &mut untrusted::Reader<'_>, tag: der::Tag) -> Result<(), Error> {
der::expect_tag(input, tag).map(|_| ())
}

1373
vendor/rustls-webpki/src/verify_cert.rs vendored Normal file

File diff suppressed because it is too large Load Diff

111
vendor/rustls-webpki/src/x509.rs vendored Normal file
View File

@@ -0,0 +1,111 @@
// Copyright 2015 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
use crate::der::{self, CONSTRUCTED, CONTEXT_SPECIFIC, DerIterator, FromDer};
use crate::error::{DerTypeId, Error};
use crate::subject_name::GeneralName;
pub(crate) struct Extension<'a> {
pub(crate) critical: bool,
pub(crate) id: untrusted::Input<'a>,
pub(crate) value: untrusted::Input<'a>,
}
impl Extension<'_> {
pub(crate) fn unsupported(&self) -> Result<(), Error> {
match self.critical {
true => Err(Error::UnsupportedCriticalExtension),
false => Ok(()),
}
}
}
impl<'a> FromDer<'a> for Extension<'a> {
fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
let id = der::expect_tag(reader, der::Tag::OID)?;
let critical = bool::from_der(reader)?;
let value = der::expect_tag(reader, der::Tag::OctetString)?;
Ok(Extension {
id,
critical,
value,
})
}
const TYPE_ID: DerTypeId = DerTypeId::Extension;
}
pub(crate) fn set_extension_once<T>(
destination: &mut Option<T>,
parser: impl Fn() -> Result<T, Error>,
) -> Result<(), Error> {
match destination {
// The extension value has already been set, indicating that we encountered it
// more than once in our serialized data. That's invalid!
Some(..) => Err(Error::ExtensionValueInvalid),
None => {
*destination = Some(parser()?);
Ok(())
}
}
}
pub(crate) fn remember_extension(
extension: &Extension<'_>,
mut handler: impl FnMut(u8) -> Result<(), Error>,
) -> Result<(), Error> {
// ISO arc for standard certificate and CRL extensions.
// https://www.rfc-editor.org/rfc/rfc5280#appendix-A.2
static ID_CE: [u8; 2] = oid![2, 5, 29];
if extension.id.len() != ID_CE.len() + 1
|| !extension.id.as_slice_less_safe().starts_with(&ID_CE)
{
return extension.unsupported();
}
// safety: we verify len is non-zero and has the correct prefix above.
let last_octet = *extension.id.as_slice_less_safe().last().unwrap();
handler(last_octet)
}
/// A certificate revocation list (CRL) distribution point name, describing a source of
/// CRL information for a given certificate as described in RFC 5280 section 4.2.3.13[^1].
///
/// [^1]: <https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13>
pub(crate) enum DistributionPointName<'a> {
/// The distribution point name is a relative distinguished name, relative to the CRL issuer.
NameRelativeToCrlIssuer,
/// The distribution point name is a sequence of [GeneralName] items.
FullName(DerIterator<'a, GeneralName<'a>>),
}
impl<'a> FromDer<'a> for DistributionPointName<'a> {
fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
// RFC 5280 section §4.2.1.13:
// When the distributionPoint field is present, it contains either a
// SEQUENCE of general names or a single value, nameRelativeToCRLIssuer
const FULL_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED;
const NAME_RELATIVE_TO_CRL_ISSUER_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 1;
let (tag, value) = der::read_tag_and_get_value(reader)?;
match tag {
FULL_NAME_TAG => Ok(DistributionPointName::FullName(DerIterator::new(value))),
NAME_RELATIVE_TO_CRL_ISSUER_TAG => Ok(DistributionPointName::NameRelativeToCrlIssuer),
_ => Err(Error::BadDer),
}
}
const TYPE_ID: DerTypeId = DerTypeId::DistributionPointName;
}