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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
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 `kSecAccessControlApplicationPassword`. See:
    /// <https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/ksecaccesscontrolprivatekeyusage>
    PrivateKeyUsage,

    /// Generate encryption-key from an application-provided password.
    ///
    /// Wrapper for `kSecAccessControlPrivateKeyUsage`. 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 {{ ... }}")
    }
}