security_framework/os/macos/
digest_transform.rs

1//! Digest Transform support
2
3use core_foundation::base::{CFIndex, TCFType};
4use core_foundation::data::CFData;
5use core_foundation::error::CFError;
6use core_foundation::string::CFString;
7use core_foundation_sys::base::CFTypeRef;
8use core_foundation_sys::data::CFDataRef;
9use core_foundation_sys::string::CFStringRef;
10use security_framework_sys::digest_transform::*;
11use security_framework_sys::transform::kSecTransformInputAttributeName;
12use std::ptr;
13
14use crate::os::macos::transform::SecTransform;
15
16#[derive(Debug, Copy, Clone)]
17/// A type of digest.
18pub struct DigestType(CFStringRef);
19
20#[allow(missing_docs)]
21impl DigestType {
22    #[inline(always)]
23    #[must_use]
24    pub fn hmac_md5() -> Self {
25        unsafe { Self(kSecDigestHMACMD5) }
26    }
27
28    #[inline(always)]
29    #[must_use]
30    pub fn hmac_sha1() -> Self {
31        unsafe { Self(kSecDigestHMACSHA1) }
32    }
33
34    #[inline(always)]
35    #[must_use]
36    pub fn hmac_sha2() -> Self {
37        unsafe { Self(kSecDigestHMACSHA2) }
38    }
39
40    #[inline(always)]
41    #[must_use]
42    pub fn md2() -> Self {
43        unsafe { Self(kSecDigestMD2) }
44    }
45
46    #[inline(always)]
47    #[must_use]
48    pub fn md4() -> Self {
49        unsafe { Self(kSecDigestMD4) }
50    }
51
52    #[inline(always)]
53    #[must_use]
54    pub fn md5() -> Self {
55        unsafe { Self(kSecDigestMD5) }
56    }
57
58    #[inline(always)]
59    #[must_use]
60    pub fn sha1() -> Self {
61        unsafe { Self(kSecDigestSHA1) }
62    }
63
64    #[inline(always)]
65    #[must_use]
66    pub fn sha2() -> Self {
67        unsafe { Self(kSecDigestSHA2) }
68    }
69
70    #[inline(always)]
71    fn to_type(self) -> CFTypeRef {
72        self.0 as CFTypeRef
73    }
74}
75
76/// A builder for digest transform operations.
77pub struct Builder {
78    digest_type: Option<DigestType>,
79    digest_length: Option<CFIndex>,
80    hmac_key: Option<CFData>,
81}
82
83impl Default for Builder {
84    #[inline(always)]
85    fn default() -> Self {
86        Self::new()
87    }
88}
89
90impl Builder {
91    /// Returns a new builder with default settings.
92    #[inline(always)]
93    #[must_use]
94    pub fn new() -> Self {
95        Self {
96            digest_type: None,
97            digest_length: None,
98            hmac_key: None,
99        }
100    }
101
102    /// Sets the type of digest to perform.
103    ///
104    /// If not set, an appropriate digest will be selected for you.
105    #[inline]
106    pub fn type_(&mut self, digest_type: DigestType) -> &mut Self {
107        self.digest_type = Some(digest_type);
108        self
109    }
110
111    /// Sets the output length of the digest.
112    ///
113    /// If not set, an appropriate length will be selected for you. Some digest
114    /// types only support specific output lengths.
115    #[inline]
116    pub fn length(&mut self, digest_length: CFIndex) -> &mut Self {
117        self.digest_length = Some(digest_length);
118        self
119    }
120
121    /// Sets the key used for HMAC digests.
122    ///
123    /// Only applies to `HmacMd5`, `HmacSha1`, and `HmacSha2` digests.
124    #[inline]
125    pub fn hmac_key(&mut self, hmac_key: CFData) -> &mut Self {
126        self.hmac_key = Some(hmac_key);
127        self
128    }
129
130    /// Computes the digest of the data.
131    // FIXME: deprecate and remove: don't expose CFData in Rust APIs.
132    pub fn execute(&self, data: &CFData) -> Result<CFData, CFError> {
133        unsafe {
134            let digest_type = match self.digest_type {
135                Some(ref digest_type) => digest_type.to_type(),
136                None => ptr::null(),
137            };
138
139            let digest_length = self.digest_length.unwrap_or(0);
140
141            let mut error = ptr::null_mut();
142            let transform = SecDigestTransformCreate(digest_type, digest_length, &mut error);
143            if transform.is_null() {
144                return Err(CFError::wrap_under_create_rule(error));
145            }
146            let mut transform = SecTransform::wrap_under_create_rule(transform);
147
148            if let Some(ref hmac_key) = self.hmac_key {
149                let key = CFString::wrap_under_get_rule(kSecDigestHMACKeyAttribute);
150                transform.set_attribute(&key, hmac_key)?;
151            }
152
153            let key = CFString::wrap_under_get_rule(kSecTransformInputAttributeName);
154            transform.set_attribute(&key, data)?;
155
156            let result = transform.execute()?;
157            Ok(CFData::wrap_under_get_rule(result.as_CFTypeRef() as CFDataRef))
158        }
159    }
160}
161
162#[cfg(test)]
163mod test {
164    use super::*;
165
166    #[test]
167    fn md5() {
168        let data = CFData::from_buffer("The quick brown fox jumps over the lazy dog".as_bytes());
169        let hash = Builder::new().type_(DigestType::md5()).execute(&data).unwrap();
170        assert_eq!(hex::encode(hash.bytes()), "9e107d9d372bb6826bd81d3542a419d6");
171    }
172
173    #[test]
174    fn hmac_sha1() {
175        let data = CFData::from_buffer("The quick brown fox jumps over the lazy dog".as_bytes());
176        let key = CFData::from_buffer(b"key");
177        let hash = Builder::new().type_(DigestType::hmac_sha1()).hmac_key(key).execute(&data).unwrap();
178        assert_eq!(hex::encode(hash.bytes()), "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9");
179    }
180}