RPM build fix (reverted CI changes which will need to be un-reverted or made conditional) and vendor Rust dependencies to make builds much faster in any CI system.

This commit is contained in:
Adam Ierymenko
2022-06-08 07:32:16 -04:00
parent 373ca30269
commit d5ca4e5f52
12611 changed files with 2898014 additions and 284 deletions

View File

@@ -0,0 +1 @@
{"files":{"Cargo.lock":"87071cf4516d35c7c5d19e2d148d435b1b9aa12300274194dd1fa3e6fc4c8edc","Cargo.toml":"08ffa834f7a93a7dd36533498db58fd63e7489a2bde340b5bf7469d74e327039","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/authorization.rs":"45ff79a001a38234beb0b932cebe2a87c5827f1d48f573267e9357defda3ef4a","src/base.rs":"f918e1a28dc37e61df16eb3e84f83e7448848eab63c8570d6017444014a597ad","src/certificate.rs":"aec9a62c2b2bbe819953e3f38377c22d3542de02e44649c5b7a5d60f12d7ca34","src/cipher_suite.rs":"97cd0b094503959fc209bb01e822d26449fadac2ec494bfa8cde324b8cef2a70","src/dlsym.rs":"0f66bb4843e4c82fe597168ccd153a7275d18ab849df18ce57210ecf43ffe6ff","src/identity.rs":"7c2730693a2cd90f78459265b5e47672875b99a3c38d91f2883e7ddf136f36c8","src/import_export.rs":"4ff8e920ef843ea6d28bddef3509784304e92c057796aa076f5a83578c245c9e","src/item.rs":"1d49723b9b8176c6b34bc9cb0d7fe2047db9b2e77dd377def50bec9a16eabb38","src/key.rs":"ff71c1b2bf290c907ce03a4a0d87c4b2b0570cbb9d9beec780336a6a088564d0","src/lib.rs":"9f4fe6640c885bc03e5696f0c53bbeabfafad1230dc585c6ab0000a13e0924f0","src/os/macos/access.rs":"0c3d5951751cf8753997dadfcd54a0941773f0071d6f5042778da8a865dc8932","src/os/macos/certificate.rs":"5ffb7a6ab2003eadbb4a6f4612c2a626829a7f391493d51adf741ff6a5d16f29","src/os/macos/certificate_oids.rs":"3fd230be625fa3f27bd83a62b73989f4ae1cc4f7fdfbfe5f1f8a9432b66d1f44","src/os/macos/code_signing.rs":"77b3152d42ea1f77fe51e119b7971b27bb4f818cf9ab60116ac5a0ebdf89af7e","src/os/macos/digest_transform.rs":"d74c4bf09f32256787df3b84e3264bce69d70b96133b4d55a19c2ac9d26f5541","src/os/macos/encrypt_transform.rs":"913dfeab4e77aa46e1f92eccd98c305553d69d0c3f5ff2d032587a3d6c3a97e0","src/os/macos/identity.rs":"f49e095142532540d2524eef6810c4201c1a28202f8f8986e8243a85939279ea","src/os/macos/import_export.rs":"bf11d037b1c2088a484b1323303f3952039078d9ac4d7bef242279ffa0d3ff10","src/os/macos/item.rs":"72aff126819036bbfcaca2bfeba40130cacbd6295158ee30a27c4b3de7d58b9e","src/os/macos/key.rs":"470af76b7e41a79fe3f39fba2111efae1536ac729ef44f4e6b481c95a437c76d","src/os/macos/keychain.rs":"be5a93aa25d9f8b7a319bf42eb41bbd2a0bdf633c970dcf846712523d7497770","src/os/macos/keychain_item.rs":"da209640bf61c902faf204bc6de88590d208ebcb105eef8183063a60440b8fb6","src/os/macos/mod.rs":"28e68e8bbc4053db35dd0455315f8c70999c21a8db1771878285fe293d669d3c","src/os/macos/passwords.rs":"a69c124661d685f8b8bf9869b8cc14439311528a17e3db701e90569b2c9243f3","src/os/macos/secure_transport.rs":"2345a2b2ee8e2a52d6046c2cd8cbb123cc2a82fcf546a1e01efbf5e9b1b88746","src/os/macos/transform.rs":"00e25dd4e729883f4bc7b07e003fe7e7f1e4957dcadd7f72f712835cb59e58d9","src/os/mod.rs":"287f2cd9a3b93f5db523974ac5f395cd6e5d1245d96c9782300d35bc5762c2f3","src/passwords.rs":"d931bebc766656959d0777d606419c70650bd5755ad8d3ec87e3b97055c83b07","src/policy.rs":"46e5269668185384438f1035f0615566366f276b7e94590e9748f361df131bb8","src/random.rs":"f6b6fe79f0fba41be4f5c7ccbb4090608fc3e8885e43c342bef2387107f5b543","src/secure_transport.rs":"aecf99344b286b22b5159d553b1beffb16c3c1235635b5bc89d26c9ea6097a2c","src/trust.rs":"eeecf6b609cad9a31d5a6f6e3fed6cb11a0b6405dc112f6951704c738c35efea","src/trust_settings.rs":"f8479c857287d849aabee94561ec02677635da801254631cd82dd0d3482da932"},"package":"2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"}

453
zeroidc/vendor/security-framework/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,453 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "core-foundation"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "data-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
[[package]]
name = "der-oid-macro"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c73af209b6a5dc8ca7cbaba720732304792cddc933cfea3d74509c2b1ef2f436"
dependencies = [
"num-bigint",
"num-traits",
"syn",
]
[[package]]
name = "der-parser"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cddf120f700b411b2b02ebeb7f04dc0b7c8835909a6c2f52bf72ed0dd3433b2"
dependencies = [
"der-oid-macro",
"nom",
"num-bigint",
"num-traits",
"rusticata-macros",
]
[[package]]
name = "env_logger"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[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 = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[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.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
dependencies = [
"memchr",
"minimal-lexical",
"version_check",
]
[[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.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "oid-registry"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe554cb2393bc784fd678c82c84cc0599c31ceadc7f03a594911f822cb8d1815"
dependencies = [
"der-parser",
]
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "rusticata-macros"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65c52377bb2288aa522a0c8208947fada1e0c76397f108cc08f57efe6077b50d"
dependencies = [
"nom",
]
[[package]]
name = "security-framework"
version = "2.6.1"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"env_logger",
"hex",
"libc",
"log",
"num-bigint",
"security-framework-sys",
"tempdir",
"x509-parser",
]
[[package]]
name = "security-framework-sys"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
"rand",
"remove_dir_all",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[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 = "x509-parser"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc90836a84cb72e6934137b1504d0cae304ef5d83904beb0c8d773bbfe256ed"
dependencies = [
"base64",
"chrono",
"data-encoding",
"der-parser",
"lazy_static",
"nom",
"oid-registry",
"rusticata-macros",
"thiserror",
]

View File

@@ -0,0 +1,85 @@
# 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 = "2018"
name = "security-framework"
version = "2.6.1"
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"
[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.2"
[dependencies.core-foundation-sys]
version = "0.8.3"
[dependencies.libc]
version = "0.2.100"
[dependencies.log]
version = "0.4.14"
optional = true
[dependencies.num-bigint]
version = "0.4.3"
optional = true
[dependencies.security-framework-sys]
version = "2.6.0"
default-features = false
[dev-dependencies.env_logger]
version = "0.9.0"
[dev-dependencies.hex]
version = "0.4.3"
[dev-dependencies.tempdir]
version = "0.3.7"
[dev-dependencies.x509-parser]
version = "0.12.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_9 = ["security-framework-sys/OSX_10_9"]
alpn = []
default = ["OSX_10_9"]
nightly = []
serial-number-bigint = ["num-bigint"]
session-tickets = []
[badges.maintenance]
status = "passively-maintained"

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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."

View File

@@ -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));
}

View File

@@ -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);
}
}
}}

View File

@@ -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);
}
}
}}

View File

@@ -0,0 +1,779 @@
//! 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};
use core_foundation::base::{CFTypeRef, TCFType};
use core_foundation::bundle::CFBundleRef;
use core_foundation::dictionary::{CFDictionary, CFDictionaryRef};
use core_foundation::string::{CFString, CFStringRef};
use security_framework_sys::authorization as sys;
use security_framework_sys::base::errSecConversionError;
use std::mem::MaybeUninit;
use std::os::raw::c_void;
use std::{
convert::TryFrom,
ffi::{CStr, CString},
fs::File,
};
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).
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]
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)]
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.
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.
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<'a> 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(
rights: Option<AuthorizationItemSetStorage>,
environment: Option<AuthorizationItemSetStorage>,
flags: Flags,
) -> Result<Self> {
let rights_ptr = rights.as_ref().map_or(std::ptr::null(), |r| {
&r.set as *const sys::AuthorizationItemSet
});
let env_ptr = environment.as_ref().map_or(std::ptr::null(), |e| {
&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())
}
// 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(())
}
}

View File

@@ -0,0 +1,92 @@
//! Support types for other modules.
#[cfg(target_os = "macos")]
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]
pub fn from_code(code: OSStatus) -> Self {
Self(NonZeroI32::new(code as i32).unwrap_or_else(|| NonZeroI32::new(1).unwrap()))
}
/// Returns a string describing the current error, if available.
#[inline(always)]
pub fn message(self) -> Option<String> {
self.inner_message()
}
#[cfg(target_os = "macos")]
#[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())
}
}
}
#[cfg(not(target_os = "macos"))]
#[inline(always)]
fn inner_message(&self) -> Option<String> {
None
}
/// Returns the code of the current error.
#[inline(always)]
pub fn code(self) -> OSStatus {
self.0.get() as _
}
}
impl From<OSStatus> for Error {
#[inline(always)]
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 {}

View File

@@ -0,0 +1,283 @@
//! Certificate support.
use core_foundation::array::{CFArray, CFArrayRef};
use core_foundation::base::TCFType;
use core_foundation::data::CFData;
use core_foundation::string::CFString;
use core_foundation_sys::base::kCFAllocatorDefault;
use security_framework_sys::base::{errSecParam, SecCertificateRef};
#[cfg(target_os = "ios")]
use security_framework_sys::base::{errSecSuccess, errSecNotTrusted};
use security_framework_sys::certificate::*;
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(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;
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use security_framework_sys::item::*;
#[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.
pub fn to_der(&self) -> Vec<u8> {
unsafe {
let der_data = SecCertificateCopyData(self.0);
CFData::wrap_under_create_rule(der_data).to_vec()
}
}
/// Returns a human readable summary of this certificate.
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.
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.
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() {
Err(CFError::wrap_under_create_rule(error))
} else {
Ok(CFData::wrap_under_create_rule(serial_number).to_vec())
}
}
}
/// 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"))]
fn pk_to_der(&self, public_key: key::SecKey) -> Option<Vec<u8>> {
let public_key_attributes = public_key.attributes();
let public_key_type = public_key_attributes
.find(unsafe { kSecAttrKeyType } as *const ::std::os::raw::c_void)?;
let public_keysize = public_key_attributes
.find(unsafe { kSecAttrKeySizeInBits } as *const ::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))?;
#[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()
}
}
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
fn get_asn1_header_bytes(pkt: CFString, ksz: u32) -> Option<&'static [u8]> {
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);
}
}

View File

@@ -0,0 +1,244 @@
//! 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)]
pub fn from_raw(raw: SSLCipherSuite) -> Self {
Self(raw)
}
#[inline(always)]
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
}

View File

@@ -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,
}
}

View File

@@ -0,0 +1,68 @@
//! Identity support.
use core_foundation::base::TCFType;
use security_framework_sys::base::SecIdentityRef;
use security_framework_sys::identity::*;
use std::fmt;
use std::ptr;
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))
}
}
}
#[cfg(test)]
mod test {
use super::SecIdentity;
#[test]
fn identity_has_send_bound() {
fn assert_send<T: Send>() {}
assert_send::<SecIdentity>();
}
}

View File

@@ -0,0 +1,173 @@
//! 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)]
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 as *const _).to_string());
let key_id = raw_item
.find(kSecImportItemKeyID)
.map(|key_id| CFData::wrap_under_get_rule(*key_id as *const _).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 as *const _).map(
|cert_chain| {
CFArray::<SecCertificate>::wrap_under_get_rule(*cert_chain as *const _)
.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());
}
}

View File

@@ -0,0 +1,423 @@
//! Support to search for items in a keychain.
use core_foundation::array::CFArray;
use core_foundation::base::{CFType, TCFType};
use core_foundation::boolean::CFBoolean;
use core_foundation::data::CFData;
use core_foundation::date::CFDate;
use core_foundation::dictionary::CFDictionary;
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::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)]
pub fn generic_password() -> Self {
unsafe { Self(kSecClassGenericPassword) }
}
/// Look for `SecKeychainItem`s corresponding to internet passwords.
#[inline(always)]
pub fn internet_password() -> Self {
unsafe { Self(kSecClassInternetPassword) }
}
/// Look for `SecCertificate`s.
#[inline(always)]
pub fn certificate() -> Self {
unsafe { Self(kSecClassCertificate) }
}
/// Look for `SecKey`s.
#[inline(always)]
pub fn key() -> Self {
unsafe { Self(kSecClassKey) }
}
/// Look for `SecIdentity`s.
#[inline(always)]
pub fn identity() -> Self {
unsafe { Self(kSecClassIdentity) }
}
#[inline]
fn to_value(self) -> CFType {
unsafe { CFType::wrap_under_get_rule(self.0 as *const _) }
}
}
/// 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).as_CFType() },
Self::Max(l) => CFNumber::from(l).as_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>,
load_refs: bool,
load_attributes: bool,
load_data: bool,
limit: Option<Limit>,
label: Option<CFString>,
access_group: Option<CFString>,
}
#[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)]
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
}
/// 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
}
/// 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 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 self.load_refs {
params.push((
CFString::wrap_under_get_rule(kSecReturnRef),
CFBoolean::true_value().as_CFType(),
));
}
if self.load_attributes {
params.push((
CFString::wrap_under_get_rule(kSecReturnAttributes),
CFBoolean::true_value().as_CFType(),
));
}
if self.load_data {
params.push((
CFString::wrap_under_get_rule(kSecReturnData),
CFBoolean::true_value().as_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 access_group) = self.access_group {
params.push((
CFString::wrap_under_get_rule(kSecAttrAccessGroup),
access_group.as_CFType(),
));
}
let params = CFDictionary::from_CFType_pairs(&params);
let mut ret = ptr::null();
cvt(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?;
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.
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 as *const _);
let val: String = match CFGetTypeID(*v) {
cfstring if cfstring == CFString::type_id() => {
format!("{}", CFString::wrap_under_get_rule(*v as *const _))
}
cfdata if cfdata == CFData::type_id() => {
let buf = CFData::wrap_under_get_rule(*v as *const _);
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,
}
}
}
#[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);
}
}

View File

@@ -0,0 +1,84 @@
//! Encryption key support
use core_foundation::base::TCFType;
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use core_foundation::base::ToVoid;
#[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::error::{CFError, CFErrorRef};
use security_framework_sys::base::SecKeyRef;
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
pub use security_framework_sys::key::Algorithm;
use security_framework_sys::key::SecKeyGetTypeID;
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use security_framework_sys::key::{
SecKeyCopyAttributes, SecKeyCopyExternalRepresentation, SecKeyCreateSignature,
};
use std::fmt;
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 SecKeyCopyAttributes
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
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"))]
/// Creates the cryptographic signature for a block of data using a private
/// key and specified algorithm.
pub fn create_signature(
&self,
algorithm: Algorithm,
input: impl AsRef<[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_ref()).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())
}
}
}
// FIXME
impl fmt::Debug for SecKey {
#[cold]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "SecKey")
}
}

View File

@@ -0,0 +1,82 @@
//! 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;
#[cfg(target_os = "macos")]
pub mod authorization;
pub mod base;
pub mod certificate;
pub mod cipher_suite;
pub mod identity;
pub mod import_export;
pub mod item;
pub mod key;
pub mod os;
pub mod passwords;
pub mod policy;
pub mod random;
pub mod secure_transport;
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))
}
}

View File

@@ -0,0 +1,14 @@
//! Access control functionality.
use core_foundation::base::TCFType;
use security_framework_sys::access::SecAccessGetTypeID;
use security_framework_sys::base::SecAccessRef;
declare_TCFType! {
/// A type representing access control settings.
SecAccess, SecAccessRef
}
impl_TCFType!(SecAccess, SecAccessRef, SecAccessGetTypeID);
unsafe impl Sync for SecAccess {}
unsafe impl Send for SecAccess {}

View File

@@ -0,0 +1,266 @@
//! 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.
pub fn get(&self, oid: CertificateOid) -> Option<CertificateProperty> {
unsafe {
self.0.find(oid.as_ptr() as *const 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.
pub fn label(&self) -> CFString {
unsafe {
CFString::wrap_under_get_rule(*self.0.get(kSecPropertyKeyLabel.to_void()) as *const _)
}
}
/// Returns an enum of the underlying data for this property.
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 as *const _,
)))
} else if type_ == CFString::wrap_under_get_rule(kSecPropertyTypeString) {
PropertyType::String(CFString::wrap_under_get_rule(*value as *const _))
} 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)]
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");
}
}

View File

@@ -0,0 +1,29 @@
//! 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::*;
/// An identifier of a property of a certificate.
#[derive(Copy, Clone)]
pub struct CertificateOid(CFStringRef);
#[allow(missing_docs)]
impl CertificateOid {
#[inline(always)]
pub fn x509_v1_signature_algorithm() -> Self {
unsafe { Self(kSecOIDX509V1SignatureAlgorithm) }
}
/// Returns the underlying raw pointer corresponding to this OID.
#[inline(always)]
pub fn as_ptr(&self) -> CFStringRef {
self.0
}
/// Returns the string representation of the OID.
#[inline]
pub fn to_str(&self) -> CFString {
unsafe { CFString::wrap_under_get_rule(self.0) }
}
}

View File

@@ -0,0 +1,484 @@
//! 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.
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
);
}
}

View File

@@ -0,0 +1,187 @@
//! 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)]
pub fn hmac_md5() -> Self {
unsafe { Self(kSecDigestHMACMD5) }
}
#[inline(always)]
pub fn hmac_sha1() -> Self {
unsafe { Self(kSecDigestHMACSHA1) }
}
#[inline(always)]
pub fn hmac_sha2() -> Self {
unsafe { Self(kSecDigestHMACSHA2) }
}
#[inline(always)]
pub fn md2() -> Self {
unsafe { Self(kSecDigestMD2) }
}
#[inline(always)]
pub fn md4() -> Self {
unsafe { Self(kSecDigestMD4) }
}
#[inline(always)]
pub fn md5() -> Self {
unsafe { Self(kSecDigestMD5) }
}
#[inline(always)]
pub fn sha1() -> Self {
unsafe { Self(kSecDigestSHA1) }
}
#[inline(always)]
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)]
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"
);
}
}

View File

@@ -0,0 +1,245 @@
//! 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)]
pub fn none() -> Self {
unsafe { Self(kSecPaddingNoneKey) }
}
/// Use PKCS#1 padding.
#[inline(always)]
pub fn pkcs1() -> Self {
unsafe { Self(kSecPaddingPKCS1Key) }
}
/// Use PKCS#5 padding.
#[inline(always)]
pub fn pkcs5() -> Self {
unsafe { Self(kSecPaddingPKCS5Key) }
}
/// Use PKCS#7 padding.
#[inline(always)]
pub fn pkcs7() -> Self {
unsafe { Self(kSecPaddingPKCS7Key) }
}
/// Use OAEP padding.
#[inline(always)]
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)]
pub fn none() -> Self {
unsafe { Self(kSecModeNoneKey) }
}
#[inline(always)]
pub fn ecb() -> Self {
unsafe { Self(kSecModeECBKey) }
}
#[inline(always)]
pub fn cbc() -> Self {
unsafe { Self(kSecModeCBCKey) }
}
#[inline(always)]
pub fn cfb() -> Self {
unsafe { Self(kSecModeCFBKey) }
}
#[inline(always)]
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)]
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());
}
}

View File

@@ -0,0 +1,85 @@
//! OSX specific extensions to identity functionality.
use core_foundation::array::CFArray;
use core_foundation::base::TCFType;
use security_framework_sys::identity::*;
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.
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(
keychains.as_CFTypeRef(),
certificate.as_concrete_TypeRef(),
&mut identity,
))?;
Ok(Self::wrap_under_create_rule(identity))
}
}
}
#[cfg(test)]
mod test {
use tempdir::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::new("certificate"));
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::new("private_key"));
let identity = identity(dir.path());
p!(identity.private_key());
}
#[test]
fn with_certificate() {
let dir = p!(TempDir::new("with_certificate"));
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));
}
}

View File

@@ -0,0 +1,344 @@
//! 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)]
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().as_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).as_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(_) => &mut raw_items as *mut _,
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 tempdir::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::new("identity").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::new("identity").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::new("pkcs12_import").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"
);
}
}

View File

@@ -0,0 +1,102 @@
//! OSX specific functionality for items.
use core_foundation::base::TCFType;
use core_foundation::string::CFString;
use core_foundation_sys::string::CFStringRef;
use security_framework_sys::item::*;
use crate::item::ItemSearchOptions;
use crate::os::macos::keychain::SecKeychain;
use crate::ItemSearchOptionsInternals;
/// Types of `SecKey`s.
#[derive(Debug, Copy, Clone)]
pub struct KeyType(CFStringRef);
#[allow(missing_docs)]
impl KeyType {
#[inline(always)]
pub fn rsa() -> Self {
unsafe { Self(kSecAttrKeyTypeRSA) }
}
#[inline(always)]
pub fn dsa() -> Self {
unsafe { Self(kSecAttrKeyTypeDSA) }
}
#[inline(always)]
pub fn aes() -> Self {
unsafe { Self(kSecAttrKeyTypeAES) }
}
#[inline(always)]
pub fn des() -> Self {
unsafe { Self(kSecAttrKeyTypeDES) }
}
#[inline(always)]
pub fn triple_des() -> Self {
unsafe { Self(kSecAttrKeyType3DES) }
}
#[inline(always)]
pub fn rc4() -> Self {
unsafe { Self(kSecAttrKeyTypeRC4) }
}
#[inline(always)]
pub fn cast() -> Self {
unsafe { Self(kSecAttrKeyTypeCAST) }
}
#[cfg(feature = "OSX_10_9")]
#[inline(always)]
pub fn ec() -> Self {
unsafe { Self(kSecAttrKeyTypeEC) }
}
pub(crate) fn to_str(self) -> CFString {
unsafe { CFString::wrap_under_get_rule(self.0) }
}
}
/// 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 tempdir::TempDir;
#[test]
fn find_certificate() {
let dir = p!(TempDir::new("find_certificate"));
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()));
}
}

View File

@@ -0,0 +1,39 @@
//! 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::*;
use std::ptr;
use crate::key::SecKey;
use crate::os::macos::item::KeyType;
/// 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))
}
}
}
}

View File

@@ -0,0 +1,277 @@
//! 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();
// FIXME
let path_name = CString::new(path_name).unwrap();
unsafe {
let mut keychain = ptr::null_mut();
cvt(SecKeychainOpen(path_name.as_ptr(), &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() as *const _, 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)]
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() as *const 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]
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 tempdir::TempDir;
use super::*;
#[test]
fn create_options() {
let dir = TempDir::new("keychain").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());
}
}

View File

@@ -0,0 +1,27 @@
//! 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 {}
// FIXME
impl fmt::Debug for SecKeychainItem {
#[cold]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str("SecKeychainItem")
}
}

View File

@@ -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
}
}

View File

@@ -0,0 +1,524 @@
//! 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() as *const _,
))?;
}
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() as *const _,
account.len() as u32,
account.as_ptr() as *const _,
&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() as *const _,
security_domain.map_or(0, |s| s.len() as u32),
security_domain
.map_or(ptr::null(), |s| s.as_ptr() as *const _),
account.len() as u32,
account.as_ptr() as *const _,
path.len() as u32,
path.as_ptr() as *const _,
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() as *const _,
account.len() as u32,
account.as_ptr() as *const _,
password.len() as u32,
password.as_ptr() as *const _,
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() as *const _,
security_domain.map_or(0, |s| s.len() as u32),
security_domain
.map_or(ptr::null(), |s| s.as_ptr() as *const _),
account.len() as u32,
account.as_ptr() as *const _,
path.len() as u32,
path.as_ptr() as *const _,
port.unwrap_or(0),
protocol,
authentication_type,
password.len() as u32,
password.as_ptr() as *const _,
ptr::null_mut(),
))?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::os::macos::keychain::{CreateOptions, SecKeychain};
use tempdir::TempDir;
fn temp_keychain_setup(name: &str) -> (TempDir, SecKeychain) {
let dir = TempDir::new("passwords").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);
}
}

View File

@@ -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 as *const u8, len)))
}
}
}
fn set_diffie_hellman_params(&mut self, dh_params: &[u8]) -> Result<()> {
unsafe {
cvt(SSLSetDiffieHellmanParams(
self.as_inner(),
dh_params.as_ptr() as *const _,
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 tempdir::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::new("server_client"));
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::new("server_client_builders"));
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::new("client_bad_cert"));
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::new("client_bad_cert"));
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::new("negotiated_cipher"));
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(), &params[..]);
}
#[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::new("negotiated_cipher"));
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::new("negotiated_cipher"));
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::new("negotiated_cipher"));
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::new("negotiated_cipher"));
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::new("close"));
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::new("short_read"));
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());
}
}

View File

@@ -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))
}
}
}

View File

@@ -0,0 +1,4 @@
//! OS specific extensions.
#[cfg(target_os = "macos")]
pub mod macos;

View File

@@ -0,0 +1,425 @@
//! 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::{cvt, Error};
use core_foundation::base::{CFType, TCFType};
use core_foundation::boolean::CFBoolean;
use core_foundation::data::CFData;
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::CFNumber;
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::{
kSecAttrAccount, kSecAttrAuthenticationType, kSecAttrPath, kSecAttrPort, kSecAttrProtocol,
kSecAttrSecurityDomain, kSecAttrServer, kSecAttrService, kSecClass, kSecClassGenericPassword,
kSecClassInternetPassword, 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 query = generic_password_query(service, account);
set_password_internal(&mut query, 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 query = generic_password_query(service, account);
query.push((
unsafe { CFString::wrap_under_get_rule(kSecReturnData) },
CFBoolean::from(true).as_CFType(),
));
let params = CFDictionary::from_CFType_pairs(&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 query = generic_password_query(service, account);
let params = CFDictionary::from_CFType_pairs(&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 query = internet_password_query(
server,
security_domain,
account,
path,
port,
protocol,
authentication_type,
);
set_password_internal(&mut query, 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 query = internet_password_query(
server,
security_domain,
account,
path,
port,
protocol,
authentication_type,
);
query.push((
unsafe { CFString::wrap_under_get_rule(kSecReturnData) },
CFBoolean::from(true).as_CFType(),
));
let params = CFDictionary::from_CFType_pairs(&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 query = internet_password_query(
server,
security_domain,
account,
path,
port,
protocol,
authentication_type,
);
let params = CFDictionary::from_CFType_pairs(&query);
cvt(unsafe { SecItemDelete(params.as_concrete_TypeRef()) })
}
// Generic passwords are identified by service and account. They have other
// attributes, but this interface doesn't allow specifying them.
fn generic_password_query(service: &str, account: &str) -> Vec<(CFString, CFType)> {
let query = vec![
(
unsafe { CFString::wrap_under_get_rule(kSecClass) },
unsafe { CFString::wrap_under_get_rule(kSecClassGenericPassword).as_CFType() },
),
(
unsafe { CFString::wrap_under_get_rule(kSecAttrService) },
CFString::from(service).as_CFType(),
),
(
unsafe { CFString::wrap_under_get_rule(kSecAttrAccount) },
CFString::from(account).as_CFType(),
),
];
query
}
// Internet passwords are identified by a number of attributes.
// They can have others, but this interface doesn't allow specifying them.
fn internet_password_query(
server: &str,
security_domain: Option<&str>,
account: &str,
path: &str,
port: Option<u16>,
protocol: SecProtocolType,
authentication_type: SecAuthenticationType,
) -> Vec<(CFString, CFType)> {
let mut query = vec![
(
unsafe { CFString::wrap_under_get_rule(kSecClass) },
unsafe { CFString::wrap_under_get_rule(kSecClassInternetPassword) }.as_CFType(),
),
(
unsafe { CFString::wrap_under_get_rule(kSecAttrServer) },
CFString::from(server).as_CFType(),
),
(
unsafe { CFString::wrap_under_get_rule(kSecAttrPath) },
CFString::from(path).as_CFType(),
),
(
unsafe { CFString::wrap_under_get_rule(kSecAttrAccount) },
CFString::from(account).as_CFType(),
),
(
unsafe { CFString::wrap_under_get_rule(kSecAttrProtocol) },
CFNumber::from(protocol as i32).as_CFType(),
),
(
unsafe { CFString::wrap_under_get_rule(kSecAttrAuthenticationType) },
CFNumber::from(authentication_type as i32).as_CFType(),
),
];
if let Some(domain) = security_domain {
query.push((
unsafe { CFString::wrap_under_get_rule(kSecAttrSecurityDomain) },
CFString::from(domain).as_CFType(),
))
}
if let Some(port) = port {
query.push((
unsafe { CFString::wrap_under_get_rule(kSecAttrPort) },
CFNumber::from(port as i32).as_CFType(),
))
}
query
}
// 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(query: &mut Vec<(CFString, CFType)>, password: &[u8]) -> Result<()> {
let query_len = query.len();
query.push((
unsafe { CFString::wrap_under_get_rule(kSecValueData) },
CFData::from_buffer(password).as_CFType(),
));
let params = CFDictionary::from_CFType_pairs(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(&query[0..query_len]);
let update = CFDictionary::from_CFType_pairs(&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 security_framework_sys::base::errSecItemNotFound;
use super::*;
#[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");
}
}

View File

@@ -0,0 +1,103 @@
//! Security Policies support.
use core_foundation::base::TCFType;
#[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
use core_foundation::base::CFOptionFlags;
use core_foundation::string::CFString;
use security_framework_sys::base::SecPolicyRef;
#[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
use security_framework_sys::base::errSecParam;
use security_framework_sys::policy::*;
use std::fmt;
use std::ptr;
#[cfg(any(feature = "OSX_10_9", target_os = "ios"))]
use crate::Error;
use crate::secure_transport::SslProtocolSide;
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.
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"));
}
}

View File

@@ -0,0 +1,39 @@
//! Randomness support.
use security_framework_sys::random::*;
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() as *mut _) } == 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();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,393 @@
//! 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]
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)]
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")]
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 as *mut _))
}
}
}
}
#[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());
}
}

View File

@@ -0,0 +1,274 @@
//! 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 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)]
pub enum Domain {
/// Per-user trust settings
User,
/// Locally administered, system-wide trust settings
Admin,
/// System trust settings
System,
}
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)]
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 })
}
/// 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() as *const _)
.map(|name| unsafe { CFString::wrap_under_get_rule(*name as *const _) });
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() as *const _)
.map(|num| unsafe { CFNumber::wrap_under_get_rule(*num as *const _) })
.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()));
}
}