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
162
//! Keys stored in macOS Keychain Services.

mod algorithm;
mod pair;

pub use self::{algorithm::*, pair::*};
use crate::{
    attr::*,
    dictionary::{Dictionary, DictionaryBuilder},
    error::Error,
    ffi::*,
    keychain::item::{self, MatchLimit},
    signature::Signature,
};
use core_foundation::{
    base::{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) })
        })
    }

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

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

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