1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! 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::IsPermanent, 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 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
    }
}