feat: add keychain-services
This commit is contained in:
201
__security/keychain-services/Cargo.lock
generated
Normal file
201
__security/keychain-services/Cargo.lock
generated
Normal 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"
|
||||||
10
__security/keychain-services/Cargo.toml
Normal file
10
__security/keychain-services/Cargo.toml
Normal 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"}
|
||||||
|
|
||||||
10
__security/keychain-services/keychain-services.rs/.gitignore
vendored
Normal file
10
__security/keychain-services/keychain-services.rs/.gitignore
vendored
Normal 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
|
||||||
@@ -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
|
||||||
28
__security/keychain-services/keychain-services.rs/CHANGES.md
Normal file
28
__security/keychain-services/keychain-services.rs/CHANGES.md
Normal 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
|
||||||
@@ -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
|
||||||
|
|
||||||
35
__security/keychain-services/keychain-services.rs/Cargo.toml
Normal file
35
__security/keychain-services/keychain-services.rs/Cargo.toml
Normal 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 = []
|
||||||
201
__security/keychain-services/keychain-services.rs/LICENSE-APACHE
Normal file
201
__security/keychain-services/keychain-services.rs/LICENSE-APACHE
Normal 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.
|
||||||
@@ -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.
|
||||||
15
__security/keychain-services/keychain-services.rs/Makefile
Normal file
15
__security/keychain-services/keychain-services.rs/Makefile
Normal 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"
|
||||||
103
__security/keychain-services/keychain-services.rs/README.md
Normal file
103
__security/keychain-services/keychain-services.rs/README.md
Normal 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
|
||||||
207
__security/keychain-services/keychain-services.rs/src/access.rs
Normal file
207
__security/keychain-services/keychain-services.rs/src/access.rs
Normal 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 {{ ... }}")
|
||||||
|
}
|
||||||
|
}
|
||||||
898
__security/keychain-services/keychain-services.rs/src/attr.rs
Normal file
898
__security/keychain-services/keychain-services.rs/src/attr.rs
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
562
__security/keychain-services/keychain-services.rs/src/error.rs
Normal file
562
__security/keychain-services/keychain-services.rs/src/error.rs
Normal 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),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
443
__security/keychain-services/keychain-services.rs/src/ffi.rs
Normal file
443
__security/keychain-services/keychain-services.rs/src/ffi.rs
Normal 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;
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(¶ms.key_type);
|
||||||
|
result.add_number(AttrKind::KeySizeInBits, params.key_size as i64);
|
||||||
|
result.add(
|
||||||
|
unsafe { kSecPrivateKeyAttrs },
|
||||||
|
&Dictionary::from(params.attrs),
|
||||||
|
);
|
||||||
|
result.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builder for restoring a key from an external representation of that key parameters
|
||||||
|
/// (passed to the underlying `SecKeyCreateWithData` function).
|
||||||
|
///
|
||||||
|
/// The key must have already been imported or generated.
|
||||||
|
///
|
||||||
|
/// For more information on restoring cryptographic keys in keychain, see
|
||||||
|
/// <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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
67
__security/keychain-services/keychain-services.rs/src/lib.rs
Normal file
67
__security/keychain-services/keychain-services.rs/src/lib.rs
Normal 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::*;
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
176
__security/keychain-services/keychain-services.rs/tests/core.rs
Normal file
176
__security/keychain-services/keychain-services.rs/tests/core.rs
Normal 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());
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
34
__security/keychain-services/src/main.rs
Normal file
34
__security/keychain-services/src/main.rs
Normal 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()
|
||||||
|
}
|
||||||
10
__security/keychain-services/test.entitlements
Normal file
10
__security/keychain-services/test.entitlements
Normal 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>
|
||||||
Reference in New Issue
Block a user