diff --git a/__security/keychain-services/Cargo.lock b/__security/keychain-services/Cargo.lock new file mode 100644 index 0000000..3ed146e --- /dev/null +++ b/__security/keychain-services/Cargo.lock @@ -0,0 +1,201 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + +[[package]] +name = "keychain-services" +version = "0.1.0" +dependencies = [ + "keychain-services 0.1.2", +] + +[[package]] +name = "keychain-services" +version = "0.1.2" +dependencies = [ + "core-foundation", + "failure", + "failure_derive", + "zeroize", +] + +[[package]] +name = "libc" +version = "0.2.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "syn" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/__security/keychain-services/Cargo.toml b/__security/keychain-services/Cargo.toml new file mode 100644 index 0000000..37063af --- /dev/null +++ b/__security/keychain-services/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "keychain-services" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +keychain-services = {version = "0.1.1", path = "./keychain-services.rs"} + diff --git a/__security/keychain-services/keychain-services.rs/.gitignore b/__security/keychain-services/keychain-services.rs/.gitignore new file mode 100644 index 0000000..088ba6b --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/__security/keychain-services/keychain-services.rs/.travis.yml b/__security/keychain-services/keychain-services.rs/.travis.yml new file mode 100644 index 0000000..6b953aa --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/.travis.yml @@ -0,0 +1,36 @@ +language: rust +cache: cargo +os: osx +rust: stable + +branches: + only: + - master + +install: +- rustup component add rustfmt-preview +- rustup component add clippy-preview +- command -v cargo-audit >/dev/null 2>&1 || cargo install cargo-audit + +script: +# build +- cargo build --no-default-features +- cargo build + +# test +- cargo test + +# build (but do not run) interactive tests +- cargo test --features=interactive-tests --no-run + +# audit +- cargo audit + +# lint +- cargo fmt --version +- cargo fmt -- --check +- cargo clippy --version +- cargo clippy + +# doc build +- cargo doc --no-deps diff --git a/__security/keychain-services/keychain-services.rs/CHANGES.md b/__security/keychain-services/keychain-services.rs/CHANGES.md new file mode 100644 index 0000000..ebfcea7 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/CHANGES.md @@ -0,0 +1,28 @@ +## [0.1.1] (2018-12-10) + +- Update to Rust 2018 edition ([#20]) +- Update links to use keychain-services.rs repo name ([#19]) + +## [0.1.0] (2018-11-06) + +- Initial password support ([#17]) +- Factor related types into modules ([#16]) +- Remove `Sec*` prefix from all type names ([#15]) +- Implement keychain types and refactor builders ([#14]) + +## 0.0.2 (2018-11-01) + +- Build and link to documentation. + +## 0.0.1 (2018-10-31) + +- Initial release + +[0.1.1]: https://github.com/iqlusioninc/keychain-services.rs/pull/21 +[#20]: https://github.com/iqlusioninc/keychain-services.rs/pull/20 +[#19]: https://github.com/iqlusioninc/keychain-services.rs/pull/19 +[0.1.0]: https://github.com/iqlusioninc/keychain-services.rs/pull/18 +[#17]: https://github.com/iqlusioninc/keychain-services.rs/pull/17 +[#16]: https://github.com/iqlusioninc/keychain-services.rs/pull/16 +[#15]: https://github.com/iqlusioninc/keychain-services.rs/pull/15 +[#14]: https://github.com/iqlusioninc/keychain-services.rs/pull/14 diff --git a/__security/keychain-services/keychain-services.rs/CODE_OF_CONDUCT.md b/__security/keychain-services/keychain-services.rs/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..2633eef --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [oss@iqlusion.io](mailto:oss@iqlusion.io). +All complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + diff --git a/__security/keychain-services/keychain-services.rs/Cargo.toml b/__security/keychain-services/keychain-services.rs/Cargo.toml new file mode 100644 index 0000000..ca71169 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "keychain-services" +description = """ + Rust access to macOS Keychain Services, including TouchID-guarded + access to cryptographic keys stored in the Secure Enclave + Processor (SEP). + """ +version = "0.1.2" +authors = ["Tony Arcieri "] +license = "Apache-2.0" +homepage = "https://keychain-services.rs/" +documentation = "https://keychain-services.rs/docs/" +repository = "https://github.com/iqlusioninc/keychain-services.rs/" +readme = "README.md" +categories = ["api-bindings", "authentication", "cryptography", "hardware-support"] +keywords = ["ecdsa", "macos", "keychain", "touchid", "signatures"] +edition = "2018" + +[badges] +maintenance = { status = "experimental" } +travis-ci = { repository = "iqlusioninc/keychain-services.rs" } + +[dependencies] +core-foundation = "0.7" +failure = "0.1" +failure_derive = "0.1" +zeroize = "1.1" + +[dev-dependencies] +ring = "0.13" +tempfile = "3" +untrusted = "0.6" + +[features] +interactive-tests = [] diff --git a/__security/keychain-services/keychain-services.rs/LICENSE-APACHE b/__security/keychain-services/keychain-services.rs/LICENSE-APACHE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/__security/keychain-services/keychain-services.rs/LICENSE-MIT b/__security/keychain-services/keychain-services.rs/LICENSE-MIT new file mode 100644 index 0000000..95d708f --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/LICENSE-MIT @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) iqlusion + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/__security/keychain-services/keychain-services.rs/Makefile b/__security/keychain-services/keychain-services.rs/Makefile new file mode 100644 index 0000000..ba45042 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/Makefile @@ -0,0 +1,15 @@ +target/doc/keychain_services: + cargo rustdoc + +docs: target/doc/keychain_services + -git branch -D gh-pages + git checkout --orphan gh-pages + git reset README.md + git reset --hard + cp -r target/doc/* . + cp -r keychain_services docs + rm -rf target + echo 'keychain-services.rs' > CNAME + git add . + git commit -m "Generate docs using 'make docs'" + @echo "Use 'git push -f origin gh-pages' to deploy" diff --git a/__security/keychain-services/keychain-services.rs/README.md b/__security/keychain-services/keychain-services.rs/README.md new file mode 100644 index 0000000..d1451c6 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/README.md @@ -0,0 +1,103 @@ +# Keychain Services for Rust 🔐 iqlusion + +[![Crate][crate-image]][crate-link] +[![Build Status][build-image]][build-link] +[![Apache 2.0 Licensed][license-image]][license-link] +![Maintenance Status: Experimental][maintenance-image] + +Rust binding for macOS Keychain Services, including TouchID-guarded access to +cryptographic keys stored in the Secure Enclave Processor (SEP). + +This binding aims to provide a thin wrapper using largely the same type names +as Keychain Services itself, but also provide a safe, mostly idiomatic API +which does not rely on e.g. Core Foundation types. + +**NOTE:** This is an unofficial binding which is in no way affiliated with Apple! + +[Documentation] + +## Status + +This crate is **experimental** and may have bugs/memory safety issues. +*USE AT YOUR OWN RISK!* + +Below is a rough outline of the Keychain Service API and what is supported +by this crate: + +- [ ] Keychains (`SecKeychain`) + - [x] Creating keychains + - [x] Deleting keychains + - [ ] Open keychain (`SecKeychainOpen`) + - [ ] Keychain status (`SecKeychainGetStatus`) + - [ ] Keychain version (`SecKeychainGetVersion`) + - [ ] Set default keychain (`SecKeychainSetDefault`) +- [ ] Keychain Items (`SecKeychainItem`) + - [x] Creating keychain items + - [x] Fetching keychain items + - [x] Getting keychain item attributes + - [ ] Deleting keychain items +- [ ] Certificates / Identities (`SecCertificate`) + - [ ] Creating certificates + - [ ] Deleting certificates + - [ ] Querying certificates + - [ ] Signing certificates +- [x] Cryptographic keys (`SecKey`) + - [x] Generating cryptographic keys + - [x] Importing cryptographic keys + - [x] Exporting cryptographic keys + - [x] Deleting cryptographic keys + - [x] Querying cryptographic keys + - [x] Querying cryptographic key attributes + - [x] Digital signatures (ECDSA/RSA) + - [x] Encryption +- [x] Passwords + - [x] Creating passwords + - [x] Querying passwords + - [ ] Deleting passwords + +## Tests + +This crate has two suites of tests: + +- Core: `cargo test` - run a minimal set of tests (e.g. in CI) that work + everywhere, but don't cover all functionality. +- Interactive: `cargo test --features=interactive-tests --no-run` + compile tests which require user interactions, and additionally must be + signed by macOS's code signing in order to work. See code signing notes. + +## Code Signing + +The Keychain Service API requires signed code to access much of its +functionality. Accessing many APIs from an unsigned app will return +an `ErrorKind::MissingEntitlement`. + +Follow the instructions here to create a self-signed code signing certificate: + + +You will need to use the [codesign] command-line utility (or XCode) to sign +your code before it will be able to access most Keychain Services API +functionality. + +## 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. + +[crate-image]: https://img.shields.io/crates/v/keychain-services.svg +[crate-link]: https://crates.io/crates/keychain-services +[build-image]: https://travis-ci.org/iqlusioninc/keychain-services.rs.svg?branch=master +[build-link]: https://travis-ci.org/iqlusioninc/keychain-services.rs +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[license-link]: https://github.com/iqlusioninc/keychain-services.rs/blob/master/LICENSE-APACHE +[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg +[Documentation]: https://keychain-services.rs/docs/ +[codesign]: https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html#//apple_ref/doc/uid/TP40005929-CH4-SW4 diff --git a/__security/keychain-services/keychain-services.rs/src/access.rs b/__security/keychain-services/keychain-services.rs/src/access.rs new file mode 100644 index 0000000..f3dbee4 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/access.rs @@ -0,0 +1,207 @@ +//! Keychain item access control types: ACLs and policies around usage of +//! private keys stored in the keychain. + +use crate::{attr::AttrAccessible, error::Error, ffi::*}; +use core_foundation::{ + base::{kCFAllocatorDefault, CFOptionFlags, TCFType}, + error::CFErrorRef, +}; +use std::{ + fmt::{self, Debug}, + ptr, +}; + +/// Marker trait for types which can be used as `AccessControlFlags`. +pub trait AccessControlFlag: Copy + Clone + Sized + Into {} + +/// Constraints on keychain item access. +/// +/// See "Constraints" topic under the "Topics" section of the +/// `SecAccessControlCreateFlags` documentation at: +/// +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum AccessConstraint { + /// Require either passcode or biometric auth (TouchID/FaceID). + /// + /// Wrapper for `kSecAccessControlUserPresence`. See: + /// + UserPresence, + + /// Require biometric auth (TouchID/FaceID) from any enrolled user for this device. + /// + /// Wrapper for `kSecAccessControlBiometryAny`. See: + /// + BiometryAny, + + /// Require biometric auth (TouchID/FaceID) from the current user. + /// + /// Wrapper for `kSecAccessControlBiometryCurrentSet`. See: + /// + BiometryCurrentSet, + + /// Require device passcode. + /// + /// Wrapper for `kSecAccessControlDevicePasscode`. See: + /// + DevicePasscode, +} + +impl AccessControlFlag for AccessConstraint {} + +impl From for CFOptionFlags { + fn from(constraint: AccessConstraint) -> CFOptionFlags { + match constraint { + AccessConstraint::UserPresence => 1, + AccessConstraint::BiometryAny => 1 << 1, + AccessConstraint::BiometryCurrentSet => 1 << 3, + AccessConstraint::DevicePasscode => 1 << 4, + } + } +} + +/// Conjunctions (and/or) on keychain item access. +/// +/// See "Conjunctions" topic under the "Topics" section of the +/// `SecAccessControlCreateFlags` documentation at: +/// +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum AccessConjunction { + /// Require *all* constraints be satisfied. + /// + /// Wrapper for `kSecAccessControlAnd`. See: + /// + And, + + /// Require *at least one* constraint must be satisfied. + /// + /// Wrapper for `kSecAccessControlOr`. See: + /// + Or, +} + +impl AccessControlFlag for AccessConjunction {} + +impl From for CFOptionFlags { + fn from(conjunction: AccessConjunction) -> CFOptionFlags { + match conjunction { + AccessConjunction::Or => 1 << 14, + AccessConjunction::And => 1 << 15, + } + } +} + +/// Options for keychain item access. +/// +/// See "Additional Options" topic under the "Topics" section of the +/// `SecAccessControlCreateFlags` documentation at: +/// +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum AccessOption { + /// Require private key be stored in the device's Secure Enclave. + /// + /// Wrapper for `kSecAccessControlPrivateKeyUsage`. See: + /// + PrivateKeyUsage, + + /// Generate encryption-key from an application-provided password. + /// + /// Wrapper for `kSecAccessControlApplicationPassword`. See: + /// + ApplicationPassword, +} + +impl AccessControlFlag for AccessOption {} + +impl From for CFOptionFlags { + fn from(option: AccessOption) -> CFOptionFlags { + match option { + AccessOption::PrivateKeyUsage => 1 << 30, + AccessOption::ApplicationPassword => 1 << 31, + } + } +} + +/// Access control restrictions for a particular keychain item. +/// +/// More information about restricting keychain items can be found at: +/// +/// +/// Wrapper for the `SecAccessControlCreateFlags` type: +/// +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct AccessControlFlags(CFOptionFlags); + +impl AccessControlFlags { + /// Create `SecAccessControlFlags` with no policy set + pub fn new() -> Self { + Self::default() + } + + /// Add an `AccessControlFlag` to this set of flags. + // TODO: handle illegal combinations of flags? + pub fn add(&mut self, flag: F) { + self.0 |= flag.into(); + } +} + +/// Shorthand syntax for when flags are all of the same type +impl<'a, F> From<&'a [F]> for AccessControlFlags +where + F: AccessControlFlag, +{ + fn from(flags: &[F]) -> AccessControlFlags { + let mut result = AccessControlFlags::new(); + + for flag in flags { + result.add(*flag) + } + + result + } +} + +declare_TCFType! { + /// Access control policy (a.k.a. ACL) for a keychain item, combining both a + /// set of `AccessControlFlags` and a `AttrAccessible` restriction. + /// + /// Wrapper for the `SecAccessControl`/`SecAccessControlRef` types: + /// + AccessControl, AccessControlRef +} + +impl_TCFType!(AccessControl, AccessControlRef, SecAccessControlGetTypeID); + +impl AccessControl { + /// Create a new `AccessControl` policy/ACL. + /// + /// Wrapper for the `SecAccessControlCreateWithFlags()` function: + /// + pub fn create_with_flags( + protection: AttrAccessible, + flags: AccessControlFlags, + ) -> Result { + let mut error: CFErrorRef = ptr::null_mut(); + + let result = unsafe { + SecAccessControlCreateWithFlags( + kCFAllocatorDefault, + protection.as_CFString().as_CFTypeRef(), + flags.0, + &mut error, + ) + }; + + if error.is_null() { + Ok(unsafe { Self::wrap_under_create_rule(result) }) + } else { + Err(error.into()) + } + } +} + +impl Debug for AccessControl { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: display more information about `AccessControl`s + write!(f, "SecAccessControl {{ ... }}") + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/attr.rs b/__security/keychain-services/keychain-services.rs/src/attr.rs new file mode 100644 index 0000000..bd6d03c --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/attr.rs @@ -0,0 +1,898 @@ +//! Keychain item attributes (i.e. `SecAttr*`) + +use crate::ffi::*; +use core_foundation::{ + base::{CFType, TCFType, ToVoid}, + data::CFData, + string::{CFString, CFStringRef}, +}; +use std::{ + ffi::c_void, + fmt::{self, Debug, Display}, + str::{self, Utf8Error}, +}; + +/// Trait implemented by all `Attr*` types to simplify adding them to +/// attribute dictionaries. +pub(crate) trait TAttr { + /// Get the `AttrKind` for this attribute. + fn kind(&self) -> AttrKind; + + /// Get a `CFType` object representing this attribute. + fn as_CFType(&self) -> CFType; +} + +/// Enum of attribute types passed in parameter dictionaries. This wraps up +/// access to framework constants which would otherwise be unsafe. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum AttrKind { + /// Wrapper for the `kSecAttrAccessControl` attribute key. See: + /// + AccessControl, + + /// Wrapper for the `kSecAttrAccessible` attribute key. See: + /// + Accessible, + + /// Wrapper for the `kSecAttrAccount` attribute key. See: + /// + Account, + + /// Wrapper for the `kSecAttrApplicationLabel` attribute key. See: + /// + ApplicationLabel, + + /// Wrapper for the `kSecAttrApplicationTag` attribute key. See: + /// + ApplicationTag, + + /// Wrapper for the `kSecKeyDerive` attribute key. See: + /// + Derive, + + /// Wrapper for the `kSecKeyDecrypt` attribute key. See: + /// + Decrypt, + + /// Wrapper for the `kSecKeyEncrypt` attribute key. See: + /// https://developer.apple.com/documentation/security/ksecattrcanencrypt> + Encrypt, + + /// Wrapper for the `kSecKeyExtractable` attribute key. See: + /// + Extractable, + + /// Wrapper for the `kSecAttrKeyClass` attribute key. See: + /// + KeyClass, + + /// Wrapper for the `kSecAttrKeySizeInBits` attribute key. See: + /// + KeySizeInBits, + + /// Wrapper for the `kSecAttrKeyType` attribute key. See: + /// + KeyType, + + /// Wrapper for the `kSecAttrLabel` attribute key. See: + /// + Label, + + /// Wrapper for the `kSecAttrIsPermanent` attribute key. See: + /// + Permanent, + + /// Wrapper for the `kSecAttrProtocol` attribute key. See: + /// + Protocol, + + /// Wrapper for `kSecKeySensitive` attribute key. See + /// + Sensitive, + + /// Wrapper for the `kSecAttrServer` attribute key. See: + /// + Server, + + /// Wrapper for the `kSecAttrService` attribute key. See: + /// + Service, + + /// Wrapper for the `kSecKeySign` attribute key. See: + /// + Sign, + + /// Wrapper for the `kSecAttrSynchronizable` attribute key. See: + /// + Synchronizable, + + /// Wrapper for the `kSecAttrTokenID` attribute key. See: + /// + TokenId, + + /// Wrapper for the `kSecKeyUnwrap` attribute key. See: + /// + Unwrap, + + /// Wrapper for the `kSecKeyVerify` attribute key. See: + /// + Verify, + + /// Wrapper for the `kSecKeyWrap` attribute key. See: + /// + Wrap, +} + +impl AttrKind { + /// Attempt to look up an attribute kind by its `SecKeychainAttrType`. + // TODO: cache `SecKeychainAttrTypes`? e.g. as `lazy_static` + pub(crate) fn from_tag(tag: SecKeychainAttrType) -> Option { + let result = unsafe { + if tag == SecKeychainAttrType::from(kSecAttrAccessControl) { + AttrKind::AccessControl + } else if tag == SecKeychainAttrType::from(kSecAttrAccessible) { + AttrKind::Accessible + } else if tag == SecKeychainAttrType::from(kSecAttrAccount) { + AttrKind::Account + } else if tag == SecKeychainAttrType::from(kSecAttrApplicationLabel) { + AttrKind::ApplicationLabel + } else if tag == SecKeychainAttrType::from(kSecAttrApplicationTag) { + AttrKind::ApplicationTag + } else if tag == SecKeychainAttrType::from(kSecAttrKeyClass) { + AttrKind::KeyClass + } else if tag == SecKeychainAttrType::from(kSecAttrKeySizeInBits) { + AttrKind::KeySizeInBits + } else if tag == SecKeychainAttrType::from(kSecAttrKeyType) { + AttrKind::KeyType + } else if tag == SecKeychainAttrType::from(kSecAttrIsPermanent) { + AttrKind::Permanent + } else if tag == SecKeychainAttrType::from(kSecAttrLabel) { + AttrKind::Label + } else if tag == SecKeychainAttrType::from(kSecAttrProtocol) { + AttrKind::Protocol + } else if tag == SecKeychainAttrType::from(kSecAttrServer) { + AttrKind::Server + } else if tag == SecKeychainAttrType::from(kSecAttrService) { + AttrKind::Service + } else if tag == SecKeychainAttrType::from(kSecAttrSynchronizable) { + AttrKind::Synchronizable + } else if tag == SecKeychainAttrType::from(kSecAttrTokenID) { + AttrKind::TokenId + } else if tag == SecKeychainAttrType::from(kSecAttrCanDerive) { + AttrKind::Derive + } else if tag == SecKeychainAttrType::from(kSecAttrCanDecrypt) { + AttrKind::Decrypt + } else if tag == SecKeychainAttrType::from(kSecAttrCanEncrypt) { + AttrKind::Encrypt + } else if tag == SecKeychainAttrType::from(kSecAttrCanSign) { + AttrKind::Sign + } else if tag == SecKeychainAttrType::from(kSecAttrCanVerify) { + AttrKind::Verify + } else if tag == SecKeychainAttrType::from(kSecAttrCanWrap) { + AttrKind::Wrap + } else if tag == SecKeychainAttrType::from(kSecAttrCanUnwrap) { + AttrKind::Unwrap + } else if tag == SecKeychainAttrType::from(kSecAttrIsExtractable) { + AttrKind::Extractable + } else if tag == SecKeychainAttrType::from(kSecAttrIsSensitive) { + AttrKind::Sensitive + } else { + return None; + } + }; + + Some(result) + } +} + +impl From for AttrKind { + fn from(tag: SecKeychainAttrType) -> Self { + Self::from_tag(tag).unwrap_or_else(|| panic!("invalid SecKeychainAttrType tag: {:?}", tag)) + } +} + +impl From for CFStringRef { + fn from(attr: AttrKind) -> CFStringRef { + unsafe { + match attr { + AttrKind::AccessControl => kSecAttrAccessControl, + AttrKind::Accessible => kSecAttrAccessible, + AttrKind::Account => kSecAttrAccount, + AttrKind::ApplicationLabel => kSecAttrApplicationLabel, + AttrKind::ApplicationTag => kSecAttrApplicationTag, + AttrKind::Derive => kSecAttrCanDerive, + AttrKind::Decrypt => kSecAttrCanDecrypt, + AttrKind::Encrypt => kSecAttrCanEncrypt, + AttrKind::Extractable => kSecAttrIsExtractable, + AttrKind::KeyClass => kSecAttrKeyClass, + AttrKind::KeySizeInBits => kSecAttrKeySizeInBits, + AttrKind::KeyType => kSecAttrKeyType, + AttrKind::Permanent => kSecAttrIsPermanent, + AttrKind::Sensitive => kSecAttrIsSensitive, + AttrKind::Sign => kSecAttrCanSign, + AttrKind::Verify => kSecAttrCanVerify, + AttrKind::Wrap => kSecAttrCanWrap, + AttrKind::Unwrap => kSecAttrCanUnwrap, + AttrKind::Label => kSecAttrLabel, + AttrKind::Protocol => kSecAttrProtocol, + AttrKind::Server => kSecAttrServer, + AttrKind::Service => kSecAttrService, + AttrKind::Synchronizable => kSecAttrSynchronizable, + AttrKind::TokenId => kSecAttrTokenID, + } + } + } +} + +unsafe impl ToVoid for AttrKind { + fn to_void(&self) -> *const c_void { + CFStringRef::from(*self).to_void() + } +} + +/// Keychain item accessibility restrictions (from most to least restrictive). +/// +/// More information about restricting keychain items can be found at: +/// +/// +/// Wrapper for the `kSecAttrAccessible` attribute key. See +/// "Accessibility Values" section of "Item Attribute Keys and Values": +/// +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum AttrAccessible { + /// Device is unlocked and a passcode has been set on the device. + /// + WhenPasscodeSetThisDeviceOnly, + + /// The device is unlocked (no passcode mandatory). Non-exportable. + /// + WhenUnlockedThisDeviceOnly, + + /// The device is unlocked (no passcode mandatory). + /// + WhenUnlocked, + + /// Permanently accessible after the device is first unlocked after boot. + /// Non-exportable. + /// + AfterFirstUnlockThisDeviceOnly, + + /// Permanently accessible after the device is first unlocked after boot. + /// + AfterFirstUnlock, + + /// Item is always accessible on this device. Non-exportable. + /// + AlwaysThisDeviceOnly, + + /// Item is always accessible. + /// + Always, +} + +impl AttrAccessible { + /// Get pointer to an accessibility value to associate with the + /// `kSecAttrAccessible` key for a keychain item + pub fn as_CFString(self) -> CFString { + unsafe { + CFString::wrap_under_get_rule(match self { + AttrAccessible::WhenPasscodeSetThisDeviceOnly => { + kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly + } + AttrAccessible::WhenUnlockedThisDeviceOnly => { + kSecAttrAccessibleWhenUnlockedThisDeviceOnly + } + AttrAccessible::WhenUnlocked => kSecAttrAccessibleWhenUnlocked, + AttrAccessible::AfterFirstUnlockThisDeviceOnly => { + kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly + } + AttrAccessible::AfterFirstUnlock => kSecAttrAccessibleAfterFirstUnlock, + AttrAccessible::AlwaysThisDeviceOnly => kSecAttrAccessibleAlwaysThisDeviceOnly, + AttrAccessible::Always => kSecAttrAccessibleAlways, + }) + } + } +} + +impl TAttr for AttrAccessible { + fn kind(&self) -> AttrKind { + AttrKind::Accessible + } + + fn as_CFType(&self) -> CFType { + self.as_CFString().as_CFType() + } +} + +/// Application-specific key labels, i.e. key fingerprints. +/// +/// Not to be confused with `SecAttrApplicationTag` or `SecAttrLabel`, the +/// `SecAttrApplicationLabel` value is useful for programatically looking up +/// public/private key pairs, and is set to the hash of the public key, a.k.a. +/// the public key fingerprint. +/// +/// Wrapper for the `kSecAttrApplicationLabel` attribute key. See: +/// +#[derive(Clone, Eq, PartialEq)] +pub struct AttrApplicationLabel(pub(crate) CFData); + +impl AttrApplicationLabel { + /// Create a new application label from a byte slice + pub fn new(bytes: &[u8]) -> Self { + AttrApplicationLabel(CFData::from_buffer(bytes)) + } + + /// Borrow this value as a byte slice + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } +} + +impl AsRef<[u8]> for AttrApplicationLabel { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl Debug for AttrApplicationLabel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let bytes = Vec::from(self.as_bytes()); + write!(f, "SecAttrApplicationLabel({:?})", bytes) + } +} + +impl<'a> From<&'a [u8]> for AttrApplicationLabel { + fn from(bytes: &[u8]) -> Self { + Self::new(bytes) + } +} + +impl TAttr for AttrApplicationLabel { + fn kind(&self) -> AttrKind { + AttrKind::ApplicationLabel + } + + fn as_CFType(&self) -> CFType { + self.0.as_CFType() + } +} + +/// Application-specific tags for keychain items. +/// +/// These should be unique for a specific item (i.e. named after its purpose +/// and used as the "primary key" for locating a particular keychain item), +/// and often use a reversed domain name-like syntax, e.g. +/// `io.crates.PackageSigning` +/// +/// Wrapper for the `kSecAttrApplicationTag` attribute key. See: +/// +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AttrApplicationTag(pub(crate) CFData); + +impl AttrApplicationTag { + /// Create a new application tag from a byte slice + pub fn new(bytes: &[u8]) -> Self { + AttrApplicationTag(CFData::from_buffer(bytes)) + } + + /// Borrow the tag data as a byte slice + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Borrow the tag data as a `str` (if it is valid UTF-8) + pub fn as_str(&self) -> Result<&str, Utf8Error> { + str::from_utf8(self.as_bytes()) + } +} + +impl AsRef<[u8]> for AttrApplicationTag { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl Display for AttrApplicationTag { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", String::from_utf8_lossy(&self.0)) + } +} + +impl<'a> From<&'a [u8]> for AttrApplicationTag { + fn from(bytes: &[u8]) -> Self { + Self::new(bytes) + } +} + +impl<'a> From<&'a str> for AttrApplicationTag { + fn from(string: &str) -> Self { + Self::new(string.as_bytes()) + } +} + +impl TAttr for AttrApplicationTag { + fn kind(&self) -> AttrKind { + AttrKind::ApplicationTag + } + + fn as_CFType(&self) -> CFType { + self.0.as_CFType() + } +} + +/// Human readable/meaningful labels for keychain items. +/// +/// Wrapper for the `kSecAttrLabel` attribute key. See: +/// +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AttrLabel(pub(crate) CFString); + +impl AttrLabel { + /// Create a new label from a `&str` + pub fn new(label: &str) -> Self { + AttrLabel(CFString::new(label)) + } +} + +impl Display for AttrLabel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +impl<'a> From<&'a str> for AttrLabel { + fn from(label: &str) -> Self { + Self::new(label) + } +} + +impl TAttr for AttrLabel { + fn kind(&self) -> AttrKind { + AttrKind::Label + } + + fn as_CFType(&self) -> CFType { + self.0.as_CFType() + } +} + +/// Classes of keys supported by Keychain Services (not to be confused with +/// `SecClass`, `SecAttrClass` or `SecAttrKeyType`) +/// +/// Wrapper for the `kSecAttrKeyClass` attribute key. See: +/// +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum AttrKeyClass { + /// Public keys. + /// + /// Wrapper for the `kSecAttrKeyClassPublic` attribute value. See: + /// + Public, + + /// Private keys. + /// + /// Wrapper for the `kSecAttrKeyClassPrivate` attribute value. See: + /// + Private, + + /// Symmetric keys + /// + /// Wrapper for the `kSecAttrKeyClassSymmetric` attribute value. See: + /// + // TODO: support for symmetric encryption + Symmetric, +} + +impl AttrKeyClass { + /// Get `CFString` containing the `kSecAttrKeyClass` dictionary value for + /// this particular `SecAttrKeyClass`. + pub fn as_CFString(self) -> CFString { + unsafe { + CFString::wrap_under_get_rule(match self { + AttrKeyClass::Public => kSecAttrKeyClassPublic, + AttrKeyClass::Private => kSecAttrKeyClassPrivate, + AttrKeyClass::Symmetric => kSecAttrKeyClassSymmetric, + }) + } + } +} + +impl TAttr for AttrKeyClass { + fn kind(&self) -> AttrKind { + AttrKind::KeyClass + } + + fn as_CFType(&self) -> CFType { + self.as_CFString().as_CFType() + } +} + +impl From for AttrKeyClass { + fn from(string_ref: CFStringRef) -> AttrKeyClass { + unsafe { + if string_ref == kSecAttrKeyClassPublic { + AttrKeyClass::Public + } else if string_ref == kSecAttrKeyClassPrivate { + AttrKeyClass::Private + } else { + AttrKeyClass::Symmetric + } + } + } +} + +impl<'a> From<&'a CFString> for AttrKeyClass { + fn from(string: &'a CFString) -> AttrKeyClass { + unsafe { + if *string == CFString::wrap_under_get_rule(kSecAttrKeyClassPublic) { + AttrKeyClass::Public + } else if *string == CFString::wrap_under_get_rule(kSecAttrKeyClassPrivate) { + AttrKeyClass::Private + } else { + AttrKeyClass::Symmetric + } + } + } +} + +/// Types of keys supported by Keychain Services (not to be confused with +/// `AttrKeyClass`) +/// +/// Wrapper for the `kSecAttrKeyType` attribute key. See: +/// +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum AttrKeyType { + /// AES algorithm. + /// + /// Wrapper for the `kSecAttrKeyTypeAES` attribute value. See: + /// + // TODO: support for AES encryption + Aes, + + /// RSA algorithm. + /// + /// Wrapper for the `kSecAttrKeyTypeRSA` attribute value. See: + /// + Rsa, + + /// Elliptic curve cryptography over the NIST curves (e.g. P-256) + /// + /// Wrapper for the `kSecAttrKeyTypeECSECPrimeRandom` attribute value. See: + /// + EcSecPrimeRandom, +} + +impl AttrKeyType { + /// Get `CFString` containing the `kSecAttrKeyType` dictionary value for + /// this particular `SecAttrKeyType`. + pub fn as_CFString(self) -> CFString { + unsafe { + CFString::wrap_under_get_rule(match self { + AttrKeyType::Aes => kSecAttrKeyTypeAES, + AttrKeyType::Rsa => kSecAttrKeyTypeRSA, + AttrKeyType::EcSecPrimeRandom => kSecAttrKeyTypeECSECPrimeRandom, + }) + } + } +} + +impl TAttr for AttrKeyType { + fn kind(&self) -> AttrKind { + AttrKind::KeyType + } + + fn as_CFType(&self) -> CFType { + self.as_CFString().as_CFType() + } +} + +impl From for AttrKeyType { + fn from(string_ref: CFStringRef) -> AttrKeyType { + unsafe { + if string_ref == kSecAttrKeyTypeECSECPrimeRandom { + AttrKeyType::EcSecPrimeRandom + } else if string_ref == kSecAttrKeyTypeRSA { + AttrKeyType::Rsa + } else { + AttrKeyType::Aes + } + } + } +} + +impl<'a> From<&'a CFString> for AttrKeyType { + fn from(string: &'a CFString) -> AttrKeyType { + unsafe { + if *string == CFString::wrap_under_get_rule(kSecAttrKeyTypeECSECPrimeRandom) { + AttrKeyType::EcSecPrimeRandom + } else if *string == CFString::wrap_under_get_rule(kSecAttrKeyTypeRSA) { + AttrKeyType::Rsa + } else { + AttrKeyType::Aes + } + } + } +} + +/// Internet protocols optionally associated with `SecClass::InternetPassword` +/// keychain items. +/// +/// Wrapper for the `kSecAttrProtocol` attribute key. See: +/// +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum AttrProtocol { + /// File Transfer Protocol + FTP, + + /// Client-side FTP account. + FTPAccount, + + /// Hypertext Transfer Protocol. + HTTP, + + /// Internet Relay Chat. + IRC, + + /// Network News Transfer Protocol. + NNTP, + + /// Post Office Protocol v3. + POP3, + + /// Simple Mail Transfer Protocol. + SMTP, + + /// SOCKS protocol. + SOCKS, + + /// Internet Message Access Protocol. + IMAP, + + /// Lightweight Directory Access Protocol. + LDAP, + + /// AFP over AppleTalk. + AppleTalk, + + /// AFP over TCP. + AFP, + + /// Telnet protocol. + Telnet, + + /// Secure Shell Protocol. + SSH, + + /// FTP over TLS/SSL. + FTPS, + + /// HTTP over TLS/SSL. + HTTPS, + + /// HTTP proxy. + HTTPProxy, + + /// HTTPS proxy. + HTTPSProxy, + + /// FTP proxy. + FTPProxy, + + /// Server Message Block protocol. + SMB, + + /// Real Time Streaming Protocol + RTSP, + + /// RTSP proxy. + RTSPProxy, + + /// DAAP protocol. + DAAP, + + /// Remote Apple Events. + EPPC, + + /// IPP protocol. + IPP, + + /// NNTP over TLS/SSL. + NNTPS, + + /// LDAP over TLS/SSL. + LDAPS, + + /// Telnet over TLS/SSL. + TelnetS, + + /// IMAP over TLS/SSL. + IMAPS, + + /// IRC over TLS/SSL. + IRCS, + + /// POP3 over TLS/SSL. + POP3S, +} + +impl AttrProtocol { + /// Get `CFString` containing the `kSecAttrProtocol` dictionary value for + /// this particular `SecAttrProtocol`. + pub fn as_CFString(self) -> CFString { + unsafe { + CFString::wrap_under_get_rule(match self { + AttrProtocol::FTP => kSecAttrProtocolFTP, + AttrProtocol::FTPAccount => kSecAttrProtocolFTPAccount, + AttrProtocol::HTTP => kSecAttrProtocolHTTP, + AttrProtocol::IRC => kSecAttrProtocolIRC, + AttrProtocol::NNTP => kSecAttrProtocolNNTP, + AttrProtocol::POP3 => kSecAttrProtocolPOP3, + AttrProtocol::SMTP => kSecAttrProtocolSMTP, + AttrProtocol::SOCKS => kSecAttrProtocolSOCKS, + AttrProtocol::IMAP => kSecAttrProtocolIMAP, + AttrProtocol::LDAP => kSecAttrProtocolLDAP, + AttrProtocol::AppleTalk => kSecAttrProtocolAppleTalk, + AttrProtocol::AFP => kSecAttrProtocolAFP, + AttrProtocol::Telnet => kSecAttrProtocolTelnet, + AttrProtocol::SSH => kSecAttrProtocolSSH, + AttrProtocol::FTPS => kSecAttrProtocolFTPS, + AttrProtocol::HTTPS => kSecAttrProtocolHTTPS, + AttrProtocol::HTTPProxy => kSecAttrProtocolHTTPProxy, + AttrProtocol::HTTPSProxy => kSecAttrProtocolHTTPSProxy, + AttrProtocol::FTPProxy => kSecAttrProtocolFTPProxy, + AttrProtocol::SMB => kSecAttrProtocolSMB, + AttrProtocol::RTSP => kSecAttrProtocolRTSP, + AttrProtocol::RTSPProxy => kSecAttrProtocolRTSPProxy, + AttrProtocol::DAAP => kSecAttrProtocolDAAP, + AttrProtocol::EPPC => kSecAttrProtocolEPPC, + AttrProtocol::IPP => kSecAttrProtocolIPP, + AttrProtocol::NNTPS => kSecAttrProtocolNNTPS, + AttrProtocol::LDAPS => kSecAttrProtocolLDAPS, + AttrProtocol::TelnetS => kSecAttrProtocolTelnetS, + AttrProtocol::IMAPS => kSecAttrProtocolIMAPS, + AttrProtocol::IRCS => kSecAttrProtocolIRCS, + AttrProtocol::POP3S => kSecAttrProtocolPOP3S, + }) + } + } +} + +impl TAttr for AttrProtocol { + fn kind(&self) -> AttrKind { + AttrKind::Protocol + } + + fn as_CFType(&self) -> CFType { + self.as_CFString().as_CFType() + } +} + +/// Identifiers for external storage tokens for cryptographic keys +/// (i.e. Secure Enclave). +/// +/// Wrapper for the `kSecAttrTokenID` attribute key. See: +/// +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum AttrTokenId { + /// Secure Enclave Processor (SEP), e.g. T1/T2 chip. + /// + /// Wrapper for the `kSecAttrTokenIDSecureEnclave` attribute value. See: + /// + SecureEnclave, +} + +impl AttrTokenId { + /// Get `CFString` containing the `kSecAttrTokenID` dictionary value for + /// this particular `SecAttrTokenId`. + pub fn as_CFString(self) -> CFString { + unsafe { + CFString::wrap_under_get_rule(match self { + AttrTokenId::SecureEnclave => kSecAttrTokenIDSecureEnclave, + }) + } + } +} + +impl TAttr for AttrTokenId { + fn kind(&self) -> AttrKind { + AttrKind::TokenId + } + + fn as_CFType(&self) -> CFType { + self.as_CFString().as_CFType() + } +} + +/// Keychain Item Attribute Constants For Keys +/// +/// +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum KeyAttr { + /// Wrapper for the `kSecKeyAlwaysSensitive` attribute value see: + /// + AlwaysSensitive, + /// Wrapper for the `kSecKeyDerive` attribute value see: + /// + CanDerive, + /// Wrapper for the `kSecKeyDecrypt` attribute value see: + /// + CanDecrypt, + /// Wrapper for the `kSecKeyEncrypt` attribute value see: + /// + CanEncrypt, + /// Wrapper for the `kSecKeySign` attribute value see: + /// + CanSign, + /// Wrapper for the `kSecKeyUnwrap` attribute value see: + /// + CanUnwrap, + /// Wrapper for the `kSecKeyVerify` attribute value see: + /// + CanVerify, + /// Wrapper for the `kSecKeyWrap` attribute value see: + /// + CanWrap, + /// Wrapper for the `kSecKeyEffectiveKeySize` attribute value see: + /// + EffectiveKeySize, + /// Wrapper for the `kSecKeyEndDate` attribute value see: + /// + EndDate, + /// Wrapper for the `kSecKeyExtractable` attribute value see: + /// + Extractable, + /// Wrapper for the `kSecKeyModifiable` attribute value see: + /// + Modifiable, + /// Wrapper for the `kSecKeyNeverExtractable` attribute value see: + /// + NeverExtractable, + /// Wrapper for the `kSecKeyPermanent` attribute value see: + /// + Permanent, + /// Wrapper for the `kSecKeyPrivate` attribute value see: + /// + Private, + /// Wrapper for the `kSecKeySensitive` attribute value see: + /// + Sensitive, + /// Wrapper for the `kSecKeyKeySizeInBits` attribute value see: + /// + SizeInBits, + /// Wrapper for the `kSecKeyStartDate` attribute value see: + /// + StartDate, + /// Wrapper for the `kSecKeyKeyType` attribute value see: + /// + Type, +} + +impl KeyAttr { + /// Get `CFString` containing the `kSecKeyAttr` dictionary value for + /// this particular `SecKeyAttr`. + pub fn as_CFString(self) -> CFString { + unsafe { + CFString::wrap_under_get_rule(match self { + KeyAttr::AlwaysSensitive => kSecKeyAlwaysSensitive, + KeyAttr::CanDerive => kSecKeyDerive, + KeyAttr::CanDecrypt => kSecKeyDecrypt, + KeyAttr::CanEncrypt => kSecKeyEncrypt, + KeyAttr::CanSign => kSecKeySign, + KeyAttr::CanUnwrap => kSecKeyUnwrap, + KeyAttr::CanVerify => kSecKeyVerify, + KeyAttr::CanWrap => kSecKeyWrap, + KeyAttr::Extractable => kSecKeyExtractable, + KeyAttr::EffectiveKeySize => kSecKeyEffectiveKeySize, + KeyAttr::EndDate => kSecKeyEndDate, + KeyAttr::Modifiable => kSecKeyModifiable, + KeyAttr::NeverExtractable => kSecKeyNeverExtractable, + KeyAttr::Permanent => kSecKeyPermanent, + KeyAttr::Private => kSecKeyPrivate, + KeyAttr::Sensitive => kSecKeySensitive, + KeyAttr::SizeInBits => kSecKeyKeySizeInBits, + KeyAttr::StartDate => kSecKeyStartDate, + KeyAttr::Type => kSecKeyKeyType, + }) + } + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/ciphertext.rs b/__security/keychain-services/keychain-services.rs/src/ciphertext.rs new file mode 100644 index 0000000..9e68934 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/ciphertext.rs @@ -0,0 +1,48 @@ +//! Ciphertext produced by this library. +//! +//! This type doesn't map directly to any type in the Keychain Services API, +//! but instead provides a newtype for ciphertexts this binding produces. + +use crate::key::KeyAlgorithm; + +/// Cryptographic signatures +#[derive(Clone, Debug)] +pub struct Ciphertext { + alg: KeyAlgorithm, + bytes: Vec, +} + +impl Ciphertext { + /// Create a new `Ciphertext` + pub fn new(alg: KeyAlgorithm, bytes: Vec) -> Self { + // TODO: restrict valid algorithms to encryption algorithms? + Self { alg, bytes } + } + + /// Get the algorithm which produced this signature + pub fn algorithm(&self) -> KeyAlgorithm { + self.alg + } + + /// Borrow the ciphertext data as bytes + pub fn as_bytes(&self) -> &[u8] { + &self.bytes + } + + /// Convert into a byte vector + pub fn into_vec(self) -> Vec { + self.bytes + } +} + +impl AsRef<[u8]> for Ciphertext { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl From for Vec { + fn from(ciphertext: Ciphertext) -> Vec { + ciphertext.into_vec() + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/dictionary.rs b/__security/keychain-services/keychain-services.rs/src/dictionary.rs new file mode 100644 index 0000000..347c4b7 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/dictionary.rs @@ -0,0 +1,79 @@ +//! Builder for constructing a `CFDictionary` from attribute pairs. + +use crate::{attr::TAttr, ffi::kSecClass, keychain::item}; +use core_foundation::{ + self, + base::{CFType, TCFType}, + boolean::CFBoolean, + number::CFNumber, + string::{CFString, CFStringRef}, +}; + +/// All CFDictionary types we use follow this signature +pub(crate) type Dictionary = core_foundation::dictionary::CFDictionary; + +/// Builder for attribute/parameter dictionaries we pass as arguments. +// TODO: ensure there are no duplicate items, e.g. with `HashMap`/`BTreeMap` +// storage and checking if the same key is added twice. +#[derive(Clone, Default, Debug)] +pub(crate) struct DictionaryBuilder(Vec<(CFType, CFType)>); + +impl DictionaryBuilder { + /// Create a new dictionary builder + pub(crate) fn new() -> DictionaryBuilder { + DictionaryBuilder(vec![]) + } + + /// Add a key/value pair to the dictionary + pub(crate) fn add(&mut self, key: K, value: &V) + where + K: Into, + V: TCFType, + { + self.0.push(( + unsafe { CFString::wrap_under_get_rule(key.into()) }.as_CFType(), + value.as_CFType(), + )) + } + + /// Add an attribute (i.e. `TSecAttr`) to the dictionary + pub(crate) fn add_attr(&mut self, attr: &dyn TAttr) { + self.add(attr.kind(), &attr.as_CFType()) + } + + /// Add a key/value pair with a `bool` value to the dictionary + pub(crate) fn add_boolean(&mut self, key: K, value: bool) + where + K: Into, + { + self.add(key, &CFBoolean::from(value)) + } + + /// Add a `keychain::item::Class` value to the dictionary + pub(crate) fn add_class(&mut self, class: item::Class) { + self.add(unsafe { kSecClass }, &class.as_CFString()); + } + + /// Add a key/value pair with an `i64` value to the dictionary + pub(crate) fn add_number(&mut self, key: K, value: i64) + where + K: Into, + { + self.add(key, &CFNumber::from(value)) + } + + /// Add a key/value pair with an `AsRef` value to the dictionary + pub(crate) fn add_string(&mut self, key: K, value: V) + where + K: Into, + V: AsRef, + { + self.add(key, &CFString::from(value.as_ref())) + } +} + +impl From for Dictionary { + fn from(builder: DictionaryBuilder) -> Dictionary { + Dictionary::from_CFType_pairs(&builder.0) + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/error.rs b/__security/keychain-services/keychain-services.rs/src/error.rs new file mode 100644 index 0000000..bb5d6de --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/error.rs @@ -0,0 +1,562 @@ +//! Error types + +use crate::ffi::*; +use core_foundation::{ + base::{CFRelease, CFTypeRef, OSStatus, TCFType}, + error::{CFErrorCopyDescription, CFErrorGetCode, CFErrorGetDomain, CFErrorRef}, + string::CFString, +}; +use failure::{Backtrace, Fail}; +use std::{ + fmt::{self, Display}, + io, ptr, +}; + +/// No error occurred. +/// +const errSecSuccess: OSStatus = 0; + +/// Authentication and/or authorization failed. +/// +const errSecAuthFailed: OSStatus = -25293; + +/// Buffer is too small. +/// +const errSecBufferTooSmall: OSStatus = -25301; + +/// Certificate chain creation attempt failed. +/// +const errSecCreateChainFailed: OSStatus = -25318; + +/// Data too large for the given data type. +/// +const errSecDataTooLarge: OSStatus = -25302; + +/// Data is not available. +/// +const errSecDataNotAvailable: OSStatus = -25316; + +/// Data cannot be modified. +/// +const errSecDataNotModifiable: OSStatus = -25317; + +/// Callback with the same name already exists. +/// +const errSecDuplicateCallback: OSStatus = -25297; + +/// Item already exists. +/// +const errSecDuplicateItem: OSStatus = -25299; + +/// Keychain with the same name already exists. +/// +const errSecDuplicateKeychain: OSStatus = -25296; + +/// Base number for `OSStatus` values which map directly to errno values +const errSecErrnoBase: OSStatus = 100_000; + +/// Upper limit number for `OSStatus` values which map directly to errno values +const errSecErrnoLimit: OSStatus = 100_255; + +/// System is in a dark wake state - user interface cannot be displayed. +/// +const errSecInDarkWake: OSStatus = -25320; + +/// Security Server interactions not allowed in this context. +/// +const errSecInteractionNotAllowed: OSStatus = -25308; + +/// User interaction required. +/// +const errSecInteractionRequired: OSStatus = -25315; + +/// Callback is invalid. +/// +const errSecInvalidCallback: OSStatus = -25298; + +/// Item reference is invalid. +/// +const errSecInvalidItemRef: OSStatus = -25304; + +/// Keychain is invalid. +/// +const errSecInvalidKeychain: OSStatus = -25295; + +/// Specified preference domain is not valid. +/// +const errSecInvalidPrefsDomain: OSStatus = -25319; + +/// Search reference is invalid. +/// +const errSecInvalidSearchRef: OSStatus = -25305; + +/// Item could not be found. +/// +const errSecItemNotFound: OSStatus = -25300; + +/// Invalid key size. +/// +const errSecKeySizeNotAllowed: OSStatus = -25311; + +/// Missing entitlement: keychain access disallowed because app is unsigned. +/// +const errSecMissingEntitlement: OSStatus = -34018; + +/// Certificate module unavailable. +/// +const errSecNoCertificateModule: OSStatus = -25313; + +/// Default keychain does not exist. +/// +const errSecNoDefaultKeychain: OSStatus = -25307; + +/// Policy module unavailable. +/// +const errSecNoPolicyModule: OSStatus = -25314; + +/// Storage module unavailable. +/// +const errSecNoStorageModule: OSStatus = -25312; + +/// Specified attribute does not exist. +/// +const errSecNoSuchAttr: OSStatus = -25303; + +/// Specified keychain item class does not exist. +/// +const errSecNoSuchClass: OSStatus = -25306; + +/// Specified keychain does not exist. +/// +const errSecNoSuchKeychain: OSStatus = -25294; + +/// Trust results not available. +/// +const errSecNotAvailable: OSStatus = -25291; + +/// Can't perform given action on read-only item. +/// +const errSecReadOnly: OSStatus = -25292; + +/// Can't perform action on read-only attribute +/// +const errSecReadOnlyAttr: OSStatus = -25309; + +/// Invalid version. +/// +const errSecWrongSecVersion: OSStatus = -25310; + +/// Error type. +/// +/// Wrapper for the `CFError` type: +/// +#[derive(Debug)] +pub struct Error { + kind: ErrorKind, + backtrace: Backtrace, + description: String, +} + +impl Error { + /// Create a new error of the given kind with the given description + pub fn new(kind: ErrorKind, description: &D) -> Self + where + D: ToString + ?Sized, + { + Error { + kind, + backtrace: Backtrace::new(), + description: description.to_string(), + } + } + + /// Create an error from an `OSStatus` if the status is not success + pub fn maybe_from_OSStatus(status: OSStatus) -> Option { + if status == errSecSuccess { + None + } else { + let kind = ErrorKind::from(status); + let description = unsafe { + CFString::wrap_under_create_rule(SecCopyErrorMessageString(status, ptr::null())) + }; + + Some(Error::new(kind, &description)) + } + } + + /// Get the `ErrorKind` for this error + pub fn kind(&self) -> &ErrorKind { + &self.kind + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} ({})", &self.description, &self.kind) + } +} + +impl Fail for Error { + fn cause(&self) -> Option<&dyn Fail> { + None + } + + fn backtrace(&self) -> Option<&Backtrace> { + Some(&self.backtrace) + } +} + +impl From for Error { + /// Creates an `Error` with copies of all error data on the Rust heap. + /// + /// Calls `CFRelease` on the provided `CFErrorRef`. + fn from(error_ref: CFErrorRef) -> Error { + let kind = ErrorKind::from(error_ref); + let backtrace = Backtrace::new(); + let description = + unsafe { CFString::wrap_under_create_rule(CFErrorCopyDescription(error_ref)) } + .to_string(); + + // Free the error reference + unsafe { + CFRelease(error_ref as CFTypeRef); + } + + Error { + kind, + backtrace, + description, + } + } +} + +/// Kinds of errors. +#[derive(Clone, Debug, Fail)] +pub enum ErrorKind { + /// Authentication and/or authorization failed. + /// + /// Wrapper for the `errSecAuthFailed` status code. See: + /// + #[fail(display = "authentication failed")] + AuthFailed, + + /// Buffer is too small. + /// + /// Wrapper for the `errSecBufferTooSmall` status code. See: + /// + #[fail(display = "buffer too small")] + BufferTooSmall, + + /// Certificate chain creation attempt failed. + /// + /// Wrapper for the `errSecCreateChainFailed` status code. See: + /// + #[fail(display = "certificate chain creation attempt failed")] + CreateChainFailed, + + /// Data too large for the given data type. + /// + /// Wrapper for the `errSecDataTooLarge` status code. See: + /// + #[fail(display = "data too large")] + DataTooLarge, + + /// Data is not available. + /// + /// Wrapper for the `errSecDataNotAvailable` status code. See: + /// + #[fail(display = "data not available")] + DataNotAvailable, + + /// Data cannot be modified. + /// + /// Wrapper for the `errSecDataNotModifiable` status code. See: + /// + #[fail(display = "data not modifiable")] + DataNotModifiable, + + /// Callback with the same name already exists. + /// + /// Wrapper for the `errSecDuplicateCallback` status code. See: + /// + #[fail(display = "duplicate callback")] + DuplicateCallback, + + /// Item already exists. + /// + /// Wrapper for the `errSecDuplicateItem` status code. See: + /// + #[fail(display = "duplicate item")] + DuplicateItem, + + /// Keychain with the same name already exists. + /// + /// Wrapper for the `errSecDuplicateKeychain` status code. See: + /// + #[fail(display = "duplicate keychain")] + DuplicateKeychain, + + /// System is in a dark wake state - user interface cannot be displayed. + /// + /// Wrapper for the `errSecInDarkWake` status code. See: + /// + #[fail(display = "in dark wake")] + InDarkWake, + + /// Security Server interactions not allowed in this context. + /// + /// Wrapper for the `errSecInteractionNotAllowed` status code. See: + /// + #[fail(display = "interaction not allowed")] + InteractionNotAllowed, + + /// User interaction required. + /// + /// Wrapper for the `errSecInteractionRequired` status code. See: + /// + #[fail(display = "user interaction required")] + InteractionRequired, + + /// Callback is invalid. + /// + /// Wrapper for the `errSecInvalidCallback` status code. See: + /// + #[fail(display = "invalid callback")] + InvalidCallback, + + /// Item reference is invalid. + /// + /// Wrapper for the `errSecInvalidItemRef` status code. See: + /// + #[fail(display = "invalid item ref")] + InvalidItemRef, + + /// Keychain is invalid. + /// + /// Wrapper for the `errSecInvalidKeychain` status code. See: + /// + #[fail(display = "invalid keychain")] + InvalidKeychain, + + /// Specified preference domain is not valid. + /// + /// Wrapper for the `errSecInvalidPrefsDomain` status code. See: + /// + #[fail(display = "invalid preference domain")] + InvalidPrefsDomain, + + /// Search reference is invalid. + /// + /// Wrapper for the `errSecInvalidSearchRef` status code. See: + /// + #[fail(display = "search ref is invalid")] + InvalidSearchRef, + + /// Item could not be found. + /// + /// Wrapper for the `errSecItemNotFound` status code. See: + /// + #[fail(display = "item not found")] + ItemNotFound, + + /// Invalid key size. + /// + /// Wrapper for the `errSecKeySizeNotAllowed` status code. See: + /// + #[fail(display = "key size not allowed")] + KeySizeNotAllowed, + + /// Required entitlement for accessing the keychain is missing. This error + /// occurs when attempting to access certain keychain functionality from an + /// application which is either unsigned or missing a required entitlement. + /// + /// Wrapper for the `errSecMissingEntitlement` status code. See: + /// + #[fail(display = "missing application entitlement (errSecMissingEntitlement)")] + MissingEntitlement, + + /// Certificate module unavailable. + /// + /// Wrapper for the `errSecNoCertificateModule` status code. See: + /// + #[fail(display = "no certificate module")] + NoCertificateModule, + + /// Default keychain does not exist. + /// + /// Wrapper for the `errSecNoDefaultKeychain` status code. See: + /// + #[fail(display = "no default keychain")] + NoDefaultKeychain, + + /// Policy module unavailable. + /// + /// Wrapper for the `errSecNoPolicyModule` status code. See: + /// + #[fail(display = "no policy module")] + NoPolicyModule, + + /// Storage module unavailable. + /// + /// Wrapper for the `errSecNoStorageModule` status code. See: + /// + #[fail(display = "no storage module")] + NoStorageModule, + + /// Specified attribute does not exist. + /// + /// Wrapper for the `errSecNoSuchAttr` status code. See:; + /// + #[fail(display = "no such attr")] + NoSuchAttr, + + /// Specified keychain item class does not exist. + /// + /// Wrapper for the `errSecNoSuchClass` status code. See: + /// + #[fail(display = "no such class")] + NoSuchClass, + + /// Specified keychain does not exist. + /// + /// Wrapper for the `errSecNoSuchKeychain` status code. See: + /// + #[fail(display = "no such keychain")] + NoSuchKeychain, + + /// Trust results not available. + /// + /// Wrapper for the `errSecNotAvailable` status code. See: + /// + #[fail(display = "not available")] + NotAvailable, + + /// Can't perform given action on read-only item. + /// + /// Wrapper for the `errSecReadOnly` status code. See: + /// + #[fail(display = "read-only")] + ReadOnly, + + /// Can't perform action on read-only attribute + /// + /// Wrapper for the `errSecReadOnlyAttr` status code. See: + /// + #[fail(display = "read-only attr")] + ReadOnlyAttr, + + /// Invalid version. + /// + /// Wrapper for the `errSecWrongSecVersion` status code. See: + /// + #[fail(display = "wrong version")] + WrongSecVersion, + + /// Input/output errors. + /// + /// Wrapper for errno codes we know/commonly encounter. + #[fail(display = "I/O error ({:?})", kind)] + Io { + /// `std::io::ErrorKind` value representing the I/O error + kind: io::ErrorKind, + }, + + /// Errors returned from CoreFoundation. + /// + /// Codes correspond to the return value of the `CFErrorGetCode` function. + /// + /// For more information, see: + /// + #[fail(display = "Core Foundation error (code: {}, domain: {})", code, domain)] + CFError { + /// Code identifying this type of `CFError`. + /// + /// See `CFErrorGetCode()` for more information: + /// + code: i64, + + /// Domain associated with this error. + /// + /// See `CFErrorGetDomain()` for more information: + /// + domain: String, + }, + + /// Unix errno values we receive from the underlying OS + // TODO: create `ErrorKind` variants for ones we commonly encounter? + #[fail(display = "POSIX error (errno: {})", code)] + Errno { + /// Raw errno value + code: u8, + }, + + /// `OSStatus` codes which we can't otherwise decode. + #[fail(display = "unknown OS error (code: {})", code)] + OSError { + /// OS error code + code: i64, + }, +} + +impl From for ErrorKind { + fn from(error_ref: CFErrorRef) -> ErrorKind { + ErrorKind::CFError { + code: unsafe { CFErrorGetCode(error_ref) } as i64, + domain: unsafe { CFString::wrap_under_get_rule(CFErrorGetDomain(error_ref)) } + .to_string(), + } + } +} + +impl From for ErrorKind { + fn from(status: OSStatus) -> ErrorKind { + match status { + errSecAuthFailed => ErrorKind::AuthFailed, + errSecBufferTooSmall => ErrorKind::BufferTooSmall, + errSecCreateChainFailed => ErrorKind::CreateChainFailed, + errSecDataTooLarge => ErrorKind::DataTooLarge, + errSecDataNotAvailable => ErrorKind::DataNotAvailable, + errSecDataNotModifiable => ErrorKind::DataNotModifiable, + errSecDuplicateCallback => ErrorKind::DuplicateCallback, + errSecDuplicateItem => ErrorKind::DuplicateItem, + errSecDuplicateKeychain => ErrorKind::DuplicateKeychain, + errSecInDarkWake => ErrorKind::InDarkWake, + errSecInteractionNotAllowed => ErrorKind::InteractionNotAllowed, + errSecInteractionRequired => ErrorKind::InteractionRequired, + errSecInvalidCallback => ErrorKind::InvalidCallback, + errSecInvalidItemRef => ErrorKind::InvalidItemRef, + errSecInvalidKeychain => ErrorKind::InvalidKeychain, + errSecInvalidPrefsDomain => ErrorKind::InvalidPrefsDomain, + errSecInvalidSearchRef => ErrorKind::InvalidSearchRef, + errSecItemNotFound => ErrorKind::ItemNotFound, + errSecKeySizeNotAllowed => ErrorKind::KeySizeNotAllowed, + errSecMissingEntitlement => ErrorKind::MissingEntitlement, + errSecNoCertificateModule => ErrorKind::NoCertificateModule, + errSecNoDefaultKeychain => ErrorKind::NoDefaultKeychain, + errSecNoPolicyModule => ErrorKind::NoPolicyModule, + errSecNoStorageModule => ErrorKind::NoStorageModule, + errSecNoSuchAttr => ErrorKind::NoSuchAttr, + errSecNoSuchClass => ErrorKind::NoSuchClass, + errSecNoSuchKeychain => ErrorKind::NoSuchKeychain, + errSecNotAvailable => ErrorKind::NotAvailable, + errSecReadOnly => ErrorKind::ReadOnly, + errSecReadOnlyAttr => ErrorKind::ReadOnlyAttr, + errSecWrongSecVersion => ErrorKind::WrongSecVersion, + errSecErrnoBase..=errSecErrnoLimit => match (status - errSecErrnoBase) as u8 { + 1 => ErrorKind::Io { + kind: io::ErrorKind::PermissionDenied, + }, + 2 => ErrorKind::Io { + kind: io::ErrorKind::NotFound, + }, + 17 => ErrorKind::Io { + kind: io::ErrorKind::AlreadyExists, + }, + code => ErrorKind::Errno { code }, + }, + _ => ErrorKind::OSError { + code: i64::from(status), + }, + } + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/ffi.rs b/__security/keychain-services/keychain-services.rs/src/ffi.rs new file mode 100644 index 0000000..b05a673 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/ffi.rs @@ -0,0 +1,443 @@ +use core_foundation::{ + base::{CFAllocatorRef, CFIndex, CFOptionFlags, CFTypeID, CFTypeRef, OSStatus, TCFType}, + data::CFDataRef, + dictionary::CFDictionaryRef, + error::CFErrorRef, + string::{CFString, CFStringRef}, +}; +use std::{ + borrow::Cow, + fmt::{self, Debug}, + os::raw::{c_char, c_void}, + ptr, slice, str, +}; + +/// Four character codes used as identifiers. See: +/// +#[repr(transparent)] +#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub(crate) struct FourCharacterCode(u32); + +impl FourCharacterCode { + fn as_bytes(&self) -> &[u8; 4] { + unsafe { &*(self as *const FourCharacterCode as *const [u8; 4]) } + } + + fn as_str(&self) -> &str { + str::from_utf8(self.as_bytes()).unwrap() + } +} + +impl AsRef for FourCharacterCode { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl Debug for FourCharacterCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "FourCharacterCode({})", self.as_str()) + } +} + +impl From for FourCharacterCode { + fn from(num: u32) -> FourCharacterCode { + FourCharacterCode(num) + } +} + +impl From<[u8; 4]> for FourCharacterCode { + fn from(bytes: [u8; 4]) -> FourCharacterCode { + Self::from(&bytes) + } +} + +impl<'a> From<&'a [u8; 4]> for FourCharacterCode { + fn from(bytes: &[u8; 4]) -> FourCharacterCode { + let mut result: u32 = 0; + + unsafe { + ptr::copy_nonoverlapping(bytes.as_ptr(), (&mut result as *mut u32) as *mut u8, 4); + } + + FourCharacterCode(result) + } +} + +impl From for FourCharacterCode { + fn from(string_ref: CFStringRef) -> FourCharacterCode { + Self::from(&unsafe { CFString::wrap_under_get_rule(string_ref) }) + } +} + +impl<'a> From<&'a CFString> for FourCharacterCode { + fn from(string: &'a CFString) -> FourCharacterCode { + let string = Cow::from(string); + assert_eq!(string.as_bytes().len(), 4); + + let mut code = [0u8; 4]; + code.copy_from_slice(string.as_bytes()); + code.into() + } +} + +/// Reference to an access control policy. +/// +/// See `SecAccessControlRef` documentation: +/// +pub(crate) type AccessControlRef = CFTypeRef; + +/// Reference to a `Key` +/// +/// See `SecKeyRef` documentation: +/// +pub(crate) type KeyRef = CFTypeRef; + +/// Reference to a `Keychain` +/// +/// See `SecKeychainRef` documentation: +/// +pub(crate) type KeychainRef = CFTypeRef; + +/// Reference to a `keychain::Item` +/// +/// See `SecKeychainItemRef` documentation: +/// +pub(crate) type ItemRef = CFTypeRef; + +/// Attribute type codes. +/// +/// Wrapper for `SecKeychainAttrType`. See: +/// +pub(crate) type SecKeychainAttrType = FourCharacterCode; + +/// Individual keychain attribute. +/// +/// Wrapper for the `SecKeychainAttribute` struct. See: +/// +#[repr(C)] +pub(super) struct SecKeychainAttribute { + tag: SecKeychainAttrType, + length: u32, + data: *mut u8, +} + +impl SecKeychainAttribute { + /// Get the `FourCharacterCode` tag identifying this attribute's type + pub(crate) fn tag(&self) -> SecKeychainAttrType { + self.tag + } + + /// Get the data associated with this attribute as a byte slice. + pub(crate) fn data(&self) -> Option<&[u8]> { + if self.data.is_null() { + None + } else { + Some(unsafe { slice::from_raw_parts(self.data, self.length as usize) }) + } + } +} + +/// List of attributes (as returned from e.g. `SecKeychainItemCopyContent`). +/// +/// NOTE: This type does not implement `Drop` as there are various ways it can +/// be allocated/deallocated. The caller must take care to free it! +/// +/// Wrapper for the `SecKeychainAttributeList` struct. See: +/// +#[repr(C)] +pub(super) struct SecKeychainAttributeList { + count: u32, + attr: *mut SecKeychainAttribute, +} + +impl SecKeychainAttributeList { + /// Get an iterator over this attribute list. + pub(crate) fn iter(&self) -> slice::Iter { + self.as_slice().iter() + } + + /// Get a slice of `Attribute` values + pub(crate) fn as_slice(&self) -> &[SecKeychainAttribute] { + unsafe { slice::from_raw_parts(self.attr, self.count as usize) } + } +} + +#[link(name = "Security", kind = "framework")] +extern "C" { + pub(crate) static kSecAttrAccessControl: CFStringRef; + pub(crate) static kSecAttrAccessible: CFStringRef; + pub(crate) static kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly: CFStringRef; + pub(crate) static kSecAttrAccessibleWhenUnlockedThisDeviceOnly: CFStringRef; + pub(crate) static kSecAttrAccessibleWhenUnlocked: CFStringRef; + pub(crate) static kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly: CFStringRef; + pub(crate) static kSecAttrAccessibleAfterFirstUnlock: CFStringRef; + pub(crate) static kSecAttrAccessibleAlwaysThisDeviceOnly: CFStringRef; + pub(crate) static kSecAttrAccessibleAlways: CFStringRef; + pub(crate) static kSecAttrAccount: CFStringRef; + pub(crate) static kSecAttrApplicationLabel: CFStringRef; + pub(crate) static kSecAttrApplicationTag: CFStringRef; + pub(crate) static kSecAttrCanEncrypt: CFStringRef; + pub(crate) static kSecAttrCanDecrypt: CFStringRef; + pub(crate) static kSecAttrCanDerive: CFStringRef; + pub(crate) static kSecAttrCanSign: CFStringRef; + pub(crate) static kSecAttrCanVerify: CFStringRef; + pub(crate) static kSecAttrCanWrap: CFStringRef; + pub(crate) static kSecAttrCanUnwrap: CFStringRef; + pub(crate) static kSecAttrIsExtractable: CFStringRef; + pub(crate) static kSecAttrIsPermanent: CFStringRef; + pub(crate) static kSecAttrIsSensitive: CFStringRef; + pub(crate) static kSecAttrKeyClass: CFStringRef; + pub(crate) static kSecAttrKeyClassPublic: CFStringRef; + pub(crate) static kSecAttrKeyClassPrivate: CFStringRef; + pub(crate) static kSecAttrKeyClassSymmetric: CFStringRef; + pub(crate) static kSecAttrKeyType: CFStringRef; + pub(crate) static kSecAttrKeyTypeAES: CFStringRef; + pub(crate) static kSecAttrKeyTypeRSA: CFStringRef; + pub(crate) static kSecAttrKeyTypeECSECPrimeRandom: CFStringRef; + pub(crate) static kSecAttrKeySizeInBits: CFStringRef; + pub(crate) static kSecAttrLabel: CFStringRef; + pub(crate) static kSecAttrProtocol: CFStringRef; + pub(crate) static kSecAttrProtocolFTP: CFStringRef; + pub(crate) static kSecAttrProtocolFTPAccount: CFStringRef; + pub(crate) static kSecAttrProtocolHTTP: CFStringRef; + pub(crate) static kSecAttrProtocolIRC: CFStringRef; + pub(crate) static kSecAttrProtocolNNTP: CFStringRef; + pub(crate) static kSecAttrProtocolPOP3: CFStringRef; + pub(crate) static kSecAttrProtocolSMTP: CFStringRef; + pub(crate) static kSecAttrProtocolSOCKS: CFStringRef; + pub(crate) static kSecAttrProtocolIMAP: CFStringRef; + pub(crate) static kSecAttrProtocolLDAP: CFStringRef; + pub(crate) static kSecAttrProtocolAppleTalk: CFStringRef; + pub(crate) static kSecAttrProtocolAFP: CFStringRef; + pub(crate) static kSecAttrProtocolTelnet: CFStringRef; + pub(crate) static kSecAttrProtocolSSH: CFStringRef; + pub(crate) static kSecAttrProtocolFTPS: CFStringRef; + pub(crate) static kSecAttrProtocolHTTPS: CFStringRef; + pub(crate) static kSecAttrProtocolHTTPProxy: CFStringRef; + pub(crate) static kSecAttrProtocolHTTPSProxy: CFStringRef; + pub(crate) static kSecAttrProtocolFTPProxy: CFStringRef; + pub(crate) static kSecAttrProtocolSMB: CFStringRef; + pub(crate) static kSecAttrProtocolRTSP: CFStringRef; + pub(crate) static kSecAttrProtocolRTSPProxy: CFStringRef; + pub(crate) static kSecAttrProtocolDAAP: CFStringRef; + pub(crate) static kSecAttrProtocolEPPC: CFStringRef; + pub(crate) static kSecAttrProtocolIPP: CFStringRef; + pub(crate) static kSecAttrProtocolNNTPS: CFStringRef; + pub(crate) static kSecAttrProtocolLDAPS: CFStringRef; + pub(crate) static kSecAttrProtocolTelnetS: CFStringRef; + pub(crate) static kSecAttrProtocolIMAPS: CFStringRef; + pub(crate) static kSecAttrProtocolIRCS: CFStringRef; + pub(crate) static kSecAttrProtocolPOP3S: CFStringRef; + pub(crate) static kSecAttrServer: CFStringRef; + pub(crate) static kSecAttrService: CFStringRef; + pub(crate) static kSecAttrSynchronizable: CFStringRef; + pub(crate) static kSecAttrTokenID: CFStringRef; + pub(crate) static kSecAttrTokenIDSecureEnclave: CFStringRef; + pub(crate) static kSecClass: CFStringRef; + pub(crate) static kSecClassGenericPassword: CFStringRef; + pub(crate) static kSecClassInternetPassword: CFStringRef; + pub(crate) static kSecClassCertificate: CFStringRef; + pub(crate) static kSecClassKey: CFStringRef; + pub(crate) static kSecClassIdentity: CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionStandardX963SHA1AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionStandardX963SHA224AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionStandardX963SHA256AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionStandardX963SHA384AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionStandardX963SHA512AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionStandardVariableIVX963SHA224AESGCM: + CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionStandardVariableIVX963SHA256AESGCM: + CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionStandardVariableIVX963SHA384AESGCM: + CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionStandardVariableIVX963SHA512AESGCM: + CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA224AESGCM: + CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA256AESGCM: + CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA384AESGCM: + CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA512AESGCM: + CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionCofactorX963SHA1AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionCofactorX963SHA224AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionCofactorX963SHA384AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmECIESEncryptionCofactorX963SHA512AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDSASignatureRFC4754: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDSASignatureDigestX962: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDSASignatureDigestX962SHA1: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDSASignatureDigestX962SHA224: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDSASignatureDigestX962SHA256: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDSASignatureDigestX962SHA384: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDSASignatureDigestX962SHA512: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDSASignatureMessageX962SHA1: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDSASignatureMessageX962SHA224: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDSASignatureMessageX962SHA256: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDSASignatureMessageX962SHA384: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDSASignatureMessageX962SHA512: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDHKeyExchangeCofactor: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDHKeyExchangeStandard: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDHKeyExchangeCofactorX963SHA1: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDHKeyExchangeStandardX963SHA1: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDHKeyExchangeCofactorX963SHA224: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDHKeyExchangeCofactorX963SHA256: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDHKeyExchangeCofactorX963SHA384: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDHKeyExchangeCofactorX963SHA512: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDHKeyExchangeStandardX963SHA224: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDHKeyExchangeStandardX963SHA256: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDHKeyExchangeStandardX963SHA384: CFStringRef; + pub(crate) static kSecKeyAlgorithmECDHKeyExchangeStandardX963SHA512: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSAEncryptionRaw: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSAEncryptionPKCS1: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSAEncryptionOAEPSHA1: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSAEncryptionOAEPSHA224: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSAEncryptionOAEPSHA256: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSAEncryptionOAEPSHA384: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSAEncryptionOAEPSHA512: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSAEncryptionOAEPSHA1AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSAEncryptionOAEPSHA224AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSAEncryptionOAEPSHA256AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSAEncryptionOAEPSHA384AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSAEncryptionOAEPSHA512AESGCM: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureRaw: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureDigestPKCS1v15Raw: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA1: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA224: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA1: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA224: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA384: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA512: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureDigestPSSSHA1: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureDigestPSSSHA224: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureDigestPSSSHA256: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureDigestPSSSHA384: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureDigestPSSSHA512: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureMessagePSSSHA1: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureMessagePSSSHA224: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureMessagePSSSHA256: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureMessagePSSSHA384: CFStringRef; + pub(crate) static kSecKeyAlgorithmRSASignatureMessagePSSSHA512: CFStringRef; + pub(crate) static kSecKeyAlwaysSensitive: CFStringRef; + pub(crate) static kSecKeyDecrypt: CFStringRef; + pub(crate) static kSecKeyDerive: CFStringRef; + pub(crate) static kSecKeyEffectiveKeySize: CFStringRef; + pub(crate) static kSecKeyEncrypt: CFStringRef; + pub(crate) static kSecKeyEndDate: CFStringRef; + pub(crate) static kSecKeyExtractable: CFStringRef; + pub(crate) static kSecKeyKeySizeInBits: CFStringRef; + pub(crate) static kSecKeyKeyType: CFStringRef; + pub(crate) static kSecKeyModifiable: CFStringRef; + pub(crate) static kSecKeyNeverExtractable: CFStringRef; + pub(crate) static kSecKeyPermanent: CFStringRef; + pub(crate) static kSecKeyPrivate: CFStringRef; + pub(crate) static kSecKeySensitive: CFStringRef; + pub(crate) static kSecKeySign: CFStringRef; + pub(crate) static kSecKeyStartDate: CFStringRef; + pub(crate) static kSecKeyUnwrap: CFStringRef; + pub(crate) static kSecKeyVerify: CFStringRef; + pub(crate) static kSecKeyWrap: CFStringRef; + pub(crate) static kSecMatchLimit: CFStringRef; + pub(crate) static kSecMatchLimitOne: CFStringRef; + pub(crate) static kSecMatchLimitAll: CFStringRef; + pub(crate) static kSecPrivateKeyAttrs: CFStringRef; + pub(crate) static kSecReturnRef: CFStringRef; + pub(crate) static kSecUseKeychain: CFStringRef; + pub(crate) static kSecUseOperationPrompt: CFStringRef; + pub(crate) static kSecValueData: CFStringRef; + + pub(crate) fn SecAccessControlCreateWithFlags( + allocator: CFAllocatorRef, + protection: CFTypeRef, + flags: CFOptionFlags, + error: *mut CFErrorRef, + ) -> CFTypeRef; + pub(crate) fn SecAccessControlGetTypeID() -> CFTypeID; + pub(crate) fn SecCopyErrorMessageString( + status: OSStatus, + reserved: *const c_void, + ) -> CFStringRef; + pub(crate) fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus; + pub(crate) fn SecItemDelete(attributes: CFDictionaryRef) -> OSStatus; + pub(crate) fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus; + pub(crate) fn SecKeyCopyAttributes(key: KeyRef) -> CFDictionaryRef; + pub(crate) fn SecKeyCreateWithData( + keyData: CFDataRef, + attributes: CFDictionaryRef, + error: *mut CFErrorRef, + ) -> KeyRef; + pub(crate) fn SecKeyCopyExternalRepresentation( + key: KeyRef, + error: *mut CFErrorRef, + ) -> CFDataRef; + pub(crate) fn SecKeyCreateSignature( + key: KeyRef, + algorithm: CFTypeRef, + data_to_sign: CFDataRef, + error: *mut CFErrorRef, + ) -> CFDataRef; + pub(crate) fn SecKeyVerifySignature( + key: KeyRef, + algorithm: CFTypeRef, + data_to_verify: CFDataRef, + signature: CFDataRef, + error: *mut CFErrorRef, + ) -> u8; + pub(crate) fn SecKeyCreateEncryptedData( + key: KeyRef, + algorithm: CFTypeRef, + plaintext: CFDataRef, + error: *mut CFErrorRef, + ) -> CFDataRef; + pub(crate) fn SecKeyCreateDecryptedData( + key: KeyRef, + algorithm: CFTypeRef, + ciphertext: CFDataRef, + error: *mut CFErrorRef, + ) -> CFDataRef; + pub(crate) fn SecKeyGeneratePair( + parameters: CFDictionaryRef, + publicKey: *mut KeyRef, + privateKey: *mut KeyRef, + ) -> OSStatus; + pub(crate) fn SecKeyCreateRandomKey( + parameters: CFDictionaryRef, + error: *mut CFErrorRef, + ) -> KeyRef; + pub(crate) fn SecKeyIsAlgorithmSupported( + key: KeyRef, + operationType: CFIndex, + algorithm: CFTypeRef, + ) -> u8; + pub(crate) fn SecKeyCopyPublicKey(privatekey: KeyRef) -> KeyRef; + pub(crate) fn SecKeyGetTypeID() -> CFTypeID; + pub(crate) fn SecKeychainCopyDefault(keychain: *mut KeychainRef) -> OSStatus; + pub(crate) fn SecKeychainCreate( + path_name: *const c_char, + password_length: u32, + password: *const c_char, + prompt_user: bool, + initial_access: CFTypeRef, + keychain: *mut KeychainRef, + ) -> OSStatus; + pub(crate) fn SecKeychainDelete(keychain_or_array: KeychainRef) -> OSStatus; + pub(crate) fn SecKeychainGetTypeID() -> CFTypeID; + pub(crate) fn SecKeychainItemGetTypeID() -> CFTypeID; + pub(crate) fn SecKeychainItemCopyContent( + item_ref: ItemRef, + itemClass: *mut FourCharacterCode, + attr_list: *mut SecKeychainAttributeList, + data_length: *mut u32, + data_out: *mut *mut c_void, + ) -> OSStatus; + pub(crate) fn SecKeychainItemFreeContent( + attr_list: *mut SecKeychainAttributeList, + data: *mut c_void, + ) -> OSStatus; +} diff --git a/__security/keychain-services/keychain-services.rs/src/keychain/item/class.rs b/__security/keychain-services/keychain-services.rs/src/keychain/item/class.rs new file mode 100644 index 0000000..c01c358 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/keychain/item/class.rs @@ -0,0 +1,83 @@ +use crate::ffi::*; +use core_foundation::{base::TCFType, string::CFString}; + +/// Classes of keychain items supported by Keychain Services +/// (not to be confused with `SecAttrClass` or `SecType`) +/// +/// Wrapper for the `kSecClass` attribute key. See: +/// +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Class { + /// Generic password items. + /// + /// Wrapper for the `kSecClassGenericPassword` attribute value. See: + /// + GenericPassword, + + /// Internet passwords. + /// + /// Wrapper for the `kSecClassInternetPassword` attribute value. See: + /// + InternetPassword, + + /// Certificates. + /// + /// Wrapper for the `kSecClassCertificate` attribute value. See: + /// + Certificate, + + /// Cryptographic keys. + /// + /// Wrapper for the `kSecClassKey` attribute value. See: + /// + Key, + + /// Identities. + /// + /// Wrapper for the `kSecClassIdentity` attribute value. See: + /// + Identity, +} + +impl Class { + /// Attempt to look up an attribute kind by its `FourCharacterCode`. + // TODO: cache `FourCharacterCodes`? e.g. as `lazy_static` + pub(crate) fn from_tag(tag: FourCharacterCode) -> Option { + let result = unsafe { + if tag == FourCharacterCode::from(kSecClassGenericPassword) { + Class::GenericPassword + } else if tag == FourCharacterCode::from(kSecClassInternetPassword) { + Class::InternetPassword + } else if tag == FourCharacterCode::from(kSecClassCertificate) { + Class::Certificate + } else if tag == FourCharacterCode::from(kSecClassKey) { + Class::Key + } else if tag == FourCharacterCode::from(kSecClassIdentity) { + Class::Identity + } else { + return None; + } + }; + + Some(result) + } + /// Get `CFString` containing the `kSecClass` dictionary value for + /// this particular `SecClass`. + pub fn as_CFString(self) -> CFString { + unsafe { + CFString::wrap_under_get_rule(match self { + Class::GenericPassword => kSecClassGenericPassword, + Class::InternetPassword => kSecClassInternetPassword, + Class::Certificate => kSecClassCertificate, + Class::Key => kSecClassKey, + Class::Identity => kSecClassIdentity, + }) + } + } +} + +impl From for Class { + fn from(tag: FourCharacterCode) -> Self { + Self::from_tag(tag).unwrap_or_else(|| panic!("invalid SecItemClass tag: {:?}", tag)) + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/keychain/item/mod.rs b/__security/keychain-services/keychain-services.rs/src/keychain/item/mod.rs new file mode 100644 index 0000000..9e40716 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/keychain/item/mod.rs @@ -0,0 +1,125 @@ +//! Items stored in a keychain (e.g. certificates, keys, passwords) + +mod class; +mod password; +mod query; + +pub use self::{class::*, password::*, query::*}; +use crate::{attr::AttrKind, error::*, ffi::*}; +use core_foundation::base::TCFType; +use std::{mem, os::raw::c_void, ptr, slice}; + +declare_TCFType! { + /// Items stored in the keychain. + /// + /// Wrapper for the `SecKeychainItem`/`SecKeychainItemRef` types: + /// + Item, ItemRef +} + +impl_TCFType!(Item, ItemRef, SecKeychainItemGetTypeID); + +impl Item { + /// Get the class of this item + pub fn class(&self) -> Class { + let mut result = FourCharacterCode::from(b"NULL"); + + Error::maybe_from_OSStatus(unsafe { + SecKeychainItemCopyContent( + self.as_concrete_TypeRef(), + &mut result, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ) + }) + .unwrap(); + + result.into() + } + + /// Get the raw data associated with this keychain item + pub(crate) fn data(&self) -> Result, Error> { + let result_ptr: *mut u8 = ptr::null_mut(); + let mut length = 0; + + let status = unsafe { + SecKeychainItemCopyContent( + self.as_concrete_TypeRef(), + ptr::null_mut(), + ptr::null_mut(), + &mut length, + &mut (result_ptr as *mut c_void), + ) + }; + + if let Some(e) = Error::maybe_from_OSStatus(status) { + Err(e) + } else if result_ptr.is_null() { + Err(Error::new( + ErrorKind::MissingEntitlement, + "SecKeychainItemCopyContent refused to return data", + )) + } else { + // Copy the data into a vector we've allocated + let result = Vec::from(unsafe { slice::from_raw_parts(result_ptr, length as usize) }); + + // Free the original data + Error::maybe_from_OSStatus(unsafe { + SecKeychainItemFreeContent(ptr::null_mut(), result_ptr as *mut c_void) + }) + .unwrap(); + + Ok(result) + } + } + + /// Get an attribute of this item as a `String`. + // TODO: handle attribute types other than `String`? + pub(crate) fn attribute(&self, attr_kind: AttrKind) -> Result { + let mut attrs = unsafe { self.attributes() }?; + + let result = attrs + .iter() + .find(|attr| { + if let Some(kind) = AttrKind::from_tag(attr.tag()) { + kind == attr_kind + } else { + false + } + }) + .map(|attr| String::from_utf8(attr.data().unwrap().into()).unwrap()); + + Error::maybe_from_OSStatus(unsafe { + SecKeychainItemFreeContent(&mut attrs, ptr::null_mut()) + }) + .unwrap(); + + result.ok_or_else(|| { + Error::new( + ErrorKind::NoSuchAttr, + &format!("missing attribute {:?}", attr_kind), + ) + }) + } + + /// Get the attributes of a keychain item. Note that this does not handle + /// deallocating the attribute list so the caller must take care to do so. + unsafe fn attributes(&self) -> Result { + let mut result: SecKeychainAttributeList = mem::zeroed(); + + let status = SecKeychainItemCopyContent( + self.as_concrete_TypeRef(), + ptr::null_mut(), + &mut result, + ptr::null_mut(), + ptr::null_mut(), + ); + + if let Some(e) = Error::maybe_from_OSStatus(status) { + Err(e) + } else { + Ok(result) + } + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/keychain/item/password.rs b/__security/keychain-services/keychain-services.rs/src/keychain/item/password.rs new file mode 100644 index 0000000..ef04557 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/keychain/item/password.rs @@ -0,0 +1,140 @@ +use crate::{attr::*, dictionary::DictionaryBuilder, error::Error, ffi::*, keychain::*}; +use std::str; +use zeroize::Zeroize; + +/// Generic passwords +pub struct GenericPassword(Item); + +impl GenericPassword { + /// Create a new generic password item in the given keychain. + pub fn create( + keychain: &Keychain, + service: &str, + account: &str, + password: &str, + ) -> Result { + let mut attrs = DictionaryBuilder::new(); + attrs.add_class(item::Class::GenericPassword); + attrs.add_string(AttrKind::Service, service); + attrs.add_string(AttrKind::Account, account); + attrs.add_string(unsafe { kSecValueData }, password); + + Ok(GenericPassword(keychain.add_item(attrs)?)) + } + + /// Find a generic password in the given keychain. + pub fn find(keychain: &Keychain, service: &str, account: &str) -> Result { + let mut attrs = DictionaryBuilder::new(); + attrs.add_class(item::Class::GenericPassword); + attrs.add_string(AttrKind::Service, service); + attrs.add_string(AttrKind::Account, account); + + Ok(GenericPassword(keychain.find_item(attrs)?)) + } + + /// Get the account this password is associated with + pub fn account(&self) -> Result { + self.0.attribute(AttrKind::Account) + } + + /// Get the service this password is associated with + pub fn service(&self) -> Result { + self.0.attribute(AttrKind::Service) + } + + /// Get the raw password value + pub fn password(&self) -> Result { + Ok(PasswordData(self.0.data()?)) + } +} + +/// Internet passwords +pub struct InternetPassword(Item); + +impl InternetPassword { + /// Create a new Internet password item in the given keychain. + pub fn create( + keychain: &Keychain, + server: &str, + account: &str, + password: &str, + ) -> Result { + let mut attrs = DictionaryBuilder::new(); + attrs.add_class(item::Class::InternetPassword); + attrs.add_string(AttrKind::Server, server); + attrs.add_string(AttrKind::Account, account); + attrs.add_string(unsafe { kSecValueData }, password); + + Ok(InternetPassword(keychain.add_item(attrs)?)) + } + + /// Find an Internet password in the given keychain. + pub fn find( + keychain: &Keychain, + server: &str, + account: &str, + protocol: Option, + ) -> Result { + let mut attrs = DictionaryBuilder::new(); + attrs.add_class(item::Class::InternetPassword); + attrs.add_string(AttrKind::Server, server); + attrs.add_string(AttrKind::Account, account); + + if let Some(proto) = protocol { + attrs.add_attr(&proto); + } + + Ok(InternetPassword(keychain.find_item(attrs)?)) + } + + /// Get the account this password is associated with + pub fn account(&self) -> Result { + self.0.attribute(AttrKind::Account) + } + + /// Get the service this password is associated with + pub fn server(&self) -> Result { + self.0.attribute(AttrKind::Server) + } + + /// Get the raw password value + pub fn password(&self) -> Result { + Ok(PasswordData(self.0.data()?)) + } +} + +/// Wrapper around password data that ensures it is cleared from memory after +/// being used. +#[derive(Clone)] +pub struct PasswordData(Vec); + +impl PasswordData { + /// Borrow the password as a byte slice + pub fn as_bytes(&self) -> &[u8] { + self.0.as_ref() + } + + /// Borrow the password as a `str` (if valid UTF-8), panicking if the + /// UTF-8 conversion fails. + pub fn as_str(&self) -> &str { + self.try_as_str().expect("password contained invalid UTF-8") + } + + /// Borrow the password as a `str` (if valid UTF-8), returning a + /// `Utf8Error` if the UTF-8 conversion failed. + pub fn try_as_str(&self) -> Result<&str, str::Utf8Error> { + str::from_utf8(self.as_bytes()) + } +} + +impl AsRef<[u8]> for PasswordData { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl Drop for PasswordData { + fn drop(&mut self) { + self.0.zeroize(); + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/keychain/item/query.rs b/__security/keychain-services/keychain-services.rs/src/keychain/item/query.rs new file mode 100644 index 0000000..1b719d5 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/keychain/item/query.rs @@ -0,0 +1,170 @@ +//! Query the keychain, looking for particular items + +use crate::{attr::*, dictionary::DictionaryBuilder, ffi::*}; +use core_foundation::{ + base::{CFType, TCFType}, + number::CFNumber, + string::CFString, +}; + +/// Limit the number of matched items to one or an unlimited number. +/// +/// Wrapper for the `kSecMatchLimit` attribute key. See: +/// +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum MatchLimit { + /// Match exactly one item. + /// + /// Wrapper for the `kSecMatchLimitOne` attribute value. See: + /// + One, + + /// Match the specified number of items. + /// + /// Equivalent to passing a `CFNumberRef` as the value for + /// `kSecMatchLimit`. See: + /// + Number(usize), + + /// Match an unlimited number of items. + /// + /// Wrapper for the `kSecMatchLimitAll` attribute value. See: + /// + All, +} + +impl MatchLimit { + /// Get `CFType` containing the `kSecMatchLimit` dictionary value for + /// this particular `SecMatchLimit`. + pub fn as_CFType(self) -> CFType { + match self { + MatchLimit::One => { + unsafe { CFString::wrap_under_get_rule(kSecMatchLimitOne) }.as_CFType() + } + MatchLimit::Number(n) => CFNumber::from(n as i64).as_CFType(), + MatchLimit::All => { + unsafe { CFString::wrap_under_get_rule(kSecMatchLimitAll) }.as_CFType() + } + } + } +} + +/// Query builder for locating particular keychain items. +/// +/// For more information, see "Search Attribute Keys and Values": +/// +#[derive(Default, Debug)] +pub struct Query(DictionaryBuilder); + +impl Query { + /// Create a new keychain item query builder + pub fn new() -> Self { + Self::default() + } + + /// Query for keychain items with the provided `SecAttrApplicationLabel` + /// (not to be confused with a `SecAttrLabel`), i.e. the hash/fingerprint + /// of a public key in the keychain. + /// + /// Both the private and public key in a keypair have a + /// `SecAttrApplicationLabel` set to the public key's fingerprint. + /// + /// Wrapper for the `kSecAttrApplicationLabel` attribute key. See: + /// + pub fn application_label>(mut self, label: L) -> Self { + self.0.add_attr(&label.into()); + self + } + + /// Query for keychain items with the provided `SecAttrApplicationTag`. + /// + /// Wrapper for the `kSecAttrApplicationTag` attribute key. See: + /// + pub fn application_tag(mut self, tag: T) -> Self + where + T: Into, + { + self.0.add_attr(&tag.into()); + self + } + + /// Query for keys with the given `SecAttrKeyClass`. + /// + /// Wrapper for the `kSecAttrKeyClass` attribute key. See: + /// + pub fn key_class(mut self, key_class: AttrKeyClass) -> Self { + self.0.add_attr(&key_class); + self + } + + /// Query for keys with the given `SecAttrKeyType`. + /// + /// Wrapper for the `kSecAttrKeyType` attribute key. See: + /// + pub fn key_type(mut self, key_type: AttrKeyType) -> Self { + self.0.add_attr(&key_type); + self + } + + /// Query for a particular (human-meaningful) label on keys + /// + /// Wrapper for the `kSecAttrLabel` attribute key. See: + /// + pub fn label>(mut self, label: L) -> Self { + self.0.add_attr(&label.into()); + self + } + + /// Query for keys which are or not permanent members of the default keychain. + /// + /// Wrapper for the `kSecAttrIsPermanent` attribute key. See: + /// + pub fn permanent(mut self, value: bool) -> Self { + self.0.add_boolean(AttrKind::Permanent, value); + self + } + + /// Query for keys which are or are not synchronizable. + /// + /// Wrapper for the `kSecAttrSynchronizable` attribute key. See: + /// + pub fn synchronizable(mut self, value: bool) -> Self { + self.0.add_boolean(AttrKind::Synchronizable, value); + self + } + + /// Query for keys which are or are not sensitive. + /// + /// Wrapper for the `kSecAttrIsSensitive` attribute key. See: + /// + pub fn sensitive(mut self, value: bool) -> Self { + self.0.add_boolean(AttrKind::Sensitive, value); + self + } + + /// Query for keys stored in an external token i.e. the + /// Secure Enclave Processor (SEP). + /// + /// Wrapper for the `kSecAttrTokenID` attribute key. See: + /// + pub fn token_id(mut self, value: AttrTokenId) -> Self { + self.0.add_attr(&value); + self + } + + /// Prompt the user with the given custom message when using keys returned + /// from this query. + /// + /// Wrapper for the `kSecUseOperationPrompt`. See: + /// + pub fn use_operation_prompt(mut self, value: &str) -> Self { + self.0.add_string(unsafe { kSecUseOperationPrompt }, value); + self + } +} + +impl From for DictionaryBuilder { + fn from(params: Query) -> DictionaryBuilder { + params.0 + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/keychain/key/algorithm.rs b/__security/keychain-services/keychain-services.rs/src/keychain/key/algorithm.rs new file mode 100644 index 0000000..f91da04 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/keychain/key/algorithm.rs @@ -0,0 +1,451 @@ +use crate::ffi::*; +use core_foundation::{base::TCFType, string::CFString}; + +/// Cryptographic algorithms for use with keys stored in the keychain. +/// +/// Wrapper for `SecKeyAlgorithm`. See: +/// +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum KeyAlgorithm { + /// Elliptic Curve Encryption Standard X963 + ECIESEncryptionStandardX963SHA1AESGCM, + + /// Elliptic Curve Encryption Standard X963 + ECIESEncryptionStandardX963SHA224AESGCM, + + /// Elliptic Curve Encryption Standard X963 + ECIESEncryptionStandardX963SHA256AESGCM, + + /// Elliptic Curve Encryption Standard X963 + ECIESEncryptionStandardX963SHA384AESGCM, + + /// Elliptic Curve Encryption Standard X963 + ECIESEncryptionStandardX963SHA512AESGCM, + + /// Elliptic Curve Encryption Standard Variable IVX963 + ECIESEncryptionStandardVariableIVX963SHA224AESGCM, + + /// Elliptic Curve Encryption Standard Variable IVX963 + ECIESEncryptionStandardVariableIVX963SHA256AESGCM, + + /// Elliptic Curve Encryption Standard Variable IVX963 + ECIESEncryptionStandardVariableIVX963SHA384AESGCM, + + /// Elliptic Curve Encryption Standard Variable IVX963 + ECIESEncryptionStandardVariableIVX963SHA512AESGCM, + + /// Elliptic Curve Encryption Cofactor Variable IVX963 + ECIESEncryptionCofactorVariableIVX963SHA224AESGCM, + + /// Elliptic Curve Encryption Cofactor Variable IVX963 + ECIESEncryptionCofactorVariableIVX963SHA256AESGCM, + + /// Elliptic Curve Encryption Cofactor Variable IVX963 + ECIESEncryptionCofactorVariableIVX963SHA384AESGCM, + + /// Elliptic Curve Encryption Cofactor Variable IVX963 + ECIESEncryptionCofactorVariableIVX963SHA512AESGCM, + + /// Elliptic Curve Encryption Cofactor X963 + ECIESEncryptionCofactorX963SHA1AESGCM, + + /// Elliptic Curve Encryption Cofactor X963 + ECIESEncryptionCofactorX963SHA224AESGCM, + + /// Elliptic Curve Encryption Cofactor X963 + ECIESEncryptionCofactorX963SHA256AESGCM, + + /// Elliptic Curve Encryption Cofactor X963 + ECIESEncryptionCofactorX963SHA384AESGCM, + + /// Elliptic Curve Encryption Cofactor X963 + ECIESEncryptionCofactorX963SHA512AESGCM, + + /// Elliptic Curve Signature RFC4754 + ECDSASignatureRFC4754, + + /// Elliptic Curve Signature Digest X962 + ECDSASignatureDigestX962, + + /// Elliptic Curve Signature Digest X962 + ECDSASignatureDigestX962SHA1, + + /// Elliptic Curve Signature Digest X962 + ECDSASignatureDigestX962SHA224, + + /// Elliptic Curve Signature Digest X962 + ECDSASignatureDigestX962SHA256, + + /// Elliptic Curve Signature Digest X962 + ECDSASignatureDigestX962SHA384, + + /// Elliptic Curve Signature Digest X962 + ECDSASignatureDigestX962SHA512, + + /// Elliptic Curve Signature Message X962 + ECDSASignatureMessageX962SHA1, + + /// Elliptic Curve Signature Digest X962 + ECDSASignatureMessageX962SHA224, + + /// Elliptic Curve Signature Digest X962 + ECDSASignatureMessageX962SHA256, + + /// Elliptic Curve Signature Digest X962 + ECDSASignatureMessageX962SHA384, + + /// Elliptic Curve Signature Digest X962 + ECDSASignatureMessageX962SHA512, + + /// Elliptic Curve Key Exchange + ECDHKeyExchangeCofactor, + + /// Elliptic Curve Key Exchange + ECDHKeyExchangeStandard, + + /// Elliptic Curve Key Exchange + ECDHKeyExchangeCofactorX963SHA1, + + /// Elliptic Curve Key Exchange + ECDHKeyExchangeStandardX963SHA1, + + /// Elliptic Curve Key Exchange + ECDHKeyExchangeCofactorX963SHA224, + + /// Elliptic Curve Key Exchange + ECDHKeyExchangeCofactorX963SHA256, + + /// Elliptic Curve Key Exchange + ECDHKeyExchangeCofactorX963SHA384, + + /// Elliptic Curve Key Exchange + ECDHKeyExchangeCofactorX963SHA512, + + /// Elliptic Curve Key Exchange + ECDHKeyExchangeStandardX963SHA224, + + /// Elliptic Curve Key Exchange + ECDHKeyExchangeStandardX963SHA256, + + /// Elliptic Curve Key Exchange + ECDHKeyExchangeStandardX963SHA384, + + /// Elliptic Curve Key Exchange + ECDHKeyExchangeStandardX963SHA512, + + /// RSA Encryption + RSAEncryptionRaw, + + /// RSA Encryption + RSAEncryptionPKCS1, + /// RSA Encryption OAEP + RSAEncryptionOAEPSHA1, + + /// RSA Encryption OAEP + RSAEncryptionOAEPSHA224, + + /// RSA Encryption OAEP + RSAEncryptionOAEPSHA256, + + /// RSA Encryption OAEP + RSAEncryptionOAEPSHA384, + + /// RSA Encryption OAEP + RSAEncryptionOAEPSHA512, + + /// RSA Encryption OAEP AES-GCM + RSAEncryptionOAEPSHA1AESGCM, + + /// RSA Encryption OAEP AES-GCM + RSAEncryptionOAEPSHA224AESGCM, + + /// RSA Encryption OAEP AES-GCM + RSAEncryptionOAEPSHA256AESGCM, + + /// RSA Encryption OAEP AES-GCM + RSAEncryptionOAEPSHA384AESGCM, + + /// RSA Encryption OAEP AES-GCM + RSAEncryptionOAEPSHA512AESGCM, + + /// RSA Signature Raw + RSASignatureRaw, + + /// RSA Signature Digest PKCS1v15 + RSASignatureDigestPKCS1v15Raw, + + /// RSA Signature Digest PKCS1v15 + RSASignatureDigestPKCS1v15SHA1, + + /// RSA Signature Digest PKCS1v15 + RSASignatureDigestPKCS1v15SHA224, + + /// RSA Signature Digest PKCS1v15 + RSASignatureDigestPKCS1v15SHA256, + + /// RSA Signature Digest PKCS1v15 + RSASignatureDigestPKCS1v15SHA384, + + /// RSA Signature Digest PKCS1v15 + RSASignatureDigestPKCS1v15SHA512, + + /// RSA Signature Message PKCS1v15 + RSASignatureMessagePKCS1v15SHA1, + + /// RSA Signature Digest PKCS1v15 + RSASignatureMessagePKCS1v15SHA224, + + /// RSA Signature Digest PKCS1v15 + RSASignatureMessagePKCS1v15SHA256, + + /// RSA Signature Digest PKCS1v15 + RSASignatureMessagePKCS1v15SHA384, + + /// RSA Signature Digest PKCS1v15 + RSASignatureMessagePKCS1v15SHA512, + + /// RSA Signature Digest PSS + RSASignatureDigestPSSSHA1, + + /// RSA Signature Digest PSS + RSASignatureDigestPSSSHA224, + + /// RSA Signature Digest PSS + RSASignatureDigestPSSSHA256, + + /// RSA Signature Digest PSS + RSASignatureDigestPSSSHA384, + + /// RSA Signature Digest PSS + RSASignatureDigestPSSSHA512, + + /// RSA Signature Message PSS + RSASignatureMessagePSSSHA1, + + /// RSA Signature Message PSS + RSASignatureMessagePSSSHA224, + + /// RSA Signature Message PSS + RSASignatureMessagePSSSHA256, + + /// RSA Signature Message PSS + RSASignatureMessagePSSSHA384, + + /// RSA Signature Message PSS + RSASignatureMessagePSSSHA512, +} + +impl KeyAlgorithm { + /// Get `CFString` containing the `kSecKeyAlgorithm` dictionary value for + /// a particular cryptographic algorithm. + pub fn as_CFString(self) -> CFString { + unsafe { + CFString::wrap_under_get_rule(match self { + KeyAlgorithm::ECIESEncryptionStandardX963SHA1AESGCM => { + kSecKeyAlgorithmECIESEncryptionStandardX963SHA1AESGCM + } + KeyAlgorithm::ECIESEncryptionStandardX963SHA224AESGCM => { + kSecKeyAlgorithmECIESEncryptionStandardX963SHA224AESGCM + } + KeyAlgorithm::ECIESEncryptionStandardX963SHA256AESGCM => { + kSecKeyAlgorithmECIESEncryptionStandardX963SHA256AESGCM + } + KeyAlgorithm::ECIESEncryptionStandardX963SHA384AESGCM => { + kSecKeyAlgorithmECIESEncryptionStandardX963SHA384AESGCM + } + KeyAlgorithm::ECIESEncryptionStandardX963SHA512AESGCM => { + kSecKeyAlgorithmECIESEncryptionStandardX963SHA512AESGCM + } + KeyAlgorithm::ECIESEncryptionStandardVariableIVX963SHA224AESGCM => { + kSecKeyAlgorithmECIESEncryptionStandardVariableIVX963SHA224AESGCM + } + KeyAlgorithm::ECIESEncryptionStandardVariableIVX963SHA256AESGCM => { + kSecKeyAlgorithmECIESEncryptionStandardVariableIVX963SHA256AESGCM + } + KeyAlgorithm::ECIESEncryptionStandardVariableIVX963SHA384AESGCM => { + kSecKeyAlgorithmECIESEncryptionStandardVariableIVX963SHA384AESGCM + } + KeyAlgorithm::ECIESEncryptionStandardVariableIVX963SHA512AESGCM => { + kSecKeyAlgorithmECIESEncryptionStandardVariableIVX963SHA512AESGCM + } + KeyAlgorithm::ECIESEncryptionCofactorVariableIVX963SHA224AESGCM => { + kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA224AESGCM + } + KeyAlgorithm::ECIESEncryptionCofactorVariableIVX963SHA256AESGCM => { + kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA256AESGCM + } + KeyAlgorithm::ECIESEncryptionCofactorVariableIVX963SHA384AESGCM => { + kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA384AESGCM + } + KeyAlgorithm::ECIESEncryptionCofactorVariableIVX963SHA512AESGCM => { + kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA512AESGCM + } + KeyAlgorithm::ECIESEncryptionCofactorX963SHA1AESGCM => { + kSecKeyAlgorithmECIESEncryptionCofactorX963SHA1AESGCM + } + KeyAlgorithm::ECIESEncryptionCofactorX963SHA224AESGCM => { + kSecKeyAlgorithmECIESEncryptionCofactorX963SHA224AESGCM + } + KeyAlgorithm::ECIESEncryptionCofactorX963SHA256AESGCM => { + kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM + } + KeyAlgorithm::ECIESEncryptionCofactorX963SHA384AESGCM => { + kSecKeyAlgorithmECIESEncryptionCofactorX963SHA384AESGCM + } + KeyAlgorithm::ECIESEncryptionCofactorX963SHA512AESGCM => { + kSecKeyAlgorithmECIESEncryptionCofactorX963SHA512AESGCM + } + KeyAlgorithm::ECDSASignatureRFC4754 => kSecKeyAlgorithmECDSASignatureRFC4754, + KeyAlgorithm::ECDSASignatureDigestX962 => kSecKeyAlgorithmECDSASignatureDigestX962, + KeyAlgorithm::ECDSASignatureDigestX962SHA1 => { + kSecKeyAlgorithmECDSASignatureDigestX962SHA1 + } + KeyAlgorithm::ECDSASignatureDigestX962SHA224 => { + kSecKeyAlgorithmECDSASignatureDigestX962SHA224 + } + KeyAlgorithm::ECDSASignatureDigestX962SHA256 => { + kSecKeyAlgorithmECDSASignatureDigestX962SHA256 + } + KeyAlgorithm::ECDSASignatureDigestX962SHA384 => { + kSecKeyAlgorithmECDSASignatureDigestX962SHA384 + } + KeyAlgorithm::ECDSASignatureDigestX962SHA512 => { + kSecKeyAlgorithmECDSASignatureDigestX962SHA512 + } + KeyAlgorithm::ECDSASignatureMessageX962SHA1 => { + kSecKeyAlgorithmECDSASignatureMessageX962SHA1 + } + KeyAlgorithm::ECDSASignatureMessageX962SHA224 => { + kSecKeyAlgorithmECDSASignatureMessageX962SHA224 + } + KeyAlgorithm::ECDSASignatureMessageX962SHA256 => { + kSecKeyAlgorithmECDSASignatureMessageX962SHA256 + } + KeyAlgorithm::ECDSASignatureMessageX962SHA384 => { + kSecKeyAlgorithmECDSASignatureMessageX962SHA384 + } + KeyAlgorithm::ECDSASignatureMessageX962SHA512 => { + kSecKeyAlgorithmECDSASignatureMessageX962SHA512 + } + KeyAlgorithm::ECDHKeyExchangeCofactor => kSecKeyAlgorithmECDHKeyExchangeCofactor, + KeyAlgorithm::ECDHKeyExchangeStandard => kSecKeyAlgorithmECDHKeyExchangeStandard, + KeyAlgorithm::ECDHKeyExchangeCofactorX963SHA1 => { + kSecKeyAlgorithmECDHKeyExchangeCofactorX963SHA1 + } + KeyAlgorithm::ECDHKeyExchangeStandardX963SHA1 => { + kSecKeyAlgorithmECDHKeyExchangeStandardX963SHA1 + } + KeyAlgorithm::ECDHKeyExchangeCofactorX963SHA224 => { + kSecKeyAlgorithmECDHKeyExchangeCofactorX963SHA224 + } + KeyAlgorithm::ECDHKeyExchangeCofactorX963SHA256 => { + kSecKeyAlgorithmECDHKeyExchangeCofactorX963SHA256 + } + KeyAlgorithm::ECDHKeyExchangeCofactorX963SHA384 => { + kSecKeyAlgorithmECDHKeyExchangeCofactorX963SHA384 + } + KeyAlgorithm::ECDHKeyExchangeCofactorX963SHA512 => { + kSecKeyAlgorithmECDHKeyExchangeCofactorX963SHA512 + } + KeyAlgorithm::ECDHKeyExchangeStandardX963SHA224 => { + kSecKeyAlgorithmECDHKeyExchangeStandardX963SHA224 + } + KeyAlgorithm::ECDHKeyExchangeStandardX963SHA256 => { + kSecKeyAlgorithmECDHKeyExchangeStandardX963SHA256 + } + KeyAlgorithm::ECDHKeyExchangeStandardX963SHA384 => { + kSecKeyAlgorithmECDHKeyExchangeStandardX963SHA384 + } + KeyAlgorithm::ECDHKeyExchangeStandardX963SHA512 => { + kSecKeyAlgorithmECDHKeyExchangeStandardX963SHA512 + } + KeyAlgorithm::RSAEncryptionRaw => kSecKeyAlgorithmRSAEncryptionRaw, + KeyAlgorithm::RSAEncryptionPKCS1 => kSecKeyAlgorithmRSAEncryptionPKCS1, + KeyAlgorithm::RSAEncryptionOAEPSHA1 => kSecKeyAlgorithmRSAEncryptionOAEPSHA1, + KeyAlgorithm::RSAEncryptionOAEPSHA224 => kSecKeyAlgorithmRSAEncryptionOAEPSHA224, + KeyAlgorithm::RSAEncryptionOAEPSHA256 => kSecKeyAlgorithmRSAEncryptionOAEPSHA256, + KeyAlgorithm::RSAEncryptionOAEPSHA384 => kSecKeyAlgorithmRSAEncryptionOAEPSHA384, + KeyAlgorithm::RSAEncryptionOAEPSHA512 => kSecKeyAlgorithmRSAEncryptionOAEPSHA512, + KeyAlgorithm::RSAEncryptionOAEPSHA1AESGCM => { + kSecKeyAlgorithmRSAEncryptionOAEPSHA1AESGCM + } + KeyAlgorithm::RSAEncryptionOAEPSHA224AESGCM => { + kSecKeyAlgorithmRSAEncryptionOAEPSHA224AESGCM + } + KeyAlgorithm::RSAEncryptionOAEPSHA256AESGCM => { + kSecKeyAlgorithmRSAEncryptionOAEPSHA256AESGCM + } + KeyAlgorithm::RSAEncryptionOAEPSHA384AESGCM => { + kSecKeyAlgorithmRSAEncryptionOAEPSHA384AESGCM + } + KeyAlgorithm::RSAEncryptionOAEPSHA512AESGCM => { + kSecKeyAlgorithmRSAEncryptionOAEPSHA512AESGCM + } + KeyAlgorithm::RSASignatureRaw => kSecKeyAlgorithmRSASignatureRaw, + KeyAlgorithm::RSASignatureDigestPKCS1v15Raw => { + kSecKeyAlgorithmRSASignatureDigestPKCS1v15Raw + } + KeyAlgorithm::RSASignatureDigestPKCS1v15SHA1 => { + kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA1 + } + KeyAlgorithm::RSASignatureDigestPKCS1v15SHA224 => { + kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA224 + } + KeyAlgorithm::RSASignatureDigestPKCS1v15SHA256 => { + kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256 + } + KeyAlgorithm::RSASignatureDigestPKCS1v15SHA384 => { + kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384 + } + KeyAlgorithm::RSASignatureDigestPKCS1v15SHA512 => { + kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512 + } + KeyAlgorithm::RSASignatureMessagePKCS1v15SHA1 => { + kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA1 + } + KeyAlgorithm::RSASignatureMessagePKCS1v15SHA224 => { + kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA224 + } + KeyAlgorithm::RSASignatureMessagePKCS1v15SHA256 => { + kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256 + } + KeyAlgorithm::RSASignatureMessagePKCS1v15SHA384 => { + kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA384 + } + KeyAlgorithm::RSASignatureMessagePKCS1v15SHA512 => { + kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA512 + } + KeyAlgorithm::RSASignatureDigestPSSSHA1 => { + kSecKeyAlgorithmRSASignatureDigestPSSSHA1 + } + KeyAlgorithm::RSASignatureDigestPSSSHA224 => { + kSecKeyAlgorithmRSASignatureDigestPSSSHA224 + } + KeyAlgorithm::RSASignatureDigestPSSSHA256 => { + kSecKeyAlgorithmRSASignatureDigestPSSSHA256 + } + KeyAlgorithm::RSASignatureDigestPSSSHA384 => { + kSecKeyAlgorithmRSASignatureDigestPSSSHA384 + } + KeyAlgorithm::RSASignatureDigestPSSSHA512 => { + kSecKeyAlgorithmRSASignatureDigestPSSSHA512 + } + KeyAlgorithm::RSASignatureMessagePSSSHA1 => { + kSecKeyAlgorithmRSASignatureMessagePSSSHA1 + } + KeyAlgorithm::RSASignatureMessagePSSSHA224 => { + kSecKeyAlgorithmRSASignatureMessagePSSSHA224 + } + KeyAlgorithm::RSASignatureMessagePSSSHA256 => { + kSecKeyAlgorithmRSASignatureMessagePSSSHA256 + } + KeyAlgorithm::RSASignatureMessagePSSSHA384 => { + kSecKeyAlgorithmRSASignatureMessagePSSSHA384 + } + KeyAlgorithm::RSASignatureMessagePSSSHA512 => { + kSecKeyAlgorithmRSASignatureMessagePSSSHA512 + } + }) + } + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/keychain/key/mod.rs b/__security/keychain-services/keychain-services.rs/src/keychain/key/mod.rs new file mode 100644 index 0000000..c050fb3 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/keychain/key/mod.rs @@ -0,0 +1,313 @@ +//! Keys stored in macOS Keychain Services. + +mod algorithm; +mod operation; +mod pair; + +pub use self::{algorithm::*, operation::*, pair::*}; +use crate::{ + attr::*, + ciphertext::Ciphertext, + dictionary::{Dictionary, DictionaryBuilder}, + error::Error, + ffi::*, + keychain::item::{self, MatchLimit}, + signature::Signature, +}; +use core_foundation::{ + base::{CFIndexConvertible, CFTypeRef, TCFType}, + data::{CFData, CFDataRef}, + error::CFErrorRef, + string::{CFString, CFStringRef}, +}; +use std::{ + fmt::{self, Debug}, + ptr, +}; + +declare_TCFType! { + /// Object which represents a cryptographic key. + /// + /// Wrapper for the `SecKey`/`SecKeyRef` types: + /// + Key, KeyRef +} + +impl_TCFType!(Key, KeyRef, SecKeyGetTypeID); + +impl Key { + /// Find a `Key` in the keyring using the given `ItemQuery`. + /// + /// Wrapper for `SecItemCopyMatching`. See: + /// + pub fn find(query: item::Query) -> Result { + let mut params = DictionaryBuilder::from(query); + params.add(unsafe { kSecClass }, &item::Class::Key.as_CFString()); + params.add(unsafe { kSecMatchLimit }, &MatchLimit::One.as_CFType()); + params.add_boolean(unsafe { kSecReturnRef }, true); + + let mut result: KeyRef = ptr::null_mut(); + let status = unsafe { + SecItemCopyMatching( + Dictionary::from(params).as_concrete_TypeRef(), + &mut result as &mut CFTypeRef, + ) + }; + + // Return an error if the status was unsuccessful + if let Some(e) = Error::maybe_from_OSStatus(status) { + return Err(e); + } + + Ok(unsafe { Key::wrap_under_create_rule(result) }) + } + + /// Get the `AttrApplicationLabel` for this `Key`. + pub fn application_label(&self) -> Option { + self.attributes() + .find(AttrKind::ApplicationLabel) + .map(|tag| { + AttrApplicationLabel(unsafe { + CFData::wrap_under_get_rule(tag.as_CFTypeRef() as CFDataRef) + }) + }) + } + + /// Get the `AttrApplicationTag` for this `Key`. + pub fn application_tag(&self) -> Option { + self.attributes().find(AttrKind::ApplicationTag).map(|tag| { + AttrApplicationTag(unsafe { + CFData::wrap_under_get_rule(tag.as_CFTypeRef() as CFDataRef) + }) + }) + } + + /// Get the `AttrLabel` for this `Key`. + pub fn label(&self) -> Option { + self.attributes().find(AttrKind::Label).map(|label| { + AttrLabel(unsafe { CFString::wrap_under_get_rule(label.as_CFTypeRef() as CFStringRef) }) + }) + } + + /// Get the `AttrKeyClass` for this `Key`. + pub fn class(&self) -> Option { + self.attributes() + .find(AttrKind::KeyClass) + .map(|class| AttrKeyClass::from(class.as_CFTypeRef() as CFStringRef)) + } + + /// Get the `AttrKeyType` for this `Key`. + pub fn key_type(&self) -> Option { + self.attributes() + .find(AttrKind::KeyType) + .map(|keytype| AttrKeyType::from(keytype.as_CFTypeRef() as CFStringRef)) + } + + /// Determine whether a key is suitable for an operation using a certain algorithm + /// + /// Wrapper for the `SecKeyIsAlgorithmSupported` function. See: + /// + pub fn is_supported(&self, operation: KeyOperation, alg: KeyAlgorithm) -> bool { + let res = unsafe { + SecKeyIsAlgorithmSupported( + self.as_concrete_TypeRef(), + operation.to_CFIndex(), + alg.as_CFString().as_CFTypeRef(), + ) + }; + res == 1 + } + + /// Create a cryptographic signature of the given data using this key. + /// + /// Wrapper for the `SecKeyCreateSignature` function. See: + /// + pub fn sign(&self, alg: KeyAlgorithm, data: &[u8]) -> Result { + let mut error: CFErrorRef = ptr::null_mut(); + let signature = unsafe { + SecKeyCreateSignature( + self.as_concrete_TypeRef(), + alg.as_CFString().as_CFTypeRef(), + CFData::from_buffer(data).as_concrete_TypeRef(), + &mut error, + ) + }; + + if error.is_null() { + let bytes = unsafe { CFData::wrap_under_create_rule(signature) }.to_vec(); + Ok(Signature::new(alg, bytes)) + } else { + Err(error.into()) + } + } + + /// Verifies the cryptographic signature of the given data using this key. + /// + /// Wrapper for the `SecKeyVerifySignature` function. See: + /// + pub fn verify(&self, signed_data: &[u8], signature: &Signature) -> Result { + let mut error: CFErrorRef = ptr::null_mut(); + let result = unsafe { + SecKeyVerifySignature( + self.as_concrete_TypeRef(), + signature.algorithm().as_CFString().as_CFTypeRef(), + CFData::from_buffer(signed_data).as_concrete_TypeRef(), + CFData::from_buffer(signature.as_bytes()).as_concrete_TypeRef(), + &mut error, + ) + }; + + if error.is_null() { + Ok(result == 0x1) + } else { + Err(error.into()) + } + } + + /// Encrypts a block of data using a public key and specified algorithm + /// + /// Wrapper for the `SecKeyCreateEncryptedData` function. See: + /// + pub fn encrypt(&self, alg: KeyAlgorithm, plaintext: &[u8]) -> Result { + let mut error: CFErrorRef = ptr::null_mut(); + let ciphertext = unsafe { + SecKeyCreateEncryptedData( + self.as_concrete_TypeRef(), + alg.as_CFString().as_CFTypeRef(), + CFData::from_buffer(plaintext).as_concrete_TypeRef(), + &mut error, + ) + }; + + if error.is_null() { + let bytes = unsafe { CFData::wrap_under_create_rule(ciphertext) }.to_vec(); + Ok(Ciphertext::new(alg, bytes)) + } else { + Err(error.into()) + } + } + + /// Decrypts a block of data using a private key and specified algorithm + /// + /// Wrapper for the `SecKeyCreateDecryptedData` function. See: + /// + pub fn decrypt(&self, ciphertext: Ciphertext) -> Result, Error> { + let mut error: CFErrorRef = ptr::null_mut(); + let plaintext = unsafe { + SecKeyCreateDecryptedData( + self.as_concrete_TypeRef(), + ciphertext.algorithm().as_CFString().as_CFTypeRef(), + CFData::from_buffer(ciphertext.as_ref()).as_concrete_TypeRef(), + &mut error, + ) + }; + + if error.is_null() { + let bytes = unsafe { CFData::wrap_under_create_rule(plaintext) }.to_vec(); + Ok(bytes) + } else { + Err(error.into()) + } + } + + /// Delete this key from the keychain + /// + /// Wrapper for `SecItemDelete` function. See: + /// + pub fn delete(self) -> Result<(), Error> { + let mut query = DictionaryBuilder::new(); + let key_class = self.class().unwrap(); + query.add(unsafe { kSecClass }, &item::Class::Key.as_CFString()); + query.add(unsafe { kSecAttrKeyClass }, &key_class.as_CFString()); + if key_class == AttrKeyClass::Public { + query.add(unsafe { kSecAttrKeyType }, &self.key_type().unwrap().as_CFString()); + query.add( + unsafe { kSecAttrApplicationTag }, + &self.application_tag().unwrap().as_CFType(), + ); + } else if key_class == AttrKeyClass::Private { + query.add( + unsafe { kSecAttrApplicationLabel }, + &self.application_label().unwrap().as_CFType(), + ); + query.add_boolean(unsafe { kSecReturnRef }, true); + } + let status = unsafe { SecItemDelete(Dictionary::from(query).as_concrete_TypeRef()) }; + if let Some(e) = Error::maybe_from_OSStatus(status) { + Err(e) + } else { + Ok(()) + } + } + + /// Export this key as an external representation. + /// + /// If the key is not exportable the operation will fail (e.g. if it + /// was generated inside of the Secure Enclave, or if the "Extractable" + /// flag is set to NO). + /// + /// The data returned depends on the key type: + /// + /// - RSA: PKCS#1 format + /// - EC: ANSI X9.63 bytestring: + /// - Public key: `04 || X || Y` + /// - Private key: Concatenation of public key with big endian encoding + /// of the secret scalar, i.e. `04 || X || Y || K` + /// + /// All representations use fixed-size integers with leading zeroes. + /// + /// Wrapper for the `SecKeyCopyExternalRepresentation` function. See: + /// + pub fn to_external_representation(&self) -> Result, Error> { + let mut error: CFErrorRef = ptr::null_mut(); + let data = + unsafe { SecKeyCopyExternalRepresentation(self.as_concrete_TypeRef(), &mut error) }; + + if error.is_null() { + Ok(unsafe { CFData::wrap_under_create_rule(data) }.to_vec()) + } else { + Err(error.into()) + } + } + + /// Restores a key from an external representation of that key. + /// + /// Wrapper for the `SecKeyCreateWithData` function. See: + /// + pub fn from_external_representation(params: RestoreKeyParams) -> Result { + let mut error: CFErrorRef = ptr::null_mut(); + let data = unsafe { + SecKeyCreateWithData( + CFData::from_buffer(params.as_bytes()).as_concrete_TypeRef(), + params.attributes().as_concrete_TypeRef(), + &mut error, + ) + }; + + if error.is_null() { + Ok(unsafe { Key::wrap_under_create_rule(data) }) + } else { + Err(error.into()) + } + } + + /// Fetch attributes for this `Key`. + /// + /// Wrapper for `SecKeyCopyAttributes`. See: + /// + fn attributes(&self) -> Dictionary { + unsafe { Dictionary::wrap_under_get_rule(SecKeyCopyAttributes(self.as_concrete_TypeRef())) } + } +} + +impl Debug for Key { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "SecKey {{ application_label: {:?}, application_tag: {:?}, label: {:?} }}", + self.application_label(), + self.application_tag(), + self.label() + ) + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/keychain/key/operation.rs b/__security/keychain-services/keychain-services.rs/src/keychain/key/operation.rs new file mode 100644 index 0000000..4740600 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/keychain/key/operation.rs @@ -0,0 +1,33 @@ +use core_foundation::base::{CFIndex, CFIndexConvertible}; + +use self::KeyOperation::*; +/// Types of operations that a cryptographic key can perform +/// +/// Wrapper for `SecKeyOperationType`. See: +/// +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum KeyOperation { + /// Decrypt operation + Decrypt, + /// Encrypt operation + Encrypt, + /// KeyExchange operation + KeyExchange, + /// Sign operation + Sign, + /// Verify operation + Verify, +} + +impl CFIndexConvertible for KeyOperation { + fn to_CFIndex(self) -> CFIndex { + let i = match self { + Decrypt => 3, + Encrypt => 2, + KeyExchange => 4, + Sign => 0, + Verify => 1, + }; + i as CFIndex + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/keychain/key/pair.rs b/__security/keychain-services/keychain-services.rs/src/keychain/key/pair.rs new file mode 100644 index 0000000..ff8e325 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/keychain/key/pair.rs @@ -0,0 +1,305 @@ +use super::*; +use crate::{access::AccessControl, dictionary::*, error::Error}; +use core_foundation::base::TCFType; +use std::ptr; + +/// Public key pairs (i.e. public and private key) stored in the keychain. +#[derive(Debug)] +pub struct KeyPair { + /// Public key + pub public_key: Key, + + /// Private key + pub private_key: Key, +} + +impl KeyPair { + /// An asymmetric cryptographic key pair is composed of a public and a private key that are generated together. + /// The public key can be distributed freely, but keep the private key secret. + /// One or both may be stored in a keychain for safekeeping. + /// + /// Wrapper for the `SecKeyCreateRandomKey` function see: + /// + pub fn create(params: KeyPairGenerateParams) -> Result { + let mut error: CFErrorRef = ptr::null_mut(); + let private_key_ref: KeyRef = unsafe { + SecKeyCreateRandomKey(Dictionary::from(params).as_concrete_TypeRef(), &mut error) + }; + if private_key_ref.is_null() { + Err(error.into()) + } else { + let public_key_ref = unsafe { SecKeyCopyPublicKey(private_key_ref) }; + assert!(!public_key_ref.is_null()); + assert!(!private_key_ref.is_null()); + + Ok(unsafe { + KeyPair { + public_key: Key::wrap_under_create_rule(public_key_ref), + private_key: Key::wrap_under_create_rule(private_key_ref), + } + }) + } + } + + /// Generate a public/private `KeyPair` using the given + /// `GeneratePairParams`. + /// + /// Wrapper for the `SecKeyGeneratePair` function. See: + /// + pub fn generate(params: KeyPairGenerateParams) -> Result { + let mut public_key_ref: KeyRef = ptr::null_mut(); + let mut private_key_ref: KeyRef = ptr::null_mut(); + + let status = unsafe { + SecKeyGeneratePair( + Dictionary::from(params).as_concrete_TypeRef(), + &mut public_key_ref, + &mut private_key_ref, + ) + }; + + // Return an error if the status was unsuccessful + if let Some(e) = Error::maybe_from_OSStatus(status) { + return Err(e); + } + + assert!(!public_key_ref.is_null()); + assert!(!private_key_ref.is_null()); + + Ok(unsafe { + KeyPair { + public_key: Key::wrap_under_create_rule(public_key_ref), + private_key: Key::wrap_under_create_rule(private_key_ref), + } + }) + } +} + +/// Builder for key generation parameters (passed to the underlying +/// `SecKeyGeneratePair` function) +/// +/// For more information on generating cryptographic keys in a keychain, see: +/// +#[derive(Clone, Debug)] +pub struct KeyPairGenerateParams { + key_type: AttrKeyType, + key_size: usize, + attrs: DictionaryBuilder, +} + +impl KeyPairGenerateParams { + /// Create a new `GeneratePairParams` + pub fn new(key_type: AttrKeyType, key_size: usize) -> Self { + Self { + key_type, + key_size, + attrs: <_>::default(), + } + } + + /// Set the access control policy (a.k.a. ACL) for the `Key`. + /// + /// Wrapper for the `kSecAttrAccessControl` attribute key. See: + /// + pub fn access_control(mut self, access_control: &AccessControl) -> Self { + self.attrs.add(AttrKind::AccessControl, access_control); + self + } + + /// Set a tag (private, application-specific identifier) on this key. + /// Tags are useful as the "primary key" for looking up keychain items. + /// + /// Wrapper for `kSecAttrApplicationTag` attribute key. See: + /// + pub fn application_tag(mut self, tag: T) -> Self + where + T: Into, + { + self.attrs.add_attr(&tag.into()); + self + } + + /// Set whether this key can be used in a key derivation operation + /// + /// Wrapper for the `kSecKeyDerive` attribute key. See: + /// + pub fn can_derive(mut self, value: bool) -> Self { + self.attrs.add_boolean(AttrKind::Derive, value); + self + } + + /// Set whether this key can be used in a decrypt operation. + /// + /// Wrapper for the `kSecKeyDecrypt` attribute key. See: + /// + pub fn can_decrypt(mut self, value: bool) -> Self { + self.attrs.add_boolean(AttrKind::Decrypt, value); + self + } + + /// Set whether this key can be used in a encrypt operation. + /// + /// Wrapper for the `kSecKeyEncrypt` attribute key. See: + /// + pub fn can_encrypt(mut self, value: bool) -> Self { + self.attrs.add_boolean(AttrKind::Encrypt, value); + self + } + + /// Set whether this key can be used in a signing operation. + /// + /// Wrapper for the `kSecKeySign` attribute key. See: + /// + pub fn can_sign(mut self, value: bool) -> Self { + self.attrs.add_boolean(AttrKind::Sign, value); + self + } + + /// Set whether this key can be used to verify a signatures. + /// + /// Wrapper for the `kSecKeyVerify` attribute key. See: + /// + pub fn can_verify(mut self, value: bool) -> Self { + self.attrs.add_boolean(AttrKind::Verify, value); + self + } + + /// Set whether this key can be used to wrap another key. + /// + /// Wrapper for the `kSecKeyWrap` attribute key. See: + /// + pub fn can_wrap(mut self, value: bool) -> Self { + self.attrs.add_boolean(AttrKind::Wrap, value); + self + } + + /// Set whether this key can be used to unwrap another key. + /// + /// Wrapper for the `kSecKeyUnwrap` attribute key. See: + /// + pub fn can_unwrap(mut self, value: bool) -> Self { + self.attrs.add_boolean(AttrKind::Unwrap, value); + self + } + + /// Set a key's cryptographic class. + /// + /// Wrapper for the `kSecAttrKeyClass` attribute key. See: + /// + pub fn key_class(mut self, value: AttrKeyClass) -> Self { + self.attrs.add(AttrKind::KeyClass, &value.as_CFString()); + self + } + + /// Set whether this key can be extractable when wrapped + /// + /// Wrapper for the `kSecKeyExtractable` attribute key. See: + /// + pub fn extractable(mut self, value: bool) -> Self { + self.attrs.add_boolean(AttrKind::Extractable, value); + self + } + + /// Set whether this key is stored permanently in the keychain (default: false). + /// + /// Wrapper for the `kSecAttrIsPermanent` attribute key. See: + /// + pub fn permanent(mut self, value: bool) -> Self { + self.attrs.add_boolean(AttrKind::Permanent, value); + self + } + + /// Set whether this key can be wrapped with NONE algorithm. True + /// means it cannot be wrapped with NONE, false means it can. + /// + /// Wrapper for `kSecKeySensitive` attribute key. See + /// + pub fn sensitive(mut self, value: bool) -> Self { + self.attrs.add_boolean(AttrKind::Sensitive, value); + self + } + + /// Set a string label on this key. SecAttrLabels are useful for providing + /// additional descriptions or context on keys. + /// + /// Wrapper for the `kSecAttrLabel` attribute key. See: + /// + pub fn label>(mut self, label: L) -> Self { + self.attrs.add_attr(&label.into()); + self + } + + /// Set whether this key can be synchronized with other devices owned by + /// the same account (default: false). + /// + /// Wrapper for the `kSecAttrSynchronizable` attribute key. See: + /// + pub fn synchronizable(mut self, value: bool) -> Self { + self.attrs.add_boolean(AttrKind::Synchronizable, value); + self + } + + /// Store this key in an external token i.e. Secure Enclave Processor (SEP). + /// + /// Wrapper for the `kSecAttrTokenID` attribute key. See: + /// + pub fn token_id(mut self, value: AttrTokenId) -> Self { + self.attrs.add_attr(&value); + self + } +} + +impl From for Dictionary { + fn from(params: KeyPairGenerateParams) -> Dictionary { + let mut result = DictionaryBuilder::new(); + result.add_attr(¶ms.key_type); + result.add_number(AttrKind::KeySizeInBits, params.key_size as i64); + result.add( + unsafe { kSecPrivateKeyAttrs }, + &Dictionary::from(params.attrs), + ); + result.into() + } +} + +/// Builder for restoring a key from an external representation of that key parameters +/// (passed to the underlying `SecKeyCreateWithData` function). +/// +/// The key must have already been imported or generated. +/// +/// For more information on restoring cryptographic keys in keychain, see +/// +#[derive(Clone, Debug)] +pub struct RestoreKeyParams { + /// The category the key fits (public, private, or symmetric) + pub key_class: AttrKeyClass, + /// Data representing the key. The format of the data depends on the type of key + /// being created. + /// + /// - RSA: PKCS#1 format + /// - EC: ANSI X9.63 bytestring: + /// - Public key: `04 || X || Y` + /// - Private key: Concatenation of public key with big endian encoding + /// of the secret scalar, i.e. `04 || X || Y || K` + /// + /// All representations use fixed-size integers with leading zeroes. + pub key_data: Vec, + /// The type of key algorithm + pub key_type: AttrKeyType, +} + +impl RestoreKeyParams { + /// Return the attributes that will be used to restore the key + pub fn attributes(&self) -> Dictionary { + let mut result = DictionaryBuilder::new(); + result.add_attr(&self.key_type); + result.add(AttrKind::KeyClass, &self.key_class.as_CFString()); + result.add_number(AttrKind::KeySizeInBits, (self.key_data.len() * 8) as i64); + result.into() + } + + /// Return the `key_data` as a slice + pub fn as_bytes(&self) -> &[u8] { + self.key_data.as_slice() + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/keychain/mod.rs b/__security/keychain-services/keychain-services.rs/src/keychain/mod.rs new file mode 100644 index 0000000..e8351ca --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/keychain/mod.rs @@ -0,0 +1,151 @@ +//! Keychains + +pub mod item; +pub mod key; + +use self::item::MatchLimit; +pub use self::{item::Item, key::Key}; +use crate::dictionary::*; +use crate::error::Error; +use crate::ffi::*; +use core_foundation::base::{CFTypeRef, TCFType}; +use std::{ffi::CString, os::raw::c_char, os::unix::ffi::OsStrExt, path::Path, ptr}; + +declare_TCFType! { + /// Keychains which store cryptographic keys, passwords, and other secrets. + /// + /// Wrapper for the `SecKeychain`/`SecKeychainRef` types: + /// + Keychain, KeychainRef +} + +impl_TCFType!(Keychain, KeychainRef, SecKeychainGetTypeID); + +impl Keychain { + /// Find the default keychain. Returns an `Error` result with a kind of + /// `ErrorKind::NoDefaultKeychain` if there is no default keychain. + /// + /// This is a non-panicking alternative to `Keychain::default()`. + /// + /// Wrapper for the `SecKeychainCopyDefault` function. See: + /// + pub fn find_default() -> Result { + let mut result: KeychainRef = ptr::null_mut(); + let status = unsafe { SecKeychainCopyDefault(&mut result) }; + + if let Some(e) = Error::maybe_from_OSStatus(status) { + Err(e) + } else { + Ok(unsafe { Keychain::wrap_under_create_rule(result) }) + } + } + + /// Create a new keychain. Accepts a path where the new keychain will be + /// located along with an optional password. If no password is given, the + /// user will be prompted for a password. + /// + /// Wrapper for the `SecKeychainCreate` function. See: + /// + pub fn create(path: &Path, password: Option<&str>) -> Result { + let path_cstring = CString::new(path.as_os_str().as_bytes()).unwrap(); + let mut result: KeychainRef = ptr::null_mut(); + + let status = match password { + Some(pw) => unsafe { + SecKeychainCreate( + path_cstring.as_ptr() as *const c_char, + pw.len() as u32, + pw.as_bytes().as_ptr() as *const c_char, + false, + ptr::null(), + &mut result, + ) + }, + None => unsafe { + SecKeychainCreate( + path_cstring.as_ptr() as *const c_char, + 0, + ptr::null(), + true, + ptr::null(), + &mut result, + ) + }, + }; + + if let Some(e) = Error::maybe_from_OSStatus(status) { + Err(e) + } else { + Ok(unsafe { Keychain::wrap_under_create_rule(result) }) + } + } + + /// Delete this keychain. + /// + /// Wrapper for the `SecKeychainDelete` function. See: + /// + pub fn delete(self) -> Result<(), Error> { + let status = unsafe { SecKeychainDelete(self.as_concrete_TypeRef()) }; + + if let Some(e) = Error::maybe_from_OSStatus(status) { + Err(e) + } else { + Ok(()) + } + } + + /// Find an item in this keychain. + /// + /// This is a private method we wrap using builders for querying various + /// keychain item types. + /// + /// Wrapper for `SecItemCopyMatching`. See: + /// + fn find_item(&self, mut attrs: DictionaryBuilder) -> Result { + attrs.add(unsafe { kSecMatchLimit }, &MatchLimit::One.as_CFType()); + attrs.add_boolean(unsafe { kSecReturnRef }, true); + + let mut result: ItemRef = ptr::null_mut(); + let status = unsafe { + SecItemCopyMatching( + Dictionary::from(attrs).as_concrete_TypeRef(), + &mut result as &mut CFTypeRef, + ) + }; + + // Return an error if the status was unsuccessful + if let Some(e) = Error::maybe_from_OSStatus(status) { + return Err(e); + } + + Ok(unsafe { Item::wrap_under_create_rule(result) }) + } + + /// Add an item to this keychain. + /// + /// This is a private method we wrap using builders for various keychain + /// item types. + /// + /// Wrapper for the `SecItemAdd` function. See: + /// + fn add_item(&self, mut attrs: DictionaryBuilder) -> Result { + attrs.add(unsafe { kSecUseKeychain }, self); + attrs.add_boolean(unsafe { kSecReturnRef }, true); + + let mut result: ItemRef = ptr::null_mut(); + let status = + unsafe { SecItemAdd(Dictionary::from(attrs).as_concrete_TypeRef(), &mut result) }; + + if let Some(e) = Error::maybe_from_OSStatus(status) { + Err(e) + } else { + Ok(unsafe { Item::wrap_under_create_rule(result) }) + } + } +} + +impl Default for Keychain { + fn default() -> Keychain { + Self::find_default().expect("no default keychain available") + } +} diff --git a/__security/keychain-services/keychain-services.rs/src/lib.rs b/__security/keychain-services/keychain-services.rs/src/lib.rs new file mode 100644 index 0000000..c43e521 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/lib.rs @@ -0,0 +1,67 @@ +//! macOS Keychain Services wrapper for accessing the system and user's +//! cryptographic keychains, as well as keys stored in the Secure Enclave +//! Processor (SEP). +//! +//! This crate provides a thin, low-level binding with a safe, mostly idiomatic +//! Rust API. Ideally however, it should be wrapped up in higher level, easy-to-use +//! libraries, as the API it presents is rather complicated and arcane. +//! +//! For more information on Keychain Services`, see: +//! +//! +//! ## Code Signing +//! +//! The Keychain Service API requires signed code to access much of its +//! functionality. Accessing many APIs from an unsigned app will return +//! an error with a kind of `ErrorKind::MissingEntitlement`. +//! +//! Follow the instructions here to create a self-signed code signing certificate: +//! +//! +//! You will need to use the [codesign] command-line utility (or XCode) to sign +//! your code before it will be able to access most Keychain Services API +//! functionality. When you sign, you will need an entitlements file which +//! grants access to the Keychain Services API. Below is an example: +//! +//! ```xml +//! +//! +//! +//! +//! keychain-access-groups +//! +//! $(AppIdentifierPrefix)com.example.MyApplication +//! +//! +//! +//! ``` +//! +//! [codesign]: https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html#//apple_ref/doc/uid/TP40005929-CH4-SW4 + +#![crate_name = "keychain_services"] +#![crate_type = "rlib"] +#![allow(non_snake_case, non_upper_case_globals)] +#![deny(warnings, missing_docs, unused_import_braces, unused_qualifications)] + +#[cfg(not(target_os = "macos"))] +compile_error!("This crate presently only compiles on macOS (see GH issue #5 for iOS support)"); + +#[macro_use] +extern crate core_foundation; + +mod access; +mod attr; +mod ciphertext; +mod dictionary; +mod error; +mod ffi; +pub mod keychain; +mod signature; + +pub use crate::access::*; +pub use crate::attr::*; +pub use crate::ciphertext::*; +pub use crate::error::*; +pub use crate::key::*; +pub use crate::keychain::*; +pub use crate::signature::*; diff --git a/__security/keychain-services/keychain-services.rs/src/signature.rs b/__security/keychain-services/keychain-services.rs/src/signature.rs new file mode 100644 index 0000000..4d83687 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/src/signature.rs @@ -0,0 +1,48 @@ +//! Signatures produced by this library. +//! +//! This type doesn't map directly to any type in the Keychain Services API, +//! but instead provides a newtype for signatures this binding produces. + +use crate::key::KeyAlgorithm; + +/// Cryptographic signatures +#[derive(Clone, Debug)] +pub struct Signature { + alg: KeyAlgorithm, + bytes: Vec, +} + +impl Signature { + /// Create a new `Signature` + pub(crate) fn new(alg: KeyAlgorithm, bytes: Vec) -> Self { + // TODO: restrict valid algorithms to signature algorithms? + Self { alg, bytes } + } + + /// Get the algorithm which produced this signature + pub fn algorithm(&self) -> KeyAlgorithm { + self.alg + } + + /// Borrow the signature data as bytes + pub fn as_bytes(&self) -> &[u8] { + &self.bytes + } + + /// Convert into a byte vector + pub fn into_vec(self) -> Vec { + self.bytes + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl From for Vec { + fn from(sig: Signature) -> Vec { + sig.into_vec() + } +} diff --git a/__security/keychain-services/keychain-services.rs/tests/core.rs b/__security/keychain-services/keychain-services.rs/tests/core.rs new file mode 100644 index 0000000..aa93964 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/tests/core.rs @@ -0,0 +1,176 @@ +//! Core suite of tests which should work on all supported macOS platforms. +//! +//! This suite is mainly intended to run in CI. See `tests/interactive.rs` +//! for notes on how to run the full test suite. + +use keychain_services::*; + +const TEST_MESSAGE: &[u8] = b"Embed confidential information in items that you store in a keychain"; + +/// Soft ECDSA key support +#[test] +fn generate_and_sign_with_generate_ecdsa_keys() { + let acl = + AccessControl::create_with_flags(AttrAccessible::WhenUnlocked, Default::default()).unwrap(); + + let generate_params = + KeyPairGenerateParams::new(AttrKeyType::EcSecPrimeRandom, 256).access_control(&acl); + + let keypair = KeyPair::generate(generate_params).unwrap(); + + let public_key_bytes = keypair.public_key.to_external_representation().unwrap(); + + let signature = keypair + .private_key + .sign(KeyAlgorithm::ECDSASignatureMessageX962SHA256, TEST_MESSAGE) + .unwrap(); + + ring::signature::verify( + &ring::signature::ECDSA_P256_SHA256_ASN1, + untrusted::Input::from(&public_key_bytes), + untrusted::Input::from(TEST_MESSAGE), + untrusted::Input::from(signature.as_ref()), + ) + .unwrap(); + + let res = keypair.public_key.verify(TEST_MESSAGE, &signature); + assert!(res.is_ok()); + assert!(res.unwrap()); + let res = keypair.public_key.verify(&[0u8, 0u8], &signature); + assert!(res.is_err()); +} + +/// Soft ECDSA key support with new functions +#[test] +fn generate_and_sign_with_create_ecdsa_keys() { + let acl = + AccessControl::create_with_flags(AttrAccessible::WhenUnlocked, Default::default()).unwrap(); + + let generate_params = + KeyPairGenerateParams::new(AttrKeyType::EcSecPrimeRandom, 256).access_control(&acl); + + let keypair = KeyPair::create(generate_params).unwrap(); + + let public_key_bytes = keypair.public_key.to_external_representation().unwrap(); + + let signature = keypair + .private_key + .sign(KeyAlgorithm::ECDSASignatureMessageX962SHA256, TEST_MESSAGE) + .unwrap(); + + ring::signature::verify( + &ring::signature::ECDSA_P256_SHA256_ASN1, + untrusted::Input::from(&public_key_bytes), + untrusted::Input::from(TEST_MESSAGE), + untrusted::Input::from(signature.as_ref()), + ) + .unwrap(); + + let res = keypair.public_key.verify(TEST_MESSAGE, &signature); + assert!(res.is_ok()); + assert!(res.unwrap()); + let res = keypair.public_key.verify(&[0u8, 0u8], &signature); + assert!(res.is_err()); +} + +/// Soft ECDSA key create from external representation +#[test] +fn export_and_import_ecdsa_keys() { + let acl = + AccessControl::create_with_flags(AttrAccessible::WhenUnlocked, Default::default()).unwrap(); + + let generate_params = + KeyPairGenerateParams::new(AttrKeyType::EcSecPrimeRandom, 256).access_control(&acl); + + let keypair = KeyPair::create(generate_params).unwrap(); + + let public_key_bytes = keypair.public_key.to_external_representation().unwrap(); + + let restore_params = RestoreKeyParams { + key_type: AttrKeyType::EcSecPrimeRandom, + key_data: public_key_bytes.clone(), + key_class: AttrKeyClass::Public, + }; + + let res = Key::from_external_representation(restore_params); + + assert!(res.is_ok()); + let public_key = res.unwrap(); + let pub1bytes = public_key.application_tag().map(|t| t.as_bytes().to_vec()); + let pub2bytes = keypair + .public_key + .application_tag() + .map(|t| t.as_bytes().to_vec()); + assert_eq!(pub1bytes, pub2bytes); + + let restore_params = RestoreKeyParams { + key_type: AttrKeyType::EcSecPrimeRandom, + key_data: public_key_bytes, + key_class: AttrKeyClass::Private, + }; + + let res = Key::from_external_representation(restore_params); + assert!(res.is_err()); +} + +#[test] +fn generate_and_use_rsa_keys() { + let acl = + AccessControl::create_with_flags(AttrAccessible::WhenUnlocked, Default::default()).unwrap(); + + let generate_params = KeyPairGenerateParams::new(AttrKeyType::Rsa, 2048).access_control(&acl); + + let keypair = KeyPair::create(generate_params).unwrap(); + + let signature = keypair + .private_key + .sign(KeyAlgorithm::RSASignatureMessagePSSSHA256, TEST_MESSAGE) + .unwrap(); + + let public_key_bytes = keypair.public_key.to_external_representation().unwrap(); + + let res = ring::signature::verify( + &ring::signature::RSA_PSS_2048_8192_SHA256, + untrusted::Input::from(&public_key_bytes), + untrusted::Input::from(TEST_MESSAGE), + untrusted::Input::from(signature.as_ref()), + ); + assert!(res.is_ok()); + + let res = keypair.public_key.verify(TEST_MESSAGE, &signature); + assert!(res.is_ok()); + assert!(res.unwrap()); + let res = keypair.public_key.verify(&[0u8, 0u8], &signature); + assert!(res.is_err()); +} + +#[test] +fn encrypt_and_decrypt_rsa_keys() { + let acl = + AccessControl::create_with_flags(AttrAccessible::WhenUnlocked, Default::default()).unwrap(); + + let generate_params = KeyPairGenerateParams::new(AttrKeyType::Rsa, 2048).access_control(&acl); + + let keypair = KeyPair::create(generate_params).unwrap(); + + let ciphertext = keypair + .public_key + .encrypt(KeyAlgorithm::RSAEncryptionOAEPSHA256, TEST_MESSAGE) + .unwrap(); + + let res = keypair.private_key.decrypt(ciphertext); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), TEST_MESSAGE); + let ciphertext = Ciphertext::new(KeyAlgorithm::RSAEncryptionOAEPSHA256, vec![0u8, 0u8]); + let res = keypair.private_key.decrypt(ciphertext); + assert!(res.is_err()); + + assert!(!keypair + .private_key + .is_supported(KeyOperation::Encrypt, KeyAlgorithm::RSAEncryptionOAEPSHA256)); + let res = keypair + .private_key + .encrypt(KeyAlgorithm::RSAEncryptionOAEPSHA256, TEST_MESSAGE); + assert!(res.is_err()); +} + diff --git a/__security/keychain-services/keychain-services.rs/tests/interactive.rs b/__security/keychain-services/keychain-services.rs/tests/interactive.rs new file mode 100644 index 0000000..db9c197 --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/tests/interactive.rs @@ -0,0 +1,99 @@ +#![cfg(feature = "interactive-tests")] + +//! Interactive tests intended to be manually run by a person. +//! +//! These tests require a signed `target/debug/interactive-*` executable in +//! order to pass. To sign the test executable, you'll first need to +//! create a self-signed code signing certificate, see the +//! "To obtain a self-signed certificate using Certificate Assistant" +//! section of: +//! +//! + +// TODO: these tests presently fail (possibly due to a codesigning issue?) + +use keychain_services::*; +use tempfile::TempDir; + +const TEST_PASSWORD: &str = "test password. do not really use"; + +/// Creates a temporary keychain in a temporary directory +struct TempKeychain { + pub dir: TempDir, + pub keychain: Keychain, +} + +/// Create a temporary keychain we can use for testing +fn temp_keychain() -> TempKeychain { + let dir = tempfile::tempdir().unwrap(); + let keychain = + Keychain::create(&dir.path().join("test-keychain"), Some(TEST_PASSWORD)).unwrap(); + + TempKeychain { dir, keychain } +} + +/// Generate a `key::Pair` for testing purposes +fn generate_keypair(tag: &str, label: &str) -> KeyPair { + let acl = + AccessControl::create_with_flags(AttrAccessible::WhenUnlocked, Default::default()).unwrap(); + + let generate_params = KeyPairGenerateParams::new(AttrKeyType::EcSecPrimeRandom, 256) + .access_control(&acl) + .application_tag(tag) + .label(label) + .permanent(true); + + KeyPair::generate(generate_params).unwrap() +} + +/// Queries for secret keys +#[test] +fn key_query() { + let keypair = generate_keypair( + "rs.keychain-services.test.integration.query", + "keychain-services.rs integration test query key", + ); + + let private_key_query = keychain::item::Query::new() + .key_class(AttrKeyClass::Private) + .key_type(AttrKeyType::EcSecPrimeRandom) + .application_label(keypair.public_key.application_label().unwrap()); + + let private_key = Key::find(private_key_query).unwrap(); + + assert_eq!( + keypair.private_key.application_label(), + private_key.application_label() + ); +} + +/// Passwords +#[test] +fn store_and_retrieve_passwords() { + let tmp = temp_keychain(); + let service = "example.com"; + let account = "example"; + + let keychain_item = + keychain::item::GenericPassword::create(&tmp.keychain, service, account, TEST_PASSWORD) + .unwrap(); + + assert_eq!(keychain_item.service().unwrap(), service); + assert_eq!(keychain_item.account().unwrap(), account); + assert_eq!(keychain_item.password().unwrap().as_str(), TEST_PASSWORD); +} + +/// +#[test] +fn key_delete() { + let acl = + AccessControl::create_with_flags(AttrAccessible::WhenUnlocked, Default::default()).unwrap(); + + let generate_params = KeyPairGenerateParams::new(AttrKeyType::EcSecPrimeRandom, 256).access_control(&acl) + .permanent(true); + + let keypair = KeyPair::generate(generate_params).unwrap(); + let res = keypair.private_key.delete(); + println!("{:?}", res); + assert!(res.is_ok()); +} diff --git a/__security/keychain-services/keychain-services.rs/tests/test.entitlements b/__security/keychain-services/keychain-services.rs/tests/test.entitlements new file mode 100644 index 0000000..3eb4e0f --- /dev/null +++ b/__security/keychain-services/keychain-services.rs/tests/test.entitlements @@ -0,0 +1,10 @@ + + + + + keychain-access-groups + + $(AppIdentifierPrefix)rs.keychain-services.tests + + + diff --git a/__security/keychain-services/src/main.rs b/__security/keychain-services/src/main.rs new file mode 100644 index 0000000..c4851c7 --- /dev/null +++ b/__security/keychain-services/src/main.rs @@ -0,0 +1,34 @@ +use keychain_services::{AccessControl, AttrAccessible, AttrKeyClass, + AttrKeyType, Key, keychain, KeyPair, KeyPairGenerateParams}; + +fn main() { + let keypair = generate_keypair( + "rs.keychain-services.test.integration.query", + "keychain-services.rs integration test query key", + ); + + let private_key_query = keychain::item::Query::new() + .key_class(AttrKeyClass::Private) + .key_type(AttrKeyType::EcSecPrimeRandom) + .application_label(keypair.public_key.application_label().unwrap()); + + let private_key = Key::find(private_key_query).unwrap(); + + assert_eq!( + keypair.private_key.application_label(), + private_key.application_label() + ); +} + +fn generate_keypair(tag: &str, label: &str) -> KeyPair { + let acl = + AccessControl::create_with_flags(AttrAccessible::WhenUnlocked, Default::default()).unwrap(); + + let generate_params = KeyPairGenerateParams::new(AttrKeyType::EcSecPrimeRandom, 256) + .access_control(&acl) + .application_tag(tag) + .label(label) + .permanent(true); + + KeyPair::generate(generate_params).unwrap() +} diff --git a/__security/keychain-services/test.entitlements b/__security/keychain-services/test.entitlements new file mode 100644 index 0000000..3eb4e0f --- /dev/null +++ b/__security/keychain-services/test.entitlements @@ -0,0 +1,10 @@ + + + + + keychain-access-groups + + $(AppIdentifierPrefix)rs.keychain-services.tests + + +