diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:59:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:59:35 +0000 |
commit | d1b2d29528b7794b41e66fc2136e395a02f8529b (patch) | |
tree | a4a17504b260206dec3cf55b2dca82929a348ac2 /vendor/security-framework | |
parent | Releasing progress-linux version 1.72.1+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.tar.xz rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.zip |
Merging upstream version 1.73.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/security-framework')
45 files changed, 10775 insertions, 0 deletions
diff --git a/vendor/security-framework/.cargo-checksum.json b/vendor/security-framework/.cargo-checksum.json new file mode 100644 index 000000000..2a1030fb0 --- /dev/null +++ b/vendor/security-framework/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.lock":"db386751ea759fd41dd3e01f50fbde8abc3abea6b686bc6d7e5e8d2288e255cb","Cargo.toml":"223949daaf6dd1f112046f8e4a903c262e691aad7fa4f4a85ca68d232df73893","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"91e934255ba3b2f21103d68c5581c23ef34aa95c4628e4405b8c901935e11c69","README.md":"ff1d022f35ff76531a361ad421ba7ee61ce186dc6516f72fc819ec4b2a5567a4","THIRD_PARTY":"522fccc56dcbadbe90ab5524a17b8127cde3f951c30c17002641caf44959c22a","examples/client.rs":"63cbec24a63ceb2ab7e37e7dce1122797183cdab7cf65158c1f5f1eb6fa5f904","examples/find_internet_password.rs":"e8beeabe59087a3d11cd0a75a7dbcddf673e40c28497f2b7e1a11fb121cb05c2","examples/set_internet_password.rs":"bf79fb83607bd196e39614703e6c77c02974d171d4bb2a1dc21fba0ec1100ac9","src/access_control.rs":"3f7547747d78c11f65efd068c69663f8ca1fd7147e455c7c685cad953f3cabf8","src/authorization.rs":"84be74a6676e2a82d4be0131cd624c6f6598a087f7e1cad6b8374b5f6ad45e93","src/base.rs":"aa375e8ff01ad0f8268967c896ecd7562f8c0b98913aead6012a17f325d13d94","src/certificate.rs":"016e09cd6cf523d0e3a3f89123e63f21dcbf4d00e5feb4bb770bda630975440d","src/cipher_suite.rs":"9395ee4ea08e04e1b28ece703ab0854907b997a6cc7cb4165bb6fbcc0e6ad861","src/dlsym.rs":"0f66bb4843e4c82fe597168ccd153a7275d18ab849df18ce57210ecf43ffe6ff","src/identity.rs":"f6c1c5f7744aa5e41dbeac4c602dd32a52f35f351df2c459346415b9c4df3e0b","src/import_export.rs":"0fe9fd1c411cbacf1b821965958f53d88c22320171efb37ebc9f5f4660803ac7","src/item.rs":"87cd13755ed9a4f17c731d6b64199450fbe8fe6b64b6e2aa437c57b69c2ad739","src/key.rs":"bf532f55b1097799bf046be4fbb3f8a413452517644ec8900dda183eb31a2490","src/lib.rs":"1ead1568d2e2ccbb3f0cd4efe2d246623a06b791c1141b6fbbabc561a72f4fc3","src/os/macos/access.rs":"b03fa782757510d683b1123d27be37b522fc465ec2b5ec3183085f9417c587d5","src/os/macos/certificate.rs":"6fe4966256cea1430c431fcaae870e8eab99e6b76b6ed74587bd595d6bc92b49","src/os/macos/certificate_oids.rs":"f36f07134d1f035f9fabc62aaf8a4be3f542a5d97dfbd866010e2056216e2e30","src/os/macos/code_signing.rs":"8097e52baaf8907f42f21dd881778a78db8df22d0cba6c6102353abc4cfde69b","src/os/macos/digest_transform.rs":"25b6d2e00aad81785ae032cd3ea73a4e4c560a45f99ea8f1c58b26b2d5e21f70","src/os/macos/encrypt_transform.rs":"730a54e9d77ce41626838dce2e2a6ad637cb9810eacc38c50b0db4755cdfabaa","src/os/macos/identity.rs":"4bc211bfc97d873d91ac046100867ab2c3dbb0b51bfb081cba28a4fde0bac337","src/os/macos/import_export.rs":"3871117749d81ad69067edf20c35f46afec389fe596350f7b584233112aa3f29","src/os/macos/item.rs":"46e31889d7648d461fa35a1f87a1c3f45c57f85220c25cfa6480097ac7101b85","src/os/macos/key.rs":"1ebde4066795a80857987d471c9e43d62eedf1fa81da963e6ca73f140dc03412","src/os/macos/keychain.rs":"1bf7a02f545823f72a91928e5664bd5dc63425386a515818e50e22000fbbeb18","src/os/macos/keychain_item.rs":"fb2352bf631dcc4a8ebbd4377a1d5ccdfb01d6525dcaf0147a7d5989be8c2e42","src/os/macos/mod.rs":"28e68e8bbc4053db35dd0455315f8c70999c21a8db1771878285fe293d669d3c","src/os/macos/passwords.rs":"084068ace872ef57348b92ecafd9877f470dd018dfb621e926684175d8363c31","src/os/macos/secure_transport.rs":"0671624515400dc0e31a76ddcd7091387bc772e531058d111b98e74195979abf","src/os/macos/transform.rs":"00e25dd4e729883f4bc7b07e003fe7e7f1e4957dcadd7f72f712835cb59e58d9","src/os/mod.rs":"287f2cd9a3b93f5db523974ac5f395cd6e5d1245d96c9782300d35bc5762c2f3","src/passwords.rs":"ca9edd609765891b61712ccc1af5fccbf43e815f72cf59863c67d932181e776c","src/passwords_options.rs":"b7b8d43f84c33e4f3d7152d70906c331e314bc9f887118d7f16dbedd9085e27e","src/policy.rs":"89149de585c80ec928f81eb0ba1a7d8d02411a067671ef8372c738bd8d062e0e","src/random.rs":"28cba598834f47ace69a2e7f69469809278c96178422ee9e2b4a2bb3e418cf87","src/secure_transport.rs":"6ee6188deebcf42a496c4586b9e5c3463d1328b6d6a90378031bd4acbf777ab4","src/trust.rs":"c6fd8cc0515020efe68ef5ee8e70132941c27ebff920c79f43a9f8c61a3b35b7","src/trust_settings.rs":"0abffec991509b03ac40936985fd9e9122040a0f83d65373fa45ae114983be6b"},"package":"05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"}
\ No newline at end of file diff --git a/vendor/security-framework/Cargo.lock b/vendor/security-framework/Cargo.lock new file mode 100644 index 000000000..f1c9a3c30 --- /dev/null +++ b/vendor/security-framework/Cargo.lock @@ -0,0 +1,705 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "data-encoding" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" + +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "linux-raw-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "env_logger", + "hex", + "libc", + "log", + "num-bigint", + "security-framework-sys", + "tempfile", + "time", + "x509-parser", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "time" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "x509-parser" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab0c2f54ae1d92f4fcb99c0b7ccf0b1e3451cbd395e5f115ccbdbcb18d4f634" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] diff --git a/vendor/security-framework/Cargo.toml b/vendor/security-framework/Cargo.toml new file mode 100644 index 000000000..08804452f --- /dev/null +++ b/vendor/security-framework/Cargo.toml @@ -0,0 +1,133 @@ +# 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.60" +name = "security-framework" +version = "2.9.2" +authors = [ + "Steven Fackler <sfackler@gmail.com>", + "Kornel <kornel@geekhood.net>", +] +exclude = ["test/*"] +description = "Security.framework bindings for macOS and iOS" +homepage = "https://lib.rs/crates/security_framework" +documentation = "https://docs.rs/security_framework" +readme = "README.md" +keywords = [ + "iOS", + "TLS", + "SSL", + "crypto", + "keychain", +] +categories = [ + "os::macos-apis", + "cryptography", + "api-bindings", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/kornelski/rust-security-framework" +resolver = "1" + +[package.metadata.docs.rs] +targets = [ + "x86_64-apple-darwin", + "aarch64-apple-ios", +] + +[[example]] +name = "client" + +[[example]] +name = "find_internet_password" + +[[example]] +name = "set_internet_password" + +[dependencies.bitflags] +version = "1.3.2" + +[dependencies.core-foundation] +version = "0.9.3" + +[dependencies.core-foundation-sys] +version = "0.8.3" + +[dependencies.libc] +version = "0.2.139" + +[dependencies.log] +version = "0.4.17" +optional = true + +[dependencies.num-bigint] +version = "0.4.3" +optional = true + +[dependencies.security-framework-sys] +version = "2.9.0" +default-features = false + +[dev-dependencies.env_logger] +version = "0.10.0" + +[dev-dependencies.hex] +version = "0.4.3" + +[dev-dependencies.tempfile] +version = "3.3.0" + +[dev-dependencies.time] +version = "0.3.17" + +[dev-dependencies.x509-parser] +version = "0.15.0" + +[features] +OSX_10_10 = [ + "OSX_10_9", + "security-framework-sys/OSX_10_10", +] +OSX_10_11 = [ + "OSX_10_10", + "security-framework-sys/OSX_10_11", +] +OSX_10_12 = [ + "OSX_10_11", + "security-framework-sys/OSX_10_12", +] +OSX_10_13 = [ + "OSX_10_12", + "security-framework-sys/OSX_10_13", + "alpn", + "session-tickets", + "serial-number-bigint", +] +OSX_10_14 = [ + "OSX_10_13", + "security-framework-sys/OSX_10_14", +] +OSX_10_15 = [ + "OSX_10_14", + "security-framework-sys/OSX_10_15", +] +OSX_10_9 = ["security-framework-sys/OSX_10_9"] +alpn = [] +default = ["OSX_10_9"] +job-bless = [] +nightly = [] +serial-number-bigint = ["dep:num-bigint"] +session-tickets = [] + +[badges.maintenance] +status = "looking-for-maintainer" diff --git a/vendor/security-framework/LICENSE-APACHE b/vendor/security-framework/LICENSE-APACHE new file mode 100644 index 000000000..16fe87b06 --- /dev/null +++ b/vendor/security-framework/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/security-framework/LICENSE-MIT b/vendor/security-framework/LICENSE-MIT new file mode 100644 index 000000000..755eccdb0 --- /dev/null +++ b/vendor/security-framework/LICENSE-MIT @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 Steven Fackler + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/security-framework/README.md b/vendor/security-framework/README.md new file mode 100644 index 000000000..0da3780d0 --- /dev/null +++ b/vendor/security-framework/README.md @@ -0,0 +1,22 @@ +# macOS/iOS Security framework for Rust + +[![Latest Version](https://img.shields.io/crates/v/security-framework.svg)](https://lib.rs/crates/security-framework) + +[Documentation](https://docs.rs/security-framework) + +Bindings to the Apple's `Security.framework`. Allows use of TLS and Keychain from Rust. + +## License + +Licensed under either of + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you shall be dual licensed as above, without any +additional terms or conditions. + diff --git a/vendor/security-framework/THIRD_PARTY b/vendor/security-framework/THIRD_PARTY new file mode 100644 index 000000000..fee47c24a --- /dev/null +++ b/vendor/security-framework/THIRD_PARTY @@ -0,0 +1,103 @@ +This project contains documentation adapted from Apple Inc.'s Security Framework +under the following license: + +APPLE PUBLIC SOURCE LICENSE +Version 2.0 - August 6, 2003 + +Please read this License carefully before downloading this software. By downloading or using this software, you are agreeing to be bound by the terms of this License. If you do not or cannot agree to the terms of this License, please do not download or use the software. + +Apple Note: In January 2007, Apple changed its corporate name from "Apple Computer, Inc." to "Apple Inc." This change has been reflected below and copyright years updated, but no other changes have been made to the APSL 2.0. + +1. General; Definitions. This License applies to any program or other work which Apple Inc. ("Apple") makes publicly available and which contains a notice placed by Apple identifying such program or work as "Original Code" and stating that it is subject to the terms of this Apple Public Source License version 2.0 ("License"). As used in this License: + +1.1 "Applicable Patent Rights" mean: (a) in the case where Apple is the grantor of rights, (i) claims of patents that are now or hereafter acquired, owned by or assigned to Apple and (ii) that cover subject matter contained in the Original Code, but only to the extent necessary to use, reproduce and/or distribute the Original Code without infringement; and (b) in the case where You are the grantor of rights, (i) claims of patents that are now or hereafter acquired, owned by or assigned to You and (ii) that cover subject matter in Your Modifications, taken alone or in combination with Original Code. + +1.2 "Contributor" means any person or entity that creates or contributes to the creation of Modifications. + +1.3 "Covered Code" means the Original Code, Modifications, the combination of Original Code and any Modifications, and/or any respective portions thereof. + +1.4 "Externally Deploy" means: (a) to sublicense, distribute or otherwise make Covered Code available, directly or indirectly, to anyone other than You; and/or (b) to use Covered Code, alone or as part of a Larger Work, in any way to provide a service, including but not limited to delivery of content, through electronic communication with a client other than You. + +1.5 "Larger Work" means a work which combines Covered Code or portions thereof with code not governed by the terms of this License. + +1.6 "Modifications" mean any addition to, deletion from, and/or change to, the substance and/or structure of the Original Code, any previous Modifications, the combination of Original Code and any previous Modifications, and/or any respective portions thereof. When code is released as a series of files, a Modification is: (a) any addition to or deletion from the contents of a file containing Covered Code; and/or (b) any new file or other representation of computer program statements that contains any part of Covered Code. + +1.7 "Original Code" means (a) the Source Code of a program or other work as originally made available by Apple under this License, including the Source Code of any updates or upgrades to such programs or works made available by Apple under this License, and that has been expressly identified by Apple as such in the header file(s) of such work; and (b) the object code compiled from such Source Code and originally made available by Apple under this License + +1.8 "Source Code" means the human readable form of a program or other work that is suitable for making modifications to it, including all modules it contains, plus any associated interface definition files, scripts used to control compilation and installation of an executable (object code). + +1.9 "You" or "Your" means an individual or a legal entity exercising rights under this License. For legal entities, "You" or "Your" includes any entity which controls, is controlled by, or is under common control with, You, where "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of fifty percent (50%) or more of the outstanding shares or beneficial ownership of such entity. + +2. Permitted Uses; Conditions & Restrictions. Subject to the terms and conditions of this License, Apple hereby grants You, effective on the date You accept this License and download the Original Code, a world-wide, royalty-free, non-exclusive license, to the extent of Apple's Applicable Patent Rights and copyrights covering the Original Code, to do the following: + +2.1 Unmodified Code. You may use, reproduce, display, perform, internally distribute within Your organization, and Externally Deploy verbatim, unmodified copies of the Original Code, for commercial or non-commercial purposes, provided that in each instance: + +(a) You must retain and reproduce in all copies of Original Code the copyright and other proprietary notices and disclaimers of Apple as they appear in the Original Code, and keep intact all notices in the Original Code that refer to this License; and + +(b) You must include a copy of this License with every copy of Source Code of Covered Code and documentation You distribute or Externally Deploy, and You may not offer or impose any terms on such Source Code that alter or restrict this License or the recipients' rights hereunder, except as permitted under Section 6. + +2.2 Modified Code. You may modify Covered Code and use, reproduce, display, perform, internally distribute within Your organization, and Externally Deploy Your Modifications and Covered Code, for commercial or non-commercial purposes, provided that in each instance You also meet all of these conditions: + +(a) You must satisfy all the conditions of Section 2.1 with respect to the Source Code of the Covered Code; + +(b) You must duplicate, to the extent it does not already exist, the notice in Exhibit A in each file of the Source Code of all Your Modifications, and cause the modified files to carry prominent notices stating that You changed the files and the date of any change; and + +(c) If You Externally Deploy Your Modifications, You must make Source Code of all Your Externally Deployed Modifications either available to those to whom You have Externally Deployed Your Modifications, or publicly available. Source Code of Your Externally Deployed Modifications must be released under the terms set forth in this License, including the license grants set forth in Section 3 below, for as long as you Externally Deploy the Covered Code or twelve (12) months from the date of initial External Deployment, whichever is longer. You should preferably distribute the Source Code of Your Externally Deployed Modifications electronically (e.g. download from a web site). + +2.3 Distribution of Executable Versions. In addition, if You Externally Deploy Covered Code (Original Code and/or Modifications) in object code, executable form only, You must include a prominent notice, in the code itself as well as in related documentation, stating that Source Code of the Covered Code is available under the terms of this License with information on how and where to obtain such Source Code. + +2.4 Third Party Rights. You expressly acknowledge and agree that although Apple and each Contributor grants the licenses to their respective portions of the Covered Code set forth herein, no assurances are provided by Apple or any Contributor that the Covered Code does not infringe the patent or other intellectual property rights of any other entity. Apple and each Contributor disclaim any liability to You for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, You hereby assume sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow You to distribute the Covered Code, it is Your responsibility to acquire that license before distributing the Covered Code. + +3. Your Grants. In consideration of, and as a condition to, the licenses granted to You under this License, You hereby grant to any person or entity receiving or distributing Covered Code under this License a non-exclusive, royalty-free, perpetual, irrevocable license, under Your Applicable Patent Rights and other intellectual property rights (other than patent) owned or controlled by You, to use, reproduce, display, perform, modify, sublicense, distribute and Externally Deploy Your Modifications of the same scope and extent as Apple's licenses under Sections 2.1 and 2.2 above. + +4. Larger Works. You may create a Larger Work by combining Covered Code with other code not governed by the terms of this License and distribute the Larger Work as a single product. In each such instance, You must make sure the requirements of this License are fulfilled for the Covered Code or any portion thereof. + +5. Limitations on Patent License. Except as expressly stated in Section 2, no other patent rights, express or implied, are granted by Apple herein. Modifications and/or Larger Works may require additional patent licenses from Apple which Apple may grant in its sole discretion. + +6. Additional Terms. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations and/or other rights consistent with the scope of the license granted herein ("Additional Terms") to one or more recipients of Covered Code. However, You may do so only on Your own behalf and as Your sole responsibility, and not on behalf of Apple or any Contributor. You must obtain the recipient's agreement that any such Additional Terms are offered by You alone, and You hereby agree to indemnify, defend and hold Apple and every Contributor harmless for any liability incurred by or claims asserted against Apple or such Contributor by reason of any such Additional Terms. + +7. Versions of the License. Apple may publish revised and/or new versions of this License from time to time. Each version will be given a distinguishing version number. Once Original Code has been published under a particular version of this License, You may continue to use it under the terms of that version. You may also choose to use such Original Code under the terms of any subsequent version of this License published by Apple. No one other than Apple has the right to modify the terms applicable to Covered Code created under this License. + +8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in part pre-release, untested, or not fully tested works. The Covered Code may contain errors that could cause failures or loss of data, and may be incomplete or contain inaccuracies. You expressly acknowledge and agree that use of the Covered Code, or any portion thereof, is at Your sole and entire risk. THE COVERED CODE IS PROVIDED "AS IS" AND WITHOUT WARRANTY, UPGRADES OR SUPPORT OF ANY KIND AND APPLE AND APPLE'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY DISCLAIM ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. APPLE AND EACH CONTRIBUTOR DOES NOT WARRANT AGAINST INTERFERENCE WITH YOUR ENJOYMENT OF THE COVERED CODE, THAT THE FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR REQUIREMENTS, THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE, AN APPLE AUTHORIZED REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY. You acknowledge that the Covered Code is not intended for use in the operation of nuclear facilities, aircraft navigation, communication systems, or air traffic control machines in which case the failure of the Covered Code could lead to death, personal injury, or severe physical or environmental damage. + +9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT SHALL APPLE OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING TO THIS LICENSE OR YOUR USE OR INABILITY TO USE THE COVERED CODE, OR ANY PORTION THEREOF, WHETHER UNDER A THEORY OF CONTRACT, WARRANTY, TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, EVEN IF APPLE OR SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY TO YOU. In no event shall Apple's total liability to You for all damages (other than as may be required by applicable law) under this License exceed the amount of fifty dollars ($50.00). + +10. Trademarks. This License does not grant any rights to use the trademarks or trade names "Apple", "Mac", "Mac OS", "QuickTime", "QuickTime Streaming Server" or any other trademarks, service marks, logos or trade names belonging to Apple (collectively "Apple Marks") or to any trademark, service mark, logo or trade name belonging to any Contributor. You agree not to use any Apple Marks in or as part of the name of products derived from the Original Code or to endorse or promote products derived from the Original Code other than as expressly permitted by and in strict compliance at all times with Apple's third party trademark usage guidelines which are posted at http://www.apple.com/legal/guidelinesfor3rdparties.html. + +11. Ownership. Subject to the licenses granted under this License, each Contributor retains all rights, title and interest in and to any Modifications made by such Contributor. Apple retains all rights, title and interest in and to the Original Code and any Modifications made by or on behalf of Apple ("Apple Modifications"), and such Apple Modifications will not be automatically subject to this License. Apple may, at its sole discretion, choose to license such Apple Modifications under this License, or on different terms from those contained in this License or may choose not to license them at all. + +12. Termination. + +12.1 Termination. This License and the rights granted hereunder will terminate: + +(a) automatically without notice from Apple if You fail to comply with any term(s) of this License and fail to cure such breach within 30 days of becoming aware of such breach; +(b) immediately in the event of the circumstances described in Section 13.5(b); or +(c) automatically without notice from Apple if You, at any time during the term of this License, commence an action for patent infringement against Apple; provided that Apple did not first commence an action for patent infringement against You in that instance. + +12.2 Effect of Termination. Upon termination, You agree to immediately stop any further use, reproduction, modification, sublicensing and distribution of the Covered Code. All sublicenses to the Covered Code which have been properly granted prior to termination shall survive any termination of this License. Provisions which, by their nature, should remain in effect beyond the termination of this License shall survive, including but not limited to Sections 3, 5, 8, 9, 10, 11, 12.2 and 13. No party will be liable to any other for compensation, indemnity or damages of any sort solely as a result of terminating this License in accordance with its terms, and termination of this License will be without prejudice to any other right or remedy of any party. + +13. Miscellaneous. + +13.1 Government End Users. The Covered Code is a "commercial item" as defined in FAR 2.101. Government software and technical data rights in the Covered Code include only those rights customarily provided to the public as defined in this License. This customary commercial license in technical data and software is provided in accordance with FAR 12.211 (Technical Data) and 12.212 (Computer Software) and, for Department of Defense purchases, DFAR 252.227-7015 (Technical Data -- Commercial Items) and 227.7202-3 (Rights in Commercial Computer Software or Computer Software Documentation). Accordingly, all U.S. Government End Users acquire Covered Code with only those rights set forth herein. + +13.2 Relationship of Parties. This License will not be construed as creating an agency, partnership, joint venture or any other form of legal association between or among You, Apple or any Contributor, and You will not represent to the contrary, whether expressly, by implication, appearance or otherwise. + +13.3 Independent Development. Nothing in this License will impair Apple's right to acquire, license, develop, have others develop for it, market and/or distribute technology or products that perform the same or similar functions as, or otherwise compete with, Modifications, Larger Works, technology or products that You may develop, produce, market or distribute. + +13.4 Waiver; Construction. Failure by Apple or any Contributor to enforce any provision of this License will not be deemed a waiver of future enforcement of that or any other provision. Any law or regulation which provides that the language of a contract shall be construed against the drafter will not apply to this License. + +13.5 Severability. (a) If for any reason a court of competent jurisdiction finds any provision of this License, or portion thereof, to be unenforceable, that provision of the License will be enforced to the maximum extent permissible so as to effect the economic benefits and intent of the parties, and the remainder of this License will continue in full force and effect. (b) Notwithstanding the foregoing, if applicable law prohibits or restricts You from fully and/or specifically complying with Sections 2 and/or 3 or prevents the enforceability of either of those Sections, this License will immediately terminate and You must immediately discontinue any use of the Covered Code and destroy all copies of it that are in your possession or control. + +13.6 Dispute Resolution. Any litigation or other dispute resolution between You and Apple relating to this License shall take place in the Northern District of California, and You and Apple hereby consent to the personal jurisdiction of, and venue in, the state and federal courts within that District with respect to this License. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. + +13.7 Entire Agreement; Governing Law. This License constitutes the entire agreement between the parties with respect to the subject matter hereof. This License shall be governed by the laws of the United States and the State of California, except that body of California law concerning conflicts of law. + +Where You are located in the province of Quebec, Canada, the following clause applies: The parties hereby confirm that they have requested that this License and all related documents be drafted in English. Les parties ont exigé que le présent contrat et tous les documents connexes soient rédigés en anglais. + +EXHIBIT A. + +"Portions Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. + +This file contains Original Code and/or Modifications of Original Code as defined in and that are subject to the Apple Public Source License Version 2.0 (the 'License'). You may not use this file except in compliance with the License. Please obtain a copy of the License at http://www.opensource.apple.com/apsl/ and read it before using this file. + +The Original Code and all software distributed under the License are distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the specific language governing rights and limitations under the License." diff --git a/vendor/security-framework/examples/client.rs b/vendor/security-framework/examples/client.rs new file mode 100644 index 000000000..a352bf94f --- /dev/null +++ b/vendor/security-framework/examples/client.rs @@ -0,0 +1,24 @@ +use security_framework::secure_transport::ClientBuilder; +use std::io::{Read, Write}; +use std::net::TcpStream; + +fn main() { + let stream = TcpStream::connect("google.com:443").unwrap(); + let mut stream = ClientBuilder::new() + .handshake("google.com", stream) + .unwrap(); + println!( + "negotiated chipher: {:?}", + stream.context().negotiated_cipher().unwrap() + ); + println!( + "negotiated version: {:?}", + stream.context().negotiated_protocol_version().unwrap() + ); + + stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); + stream.flush().unwrap(); + let mut buf = vec![]; + stream.read_to_end(&mut buf).unwrap(); + println!("{}", String::from_utf8_lossy(&buf)); +} diff --git a/vendor/security-framework/examples/find_internet_password.rs b/vendor/security-framework/examples/find_internet_password.rs new file mode 100644 index 000000000..58a2d8bea --- /dev/null +++ b/vendor/security-framework/examples/find_internet_password.rs @@ -0,0 +1,36 @@ +#[cfg(target_os = "macos")] +use security_framework::os::macos::keychain::SecKeychain; +#[cfg(target_os = "macos")] +use security_framework::os::macos::passwords::{SecAuthenticationType, SecProtocolType}; + +fn main() { + #[cfg(target_os = "macos")] { + let hostname = "example.com"; + let username = "rusty"; + let res = SecKeychain::default().unwrap().find_internet_password( + hostname, + None, + username, + "", + None, + SecProtocolType::Any, + SecAuthenticationType::Any, + ); + match res { + Ok((password, _)) => { + println!( + "Password for {}@{} is {} bytes long", + username, + hostname, + password.len() + ); + } + Err(err) if err.code() == -128 => { + eprintln!("Account was found in the Keychain, but user denied access"); + } + Err(err) => { + eprintln!("Password not found. Open Keychain Access.app and add internet password for '{}' at 'https://{}': {:?}", + username, hostname, err); + } + } +}} diff --git a/vendor/security-framework/examples/set_internet_password.rs b/vendor/security-framework/examples/set_internet_password.rs new file mode 100644 index 000000000..9c5e2cc1b --- /dev/null +++ b/vendor/security-framework/examples/set_internet_password.rs @@ -0,0 +1,33 @@ +#[cfg(target_os = "macos")] +use security_framework::os::macos::keychain::SecKeychain; +#[cfg(target_os = "macos")] +use security_framework::os::macos::passwords::{SecAuthenticationType, SecProtocolType}; + +fn main() { + #[cfg(target_os = "macos")] { + let hostname = "example.com"; + let username = "rusty"; + let password = b"oxidize"; + + let res = SecKeychain::default().unwrap().set_internet_password( + hostname, + None, + username, + "", + None, + SecProtocolType::HTTPS, + SecAuthenticationType::HTMLForm, + password, + ); + match res { + Ok(_) => { + println!( + "Password set for {}@{}. You can read it using find_internet_password example", + username, hostname + ); + } + Err(err) => { + eprintln!("Could not set password: {:?}", err); + } + } +}} diff --git a/vendor/security-framework/src/access_control.rs b/vendor/security-framework/src/access_control.rs new file mode 100644 index 000000000..15a6d700d --- /dev/null +++ b/vendor/security-framework/src/access_control.rs @@ -0,0 +1,40 @@ +//! Access Control support. + +use std::ptr::{self, null}; + +use core_foundation::base::{TCFType, CFOptionFlags, kCFAllocatorDefault}; +use security_framework_sys::access_control::{SecAccessControlGetTypeID, SecAccessControlCreateWithFlags}; +use security_framework_sys::base::{SecAccessControlRef, errSecParam}; +use crate::base::{Error, Result}; + +declare_TCFType! { + /// A type representing sec access control settings. + SecAccessControl, SecAccessControlRef +} +impl_TCFType!( + SecAccessControl, + SecAccessControlRef, + SecAccessControlGetTypeID +); + +unsafe impl Sync for SecAccessControl {} +unsafe impl Send for SecAccessControl {} + +impl SecAccessControl { + /// Create `AccessControl` object from flags + pub fn create_with_flags(flags: CFOptionFlags) -> Result<Self> { + unsafe { + let access_control = SecAccessControlCreateWithFlags( + kCFAllocatorDefault, + null(), + flags, + ptr::null_mut(), + ); + if access_control.is_null() { + Err(Error::from_code(errSecParam)) + } else { + Ok(Self::wrap_under_create_rule(access_control)) + } + } + } +} diff --git a/vendor/security-framework/src/authorization.rs b/vendor/security-framework/src/authorization.rs new file mode 100644 index 000000000..f19f8ecc9 --- /dev/null +++ b/vendor/security-framework/src/authorization.rs @@ -0,0 +1,819 @@ +//! Authorization Services support. + +/// # Potential improvements +/// +/// * When generic specialization stabilizes prevent copying from `CString` +/// arguments. +/// * `AuthorizationCopyRightsAsync` +/// * Provide constants for well known item names +use crate::base::{Error, Result}; +#[cfg(all(target_os = "macos", feature = "job-bless"))] +use core_foundation::base::Boolean; +use core_foundation::base::{CFTypeRef, TCFType}; +use core_foundation::bundle::CFBundleRef; +use core_foundation::dictionary::{CFDictionary, CFDictionaryRef}; +#[cfg(all(target_os = "macos", feature = "job-bless"))] +use core_foundation::error::CFError; +#[cfg(all(target_os = "macos", feature = "job-bless"))] +use core_foundation::error::CFErrorRef; +use core_foundation::string::{CFString, CFStringRef}; +use security_framework_sys::authorization as sys; +use security_framework_sys::base::errSecConversionError; +use std::convert::TryFrom; +use std::ffi::{CStr, CString}; +use std::fs::File; +use std::mem::MaybeUninit; +use std::os::raw::c_void; +use std::ptr::addr_of; +use std::{convert::TryInto, marker::PhantomData}; +use sys::AuthorizationExternalForm; + +macro_rules! optional_str_to_cfref { + ($string:ident) => {{ + $string + .map(CFString::new) + .map_or(std::ptr::null(), |cfs| cfs.as_concrete_TypeRef()) + }}; +} + +macro_rules! cstring_or_err { + ($x:expr) => {{ + CString::new($x).map_err(|_| Error::from_code(errSecConversionError)) + }}; +} + +bitflags::bitflags! { + /// The flags used to specify authorization options. + pub struct Flags: sys::AuthorizationFlags { + /// An empty flag set that you use as a placeholder when you don't want + /// any of the other flags. + const DEFAULTS = sys::kAuthorizationFlagDefaults; + + /// A flag that permits user interaction as needed. + const INTERACTION_ALLOWED = sys::kAuthorizationFlagInteractionAllowed; + + /// A flag that permits the Security Server to attempt to grant the + /// rights requested. + const EXTEND_RIGHTS = sys::kAuthorizationFlagExtendRights; + + /// A flag that permits the Security Server to grant rights on an + /// individual basis. + const PARTIAL_RIGHTS = sys::kAuthorizationFlagPartialRights; + + /// A flag that instructs the Security Server to revoke authorization. + const DESTROY_RIGHTS = sys::kAuthorizationFlagDestroyRights; + + /// A flag that instructs the Security Server to preauthorize the rights + /// requested. + const PREAUTHORIZE = sys::kAuthorizationFlagPreAuthorize; + } +} + +impl Default for Flags { + #[inline(always)] + fn default() -> Flags { + Flags::DEFAULTS + } +} + +/// Information about an authorization right or the environment. +#[repr(C)] +pub struct AuthorizationItem(sys::AuthorizationItem); + +impl AuthorizationItem { + /// The required name of the authorization right or environment data. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + #[must_use] pub fn name(&self) -> &str { + unsafe { + CStr::from_ptr(self.0.name) + .to_str() + .expect("AuthorizationItem::name failed to convert &str to CStr") + } + } + + /// The information pertaining to the name field. Do not rely on NULL + /// termination of string data. + #[inline] + #[must_use] pub fn value(&self) -> Option<&[u8]> { + if self.0.value.is_null() { + return None; + } + + let value = + unsafe { std::slice::from_raw_parts(self.0.value as *const u8, self.0.valueLength) }; + + Some(value) + } +} + +/// A set of authorization items returned and owned by the Security Server. +#[derive(Debug)] +#[repr(C)] +pub struct AuthorizationItemSet<'a> { + inner: *const sys::AuthorizationItemSet, + phantom: PhantomData<&'a sys::AuthorizationItemSet>, +} + +impl<'a> Drop for AuthorizationItemSet<'a> { + #[inline] + fn drop(&mut self) { + unsafe { + sys::AuthorizationFreeItemSet(self.inner as *mut sys::AuthorizationItemSet); + } + } +} + +/// Used by `AuthorizationItemSetBuilder` to store data pointed to by +/// `sys::AuthorizationItemSet`. +#[derive(Debug)] +pub struct AuthorizationItemSetStorage { + /// The layout of this is a little awkward because of the requirements of + /// Apple's APIs. `items` contains pointers to data owned by `names` and + /// `values`, so we must not modify them once `items` has been set up. + names: Vec<CString>, + values: Vec<Option<Vec<u8>>>, + items: Vec<sys::AuthorizationItem>, + + /// Must not be given to APIs which would attempt to modify it. + /// + /// See `AuthorizationItemSet` for sets owned by the Security Server which + /// are writable. + pub set: sys::AuthorizationItemSet, +} + +impl Default for AuthorizationItemSetStorage { + #[inline] + fn default() -> Self { + AuthorizationItemSetStorage { + names: Vec::new(), + values: Vec::new(), + items: Vec::new(), + set: sys::AuthorizationItemSet { + count: 0, + items: std::ptr::null_mut(), + }, + } + } +} + +/// A convenience `AuthorizationItemSetBuilder` builder which enabled you to use +/// rust types. All names and values passed in will be copied. +#[derive(Debug, Default)] +pub struct AuthorizationItemSetBuilder { + storage: AuthorizationItemSetStorage, +} + +// Stores AuthorizationItems contiguously, and their items separately +impl AuthorizationItemSetBuilder { + /// Creates a new `AuthorizationItemSetStore`, which simplifies creating + /// owned vectors of `AuthorizationItem`s. + #[inline(always)] + #[must_use] + pub fn new() -> AuthorizationItemSetBuilder { + Default::default() + } + + /// Adds an `AuthorizationItem` with the name set to a right and an empty + /// value. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn add_right<N: Into<Vec<u8>>>(mut self, name: N) -> Result<Self> { + self.storage.names.push(cstring_or_err!(name)?); + self.storage.values.push(None); + Ok(self) + } + + /// Adds an `AuthorizationItem` with arbitrary data. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn add_data<N, V>(mut self, name: N, value: V) -> Result<Self> + where + N: Into<Vec<u8>>, + V: Into<Vec<u8>>, + { + self.storage.names.push(cstring_or_err!(name)?); + self.storage.values.push(Some(value.into())); + Ok(self) + } + + /// Adds an `AuthorizationItem` with NULL terminated string data. + /// + /// If `name` or `value` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn add_string<N, V>(mut self, name: N, value: V) -> Result<Self> + where + N: Into<Vec<u8>>, + V: Into<Vec<u8>>, + { + self.storage.names.push(cstring_or_err!(name)?); + self.storage + .values + .push(Some(cstring_or_err!(value)?.to_bytes().to_vec())); + Ok(self) + } + + /// Creates the `sys::AuthorizationItemSet`, and gives you ownership of the + /// data it points to. + #[must_use] + pub fn build(mut self) -> AuthorizationItemSetStorage { + self.storage.items = self + .storage + .names + .iter() + .zip(self.storage.values.iter()) + .map(|(n, v)| sys::AuthorizationItem { + name: n.as_ptr(), + value: v + .as_ref() + .map_or(std::ptr::null_mut(), |v| v.as_ptr() as *mut c_void), + valueLength: v.as_ref().map_or(0, |v| v.len()), + flags: 0, + }) + .collect(); + + self.storage.set = sys::AuthorizationItemSet { + count: self.storage.items.len() as u32, + items: self.storage.items.as_ptr() as *mut sys::AuthorizationItem, + }; + + self.storage + } +} + +/// Used by `Authorization::set_item` to define the rules of he right. +#[derive(Copy, Clone)] +pub enum RightDefinition<'a> { + /// The dictionary will contain the keys and values that define the rules. + FromDictionary(&'a CFDictionary<CFStringRef, CFTypeRef>), + + /// The specified right's rules will be duplicated. + FromExistingRight(&'a str), +} + +/// A wrapper around `AuthorizationCreate` and functions which operate on an +/// `AuthorizationRef`. +#[derive(Debug)] +pub struct Authorization { + handle: sys::AuthorizationRef, + free_flags: Flags, +} + +impl TryFrom<AuthorizationExternalForm> for Authorization { + type Error = Error; + + /// Internalizes the external representation of an authorization reference. + #[cold] + fn try_from(external_form: AuthorizationExternalForm) -> Result<Self> { + let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit(); + + let status = unsafe { + sys::AuthorizationCreateFromExternalForm(&external_form, handle.as_mut_ptr()) + }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from_code(status)); + } + + let auth = Authorization { + handle: unsafe { handle.assume_init() }, + free_flags: Default::default(), + }; + + Ok(auth) + } +} + +impl Authorization { + /// Creates an authorization object which has no environment or associated + /// rights. + #[inline] + pub fn default() -> Result<Self> { + Self::new(None, None, Default::default()) + } + + /// Creates an authorization reference and provides an option to authorize + /// or preauthorize rights. + /// + /// `rights` should be the names of the rights you want to create. + /// + /// `environment` is used when authorizing or preauthorizing rights. Not + /// used in OS X v10.2 and earlier. In macOS 10.3 and later, you can pass + /// icon or prompt data to be used in the authentication dialog box. In + /// macOS 10.4 and later, you can also pass a user name and password in + /// order to authorize a user without user interaction. + pub fn new( + // FIXME: this should have been by reference + rights: Option<AuthorizationItemSetStorage>, + environment: Option<AuthorizationItemSetStorage>, + flags: Flags, + ) -> Result<Self> { + let rights_ptr = rights.as_ref().map_or(std::ptr::null(), |r| { + addr_of!(r.set) as *const sys::AuthorizationItemSet + }); + + let env_ptr = environment.as_ref().map_or(std::ptr::null(), |e| { + addr_of!(e.set) as *const sys::AuthorizationItemSet + }); + + let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit(); + + let status = unsafe { + sys::AuthorizationCreate(rights_ptr, env_ptr, flags.bits(), handle.as_mut_ptr()) + }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from_code(status)); + } + + Ok(Authorization { + handle: unsafe { handle.assume_init() }, + free_flags: Default::default(), + }) + } + + /// Internalizes the external representation of an authorization reference. + #[deprecated(since = "2.0.1", note = "Please use the TryFrom trait instead")] + pub fn from_external_form(external_form: sys::AuthorizationExternalForm) -> Result<Self> { + external_form.try_into() + } + + /// By default the rights acquired will be retained by the Security Server. + /// Use this to ensure they are destroyed and to prevent shared rights' + /// continued used by other processes. + #[inline(always)] + pub fn destroy_rights(mut self) { + self.free_flags = Flags::DESTROY_RIGHTS; + } + + /// Retrieve's the right's definition as a dictionary. Use `right_exists` + /// if you want to avoid retrieving the dictionary. + /// + /// `name` can be a wildcard right name. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn get_right<T: Into<Vec<u8>>>(name: T) -> Result<CFDictionary<CFString, CFTypeRef>> { + let name = cstring_or_err!(name)?; + let mut dict = MaybeUninit::<CFDictionaryRef>::uninit(); + + let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), dict.as_mut_ptr()) }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from_code(status)); + } + + let dict = unsafe { CFDictionary::wrap_under_create_rule(dict.assume_init()) }; + + Ok(dict) + } + + /// Checks if a right exists within the policy database. This is the same as + /// `get_right`, but avoids a dictionary allocation. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn right_exists<T: Into<Vec<u8>>>(name: T) -> Result<bool> { + let name = cstring_or_err!(name)?; + + let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), std::ptr::null_mut()) }; + + Ok(status == sys::errAuthorizationSuccess) + } + + /// Removes a right from the policy database. + /// + /// `name` cannot be a wildcard right name. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn remove_right<T: Into<Vec<u8>>>(&self, name: T) -> Result<()> { + let name = cstring_or_err!(name)?; + + let status = unsafe { sys::AuthorizationRightRemove(self.handle, name.as_ptr()) }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from_code(status)); + } + + Ok(()) + } + + /// Creates or updates a right entry in the policy database. Your process + /// must have a code signature in order to be able to add rights to the + /// authorization database. + /// + /// `name` cannot be a wildcard right. + /// + /// `definition` can be either a `CFDictionaryRef` containing keys defining + /// the rules or a `CFStringRef` representing the name of another right + /// whose rules you wish to duplicaate. + /// + /// `description` is a key which can be used to look up localized + /// descriptions. + /// + /// `bundle` will be used to get localizations from if not the main bundle. + /// + /// `localeTableName` will be used to get localizations if provided. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn set_right<T: Into<Vec<u8>>>( + &self, + name: T, + definition: RightDefinition<'_>, + description: Option<&str>, + bundle: Option<CFBundleRef>, + locale: Option<&str>, + ) -> Result<()> { + let name = cstring_or_err!(name)?; + + let definition_cfstring: CFString; + let definition_ref = match definition { + RightDefinition::FromDictionary(def) => def.as_CFTypeRef(), + RightDefinition::FromExistingRight(def) => { + definition_cfstring = CFString::new(def); + definition_cfstring.as_CFTypeRef() + } + }; + + let status = unsafe { + sys::AuthorizationRightSet( + self.handle, + name.as_ptr(), + definition_ref, + optional_str_to_cfref!(description), + bundle.unwrap_or(std::ptr::null_mut()), + optional_str_to_cfref!(locale), + ) + }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from_code(status)); + } + + Ok(()) + } + + /// An authorization plugin can store the results of an authentication + /// operation by calling the `SetContextValue` function. You can then + /// retrieve this supporting data, such as the user name. + /// + /// `tag` should specify the type of data the Security Server should return. + /// If `None`, all available information is retreieved. + /// + /// If `tag` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn copy_info<T: Into<Vec<u8>>>(&self, tag: Option<T>) -> Result<AuthorizationItemSet<'_>> { + let tag_with_nul: CString; + + let tag_ptr = match tag { + Some(tag) => { + tag_with_nul = cstring_or_err!(tag)?; + tag_with_nul.as_ptr() + } + None => std::ptr::null(), + }; + + let mut inner = MaybeUninit::<*mut sys::AuthorizationItemSet>::uninit(); + + let status = + unsafe { sys::AuthorizationCopyInfo(self.handle, tag_ptr, inner.as_mut_ptr()) }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from(status)); + } + + let set = AuthorizationItemSet { + inner: unsafe { inner.assume_init() }, + phantom: PhantomData, + }; + + Ok(set) + } + + /// Creates an external representation of an authorization reference so that + /// you can transmit it between processes. + pub fn make_external_form(&self) -> Result<sys::AuthorizationExternalForm> { + let mut external_form = MaybeUninit::<sys::AuthorizationExternalForm>::uninit(); + + let status = + unsafe { sys::AuthorizationMakeExternalForm(self.handle, external_form.as_mut_ptr()) }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from(status)); + } + + Ok(unsafe { external_form.assume_init() }) + } + + /// Runs an executable tool with root privileges. + /// Discards executable's output + #[cfg(target_os = "macos")] + #[inline(always)] + pub fn execute_with_privileges<P, S, I>( + &self, + command: P, + arguments: I, + flags: Flags, + ) -> Result<()> + where + P: AsRef<std::path::Path>, + I: IntoIterator<Item = S>, + S: AsRef<std::ffi::OsStr>, + { + use std::os::unix::ffi::OsStrExt; + + let arguments = arguments + .into_iter().flat_map(|a| CString::new(a.as_ref().as_bytes())) + .collect::<Vec<_>>(); + self.execute_with_privileges_internal(command.as_ref().as_os_str().as_bytes(), &arguments, flags, false)?; + Ok(()) + } + + /// Runs an executable tool with root privileges, + /// and returns a `File` handle to its communication pipe + #[cfg(target_os = "macos")] + #[inline(always)] + pub fn execute_with_privileges_piped<P, S, I>( + &self, + command: P, + arguments: I, + flags: Flags, + ) -> Result<File> + where + P: AsRef<std::path::Path>, + I: IntoIterator<Item = S>, + S: AsRef<std::ffi::OsStr>, + { + use std::os::unix::ffi::OsStrExt; + + let arguments = arguments + .into_iter().flat_map(|a| CString::new(a.as_ref().as_bytes())) + .collect::<Vec<_>>(); + Ok(self.execute_with_privileges_internal(command.as_ref().as_os_str().as_bytes(), &arguments, flags, true)?.unwrap()) + } + + /// Submits the executable for the given label as a `launchd` job. + #[cfg(all(target_os = "macos", feature = "job-bless"))] + pub fn job_bless(&self, label: &str) -> Result<(), CFError> { + #[link(name = "ServiceManagement", kind = "framework")] + extern "C" { + static kSMDomainSystemLaunchd: CFStringRef; + + fn SMJobBless( + domain: CFStringRef, + executableLabel: CFStringRef, + auth: sys::AuthorizationRef, + error: *mut CFErrorRef, + ) -> Boolean; + } + + unsafe { + let mut error = std::ptr::null_mut(); + SMJobBless( + kSMDomainSystemLaunchd, + CFString::new(label).as_concrete_TypeRef(), + self.handle, + &mut error, + ); + if !error.is_null() { + return Err(CFError::wrap_under_create_rule(error)); + } + + Ok(()) + } + } + + // Runs an executable tool with root privileges. + #[cfg(target_os = "macos")] + fn execute_with_privileges_internal( + &self, + command: &[u8], + arguments: &[CString], + flags: Flags, + make_pipe: bool, + ) -> Result<Option<File>> { + use std::os::unix::io::{FromRawFd, RawFd}; + + let c_cmd = cstring_or_err!(command)?; + + let mut c_args = arguments.iter().map(|a| a.as_ptr() as _).collect::<Vec<_>>(); + c_args.push(std::ptr::null_mut()); + + let mut pipe: *mut libc::FILE = std::ptr::null_mut(); + + let status = unsafe { + sys::AuthorizationExecuteWithPrivileges( + self.handle, + c_cmd.as_ptr(), + flags.bits(), + c_args.as_ptr(), + if make_pipe { &mut pipe } else { std::ptr::null_mut() }, + ) + }; + + crate::cvt(status)?; + Ok(if make_pipe { + if pipe.is_null() { + return Err(Error::from_code(32)); // EPIPE? + } + Some(unsafe { File::from_raw_fd(libc::fileno(pipe) as RawFd) }) + } else { + None + }) + } +} + +impl Drop for Authorization { + #[inline] + fn drop(&mut self) { + unsafe { + sys::AuthorizationFree(self.handle, self.free_flags.bits()); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core_foundation::string::CFString; + + #[test] + fn test_create_default_authorization() { + Authorization::default().unwrap(); + } + + #[test] + fn test_create_allowed_authorization() -> Result<()> { + let rights = AuthorizationItemSetBuilder::new() + .add_right("system.hdd.smart")? + .add_right("system.login.done")? + .build(); + + Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap(); + + Ok(()) + } + + #[test] + fn test_create_then_destroy_allowed_authorization() -> Result<()> { + let rights = AuthorizationItemSetBuilder::new() + .add_right("system.hdd.smart")? + .add_right("system.login.done")? + .build(); + + let auth = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap(); + auth.destroy_rights(); + + Ok(()) + } + + #[test] + fn test_create_authorization_requiring_interaction() -> Result<()> { + let rights = AuthorizationItemSetBuilder::new() + .add_right("system.privilege.admin")? + .build(); + + let error = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap_err(); + + assert_eq!(error.code(), sys::errAuthorizationInteractionNotAllowed); + + Ok(()) + } + + fn create_credentials_env() -> Result<AuthorizationItemSetStorage> { + let set = AuthorizationItemSetBuilder::new() + .add_string( + "username", + option_env!("USER").expect("You must set the USER environment variable"), + )? + .add_string( + "password", + option_env!("PASSWORD").expect("You must set the PASSWORD environment varible"), + )? + .build(); + + Ok(set) + } + + #[test] + fn test_create_authorization_with_bad_credentials() -> Result<()> { + let rights = AuthorizationItemSetBuilder::new() + .add_right("system.privilege.admin")? + .build(); + + let env = AuthorizationItemSetBuilder::new() + .add_string("username", "Tim Apple")? + .add_string("password", "butterfly")? + .build(); + + let error = + Authorization::new(Some(rights), Some(env), Flags::INTERACTION_ALLOWED).unwrap_err(); + + assert_eq!(error.code(), sys::errAuthorizationDenied); + + Ok(()) + } + + #[test] + fn test_create_authorization_with_credentials() -> Result<()> { + if option_env!("PASSWORD").is_none() { + return Ok(()); + } + + let rights = AuthorizationItemSetBuilder::new() + .add_right("system.privilege.admin")? + .build(); + + let env = create_credentials_env()?; + + Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap(); + + Ok(()) + } + + #[test] + fn test_query_authorization_database() -> Result<()> { + assert!(Authorization::right_exists("system.hdd.smart")?); + assert!(!Authorization::right_exists("EMPTY")?); + + let dict = Authorization::get_right("system.hdd.smart").unwrap(); + + let key = CFString::from_static_string("class"); + assert!(dict.contains_key(&key)); + + let invalid_key = CFString::from_static_string("EMPTY"); + assert!(!dict.contains_key(&invalid_key)); + + Ok(()) + } + + /// This test will only pass if its process has a valid code signature. + #[test] + fn test_modify_authorization_database() -> Result<()> { + if option_env!("PASSWORD").is_none() { + return Ok(()); + } + + let rights = AuthorizationItemSetBuilder::new() + .add_right("config.modify.")? + .build(); + + let env = create_credentials_env()?; + + let auth = Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap(); + + assert!(!Authorization::right_exists("TEST_RIGHT")?); + + auth.set_right( + "TEST_RIGHT", + RightDefinition::FromExistingRight("system.hdd.smart"), + None, + None, + None, + ) + .unwrap(); + + assert!(Authorization::right_exists("TEST_RIGHT")?); + + auth.remove_right("TEST_RIGHT").unwrap(); + + assert!(!Authorization::right_exists("TEST_RIGHT")?); + + Ok(()) + } + + /// This test will succeed if authorization popup is approved. + #[test] + fn test_execute_with_privileges() -> Result<()> { + if option_env!("PASSWORD").is_none() { + return Ok(()); + } + + let rights = AuthorizationItemSetBuilder::new() + .add_right("system.privilege.admin")? + .build(); + + let auth = Authorization::new( + Some(rights), + None, + Flags::DEFAULTS + | Flags::INTERACTION_ALLOWED + | Flags::PREAUTHORIZE + | Flags::EXTEND_RIGHTS, + )?; + + let file = auth.execute_with_privileges_piped("/bin/ls", ["/"], Flags::DEFAULTS)?; + + use std::io::{self, BufRead}; + for line in io::BufReader::new(file).lines() { + let _ = line.unwrap(); + } + + Ok(()) + } +} diff --git a/vendor/security-framework/src/base.rs b/vendor/security-framework/src/base.rs new file mode 100644 index 000000000..8c5f8e72f --- /dev/null +++ b/vendor/security-framework/src/base.rs @@ -0,0 +1,88 @@ +//! Support types for other modules. + +use core_foundation::string::CFString; +use core_foundation_sys::base::OSStatus; +use std::error; +use std::fmt; +use std::num::NonZeroI32; +use std::result; + +/// A `Result` type commonly returned by functions. +pub type Result<T, E = Error> = result::Result<T, E>; + +/// A Security Framework error. +#[derive(Copy, Clone)] +pub struct Error(NonZeroI32); + +impl fmt::Debug for Error { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = fmt.debug_struct("Error"); + builder.field("code", &self.0); + if let Some(message) = self.message() { + builder.field("message", &message); + } + builder.finish() + } +} + +impl Error { + /// Creates a new `Error` from a status code. + /// The code must not be zero + #[inline] + #[must_use] + pub fn from_code(code: OSStatus) -> Self { + Self(NonZeroI32::new(code).unwrap_or_else(|| NonZeroI32::new(1).unwrap())) + } + + /// Returns a string describing the current error, if available. + #[inline(always)] + #[must_use] + pub fn message(self) -> Option<String> { + self.inner_message() + } + + #[cold] + fn inner_message(self) -> Option<String> { + use core_foundation::base::TCFType; + use security_framework_sys::base::SecCopyErrorMessageString; + use std::ptr; + + unsafe { + let s = SecCopyErrorMessageString(self.code(), ptr::null_mut()); + if s.is_null() { + None + } else { + Some(CFString::wrap_under_create_rule(s).to_string()) + } + } + } + + /// Returns the code of the current error. + #[inline(always)] + #[must_use] + pub fn code(self) -> OSStatus { + self.0.get() as _ + } +} + +impl From<OSStatus> for Error { + #[inline(always)] + #[must_use] + fn from(code: OSStatus) -> Self { + Self::from_code(code) + } +} + +impl fmt::Display for Error { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(message) = self.message() { + write!(fmt, "{}", message) + } else { + write!(fmt, "error code {}", self.code()) + } + } +} + +impl error::Error for Error {} diff --git a/vendor/security-framework/src/certificate.rs b/vendor/security-framework/src/certificate.rs new file mode 100644 index 000000000..e33168a75 --- /dev/null +++ b/vendor/security-framework/src/certificate.rs @@ -0,0 +1,320 @@ +//! Certificate support. + +use core_foundation::array::{CFArray, CFArrayRef}; +use core_foundation::base::{TCFType, ToVoid}; +use core_foundation::data::CFData; +use core_foundation::dictionary::CFMutableDictionary; +use core_foundation::string::CFString; +use core_foundation_sys::base::kCFAllocatorDefault; +#[cfg(target_os = "ios")] +use security_framework_sys::base::{errSecNotTrusted, errSecSuccess}; +use security_framework_sys::base::{errSecParam, SecCertificateRef}; +use security_framework_sys::certificate::*; +use security_framework_sys::keychain_item::SecItemDelete; +use std::fmt; +use std::ptr; + +use crate::base::{Error, Result}; +use crate::cvt; +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use crate::key; +#[cfg(target_os = "macos")] +use crate::os::macos::keychain::SecKeychain; +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use core_foundation::base::FromVoid; +#[cfg(any(feature = "OSX_10_13", target_os = "ios"))] +use core_foundation::error::{CFError, CFErrorRef}; +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use core_foundation::number::CFNumber; +#[cfg(feature = "serial-number-bigint")] +use num_bigint::BigUint; +use security_framework_sys::item::kSecValueRef; +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use std::ops::Deref; + +declare_TCFType! { + /// A type representing a certificate. + SecCertificate, SecCertificateRef +} +impl_TCFType!(SecCertificate, SecCertificateRef, SecCertificateGetTypeID); + +unsafe impl Sync for SecCertificate {} +unsafe impl Send for SecCertificate {} + +impl fmt::Debug for SecCertificate { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("SecCertificate") + .field("subject", &self.subject_summary()) + .finish() + } +} + +impl SecCertificate { + /// Creates a `SecCertificate` from DER encoded certificate data. + pub fn from_der(der_data: &[u8]) -> Result<Self> { + let der_data = CFData::from_buffer(der_data); + unsafe { + let certificate = + SecCertificateCreateWithData(kCFAllocatorDefault, der_data.as_concrete_TypeRef()); + if certificate.is_null() { + Err(Error::from_code(errSecParam)) + } else { + Ok(Self::wrap_under_create_rule(certificate)) + } + } + } + + /// Returns DER encoded data describing this certificate. + #[must_use] + pub fn to_der(&self) -> Vec<u8> { + unsafe { + let der_data = SecCertificateCopyData(self.0); + CFData::wrap_under_create_rule(der_data).to_vec() + } + } + + /// Adds a certificate to a keychain. + #[cfg(target_os="macos")] + pub fn add_to_keychain(&self, keychain: Option<SecKeychain>) -> Result<()> { + let kch = match keychain { + Some(kch) => kch, + _ => SecKeychain::default()?, + }; + cvt(unsafe { + SecCertificateAddToKeychain(self.as_CFTypeRef() as *mut _, kch.as_CFTypeRef() as *mut _) + }) + } + + /// Returns a human readable summary of this certificate. + #[must_use] + pub fn subject_summary(&self) -> String { + unsafe { + let summary = SecCertificateCopySubjectSummary(self.0); + CFString::wrap_under_create_rule(summary).to_string() + } + } + + /// Returns a vector of email addresses for the subject of the certificate. + pub fn email_addresses(&self) -> Result<Vec<String>, Error> { + let mut array: CFArrayRef = ptr::null(); + unsafe { + cvt(SecCertificateCopyEmailAddresses( + self.as_concrete_TypeRef(), + &mut array, + ))?; + + let array = CFArray::<CFString>::wrap_under_create_rule(array); + Ok(array.into_iter().map(|p| p.to_string()).collect()) + } + } + + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + /// Returns DER encoded X.509 distinguished name of the certificate issuer. + #[must_use] + pub fn issuer(&self) -> Vec<u8> { + unsafe { + let issuer = SecCertificateCopyNormalizedIssuerSequence(self.0); + CFData::wrap_under_create_rule(issuer).to_vec() + } + } + + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + /// Returns DER encoded X.509 distinguished name of the certificate subject. + #[must_use] + pub fn subject(&self) -> Vec<u8> { + unsafe { + let subject = SecCertificateCopyNormalizedSubjectSequence(self.0); + CFData::wrap_under_create_rule(subject).to_vec() + } + } + + #[cfg(any(feature = "OSX_10_13", target_os = "ios"))] + /// Returns DER encoded serial number of the certificate. + pub fn serial_number_bytes(&self) -> Result<Vec<u8>, CFError> { + unsafe { + let mut error: CFErrorRef = ptr::null_mut(); + let serial_number = SecCertificateCopySerialNumberData(self.0, &mut error); + if error.is_null() { + Ok(CFData::wrap_under_create_rule(serial_number).to_vec()) + } else { + Err(CFError::wrap_under_create_rule(error)) + } + } + } + + /// Use `BigUint::from_bytes_be(serial_number_bytes())` instead + #[deprecated(note = "use serial_number_bytes()")] + #[cfg(feature = "serial-number-bigint")] + pub fn serial_number(&self) -> Result<BigUint, CFError> { + Ok(BigUint::from_bytes_be(&self.serial_number_bytes()?)) + } + + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + /// Returns DER encoded subjectPublicKeyInfo of certificate if available. This can be used + /// for certificate pinning. + pub fn public_key_info_der(&self) -> Result<Option<Vec<u8>>> { + // Imported from TrustKit + // https://github.com/datatheorem/TrustKit/blob/master/TrustKit/Pinning/TSKSPKIHashCache.m + let public_key = self.public_key()?; + Ok(self.pk_to_der(public_key)) + } + + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + #[must_use] + fn pk_to_der(&self, public_key: key::SecKey) -> Option<Vec<u8>> { + use security_framework_sys::item::kSecAttrKeyType; + use security_framework_sys::item::kSecAttrKeySizeInBits; + + let public_key_attributes = public_key.attributes(); + let public_key_type = public_key_attributes + .find(unsafe { kSecAttrKeyType }.cast::<std::os::raw::c_void>())?; + let public_keysize = public_key_attributes + .find(unsafe { kSecAttrKeySizeInBits }.cast::<std::os::raw::c_void>())?; + let public_keysize = unsafe { CFNumber::from_void(*public_keysize.deref()) }; + let public_keysize_val = public_keysize.to_i64()? as u32; + let hdr_bytes = get_asn1_header_bytes( + unsafe { CFString::wrap_under_get_rule(*public_key_type.deref() as _) }, + public_keysize_val, + )?; + let public_key_data = public_key.external_representation()?; + let mut out = Vec::with_capacity(hdr_bytes.len() + public_key_data.len() as usize); + out.extend_from_slice(hdr_bytes); + out.extend_from_slice(public_key_data.bytes()); + Some(out) + } + + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + /// Get public key from certificate + pub fn public_key(&self) -> Result<key::SecKey> { + use crate::policy::SecPolicy; + use crate::trust::SecTrust; + use std::slice::from_ref; + + let policy = SecPolicy::create_x509(); + let mut trust = SecTrust::create_with_certificates(from_ref(self), from_ref(&policy))?; + #[allow(deprecated)] + #[cfg(not(target_os = "ios"))] + trust.evaluate()?; + #[cfg(target_os = "ios")] + cvt(match trust.evaluate_with_error() { + Ok(_) => errSecSuccess, + Err(_) => errSecNotTrusted, + })?; + trust.copy_public_key() + } + + /// Translates to `SecItemDelete`, passing in the `SecCertificateRef` + pub fn delete(&self) -> Result<(), Error> { + let query = CFMutableDictionary::from_CFType_pairs(&[( + unsafe { kSecValueRef }.to_void(), + self.to_void(), + )]); + + cvt(unsafe { SecItemDelete(query.as_concrete_TypeRef()) }) + } +} + +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +fn get_asn1_header_bytes(pkt: CFString, ksz: u32) -> Option<&'static [u8]> { + use security_framework_sys::item::kSecAttrKeyTypeRSA; + use security_framework_sys::item::kSecAttrKeyTypeECSECPrimeRandom; + + if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeRSA) } && ksz == 2048 { + return Some(&RSA_2048_ASN1_HEADER); + } + if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeRSA) } && ksz == 4096 { + return Some(&RSA_4096_ASN1_HEADER); + } + if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeECSECPrimeRandom) } + && ksz == 256 + { + return Some(&EC_DSA_SECP_256_R1_ASN1_HEADER); + } + if pkt == unsafe { CFString::wrap_under_get_rule(kSecAttrKeyTypeECSECPrimeRandom) } + && ksz == 384 + { + return Some(&EC_DSA_SECP_384_R1_ASN1_HEADER); + } + None +} + +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +const RSA_2048_ASN1_HEADER: [u8; 24] = [ + 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, +]; + +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +const RSA_4096_ASN1_HEADER: [u8; 24] = [ + 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, +]; + +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +const EC_DSA_SECP_256_R1_ASN1_HEADER: [u8; 26] = [ + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, +]; + +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +const EC_DSA_SECP_384_R1_ASN1_HEADER: [u8; 23] = [ + 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, + 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, +]; + +#[cfg(test)] +mod test { + use crate::test::certificate; + #[cfg(feature = "serial-number-bigint")] + use num_bigint::BigUint; + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + use x509_parser::prelude::*; + + #[test] + fn subject_summary() { + let cert = certificate(); + assert_eq!("foobar.com", cert.subject_summary()); + } + + #[test] + fn email_addresses() { + let cert = certificate(); + assert_eq!(Vec::<String>::new(), cert.email_addresses().unwrap()); + } + + #[test] + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + fn issuer() { + let cert = certificate(); + let issuer = cert.issuer(); + let (_, name) = X509Name::from_der(&issuer).unwrap(); + let name_str = name.to_string_with_registry(oid_registry()).unwrap(); + assert_eq!( + "C=US, ST=CALIFORNIA, L=PALO ALTO, O=FOOBAR LLC, OU=DEV LAND, CN=FOOBAR.COM", + name_str + ); + } + + #[test] + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + fn subject() { + let cert = certificate(); + let subject = cert.subject(); + let (_, name) = X509Name::from_der(&subject).unwrap(); + let name_str = name.to_string_with_registry(oid_registry()).unwrap(); + assert_eq!( + "C=US, ST=CALIFORNIA, L=PALO ALTO, O=FOOBAR LLC, OU=DEV LAND, CN=FOOBAR.COM", + name_str + ); + } + + #[test] + #[cfg(feature = "serial-number-bigint")] + #[allow(deprecated)] + fn serial_number() { + let cert = certificate(); + let serial_number = cert.serial_number().unwrap(); + assert_eq!(BigUint::from(16452297291294946383_u128), serial_number); + } +} diff --git a/vendor/security-framework/src/cipher_suite.rs b/vendor/security-framework/src/cipher_suite.rs new file mode 100644 index 000000000..4462b5e5e --- /dev/null +++ b/vendor/security-framework/src/cipher_suite.rs @@ -0,0 +1,246 @@ +//! Cipher Suites supported by Secure Transport + +use security_framework_sys::cipher_suite::*; + +macro_rules! make_suites { + ($($suite:ident),+) => { + /// TLS cipher suites. + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] + pub struct CipherSuite(SSLCipherSuite); + + #[allow(missing_docs)] + impl CipherSuite { + $( + pub const $suite: Self = Self($suite); + )+ + + #[inline(always)] + #[must_use] + pub fn from_raw(raw: SSLCipherSuite) -> Self { + Self(raw) + } + + #[inline(always)] + #[must_use] + pub fn to_raw(&self) -> SSLCipherSuite { + self.0 + } + } + } +} + +make_suites! { + // The commented out ones up here are aliases of the matching TLS suites + SSL_NULL_WITH_NULL_NULL, + SSL_RSA_WITH_NULL_MD5, + SSL_RSA_WITH_NULL_SHA, + SSL_RSA_EXPORT_WITH_RC4_40_MD5, + SSL_RSA_WITH_RC4_128_MD5, + SSL_RSA_WITH_RC4_128_SHA, + SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5, + SSL_RSA_WITH_IDEA_CBC_SHA, + SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, + SSL_RSA_WITH_DES_CBC_SHA, + //SSL_RSA_WITH_3DES_EDE_CBC_SHA, + SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA, + SSL_DH_DSS_WITH_DES_CBC_SHA, + //SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA, + SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA, + SSL_DH_RSA_WITH_DES_CBC_SHA, + //SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA, + SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, + SSL_DHE_DSS_WITH_DES_CBC_SHA, + //SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, + SSL_DHE_RSA_WITH_DES_CBC_SHA, + //SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + SSL_DH_anon_EXPORT_WITH_RC4_40_MD5, + //SSL_DH_anon_WITH_RC4_128_MD5, + SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA, + SSL_DH_anon_WITH_DES_CBC_SHA, + //SSL_DH_anon_WITH_3DES_EDE_CBC_SHA, + SSL_FORTEZZA_DMS_WITH_NULL_SHA, + SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA, + + /* TLS addenda using AES, per RFC 3268 */ + TLS_RSA_WITH_AES_128_CBC_SHA, + TLS_DH_DSS_WITH_AES_128_CBC_SHA, + TLS_DH_RSA_WITH_AES_128_CBC_SHA, + TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + TLS_DH_anon_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA, + TLS_DH_DSS_WITH_AES_256_CBC_SHA, + TLS_DH_RSA_WITH_AES_256_CBC_SHA, + TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + TLS_DH_anon_WITH_AES_256_CBC_SHA, + + /* ECDSA addenda, RFC 4492 */ + TLS_ECDH_ECDSA_WITH_NULL_SHA, + TLS_ECDH_ECDSA_WITH_RC4_128_SHA, + TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, + TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, + TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, + TLS_ECDHE_ECDSA_WITH_NULL_SHA, + TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + TLS_ECDH_RSA_WITH_NULL_SHA, + TLS_ECDH_RSA_WITH_RC4_128_SHA, + TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, + TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, + TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, + TLS_ECDHE_RSA_WITH_NULL_SHA, + TLS_ECDHE_RSA_WITH_RC4_128_SHA, + TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + TLS_ECDH_anon_WITH_NULL_SHA, + TLS_ECDH_anon_WITH_RC4_128_SHA, + TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, + TLS_ECDH_anon_WITH_AES_128_CBC_SHA, + TLS_ECDH_anon_WITH_AES_256_CBC_SHA, + + /* TLS 1.2 addenda, RFC 5246 */ + + /* Initial state. */ + TLS_NULL_WITH_NULL_NULL, + + /* Server provided RSA certificate for key exchange. */ + TLS_RSA_WITH_NULL_MD5, + TLS_RSA_WITH_NULL_SHA, + TLS_RSA_WITH_RC4_128_MD5, + TLS_RSA_WITH_RC4_128_SHA, + TLS_RSA_WITH_3DES_EDE_CBC_SHA, + //TLS_RSA_WITH_AES_128_CBC_SHA, + //TLS_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_NULL_SHA256, + TLS_RSA_WITH_AES_128_CBC_SHA256, + TLS_RSA_WITH_AES_256_CBC_SHA256, + + /* Server-authenticated (and optionally client-authenticated) Diffie-Hellman. */ + TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA, + TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA, + TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + //TLS_DH_DSS_WITH_AES_128_CBC_SHA, + //TLS_DH_RSA_WITH_AES_128_CBC_SHA, + //TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + //TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + //TLS_DH_DSS_WITH_AES_256_CBC_SHA, + //TLS_DH_RSA_WITH_AES_256_CBC_SHA, + //TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + //TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + TLS_DH_DSS_WITH_AES_128_CBC_SHA256, + TLS_DH_RSA_WITH_AES_128_CBC_SHA256, + TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + TLS_DH_DSS_WITH_AES_256_CBC_SHA256, + TLS_DH_RSA_WITH_AES_256_CBC_SHA256, + TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + + /* Completely anonymous Diffie-Hellman */ + TLS_DH_anon_WITH_RC4_128_MD5, + TLS_DH_anon_WITH_3DES_EDE_CBC_SHA, + //TLS_DH_anon_WITH_AES_128_CBC_SHA, + //TLS_DH_anon_WITH_AES_256_CBC_SHA, + TLS_DH_anon_WITH_AES_128_CBC_SHA256, + TLS_DH_anon_WITH_AES_256_CBC_SHA256, + + /* Addendum from RFC 4279, TLS PSK */ + + TLS_PSK_WITH_RC4_128_SHA, + TLS_PSK_WITH_3DES_EDE_CBC_SHA, + TLS_PSK_WITH_AES_128_CBC_SHA, + TLS_PSK_WITH_AES_256_CBC_SHA, + TLS_DHE_PSK_WITH_RC4_128_SHA, + TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA, + TLS_DHE_PSK_WITH_AES_128_CBC_SHA, + TLS_DHE_PSK_WITH_AES_256_CBC_SHA, + TLS_RSA_PSK_WITH_RC4_128_SHA, + TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, + TLS_RSA_PSK_WITH_AES_128_CBC_SHA, + TLS_RSA_PSK_WITH_AES_256_CBC_SHA, + + /* RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */ + + TLS_PSK_WITH_NULL_SHA, + TLS_DHE_PSK_WITH_NULL_SHA, + TLS_RSA_PSK_WITH_NULL_SHA, + + /* Addenda from rfc 5288 AES Galois Counter Mode (GCM) Cipher Suites + for TLS. */ + TLS_RSA_WITH_AES_128_GCM_SHA256, + TLS_RSA_WITH_AES_256_GCM_SHA384, + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_DH_RSA_WITH_AES_128_GCM_SHA256, + TLS_DH_RSA_WITH_AES_256_GCM_SHA384, + TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, + TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, + TLS_DH_DSS_WITH_AES_128_GCM_SHA256, + TLS_DH_DSS_WITH_AES_256_GCM_SHA384, + TLS_DH_anon_WITH_AES_128_GCM_SHA256, + TLS_DH_anon_WITH_AES_256_GCM_SHA384, + + /* RFC 5487 - PSK with SHA-256/384 and AES GCM */ + TLS_PSK_WITH_AES_128_GCM_SHA256, + TLS_PSK_WITH_AES_256_GCM_SHA384, + TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, + TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, + TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, + TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, + + TLS_PSK_WITH_AES_128_CBC_SHA256, + TLS_PSK_WITH_AES_256_CBC_SHA384, + TLS_PSK_WITH_NULL_SHA256, + TLS_PSK_WITH_NULL_SHA384, + + TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, + TLS_DHE_PSK_WITH_AES_256_CBC_SHA384, + TLS_DHE_PSK_WITH_NULL_SHA256, + TLS_DHE_PSK_WITH_NULL_SHA384, + + TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, + TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, + TLS_RSA_PSK_WITH_NULL_SHA256, + TLS_RSA_PSK_WITH_NULL_SHA384, + + + /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with + HMAC SHA-256/384. */ + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, + TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, + TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, + + /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with + SHA-256/384 and AES Galois Counter Mode (GCM) */ + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, + + /* RFC 5746 - Secure Renegotiation */ + TLS_EMPTY_RENEGOTIATION_INFO_SCSV, + /* + * Tags for SSL 2 cipher kinds which are not specified + * for SSL 3. + */ + SSL_RSA_WITH_RC2_CBC_MD5, + SSL_RSA_WITH_IDEA_CBC_MD5, + SSL_RSA_WITH_DES_CBC_MD5, + SSL_RSA_WITH_3DES_EDE_CBC_MD5, + SSL_NO_SUCH_CIPHERSUITE +} diff --git a/vendor/security-framework/src/dlsym.rs b/vendor/security-framework/src/dlsym.rs new file mode 100644 index 000000000..d3229298f --- /dev/null +++ b/vendor/security-framework/src/dlsym.rs @@ -0,0 +1,50 @@ +// dlsym.rs is taken from mio +// https://github.com/carllerche/mio/blob/master/src/sys/unix/dlsym.rs + +use std::marker; +use std::mem; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use libc; + +macro_rules! dlsym { + (fn $name:ident($($t:ty),*) -> $ret:ty) => ( + #[allow(bad_style)] + static $name: $crate::dlsym::DlSym<unsafe extern fn($($t),*) -> $ret> = + $crate::dlsym::DlSym { + name: concat!(stringify!($name), "\0"), + addr: ::std::sync::atomic::AtomicUsize::new(0), + _marker: ::std::marker::PhantomData, + }; + ) +} + +pub struct DlSym<F> { + pub name: &'static str, + pub addr: AtomicUsize, + pub _marker: marker::PhantomData<F>, +} + +impl<F> DlSym<F> { + pub fn get(&self) -> Option<&F> { + assert_eq!(mem::size_of::<F>(), mem::size_of::<usize>()); + unsafe { + if self.addr.load(Ordering::SeqCst) == 0 { + self.addr.store(fetch(self.name), Ordering::SeqCst); + } + if self.addr.load(Ordering::SeqCst) == 1 { + None + } else { + mem::transmute::<&AtomicUsize, Option<&F>>(&self.addr) + } + } + } +} + +unsafe fn fetch(name: &str) -> usize { + assert_eq!(name.as_bytes()[name.len() - 1], 0); + match libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize { + 0 => 1, + n => n, + } +} diff --git a/vendor/security-framework/src/identity.rs b/vendor/security-framework/src/identity.rs new file mode 100644 index 000000000..337752511 --- /dev/null +++ b/vendor/security-framework/src/identity.rs @@ -0,0 +1,83 @@ +//! Identity support. + +use core_foundation::base::TCFType; +use core_foundation::base::ToVoid; +use core_foundation::dictionary::CFMutableDictionary; +use security_framework_sys::base::SecIdentityRef; +use security_framework_sys::identity::{SecIdentityCopyCertificate, SecIdentityCopyPrivateKey, SecIdentityGetTypeID}; +use security_framework_sys::item::kSecValueRef; +use security_framework_sys::keychain_item::SecItemDelete; +use std::fmt; +use std::ptr; + +use crate::base::Error; +use crate::base::Result; +use crate::certificate::SecCertificate; +use crate::cvt; +use crate::key::SecKey; + +declare_TCFType! { + /// A type representing an identity. + /// + /// Identities are a certificate paired with the corresponding private key. + SecIdentity, SecIdentityRef +} +impl_TCFType!(SecIdentity, SecIdentityRef, SecIdentityGetTypeID); + +unsafe impl Sync for SecIdentity {} +unsafe impl Send for SecIdentity {} + +impl fmt::Debug for SecIdentity { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = fmt.debug_struct("SecIdentity"); + if let Ok(cert) = self.certificate() { + builder.field("certificate", &cert); + } + if let Ok(key) = self.private_key() { + builder.field("private_key", &key); + } + builder.finish() + } +} + +impl SecIdentity { + /// Returns the certificate corresponding to this identity. + pub fn certificate(&self) -> Result<SecCertificate> { + unsafe { + let mut certificate = ptr::null_mut(); + cvt(SecIdentityCopyCertificate(self.0, &mut certificate))?; + Ok(SecCertificate::wrap_under_create_rule(certificate)) + } + } + + /// Returns the private key corresponding to this identity. + pub fn private_key(&self) -> Result<SecKey> { + unsafe { + let mut key = ptr::null_mut(); + cvt(SecIdentityCopyPrivateKey(self.0, &mut key))?; + Ok(SecKey::wrap_under_create_rule(key)) + } + } + + /// Translates to `SecItemDelete`, passing in the `SecIdentityRef` + pub fn delete(&self) -> Result<(), Error> { + let query = CFMutableDictionary::from_CFType_pairs(&[( + unsafe { kSecValueRef }.to_void(), + self.to_void(), + )]); + + cvt(unsafe { SecItemDelete(query.as_concrete_TypeRef()) }) + } +} + +#[cfg(test)] +mod test { + use super::SecIdentity; + + #[test] + fn identity_has_send_bound() { + fn assert_send<T: Send>() {} + assert_send::<SecIdentity>(); + } +} diff --git a/vendor/security-framework/src/import_export.rs b/vendor/security-framework/src/import_export.rs new file mode 100644 index 000000000..378a2d5be --- /dev/null +++ b/vendor/security-framework/src/import_export.rs @@ -0,0 +1,174 @@ +//! Security Framework type import/export support. + +use core_foundation::array::CFArray; +use core_foundation::base::{CFType, TCFType}; +use core_foundation::data::CFData; +use core_foundation::dictionary::CFDictionary; +use core_foundation::string::CFString; +use security_framework_sys::import_export::*; +use std::ptr; + +use crate::base::Result; +use crate::certificate::SecCertificate; +use crate::cvt; +use crate::identity::SecIdentity; +#[cfg(target_os = "macos")] +use crate::os::macos::access::SecAccess; +#[cfg(target_os = "macos")] +use crate::os::macos::keychain::SecKeychain; +use crate::trust::SecTrust; + +/// Information about an imported identity. +pub struct ImportedIdentity { + /// The label of the identity. + pub label: Option<String>, + /// The ID of the identity. Typically the SHA-1 hash of the public key. + pub key_id: Option<Vec<u8>>, + /// A `SecTrust` object set up to validate this identity. + pub trust: Option<SecTrust>, + /// A certificate chain validating this identity. + pub cert_chain: Option<Vec<SecCertificate>>, + /// The identity itself. + pub identity: Option<SecIdentity>, + _p: (), +} + +/// A builder type to import an identity from PKCS#12 formatted data. +#[derive(Default)] +pub struct Pkcs12ImportOptions { + passphrase: Option<CFString>, + #[cfg(target_os = "macos")] + keychain: Option<SecKeychain>, + #[cfg(target_os = "macos")] + access: Option<SecAccess>, +} + +#[cfg(target_os = "macos")] +impl crate::Pkcs12ImportOptionsInternals for Pkcs12ImportOptions { + #[inline(always)] + fn keychain(&mut self, keychain: SecKeychain) -> &mut Self { + self.keychain = Some(keychain); + self + } + + #[inline(always)] + fn access(&mut self, access: SecAccess) -> &mut Self { + self.access = Some(access); + self + } +} + +impl Pkcs12ImportOptions { + /// Creates a new builder with default options. + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Specifies the passphrase to be used to decrypt the data. + /// + /// This must be specified, as unencrypted PKCS#12 data is not supported. + #[inline] + pub fn passphrase(&mut self, passphrase: &str) -> &mut Self { + self.passphrase = Some(CFString::new(passphrase)); + self + } + + /// Imports identities from PKCS#12 encoded data. + pub fn import(&self, pkcs12_data: &[u8]) -> Result<Vec<ImportedIdentity>> { + unsafe { + let pkcs12_data = CFData::from_buffer(pkcs12_data); + + let mut options = vec![]; + + if let Some(ref passphrase) = self.passphrase { + options.push(( + CFString::wrap_under_get_rule(kSecImportExportPassphrase), + passphrase.as_CFType(), + )); + } + + self.import_setup(&mut options); + + let options = CFDictionary::from_CFType_pairs(&options); + + let mut raw_items = ptr::null(); + cvt(SecPKCS12Import( + pkcs12_data.as_concrete_TypeRef(), + options.as_concrete_TypeRef(), + &mut raw_items, + ))?; + let raw_items = CFArray::<CFDictionary<CFString, *const _>>::wrap_under_create_rule(raw_items); + + let mut items = vec![]; + + for raw_item in &raw_items { + let label = raw_item + .find(kSecImportItemLabel) + .map(|label| CFString::wrap_under_get_rule((*label).cast()).to_string()); + let key_id = raw_item + .find(kSecImportItemKeyID) + .map(|key_id| CFData::wrap_under_get_rule((*key_id).cast()).to_vec()); + let trust = raw_item + .find(kSecImportItemTrust) + .map(|trust| SecTrust::wrap_under_get_rule(*trust as *mut _)); + let cert_chain = raw_item.find(kSecImportItemCertChain.cast()).map( + |cert_chain| { + CFArray::<SecCertificate>::wrap_under_get_rule((*cert_chain).cast()) + .iter() + .map(|c| c.clone()) + .collect() + }, + ); + let identity = raw_item + .find(kSecImportItemIdentity) + .map(|identity| SecIdentity::wrap_under_get_rule(*identity as *mut _)); + + items.push(ImportedIdentity { + label, + key_id, + trust, + cert_chain, + identity, + _p: (), + }); + } + + Ok(items) + } + } + + #[cfg(target_os = "macos")] + fn import_setup(&self, options: &mut Vec<(CFString, CFType)>) { + unsafe { + if let Some(ref keychain) = self.keychain { + options.push(( + CFString::wrap_under_get_rule(kSecImportExportKeychain), + keychain.as_CFType(), + )); + } + + if let Some(ref access) = self.access { + options.push(( + CFString::wrap_under_get_rule(kSecImportExportAccess), + access.as_CFType(), + )); + } + } + } + + #[cfg(not(target_os = "macos"))] + fn import_setup(&self, _: &mut Vec<(CFString, CFType)>) {} +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn missing_passphrase() { + let data = include_bytes!("../test/server.p12"); + assert!(Pkcs12ImportOptions::new().import(data).is_err()); + } +} diff --git a/vendor/security-framework/src/item.rs b/vendor/security-framework/src/item.rs new file mode 100644 index 000000000..6f252e00f --- /dev/null +++ b/vendor/security-framework/src/item.rs @@ -0,0 +1,689 @@ +//! Support to search for items in a keychain. + +use core_foundation::array::CFArray; +use core_foundation::base::{CFType, TCFType, ToVoid}; +use core_foundation::boolean::CFBoolean; +use core_foundation::data::CFData; +use core_foundation::date::CFDate; +use core_foundation::dictionary::{CFDictionary, CFMutableDictionary}; +use core_foundation::number::CFNumber; +use core_foundation::string::CFString; +use core_foundation_sys::base::{CFCopyDescription, CFGetTypeID, CFRelease, CFTypeRef}; +use core_foundation_sys::string::CFStringRef; +use security_framework_sys::item::*; +use security_framework_sys::keychain_item::{SecItemAdd, SecItemCopyMatching}; +use std::collections::HashMap; +use std::fmt; +use std::ptr; + +use crate::base::Result; +use crate::certificate::SecCertificate; +use crate::cvt; +use crate::identity::SecIdentity; +use crate::key::SecKey; +#[cfg(target_os = "macos")] +use crate::os::macos::keychain::SecKeychain; + +/// Specifies the type of items to search for. +#[derive(Debug, Copy, Clone)] +pub struct ItemClass(CFStringRef); + +impl ItemClass { + /// Look for `SecKeychainItem`s corresponding to generic passwords. + #[inline(always)] + #[must_use] + pub fn generic_password() -> Self { + unsafe { Self(kSecClassGenericPassword) } + } + + /// Look for `SecKeychainItem`s corresponding to internet passwords. + #[inline(always)] + #[must_use] + pub fn internet_password() -> Self { + unsafe { Self(kSecClassInternetPassword) } + } + + /// Look for `SecCertificate`s. + #[inline(always)] + #[must_use] + pub fn certificate() -> Self { + unsafe { Self(kSecClassCertificate) } + } + + /// Look for `SecKey`s. + #[inline(always)] + #[must_use] + pub fn key() -> Self { + unsafe { Self(kSecClassKey) } + } + + /// Look for `SecIdentity`s. + #[inline(always)] + #[must_use] + pub fn identity() -> Self { + unsafe { Self(kSecClassIdentity) } + } + + #[inline] + fn to_value(self) -> CFType { + unsafe { CFType::wrap_under_get_rule(self.0.cast()) } + } +} + +/// Specifies the type of keys to search for. +#[derive(Debug, Copy, Clone)] +pub struct KeyClass(CFStringRef); + +impl KeyClass { + /// `kSecAttrKeyClassPublic` + #[inline(always)] + #[must_use] pub fn public() -> Self { + unsafe { Self(kSecAttrKeyClassPublic) } + } + /// `kSecAttrKeyClassPrivate` + #[inline(always)] + #[must_use] pub fn private() -> Self { + unsafe { Self(kSecAttrKeyClassPrivate) } + } + /// `kSecAttrKeyClassSymmetric` + #[inline(always)] + #[must_use] pub fn symmetric() -> Self { + unsafe { Self(kSecAttrKeyClassSymmetric) } + } + + #[inline] + fn to_value(self) -> CFType { + unsafe { CFType::wrap_under_get_rule(self.0.cast()) } + } +} + +/// Specifies the number of results returned by a search +#[derive(Debug, Copy, Clone)] +pub enum Limit { + /// Always return all results + All, + + /// Return up to the specified number of results + Max(i64), +} + +impl Limit { + #[inline] + fn to_value(self) -> CFType { + match self { + Self::All => unsafe { CFString::wrap_under_get_rule(kSecMatchLimitAll).into_CFType() }, + Self::Max(l) => CFNumber::from(l).into_CFType(), + } + } +} + +impl From<i64> for Limit { + #[inline] + fn from(limit: i64) -> Self { + Self::Max(limit) + } +} + +/// A builder type to search for items in keychains. +#[derive(Default)] +pub struct ItemSearchOptions { + #[cfg(target_os = "macos")] + keychains: Option<CFArray<SecKeychain>>, + #[cfg(not(target_os = "macos"))] + keychains: Option<CFArray<CFType>>, + class: Option<ItemClass>, + key_class: Option<KeyClass>, + load_refs: bool, + load_attributes: bool, + load_data: bool, + limit: Option<Limit>, + label: Option<CFString>, + service: Option<CFString>, + account: Option<CFString>, + access_group: Option<CFString>, + pub_key_hash: Option<CFData>, + app_label: Option<CFData>, +} + +#[cfg(target_os = "macos")] +impl crate::ItemSearchOptionsInternals for ItemSearchOptions { + #[inline] + fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self { + self.keychains = Some(CFArray::from_CFTypes(keychains)); + self + } +} + +impl ItemSearchOptions { + /// Creates a new builder with default options. + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Search only for items of the specified class. + #[inline(always)] + pub fn class(&mut self, class: ItemClass) -> &mut Self { + self.class = Some(class); + self + } + + /// Search only for keys of the specified class. Also sets self.class to + /// ItemClass::key(). + #[inline(always)] + pub fn key_class(&mut self, key_class: KeyClass) -> &mut Self { + self.class(ItemClass::key()); + self.key_class = Some(key_class); + self + } + + /// Load Security Framework objects (`SecCertificate`, `SecKey`, etc) for + /// the results. + #[inline(always)] + pub fn load_refs(&mut self, load_refs: bool) -> &mut Self { + self.load_refs = load_refs; + self + } + + /// Load Security Framework object attributes for + /// the results. + #[inline(always)] + pub fn load_attributes(&mut self, load_attributes: bool) -> &mut Self { + self.load_attributes = load_attributes; + self + } + + /// Load Security Framework objects data for + /// the results. + #[inline(always)] + pub fn load_data(&mut self, load_data: bool) -> &mut Self { + self.load_data = load_data; + self + } + + /// Limit the number of search results. + /// + /// If this is not called, the default limit is 1. + #[inline(always)] + pub fn limit<T: Into<Limit>>(&mut self, limit: T) -> &mut Self { + self.limit = Some(limit.into()); + self + } + + /// Search for an item with the given label. + #[inline(always)] + pub fn label(&mut self, label: &str) -> &mut Self { + self.label = Some(CFString::new(label)); + self + } + + /// Search for an item with the given service. + #[inline(always)] + pub fn service(&mut self, service: &str) -> &mut Self { + self.service = Some(CFString::new(service)); + self + } + + /// Search for an item with the given account. + #[inline(always)] + pub fn account(&mut self, account: &str) -> &mut Self { + self.account = Some(CFString::new(account)); + self + } + + /// Sets `kSecAttrAccessGroup` to `kSecAttrAccessGroupToken` + #[inline(always)] + pub fn access_group_token(&mut self) -> &mut Self { + self.access_group = unsafe { Some(CFString::wrap_under_get_rule(kSecAttrAccessGroupToken)) }; + self + } + + /// Search for a certificate with the given public key hash. + /// + /// This is only compatible with [`ItemClass::certificate`], to search for + /// a key by public key hash use [`ItemSearchOptions::application_label`] + /// instead. + #[inline(always)] + pub fn pub_key_hash(&mut self, pub_key_hash: &[u8]) -> &mut Self { + self.pub_key_hash = Some(CFData::from_buffer(pub_key_hash)); + self + } + + /// Search for a key with the given public key hash. + /// + /// This is only compatible with [`ItemClass::key`], to search for a + /// certificate by the public key hash use [`ItemSearchOptions::pub_key_hash`] + /// instead. + #[inline(always)] + pub fn application_label(&mut self, app_label: &[u8]) -> &mut Self { + self.app_label = Some(CFData::from_buffer(app_label)); + self + } + + /// Search for objects. + pub fn search(&self) -> Result<Vec<SearchResult>> { + unsafe { + let mut params = vec![]; + + if let Some(ref keychains) = self.keychains { + params.push(( + CFString::wrap_under_get_rule(kSecMatchSearchList), + keychains.as_CFType(), + )); + } + + if let Some(class) = self.class { + params.push((CFString::wrap_under_get_rule(kSecClass), class.to_value())); + } + + if let Some(key_class) = self.key_class { + params.push((CFString::wrap_under_get_rule(kSecAttrKeyClass), key_class.to_value())); + } + + if self.load_refs { + params.push(( + CFString::wrap_under_get_rule(kSecReturnRef), + CFBoolean::true_value().into_CFType(), + )); + } + + if self.load_attributes { + params.push(( + CFString::wrap_under_get_rule(kSecReturnAttributes), + CFBoolean::true_value().into_CFType(), + )); + } + + if self.load_data { + params.push(( + CFString::wrap_under_get_rule(kSecReturnData), + CFBoolean::true_value().into_CFType(), + )); + } + + if let Some(limit) = self.limit { + params.push(( + CFString::wrap_under_get_rule(kSecMatchLimit), + limit.to_value(), + )); + } + + if let Some(ref label) = self.label { + params.push(( + CFString::wrap_under_get_rule(kSecAttrLabel), + label.as_CFType(), + )); + } + + if let Some(ref service) = self.service { + params.push(( + CFString::wrap_under_get_rule(kSecAttrService), + service.as_CFType(), + )); + } + + if let Some(ref account) = self.account { + params.push(( + CFString::wrap_under_get_rule(kSecAttrAccount), + account.as_CFType(), + )); + } + + if let Some(ref access_group) = self.access_group { + params.push(( + CFString::wrap_under_get_rule(kSecAttrAccessGroup), + access_group.as_CFType(), + )); + } + + if let Some(ref pub_key_hash) = self.pub_key_hash { + params.push(( + CFString::wrap_under_get_rule(kSecAttrPublicKeyHash), + pub_key_hash.as_CFType(), + )); + } + + if let Some(ref app_label) = self.app_label { + params.push(( + CFString::wrap_under_get_rule(kSecAttrApplicationLabel), + app_label.as_CFType(), + )); + } + + let params = CFDictionary::from_CFType_pairs(¶ms); + + let mut ret = ptr::null(); + cvt(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?; + if ret.is_null() { + // SecItemCopyMatching returns NULL if no load_* was specified, + // causing a segfault. + return Ok(vec![]); + } + let type_id = CFGetTypeID(ret); + + let mut items = vec![]; + + if type_id == CFArray::<CFType>::type_id() { + let array: CFArray<CFType> = CFArray::wrap_under_create_rule(ret as *mut _); + for item in array.iter() { + items.push(get_item(item.as_CFTypeRef())); + } + } else { + items.push(get_item(ret)); + // This is a bit janky, but get_item uses wrap_under_get_rule + // which bumps the refcount but we want create semantics + CFRelease(ret); + } + + Ok(items) + } + } +} + +unsafe fn get_item(item: CFTypeRef) -> SearchResult { + let type_id = CFGetTypeID(item); + + if type_id == CFData::type_id() { + let data = CFData::wrap_under_get_rule(item as *mut _); + let mut buf = Vec::new(); + buf.extend_from_slice(data.bytes()); + return SearchResult::Data(buf); + } + + if type_id == CFDictionary::<*const u8, *const u8>::type_id() { + return SearchResult::Dict(CFDictionary::wrap_under_get_rule(item as *mut _)); + } + + #[cfg(target_os = "macos")] + { + use crate::os::macos::keychain_item::SecKeychainItem; + if type_id == SecKeychainItem::type_id() { + return SearchResult::Ref(Reference::KeychainItem( + SecKeychainItem::wrap_under_get_rule(item as *mut _), + )); + } + } + + let reference = if type_id == SecCertificate::type_id() { + Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _)) + } else if type_id == SecKey::type_id() { + Reference::Key(SecKey::wrap_under_get_rule(item as *mut _)) + } else if type_id == SecIdentity::type_id() { + Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _)) + } else { + panic!("Got bad type from SecItemCopyMatching: {}", type_id); + }; + + SearchResult::Ref(reference) +} + +/// An enum including all objects whose references can be returned from a search. +/// Note that generic _Keychain Items_, such as passwords and preferences, do +/// not have specific object types; they are modeled using dictionaries and so +/// are available directly as search results in variant `SearchResult::Dict`. +#[derive(Debug)] +pub enum Reference { + /// A `SecIdentity`. + Identity(SecIdentity), + /// A `SecCertificate`. + Certificate(SecCertificate), + /// A `SecKey`. + Key(SecKey), + /// A `SecKeychainItem`. + /// + /// Only defined on OSX + #[cfg(target_os = "macos")] + KeychainItem(crate::os::macos::keychain_item::SecKeychainItem), + #[doc(hidden)] + __NonExhaustive, +} + +/// An individual search result. +pub enum SearchResult { + /// A reference to the Security Framework object, if asked for. + Ref(Reference), + /// A dictionary of data about the Security Framework object, if asked for. + Dict(CFDictionary), + /// The Security Framework object as bytes, if asked for. + Data(Vec<u8>), + /// An unknown representation of the Security Framework object. + Other, +} + +impl fmt::Debug for SearchResult { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::Ref(ref reference) => fmt + .debug_struct("SearchResult::Ref") + .field("reference", reference) + .finish(), + Self::Data(ref buf) => fmt + .debug_struct("SearchResult::Data") + .field("data", buf) + .finish(), + Self::Dict(_) => { + let mut debug = fmt.debug_struct("SearchResult::Dict"); + for (k, v) in self.simplify_dict().unwrap() { + debug.field(&k, &v); + } + debug.finish() + } + Self::Other => write!(fmt, "SearchResult::Other"), + } + } +} + +impl SearchResult { + /// If the search result is a `CFDict`, simplify that to a + /// `HashMap<String, String>`. This transformation isn't + /// comprehensive, it only supports `CFString`, `CFDate`, and `CFData` + /// value types. + #[must_use] + pub fn simplify_dict(&self) -> Option<HashMap<String, String>> { + match *self { + Self::Dict(ref d) => unsafe { + let mut retmap = HashMap::new(); + let (keys, values) = d.get_keys_and_values(); + for (k, v) in keys.iter().zip(values.iter()) { + let keycfstr = CFString::wrap_under_get_rule((*k).cast()); + let val: String = match CFGetTypeID(*v) { + cfstring if cfstring == CFString::type_id() => { + format!("{}", CFString::wrap_under_get_rule((*v).cast())) + } + cfdata if cfdata == CFData::type_id() => { + let buf = CFData::wrap_under_get_rule((*v).cast()); + let mut vec = Vec::new(); + vec.extend_from_slice(buf.bytes()); + format!("{}", String::from_utf8_lossy(&vec)) + } + cfdate if cfdate == CFDate::type_id() => format!( + "{}", + CFString::wrap_under_create_rule(CFCopyDescription(*v)) + ), + _ => String::from("unknown"), + }; + retmap.insert(format!("{}", keycfstr), val); + } + Some(retmap) + }, + _ => None, + } + } +} + +/// Builder-pattern struct for specifying options for `add_item` (`SecAddItem` +/// wrapper). +/// +/// When finished populating options, call `to_dictionary()` and pass the +/// resulting `CFDictionary` to `add_item`. +pub struct ItemAddOptions { + /// The value (by ref or data) of the item to add, required. + pub value: ItemAddValue, + /// Optional kSecAttrLabel attribute. + pub label: Option<String>, + /// Optional keychain location. + pub location: Option<Location>, +} + +impl ItemAddOptions { + /// Specifies the item to add. + #[must_use] pub fn new(value: ItemAddValue) -> Self { + Self{ value, label: None, location: None } + } + /// Specifies the `kSecAttrLabel` attribute. + pub fn set_label(&mut self, label: impl Into<String>) -> &mut Self { + self.label = Some(label.into()); + self + } + /// Specifies which keychain to add the item to. + pub fn set_location(&mut self, location: Location) -> &mut Self { + self.location = Some(location); + self + } + /// Populates a `CFDictionary` to be passed to + pub fn to_dictionary(&self) -> CFDictionary { + let mut dict = CFMutableDictionary::from_CFType_pairs(&[]); + + let class_opt = match &self.value { + ItemAddValue::Ref(ref_) => ref_.class(), + ItemAddValue::Data { class, .. } => Some(*class), + }; + if let Some(class) = class_opt { + dict.add(&unsafe { kSecClass }.to_void(), &class.0.to_void()); + } + + let value_pair = match &self.value { + ItemAddValue::Ref(ref_) => (unsafe { kSecValueRef }.to_void(), ref_.ref_()), + ItemAddValue::Data { data, .. } => (unsafe { kSecValueData }.to_void(), data.to_void()), + }; + dict.add(&value_pair.0, &value_pair.1); + + if let Some(location) = &self.location { + match location { + #[cfg(any(feature = "OSX_10_15", target_os = "ios"))] + Location::DataProtectionKeychain => { + dict.add( + &unsafe { kSecUseDataProtectionKeychain }.to_void(), + &CFBoolean::true_value().to_void(), + ); + } + #[cfg(target_os = "macos")] + Location::DefaultFileKeychain => {} + #[cfg(target_os = "macos")] + Location::FileKeychain(keychain) => { + dict.add(&unsafe { kSecUseKeychain }.to_void(), &keychain.to_void()); + }, + } + } + + let label = self.label.as_deref().map(CFString::from); + if let Some(label) = &label { + dict.add(&unsafe { kSecAttrLabel }.to_void(), &label.to_void()); + } + + dict.to_immutable() + } +} + +/// Value of an item to add to the keychain. +pub enum ItemAddValue { + /// Pass item by Ref (kSecValueRef) + Ref(AddRef), + /// Pass item by Data (kSecValueData) + Data { + /// The item class (kSecClass). + class: ItemClass, + /// The item data. + data: CFData, + }, +} + +/// Type of Ref to add to the keychain. +pub enum AddRef { + /// SecKey + Key(SecKey), + /// SecIdentity + Identity(SecIdentity), + /// SecCertificate + Certificate(SecCertificate), +} + +impl AddRef { + fn class(&self) -> Option<ItemClass> { + match self { + AddRef::Key(_) => Some(ItemClass::key()), + // kSecClass should not be specified when adding a SecIdentityRef: + // https://developer.apple.com/forums/thread/25751 + AddRef::Identity(_) => None, + AddRef::Certificate(_) => Some(ItemClass::certificate()), + } + } + fn ref_(&self) -> CFTypeRef { + match self { + AddRef::Key(key) => key.as_CFTypeRef(), + AddRef::Identity(id) => id.as_CFTypeRef(), + AddRef::Certificate(cert) => cert.as_CFTypeRef(), + } + } +} + +/// Which keychain to add an item to. +/// +/// <https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains> +pub enum Location { + /// Store the item in the newer DataProtectionKeychain. This is the only + /// keychain on iOS. On macOS, this is the newer and more consistent + /// keychain implementation. Keys stored in the Secure Enclave _must_ use + /// this keychain. + /// + /// This keychain requires the calling binary to be codesigned with + /// entitlements for the KeychainAccessGroups it is supposed to + /// access. + #[cfg(any(feature = "OSX_10_15", target_os = "ios"))] + DataProtectionKeychain, + /// Store the key in the default file-based keychain. On macOS, defaults to + /// the Login keychain. + #[cfg(target_os = "macos")] + DefaultFileKeychain, + /// Store the key in a specific file-based keychain. + #[cfg(target_os = "macos")] + FileKeychain(crate::os::macos::keychain::SecKeychain), +} + +/// Translates to `SecItemAdd`. Use `ItemAddOptions` to build an `add_params` +/// `CFDictionary`. +pub fn add_item(add_params: CFDictionary) -> Result<()> { + cvt(unsafe { SecItemAdd(add_params.as_concrete_TypeRef(), std::ptr::null_mut()) }) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn find_nothing() { + assert!(ItemSearchOptions::new().search().is_err()); + } + + #[test] + fn limit_two() { + let results = ItemSearchOptions::new() + .class(ItemClass::certificate()) + .limit(2) + .search() + .unwrap(); + assert_eq!(results.len(), 2); + } + + #[test] + fn limit_all() { + let results = ItemSearchOptions::new() + .class(ItemClass::certificate()) + .limit(Limit::All) + .search() + .unwrap(); + assert!(results.len() >= 2); + } +} diff --git a/vendor/security-framework/src/key.rs b/vendor/security-framework/src/key.rs new file mode 100644 index 000000000..6609ffded --- /dev/null +++ b/vendor/security-framework/src/key.rs @@ -0,0 +1,387 @@ +//! Encryption key support + +use crate::cvt; +use core_foundation::{ + base::TCFType, string::{CFStringRef, CFString}, + dictionary::CFMutableDictionary, +}; +use core_foundation::base::ToVoid; +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use core_foundation::boolean::CFBoolean; +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use core_foundation::data::CFData; +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use core_foundation::dictionary::CFDictionary; +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use core_foundation::number::CFNumber; +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use core_foundation::error::{CFError, CFErrorRef}; + +use security_framework_sys::{ + item::{kSecAttrKeyTypeRSA, kSecValueRef}, + keychain_item::SecItemDelete +}; +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use security_framework_sys::{item::{ + kSecAttrIsPermanent, kSecAttrLabel, kSecAttrKeyType, + kSecAttrKeySizeInBits, kSecPrivateKeyAttrs +}}; +#[cfg(target_os="macos")] +use security_framework_sys::item::{ + kSecAttrKeyType3DES, kSecAttrKeyTypeDSA, kSecAttrKeyTypeAES, + kSecAttrKeyTypeDES, kSecAttrKeyTypeRC4, kSecAttrKeyTypeCAST, +}; + +use security_framework_sys::key::SecKeyGetTypeID; +use security_framework_sys::base::SecKeyRef; + +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +pub use security_framework_sys::key::Algorithm; + +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use security_framework_sys::key::{ + SecKeyCopyAttributes, SecKeyCopyExternalRepresentation, + SecKeyCreateSignature, SecKeyCreateRandomKey, + SecKeyCopyPublicKey, +}; +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use security_framework_sys::item::kSecAttrApplicationLabel; +use std::fmt; + +use crate::base::Error; +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +use crate::item::Location; + +/// Types of `SecKey`s. +#[derive(Debug, Copy, Clone)] +pub struct KeyType(CFStringRef); + +#[allow(missing_docs)] +impl KeyType { + #[inline(always)] + #[must_use] + pub fn rsa() -> Self { + unsafe { Self(kSecAttrKeyTypeRSA) } + } + + #[cfg(target_os = "macos")] + #[inline(always)] + #[must_use] + pub fn dsa() -> Self { + unsafe { Self(kSecAttrKeyTypeDSA) } + } + + #[cfg(target_os = "macos")] + #[inline(always)] + #[must_use] + pub fn aes() -> Self { + unsafe { Self(kSecAttrKeyTypeAES) } + } + + #[cfg(target_os = "macos")] + #[inline(always)] + #[must_use] + pub fn des() -> Self { + unsafe { Self(kSecAttrKeyTypeDES) } + } + + #[cfg(target_os = "macos")] + #[inline(always)] + #[must_use] + pub fn triple_des() -> Self { + unsafe { Self(kSecAttrKeyType3DES) } + } + + #[cfg(target_os = "macos")] + #[inline(always)] + #[must_use] + pub fn rc4() -> Self { + unsafe { Self(kSecAttrKeyTypeRC4) } + } + + #[cfg(target_os = "macos")] + #[inline(always)] + #[must_use] + pub fn cast() -> Self { + unsafe { Self(kSecAttrKeyTypeCAST) } + } + + #[cfg(any(feature = "OSX_10_9", target_os = "ios"))] + #[inline(always)] + #[must_use] + pub fn ec() -> Self { + use security_framework_sys::item::kSecAttrKeyTypeEC; + + unsafe { Self(kSecAttrKeyTypeEC) } + } + + pub(crate) fn to_str(self) -> CFString { + unsafe { CFString::wrap_under_get_rule(self.0) } + } +} + +declare_TCFType! { + /// A type representing an encryption key. + SecKey, SecKeyRef +} +impl_TCFType!(SecKey, SecKeyRef, SecKeyGetTypeID); + +unsafe impl Sync for SecKey {} +unsafe impl Send for SecKey {} + +impl SecKey { + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + /// Translates to `SecKeyCreateRandomKey` + /// `GenerateKeyOptions` provides a helper to create an attribute + /// `CFDictionary`. + pub fn generate(attributes: CFDictionary) -> Result<Self, CFError> { + let mut error: CFErrorRef = ::std::ptr::null_mut(); + let sec_key = unsafe { SecKeyCreateRandomKey(attributes.as_concrete_TypeRef(), &mut error)}; + if !error.is_null() { + Err(unsafe { CFError::wrap_under_create_rule(error) }) + } else { + Ok(unsafe { SecKey::wrap_under_create_rule(sec_key) }) + } + } + + /// Returns the programmatic identifier for the key. For keys of class + /// kSecAttrKeyClassPublic and kSecAttrKeyClassPrivate, the value is the + /// hash of the public key. + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + pub fn application_label(&self) -> Option<Vec<u8>> { + self.attributes() + .find(unsafe { kSecAttrApplicationLabel.to_void() }) + .map(|v| unsafe { CFData::wrap_under_get_rule(v.cast()) }.to_vec()) + } + + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + /// Translates to `SecKeyCopyAttributes` + #[must_use] + pub fn attributes(&self) -> CFDictionary { + let pka = unsafe { SecKeyCopyAttributes(self.to_void() as _) }; + unsafe { CFDictionary::wrap_under_create_rule(pka) } + } + + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + /// Translates to `SecKeyCopyExternalRepresentation` + #[must_use] + pub fn external_representation(&self) -> Option<CFData> { + let mut error: CFErrorRef = ::std::ptr::null_mut(); + let data = unsafe { SecKeyCopyExternalRepresentation(self.to_void() as _, &mut error) }; + if data.is_null() { + return None; + } + Some(unsafe { CFData::wrap_under_create_rule(data) }) + } + + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + /// Translates to `SecKeyCopyPublicKey` + #[must_use] + pub fn public_key(&self) -> Option<Self> { + let pub_seckey = unsafe { SecKeyCopyPublicKey(self.0.cast()) }; + if pub_seckey.is_null() { + return None; + } + + Some(unsafe { SecKey::wrap_under_create_rule(pub_seckey) }) + } + + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + /// Creates the cryptographic signature for a block of data using a private + /// key and specified algorithm. + pub fn create_signature(&self, algorithm: Algorithm, input: &[u8]) -> Result<Vec<u8>, CFError> { + let mut error: CFErrorRef = std::ptr::null_mut(); + + let output = unsafe { + SecKeyCreateSignature( + self.as_concrete_TypeRef(), + algorithm.into(), + CFData::from_buffer(input).as_concrete_TypeRef(), + &mut error, + ) + }; + + if !error.is_null() { + Err(unsafe { CFError::wrap_under_create_rule(error) }) + } else { + let output = unsafe { CFData::wrap_under_create_rule(output) }; + Ok(output.to_vec()) + } + } + + /// Verifies the cryptographic signature for a block of data using a public + /// key and specified algorithm. + #[cfg(any(feature = "OSX_10_12", target_os = "ios"))] + pub fn verify_signature(&self, algorithm: Algorithm, signed_data: &[u8], signature: &[u8]) -> Result<bool, CFError> { + use security_framework_sys::key::SecKeyVerifySignature; + let mut error: CFErrorRef = std::ptr::null_mut(); + + let valid = unsafe { + SecKeyVerifySignature( + self.as_concrete_TypeRef(), + algorithm.into(), + CFData::from_buffer(signed_data).as_concrete_TypeRef(), + CFData::from_buffer(signature).as_concrete_TypeRef(), + &mut error, + ) + }; + + if !error.is_null() { + return Err(unsafe { CFError::wrap_under_create_rule(error) })?; + } + Ok(valid != 0) + } + + /// Translates to `SecItemDelete`, passing in the `SecKeyRef` + pub fn delete(&self) -> Result<(), Error> { + let query = CFMutableDictionary::from_CFType_pairs(&[( + unsafe { kSecValueRef }.to_void(), + self.to_void(), + )]); + + cvt(unsafe { SecItemDelete(query.as_concrete_TypeRef()) }) + } +} + +/// Where to generate the key. +pub enum Token { + /// Generate the key in software, compatible with all `KeyType`s. + Software, + /// Generate the key in the Secure Enclave such that the private key is not + /// extractable. Only compatible with `KeyType::ec()`. + SecureEnclave, +} + +/// Helper for creating `CFDictionary` attributes for `SecKey::generate` +/// Recommended reading: +/// <https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains> +#[derive(Default)] +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +pub struct GenerateKeyOptions { + /// kSecAttrKeyType + pub key_type: Option<KeyType>, + /// kSecAttrKeySizeInBits + pub size_in_bits: Option<u32>, + /// kSecAttrLabel + pub label: Option<String>, + /// kSecAttrTokenID + pub token: Option<Token>, + /// Which keychain to store the key in, if any. + pub location: Option<Location>, +} + +#[cfg(any(feature = "OSX_10_12", target_os = "ios"))] +impl GenerateKeyOptions { + /// Set `key_type` + pub fn set_key_type(&mut self, key_type: KeyType) -> &mut Self { + self.key_type = Some(key_type); + self + } + /// Set `size_in_bits` + pub fn set_size_in_bits(&mut self, size_in_bits: u32) -> &mut Self { + self.size_in_bits = Some(size_in_bits); + self + } + /// Set `label` + pub fn set_label(&mut self, label: impl Into<String>) -> &mut Self { + self.label = Some(label.into()); + self + } + /// Set `token` + pub fn set_token(&mut self, token: Token) -> &mut Self { + self.token = Some(token); + self + } + /// Set `location` + pub fn set_location(&mut self, location: Location) -> &mut Self { + self.location = Some(location); + self + } + + /// Collect options into a `CFDictioanry` + pub fn to_dictionary(&self) -> CFDictionary { + #[cfg(target_os = "macos")] + use security_framework_sys::item::kSecUseKeychain; + use security_framework_sys::item::{ + kSecAttrTokenID, kSecAttrTokenIDSecureEnclave, kSecPublicKeyAttrs, + }; + + let is_permanent = CFBoolean::from(self.location.is_some()); + let private_attributes = CFMutableDictionary::from_CFType_pairs(&[( + unsafe { kSecAttrIsPermanent }.to_void(), + is_permanent.to_void(), + )]); + + let public_attributes = CFMutableDictionary::from_CFType_pairs(&[( + unsafe { kSecAttrIsPermanent }.to_void(), + is_permanent.to_void(), + )]); + + let key_type = self.key_type.unwrap_or_else(KeyType::rsa).to_str(); + + let size_in_bits = self.size_in_bits.unwrap_or(match () { + _ if key_type == KeyType::rsa().to_str() => 2048, + _ if key_type == KeyType::ec().to_str() => 256, + _ => 256, + }); + let size_in_bits = CFNumber::from(size_in_bits as i32); + + let mut attribute_key_values = vec![ + (unsafe { kSecAttrKeyType }.to_void(), key_type.to_void()), + ( + unsafe { kSecAttrKeySizeInBits }.to_void(), + size_in_bits.to_void(), + ), + ( + unsafe { kSecPrivateKeyAttrs }.to_void(), + private_attributes.to_void(), + ), + ( + unsafe { kSecPublicKeyAttrs }.to_void(), + public_attributes.to_void(), + ), + ]; + let label = self.label.as_deref().map(CFString::new); + if let Some(label) = &label { + attribute_key_values.push((unsafe { kSecAttrLabel }.to_void(), label.to_void())); + } + + #[cfg(target_os = "macos")] + match &self.location { + #[cfg(feature = "OSX_10_15")] + Some(Location::DataProtectionKeychain) => { + use security_framework_sys::item::kSecUseDataProtectionKeychain; + attribute_key_values.push(( + unsafe { kSecUseDataProtectionKeychain }.to_void(), + CFBoolean::true_value().to_void(), + )); + } + Some(Location::FileKeychain(keychain)) => { + attribute_key_values.push(( + unsafe { kSecUseKeychain }.to_void(), + keychain.as_concrete_TypeRef().to_void(), + )); + } + _ => {} + } + + match self.token.as_ref().unwrap_or(&Token::Software) { + Token::Software => {}, + Token::SecureEnclave => { + attribute_key_values.push(( + unsafe { kSecAttrTokenID }.to_void(), + unsafe { kSecAttrTokenIDSecureEnclave }.to_void(), + )); + } + } + + CFMutableDictionary::from_CFType_pairs(&attribute_key_values).to_immutable() + } +} + +impl fmt::Debug for SecKey { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("SecKey").finish_non_exhaustive() + } +} diff --git a/vendor/security-framework/src/lib.rs b/vendor/security-framework/src/lib.rs new file mode 100644 index 000000000..304c80734 --- /dev/null +++ b/vendor/security-framework/src/lib.rs @@ -0,0 +1,92 @@ +//! Wrappers around the OSX Security Framework. +#![warn(missing_docs)] +#![allow(non_upper_case_globals)] +#![allow(clippy::manual_non_exhaustive)] // MSRV + +#[macro_use] +extern crate core_foundation; + +use core_foundation_sys::base::OSStatus; +use security_framework_sys::base::errSecSuccess; + +use crate::base::{Error, Result}; +#[cfg(target_os = "macos")] +use crate::os::macos::access::SecAccess; +#[cfg(target_os = "macos")] +use crate::os::macos::keychain::SecKeychain; + +#[cfg(test)] +macro_rules! p { + ($e:expr) => { + match $e { + Ok(s) => s, + Err(e) => panic!("{:?}", e), + } + }; +} + +#[cfg(all(not(feature = "OSX_10_13"), any(feature = "alpn", feature = "session-tickets")))] +#[macro_use] +mod dlsym; + +pub mod access_control; +#[cfg(target_os = "macos")] +pub mod authorization; +pub mod base; +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub mod certificate; +pub mod cipher_suite; +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub mod identity; +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub mod import_export; +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub mod item; +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub mod key; +pub mod os; +pub mod passwords; +pub mod passwords_options; +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub mod policy; +pub mod random; +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub mod secure_transport; +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub mod trust; +#[cfg(target_os = "macos")] +pub mod trust_settings; + +#[cfg(target_os = "macos")] +trait Pkcs12ImportOptionsInternals { + fn keychain(&mut self, keychain: SecKeychain) -> &mut Self; + fn access(&mut self, access: SecAccess) -> &mut Self; +} + +#[cfg(target_os = "macos")] +trait ItemSearchOptionsInternals { + fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self; +} + +trait AsInner { + type Inner; + fn as_inner(&self) -> Self::Inner; +} + +#[inline(always)] +fn cvt(err: OSStatus) -> Result<()> { + match err { + errSecSuccess => Ok(()), + err => Err(Error::from_code(err)), + } +} + +#[cfg(test)] +mod test { + use crate::certificate::SecCertificate; + + pub fn certificate() -> SecCertificate { + let certificate = include_bytes!("../test/server.der"); + p!(SecCertificate::from_der(certificate)) + } +} diff --git a/vendor/security-framework/src/os/macos/access.rs b/vendor/security-framework/src/os/macos/access.rs new file mode 100644 index 000000000..1c41d85d9 --- /dev/null +++ b/vendor/security-framework/src/os/macos/access.rs @@ -0,0 +1,14 @@ +//! Access functionality. + +use core_foundation::base::TCFType; +use security_framework_sys::access::SecAccessGetTypeID; +use security_framework_sys::base::SecAccessRef; + +declare_TCFType! { + /// A type representing access settings. + SecAccess, SecAccessRef +} +impl_TCFType!(SecAccess, SecAccessRef, SecAccessGetTypeID); + +unsafe impl Sync for SecAccess {} +unsafe impl Send for SecAccess {} diff --git a/vendor/security-framework/src/os/macos/certificate.rs b/vendor/security-framework/src/os/macos/certificate.rs new file mode 100644 index 000000000..7f910dc6c --- /dev/null +++ b/vendor/security-framework/src/os/macos/certificate.rs @@ -0,0 +1,269 @@ +//! OSX specific extensions to certificate functionality. + +use core_foundation::array::{CFArray, CFArrayIterator}; +use core_foundation::base::TCFType; +use core_foundation::base::ToVoid; +use core_foundation::data::CFData; +use core_foundation::dictionary::CFDictionary; +use core_foundation::error::CFError; +use core_foundation::string::CFString; +use security_framework_sys::certificate::*; +use std::convert::TryInto; +use std::os::raw::c_void; +use std::ptr; + +use crate::base::Error; +use crate::certificate::SecCertificate; +use crate::cvt; +use crate::key::SecKey; +use crate::os::macos::certificate_oids::CertificateOid; +use crate::os::macos::digest_transform::{Builder, DigestType}; + +/// An extension trait adding OSX specific functionality to `SecCertificate`. +pub trait SecCertificateExt { + /// Returns the common name associated with the certificate. + fn common_name(&self) -> Result<String, Error>; + + /// Returns the public key associated with the certificate. + #[cfg_attr(not(feature = "OSX_10_14"), deprecated(note = "Uses deprecated SecCertificateCopyPublicKey. Enable OSX_10_14 feature to avoid it"))] + fn public_key(&self) -> Result<SecKey, Error>; + + /// Returns the set of properties associated with the certificate. + /// + /// The `keys` argument can optionally be used to filter the properties loaded to an explicit + /// subset. + fn properties(&self, keys: Option<&[CertificateOid]>) + -> Result<CertificateProperties, CFError>; + + /// Returns the SHA-256 fingerprint of the certificate. + fn fingerprint(&self) -> Result<[u8; 32], CFError> { unimplemented!() } +} + +impl SecCertificateExt for SecCertificate { + fn common_name(&self) -> Result<String, Error> { + unsafe { + let mut string = ptr::null(); + cvt(SecCertificateCopyCommonName( + self.as_concrete_TypeRef(), + &mut string, + ))?; + Ok(CFString::wrap_under_create_rule(string).to_string()) + } + } + + #[cfg(feature = "OSX_10_14")] + fn public_key(&self) -> Result<SecKey, Error> { + unsafe { + let key = SecCertificateCopyKey(self.as_concrete_TypeRef()); + if key.is_null() { + return Err(Error::from_code(-26275)); + } + Ok(SecKey::wrap_under_create_rule(key)) + } + } + + #[cfg(not(feature = "OSX_10_14"))] + fn public_key(&self) -> Result<SecKey, Error> { + #[allow(deprecated)] + unsafe { + let mut key = ptr::null_mut(); + cvt(SecCertificateCopyPublicKey( + self.as_concrete_TypeRef(), + &mut key, + ))?; + Ok(SecKey::wrap_under_create_rule(key)) + } + } + + fn properties( + &self, + keys: Option<&[CertificateOid]>, + ) -> Result<CertificateProperties, CFError> { + unsafe { + let keys = keys.map(|oids| { + let oids = oids.iter().map(|oid| oid.to_str()).collect::<Vec<_>>(); + CFArray::from_CFTypes(&oids) + }); + + let keys = match keys { + Some(ref keys) => keys.as_concrete_TypeRef(), + None => ptr::null_mut(), + }; + + let mut error = ptr::null_mut(); + + let dictionary = SecCertificateCopyValues(self.as_concrete_TypeRef(), keys, &mut error); + + if error.is_null() { + Ok(CertificateProperties(CFDictionary::wrap_under_create_rule( + dictionary, + ))) + } else { + Err(CFError::wrap_under_create_rule(error)) + } + } + } + + /// Returns the SHA-256 fingerprint of the certificate. + fn fingerprint(&self) -> Result<[u8; 32], CFError> { + let data = CFData::from_buffer(&self.to_der()); + let hash = Builder::new() + .type_(DigestType::sha2()) + .length(256) + .execute(&data)?; + Ok(hash.bytes().try_into().unwrap()) + } +} + +/// Properties associated with a certificate. +pub struct CertificateProperties(CFDictionary); + +impl CertificateProperties { + /// Retrieves a specific property identified by its OID. + #[must_use] pub fn get(&self, oid: CertificateOid) -> Option<CertificateProperty> { + unsafe { + self.0.find(oid.as_ptr().cast::<c_void>()).map(|value| { + CertificateProperty(CFDictionary::wrap_under_get_rule(*value as *mut _)) + }) + } + } +} + +/// A property associated with a certificate. +pub struct CertificateProperty(CFDictionary); + +impl CertificateProperty { + /// Returns the label of this property. + #[must_use] + pub fn label(&self) -> CFString { + unsafe { + CFString::wrap_under_get_rule((*self.0.get(kSecPropertyKeyLabel.to_void())).cast()) + } + } + + /// Returns an enum of the underlying data for this property. + #[must_use] + pub fn get(&self) -> PropertyType { + unsafe { + let type_ = + CFString::wrap_under_get_rule(*self.0.get(kSecPropertyKeyType.to_void()) as *mut _); + let value = self.0.get(kSecPropertyKeyValue.to_void()); + + if type_ == CFString::wrap_under_get_rule(kSecPropertyTypeSection) { + PropertyType::Section(PropertySection(CFArray::wrap_under_get_rule( + (*value).cast(), + ))) + } else if type_ == CFString::wrap_under_get_rule(kSecPropertyTypeString) { + PropertyType::String(CFString::wrap_under_get_rule((*value).cast())) + } else { + PropertyType::__Unknown + } + } + } +} + +/// A "section" property. +/// +/// Sections are sequences of other properties. +pub struct PropertySection(CFArray<CFDictionary>); + +impl PropertySection { + /// Returns an iterator over the properties in this section. + #[inline(always)] + #[must_use] + pub fn iter(&self) -> PropertySectionIter<'_> { + PropertySectionIter(self.0.iter()) + } +} + +impl<'a> IntoIterator for &'a PropertySection { + type IntoIter = PropertySectionIter<'a>; + type Item = CertificateProperty; + + #[inline(always)] + fn into_iter(self) -> PropertySectionIter<'a> { + self.iter() + } +} + +/// An iterator over the properties in a section. +pub struct PropertySectionIter<'a>(CFArrayIterator<'a, CFDictionary>); + +impl<'a> Iterator for PropertySectionIter<'a> { + type Item = CertificateProperty; + + #[inline] + fn next(&mut self) -> Option<CertificateProperty> { + self.0.next().map(|t| CertificateProperty(t.clone())) + } + + #[inline(always)] + fn size_hint(&self) -> (usize, Option<usize>) { + self.0.size_hint() + } +} + +/// An enum of the various types of properties. +pub enum PropertyType { + /// A section. + Section(PropertySection), + /// A string. + String(CFString), + #[doc(hidden)] + __Unknown, +} + +#[cfg(test)] +mod test { + use super::*; + use crate::os::macos::certificate_oids::CertificateOid; + use crate::test::certificate; + use std::collections::HashMap; + + #[test] + fn common_name() { + let certificate = certificate(); + assert_eq!("foobar.com", p!(certificate.common_name())); + } + + #[test] + #[allow(deprecated)] + fn public_key() { + let certificate = certificate(); + p!(certificate.public_key()); + } + + #[test] + fn fingerprint() { + let certificate = certificate(); + let fingerprint = p!(certificate.fingerprint()); + assert_eq!( + "af9dd180a326ae08b37e6398f9262f8b9d4c55674a233a7c84975024f873655d", + hex::encode(fingerprint) + ); + } + + #[test] + fn signature_algorithm() { + let certificate = certificate(); + let properties = certificate + .properties(Some(&[CertificateOid::x509_v1_signature_algorithm()])) + .unwrap(); + let value = properties + .get(CertificateOid::x509_v1_signature_algorithm()) + .unwrap(); + let section = match value.get() { + PropertyType::Section(section) => section, + _ => panic!(), + }; + let properties = section + .iter() + .map(|p| (p.label().to_string(), p.get())) + .collect::<HashMap<_, _>>(); + let algorithm = match properties["Algorithm"] { + PropertyType::String(ref s) => s.to_string(), + _ => panic!(), + }; + assert_eq!(algorithm, "1.2.840.113549.1.1.5"); + } +} diff --git a/vendor/security-framework/src/os/macos/certificate_oids.rs b/vendor/security-framework/src/os/macos/certificate_oids.rs new file mode 100644 index 000000000..d820afe4a --- /dev/null +++ b/vendor/security-framework/src/os/macos/certificate_oids.rs @@ -0,0 +1,32 @@ +//! OIDs associated with certificate properties. +use core_foundation::base::TCFType; +use core_foundation::string::CFString; +use core_foundation_sys::string::CFStringRef; +use security_framework_sys::certificate_oids::kSecOIDX509V1SignatureAlgorithm; + +/// An identifier of a property of a certificate. +#[derive(Copy, Clone)] +pub struct CertificateOid(CFStringRef); + +#[allow(missing_docs)] +impl CertificateOid { + #[inline(always)] + #[must_use] + pub fn x509_v1_signature_algorithm() -> Self { + unsafe { Self(kSecOIDX509V1SignatureAlgorithm) } + } + + /// Returns the underlying raw pointer corresponding to this OID. + #[inline(always)] + #[must_use] + pub fn as_ptr(&self) -> CFStringRef { + self.0 + } + + /// Returns the string representation of the OID. + #[inline] + #[must_use] + pub fn to_str(&self) -> CFString { + unsafe { CFString::wrap_under_get_rule(self.0) } + } +} diff --git a/vendor/security-framework/src/os/macos/code_signing.rs b/vendor/security-framework/src/os/macos/code_signing.rs new file mode 100644 index 000000000..0a3a9c9ae --- /dev/null +++ b/vendor/security-framework/src/os/macos/code_signing.rs @@ -0,0 +1,485 @@ +//! Code signing services. + +use std::{fmt::Debug, mem::MaybeUninit, str::FromStr}; + +use core_foundation::{ + base::{TCFType, TCFTypeRef, ToVoid}, + data::CFDataRef, + dictionary::CFMutableDictionary, + number::CFNumber, + string::{CFString, CFStringRef}, + url::CFURL, +}; +use libc::pid_t; +use security_framework_sys::code_signing::{ + kSecCSBasicValidateOnly, kSecCSCheckAllArchitectures, kSecCSCheckGatekeeperArchitectures, + kSecCSCheckNestedCode, kSecCSCheckTrustedAnchors, kSecCSConsiderExpiration, + kSecCSDoNotValidateExecutable, kSecCSDoNotValidateResources, kSecCSEnforceRevocationChecks, + kSecCSFullReport, kSecCSNoNetworkAccess, kSecCSQuickCheck, kSecCSReportProgress, + kSecCSRestrictSidebandData, kSecCSRestrictSymlinks, kSecCSRestrictToAppLike, + kSecCSSingleThreaded, kSecCSStrictValidate, kSecCSUseSoftwareSigningCert, kSecCSValidatePEH, + kSecGuestAttributeAudit, kSecGuestAttributePid, SecCodeCheckValidity, + SecCodeCopyGuestWithAttributes, SecCodeCopyPath, SecCodeCopySelf, SecCodeGetTypeID, SecCodeRef, + SecRequirementCreateWithString, SecRequirementGetTypeID, SecRequirementRef, + SecStaticCodeCheckValidity, SecStaticCodeCreateWithPath, SecStaticCodeGetTypeID, + SecStaticCodeRef, +}; + +use crate::{cvt, Result}; + +bitflags::bitflags! { + + /// Values that can be used in the flags parameter to most code signing + /// functions. + pub struct Flags: u32 { + /// Use the default behaviour. + const NONE = 0; + + /// For multi-architecture (universal) Mach-O programs, validate all + /// architectures included. + const CHECK_ALL_ARCHITECTURES = kSecCSCheckAllArchitectures; + + /// Do not validate the contents of the main executable. + const DO_NOT_VALIDATE_EXECUTABLE = kSecCSDoNotValidateExecutable; + + /// Do not validate the presence and contents of all bundle resources + /// if any. + const DO_NOT_VALIDATE_RESOURCES = kSecCSDoNotValidateResources; + + /// Do not validate either the main executable or the bundle resources, + /// if any. + const BASIC_VALIDATE_ONLY = kSecCSBasicValidateOnly; + + /// For code in bundle form, locate and recursively check embedded code. + const CHECK_NESTED_CODE = kSecCSCheckNestedCode; + + /// Perform additional checks to ensure the validity of code in bundle + /// form. + const STRICT_VALIDATE = kSecCSStrictValidate; + + /// Apple have not documented this flag. + const FULL_REPORT = kSecCSFullReport; + + /// Apple have not documented this flag. + const CHECK_GATEKEEPER_ARCHITECTURES = kSecCSCheckGatekeeperArchitectures; + + /// Apple have not documented this flag. + const RESTRICT_SYMLINKS = kSecCSRestrictSymlinks; + + /// Apple have not documented this flag. + const RESTRICT_TO_APP_LIKE = kSecCSRestrictToAppLike; + + /// Apple have not documented this flag. + const RESTRICT_SIDEBAND_DATA = kSecCSRestrictSidebandData; + + /// Apple have not documented this flag. + const USE_SOFTWARE_SIGNING_CERT = kSecCSUseSoftwareSigningCert; + + /// Apple have not documented this flag. + const VALIDATE_PEH = kSecCSValidatePEH; + + /// Apple have not documented this flag. + const SINGLE_THREADED = kSecCSSingleThreaded; + + /// Apple have not documented this flag. + const QUICK_CHECK = kSecCSQuickCheck; + + /// Apple have not documented this flag. + const CHECK_TRUSTED_ANCHORS = kSecCSCheckTrustedAnchors; + + /// Apple have not documented this flag. + const REPORT_PROGRESS = kSecCSReportProgress; + + /// Apple have not documented this flag. + const NO_NETWORK_ACCESS = kSecCSNoNetworkAccess; + + /// Apple have not documented this flag. + const ENFORCE_REVOCATION_CHECKS = kSecCSEnforceRevocationChecks; + + /// Apple have not documented this flag. + const CONSIDER_EXPIRATION = kSecCSConsiderExpiration; + } +} + +impl Default for Flags { + #[inline(always)] + fn default() -> Self { + Self::NONE + } +} + +/// A helper to create guest attributes, which are normally passed as a +/// `CFDictionary` with varying types. +pub struct GuestAttributes { + inner: CFMutableDictionary, +} + +impl GuestAttributes { + // Not implemented: + // - architecture + // - canonical + // - dynamic code + // - dynamic code info plist + // - hash + // - mach port + // - sub-architecture + + /// Creates a new, empty `GuestAttributes`. You must add values to it in + /// order for it to be of any use. + #[must_use] + pub fn new() -> Self { + Self { + inner: CFMutableDictionary::new(), + } + } + + /// The guest's audit token. + pub fn set_audit_token(&mut self, token: CFDataRef) { + let key = unsafe { CFString::wrap_under_get_rule(kSecGuestAttributeAudit) }; + self.inner.add(&key.as_CFTypeRef(), &token.to_void()); + } + + /// The guest's pid. + pub fn set_pid(&mut self, pid: pid_t) { + let key = unsafe { CFString::wrap_under_get_rule(kSecGuestAttributePid) }; + let pid = CFNumber::from(pid); + self.inner.add(&key.as_CFTypeRef(), &pid.as_CFTypeRef()); + } + + /// Support for arbirtary guest attributes. + pub fn set_other<V: ToVoid<V>>(&mut self, key: CFStringRef, value: V) { + self.inner.add(&key.as_void_ptr(), &value.to_void()); + } +} + +impl Default for GuestAttributes { + fn default() -> Self { + Self::new() + } +} + +declare_TCFType! { + /// A code object representing signed code running on the system. + SecRequirement, SecRequirementRef +} +impl_TCFType!(SecRequirement, SecRequirementRef, SecRequirementGetTypeID); + +impl FromStr for SecRequirement { + type Err = crate::base::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let text = CFString::new(s); + let mut requirement = MaybeUninit::uninit(); + + unsafe { + cvt(SecRequirementCreateWithString( + text.as_concrete_TypeRef(), + 0, + requirement.as_mut_ptr(), + ))?; + + Ok(Self::wrap_under_create_rule(requirement.assume_init())) + } + } +} + +declare_TCFType! { + /// A code object representing signed code running on the system. + SecCode, SecCodeRef +} +impl_TCFType!(SecCode, SecCodeRef, SecCodeGetTypeID); + +impl Debug for SecCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("SecCode") + } +} + +impl SecCode { + /// Retrieves the code object for the code making the call. + pub fn for_self(flags: Flags) -> Result<Self> { + let mut code = MaybeUninit::uninit(); + + unsafe { + cvt(SecCodeCopySelf(flags.bits(), code.as_mut_ptr()))?; + Ok(Self::wrap_under_create_rule(code.assume_init())) + } + } + + /// Performs dynamic validation of signed code. + pub fn check_validity(&self, flags: Flags, requirement: &SecRequirement) -> Result<()> { + unsafe { + cvt(SecCodeCheckValidity( + self.as_concrete_TypeRef(), + flags.bits(), + requirement.as_concrete_TypeRef(), + )) + } + } + + /// Asks a code host to identify one of its guests given + /// the type and value of specific attributes of the guest code. + /// + /// If `host` is `None` then the code signing root of trust (currently, the + // system kernel) should be used as the code host. + pub fn copy_guest_with_attribues( + host: Option<&SecCode>, + attrs: &GuestAttributes, + flags: Flags, + ) -> Result<SecCode> { + let mut code = MaybeUninit::uninit(); + + let host = match host { + Some(host) => host.as_concrete_TypeRef(), + None => std::ptr::null_mut(), + }; + + unsafe { + cvt(SecCodeCopyGuestWithAttributes( + host, + attrs.inner.as_concrete_TypeRef(), + flags.bits(), + code.as_mut_ptr(), + ))?; + + Ok(SecCode::wrap_under_create_rule(code.assume_init())) + } + } + + /// Retrieves the location on disk of signed code, given a code or static + /// code object. + pub fn path(&self, flags: Flags) -> Result<CFURL> { + let mut url = MaybeUninit::uninit(); + + // The docs say we can pass a SecCodeRef instead of a SecStaticCodeRef. + unsafe { + cvt(SecCodeCopyPath( + self.as_CFTypeRef() as _, + flags.bits(), + url.as_mut_ptr(), + ))?; + + Ok(CFURL::wrap_under_create_rule(url.assume_init())) + } + } +} + +declare_TCFType! { + /// A static code object representing signed code on disk. + SecStaticCode, SecStaticCodeRef +} +impl_TCFType!(SecStaticCode, SecStaticCodeRef, SecStaticCodeGetTypeID); + +impl SecStaticCode { + /// Creates a static code object representing the code at a specified file + /// system path. + pub fn from_path(path: &CFURL, flags: Flags) -> Result<Self> { + let mut code = MaybeUninit::uninit(); + + unsafe { + cvt(SecStaticCodeCreateWithPath( + path.as_concrete_TypeRef(), + flags.bits(), + code.as_mut_ptr(), + ))?; + + Ok(Self::wrap_under_get_rule(code.assume_init())) + } + } + + /// Retrieves the location on disk of signed code, given a code or static + /// code object. + pub fn path(&self, flags: Flags) -> Result<CFURL> { + let mut url = MaybeUninit::uninit(); + + // The docs say we can pass a SecCodeRef instead of a SecStaticCodeRef. + unsafe { + cvt(SecCodeCopyPath( + self.as_concrete_TypeRef(), + flags.bits(), + url.as_mut_ptr(), + ))?; + + Ok(CFURL::wrap_under_create_rule(url.assume_init())) + } + } + + /// Performs dynamic validation of signed code. + pub fn check_validity(&self, flags: Flags, requirement: &SecRequirement) -> Result<()> { + unsafe { + cvt(SecStaticCodeCheckValidity( + self.as_concrete_TypeRef(), + flags.bits(), + requirement.as_concrete_TypeRef(), + )) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use core_foundation::data::CFData; + use libc::{c_uint, c_void, KERN_SUCCESS}; + + #[test] + fn path_to_static_code_and_back() { + let path = CFURL::from_path("/bin/bash", false).unwrap(); + let code = SecStaticCode::from_path(&path, Flags::NONE).unwrap(); + assert_eq!(code.path(Flags::NONE).unwrap(), path); + } + + #[test] + fn self_to_path() { + let path = CFURL::from_path(std::env::current_exe().unwrap(), false).unwrap(); + let code = SecCode::for_self(Flags::NONE).unwrap(); + assert_eq!(code.path(Flags::NONE).unwrap(), path); + } + + #[test] + fn bash_is_signed_by_apple() { + let path = CFURL::from_path("/bin/bash", false).unwrap(); + let code = SecStaticCode::from_path(&path, Flags::NONE).unwrap(); + let requirement: SecRequirement = "anchor apple".parse().unwrap(); + code.check_validity(Flags::NONE, &requirement).unwrap(); + } + + #[cfg(target_arch = "aarch64")] + #[test] + fn self_is_not_signed_by_apple() { + let code = SecCode::for_self(Flags::NONE).unwrap(); + let requirement: SecRequirement = "anchor apple".parse().unwrap(); + + assert_eq!( + code.check_validity(Flags::NONE, &requirement) + .unwrap_err() + .code(), + // "code failed to satisfy specified code requirement(s)" + -67050 + ); + } + + #[cfg(not(target_arch = "aarch64"))] + #[test] + fn self_is_not_signed_by_apple() { + let code = SecCode::for_self(Flags::NONE).unwrap(); + let requirement: SecRequirement = "anchor apple".parse().unwrap(); + + assert_eq!( + code.check_validity(Flags::NONE, &requirement) + .unwrap_err() + .code(), + // "code object is not signed at all" + -67062 + ); + } + + #[test] + fn copy_kernel_guest_with_launchd_pid() { + let mut attrs = GuestAttributes::new(); + attrs.set_pid(1); + + assert_eq!( + SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE) + .unwrap() + .path(Flags::NONE) + .unwrap() + .get_string() + .to_string(), + "file:///sbin/launchd" + ); + } + + #[test] + fn copy_current_guest_with_launchd_pid() { + let host_code = SecCode::for_self(Flags::NONE).unwrap(); + + let mut attrs = GuestAttributes::new(); + attrs.set_pid(1); + + assert_eq!( + SecCode::copy_guest_with_attribues(Some(&host_code), &attrs, Flags::NONE) + .unwrap_err() + .code(), + // "host has no guest with the requested attributes" + -67065 + ); + } + + #[test] + fn copy_kernel_guest_with_unmatched_pid() { + let mut attrs = GuestAttributes::new(); + attrs.set_pid(999_999_999); + + assert_eq!( + SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE) + .unwrap_err() + .code(), + // "UNIX[No such process]" + 100003 + ); + } + + #[test] + fn copy_kernel_guest_with_current_token() { + let mut token: [u8; 32] = [0; 32]; + let mut token_len = 32u32; + + enum OpaqueTaskName {} + + extern "C" { + fn mach_task_self() -> *const OpaqueTaskName; + fn task_info( + task_name: *const OpaqueTaskName, + task_flavor: u32, + out: *mut c_void, + out_len: *mut u32, + ) -> i32; + } + + const TASK_AUDIT_TOKEN: c_uint = 15; + + let result = unsafe { + task_info( + mach_task_self(), + TASK_AUDIT_TOKEN, + token.as_mut_ptr() as *mut c_void, + &mut token_len, + ) + }; + + assert_eq!(result, KERN_SUCCESS); + + let token_data = CFData::from_buffer(&token); + + let mut attrs = GuestAttributes::new(); + attrs.set_audit_token(token_data.as_concrete_TypeRef()); + + assert_eq!( + SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE) + .unwrap() + .path(Flags::NONE) + .unwrap() + .to_path() + .unwrap(), + std::env::current_exe().unwrap() + ); + } + + #[test] + fn copy_kernel_guest_with_unmatched_token() { + let token: [u8; 32] = [0; 32]; + let token_data = CFData::from_buffer(&token); + + let mut attrs = GuestAttributes::new(); + attrs.set_audit_token(token_data.as_concrete_TypeRef()); + + assert_eq!( + SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE) + .unwrap_err() + .code(), + // "UNIX[No such process]" + 100003 + ); + } +} diff --git a/vendor/security-framework/src/os/macos/digest_transform.rs b/vendor/security-framework/src/os/macos/digest_transform.rs new file mode 100644 index 000000000..7577f083e --- /dev/null +++ b/vendor/security-framework/src/os/macos/digest_transform.rs @@ -0,0 +1,196 @@ +//! Digest Transform support + +use core_foundation::base::{CFIndex, TCFType}; +use core_foundation::data::CFData; +use core_foundation::error::CFError; +use core_foundation::string::CFString; +use core_foundation_sys::base::CFTypeRef; +use core_foundation_sys::data::CFDataRef; +use core_foundation_sys::string::CFStringRef; +use security_framework_sys::digest_transform::*; +use security_framework_sys::transform::*; +use std::ptr; + +use crate::os::macos::transform::SecTransform; + +#[derive(Debug, Copy, Clone)] +/// A type of digest. +pub struct DigestType(CFStringRef); + +#[allow(missing_docs)] +impl DigestType { + #[inline(always)] + #[must_use] + pub fn hmac_md5() -> Self { + unsafe { Self(kSecDigestHMACMD5) } + } + + #[inline(always)] + #[must_use] + pub fn hmac_sha1() -> Self { + unsafe { Self(kSecDigestHMACSHA1) } + } + + #[inline(always)] + #[must_use] + pub fn hmac_sha2() -> Self { + unsafe { Self(kSecDigestHMACSHA2) } + } + + #[inline(always)] + #[must_use] + pub fn md2() -> Self { + unsafe { Self(kSecDigestMD2) } + } + + #[inline(always)] + #[must_use] + pub fn md4() -> Self { + unsafe { Self(kSecDigestMD4) } + } + + #[inline(always)] + #[must_use] + pub fn md5() -> Self { + unsafe { Self(kSecDigestMD5) } + } + + #[inline(always)] + #[must_use] + pub fn sha1() -> Self { + unsafe { Self(kSecDigestSHA1) } + } + + #[inline(always)] + #[must_use] + pub fn sha2() -> Self { + unsafe { Self(kSecDigestSHA2) } + } + + #[inline(always)] + fn to_type(self) -> CFTypeRef { + self.0 as CFTypeRef + } +} + +/// A builder for digest transform operations. +pub struct Builder { + digest_type: Option<DigestType>, + digest_length: Option<CFIndex>, + hmac_key: Option<CFData>, +} + +impl Default for Builder { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + +impl Builder { + /// Returns a new builder with default settings. + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Self { + digest_type: None, + digest_length: None, + hmac_key: None, + } + } + + /// Sets the type of digest to perform. + /// + /// If not set, an appropriate digest will be selected for you. + #[inline] + pub fn type_(&mut self, digest_type: DigestType) -> &mut Self { + self.digest_type = Some(digest_type); + self + } + + /// Sets the output length of the digest. + /// + /// If not set, an appropriate length will be selected for you. Some digest + /// types only support specific output lengths. + #[inline] + pub fn length(&mut self, digest_length: CFIndex) -> &mut Self { + self.digest_length = Some(digest_length); + self + } + + /// Sets the key used for HMAC digests. + /// + /// Only applies to `HmacMd5`, `HmacSha1`, and `HmacSha2` digests. + #[inline] + pub fn hmac_key(&mut self, hmac_key: CFData) -> &mut Self { + self.hmac_key = Some(hmac_key); + self + } + + /// Computes the digest of the data. + pub fn execute(&self, data: &CFData) -> Result<CFData, CFError> { + unsafe { + let digest_type = match self.digest_type { + Some(ref digest_type) => digest_type.to_type(), + None => ptr::null(), + }; + + let digest_length = self.digest_length.unwrap_or(0); + + let mut error = ptr::null_mut(); + let transform = SecDigestTransformCreate(digest_type, digest_length, &mut error); + if transform.is_null() { + return Err(CFError::wrap_under_create_rule(error)); + } + let mut transform = SecTransform::wrap_under_create_rule(transform); + + if let Some(ref hmac_key) = self.hmac_key { + let key = CFString::wrap_under_get_rule(kSecDigestHMACKeyAttribute); + transform.set_attribute(&key, hmac_key)?; + } + + let key = CFString::wrap_under_get_rule(kSecTransformInputAttributeName); + transform.set_attribute(&key, data)?; + + let result = transform.execute()?; + Ok(CFData::wrap_under_get_rule( + result.as_CFTypeRef() as CFDataRef + )) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use core_foundation::data::CFData; + use hex; + + #[test] + fn md5() { + let data = CFData::from_buffer("The quick brown fox jumps over the lazy dog".as_bytes()); + let hash = Builder::new() + .type_(DigestType::md5()) + .execute(&data) + .unwrap(); + assert_eq!( + hex::encode(hash.bytes()), + "9e107d9d372bb6826bd81d3542a419d6" + ); + } + + #[test] + fn hmac_sha1() { + let data = CFData::from_buffer("The quick brown fox jumps over the lazy dog".as_bytes()); + let key = CFData::from_buffer(b"key"); + let hash = Builder::new() + .type_(DigestType::hmac_sha1()) + .hmac_key(key) + .execute(&data) + .unwrap(); + assert_eq!( + hex::encode(hash.bytes()), + "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9" + ); + } +} diff --git a/vendor/security-framework/src/os/macos/encrypt_transform.rs b/vendor/security-framework/src/os/macos/encrypt_transform.rs new file mode 100644 index 000000000..f0ab3c4a2 --- /dev/null +++ b/vendor/security-framework/src/os/macos/encrypt_transform.rs @@ -0,0 +1,256 @@ +//! Encryption and Decryption transform support. + +use core_foundation::base::TCFType; +use core_foundation::data::CFData; +use core_foundation::error::CFError; +use core_foundation::string::CFString; +use core_foundation_sys::data::CFDataRef; +use core_foundation_sys::string::CFStringRef; +use security_framework_sys::encrypt_transform::*; +use security_framework_sys::transform::*; +use std::ptr; + +use crate::key::SecKey; +use crate::os::macos::transform::SecTransform; + +#[derive(Debug, Copy, Clone)] +/// The padding scheme to use for encryption. +pub struct Padding(CFStringRef); + +impl Padding { + /// Do not pad. + #[inline(always)] + #[must_use] + pub fn none() -> Self { + unsafe { Self(kSecPaddingNoneKey) } + } + + /// Use PKCS#1 padding. + #[inline(always)] + #[must_use] + pub fn pkcs1() -> Self { + unsafe { Self(kSecPaddingPKCS1Key) } + } + + /// Use PKCS#5 padding. + #[inline(always)] + #[must_use] + pub fn pkcs5() -> Self { + unsafe { Self(kSecPaddingPKCS5Key) } + } + + /// Use PKCS#7 padding. + #[inline(always)] + #[must_use] + pub fn pkcs7() -> Self { + unsafe { Self(kSecPaddingPKCS7Key) } + } + + /// Use OAEP padding. + #[inline(always)] + #[must_use] + pub fn oaep() -> Self { + unsafe { Self(kSecPaddingOAEPKey) } + } + + #[inline] + fn to_str(self) -> CFString { + unsafe { CFString::wrap_under_get_rule(self.0) } + } +} + +/// The cipher mode to use. +/// +/// Only applies to AES encryption. +#[derive(Debug, Copy, Clone)] +pub struct Mode(CFStringRef); + +#[allow(missing_docs)] +impl Mode { + #[inline(always)] + #[must_use] + pub fn none() -> Self { + unsafe { Self(kSecModeNoneKey) } + } + + #[inline(always)] + #[must_use] + pub fn ecb() -> Self { + unsafe { Self(kSecModeECBKey) } + } + + #[inline(always)] + #[must_use] + pub fn cbc() -> Self { + unsafe { Self(kSecModeCBCKey) } + } + + #[inline(always)] + #[must_use] + pub fn cfb() -> Self { + unsafe { Self(kSecModeCFBKey) } + } + + #[inline(always)] + #[must_use] + pub fn ofb() -> Self { + unsafe { Self(kSecModeOFBKey) } + } + + fn to_str(self) -> CFString { + unsafe { CFString::wrap_under_get_rule(self.0) } + } +} + +/// A builder for encryption and decryption transform operations. +#[derive(Default)] +pub struct Builder { + padding: Option<Padding>, + mode: Option<Mode>, + iv: Option<CFData>, +} + +impl Builder { + /// Creates a new `Builder` with a default configuration. + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Selects the padding scheme to use. + /// + /// If not set, an appropriate scheme will be selected for you. + #[inline(always)] + pub fn padding(&mut self, padding: Padding) -> &mut Self { + self.padding = Some(padding); + self + } + + /// Selects the encryption mode to use. + /// + /// If not set, an appropriate mode will be selected for you. + #[inline(always)] + pub fn mode(&mut self, mode: Mode) -> &mut Self { + self.mode = Some(mode); + self + } + + /// Sets the initialization vector to use. + /// + /// If not set, an appropriate value will be supplied for you. + #[inline(always)] + pub fn iv(&mut self, iv: CFData) -> &mut Self { + self.iv = Some(iv); + self + } + + /// Encrypts data with a provided key. + pub fn encrypt(&self, key: &SecKey, data: &CFData) -> Result<CFData, CFError> { + unsafe { + let mut error = ptr::null_mut(); + let transform = SecEncryptTransformCreate(key.as_concrete_TypeRef(), &mut error); + if transform.is_null() { + return Err(CFError::wrap_under_create_rule(error)); + } + let transform = SecTransform::wrap_under_create_rule(transform); + + self.finish(transform, data) + } + } + + /// Decrypts data with a provided key. + pub fn decrypt(&self, key: &SecKey, data: &CFData) -> Result<CFData, CFError> { + unsafe { + let mut error = ptr::null_mut(); + let transform = SecDecryptTransformCreate(key.as_concrete_TypeRef(), &mut error); + if transform.is_null() { + return Err(CFError::wrap_under_create_rule(error)); + } + let transform = SecTransform::wrap_under_create_rule(transform); + + self.finish(transform, data) + } + } + + fn finish(&self, mut transform: SecTransform, data: &CFData) -> Result<CFData, CFError> { + unsafe { + if let Some(ref padding) = self.padding { + let key = CFString::wrap_under_get_rule(kSecPaddingKey); + transform.set_attribute(&key, &padding.to_str())?; + } + + if let Some(ref mode) = self.mode { + let key = CFString::wrap_under_get_rule(kSecEncryptionMode); + transform.set_attribute(&key, &mode.to_str())?; + } + + if let Some(ref iv) = self.iv { + let key = CFString::wrap_under_get_rule(kSecIVKey); + transform.set_attribute(&key, iv)?; + } + + let key = CFString::wrap_under_get_rule(kSecTransformInputAttributeName); + transform.set_attribute(&key, data)?; + + let result = transform.execute()?; + Ok(CFData::wrap_under_get_rule( + result.as_CFTypeRef() as CFDataRef + )) + } + } +} + +#[cfg(test)] +mod test { + use core_foundation::data::CFData; + use hex::FromHex; + + use super::*; + use crate::key::SecKey; + use crate::os::macos::item::KeyType; + use crate::os::macos::key::SecKeyExt; + + #[test] + fn cbc_mmt_256() { + // test 9 + let key = "87725bd43a45608814180773f0e7ab95a3c859d83a2130e884190e44d14c6996"; + let iv = "e49651988ebbb72eb8bb80bb9abbca34"; + let ciphertext = "5b97a9d423f4b97413f388d9a341e727bb339f8e18a3fac2f2fb85abdc8f135deb30054a\ + 1afdc9b6ed7da16c55eba6b0d4d10c74e1d9a7cf8edfaeaa684ac0bd9f9d24ba674955c7\ + 9dc6be32aee1c260b558ff07e3a4d49d24162011ff254db8be078e8ad07e648e6bf56793\ + 76cb4321a5ef01afe6ad8816fcc7634669c8c4389295c9241e45fff39f3225f7745032da\ + eebe99d4b19bcb215d1bfdb36eda2c24"; + let plaintext = "bfe5c6354b7a3ff3e192e05775b9b75807de12e38a626b8bf0e12d5fff78e4f1775aa7d79\ + 2d885162e66d88930f9c3b2cdf8654f56972504803190386270f0aa43645db187af41fcea\ + 639b1f8026ccdd0c23e0de37094a8b941ecb7602998a4b2604e69fc04219585d854600e0a\ + d6f99a53b2504043c08b1c3e214d17cde053cbdf91daa999ed5b47c37983ba3ee254bc5c7\ + 93837daaa8c85cfc12f7f54f699f"; + + let key = Vec::<u8>::from_hex(key).unwrap(); + let key = CFData::from_buffer(&key); + let key = SecKey::from_data(KeyType::aes(), &key).unwrap(); + + let iv = Vec::<u8>::from_hex(iv).unwrap(); + + let ciphertext = Vec::<u8>::from_hex(ciphertext).unwrap(); + + let plaintext = Vec::<u8>::from_hex(plaintext).unwrap(); + + let decrypted = Builder::new() + .padding(Padding::none()) + .iv(CFData::from_buffer(&iv)) + .decrypt(&key, &CFData::from_buffer(&ciphertext)) + .unwrap(); + + assert_eq!(plaintext, decrypted.bytes()); + + let encrypted = Builder::new() + .padding(Padding::none()) + .iv(CFData::from_buffer(&iv)) + .encrypt(&key, &CFData::from_buffer(&plaintext)) + .unwrap(); + + assert_eq!(ciphertext, encrypted.bytes()); + } +} diff --git a/vendor/security-framework/src/os/macos/identity.rs b/vendor/security-framework/src/os/macos/identity.rs new file mode 100644 index 000000000..d54cbe00d --- /dev/null +++ b/vendor/security-framework/src/os/macos/identity.rs @@ -0,0 +1,86 @@ +//! OSX specific extensions to identity functionality. +use core_foundation::array::CFArray; +use core_foundation::base::TCFType; +use security_framework_sys::identity::SecIdentityCreateWithCertificate; +use std::ptr; + +use crate::base::Result; +use crate::certificate::SecCertificate; +use crate::cvt; +use crate::identity::SecIdentity; +use crate::os::macos::keychain::SecKeychain; + +/// An extension trait adding OSX specific functionality to `SecIdentity`. +pub trait SecIdentityExt { + /// Creates an identity corresponding to a certificate, looking in the + /// provided keychains for the corresponding private key. + /// + /// To search the default keychains, use an empty slice for `keychains`. + /// + /// <https://developer.apple.com/documentation/security/1401160-secidentitycreatewithcertificate> + fn with_certificate( + keychains: &[SecKeychain], + certificate: &SecCertificate, + ) -> Result<SecIdentity>; +} + +impl SecIdentityExt for SecIdentity { + fn with_certificate(keychains: &[SecKeychain], certificate: &SecCertificate) -> Result<Self> { + let keychains = CFArray::from_CFTypes(keychains); + unsafe { + let mut identity = ptr::null_mut(); + cvt(SecIdentityCreateWithCertificate( + if keychains.len() > 0 {keychains.as_CFTypeRef()} else {ptr::null()}, + certificate.as_concrete_TypeRef(), + &mut identity, + ))?; + Ok(Self::wrap_under_create_rule(identity)) + } + } +} + +#[cfg(test)] +mod test { + use tempfile::tempdir; + + use super::*; + use crate::identity::SecIdentity; + use crate::os::macos::certificate::SecCertificateExt; + use crate::os::macos::import_export::ImportOptions; + use crate::os::macos::keychain::CreateOptions; + use crate::os::macos::test::identity; + use crate::test; + + #[test] + fn certificate() { + let dir = p!(tempdir()); + let identity = identity(dir.path()); + let certificate = p!(identity.certificate()); + assert_eq!("foobar.com", p!(certificate.common_name())); + } + + #[test] + fn private_key() { + let dir = p!(tempdir()); + let identity = identity(dir.path()); + p!(identity.private_key()); + } + + #[test] + fn with_certificate() { + let dir = p!(tempdir()); + + let mut keychain = p!(CreateOptions::new() + .password("foobar") + .create(dir.path().join("test.keychain"))); + + let key = include_bytes!("../../../test/server.key"); + p!(ImportOptions::new() + .filename("server.key") + .keychain(&mut keychain) + .import(key)); + + let cert = test::certificate(); + p!(SecIdentity::with_certificate(&[keychain], &cert)); + } +} diff --git a/vendor/security-framework/src/os/macos/import_export.rs b/vendor/security-framework/src/os/macos/import_export.rs new file mode 100644 index 000000000..05a4b4907 --- /dev/null +++ b/vendor/security-framework/src/os/macos/import_export.rs @@ -0,0 +1,345 @@ +//! OSX specific extensions to import/export functionality. + +use core_foundation::array::CFArray; +use core_foundation::base::{CFType, TCFType}; +use core_foundation::data::CFData; +use core_foundation::string::CFString; +use security_framework_sys::base::errSecSuccess; +use security_framework_sys::import_export::*; +use std::ptr; +use std::str::FromStr; + +use crate::base::{Error, Result}; +use crate::certificate::SecCertificate; +use crate::identity::SecIdentity; +use crate::import_export::Pkcs12ImportOptions; +use crate::key::SecKey; +use crate::os::macos::access::SecAccess; +use crate::os::macos::keychain::SecKeychain; + +/// An extension trait adding OSX specific functionality to `Pkcs12ImportOptions`. +pub trait Pkcs12ImportOptionsExt { + /// Specifies the keychain in which to import the identity. + /// + /// If this is not called, the default keychain will be used. + fn keychain(&mut self, keychain: SecKeychain) -> &mut Self; + + /// Specifies the access control to be associated with the identity. + fn access(&mut self, access: SecAccess) -> &mut Self; +} + +impl Pkcs12ImportOptionsExt for Pkcs12ImportOptions { + #[inline(always)] + fn keychain(&mut self, keychain: SecKeychain) -> &mut Self { + crate::Pkcs12ImportOptionsInternals::keychain(self, keychain) + } + + #[inline(always)] + fn access(&mut self, access: SecAccess) -> &mut Self { + crate::Pkcs12ImportOptionsInternals::access(self, access) + } +} + +/// A builder type to import Security Framework types from serialized formats. +#[derive(Default)] +pub struct ImportOptions<'a> { + filename: Option<CFString>, + passphrase: Option<CFType>, + secure_passphrase: bool, + no_access_control: bool, + alert_title: Option<CFString>, + alert_prompt: Option<CFString>, + items: Option<&'a mut SecItems>, + keychain: Option<SecKeychain>, +} + +impl<'a> ImportOptions<'a> { + /// Creates a new builder with default options. + #[inline(always)] + #[must_use] + pub fn new() -> ImportOptions<'a> { + ImportOptions::default() + } + + /// Sets the filename from which the imported data came. + /// + /// The extension of the file will used as a hint for parsing. + #[inline] + pub fn filename(&mut self, filename: &str) -> &mut ImportOptions<'a> { + self.filename = Some(CFString::from_str(filename).unwrap()); + self + } + + /// Sets the passphrase to be used to decrypt the imported data. + #[inline] + pub fn passphrase(&mut self, passphrase: &str) -> &mut ImportOptions<'a> { + self.passphrase = Some(CFString::from_str(passphrase).unwrap().into_CFType()); + self + } + + /// Sets the passphrase to be used to decrypt the imported data. + #[inline] + pub fn passphrase_bytes(&mut self, passphrase: &[u8]) -> &mut ImportOptions<'a> { + self.passphrase = Some(CFData::from_buffer(passphrase).into_CFType()); + self + } + + /// If set, the user will be prompted to imput the passphrase used to + /// decrypt the imported data. + #[inline(always)] + pub fn secure_passphrase(&mut self, secure_passphrase: bool) -> &mut ImportOptions<'a> { + self.secure_passphrase = secure_passphrase; + self + } + + /// If set, imported items will have no access controls imposed on them. + #[inline(always)] + pub fn no_access_control(&mut self, no_access_control: bool) -> &mut ImportOptions<'a> { + self.no_access_control = no_access_control; + self + } + + /// Sets the title of the alert popup used with the `secure_passphrase` + /// option. + #[inline] + pub fn alert_title(&mut self, alert_title: &str) -> &mut ImportOptions<'a> { + self.alert_title = Some(CFString::from_str(alert_title).unwrap()); + self + } + + /// Sets the prompt of the alert popup used with the `secure_passphrase` + /// option. + #[inline] + pub fn alert_prompt(&mut self, alert_prompt: &str) -> &mut ImportOptions<'a> { + self.alert_prompt = Some(CFString::from_str(alert_prompt).unwrap()); + self + } + + /// Sets the object into which imported items will be placed. + #[inline(always)] + pub fn items(&mut self, items: &'a mut SecItems) -> &mut ImportOptions<'a> { + self.items = Some(items); + self + } + + /// Sets the keychain into which items will be imported. + /// + /// This must be specified to import `SecIdentity`s. + #[inline] + pub fn keychain(&mut self, keychain: &SecKeychain) -> &mut ImportOptions<'a> { + self.keychain = Some(keychain.clone()); + self + } + + /// Imports items from serialized data. + pub fn import(&mut self, data: &[u8]) -> Result<()> { + let data = CFData::from_buffer(data); + let data = data.as_concrete_TypeRef(); + + let filename = match self.filename { + Some(ref filename) => filename.as_concrete_TypeRef(), + None => ptr::null(), + }; + + let mut key_params = SecItemImportExportKeyParameters { + version: SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION, + flags: 0, + passphrase: ptr::null(), + alertTitle: ptr::null(), + alertPrompt: ptr::null(), + accessRef: ptr::null_mut(), + keyUsage: ptr::null_mut(), + keyAttributes: ptr::null(), + }; + + if let Some(ref passphrase) = self.passphrase { + key_params.passphrase = passphrase.as_CFTypeRef(); + } + + if self.secure_passphrase { + key_params.flags |= kSecKeySecurePassphrase; + } + + if self.no_access_control { + key_params.flags |= kSecKeyNoAccessControl; + } + + if let Some(ref alert_title) = self.alert_title { + key_params.alertTitle = alert_title.as_concrete_TypeRef(); + } + + if let Some(ref alert_prompt) = self.alert_prompt { + key_params.alertPrompt = alert_prompt.as_concrete_TypeRef(); + } + + let keychain = match self.keychain { + Some(ref keychain) => keychain.as_concrete_TypeRef(), + None => ptr::null_mut(), + }; + + let mut raw_items = ptr::null(); + let items_ref = match self.items { + Some(_) => std::ptr::addr_of_mut!(raw_items), + None => ptr::null_mut(), + }; + + unsafe { + let ret = SecItemImport( + data, + filename, + ptr::null_mut(), + ptr::null_mut(), + 0, + &key_params, + keychain, + items_ref, + ); + if ret != errSecSuccess { + return Err(Error::from_code(ret)); + } + + if let Some(ref mut items) = self.items { + let raw_items = CFArray::<CFType>::wrap_under_create_rule(raw_items); + for item in raw_items.iter() { + let type_id = item.type_of(); + if type_id == SecCertificate::type_id() { + items.certificates.push(SecCertificate::wrap_under_get_rule( + item.as_CFTypeRef() as *mut _, + )); + } else if type_id == SecIdentity::type_id() { + items.identities.push(SecIdentity::wrap_under_get_rule( + item.as_CFTypeRef() as *mut _, + )); + } else if type_id == SecKey::type_id() { + items + .keys + .push(SecKey::wrap_under_get_rule(item.as_CFTypeRef() as *mut _)); + } else { + panic!("Got bad type from SecItemImport: {}", type_id); + } + } + } + } + + Ok(()) + } +} + +/// A type which holds items imported from serialized data. +/// +/// Pass a reference to `ImportOptions::items`. +#[derive(Default)] +pub struct SecItems { + /// Imported certificates. + pub certificates: Vec<SecCertificate>, + /// Imported identities. + pub identities: Vec<SecIdentity>, + /// Imported keys. + pub keys: Vec<SecKey>, +} + +#[cfg(test)] +mod test { + use super::*; + use crate::import_export::*; + use crate::os::macos::keychain; + use hex; + use tempfile::tempdir; + + #[test] + fn certificate() { + let data = include_bytes!("../../../test/server.der"); + let mut items = SecItems::default(); + ImportOptions::new() + .filename("server.der") + .items(&mut items) + .import(data) + .unwrap(); + assert_eq!(1, items.certificates.len()); + assert_eq!(0, items.identities.len()); + assert_eq!(0, items.keys.len()); + } + + #[test] + fn key() { + let data = include_bytes!("../../../test/server.key"); + let mut items = SecItems::default(); + ImportOptions::new() + .filename("server.key") + .items(&mut items) + .import(data) + .unwrap(); + assert_eq!(0, items.certificates.len()); + assert_eq!(0, items.identities.len()); + assert_eq!(1, items.keys.len()); + } + + #[test] + fn identity() { + let dir = tempdir().unwrap(); + let keychain = keychain::CreateOptions::new() + .password("password") + .create(dir.path().join("identity.keychain")) + .unwrap(); + + let data = include_bytes!("../../../test/server.p12"); + let mut items = SecItems::default(); + ImportOptions::new() + .filename("server.p12") + .passphrase("password123") + .items(&mut items) + .keychain(&keychain) + .import(data) + .unwrap(); + assert_eq!(1, items.identities.len()); + assert_eq!(0, items.certificates.len()); + assert_eq!(0, items.keys.len()); + } + + #[test] + #[ignore] // since it requires manual intervention + fn secure_passphrase_identity() { + let dir = tempdir().unwrap(); + let keychain = keychain::CreateOptions::new() + .password("password") + .create(dir.path().join("identity.keychain")) + .unwrap(); + + let data = include_bytes!("../../../test/server.p12"); + let mut items = SecItems::default(); + ImportOptions::new() + .filename("server.p12") + .secure_passphrase(true) + .alert_title("alert title") + .alert_prompt("alert prompt") + .items(&mut items) + .keychain(&keychain) + .import(data) + .unwrap(); + assert_eq!(1, items.identities.len()); + assert_eq!(0, items.certificates.len()); + assert_eq!(0, items.keys.len()); + } + + #[test] + fn pkcs12_import() { + use super::Pkcs12ImportOptionsExt; + + let dir = tempdir().unwrap(); + let keychain = keychain::CreateOptions::new() + .password("password") + .create(dir.path().join("pkcs12_import")) + .unwrap(); + + let data = include_bytes!("../../../test/server.p12"); + let identities = p!(Pkcs12ImportOptions::new() + .passphrase("password123") + .keychain(keychain) + .import(data)); + assert_eq!(1, identities.len()); + assert_eq!( + hex::encode(identities[0].key_id.as_ref().unwrap()), + "ed6492936dcc8907e397e573b36e633458dc33f1" + ); + } +} diff --git a/vendor/security-framework/src/os/macos/item.rs b/vendor/security-framework/src/os/macos/item.rs new file mode 100644 index 000000000..18a4d2e8f --- /dev/null +++ b/vendor/security-framework/src/os/macos/item.rs @@ -0,0 +1,47 @@ +//! OSX specific functionality for items. +use crate::item::ItemSearchOptions; +use crate::os::macos::keychain::SecKeychain; +use crate::ItemSearchOptionsInternals; + +// Moved to crate::Key +pub use crate::key::KeyType; + +/// An extension trait adding OSX specific functionality to `ItemSearchOptions`. +pub trait ItemSearchOptionsExt { + /// Search within the specified keychains. + /// + /// If this is not called, the default keychain will be searched. + fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self; +} + +impl ItemSearchOptionsExt for ItemSearchOptions { + #[inline(always)] + fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self { + ItemSearchOptionsInternals::keychains(self, keychains) + } +} + +#[cfg(test)] +mod test { + use crate::item::*; + use crate::os::macos::certificate::SecCertificateExt; + use crate::os::macos::item::ItemSearchOptionsExt; + use crate::os::macos::test::keychain; + use tempfile::tempdir; + + #[test] + fn find_certificate() { + let dir = p!(tempdir()); + let keychain = keychain(dir.path()); + let results = p!(ItemSearchOptions::new() + .keychains(&[keychain]) + .class(ItemClass::certificate()) + .search()); + assert_eq!(1, results.len()); + let certificate = match results[0] { + SearchResult::Ref(Reference::Certificate(ref cert)) => cert, + _ => panic!("expected certificate"), + }; + assert_eq!("foobar.com", p!(certificate.common_name())); + } +} diff --git a/vendor/security-framework/src/os/macos/key.rs b/vendor/security-framework/src/os/macos/key.rs new file mode 100644 index 000000000..f6a20e93c --- /dev/null +++ b/vendor/security-framework/src/os/macos/key.rs @@ -0,0 +1,38 @@ +//! OSX specific functionality for keys. +use core_foundation::base::TCFType; +use core_foundation::data::CFData; +use core_foundation::dictionary::CFDictionary; +use core_foundation::error::CFError; +use core_foundation::string::CFString; +use security_framework_sys::item::kSecAttrKeyType; +use security_framework_sys::key::SecKeyCreateFromData; +use std::ptr; + +use crate::key::{KeyType, SecKey}; + +/// An extension trait adding OSX specific functionality to `SecKey`. +pub trait SecKeyExt { + /// Creates a new `SecKey` from a buffer containing key data. + fn from_data(key_type: KeyType, key_data: &CFData) -> Result<SecKey, CFError>; +} + +impl SecKeyExt for SecKey { + fn from_data(key_type: KeyType, key_data: &CFData) -> Result<Self, CFError> { + unsafe { + let key = CFString::wrap_under_get_rule(kSecAttrKeyType); + let dict = CFDictionary::from_CFType_pairs(&[(key, key_type.to_str())]); + + let mut err = ptr::null_mut(); + let key = SecKeyCreateFromData( + dict.as_concrete_TypeRef(), + key_data.as_concrete_TypeRef(), + &mut err, + ); + if key.is_null() { + Err(CFError::wrap_under_create_rule(err)) + } else { + Ok(Self::wrap_under_create_rule(key)) + } + } + } +} diff --git a/vendor/security-framework/src/os/macos/keychain.rs b/vendor/security-framework/src/os/macos/keychain.rs new file mode 100644 index 000000000..68e1c0cb4 --- /dev/null +++ b/vendor/security-framework/src/os/macos/keychain.rs @@ -0,0 +1,280 @@ +//! Keychain support. + +use core_foundation::base::{Boolean, TCFType}; +use security_framework_sys::base::{errSecSuccess, SecKeychainRef}; +use security_framework_sys::keychain::*; +use std::ffi::CString; +use std::os::raw::c_void; +use std::os::unix::ffi::OsStrExt; +use std::path::Path; +use std::ptr; + +use crate::base::{Error, Result}; +use crate::cvt; +use crate::os::macos::access::SecAccess; + +pub use security_framework_sys::keychain::SecPreferencesDomain; + +declare_TCFType! { + /// A type representing a keychain. + SecKeychain, SecKeychainRef +} +impl_TCFType!(SecKeychain, SecKeychainRef, SecKeychainGetTypeID); + +unsafe impl Sync for SecKeychain {} +unsafe impl Send for SecKeychain {} + +impl SecKeychain { + /// Creates a `SecKeychain` object corresponding to the user's default + /// keychain. + #[inline] + pub fn default() -> Result<Self> { + unsafe { + let mut keychain = ptr::null_mut(); + cvt(SecKeychainCopyDefault(&mut keychain))?; + Ok(Self::wrap_under_create_rule(keychain)) + } + } + + /// Creates a `SecKeychain` object corresponding to the user's default + /// keychain for the given domain. + pub fn default_for_domain(domain: SecPreferencesDomain) -> Result<Self> { + unsafe { + let mut keychain = ptr::null_mut(); + cvt(SecKeychainCopyDomainDefault(domain, &mut keychain))?; + Ok(Self::wrap_under_create_rule(keychain)) + } + } + + /// Opens a keychain from a file. + pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> { + let path_name = [ + path.as_ref().as_os_str().as_bytes(), + std::slice::from_ref(&0) + ].concat(); + + unsafe { + let mut keychain = ptr::null_mut(); + cvt(SecKeychainOpen(path_name.as_ptr().cast(), &mut keychain))?; + Ok(Self::wrap_under_create_rule(keychain)) + } + } + + /// Unlocks the keychain. + /// + /// If a password is not specified, the user will be prompted to enter it. + pub fn unlock(&mut self, password: Option<&str>) -> Result<()> { + let (len, ptr, use_password) = match password { + Some(password) => (password.len(), password.as_ptr().cast(), true), + None => (0, ptr::null(), false), + }; + + unsafe { + cvt(SecKeychainUnlock( + self.as_concrete_TypeRef(), + len as u32, + ptr, + use_password as Boolean, + )) + } + } + + /// Sets settings of the keychain. + #[inline] + pub fn set_settings(&mut self, settings: &KeychainSettings) -> Result<()> { + unsafe { + cvt(SecKeychainSetSettings( + self.as_concrete_TypeRef(), + &settings.0, + )) + } + } + + #[cfg(target_os = "macos")] + /// Disables the user interface for keychain services functions that + /// automatically display a user interface. + pub fn disable_user_interaction() -> Result<KeychainUserInteractionLock> { + let code = unsafe { SecKeychainSetUserInteractionAllowed(0u8) }; + + if code != errSecSuccess { + Err(Error::from_code(code)) + } else { + Ok(KeychainUserInteractionLock) + } + } + + #[cfg(target_os = "macos")] + /// Indicates whether keychain services functions that normally display a + /// user interaction are allowed to do so. + pub fn user_interaction_allowed() -> Result<bool> { + let mut state: Boolean = 0; + let code = unsafe { SecKeychainGetUserInteractionAllowed(&mut state) }; + + if code != errSecSuccess { + Err(Error::from_code(code)) + } else { + Ok(state != 0) + } + } +} + +/// A builder type to create new keychains. +#[derive(Default)] +pub struct CreateOptions { + password: Option<String>, + prompt_user: bool, + access: Option<SecAccess>, +} + +impl CreateOptions { + /// Creates a new builder with default options. + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Sets the password to be used to protect the keychain. + #[inline] + pub fn password(&mut self, password: &str) -> &mut Self { + self.password = Some(password.into()); + self + } + + /// If set, the user will be prompted to provide a password used to + /// protect the keychain. + #[inline(always)] + pub fn prompt_user(&mut self, prompt_user: bool) -> &mut Self { + self.prompt_user = prompt_user; + self + } + + /// Sets the access control applied to the keychain. + #[inline(always)] + pub fn access(&mut self, access: SecAccess) -> &mut Self { + self.access = Some(access); + self + } + + /// Creates a new keychain at the specified location on the filesystem. + pub fn create<P: AsRef<Path>>(&self, path: P) -> Result<SecKeychain> { + unsafe { + let path_name = path.as_ref().as_os_str().as_bytes(); + // FIXME + let path_name = CString::new(path_name).unwrap(); + + let (password, password_len) = match self.password { + Some(ref password) => (password.as_ptr().cast::<c_void>(), password.len() as u32), + None => (ptr::null(), 0), + }; + + let access = match self.access { + Some(ref access) => access.as_concrete_TypeRef(), + None => ptr::null_mut(), + }; + + let mut keychain = ptr::null_mut(); + cvt(SecKeychainCreate( + path_name.as_ptr(), + password_len, + password, + self.prompt_user as Boolean, + access, + &mut keychain, + ))?; + + Ok(SecKeychain::wrap_under_create_rule(keychain)) + } + } +} + +/// Settings associated with a `SecKeychain`. +pub struct KeychainSettings(SecKeychainSettings); + +impl KeychainSettings { + /// Creates a new `KeychainSettings` with default settings. + #[inline] + #[must_use] + pub fn new() -> Self { + Self(SecKeychainSettings { + version: SEC_KEYCHAIN_SETTINGS_VERS1, + lockOnSleep: 0, + useLockInterval: 0, + lockInterval: i32::max_value() as u32, + }) + } + + /// If set, the keychain will automatically lock when the computer sleeps. + /// + /// Defaults to `false`. + #[inline(always)] + pub fn set_lock_on_sleep(&mut self, lock_on_sleep: bool) { + self.0.lockOnSleep = lock_on_sleep as Boolean; + } + + /// Sets the interval of time in seconds after which the keychain is + /// automatically locked. + /// + /// Defaults to `None`. + pub fn set_lock_interval(&mut self, lock_interval: Option<u32>) { + match lock_interval { + Some(lock_interval) => { + self.0.useLockInterval = 1; + self.0.lockInterval = lock_interval; + } + None => { + self.0.useLockInterval = 0; + self.0.lockInterval = i32::max_value() as u32; + } + } + } +} + +impl Default for KeychainSettings { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + +#[cfg(target_os = "macos")] +#[must_use = "The user interaction is disabled for the lifetime of the returned object"] +/// Automatically re-enables user interaction. +pub struct KeychainUserInteractionLock; + +#[cfg(target_os = "macos")] +impl Drop for KeychainUserInteractionLock { + #[inline(always)] + fn drop(&mut self) { + unsafe { SecKeychainSetUserInteractionAllowed(1u8) }; + } +} + +#[cfg(test)] +mod test { + use tempfile::tempdir; + + use super::*; + + #[test] + fn create_options() { + let dir = tempdir().unwrap(); + + let mut keychain = CreateOptions::new() + .password("foobar") + .create(dir.path().join("test.keychain")) + .unwrap(); + + keychain.set_settings(&KeychainSettings::new()).unwrap(); + } + + #[test] + fn disable_user_interaction() { + assert!(SecKeychain::user_interaction_allowed().unwrap()); + { + let _lock = SecKeychain::disable_user_interaction().unwrap(); + assert!(!SecKeychain::user_interaction_allowed().unwrap()); + } + assert!(SecKeychain::user_interaction_allowed().unwrap()); + } +} diff --git a/vendor/security-framework/src/os/macos/keychain_item.rs b/vendor/security-framework/src/os/macos/keychain_item.rs new file mode 100644 index 000000000..fd7b452a2 --- /dev/null +++ b/vendor/security-framework/src/os/macos/keychain_item.rs @@ -0,0 +1,26 @@ +//! Keychain item support. + +use core_foundation::base::TCFType; +use security_framework_sys::base::SecKeychainItemRef; +use security_framework_sys::keychain_item::SecKeychainItemGetTypeID; +use std::fmt; + +declare_TCFType! { + /// A type representing a keychain item. + SecKeychainItem, SecKeychainItemRef +} +impl_TCFType!( + SecKeychainItem, + SecKeychainItemRef, + SecKeychainItemGetTypeID +); + +unsafe impl Sync for SecKeychainItem {} +unsafe impl Send for SecKeychainItem {} + +impl fmt::Debug for SecKeychainItem { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("SecKeychainItem").finish_non_exhaustive() + } +} diff --git a/vendor/security-framework/src/os/macos/mod.rs b/vendor/security-framework/src/os/macos/mod.rs new file mode 100644 index 000000000..5fe7d0d2c --- /dev/null +++ b/vendor/security-framework/src/os/macos/mod.rs @@ -0,0 +1,52 @@ +//! OSX specific extensions. + +pub mod access; +pub mod certificate; +pub mod certificate_oids; +pub mod code_signing; +pub mod digest_transform; +pub mod encrypt_transform; +pub mod identity; +pub mod import_export; +pub mod item; +pub mod key; +pub mod keychain; +pub mod keychain_item; +pub mod passwords; +pub mod secure_transport; +pub mod transform; + +#[cfg(test)] +pub mod test { + use crate::identity::SecIdentity; + use crate::item::{ItemClass, ItemSearchOptions, Reference, SearchResult}; + use crate::os::macos::item::ItemSearchOptionsExt; + use crate::os::macos::keychain::SecKeychain; + use std::fs::File; + use std::io::prelude::*; + use std::path::Path; + + pub fn identity(dir: &Path) -> SecIdentity { + // FIXME https://github.com/rust-lang/rust/issues/30018 + let keychain = keychain(dir); + let mut items = p!(ItemSearchOptions::new() + .class(ItemClass::identity()) + .keychains(&[keychain]) + .search()); + match items.pop().unwrap() { + SearchResult::Ref(Reference::Identity(identity)) => identity, + _ => panic!("expected identity"), + } + } + + pub fn keychain(dir: &Path) -> SecKeychain { + let path = dir.join("server.keychain"); + let mut file = p!(File::create(&path)); + p!(file.write_all(include_bytes!("../../../test/server.keychain"))); + drop(file); + + let mut keychain = p!(SecKeychain::open(&path)); + p!(keychain.unlock(Some("password123"))); + keychain + } +} diff --git a/vendor/security-framework/src/os/macos/passwords.rs b/vendor/security-framework/src/os/macos/passwords.rs new file mode 100644 index 000000000..94e1d6fa5 --- /dev/null +++ b/vendor/security-framework/src/os/macos/passwords.rs @@ -0,0 +1,525 @@ +//! Password support. + +use crate::os::macos::keychain::SecKeychain; +use crate::os::macos::keychain_item::SecKeychainItem; +use core_foundation::array::CFArray; +use core_foundation::base::TCFType; +pub use security_framework_sys::keychain::{SecAuthenticationType, SecProtocolType}; +use security_framework_sys::keychain::{ + SecKeychainAddGenericPassword, SecKeychainAddInternetPassword, SecKeychainFindGenericPassword, + SecKeychainFindInternetPassword, +}; +use security_framework_sys::keychain_item::{ + SecKeychainItemDelete, SecKeychainItemFreeContent, SecKeychainItemModifyAttributesAndData, +}; +use std::fmt; +use std::fmt::Write; +use std::ops::Deref; +use std::ptr; +use std::slice; + +use crate::base::Result; +use crate::cvt; + +/// Password slice. Use `.as_ref()` to get `&[u8]` or `.to_owned()` to get `Vec<u8>` +pub struct SecKeychainItemPassword { + data: *const u8, + data_len: usize, +} + +impl fmt::Debug for SecKeychainItemPassword { + #[cold] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for _ in 0..self.data_len { + f.write_char('•')?; + } + Ok(()) + } +} + +impl AsRef<[u8]> for SecKeychainItemPassword { + #[inline] + fn as_ref(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.data, self.data_len) } + } +} + +impl Deref for SecKeychainItemPassword { + type Target = [u8]; + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl Drop for SecKeychainItemPassword { + #[inline] + fn drop(&mut self) { + unsafe { + SecKeychainItemFreeContent(ptr::null_mut(), self.data as *mut _); + } + } +} + +impl SecKeychainItem { + /// Modify keychain item in-place, replacing its password with the given one + pub fn set_password(&mut self, password: &[u8]) -> Result<()> { + unsafe { + cvt(SecKeychainItemModifyAttributesAndData( + self.as_CFTypeRef() as *mut _, + ptr::null(), + password.len() as u32, + password.as_ptr().cast(), + ))?; + } + Ok(()) + } + + /// Delete this item from its keychain + #[inline] + pub fn delete(self) { + unsafe { + SecKeychainItemDelete(self.as_CFTypeRef() as *mut _); + } + } +} + +/// Find a generic password. +/// +/// The underlying system supports passwords with 0 values, so this +/// returns a vector of bytes rather than a string. +/// +/// * `keychains` is an array of keychains to search or None to search +/// the default keychain. +/// * `service` is the name of the service to search for. +/// * `account` is the name of the account to search for. +pub fn find_generic_password( + keychains: Option<&[SecKeychain]>, + service: &str, + account: &str, +) -> Result<(SecKeychainItemPassword, SecKeychainItem)> { + let keychains_or_none = keychains.map(CFArray::from_CFTypes); + + let keychains_or_null = match keychains_or_none { + None => ptr::null(), + Some(ref keychains) => keychains.as_CFTypeRef(), + }; + + let mut data_len = 0; + let mut data = ptr::null_mut(); + let mut item = ptr::null_mut(); + + unsafe { + cvt(SecKeychainFindGenericPassword( + keychains_or_null, + service.len() as u32, + service.as_ptr().cast(), + account.len() as u32, + account.as_ptr().cast(), + &mut data_len, + &mut data, + &mut item, + ))?; + Ok(( + SecKeychainItemPassword { + data: data as *const _, + data_len: data_len as usize, + }, + SecKeychainItem::wrap_under_create_rule(item), + )) + } +} + +/// * `keychains` is an array of keychains to search or None to search +/// the default keychain. +/// * `server`: server name. +/// * `security_domain`: security domain. This parameter is optional. +/// * `account`: account name. +/// * `path`: the path. +/// * `port`: The TCP/IP port number. +/// * `protocol`: The protocol associated with this password. +/// * `authentication_type`: The authentication scheme used. +#[allow(clippy::too_many_arguments)] +pub fn find_internet_password( + keychains: Option<&[SecKeychain]>, + server: &str, + security_domain: Option<&str>, + account: &str, + path: &str, + port: Option<u16>, + protocol: SecProtocolType, + authentication_type: SecAuthenticationType, +) -> Result<(SecKeychainItemPassword, SecKeychainItem)> { + let keychains_or_none = keychains.map(CFArray::from_CFTypes); + + let keychains_or_null = match keychains_or_none { + None => ptr::null(), + Some(ref keychains) => keychains.as_CFTypeRef(), + }; + + let mut data_len = 0; + let mut data = ptr::null_mut(); + let mut item = ptr::null_mut(); + + unsafe { + cvt(SecKeychainFindInternetPassword( + keychains_or_null, + server.len() as u32, + server.as_ptr().cast(), + security_domain.map_or(0, |s| s.len() as u32), + security_domain + .map_or(ptr::null(), |s| s.as_ptr().cast()), + account.len() as u32, + account.as_ptr().cast(), + path.len() as u32, + path.as_ptr().cast(), + port.unwrap_or(0), + protocol, + authentication_type, + &mut data_len, + &mut data, + &mut item, + ))?; + Ok(( + SecKeychainItemPassword { + data: data as *const _, + data_len: data_len as usize, + }, + SecKeychainItem::wrap_under_create_rule(item), + )) + } +} + +impl SecKeychain { + /// Find application password in this keychain + #[inline] + pub fn find_generic_password( + &self, + service: &str, + account: &str, + ) -> Result<(SecKeychainItemPassword, SecKeychainItem)> { + find_generic_password(Some(&[self.clone()]), service, account) + } + + /// Find internet password in this keychain + #[inline] + #[allow(clippy::too_many_arguments)] + pub fn find_internet_password( + &self, + server: &str, + security_domain: Option<&str>, + account: &str, + path: &str, + port: Option<u16>, + protocol: SecProtocolType, + authentication_type: SecAuthenticationType, + ) -> Result<(SecKeychainItemPassword, SecKeychainItem)> { + find_internet_password( + Some(&[self.clone()]), + server, + security_domain, + account, + path, + port, + protocol, + authentication_type, + ) + } + + /// Update existing or add new internet password + #[allow(clippy::too_many_arguments)] + pub fn set_internet_password( + &self, + server: &str, + security_domain: Option<&str>, + account: &str, + path: &str, + port: Option<u16>, + protocol: SecProtocolType, + authentication_type: SecAuthenticationType, + password: &[u8], + ) -> Result<()> { + match self.find_internet_password( + server, + security_domain, + account, + path, + port, + protocol, + authentication_type, + ) { + Ok((_, mut item)) => item.set_password(password), + _ => self.add_internet_password( + server, + security_domain, + account, + path, + port, + protocol, + authentication_type, + password, + ), + } + } + + /// Set a generic password. + /// + /// * `keychain_opt` is the keychain to use or None to use the default + /// keychain. + /// * `service` is the associated service name for the password. + /// * `account` is the associated account name for the password. + /// * `password` is the password itself. + pub fn set_generic_password( + &self, + service: &str, + account: &str, + password: &[u8], + ) -> Result<()> { + match self.find_generic_password(service, account) { + Ok((_, mut item)) => item.set_password(password), + _ => self.add_generic_password(service, account, password), + } + } + + /// Add application password to the keychain, without checking if it exists already + /// + /// See `set_generic_password()` + #[inline] + pub fn add_generic_password( + &self, + service: &str, + account: &str, + password: &[u8], + ) -> Result<()> { + unsafe { + cvt(SecKeychainAddGenericPassword( + self.as_CFTypeRef() as *mut _, + service.len() as u32, + service.as_ptr().cast(), + account.len() as u32, + account.as_ptr().cast(), + password.len() as u32, + password.as_ptr().cast(), + ptr::null_mut(), + ))?; + } + Ok(()) + } + + /// Add internet password to the keychain, without checking if it exists already + /// + /// See `set_internet_password()` + #[inline] + #[allow(clippy::too_many_arguments)] + pub fn add_internet_password( + &self, + server: &str, + security_domain: Option<&str>, + account: &str, + path: &str, + port: Option<u16>, + protocol: SecProtocolType, + authentication_type: SecAuthenticationType, + password: &[u8], + ) -> Result<()> { + unsafe { + cvt(SecKeychainAddInternetPassword( + self.as_CFTypeRef() as *mut _, + server.len() as u32, + server.as_ptr().cast(), + security_domain.map_or(0, |s| s.len() as u32), + security_domain + .map_or(ptr::null(), |s| s.as_ptr().cast()), + account.len() as u32, + account.as_ptr().cast(), + path.len() as u32, + path.as_ptr().cast(), + port.unwrap_or(0), + protocol, + authentication_type, + password.len() as u32, + password.as_ptr().cast(), + ptr::null_mut(), + ))?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::os::macos::keychain::{CreateOptions, SecKeychain}; + use tempfile::tempdir; + use tempfile::TempDir; + + fn temp_keychain_setup(name: &str) -> (TempDir, SecKeychain) { + let dir = tempdir().expect("TempDir::new"); + let keychain = CreateOptions::new() + .password("foobar") + .create(dir.path().join(name.to_string() + ".keychain")) + .expect("create keychain"); + + (dir, keychain) + } + + fn temp_keychain_teardown(dir: TempDir) { + dir.close().expect("temp dir close"); + } + + #[test] + fn missing_password_temp() { + let (dir, keychain) = temp_keychain_setup("missing_password"); + let keychains = vec![keychain]; + + let service = "temp_this_service_does_not_exist"; + let account = "this_account_is_bogus"; + let found = find_generic_password(Some(&keychains), service, account); + + assert!(found.is_err()); + + temp_keychain_teardown(dir); + } + + #[test] + #[cfg(feature = "default_keychain_tests")] + fn missing_password_default() { + let service = "default_this_service_does_not_exist"; + let account = "this_account_is_bogus"; + let found = find_generic_password(None, service, account); + + assert!(found.is_err()); + } + + #[test] + fn round_trip_password_temp() { + let (dir, keychain) = temp_keychain_setup("round_trip_password"); + + let service = "test_round_trip_password_temp"; + let account = "temp_this_is_the_test_account"; + let password = String::from("deadbeef").into_bytes(); + + keychain + .set_generic_password(service, account, &password) + .expect("set_generic_password"); + let (found, item) = keychain + .find_generic_password(service, account) + .expect("find_generic_password"); + assert_eq!(found.to_owned(), password); + + item.delete(); + + temp_keychain_teardown(dir); + } + + #[test] + #[cfg(feature = "default_keychain_tests")] + fn round_trip_password_default() { + let service = "test_round_trip_password_default"; + let account = "this_is_the_test_account"; + let password = String::from("deadbeef").into_bytes(); + + SecKeychain::default() + .expect("default keychain") + .set_generic_password(service, account, &password) + .expect("set_generic_password"); + let (found, item) = + find_generic_password(None, service, account).expect("find_generic_password"); + assert_eq!(&*found, &password[..]); + + item.delete(); + } + + #[test] + fn change_password_temp() { + let (dir, keychain) = temp_keychain_setup("change_password"); + let keychains = vec![keychain]; + + let service = "test_change_password_temp"; + let account = "this_is_the_test_account"; + let pw1 = String::from("password1").into_bytes(); + let pw2 = String::from("password2").into_bytes(); + + keychains[0] + .set_generic_password(service, account, &pw1) + .expect("set_generic_password1"); + let (found, _) = find_generic_password(Some(&keychains), service, account) + .expect("find_generic_password1"); + assert_eq!(found.as_ref(), &pw1[..]); + + keychains[0] + .set_generic_password(service, account, &pw2) + .expect("set_generic_password2"); + let (found, item) = find_generic_password(Some(&keychains), service, account) + .expect("find_generic_password2"); + assert_eq!(&*found, &pw2[..]); + + item.delete(); + + temp_keychain_teardown(dir); + } + + #[test] + #[cfg(feature = "default_keychain_tests")] + fn change_password_default() { + let service = "test_change_password_default"; + let account = "this_is_the_test_account"; + let pw1 = String::from("password1").into_bytes(); + let pw2 = String::from("password2").into_bytes(); + + SecKeychain::default() + .expect("default keychain") + .set_generic_password(service, account, &pw1) + .expect("set_generic_password1"); + let (found, _) = + find_generic_password(None, service, account).expect("find_generic_password1"); + assert_eq!(found.to_owned(), pw1); + + SecKeychain::default() + .expect("default keychain") + .set_generic_password(service, account, &pw2) + .expect("set_generic_password2"); + let (found, item) = + find_generic_password(None, service, account).expect("find_generic_password2"); + assert_eq!(found.to_owned(), pw2); + + item.delete(); + } + + #[test] + fn cross_keychain_corruption_temp() { + let (dir1, keychain1) = temp_keychain_setup("cross_corrupt1"); + let (dir2, keychain2) = temp_keychain_setup("cross_corrupt2"); + let keychains1 = vec![keychain1.clone()]; + let keychains2 = vec![keychain2.clone()]; + let both_keychains = vec![keychain1, keychain2]; + + let service = "temp_this_service_does_not_exist"; + let account = "this_account_is_bogus"; + let password = String::from("deadbeef").into_bytes(); + + // Make sure this password doesn't exist in either keychain. + let found = find_generic_password(Some(&both_keychains), service, account); + assert!(found.is_err()); + + // Set a password in one keychain. + keychains1[0] + .set_generic_password(service, account, &password) + .expect("set_generic_password"); + + // Make sure it's found in that keychain. + let (found, item) = find_generic_password(Some(&keychains1), service, account) + .expect("find_generic_password1"); + assert_eq!(found.to_owned(), password); + + // Make sure it's _not_ found in the other keychain. + let found = find_generic_password(Some(&keychains2), service, account); + assert!(found.is_err()); + + // Cleanup. + item.delete(); + + temp_keychain_teardown(dir1); + temp_keychain_teardown(dir2); + } +} diff --git a/vendor/security-framework/src/os/macos/secure_transport.rs b/vendor/security-framework/src/os/macos/secure_transport.rs new file mode 100644 index 000000000..d98790b89 --- /dev/null +++ b/vendor/security-framework/src/os/macos/secure_transport.rs @@ -0,0 +1,647 @@ +//! OSX specific extensions to Secure Transport functionality. + +use core_foundation::array::CFArray; +use core_foundation::base::TCFType; +use security_framework_sys::secure_transport::*; +use std::ptr; +use std::slice; + +use crate::base::Result; +use crate::certificate::SecCertificate; +use crate::secure_transport::{MidHandshakeSslStream, SslContext}; +use crate::{cvt, AsInner}; + +/// An extension trait adding OSX specific functionality to the `SslContext` +/// type. +pub trait SslContextExt { + /// Returns the DER encoded data specifying the parameters used for + /// Diffie-Hellman key exchange. + fn diffie_hellman_params(&self) -> Result<Option<&[u8]>>; + + /// Sets the parameters used for Diffie-Hellman key exchange, in the + /// DER format used by OpenSSL. + /// + /// If a cipher suite which uses Diffie-Hellman key exchange is selected, + /// parameters will automatically be generated if none are provided with + /// this method, but this process can take up to 30 seconds. + /// + /// This can only be called on server-side sessions. + fn set_diffie_hellman_params(&mut self, dh_params: &[u8]) -> Result<()>; + + /// Returns the certificate authorities used to validate client + /// certificates. + fn certificate_authorities(&self) -> Result<Option<Vec<SecCertificate>>>; + + /// Sets the certificate authorities used to validate client certificates, + /// replacing any that are already present. + fn set_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()>; + + /// Adds certificate authorities used to validate client certificates. + fn add_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()>; + + /// If enabled, server identity changes are allowed during renegotiation. + /// + /// It is disabled by default to protect against triple handshake attacks. + /// + /// Requires the `OSX_10_11` (or greater) feature. + #[cfg(feature = "OSX_10_11")] + fn allow_server_identity_change(&self) -> Result<bool>; + + /// If enabled, server identity changes are allowed during renegotiation. + /// + /// It is disabled by default to protect against triple handshake attacks. + /// + /// Requires the `OSX_10_11` (or greater) feature. + #[cfg(feature = "OSX_10_11")] + fn set_allow_server_identity_change(&mut self, value: bool) -> Result<()>; + + /// If enabled, fallback countermeasures will be used during negotiation. + /// + /// It should be enabled when renegotiating with a peer with a lower + /// maximum protocol version due to an earlier failure to connect. + /// + /// Requires the `OSX_10_10` (or greater) feature. + #[cfg(feature = "OSX_10_10")] + fn fallback(&self) -> Result<bool>; + + /// If enabled, fallback countermeasures will be used during negotiation. + /// + /// It should be enabled when renegotiating with a peer with a lower + /// maximum protocol version due to an earlier failure to connect. + /// + /// Requires the `OSX_10_10` (or greater) feature. + #[cfg(feature = "OSX_10_10")] + fn set_fallback(&mut self, value: bool) -> Result<()>; + + /// If enabled, the handshake process will pause and return when the client + /// hello is recieved to support server name identification. + /// + /// Requires the `OSX_10_11` (or greater) feature. + #[cfg(feature = "OSX_10_11")] + fn break_on_client_hello(&self) -> Result<bool>; + + /// If enabled, the handshake process will pause and return when the client + /// hello is recieved to support server name identification. + /// + /// Requires the `OSX_10_11` (or greater) feature. + #[cfg(feature = "OSX_10_11")] + fn set_break_on_client_hello(&mut self, value: bool) -> Result<()>; +} + +macro_rules! impl_options { + ($($(#[$a:meta])* const $opt:ident: $get:ident & $set:ident,)*) => { + $( + $(#[$a])* + #[inline] + fn $set(&mut self, value: bool) -> Result<()> { + unsafe { + cvt(SSLSetSessionOption(self.as_inner(), + $opt, + value as ::core_foundation::base::Boolean)) + } + } + + $(#[$a])* + #[inline] + fn $get(&self) -> Result<bool> { + let mut value = 0; + unsafe { cvt(SSLGetSessionOption(self.as_inner(), $opt, &mut value))?; } + Ok(value != 0) + } + )* + } +} + +impl SslContextExt for SslContext { + fn diffie_hellman_params(&self) -> Result<Option<&[u8]>> { + unsafe { + let mut ptr = ptr::null(); + let mut len = 0; + cvt(SSLGetDiffieHellmanParams( + self.as_inner(), + &mut ptr, + &mut len, + ))?; + if ptr.is_null() { + Ok(None) + } else { + Ok(Some(slice::from_raw_parts(ptr.cast::<u8>(), len))) + } + } + } + + fn set_diffie_hellman_params(&mut self, dh_params: &[u8]) -> Result<()> { + unsafe { + cvt(SSLSetDiffieHellmanParams( + self.as_inner(), + dh_params.as_ptr().cast(), + dh_params.len(), + )) + } + } + + fn certificate_authorities(&self) -> Result<Option<Vec<SecCertificate>>> { + unsafe { + let mut raw_certs = ptr::null(); + cvt(SSLCopyCertificateAuthorities( + self.as_inner(), + &mut raw_certs, + ))?; + if raw_certs.is_null() { + Ok(None) + } else { + let certs = CFArray::<SecCertificate>::wrap_under_create_rule(raw_certs) + .iter() + .map(|c| c.clone()) + .collect(); + Ok(Some(certs)) + } + } + } + + fn set_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()> { + unsafe { + let certs = CFArray::from_CFTypes(certs); + cvt(SSLSetCertificateAuthorities( + self.as_inner(), + certs.as_CFTypeRef(), + 1, + )) + } + } + + fn add_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()> { + unsafe { + let certs = CFArray::from_CFTypes(certs); + cvt(SSLSetCertificateAuthorities( + self.as_inner(), + certs.as_CFTypeRef(), + 0, + )) + } + } + + impl_options! { + #[cfg(feature = "OSX_10_11")] + const kSSLSessionOptionAllowServerIdentityChange: allow_server_identity_change & set_allow_server_identity_change, + #[cfg(feature = "OSX_10_10")] + const kSSLSessionOptionFallback: fallback & set_fallback, + #[cfg(feature = "OSX_10_11")] + const kSSLSessionOptionBreakOnClientHello: break_on_client_hello & set_break_on_client_hello, + } +} + +/// An extension trait adding OSX specific functionality to the +/// `MidHandshakeSslStream` type. +pub trait MidHandshakeSslStreamExt { + /// Returns `true` iff `break_on_client_hello` was set and the handshake + /// has progressed to that point. + /// + /// Requires the `OSX_10_11` (or greater) feature. + #[cfg(feature = "OSX_10_11")] + fn client_hello_received(&self) -> bool; +} + +impl<S> MidHandshakeSslStreamExt for MidHandshakeSslStream<S> { + #[cfg(feature = "OSX_10_11")] + fn client_hello_received(&self) -> bool { + self.error().code() == errSSLClientHelloReceived + } +} + +#[cfg(test)] +mod test { + use std::io::prelude::*; + use std::net::{TcpListener, TcpStream}; + use std::thread; + use tempfile::tempdir; + + use super::*; + use crate::cipher_suite::CipherSuite; + use crate::os::macos::test::identity; + use crate::secure_transport::*; + use crate::test::certificate; + + #[test] + fn server_client() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + + let stream = p!(listener.accept()).0; + let mut stream = p!(ctx.handshake(stream)); + + let mut buf = [0; 12]; + p!(stream.read(&mut buf)); + assert_eq!(&buf[..], b"hello world!"); + }); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_break_on_server_auth(true)); + let stream = p!(TcpStream::connect(("localhost", port))); + + let stream = match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Interrupted(stream)) => stream, + Err(err) => panic!("unexpected error {:?}", err), + }; + + assert!(stream.server_auth_completed()); + let mut peer_trust = p!(stream.context().peer_trust2()).unwrap(); + p!(peer_trust.set_anchor_certificates(&[certificate()])); + p!(peer_trust.evaluate_with_error()); + + let mut stream = p!(stream.handshake()); + p!(stream.write_all(b"hello world!")); + + handle.join().unwrap(); + } + + #[test] + #[ignore] + fn server_client_builders() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let identity = identity(dir.path()); + let builder = ServerBuilder::new(&identity, &[]); + + let stream = p!(listener.accept()).0; + let mut stream = p!(builder.handshake(stream)); + + let mut buf = [0; 12]; + p!(stream.read(&mut buf)); + assert_eq!(&buf[..], b"hello world!"); + }); + + let stream = p!(TcpStream::connect(("localhost", port))); + let mut stream = p!(ClientBuilder::new() + .anchor_certificates(&[certificate()]) + .handshake("foobar.com", stream)); + + p!(stream.write_all(b"hello world!")); + + handle.join().unwrap(); + } + + #[test] + fn client_bad_cert() { + let _ = env_logger::try_init(); + + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + + let stream = p!(listener.accept()).0; + let _ = ctx.handshake(stream); + }); + + let stream = p!(TcpStream::connect(("localhost", port))); + assert!(ClientBuilder::new() + .handshake("foobar.com", stream) + .is_err()); + + handle.join().unwrap(); + } + + #[test] + #[ignore] + fn client() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + + let stream = p!(listener.accept()).0; + let mut stream = p!(ctx.handshake(stream)); + + let mut buf = [0; 12]; + p!(stream.read(&mut buf)); + assert_eq!(&buf[..], b"hello world!"); + }); + + let stream = p!(TcpStream::connect(("localhost", port))); + let mut stream = p!(ClientBuilder::new() + .anchor_certificates(&[certificate()]) + .handshake("foobar.com", stream)); + p!(stream.write_all(b"hello world!")); + + handle.join().unwrap(); + } + + #[test] + fn negotiated_cipher() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + p!(ctx.set_enabled_ciphers(&[ + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 + ])); + + let stream = p!(listener.accept()).0; + let mut stream = p!(ctx.handshake(stream)); + assert_eq!( + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + p!(stream.context().negotiated_cipher()) + ); + let mut buf = [0; 1]; + p!(stream.read(&mut buf)); + }); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_break_on_server_auth(true)); + p!(ctx.set_enabled_ciphers(&[ + CipherSuite::TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 + ])); + let stream = p!(TcpStream::connect(("localhost", port))); + + let stream = match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Interrupted(stream)) => stream, + Err(err) => panic!("unexpected error {:?}", err), + }; + + let mut stream = p!(stream.handshake()); + assert_eq!( + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + p!(stream.context().negotiated_cipher()) + ); + p!(stream.write(&[0])); + + handle.join().unwrap(); + } + + #[test] + fn dh_params() { + let params = include_bytes!("../../../test/dhparam.der"); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + assert!(p!(ctx.diffie_hellman_params()).is_none()); + p!(ctx.set_diffie_hellman_params(params)); + assert_eq!(p!(ctx.diffie_hellman_params()).unwrap(), ¶ms[..]); + } + + #[test] + fn try_authenticate_no_cert() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + p!(ctx.set_client_side_authenticate(SslAuthenticate::TRY)); + let cert = certificate(); + p!(ctx.add_certificate_authorities(&[cert])); + + let stream = p!(listener.accept()).0; + let mut stream = p!(ctx.handshake(stream)); + let mut buf = [0; 1]; + p!(stream.read(&mut buf)); + }); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_break_on_server_auth(true)); + let stream = p!(TcpStream::connect(("localhost", port))); + + let stream = match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Interrupted(stream)) => stream, + Err(err) => panic!("unexpected error {:?}", err), + }; + + let mut stream = p!(stream.handshake()); + p!(stream.write(&[0])); + + handle.join().unwrap(); + } + + #[test] + fn always_authenticate_no_cert() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + p!(ctx.set_client_side_authenticate(SslAuthenticate::ALWAYS)); + + let stream = p!(listener.accept()).0; + + match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Failure(_)) => {} + Err(err) => panic!("unexpected error {:?}", err), + } + }); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_break_on_server_auth(true)); + let stream = p!(TcpStream::connect(("localhost", port))); + + let stream = match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Interrupted(stream)) => stream, + Err(err) => panic!("unexpected error {:?}", err), + }; + + match stream.handshake() { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Failure(_)) => {} + Err(err) => panic!("unexpected error {:?}", err), + } + + handle.join().unwrap(); + } + + #[test] + fn always_authenticate_with_cert() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + p!(ctx.set_client_side_authenticate(SslAuthenticate::ALWAYS)); + + let stream = p!(listener.accept()).0; + + match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Failure(_)) => {} + Err(err) => panic!("unexpected error {:?}", err), + } + }); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_break_on_server_auth(true)); + let dir = p!(tempdir()); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + let stream = p!(TcpStream::connect(("localhost", port))); + + let stream = match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Interrupted(stream)) => stream, + Err(err) => panic!("unexpected error {:?}", err), + }; + + match stream.handshake() { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Failure(_)) => {} + Err(err) => panic!("unexpected error {:?}", err), + } + + handle.join().unwrap(); + } + + #[test] + fn certificate_authorities() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + assert!(p!(ctx.certificate_authorities()).is_none()); + p!(ctx.set_certificate_authorities(&[certificate()])); + assert_eq!(p!(ctx.certificate_authorities()).unwrap().len(), 1); + } + + #[test] + #[ignore] + fn close() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let identity = identity(dir.path()); + let builder = ServerBuilder::new(&identity, &[]); + + let stream = p!(listener.accept()).0; + let mut stream = p!(builder.handshake(stream)); + p!(stream.close()); + }); + + let stream = p!(TcpStream::connect(("localhost", port))); + let mut stream = p!(ClientBuilder::new() + .anchor_certificates(&[certificate()]) + .handshake("foobar.com", stream)); + + let mut buf = [0; 1]; + assert_eq!(p!(stream.read(&mut buf)), 0); + p!(stream.close()); + + p!(handle.join()); + } + + #[test] + #[ignore] + fn short_read() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let identity = identity(dir.path()); + let builder = ServerBuilder::new(&identity, &[]); + + let stream = p!(listener.accept()).0; + let mut stream = p!(builder.handshake(stream)); + + stream.write_all(b"hello").unwrap(); + // make sure stream doesn't close + stream + }); + + let stream = p!(TcpStream::connect(("localhost", port))); + let mut stream = p!(ClientBuilder::new() + .anchor_certificates(&[certificate()]) + .handshake("foobar.com", stream)); + + let mut b = [0; 1]; + stream.read_exact(&mut b).unwrap(); + assert_eq!(stream.context().buffered_read_size().unwrap(), 4); + let mut b = [0; 5]; + let read = stream.read(&mut b).unwrap(); + assert_eq!(read, 4); + + p!(handle.join()); + } +} diff --git a/vendor/security-framework/src/os/macos/transform.rs b/vendor/security-framework/src/os/macos/transform.rs new file mode 100644 index 000000000..d03bc1f76 --- /dev/null +++ b/vendor/security-framework/src/os/macos/transform.rs @@ -0,0 +1,54 @@ +//! Transform support + +use core_foundation::base::{CFType, TCFType}; +use core_foundation::error::CFError; +use core_foundation::string::CFString; +use security_framework_sys::transform::*; +use std::ptr; + +declare_TCFType! { + /// A type representing a transform. + SecTransform, SecTransformRef +} +impl_TCFType!(SecTransform, SecTransformRef, SecTransformGetTypeID); + +unsafe impl Sync for SecTransform {} +unsafe impl Send for SecTransform {} + +impl SecTransform { + /// Sets an attribute of the transform. + pub fn set_attribute<T>(&mut self, key: &CFString, value: &T) -> Result<(), CFError> + where + T: TCFType, + { + unsafe { + let mut error = ptr::null_mut(); + SecTransformSetAttribute( + self.0, + key.as_concrete_TypeRef(), + value.as_CFTypeRef(), + &mut error, + ); + if !error.is_null() { + return Err(CFError::wrap_under_create_rule(error)); + } + + Ok(()) + } + } + + /// Executes the transform. + /// + /// The return type depends on the type of transform. + pub fn execute(&mut self) -> Result<CFType, CFError> { + unsafe { + let mut error = ptr::null_mut(); + let result = SecTransformExecute(self.0, &mut error); + if result.is_null() { + return Err(CFError::wrap_under_create_rule(error)); + } + + Ok(CFType::wrap_under_create_rule(result)) + } + } +} diff --git a/vendor/security-framework/src/os/mod.rs b/vendor/security-framework/src/os/mod.rs new file mode 100644 index 000000000..ec7e38b34 --- /dev/null +++ b/vendor/security-framework/src/os/mod.rs @@ -0,0 +1,4 @@ +//! OS specific extensions. + +#[cfg(target_os = "macos")] +pub mod macos; diff --git a/vendor/security-framework/src/passwords.rs b/vendor/security-framework/src/passwords.rs new file mode 100644 index 000000000..83dad6d28 --- /dev/null +++ b/vendor/security-framework/src/passwords.rs @@ -0,0 +1,332 @@ +//! Support for password entries in the keychain. Works on both iOS and macOS. +//! +//! If you want the extended keychain facilities only available on macOS, use the +//! version of these functions in the macOS extensions module. + +use crate::base::Result; +use crate::passwords_options::PasswordOptions; +use crate::{cvt, Error}; +use core_foundation::base::TCFType; +use core_foundation::boolean::CFBoolean; +use core_foundation::data::CFData; +use core_foundation::dictionary::CFDictionary; +use core_foundation::string::CFString; +use core_foundation_sys::base::{CFGetTypeID, CFRelease, CFTypeRef}; +use core_foundation_sys::data::CFDataRef; +use security_framework_sys::base::{errSecDuplicateItem, errSecParam}; +use security_framework_sys::item::{kSecReturnData, kSecValueData}; +use security_framework_sys::keychain::{SecAuthenticationType, SecProtocolType}; +use security_framework_sys::keychain_item::{ + SecItemAdd, SecItemCopyMatching, SecItemDelete, SecItemUpdate, +}; + +/// Set a generic password for the given service and account. +/// Creates or updates a keychain entry. +pub fn set_generic_password(service: &str, account: &str, password: &[u8]) -> Result<()> { + let mut options = PasswordOptions::new_generic_password(service, account); + set_password_internal(&mut options, password) +} + +/// Get the generic password for the given service and account. If no matching +/// keychain entry exists, fails with error code `errSecItemNotFound`. +pub fn get_generic_password(service: &str, account: &str) -> Result<Vec<u8>> { + let mut options = PasswordOptions::new_generic_password(service, account); + options.query.push(( + unsafe { CFString::wrap_under_get_rule(kSecReturnData) }, + CFBoolean::from(true).into_CFType(), + )); + let params = CFDictionary::from_CFType_pairs(&options.query); + let mut ret: CFTypeRef = std::ptr::null(); + cvt(unsafe { SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret) })?; + get_password_and_release(ret) +} + +/// Delete the generic password keychain entry for the given service and account. +/// If none exists, fails with error code `errSecItemNotFound`. +pub fn delete_generic_password(service: &str, account: &str) -> Result<()> { + let options = PasswordOptions::new_generic_password(service, account); + let params = CFDictionary::from_CFType_pairs(&options.query); + cvt(unsafe { SecItemDelete(params.as_concrete_TypeRef()) }) +} + +/// Set an internet password for the given endpoint parameters. +/// Creates or updates a keychain entry. +#[allow(clippy::too_many_arguments)] +pub fn set_internet_password( + server: &str, + security_domain: Option<&str>, + account: &str, + path: &str, + port: Option<u16>, + protocol: SecProtocolType, + authentication_type: SecAuthenticationType, + password: &[u8], +) -> Result<()> { + let mut options = PasswordOptions::new_internet_password( + server, + security_domain, + account, + path, + port, + protocol, + authentication_type, + ); + set_password_internal(&mut options, password) +} + +/// Get the internet password for the given endpoint parameters. If no matching +/// keychain entry exists, fails with error code `errSecItemNotFound`. +pub fn get_internet_password( + server: &str, + security_domain: Option<&str>, + account: &str, + path: &str, + port: Option<u16>, + protocol: SecProtocolType, + authentication_type: SecAuthenticationType, +) -> Result<Vec<u8>> { + let mut options = PasswordOptions::new_internet_password( + server, + security_domain, + account, + path, + port, + protocol, + authentication_type, + ); + options.query.push(( + unsafe { CFString::wrap_under_get_rule(kSecReturnData) }, + CFBoolean::from(true).into_CFType(), + )); + let params = CFDictionary::from_CFType_pairs(&options.query); + let mut ret: CFTypeRef = std::ptr::null(); + cvt(unsafe { SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret) })?; + get_password_and_release(ret) +} + +/// Delete the internet password for the given endpoint parameters. +/// If none exists, fails with error code `errSecItemNotFound`. +pub fn delete_internet_password( + server: &str, + security_domain: Option<&str>, + account: &str, + path: &str, + port: Option<u16>, + protocol: SecProtocolType, + authentication_type: SecAuthenticationType, +) -> Result<()> { + let options = PasswordOptions::new_internet_password( + server, + security_domain, + account, + path, + port, + protocol, + authentication_type, + ); + let params = CFDictionary::from_CFType_pairs(&options.query); + cvt(unsafe { SecItemDelete(params.as_concrete_TypeRef()) }) +} + +// This starts by trying to create the password with the given query params. +// If the creation attempt reveals that one exists, its password is updated. +fn set_password_internal(options: &mut PasswordOptions, password: &[u8]) -> Result<()> { + let query_len = options.query.len(); + options.query.push(( + unsafe { CFString::wrap_under_get_rule(kSecValueData) }, + CFData::from_buffer(password).into_CFType(), + )); + + let params = CFDictionary::from_CFType_pairs(&options.query); + let mut ret = std::ptr::null(); + let status = unsafe { SecItemAdd(params.as_concrete_TypeRef(), &mut ret) }; + if status == errSecDuplicateItem { + let params = CFDictionary::from_CFType_pairs(&options.query[0..query_len]); + let update = CFDictionary::from_CFType_pairs(&options.query[query_len..]); + cvt(unsafe { SecItemUpdate(params.as_concrete_TypeRef(), update.as_concrete_TypeRef()) }) + } else { + cvt(status) + } +} + +// Having retrieved a password entry, this copies and returns the password. +// +// # Safety +// The data element passed in is assumed to have been returned from a Copy +// call, so it's released after we are done with it. +fn get_password_and_release(data: CFTypeRef) -> Result<Vec<u8>> { + if !data.is_null() { + let type_id = unsafe { CFGetTypeID(data) }; + if type_id == CFData::type_id() { + let val = unsafe { CFData::wrap_under_create_rule(data as CFDataRef) }; + let mut vec = Vec::new(); + vec.extend_from_slice(val.bytes()); + return Ok(vec); + } else { + // unexpected: we got a reference to some other type. + // Release it to make sure there's no leak, but + // we can't return the password in this case. + unsafe { CFRelease(data) }; + } + } + Err(Error::from_code(errSecParam)) +} + +#[cfg(test)] +mod test { + use super::*; + use security_framework_sys::base::errSecItemNotFound; + + #[test] + fn missing_generic() { + let name = "a string not likely to already be in the keychain as service or account"; + let result = delete_generic_password(name, name); + match result { + Ok(()) => (), // this is ok because the name _might_ be in the keychain + Err(err) if err.code() == errSecItemNotFound => (), + Err(err) => panic!("missing_generic: delete failed with status: {}", err.code()), + }; + let result = get_generic_password(name, name); + match result { + Ok(bytes) => panic!("missing_generic: get returned {:?}", bytes), + Err(err) if err.code() == errSecItemNotFound => (), + Err(err) => panic!("missing_generic: get failed with status: {}", err.code()), + }; + let result = delete_generic_password(name, name); + match result { + Ok(()) => panic!("missing_generic: second delete found a password"), + Err(err) if err.code() == errSecItemNotFound => (), + Err(err) => panic!("missing_generic: delete failed with status: {}", err.code()), + }; + } + + #[test] + fn roundtrip_generic() { + let name = "roundtrip_generic"; + set_generic_password(name, name, name.as_bytes()).expect("set_generic_password"); + let pass = get_generic_password(name, name).expect("get_generic_password"); + assert_eq!(name.as_bytes(), pass); + delete_generic_password(name, name).expect("delete_generic_password") + } + + #[test] + fn update_generic() { + let name = "update_generic"; + set_generic_password(name, name, name.as_bytes()).expect("set_generic_password"); + let alternate = "update_generic_alternate"; + set_generic_password(name, name, alternate.as_bytes()).expect("set_generic_password"); + let pass = get_generic_password(name, name).expect("get_generic_password"); + assert_eq!(pass, alternate.as_bytes()); + delete_generic_password(name, name).expect("delete_generic_password") + } + + #[test] + fn missing_internet() { + let name = "a string not likely to already be in the keychain as service or account"; + let (server, domain, account, path, port, protocol, auth) = ( + name, + None, + name, + "/", + Some(8080u16), + SecProtocolType::HTTP, + SecAuthenticationType::Any, + ); + let result = delete_internet_password(server, domain, account, path, port, protocol, auth); + match result { + Ok(()) => (), // this is ok because the name _might_ be in the keychain + Err(err) if err.code() == errSecItemNotFound => (), + Err(err) => panic!( + "missing_internet: delete failed with status: {}", + err.code() + ), + }; + let result = get_internet_password(server, domain, account, path, port, protocol, auth); + match result { + Ok(bytes) => panic!("missing_internet: get returned {:?}", bytes), + Err(err) if err.code() == errSecItemNotFound => (), + Err(err) => panic!("missing_internet: get failed with status: {}", err.code()), + }; + let result = delete_internet_password(server, domain, account, path, port, protocol, auth); + match result { + Ok(()) => panic!("missing_internet: second delete found a password"), + Err(err) if err.code() == errSecItemNotFound => (), + Err(err) => panic!( + "missing_internet: delete failed with status: {}", + err.code() + ), + }; + } + + #[test] + fn roundtrip_internet() { + let name = "roundtrip_internet"; + let (server, domain, account, path, port, protocol, auth) = ( + name, + None, + name, + "/", + Some(8080u16), + SecProtocolType::HTTP, + SecAuthenticationType::Any, + ); + set_internet_password( + server, + domain, + account, + path, + port, + protocol, + auth, + name.as_bytes(), + ) + .expect("set_internet_password"); + let pass = get_internet_password(server, domain, account, path, port, protocol, auth) + .expect("get_internet_password"); + assert_eq!(name.as_bytes(), pass); + delete_internet_password(server, domain, account, path, port, protocol, auth) + .expect("delete_internet_password"); + } + + #[test] + fn update_internet() { + let name = "update_internet"; + let (server, domain, account, path, port, protocol, auth) = ( + name, + None, + name, + "/", + Some(8080u16), + SecProtocolType::HTTP, + SecAuthenticationType::Any, + ); + set_internet_password( + server, + domain, + account, + path, + port, + protocol, + auth, + name.as_bytes(), + ) + .expect("set_internet_password"); + let alternate = "alternate_internet_password"; + set_internet_password( + server, + domain, + account, + path, + port, + protocol, + auth, + alternate.as_bytes(), + ) + .expect("set_internet_password"); + let pass = get_internet_password(server, domain, account, path, port, protocol, auth) + .expect("get_internet_password"); + assert_eq!(pass, alternate.as_bytes()); + delete_internet_password(server, domain, account, path, port, protocol, auth) + .expect("delete_internet_password"); + } +} diff --git a/vendor/security-framework/src/passwords_options.rs b/vendor/security-framework/src/passwords_options.rs new file mode 100644 index 000000000..d94bcf97d --- /dev/null +++ b/vendor/security-framework/src/passwords_options.rs @@ -0,0 +1,129 @@ +//! Support for password options, to be used with the passwords module + +use core_foundation::{string::CFString, base::{CFType, TCFType, CFOptionFlags}, number::CFNumber}; +use security_framework_sys::{keychain::{SecProtocolType, SecAuthenticationType}, access_control::*}; +use security_framework_sys::item::{ + kSecAttrAccessControl, kSecAttrAccount, kSecAttrAuthenticationType, kSecAttrPath, kSecAttrPort, kSecAttrProtocol, + kSecAttrSecurityDomain, kSecAttrServer, kSecAttrService, kSecClass, kSecClassGenericPassword, + kSecClassInternetPassword, +}; +use crate::access_control::SecAccessControl; + +/// `PasswordOptions` constructor +pub struct PasswordOptions { + /// query built for the keychain request + pub query: Vec<(CFString, CFType)>, +} + +bitflags::bitflags! { + /// The option flags used to configure the evaluation of a `SecAccessControl`. + pub struct AccessControlOptions: CFOptionFlags { + /** Constraint to access an item with either biometry or passcode. */ + const USER_PRESENCE = kSecAccessControlUserPresence; + #[cfg(feature = "OSX_10_13")] + /** Constraint to access an item with Touch ID for any enrolled fingers, or Face ID. */ + const BIOMETRY_ANY = kSecAccessControlBiometryAny; + #[cfg(feature = "OSX_10_13")] + /** Constraint to access an item with Touch ID for currently enrolled fingers, or from Face ID with the currently enrolled user. */ + const BIOMETRY_CURRENT_SET = kSecAccessControlBiometryCurrentSet; + /** Constraint to access an item with a passcode. */ + const DEVICE_PASSCODE = kSecAccessControlDevicePasscode; + #[cfg(feature = "OSX_10_15")] + /** Constraint to access an item with a watch. */ + const WATCH = kSecAccessControlWatch; + /** Indicates that at least one constraint must be satisfied. */ + const OR = kSecAccessControlOr; + /** Indicates that all constraints must be satisfied. */ + const AND = kSecAccessControlAnd; + /** Enable a private key to be used in signing a block of data or verifying a signed block. */ + const PRIVATE_KEY_USAGE = kSecAccessControlPrivateKeyUsage; + /** Option to use an application-provided password for data encryption key generation. */ + const APPLICATION_PASSWORD = kSecAccessControlApplicationPassword; + } +} + +impl PasswordOptions { + /// Create a new generic password options + /// Generic passwords are identified by service and account. They have other + /// attributes, but this interface doesn't allow specifying them. + #[must_use] pub fn new_generic_password(service: &str, account: &str) -> Self { + let query = vec![ + ( + unsafe { CFString::wrap_under_get_rule(kSecClass) }, + unsafe { CFString::wrap_under_get_rule(kSecClassGenericPassword).into_CFType() }, + ), + ( + unsafe { CFString::wrap_under_get_rule(kSecAttrService) }, + CFString::from(service).into_CFType(), + ), + ( + unsafe { CFString::wrap_under_get_rule(kSecAttrAccount) }, + CFString::from(account).into_CFType(), + ), + ]; + Self { query } + } + + /// Create a new internet password options + /// Internet passwords are identified by a number of attributes. + /// They can have others, but this interface doesn't allow specifying them. + #[must_use] pub fn new_internet_password( + server: &str, + security_domain: Option<&str>, + account: &str, + path: &str, + port: Option<u16>, + protocol: SecProtocolType, + authentication_type: SecAuthenticationType, + ) -> Self { + let mut query = vec![ + ( + unsafe { CFString::wrap_under_get_rule(kSecClass) }, + unsafe { CFString::wrap_under_get_rule(kSecClassInternetPassword) }.into_CFType(), + ), + ( + unsafe { CFString::wrap_under_get_rule(kSecAttrServer) }, + CFString::from(server).into_CFType(), + ), + ( + unsafe { CFString::wrap_under_get_rule(kSecAttrPath) }, + CFString::from(path).into_CFType(), + ), + ( + unsafe { CFString::wrap_under_get_rule(kSecAttrAccount) }, + CFString::from(account).into_CFType(), + ), + ( + unsafe { CFString::wrap_under_get_rule(kSecAttrProtocol) }, + CFNumber::from(protocol as i32).into_CFType(), + ), + ( + unsafe { CFString::wrap_under_get_rule(kSecAttrAuthenticationType) }, + CFNumber::from(authentication_type as i32).into_CFType(), + ), + ]; + if let Some(domain) = security_domain { + query.push(( + unsafe { CFString::wrap_under_get_rule(kSecAttrSecurityDomain) }, + CFString::from(domain).into_CFType(), + )) + } + if let Some(port) = port { + query.push(( + unsafe { CFString::wrap_under_get_rule(kSecAttrPort) }, + CFNumber::from(i32::from(port)).into_CFType(), + )) + } + Self { query } + } + + /// Add access control to the password + pub fn set_access_control_options(&mut self, options: AccessControlOptions) { + self.query.push(( + unsafe { CFString::wrap_under_get_rule(kSecAttrAccessControl) }, + SecAccessControl::create_with_flags(options.bits()) + .unwrap() + .into_CFType(), + )) + } +} diff --git a/vendor/security-framework/src/policy.rs b/vendor/security-framework/src/policy.rs new file mode 100644 index 000000000..85f361cb0 --- /dev/null +++ b/vendor/security-framework/src/policy.rs @@ -0,0 +1,104 @@ +//! Security Policies support. +#[cfg(any(feature = "OSX_10_9", target_os = "ios"))] +use core_foundation::base::CFOptionFlags; +use core_foundation::base::TCFType; +use core_foundation::string::CFString; +#[cfg(any(feature = "OSX_10_9", target_os = "ios"))] +use security_framework_sys::base::errSecParam; +use security_framework_sys::base::SecPolicyRef; +use security_framework_sys::policy::*; +use std::fmt; +use std::ptr; + +use crate::secure_transport::SslProtocolSide; +#[cfg(any(feature = "OSX_10_9", target_os = "ios"))] +use crate::Error; + +declare_TCFType! { + /// A type representing a certificate validation policy. + SecPolicy, SecPolicyRef +} +impl_TCFType!(SecPolicy, SecPolicyRef, SecPolicyGetTypeID); + +unsafe impl Sync for SecPolicy {} +unsafe impl Send for SecPolicy {} + +impl fmt::Debug for SecPolicy { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("SecPolicy").finish() + } +} + +#[cfg(any(feature = "OSX_10_9", target_os = "ios"))] +bitflags::bitflags! { + /// The flags used to specify revocation policy options. + pub struct RevocationPolicy: CFOptionFlags { + /// Perform revocation checking using OCSP (Online Certificate Status Protocol). + const OCSP_METHOD = kSecRevocationOCSPMethod; + /// Perform revocation checking using the CRL (Certification Revocation List) method. + const CRL_METHOD = kSecRevocationCRLMethod; + /// Prefer CRL revocation checking over OCSP; by default, OCSP is preferred. + const PREFER_CRL = kSecRevocationPreferCRL; + /// Require a positive response to pass the policy. + const REQUIRE_POSITIVE_RESPONSE = kSecRevocationRequirePositiveResponse; + /// Consult only locally cached replies; do not use network access. + const NETWORK_ACCESS_DISABLED = kSecRevocationNetworkAccessDisabled; + /// Perform either OCSP or CRL checking. + const USE_ANY_METHOD_AVAILABLE = kSecRevocationUseAnyAvailableMethod; + } +} + +impl SecPolicy { + /// Creates a `SecPolicy` for evaluating SSL certificate chains. + /// + /// The side which you are evaluating should be provided (i.e. pass `SslSslProtocolSide::SERVER` if + /// you are a client looking to validate a server's certificate chain). + pub fn create_ssl(protocol_side: SslProtocolSide, hostname: Option<&str>) -> Self { + let hostname = hostname.map(CFString::new); + let hostname = hostname + .as_ref() + .map(|s| s.as_concrete_TypeRef()) + .unwrap_or(ptr::null_mut()); + let is_server = protocol_side == SslProtocolSide::SERVER; + unsafe { + let policy = SecPolicyCreateSSL(is_server as _, hostname); + Self::wrap_under_create_rule(policy) + } + } + + #[cfg(any(feature = "OSX_10_9", target_os = "ios"))] + /// Creates a `SecPolicy` for checking revocation of certificates. + /// + /// If you do not specify this policy creating a `SecTrust` object, the system defaults + /// will be used during evaluation. + pub fn create_revocation(options: RevocationPolicy) -> crate::Result<Self> { + let policy = unsafe { SecPolicyCreateRevocation(options.bits()) }; + + if policy.is_null() { + Err(Error::from_code(errSecParam)) + } else { + Ok(unsafe { Self::wrap_under_create_rule(policy) }) + } + } + + /// Returns a policy object for the default X.509 policy. + #[must_use] + pub fn create_x509() -> Self { + unsafe { + let policy = SecPolicyCreateBasicX509(); + Self::wrap_under_create_rule(policy) + } + } +} + +#[cfg(test)] +mod test { + use crate::policy::SecPolicy; + use crate::secure_transport::SslProtocolSide; + + #[test] + fn create_ssl() { + SecPolicy::create_ssl(SslProtocolSide::SERVER, Some("certifi.org")); + } +} diff --git a/vendor/security-framework/src/random.rs b/vendor/security-framework/src/random.rs new file mode 100644 index 000000000..7bd7f6141 --- /dev/null +++ b/vendor/security-framework/src/random.rs @@ -0,0 +1,39 @@ +//! Randomness support. + +use security_framework_sys::random::{SecRandomCopyBytes, SecRandomRef, kSecRandomDefault}; +use std::io; + +/// A source of random data. +pub struct SecRandom(SecRandomRef); + +unsafe impl Sync for SecRandom {} +unsafe impl Send for SecRandom {} + +impl Default for SecRandom { + #[inline(always)] + fn default() -> Self { + unsafe { Self(kSecRandomDefault) } + } +} + +impl SecRandom { + /// Fills the buffer with cryptographically secure random bytes. + pub fn copy_bytes(&self, buf: &mut [u8]) -> io::Result<()> { + if unsafe { SecRandomCopyBytes(self.0, buf.len(), buf.as_mut_ptr().cast()) } == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn basic() { + let mut buf = [0; 10]; + SecRandom::default().copy_bytes(&mut buf).unwrap(); + } +} diff --git a/vendor/security-framework/src/secure_transport.rs b/vendor/security-framework/src/secure_transport.rs new file mode 100644 index 000000000..18ce794e7 --- /dev/null +++ b/vendor/security-framework/src/secure_transport.rs @@ -0,0 +1,1848 @@ +//! SSL/TLS encryption support using Secure Transport. +//! +//! # Examples +//! +//! To connect as a client to a server with a certificate trusted by the system: +//! +//! ```rust +//! use std::io::prelude::*; +//! use std::net::TcpStream; +//! use security_framework::secure_transport::ClientBuilder; +//! +//! let stream = TcpStream::connect("google.com:443").unwrap(); +//! let mut stream = ClientBuilder::new().handshake("google.com", stream).unwrap(); +//! +//! stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); +//! let mut page = vec![]; +//! stream.read_to_end(&mut page).unwrap(); +//! println!("{}", String::from_utf8_lossy(&page)); +//! ``` +//! +//! To connect to a server with a certificate that's *not* trusted by the +//! system, specify the root certificates for the server's chain to the +//! `ClientBuilder`: +//! +//! ```rust,no_run +//! use std::io::prelude::*; +//! use std::net::TcpStream; +//! use security_framework::secure_transport::ClientBuilder; +//! +//! # let root_cert = unsafe { std::mem::zeroed() }; +//! let stream = TcpStream::connect("my_server.com:443").unwrap(); +//! let mut stream = ClientBuilder::new() +//! .anchor_certificates(&[root_cert]) +//! .handshake("my_server.com", stream) +//! .unwrap(); +//! +//! stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); +//! let mut page = vec![]; +//! stream.read_to_end(&mut page).unwrap(); +//! println!("{}", String::from_utf8_lossy(&page)); +//! ``` +//! +//! For more advanced configuration, the `SslContext` type can be used directly. +//! +//! To run a server: +//! +//! ```rust,no_run +//! use std::net::TcpListener; +//! use std::thread; +//! use security_framework::secure_transport::{SslContext, SslProtocolSide, SslConnectionType}; +//! +//! // Create a TCP listener and start accepting on it. +//! let mut listener = TcpListener::bind("0.0.0.0:443").unwrap(); +//! +//! for stream in listener.incoming() { +//! let stream = stream.unwrap(); +//! thread::spawn(move || { +//! // Create a new context configured to operate on the server side of +//! // a traditional SSL/TLS session. +//! let mut ctx = SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM) +//! .unwrap(); +//! +//! // Install the certificate chain that we will be using. +//! # let identity = unsafe { std::mem::zeroed() }; +//! # let intermediate_cert = unsafe { std::mem::zeroed() }; +//! # let root_cert = unsafe { std::mem::zeroed() }; +//! ctx.set_certificate(identity, &[intermediate_cert, root_cert]).unwrap(); +//! +//! // Perform the SSL/TLS handshake and get our stream. +//! let mut stream = ctx.handshake(stream).unwrap(); +//! }); +//! } +//! +//! ``` +#[allow(unused_imports)] +use core_foundation::array::{CFArray, CFArrayRef}; + +use core_foundation::base::{Boolean, TCFType}; +#[cfg(feature = "alpn")] +use core_foundation::string::CFString; +use core_foundation_sys::base::{kCFAllocatorDefault, OSStatus}; +use std::os::raw::c_void; + +#[allow(unused_imports)] +use security_framework_sys::base::{ + errSecBadReq, errSecIO, errSecNotTrusted, errSecSuccess, errSecTrustSettingDeny, + errSecUnimplemented, +}; + +use security_framework_sys::secure_transport::*; +use std::any::Any; +use std::cmp; +use std::fmt; +use std::io; +use std::io::prelude::*; +use std::marker::PhantomData; +use std::panic::{self, AssertUnwindSafe}; +use std::ptr; +use std::result; +use std::slice; + +use crate::base::{Error, Result}; +use crate::certificate::SecCertificate; +use crate::cipher_suite::CipherSuite; +use crate::identity::SecIdentity; +use crate::import_export::Pkcs12ImportOptions; +use crate::policy::SecPolicy; +use crate::trust::SecTrust; +use crate::{cvt, AsInner}; +use security_framework_sys::base::errSecParam; + +/// Specifies a side of a TLS session. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct SslProtocolSide(SSLProtocolSide); + +impl SslProtocolSide { + /// The server side of the session. + pub const SERVER: Self = Self(kSSLServerSide); + + /// The client side of the session. + pub const CLIENT: Self = Self(kSSLClientSide); +} + +/// Specifies the type of TLS session. +#[derive(Debug, Copy, Clone)] +pub struct SslConnectionType(SSLConnectionType); + +impl SslConnectionType { + /// A traditional TLS stream. + pub const STREAM: Self = Self(kSSLStreamType); + + /// A DTLS session. + pub const DATAGRAM: Self = Self(kSSLDatagramType); +} + +/// An error or intermediate state after a TLS handshake attempt. +#[derive(Debug)] +pub enum HandshakeError<S> { + /// The handshake failed. + Failure(Error), + /// The handshake was interrupted midway through. + Interrupted(MidHandshakeSslStream<S>), +} + +impl<S> From<Error> for HandshakeError<S> { + #[inline(always)] + fn from(err: Error) -> Self { + Self::Failure(err) + } +} + +/// An error or intermediate state after a TLS handshake attempt. +#[derive(Debug)] +pub enum ClientHandshakeError<S> { + /// The handshake failed. + Failure(Error), + /// The handshake was interrupted midway through. + Interrupted(MidHandshakeClientBuilder<S>), +} + +impl<S> From<Error> for ClientHandshakeError<S> { + #[inline(always)] + fn from(err: Error) -> Self { + Self::Failure(err) + } +} + +/// An SSL stream midway through the handshake process. +#[derive(Debug)] +pub struct MidHandshakeSslStream<S> { + stream: SslStream<S>, + error: Error, +} + +impl<S> MidHandshakeSslStream<S> { + /// Returns a shared reference to the inner stream. + #[inline(always)] + #[must_use] + pub fn get_ref(&self) -> &S { + self.stream.get_ref() + } + + /// Returns a mutable reference to the inner stream. + #[inline(always)] + pub fn get_mut(&mut self) -> &mut S { + self.stream.get_mut() + } + + /// Returns a shared reference to the `SslContext` of the stream. + #[inline(always)] + #[must_use] + pub fn context(&self) -> &SslContext { + self.stream.context() + } + + /// Returns a mutable reference to the `SslContext` of the stream. + #[inline(always)] + pub fn context_mut(&mut self) -> &mut SslContext { + self.stream.context_mut() + } + + /// Returns `true` iff `break_on_server_auth` was set and the handshake has + /// progressed to that point. + #[inline(always)] + #[must_use] + pub fn server_auth_completed(&self) -> bool { + self.error.code() == errSSLPeerAuthCompleted + } + + /// Returns `true` iff `break_on_cert_requested` was set and the handshake + /// has progressed to that point. + #[inline(always)] + #[must_use] + pub fn client_cert_requested(&self) -> bool { + self.error.code() == errSSLClientCertRequested + } + + /// Returns `true` iff the underlying stream returned an error with the + /// `WouldBlock` kind. + #[inline(always)] + #[must_use] + pub fn would_block(&self) -> bool { + self.error.code() == errSSLWouldBlock + } + + /// Returns the error which caused the handshake interruption. + #[inline(always)] + #[must_use] + pub fn error(&self) -> &Error { + &self.error + } + + /// Restarts the handshake process. + #[inline(always)] + pub fn handshake(self) -> result::Result<SslStream<S>, HandshakeError<S>> { + self.stream.handshake() + } +} + +/// An SSL stream midway through the handshake process. +#[derive(Debug)] +pub struct MidHandshakeClientBuilder<S> { + stream: MidHandshakeSslStream<S>, + domain: Option<String>, + certs: Vec<SecCertificate>, + trust_certs_only: bool, + danger_accept_invalid_certs: bool, +} + +impl<S> MidHandshakeClientBuilder<S> { + /// Returns a shared reference to the inner stream. + #[inline(always)] + #[must_use] + pub fn get_ref(&self) -> &S { + self.stream.get_ref() + } + + /// Returns a mutable reference to the inner stream. + #[inline(always)] + pub fn get_mut(&mut self) -> &mut S { + self.stream.get_mut() + } + + /// Returns the error which caused the handshake interruption. + #[inline(always)] + #[must_use] + pub fn error(&self) -> &Error { + self.stream.error() + } + + /// Restarts the handshake process. + pub fn handshake(self) -> result::Result<SslStream<S>, ClientHandshakeError<S>> { + let MidHandshakeClientBuilder { + stream, + domain, + certs, + trust_certs_only, + danger_accept_invalid_certs, + } = self; + + let mut result = stream.handshake(); + loop { + let stream = match result { + Ok(stream) => return Ok(stream), + Err(HandshakeError::Interrupted(stream)) => stream, + Err(HandshakeError::Failure(err)) => { + return Err(ClientHandshakeError::Failure(err)) + } + }; + + if stream.would_block() { + let ret = MidHandshakeClientBuilder { + stream, + domain, + certs, + trust_certs_only, + danger_accept_invalid_certs, + }; + return Err(ClientHandshakeError::Interrupted(ret)); + } + + if stream.server_auth_completed() { + if danger_accept_invalid_certs { + result = stream.handshake(); + continue; + } + let mut trust = match stream.context().peer_trust2()? { + Some(trust) => trust, + None => { + result = stream.handshake(); + continue; + } + }; + trust.set_anchor_certificates(&certs)?; + trust.set_trust_anchor_certificates_only(self.trust_certs_only)?; + let policy = SecPolicy::create_ssl(SslProtocolSide::SERVER, domain.as_deref()); + trust.set_policy(&policy)?; + trust.evaluate_with_error().map_err(|error| { + #[cfg(feature = "log")] + log::warn!("SecTrustEvaluateWithError: {}", error.to_string()); + Error::from_code(error.code() as _) + })?; + result = stream.handshake(); + continue; + } + + let err = Error::from_code(stream.error().code()); + return Err(ClientHandshakeError::Failure(err)); + } + } +} + +/// Specifies the state of a TLS session. +#[derive(Debug, PartialEq, Eq)] +pub struct SessionState(SSLSessionState); + +impl SessionState { + /// The session has not yet started. + pub const IDLE: Self = Self(kSSLIdle); + + /// The session is in the handshake process. + pub const HANDSHAKE: Self = Self(kSSLHandshake); + + /// The session is connected. + pub const CONNECTED: Self = Self(kSSLConnected); + + /// The session has been terminated. + pub const CLOSED: Self = Self(kSSLClosed); + + /// The session has been aborted due to an error. + pub const ABORTED: Self = Self(kSSLAborted); +} + +/// Specifies a server's requirement for client certificates. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct SslAuthenticate(SSLAuthenticate); + +impl SslAuthenticate { + /// Do not request a client certificate. + pub const NEVER: Self = Self(kNeverAuthenticate); + + /// Require a client certificate. + pub const ALWAYS: Self = Self(kAlwaysAuthenticate); + + /// Request but do not require a client certificate. + pub const TRY: Self = Self(kTryAuthenticate); +} + +/// Specifies the state of client certificate processing. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct SslClientCertificateState(SSLClientCertificateState); + +impl SslClientCertificateState { + /// A client certificate has not been requested or sent. + pub const NONE: Self = Self(kSSLClientCertNone); + + /// A client certificate has been requested but not recieved. + pub const REQUESTED: Self = Self(kSSLClientCertRequested); + /// A client certificate has been received and successfully validated. + pub const SENT: Self = Self(kSSLClientCertSent); + + /// A client certificate has been received but has failed to validate. + pub const REJECTED: Self = Self(kSSLClientCertRejected); +} + +/// Specifies protocol versions. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct SslProtocol(SSLProtocol); + +impl SslProtocol { + /// No protocol has been or should be negotiated or specified; use the default. + pub const UNKNOWN: Self = Self(kSSLProtocolUnknown); + + /// The SSL 3.0 protocol is preferred, though SSL 2.0 may be used if the peer does not support + /// SSL 3.0. + pub const SSL3: Self = Self(kSSLProtocol3); + + /// The TLS 1.0 protocol is preferred, though lower versions may be used + /// if the peer does not support TLS 1.0. + pub const TLS1: Self = Self(kTLSProtocol1); + + /// The TLS 1.1 protocol is preferred, though lower versions may be used + /// if the peer does not support TLS 1.1. + pub const TLS11: Self = Self(kTLSProtocol11); + + /// The TLS 1.2 protocol is preferred, though lower versions may be used + /// if the peer does not support TLS 1.2. + pub const TLS12: Self = Self(kTLSProtocol12); + + /// The TLS 1.3 protocol is preferred, though lower versions may be used + /// if the peer does not support TLS 1.3. + pub const TLS13: Self = Self(kTLSProtocol13); + + /// Only the SSL 2.0 protocol is accepted. + pub const SSL2: Self = Self(kSSLProtocol2); + + /// The `DTLSv1` protocol is preferred. + pub const DTLS1: Self = Self(kDTLSProtocol1); + + /// Only the SSL 3.0 protocol is accepted. + pub const SSL3_ONLY: Self = Self(kSSLProtocol3Only); + + /// Only the TLS 1.0 protocol is accepted. + pub const TLS1_ONLY: Self = Self(kTLSProtocol1Only); + + /// All supported TLS/SSL versions are accepted. + pub const ALL: Self = Self(kSSLProtocolAll); +} + +declare_TCFType! { + /// A Secure Transport SSL/TLS context object. + SslContext, SSLContextRef +} + +impl_TCFType!(SslContext, SSLContextRef, SSLContextGetTypeID); + +impl fmt::Debug for SslContext { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = fmt.debug_struct("SslContext"); + if let Ok(state) = self.state() { + builder.field("state", &state); + } + builder.finish() + } +} + +unsafe impl Sync for SslContext {} +unsafe impl Send for SslContext {} + +impl AsInner for SslContext { + type Inner = SSLContextRef; + + #[inline(always)] + fn as_inner(&self) -> SSLContextRef { + self.0 + } +} + +macro_rules! impl_options { + ($($(#[$a:meta])* const $opt:ident: $get:ident & $set:ident,)*) => { + $( + $(#[$a])* + #[inline(always)] + pub fn $set(&mut self, value: bool) -> Result<()> { + unsafe { cvt(SSLSetSessionOption(self.0, $opt, value as Boolean)) } + } + + $(#[$a])* + #[inline] + pub fn $get(&self) -> Result<bool> { + let mut value = 0; + unsafe { cvt(SSLGetSessionOption(self.0, $opt, &mut value))?; } + Ok(value != 0) + } + )* + } +} + +impl SslContext { + /// Creates a new `SslContext` for the specified side and type of SSL + /// connection. + #[inline] + pub fn new(side: SslProtocolSide, type_: SslConnectionType) -> Result<Self> { + unsafe { + let ctx = SSLCreateContext(kCFAllocatorDefault, side.0, type_.0); + Ok(Self(ctx)) + } + } + + /// Sets the fully qualified domain name of the peer. + /// + /// This will be used on the client side of a session to validate the + /// common name field of the server's certificate. It has no effect if + /// called on a server-side `SslContext`. + /// + /// It is *highly* recommended to call this method before starting the + /// handshake process. + #[inline] + pub fn set_peer_domain_name(&mut self, peer_name: &str) -> Result<()> { + unsafe { + // SSLSetPeerDomainName doesn't need a null terminated string + cvt(SSLSetPeerDomainName( + self.0, + peer_name.as_ptr().cast(), + peer_name.len(), + )) + } + } + + /// Returns the peer domain name set by `set_peer_domain_name`. + pub fn peer_domain_name(&self) -> Result<String> { + unsafe { + let mut len = 0; + cvt(SSLGetPeerDomainNameLength(self.0, &mut len))?; + let mut buf = vec![0; len]; + cvt(SSLGetPeerDomainName( + self.0, + buf.as_mut_ptr().cast(), + &mut len, + ))?; + Ok(String::from_utf8(buf).unwrap()) + } + } + + /// Sets the certificate to be used by this side of the SSL session. + /// + /// This must be called before the handshake for server-side connections, + /// and can be used on the client-side to specify a client certificate. + /// + /// The `identity` corresponds to the leaf certificate and private + /// key, and the `certs` correspond to extra certificates in the chain. + pub fn set_certificate( + &mut self, + identity: &SecIdentity, + certs: &[SecCertificate], + ) -> Result<()> { + let mut arr = vec![identity.as_CFType()]; + arr.extend(certs.iter().map(|c| c.as_CFType())); + let certs = CFArray::from_CFTypes(&arr); + + unsafe { cvt(SSLSetCertificate(self.0, certs.as_concrete_TypeRef())) } + } + + /// Sets the peer ID of this session. + /// + /// A peer ID is an opaque sequence of bytes that will be used by Secure + /// Transport to identify the peer of an SSL session. If the peer ID of + /// this session matches that of a previously terminated session, the + /// previous session can be resumed without requiring a full handshake. + #[inline] + pub fn set_peer_id(&mut self, peer_id: &[u8]) -> Result<()> { + unsafe { cvt(SSLSetPeerID(self.0, peer_id.as_ptr().cast(), peer_id.len())) } + } + + /// Returns the peer ID of this session. + pub fn peer_id(&self) -> Result<Option<&[u8]>> { + unsafe { + let mut ptr = ptr::null(); + let mut len = 0; + cvt(SSLGetPeerID(self.0, &mut ptr, &mut len))?; + if ptr.is_null() { + Ok(None) + } else { + Ok(Some(slice::from_raw_parts(ptr.cast(), len))) + } + } + } + + /// Returns the list of ciphers that are supported by Secure Transport. + pub fn supported_ciphers(&self) -> Result<Vec<CipherSuite>> { + unsafe { + let mut num_ciphers = 0; + cvt(SSLGetNumberSupportedCiphers(self.0, &mut num_ciphers))?; + let mut ciphers = vec![0; num_ciphers]; + cvt(SSLGetSupportedCiphers( + self.0, + ciphers.as_mut_ptr(), + &mut num_ciphers, + ))?; + Ok(ciphers.iter().map(|c| CipherSuite::from_raw(*c)).collect()) + } + } + + /// Returns the list of ciphers that are eligible to be used for + /// negotiation. + pub fn enabled_ciphers(&self) -> Result<Vec<CipherSuite>> { + unsafe { + let mut num_ciphers = 0; + cvt(SSLGetNumberEnabledCiphers(self.0, &mut num_ciphers))?; + let mut ciphers = vec![0; num_ciphers]; + cvt(SSLGetEnabledCiphers( + self.0, + ciphers.as_mut_ptr(), + &mut num_ciphers, + ))?; + Ok(ciphers.iter().map(|c| CipherSuite::from_raw(*c)).collect()) + } + } + + /// Sets the list of ciphers that are eligible to be used for negotiation. + pub fn set_enabled_ciphers(&mut self, ciphers: &[CipherSuite]) -> Result<()> { + let ciphers = ciphers.iter().map(|c| c.to_raw()).collect::<Vec<_>>(); + unsafe { + cvt(SSLSetEnabledCiphers( + self.0, + ciphers.as_ptr(), + ciphers.len(), + )) + } + } + + /// Returns the cipher being used by the session. + #[inline] + pub fn negotiated_cipher(&self) -> Result<CipherSuite> { + unsafe { + let mut cipher = 0; + cvt(SSLGetNegotiatedCipher(self.0, &mut cipher))?; + Ok(CipherSuite::from_raw(cipher)) + } + } + + /// Sets the requirements for client certificates. + /// + /// Should only be called on server-side sessions. + #[inline] + pub fn set_client_side_authenticate(&mut self, auth: SslAuthenticate) -> Result<()> { + unsafe { cvt(SSLSetClientSideAuthenticate(self.0, auth.0)) } + } + + /// Returns the state of client certificate processing. + #[inline] + pub fn client_certificate_state(&self) -> Result<SslClientCertificateState> { + let mut state = 0; + + unsafe { + cvt(SSLGetClientCertificateState(self.0, &mut state))?; + } + Ok(SslClientCertificateState(state)) + } + + /// Returns the `SecTrust` object corresponding to the peer. + /// + /// This can be used in conjunction with `set_break_on_server_auth` to + /// validate certificates which do not have roots in the default set. + pub fn peer_trust2(&self) -> Result<Option<SecTrust>> { + // Calling SSLCopyPeerTrust on an idle connection does not seem to be well defined, + // so explicitly check for that + if self.state()? == SessionState::IDLE { + return Err(Error::from_code(errSecBadReq)); + } + + unsafe { + let mut trust = ptr::null_mut(); + cvt(SSLCopyPeerTrust(self.0, &mut trust))?; + if trust.is_null() { + Ok(None) + } else { + Ok(Some(SecTrust::wrap_under_create_rule(trust))) + } + } + } + + /// Returns the state of the session. + #[inline] + pub fn state(&self) -> Result<SessionState> { + unsafe { + let mut state = 0; + cvt(SSLGetSessionState(self.0, &mut state))?; + Ok(SessionState(state)) + } + } + + /// Returns the protocol version being used by the session. + #[inline] + pub fn negotiated_protocol_version(&self) -> Result<SslProtocol> { + unsafe { + let mut version = 0; + cvt(SSLGetNegotiatedProtocolVersion(self.0, &mut version))?; + Ok(SslProtocol(version)) + } + } + + /// Returns the maximum protocol version allowed by the session. + #[inline] + pub fn protocol_version_max(&self) -> Result<SslProtocol> { + unsafe { + let mut version = 0; + cvt(SSLGetProtocolVersionMax(self.0, &mut version))?; + Ok(SslProtocol(version)) + } + } + + /// Sets the maximum protocol version allowed by the session. + #[inline] + pub fn set_protocol_version_max(&mut self, max_version: SslProtocol) -> Result<()> { + unsafe { cvt(SSLSetProtocolVersionMax(self.0, max_version.0)) } + } + + /// Returns the minimum protocol version allowed by the session. + #[inline] + pub fn protocol_version_min(&self) -> Result<SslProtocol> { + unsafe { + let mut version = 0; + cvt(SSLGetProtocolVersionMin(self.0, &mut version))?; + Ok(SslProtocol(version)) + } + } + + /// Sets the minimum protocol version allowed by the session. + #[inline] + pub fn set_protocol_version_min(&mut self, min_version: SslProtocol) -> Result<()> { + unsafe { cvt(SSLSetProtocolVersionMin(self.0, min_version.0)) } + } + + /// Returns the set of protocols selected via ALPN if it succeeded. + #[cfg(feature = "alpn")] + pub fn alpn_protocols(&self) -> Result<Vec<String>> { + let mut array: CFArrayRef = ptr::null(); + unsafe { + #[cfg(feature = "OSX_10_13")] + { + cvt(SSLCopyALPNProtocols(self.0, &mut array))?; + } + + #[cfg(not(feature = "OSX_10_13"))] + { + dlsym! { fn SSLCopyALPNProtocols(SSLContextRef, *mut CFArrayRef) -> OSStatus } + if let Some(f) = SSLCopyALPNProtocols.get() { + cvt(f(self.0, &mut array))?; + } else { + return Err(Error::from_code(errSecUnimplemented)); + } + } + + if array.is_null() { + return Ok(vec![]); + } + + let array = CFArray::<CFString>::wrap_under_create_rule(array); + Ok(array.into_iter().map(|p| p.to_string()).collect()) + } + } + + /// Configures the set of protocols use for ALPN. + /// + /// This is only used for client-side connections. + #[cfg(feature = "alpn")] + pub fn set_alpn_protocols(&mut self, protocols: &[&str]) -> Result<()> { + // When CFMutableArray is added to core-foundation and IntoIterator trait + // is implemented for CFMutableArray, the code below should directly collect + // into a CFMutableArray. + let protocols = CFArray::from_CFTypes( + &protocols + .iter() + .map(|proto| CFString::new(proto)) + .collect::<Vec<_>>(), + ); + + #[cfg(feature = "OSX_10_13")] + { + unsafe { cvt(SSLSetALPNProtocols(self.0, protocols.as_concrete_TypeRef())) } + } + #[cfg(not(feature = "OSX_10_13"))] + { + dlsym! { fn SSLSetALPNProtocols(SSLContextRef, CFArrayRef) -> OSStatus } + if let Some(f) = SSLSetALPNProtocols.get() { + unsafe { cvt(f(self.0, protocols.as_concrete_TypeRef())) } + } else { + Err(Error::from_code(errSecUnimplemented)) + } + } + } + + /// Sets whether the client sends the `SessionTicket` extension in its `ClientHello`. + /// + /// On its own, this will just cause the client to send an empty `SessionTicket` extension on + /// every connection. [`SslContext::set_peer_id`] must also be used to key the session + /// ticket returned by the server. + /// + /// [`SslContext::set_peer_id`]: #method.set_peer_id + #[cfg(feature = "session-tickets")] + pub fn set_session_tickets_enabled(&mut self, enabled: bool) -> Result<()> { + #[cfg(feature = "OSX_10_13")] + { + unsafe { cvt(SSLSetSessionTicketsEnabled(self.0, enabled as Boolean)) } + } + #[cfg(not(feature = "OSX_10_13"))] + { + dlsym! { fn SSLSetSessionTicketsEnabled(SSLContextRef, Boolean) -> OSStatus } + if let Some(f) = SSLSetSessionTicketsEnabled.get() { + unsafe { cvt(f(self.0, enabled as Boolean)) } + } else { + Err(Error::from_code(errSecUnimplemented)) + } + } + } + + /// Sets whether a protocol is enabled or not. + /// + /// # Note + /// + /// On OSX this is a deprecated API in favor of `set_protocol_version_max` and + /// `set_protocol_version_min`, although if you're working with OSX 10.8 or before you may have + /// to use this API instead. + #[cfg(target_os = "macos")] + #[deprecated(note = "use `set_protocol_version_max`")] + pub fn set_protocol_version_enabled( + &mut self, + protocol: SslProtocol, + enabled: bool, + ) -> Result<()> { + unsafe { + cvt(SSLSetProtocolVersionEnabled( + self.0, + protocol.0, + enabled as Boolean, + )) + } + } + + /// Returns the number of bytes which can be read without triggering a + /// `read` call in the underlying stream. + #[inline] + pub fn buffered_read_size(&self) -> Result<usize> { + unsafe { + let mut size = 0; + cvt(SSLGetBufferedReadSize(self.0, &mut size))?; + Ok(size) + } + } + + impl_options! { + /// If enabled, the handshake process will pause and return instead of + /// automatically validating a server's certificate. + const kSSLSessionOptionBreakOnServerAuth: break_on_server_auth & set_break_on_server_auth, + /// If enabled, the handshake process will pause and return after + /// the server requests a certificate from the client. + const kSSLSessionOptionBreakOnCertRequested: break_on_cert_requested & set_break_on_cert_requested, + /// If enabled, the handshake process will pause and return instead of + /// automatically validating a client's certificate. + const kSSLSessionOptionBreakOnClientAuth: break_on_client_auth & set_break_on_client_auth, + /// If enabled, TLS false start will be performed if an appropriate + /// cipher suite is negotiated. + /// + /// Requires the `OSX_10_9` (or greater) feature. + #[cfg(feature = "OSX_10_9")] + const kSSLSessionOptionFalseStart: false_start & set_false_start, + /// If enabled, 1/n-1 record splitting will be enabled for TLS 1.0 + /// connections using block ciphers to mitigate the BEAST attack. + /// + /// Requires the `OSX_10_9` (or greater) feature. + #[cfg(feature = "OSX_10_9")] + const kSSLSessionOptionSendOneByteRecord: send_one_byte_record & set_send_one_byte_record, + } + + fn into_stream<S>(self, stream: S) -> Result<SslStream<S>> + where + S: Read + Write, + { + unsafe { + let ret = SSLSetIOFuncs(self.0, read_func::<S>, write_func::<S>); + if ret != errSecSuccess { + return Err(Error::from_code(ret)); + } + + let stream = Connection { + stream, + err: None, + panic: None, + }; + let stream = Box::into_raw(Box::new(stream)); + let ret = SSLSetConnection(self.0, stream.cast()); + if ret != errSecSuccess { + let _conn = Box::from_raw(stream); + return Err(Error::from_code(ret)); + } + + Ok(SslStream { + ctx: self, + _m: PhantomData, + }) + } + } + + /// Performs the SSL/TLS handshake. + pub fn handshake<S>(self, stream: S) -> result::Result<SslStream<S>, HandshakeError<S>> + where + S: Read + Write, + { + self.into_stream(stream) + .map_err(HandshakeError::Failure) + .and_then(SslStream::handshake) + } +} + +struct Connection<S> { + stream: S, + err: Option<io::Error>, + panic: Option<Box<dyn Any + Send>>, +} + +// the logic here is based off of libcurl's +#[cold] +fn translate_err(e: &io::Error) -> OSStatus { + match e.kind() { + io::ErrorKind::NotFound => errSSLClosedGraceful, + io::ErrorKind::ConnectionReset => errSSLClosedAbort, + io::ErrorKind::WouldBlock | + io::ErrorKind::NotConnected => errSSLWouldBlock, + _ => errSecIO, + } +} + +unsafe extern "C" fn read_func<S>( + connection: SSLConnectionRef, + data: *mut c_void, + data_length: *mut usize, +) -> OSStatus +where + S: Read, +{ + let conn: &mut Connection<S> = &mut *(connection as *mut _); + let data = slice::from_raw_parts_mut(data.cast::<u8>(), *data_length); + let mut start = 0; + let mut ret = errSecSuccess; + + while start < data.len() { + match panic::catch_unwind(AssertUnwindSafe(|| conn.stream.read(&mut data[start..]))) { + Ok(Ok(0)) => { + ret = errSSLClosedNoNotify; + break; + } + Ok(Ok(len)) => start += len, + Ok(Err(e)) => { + ret = translate_err(&e); + conn.err = Some(e); + break; + } + Err(e) => { + ret = errSecIO; + conn.panic = Some(e); + break; + } + } + } + + *data_length = start; + ret +} + +unsafe extern "C" fn write_func<S>( + connection: SSLConnectionRef, + data: *const c_void, + data_length: *mut usize, +) -> OSStatus +where + S: Write, +{ + let conn: &mut Connection<S> = &mut *(connection as *mut _); + let data = slice::from_raw_parts(data as *mut u8, *data_length); + let mut start = 0; + let mut ret = errSecSuccess; + + while start < data.len() { + match panic::catch_unwind(AssertUnwindSafe(|| conn.stream.write(&data[start..]))) { + Ok(Ok(0)) => { + ret = errSSLClosedNoNotify; + break; + } + Ok(Ok(len)) => start += len, + Ok(Err(e)) => { + ret = translate_err(&e); + conn.err = Some(e); + break; + } + Err(e) => { + ret = errSecIO; + conn.panic = Some(e); + break; + } + } + } + + *data_length = start; + ret +} + +/// A type implementing SSL/TLS encryption over an underlying stream. +pub struct SslStream<S> { + ctx: SslContext, + _m: PhantomData<S>, +} + +impl<S: fmt::Debug> fmt::Debug for SslStream<S> { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("SslStream") + .field("context", &self.ctx) + .field("stream", self.get_ref()) + .finish() + } +} + +impl<S> Drop for SslStream<S> { + fn drop(&mut self) { + unsafe { + let mut conn = ptr::null(); + let ret = SSLGetConnection(self.ctx.0, &mut conn); + assert!(ret == errSecSuccess); + let _ = Box::<Connection<S>>::from_raw(conn as *mut _); + } + } +} + +impl<S> SslStream<S> { + fn handshake(mut self) -> result::Result<Self, HandshakeError<S>> { + match unsafe { SSLHandshake(self.ctx.0) } { + errSecSuccess => Ok(self), + reason @ errSSLPeerAuthCompleted + | reason @ errSSLClientCertRequested + | reason @ errSSLWouldBlock + | reason @ errSSLClientHelloReceived => { + Err(HandshakeError::Interrupted(MidHandshakeSslStream { + stream: self, + error: Error::from_code(reason), + })) + } + err => { + self.check_panic(); + Err(HandshakeError::Failure(Error::from_code(err))) + } + } + } + + /// Returns a shared reference to the inner stream. + #[inline(always)] + #[must_use] + pub fn get_ref(&self) -> &S { + &self.connection().stream + } + + /// Returns a mutable reference to the underlying stream. + #[inline(always)] + pub fn get_mut(&mut self) -> &mut S { + &mut self.connection_mut().stream + } + + /// Returns a shared reference to the `SslContext` of the stream. + #[inline(always)] + #[must_use] + pub fn context(&self) -> &SslContext { + &self.ctx + } + + /// Returns a mutable reference to the `SslContext` of the stream. + #[inline(always)] + pub fn context_mut(&mut self) -> &mut SslContext { + &mut self.ctx + } + + /// Shuts down the connection. + pub fn close(&mut self) -> result::Result<(), io::Error> { + unsafe { + let ret = SSLClose(self.ctx.0); + if ret == errSecSuccess { + Ok(()) + } else { + Err(self.get_error(ret)) + } + } + } + + fn connection(&self) -> &Connection<S> { + unsafe { + let mut conn = ptr::null(); + let ret = SSLGetConnection(self.ctx.0, &mut conn); + assert!(ret == errSecSuccess); + + &mut *(conn as *mut Connection<S>) + } + } + + fn connection_mut(&mut self) -> &mut Connection<S> { + unsafe { + let mut conn = ptr::null(); + let ret = SSLGetConnection(self.ctx.0, &mut conn); + assert!(ret == errSecSuccess); + + &mut *(conn as *mut Connection<S>) + } + } + + #[cold] + fn check_panic(&mut self) { + let conn = self.connection_mut(); + if let Some(err) = conn.panic.take() { + panic::resume_unwind(err); + } + } + + #[cold] + fn get_error(&mut self, ret: OSStatus) -> io::Error { + self.check_panic(); + + if let Some(err) = self.connection_mut().err.take() { + err + } else { + io::Error::new(io::ErrorKind::Other, Error::from_code(ret)) + } + } +} + +impl<S: Read + Write> Read for SslStream<S> { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + // Below we base our return value off the amount of data read, so a + // zero-length buffer might cause us to erroneously interpret this + // request as an error. Instead short-circuit that logic and return + // `Ok(0)` instead. + if buf.is_empty() { + return Ok(0); + } + + // If some data was buffered but not enough to fill `buf`, SSLRead + // will try to read a new packet. This is bad because there may be + // no more data but the socket is remaining open (e.g HTTPS with + // Connection: keep-alive). + let buffered = self.context().buffered_read_size().unwrap_or(0); + let to_read = if buffered > 0 { + cmp::min(buffered, buf.len()) + } else { + buf.len() + }; + + unsafe { + let mut nread = 0; + let ret = SSLRead(self.ctx.0, buf.as_mut_ptr().cast(), to_read, &mut nread); + // SSLRead can return an error at the same time it returns the last + // chunk of data (!) + if nread > 0 { + return Ok(nread); + } + + match ret { + errSSLClosedGraceful | errSSLClosedAbort | errSSLClosedNoNotify => Ok(0), + // this error isn't fatal + errSSLPeerAuthCompleted => self.read(buf), + _ => Err(self.get_error(ret)), + } + } + } +} + +impl<S: Read + Write> Write for SslStream<S> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + // Like above in read, short circuit a 0-length write + if buf.is_empty() { + return Ok(0); + } + unsafe { + let mut nwritten = 0; + let ret = SSLWrite( + self.ctx.0, + buf.as_ptr().cast(), + buf.len(), + &mut nwritten, + ); + // just to be safe, base success off of nwritten rather than ret + // for the same reason as in read + if nwritten > 0 { + Ok(nwritten) + } else { + Err(self.get_error(ret)) + } + } + } + + fn flush(&mut self) -> io::Result<()> { + self.connection_mut().stream.flush() + } +} + +/// A builder type to simplify the creation of client side `SslStream`s. +#[derive(Debug)] +pub struct ClientBuilder { + identity: Option<SecIdentity>, + certs: Vec<SecCertificate>, + chain: Vec<SecCertificate>, + protocol_min: Option<SslProtocol>, + protocol_max: Option<SslProtocol>, + trust_certs_only: bool, + use_sni: bool, + danger_accept_invalid_certs: bool, + danger_accept_invalid_hostnames: bool, + whitelisted_ciphers: Vec<CipherSuite>, + blacklisted_ciphers: Vec<CipherSuite>, + #[cfg(feature = "alpn")] + alpn: Option<Vec<String>>, + #[cfg(feature = "session-tickets")] + enable_session_tickets: bool, +} + +impl Default for ClientBuilder { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + +impl ClientBuilder { + /// Creates a new builder with default options. + #[inline] + #[must_use] + pub fn new() -> Self { + Self { + identity: None, + certs: Vec::new(), + chain: Vec::new(), + protocol_min: None, + protocol_max: None, + trust_certs_only: false, + use_sni: true, + danger_accept_invalid_certs: false, + danger_accept_invalid_hostnames: false, + whitelisted_ciphers: Vec::new(), + blacklisted_ciphers: Vec::new(), + #[cfg(feature = "alpn")] + alpn: None, + #[cfg(feature = "session-tickets")] + enable_session_tickets: false, + } + } + + /// Specifies the set of root certificates to trust when + /// verifying the server's certificate. + #[inline] + pub fn anchor_certificates(&mut self, certs: &[SecCertificate]) -> &mut Self { + self.certs = certs.to_owned(); + self + } + + /// Add the certificate the set of root certificates to trust + /// when verifying the server's certificate. + #[inline] + pub fn add_anchor_certificate(&mut self, certs: &SecCertificate) -> &mut Self { + self.certs.push(certs.to_owned()); + self + } + + /// Specifies whether to trust the built-in certificates in addition + /// to specified anchor certificates. + #[inline(always)] + pub fn trust_anchor_certificates_only(&mut self, only: bool) -> &mut Self { + self.trust_certs_only = only; + self + } + + /// Specifies whether to trust invalid certificates. + /// + /// # Warning + /// + /// You should think very carefully before using this method. If invalid + /// certificates are trusted, *any* certificate for *any* site will be + /// trusted for use. This includes expired certificates. This introduces + /// significant vulnerabilities, and should only be used as a last resort. + #[inline(always)] + pub fn danger_accept_invalid_certs(&mut self, noverify: bool) -> &mut Self { + self.danger_accept_invalid_certs = noverify; + self + } + + /// Specifies whether to use Server Name Indication (SNI). + #[inline(always)] + pub fn use_sni(&mut self, use_sni: bool) -> &mut Self { + self.use_sni = use_sni; + self + } + + /// Specifies whether to verify that the server's hostname matches its certificate. + /// + /// # Warning + /// + /// You should think very carefully before using this method. If hostnames are not verified, + /// *any* valid certificate for *any* site will be trusted for use. This introduces significant + /// vulnerabilities, and should only be used as a last resort. + #[inline(always)] + pub fn danger_accept_invalid_hostnames( + &mut self, + danger_accept_invalid_hostnames: bool, + ) -> &mut Self { + self.danger_accept_invalid_hostnames = danger_accept_invalid_hostnames; + self + } + + /// Set a whitelist of enabled ciphers. Any ciphers not whitelisted will be disabled. + pub fn whitelist_ciphers(&mut self, whitelisted_ciphers: &[CipherSuite]) -> &mut Self { + self.whitelisted_ciphers = whitelisted_ciphers.to_owned(); + self + } + + /// Set a blacklist of disabled ciphers. Blacklisted ciphers will be disabled. + pub fn blacklist_ciphers(&mut self, blacklisted_ciphers: &[CipherSuite]) -> &mut Self { + self.blacklisted_ciphers = blacklisted_ciphers.to_owned(); + self + } + + /// Use the specified identity as a SSL/TLS client certificate. + pub fn identity(&mut self, identity: &SecIdentity, chain: &[SecCertificate]) -> &mut Self { + self.identity = Some(identity.clone()); + self.chain = chain.to_owned(); + self + } + + /// Configure the minimum protocol that this client will support. + #[inline(always)] + pub fn protocol_min(&mut self, min: SslProtocol) -> &mut Self { + self.protocol_min = Some(min); + self + } + + /// Configure the minimum protocol that this client will support. + #[inline(always)] + pub fn protocol_max(&mut self, max: SslProtocol) -> &mut Self { + self.protocol_max = Some(max); + self + } + + /// Configures the set of protocols used for ALPN. + #[cfg(feature = "alpn")] + pub fn alpn_protocols(&mut self, protocols: &[&str]) -> &mut Self { + self.alpn = Some(protocols.iter().map(|s| s.to_string()).collect()); + self + } + + /// Configures the use of the RFC 5077 `SessionTicket` extension. + /// + /// Defaults to `false`. + #[cfg(feature = "session-tickets")] + #[inline(always)] + pub fn enable_session_tickets(&mut self, enable: bool) -> &mut Self { + self.enable_session_tickets = enable; + self + } + + /// Initiates a new SSL/TLS session over a stream connected to the specified domain. + /// + /// If both SNI and hostname verification are disabled, the value of `domain` will be ignored. + pub fn handshake<S>( + &self, + domain: &str, + stream: S, + ) -> result::Result<SslStream<S>, ClientHandshakeError<S>> + where + S: Read + Write, + { + // the logic for trust validation is in MidHandshakeClientBuilder::connect, so run all + // of the handshake logic through that. + let stream = MidHandshakeSslStream { + stream: self.ctx_into_stream(domain, stream)?, + error: Error::from(errSecSuccess), + }; + + let certs = self.certs.clone(); + let stream = MidHandshakeClientBuilder { + stream, + domain: if self.danger_accept_invalid_hostnames { + None + } else { + Some(domain.to_string()) + }, + certs, + trust_certs_only: self.trust_certs_only, + danger_accept_invalid_certs: self.danger_accept_invalid_certs, + }; + stream.handshake() + } + + fn ctx_into_stream<S>(&self, domain: &str, stream: S) -> Result<SslStream<S>> + where + S: Read + Write, + { + let mut ctx = SslContext::new(SslProtocolSide::CLIENT, SslConnectionType::STREAM)?; + + if self.use_sni { + ctx.set_peer_domain_name(domain)?; + } + if let Some(ref identity) = self.identity { + ctx.set_certificate(identity, &self.chain)?; + } + #[cfg(feature = "alpn")] + { + if let Some(ref alpn) = self.alpn { + ctx.set_alpn_protocols(&alpn.iter().map(|s| &**s).collect::<Vec<_>>())?; + } + } + #[cfg(feature = "session-tickets")] + { + if self.enable_session_tickets { + // We must use the domain here to ensure that we go through certificate validation + // again rather than resuming the session if the domain changes. + ctx.set_peer_id(domain.as_bytes())?; + ctx.set_session_tickets_enabled(true)?; + } + } + ctx.set_break_on_server_auth(true)?; + self.configure_protocols(&mut ctx)?; + self.configure_ciphers(&mut ctx)?; + + ctx.into_stream(stream) + } + + fn configure_protocols(&self, ctx: &mut SslContext) -> Result<()> { + if let Some(min) = self.protocol_min { + ctx.set_protocol_version_min(min)?; + } + if let Some(max) = self.protocol_max { + ctx.set_protocol_version_max(max)?; + } + Ok(()) + } + + fn configure_ciphers(&self, ctx: &mut SslContext) -> Result<()> { + let mut ciphers = if self.whitelisted_ciphers.is_empty() { + ctx.enabled_ciphers()? + } else { + self.whitelisted_ciphers.clone() + }; + + if !self.blacklisted_ciphers.is_empty() { + ciphers.retain(|cipher| !self.blacklisted_ciphers.contains(cipher)); + } + + ctx.set_enabled_ciphers(&ciphers)?; + Ok(()) + } +} + +/// A builder type to simplify the creation of server-side `SslStream`s. +#[derive(Debug)] +pub struct ServerBuilder { + identity: SecIdentity, + certs: Vec<SecCertificate>, +} + +impl ServerBuilder { + /// Creates a new `ServerBuilder` which will use the specified identity + /// and certificate chain for handshakes. + #[must_use] + pub fn new(identity: &SecIdentity, certs: &[SecCertificate]) -> Self { + Self { + identity: identity.clone(), + certs: certs.to_owned(), + } + } + + /// Creates a new `ServerBuilder` which will use the identity + /// from the given PKCS #12 data. + /// + /// This operation fails if PKCS #12 file contains zero or more than one identity. + /// + /// This is a shortcut for the most common operation. + pub fn from_pkcs12(pkcs12_der: &[u8], passphrase: &str) -> Result<Self> { + let mut identities: Vec<(SecIdentity, Vec<SecCertificate>)> = Pkcs12ImportOptions::new() + .passphrase(passphrase) + .import(pkcs12_der)? + .into_iter() + .filter_map(|idendity| { + let certs = idendity.cert_chain.unwrap_or_default(); + idendity.identity.map(|identity| (identity, certs)) + }) + .collect(); + if identities.len() == 1 { + let (identity, certs) = identities.pop().unwrap(); + Ok(ServerBuilder::new(&identity, &certs)) + } else { + // This error code is not really helpful + Err(Error::from_code(errSecParam)) + } + } + + /// Create a SSL context for lower-level stream initialization. + pub fn new_ssl_context(&self) -> Result<SslContext> { + let mut ctx = SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM)?; + ctx.set_certificate(&self.identity, &self.certs)?; + Ok(ctx) + } + + /// Initiates a new SSL/TLS session over a stream. + pub fn handshake<S>(&self, stream: S) -> Result<SslStream<S>> + where + S: Read + Write, + { + match self.new_ssl_context()?.handshake(stream) { + Ok(stream) => Ok(stream), + Err(HandshakeError::Interrupted(stream)) => Err(*stream.error()), + Err(HandshakeError::Failure(err)) => Err(err), + } + } +} + +#[cfg(test)] +mod test { + use std::io; + use std::io::prelude::*; + use std::net::TcpStream; + + use super::*; + + #[test] + fn server_builder_from_pkcs12() { + let pkcs12_der = include_bytes!("../test/server.p12"); + ServerBuilder::from_pkcs12(pkcs12_der, "password123").unwrap(); + } + + #[test] + fn connect() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_peer_domain_name("google.com")); + let stream = p!(TcpStream::connect("google.com:443")); + p!(ctx.handshake(stream)); + } + + #[test] + fn connect_bad_domain() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_peer_domain_name("foobar.com")); + let stream = p!(TcpStream::connect("google.com:443")); + match ctx.handshake(stream) { + Ok(_) => panic!("expected failure"), + Err(_) => {} + } + } + + #[test] + fn load_page() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_peer_domain_name("google.com")); + let stream = p!(TcpStream::connect("google.com:443")); + let mut stream = p!(ctx.handshake(stream)); + p!(stream.write_all(b"GET / HTTP/1.0\r\n\r\n")); + p!(stream.flush()); + let mut buf = vec![]; + p!(stream.read_to_end(&mut buf)); + println!("{}", String::from_utf8_lossy(&buf)); + } + + #[test] + fn client_no_session_ticket_resumption() { + for _ in 0..2 { + let stream = p!(TcpStream::connect("google.com:443")); + + // Manually handshake here. + let stream = MidHandshakeSslStream { + stream: ClientBuilder::new() + .ctx_into_stream("google.com", stream) + .unwrap(), + error: Error::from(errSecSuccess), + }; + + let mut result = stream.handshake(); + + if let Err(HandshakeError::Interrupted(stream)) = result { + assert!(stream.server_auth_completed()); + result = stream.handshake(); + } else { + panic!("Unexpectedly skipped server auth"); + } + + assert!(result.is_ok()); + } + } + + #[test] + #[cfg(feature = "session-tickets")] + fn client_session_ticket_resumption() { + // The first time through this loop, we should do a full handshake. The second time, we + // should immediately finish the handshake without breaking on server auth. + for i in 0..2 { + let stream = p!(TcpStream::connect("google.com:443")); + let mut builder = ClientBuilder::new(); + builder.enable_session_tickets(true); + + // Manually handshake here. + let stream = MidHandshakeSslStream { + stream: builder.ctx_into_stream("google.com", stream).unwrap(), + error: Error::from(errSecSuccess), + }; + + let mut result = stream.handshake(); + + if let Err(HandshakeError::Interrupted(stream)) = result { + assert!(stream.server_auth_completed()); + assert_eq!( + i, 0, + "Session ticket resumption did not work, server auth was not skipped" + ); + result = stream.handshake(); + } else { + assert_eq!(i, 1, "Unexpectedly skipped server auth"); + } + + assert!(result.is_ok()); + } + } + + #[test] + #[cfg(feature = "alpn")] + fn client_alpn_accept() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_peer_domain_name("google.com")); + p!(ctx.set_alpn_protocols(&vec!["h2"])); + let stream = p!(TcpStream::connect("google.com:443")); + let stream = ctx.handshake(stream).unwrap(); + assert_eq!(vec!["h2"], stream.context().alpn_protocols().unwrap()); + } + + #[test] + #[cfg(feature = "alpn")] + fn client_alpn_reject() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_peer_domain_name("google.com")); + p!(ctx.set_alpn_protocols(&vec!["h2c"])); + let stream = p!(TcpStream::connect("google.com:443")); + let stream = ctx.handshake(stream).unwrap(); + assert!(stream.context().alpn_protocols().is_err()); + } + + #[test] + fn client_no_anchor_certs() { + let stream = p!(TcpStream::connect("google.com:443")); + assert!(ClientBuilder::new() + .trust_anchor_certificates_only(true) + .handshake("google.com", stream) + .is_err()); + } + + #[test] + fn client_bad_domain() { + let stream = p!(TcpStream::connect("google.com:443")); + assert!(ClientBuilder::new() + .handshake("foobar.com", stream) + .is_err()); + } + + #[test] + fn client_bad_domain_ignored() { + let stream = p!(TcpStream::connect("google.com:443")); + ClientBuilder::new() + .danger_accept_invalid_hostnames(true) + .handshake("foobar.com", stream) + .unwrap(); + } + + #[test] + fn connect_no_verify_ssl() { + let stream = p!(TcpStream::connect("expired.badssl.com:443")); + let mut builder = ClientBuilder::new(); + builder.danger_accept_invalid_certs(true); + builder.handshake("expired.badssl.com", stream).unwrap(); + } + + #[test] + fn load_page_client() { + let stream = p!(TcpStream::connect("google.com:443")); + let mut stream = p!(ClientBuilder::new().handshake("google.com", stream)); + p!(stream.write_all(b"GET / HTTP/1.0\r\n\r\n")); + p!(stream.flush()); + let mut buf = vec![]; + p!(stream.read_to_end(&mut buf)); + println!("{}", String::from_utf8_lossy(&buf)); + } + + #[test] + #[cfg_attr(target_os = "ios", ignore)] // FIXME what's going on with ios? + fn cipher_configuration() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let ciphers = p!(ctx.enabled_ciphers()); + let ciphers = ciphers + .iter() + .enumerate() + .filter_map(|(i, c)| if i % 2 == 0 { Some(*c) } else { None }) + .collect::<Vec<_>>(); + p!(ctx.set_enabled_ciphers(&ciphers)); + assert_eq!(ciphers, p!(ctx.enabled_ciphers())); + } + + #[test] + fn test_builder_whitelist_ciphers() { + let stream = p!(TcpStream::connect("google.com:443")); + + let ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + assert!(p!(ctx.enabled_ciphers()).len() > 1); + + let ciphers = p!(ctx.enabled_ciphers()); + let cipher = ciphers.first().unwrap(); + let stream = p!(ClientBuilder::new() + .whitelist_ciphers(&[*cipher]) + .ctx_into_stream("google.com", stream)); + + assert_eq!(1, p!(stream.context().enabled_ciphers()).len()); + } + + #[test] + #[cfg_attr(target_os = "ios", ignore)] // FIXME same issue as cipher_configuration + fn test_builder_blacklist_ciphers() { + let stream = p!(TcpStream::connect("google.com:443")); + + let ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + let num = p!(ctx.enabled_ciphers()).len(); + assert!(num > 1); + + let ciphers = p!(ctx.enabled_ciphers()); + let cipher = ciphers.first().unwrap(); + let stream = p!(ClientBuilder::new() + .blacklist_ciphers(&[*cipher]) + .ctx_into_stream("google.com", stream)); + + assert_eq!(num - 1, p!(stream.context().enabled_ciphers()).len()); + } + + #[test] + fn idle_context_peer_trust() { + let ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + assert!(ctx.peer_trust2().is_err()); + } + + #[test] + fn peer_id() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + assert!(p!(ctx.peer_id()).is_none()); + p!(ctx.set_peer_id(b"foobar")); + assert_eq!(p!(ctx.peer_id()), Some(&b"foobar"[..])); + } + + #[test] + fn peer_domain_name() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + assert_eq!("", p!(ctx.peer_domain_name())); + p!(ctx.set_peer_domain_name("foobar.com")); + assert_eq!("foobar.com", p!(ctx.peer_domain_name())); + } + + #[test] + #[should_panic(expected = "blammo")] + fn write_panic() { + struct ExplodingStream(TcpStream); + + impl Read for ExplodingStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + self.0.read(buf) + } + } + + impl Write for ExplodingStream { + fn write(&mut self, _: &[u8]) -> io::Result<usize> { + panic!("blammo"); + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + } + + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_peer_domain_name("google.com")); + let stream = p!(TcpStream::connect("google.com:443")); + let _ = ctx.handshake(ExplodingStream(stream)); + } + + #[test] + #[should_panic(expected = "blammo")] + fn read_panic() { + struct ExplodingStream(TcpStream); + + impl Read for ExplodingStream { + fn read(&mut self, _: &mut [u8]) -> io::Result<usize> { + panic!("blammo"); + } + } + + impl Write for ExplodingStream { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.0.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } + } + + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_peer_domain_name("google.com")); + let stream = p!(TcpStream::connect("google.com:443")); + let _ = ctx.handshake(ExplodingStream(stream)); + } + + #[test] + fn zero_length_buffers() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_peer_domain_name("google.com")); + let stream = p!(TcpStream::connect("google.com:443")); + let mut stream = ctx.handshake(stream).unwrap(); + assert_eq!(stream.write(b"").unwrap(), 0); + assert_eq!(stream.read(&mut []).unwrap(), 0); + } +} diff --git a/vendor/security-framework/src/trust.rs b/vendor/security-framework/src/trust.rs new file mode 100644 index 000000000..fcf19a733 --- /dev/null +++ b/vendor/security-framework/src/trust.rs @@ -0,0 +1,396 @@ +//! Trust evaluation support. + +use core_foundation::array::CFArray; +#[cfg(target_os = "macos")] +use core_foundation::array::CFArrayRef; +use core_foundation::base::TCFType; +#[cfg(any(feature = "OSX_10_9", target_os = "ios"))] +use core_foundation::data::CFData; +use core_foundation::date::CFDate; +use core_foundation_sys::base::{Boolean, CFIndex}; + +use security_framework_sys::trust::*; +use std::ptr; + +use crate::base::Result; +use crate::certificate::SecCertificate; +use crate::cvt; +use crate::key::SecKey; +use crate::policy::SecPolicy; +use core_foundation::error::{CFError, CFErrorRef}; + +/// The result of trust evaluation. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct TrustResult(SecTrustResultType); + +impl TrustResult { + /// An invalid setting or result. + pub const INVALID: Self = Self(kSecTrustResultInvalid); + + /// You may proceed. + pub const PROCEED: Self = Self(kSecTrustResultProceed); + + /// Indicates a denial by the user, do not proceed. + pub const DENY: Self = Self(kSecTrustResultDeny); + + /// The certificate is implicitly trusted. + pub const UNSPECIFIED: Self = Self(kSecTrustResultUnspecified); + + /// Indicates a trust policy failure that the user can override. + pub const RECOVERABLE_TRUST_FAILURE: Self = Self(kSecTrustResultRecoverableTrustFailure); + + /// Indicates a trust policy failure that the user cannot override. + pub const FATAL_TRUST_FAILURE: Self = Self(kSecTrustResultFatalTrustFailure); + + /// An error not related to trust validation. + pub const OTHER_ERROR: Self = Self(kSecTrustResultOtherError); +} + +impl TrustResult { + /// Returns true if the result is "successful" - specifically `PROCEED` or `UNSPECIFIED`. + #[inline] + #[must_use] + pub fn success(self) -> bool { + matches!(self, Self::PROCEED | Self::UNSPECIFIED) + } +} + +declare_TCFType! { + /// A type representing a trust evaluation for a certificate. + SecTrust, SecTrustRef +} +impl_TCFType!(SecTrust, SecTrustRef, SecTrustGetTypeID); + +unsafe impl Sync for SecTrust {} +unsafe impl Send for SecTrust {} + +#[cfg(target_os = "macos")] +bitflags::bitflags! { + /// The option flags used to configure the evaluation of a `SecTrust`. + pub struct TrustOptions: SecTrustOptionFlags { + /// Allow expired certificates (except for the root certificate). + const ALLOW_EXPIRED = kSecTrustOptionAllowExpired; + /// Allow CA certificates as leaf certificates. + const LEAF_IS_CA = kSecTrustOptionLeafIsCA; + /// Allow network downloads of CA certificates. + const FETCH_ISSUER_FROM_NET = kSecTrustOptionFetchIssuerFromNet; + /// Allow expired root certificates. + const ALLOW_EXPIRED_ROOT = kSecTrustOptionAllowExpiredRoot; + /// Require a positive revocation check for each certificate. + const REQUIRE_REVOCATION_PER_CERT = kSecTrustOptionRequireRevPerCert; + /// Use TrustSettings instead of anchors. + const USE_TRUST_SETTINGS = kSecTrustOptionUseTrustSettings; + /// Treat properly self-signed certificates as anchors implicitly. + const IMPLICIT_ANCHORS = kSecTrustOptionImplicitAnchors; + } +} + +impl SecTrust { + /// Creates a `SecTrustRef` that is configured with a certificate chain, for validating + /// that chain against a collection of policies. + pub fn create_with_certificates( + certs: &[SecCertificate], + policies: &[SecPolicy], + ) -> Result<Self> { + let cert_array = CFArray::from_CFTypes(certs); + let policy_array = CFArray::from_CFTypes(policies); + let mut trust = ptr::null_mut(); + unsafe { + cvt(SecTrustCreateWithCertificates( + cert_array.as_CFTypeRef(), + policy_array.as_CFTypeRef(), + &mut trust, + ))?; + Ok(Self(trust)) + } + } + + /// Sets the date and time against which the certificates in this trust object + /// are verified. + #[inline] + pub fn set_trust_verify_date(&mut self, date: &CFDate) -> Result<()> { + unsafe { cvt(SecTrustSetVerifyDate(self.0, date.as_concrete_TypeRef())) } + } + + /// Sets additional anchor certificates used to validate trust. + pub fn set_anchor_certificates(&mut self, certs: &[SecCertificate]) -> Result<()> { + let certs = CFArray::from_CFTypes(certs); + + unsafe { + cvt(SecTrustSetAnchorCertificates( + self.0, + certs.as_concrete_TypeRef(), + )) + } + } + + /// Retrieves the anchor (root) certificates stored by macOS + #[cfg(target_os = "macos")] + pub fn copy_anchor_certificates() -> Result<Vec<SecCertificate>> { + let mut array: CFArrayRef = ptr::null(); + + unsafe { + cvt(SecTrustCopyAnchorCertificates(&mut array))?; + } + + if array.is_null() { + return Ok(vec![]); + } + + let array = unsafe { CFArray::<SecCertificate>::wrap_under_create_rule(array) }; + Ok(array.into_iter().map(|c| c.clone()).collect()) + } + + /// If set to `true`, only the certificates specified by + /// `set_anchor_certificates` will be trusted, but not globally trusted + /// certificates. + #[inline] + pub fn set_trust_anchor_certificates_only(&mut self, only: bool) -> Result<()> { + unsafe { cvt(SecTrustSetAnchorCertificatesOnly(self.0, only as Boolean)) } + } + + /// Sets the policy used to evaluate trust. + #[inline] + pub fn set_policy(&mut self, policy: &SecPolicy) -> Result<()> { + unsafe { cvt(SecTrustSetPolicies(self.0, policy.as_CFTypeRef())) } + } + + /// Sets option flags for customizing evaluation of a trust object. + #[cfg(target_os = "macos")] + #[inline] + pub fn set_options(&mut self, options: TrustOptions) -> Result<()> { + unsafe { cvt(SecTrustSetOptions(self.0, options.bits())) } + } + + /// Indicates whether this trust object is permitted to + /// fetch missing intermediate certificates from the network. + #[cfg(any(feature = "OSX_10_9", target_os = "ios"))] + pub fn get_network_fetch_allowed(&mut self) -> Result<bool> { + let mut allowed = 0; + + unsafe { cvt(SecTrustGetNetworkFetchAllowed(self.0, &mut allowed))? }; + + Ok(allowed != 0) + } + + /// Specifies whether this trust object is permitted to + /// fetch missing intermediate certificates from the network. + #[cfg(any(feature = "OSX_10_9", target_os = "ios"))] + #[inline] + pub fn set_network_fetch_allowed(&mut self, allowed: bool) -> Result<()> { + unsafe { cvt(SecTrustSetNetworkFetchAllowed(self.0, allowed as u8)) } + } + + /// Attaches Online Certificate Status Protocol (OSCP) response data + /// to this trust object. + #[cfg(any(feature = "OSX_10_9", target_os = "ios"))] + pub fn set_trust_ocsp_response<I: Iterator<Item = impl AsRef<[u8]>>>( + &mut self, + ocsp_response: I, + ) -> Result<()> { + let response: Vec<CFData> = ocsp_response + .into_iter() + .map(|bytes| CFData::from_buffer(bytes.as_ref())) + .collect(); + + let response = CFArray::from_CFTypes(&response); + + unsafe { cvt(SecTrustSetOCSPResponse(self.0, response.as_CFTypeRef())) } + } + + /// Attaches signed certificate timestamp data to this trust object. + #[cfg(any(feature = "OSX_10_14", target_os = "ios"))] + pub fn set_signed_certificate_timestamps<I: Iterator<Item = impl AsRef<[u8]>>>( + &mut self, + scts: I, + ) -> Result<()> { + let scts: Vec<CFData> = scts + .into_iter() + .map(|bytes| CFData::from_buffer(bytes.as_ref())) + .collect(); + + let scts = CFArray::from_CFTypes(&scts); + + unsafe { cvt(SecTrustSetSignedCertificateTimestamps(self.0, scts.as_concrete_TypeRef())) } + } + + /// Returns the public key for a leaf certificate after it has been evaluated. + #[inline] + pub fn copy_public_key(&mut self) -> Result<SecKey> { + unsafe { + Ok(SecKey::wrap_under_create_rule(SecTrustCopyPublicKey( + self.0, + ))) + } + } + + /// Evaluates trust. + #[deprecated(note = "use evaluate_with_error")] + pub fn evaluate(&self) -> Result<TrustResult> { + #[allow(deprecated)] + unsafe { + let mut result = kSecTrustResultInvalid; + cvt(SecTrustEvaluate(self.0, &mut result))?; + Ok(TrustResult(result)) + } + } + + /// Evaluates trust. Requires macOS 10.14 or iOS, otherwise it just calls `evaluate()` + pub fn evaluate_with_error(&self) -> Result<(), CFError> { + #[cfg(any(feature = "OSX_10_14", target_os = "ios"))] + unsafe { + let mut error: CFErrorRef = ::std::ptr::null_mut(); + if !SecTrustEvaluateWithError(self.0, &mut error) { + assert!(!error.is_null()); + let error = CFError::wrap_under_create_rule(error); + return Err(error); + } + Ok(()) + } + #[cfg(not(any(feature = "OSX_10_14", target_os = "ios")))] + #[allow(deprecated)] + { + use security_framework_sys::base::errSecNotTrusted; + use security_framework_sys::base::errSecTrustSettingDeny; + + let code = match self.evaluate() { + Ok(res) if res.success() => return Ok(()), + Ok(TrustResult::DENY) => errSecTrustSettingDeny, + Ok(_) => errSecNotTrusted, + Err(err) => err.code(), + }; + Err(cferror_from_osstatus(code)) + } + } + + /// Returns the number of certificates in an evaluated certificate chain. + /// + /// Note: evaluate must first be called on the `SecTrust`. + #[inline(always)] + #[must_use] + pub fn certificate_count(&self) -> CFIndex { + unsafe { SecTrustGetCertificateCount(self.0) } + } + + /// Returns a specific certificate from the certificate chain used to evaluate trust. + /// + /// Note: evaluate must first be called on the `SecTrust`. + #[deprecated(note = "deprecated by Apple")] + #[must_use] + pub fn certificate_at_index(&self, ix: CFIndex) -> Option<SecCertificate> { + #[allow(deprecated)] + unsafe { + if self.certificate_count() <= ix { + None + } else { + let certificate = SecTrustGetCertificateAtIndex(self.0, ix); + Some(SecCertificate::wrap_under_get_rule(certificate.cast())) + } + } + } +} + +#[cfg(not(any(feature = "OSX_10_14", target_os = "ios")))] +extern "C" { + fn CFErrorCreate(allocator: core_foundation_sys::base::CFAllocatorRef, domain: core_foundation_sys::string::CFStringRef, code: CFIndex, userInfo: core_foundation_sys::dictionary::CFDictionaryRef) -> CFErrorRef; +} + +#[cfg(not(any(feature = "OSX_10_14", target_os = "ios")))] +fn cferror_from_osstatus(code: core_foundation_sys::base::OSStatus) -> CFError { + unsafe { + let error = CFErrorCreate(ptr::null_mut(), core_foundation_sys::error::kCFErrorDomainOSStatus, code as _, ptr::null_mut()); + assert!(!error.is_null()); + CFError::wrap_under_create_rule(error) + } +} + +#[cfg(test)] +mod test { + use crate::policy::SecPolicy; + use crate::secure_transport::SslProtocolSide; + use crate::test::certificate; + use crate::trust::SecTrust; + + #[test] + #[allow(deprecated)] + fn create_with_certificates() { + let cert = certificate(); + let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); + let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); + assert_eq!(trust.evaluate().unwrap().success(), false) + } + + #[test] + fn create_with_certificates_new() { + let cert = certificate(); + let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); + let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); + assert!(trust.evaluate_with_error().is_err()); + } + + #[test] + #[allow(deprecated)] + fn certificate_count_and_at_index() { + let cert = certificate(); + let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); + let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); + trust.evaluate().unwrap(); + + let count = trust.certificate_count(); + assert_eq!(count, 1); + + let cert_bytes = trust.certificate_at_index(0).unwrap().to_der(); + assert_eq!(cert_bytes, certificate().to_der()); + } + + #[test] + #[allow(deprecated)] + fn certificate_count_and_at_index_new() { + let cert = certificate(); + let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); + let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); + assert!(trust.evaluate_with_error().is_err()); + + let count = trust.certificate_count(); + assert_eq!(count, 1); + + let cert_bytes = trust.certificate_at_index(0).unwrap().to_der(); + assert_eq!(cert_bytes, certificate().to_der()); + } + + #[test] + #[allow(deprecated)] + fn certificate_at_index_out_of_bounds() { + let cert = certificate(); + let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); + + let trust = SecTrust::create_with_certificates(&[cert.clone()], &[ssl_policy.clone()]).unwrap(); + trust.evaluate().unwrap(); + assert!(trust.certificate_at_index(1).is_none()); + + let trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); + assert!(trust.evaluate_with_error().is_err()); + assert!(trust.certificate_at_index(1).is_none()); + } + + #[test] + #[allow(deprecated)] + fn set_policy() { + let cert = certificate(); + let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io.bogus")); + let mut trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); + let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); + trust.set_policy(&ssl_policy).unwrap(); + assert_eq!(trust.evaluate().unwrap().success(), false) + } + + #[test] + fn set_policy_new() { + let cert = certificate(); + let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io.bogus")); + let mut trust = SecTrust::create_with_certificates(&[cert], &[ssl_policy]).unwrap(); + let ssl_policy = SecPolicy::create_ssl(SslProtocolSide::CLIENT, Some("certifi.io")); + trust.set_policy(&ssl_policy).unwrap(); + assert!(trust.evaluate_with_error().is_err()); + } +} diff --git a/vendor/security-framework/src/trust_settings.rs b/vendor/security-framework/src/trust_settings.rs new file mode 100644 index 000000000..23efdf1f7 --- /dev/null +++ b/vendor/security-framework/src/trust_settings.rs @@ -0,0 +1,305 @@ +//! Querying trust settings. + +use core_foundation::array::{CFArray, CFArrayRef}; +use core_foundation::base::{CFIndex, TCFType}; +use core_foundation::dictionary::CFDictionary; +use core_foundation::number::CFNumber; +use core_foundation::string::CFString; + +use core_foundation_sys::base::CFTypeRef; +use security_framework_sys::base::errSecNoTrustSettings; +use security_framework_sys::base::errSecSuccess; +use security_framework_sys::trust_settings::*; + +use std::ptr; + +use crate::base::Error; +use crate::base::Result; +use crate::certificate::SecCertificate; +use crate::cvt; + +/// Which set of trust settings to query +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum Domain { + /// Per-user trust settings + User = kSecTrustSettingsDomainUser, + /// Locally administered, system-wide trust settings + Admin = kSecTrustSettingsDomainAdmin, + /// System trust settings + System = kSecTrustSettingsDomainSystem, +} + +impl From<Domain> for SecTrustSettingsDomain { + #[inline] + fn from(domain: Domain) -> SecTrustSettingsDomain { + match domain { + Domain::User => kSecTrustSettingsDomainUser, + Domain::Admin => kSecTrustSettingsDomainAdmin, + Domain::System => kSecTrustSettingsDomainSystem, + } + } +} + +/// Trust settings for a specific certificate in a specific domain +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum TrustSettingsForCertificate { + /// Not used + Invalid, + + /// This is a root certificate and is trusted, either explicitly or + /// implicitly. + TrustRoot, + + /// This is a non-root certificate but is explicitly trusted. + TrustAsRoot, + + /// Cert is explicitly distrusted. + Deny, + + /// Neither trusted nor distrusted. + Unspecified, +} + +impl TrustSettingsForCertificate { + /// Create from `kSecTrustSettingsResult*` constant + fn new(value: i64) -> Self { + if value < 0 || value > i64::from(u32::max_value()) { + return Self::Invalid; + } + match value as u32 { + kSecTrustSettingsResultTrustRoot => Self::TrustRoot, + kSecTrustSettingsResultTrustAsRoot => Self::TrustAsRoot, + kSecTrustSettingsResultDeny => Self::Deny, + kSecTrustSettingsResultUnspecified => Self::Unspecified, + _ => Self::Invalid, + } + } +} + +/// Allows access to the certificates and their trust settings in a given domain. +pub struct TrustSettings { + domain: Domain, +} + +impl TrustSettings { + /// Create a new `TrustSettings` for the given domain. + /// + /// You can call `iter()` to discover the certificates with settings in this domain. + /// + /// Then you can call `tls_trust_settings_for_certificate()` with a given certificate + /// to learn what the aggregate trust setting for that certificate within this domain. + #[inline(always)] + #[must_use] + pub fn new(domain: Domain) -> Self { + Self { domain } + } + + /// Create an iterator over the certificates with settings in this domain. + /// This produces an empty iterator if there are no such certificates. + pub fn iter(&self) -> Result<TrustSettingsIter> { + let array = unsafe { + let mut array_ptr: CFArrayRef = ptr::null_mut(); + + // SecTrustSettingsCopyCertificates returns errSecNoTrustSettings + // if no items have trust settings in the given domain. We map + // that to an empty TrustSettings iterator. + match SecTrustSettingsCopyCertificates(self.domain.into(), &mut array_ptr) { + errSecNoTrustSettings => CFArray::from_CFTypes(&[]), + errSecSuccess => CFArray::<SecCertificate>::wrap_under_create_rule(array_ptr), + err => return Err(Error::from_code(err)), + } + }; + + Ok(TrustSettingsIter { index: 0, array }) + } + + ///set trust settings to ""always trust this root certificate regardless of use.". + /// Sets the trust settings for the provided certificate to "always trust this root certificate + /// regardless of use." + /// + /// This method configures the trust settings for the specified certificate, indicating that it should + /// always be trusted as a TLS root certificate, regardless of its usage. + /// + /// If successful, the trust settings are updated for the certificate in the given domain. If the + /// certificate had no previous trust settings in the domain, new trust settings are created. If the + /// certificate had existing trust settings, they are replaced with the new settings. + /// + /// It is not possible to modify per-user trust settings when not running in a GUI + /// environment, if you try it will return error `2070: errSecInternalComponent` + #[cfg(target_os="macos")] + pub fn set_trust_settings_always(&self, cert: &SecCertificate) -> Result<()> { + let domain = self.domain; + let trust_settings: CFTypeRef = ptr::null_mut(); + cvt(unsafe { + SecTrustSettingsSetTrustSettings( + cert.as_CFTypeRef() as *mut _, + domain.into(), + trust_settings, + ) + }) + } + + /// Returns the aggregate trust setting for the given certificate. + /// + /// This tells you whether the certificate should be trusted as a TLS + /// root certificate. + /// + /// If the certificate has no trust settings in the given domain, the + /// `errSecItemNotFound` error is returned. + /// + /// If the certificate has no specific trust settings for TLS in the + /// given domain `None` is returned. + /// + /// Otherwise, the specific trust settings are aggregated and returned. + pub fn tls_trust_settings_for_certificate(&self, cert: &SecCertificate) + -> Result<Option<TrustSettingsForCertificate>> { + let trust_settings = unsafe { + let mut array_ptr: CFArrayRef = ptr::null_mut(); + let cert_ptr = cert.as_CFTypeRef() as *mut _; + cvt(SecTrustSettingsCopyTrustSettings(cert_ptr, + self.domain.into(), + &mut array_ptr))?; + CFArray::<CFDictionary>::wrap_under_create_rule(array_ptr) + }; + + for settings in trust_settings.iter() { + // Reject settings for non-SSL policies + let is_not_ssl_policy = { + let policy_name_key = CFString::from_static_string("kSecTrustSettingsPolicyName"); + let ssl_policy_name = CFString::from_static_string("sslServer"); + + let maybe_name: Option<CFString> = settings + .find(policy_name_key.as_CFTypeRef().cast()) + .map(|name| unsafe { CFString::wrap_under_get_rule((*name).cast()) }); + + matches!(maybe_name, Some(ref name) if name != &ssl_policy_name) + }; + + if is_not_ssl_policy { + continue; + } + + // Evaluate "effective trust settings" for this usage constraint. + let maybe_trust_result = { + let settings_result_key = CFString::from_static_string("kSecTrustSettingsResult"); + settings + .find(settings_result_key.as_CFTypeRef().cast()) + .map(|num| unsafe { CFNumber::wrap_under_get_rule((*num).cast()) }) + .and_then(|num| num.to_i64()) + }; + + // "Note that an empty Trust Settings array means "always trust this cert, + // with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot"." + let trust_result = TrustSettingsForCertificate::new(maybe_trust_result + .unwrap_or_else(|| i64::from(kSecTrustSettingsResultTrustRoot))); + + match trust_result { + TrustSettingsForCertificate::Unspecified | + TrustSettingsForCertificate::Invalid => { continue; }, + _ => return Ok(Some(trust_result)), + } + } + + // There were no more specific settings. This might mean the certificate + // is to be trusted anyway (since, eg, it's in system store), but leave + // the caller to make this decision. + Ok(None) + } +} + +/// Iterator over certificates. +pub struct TrustSettingsIter { + array: CFArray<SecCertificate>, + index: CFIndex, +} + +impl Iterator for TrustSettingsIter { + type Item = SecCertificate; + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + if self.index >= self.array.len() { + None + } else { + let cert = self.array.get(self.index).unwrap(); + self.index += 1; + Some(cert.clone()) + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option<usize>) { + let left = (self.array.len() as usize).saturating_sub(self.index as usize); + (left, Some(left)) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::test::certificate; + + fn list_for_domain(domain: Domain) { + println!("--- domain: {:?}", domain); + let ts = TrustSettings::new(domain); + let iterator = ts.iter().unwrap(); + + for (i, cert) in iterator.enumerate() { + println!("cert({:?}) = {:?}", i, cert); + println!(" settings = {:?}", ts.tls_trust_settings_for_certificate(&cert)); + } + println!("---"); + } + + #[test] + fn list_for_user() { + list_for_domain(Domain::User); + } + + #[test] + fn list_for_system() { + list_for_domain(Domain::System); + } + + #[test] + fn list_for_admin() { + list_for_domain(Domain::Admin); + } + + #[test] + fn test_system_certs_are_present() { + let system = TrustSettings::new(Domain::System).iter().unwrap().count(); + + // 168 at the time of writing + assert!(system > 100); + } + + #[test] + fn test_isrg_root_exists_and_is_trusted() { + let ts = TrustSettings::new(Domain::System); + assert_eq!( + ts.iter() + .unwrap() + .find(|cert| cert.subject_summary() == "ISRG Root X1") + .and_then(|cert| ts.tls_trust_settings_for_certificate(&cert).unwrap()), + None + ); + // ^ this is a case where None means "always trust", according to Apple docs: + // + // "Note that an empty Trust Settings array means "always trust this cert, + // with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot"." + } + + #[test] + fn test_unknown_cert_is_not_trusted() { + let ts = TrustSettings::new(Domain::System); + let cert = certificate(); + assert_eq!(ts.tls_trust_settings_for_certificate(&cert) + .err() + .unwrap() + .message(), + Some("The specified item could not be found in the keychain.".into())); + } +} + |