feat: add keychain-services

This commit is contained in:
2022-10-16 21:16:13 +08:00
parent 9ab7685f2d
commit a49d639b18
33 changed files with 5185 additions and 0 deletions

201
__security/keychain-services/Cargo.lock generated Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 <tony@iqlusion.io>"]
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 = []

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,20 @@
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.

View File

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

View File

@@ -0,0 +1,103 @@
# Keychain Services for Rust 🔐 <a href="https://www.iqlusion.io"><img src="https://storage.googleapis.com/iqlusion-prod-web-assets/img/logo/iqlusion-rings-sm.png" alt="iqlusion" width="24" height="24"></a>
[![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:
<https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html>
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

View File

@@ -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<CFOptionFlags> {}
/// Constraints on keychain item access.
///
/// See "Constraints" topic under the "Topics" section of the
/// `SecAccessControlCreateFlags` documentation at:
/// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags>
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum AccessConstraint {
/// Require either passcode or biometric auth (TouchID/FaceID).
///
/// Wrapper for `kSecAccessControlUserPresence`. See:
/// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontroluserpresence>
UserPresence,
/// Require biometric auth (TouchID/FaceID) from any enrolled user for this device.
///
/// Wrapper for `kSecAccessControlBiometryAny`. See:
/// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontrolbiometryany>
BiometryAny,
/// Require biometric auth (TouchID/FaceID) from the current user.
///
/// Wrapper for `kSecAccessControlBiometryCurrentSet`. See:
/// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontrolbiometrycurrentset>
BiometryCurrentSet,
/// Require device passcode.
///
/// Wrapper for `kSecAccessControlDevicePasscode`. See:
/// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontroldevicepasscode>
DevicePasscode,
}
impl AccessControlFlag for AccessConstraint {}
impl From<AccessConstraint> 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:
/// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags>
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum AccessConjunction {
/// Require *all* constraints be satisfied.
///
/// Wrapper for `kSecAccessControlAnd`. See:
/// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontroland>
And,
/// Require *at least one* constraint must be satisfied.
///
/// Wrapper for `kSecAccessControlOr`. See:
/// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontrolor>
Or,
}
impl AccessControlFlag for AccessConjunction {}
impl From<AccessConjunction> 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:
/// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags>
#[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:
/// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontrolprivatekeyusage>
PrivateKeyUsage,
/// Generate encryption-key from an application-provided password.
///
/// Wrapper for `kSecAccessControlApplicationPassword`. See:
/// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontrolapplicationpassword>
ApplicationPassword,
}
impl AccessControlFlag for AccessOption {}
impl From<AccessOption> 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:
/// <https://developer.apple.com/documentation/security/keychain_services/keychain_items/restricting_keychain_item_accessibility>
///
/// Wrapper for the `SecAccessControlCreateFlags` type:
/// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags>
#[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<F: AccessControlFlag>(&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:
/// <https://developer.apple.com/documentation/security/secaccesscontrolref>
AccessControl, AccessControlRef
}
impl_TCFType!(AccessControl, AccessControlRef, SecAccessControlGetTypeID);
impl AccessControl {
/// Create a new `AccessControl` policy/ACL.
///
/// Wrapper for the `SecAccessControlCreateWithFlags()` function:
/// <https://developer.apple.com/documentation/security/1394452-secaccesscontrolcreatewithflags>
pub fn create_with_flags(
protection: AttrAccessible,
flags: AccessControlFlags,
) -> Result<Self, Error> {
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 {{ ... }}")
}
}

View File

@@ -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:
/// <https://developer.apple.com/documentation/security/ksecattraccesscontrol>
AccessControl,
/// Wrapper for the `kSecAttrAccessible` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattraccessible>
Accessible,
/// Wrapper for the `kSecAttrAccount` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattraccount>
Account,
/// Wrapper for the `kSecAttrApplicationLabel` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrlabel>
ApplicationLabel,
/// Wrapper for the `kSecAttrApplicationTag` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrapplicationtag>
ApplicationTag,
/// Wrapper for the `kSecKeyDerive` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrcanderive>
Derive,
/// Wrapper for the `kSecKeyDecrypt` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrcandecrypt>
Decrypt,
/// Wrapper for the `kSecKeyEncrypt` attribute key. See:
/// https://developer.apple.com/documentation/security/ksecattrcanencrypt>
Encrypt,
/// Wrapper for the `kSecKeyExtractable` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrisextractable>
Extractable,
/// Wrapper for the `kSecAttrKeyClass` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrkeyclass>
KeyClass,
/// Wrapper for the `kSecAttrKeySizeInBits` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrkeysizeinbits>
KeySizeInBits,
/// Wrapper for the `kSecAttrKeyType` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrkeytype>
KeyType,
/// Wrapper for the `kSecAttrLabel` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrlabel>
Label,
/// Wrapper for the `kSecAttrIsPermanent` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrispermanent>
Permanent,
/// Wrapper for the `kSecAttrProtocol` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrprotocol>
Protocol,
/// Wrapper for `kSecKeySensitive` attribute key. See
/// <https://developer.apple.com/documentation/security/ksecattrissensitive>
Sensitive,
/// Wrapper for the `kSecAttrServer` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrserver>
Server,
/// Wrapper for the `kSecAttrService` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrservice>
Service,
/// Wrapper for the `kSecKeySign` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrcansign>
Sign,
/// Wrapper for the `kSecAttrSynchronizable` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrsynchronizable>
Synchronizable,
/// Wrapper for the `kSecAttrTokenID` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrtokenid>
TokenId,
/// Wrapper for the `kSecKeyUnwrap` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrcanunwrap>
Unwrap,
/// Wrapper for the `kSecKeyVerify` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrcanverify>
Verify,
/// Wrapper for the `kSecKeyWrap` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrcanwrap>
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<Self> {
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<SecKeychainAttrType> for AttrKind {
fn from(tag: SecKeychainAttrType) -> Self {
Self::from_tag(tag).unwrap_or_else(|| panic!("invalid SecKeychainAttrType tag: {:?}", tag))
}
}
impl From<AttrKind> 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<CFType> 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:
/// <https://developer.apple.com/documentation/security/keychain_services/keychain_items/restricting_keychain_item_accessibility>
///
/// Wrapper for the `kSecAttrAccessible` attribute key. See
/// "Accessibility Values" section of "Item Attribute Keys and Values":
/// <https://developer.apple.com/documentation/security/keychain_services/keychain_items/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.
/// <https://developer.apple.com/documentation/security/ksecattraccessiblewhenpasscodesetthisdeviceonly>
WhenPasscodeSetThisDeviceOnly,
/// The device is unlocked (no passcode mandatory). Non-exportable.
/// <https://developer.apple.com/documentation/security/ksecattraccessiblewhenunlockedthisdeviceonly>
WhenUnlockedThisDeviceOnly,
/// The device is unlocked (no passcode mandatory).
/// <https://developer.apple.com/documentation/security/ksecattraccessiblewhenunlocked>
WhenUnlocked,
/// Permanently accessible after the device is first unlocked after boot.
/// Non-exportable.
/// <https://developer.apple.com/documentation/security/ksecattraccessibleafterfirstunlockthisdeviceonly>
AfterFirstUnlockThisDeviceOnly,
/// Permanently accessible after the device is first unlocked after boot.
/// <https://developer.apple.com/documentation/security/ksecattraccessibleafterfirstunlock>
AfterFirstUnlock,
/// Item is always accessible on this device. Non-exportable.
/// <https://developer.apple.com/documentation/security/ksecattraccessiblealwaysthisdeviceonly>
AlwaysThisDeviceOnly,
/// Item is always accessible.
/// <https://developer.apple.com/documentation/security/ksecattraccessiblealways>
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:
/// <https://developer.apple.com/documentation/security/ksecattrapplicationlabel>
#[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:
/// <https://developer.apple.com/documentation/security/ksecattrapplicationtag>
#[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:
/// <https://developer.apple.com/documentation/security/ksecattrlabel>
#[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:
/// <https://developer.apple.com/documentation/security/ksecattrkeyclass>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum AttrKeyClass {
/// Public keys.
///
/// Wrapper for the `kSecAttrKeyClassPublic` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecattrkeyclasspublic>
Public,
/// Private keys.
///
/// Wrapper for the `kSecAttrKeyClassPrivate` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecattrkeyclassprivate>
Private,
/// Symmetric keys
///
/// Wrapper for the `kSecAttrKeyClassSymmetric` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecattrkeyclasssymmetric>
// 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<CFStringRef> 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:
/// <https://developer.apple.com/documentation/security/ksecattrkeytype>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum AttrKeyType {
/// AES algorithm.
///
/// Wrapper for the `kSecAttrKeyTypeAES` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecattrkeytypeaes>
// TODO: support for AES encryption
Aes,
/// RSA algorithm.
///
/// Wrapper for the `kSecAttrKeyTypeRSA` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecattrkeytypersa>
Rsa,
/// Elliptic curve cryptography over the NIST curves (e.g. P-256)
///
/// Wrapper for the `kSecAttrKeyTypeECSECPrimeRandom` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecattrkeytypeecsecprimerandom>
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<CFStringRef> 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:
/// <https://developer.apple.com/documentation/security/ksecattrprotocol>
#[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:
/// <https://developer.apple.com/documentation/security/ksecattrtokenid>
#[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:
/// <https://developer.apple.com/documentation/security/ksecattrtokenidsecureenclave>
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
///
/// <https://developer.apple.com/documentation/security/keychain_services/keychain_items/1495743-keychain_item_attribute_constant>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum KeyAttr {
/// Wrapper for the `kSecKeyAlwaysSensitive` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeyalwayssensitive>
AlwaysSensitive,
/// Wrapper for the `kSecKeyDerive` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeyderive>
CanDerive,
/// Wrapper for the `kSecKeyDecrypt` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeydecrypt>
CanDecrypt,
/// Wrapper for the `kSecKeyEncrypt` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeyencrypt>
CanEncrypt,
/// Wrapper for the `kSecKeySign` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeysign>
CanSign,
/// Wrapper for the `kSecKeyUnwrap` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeyunwrap>
CanUnwrap,
/// Wrapper for the `kSecKeyVerify` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeyverify>
CanVerify,
/// Wrapper for the `kSecKeyWrap` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeywrap>
CanWrap,
/// Wrapper for the `kSecKeyEffectiveKeySize` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeyeffectivekeysize>
EffectiveKeySize,
/// Wrapper for the `kSecKeyEndDate` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeyenddate>
EndDate,
/// Wrapper for the `kSecKeyExtractable` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeyextractable>
Extractable,
/// Wrapper for the `kSecKeyModifiable` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeymodifiable>
Modifiable,
/// Wrapper for the `kSecKeyNeverExtractable` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeyneverextractable>
NeverExtractable,
/// Wrapper for the `kSecKeyPermanent` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeypermanent>
Permanent,
/// Wrapper for the `kSecKeyPrivate` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeyprivate>
Private,
/// Wrapper for the `kSecKeySensitive` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeysensitive>
Sensitive,
/// Wrapper for the `kSecKeyKeySizeInBits` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeykeysizeinbits>
SizeInBits,
/// Wrapper for the `kSecKeyStartDate` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeystartdate>
StartDate,
/// Wrapper for the `kSecKeyKeyType` attribute value see:
/// <https://developer.apple.com/documentation/security/kseckeykeytype>
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,
})
}
}
}

View File

@@ -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<u8>,
}
impl Ciphertext {
/// Create a new `Ciphertext`
pub fn new(alg: KeyAlgorithm, bytes: Vec<u8>) -> 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<u8> {
self.bytes
}
}
impl AsRef<[u8]> for Ciphertext {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl From<Ciphertext> for Vec<u8> {
fn from(ciphertext: Ciphertext) -> Vec<u8> {
ciphertext.into_vec()
}
}

View File

@@ -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<CFType, CFType>;
/// 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<K, V>(&mut self, key: K, value: &V)
where
K: Into<CFStringRef>,
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<K>(&mut self, key: K, value: bool)
where
K: Into<CFStringRef>,
{
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<K>(&mut self, key: K, value: i64)
where
K: Into<CFStringRef>,
{
self.add(key, &CFNumber::from(value))
}
/// Add a key/value pair with an `AsRef<str>` value to the dictionary
pub(crate) fn add_string<K, V>(&mut self, key: K, value: V)
where
K: Into<CFStringRef>,
V: AsRef<str>,
{
self.add(key, &CFString::from(value.as_ref()))
}
}
impl From<DictionaryBuilder> for Dictionary {
fn from(builder: DictionaryBuilder) -> Dictionary {
Dictionary::from_CFType_pairs(&builder.0)
}
}

View File

@@ -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.
/// <https://developer.apple.com/documentation/security/errsecsuccess>
const errSecSuccess: OSStatus = 0;
/// Authentication and/or authorization failed.
/// <https://developer.apple.com/documentation/security/errsecauthfailed>
const errSecAuthFailed: OSStatus = -25293;
/// Buffer is too small.
/// <https://developer.apple.com/documentation/security/errsecbuffertoosmall>
const errSecBufferTooSmall: OSStatus = -25301;
/// Certificate chain creation attempt failed.
/// <https://developer.apple.com/documentation/security/errseccreatechainfailed>
const errSecCreateChainFailed: OSStatus = -25318;
/// Data too large for the given data type.
/// <https://developer.apple.com/documentation/security/errsecdatatoolarge>
const errSecDataTooLarge: OSStatus = -25302;
/// Data is not available.
/// <https://developer.apple.com/documentation/security/errsecdatanotavailable>
const errSecDataNotAvailable: OSStatus = -25316;
/// Data cannot be modified.
/// <https://developer.apple.com/documentation/security/errsecdatanotmodifiable>
const errSecDataNotModifiable: OSStatus = -25317;
/// Callback with the same name already exists.
/// <https://developer.apple.com/documentation/security/errsecduplicatecallback>
const errSecDuplicateCallback: OSStatus = -25297;
/// Item already exists.
/// <https://developer.apple.com/documentation/security/errsecduplicateitem>
const errSecDuplicateItem: OSStatus = -25299;
/// Keychain with the same name already exists.
/// <https://developer.apple.com/documentation/security/errsecduplicatekeychain>
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.
/// <https://developer.apple.com/documentation/security/errsecindarkwake>
const errSecInDarkWake: OSStatus = -25320;
/// Security Server interactions not allowed in this context.
/// <https://developer.apple.com/documentation/security/errsecinteractionnotallowed>
const errSecInteractionNotAllowed: OSStatus = -25308;
/// User interaction required.
/// <https://developer.apple.com/documentation/security/errsecinteractionrequired>
const errSecInteractionRequired: OSStatus = -25315;
/// Callback is invalid.
/// <https://developer.apple.com/documentation/security/errsecinvalidcallback>
const errSecInvalidCallback: OSStatus = -25298;
/// Item reference is invalid.
/// <https://developer.apple.com/documentation/security/errsecinvaliditemref>
const errSecInvalidItemRef: OSStatus = -25304;
/// Keychain is invalid.
/// <https://developer.apple.com/documentation/security/errsecinvalidkeychain>
const errSecInvalidKeychain: OSStatus = -25295;
/// Specified preference domain is not valid.
/// <https://developer.apple.com/documentation/security/errsecinvalidprefsdomain>
const errSecInvalidPrefsDomain: OSStatus = -25319;
/// Search reference is invalid.
/// <https://developer.apple.com/documentation/security/errsecinvalidsearchref>
const errSecInvalidSearchRef: OSStatus = -25305;
/// Item could not be found.
/// <https://developer.apple.com/documentation/security/errsecitemnotfound>
const errSecItemNotFound: OSStatus = -25300;
/// Invalid key size.
/// <https://developer.apple.com/documentation/security/errseckeysizenotallowed>
const errSecKeySizeNotAllowed: OSStatus = -25311;
/// Missing entitlement: keychain access disallowed because app is unsigned.
/// <https://developer.apple.com/documentation/security/errsecmissingentitlement>
const errSecMissingEntitlement: OSStatus = -34018;
/// Certificate module unavailable.
/// <https://developer.apple.com/documentation/security/errsecnocertificatemodule>
const errSecNoCertificateModule: OSStatus = -25313;
/// Default keychain does not exist.
/// <https://developer.apple.com/documentation/security/errsecnodefaultkeychain>
const errSecNoDefaultKeychain: OSStatus = -25307;
/// Policy module unavailable.
/// <https://developer.apple.com/documentation/security/errsecnopolicymodule>
const errSecNoPolicyModule: OSStatus = -25314;
/// Storage module unavailable.
/// <https://developer.apple.com/documentation/security/errsecnostoragemodule>
const errSecNoStorageModule: OSStatus = -25312;
/// Specified attribute does not exist.
/// <https://developer.apple.com/documentation/security/errsecnosuchattr>
const errSecNoSuchAttr: OSStatus = -25303;
/// Specified keychain item class does not exist.
/// <https://developer.apple.com/documentation/security/errsecnosuchclass>
const errSecNoSuchClass: OSStatus = -25306;
/// Specified keychain does not exist.
/// <https://developer.apple.com/documentation/security/errsecnosuchkeychain>
const errSecNoSuchKeychain: OSStatus = -25294;
/// Trust results not available.
/// <https://developer.apple.com/documentation/security/errsecnotavailable>
const errSecNotAvailable: OSStatus = -25291;
/// Can't perform given action on read-only item.
/// <https://developer.apple.com/documentation/security/errsecreadonly>
const errSecReadOnly: OSStatus = -25292;
/// Can't perform action on read-only attribute
/// <https://developer.apple.com/documentation/security/errsecreadonlyattr>
const errSecReadOnlyAttr: OSStatus = -25309;
/// Invalid version.
/// <https://developer.apple.com/documentation/security/errsecwrongversion>
const errSecWrongSecVersion: OSStatus = -25310;
/// Error type.
///
/// Wrapper for the `CFError` type:
/// <https://developer.apple.com/documentation/corefoundation/cferror>
#[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<D>(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<Self> {
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<CFErrorRef> 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:
/// <https://developer.apple.com/documentation/security/errsecauthfailed>
#[fail(display = "authentication failed")]
AuthFailed,
/// Buffer is too small.
///
/// Wrapper for the `errSecBufferTooSmall` status code. See:
/// <https://developer.apple.com/documentation/security/errsecbuffertoosmall>
#[fail(display = "buffer too small")]
BufferTooSmall,
/// Certificate chain creation attempt failed.
///
/// Wrapper for the `errSecCreateChainFailed` status code. See:
/// <https://developer.apple.com/documentation/security/errseccreatechainfailed>
#[fail(display = "certificate chain creation attempt failed")]
CreateChainFailed,
/// Data too large for the given data type.
///
/// Wrapper for the `errSecDataTooLarge` status code. See:
/// <https://developer.apple.com/documentation/security/errsecdatatoolarge>
#[fail(display = "data too large")]
DataTooLarge,
/// Data is not available.
///
/// Wrapper for the `errSecDataNotAvailable` status code. See:
/// <https://developer.apple.com/documentation/security/errsecdatanotavailable>
#[fail(display = "data not available")]
DataNotAvailable,
/// Data cannot be modified.
///
/// Wrapper for the `errSecDataNotModifiable` status code. See:
/// <https://developer.apple.com/documentation/security/errsecdatanotmodifiable>
#[fail(display = "data not modifiable")]
DataNotModifiable,
/// Callback with the same name already exists.
///
/// Wrapper for the `errSecDuplicateCallback` status code. See:
/// <https://developer.apple.com/documentation/security/errsecduplicatecallback>
#[fail(display = "duplicate callback")]
DuplicateCallback,
/// Item already exists.
///
/// Wrapper for the `errSecDuplicateItem` status code. See:
/// <https://developer.apple.com/documentation/security/errsecduplicateitem>
#[fail(display = "duplicate item")]
DuplicateItem,
/// Keychain with the same name already exists.
///
/// Wrapper for the `errSecDuplicateKeychain` status code. See:
/// <https://developer.apple.com/documentation/security/errsecduplicatekeychain>
#[fail(display = "duplicate keychain")]
DuplicateKeychain,
/// System is in a dark wake state - user interface cannot be displayed.
///
/// Wrapper for the `errSecInDarkWake` status code. See:
/// <https://developer.apple.com/documentation/security/errsecindarkwake>
#[fail(display = "in dark wake")]
InDarkWake,
/// Security Server interactions not allowed in this context.
///
/// Wrapper for the `errSecInteractionNotAllowed` status code. See:
/// <https://developer.apple.com/documentation/security/errsecinteractionnotallowed>
#[fail(display = "interaction not allowed")]
InteractionNotAllowed,
/// User interaction required.
///
/// Wrapper for the `errSecInteractionRequired` status code. See:
/// <https://developer.apple.com/documentation/security/errsecinteractionrequired>
#[fail(display = "user interaction required")]
InteractionRequired,
/// Callback is invalid.
///
/// Wrapper for the `errSecInvalidCallback` status code. See:
/// <https://developer.apple.com/documentation/security/errsecinvalidcallback>
#[fail(display = "invalid callback")]
InvalidCallback,
/// Item reference is invalid.
///
/// Wrapper for the `errSecInvalidItemRef` status code. See:
/// <https://developer.apple.com/documentation/security/errsecinvaliditemref>
#[fail(display = "invalid item ref")]
InvalidItemRef,
/// Keychain is invalid.
///
/// Wrapper for the `errSecInvalidKeychain` status code. See:
/// <https://developer.apple.com/documentation/security/errsecinvalidkeychain>
#[fail(display = "invalid keychain")]
InvalidKeychain,
/// Specified preference domain is not valid.
///
/// Wrapper for the `errSecInvalidPrefsDomain` status code. See:
/// <https://developer.apple.com/documentation/security/errsecinvalidprefsdomain>
#[fail(display = "invalid preference domain")]
InvalidPrefsDomain,
/// Search reference is invalid.
///
/// Wrapper for the `errSecInvalidSearchRef` status code. See:
/// <https://developer.apple.com/documentation/security/errsecinvalidsearchref>
#[fail(display = "search ref is invalid")]
InvalidSearchRef,
/// Item could not be found.
///
/// Wrapper for the `errSecItemNotFound` status code. See:
/// <https://developer.apple.com/documentation/security/errsecitemnotfound>
#[fail(display = "item not found")]
ItemNotFound,
/// Invalid key size.
///
/// Wrapper for the `errSecKeySizeNotAllowed` status code. See:
/// <https://developer.apple.com/documentation/security/errseckeysizenotallowed>
#[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:
/// <https://developer.apple.com/documentation/security/errsecmissingentitlement>
#[fail(display = "missing application entitlement (errSecMissingEntitlement)")]
MissingEntitlement,
/// Certificate module unavailable.
///
/// Wrapper for the `errSecNoCertificateModule` status code. See:
/// <https://developer.apple.com/documentation/security/errsecnocertificatemodule>
#[fail(display = "no certificate module")]
NoCertificateModule,
/// Default keychain does not exist.
///
/// Wrapper for the `errSecNoDefaultKeychain` status code. See:
/// <https://developer.apple.com/documentation/security/errsecnodefaultkeychain>
#[fail(display = "no default keychain")]
NoDefaultKeychain,
/// Policy module unavailable.
///
/// Wrapper for the `errSecNoPolicyModule` status code. See:
/// <https://developer.apple.com/documentation/security/errsecnopolicymodule>
#[fail(display = "no policy module")]
NoPolicyModule,
/// Storage module unavailable.
///
/// Wrapper for the `errSecNoStorageModule` status code. See:
/// <https://developer.apple.com/documentation/security/errsecnostoragemodule>
#[fail(display = "no storage module")]
NoStorageModule,
/// Specified attribute does not exist.
///
/// Wrapper for the `errSecNoSuchAttr` status code. See:;
/// <https://developer.apple.com/documentation/security/errsecnosuchattr>
#[fail(display = "no such attr")]
NoSuchAttr,
/// Specified keychain item class does not exist.
///
/// Wrapper for the `errSecNoSuchClass` status code. See:
/// <https://developer.apple.com/documentation/security/errsecnosuchclass>
#[fail(display = "no such class")]
NoSuchClass,
/// Specified keychain does not exist.
///
/// Wrapper for the `errSecNoSuchKeychain` status code. See:
/// <https://developer.apple.com/documentation/security/errsecnosuchkeychain>
#[fail(display = "no such keychain")]
NoSuchKeychain,
/// Trust results not available.
///
/// Wrapper for the `errSecNotAvailable` status code. See:
/// <https://developer.apple.com/documentation/security/errsecnotavailable>
#[fail(display = "not available")]
NotAvailable,
/// Can't perform given action on read-only item.
///
/// Wrapper for the `errSecReadOnly` status code. See:
/// <https://developer.apple.com/documentation/security/errsecreadonly>
#[fail(display = "read-only")]
ReadOnly,
/// Can't perform action on read-only attribute
///
/// Wrapper for the `errSecReadOnlyAttr` status code. See:
/// <https://developer.apple.com/documentation/security/errsecreadonlyattr>
#[fail(display = "read-only attr")]
ReadOnlyAttr,
/// Invalid version.
///
/// Wrapper for the `errSecWrongSecVersion` status code. See:
/// <https://developer.apple.com/documentation/security/errsecwrongversion>
#[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:
/// <https://developer.apple.com/documentation/corefoundation/1494656-cferrorgetcode?language=objc>
#[fail(display = "Core Foundation error (code: {}, domain: {})", code, domain)]
CFError {
/// Code identifying this type of `CFError`.
///
/// See `CFErrorGetCode()` for more information:
/// <https://developer.apple.com/documentation/corefoundation/1494656-cferrorgetcode>
code: i64,
/// Domain associated with this error.
///
/// See `CFErrorGetDomain()` for more information:
/// <https://developer.apple.com/documentation/corefoundation/1494657-cferrorgetdomain>
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<CFErrorRef> 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<OSStatus> 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),
},
}
}
}

View File

@@ -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:
/// <https://developer.apple.com/documentation/kernel/fourcharcode>
#[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<str> 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<u32> 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<CFStringRef> 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:
/// <https://developer.apple.com/documentation/security/secaccesscontrolref>
pub(crate) type AccessControlRef = CFTypeRef;
/// Reference to a `Key`
///
/// See `SecKeyRef` documentation:
/// <https://developer.apple.com/documentation/security/seckeyref>
pub(crate) type KeyRef = CFTypeRef;
/// Reference to a `Keychain`
///
/// See `SecKeychainRef` documentation:
/// <https://developer.apple.com/documentation/security/seckeychainref>
pub(crate) type KeychainRef = CFTypeRef;
/// Reference to a `keychain::Item`
///
/// See `SecKeychainItemRef` documentation:
/// <https://developer.apple.com/documentation/security/seckeychainitemref>
pub(crate) type ItemRef = CFTypeRef;
/// Attribute type codes.
///
/// Wrapper for `SecKeychainAttrType`. See:
/// <https://developer.apple.com/documentation/security/seckeychainattrtype>
pub(crate) type SecKeychainAttrType = FourCharacterCode;
/// Individual keychain attribute.
///
/// Wrapper for the `SecKeychainAttribute` struct. See:
/// <https://developer.apple.com/documentation/security/seckeychainattribute>
#[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:
/// <https://developer.apple.com/documentation/security/seckeychainattributelist>
#[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<SecKeychainAttribute> {
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;
}

View File

@@ -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:
/// <https://developer.apple.com/documentation/security/ksecclass>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Class {
/// Generic password items.
///
/// Wrapper for the `kSecClassGenericPassword` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecclassgenericpassword>
GenericPassword,
/// Internet passwords.
///
/// Wrapper for the `kSecClassInternetPassword` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecclassinternetpassword>
InternetPassword,
/// Certificates.
///
/// Wrapper for the `kSecClassCertificate` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecclasscertificate>
Certificate,
/// Cryptographic keys.
///
/// Wrapper for the `kSecClassKey` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecclasskey>
Key,
/// Identities.
///
/// Wrapper for the `kSecClassIdentity` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecclassidentity>
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<Self> {
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<FourCharacterCode> for Class {
fn from(tag: FourCharacterCode) -> Self {
Self::from_tag(tag).unwrap_or_else(|| panic!("invalid SecItemClass tag: {:?}", tag))
}
}

View File

@@ -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:
/// <https://developer.apple.com/documentation/security/seckeychainitemref>
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<Vec<u8>, 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<String, Error> {
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<SecKeychainAttributeList, Error> {
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)
}
}
}

View File

@@ -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<Self, Error> {
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<Self, Error> {
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<String, Error> {
self.0.attribute(AttrKind::Account)
}
/// Get the service this password is associated with
pub fn service(&self) -> Result<String, Error> {
self.0.attribute(AttrKind::Service)
}
/// Get the raw password value
pub fn password(&self) -> Result<PasswordData, Error> {
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<Self, Error> {
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<AttrProtocol>,
) -> Result<Self, Error> {
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<String, Error> {
self.0.attribute(AttrKind::Account)
}
/// Get the service this password is associated with
pub fn server(&self) -> Result<String, Error> {
self.0.attribute(AttrKind::Server)
}
/// Get the raw password value
pub fn password(&self) -> Result<PasswordData, Error> {
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<u8>);
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();
}
}

View File

@@ -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:
/// <https://developer.apple.com/documentation/security/ksecmatchlimit>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MatchLimit {
/// Match exactly one item.
///
/// Wrapper for the `kSecMatchLimitOne` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecmatchlimitone>
One,
/// Match the specified number of items.
///
/// Equivalent to passing a `CFNumberRef` as the value for
/// `kSecMatchLimit`. See:
/// <https://developer.apple.com/documentation/security/ksecmatchlimit>
Number(usize),
/// Match an unlimited number of items.
///
/// Wrapper for the `kSecMatchLimitAll` attribute value. See:
/// <https://developer.apple.com/documentation/security/ksecmatchlimitall>
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":
/// <https://developer.apple.com/documentation/security/keychain_services/keychain_items/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:
/// <https://developer.apple.com/documentation/security/ksecattrlabel>
pub fn application_label<L: Into<AttrApplicationLabel>>(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:
/// <https://developer.apple.com/documentation/security/ksecattrapplicationtag>
pub fn application_tag<T>(mut self, tag: T) -> Self
where
T: Into<AttrApplicationTag>,
{
self.0.add_attr(&tag.into());
self
}
/// Query for keys with the given `SecAttrKeyClass`.
///
/// Wrapper for the `kSecAttrKeyClass` attribute key. See:
/// <https://developer.apple.com/documentation/security/ksecattrkeyclass>
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:
/// <https://developer.apple.com/documentation/security/ksecattrkeytype>
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:
/// <https://developer.apple.com/documentation/security/ksecattrlabel>
pub fn label<L: Into<AttrLabel>>(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:
/// <https://developer.apple.com/documentation/security/ksecattrispermanent>
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:
/// <https://developer.apple.com/documentation/security/ksecattrsynchronizable>
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:
/// <https://developer.apple.com/documentation/security/ksecattrissensitive>
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:
/// <https://developer.apple.com/documentation/security/ksecattrtokenid>
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:
/// <https://developer.apple.com/documentation/security/ksecuseoperationprompt>
pub fn use_operation_prompt(mut self, value: &str) -> Self {
self.0.add_string(unsafe { kSecUseOperationPrompt }, value);
self
}
}
impl From<Query> for DictionaryBuilder {
fn from(params: Query) -> DictionaryBuilder {
params.0
}
}

View File

@@ -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:
/// <https://developer.apple.com/documentation/security/seckeyalgorithm>
#[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
}
})
}
}
}

View File

@@ -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:
/// <https://developer.apple.com/documentation/security/seckeyref>
Key, KeyRef
}
impl_TCFType!(Key, KeyRef, SecKeyGetTypeID);
impl Key {
/// Find a `Key` in the keyring using the given `ItemQuery`.
///
/// Wrapper for `SecItemCopyMatching`. See:
/// <https://developer.apple.com/documentation/security/1398306-secitemcopymatching>
pub fn find(query: item::Query) -> Result<Self, Error> {
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<AttrApplicationLabel> {
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<AttrApplicationTag> {
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<AttrLabel> {
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<AttrKeyClass> {
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<AttrKeyType> {
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:
/// <https://developer.apple.com/documentation/security/1644057-seckeyisalgorithmsupported>
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:
/// <https://developer.apple.com/documentation/security/1643916-seckeycreatesignature>
pub fn sign(&self, alg: KeyAlgorithm, data: &[u8]) -> Result<Signature, Error> {
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:
/// <https://developer.apple.com/documentation/security/1643715-seckeyverifysignature>
pub fn verify(&self, signed_data: &[u8], signature: &Signature) -> Result<bool, Error> {
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:
/// <https://developer.apple.com/documentation/security/1643957-seckeycreateencrypteddata>
pub fn encrypt(&self, alg: KeyAlgorithm, plaintext: &[u8]) -> Result<Ciphertext, Error> {
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:
/// <https://developer.apple.com/documentation/security/1644043-seckeycreatedecrypteddata>
pub fn decrypt(&self, ciphertext: Ciphertext) -> Result<Vec<u8>, 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:
/// <https://developer.apple.com/documentation/security/1395547-secitemdelete>
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:
/// <https://developer.apple.com/documentation/security/1643698-seckeycopyexternalrepresentation>
pub fn to_external_representation(&self) -> Result<Vec<u8>, 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:
/// <https://developer.apple.com/documentation/security/1643701-seckeycreatewithdata>
pub fn from_external_representation(params: RestoreKeyParams) -> Result<Self, Error> {
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:
/// <https://developer.apple.com/documentation/security/1643699-seckeycopyattributes>
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()
)
}
}

View File

@@ -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:
/// <https://developer.apple.com/documentation/security/seckeyoperationtype>
#[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
}
}

View File

@@ -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:
/// <https://developer.apple.com/documentation/security/1823694-seckeycreaterandomkey>
pub fn create(params: KeyPairGenerateParams) -> Result<KeyPair, Error> {
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:
/// <https://developer.apple.com/documentation/security/1395339-seckeygeneratepair>
pub fn generate(params: KeyPairGenerateParams) -> Result<KeyPair, Error> {
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:
/// <https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/generating_new_cryptographic_keys>
#[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:
/// <https://developer.apple.com/documentation/security/ksecattraccesscontrol>
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:
/// <https://developer.apple.com/documentation/security/ksecattrapplicationtag>
pub fn application_tag<T>(mut self, tag: T) -> Self
where
T: Into<AttrApplicationTag>,
{
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:
/// <https://developer.apple.com/documentation/security/kseckeyderive>
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:
/// <https://developer.apple.com/documentation/security/kseckeydecrypt>
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:
/// <https://developer.apple.com/documentation/security/kseckeyencrypt>
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:
/// <https://developer.apple.com/documentation/security/kseckeysign>
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:
/// <https://developer.apple.com/documentation/security/kseckeyverify>
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:
/// <https://developer.apple.com/documentation/security/kseckeywrap>
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:
/// <https://developer.apple.com/documentation/security/kseckeyunwrap>
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:
/// <https://developer.apple.com/documentation/security/ksecattrkeyclass>
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:
/// <https://developer.apple.com/documentation/security/kseckeyextractable>
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:
/// <https://developer.apple.com/documentation/security/ksecattrispermanent>
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
/// <https://developer.apple.com/documentation/security/kseckeysensitive>
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:
/// <https://developer.apple.com/documentation/security/ksecattrlabel>
pub fn label<L: Into<AttrLabel>>(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:
/// <https://developer.apple.com/documentation/security/ksecattrsynchronizable>
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:
/// <https://developer.apple.com/documentation/security/ksecattrtokenid>
pub fn token_id(mut self, value: AttrTokenId) -> Self {
self.attrs.add_attr(&value);
self
}
}
impl From<KeyPairGenerateParams> for Dictionary {
fn from(params: KeyPairGenerateParams) -> Dictionary {
let mut result = DictionaryBuilder::new();
result.add_attr(&params.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
/// <https://developer.apple.com/documentation/security/1643701-seckeycreatewithdata>
#[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<u8>,
/// 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()
}
}

View File

@@ -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:
/// <https://developer.apple.com/documentation/security/seckeychainref>
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:
/// <https://developer.apple.com/documentation/security/1400743-seckeychaincopydefault>
pub fn find_default() -> Result<Keychain, Error> {
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:
/// <https://developer.apple.com/documentation/security/1401214-seckeychaincreate>
pub fn create(path: &Path, password: Option<&str>) -> Result<Keychain, Error> {
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:
/// <https://developer.apple.com/documentation/security/1395206-seckeychaindelete>
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:
/// <https://developer.apple.com/documentation/security/1398306-secitemcopymatching>
fn find_item(&self, mut attrs: DictionaryBuilder) -> Result<Item, Error> {
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:
/// <https://developer.apple.com/documentation/security/1401659-secitemadd>
fn add_item(&self, mut attrs: DictionaryBuilder) -> Result<Item, Error> {
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")
}
}

View File

@@ -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:
//! <https://developer.apple.com/documentation/security/keychain_services/keychains>
//!
//! ## 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:
//! <https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html>
//!
//! 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
//! <?xml version="1.0" encoding="UTF-8"?>
//! <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
//! <plist version="1.0">
//! <dict>
//! <key>keychain-access-groups</key>
//! <array>
//! <string>$(AppIdentifierPrefix)com.example.MyApplication</string>
//! </array>
//! </dict>
//! </plist>
//! ```
//!
//! [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::*;

View File

@@ -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<u8>,
}
impl Signature {
/// Create a new `Signature`
pub(crate) fn new(alg: KeyAlgorithm, bytes: Vec<u8>) -> 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<u8> {
self.bytes
}
}
impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl From<Signature> for Vec<u8> {
fn from(sig: Signature) -> Vec<u8> {
sig.into_vec()
}
}

View File

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

View File

@@ -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:
//!
//! <https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html>
// 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());
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)rs.keychain-services.tests</string>
</array>
</dict>
</plist>

View File

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

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)rs.keychain-services.tests</string>
</array>
</dict>
</plist>